Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make double click on building available on touch device #259

Merged
merged 4 commits into from
Sep 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
"react-map-gl": "^7.1.7",
"react-router-dom": "^6.26.1",
"react-three-fiber": "^6.0.13",
"three": "^0.168.0"
"three": "^0.167.0"
},
"devDependencies": {
"@vitejs/plugin-react": "^4.3.1",
Expand Down
88 changes: 52 additions & 36 deletions src/components/ThreeViewer/Controls/CustomMapControl.jsx
Original file line number Diff line number Diff line change
@@ -1,32 +1,36 @@
import { OrbitControls } from "@react-three/drei"
import { useFrame, useThree } from "@react-three/fiber"
import React, { useEffect, useRef } from "react"
import { extend, useFrame, useThree } from "react-three-fiber"
import * as THREE from "three"

import { MapControls } from "three/examples/jsm/Addons.js"

extend({ MapControls })

function CustomMapControl(props) {
const controls = useRef()
const controlsRef = useRef()
const raycaster = useRef(new THREE.Raycaster())
const mouse = useRef(new THREE.Vector2())

const { gl, camera, scene } = useThree()

const handleDoubleClick = (event) => {
// Calculate mouse position in normalized device coordinates (-1 to +1) for both components
let lastTap = 0

const handleInteraction = (event) => {
event.preventDefault()

const isTouch = event.type.startsWith("touch")
const clientX = isTouch ? event.touches[0].clientX : event.clientX
const clientY = isTouch ? event.touches[0].clientY : event.clientY

const rect = event.target.getBoundingClientRect()
mouse.current.x = ((event.clientX - rect.left) / rect.width) * 2 - 1
mouse.current.y = (-(event.clientY - rect.top) / rect.height) * 2 + 1
mouse.current.x = ((clientX - rect.left) / rect.width) * 2 - 1
mouse.current.y = (-(clientY - rect.top) / rect.height) * 2 + 1

raycaster.current.setFromCamera(mouse.current, camera)

const intersects = raycaster.current.intersectObjects(scene.children, true)

if (intersects.length > 0) {
const intersectedMesh = intersects[0].object
console.log("Intersected Mesh", intersectedMesh)

if (!intersectedMesh) return

if (
intersectedMesh.geometry.name &&
(intersectedMesh.geometry.name.includes("surrounding") ||
Expand All @@ -35,14 +39,13 @@ function CustomMapControl(props) {
const existingIndex = props.selectedMesh.findIndex(
(mesh) => mesh.geometry.name === intersectedMesh.geometry.name
)

if (existingIndex > -1) {
// Remove the mesh from the list if it already exists
props.setSelectedMesh([
...props.selectedMesh.slice(0, existingIndex),
...props.selectedMesh.slice(existingIndex + 1),
])
} else {
// Add the mesh to the list if it does not exist
props.setSelectedMesh([
...props.selectedMesh,
{
Expand All @@ -57,38 +60,51 @@ function CustomMapControl(props) {
}
}

useEffect(() => {
if (controls.current) {
controls.current.target = props.middle // Set your desired target
controls.current.mouseButtons = {
LEFT: THREE.MOUSE.PAN,
MIDDLE: THREE.MOUSE.DOLLY,
RIGHT: THREE.MOUSE.ROTATE,
}
const handleDoubleClick = (event) => {
handleInteraction(event)
}

controls.current.screenSpacePanning = false
controls.current.maxPolarAngle = Math.PI / 2
controls.current.update()
const handleDoubleTap = (event) => {
const currentTime = new Date().getTime()
const tapLength = currentTime - lastTap
if (tapLength < 300 && tapLength > 0) {
handleInteraction(event)
}
}, [])

useFrame(() => {
controls.current.update()
})
lastTap = currentTime
}

useEffect(() => {
// Add the event listener
window.addEventListener("dblclick", handleDoubleClick)
const canvas = gl.domElement

canvas.addEventListener("dblclick", handleDoubleClick)
canvas.addEventListener("touchstart", handleDoubleTap)

// Clean up the event listener on component unmount
return () => {
window.removeEventListener("dblclick", handleDoubleClick)
canvas.removeEventListener("dblclick", handleDoubleClick)
canvas.removeEventListener("touchstart", handleDoubleTap)
}
}, [camera, scene])
window.addEventListener("dblclick", handleDoubleClick)

useFrame(() => {
if (controlsRef.current) {
controlsRef.current.update()
}
})

return (
<mapControls ref={controls} args={[camera, gl.domElement]} {...props} />
<OrbitControls
ref={controlsRef}
args={[camera, gl.domElement]}
target={props.middle}
mouseButtons={{
LEFT: THREE.MOUSE.PAN,
MIDDLE: THREE.MOUSE.DOLLY,
RIGHT: THREE.MOUSE.ROTATE,
}}
screenSpacePanning={false}
dampingFactor={1}
maxPolarAngle={Math.PI / 2}
/>
)
}

Expand Down
48 changes: 33 additions & 15 deletions src/simulation/preprocessing.js
Original file line number Diff line number Diff line change
@@ -1,39 +1,57 @@
import * as THREE from "three"

export function processGeometries(geometries, simulationCenter, shadingCutoff) {
// TODO: This is a hand-wavy way of converting real-world meters to WebMercator meters
// in mid-latitudes need to do this more accurately using the latitude of the center point

// The geometries from the input are centered around the simulation geometry
console.log("simulationCenter", simulationCenter)
console.log("shadingCutoff", shadingCutoff)

const simulationRadius = 10 // 10 meters radius for simulation
const simulationRadius2 = simulationRadius * simulationRadius
const cutoff2 = shadingCutoff * shadingCutoff

let minDist = Infinity
let indexOfSimulationInSurrounding = 0
let simulation = []
let surrounding = []
let background = []
let closestGeometryCenter = new THREE.Vector3()

// Step 1: Find the minimum distance and the center of the closest geometry
for (let geom of geometries) {
geom.computeBoundingBox()
let center = new THREE.Vector3()
geom.boundingBox.getCenter(center)
const d2 =
(center.x - simulationCenter.x) ** 2 +
(center.y - simulationCenter.y) ** 2
if (d2 <= cutoff2) {
if (d2 < minDist) {
simulation = [geom]
minDist = d2
indexOfSimulationInSurrounding = surrounding.length
}
if (d2 < minDist) {
minDist = d2
closestGeometryCenter.copy(center)
}
}

// Step 2: Recenter the coordinates
const offset = new THREE.Vector3().subVectors(
closestGeometryCenter,
simulationCenter
)

let simulation = []
let surrounding = []
let background = []

// Steps 3 and 4: Categorize geometries based on the new center
for (let geom of geometries) {
let center = new THREE.Vector3()
geom.boundingBox.getCenter(center)
center.sub(offset) // Recenter

const d2 = center.x * center.x + center.y * center.y

if (d2 <= simulationRadius2) {
simulation.push(geom)
} else if (d2 <= cutoff2) {
surrounding.push(geom)
} else {
background.push(geom)
}
}
surrounding.splice(indexOfSimulationInSurrounding, 1)

simulation.forEach((geom, index) => (geom.name = `simulation-${index}`))
surrounding.forEach((geom, index) => (geom.name = `surrounding-${index}`))
background.forEach((geom, index) => (geom.name = `background-${index}`))
Expand Down