add Potree (version 1.8)

This commit is contained in:
Tim Wundenberg
2021-08-04 21:30:59 +02:00
parent 04de194255
commit a90fcc336e
1693 changed files with 740830 additions and 0 deletions

View 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 };

View 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 };

View File

@@ -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 };

View File

@@ -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 };

View File

@@ -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 };

View File

@@ -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 };