add Potree (version 1.8)
This commit is contained in:
191
PointCloudWeb.Web/public/Potree/libs/three.js/webxr/ARButton.js
Normal file
191
PointCloudWeb.Web/public/Potree/libs/three.js/webxr/ARButton.js
Normal file
@@ -0,0 +1,191 @@
|
||||
class ARButton {
|
||||
|
||||
static createButton( renderer, sessionInit = {} ) {
|
||||
|
||||
const button = document.createElement( 'button' );
|
||||
|
||||
function showStartAR( /*device*/ ) {
|
||||
|
||||
if ( sessionInit.domOverlay === undefined ) {
|
||||
|
||||
var overlay = document.createElement( 'div' );
|
||||
overlay.style.display = 'none';
|
||||
document.body.appendChild( overlay );
|
||||
|
||||
var svg = document.createElementNS( 'http://www.w3.org/2000/svg', 'svg' );
|
||||
svg.setAttribute( 'width', 38 );
|
||||
svg.setAttribute( 'height', 38 );
|
||||
svg.style.position = 'absolute';
|
||||
svg.style.right = '20px';
|
||||
svg.style.top = '20px';
|
||||
svg.addEventListener( 'click', function () {
|
||||
|
||||
currentSession.end();
|
||||
|
||||
} );
|
||||
overlay.appendChild( svg );
|
||||
|
||||
var path = document.createElementNS( 'http://www.w3.org/2000/svg', 'path' );
|
||||
path.setAttribute( 'd', 'M 12,12 L 28,28 M 28,12 12,28' );
|
||||
path.setAttribute( 'stroke', '#fff' );
|
||||
path.setAttribute( 'stroke-width', 2 );
|
||||
svg.appendChild( path );
|
||||
|
||||
sessionInit.optionalFeatures = [ 'dom-overlay' ];
|
||||
sessionInit.domOverlay = { root: overlay };
|
||||
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
let currentSession = null;
|
||||
|
||||
function onSessionStarted( session ) {
|
||||
|
||||
session.addEventListener( 'end', onSessionEnded );
|
||||
|
||||
renderer.xr.setReferenceSpaceType( 'local' );
|
||||
renderer.xr.setSession( session );
|
||||
|
||||
button.textContent = 'STOP AR';
|
||||
sessionInit.domOverlay.root.style.display = '';
|
||||
|
||||
currentSession = session;
|
||||
|
||||
}
|
||||
|
||||
function onSessionEnded( /*event*/ ) {
|
||||
|
||||
currentSession.removeEventListener( 'end', onSessionEnded );
|
||||
|
||||
button.textContent = 'START AR';
|
||||
sessionInit.domOverlay.root.style.display = 'none';
|
||||
|
||||
currentSession = null;
|
||||
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
button.style.display = '';
|
||||
|
||||
button.style.cursor = 'pointer';
|
||||
button.style.left = 'calc(50% - 50px)';
|
||||
button.style.width = '100px';
|
||||
|
||||
button.textContent = 'START AR';
|
||||
|
||||
button.onmouseenter = function () {
|
||||
|
||||
button.style.opacity = '1.0';
|
||||
|
||||
};
|
||||
|
||||
button.onmouseleave = function () {
|
||||
|
||||
button.style.opacity = '0.5';
|
||||
|
||||
};
|
||||
|
||||
button.onclick = function () {
|
||||
|
||||
if ( currentSession === null ) {
|
||||
|
||||
navigator.xr.requestSession( 'immersive-ar', sessionInit ).then( onSessionStarted );
|
||||
|
||||
} else {
|
||||
|
||||
currentSession.end();
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
function disableButton() {
|
||||
|
||||
button.style.display = '';
|
||||
|
||||
button.style.cursor = 'auto';
|
||||
button.style.left = 'calc(50% - 75px)';
|
||||
button.style.width = '150px';
|
||||
|
||||
button.onmouseenter = null;
|
||||
button.onmouseleave = null;
|
||||
|
||||
button.onclick = null;
|
||||
|
||||
}
|
||||
|
||||
function showARNotSupported() {
|
||||
|
||||
disableButton();
|
||||
|
||||
button.textContent = 'AR NOT SUPPORTED';
|
||||
|
||||
}
|
||||
|
||||
function stylizeElement( element ) {
|
||||
|
||||
element.style.position = 'absolute';
|
||||
element.style.bottom = '20px';
|
||||
element.style.padding = '12px 6px';
|
||||
element.style.border = '1px solid #fff';
|
||||
element.style.borderRadius = '4px';
|
||||
element.style.background = 'rgba(0,0,0,0.1)';
|
||||
element.style.color = '#fff';
|
||||
element.style.font = 'normal 13px sans-serif';
|
||||
element.style.textAlign = 'center';
|
||||
element.style.opacity = '0.5';
|
||||
element.style.outline = 'none';
|
||||
element.style.zIndex = '999';
|
||||
|
||||
}
|
||||
|
||||
if ( 'xr' in navigator ) {
|
||||
|
||||
button.id = 'ARButton';
|
||||
button.style.display = 'none';
|
||||
|
||||
stylizeElement( button );
|
||||
|
||||
navigator.xr.isSessionSupported( 'immersive-ar' ).then( function ( supported ) {
|
||||
|
||||
supported ? showStartAR() : showARNotSupported();
|
||||
|
||||
} ).catch( showARNotSupported );
|
||||
|
||||
return button;
|
||||
|
||||
} else {
|
||||
|
||||
const message = document.createElement( 'a' );
|
||||
|
||||
if ( window.isSecureContext === false ) {
|
||||
|
||||
message.href = document.location.href.replace( /^http:/, 'https:' );
|
||||
message.innerHTML = 'WEBXR NEEDS HTTPS'; // TODO Improve message
|
||||
|
||||
} else {
|
||||
|
||||
message.href = 'https://immersiveweb.dev/';
|
||||
message.innerHTML = 'WEBXR NOT AVAILABLE';
|
||||
|
||||
}
|
||||
|
||||
message.style.left = 'calc(50% - 90px)';
|
||||
message.style.width = '180px';
|
||||
message.style.textDecoration = 'none';
|
||||
|
||||
stylizeElement( message );
|
||||
|
||||
return message;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export { ARButton };
|
||||
169
PointCloudWeb.Web/public/Potree/libs/three.js/webxr/VRButton.js
Normal file
169
PointCloudWeb.Web/public/Potree/libs/three.js/webxr/VRButton.js
Normal file
@@ -0,0 +1,169 @@
|
||||
class VRButton {
|
||||
|
||||
static createButton( renderer, options ) {
|
||||
|
||||
if ( options ) {
|
||||
|
||||
console.error( 'THREE.VRButton: The "options" parameter has been removed. Please set the reference space type via renderer.xr.setReferenceSpaceType() instead.' );
|
||||
|
||||
}
|
||||
|
||||
const button = document.createElement( 'button' );
|
||||
|
||||
function showEnterVR( /*device*/ ) {
|
||||
|
||||
let currentSession = null;
|
||||
|
||||
function onSessionStarted( session ) {
|
||||
|
||||
session.addEventListener( 'end', onSessionEnded );
|
||||
|
||||
renderer.xr.setSession( session );
|
||||
button.textContent = 'EXIT VR';
|
||||
|
||||
currentSession = session;
|
||||
|
||||
}
|
||||
|
||||
function onSessionEnded( /*event*/ ) {
|
||||
|
||||
currentSession.removeEventListener( 'end', onSessionEnded );
|
||||
|
||||
button.textContent = 'ENTER VR';
|
||||
|
||||
currentSession = null;
|
||||
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
button.style.display = '';
|
||||
|
||||
button.style.cursor = 'pointer';
|
||||
button.style.left = 'calc(50% - 50px)';
|
||||
button.style.width = '100px';
|
||||
|
||||
button.textContent = 'ENTER VR';
|
||||
|
||||
button.onmouseenter = function () {
|
||||
|
||||
button.style.opacity = '1.0';
|
||||
|
||||
};
|
||||
|
||||
button.onmouseleave = function () {
|
||||
|
||||
button.style.opacity = '0.5';
|
||||
|
||||
};
|
||||
|
||||
button.onclick = function () {
|
||||
|
||||
if ( currentSession === null ) {
|
||||
|
||||
// WebXR's requestReferenceSpace only works if the corresponding feature
|
||||
// was requested at session creation time. For simplicity, just ask for
|
||||
// the interesting ones as optional features, but be aware that the
|
||||
// requestReferenceSpace call will fail if it turns out to be unavailable.
|
||||
// ('local' is always available for immersive sessions and doesn't need to
|
||||
// be requested separately.)
|
||||
|
||||
const sessionInit = { optionalFeatures: [ 'local-floor', 'bounded-floor', 'hand-tracking' ] };
|
||||
navigator.xr.requestSession( 'immersive-vr', sessionInit ).then( onSessionStarted );
|
||||
|
||||
} else {
|
||||
|
||||
currentSession.end();
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
function disableButton() {
|
||||
|
||||
button.style.display = '';
|
||||
|
||||
button.style.cursor = 'auto';
|
||||
button.style.left = 'calc(50% - 75px)';
|
||||
button.style.width = '150px';
|
||||
|
||||
button.onmouseenter = null;
|
||||
button.onmouseleave = null;
|
||||
|
||||
button.onclick = null;
|
||||
|
||||
}
|
||||
|
||||
function showWebXRNotFound() {
|
||||
|
||||
disableButton();
|
||||
|
||||
button.textContent = 'VR NOT SUPPORTED';
|
||||
|
||||
}
|
||||
|
||||
function stylizeElement( element ) {
|
||||
|
||||
element.style.position = 'absolute';
|
||||
element.style.bottom = '20px';
|
||||
element.style.padding = '12px 6px';
|
||||
element.style.border = '1px solid #fff';
|
||||
element.style.borderRadius = '4px';
|
||||
element.style.background = 'rgba(0,0,0,0.1)';
|
||||
element.style.color = '#fff';
|
||||
element.style.font = 'normal 13px sans-serif';
|
||||
element.style.textAlign = 'center';
|
||||
element.style.opacity = '0.5';
|
||||
element.style.outline = 'none';
|
||||
element.style.zIndex = '999';
|
||||
|
||||
}
|
||||
|
||||
if ( 'xr' in navigator ) {
|
||||
|
||||
button.id = 'VRButton';
|
||||
button.style.display = 'none';
|
||||
|
||||
stylizeElement( button );
|
||||
|
||||
navigator.xr.isSessionSupported( 'immersive-vr' ).then( function ( supported ) {
|
||||
|
||||
supported ? showEnterVR() : showWebXRNotFound();
|
||||
|
||||
} );
|
||||
|
||||
return button;
|
||||
|
||||
} else {
|
||||
|
||||
const message = document.createElement( 'a' );
|
||||
|
||||
if ( window.isSecureContext === false ) {
|
||||
|
||||
message.href = document.location.href.replace( /^http:/, 'https:' );
|
||||
message.innerHTML = 'WEBXR NEEDS HTTPS'; // TODO Improve message
|
||||
|
||||
} else {
|
||||
|
||||
message.href = 'https://immersiveweb.dev/';
|
||||
message.innerHTML = 'WEBXR NOT AVAILABLE';
|
||||
|
||||
}
|
||||
|
||||
message.style.left = 'calc(50% - 90px)';
|
||||
message.style.width = '180px';
|
||||
message.style.textDecoration = 'none';
|
||||
|
||||
stylizeElement( message );
|
||||
|
||||
return message;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export { VRButton };
|
||||
@@ -0,0 +1,311 @@
|
||||
import {
|
||||
Mesh,
|
||||
MeshBasicMaterial,
|
||||
Object3D,
|
||||
Quaternion,
|
||||
SphereBufferGeometry,
|
||||
} from '../build/three.module.js';
|
||||
|
||||
import { GLTFLoader } from '../loaders/GLTFLoader.js';
|
||||
|
||||
import {
|
||||
Constants as MotionControllerConstants,
|
||||
fetchProfile,
|
||||
MotionController
|
||||
} from '../libs/motion-controllers.module.js';
|
||||
|
||||
const DEFAULT_PROFILES_PATH = 'https://cdn.jsdelivr.net/npm/@webxr-input-profiles/assets@1.0/dist/profiles';
|
||||
const DEFAULT_PROFILE = 'generic-trigger';
|
||||
|
||||
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 SphereBufferGeometry( 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 ${component.id}` );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 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` );
|
||||
|
||||
}
|
||||
|
||||
} );
|
||||
|
||||
} );
|
||||
|
||||
}
|
||||
|
||||
function addAssetSceneToControllerModel( controllerModel, 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 );
|
||||
|
||||
}
|
||||
|
||||
var XRControllerModelFactory = ( function () {
|
||||
|
||||
function XRControllerModelFactory( gltfLoader = null ) {
|
||||
|
||||
this.gltfLoader = gltfLoader;
|
||||
this.path = DEFAULT_PROFILES_PATH;
|
||||
this._assetCache = {};
|
||||
|
||||
// If a GLTFLoader wasn't supplied to the constructor create a new one.
|
||||
if ( ! this.gltfLoader ) {
|
||||
|
||||
this.gltfLoader = new GLTFLoader();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
XRControllerModelFactory.prototype = {
|
||||
|
||||
constructor: XRControllerModelFactory,
|
||||
|
||||
createControllerModel: function ( controller ) {
|
||||
|
||||
const controllerModel = new XRControllerModel();
|
||||
let scene = null;
|
||||
|
||||
controller.addEventListener( 'connected', ( event ) => {
|
||||
|
||||
const xrInputSource = event.data;
|
||||
|
||||
if ( xrInputSource.targetRayMode !== 'tracked-pointer' || ! xrInputSource.gamepad ) return;
|
||||
|
||||
fetchProfile( xrInputSource, this.path, DEFAULT_PROFILE ).then( ( { profile, assetPath } ) => {
|
||||
|
||||
controllerModel.motionController = new MotionController(
|
||||
xrInputSource,
|
||||
profile,
|
||||
assetPath
|
||||
);
|
||||
|
||||
const cachedAsset = this._assetCache[ controllerModel.motionController.assetUrl ];
|
||||
if ( cachedAsset ) {
|
||||
|
||||
scene = cachedAsset.scene.clone();
|
||||
|
||||
addAssetSceneToControllerModel( controllerModel, scene );
|
||||
|
||||
} else {
|
||||
|
||||
if ( ! this.gltfLoader ) {
|
||||
|
||||
throw new Error( 'GLTFLoader not set.' );
|
||||
|
||||
}
|
||||
|
||||
this.gltfLoader.setPath( '' );
|
||||
this.gltfLoader.load( controllerModel.motionController.assetUrl, ( asset ) => {
|
||||
|
||||
this._assetCache[ controllerModel.motionController.assetUrl ] = asset;
|
||||
|
||||
scene = asset.scene.clone();
|
||||
|
||||
addAssetSceneToControllerModel( controllerModel, scene );
|
||||
|
||||
},
|
||||
null,
|
||||
() => {
|
||||
|
||||
throw new Error( `Asset ${controllerModel.motionController.assetUrl} missing or malformed.` );
|
||||
|
||||
} );
|
||||
|
||||
}
|
||||
|
||||
} ).catch( ( err ) => {
|
||||
|
||||
console.warn( err );
|
||||
|
||||
} );
|
||||
|
||||
} );
|
||||
|
||||
controller.addEventListener( 'disconnected', () => {
|
||||
|
||||
controllerModel.motionController = null;
|
||||
controllerModel.remove( scene );
|
||||
scene = null;
|
||||
|
||||
} );
|
||||
|
||||
return controllerModel;
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
return XRControllerModelFactory;
|
||||
|
||||
} )();
|
||||
|
||||
export { XRControllerModelFactory };
|
||||
@@ -0,0 +1,113 @@
|
||||
import {
|
||||
Object3D
|
||||
} from '../../../build/three.module.js';
|
||||
|
||||
import {
|
||||
XRHandPrimitiveModel
|
||||
} from './XRHandPrimitiveModel.js';
|
||||
|
||||
import {
|
||||
XRHandOculusMeshModel
|
||||
} from './XRHandOculusMeshModel.js';
|
||||
|
||||
function XRHandModel( controller ) {
|
||||
|
||||
Object3D.call( this );
|
||||
|
||||
this.controller = controller;
|
||||
this.motionController = null;
|
||||
this.envMap = null;
|
||||
|
||||
this.mesh = null;
|
||||
|
||||
}
|
||||
|
||||
XRHandModel.prototype = Object.assign( Object.create( Object3D.prototype ), {
|
||||
|
||||
constructor: XRHandModel,
|
||||
|
||||
updateMatrixWorld: function ( force ) {
|
||||
|
||||
Object3D.prototype.updateMatrixWorld.call( this, force );
|
||||
|
||||
if ( this.motionController ) {
|
||||
|
||||
this.motionController.updateMesh();
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
} );
|
||||
|
||||
|
||||
const XRHandModelFactory = ( function () {
|
||||
|
||||
function XRHandModelFactory() {
|
||||
|
||||
this.path = '';
|
||||
|
||||
}
|
||||
|
||||
XRHandModelFactory.prototype = {
|
||||
|
||||
constructor: XRHandModelFactory,
|
||||
|
||||
setPath: function ( path ) {
|
||||
|
||||
this.path = path;
|
||||
return this;
|
||||
|
||||
},
|
||||
|
||||
createHandModel: function ( controller, profile, options ) {
|
||||
|
||||
const handModel = new XRHandModel( controller );
|
||||
|
||||
controller.addEventListener( 'connected', ( event ) => {
|
||||
|
||||
const xrInputSource = event.data;
|
||||
|
||||
if ( xrInputSource.hand && ! handModel.motionController ) {
|
||||
|
||||
handModel.visible = true;
|
||||
handModel.xrInputSource = xrInputSource;
|
||||
|
||||
// @todo Detect profile if not provided
|
||||
if ( profile === undefined || profile === 'spheres' ) {
|
||||
|
||||
handModel.motionController = new XRHandPrimitiveModel( handModel, controller, this.path, xrInputSource.handedness, { primitive: 'sphere' } );
|
||||
|
||||
} else if ( profile === 'boxes' ) {
|
||||
|
||||
handModel.motionController = new XRHandPrimitiveModel( handModel, controller, this.path, xrInputSource.handedness, { primitive: 'box' } );
|
||||
|
||||
} else if ( profile === 'oculus' ) {
|
||||
|
||||
handModel.motionController = new XRHandOculusMeshModel( handModel, controller, this.path, xrInputSource.handedness, options );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} );
|
||||
|
||||
controller.addEventListener( 'disconnected', () => {
|
||||
|
||||
// handModel.motionController = null;
|
||||
// handModel.remove( scene );
|
||||
// scene = null;
|
||||
|
||||
} );
|
||||
|
||||
return handModel;
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
return XRHandModelFactory;
|
||||
|
||||
} )();
|
||||
|
||||
|
||||
export { XRHandModelFactory };
|
||||
@@ -0,0 +1,110 @@
|
||||
import { FBXLoader } from '../loaders/FBXLoader.js';
|
||||
|
||||
class XRHandOculusMeshModel {
|
||||
|
||||
constructor( handModel, controller, path, handedness, options ) {
|
||||
|
||||
this.controller = controller;
|
||||
this.handModel = handModel;
|
||||
|
||||
this.bones = [];
|
||||
const loader = new FBXLoader();
|
||||
const low = options && options.model === 'lowpoly' ? '_low' : '';
|
||||
|
||||
loader.setPath( path );
|
||||
loader.load( `OculusHand_${handedness === 'right' ? 'R' : 'L'}${low}.fbx`, object => {
|
||||
|
||||
this.handModel.add( object );
|
||||
// Hack because of the scale of the skinnedmesh
|
||||
object.scale.setScalar( 0.01 );
|
||||
|
||||
const mesh = object.getObjectByProperty( 'type', 'SkinnedMesh' );
|
||||
mesh.frustumCulled = false;
|
||||
mesh.castShadow = true;
|
||||
mesh.receiveShadow = true;
|
||||
|
||||
const bonesMapping = [
|
||||
'b_%_wrist', // XRHand.WRIST,
|
||||
|
||||
'b_%_thumb1', // XRHand.THUMB_METACARPAL,
|
||||
'b_%_thumb2', // XRHand.THUMB_PHALANX_PROXIMAL,
|
||||
'b_%_thumb3', // XRHand.THUMB_PHALANX_DISTAL,
|
||||
'b_%_thumb_null', // XRHand.THUMB_PHALANX_TIP,
|
||||
|
||||
null, //'b_%_index1', // XRHand.INDEX_METACARPAL,
|
||||
'b_%_index1', // XRHand.INDEX_PHALANX_PROXIMAL,
|
||||
'b_%_index2', // XRHand.INDEX_PHALANX_INTERMEDIATE,
|
||||
'b_%_index3', // XRHand.INDEX_PHALANX_DISTAL,
|
||||
'b_%_index_null', // XRHand.INDEX_PHALANX_TIP,
|
||||
|
||||
null, //'b_%_middle1', // XRHand.MIDDLE_METACARPAL,
|
||||
'b_%_middle1', // XRHand.MIDDLE_PHALANX_PROXIMAL,
|
||||
'b_%_middle2', // XRHand.MIDDLE_PHALANX_INTERMEDIATE,
|
||||
'b_%_middle3', // XRHand.MIDDLE_PHALANX_DISTAL,
|
||||
'b_%_middlenull', // XRHand.MIDDLE_PHALANX_TIP,
|
||||
|
||||
null, //'b_%_ring1', // XRHand.RING_METACARPAL,
|
||||
'b_%_ring1', // XRHand.RING_PHALANX_PROXIMAL,
|
||||
'b_%_ring2', // XRHand.RING_PHALANX_INTERMEDIATE,
|
||||
'b_%_ring3', // XRHand.RING_PHALANX_DISTAL,
|
||||
'b_%_ring_inull', // XRHand.RING_PHALANX_TIP,
|
||||
|
||||
'b_%_pinky0', // XRHand.LITTLE_METACARPAL,
|
||||
'b_%_pinky1', // XRHand.LITTLE_PHALANX_PROXIMAL,
|
||||
'b_%_pinky2', // XRHand.LITTLE_PHALANX_INTERMEDIATE,
|
||||
'b_%_pinky3', // XRHand.LITTLE_PHALANX_DISTAL,
|
||||
'b_%_pinkynull', // XRHand.LITTLE_PHALANX_TIP
|
||||
];
|
||||
bonesMapping.forEach( boneName => {
|
||||
|
||||
if ( boneName ) {
|
||||
|
||||
const bone = object.getObjectByName( boneName.replace( /%/g, handedness === 'right' ? 'r' : 'l' ) );
|
||||
this.bones.push( bone );
|
||||
|
||||
} else {
|
||||
|
||||
this.bones.push( null );
|
||||
|
||||
}
|
||||
|
||||
} );
|
||||
|
||||
} );
|
||||
|
||||
}
|
||||
|
||||
updateMesh() {
|
||||
|
||||
// XR Joints
|
||||
const XRJoints = this.controller.joints;
|
||||
for ( let i = 0; i < this.bones.length; i ++ ) {
|
||||
|
||||
const bone = this.bones[ i ];
|
||||
const XRJoint = XRJoints[ i ];
|
||||
|
||||
if ( XRJoint ) {
|
||||
|
||||
if ( XRJoint.visible ) {
|
||||
|
||||
const position = XRJoint.position;
|
||||
|
||||
if ( bone ) {
|
||||
|
||||
bone.position.copy( position.clone().multiplyScalar( 100 ) );
|
||||
bone.quaternion.copy( XRJoint.quaternion );
|
||||
// bone.scale.setScalar( XRJoint.jointRadius || defaultRadius );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export { XRHandOculusMeshModel };
|
||||
@@ -0,0 +1,87 @@
|
||||
import {
|
||||
SphereBufferGeometry,
|
||||
BoxBufferGeometry,
|
||||
MeshStandardMaterial,
|
||||
Mesh,
|
||||
Group
|
||||
} from '../../../build/three.module.js';
|
||||
|
||||
class XRHandPrimitiveModel {
|
||||
|
||||
constructor( handModel, controller, path, handedness, options ) {
|
||||
|
||||
this.controller = controller;
|
||||
this.handModel = handModel;
|
||||
|
||||
this.envMap = null;
|
||||
|
||||
this.handMesh = new Group();
|
||||
this.handModel.add( this.handMesh );
|
||||
|
||||
if ( window.XRHand ) {
|
||||
|
||||
let geometry;
|
||||
|
||||
if ( ! options || ! options.primitive || options.primitive === 'sphere' ) {
|
||||
|
||||
geometry = new SphereBufferGeometry( 1, 10, 10 );
|
||||
|
||||
} else if ( options.primitive === 'box' ) {
|
||||
|
||||
geometry = new BoxBufferGeometry( 1, 1, 1 );
|
||||
|
||||
}
|
||||
|
||||
const jointMaterial = new MeshStandardMaterial( { color: 0xffffff, roughness: 1, metalness: 0 } );
|
||||
const tipMaterial = new MeshStandardMaterial( { color: 0x999999, roughness: 1, metalness: 0 } );
|
||||
|
||||
const tipIndexes = [
|
||||
window.XRHand.THUMB_PHALANX_TIP,
|
||||
window.XRHand.INDEX_PHALANX_TIP,
|
||||
window.XRHand.MIDDLE_PHALANX_TIP,
|
||||
window.XRHand.RING_PHALANX_TIP,
|
||||
window.XRHand.LITTLE_PHALANX_TIP
|
||||
];
|
||||
for ( let i = 0; i <= window.XRHand.LITTLE_PHALANX_TIP; i ++ ) {
|
||||
|
||||
var cube = new Mesh( geometry, tipIndexes.indexOf( i ) !== - 1 ? tipMaterial : jointMaterial );
|
||||
cube.castShadow = true;
|
||||
cube.receiveShadow = true;
|
||||
this.handMesh.add( cube );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
updateMesh() {
|
||||
|
||||
const defaultRadius = 0.008;
|
||||
const objects = this.handMesh.children;
|
||||
|
||||
// XR Joints
|
||||
const XRJoints = this.controller.joints;
|
||||
|
||||
for ( let i = 0; i < objects.length; i ++ ) {
|
||||
|
||||
const jointMesh = objects[ i ];
|
||||
const XRJoint = XRJoints[ i ];
|
||||
|
||||
if ( XRJoint.visible ) {
|
||||
|
||||
jointMesh.position.copy( XRJoint.position );
|
||||
jointMesh.quaternion.copy( XRJoint.quaternion );
|
||||
jointMesh.scale.setScalar( XRJoint.jointRadius || defaultRadius );
|
||||
|
||||
}
|
||||
|
||||
jointMesh.visible = XRJoint.visible;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export { XRHandPrimitiveModel };
|
||||
Reference in New Issue
Block a user