-
Notifications
You must be signed in to change notification settings - Fork 118
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
c8fa93e
commit 085042b
Showing
2 changed files
with
123 additions
and
70 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,6 +7,15 @@ import { fetchProfile, MotionController, MotionControllerConstants } from '../li | |
const DEFAULT_PROFILES_PATH = 'https://cdn.jsdelivr.net/npm/@webxr-input-profiles/[email protected]/dist/profiles' | ||
const DEFAULT_PROFILE = 'generic-trigger' | ||
|
||
const applyEnvironmentMap = (envMap: Texture, obj: Object3D): void => { | ||
obj.traverse((child) => { | ||
if (child instanceof Mesh && 'envMap' in child.material) { | ||
child.material.envMap = envMap | ||
child.material.needsUpdate = true | ||
} | ||
}) | ||
} | ||
|
||
class XRControllerModel extends Object3D { | ||
envMap: Texture | null | ||
motionController: MotionController | null | ||
|
@@ -23,12 +32,7 @@ class XRControllerModel extends Object3D { | |
} | ||
|
||
this.envMap = envMap | ||
this.traverse((child) => { | ||
if ((child as Mesh).isMesh) { | ||
;((child as Mesh).material as MeshBasicMaterial).envMap = this.envMap | ||
;((child as Mesh).material as MeshBasicMaterial).needsUpdate = true | ||
} | ||
}) | ||
applyEnvironmentMap(this.envMap, this) | ||
|
||
return this | ||
} | ||
|
@@ -56,12 +60,20 @@ class XRControllerModel extends Object3D { | |
if (!valueNode) return | ||
|
||
// Calculate the new properties based on the weight supplied | ||
if (valueNodeProperty === MotionControllerConstants.VisualResponseProperty.VISIBILITY) { | ||
valueNode.visible = value as boolean | ||
} else if (valueNodeProperty === MotionControllerConstants.VisualResponseProperty.TRANSFORM) { | ||
valueNode.quaternion.slerpQuaternions(minNode!.quaternion, maxNode!.quaternion, value as number) | ||
|
||
valueNode.position.lerpVectors(minNode!.position, maxNode!.position, value as number) | ||
if ( | ||
valueNodeProperty === MotionControllerConstants.VisualResponseProperty.VISIBILITY && | ||
typeof value === 'boolean' | ||
) { | ||
valueNode.visible = value | ||
} else if ( | ||
valueNodeProperty === MotionControllerConstants.VisualResponseProperty.TRANSFORM && | ||
minNode && | ||
maxNode && | ||
typeof value === 'number' | ||
) { | ||
valueNode.quaternion.slerpQuaternions(minNode.quaternion, maxNode.quaternion, value) | ||
|
||
valueNode.position.lerpVectors(minNode.position, maxNode.position, value) | ||
} | ||
}) | ||
}) | ||
|
@@ -78,7 +90,7 @@ function findNodes(motionController: MotionController, scene: Object3D): void { | |
Object.values(motionController.components).forEach((component) => { | ||
const { type, touchPointNodeName, visualResponses } = component | ||
|
||
if (type === MotionControllerConstants.ComponentType.TOUCHPAD) { | ||
if (type === MotionControllerConstants.ComponentType.TOUCHPAD && touchPointNodeName) { | ||
component.touchPointNode = scene.getObjectByName(touchPointNodeName) | ||
if (component.touchPointNode) { | ||
// Attach a touch dot to the touchpad. | ||
|
@@ -96,9 +108,13 @@ function findNodes(motionController: MotionController, scene: Object3D): void { | |
const { valueNodeName, minNodeName, maxNodeName, valueNodeProperty } = visualResponse | ||
|
||
// If animating a transform, find the two nodes to be interpolated between. | ||
if (valueNodeProperty === MotionControllerConstants.VisualResponseProperty.TRANSFORM) { | ||
visualResponse.minNode = scene.getObjectByName(minNodeName!) | ||
visualResponse.maxNode = scene.getObjectByName(maxNodeName!) | ||
if ( | ||
valueNodeProperty === MotionControllerConstants.VisualResponseProperty.TRANSFORM && | ||
minNodeName && | ||
maxNodeName | ||
) { | ||
visualResponse.minNode = scene.getObjectByName(minNodeName) | ||
visualResponse.maxNode = scene.getObjectByName(maxNodeName) | ||
|
||
// If the extents cannot be found, skip this animation | ||
if (!visualResponse.minNode) { | ||
|
@@ -127,12 +143,7 @@ function addAssetSceneToControllerModel(controllerModel: XRControllerModel, scen | |
|
||
// Apply any environment map that the mesh already has set. | ||
if (controllerModel.envMap) { | ||
scene.traverse((child) => { | ||
if ((child as Mesh).isMesh) { | ||
;((child as Mesh).material as MeshBasicMaterial).envMap = this.envMap | ||
;((child as Mesh).material as MeshBasicMaterial).needsUpdate = true | ||
} | ||
}) | ||
applyEnvironmentMap(controllerModel.envMap, scene) | ||
} | ||
|
||
// Add the glTF scene to the controllerModel. | ||
|
@@ -143,9 +154,9 @@ class XRControllerModelFactory { | |
gltfLoader: GLTFLoader | ||
path: string | ||
private _assetCache: Record<string, { scene: Object3D } | undefined> | ||
constructor(gltfLoader: GLTFLoader = null) { | ||
constructor(gltfLoader: GLTFLoader = null, path = DEFAULT_PROFILES_PATH) { | ||
this.gltfLoader = gltfLoader | ||
this.path = DEFAULT_PROFILES_PATH | ||
this.path = path | ||
this._assetCache = {} | ||
|
||
// If a GLTFLoader wasn't supplied to the constructor create a new one. | ||
|
@@ -154,7 +165,11 @@ class XRControllerModelFactory { | |
} | ||
} | ||
|
||
createControllerModel(controller: Group): XRControllerModel { | ||
createControllerModel( | ||
controller: Group, | ||
onMotionControllerCreated?: (motionContoller: MotionController) => void, | ||
onMotionControllerDestroyed?: () => void, | ||
): XRControllerModel { | ||
const controllerModel = new XRControllerModel() | ||
let scene: Object3D | null = null | ||
|
||
|
@@ -165,13 +180,17 @@ class XRControllerModelFactory { | |
|
||
fetchProfile(xrInputSource, this.path, DEFAULT_PROFILE) | ||
.then(({ profile, assetPath }) => { | ||
if (!assetPath) { | ||
throw new Error('no asset path') | ||
} | ||
controllerModel.motionController = new MotionController(xrInputSource, profile, assetPath) | ||
onMotionControllerCreated?.(controllerModel.motionController) | ||
|
||
const cachedAsset = this._assetCache[controllerModel.motionController.assetUrl] | ||
if (cachedAsset) { | ||
scene = cachedAsset.scene.clone() | ||
|
||
addAssetSceneToControllerModel(controllerModel, scene!) | ||
addAssetSceneToControllerModel(controllerModel, scene) | ||
} else { | ||
if (!this.gltfLoader) { | ||
throw new Error('GLTFLoader not set.') | ||
|
@@ -181,15 +200,20 @@ class XRControllerModelFactory { | |
this.gltfLoader.load( | ||
controllerModel.motionController.assetUrl, | ||
(asset: { scene: Object3D }) => { | ||
this._assetCache[controllerModel.motionController!.assetUrl] = asset | ||
if (!controllerModel.motionController) { | ||
console.warn('motionController gone while gltf load, bailing...') | ||
return | ||
} | ||
|
||
this._assetCache[controllerModel.motionController.assetUrl] = asset | ||
|
||
scene = asset.scene.clone() | ||
|
||
addAssetSceneToControllerModel(controllerModel, scene!) | ||
addAssetSceneToControllerModel(controllerModel, scene) | ||
}, | ||
null, | ||
() => { | ||
throw new Error(`Asset ${controllerModel.motionController!.assetUrl} missing or malformed.`) | ||
throw new Error(`Asset ${controllerModel.motionController?.assetUrl} missing or malformed.`) | ||
}, | ||
) | ||
} | ||
|
@@ -201,8 +225,11 @@ class XRControllerModelFactory { | |
|
||
controller.addEventListener('disconnected', () => { | ||
controllerModel.motionController = null | ||
controllerModel.remove(scene!) | ||
if (scene) { | ||
controllerModel.remove(scene) | ||
} | ||
scene = null | ||
onMotionControllerDestroyed?.() | ||
}) | ||
|
||
return controllerModel | ||
|