提交 b0210cac 编写于 作者: B Brandon Jones

Add XRControllerModelLoader

This utility makes it super simple to load controller meshes from the webxr-input-profile registry, giving users a controller visualization that matches what they're holding in their hand, with individual components animated in response to real-world input.
上级 72f1e2ea
import {
Group,
Object3D,
Texture
} from '../../../src/Three';
import { GLTFLoader } from '../loaders/GLTFLoader';
export class XRControllerModel extends Object3D {
constructor( );
motionController: any;
setEnvironmentMap( envMap: Texture ): XRControllerModel;
}
export class XRControllerModelLoader {
constructor( gltfLoader?: GLTFLoader );
gltfLoader: GLTFLoader | null;
path: string;
setGLTFLoader( gltfLoader: GLTFLoader ): XRControllerModelLoader;
getControllerModel( controller: Group ): XRControllerModel;
}
/**
* @author Nell Waliczek / https://github.com/NellWaliczek
* @author Brandon Jones / https://github.com/toji
*/
import {
Mesh,
MeshBasicMaterial,
Object3D,
Quaternion,
SphereGeometry,
} from "../../../build/three.module.js";
import {
Constants as MotionControllerConstants,
fetchProfile,
MotionController
} from 'https://cdn.jsdelivr.net/npm/@webxr-input-profiles/motion-controllers@0.1.0/dist/motion-controllers.module.js'
const DEFAULT_PROFILES_PATH = 'https://cdn.jsdelivr.net/npm/@webxr-input-profiles/assets@0.1.0/dist/profiles'
function XRControllerModel( ) {
Object3D.call( this );
this.motionController = null;
this.envMap = null;
}
XRControllerModel.prototype = Object.assign( Object.create( Object3D.prototype ), {
constructor: XRControllerModel,
setEnvironmentMap: function ( envMap ) {
if ( this.envMap == envMap ) {
return this;
}
this.envMap = envMap;
this.traverse(( child ) => {
if ( child.isMesh ) {
child.material.envMap = this.envMap;
child.material.needsUpdate = true;
}
});
return this;
},
/**
* Polls data from the XRInputSource and updates the model's components to match
* the real world data
*/
updateMatrixWorld: function ( force ) {
Object3D.prototype.updateMatrixWorld.call( this, force );
if ( !this.motionController ) return;
// Cause the MotionController to poll the Gamepad for data
this.motionController.updateFromGamepad();
// Update the 3D model to reflect the button, thumbstick, and touchpad state
Object.values( this.motionController.components ).forEach(( component ) => {
// Update node data based on the visual responses' current states
Object.values( component.visualResponses ).forEach(( visualResponse ) => {
const { valueNode, minNode, maxNode, value, valueNodeProperty } = visualResponse;
// Skip if the visual response node is not found. No error is needed,
// because it will have been reported at load time.
if ( !valueNode ) return;
// Calculate the new properties based on the weight supplied
if ( valueNodeProperty === MotionControllerConstants.VisualResponseProperty.VISIBILITY ) {
valueNode.visible = value;
} else if ( valueNodeProperty === MotionControllerConstants.VisualResponseProperty.TRANSFORM ) {
Quaternion.slerp(
minNode.quaternion,
maxNode.quaternion,
valueNode.quaternion,
value
);
valueNode.position.lerpVectors(
minNode.position,
maxNode.position,
value
);
}
});
});
}
} );
/**
* Walks the model's tree to find the nodes needed to animate the components and
* saves them to the motionContoller components for use in the frame loop. When
* touchpads are found, attaches a touch dot to them.
*/
function findNodes( motionController, scene ) {
// Loop through the components and find the nodes needed for each components' visual responses
Object.values( motionController.components ).forEach(( component ) => {
const { type, touchPointNodeName, visualResponses } = component;
if (type === MotionControllerConstants.ComponentType.TOUCHPAD) {
component.touchPointNode = scene.getObjectByName( touchPointNodeName );
if ( component.touchPointNode ) {
// Attach a touch dot to the touchpad.
const sphereGeometry = new SphereGeometry( 0.001 );
const material = new MeshBasicMaterial({ color: 0x0000FF });
const sphere = new Mesh( sphereGeometry, material );
component.touchPointNode.add( sphere );
} else {
console.warn(`Could not find touch dot, ${component.touchPointNodeName}, in touchpad component ${componentId}`);
}
}
// Loop through all the visual responses to be applied to this component
Object.values( visualResponses ).forEach(( visualResponse ) => {
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 the extents cannot be found, skip this animation
if ( !visualResponse.minNode ) {
console.warn(`Could not find ${minNodeName} in the model`);
return;
}
if ( !visualResponse.maxNode ) {
console.warn(`Could not find ${maxNodeName} in the model`);
return;
}
}
// If the target node cannot be found, skip this animation
visualResponse.valueNode = scene.getObjectByName(valueNodeName);
if ( !visualResponse.valueNode ) {
console.warn(`Could not find ${valueNodeName} in the model`);
}
});
});
}
var XRControllerModelLoader = ( function () {
function XRControllerModelLoader( gltfLoader ) {
this.gltfLoader = gltfLoader;
this.path = DEFAULT_PROFILES_PATH;
}
XRControllerModelLoader.prototype = {
constructor: XRControllerModelLoader,
setGLTFLoader: function ( gltfLoader ) {
this.gltfLoader = gltfLoader;
return this;
},
getControllerModel: function ( controller ) {
if ( !this.gltfLoader ) {
throw new Error(`GLTFLoader not set.`);
}
const controllerModel = new XRControllerModel();
let scene = null;
controller.addEventListener( 'connected', ( event ) => {
const xrInputSource = event.data;
fetchProfile(xrInputSource, this.path).then(({ profile, assetPath }) => {
controllerModel.motionController = new MotionController(
xrInputSource,
profile,
assetPath
);
this.gltfLoader.setPath('');
this.gltfLoader.load( controllerModel.motionController.assetUrl, ( asset ) => {
scene = asset.scene;
// Find the nodes needed for animation and cache them on the motionController.
findNodes( controllerModel.motionController, scene );
// Apply any environment map that the mesh already has set.
if ( controllerModel.envMap ) {
scene.traverse(( child ) => {
if ( child.isMesh ) {
child.material.envMap = controllerModel.envMap;
child.material.needsUpdate = true;
}
});
}
// Add the glTF scene to the controllerModel.
controllerModel.add( scene );
},
null,
() => {
throw new Error(`Asset ${motionController.assetUrl} missing or malformed.`);
});
}).catch((err) => {
console.warn(err);
});
});
controller.addEventListener( 'disconnected', () => {
controllerModel.motionController = null;
controllerModel.remove( scene );
});
return controllerModel;
}
};
return XRControllerModelLoader;
} )();
export { XRControllerModelLoader };
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册