diff --git a/config/threeExamples.mjs b/config/threeExamples.mjs index 9c1107d8a7..9254c2f035 100644 --- a/config/threeExamples.mjs +++ b/config/threeExamples.mjs @@ -9,6 +9,8 @@ export default { './utils/WorkerPool.js', './capabilities/WebGL.js', './libs/ktx-parse.module.js', - './libs/zstddec.module.js' + './libs/zstddec.module.js', + './libs/motion-controllers.module.js', + './webxr/XRControllerModelFactory.js', ], }; diff --git a/examples/js/XR/Controllers.js b/examples/js/XR/Controllers.js new file mode 100644 index 0000000000..aaa5f4b63b --- /dev/null +++ b/examples/js/XR/Controllers.js @@ -0,0 +1,513 @@ +const Controllers = {}; + +let renderer; + +// move clipped to a fixed altitude +let clipToground = false; + +Controllers.MIN_DELTA_ALTITUDE = 1.8; + +let deltaRotation = 0; + +let startedPressButton; + +let actionElevationPerformed = false; + +// hack mode switch between navigation Mode +let rightCtrChangeNavMode = false; +let leftCtrChangeNavMode = false; +let alreadySwitched = false; +const navigationMode = []; +let currentNavigationModeIndex = 0; + +let view; +let contextXR; +// TODO cache geodesicQuat + +/** + * Controller.userData { + * isSelecting + * lockedTeleportPosition + * } + * requires a contextXR variable. + * @param {*} _view itowns view object + * @param {*} _contextXR itowns WebXR context object + */ +Controllers.addControllers = (_view, _contextXR) => { + view = _view; + contextXR = _contextXR; + // eslint-disable-next-line no-use-before-define + navigationMode.push(Mode1, Mode2); + renderer = view.mainLoop.gfxEngine.renderer; + const controller1 = bindListeners(0); + const controller2 = bindListeners(1); + controller1.addEventListener('itowns-xr-axes-changed', onLeftAxisChanged); + controller2.addEventListener('itowns-xr-axes-changed', onRightAxisChanged); + controller2.addEventListener('itowns-xr-axes-stop', onRightAxisStop); + controller1.addEventListener('itowns-xr-axes-stop', onLeftAxisStop); + controller2.addEventListener('itowns-xr-button-pressed', onRightButtonPressed); + controller1.addEventListener('itowns-xr-button-pressed', onLeftButtonPressed); + controller1.addEventListener('itowns-xr-button-released', onLeftButtonReleased); + controller2.addEventListener('itowns-xr-button-released', onRightButtonReleased); + controller1.addEventListener('selectstart', onSelectLeftStart); + controller1.addEventListener('selectend', onSelectLeftEnd); + controller2.addEventListener('selectstart', onSelectRightStart); + controller2.addEventListener('selectend', onSelectRightEnd); + + const cameraRightCtrl = new itowns.THREE.PerspectiveCamera(view.camera.camera3D.fov); + cameraRightCtrl.position.copy(view.camera.camera3D.position); + const cameraRighthelper = new itowns.THREE.CameraHelper(cameraRightCtrl); + + XRUtils.addToScene(cameraRighthelper, true); + + + contextXR.cameraRightGrp = { camera: cameraRightCtrl, cameraHelper: cameraRighthelper }; + + contextXR.controller1 = controller1; + contextXR.controller2 = controller2; +}; + +Controllers.getGeodesicalQuaternion = () => { + // TODO can be optimized with better cache + const position = view.controls.getCameraCoordinate().clone().as(view.referenceCrs); + const geodesicNormal = new itowns.THREE.Quaternion().setFromUnitVectors(new itowns.THREE.Vector3(0, 0, 1), position.geodesicNormal).invert(); + return new itowns.THREE.Quaternion(-1, 0, 0, 1).normalize().multiply(geodesicNormal); +}; + +function bindListeners(index) { + return renderer.xr.getController(index); +} + +function clampAndApplyTransformationToXR(trans, offsetRotation) { + const transClamped = clampToGround(trans); + applyTransformationToXR(transClamped, offsetRotation); +} + + +function applyTransformationToXR(trans, offsetRotation) { + if (!offsetRotation) { + console.error('missing rotation quaternion'); + return; + } + if (!trans) { + console.error('missing translation vector'); + return; + } + const finalTransformation = trans.multiplyScalar(-1).applyQuaternion(offsetRotation); + const transform = new XRRigidTransform(finalTransformation, offsetRotation); + const teleportSpaceOffset = contextXR.baseReferenceSpace.getOffsetReferenceSpace(transform); + renderer.xr.setReferenceSpace(teleportSpaceOffset); +} + +/** + * Clamp camera to ground if option {clipToground} is active + * @param {Vector3} trans + * @returns {Vector3} coordinates clamped to ground + */ +function clampToGround(trans) { + const transCoordinate = new itowns.Coordinates(view.referenceCrs, trans.x, trans.y, trans.z); + const terrainElevation = itowns.DEMUtils.getElevationValueAt(view.tileLayer, transCoordinate, itowns.DEMUtils.PRECISE_READ_Z); + if (!terrainElevation) { + console.error('no elevation intersection possible'); + return; + } + const coordsProjected = transCoordinate.as(view.controls.getCameraCoordinate().crs); + if (clipToground || (coordsProjected.altitude - terrainElevation) - Controllers.MIN_DELTA_ALTITUDE <= 0) { + clipToground = true; + coordsProjected.altitude = terrainElevation + Controllers.MIN_DELTA_ALTITUDE; + } + return coordsProjected.as(view.referenceCrs).toVector3(); +} + +function onSelectRightStart() { + navigationMode[currentNavigationModeIndex].onSelectRightStart(this); +} + +function onSelectLeftStart() { + navigationMode[currentNavigationModeIndex].onSelectLeftStart(this); +} + +function onSelectRightEnd() { + navigationMode[currentNavigationModeIndex].onSelectRightEnd(this); +} + +function onSelectLeftEnd() { + navigationMode[currentNavigationModeIndex].onSelectLeftEnd(this); +} + +function onRightButtonPressed(data) { + if (data.target.name !== 'rightController') { + return; + } + navigationMode[currentNavigationModeIndex].onRightButtonPressed(data); + if (data.message.buttonIndex === 3) { + // hack mode, for testing many stick interaction + rightCtrChangeNavMode = true; + if (leftCtrChangeNavMode) { + switchNavigationMode(); + } + } +} + +function onLeftButtonPressed(data) { + if (data.target.name !== 'leftController') { + return; + } + navigationMode[currentNavigationModeIndex].onLeftButtonPressed(data); + if (data.message.buttonIndex === 3) { + // hack mode, for testing many stick interaction + leftCtrChangeNavMode = true; + if (rightCtrChangeNavMode) { + switchNavigationMode(); + } + } +} + +function onRightAxisChanged(data) { + if (data.target.name !== 'rightController') { + return; + } + navigationMode[currentNavigationModeIndex].onRightAxisChanged(data); +} + +function onLeftAxisChanged(data) { + if (data.target.name !== 'leftController') { + return; + } + navigationMode[currentNavigationModeIndex].onLeftAxisChanged(data); +} + +function onRightAxisStop(data) { + // camera fly reset + data.message.controller.flyDirectionQuat = undefined; + navigationMode[currentNavigationModeIndex].onRightAxisStop(data); +} + +function onLeftAxisStop(data) { + navigationMode[currentNavigationModeIndex].onLeftAxisStop(data); +} + +function onLeftButtonReleased(data) { + if (data.target.name !== 'leftController') { + return; + } + leftCtrChangeNavMode = false; + alreadySwitched = false; + navigationMode[currentNavigationModeIndex].onLeftButtonReleased(data); + if (data.message.buttonIndex === 4) { + switchDebugMode(); + } +} + +function onRightButtonReleased(data) { + if (data.target.name !== 'rightController') { + return; + } + rightCtrChangeNavMode = false; + alreadySwitched = false; + navigationMode[currentNavigationModeIndex].onRightButtonReleased(data); +} + +function switchNavigationMode() { + if (alreadySwitched) { + return; + } + alreadySwitched = true; + if (currentNavigationModeIndex >= navigationMode.length - 1) { + currentNavigationModeIndex = 0; + } else { + currentNavigationModeIndex++; + } + console.log('switching nav mode: ', currentNavigationModeIndex); +} + +function switchDebugMode() { + contextXR.showDebug = !contextXR.showDebug; + XRUtils.updateDebugVisibilities(contextXR.showDebug); + console.log('debug is: ', contextXR.showDebug); +} + + +function applyTeleportation(ctrl) { + // if locked, should I do a second click to validate as we are locked ? + if (!ctrl.userData.isSelecting) { + // if has been aborted + return; + } + ctrl.userData.isSelecting = false; + ctrl.userData.lockedTeleportPosition = false; + if (contextXR.coordOnCamera) { + const offsetRotation = Controllers.getGeodesicalQuaternion(); + const projectedCoordinate = contextXR.coordOnCamera.as(view.referenceCrs); + XRUtils.showPosition('intersect', projectedCoordinate, 0x0000ff, 50, true); + // reset continuous translation applied to headSet parent. + contextXR.xrHeadSet.position.copy(new itowns.THREE.Vector3()); + // compute targeted position relative to the origine camera. + const trans = new itowns.THREE.Vector3(projectedCoordinate.x, projectedCoordinate.y, projectedCoordinate.z); + applyTransformationToXR(trans, offsetRotation); + } +} + +function getSpeedFactor() { + const speedFactor = Math.min(Math.max(view.camera.elevationToGround / 10, 5), 2000); + return speedFactor; +} + +function getTranslationZ(axisValue, speedFactor) { + // flying following the locked camera look at + const speed = axisValue * speedFactor; + const matrixHeadset = new itowns.THREE.Matrix4(); + matrixHeadset.identity().extractRotation(view.camera.camera3D.matrixWorld); + const directionY = new itowns.THREE.Vector3(0, 0, 1).applyMatrix4(matrixHeadset).multiplyScalar(speed); + return directionY; +} + + +// ////////////////////////////////// MODE 1 + +function getRotationYaw(axisValue) { + if (axisValue === 0) { + return; + } + deltaRotation += Math.PI / (160 * axisValue); + const offsetRotation = Controllers.getGeodesicalQuaternion(); + const thetaRotMatrix = new itowns.THREE.Matrix4().identity().makeRotationY(deltaRotation); + const rotationQuartenion = new itowns.THREE.Quaternion().setFromRotationMatrix(thetaRotMatrix).normalize(); + offsetRotation.premultiply(rotationQuartenion); + return offsetRotation; +} + +function getTranslationElevation(axisValue, speedFactor) { + const speed = axisValue * speedFactor; + const direction = view.controls.getCameraCoordinate().geodesicNormal.clone(); + direction.multiplyScalar(-speed); + return direction; +} + +/** + * FIXME flying back and forth cause a permanent shift to up. + * @param {*} ctrl + * @returns + */ +function cameraOnFly(ctrl) { + if (!ctrl.flyDirectionQuat) { + // locking camera look at + // FIXME using {view.camera.camera3D.matrixWorld} or normalized quaternion produces the same effect and shift to the up direction. + ctrl.flyDirectionQuat = view.camera.camera3D.quaternion.clone().normalize(); + } + if (ctrl.gamepad.axes[2] === 0 && ctrl.gamepad.axes[3] === 0) { + return; + } + let directionX = new itowns.THREE.Vector3(); + let directionY = new itowns.THREE.Vector3(); + const speedFactor = getSpeedFactor(); + if (ctrl.gamepad.axes[3] !== 0) { + // flying following the locked camera look at + const speed = ctrl.gamepad.axes[3] * speedFactor; + directionY = new itowns.THREE.Vector3(0, 0, 1).applyQuaternion(ctrl.flyDirectionQuat).multiplyScalar(speed); + } + if (ctrl.gamepad.axes[2] !== 0) { + const speed = ctrl.gamepad.axes[2] * speedFactor; + directionX = new itowns.THREE.Vector3(1, 0, 0).applyQuaternion(ctrl.flyDirectionQuat).multiplyScalar(speed); + } + + const offsetRotation = Controllers.getGeodesicalQuaternion(); + const trans = view.camera.camera3D.position.clone().add(directionX.add(directionY)); + clampAndApplyTransformationToXR(trans, offsetRotation); +} + +const Mode1 = { + onSelectRightEnd: (ctrl) => { + applyTeleportation(ctrl); + }, + onSelectRightStart: (ctrl) => { + ctrl.userData.isSelecting = true; + }, + onSelectLeftStart: (ctrl) => { + // nothing yet needed + }, + onSelectLeftEnd: (ctrl) => { + // first left click while right selecting locks the teleportation target + // Second left click cancels teleportation target. + if (contextXR.controller2.userData.lockedTeleportPosition) { + contextXR.controller2.userData.isSelecting = false; + } + if (contextXR.controller2.userData.isSelecting) { + contextXR.controller2.userData.lockedTeleportPosition = true; + } + }, + onRightButtonPressed: (data) => { + const ctrl = data.message.controller; + if (data.message.buttonIndex === 1) { + // activate vertical adjustment + if (ctrl.gamepad.axes[3] === 0) { + return; + } + // disable clip to ground + clipToground = false; + const offsetRotation = Controllers.getGeodesicalQuaternion(); + const speedFactor = getSpeedFactor(); + const deltaTransl = getTranslationElevation(ctrl.gamepad.axes[3], speedFactor); + const trans = view.camera.camera3D.position.clone().add(deltaTransl); + clampAndApplyTransformationToXR(trans, offsetRotation); + } + }, + onLeftButtonPressed: (data) => { + if (data.message.buttonIndex === 1) { + // activate vertical adjustment + // setCameraTocontroller(); + } + }, + onRightAxisChanged: (data) => { + const ctrl = data.message.controller; + // translation controls + if (ctrl.lockButtonIndex) { + return; + } + if (contextXR.INTERSECTION) { + // updating elevation at intersection destination + contextXR.deltaAltitude -= ctrl.gamepad.axes[3] * 100; + } else { + cameraOnFly(ctrl); + } + }, + onLeftAxisChanged: (data) => { + const ctrl = data.message.controller; + // rotation controls + if (contextXR.INTERSECTION) { + // inop + } else { + const trans = view.camera.camera3D.position.clone(); + const quat = getRotationYaw(ctrl.gamepad.axes[2]); + applyTransformationToXR(trans, quat); + } + }, + onRightAxisStop: (data) => { + // inop + }, + onLeftAxisStop: (data) => { + // inop + }, + onRightButtonReleased: (data) => { + // inop + }, + onLeftButtonReleased: (data) => { + // inop + }, +}; + + +// ////////////////////////////////// MODE 2 + +const Mode2 = { + onSelectRightEnd: (ctrl) => { + applyTeleportation(ctrl); + }, + onSelectRightStart: (ctrl) => { + ctrl.userData.isSelecting = true; + }, + onSelectLeftStart: (ctrl) => { + // nothing yet needed + }, + /** + * first left click while right selecting locks the teleportation target + * Second left click cancels teleportation target. + * @param {*} ctrl + */ + onSelectLeftEnd: (ctrl) => { + if (contextXR.controller2.userData.lockedTeleportPosition) { + contextXR.controller2.userData.isSelecting = false; + } + if (contextXR.controller2.userData.isSelecting) { + contextXR.controller2.userData.lockedTeleportPosition = true; + } + }, + onRightButtonPressed: (data) => { + if (data.message.buttonIndex === 4 || data.message.buttonIndex === 5) { + if (!startedPressButton) { + startedPressButton = Date.now(); + } + // disable clip to ground + clipToground = false; + } + + const deltaTimePressed = Date.now() - startedPressButton; + if (deltaTimePressed > 2000 && !actionElevationPerformed) { + const offsetRotation = Controllers.getGeodesicalQuaternion(); + let deltaTransl; + const speedFactor = 1; + if (data.message.buttonIndex === 4) { + // activate vertical adjustment down : clamp to ground + deltaTransl = getTranslationElevation(1000000, speedFactor); + } else if (data.message.buttonIndex === 5) { + // activate vertical adjustment up : bird view + deltaTransl = getTranslationElevation(-10000, speedFactor); + } + const trans = view.camera.camera3D.position.clone().add(deltaTransl); + clampAndApplyTransformationToXR(trans, offsetRotation); + actionElevationPerformed = true; + } + }, + onLeftButtonPressed: (data) => { + // inop + }, + onRightAxisChanged: (data) => { + // translation controls + const ctrl = data.message.controller; + if (ctrl.lockButtonIndex) { + return; + } + if (contextXR.INTERSECTION) { + // updating elevation at intersection destination + contextXR.deltaAltitude -= ctrl.gamepad.axes[3] * 100; + } else { + const trans = view.camera.camera3D.position.clone(); + let quat = Controllers.getGeodesicalQuaternion(); + if (ctrl.gamepad.axes[3] !== 0) { + const deltaZ = getTranslationZ(ctrl.gamepad.axes[3], getSpeedFactor()); + trans.add(deltaZ); + } + if (ctrl.gamepad.axes[2] !== 0) { + quat = getRotationYaw(ctrl.gamepad.axes[2]); + } + clampAndApplyTransformationToXR(trans, quat); + } + }, + onLeftAxisChanged: (data) => { + // inop + }, + onRightAxisStop: (data) => { + // inop + }, + onLeftAxisStop: (data) => { + // inop + }, + onRightButtonReleased: (data) => { + let deltaTransl = new itowns.THREE.Vector3(); + startedPressButton = undefined; + + const offsetRotation = Controllers.getGeodesicalQuaternion(); + + if (!actionElevationPerformed) { + const speedFactor = getSpeedFactor(); + // lower button + if (data.message.buttonIndex === 4) { + // activate vertical adjustment down + deltaTransl = getTranslationElevation(5, speedFactor); + + // upper button + } else if (data.message.buttonIndex === 5) { + // activate vertical adjustment up + deltaTransl = getTranslationElevation(-5, speedFactor); + } + const trans = view.camera.camera3D.position.clone().add(deltaTransl); + clampAndApplyTransformationToXR(trans, offsetRotation); + } else { + actionElevationPerformed = false; + } + + }, + onLeftButtonReleased: (data) => { + // inop + }, +}; diff --git a/examples/js/XR/README.txt b/examples/js/XR/README.txt new file mode 100644 index 0000000000..8b4ad855a8 --- /dev/null +++ b/examples/js/XR/README.txt @@ -0,0 +1,77 @@ +Manettes PICO4: + +switch pour tester différents set d’interaction déplacement / rotation. +* faire une pression sur les 2 joysticks en même temps. + +## MODE 1 : + +thumbstick droit : +* X : translation gauche droite +* Y : translation avant arrière + +thumbstick gauche : +* X : rotation yaw +* Y : + +gachette#0 index droit : +* téléportation au relachement. +* + stick droit pendant pression : ajustement de la hauteur de téléportation. + +gachette#1 majeure droit : +* + stick ajustement de la hauteur de la caméra. + +bouton#4 bas (A) droit +* switch de scène prédéfinie au relachement. + +bouton#5 haut (B) droit +* + +gachette#0 index gauche : +* (si gachette index droit actif) : 1 clic verrou de la position de téléportation, 2 clic annulation de la téléportation. + +gachette#1 majeure gauche : +* afficher les coordonnées caméra actuelles. + +bouton#4 bas (X) gauche +* switch le mode debug + +bouton#5 haut (Y) gauche +* changer le mode du 3D tiles + + +## MODE 2 : + +thumbstick droit : +* X : rotation yaw +* Y : translation avant arrière + +thumbstick gauche : +* X : +* Y : + +gachette#0 index droit : +* téléportation au relachement. +* + stick droit pendant pression : ajustement de la hauteur de téléportation. + +gachette#1 majeure droit : +* + +bouton#4 bas (A) droit +* relachement courte : petit ajustement vertical bas +* pression longue (2sec) : ajustement max vertical sol + +bouton#5 haut (B) droit +* relachement courte : petit ajustement vertical haut +* pression longue (2sec) : ajustement max vertical (ciel) + +gachette#0 index gauche : +* (si gachette index droit actif) : 1 clic verrou de la position de téléportation, 2 clic annulation de la téléportation. + +gachette#1 majeure gauche : + + +bouton#4 bas (X) gauche +* + +bouton#5 haut (Y) gauche +* \ No newline at end of file diff --git a/examples/js/XR/Utils.js b/examples/js/XR/Utils.js new file mode 100644 index 0000000000..abd6d18bdb --- /dev/null +++ b/examples/js/XR/Utils.js @@ -0,0 +1,200 @@ +/** + * Reads parameter contextXR.showDebug + */ +const XRUtils = {}; + +XRUtils.objects = []; + +XRUtils.updateDebugVisibilities = (showDebugValue) => { + XRUtils.objects.forEach((obj) => { obj.visible = showDebugValue; }); + if (contextXR.visibleBbox) { + contextXR.visibleBbox.visible = showDebugValue; + } + view.notifyChange(); +}; + +XRUtils.showPosition = (name, coordinates, color, radius = 50, isDebug = false) => { + let existingChild = findExistingRef(name); + + if (existingChild) { + existingChild.position.copy(coordinates); + existingChild.scale.copy(new itowns.THREE.Vector3(1, 1, 1)).multiplyScalar(radius); + } else { + const previousPos = new itowns.THREE.Mesh( + new itowns.THREE.SphereGeometry(1, 16, 8), + new itowns.THREE.MeshBasicMaterial({ color: color, wireframe: true }), + ); + previousPos.name = name; + previousPos.position.copy(coordinates); + previousPos.scale.multiplyScalar(radius); + XRUtils.addToScene(previousPos, isDebug); + existingChild = previousPos; + } + return existingChild; +}; + +XRUtils.removeReference = (name) => { + const existingChild = findExistingRef(name); + if (existingChild) { + view.scene.remove(existingChild); + let indexToRemove = null; + XRUtils.objects.forEach((child, index) => { if (child.name === name) { indexToRemove = index; } }); + XRUtils.objects.splice(indexToRemove, 1); + } +}; + +/** + * + * @param {*} name + * @param {*} coordinates + * @param {*} color hexa color + * @param {*} size + * @param {*} isDebug + */ +XRUtils.addPositionPoints = (name, coordinates, color, size, isDebug = false) => { + const existingChild = findExistingRef(name); + if (existingChild) { + const verticesUpdated = existingChild.geometry.attributes.position.array.values().toArray(); + verticesUpdated.push(coordinates.x, coordinates.y, coordinates.z); + existingChild.geometry.setAttribute('position', new itowns.THREE.Float32BufferAttribute(verticesUpdated, 3)); + } else { + const geometry = new itowns.THREE.BufferGeometry(); + const vertices = []; + vertices.push(coordinates.x, coordinates.y, coordinates.z); + const material = new itowns.THREE.PointsMaterial({ size: size, color: color }); + geometry.setAttribute('position', new itowns.THREE.Float32BufferAttribute(vertices, 3)); + const particle = new itowns.THREE.Points(geometry, material); + particle.name = name; + XRUtils.addToScene(particle, isDebug); + } +}; + +/** + * + * @param {*} name + * @param {*} coordinates + * @param {*} color hexa color + * @param {*} upSize + * @param {*} isDebug + */ +XRUtils.showPositionVerticalLine = (name, coordinates, color, upSize, isDebug = false) => { + const existingChild = findExistingRef(name); + if (existingChild) { + existingChild.position.copy(coordinates); + existingChild.lookAt(new itowns.THREE.Vector3(0, 0, 1)); + } else { + const points = []; + points.push(new itowns.THREE.Vector3(0, 0, 0)); + // upward direction + points.push(new itowns.THREE.Vector3(0, 0, -upSize)); + const line = new itowns.THREE.Line( + new itowns.THREE.BufferGeometry().setFromPoints(points), + new itowns.THREE.LineBasicMaterial({ color: color })); + line.position.copy(coordinates); + // necessary to "look" vertically + line.lookAt(new itowns.THREE.Vector3(0, 0, 1)); + line.name = name; + XRUtils.addToScene(line, isDebug); + } +}; + +/** + * + * @param {*} name + * @param {*} originVector3 + * @param {*} directionVector3 + * @param {*} scale + * @param {*} color hexa color + * @param {*} isDebug + */ +XRUtils.renderdirectionArrow = (name, originVector3, directionVector3, scale, color, isDebug = false) => { + const existingChild = findExistingRef(name); + if (existingChild) { + existingChild.setDirection(directionVector3); + existingChild.position.copy(originVector3); + } else { + const arrow = new itowns.THREE.ArrowHelper(directionVector3, originVector3, scale, color); + arrow.name = name; + XRUtils.addToScene(arrow, isDebug); + } +}; + +/** + * + * @param {Object3D} object + * @returns {Object3D} + */ +XRUtils.generateVRBox = (object) => { + const objectBbox = new itowns.THREE.Box3(); + // better than object.geometry.computeBoundingBox(); as it copy parent position. + objectBbox.setFromObject(object); + object.VRBbox = objectBbox; + object.VRBbox = new itowns.THREE.Box3Helper(object.VRBbox, 0xffff00); + + object.VRBbox.name = (object.name || object.uuid) + '_VRBbox'; + // console.log('adding VRBbox to scene : ', object.VRBbox.name); + // no need to add each bbox to the Utils memory + view.scene.add(object.VRBbox); + object.VRBbox.visible = false; + return object.VRBbox; +}; + +/** + * + * @param {Object3D} object + * @returns + */ +XRUtils.updateBboxVisibility = (object) => { + if (!contextXR.showDebug) { + return; + } + if (contextXR.visibleBbox && contextXR.visibleBbox === object.VRBbox) { + return; + } + // proper to box3Helper + if (object.box) { + if (!object.visible) { + resetPreviousVisibleeBbox(); + contextXR.visibleBbox = object; + object.visible = true; + } + } else if (object.geometry) { + if (!object.VRBbox) { + XRUtils.generateVRBox(object); + } + if (!object.VRBbox.visible) { + resetPreviousVisibleeBbox(); + contextXR.visibleBbox = object.VRBbox; + object.VRBbox.visible = true; + } + } else if (contextXR.visibleBbox) { + resetPreviousVisibleeBbox(); + } + + function resetPreviousVisibleeBbox() { + if (contextXR.visibleBbox) { + contextXR.visibleBbox.visible = false; + contextXR.visibleBbox = undefined; + } + } +}; + +/** + * + * @param {Object3D} object + * @param {boolean} isDebug + */ +XRUtils.addToScene = (object, isDebug) => { + console.log('adding object to scene : ', object.name); + object.visible = !isDebug || (isDebug && contextXR.showDebug); + view.scene.add(object); + XRUtils.objects.push(object); +}; + +function findExistingRef(name) { + let existingChild; + view.scene.children.forEach((child) => { if (child.name === name) { existingChild = child; } }); + return existingChild; +} + + diff --git a/examples/js/XR/mode1.png b/examples/js/XR/mode1.png new file mode 100644 index 0000000000..0c76db842f Binary files /dev/null and b/examples/js/XR/mode1.png differ diff --git a/examples/js/XR/mode2.png b/examples/js/XR/mode2.png new file mode 100644 index 0000000000..3c9657311c Binary files /dev/null and b/examples/js/XR/mode2.png differ diff --git a/examples/misc_clamp_ground.html b/examples/misc_clamp_ground.html index 1e5317d0c8..383d9c0601 100644 --- a/examples/misc_clamp_ground.html +++ b/examples/misc_clamp_ground.html @@ -65,12 +65,12 @@ // position of the mesh var meshCoord = cameraTargetPosition; - meshCoord.altitude += 30; + // meshCoord.altitude += 30; // position and orientation of the mesh mesh.position.copy(meshCoord.as(view.referenceCrs)); - mesh.lookAt(new THREE.Vector3(0, 0, 0)); - mesh.rotateX(Math.PI / 2); + //mesh.lookAt(new THREE.Vector3(0, 0, 0)); + // mesh.rotateX(Math.PI / 2); // update coordinate of the mesh mesh.updateMatrixWorld(); diff --git a/examples/view_3d_map_webxr.html b/examples/view_3d_map_webxr.html index 56bfee0a60..a28e13a917 100644 --- a/examples/view_3d_map_webxr.html +++ b/examples/view_3d_map_webxr.html @@ -1,16 +1,21 @@ -
-