This commit is contained in:
2026-05-11 22:01:36 +02:00
parent 84963aa166
commit cbe5695fac
420 changed files with 661974 additions and 3 deletions

21
libs/three.js/LICENSE Normal file
View File

@@ -0,0 +1,21 @@
The MIT License
Copyright © 2010-2019 three.js authors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

91
libs/three.js/README.md Normal file
View File

@@ -0,0 +1,91 @@
three.js
========
[![NPM package][npm]][npm-url]
[![Build Size][build-size]][build-size-url]
[![Build Status][build-status]][build-status-url]
[![Dependencies][dependencies]][dependencies-url]
[![Dev Dependencies][dev-dependencies]][dev-dependencies-url]
[![Language Grade][lgtm]][lgtm-url]
#### JavaScript 3D library ####
The aim of the project is to create an easy to use, lightweight, 3D library with a default WebGL renderer. The library also provides Canvas 2D, SVG and CSS3D renderers in the examples.
[Examples](http://threejs.org/examples/) —
[Documentation](http://threejs.org/docs/) —
[Wiki](https://github.com/mrdoob/three.js/wiki) —
[Migrating](https://github.com/mrdoob/three.js/wiki/Migration-Guide) —
[Questions](http://stackoverflow.com/questions/tagged/three.js) —
[Forum](https://discourse.threejs.org/) —
[Gitter](https://gitter.im/mrdoob/three.js) —
[Slack](https://join.slack.com/t/threejs/shared_invite/enQtMzYxMzczODM2OTgxLTQ1YmY4YTQxOTFjNDAzYmQ4NjU2YzRhNzliY2RiNDEyYjU2MjhhODgyYWQ5Y2MyZTU3MWNkOGVmOGRhOTQzYTk)
### Usage ###
Download the [minified library](http://threejs.org/build/three.min.js) and include it in your HTML, or install and import it as a [module](http://threejs.org/docs/#manual/introduction/Import-via-modules),
Alternatively, see [how to build the library yourself](https://github.com/mrdoob/three.js/wiki/Build-instructions).
```html
<script src="js/three.min.js"></script>
```
This code creates a scene, a camera, and a geometric cube, and it adds the cube to the scene. It then creates a `WebGL` renderer for the scene and camera, and it adds that viewport to the `document.body` element. Finally, it animates the cube within the scene for the camera.
```javascript
var camera, scene, renderer;
var geometry, material, mesh;
init();
animate();
function init() {
camera = new THREE.PerspectiveCamera( 70, window.innerWidth / window.innerHeight, 0.01, 10 );
camera.position.z = 1;
scene = new THREE.Scene();
geometry = new THREE.BoxGeometry( 0.2, 0.2, 0.2 );
material = new THREE.MeshNormalMaterial();
mesh = new THREE.Mesh( geometry, material );
scene.add( mesh );
renderer = new THREE.WebGLRenderer( { antialias: true } );
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
}
function animate() {
requestAnimationFrame( animate );
mesh.rotation.x += 0.01;
mesh.rotation.y += 0.02;
renderer.render( scene, camera );
}
```
If everything went well you should see [this](https://jsfiddle.net/f2Lommf5/).
### Change log ###
[Releases](https://github.com/mrdoob/three.js/releases)
[npm]: https://img.shields.io/npm/v/three.svg
[npm-url]: https://www.npmjs.com/package/three
[build-size]: https://badgen.net/bundlephobia/minzip/three
[build-size-url]: https://bundlephobia.com/result?p=three
[build-status]: https://travis-ci.org/mrdoob/three.js.svg?branch=dev
[build-status-url]: https://travis-ci.org/mrdoob/three.js
[dependencies]: https://img.shields.io/david/mrdoob/three.js.svg
[dependencies-url]: https://david-dm.org/mrdoob/three.js
[dev-dependencies]: https://img.shields.io/david/dev/mrdoob/three.js.svg
[dev-dependencies-url]: https://david-dm.org/mrdoob/three.js#info=devDependencies
[lgtm]: https://img.shields.io/lgtm/grade/javascript/g/mrdoob/three.js.svg?label=code%20quality
[lgtm-url]: https://lgtm.com/projects/g/mrdoob/three.js/

38325
libs/three.js/build/three.js Normal file

File diff suppressed because one or more lines are too long

2
libs/three.js/build/three.min.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,182 @@
// Adapted from three.js VRButton
class VRButton {
constructor(){
this.onStartListeners = [];
this.onEndListeners = [];
this.element = null;
}
onStart(callback){
this.onStartListeners.push(callback);
}
onEnd(callback){
this.onEndListeners.push(callback);
}
static async 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 = new VRButton();
const element = document.createElement( 'button' );
button.element = element;
function setEnter(){
button.element.innerHTML = `
<div style="font-size: 0.5em;">ENTER</div>
<div style="font-weight: bold;">VR</div>
`;
}
function setExit(){
button.element.innerHTML = `
<div style="font-size: 0.5em;">EXIT</div>
<div style="font-weight: bold;">VR</div>
`;
}
function showEnterVR( /*device*/ ) {
let currentSession = null;
function onSessionStarted( session ) {
session.addEventListener( 'end', onSessionEnded );
for(let listener of button.onStartListeners){
listener();
}
renderer.xr.setSession( session );
setExit();
currentSession = session;
}
function onSessionEnded( /*event*/ ) {
currentSession.removeEventListener( 'end', onSessionEnded );
for(let listener of button.onEndListeners){
listener();
}
setEnter();
currentSession = null;
}
//
button.element.style.display = '';
button.element.style.cursor = 'pointer';
setEnter();
button.element.onmouseenter = function () {
button.element.style.opacity = '1.0';
};
button.element.onmouseleave = function () {
button.element.style.opacity = '0.7';
};
button.element.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 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.7';
element.style.outline = 'none';
element.style.zIndex = '999';
}
if ( 'xr' in navigator ) {
button.element.id = 'VRButton';
button.element.style.display = 'none';
stylizeElement( button.element );
let supported = await navigator.xr.isSessionSupported( 'immersive-vr' );
if(supported){
showEnterVR();
return button;
}else{
return null;
}
} else {
if ( window.isSecureContext === false ) {
console.log("WEBXR NEEDS HTTPS");
} else {
console.log("WEBXR not available");
}
return null;
}
}
}
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,856 @@
/**
* @author WestLangley / http://github.com/WestLangley
*
*/
THREE.LineSegmentsGeometry = function () {
THREE.InstancedBufferGeometry.call( this );
this.type = 'LineSegmentsGeometry';
var positions = [ - 1, 2, 0, 1, 2, 0, - 1, 1, 0, 1, 1, 0, - 1, 0, 0, 1, 0, 0, - 1, - 1, 0, 1, - 1, 0 ];
var uvs = [ - 1, 2, 1, 2, - 1, 1, 1, 1, - 1, - 1, 1, - 1, - 1, - 2, 1, - 2 ];
var index = [ 0, 2, 1, 2, 3, 1, 2, 4, 3, 4, 5, 3, 4, 6, 5, 6, 7, 5 ];
this.setIndex( index );
this.setAttribute( 'position', new THREE.Float32BufferAttribute( positions, 3 ) );
this.setAttribute( 'uv', new THREE.Float32BufferAttribute( uvs, 2 ) );
};
THREE.LineSegmentsGeometry.prototype = Object.assign( Object.create( THREE.InstancedBufferGeometry.prototype ), {
constructor: THREE.LineSegmentsGeometry,
isLineSegmentsGeometry: true,
applyMatrix: function ( matrix ) {
var start = this.attributes.instanceStart;
var end = this.attributes.instanceEnd;
if ( start !== undefined ) {
matrix.applyToBufferAttribute( start );
matrix.applyToBufferAttribute( end );
start.data.needsUpdate = true;
}
if ( this.boundingBox !== null ) {
this.computeBoundingBox();
}
if ( this.boundingSphere !== null ) {
this.computeBoundingSphere();
}
return this;
},
setPositions: function ( array ) {
var lineSegments;
if ( array instanceof Float32Array ) {
lineSegments = array;
} else if ( Array.isArray( array ) ) {
lineSegments = new Float32Array( array );
}
var instanceBuffer = new THREE.InstancedInterleavedBuffer( lineSegments, 6, 1 ); // xyz, xyz
this.setAttribute( 'instanceStart', new THREE.InterleavedBufferAttribute( instanceBuffer, 3, 0 ) ); // xyz
this.setAttribute( 'instanceEnd', new THREE.InterleavedBufferAttribute( instanceBuffer, 3, 3 ) ); // xyz
//
this.computeBoundingBox();
this.computeBoundingSphere();
return this;
},
setColors: function ( array ) {
var colors;
if ( array instanceof Float32Array ) {
colors = array;
} else if ( Array.isArray( array ) ) {
colors = new Float32Array( array );
}
var instanceColorBuffer = new THREE.InstancedInterleavedBuffer( colors, 6, 1 ); // rgb, rgb
this.setAttribute( 'instanceColorStart', new THREE.InterleavedBufferAttribute( instanceColorBuffer, 3, 0 ) ); // rgb
this.setAttribute( 'instanceColorEnd', new THREE.InterleavedBufferAttribute( instanceColorBuffer, 3, 3 ) ); // rgb
return this;
},
fromWireframeGeometry: function ( geometry ) {
this.setPositions( geometry.attributes.position.array );
return this;
},
fromEdgesGeometry: function ( geometry ) {
this.setPositions( geometry.attributes.position.array );
return this;
},
fromMesh: function ( mesh ) {
this.fromWireframeGeometry( new THREE.WireframeGeometry( mesh.geometry ) );
// set colors, maybe
return this;
},
fromLineSegements: function ( lineSegments ) {
var geometry = lineSegments.geometry;
if ( geometry.isGeometry ) {
this.setPositions( geometry.vertices );
} else if ( geometry.isBufferGeometry ) {
this.setPositions( geometry.position.array ); // assumes non-indexed
}
// set colors, maybe
return this;
},
computeBoundingBox: function () {
var box = new THREE.Box3();
return function computeBoundingBox() {
if ( this.boundingBox === null ) {
this.boundingBox = new THREE.Box3();
}
var start = this.attributes.instanceStart;
var end = this.attributes.instanceEnd;
if ( start !== undefined && end !== undefined ) {
this.boundingBox.setFromBufferAttribute( start );
box.setFromBufferAttribute( end );
this.boundingBox.union( box );
}
};
}(),
computeBoundingSphere: function () {
var vector = new THREE.Vector3();
return function computeBoundingSphere() {
if ( this.boundingSphere === null ) {
this.boundingSphere = new THREE.Sphere();
}
if ( this.boundingBox === null ) {
this.computeBoundingBox();
}
var start = this.attributes.instanceStart;
var end = this.attributes.instanceEnd;
if ( start !== undefined && end !== undefined ) {
var center = this.boundingSphere.center;
this.boundingBox.getCenter( center );
var maxRadiusSq = 0;
for ( var i = 0, il = start.count; i < il; i ++ ) {
vector.fromBufferAttribute( start, i );
maxRadiusSq = Math.max( maxRadiusSq, center.distanceToSquared( vector ) );
vector.fromBufferAttribute( end, i );
maxRadiusSq = Math.max( maxRadiusSq, center.distanceToSquared( vector ) );
}
this.boundingSphere.radius = Math.sqrt( maxRadiusSq );
if ( isNaN( this.boundingSphere.radius ) ) {
console.error( 'THREE.LineSegmentsGeometry.computeBoundingSphere(): Computed radius is NaN. The instanced position data is likely to have NaN values.', this );
}
}
};
}(),
toJSON: function () {
// todo
},
clone: function () {
// todo
},
copy: function ( /* source */ ) {
// todo
return this;
}
} );
/**
* @author WestLangley / http://github.com/WestLangley
*
*/
THREE.LineSegments2 = function ( geometry, material ) {
THREE.Mesh.call( this );
this.type = 'LineSegments2';
this.geometry = geometry !== undefined ? geometry : new THREE.LineSegmentsGeometry();
this.material = material !== undefined ? material : new THREE.LineMaterial( { color: Math.random() * 0xffffff } );
};
THREE.LineSegments2.prototype = Object.assign( Object.create( THREE.Mesh.prototype ), {
constructor: THREE.LineSegments2,
isLineSegments2: true,
computeLineDistances: ( function () { // for backwards-compatability, but could be a method of LineSegmentsGeometry...
var start = new THREE.Vector3();
var end = new THREE.Vector3();
return function computeLineDistances() {
var geometry = this.geometry;
var instanceStart = geometry.attributes.instanceStart;
var instanceEnd = geometry.attributes.instanceEnd;
var lineDistances = new Float32Array( 2 * instanceStart.data.count );
for ( var i = 0, j = 0, l = instanceStart.data.count; i < l; i ++, j += 2 ) {
start.fromBufferAttribute( instanceStart, i );
end.fromBufferAttribute( instanceEnd, i );
lineDistances[ j ] = ( j === 0 ) ? 0 : lineDistances[ j - 1 ];
lineDistances[ j + 1 ] = lineDistances[ j ] + start.distanceTo( end );
}
var instanceDistanceBuffer = new THREE.InstancedInterleavedBuffer( lineDistances, 2, 1 ); // d0, d1
geometry.setAttribute( 'instanceDistanceStart', new THREE.InterleavedBufferAttribute( instanceDistanceBuffer, 1, 0 ) ); // d0
geometry.setAttribute( 'instanceDistanceEnd', new THREE.InterleavedBufferAttribute( instanceDistanceBuffer, 1, 1 ) ); // d1
return this;
};
}() ),
copy: function ( /* source */ ) {
// todo
return this;
}
} );
/**
* @author WestLangley / http://github.com/WestLangley
*
* parameters = {
* color: <hex>,
* linewidth: <float>,
* dashed: <boolean>,
* dashScale: <float>,
* dashSize: <float>,
* gapSize: <float>,
* resolution: <Vector2>, // to be set by renderer
* }
*/
THREE.UniformsLib.line = {
linewidth: { value: 1 },
resolution: { value: new THREE.Vector2( 1, 1 ) },
dashScale: { value: 1 },
dashSize: { value: 1 },
gapSize: { value: 1 } // todo FIX - maybe change to totalSize
};
THREE.ShaderLib[ 'line' ] = {
uniforms: THREE.UniformsUtils.merge( [
THREE.UniformsLib.common,
THREE.UniformsLib.fog,
THREE.UniformsLib.line
] ),
vertexShader:
`
#include <common>
#include <color_pars_vertex>
#include <fog_pars_vertex>
#include <logdepthbuf_pars_vertex>
#include <clipping_planes_pars_vertex>
uniform float linewidth;
uniform vec2 resolution;
attribute vec3 instanceStart;
attribute vec3 instanceEnd;
attribute vec3 instanceColorStart;
attribute vec3 instanceColorEnd;
varying vec2 vUv;
#ifdef USE_DASH
uniform float dashScale;
attribute float instanceDistanceStart;
attribute float instanceDistanceEnd;
varying float vLineDistance;
#endif
void trimSegment( const in vec4 start, inout vec4 end ) {
// trim end segment so it terminates between the camera plane and the near plane
// conservative estimate of the near plane
float a = projectionMatrix[ 2 ][ 2 ]; // 3nd entry in 3th column
float b = projectionMatrix[ 3 ][ 2 ]; // 3nd entry in 4th column
float nearEstimate = - 0.5 * b / a;
float alpha = ( nearEstimate - start.z ) / ( end.z - start.z );
end.xyz = mix( start.xyz, end.xyz, alpha );
}
void main() {
#ifdef USE_COLOR
vColor.xyz = ( position.y < 0.5 ) ? instanceColorStart : instanceColorEnd;
#endif
#ifdef USE_DASH
vLineDistance = ( position.y < 0.5 ) ? dashScale * instanceDistanceStart : dashScale * instanceDistanceEnd;
#endif
float aspect = resolution.x / resolution.y;
vUv = uv;
// camera space
vec4 start = modelViewMatrix * vec4( instanceStart, 1.0 );
vec4 end = modelViewMatrix * vec4( instanceEnd, 1.0 );
// special case for perspective projection, and segments that terminate either in, or behind, the camera plane
// clearly the gpu firmware has a way of addressing this issue when projecting into ndc space
// but we need to perform ndc-space calculations in the shader, so we must address this issue directly
// perhaps there is a more elegant solution -- WestLangley
bool perspective = ( projectionMatrix[ 2 ][ 3 ] == - 1.0 ); // 4th entry in the 3rd column
if ( perspective ) {
if ( start.z < 0.0 && end.z >= 0.0 ) {
trimSegment( start, end );
} else if ( end.z < 0.0 && start.z >= 0.0 ) {
trimSegment( end, start );
}
}
// clip space
vec4 clipStart = projectionMatrix * start;
vec4 clipEnd = projectionMatrix * end;
// ndc space
vec2 ndcStart = clipStart.xy / clipStart.w;
vec2 ndcEnd = clipEnd.xy / clipEnd.w;
// direction
vec2 dir = ndcEnd - ndcStart;
// account for clip-space aspect ratio
dir.x *= aspect;
dir = normalize( dir );
// perpendicular to dir
vec2 offset = vec2( dir.y, - dir.x );
// undo aspect ratio adjustment
dir.x /= aspect;
offset.x /= aspect;
// sign flip
if ( position.x < 0.0 ) offset *= - 1.0;
// endcaps
if ( position.y < 0.0 ) {
offset += - dir;
} else if ( position.y > 1.0 ) {
offset += dir;
}
// adjust for linewidth
offset *= linewidth;
// adjust for clip-space to screen-space conversion // maybe resolution should be based on viewport ...
offset /= resolution.y;
// select end
vec4 clip = ( position.y < 0.5 ) ? clipStart : clipEnd;
// back to clip space
offset *= clip.w;
clip.xy += offset;
gl_Position = clip;
vec4 mvPosition = ( position.y < 0.5 ) ? start : end; // this is an approximation
#include <logdepthbuf_vertex>
#include <clipping_planes_vertex>
#include <fog_vertex>
}
`,
fragmentShader:
`
uniform vec3 diffuse;
uniform float opacity;
#ifdef USE_DASH
uniform float dashSize;
uniform float gapSize;
#endif
varying float vLineDistance;
#include <common>
#include <color_pars_fragment>
#include <fog_pars_fragment>
#include <logdepthbuf_pars_fragment>
#include <clipping_planes_pars_fragment>
varying vec2 vUv;
void main() {
#include <clipping_planes_fragment>
#ifdef USE_DASH
if ( vUv.y < - 1.0 || vUv.y > 1.0 ) discard; // discard endcaps
if ( mod( vLineDistance, dashSize + gapSize ) > dashSize ) discard; // todo - FIX
#endif
if ( abs( vUv.y ) > 1.0 ) {
float a = vUv.x;
float b = ( vUv.y > 0.0 ) ? vUv.y - 1.0 : vUv.y + 1.0;
float len2 = a * a + b * b;
if ( len2 > 1.0 ) discard;
}
vec4 diffuseColor = vec4( diffuse, opacity );
#include <logdepthbuf_fragment>
#include <color_fragment>
gl_FragColor = vec4( diffuseColor.rgb, diffuseColor.a );
#include <premultiplied_alpha_fragment>
#include <tonemapping_fragment>
#include <encodings_fragment>
#include <fog_fragment>
}
`
};
THREE.LineMaterial = function ( parameters ) {
THREE.ShaderMaterial.call( this, {
type: 'LineMaterial',
uniforms: THREE.UniformsUtils.clone( THREE.ShaderLib[ 'line' ].uniforms ),
vertexShader: THREE.ShaderLib[ 'line' ].vertexShader,
fragmentShader: THREE.ShaderLib[ 'line' ].fragmentShader
} );
this.dashed = false;
Object.defineProperties( this, {
color: {
enumerable: true,
get: function () {
return this.uniforms.diffuse.value;
},
set: function ( value ) {
this.uniforms.diffuse.value = value;
}
},
linewidth: {
enumerable: true,
get: function () {
return this.uniforms.linewidth.value;
},
set: function ( value ) {
this.uniforms.linewidth.value = value;
}
},
dashScale: {
enumerable: true,
get: function () {
return this.uniforms.dashScale.value;
},
set: function ( value ) {
this.uniforms.dashScale.value = value;
}
},
dashSize: {
enumerable: true,
get: function () {
return this.uniforms.dashSize.value;
},
set: function ( value ) {
this.uniforms.dashSize.value = value;
}
},
gapSize: {
enumerable: true,
get: function () {
return this.uniforms.gapSize.value;
},
set: function ( value ) {
this.uniforms.gapSize.value = value;
}
},
resolution: {
enumerable: true,
get: function () {
return this.uniforms.resolution.value;
},
set: function ( value ) {
this.uniforms.resolution.value.copy( value );
}
}
} );
this.setValues( parameters );
};
THREE.LineMaterial.prototype = Object.create( THREE.ShaderMaterial.prototype );
THREE.LineMaterial.prototype.constructor = THREE.LineMaterial;
THREE.LineMaterial.prototype.isLineMaterial = true;
THREE.LineMaterial.prototype.copy = function ( source ) {
THREE.ShaderMaterial.prototype.copy.call( this, source );
this.color.copy( source.color );
this.linewidth = source.linewidth;
this.resolution = source.resolution;
// todo
return this;
};
/**
* @author WestLangley / http://github.com/WestLangley
*
*/
THREE.LineGeometry = function () {
THREE.LineSegmentsGeometry.call( this );
this.type = 'LineGeometry';
};
THREE.LineGeometry.prototype = Object.assign( Object.create( THREE.LineSegmentsGeometry.prototype ), {
constructor: THREE.LineGeometry,
isLineGeometry: true,
setPositions: function ( array ) {
// converts [ x1, y1, z1, x2, y2, z2, ... ] to pairs format
var length = array.length - 3;
var points = new Float32Array( 2 * length );
for ( var i = 0; i < length; i += 3 ) {
points[ 2 * i ] = array[ i ];
points[ 2 * i + 1 ] = array[ i + 1 ];
points[ 2 * i + 2 ] = array[ i + 2 ];
points[ 2 * i + 3 ] = array[ i + 3 ];
points[ 2 * i + 4 ] = array[ i + 4 ];
points[ 2 * i + 5 ] = array[ i + 5 ];
}
THREE.LineSegmentsGeometry.prototype.setPositions.call( this, points );
return this;
},
setColors: function ( array ) {
// converts [ r1, g1, b1, r2, g2, b2, ... ] to pairs format
var length = array.length - 3;
var colors = new Float32Array( 2 * length );
for ( var i = 0; i < length; i += 3 ) {
colors[ 2 * i ] = array[ i ];
colors[ 2 * i + 1 ] = array[ i + 1 ];
colors[ 2 * i + 2 ] = array[ i + 2 ];
colors[ 2 * i + 3 ] = array[ i + 3 ];
colors[ 2 * i + 4 ] = array[ i + 4 ];
colors[ 2 * i + 5 ] = array[ i + 5 ];
}
THREE.LineSegmentsGeometry.prototype.setColors.call( this, colors );
return this;
},
fromLine: function ( line ) {
var geometry = line.geometry;
if ( geometry.isGeometry ) {
this.setPositions( geometry.vertices );
} else if ( geometry.isBufferGeometry ) {
this.setPositions( geometry.position.array ); // assumes non-indexed
}
// set colors, maybe
return this;
},
copy: function ( /* source */ ) {
// todo
return this;
}
} );
/**
* @author WestLangley / http://github.com/WestLangley
*
*/
THREE.Line2 = function ( geometry, material ) {
THREE.LineSegments2.call( this );
this.type = 'Line2';
this.geometry = geometry !== undefined ? geometry : new THREE.LineGeometry();
this.material = material !== undefined ? material : new THREE.LineMaterial( { color: Math.random() * 0xffffff } );
};
THREE.Line2.prototype = Object.assign( Object.create( THREE.LineSegments2.prototype ), {
constructor: THREE.Line2,
isLine2: true,
copy: function ( /* source */ ) {
// todo
return this;
}
} );

View File

@@ -0,0 +1,31 @@
/**
* @author WestLangley / http://github.com/WestLangley
*
*/
THREE.Line2 = function ( geometry, material ) {
THREE.LineSegments2.call( this );
this.type = 'Line2';
this.geometry = geometry !== undefined ? geometry : new THREE.LineGeometry();
this.material = material !== undefined ? material : new THREE.LineMaterial( { color: Math.random() * 0xffffff } );
};
THREE.Line2.prototype = Object.assign( Object.create( THREE.LineSegments2.prototype ), {
constructor: THREE.Line2,
isLine2: true,
copy: function ( /* source */ ) {
// todo
return this;
}
} );

View File

@@ -0,0 +1,98 @@
/**
* @author WestLangley / http://github.com/WestLangley
*
*/
THREE.LineGeometry = function () {
THREE.LineSegmentsGeometry.call( this );
this.type = 'LineGeometry';
};
THREE.LineGeometry.prototype = Object.assign( Object.create( THREE.LineSegmentsGeometry.prototype ), {
constructor: THREE.LineGeometry,
isLineGeometry: true,
setPositions: function ( array ) {
// converts [ x1, y1, z1, x2, y2, z2, ... ] to pairs format
var length = array.length - 3;
var points = new Float32Array( 2 * length );
for ( var i = 0; i < length; i += 3 ) {
points[ 2 * i ] = array[ i ];
points[ 2 * i + 1 ] = array[ i + 1 ];
points[ 2 * i + 2 ] = array[ i + 2 ];
points[ 2 * i + 3 ] = array[ i + 3 ];
points[ 2 * i + 4 ] = array[ i + 4 ];
points[ 2 * i + 5 ] = array[ i + 5 ];
}
THREE.LineSegmentsGeometry.prototype.setPositions.call( this, points );
return this;
},
setColors: function ( array ) {
// converts [ r1, g1, b1, r2, g2, b2, ... ] to pairs format
var length = array.length - 3;
var colors = new Float32Array( 2 * length );
for ( var i = 0; i < length; i += 3 ) {
colors[ 2 * i ] = array[ i ];
colors[ 2 * i + 1 ] = array[ i + 1 ];
colors[ 2 * i + 2 ] = array[ i + 2 ];
colors[ 2 * i + 3 ] = array[ i + 3 ];
colors[ 2 * i + 4 ] = array[ i + 4 ];
colors[ 2 * i + 5 ] = array[ i + 5 ];
}
THREE.LineSegmentsGeometry.prototype.setColors.call( this, colors );
return this;
},
fromLine: function ( line ) {
var geometry = line.geometry;
if ( geometry.isGeometry ) {
this.setPositions( geometry.vertices );
} else if ( geometry.isBufferGeometry ) {
this.setPositions( geometry.position.array ); // assumes non-indexed
}
// set colors, maybe
return this;
},
copy: function ( /* source */ ) {
// todo
return this;
}
} );

View File

@@ -0,0 +1,391 @@
/**
* @author WestLangley / http://github.com/WestLangley
*
* parameters = {
* color: <hex>,
* linewidth: <float>,
* dashed: <boolean>,
* dashScale: <float>,
* dashSize: <float>,
* gapSize: <float>,
* resolution: <Vector2>, // to be set by renderer
* }
*/
THREE.UniformsLib.line = {
linewidth: { value: 1 },
resolution: { value: new THREE.Vector2( 1, 1 ) },
dashScale: { value: 1 },
dashSize: { value: 1 },
gapSize: { value: 1 } // todo FIX - maybe change to totalSize
};
THREE.ShaderLib[ 'line' ] = {
uniforms: THREE.UniformsUtils.merge( [
THREE.UniformsLib.common,
THREE.UniformsLib.fog,
THREE.UniformsLib.line
] ),
vertexShader:
`
#include <common>
#include <color_pars_vertex>
#include <fog_pars_vertex>
#include <logdepthbuf_pars_vertex>
#include <clipping_planes_pars_vertex>
uniform float linewidth;
uniform vec2 resolution;
attribute vec3 instanceStart;
attribute vec3 instanceEnd;
attribute vec3 instanceColorStart;
attribute vec3 instanceColorEnd;
varying vec2 vUv;
#ifdef USE_DASH
uniform float dashScale;
attribute float instanceDistanceStart;
attribute float instanceDistanceEnd;
varying float vLineDistance;
#endif
void trimSegment( const in vec4 start, inout vec4 end ) {
// trim end segment so it terminates between the camera plane and the near plane
// conservative estimate of the near plane
float a = projectionMatrix[ 2 ][ 2 ]; // 3nd entry in 3th column
float b = projectionMatrix[ 3 ][ 2 ]; // 3nd entry in 4th column
float nearEstimate = - 0.5 * b / a;
float alpha = ( nearEstimate - start.z ) / ( end.z - start.z );
end.xyz = mix( start.xyz, end.xyz, alpha );
}
void main() {
#ifdef USE_COLOR
vColor.xyz = ( position.y < 0.5 ) ? instanceColorStart : instanceColorEnd;
#endif
#ifdef USE_DASH
vLineDistance = ( position.y < 0.5 ) ? dashScale * instanceDistanceStart : dashScale * instanceDistanceEnd;
#endif
float aspect = resolution.x / resolution.y;
vUv = uv;
// camera space
vec4 start = modelViewMatrix * vec4( instanceStart, 1.0 );
vec4 end = modelViewMatrix * vec4( instanceEnd, 1.0 );
// special case for perspective projection, and segments that terminate either in, or behind, the camera plane
// clearly the gpu firmware has a way of addressing this issue when projecting into ndc space
// but we need to perform ndc-space calculations in the shader, so we must address this issue directly
// perhaps there is a more elegant solution -- WestLangley
bool perspective = ( projectionMatrix[ 2 ][ 3 ] == - 1.0 ); // 4th entry in the 3rd column
if ( perspective ) {
if ( start.z < 0.0 && end.z >= 0.0 ) {
trimSegment( start, end );
} else if ( end.z < 0.0 && start.z >= 0.0 ) {
trimSegment( end, start );
}
}
// clip space
vec4 clipStart = projectionMatrix * start;
vec4 clipEnd = projectionMatrix * end;
// ndc space
vec2 ndcStart = clipStart.xy / clipStart.w;
vec2 ndcEnd = clipEnd.xy / clipEnd.w;
// direction
vec2 dir = ndcEnd - ndcStart;
// account for clip-space aspect ratio
dir.x *= aspect;
dir = normalize( dir );
// perpendicular to dir
vec2 offset = vec2( dir.y, - dir.x );
// undo aspect ratio adjustment
dir.x /= aspect;
offset.x /= aspect;
// sign flip
if ( position.x < 0.0 ) offset *= - 1.0;
// endcaps
if ( position.y < 0.0 ) {
offset += - dir;
} else if ( position.y > 1.0 ) {
offset += dir;
}
// adjust for linewidth
offset *= linewidth;
// adjust for clip-space to screen-space conversion // maybe resolution should be based on viewport ...
offset /= resolution.y;
// select end
vec4 clip = ( position.y < 0.5 ) ? clipStart : clipEnd;
// back to clip space
offset *= clip.w;
clip.xy += offset;
gl_Position = clip;
vec4 mvPosition = ( position.y < 0.5 ) ? start : end; // this is an approximation
#include <logdepthbuf_vertex>
#include <clipping_planes_vertex>
#include <fog_vertex>
}
`,
fragmentShader:
`
uniform vec3 diffuse;
uniform float opacity;
#ifdef USE_DASH
uniform float dashSize;
uniform float gapSize;
#endif
varying float vLineDistance;
#include <common>
#include <color_pars_fragment>
#include <fog_pars_fragment>
#include <logdepthbuf_pars_fragment>
#include <clipping_planes_pars_fragment>
varying vec2 vUv;
void main() {
#include <clipping_planes_fragment>
#ifdef USE_DASH
if ( vUv.y < - 1.0 || vUv.y > 1.0 ) discard; // discard endcaps
if ( mod( vLineDistance, dashSize + gapSize ) > dashSize ) discard; // todo - FIX
#endif
if ( abs( vUv.y ) > 1.0 ) {
float a = vUv.x;
float b = ( vUv.y > 0.0 ) ? vUv.y - 1.0 : vUv.y + 1.0;
float len2 = a * a + b * b;
if ( len2 > 1.0 ) discard;
}
vec4 diffuseColor = vec4( diffuse, opacity );
#include <logdepthbuf_fragment>
#include <color_fragment>
gl_FragColor = vec4( diffuseColor.rgb, diffuseColor.a );
#include <premultiplied_alpha_fragment>
#include <tonemapping_fragment>
#include <encodings_fragment>
#include <fog_fragment>
}
`
};
THREE.LineMaterial = function ( parameters ) {
THREE.ShaderMaterial.call( this, {
type: 'LineMaterial',
uniforms: THREE.UniformsUtils.clone( THREE.ShaderLib[ 'line' ].uniforms ),
vertexShader: THREE.ShaderLib[ 'line' ].vertexShader,
fragmentShader: THREE.ShaderLib[ 'line' ].fragmentShader
} );
this.dashed = false;
Object.defineProperties( this, {
color: {
enumerable: true,
get: function () {
return this.uniforms.diffuse.value;
},
set: function ( value ) {
this.uniforms.diffuse.value = value;
}
},
linewidth: {
enumerable: true,
get: function () {
return this.uniforms.linewidth.value;
},
set: function ( value ) {
this.uniforms.linewidth.value = value;
}
},
dashScale: {
enumerable: true,
get: function () {
return this.uniforms.dashScale.value;
},
set: function ( value ) {
this.uniforms.dashScale.value = value;
}
},
dashSize: {
enumerable: true,
get: function () {
return this.uniforms.dashSize.value;
},
set: function ( value ) {
this.uniforms.dashSize.value = value;
}
},
gapSize: {
enumerable: true,
get: function () {
return this.uniforms.gapSize.value;
},
set: function ( value ) {
this.uniforms.gapSize.value = value;
}
},
resolution: {
enumerable: true,
get: function () {
return this.uniforms.resolution.value;
},
set: function ( value ) {
this.uniforms.resolution.value.copy( value );
}
}
} );
this.setValues( parameters );
};
THREE.LineMaterial.prototype = Object.create( THREE.ShaderMaterial.prototype );
THREE.LineMaterial.prototype.constructor = THREE.LineMaterial;
THREE.LineMaterial.prototype.isLineMaterial = true;
THREE.LineMaterial.prototype.copy = function ( source ) {
THREE.ShaderMaterial.prototype.copy.call( this, source );
this.color.copy( source.color );
this.linewidth = source.linewidth;
this.resolution = source.resolution;
// todo
return this;
};

View File

@@ -0,0 +1,65 @@
/**
* @author WestLangley / http://github.com/WestLangley
*
*/
THREE.LineSegments2 = function ( geometry, material ) {
THREE.Mesh.call( this );
this.type = 'LineSegments2';
this.geometry = geometry !== undefined ? geometry : new THREE.LineSegmentsGeometry();
this.material = material !== undefined ? material : new THREE.LineMaterial( { color: Math.random() * 0xffffff } );
};
THREE.LineSegments2.prototype = Object.assign( Object.create( THREE.Mesh.prototype ), {
constructor: THREE.LineSegments2,
isLineSegments2: true,
computeLineDistances: ( function () { // for backwards-compatability, but could be a method of LineSegmentsGeometry...
var start = new THREE.Vector3();
var end = new THREE.Vector3();
return function computeLineDistances() {
var geometry = this.geometry;
var instanceStart = geometry.attributes.instanceStart;
var instanceEnd = geometry.attributes.instanceEnd;
var lineDistances = new Float32Array( 2 * instanceStart.data.count );
for ( var i = 0, j = 0, l = instanceStart.data.count; i < l; i ++, j += 2 ) {
start.fromBufferAttribute( instanceStart, i );
end.fromBufferAttribute( instanceEnd, i );
lineDistances[ j ] = ( j === 0 ) ? 0 : lineDistances[ j - 1 ];
lineDistances[ j + 1 ] = lineDistances[ j ] + start.distanceTo( end );
}
var instanceDistanceBuffer = new THREE.InstancedInterleavedBuffer( lineDistances, 2, 1 ); // d0, d1
geometry.setAttribute( 'instanceDistanceStart', new THREE.InterleavedBufferAttribute( instanceDistanceBuffer, 1, 0 ) ); // d0
geometry.setAttribute( 'instanceDistanceEnd', new THREE.InterleavedBufferAttribute( instanceDistanceBuffer, 1, 1 ) ); // d1
return this;
};
}() ),
copy: function ( /* source */ ) {
// todo
return this;
}
} );

View File

@@ -0,0 +1,258 @@
/**
* @author WestLangley / http://github.com/WestLangley
*
*/
THREE.LineSegmentsGeometry = function () {
THREE.InstancedBufferGeometry.call( this );
this.type = 'LineSegmentsGeometry';
var positions = [ - 1, 2, 0, 1, 2, 0, - 1, 1, 0, 1, 1, 0, - 1, 0, 0, 1, 0, 0, - 1, - 1, 0, 1, - 1, 0 ];
var uvs = [ - 1, 2, 1, 2, - 1, 1, 1, 1, - 1, - 1, 1, - 1, - 1, - 2, 1, - 2 ];
var index = [ 0, 2, 1, 2, 3, 1, 2, 4, 3, 4, 5, 3, 4, 6, 5, 6, 7, 5 ];
this.setIndex( index );
this.setAttribute( 'position', new THREE.Float32BufferAttribute( positions, 3 ) );
this.setAttribute( 'uv', new THREE.Float32BufferAttribute( uvs, 2 ) );
};
THREE.LineSegmentsGeometry.prototype = Object.assign( Object.create( THREE.InstancedBufferGeometry.prototype ), {
constructor: THREE.LineSegmentsGeometry,
isLineSegmentsGeometry: true,
applyMatrix: function ( matrix ) {
var start = this.attributes.instanceStart;
var end = this.attributes.instanceEnd;
if ( start !== undefined ) {
matrix.applyToBufferAttribute( start );
matrix.applyToBufferAttribute( end );
start.data.needsUpdate = true;
}
if ( this.boundingBox !== null ) {
this.computeBoundingBox();
}
if ( this.boundingSphere !== null ) {
this.computeBoundingSphere();
}
return this;
},
setPositions: function ( array ) {
var lineSegments;
if ( array instanceof Float32Array ) {
lineSegments = array;
} else if ( Array.isArray( array ) ) {
lineSegments = new Float32Array( array );
}
var instanceBuffer = new THREE.InstancedInterleavedBuffer( lineSegments, 6, 1 ); // xyz, xyz
this.setAttribute( 'instanceStart', new THREE.InterleavedBufferAttribute( instanceBuffer, 3, 0 ) ); // xyz
this.setAttribute( 'instanceEnd', new THREE.InterleavedBufferAttribute( instanceBuffer, 3, 3 ) ); // xyz
//
this.computeBoundingBox();
this.computeBoundingSphere();
return this;
},
setColors: function ( array ) {
var colors;
if ( array instanceof Float32Array ) {
colors = array;
} else if ( Array.isArray( array ) ) {
colors = new Float32Array( array );
}
var instanceColorBuffer = new THREE.InstancedInterleavedBuffer( colors, 6, 1 ); // rgb, rgb
this.setAttribute( 'instanceColorStart', new THREE.InterleavedBufferAttribute( instanceColorBuffer, 3, 0 ) ); // rgb
this.setAttribute( 'instanceColorEnd', new THREE.InterleavedBufferAttribute( instanceColorBuffer, 3, 3 ) ); // rgb
return this;
},
fromWireframeGeometry: function ( geometry ) {
this.setPositions( geometry.attributes.position.array );
return this;
},
fromEdgesGeometry: function ( geometry ) {
this.setPositions( geometry.attributes.position.array );
return this;
},
fromMesh: function ( mesh ) {
this.fromWireframeGeometry( new THREE.WireframeGeometry( mesh.geometry ) );
// set colors, maybe
return this;
},
fromLineSegements: function ( lineSegments ) {
var geometry = lineSegments.geometry;
if ( geometry.isGeometry ) {
this.setPositions( geometry.vertices );
} else if ( geometry.isBufferGeometry ) {
this.setPositions( geometry.position.array ); // assumes non-indexed
}
// set colors, maybe
return this;
},
computeBoundingBox: function () {
var box = new THREE.Box3();
return function computeBoundingBox() {
if ( this.boundingBox === null ) {
this.boundingBox = new THREE.Box3();
}
var start = this.attributes.instanceStart;
var end = this.attributes.instanceEnd;
if ( start !== undefined && end !== undefined ) {
this.boundingBox.setFromBufferAttribute( start );
box.setFromBufferAttribute( end );
this.boundingBox.union( box );
}
};
}(),
computeBoundingSphere: function () {
var vector = new THREE.Vector3();
return function computeBoundingSphere() {
if ( this.boundingSphere === null ) {
this.boundingSphere = new THREE.Sphere();
}
if ( this.boundingBox === null ) {
this.computeBoundingBox();
}
var start = this.attributes.instanceStart;
var end = this.attributes.instanceEnd;
if ( start !== undefined && end !== undefined ) {
var center = this.boundingSphere.center;
this.boundingBox.getCenter( center );
var maxRadiusSq = 0;
for ( var i = 0, il = start.count; i < il; i ++ ) {
vector.fromBufferAttribute( start, i );
maxRadiusSq = Math.max( maxRadiusSq, center.distanceToSquared( vector ) );
vector.fromBufferAttribute( end, i );
maxRadiusSq = Math.max( maxRadiusSq, center.distanceToSquared( vector ) );
}
this.boundingSphere.radius = Math.sqrt( maxRadiusSq );
if ( isNaN( this.boundingSphere.radius ) ) {
console.error( 'THREE.LineSegmentsGeometry.computeBoundingSphere(): Computed radius is NaN. The instanced position data is likely to have NaN values.', this );
}
}
};
}(),
toJSON: function () {
// todo
},
clone: function () {
// todo
},
copy: function ( /* source */ ) {
// todo
return this;
}
} );

View File

@@ -0,0 +1,65 @@
/**
* @author WestLangley / http://github.com/WestLangley
*
*/
THREE.Wireframe = function ( geometry, material ) {
THREE.Mesh.call( this );
this.type = 'Wireframe';
this.geometry = geometry !== undefined ? geometry : new THREE.LineSegmentsGeometry();
this.material = material !== undefined ? material : new THREE.LineMaterial( { color: Math.random() * 0xffffff } );
};
THREE.Wireframe.prototype = Object.assign( Object.create( THREE.Mesh.prototype ), {
constructor: THREE.Wireframe,
isWireframe: true,
computeLineDistances: ( function () { // for backwards-compatability, but could be a method of LineSegmentsGeometry...
var start = new THREE.Vector3();
var end = new THREE.Vector3();
return function computeLineDistances() {
var geometry = this.geometry;
var instanceStart = geometry.attributes.instanceStart;
var instanceEnd = geometry.attributes.instanceEnd;
var lineDistances = new Float32Array( 2 * instanceStart.data.count );
for ( var i = 0, j = 0, l = instanceStart.data.count; i < l; i ++, j += 2 ) {
start.fromBufferAttribute( instanceStart, i );
end.fromBufferAttribute( instanceEnd, i );
lineDistances[ j ] = ( j === 0 ) ? 0 : lineDistances[ j - 1 ];
lineDistances[ j + 1 ] = lineDistances[ j ] + start.distanceTo( end );
}
var instanceDistanceBuffer = new THREE.InstancedInterleavedBuffer( lineDistances, 2, 1 ); // d0, d1
geometry.setAttribute( 'instanceDistanceStart', new THREE.InterleavedBufferAttribute( instanceDistanceBuffer, 1, 0 ) ); // d0
geometry.setAttribute( 'instanceDistanceEnd', new THREE.InterleavedBufferAttribute( instanceDistanceBuffer, 1, 1 ) ); // d1
return this;
};
}() ),
copy: function ( /* source */ ) {
// todo
return this;
}
} );

View File

@@ -0,0 +1,32 @@
/**
* @author WestLangley / http://github.com/WestLangley
*
*/
THREE.WireframeGeometry2 = function ( geometry ) {
THREE.LineSegmentsGeometry.call( this );
this.type = 'WireframeGeometry2';
this.fromWireframeGeometry( new THREE.WireframeGeometry( geometry ) );
// set colors, maybe
};
THREE.WireframeGeometry2.prototype = Object.assign( Object.create( THREE.LineSegmentsGeometry.prototype ), {
constructor: THREE.WireframeGeometry2,
isWireframeGeometry2: true,
copy: function ( /* source */ ) {
// todo
return this;
}
} );

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,27 @@
/** @license zlib.js 2012 - imaya [ https://github.com/imaya/zlib.js ] The MIT License */ var mod={},n=void 0,w=!0,aa=mod; function ba(f,d){var c=f.split("."),e=aa;!(c[0]in e)&&e.execScript&&e.execScript("var "+c[0]);for(var b;c.length&&(b=c.shift());)!c.length&&d!==n?e[b]=d:e=e[b]?e[b]:e[b]={}};var C="undefined"!==typeof Uint8Array&&"undefined"!==typeof Uint16Array&&"undefined"!==typeof Uint32Array&&"undefined"!==typeof DataView;function K(f,d){this.index="number"===typeof d?d:0;this.e=0;this.buffer=f instanceof(C?Uint8Array:Array)?f:new (C?Uint8Array:Array)(32768);if(2*this.buffer.length<=this.index)throw Error("invalid index");this.buffer.length<=this.index&&ca(this)}function ca(f){var d=f.buffer,c,e=d.length,b=new (C?Uint8Array:Array)(e<<1);if(C)b.set(d);else for(c=0;c<e;++c)b[c]=d[c];return f.buffer=b}
K.prototype.b=function(f,d,c){var e=this.buffer,b=this.index,a=this.e,g=e[b],m;c&&1<d&&(f=8<d?(L[f&255]<<24|L[f>>>8&255]<<16|L[f>>>16&255]<<8|L[f>>>24&255])>>32-d:L[f]>>8-d);if(8>d+a)g=g<<d|f,a+=d;else for(m=0;m<d;++m)g=g<<1|f>>d-m-1&1,8===++a&&(a=0,e[b++]=L[g],g=0,b===e.length&&(e=ca(this)));e[b]=g;this.buffer=e;this.e=a;this.index=b};K.prototype.finish=function(){var f=this.buffer,d=this.index,c;0<this.e&&(f[d]<<=8-this.e,f[d]=L[f[d]],d++);C?c=f.subarray(0,d):(f.length=d,c=f);return c};
var da=new (C?Uint8Array:Array)(256),M;for(M=0;256>M;++M){for(var N=M,S=N,ea=7,N=N>>>1;N;N>>>=1)S<<=1,S|=N&1,--ea;da[M]=(S<<ea&255)>>>0}var L=da;function ia(f){this.buffer=new (C?Uint16Array:Array)(2*f);this.length=0}ia.prototype.getParent=function(f){return 2*((f-2)/4|0)};ia.prototype.push=function(f,d){var c,e,b=this.buffer,a;c=this.length;b[this.length++]=d;for(b[this.length++]=f;0<c;)if(e=this.getParent(c),b[c]>b[e])a=b[c],b[c]=b[e],b[e]=a,a=b[c+1],b[c+1]=b[e+1],b[e+1]=a,c=e;else break;return this.length};
ia.prototype.pop=function(){var f,d,c=this.buffer,e,b,a;d=c[0];f=c[1];this.length-=2;c[0]=c[this.length];c[1]=c[this.length+1];for(a=0;;){b=2*a+2;if(b>=this.length)break;b+2<this.length&&c[b+2]>c[b]&&(b+=2);if(c[b]>c[a])e=c[a],c[a]=c[b],c[b]=e,e=c[a+1],c[a+1]=c[b+1],c[b+1]=e;else break;a=b}return{index:f,value:d,length:this.length}};function ka(f,d){this.d=la;this.i=0;this.input=C&&f instanceof Array?new Uint8Array(f):f;this.c=0;d&&(d.lazy&&(this.i=d.lazy),"number"===typeof d.compressionType&&(this.d=d.compressionType),d.outputBuffer&&(this.a=C&&d.outputBuffer instanceof Array?new Uint8Array(d.outputBuffer):d.outputBuffer),"number"===typeof d.outputIndex&&(this.c=d.outputIndex));this.a||(this.a=new (C?Uint8Array:Array)(32768))}var la=2,na={NONE:0,h:1,g:la,n:3},T=[],U;
for(U=0;288>U;U++)switch(w){case 143>=U:T.push([U+48,8]);break;case 255>=U:T.push([U-144+400,9]);break;case 279>=U:T.push([U-256+0,7]);break;case 287>=U:T.push([U-280+192,8]);break;default:throw"invalid literal: "+U;}
ka.prototype.f=function(){var f,d,c,e,b=this.input;switch(this.d){case 0:c=0;for(e=b.length;c<e;){d=C?b.subarray(c,c+65535):b.slice(c,c+65535);c+=d.length;var a=d,g=c===e,m=n,k=n,p=n,t=n,u=n,l=this.a,h=this.c;if(C){for(l=new Uint8Array(this.a.buffer);l.length<=h+a.length+5;)l=new Uint8Array(l.length<<1);l.set(this.a)}m=g?1:0;l[h++]=m|0;k=a.length;p=~k+65536&65535;l[h++]=k&255;l[h++]=k>>>8&255;l[h++]=p&255;l[h++]=p>>>8&255;if(C)l.set(a,h),h+=a.length,l=l.subarray(0,h);else{t=0;for(u=a.length;t<u;++t)l[h++]=
a[t];l.length=h}this.c=h;this.a=l}break;case 1:var q=new K(C?new Uint8Array(this.a.buffer):this.a,this.c);q.b(1,1,w);q.b(1,2,w);var s=oa(this,b),x,fa,z;x=0;for(fa=s.length;x<fa;x++)if(z=s[x],K.prototype.b.apply(q,T[z]),256<z)q.b(s[++x],s[++x],w),q.b(s[++x],5),q.b(s[++x],s[++x],w);else if(256===z)break;this.a=q.finish();this.c=this.a.length;break;case la:var B=new K(C?new Uint8Array(this.a.buffer):this.a,this.c),ta,J,O,P,Q,La=[16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15],X,ua,Y,va,ga,ja=Array(19),
wa,R,ha,y,xa;ta=la;B.b(1,1,w);B.b(ta,2,w);J=oa(this,b);X=pa(this.m,15);ua=qa(X);Y=pa(this.l,7);va=qa(Y);for(O=286;257<O&&0===X[O-1];O--);for(P=30;1<P&&0===Y[P-1];P--);var ya=O,za=P,F=new (C?Uint32Array:Array)(ya+za),r,G,v,Z,E=new (C?Uint32Array:Array)(316),D,A,H=new (C?Uint8Array:Array)(19);for(r=G=0;r<ya;r++)F[G++]=X[r];for(r=0;r<za;r++)F[G++]=Y[r];if(!C){r=0;for(Z=H.length;r<Z;++r)H[r]=0}r=D=0;for(Z=F.length;r<Z;r+=G){for(G=1;r+G<Z&&F[r+G]===F[r];++G);v=G;if(0===F[r])if(3>v)for(;0<v--;)E[D++]=0,
H[0]++;else for(;0<v;)A=138>v?v:138,A>v-3&&A<v&&(A=v-3),10>=A?(E[D++]=17,E[D++]=A-3,H[17]++):(E[D++]=18,E[D++]=A-11,H[18]++),v-=A;else if(E[D++]=F[r],H[F[r]]++,v--,3>v)for(;0<v--;)E[D++]=F[r],H[F[r]]++;else for(;0<v;)A=6>v?v:6,A>v-3&&A<v&&(A=v-3),E[D++]=16,E[D++]=A-3,H[16]++,v-=A}f=C?E.subarray(0,D):E.slice(0,D);ga=pa(H,7);for(y=0;19>y;y++)ja[y]=ga[La[y]];for(Q=19;4<Q&&0===ja[Q-1];Q--);wa=qa(ga);B.b(O-257,5,w);B.b(P-1,5,w);B.b(Q-4,4,w);for(y=0;y<Q;y++)B.b(ja[y],3,w);y=0;for(xa=f.length;y<xa;y++)if(R=
f[y],B.b(wa[R],ga[R],w),16<=R){y++;switch(R){case 16:ha=2;break;case 17:ha=3;break;case 18:ha=7;break;default:throw"invalid code: "+R;}B.b(f[y],ha,w)}var Aa=[ua,X],Ba=[va,Y],I,Ca,$,ma,Da,Ea,Fa,Ga;Da=Aa[0];Ea=Aa[1];Fa=Ba[0];Ga=Ba[1];I=0;for(Ca=J.length;I<Ca;++I)if($=J[I],B.b(Da[$],Ea[$],w),256<$)B.b(J[++I],J[++I],w),ma=J[++I],B.b(Fa[ma],Ga[ma],w),B.b(J[++I],J[++I],w);else if(256===$)break;this.a=B.finish();this.c=this.a.length;break;default:throw"invalid compression type";}return this.a};
function ra(f,d){this.length=f;this.k=d}
var sa=function(){function f(b){switch(w){case 3===b:return[257,b-3,0];case 4===b:return[258,b-4,0];case 5===b:return[259,b-5,0];case 6===b:return[260,b-6,0];case 7===b:return[261,b-7,0];case 8===b:return[262,b-8,0];case 9===b:return[263,b-9,0];case 10===b:return[264,b-10,0];case 12>=b:return[265,b-11,1];case 14>=b:return[266,b-13,1];case 16>=b:return[267,b-15,1];case 18>=b:return[268,b-17,1];case 22>=b:return[269,b-19,2];case 26>=b:return[270,b-23,2];case 30>=b:return[271,b-27,2];case 34>=b:return[272,
b-31,2];case 42>=b:return[273,b-35,3];case 50>=b:return[274,b-43,3];case 58>=b:return[275,b-51,3];case 66>=b:return[276,b-59,3];case 82>=b:return[277,b-67,4];case 98>=b:return[278,b-83,4];case 114>=b:return[279,b-99,4];case 130>=b:return[280,b-115,4];case 162>=b:return[281,b-131,5];case 194>=b:return[282,b-163,5];case 226>=b:return[283,b-195,5];case 257>=b:return[284,b-227,5];case 258===b:return[285,b-258,0];default:throw"invalid length: "+b;}}var d=[],c,e;for(c=3;258>=c;c++)e=f(c),d[c]=e[2]<<24|
e[1]<<16|e[0];return d}(),Ha=C?new Uint32Array(sa):sa;
function oa(f,d){function c(b,c){var a=b.k,d=[],e=0,f;f=Ha[b.length];d[e++]=f&65535;d[e++]=f>>16&255;d[e++]=f>>24;var g;switch(w){case 1===a:g=[0,a-1,0];break;case 2===a:g=[1,a-2,0];break;case 3===a:g=[2,a-3,0];break;case 4===a:g=[3,a-4,0];break;case 6>=a:g=[4,a-5,1];break;case 8>=a:g=[5,a-7,1];break;case 12>=a:g=[6,a-9,2];break;case 16>=a:g=[7,a-13,2];break;case 24>=a:g=[8,a-17,3];break;case 32>=a:g=[9,a-25,3];break;case 48>=a:g=[10,a-33,4];break;case 64>=a:g=[11,a-49,4];break;case 96>=a:g=[12,a-
65,5];break;case 128>=a:g=[13,a-97,5];break;case 192>=a:g=[14,a-129,6];break;case 256>=a:g=[15,a-193,6];break;case 384>=a:g=[16,a-257,7];break;case 512>=a:g=[17,a-385,7];break;case 768>=a:g=[18,a-513,8];break;case 1024>=a:g=[19,a-769,8];break;case 1536>=a:g=[20,a-1025,9];break;case 2048>=a:g=[21,a-1537,9];break;case 3072>=a:g=[22,a-2049,10];break;case 4096>=a:g=[23,a-3073,10];break;case 6144>=a:g=[24,a-4097,11];break;case 8192>=a:g=[25,a-6145,11];break;case 12288>=a:g=[26,a-8193,12];break;case 16384>=
a:g=[27,a-12289,12];break;case 24576>=a:g=[28,a-16385,13];break;case 32768>=a:g=[29,a-24577,13];break;default:throw"invalid distance";}f=g;d[e++]=f[0];d[e++]=f[1];d[e++]=f[2];var k,m;k=0;for(m=d.length;k<m;++k)l[h++]=d[k];s[d[0]]++;x[d[3]]++;q=b.length+c-1;u=null}var e,b,a,g,m,k={},p,t,u,l=C?new Uint16Array(2*d.length):[],h=0,q=0,s=new (C?Uint32Array:Array)(286),x=new (C?Uint32Array:Array)(30),fa=f.i,z;if(!C){for(a=0;285>=a;)s[a++]=0;for(a=0;29>=a;)x[a++]=0}s[256]=1;e=0;for(b=d.length;e<b;++e){a=
m=0;for(g=3;a<g&&e+a!==b;++a)m=m<<8|d[e+a];k[m]===n&&(k[m]=[]);p=k[m];if(!(0<q--)){for(;0<p.length&&32768<e-p[0];)p.shift();if(e+3>=b){u&&c(u,-1);a=0;for(g=b-e;a<g;++a)z=d[e+a],l[h++]=z,++s[z];break}0<p.length?(t=Ia(d,e,p),u?u.length<t.length?(z=d[e-1],l[h++]=z,++s[z],c(t,0)):c(u,-1):t.length<fa?u=t:c(t,0)):u?c(u,-1):(z=d[e],l[h++]=z,++s[z])}p.push(e)}l[h++]=256;s[256]++;f.m=s;f.l=x;return C?l.subarray(0,h):l}
function Ia(f,d,c){var e,b,a=0,g,m,k,p,t=f.length;m=0;p=c.length;a:for(;m<p;m++){e=c[p-m-1];g=3;if(3<a){for(k=a;3<k;k--)if(f[e+k-1]!==f[d+k-1])continue a;g=a}for(;258>g&&d+g<t&&f[e+g]===f[d+g];)++g;g>a&&(b=e,a=g);if(258===g)break}return new ra(a,d-b)}
function pa(f,d){var c=f.length,e=new ia(572),b=new (C?Uint8Array:Array)(c),a,g,m,k,p;if(!C)for(k=0;k<c;k++)b[k]=0;for(k=0;k<c;++k)0<f[k]&&e.push(k,f[k]);a=Array(e.length/2);g=new (C?Uint32Array:Array)(e.length/2);if(1===a.length)return b[e.pop().index]=1,b;k=0;for(p=e.length/2;k<p;++k)a[k]=e.pop(),g[k]=a[k].value;m=Ja(g,g.length,d);k=0;for(p=a.length;k<p;++k)b[a[k].index]=m[k];return b}
function Ja(f,d,c){function e(a){var b=k[a][p[a]];b===d?(e(a+1),e(a+1)):--g[b];++p[a]}var b=new (C?Uint16Array:Array)(c),a=new (C?Uint8Array:Array)(c),g=new (C?Uint8Array:Array)(d),m=Array(c),k=Array(c),p=Array(c),t=(1<<c)-d,u=1<<c-1,l,h,q,s,x;b[c-1]=d;for(h=0;h<c;++h)t<u?a[h]=0:(a[h]=1,t-=u),t<<=1,b[c-2-h]=(b[c-1-h]/2|0)+d;b[0]=a[0];m[0]=Array(b[0]);k[0]=Array(b[0]);for(h=1;h<c;++h)b[h]>2*b[h-1]+a[h]&&(b[h]=2*b[h-1]+a[h]),m[h]=Array(b[h]),k[h]=Array(b[h]);for(l=0;l<d;++l)g[l]=c;for(q=0;q<b[c-1];++q)m[c-
1][q]=f[q],k[c-1][q]=q;for(l=0;l<c;++l)p[l]=0;1===a[c-1]&&(--g[0],++p[c-1]);for(h=c-2;0<=h;--h){s=l=0;x=p[h+1];for(q=0;q<b[h];q++)s=m[h+1][x]+m[h+1][x+1],s>f[l]?(m[h][q]=s,k[h][q]=d,x+=2):(m[h][q]=f[l],k[h][q]=l,++l);p[h]=0;1===a[h]&&e(h)}return g}
function qa(f){var d=new (C?Uint16Array:Array)(f.length),c=[],e=[],b=0,a,g,m,k;a=0;for(g=f.length;a<g;a++)c[f[a]]=(c[f[a]]|0)+1;a=1;for(g=16;a<=g;a++)e[a]=b,b+=c[a]|0,b<<=1;a=0;for(g=f.length;a<g;a++){b=e[f[a]];e[f[a]]+=1;m=d[a]=0;for(k=f[a];m<k;m++)d[a]=d[a]<<1|b&1,b>>>=1}return d};function Ka(f,d){this.input=f;this.a=new (C?Uint8Array:Array)(32768);this.d=V.g;var c={},e;if((d||!(d={}))&&"number"===typeof d.compressionType)this.d=d.compressionType;for(e in d)c[e]=d[e];c.outputBuffer=this.a;this.j=new ka(this.input,c)}var V=na;
Ka.prototype.f=function(){var f,d,c,e,b,a,g=0;a=this.a;switch(8){case 8:f=Math.LOG2E*Math.log(32768)-8;break;default:throw Error("invalid compression method");}d=f<<4|8;a[g++]=d;switch(8){case 8:switch(this.d){case V.NONE:e=0;break;case V.h:e=1;break;case V.g:e=2;break;default:throw Error("unsupported compression type");}break;default:throw Error("invalid compression method");}c=e<<6|0;a[g++]=c|31-(256*d+c)%31;var m=this.input;if("string"===typeof m){var k=m.split(""),p,t;p=0;for(t=k.length;p<t;p++)k[p]=
(k[p].charCodeAt(0)&255)>>>0;m=k}for(var u=1,l=0,h=m.length,q,s=0;0<h;){q=1024<h?1024:h;h-=q;do u+=m[s++],l+=u;while(--q);u%=65521;l%=65521}b=(l<<16|u)>>>0;this.j.c=g;a=this.j.f();g=a.length;C&&(a=new Uint8Array(a.buffer),a.length<=g+4&&(this.a=new Uint8Array(a.length+4),this.a.set(a),a=this.a),a=a.subarray(0,g+4));a[g++]=b>>24&255;a[g++]=b>>16&255;a[g++]=b>>8&255;a[g++]=b&255;return a};ba("Zlib.Deflate",Ka);ba("Zlib.Deflate.compress",function(f,d){return(new Ka(f,d)).f()});ba("Zlib.Deflate.prototype.compress",Ka.prototype.f);var Ma={NONE:V.NONE,FIXED:V.h,DYNAMIC:V.g},Na,Oa,W,Pa;if(Object.keys)Na=Object.keys(Ma);else for(Oa in Na=[],W=0,Ma)Na[W++]=Oa;W=0;for(Pa=Na.length;W<Pa;++W)Oa=Na[W],ba("Zlib.Deflate.CompressionType."+Oa,Ma[Oa]);
var Deflate=mod.Zlib.Deflate;
export { Deflate }

28
libs/three.js/libs/gunzip.module.min.js vendored Normal file
View File

@@ -0,0 +1,28 @@
/** @license zlib.js 2012 - imaya [ https://github.com/imaya/zlib.js ] The MIT License */function n(e){throw e;}var q=void 0,aa={};function r(e,c){var d=e.split("."),b=aa;!(d[0]in b)&&b.execScript&&b.execScript("var "+d[0]);for(var a;d.length&&(a=d.shift());)!d.length&&c!==q?b[a]=c:b=b[a]?b[a]:b[a]={}};var u="undefined"!==typeof Uint8Array&&"undefined"!==typeof Uint16Array&&"undefined"!==typeof Uint32Array&&"undefined"!==typeof DataView;new (u?Uint8Array:Array)(256);var v;for(v=0;256>v;++v)for(var w=v,ba=7,w=w>>>1;w;w>>>=1)--ba;function x(e,c,d){var b,a="number"===typeof c?c:c=0,f="number"===typeof d?d:e.length;b=-1;for(a=f&7;a--;++c)b=b>>>8^z[(b^e[c])&255];for(a=f>>3;a--;c+=8)b=b>>>8^z[(b^e[c])&255],b=b>>>8^z[(b^e[c+1])&255],b=b>>>8^z[(b^e[c+2])&255],b=b>>>8^z[(b^e[c+3])&255],b=b>>>8^z[(b^e[c+4])&255],b=b>>>8^z[(b^e[c+5])&255],b=b>>>8^z[(b^e[c+6])&255],b=b>>>8^z[(b^e[c+7])&255];return(b^4294967295)>>>0}
var A=[0,1996959894,3993919788,2567524794,124634137,1886057615,3915621685,2657392035,249268274,2044508324,3772115230,2547177864,162941995,2125561021,3887607047,2428444049,498536548,1789927666,4089016648,2227061214,450548861,1843258603,4107580753,2211677639,325883990,1684777152,4251122042,2321926636,335633487,1661365465,4195302755,2366115317,997073096,1281953886,3579855332,2724688242,1006888145,1258607687,3524101629,2768942443,901097722,1119000684,3686517206,2898065728,853044451,1172266101,3705015759,
2882616665,651767980,1373503546,3369554304,3218104598,565507253,1454621731,3485111705,3099436303,671266974,1594198024,3322730930,2970347812,795835527,1483230225,3244367275,3060149565,1994146192,31158534,2563907772,4023717930,1907459465,112637215,2680153253,3904427059,2013776290,251722036,2517215374,3775830040,2137656763,141376813,2439277719,3865271297,1802195444,476864866,2238001368,4066508878,1812370925,453092731,2181625025,4111451223,1706088902,314042704,2344532202,4240017532,1658658271,366619977,
2362670323,4224994405,1303535960,984961486,2747007092,3569037538,1256170817,1037604311,2765210733,3554079995,1131014506,879679996,2909243462,3663771856,1141124467,855842277,2852801631,3708648649,1342533948,654459306,3188396048,3373015174,1466479909,544179635,3110523913,3462522015,1591671054,702138776,2966460450,3352799412,1504918807,783551873,3082640443,3233442989,3988292384,2596254646,62317068,1957810842,3939845945,2647816111,81470997,1943803523,3814918930,2489596804,225274430,2053790376,3826175755,
2466906013,167816743,2097651377,4027552580,2265490386,503444072,1762050814,4150417245,2154129355,426522225,1852507879,4275313526,2312317920,282753626,1742555852,4189708143,2394877945,397917763,1622183637,3604390888,2714866558,953729732,1340076626,3518719985,2797360999,1068828381,1219638859,3624741850,2936675148,906185462,1090812512,3747672003,2825379669,829329135,1181335161,3412177804,3160834842,628085408,1382605366,3423369109,3138078467,570562233,1426400815,3317316542,2998733608,733239954,1555261956,
3268935591,3050360625,752459403,1541320221,2607071920,3965973030,1969922972,40735498,2617837225,3943577151,1913087877,83908371,2512341634,3803740692,2075208622,213261112,2463272603,3855990285,2094854071,198958881,2262029012,4057260610,1759359992,534414190,2176718541,4139329115,1873836001,414664567,2282248934,4279200368,1711684554,285281116,2405801727,4167216745,1634467795,376229701,2685067896,3608007406,1308918612,956543938,2808555105,3495958263,1231636301,1047427035,2932959818,3654703836,1088359270,
936918E3,2847714899,3736837829,1202900863,817233897,3183342108,3401237130,1404277552,615818150,3134207493,3453421203,1423857449,601450431,3009837614,3294710456,1567103746,711928724,3020668471,3272380065,1510334235,755167117],z=u?new Uint32Array(A):A;function B(){}B.prototype.getName=function(){return this.name};B.prototype.getData=function(){return this.data};B.prototype.H=function(){return this.I};r("Zlib.GunzipMember",B);r("Zlib.GunzipMember.prototype.getName",B.prototype.getName);r("Zlib.GunzipMember.prototype.getData",B.prototype.getData);r("Zlib.GunzipMember.prototype.getMtime",B.prototype.H);function D(e){var c=e.length,d=0,b=Number.POSITIVE_INFINITY,a,f,g,k,m,p,t,h,l,y;for(h=0;h<c;++h)e[h]>d&&(d=e[h]),e[h]<b&&(b=e[h]);a=1<<d;f=new (u?Uint32Array:Array)(a);g=1;k=0;for(m=2;g<=d;){for(h=0;h<c;++h)if(e[h]===g){p=0;t=k;for(l=0;l<g;++l)p=p<<1|t&1,t>>=1;y=g<<16|h;for(l=p;l<a;l+=m)f[l]=y;++k}++g;k<<=1;m<<=1}return[f,d,b]};var E=[],F;for(F=0;288>F;F++)switch(!0){case 143>=F:E.push([F+48,8]);break;case 255>=F:E.push([F-144+400,9]);break;case 279>=F:E.push([F-256+0,7]);break;case 287>=F:E.push([F-280+192,8]);break;default:n("invalid literal: "+F)}
var ca=function(){function e(a){switch(!0){case 3===a:return[257,a-3,0];case 4===a:return[258,a-4,0];case 5===a:return[259,a-5,0];case 6===a:return[260,a-6,0];case 7===a:return[261,a-7,0];case 8===a:return[262,a-8,0];case 9===a:return[263,a-9,0];case 10===a:return[264,a-10,0];case 12>=a:return[265,a-11,1];case 14>=a:return[266,a-13,1];case 16>=a:return[267,a-15,1];case 18>=a:return[268,a-17,1];case 22>=a:return[269,a-19,2];case 26>=a:return[270,a-23,2];case 30>=a:return[271,a-27,2];case 34>=a:return[272,
a-31,2];case 42>=a:return[273,a-35,3];case 50>=a:return[274,a-43,3];case 58>=a:return[275,a-51,3];case 66>=a:return[276,a-59,3];case 82>=a:return[277,a-67,4];case 98>=a:return[278,a-83,4];case 114>=a:return[279,a-99,4];case 130>=a:return[280,a-115,4];case 162>=a:return[281,a-131,5];case 194>=a:return[282,a-163,5];case 226>=a:return[283,a-195,5];case 257>=a:return[284,a-227,5];case 258===a:return[285,a-258,0];default:n("invalid length: "+a)}}var c=[],d,b;for(d=3;258>=d;d++)b=e(d),c[d]=b[2]<<24|b[1]<<
16|b[0];return c}();u&&new Uint32Array(ca);function G(e,c){this.i=[];this.j=32768;this.d=this.f=this.c=this.n=0;this.input=u?new Uint8Array(e):e;this.o=!1;this.k=H;this.z=!1;if(c||!(c={}))c.index&&(this.c=c.index),c.bufferSize&&(this.j=c.bufferSize),c.bufferType&&(this.k=c.bufferType),c.resize&&(this.z=c.resize);switch(this.k){case I:this.a=32768;this.b=new (u?Uint8Array:Array)(32768+this.j+258);break;case H:this.a=0;this.b=new (u?Uint8Array:Array)(this.j);this.e=this.F;this.q=this.B;this.l=this.D;break;default:n(Error("invalid inflate mode"))}}
var I=0,H=1;
G.prototype.g=function(){for(;!this.o;){var e=J(this,3);e&1&&(this.o=!0);e>>>=1;switch(e){case 0:var c=this.input,d=this.c,b=this.b,a=this.a,f=c.length,g=q,k=q,m=b.length,p=q;this.d=this.f=0;d+1>=f&&n(Error("invalid uncompressed block header: LEN"));g=c[d++]|c[d++]<<8;d+1>=f&&n(Error("invalid uncompressed block header: NLEN"));k=c[d++]|c[d++]<<8;g===~k&&n(Error("invalid uncompressed block header: length verify"));d+g>c.length&&n(Error("input buffer is broken"));switch(this.k){case I:for(;a+g>b.length;){p=
m-a;g-=p;if(u)b.set(c.subarray(d,d+p),a),a+=p,d+=p;else for(;p--;)b[a++]=c[d++];this.a=a;b=this.e();a=this.a}break;case H:for(;a+g>b.length;)b=this.e({t:2});break;default:n(Error("invalid inflate mode"))}if(u)b.set(c.subarray(d,d+g),a),a+=g,d+=g;else for(;g--;)b[a++]=c[d++];this.c=d;this.a=a;this.b=b;break;case 1:this.l(da,ea);break;case 2:fa(this);break;default:n(Error("unknown BTYPE: "+e))}}return this.q()};
var K=[16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15],L=u?new Uint16Array(K):K,N=[3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258,258,258],O=u?new Uint16Array(N):N,P=[0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0],Q=u?new Uint8Array(P):P,R=[1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577],ga=u?new Uint16Array(R):R,ha=[0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,
13,13],U=u?new Uint8Array(ha):ha,V=new (u?Uint8Array:Array)(288),W,ia;W=0;for(ia=V.length;W<ia;++W)V[W]=143>=W?8:255>=W?9:279>=W?7:8;var da=D(V),X=new (u?Uint8Array:Array)(30),Y,ja;Y=0;for(ja=X.length;Y<ja;++Y)X[Y]=5;var ea=D(X);function J(e,c){for(var d=e.f,b=e.d,a=e.input,f=e.c,g=a.length,k;b<c;)f>=g&&n(Error("input buffer is broken")),d|=a[f++]<<b,b+=8;k=d&(1<<c)-1;e.f=d>>>c;e.d=b-c;e.c=f;return k}
function Z(e,c){for(var d=e.f,b=e.d,a=e.input,f=e.c,g=a.length,k=c[0],m=c[1],p,t;b<m&&!(f>=g);)d|=a[f++]<<b,b+=8;p=k[d&(1<<m)-1];t=p>>>16;e.f=d>>t;e.d=b-t;e.c=f;return p&65535}
function fa(e){function c(a,c,b){var d,e=this.w,f,g;for(g=0;g<a;)switch(d=Z(this,c),d){case 16:for(f=3+J(this,2);f--;)b[g++]=e;break;case 17:for(f=3+J(this,3);f--;)b[g++]=0;e=0;break;case 18:for(f=11+J(this,7);f--;)b[g++]=0;e=0;break;default:e=b[g++]=d}this.w=e;return b}var d=J(e,5)+257,b=J(e,5)+1,a=J(e,4)+4,f=new (u?Uint8Array:Array)(L.length),g,k,m,p;for(p=0;p<a;++p)f[L[p]]=J(e,3);if(!u){p=a;for(a=f.length;p<a;++p)f[L[p]]=0}g=D(f);k=new (u?Uint8Array:Array)(d);m=new (u?Uint8Array:Array)(b);e.w=
0;e.l(D(c.call(e,d,g,k)),D(c.call(e,b,g,m)))}G.prototype.l=function(e,c){var d=this.b,b=this.a;this.r=e;for(var a=d.length-258,f,g,k,m;256!==(f=Z(this,e));)if(256>f)b>=a&&(this.a=b,d=this.e(),b=this.a),d[b++]=f;else{g=f-257;m=O[g];0<Q[g]&&(m+=J(this,Q[g]));f=Z(this,c);k=ga[f];0<U[f]&&(k+=J(this,U[f]));b>=a&&(this.a=b,d=this.e(),b=this.a);for(;m--;)d[b]=d[b++-k]}for(;8<=this.d;)this.d-=8,this.c--;this.a=b};
G.prototype.D=function(e,c){var d=this.b,b=this.a;this.r=e;for(var a=d.length,f,g,k,m;256!==(f=Z(this,e));)if(256>f)b>=a&&(d=this.e(),a=d.length),d[b++]=f;else{g=f-257;m=O[g];0<Q[g]&&(m+=J(this,Q[g]));f=Z(this,c);k=ga[f];0<U[f]&&(k+=J(this,U[f]));b+m>a&&(d=this.e(),a=d.length);for(;m--;)d[b]=d[b++-k]}for(;8<=this.d;)this.d-=8,this.c--;this.a=b};
G.prototype.e=function(){var e=new (u?Uint8Array:Array)(this.a-32768),c=this.a-32768,d,b,a=this.b;if(u)e.set(a.subarray(32768,e.length));else{d=0;for(b=e.length;d<b;++d)e[d]=a[d+32768]}this.i.push(e);this.n+=e.length;if(u)a.set(a.subarray(c,c+32768));else for(d=0;32768>d;++d)a[d]=a[c+d];this.a=32768;return a};
G.prototype.F=function(e){var c,d=this.input.length/this.c+1|0,b,a,f,g=this.input,k=this.b;e&&("number"===typeof e.t&&(d=e.t),"number"===typeof e.A&&(d+=e.A));2>d?(b=(g.length-this.c)/this.r[2],f=258*(b/2)|0,a=f<k.length?k.length+f:k.length<<1):a=k.length*d;u?(c=new Uint8Array(a),c.set(k)):c=k;return this.b=c};
G.prototype.q=function(){var e=0,c=this.b,d=this.i,b,a=new (u?Uint8Array:Array)(this.n+(this.a-32768)),f,g,k,m;if(0===d.length)return u?this.b.subarray(32768,this.a):this.b.slice(32768,this.a);f=0;for(g=d.length;f<g;++f){b=d[f];k=0;for(m=b.length;k<m;++k)a[e++]=b[k]}f=32768;for(g=this.a;f<g;++f)a[e++]=c[f];this.i=[];return this.buffer=a};
G.prototype.B=function(){var e,c=this.a;u?this.z?(e=new Uint8Array(c),e.set(this.b.subarray(0,c))):e=this.b.subarray(0,c):(this.b.length>c&&(this.b.length=c),e=this.b);return this.buffer=e};function $(e){this.input=e;this.c=0;this.m=[];this.s=!1}$.prototype.G=function(){this.s||this.g();return this.m.slice()};
$.prototype.g=function(){for(var e=this.input.length;this.c<e;){var c=new B,d=q,b=q,a=q,f=q,g=q,k=q,m=q,p=q,t=q,h=this.input,l=this.c;c.u=h[l++];c.v=h[l++];(31!==c.u||139!==c.v)&&n(Error("invalid file signature:"+c.u+","+c.v));c.p=h[l++];switch(c.p){case 8:break;default:n(Error("unknown compression method: "+c.p))}c.h=h[l++];p=h[l++]|h[l++]<<8|h[l++]<<16|h[l++]<<24;c.I=new Date(1E3*p);c.O=h[l++];c.N=h[l++];0<(c.h&4)&&(c.J=h[l++]|h[l++]<<8,l+=c.J);if(0<(c.h&8)){m=[];for(k=0;0<(g=h[l++]);)m[k++]=String.fromCharCode(g);
c.name=m.join("")}if(0<(c.h&16)){m=[];for(k=0;0<(g=h[l++]);)m[k++]=String.fromCharCode(g);c.K=m.join("")}0<(c.h&2)&&(c.C=x(h,0,l)&65535,c.C!==(h[l++]|h[l++]<<8)&&n(Error("invalid header crc16")));d=h[h.length-4]|h[h.length-3]<<8|h[h.length-2]<<16|h[h.length-1]<<24;h.length-l-4-4<512*d&&(f=d);b=new G(h,{index:l,bufferSize:f});c.data=a=b.g();l=b.c;c.L=t=(h[l++]|h[l++]<<8|h[l++]<<16|h[l++]<<24)>>>0;x(a,q,q)!==t&&n(Error("invalid CRC-32 checksum: 0x"+x(a,q,q).toString(16)+" / 0x"+t.toString(16)));c.M=
d=(h[l++]|h[l++]<<8|h[l++]<<16|h[l++]<<24)>>>0;(a.length&4294967295)!==d&&n(Error("invalid input size: "+(a.length&4294967295)+" / "+d));this.m.push(c);this.c=l}this.s=!0;var y=this.m,s,M,S=0,T=0,C;s=0;for(M=y.length;s<M;++s)T+=y[s].data.length;if(u){C=new Uint8Array(T);for(s=0;s<M;++s)C.set(y[s].data,S),S+=y[s].data.length}else{C=[];for(s=0;s<M;++s)C[s]=y[s].data;C=Array.prototype.concat.apply([],C)}return C};r("Zlib.Gunzip",$);r("Zlib.Gunzip.prototype.decompress",$.prototype.g);r("Zlib.Gunzip.prototype.getMembers",$.prototype.G);
var Zlib=aa.Zlib;
export { Zlib }

View File

@@ -0,0 +1,17 @@
/** @license zlib.js 2012 - imaya [ https://github.com/imaya/zlib.js ] The MIT License */var mod={}, l=void 0,aa=mod;function r(c,d){var a=c.split("."),b=aa;!(a[0]in b)&&b.execScript&&b.execScript("var "+a[0]);for(var e;a.length&&(e=a.shift());)!a.length&&d!==l?b[e]=d:b=b[e]?b[e]:b[e]={}};var t="undefined"!==typeof Uint8Array&&"undefined"!==typeof Uint16Array&&"undefined"!==typeof Uint32Array&&"undefined"!==typeof DataView;function v(c){var d=c.length,a=0,b=Number.POSITIVE_INFINITY,e,f,g,h,k,m,n,p,s,x;for(p=0;p<d;++p)c[p]>a&&(a=c[p]),c[p]<b&&(b=c[p]);e=1<<a;f=new (t?Uint32Array:Array)(e);g=1;h=0;for(k=2;g<=a;){for(p=0;p<d;++p)if(c[p]===g){m=0;n=h;for(s=0;s<g;++s)m=m<<1|n&1,n>>=1;x=g<<16|p;for(s=m;s<e;s+=k)f[s]=x;++h}++g;h<<=1;k<<=1}return[f,a,b]};function w(c,d){this.g=[];this.h=32768;this.d=this.f=this.a=this.l=0;this.input=t?new Uint8Array(c):c;this.m=!1;this.i=y;this.r=!1;if(d||!(d={}))d.index&&(this.a=d.index),d.bufferSize&&(this.h=d.bufferSize),d.bufferType&&(this.i=d.bufferType),d.resize&&(this.r=d.resize);switch(this.i){case A:this.b=32768;this.c=new (t?Uint8Array:Array)(32768+this.h+258);break;case y:this.b=0;this.c=new (t?Uint8Array:Array)(this.h);this.e=this.z;this.n=this.v;this.j=this.w;break;default:throw Error("invalid inflate mode");
}}var A=0,y=1,B={t:A,s:y};
w.prototype.k=function(){for(;!this.m;){var c=C(this,3);c&1&&(this.m=!0);c>>>=1;switch(c){case 0:var d=this.input,a=this.a,b=this.c,e=this.b,f=d.length,g=l,h=l,k=b.length,m=l;this.d=this.f=0;if(a+1>=f)throw Error("invalid uncompressed block header: LEN");g=d[a++]|d[a++]<<8;if(a+1>=f)throw Error("invalid uncompressed block header: NLEN");h=d[a++]|d[a++]<<8;if(g===~h)throw Error("invalid uncompressed block header: length verify");if(a+g>d.length)throw Error("input buffer is broken");switch(this.i){case A:for(;e+
g>b.length;){m=k-e;g-=m;if(t)b.set(d.subarray(a,a+m),e),e+=m,a+=m;else for(;m--;)b[e++]=d[a++];this.b=e;b=this.e();e=this.b}break;case y:for(;e+g>b.length;)b=this.e({p:2});break;default:throw Error("invalid inflate mode");}if(t)b.set(d.subarray(a,a+g),e),e+=g,a+=g;else for(;g--;)b[e++]=d[a++];this.a=a;this.b=e;this.c=b;break;case 1:this.j(ba,ca);break;case 2:for(var n=C(this,5)+257,p=C(this,5)+1,s=C(this,4)+4,x=new (t?Uint8Array:Array)(D.length),S=l,T=l,U=l,u=l,M=l,F=l,z=l,q=l,V=l,q=0;q<s;++q)x[D[q]]=
C(this,3);if(!t){q=s;for(s=x.length;q<s;++q)x[D[q]]=0}S=v(x);u=new (t?Uint8Array:Array)(n+p);q=0;for(V=n+p;q<V;)switch(M=E(this,S),M){case 16:for(z=3+C(this,2);z--;)u[q++]=F;break;case 17:for(z=3+C(this,3);z--;)u[q++]=0;F=0;break;case 18:for(z=11+C(this,7);z--;)u[q++]=0;F=0;break;default:F=u[q++]=M}T=t?v(u.subarray(0,n)):v(u.slice(0,n));U=t?v(u.subarray(n)):v(u.slice(n));this.j(T,U);break;default:throw Error("unknown BTYPE: "+c);}}return this.n()};
var G=[16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15],D=t?new Uint16Array(G):G,H=[3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258,258,258],I=t?new Uint16Array(H):H,J=[0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0],K=t?new Uint8Array(J):J,L=[1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577],da=t?new Uint16Array(L):L,ea=[0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,
13,13],N=t?new Uint8Array(ea):ea,O=new (t?Uint8Array:Array)(288),P,fa;P=0;for(fa=O.length;P<fa;++P)O[P]=143>=P?8:255>=P?9:279>=P?7:8;var ba=v(O),Q=new (t?Uint8Array:Array)(30),R,ga;R=0;for(ga=Q.length;R<ga;++R)Q[R]=5;var ca=v(Q);function C(c,d){for(var a=c.f,b=c.d,e=c.input,f=c.a,g=e.length,h;b<d;){if(f>=g)throw Error("input buffer is broken");a|=e[f++]<<b;b+=8}h=a&(1<<d)-1;c.f=a>>>d;c.d=b-d;c.a=f;return h}
function E(c,d){for(var a=c.f,b=c.d,e=c.input,f=c.a,g=e.length,h=d[0],k=d[1],m,n;b<k&&!(f>=g);)a|=e[f++]<<b,b+=8;m=h[a&(1<<k)-1];n=m>>>16;if(n>b)throw Error("invalid code length: "+n);c.f=a>>n;c.d=b-n;c.a=f;return m&65535}
w.prototype.j=function(c,d){var a=this.c,b=this.b;this.o=c;for(var e=a.length-258,f,g,h,k;256!==(f=E(this,c));)if(256>f)b>=e&&(this.b=b,a=this.e(),b=this.b),a[b++]=f;else{g=f-257;k=I[g];0<K[g]&&(k+=C(this,K[g]));f=E(this,d);h=da[f];0<N[f]&&(h+=C(this,N[f]));b>=e&&(this.b=b,a=this.e(),b=this.b);for(;k--;)a[b]=a[b++-h]}for(;8<=this.d;)this.d-=8,this.a--;this.b=b};
w.prototype.w=function(c,d){var a=this.c,b=this.b;this.o=c;for(var e=a.length,f,g,h,k;256!==(f=E(this,c));)if(256>f)b>=e&&(a=this.e(),e=a.length),a[b++]=f;else{g=f-257;k=I[g];0<K[g]&&(k+=C(this,K[g]));f=E(this,d);h=da[f];0<N[f]&&(h+=C(this,N[f]));b+k>e&&(a=this.e(),e=a.length);for(;k--;)a[b]=a[b++-h]}for(;8<=this.d;)this.d-=8,this.a--;this.b=b};
w.prototype.e=function(){var c=new (t?Uint8Array:Array)(this.b-32768),d=this.b-32768,a,b,e=this.c;if(t)c.set(e.subarray(32768,c.length));else{a=0;for(b=c.length;a<b;++a)c[a]=e[a+32768]}this.g.push(c);this.l+=c.length;if(t)e.set(e.subarray(d,d+32768));else for(a=0;32768>a;++a)e[a]=e[d+a];this.b=32768;return e};
w.prototype.z=function(c){var d,a=this.input.length/this.a+1|0,b,e,f,g=this.input,h=this.c;c&&("number"===typeof c.p&&(a=c.p),"number"===typeof c.u&&(a+=c.u));2>a?(b=(g.length-this.a)/this.o[2],f=258*(b/2)|0,e=f<h.length?h.length+f:h.length<<1):e=h.length*a;t?(d=new Uint8Array(e),d.set(h)):d=h;return this.c=d};
w.prototype.n=function(){var c=0,d=this.c,a=this.g,b,e=new (t?Uint8Array:Array)(this.l+(this.b-32768)),f,g,h,k;if(0===a.length)return t?this.c.subarray(32768,this.b):this.c.slice(32768,this.b);f=0;for(g=a.length;f<g;++f){b=a[f];h=0;for(k=b.length;h<k;++h)e[c++]=b[h]}f=32768;for(g=this.b;f<g;++f)e[c++]=d[f];this.g=[];return this.buffer=e};
w.prototype.v=function(){var c,d=this.b;t?this.r?(c=new Uint8Array(d),c.set(this.c.subarray(0,d))):c=this.c.subarray(0,d):(this.c.length>d&&(this.c.length=d),c=this.c);return this.buffer=c};function W(c,d){var a,b;this.input=c;this.a=0;if(d||!(d={}))d.index&&(this.a=d.index),d.verify&&(this.A=d.verify);a=c[this.a++];b=c[this.a++];switch(a&15){case ha:this.method=ha;break;default:throw Error("unsupported compression method");}if(0!==((a<<8)+b)%31)throw Error("invalid fcheck flag:"+((a<<8)+b)%31);if(b&32)throw Error("fdict flag is not supported");this.q=new w(c,{index:this.a,bufferSize:d.bufferSize,bufferType:d.bufferType,resize:d.resize})}
W.prototype.k=function(){var c=this.input,d,a;d=this.q.k();this.a=this.q.a;if(this.A){a=(c[this.a++]<<24|c[this.a++]<<16|c[this.a++]<<8|c[this.a++])>>>0;var b=d;if("string"===typeof b){var e=b.split(""),f,g;f=0;for(g=e.length;f<g;f++)e[f]=(e[f].charCodeAt(0)&255)>>>0;b=e}for(var h=1,k=0,m=b.length,n,p=0;0<m;){n=1024<m?1024:m;m-=n;do h+=b[p++],k+=h;while(--n);h%=65521;k%=65521}if(a!==(k<<16|h)>>>0)throw Error("invalid adler-32 checksum");}return d};var ha=8;r("Zlib.Inflate",W);r("Zlib.Inflate.prototype.decompress",W.prototype.k);var X={ADAPTIVE:B.s,BLOCK:B.t},Y,Z,$,ia;if(Object.keys)Y=Object.keys(X);else for(Z in Y=[],$=0,X)Y[$++]=Z;$=0;for(ia=Y.length;$<ia;++$)Z=Y[$],r("Zlib.Inflate.BufferType."+Z,X[Z]);
var Inflate=mod.Zlib.Inflate;
export { Inflate }

15
libs/three.js/libs/jszip.module.min.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,397 @@
/**
* @webxr-input-profiles/motion-controllers 1.0.0 https://github.com/immersive-web/webxr-input-profiles
*/
const Constants = {
Handedness: Object.freeze({
NONE: 'none',
LEFT: 'left',
RIGHT: 'right'
}),
ComponentState: Object.freeze({
DEFAULT: 'default',
TOUCHED: 'touched',
PRESSED: 'pressed'
}),
ComponentProperty: Object.freeze({
BUTTON: 'button',
X_AXIS: 'xAxis',
Y_AXIS: 'yAxis',
STATE: 'state'
}),
ComponentType: Object.freeze({
TRIGGER: 'trigger',
SQUEEZE: 'squeeze',
TOUCHPAD: 'touchpad',
THUMBSTICK: 'thumbstick',
BUTTON: 'button'
}),
ButtonTouchThreshold: 0.05,
AxisTouchThreshold: 0.1,
VisualResponseProperty: Object.freeze({
TRANSFORM: 'transform',
VISIBILITY: 'visibility'
})
};
/**
* @description Static helper function to fetch a JSON file and turn it into a JS object
* @param {string} path - Path to JSON file to be fetched
*/
async function fetchJsonFile(path) {
const response = await fetch(path);
if (!response.ok) {
throw new Error(response.statusText);
} else {
return response.json();
}
}
async function fetchProfilesList(basePath) {
if (!basePath) {
throw new Error('No basePath supplied');
}
const profileListFileName = 'profilesList.json';
const profilesList = await fetchJsonFile(`${basePath}/${profileListFileName}`);
return profilesList;
}
async function fetchProfile(xrInputSource, basePath, defaultProfile = null, getAssetPath = true) {
if (!xrInputSource) {
throw new Error('No xrInputSource supplied');
}
if (!basePath) {
throw new Error('No basePath supplied');
}
// Get the list of profiles
const supportedProfilesList = await fetchProfilesList(basePath);
// Find the relative path to the first requested profile that is recognized
let match;
xrInputSource.profiles.some((profileId) => {
const supportedProfile = supportedProfilesList[profileId];
if (supportedProfile) {
match = {
profileId,
profilePath: `${basePath}/${supportedProfile.path}`,
deprecated: !!supportedProfile.deprecated
};
}
return !!match;
});
if (!match) {
if (!defaultProfile) {
throw new Error('No matching profile name found');
}
const supportedProfile = supportedProfilesList[defaultProfile];
if (!supportedProfile) {
throw new Error(`No matching profile name found and default profile "${defaultProfile}" missing.`);
}
match = {
profileId: defaultProfile,
profilePath: `${basePath}/${supportedProfile.path}`,
deprecated: !!supportedProfile.deprecated
};
}
const profile = await fetchJsonFile(match.profilePath);
let assetPath;
if (getAssetPath) {
let layout;
if (xrInputSource.handedness === 'any') {
layout = profile.layouts[Object.keys(profile.layouts)[0]];
} else {
layout = profile.layouts[xrInputSource.handedness];
}
if (!layout) {
throw new Error(
`No matching handedness, ${xrInputSource.handedness}, in profile ${match.profileId}`
);
}
if (layout.assetPath) {
assetPath = match.profilePath.replace('profile.json', layout.assetPath);
}
}
return { profile, assetPath };
}
/** @constant {Object} */
const defaultComponentValues = {
xAxis: 0,
yAxis: 0,
button: 0,
state: Constants.ComponentState.DEFAULT
};
/**
* @description Converts an X, Y coordinate from the range -1 to 1 (as reported by the Gamepad
* API) to the range 0 to 1 (for interpolation). Also caps the X, Y values to be bounded within
* a circle. This ensures that thumbsticks are not animated outside the bounds of their physical
* range of motion and touchpads do not report touch locations off their physical bounds.
* @param {number} x The original x coordinate in the range -1 to 1
* @param {number} y The original y coordinate in the range -1 to 1
*/
function normalizeAxes(x = 0, y = 0) {
let xAxis = x;
let yAxis = y;
// Determine if the point is outside the bounds of the circle
// and, if so, place it on the edge of the circle
const hypotenuse = Math.sqrt((x * x) + (y * y));
if (hypotenuse > 1) {
const theta = Math.atan2(y, x);
xAxis = Math.cos(theta);
yAxis = Math.sin(theta);
}
// Scale and move the circle so values are in the interpolation range. The circle's origin moves
// from (0, 0) to (0.5, 0.5). The circle's radius scales from 1 to be 0.5.
const result = {
normalizedXAxis: (xAxis * 0.5) + 0.5,
normalizedYAxis: (yAxis * 0.5) + 0.5
};
return result;
}
/**
* Contains the description of how the 3D model should visually respond to a specific user input.
* This is accomplished by initializing the object with the name of a node in the 3D model and
* property that need to be modified in response to user input, the name of the nodes representing
* the allowable range of motion, and the name of the input which triggers the change. In response
* to the named input changing, this object computes the appropriate weighting to use for
* interpolating between the range of motion nodes.
*/
class VisualResponse {
constructor(visualResponseDescription) {
this.componentProperty = visualResponseDescription.componentProperty;
this.states = visualResponseDescription.states;
this.valueNodeName = visualResponseDescription.valueNodeName;
this.valueNodeProperty = visualResponseDescription.valueNodeProperty;
if (this.valueNodeProperty === Constants.VisualResponseProperty.TRANSFORM) {
this.minNodeName = visualResponseDescription.minNodeName;
this.maxNodeName = visualResponseDescription.maxNodeName;
}
// Initializes the response's current value based on default data
this.value = 0;
this.updateFromComponent(defaultComponentValues);
}
/**
* Computes the visual response's interpolation weight based on component state
* @param {Object} componentValues - The component from which to update
* @param {number} xAxis - The reported X axis value of the component
* @param {number} yAxis - The reported Y axis value of the component
* @param {number} button - The reported value of the component's button
* @param {string} state - The component's active state
*/
updateFromComponent({
xAxis, yAxis, button, state
}) {
const { normalizedXAxis, normalizedYAxis } = normalizeAxes(xAxis, yAxis);
switch (this.componentProperty) {
case Constants.ComponentProperty.X_AXIS:
this.value = (this.states.includes(state)) ? normalizedXAxis : 0.5;
break;
case Constants.ComponentProperty.Y_AXIS:
this.value = (this.states.includes(state)) ? normalizedYAxis : 0.5;
break;
case Constants.ComponentProperty.BUTTON:
this.value = (this.states.includes(state)) ? button : 0;
break;
case Constants.ComponentProperty.STATE:
if (this.valueNodeProperty === Constants.VisualResponseProperty.VISIBILITY) {
this.value = (this.states.includes(state));
} else {
this.value = this.states.includes(state) ? 1.0 : 0.0;
}
break;
default:
throw new Error(`Unexpected visualResponse componentProperty ${this.componentProperty}`);
}
}
}
class Component {
/**
* @param {Object} componentId - Id of the component
* @param {Object} componentDescription - Description of the component to be created
*/
constructor(componentId, componentDescription) {
if (!componentId
|| !componentDescription
|| !componentDescription.visualResponses
|| !componentDescription.gamepadIndices
|| Object.keys(componentDescription.gamepadIndices).length === 0) {
throw new Error('Invalid arguments supplied');
}
this.id = componentId;
this.type = componentDescription.type;
this.rootNodeName = componentDescription.rootNodeName;
this.touchPointNodeName = componentDescription.touchPointNodeName;
// Build all the visual responses for this component
this.visualResponses = {};
Object.keys(componentDescription.visualResponses).forEach((responseName) => {
const visualResponse = new VisualResponse(componentDescription.visualResponses[responseName]);
this.visualResponses[responseName] = visualResponse;
});
// Set default values
this.gamepadIndices = Object.assign({}, componentDescription.gamepadIndices);
this.values = {
state: Constants.ComponentState.DEFAULT,
button: (this.gamepadIndices.button !== undefined) ? 0 : undefined,
xAxis: (this.gamepadIndices.xAxis !== undefined) ? 0 : undefined,
yAxis: (this.gamepadIndices.yAxis !== undefined) ? 0 : undefined
};
}
get data() {
const data = { id: this.id, ...this.values };
return data;
}
/**
* @description Poll for updated data based on current gamepad state
* @param {Object} gamepad - The gamepad object from which the component data should be polled
*/
updateFromGamepad(gamepad) {
// Set the state to default before processing other data sources
this.values.state = Constants.ComponentState.DEFAULT;
// Get and normalize button
if (this.gamepadIndices.button !== undefined
&& gamepad.buttons.length > this.gamepadIndices.button) {
const gamepadButton = gamepad.buttons[this.gamepadIndices.button];
this.values.button = gamepadButton.value;
this.values.button = (this.values.button < 0) ? 0 : this.values.button;
this.values.button = (this.values.button > 1) ? 1 : this.values.button;
// Set the state based on the button
if (gamepadButton.pressed || this.values.button === 1) {
this.values.state = Constants.ComponentState.PRESSED;
} else if (gamepadButton.touched || this.values.button > Constants.ButtonTouchThreshold) {
this.values.state = Constants.ComponentState.TOUCHED;
}
}
// Get and normalize x axis value
if (this.gamepadIndices.xAxis !== undefined
&& gamepad.axes.length > this.gamepadIndices.xAxis) {
this.values.xAxis = gamepad.axes[this.gamepadIndices.xAxis];
this.values.xAxis = (this.values.xAxis < -1) ? -1 : this.values.xAxis;
this.values.xAxis = (this.values.xAxis > 1) ? 1 : this.values.xAxis;
// If the state is still default, check if the xAxis makes it touched
if (this.values.state === Constants.ComponentState.DEFAULT
&& Math.abs(this.values.xAxis) > Constants.AxisTouchThreshold) {
this.values.state = Constants.ComponentState.TOUCHED;
}
}
// Get and normalize Y axis value
if (this.gamepadIndices.yAxis !== undefined
&& gamepad.axes.length > this.gamepadIndices.yAxis) {
this.values.yAxis = gamepad.axes[this.gamepadIndices.yAxis];
this.values.yAxis = (this.values.yAxis < -1) ? -1 : this.values.yAxis;
this.values.yAxis = (this.values.yAxis > 1) ? 1 : this.values.yAxis;
// If the state is still default, check if the yAxis makes it touched
if (this.values.state === Constants.ComponentState.DEFAULT
&& Math.abs(this.values.yAxis) > Constants.AxisTouchThreshold) {
this.values.state = Constants.ComponentState.TOUCHED;
}
}
// Update the visual response weights based on the current component data
Object.values(this.visualResponses).forEach((visualResponse) => {
visualResponse.updateFromComponent(this.values);
});
}
}
/**
* @description Builds a motion controller with components and visual responses based on the
* supplied profile description. Data is polled from the xrInputSource's gamepad.
* @author Nell Waliczek / https://github.com/NellWaliczek
*/
class MotionController {
/**
* @param {Object} xrInputSource - The XRInputSource to build the MotionController around
* @param {Object} profile - The best matched profile description for the supplied xrInputSource
* @param {Object} assetUrl
*/
constructor(xrInputSource, profile, assetUrl) {
if (!xrInputSource) {
throw new Error('No xrInputSource supplied');
}
if (!profile) {
throw new Error('No profile supplied');
}
this.xrInputSource = xrInputSource;
this.assetUrl = assetUrl;
this.id = profile.profileId;
// Build child components as described in the profile description
this.layoutDescription = profile.layouts[xrInputSource.handedness];
this.components = {};
Object.keys(this.layoutDescription.components).forEach((componentId) => {
const componentDescription = this.layoutDescription.components[componentId];
this.components[componentId] = new Component(componentId, componentDescription);
});
// Initialize components based on current gamepad state
this.updateFromGamepad();
}
get gripSpace() {
return this.xrInputSource.gripSpace;
}
get targetRaySpace() {
return this.xrInputSource.targetRaySpace;
}
/**
* @description Returns a subset of component data for simplified debugging
*/
get data() {
const data = [];
Object.values(this.components).forEach((component) => {
data.push(component.data);
});
return data;
}
/**
* @description Poll for updated data based on current gamepad state
*/
updateFromGamepad() {
Object.values(this.components).forEach((component) => {
component.updateFromGamepad(this.xrInputSource.gamepad);
});
}
}
export { Constants, MotionController, fetchProfile, fetchProfilesList };

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,167 @@
var Stats = function () {
var mode = 0;
var container = document.createElement( 'div' );
container.style.cssText = 'position:fixed;top:0;left:0;cursor:pointer;opacity:0.9;z-index:10000';
container.addEventListener( 'click', function ( event ) {
event.preventDefault();
showPanel( ++ mode % container.children.length );
}, false );
//
function addPanel( panel ) {
container.appendChild( panel.dom );
return panel;
}
function showPanel( id ) {
for ( var i = 0; i < container.children.length; i ++ ) {
container.children[ i ].style.display = i === id ? 'block' : 'none';
}
mode = id;
}
//
var beginTime = ( performance || Date ).now(), prevTime = beginTime, frames = 0;
var fpsPanel = addPanel( new Stats.Panel( 'FPS', '#0ff', '#002' ) );
var msPanel = addPanel( new Stats.Panel( 'MS', '#0f0', '#020' ) );
if ( self.performance && self.performance.memory ) {
var memPanel = addPanel( new Stats.Panel( 'MB', '#f08', '#201' ) );
}
showPanel( 0 );
return {
REVISION: 16,
dom: container,
addPanel: addPanel,
showPanel: showPanel,
begin: function () {
beginTime = ( performance || Date ).now();
},
end: function () {
frames ++;
var time = ( performance || Date ).now();
msPanel.update( time - beginTime, 200 );
if ( time >= prevTime + 1000 ) {
fpsPanel.update( ( frames * 1000 ) / ( time - prevTime ), 100 );
prevTime = time;
frames = 0;
if ( memPanel ) {
var memory = performance.memory;
memPanel.update( memory.usedJSHeapSize / 1048576, memory.jsHeapSizeLimit / 1048576 );
}
}
return time;
},
update: function () {
beginTime = this.end();
},
// Backwards Compatibility
domElement: container,
setMode: showPanel
};
};
Stats.Panel = function ( name, fg, bg ) {
var min = Infinity, max = 0, round = Math.round;
var PR = round( window.devicePixelRatio || 1 );
var WIDTH = 80 * PR, HEIGHT = 48 * PR,
TEXT_X = 3 * PR, TEXT_Y = 2 * PR,
GRAPH_X = 3 * PR, GRAPH_Y = 15 * PR,
GRAPH_WIDTH = 74 * PR, GRAPH_HEIGHT = 30 * PR;
var canvas = document.createElement( 'canvas' );
canvas.width = WIDTH;
canvas.height = HEIGHT;
canvas.style.cssText = 'width:80px;height:48px';
var context = canvas.getContext( '2d' );
context.font = 'bold ' + ( 9 * PR ) + 'px Helvetica,Arial,sans-serif';
context.textBaseline = 'top';
context.fillStyle = bg;
context.fillRect( 0, 0, WIDTH, HEIGHT );
context.fillStyle = fg;
context.fillText( name, TEXT_X, TEXT_Y );
context.fillRect( GRAPH_X, GRAPH_Y, GRAPH_WIDTH, GRAPH_HEIGHT );
context.fillStyle = bg;
context.globalAlpha = 0.9;
context.fillRect( GRAPH_X, GRAPH_Y, GRAPH_WIDTH, GRAPH_HEIGHT );
return {
dom: canvas,
update: function ( value, maxValue ) {
min = Math.min( min, value );
max = Math.max( max, value );
context.fillStyle = bg;
context.globalAlpha = 1;
context.fillRect( 0, 0, WIDTH, GRAPH_Y );
context.fillStyle = fg;
context.fillText( round( value ) + ' ' + name + ' (' + round( min ) + '-' + round( max ) + ')', TEXT_X, TEXT_Y );
context.drawImage( canvas, GRAPH_X + PR, GRAPH_Y, GRAPH_WIDTH - PR, GRAPH_HEIGHT, GRAPH_X, GRAPH_Y, GRAPH_WIDTH - PR, GRAPH_HEIGHT );
context.fillRect( GRAPH_X + GRAPH_WIDTH - PR, GRAPH_Y, PR, GRAPH_HEIGHT );
context.fillStyle = bg;
context.globalAlpha = 0.9;
context.fillRect( GRAPH_X + GRAPH_WIDTH - PR, GRAPH_Y, PR, round( ( 1 - ( value / maxValue ) ) * GRAPH_HEIGHT ) );
}
};
};
export default Stats;

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,24 @@
import { LineSegments2 } from '../lines/LineSegments2.js';
import { LineGeometry } from '../lines/LineGeometry.js';
import { LineMaterial } from '../lines/LineMaterial.js';
var Line2 = function ( geometry, material ) {
if ( geometry === undefined ) geometry = new LineGeometry();
if ( material === undefined ) material = new LineMaterial( { color: Math.random() * 0xffffff } );
LineSegments2.call( this, geometry, material );
this.type = 'Line2';
};
Line2.prototype = Object.assign( Object.create( LineSegments2.prototype ), {
constructor: Line2,
isLine2: true
} );
export { Line2 };

View File

@@ -0,0 +1,97 @@
import { LineSegmentsGeometry } from '../lines/LineSegmentsGeometry.js';
var LineGeometry = function () {
LineSegmentsGeometry.call( this );
this.type = 'LineGeometry';
};
LineGeometry.prototype = Object.assign( Object.create( LineSegmentsGeometry.prototype ), {
constructor: LineGeometry,
isLineGeometry: true,
setPositions: function ( array ) {
// converts [ x1, y1, z1, x2, y2, z2, ... ] to pairs format
var length = array.length - 3;
var points = new Float32Array( 2 * length );
for ( var i = 0; i < length; i += 3 ) {
points[ 2 * i ] = array[ i ];
points[ 2 * i + 1 ] = array[ i + 1 ];
points[ 2 * i + 2 ] = array[ i + 2 ];
points[ 2 * i + 3 ] = array[ i + 3 ];
points[ 2 * i + 4 ] = array[ i + 4 ];
points[ 2 * i + 5 ] = array[ i + 5 ];
}
LineSegmentsGeometry.prototype.setPositions.call( this, points );
return this;
},
setColors: function ( array ) {
// converts [ r1, g1, b1, r2, g2, b2, ... ] to pairs format
var length = array.length - 3;
var colors = new Float32Array( 2 * length );
for ( var i = 0; i < length; i += 3 ) {
colors[ 2 * i ] = array[ i ];
colors[ 2 * i + 1 ] = array[ i + 1 ];
colors[ 2 * i + 2 ] = array[ i + 2 ];
colors[ 2 * i + 3 ] = array[ i + 3 ];
colors[ 2 * i + 4 ] = array[ i + 4 ];
colors[ 2 * i + 5 ] = array[ i + 5 ];
}
LineSegmentsGeometry.prototype.setColors.call( this, colors );
return this;
},
fromLine: function ( line ) {
var geometry = line.geometry;
if ( geometry.isGeometry ) {
this.setPositions( geometry.vertices );
} else if ( geometry.isBufferGeometry ) {
this.setPositions( geometry.attributes.position.array ); // assumes non-indexed
}
// set colors, maybe
return this;
},
copy: function ( /* source */ ) {
// todo
return this;
}
} );
export { LineGeometry };

View File

@@ -0,0 +1,424 @@
import {
ShaderLib,
ShaderMaterial,
UniformsLib,
UniformsUtils,
Vector2
} from '../build/three.module.js';
/**
* parameters = {
* color: <hex>,
* linewidth: <float>,
* dashed: <boolean>,
* dashScale: <float>,
* dashSize: <float>,
* dashOffset: <float>,
* gapSize: <float>,
* resolution: <Vector2>, // to be set by renderer
* }
*/
UniformsLib.line = {
linewidth: { value: 1 },
resolution: { value: new Vector2( 1, 1 ) },
dashScale: { value: 1 },
dashSize: { value: 1 },
dashOffset: { value: 0 },
gapSize: { value: 1 }, // todo FIX - maybe change to totalSize
opacity: { value: 1 }
};
ShaderLib[ 'line' ] = {
uniforms: UniformsUtils.merge( [
UniformsLib.common,
UniformsLib.fog,
UniformsLib.line
] ),
vertexShader:
`
#include <common>
#include <color_pars_vertex>
#include <fog_pars_vertex>
#include <logdepthbuf_pars_vertex>
#include <clipping_planes_pars_vertex>
uniform float linewidth;
uniform vec2 resolution;
attribute vec3 instanceStart;
attribute vec3 instanceEnd;
attribute vec3 instanceColorStart;
attribute vec3 instanceColorEnd;
varying vec2 vUv;
#ifdef USE_DASH
uniform float dashScale;
attribute float instanceDistanceStart;
attribute float instanceDistanceEnd;
varying float vLineDistance;
#endif
void trimSegment( const in vec4 start, inout vec4 end ) {
// trim end segment so it terminates between the camera plane and the near plane
// conservative estimate of the near plane
float a = projectionMatrix[ 2 ][ 2 ]; // 3nd entry in 3th column
float b = projectionMatrix[ 3 ][ 2 ]; // 3nd entry in 4th column
float nearEstimate = - 0.5 * b / a;
float alpha = ( nearEstimate - start.z ) / ( end.z - start.z );
end.xyz = mix( start.xyz, end.xyz, alpha );
}
void main() {
#ifdef USE_COLOR
vColor.xyz = ( position.y < 0.5 ) ? instanceColorStart : instanceColorEnd;
#endif
#ifdef USE_DASH
vLineDistance = ( position.y < 0.5 ) ? dashScale * instanceDistanceStart : dashScale * instanceDistanceEnd;
#endif
float aspect = resolution.x / resolution.y;
vUv = uv;
// camera space
vec4 start = modelViewMatrix * vec4( instanceStart, 1.0 );
vec4 end = modelViewMatrix * vec4( instanceEnd, 1.0 );
// special case for perspective projection, and segments that terminate either in, or behind, the camera plane
// clearly the gpu firmware has a way of addressing this issue when projecting into ndc space
// but we need to perform ndc-space calculations in the shader, so we must address this issue directly
// perhaps there is a more elegant solution -- WestLangley
bool perspective = ( projectionMatrix[ 2 ][ 3 ] == - 1.0 ); // 4th entry in the 3rd column
if ( perspective ) {
if ( start.z < 0.0 && end.z >= 0.0 ) {
trimSegment( start, end );
} else if ( end.z < 0.0 && start.z >= 0.0 ) {
trimSegment( end, start );
}
}
// clip space
vec4 clipStart = projectionMatrix * start;
vec4 clipEnd = projectionMatrix * end;
// ndc space
vec2 ndcStart = clipStart.xy / clipStart.w;
vec2 ndcEnd = clipEnd.xy / clipEnd.w;
// direction
vec2 dir = ndcEnd - ndcStart;
// account for clip-space aspect ratio
dir.x *= aspect;
dir = normalize( dir );
// perpendicular to dir
vec2 offset = vec2( dir.y, - dir.x );
// undo aspect ratio adjustment
dir.x /= aspect;
offset.x /= aspect;
// sign flip
if ( position.x < 0.0 ) offset *= - 1.0;
// endcaps
if ( position.y < 0.0 ) {
offset += - dir;
} else if ( position.y > 1.0 ) {
offset += dir;
}
// adjust for linewidth
offset *= linewidth;
// adjust for clip-space to screen-space conversion // maybe resolution should be based on viewport ...
offset /= resolution.y;
// select end
vec4 clip = ( position.y < 0.5 ) ? clipStart : clipEnd;
// back to clip space
offset *= clip.w;
clip.xy += offset;
gl_Position = clip;
vec4 mvPosition = ( position.y < 0.5 ) ? start : end; // this is an approximation
#include <logdepthbuf_vertex>
#include <clipping_planes_vertex>
#include <fog_vertex>
}
`,
fragmentShader:
`
uniform vec3 diffuse;
uniform float opacity;
#ifdef USE_DASH
uniform float dashSize;
uniform float dashOffset;
uniform float gapSize;
#endif
varying float vLineDistance;
#include <common>
#include <color_pars_fragment>
#include <fog_pars_fragment>
#include <logdepthbuf_pars_fragment>
#include <clipping_planes_pars_fragment>
varying vec2 vUv;
void main() {
#include <clipping_planes_fragment>
#ifdef USE_DASH
if ( vUv.y < - 1.0 || vUv.y > 1.0 ) discard; // discard endcaps
if ( mod( vLineDistance + dashOffset, dashSize + gapSize ) > dashSize ) discard; // todo - FIX
#endif
if ( abs( vUv.y ) > 1.0 ) {
float a = vUv.x;
float b = ( vUv.y > 0.0 ) ? vUv.y - 1.0 : vUv.y + 1.0;
float len2 = a * a + b * b;
if ( len2 > 1.0 ) discard;
}
vec4 diffuseColor = vec4( diffuse, opacity );
#include <logdepthbuf_fragment>
#include <color_fragment>
gl_FragColor = vec4( diffuseColor.rgb, diffuseColor.a );
#include <tonemapping_fragment>
#include <encodings_fragment>
#include <fog_fragment>
#include <premultiplied_alpha_fragment>
}
`
};
var LineMaterial = function ( parameters ) {
ShaderMaterial.call( this, {
type: 'LineMaterial',
uniforms: UniformsUtils.clone( ShaderLib[ 'line' ].uniforms ),
vertexShader: ShaderLib[ 'line' ].vertexShader,
fragmentShader: ShaderLib[ 'line' ].fragmentShader,
clipping: true // required for clipping support
} );
this.dashed = false;
Object.defineProperties( this, {
color: {
enumerable: true,
get: function () {
return this.uniforms.diffuse.value;
},
set: function ( value ) {
this.uniforms.diffuse.value = value;
}
},
linewidth: {
enumerable: true,
get: function () {
return this.uniforms.linewidth.value;
},
set: function ( value ) {
this.uniforms.linewidth.value = value;
}
},
dashScale: {
enumerable: true,
get: function () {
return this.uniforms.dashScale.value;
},
set: function ( value ) {
this.uniforms.dashScale.value = value;
}
},
dashSize: {
enumerable: true,
get: function () {
return this.uniforms.dashSize.value;
},
set: function ( value ) {
this.uniforms.dashSize.value = value;
}
},
dashOffset: {
enumerable: true,
get: function () {
return this.uniforms.dashOffset.value;
},
set: function ( value ) {
this.uniforms.dashOffset.value = value;
}
},
gapSize: {
enumerable: true,
get: function () {
return this.uniforms.gapSize.value;
},
set: function ( value ) {
this.uniforms.gapSize.value = value;
}
},
opacity: {
enumerable: true,
get: function () {
return this.uniforms.opacity.value;
},
set: function ( value ) {
this.uniforms.opacity.value = value;
}
},
resolution: {
enumerable: true,
get: function () {
return this.uniforms.resolution.value;
},
set: function ( value ) {
this.uniforms.resolution.value.copy( value );
}
}
} );
this.setValues( parameters );
};
LineMaterial.prototype = Object.create( ShaderMaterial.prototype );
LineMaterial.prototype.constructor = LineMaterial;
LineMaterial.prototype.isLineMaterial = true;
export { LineMaterial };

View File

@@ -0,0 +1,209 @@
import {
InstancedInterleavedBuffer,
InterleavedBufferAttribute,
Line3,
MathUtils,
Matrix4,
Mesh,
Vector3,
Vector4
} from '../build/three.module.js';
import { LineSegmentsGeometry } from '../lines/LineSegmentsGeometry.js';
import { LineMaterial } from '../lines/LineMaterial.js';
var LineSegments2 = function ( geometry, material ) {
if ( geometry === undefined ) geometry = new LineSegmentsGeometry();
if ( material === undefined ) material = new LineMaterial( { color: Math.random() * 0xffffff } );
Mesh.call( this, geometry, material );
this.type = 'LineSegments2';
};
LineSegments2.prototype = Object.assign( Object.create( Mesh.prototype ), {
constructor: LineSegments2,
isLineSegments2: true,
computeLineDistances: ( function () { // for backwards-compatability, but could be a method of LineSegmentsGeometry...
var start = new Vector3();
var end = new Vector3();
return function computeLineDistances() {
var geometry = this.geometry;
var instanceStart = geometry.attributes.instanceStart;
var instanceEnd = geometry.attributes.instanceEnd;
var lineDistances = new Float32Array( 2 * instanceStart.data.count );
for ( var i = 0, j = 0, l = instanceStart.data.count; i < l; i ++, j += 2 ) {
start.fromBufferAttribute( instanceStart, i );
end.fromBufferAttribute( instanceEnd, i );
lineDistances[ j ] = ( j === 0 ) ? 0 : lineDistances[ j - 1 ];
lineDistances[ j + 1 ] = lineDistances[ j ] + start.distanceTo( end );
}
var instanceDistanceBuffer = new InstancedInterleavedBuffer( lineDistances, 2, 1 ); // d0, d1
geometry.setAttribute( 'instanceDistanceStart', new InterleavedBufferAttribute( instanceDistanceBuffer, 1, 0 ) ); // d0
geometry.setAttribute( 'instanceDistanceEnd', new InterleavedBufferAttribute( instanceDistanceBuffer, 1, 1 ) ); // d1
return this;
};
}() ),
raycast: ( function () {
var start = new Vector4();
var end = new Vector4();
var ssOrigin = new Vector4();
var ssOrigin3 = new Vector3();
var mvMatrix = new Matrix4();
var line = new Line3();
var closestPoint = new Vector3();
return function raycast( raycaster, intersects ) {
if ( raycaster.camera === null ) {
console.error( 'LineSegments2: "Raycaster.camera" needs to be set in order to raycast against LineSegments2.' );
}
var threshold = ( raycaster.params.Line2 !== undefined ) ? raycaster.params.Line2.threshold || 0 : 0;
var ray = raycaster.ray;
var camera = raycaster.camera;
var projectionMatrix = camera.projectionMatrix;
var geometry = this.geometry;
var material = this.material;
var resolution = material.resolution;
var lineWidth = material.linewidth + threshold;
var instanceStart = geometry.attributes.instanceStart;
var instanceEnd = geometry.attributes.instanceEnd;
// pick a point 1 unit out along the ray to avoid the ray origin
// sitting at the camera origin which will cause "w" to be 0 when
// applying the projection matrix.
ray.at( 1, ssOrigin );
// ndc space [ - 1.0, 1.0 ]
ssOrigin.w = 1;
ssOrigin.applyMatrix4( camera.matrixWorldInverse );
ssOrigin.applyMatrix4( projectionMatrix );
ssOrigin.multiplyScalar( 1 / ssOrigin.w );
// screen space
ssOrigin.x *= resolution.x / 2;
ssOrigin.y *= resolution.y / 2;
ssOrigin.z = 0;
ssOrigin3.copy( ssOrigin );
var matrixWorld = this.matrixWorld;
mvMatrix.multiplyMatrices( camera.matrixWorldInverse, matrixWorld );
for ( var i = 0, l = instanceStart.count; i < l; i ++ ) {
start.fromBufferAttribute( instanceStart, i );
end.fromBufferAttribute( instanceEnd, i );
start.w = 1;
end.w = 1;
// camera space
start.applyMatrix4( mvMatrix );
end.applyMatrix4( mvMatrix );
// clip space
start.applyMatrix4( projectionMatrix );
end.applyMatrix4( projectionMatrix );
// ndc space [ - 1.0, 1.0 ]
start.multiplyScalar( 1 / start.w );
end.multiplyScalar( 1 / end.w );
// skip the segment if it's outside the camera near and far planes
var isBehindCameraNear = start.z < - 1 && end.z < - 1;
var isPastCameraFar = start.z > 1 && end.z > 1;
if ( isBehindCameraNear || isPastCameraFar ) {
continue;
}
// screen space
start.x *= resolution.x / 2;
start.y *= resolution.y / 2;
end.x *= resolution.x / 2;
end.y *= resolution.y / 2;
// create 2d segment
line.start.copy( start );
line.start.z = 0;
line.end.copy( end );
line.end.z = 0;
// get closest point on ray to segment
var param = line.closestPointToPointParameter( ssOrigin3, true );
line.at( param, closestPoint );
// check if the intersection point is within clip space
var zPos = MathUtils.lerp( start.z, end.z, param );
var isInClipSpace = zPos >= - 1 && zPos <= 1;
var isInside = ssOrigin3.distanceTo( closestPoint ) < lineWidth * 0.5;
if ( isInClipSpace && isInside ) {
line.start.fromBufferAttribute( instanceStart, i );
line.end.fromBufferAttribute( instanceEnd, i );
line.start.applyMatrix4( matrixWorld );
line.end.applyMatrix4( matrixWorld );
var pointOnLine = new Vector3();
var point = new Vector3();
ray.distanceSqToSegment( line.start, line.end, point, pointOnLine );
intersects.push( {
point: point,
pointOnLine: pointOnLine,
distance: ray.origin.distanceTo( point ),
object: this,
face: null,
faceIndex: i,
uv: null,
uv2: null,
} );
}
}
};
}() )
} );
export { LineSegments2 };

View File

@@ -0,0 +1,260 @@
import {
Box3,
Float32BufferAttribute,
InstancedBufferGeometry,
InstancedInterleavedBuffer,
InterleavedBufferAttribute,
Sphere,
Vector3,
WireframeGeometry
} from '../build/three.module.js';
var LineSegmentsGeometry = function () {
InstancedBufferGeometry.call( this );
this.type = 'LineSegmentsGeometry';
var positions = [ - 1, 2, 0, 1, 2, 0, - 1, 1, 0, 1, 1, 0, - 1, 0, 0, 1, 0, 0, - 1, - 1, 0, 1, - 1, 0 ];
var uvs = [ - 1, 2, 1, 2, - 1, 1, 1, 1, - 1, - 1, 1, - 1, - 1, - 2, 1, - 2 ];
var index = [ 0, 2, 1, 2, 3, 1, 2, 4, 3, 4, 5, 3, 4, 6, 5, 6, 7, 5 ];
this.setIndex( index );
this.setAttribute( 'position', new Float32BufferAttribute( positions, 3 ) );
this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) );
};
LineSegmentsGeometry.prototype = Object.assign( Object.create( InstancedBufferGeometry.prototype ), {
constructor: LineSegmentsGeometry,
isLineSegmentsGeometry: true,
applyMatrix4: function ( matrix ) {
var start = this.attributes.instanceStart;
var end = this.attributes.instanceEnd;
if ( start !== undefined ) {
start.applyMatrix4( matrix );
end.applyMatrix4( matrix );
start.needsUpdate = true;
}
if ( this.boundingBox !== null ) {
this.computeBoundingBox();
}
if ( this.boundingSphere !== null ) {
this.computeBoundingSphere();
}
return this;
},
setPositions: function ( array ) {
var lineSegments;
if ( array instanceof Float32Array ) {
lineSegments = array;
} else if ( Array.isArray( array ) ) {
lineSegments = new Float32Array( array );
}
var instanceBuffer = new InstancedInterleavedBuffer( lineSegments, 6, 1 ); // xyz, xyz
this.setAttribute( 'instanceStart', new InterleavedBufferAttribute( instanceBuffer, 3, 0 ) ); // xyz
this.setAttribute( 'instanceEnd', new InterleavedBufferAttribute( instanceBuffer, 3, 3 ) ); // xyz
//
this.computeBoundingBox();
this.computeBoundingSphere();
return this;
},
setColors: function ( array ) {
var colors;
if ( array instanceof Float32Array ) {
colors = array;
} else if ( Array.isArray( array ) ) {
colors = new Float32Array( array );
}
var instanceColorBuffer = new InstancedInterleavedBuffer( colors, 6, 1 ); // rgb, rgb
this.setAttribute( 'instanceColorStart', new InterleavedBufferAttribute( instanceColorBuffer, 3, 0 ) ); // rgb
this.setAttribute( 'instanceColorEnd', new InterleavedBufferAttribute( instanceColorBuffer, 3, 3 ) ); // rgb
return this;
},
fromWireframeGeometry: function ( geometry ) {
this.setPositions( geometry.attributes.position.array );
return this;
},
fromEdgesGeometry: function ( geometry ) {
this.setPositions( geometry.attributes.position.array );
return this;
},
fromMesh: function ( mesh ) {
this.fromWireframeGeometry( new WireframeGeometry( mesh.geometry ) );
// set colors, maybe
return this;
},
fromLineSegments: function ( lineSegments ) {
var geometry = lineSegments.geometry;
if ( geometry.isGeometry ) {
this.setPositions( geometry.vertices );
} else if ( geometry.isBufferGeometry ) {
this.setPositions( geometry.attributes.position.array ); // assumes non-indexed
}
// set colors, maybe
return this;
},
computeBoundingBox: function () {
var box = new Box3();
return function computeBoundingBox() {
if ( this.boundingBox === null ) {
this.boundingBox = new Box3();
}
var start = this.attributes.instanceStart;
var end = this.attributes.instanceEnd;
if ( start !== undefined && end !== undefined ) {
this.boundingBox.setFromBufferAttribute( start );
box.setFromBufferAttribute( end );
this.boundingBox.union( box );
}
};
}(),
computeBoundingSphere: function () {
var vector = new Vector3();
return function computeBoundingSphere() {
if ( this.boundingSphere === null ) {
this.boundingSphere = new Sphere();
}
if ( this.boundingBox === null ) {
this.computeBoundingBox();
}
var start = this.attributes.instanceStart;
var end = this.attributes.instanceEnd;
if ( start !== undefined && end !== undefined ) {
var center = this.boundingSphere.center;
this.boundingBox.getCenter( center );
var maxRadiusSq = 0;
for ( var i = 0, il = start.count; i < il; i ++ ) {
vector.fromBufferAttribute( start, i );
maxRadiusSq = Math.max( maxRadiusSq, center.distanceToSquared( vector ) );
vector.fromBufferAttribute( end, i );
maxRadiusSq = Math.max( maxRadiusSq, center.distanceToSquared( vector ) );
}
this.boundingSphere.radius = Math.sqrt( maxRadiusSq );
if ( isNaN( this.boundingSphere.radius ) ) {
console.error( 'THREE.LineSegmentsGeometry.computeBoundingSphere(): Computed radius is NaN. The instanced position data is likely to have NaN values.', this );
}
}
};
}(),
toJSON: function () {
// todo
},
applyMatrix: function ( matrix ) {
console.warn( 'THREE.LineSegmentsGeometry: applyMatrix() has been renamed to applyMatrix4().' );
return this.applyMatrix4( matrix );
}
} );
export { LineSegmentsGeometry };

View File

@@ -0,0 +1,63 @@
import {
InstancedInterleavedBuffer,
InterleavedBufferAttribute,
Mesh,
Vector3
} from '../build/three.module.js';
import { LineSegmentsGeometry } from '../lines/LineSegmentsGeometry.js';
import { LineMaterial } from '../lines/LineMaterial.js';
var Wireframe = function ( geometry, material ) {
Mesh.call( this );
this.type = 'Wireframe';
this.geometry = geometry !== undefined ? geometry : new LineSegmentsGeometry();
this.material = material !== undefined ? material : new LineMaterial( { color: Math.random() * 0xffffff } );
};
Wireframe.prototype = Object.assign( Object.create( Mesh.prototype ), {
constructor: Wireframe,
isWireframe: true,
computeLineDistances: ( function () { // for backwards-compatability, but could be a method of LineSegmentsGeometry...
var start = new Vector3();
var end = new Vector3();
return function computeLineDistances() {
var geometry = this.geometry;
var instanceStart = geometry.attributes.instanceStart;
var instanceEnd = geometry.attributes.instanceEnd;
var lineDistances = new Float32Array( 2 * instanceStart.data.count );
for ( var i = 0, j = 0, l = instanceStart.data.count; i < l; i ++, j += 2 ) {
start.fromBufferAttribute( instanceStart, i );
end.fromBufferAttribute( instanceEnd, i );
lineDistances[ j ] = ( j === 0 ) ? 0 : lineDistances[ j - 1 ];
lineDistances[ j + 1 ] = lineDistances[ j ] + start.distanceTo( end );
}
var instanceDistanceBuffer = new InstancedInterleavedBuffer( lineDistances, 2, 1 ); // d0, d1
geometry.setAttribute( 'instanceDistanceStart', new InterleavedBufferAttribute( instanceDistanceBuffer, 1, 0 ) ); // d0
geometry.setAttribute( 'instanceDistanceEnd', new InterleavedBufferAttribute( instanceDistanceBuffer, 1, 1 ) ); // d1
return this;
};
}() )
} );
export { Wireframe };

View File

@@ -0,0 +1,26 @@
import {
WireframeGeometry
} from '../build/three.module.js';
import { LineSegmentsGeometry } from '../lines/LineSegmentsGeometry.js';
var WireframeGeometry2 = function ( geometry ) {
LineSegmentsGeometry.call( this );
this.type = 'WireframeGeometry2';
this.fromWireframeGeometry( new WireframeGeometry( geometry ) );
// set colors, maybe
};
WireframeGeometry2.prototype = Object.assign( Object.create( LineSegmentsGeometry.prototype ), {
constructor: WireframeGeometry2,
isWireframeGeometry2: true
} );
export { WireframeGeometry2 };

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,522 @@
import {
BufferGeometry,
Color,
FileLoader,
Float32BufferAttribute,
Group,
Loader,
LoaderUtils,
Mesh,
MeshPhongMaterial
} from '../../../build/three.module.js';
import { JSZip } from '../libs/jszip.module.min.js';
/**
* Description: Early release of an AMF Loader following the pattern of the
* example loaders in the three.js project.
*
* More information about the AMF format: http://amf.wikispaces.com
*
* Usage:
* var loader = new AMFLoader();
* loader.load('/path/to/project.amf', function(objecttree) {
* scene.add(objecttree);
* });
*
* Materials now supported, material colors supported
* Zip support, requires jszip
* No constellation support (yet)!
*
*/
var AMFLoader = function ( manager ) {
Loader.call( this, manager );
};
AMFLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
constructor: AMFLoader,
load: function ( url, onLoad, onProgress, onError ) {
var scope = this;
var loader = new FileLoader( scope.manager );
loader.setPath( scope.path );
loader.setResponseType( 'arraybuffer' );
loader.setRequestHeader( scope.requestHeader );
loader.setWithCredentials( scope.withCredentials );
loader.load( url, function ( text ) {
try {
onLoad( scope.parse( text ) );
} catch ( e ) {
if ( onError ) {
onError( e );
} else {
console.error( e );
}
scope.manager.itemError( url );
}
}, onProgress, onError );
},
parse: function ( data ) {
function loadDocument( data ) {
var view = new DataView( data );
var magic = String.fromCharCode( view.getUint8( 0 ), view.getUint8( 1 ) );
if ( magic === 'PK' ) {
var zip = null;
var file = null;
console.log( 'THREE.AMFLoader: Loading Zip' );
try {
zip = new JSZip( data ); // eslint-disable-line no-undef
} catch ( e ) {
if ( e instanceof ReferenceError ) {
console.log( 'THREE.AMFLoader: jszip missing and file is compressed.' );
return null;
}
}
for ( file in zip.files ) {
if ( file.toLowerCase().substr( - 4 ) === '.amf' ) {
break;
}
}
console.log( 'THREE.AMFLoader: Trying to load file asset: ' + file );
view = new DataView( zip.file( file ).asArrayBuffer() );
}
var fileText = LoaderUtils.decodeText( view );
var xmlData = new DOMParser().parseFromString( fileText, 'application/xml' );
if ( xmlData.documentElement.nodeName.toLowerCase() !== 'amf' ) {
console.log( 'THREE.AMFLoader: Error loading AMF - no AMF document found.' );
return null;
}
return xmlData;
}
function loadDocumentScale( node ) {
var scale = 1.0;
var unit = 'millimeter';
if ( node.documentElement.attributes.unit !== undefined ) {
unit = node.documentElement.attributes.unit.value.toLowerCase();
}
var scaleUnits = {
millimeter: 1.0,
inch: 25.4,
feet: 304.8,
meter: 1000.0,
micron: 0.001
};
if ( scaleUnits[ unit ] !== undefined ) {
scale = scaleUnits[ unit ];
}
console.log( 'THREE.AMFLoader: Unit scale: ' + scale );
return scale;
}
function loadMaterials( node ) {
var matName = 'AMF Material';
var matId = node.attributes.id.textContent;
var color = { r: 1.0, g: 1.0, b: 1.0, a: 1.0 };
var loadedMaterial = null;
for ( var i = 0; i < node.childNodes.length; i ++ ) {
var matChildEl = node.childNodes[ i ];
if ( matChildEl.nodeName === 'metadata' && matChildEl.attributes.type !== undefined ) {
if ( matChildEl.attributes.type.value === 'name' ) {
matName = matChildEl.textContent;
}
} else if ( matChildEl.nodeName === 'color' ) {
color = loadColor( matChildEl );
}
}
loadedMaterial = new MeshPhongMaterial( {
flatShading: true,
color: new Color( color.r, color.g, color.b ),
name: matName
} );
if ( color.a !== 1.0 ) {
loadedMaterial.transparent = true;
loadedMaterial.opacity = color.a;
}
return { id: matId, material: loadedMaterial };
}
function loadColor( node ) {
var color = { r: 1.0, g: 1.0, b: 1.0, a: 1.0 };
for ( var i = 0; i < node.childNodes.length; i ++ ) {
var matColor = node.childNodes[ i ];
if ( matColor.nodeName === 'r' ) {
color.r = matColor.textContent;
} else if ( matColor.nodeName === 'g' ) {
color.g = matColor.textContent;
} else if ( matColor.nodeName === 'b' ) {
color.b = matColor.textContent;
} else if ( matColor.nodeName === 'a' ) {
color.a = matColor.textContent;
}
}
return color;
}
function loadMeshVolume( node ) {
var volume = { name: '', triangles: [], materialid: null };
var currVolumeNode = node.firstElementChild;
if ( node.attributes.materialid !== undefined ) {
volume.materialId = node.attributes.materialid.nodeValue;
}
while ( currVolumeNode ) {
if ( currVolumeNode.nodeName === 'metadata' ) {
if ( currVolumeNode.attributes.type !== undefined ) {
if ( currVolumeNode.attributes.type.value === 'name' ) {
volume.name = currVolumeNode.textContent;
}
}
} else if ( currVolumeNode.nodeName === 'triangle' ) {
var v1 = currVolumeNode.getElementsByTagName( 'v1' )[ 0 ].textContent;
var v2 = currVolumeNode.getElementsByTagName( 'v2' )[ 0 ].textContent;
var v3 = currVolumeNode.getElementsByTagName( 'v3' )[ 0 ].textContent;
volume.triangles.push( v1, v2, v3 );
}
currVolumeNode = currVolumeNode.nextElementSibling;
}
return volume;
}
function loadMeshVertices( node ) {
var vertArray = [];
var normalArray = [];
var currVerticesNode = node.firstElementChild;
while ( currVerticesNode ) {
if ( currVerticesNode.nodeName === 'vertex' ) {
var vNode = currVerticesNode.firstElementChild;
while ( vNode ) {
if ( vNode.nodeName === 'coordinates' ) {
var x = vNode.getElementsByTagName( 'x' )[ 0 ].textContent;
var y = vNode.getElementsByTagName( 'y' )[ 0 ].textContent;
var z = vNode.getElementsByTagName( 'z' )[ 0 ].textContent;
vertArray.push( x, y, z );
} else if ( vNode.nodeName === 'normal' ) {
var nx = vNode.getElementsByTagName( 'nx' )[ 0 ].textContent;
var ny = vNode.getElementsByTagName( 'ny' )[ 0 ].textContent;
var nz = vNode.getElementsByTagName( 'nz' )[ 0 ].textContent;
normalArray.push( nx, ny, nz );
}
vNode = vNode.nextElementSibling;
}
}
currVerticesNode = currVerticesNode.nextElementSibling;
}
return { 'vertices': vertArray, 'normals': normalArray };
}
function loadObject( node ) {
var objId = node.attributes.id.textContent;
var loadedObject = { name: 'amfobject', meshes: [] };
var currColor = null;
var currObjNode = node.firstElementChild;
while ( currObjNode ) {
if ( currObjNode.nodeName === 'metadata' ) {
if ( currObjNode.attributes.type !== undefined ) {
if ( currObjNode.attributes.type.value === 'name' ) {
loadedObject.name = currObjNode.textContent;
}
}
} else if ( currObjNode.nodeName === 'color' ) {
currColor = loadColor( currObjNode );
} else if ( currObjNode.nodeName === 'mesh' ) {
var currMeshNode = currObjNode.firstElementChild;
var mesh = { vertices: [], normals: [], volumes: [], color: currColor };
while ( currMeshNode ) {
if ( currMeshNode.nodeName === 'vertices' ) {
var loadedVertices = loadMeshVertices( currMeshNode );
mesh.normals = mesh.normals.concat( loadedVertices.normals );
mesh.vertices = mesh.vertices.concat( loadedVertices.vertices );
} else if ( currMeshNode.nodeName === 'volume' ) {
mesh.volumes.push( loadMeshVolume( currMeshNode ) );
}
currMeshNode = currMeshNode.nextElementSibling;
}
loadedObject.meshes.push( mesh );
}
currObjNode = currObjNode.nextElementSibling;
}
return { 'id': objId, 'obj': loadedObject };
}
var xmlData = loadDocument( data );
var amfName = '';
var amfAuthor = '';
var amfScale = loadDocumentScale( xmlData );
var amfMaterials = {};
var amfObjects = {};
var childNodes = xmlData.documentElement.childNodes;
var i, j;
for ( i = 0; i < childNodes.length; i ++ ) {
var child = childNodes[ i ];
if ( child.nodeName === 'metadata' ) {
if ( child.attributes.type !== undefined ) {
if ( child.attributes.type.value === 'name' ) {
amfName = child.textContent;
} else if ( child.attributes.type.value === 'author' ) {
amfAuthor = child.textContent;
}
}
} else if ( child.nodeName === 'material' ) {
var loadedMaterial = loadMaterials( child );
amfMaterials[ loadedMaterial.id ] = loadedMaterial.material;
} else if ( child.nodeName === 'object' ) {
var loadedObject = loadObject( child );
amfObjects[ loadedObject.id ] = loadedObject.obj;
}
}
var sceneObject = new Group();
var defaultMaterial = new MeshPhongMaterial( { color: 0xaaaaff, flatShading: true } );
sceneObject.name = amfName;
sceneObject.userData.author = amfAuthor;
sceneObject.userData.loader = 'AMF';
for ( var id in amfObjects ) {
var part = amfObjects[ id ];
var meshes = part.meshes;
var newObject = new Group();
newObject.name = part.name || '';
for ( i = 0; i < meshes.length; i ++ ) {
var objDefaultMaterial = defaultMaterial;
var mesh = meshes[ i ];
var vertices = new Float32BufferAttribute( mesh.vertices, 3 );
var normals = null;
if ( mesh.normals.length ) {
normals = new Float32BufferAttribute( mesh.normals, 3 );
}
if ( mesh.color ) {
var color = mesh.color;
objDefaultMaterial = defaultMaterial.clone();
objDefaultMaterial.color = new Color( color.r, color.g, color.b );
if ( color.a !== 1.0 ) {
objDefaultMaterial.transparent = true;
objDefaultMaterial.opacity = color.a;
}
}
var volumes = mesh.volumes;
for ( j = 0; j < volumes.length; j ++ ) {
var volume = volumes[ j ];
var newGeometry = new BufferGeometry();
var material = objDefaultMaterial;
newGeometry.setIndex( volume.triangles );
newGeometry.setAttribute( 'position', vertices.clone() );
if ( normals ) {
newGeometry.setAttribute( 'normal', normals.clone() );
}
if ( amfMaterials[ volume.materialId ] !== undefined ) {
material = amfMaterials[ volume.materialId ];
}
newGeometry.scale( amfScale, amfScale, amfScale );
newObject.add( new Mesh( newGeometry, material.clone() ) );
}
}
sceneObject.add( newObject );
}
return sceneObject;
}
} );
export { AMFLoader };

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,439 @@
import {
AnimationClip,
Bone,
FileLoader,
Loader,
Quaternion,
QuaternionKeyframeTrack,
Skeleton,
Vector3,
VectorKeyframeTrack
} from '../../../build/three.module.js';
/**
* Description: reads BVH files and outputs a single Skeleton and an AnimationClip
*
* Currently only supports bvh files containing a single root.
*
*/
var BVHLoader = function ( manager ) {
Loader.call( this, manager );
this.animateBonePositions = true;
this.animateBoneRotations = true;
};
BVHLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
constructor: BVHLoader,
load: function ( url, onLoad, onProgress, onError ) {
var scope = this;
var loader = new FileLoader( scope.manager );
loader.setPath( scope.path );
loader.setRequestHeader( scope.requestHeader );
loader.setWithCredentials( scope.withCredentials );
loader.load( url, function ( text ) {
try {
onLoad( scope.parse( text ) );
} catch ( e ) {
if ( onError ) {
onError( e );
} else {
console.error( e );
}
scope.manager.itemError( url );
}
}, onProgress, onError );
},
parse: function ( text ) {
/*
reads a string array (lines) from a BVH file
and outputs a skeleton structure including motion data
returns thee root node:
{ name: '', channels: [], children: [] }
*/
function readBvh( lines ) {
// read model structure
if ( nextLine( lines ) !== 'HIERARCHY' ) {
console.error( 'THREE.BVHLoader: HIERARCHY expected.' );
}
var list = []; // collects flat array of all bones
var root = readNode( lines, nextLine( lines ), list );
// read motion data
if ( nextLine( lines ) !== 'MOTION' ) {
console.error( 'THREE.BVHLoader: MOTION expected.' );
}
// number of frames
var tokens = nextLine( lines ).split( /[\s]+/ );
var numFrames = parseInt( tokens[ 1 ] );
if ( isNaN( numFrames ) ) {
console.error( 'THREE.BVHLoader: Failed to read number of frames.' );
}
// frame time
tokens = nextLine( lines ).split( /[\s]+/ );
var frameTime = parseFloat( tokens[ 2 ] );
if ( isNaN( frameTime ) ) {
console.error( 'THREE.BVHLoader: Failed to read frame time.' );
}
// read frame data line by line
for ( var i = 0; i < numFrames; i ++ ) {
tokens = nextLine( lines ).split( /[\s]+/ );
readFrameData( tokens, i * frameTime, root );
}
return list;
}
/*
Recursively reads data from a single frame into the bone hierarchy.
The passed bone hierarchy has to be structured in the same order as the BVH file.
keyframe data is stored in bone.frames.
- data: splitted string array (frame values), values are shift()ed so
this should be empty after parsing the whole hierarchy.
- frameTime: playback time for this keyframe.
- bone: the bone to read frame data from.
*/
function readFrameData( data, frameTime, bone ) {
// end sites have no motion data
if ( bone.type === 'ENDSITE' ) return;
// add keyframe
var keyframe = {
time: frameTime,
position: new Vector3(),
rotation: new Quaternion()
};
bone.frames.push( keyframe );
var quat = new Quaternion();
var vx = new Vector3( 1, 0, 0 );
var vy = new Vector3( 0, 1, 0 );
var vz = new Vector3( 0, 0, 1 );
// parse values for each channel in node
for ( var i = 0; i < bone.channels.length; i ++ ) {
switch ( bone.channels[ i ] ) {
case 'Xposition':
keyframe.position.x = parseFloat( data.shift().trim() );
break;
case 'Yposition':
keyframe.position.y = parseFloat( data.shift().trim() );
break;
case 'Zposition':
keyframe.position.z = parseFloat( data.shift().trim() );
break;
case 'Xrotation':
quat.setFromAxisAngle( vx, parseFloat( data.shift().trim() ) * Math.PI / 180 );
keyframe.rotation.multiply( quat );
break;
case 'Yrotation':
quat.setFromAxisAngle( vy, parseFloat( data.shift().trim() ) * Math.PI / 180 );
keyframe.rotation.multiply( quat );
break;
case 'Zrotation':
quat.setFromAxisAngle( vz, parseFloat( data.shift().trim() ) * Math.PI / 180 );
keyframe.rotation.multiply( quat );
break;
default:
console.warn( 'THREE.BVHLoader: Invalid channel type.' );
}
}
// parse child nodes
for ( var i = 0; i < bone.children.length; i ++ ) {
readFrameData( data, frameTime, bone.children[ i ] );
}
}
/*
Recursively parses the HIERACHY section of the BVH file
- lines: all lines of the file. lines are consumed as we go along.
- firstline: line containing the node type and name e.g. 'JOINT hip'
- list: collects a flat list of nodes
returns: a BVH node including children
*/
function readNode( lines, firstline, list ) {
var node = { name: '', type: '', frames: [] };
list.push( node );
// parse node type and name
var tokens = firstline.split( /[\s]+/ );
if ( tokens[ 0 ].toUpperCase() === 'END' && tokens[ 1 ].toUpperCase() === 'SITE' ) {
node.type = 'ENDSITE';
node.name = 'ENDSITE'; // bvh end sites have no name
} else {
node.name = tokens[ 1 ];
node.type = tokens[ 0 ].toUpperCase();
}
if ( nextLine( lines ) !== '{' ) {
console.error( 'THREE.BVHLoader: Expected opening { after type & name' );
}
// parse OFFSET
tokens = nextLine( lines ).split( /[\s]+/ );
if ( tokens[ 0 ] !== 'OFFSET' ) {
console.error( 'THREE.BVHLoader: Expected OFFSET but got: ' + tokens[ 0 ] );
}
if ( tokens.length !== 4 ) {
console.error( 'THREE.BVHLoader: Invalid number of values for OFFSET.' );
}
var offset = new Vector3(
parseFloat( tokens[ 1 ] ),
parseFloat( tokens[ 2 ] ),
parseFloat( tokens[ 3 ] )
);
if ( isNaN( offset.x ) || isNaN( offset.y ) || isNaN( offset.z ) ) {
console.error( 'THREE.BVHLoader: Invalid values of OFFSET.' );
}
node.offset = offset;
// parse CHANNELS definitions
if ( node.type !== 'ENDSITE' ) {
tokens = nextLine( lines ).split( /[\s]+/ );
if ( tokens[ 0 ] !== 'CHANNELS' ) {
console.error( 'THREE.BVHLoader: Expected CHANNELS definition.' );
}
var numChannels = parseInt( tokens[ 1 ] );
node.channels = tokens.splice( 2, numChannels );
node.children = [];
}
// read children
while ( true ) {
var line = nextLine( lines );
if ( line === '}' ) {
return node;
} else {
node.children.push( readNode( lines, line, list ) );
}
}
}
/*
recursively converts the internal bvh node structure to a Bone hierarchy
source: the bvh root node
list: pass an empty array, collects a flat list of all converted THREE.Bones
returns the root Bone
*/
function toTHREEBone( source, list ) {
var bone = new Bone();
list.push( bone );
bone.position.add( source.offset );
bone.name = source.name;
if ( source.type !== 'ENDSITE' ) {
for ( var i = 0; i < source.children.length; i ++ ) {
bone.add( toTHREEBone( source.children[ i ], list ) );
}
}
return bone;
}
/*
builds a AnimationClip from the keyframe data saved in each bone.
bone: bvh root node
returns: a AnimationClip containing position and quaternion tracks
*/
function toTHREEAnimation( bones ) {
var tracks = [];
// create a position and quaternion animation track for each node
for ( var i = 0; i < bones.length; i ++ ) {
var bone = bones[ i ];
if ( bone.type === 'ENDSITE' )
continue;
// track data
var times = [];
var positions = [];
var rotations = [];
for ( var j = 0; j < bone.frames.length; j ++ ) {
var frame = bone.frames[ j ];
times.push( frame.time );
// the animation system animates the position property,
// so we have to add the joint offset to all values
positions.push( frame.position.x + bone.offset.x );
positions.push( frame.position.y + bone.offset.y );
positions.push( frame.position.z + bone.offset.z );
rotations.push( frame.rotation.x );
rotations.push( frame.rotation.y );
rotations.push( frame.rotation.z );
rotations.push( frame.rotation.w );
}
if ( scope.animateBonePositions ) {
tracks.push( new VectorKeyframeTrack( '.bones[' + bone.name + '].position', times, positions ) );
}
if ( scope.animateBoneRotations ) {
tracks.push( new QuaternionKeyframeTrack( '.bones[' + bone.name + '].quaternion', times, rotations ) );
}
}
return new AnimationClip( 'animation', - 1, tracks );
}
/*
returns the next non-empty line in lines
*/
function nextLine( lines ) {
var line;
// skip empty lines
while ( ( line = lines.shift().trim() ).length === 0 ) { }
return line;
}
var scope = this;
var lines = text.split( /[\r\n]+/g );
var bones = readBvh( lines );
var threeBones = [];
toTHREEBone( bones[ 0 ], threeBones );
var threeClip = toTHREEAnimation( bones );
return {
skeleton: new Skeleton( threeBones ),
clip: threeClip
};
}
} );
export { BVHLoader };

View File

@@ -0,0 +1,547 @@
import {
CompressedTexture,
FileLoader,
LinearFilter,
LinearMipmapLinearFilter,
Loader,
RGBA_ASTC_4x4_Format,
RGBA_BPTC_Format,
RGBA_PVRTC_4BPPV1_Format,
RGB_ETC1_Format,
RGB_PVRTC_4BPPV1_Format,
UnsignedByteType
} from '../../../build/three.module.js';
/**
* Loader for Basis Universal GPU Texture Codec.
*
* Basis Universal is a "supercompressed" GPU texture and texture video
* compression system that outputs a highly compressed intermediate file format
* (.basis) that can be quickly transcoded to a wide variety of GPU texture
* compression formats.
*
* This loader parallelizes the transcoding process across a configurable number
* of web workers, before transferring the transcoded compressed texture back
* to the main thread.
*/
var BasisTextureLoader = function ( manager ) {
Loader.call( this, manager );
this.transcoderPath = '';
this.transcoderBinary = null;
this.transcoderPending = null;
this.workerLimit = 4;
this.workerPool = [];
this.workerNextTaskID = 1;
this.workerSourceURL = '';
this.workerConfig = {
format: null,
astcSupported: false,
bptcSupported: false,
etcSupported: false,
dxtSupported: false,
pvrtcSupported: false,
};
};
BasisTextureLoader.taskCache = new WeakMap();
BasisTextureLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
constructor: BasisTextureLoader,
setTranscoderPath: function ( path ) {
this.transcoderPath = path;
return this;
},
setWorkerLimit: function ( workerLimit ) {
this.workerLimit = workerLimit;
return this;
},
detectSupport: function ( renderer ) {
var config = this.workerConfig;
config.astcSupported = renderer.extensions.has( 'WEBGL_compressed_texture_astc' );
config.bptcSupported = renderer.extensions.has( 'EXT_texture_compression_bptc' );
config.etcSupported = renderer.extensions.has( 'WEBGL_compressed_texture_etc1' );
config.dxtSupported = renderer.extensions.has( 'WEBGL_compressed_texture_s3tc' );
config.pvrtcSupported = renderer.extensions.has( 'WEBGL_compressed_texture_pvrtc' )
|| renderer.extensions.has( 'WEBKIT_WEBGL_compressed_texture_pvrtc' );
if ( config.astcSupported ) {
config.format = BasisTextureLoader.BASIS_FORMAT.cTFASTC_4x4;
} else if ( config.bptcSupported ) {
config.format = BasisTextureLoader.BASIS_FORMAT.cTFBC7_M5;
} else if ( config.dxtSupported ) {
config.format = BasisTextureLoader.BASIS_FORMAT.cTFBC3;
} else if ( config.pvrtcSupported ) {
config.format = BasisTextureLoader.BASIS_FORMAT.cTFPVRTC1_4_RGBA;
} else if ( config.etcSupported ) {
config.format = BasisTextureLoader.BASIS_FORMAT.cTFETC1;
} else {
throw new Error( 'THREE.BasisTextureLoader: No suitable compressed texture format found.' );
}
return this;
},
load: function ( url, onLoad, onProgress, onError ) {
var loader = new FileLoader( this.manager );
loader.setResponseType( 'arraybuffer' );
loader.setWithCredentials( this.withCredentials );
loader.load( url, ( buffer ) => {
// Check for an existing task using this buffer. A transferred buffer cannot be transferred
// again from this thread.
if ( BasisTextureLoader.taskCache.has( buffer ) ) {
var cachedTask = BasisTextureLoader.taskCache.get( buffer );
return cachedTask.promise.then( onLoad ).catch( onError );
}
this._createTexture( buffer, url )
.then( onLoad )
.catch( onError );
}, onProgress, onError );
},
/**
* @param {ArrayBuffer} buffer
* @param {string} url
* @return {Promise<CompressedTexture>}
*/
_createTexture: function ( buffer, url ) {
var worker;
var taskID;
var taskCost = buffer.byteLength;
var texturePending = this._allocateWorker( taskCost )
.then( ( _worker ) => {
worker = _worker;
taskID = this.workerNextTaskID ++;
return new Promise( ( resolve, reject ) => {
worker._callbacks[ taskID ] = { resolve, reject };
worker.postMessage( { type: 'transcode', id: taskID, buffer }, [ buffer ] );
} );
} )
.then( ( message ) => {
var config = this.workerConfig;
var { width, height, mipmaps, format } = message;
var texture;
switch ( format ) {
case BasisTextureLoader.BASIS_FORMAT.cTFASTC_4x4:
texture = new CompressedTexture( mipmaps, width, height, RGBA_ASTC_4x4_Format );
break;
case BasisTextureLoader.BASIS_FORMAT.cTFBC7_M5:
texture = new CompressedTexture( mipmaps, width, height, RGBA_BPTC_Format );
break;
case BasisTextureLoader.BASIS_FORMAT.cTFBC1:
case BasisTextureLoader.BASIS_FORMAT.cTFBC3:
texture = new CompressedTexture( mipmaps, width, height, BasisTextureLoader.DXT_FORMAT_MAP[ config.format ], UnsignedByteType );
break;
case BasisTextureLoader.BASIS_FORMAT.cTFETC1:
texture = new CompressedTexture( mipmaps, width, height, RGB_ETC1_Format );
break;
case BasisTextureLoader.BASIS_FORMAT.cTFPVRTC1_4_RGB:
texture = new CompressedTexture( mipmaps, width, height, RGB_PVRTC_4BPPV1_Format );
break;
case BasisTextureLoader.BASIS_FORMAT.cTFPVRTC1_4_RGBA:
texture = new CompressedTexture( mipmaps, width, height, RGBA_PVRTC_4BPPV1_Format );
break;
default:
throw new Error( 'THREE.BasisTextureLoader: No supported format available.' );
}
texture.minFilter = mipmaps.length === 1 ? LinearFilter : LinearMipmapLinearFilter;
texture.magFilter = LinearFilter;
texture.generateMipmaps = false;
texture.needsUpdate = true;
return texture;
} );
// Note: replaced '.finally()' with '.catch().then()' block - iOS 11 support (#19416)
texturePending
.catch( () => true )
.then( () => {
if ( worker && taskID ) {
worker._taskLoad -= taskCost;
delete worker._callbacks[ taskID ];
}
} );
// Cache the task result.
BasisTextureLoader.taskCache.set( buffer, {
url: url,
promise: texturePending
} );
return texturePending;
},
_initTranscoder: function () {
if ( ! this.transcoderPending ) {
// Load transcoder wrapper.
var jsLoader = new FileLoader( this.manager );
jsLoader.setPath( this.transcoderPath );
jsLoader.setWithCredentials( this.withCredentials );
var jsContent = new Promise( ( resolve, reject ) => {
jsLoader.load( 'basis_transcoder.js', resolve, undefined, reject );
} );
// Load transcoder WASM binary.
var binaryLoader = new FileLoader( this.manager );
binaryLoader.setPath( this.transcoderPath );
binaryLoader.setResponseType( 'arraybuffer' );
binaryLoader.setWithCredentials( this.withCredentials );
var binaryContent = new Promise( ( resolve, reject ) => {
binaryLoader.load( 'basis_transcoder.wasm', resolve, undefined, reject );
} );
this.transcoderPending = Promise.all( [ jsContent, binaryContent ] )
.then( ( [ jsContent, binaryContent ] ) => {
var fn = BasisTextureLoader.BasisWorker.toString();
var body = [
'/* basis_transcoder.js */',
jsContent,
'/* worker */',
fn.substring( fn.indexOf( '{' ) + 1, fn.lastIndexOf( '}' ) )
].join( '\n' );
this.workerSourceURL = URL.createObjectURL( new Blob( [ body ] ) );
this.transcoderBinary = binaryContent;
} );
}
return this.transcoderPending;
},
_allocateWorker: function ( taskCost ) {
return this._initTranscoder().then( () => {
if ( this.workerPool.length < this.workerLimit ) {
var worker = new Worker( this.workerSourceURL );
worker._callbacks = {};
worker._taskLoad = 0;
worker.postMessage( {
type: 'init',
config: this.workerConfig,
transcoderBinary: this.transcoderBinary,
} );
worker.onmessage = function ( e ) {
var message = e.data;
switch ( message.type ) {
case 'transcode':
worker._callbacks[ message.id ].resolve( message );
break;
case 'error':
worker._callbacks[ message.id ].reject( message );
break;
default:
console.error( 'THREE.BasisTextureLoader: Unexpected message, "' + message.type + '"' );
}
};
this.workerPool.push( worker );
} else {
this.workerPool.sort( function ( a, b ) {
return a._taskLoad > b._taskLoad ? - 1 : 1;
} );
}
var worker = this.workerPool[ this.workerPool.length - 1 ];
worker._taskLoad += taskCost;
return worker;
} );
},
dispose: function () {
for ( var i = 0; i < this.workerPool.length; i ++ ) {
this.workerPool[ i ].terminate();
}
this.workerPool.length = 0;
return this;
}
} );
/* CONSTANTS */
BasisTextureLoader.BASIS_FORMAT = {
cTFETC1: 0,
cTFETC2: 1,
cTFBC1: 2,
cTFBC3: 3,
cTFBC4: 4,
cTFBC5: 5,
cTFBC7_M6_OPAQUE_ONLY: 6,
cTFBC7_M5: 7,
cTFPVRTC1_4_RGB: 8,
cTFPVRTC1_4_RGBA: 9,
cTFASTC_4x4: 10,
cTFATC_RGB: 11,
cTFATC_RGBA_INTERPOLATED_ALPHA: 12,
cTFRGBA32: 13,
cTFRGB565: 14,
cTFBGR565: 15,
cTFRGBA4444: 16,
};
// DXT formats, from:
// http://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_s3tc/
BasisTextureLoader.DXT_FORMAT = {
COMPRESSED_RGB_S3TC_DXT1_EXT: 0x83F0,
COMPRESSED_RGBA_S3TC_DXT1_EXT: 0x83F1,
COMPRESSED_RGBA_S3TC_DXT3_EXT: 0x83F2,
COMPRESSED_RGBA_S3TC_DXT5_EXT: 0x83F3,
};
BasisTextureLoader.DXT_FORMAT_MAP = {};
BasisTextureLoader.DXT_FORMAT_MAP[ BasisTextureLoader.BASIS_FORMAT.cTFBC1 ] =
BasisTextureLoader.DXT_FORMAT.COMPRESSED_RGB_S3TC_DXT1_EXT;
BasisTextureLoader.DXT_FORMAT_MAP[ BasisTextureLoader.BASIS_FORMAT.cTFBC3 ] =
BasisTextureLoader.DXT_FORMAT.COMPRESSED_RGBA_S3TC_DXT5_EXT;
/* WEB WORKER */
BasisTextureLoader.BasisWorker = function () {
var config;
var transcoderPending;
var _BasisFile;
onmessage = function ( e ) {
var message = e.data;
switch ( message.type ) {
case 'init':
config = message.config;
init( message.transcoderBinary );
break;
case 'transcode':
transcoderPending.then( () => {
try {
var { width, height, hasAlpha, mipmaps, format } = transcode( message.buffer );
var buffers = [];
for ( var i = 0; i < mipmaps.length; ++ i ) {
buffers.push( mipmaps[ i ].data.buffer );
}
self.postMessage( { type: 'transcode', id: message.id, width, height, hasAlpha, mipmaps, format }, buffers );
} catch ( error ) {
console.error( error );
self.postMessage( { type: 'error', id: message.id, error: error.message } );
}
} );
break;
}
};
function init( wasmBinary ) {
var BasisModule;
transcoderPending = new Promise( ( resolve ) => {
BasisModule = { wasmBinary, onRuntimeInitialized: resolve };
BASIS( BasisModule ); // eslint-disable-line no-undef
} ).then( () => {
var { BasisFile, initializeBasis } = BasisModule;
_BasisFile = BasisFile;
initializeBasis();
} );
}
function transcode( buffer ) {
var basisFile = new _BasisFile( new Uint8Array( buffer ) );
var width = basisFile.getImageWidth( 0, 0 );
var height = basisFile.getImageHeight( 0, 0 );
var levels = basisFile.getNumLevels( 0 );
var hasAlpha = basisFile.getHasAlpha();
function cleanup() {
basisFile.close();
basisFile.delete();
}
if ( ! hasAlpha ) {
switch ( config.format ) {
case 9: // Hardcoded: BasisTextureLoader.BASIS_FORMAT.cTFPVRTC1_4_RGBA
config.format = 8; // Hardcoded: BasisTextureLoader.BASIS_FORMAT.cTFPVRTC1_4_RGB;
break;
default:
break;
}
}
if ( ! width || ! height || ! levels ) {
cleanup();
throw new Error( 'THREE.BasisTextureLoader: Invalid .basis file' );
}
if ( ! basisFile.startTranscoding() ) {
cleanup();
throw new Error( 'THREE.BasisTextureLoader: .startTranscoding failed' );
}
var mipmaps = [];
for ( var mip = 0; mip < levels; mip ++ ) {
var mipWidth = basisFile.getImageWidth( 0, mip );
var mipHeight = basisFile.getImageHeight( 0, mip );
var dst = new Uint8Array( basisFile.getImageTranscodedSizeInBytes( 0, mip, config.format ) );
var status = basisFile.transcodeImage(
dst,
0,
mip,
config.format,
0,
hasAlpha
);
if ( ! status ) {
cleanup();
throw new Error( 'THREE.BasisTextureLoader: .transcodeImage failed.' );
}
mipmaps.push( { data: dst, width: mipWidth, height: mipHeight } );
}
cleanup();
return { width, height, hasAlpha, mipmaps, format: config.format };
}
};
export { BasisTextureLoader };

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,281 @@
import {
CompressedTextureLoader,
RGBAFormat,
RGBA_S3TC_DXT3_Format,
RGBA_S3TC_DXT5_Format,
RGB_ETC1_Format,
RGB_S3TC_DXT1_Format
} from '../../../build/three.module.js';
var DDSLoader = function ( manager ) {
CompressedTextureLoader.call( this, manager );
};
DDSLoader.prototype = Object.assign( Object.create( CompressedTextureLoader.prototype ), {
constructor: DDSLoader,
parse: function ( buffer, loadMipmaps ) {
var dds = { mipmaps: [], width: 0, height: 0, format: null, mipmapCount: 1 };
// Adapted from @toji's DDS utils
// https://github.com/toji/webgl-texture-utils/blob/master/texture-util/dds.js
// All values and structures referenced from:
// http://msdn.microsoft.com/en-us/library/bb943991.aspx/
var DDS_MAGIC = 0x20534444;
// var DDSD_CAPS = 0x1;
// var DDSD_HEIGHT = 0x2;
// var DDSD_WIDTH = 0x4;
// var DDSD_PITCH = 0x8;
// var DDSD_PIXELFORMAT = 0x1000;
var DDSD_MIPMAPCOUNT = 0x20000;
// var DDSD_LINEARSIZE = 0x80000;
// var DDSD_DEPTH = 0x800000;
// var DDSCAPS_COMPLEX = 0x8;
// var DDSCAPS_MIPMAP = 0x400000;
// var DDSCAPS_TEXTURE = 0x1000;
var DDSCAPS2_CUBEMAP = 0x200;
var DDSCAPS2_CUBEMAP_POSITIVEX = 0x400;
var DDSCAPS2_CUBEMAP_NEGATIVEX = 0x800;
var DDSCAPS2_CUBEMAP_POSITIVEY = 0x1000;
var DDSCAPS2_CUBEMAP_NEGATIVEY = 0x2000;
var DDSCAPS2_CUBEMAP_POSITIVEZ = 0x4000;
var DDSCAPS2_CUBEMAP_NEGATIVEZ = 0x8000;
// var DDSCAPS2_VOLUME = 0x200000;
// var DDPF_ALPHAPIXELS = 0x1;
// var DDPF_ALPHA = 0x2;
var DDPF_FOURCC = 0x4;
// var DDPF_RGB = 0x40;
// var DDPF_YUV = 0x200;
// var DDPF_LUMINANCE = 0x20000;
function fourCCToInt32( value ) {
return value.charCodeAt( 0 ) +
( value.charCodeAt( 1 ) << 8 ) +
( value.charCodeAt( 2 ) << 16 ) +
( value.charCodeAt( 3 ) << 24 );
}
function int32ToFourCC( value ) {
return String.fromCharCode(
value & 0xff,
( value >> 8 ) & 0xff,
( value >> 16 ) & 0xff,
( value >> 24 ) & 0xff
);
}
function loadARGBMip( buffer, dataOffset, width, height ) {
var dataLength = width * height * 4;
var srcBuffer = new Uint8Array( buffer, dataOffset, dataLength );
var byteArray = new Uint8Array( dataLength );
var dst = 0;
var src = 0;
for ( var y = 0; y < height; y ++ ) {
for ( var x = 0; x < width; x ++ ) {
var b = srcBuffer[ src ]; src ++;
var g = srcBuffer[ src ]; src ++;
var r = srcBuffer[ src ]; src ++;
var a = srcBuffer[ src ]; src ++;
byteArray[ dst ] = r; dst ++; //r
byteArray[ dst ] = g; dst ++; //g
byteArray[ dst ] = b; dst ++; //b
byteArray[ dst ] = a; dst ++; //a
}
}
return byteArray;
}
var FOURCC_DXT1 = fourCCToInt32( 'DXT1' );
var FOURCC_DXT3 = fourCCToInt32( 'DXT3' );
var FOURCC_DXT5 = fourCCToInt32( 'DXT5' );
var FOURCC_ETC1 = fourCCToInt32( 'ETC1' );
var headerLengthInt = 31; // The header length in 32 bit ints
// Offsets into the header array
var off_magic = 0;
var off_size = 1;
var off_flags = 2;
var off_height = 3;
var off_width = 4;
var off_mipmapCount = 7;
var off_pfFlags = 20;
var off_pfFourCC = 21;
var off_RGBBitCount = 22;
var off_RBitMask = 23;
var off_GBitMask = 24;
var off_BBitMask = 25;
var off_ABitMask = 26;
// var off_caps = 27;
var off_caps2 = 28;
// var off_caps3 = 29;
// var off_caps4 = 30;
// Parse header
var header = new Int32Array( buffer, 0, headerLengthInt );
if ( header[ off_magic ] !== DDS_MAGIC ) {
console.error( 'THREE.DDSLoader.parse: Invalid magic number in DDS header.' );
return dds;
}
if ( ! header[ off_pfFlags ] & DDPF_FOURCC ) {
console.error( 'THREE.DDSLoader.parse: Unsupported format, must contain a FourCC code.' );
return dds;
}
var blockBytes;
var fourCC = header[ off_pfFourCC ];
var isRGBAUncompressed = false;
switch ( fourCC ) {
case FOURCC_DXT1:
blockBytes = 8;
dds.format = RGB_S3TC_DXT1_Format;
break;
case FOURCC_DXT3:
blockBytes = 16;
dds.format = RGBA_S3TC_DXT3_Format;
break;
case FOURCC_DXT5:
blockBytes = 16;
dds.format = RGBA_S3TC_DXT5_Format;
break;
case FOURCC_ETC1:
blockBytes = 8;
dds.format = RGB_ETC1_Format;
break;
default:
if ( header[ off_RGBBitCount ] === 32
&& header[ off_RBitMask ] & 0xff0000
&& header[ off_GBitMask ] & 0xff00
&& header[ off_BBitMask ] & 0xff
&& header[ off_ABitMask ] & 0xff000000 ) {
isRGBAUncompressed = true;
blockBytes = 64;
dds.format = RGBAFormat;
} else {
console.error( 'THREE.DDSLoader.parse: Unsupported FourCC code ', int32ToFourCC( fourCC ) );
return dds;
}
}
dds.mipmapCount = 1;
if ( header[ off_flags ] & DDSD_MIPMAPCOUNT && loadMipmaps !== false ) {
dds.mipmapCount = Math.max( 1, header[ off_mipmapCount ] );
}
var caps2 = header[ off_caps2 ];
dds.isCubemap = caps2 & DDSCAPS2_CUBEMAP ? true : false;
if ( dds.isCubemap && (
! ( caps2 & DDSCAPS2_CUBEMAP_POSITIVEX ) ||
! ( caps2 & DDSCAPS2_CUBEMAP_NEGATIVEX ) ||
! ( caps2 & DDSCAPS2_CUBEMAP_POSITIVEY ) ||
! ( caps2 & DDSCAPS2_CUBEMAP_NEGATIVEY ) ||
! ( caps2 & DDSCAPS2_CUBEMAP_POSITIVEZ ) ||
! ( caps2 & DDSCAPS2_CUBEMAP_NEGATIVEZ )
) ) {
console.error( 'THREE.DDSLoader.parse: Incomplete cubemap faces' );
return dds;
}
dds.width = header[ off_width ];
dds.height = header[ off_height ];
var dataOffset = header[ off_size ] + 4;
// Extract mipmaps buffers
var faces = dds.isCubemap ? 6 : 1;
for ( var face = 0; face < faces; face ++ ) {
var width = dds.width;
var height = dds.height;
for ( var i = 0; i < dds.mipmapCount; i ++ ) {
if ( isRGBAUncompressed ) {
var byteArray = loadARGBMip( buffer, dataOffset, width, height );
var dataLength = byteArray.length;
} else {
var dataLength = Math.max( 4, width ) / 4 * Math.max( 4, height ) / 4 * blockBytes;
var byteArray = new Uint8Array( buffer, dataOffset, dataLength );
}
var mipmap = { 'data': byteArray, 'width': width, 'height': height };
dds.mipmaps.push( mipmap );
dataOffset += dataLength;
width = Math.max( width >> 1, 1 );
height = Math.max( height >> 1, 1 );
}
}
return dds;
}
} );
export { DDSLoader };

View File

@@ -0,0 +1,640 @@
import {
BufferAttribute,
BufferGeometry,
FileLoader,
Loader
} from '../../../build/three.module.js';
var DRACOLoader = function ( manager ) {
Loader.call( this, manager );
this.decoderPath = '';
this.decoderConfig = {};
this.decoderBinary = null;
this.decoderPending = null;
this.workerLimit = 4;
this.workerPool = [];
this.workerNextTaskID = 1;
this.workerSourceURL = '';
this.defaultAttributeIDs = {
position: 'POSITION',
normal: 'NORMAL',
color: 'COLOR',
uv: 'TEX_COORD'
};
this.defaultAttributeTypes = {
position: 'Float32Array',
normal: 'Float32Array',
color: 'Float32Array',
uv: 'Float32Array'
};
};
DRACOLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
constructor: DRACOLoader,
setDecoderPath: function ( path ) {
this.decoderPath = path;
return this;
},
setDecoderConfig: function ( config ) {
this.decoderConfig = config;
return this;
},
setWorkerLimit: function ( workerLimit ) {
this.workerLimit = workerLimit;
return this;
},
/** @deprecated */
setVerbosity: function () {
console.warn( 'THREE.DRACOLoader: The .setVerbosity() method has been removed.' );
},
/** @deprecated */
setDrawMode: function () {
console.warn( 'THREE.DRACOLoader: The .setDrawMode() method has been removed.' );
},
/** @deprecated */
setSkipDequantization: function () {
console.warn( 'THREE.DRACOLoader: The .setSkipDequantization() method has been removed.' );
},
load: function ( url, onLoad, onProgress, onError ) {
var loader = new FileLoader( this.manager );
loader.setPath( this.path );
loader.setResponseType( 'arraybuffer' );
loader.setRequestHeader( this.requestHeader );
loader.setWithCredentials( this.withCredentials );
loader.load( url, ( buffer ) => {
var taskConfig = {
attributeIDs: this.defaultAttributeIDs,
attributeTypes: this.defaultAttributeTypes,
useUniqueIDs: false
};
this.decodeGeometry( buffer, taskConfig )
.then( onLoad )
.catch( onError );
}, onProgress, onError );
},
/** @deprecated Kept for backward-compatibility with previous DRACOLoader versions. */
decodeDracoFile: function ( buffer, callback, attributeIDs, attributeTypes ) {
var taskConfig = {
attributeIDs: attributeIDs || this.defaultAttributeIDs,
attributeTypes: attributeTypes || this.defaultAttributeTypes,
useUniqueIDs: !! attributeIDs
};
this.decodeGeometry( buffer, taskConfig ).then( callback );
},
decodeGeometry: function ( buffer, taskConfig ) {
// TODO: For backward-compatibility, support 'attributeTypes' objects containing
// references (rather than names) to typed array constructors. These must be
// serialized before sending them to the worker.
for ( var attribute in taskConfig.attributeTypes ) {
var type = taskConfig.attributeTypes[ attribute ];
if ( type.BYTES_PER_ELEMENT !== undefined ) {
taskConfig.attributeTypes[ attribute ] = type.name;
}
}
//
var taskKey = JSON.stringify( taskConfig );
// Check for an existing task using this buffer. A transferred buffer cannot be transferred
// again from this thread.
if ( DRACOLoader.taskCache.has( buffer ) ) {
var cachedTask = DRACOLoader.taskCache.get( buffer );
if ( cachedTask.key === taskKey ) {
return cachedTask.promise;
} else if ( buffer.byteLength === 0 ) {
// Technically, it would be possible to wait for the previous task to complete,
// transfer the buffer back, and decode again with the second configuration. That
// is complex, and I don't know of any reason to decode a Draco buffer twice in
// different ways, so this is left unimplemented.
throw new Error(
'THREE.DRACOLoader: Unable to re-decode a buffer with different ' +
'settings. Buffer has already been transferred.'
);
}
}
//
var worker;
var taskID = this.workerNextTaskID ++;
var taskCost = buffer.byteLength;
// Obtain a worker and assign a task, and construct a geometry instance
// when the task completes.
var geometryPending = this._getWorker( taskID, taskCost )
.then( ( _worker ) => {
worker = _worker;
return new Promise( ( resolve, reject ) => {
worker._callbacks[ taskID ] = { resolve, reject };
worker.postMessage( { type: 'decode', id: taskID, taskConfig, buffer }, [ buffer ] );
// this.debug();
} );
} )
.then( ( message ) => this._createGeometry( message.geometry ) );
// Remove task from the task list.
// Note: replaced '.finally()' with '.catch().then()' block - iOS 11 support (#19416)
geometryPending
.catch( () => true )
.then( () => {
if ( worker && taskID ) {
this._releaseTask( worker, taskID );
// this.debug();
}
} );
// Cache the task result.
DRACOLoader.taskCache.set( buffer, {
key: taskKey,
promise: geometryPending
} );
return geometryPending;
},
_createGeometry: function ( geometryData ) {
var geometry = new BufferGeometry();
if ( geometryData.index ) {
geometry.setIndex( new BufferAttribute( geometryData.index.array, 1 ) );
}
for ( var i = 0; i < geometryData.attributes.length; i ++ ) {
var attribute = geometryData.attributes[ i ];
var name = attribute.name;
var array = attribute.array;
var itemSize = attribute.itemSize;
geometry.setAttribute( name, new BufferAttribute( array, itemSize ) );
}
return geometry;
},
_loadLibrary: function ( url, responseType ) {
var loader = new FileLoader( this.manager );
loader.setPath( this.decoderPath );
loader.setResponseType( responseType );
loader.setWithCredentials( this.withCredentials );
return new Promise( ( resolve, reject ) => {
loader.load( url, resolve, undefined, reject );
} );
},
preload: function () {
this._initDecoder();
return this;
},
_initDecoder: function () {
if ( this.decoderPending ) return this.decoderPending;
var useJS = typeof WebAssembly !== 'object' || this.decoderConfig.type === 'js';
var librariesPending = [];
if ( useJS ) {
librariesPending.push( this._loadLibrary( 'draco_decoder.js', 'text' ) );
} else {
librariesPending.push( this._loadLibrary( 'draco_wasm_wrapper.js', 'text' ) );
librariesPending.push( this._loadLibrary( 'draco_decoder.wasm', 'arraybuffer' ) );
}
this.decoderPending = Promise.all( librariesPending )
.then( ( libraries ) => {
var jsContent = libraries[ 0 ];
if ( ! useJS ) {
this.decoderConfig.wasmBinary = libraries[ 1 ];
}
var fn = DRACOLoader.DRACOWorker.toString();
var body = [
'/* draco decoder */',
jsContent,
'',
'/* worker */',
fn.substring( fn.indexOf( '{' ) + 1, fn.lastIndexOf( '}' ) )
].join( '\n' );
this.workerSourceURL = URL.createObjectURL( new Blob( [ body ] ) );
} );
return this.decoderPending;
},
_getWorker: function ( taskID, taskCost ) {
return this._initDecoder().then( () => {
if ( this.workerPool.length < this.workerLimit ) {
var worker = new Worker( this.workerSourceURL );
worker._callbacks = {};
worker._taskCosts = {};
worker._taskLoad = 0;
worker.postMessage( { type: 'init', decoderConfig: this.decoderConfig } );
worker.onmessage = function ( e ) {
var message = e.data;
switch ( message.type ) {
case 'decode':
worker._callbacks[ message.id ].resolve( message );
break;
case 'error':
worker._callbacks[ message.id ].reject( message );
break;
default:
console.error( 'THREE.DRACOLoader: Unexpected message, "' + message.type + '"' );
}
};
this.workerPool.push( worker );
} else {
this.workerPool.sort( function ( a, b ) {
return a._taskLoad > b._taskLoad ? - 1 : 1;
} );
}
var worker = this.workerPool[ this.workerPool.length - 1 ];
worker._taskCosts[ taskID ] = taskCost;
worker._taskLoad += taskCost;
return worker;
} );
},
_releaseTask: function ( worker, taskID ) {
worker._taskLoad -= worker._taskCosts[ taskID ];
delete worker._callbacks[ taskID ];
delete worker._taskCosts[ taskID ];
},
debug: function () {
console.log( 'Task load: ', this.workerPool.map( ( worker ) => worker._taskLoad ) );
},
dispose: function () {
for ( var i = 0; i < this.workerPool.length; ++ i ) {
this.workerPool[ i ].terminate();
}
this.workerPool.length = 0;
return this;
}
} );
/* WEB WORKER */
DRACOLoader.DRACOWorker = function () {
var decoderConfig;
var decoderPending;
onmessage = function ( e ) {
var message = e.data;
switch ( message.type ) {
case 'init':
decoderConfig = message.decoderConfig;
decoderPending = new Promise( function ( resolve/*, reject*/ ) {
decoderConfig.onModuleLoaded = function ( draco ) {
// Module is Promise-like. Wrap before resolving to avoid loop.
resolve( { draco: draco } );
};
DracoDecoderModule( decoderConfig ); // eslint-disable-line no-undef
} );
break;
case 'decode':
var buffer = message.buffer;
var taskConfig = message.taskConfig;
decoderPending.then( ( module ) => {
var draco = module.draco;
var decoder = new draco.Decoder();
var decoderBuffer = new draco.DecoderBuffer();
decoderBuffer.Init( new Int8Array( buffer ), buffer.byteLength );
try {
var geometry = decodeGeometry( draco, decoder, decoderBuffer, taskConfig );
var buffers = geometry.attributes.map( ( attr ) => attr.array.buffer );
if ( geometry.index ) buffers.push( geometry.index.array.buffer );
self.postMessage( { type: 'decode', id: message.id, geometry }, buffers );
} catch ( error ) {
console.error( error );
self.postMessage( { type: 'error', id: message.id, error: error.message } );
} finally {
draco.destroy( decoderBuffer );
draco.destroy( decoder );
}
} );
break;
}
};
function decodeGeometry( draco, decoder, decoderBuffer, taskConfig ) {
var attributeIDs = taskConfig.attributeIDs;
var attributeTypes = taskConfig.attributeTypes;
var dracoGeometry;
var decodingStatus;
var geometryType = decoder.GetEncodedGeometryType( decoderBuffer );
if ( geometryType === draco.TRIANGULAR_MESH ) {
dracoGeometry = new draco.Mesh();
decodingStatus = decoder.DecodeBufferToMesh( decoderBuffer, dracoGeometry );
} else if ( geometryType === draco.POINT_CLOUD ) {
dracoGeometry = new draco.PointCloud();
decodingStatus = decoder.DecodeBufferToPointCloud( decoderBuffer, dracoGeometry );
} else {
throw new Error( 'THREE.DRACOLoader: Unexpected geometry type.' );
}
if ( ! decodingStatus.ok() || dracoGeometry.ptr === 0 ) {
throw new Error( 'THREE.DRACOLoader: Decoding failed: ' + decodingStatus.error_msg() );
}
var geometry = { index: null, attributes: [] };
// Gather all vertex attributes.
for ( var attributeName in attributeIDs ) {
var attributeType = self[ attributeTypes[ attributeName ] ];
var attribute;
var attributeID;
// A Draco file may be created with default vertex attributes, whose attribute IDs
// are mapped 1:1 from their semantic name (POSITION, NORMAL, ...). Alternatively,
// a Draco file may contain a custom set of attributes, identified by known unique
// IDs. glTF files always do the latter, and `.drc` files typically do the former.
if ( taskConfig.useUniqueIDs ) {
attributeID = attributeIDs[ attributeName ];
attribute = decoder.GetAttributeByUniqueId( dracoGeometry, attributeID );
} else {
attributeID = decoder.GetAttributeId( dracoGeometry, draco[ attributeIDs[ attributeName ] ] );
if ( attributeID === - 1 ) continue;
attribute = decoder.GetAttribute( dracoGeometry, attributeID );
}
geometry.attributes.push( decodeAttribute( draco, decoder, dracoGeometry, attributeName, attributeType, attribute ) );
}
// Add index.
if ( geometryType === draco.TRIANGULAR_MESH ) {
geometry.index = decodeIndex( draco, decoder, dracoGeometry );
}
draco.destroy( dracoGeometry );
return geometry;
}
function decodeIndex( draco, decoder, dracoGeometry ) {
var numFaces = dracoGeometry.num_faces();
var numIndices = numFaces * 3;
var byteLength = numIndices * 4;
var ptr = draco._malloc( byteLength );
decoder.GetTrianglesUInt32Array( dracoGeometry, byteLength, ptr );
var index = new Uint32Array( draco.HEAPF32.buffer, ptr, numIndices ).slice();
draco._free( ptr );
return { array: index, itemSize: 1 };
}
function decodeAttribute( draco, decoder, dracoGeometry, attributeName, attributeType, attribute ) {
var numComponents = attribute.num_components();
var numPoints = dracoGeometry.num_points();
var numValues = numPoints * numComponents;
var byteLength = numValues * attributeType.BYTES_PER_ELEMENT;
var dataType = getDracoDataType( draco, attributeType );
var ptr = draco._malloc( byteLength );
decoder.GetAttributeDataArrayForAllPoints( dracoGeometry, attribute, dataType, byteLength, ptr );
var array = new attributeType( draco.HEAPF32.buffer, ptr, numValues ).slice();
draco._free( ptr );
return {
name: attributeName,
array: array,
itemSize: numComponents
};
}
function getDracoDataType( draco, attributeType ) {
switch ( attributeType ) {
case Float32Array: return draco.DT_FLOAT32;
case Int8Array: return draco.DT_INT8;
case Int16Array: return draco.DT_INT16;
case Int32Array: return draco.DT_INT32;
case Uint8Array: return draco.DT_UINT8;
case Uint16Array: return draco.DT_UINT16;
case Uint32Array: return draco.DT_UINT32;
}
}
};
DRACOLoader.taskCache = new WeakMap();
/** Deprecated static methods */
/** @deprecated */
DRACOLoader.setDecoderPath = function () {
console.warn( 'THREE.DRACOLoader: The .setDecoderPath() method has been removed. Use instance methods.' );
};
/** @deprecated */
DRACOLoader.setDecoderConfig = function () {
console.warn( 'THREE.DRACOLoader: The .setDecoderConfig() method has been removed. Use instance methods.' );
};
/** @deprecated */
DRACOLoader.releaseDecoderModule = function () {
console.warn( 'THREE.DRACOLoader: The .releaseDecoderModule() method has been removed. Use instance methods.' );
};
/** @deprecated */
DRACOLoader.getDecoderModule = function () {
console.warn( 'THREE.DRACOLoader: The .getDecoderModule() method has been removed. Use instance methods.' );
};
export { DRACOLoader };

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,265 @@
import {
BufferGeometry,
Euler,
FileLoader,
Float32BufferAttribute,
Group,
LineBasicMaterial,
LineSegments,
Loader
} from '../../../build/three.module.js';
/**
* GCodeLoader is used to load gcode files usually used for 3D printing or CNC applications.
*
* Gcode files are composed by commands used by machines to create objects.
*
* @class GCodeLoader
* @param {Manager} manager Loading manager.
*/
var GCodeLoader = function ( manager ) {
Loader.call( this, manager );
this.splitLayer = false;
};
GCodeLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
constructor: GCodeLoader,
load: function ( url, onLoad, onProgress, onError ) {
var scope = this;
var loader = new FileLoader( scope.manager );
loader.setPath( scope.path );
loader.setRequestHeader( scope.requestHeader );
loader.setWithCredentials( scope.withCredentials );
loader.load( url, function ( text ) {
try {
onLoad( scope.parse( text ) );
} catch ( e ) {
if ( onError ) {
onError( e );
} else {
console.error( e );
}
scope.manager.itemError( url );
}
}, onProgress, onError );
},
parse: function ( data ) {
var state = { x: 0, y: 0, z: 0, e: 0, f: 0, extruding: false, relative: false };
var layers = [];
var currentLayer = undefined;
var pathMaterial = new LineBasicMaterial( { color: 0xFF0000 } );
pathMaterial.name = 'path';
var extrudingMaterial = new LineBasicMaterial( { color: 0x00FF00 } );
extrudingMaterial.name = 'extruded';
function newLayer( line ) {
currentLayer = { vertex: [], pathVertex: [], z: line.z };
layers.push( currentLayer );
}
//Create lie segment between p1 and p2
function addSegment( p1, p2 ) {
if ( currentLayer === undefined ) {
newLayer( p1 );
}
if ( line.extruding ) {
currentLayer.vertex.push( p1.x, p1.y, p1.z );
currentLayer.vertex.push( p2.x, p2.y, p2.z );
} else {
currentLayer.pathVertex.push( p1.x, p1.y, p1.z );
currentLayer.pathVertex.push( p2.x, p2.y, p2.z );
}
}
function delta( v1, v2 ) {
return state.relative ? v2 : v2 - v1;
}
function absolute( v1, v2 ) {
return state.relative ? v1 + v2 : v2;
}
var lines = data.replace( /;.+/g, '' ).split( '\n' );
for ( var i = 0; i < lines.length; i ++ ) {
var tokens = lines[ i ].split( ' ' );
var cmd = tokens[ 0 ].toUpperCase();
//Argumments
var args = {};
tokens.splice( 1 ).forEach( function ( token ) {
if ( token[ 0 ] !== undefined ) {
var key = token[ 0 ].toLowerCase();
var value = parseFloat( token.substring( 1 ) );
args[ key ] = value;
}
} );
//Process commands
//G0/G1 Linear Movement
if ( cmd === 'G0' || cmd === 'G1' ) {
var line = {
x: args.x !== undefined ? absolute( state.x, args.x ) : state.x,
y: args.y !== undefined ? absolute( state.y, args.y ) : state.y,
z: args.z !== undefined ? absolute( state.z, args.z ) : state.z,
e: args.e !== undefined ? absolute( state.e, args.e ) : state.e,
f: args.f !== undefined ? absolute( state.f, args.f ) : state.f,
};
//Layer change detection is or made by watching Z, it's made by watching when we extrude at a new Z position
if ( delta( state.e, line.e ) > 0 ) {
line.extruding = delta( state.e, line.e ) > 0;
if ( currentLayer == undefined || line.z != currentLayer.z ) {
newLayer( line );
}
}
addSegment( state, line );
state = line;
} else if ( cmd === 'G2' || cmd === 'G3' ) {
//G2/G3 - Arc Movement ( G2 clock wise and G3 counter clock wise )
//console.warn( 'THREE.GCodeLoader: Arc command not supported' );
} else if ( cmd === 'G90' ) {
//G90: Set to Absolute Positioning
state.relative = false;
} else if ( cmd === 'G91' ) {
//G91: Set to state.relative Positioning
state.relative = true;
} else if ( cmd === 'G92' ) {
//G92: Set Position
var line = state;
line.x = args.x !== undefined ? args.x : line.x;
line.y = args.y !== undefined ? args.y : line.y;
line.z = args.z !== undefined ? args.z : line.z;
line.e = args.e !== undefined ? args.e : line.e;
state = line;
} else {
//console.warn( 'THREE.GCodeLoader: Command not supported:' + cmd );
}
}
function addObject( vertex, extruding ) {
var geometry = new BufferGeometry();
geometry.setAttribute( 'position', new Float32BufferAttribute( vertex, 3 ) );
var segments = new LineSegments( geometry, extruding ? extrudingMaterial : pathMaterial );
segments.name = 'layer' + i;
object.add( segments );
}
var object = new Group();
object.name = 'gcode';
if ( this.splitLayer ) {
for ( var i = 0; i < layers.length; i ++ ) {
var layer = layers[ i ];
addObject( layer.vertex, true );
addObject( layer.pathVertex, false );
}
} else {
var vertex = [], pathVertex = [];
for ( var i = 0; i < layers.length; i ++ ) {
var layer = layers[ i ];
var layerVertex = layer.vertex;
var layerPathVertex = layer.pathVertex;
for ( var j = 0; j < layerVertex.length; j ++ ) {
vertex.push( layerVertex[ j ] );
}
for ( var j = 0; j < layerPathVertex.length; j ++ ) {
pathVertex.push( layerPathVertex[ j ] );
}
}
addObject( vertex, true );
addObject( pathVertex, false );
}
object.quaternion.setFromEuler( new Euler( - Math.PI / 2, 0, 0 ) );
return object;
}
} );
export { GCodeLoader };

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,146 @@
import {
CubeTexture,
DataTexture,
FileLoader,
FloatType,
HalfFloatType,
LinearEncoding,
LinearFilter,
Loader,
NearestFilter,
RGBAFormat,
RGBEEncoding,
RGBFormat,
UnsignedByteType
} from '../../../build/three.module.js';
import { RGBELoader } from '../loaders/RGBELoader.js';
var HDRCubeTextureLoader = function ( manager ) {
Loader.call( this, manager );
this.hdrLoader = new RGBELoader();
this.type = UnsignedByteType;
};
HDRCubeTextureLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
constructor: HDRCubeTextureLoader,
load: function ( urls, onLoad, onProgress, onError ) {
if ( ! Array.isArray( urls ) ) {
console.warn( 'THREE.HDRCubeTextureLoader signature has changed. Use .setDataType() instead.' );
this.setDataType( urls );
urls = onLoad;
onLoad = onProgress;
onProgress = onError;
onError = arguments[ 4 ];
}
var texture = new CubeTexture();
texture.type = this.type;
switch ( texture.type ) {
case UnsignedByteType:
texture.encoding = RGBEEncoding;
texture.format = RGBAFormat;
texture.minFilter = NearestFilter;
texture.magFilter = NearestFilter;
texture.generateMipmaps = false;
break;
case FloatType:
texture.encoding = LinearEncoding;
texture.format = RGBFormat;
texture.minFilter = LinearFilter;
texture.magFilter = LinearFilter;
texture.generateMipmaps = false;
break;
case HalfFloatType:
texture.encoding = LinearEncoding;
texture.format = RGBFormat;
texture.minFilter = LinearFilter;
texture.magFilter = LinearFilter;
texture.generateMipmaps = false;
break;
}
var scope = this;
var loaded = 0;
function loadHDRData( i, onLoad, onProgress, onError ) {
new FileLoader( scope.manager )
.setPath( scope.path )
.setResponseType( 'arraybuffer' )
.setWithCredentials( scope.withCredentials )
.load( urls[ i ], function ( buffer ) {
loaded ++;
var texData = scope.hdrLoader.parse( buffer );
if ( ! texData ) return;
if ( texData.data !== undefined ) {
var dataTexture = new DataTexture( texData.data, texData.width, texData.height );
dataTexture.type = texture.type;
dataTexture.encoding = texture.encoding;
dataTexture.format = texture.format;
dataTexture.minFilter = texture.minFilter;
dataTexture.magFilter = texture.magFilter;
dataTexture.generateMipmaps = texture.generateMipmaps;
texture.images[ i ] = dataTexture;
}
if ( loaded === 6 ) {
texture.needsUpdate = true;
if ( onLoad ) onLoad( texture );
}
}, onProgress, onError );
}
for ( var i = 0; i < urls.length; i ++ ) {
loadHDRData( i, onLoad, onProgress, onError );
}
return texture;
},
setDataType: function ( value ) {
this.type = value;
this.hdrLoader.setDataType( value );
return this;
}
} );
export { HDRCubeTextureLoader };

View File

@@ -0,0 +1,132 @@
import {
FileLoader,
Group,
Loader,
LoadingManager
} from '../../../build/three.module.js';
import { ColladaLoader } from '../loaders/ColladaLoader.js';
import { JSZip } from '../libs/jszip.module.min.js';
var KMZLoader = function ( manager ) {
Loader.call( this, manager );
};
KMZLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
constructor: KMZLoader,
load: function ( url, onLoad, onProgress, onError ) {
var scope = this;
var loader = new FileLoader( scope.manager );
loader.setPath( scope.path );
loader.setResponseType( 'arraybuffer' );
loader.setRequestHeader( scope.requestHeader );
loader.setWithCredentials( scope.withCredentials );
loader.load( url, function ( text ) {
try {
onLoad( scope.parse( text ) );
} catch ( e ) {
if ( onError ) {
onError( e );
} else {
console.error( e );
}
scope.manager.itemError( url );
}
}, onProgress, onError );
},
parse: function ( data ) {
function findFile( url ) {
for ( var path in zip.files ) {
if ( path.substr( - url.length ) === url ) {
return zip.files[ path ];
}
}
}
var manager = new LoadingManager();
manager.setURLModifier( function ( url ) {
var image = findFile( url );
if ( image ) {
console.log( 'Loading', url );
var blob = new Blob( [ image.asArrayBuffer() ], { type: 'application/octet-stream' } );
return URL.createObjectURL( blob );
}
return url;
} );
//
var zip = new JSZip( data ); // eslint-disable-line no-undef
if ( zip.files[ 'doc.kml' ] ) {
var xml = new DOMParser().parseFromString( zip.files[ 'doc.kml' ].asText(), 'application/xml' );
var model = xml.querySelector( 'Placemark Model Link href' );
if ( model ) {
var loader = new ColladaLoader( manager );
return loader.parse( zip.files[ model.textContent ].asText() );
}
} else {
console.warn( 'KMZLoader: Missing doc.kml file.' );
for ( var path in zip.files ) {
var extension = path.split( '.' ).pop().toLowerCase();
if ( extension === 'dae' ) {
var loader = new ColladaLoader( manager );
return loader.parse( zip.files[ path ].asText() );
}
}
}
console.error( 'KMZLoader: Couldn\'t find .dae file.' );
return { scene: new Group() };
}
} );
export { KMZLoader };

View File

@@ -0,0 +1,743 @@
/**
* References:
* - KTX: http://github.khronos.org/KTX-Specification/
* - DFD: https://www.khronos.org/registry/DataFormat/specs/1.3/dataformat.1.3.html#basicdescriptor
*
* To do:
* - [ ] High-quality demo
* - [ ] Documentation
* - [ ] (Optional) Include BC5
* - [ ] (Optional) Include EAC RG on mobile (WEBGL_compressed_texture_etc)
* - [ ] (Optional) Include two-texture output mode (see: clearcoat + clearcoatRoughness)
* - [ ] (Optional) Support Web Workers, after #18234
*/
import {
CompressedTexture,
CompressedTextureLoader,
FileLoader,
LinearEncoding,
LinearFilter,
LinearMipmapLinearFilter,
MathUtils,
RGBAFormat,
RGBA_ASTC_4x4_Format,
RGBA_BPTC_Format,
RGBA_ETC2_EAC_Format,
RGBA_PVRTC_4BPPV1_Format,
RGBA_S3TC_DXT5_Format,
RGB_ETC1_Format,
RGB_ETC2_Format,
RGB_PVRTC_4BPPV1_Format,
RGB_S3TC_DXT1_Format,
UnsignedByteType,
sRGBEncoding,
} from '../../../build/three.module.js';
import { ZSTDDecoder } from '../libs/zstddec.module.js';
// Data Format Descriptor (DFD) constants.
const DFDModel = {
ETC1S: 163,
UASTC: 166,
};
const DFDChannel = {
ETC1S: {
RGB: 0,
RRR: 3,
GGG: 4,
AAA: 15,
},
UASTC: {
RGB: 0,
RGBA: 3,
RRR: 4,
RRRG: 5
},
};
//
class KTX2Loader extends CompressedTextureLoader {
constructor( manager ) {
super( manager );
this.basisModule = null;
this.basisModulePending = null;
this.transcoderConfig = {};
}
detectSupport( renderer ) {
this.transcoderConfig = {
astcSupported: renderer.extensions.has( 'WEBGL_compressed_texture_astc' ),
etc1Supported: renderer.extensions.has( 'WEBGL_compressed_texture_etc1' ),
etc2Supported: renderer.extensions.has( 'WEBGL_compressed_texture_etc' ),
dxtSupported: renderer.extensions.has( 'WEBGL_compressed_texture_s3tc' ),
bptcSupported: renderer.extensions.has( 'EXT_texture_compression_bptc' ),
pvrtcSupported: renderer.extensions.has( 'WEBGL_compressed_texture_pvrtc' )
|| renderer.extensions.has( 'WEBKIT_WEBGL_compressed_texture_pvrtc' )
};
return this;
}
initModule() {
if ( this.basisModulePending ) {
return;
}
var scope = this;
// The Emscripten wrapper returns a fake Promise, which can cause
// infinite recursion when mixed with native Promises. Wrap the module
// initialization to return a native Promise.
scope.basisModulePending = new Promise( function ( resolve ) {
MSC_TRANSCODER().then( function ( basisModule ) { // eslint-disable-line no-undef
scope.basisModule = basisModule;
basisModule.initTranscoders();
resolve();
} );
} );
}
load( url, onLoad, onProgress, onError ) {
var scope = this;
var texture = new CompressedTexture();
var bufferPending = new Promise( function ( resolve, reject ) {
new FileLoader( scope.manager )
.setPath( scope.path )
.setResponseType( 'arraybuffer' )
.load( url, resolve, onProgress, reject );
} );
// parse() will call initModule() again, but starting the process early
// should allow the WASM to load in parallel with the texture.
this.initModule();
Promise.all( [ bufferPending, this.basisModulePending ] )
.then( function ( [ buffer ] ) {
scope.parse( buffer, function ( _texture ) {
texture.copy( _texture );
texture.needsUpdate = true;
if ( onLoad ) onLoad( texture );
}, onError );
} )
.catch( onError );
return texture;
}
parse( buffer, onLoad, onError ) {
var scope = this;
// load() may have already called initModule(), but call it again here
// in case the user called parse() directly. Method is idempotent.
this.initModule();
this.basisModulePending.then( function () {
var BasisLzEtc1sImageTranscoder = scope.basisModule.BasisLzEtc1sImageTranscoder;
var UastcImageTranscoder = scope.basisModule.UastcImageTranscoder;
var TextureFormat = scope.basisModule.TextureFormat;
var ktx = new KTX2Container( scope.basisModule, buffer );
// TODO(donmccurdy): Should test if texture is transcodable before attempting
// any transcoding. If supercompressionScheme is KTX_SS_BASIS_LZ and dfd
// colorModel is ETC1S (163) or if dfd colorModel is UASTCF (166)
// then texture must be transcoded.
var transcoder = ktx.getTexFormat() === TextureFormat.UASTC4x4
? new UastcImageTranscoder()
: new BasisLzEtc1sImageTranscoder();
ktx.initMipmaps( transcoder, scope.transcoderConfig )
.then( function () {
var texture = new CompressedTexture(
ktx.mipmaps,
ktx.getWidth(),
ktx.getHeight(),
ktx.transcodedFormat,
UnsignedByteType
);
texture.encoding = ktx.getEncoding();
texture.premultiplyAlpha = ktx.getPremultiplyAlpha();
texture.minFilter = ktx.mipmaps.length === 1 ? LinearFilter : LinearMipmapLinearFilter;
texture.magFilter = LinearFilter;
onLoad( texture );
} )
.catch( onError );
} );
return this;
}
}
class KTX2Container {
constructor( basisModule, arrayBuffer ) {
this.basisModule = basisModule;
this.arrayBuffer = arrayBuffer;
this.zstd = new ZSTDDecoder();
this.zstd.init();
this.mipmaps = null;
this.transcodedFormat = null;
// Confirm this is a KTX 2.0 file, based on the identifier in the first 12 bytes.
var idByteLength = 12;
var id = new Uint8Array( this.arrayBuffer, 0, idByteLength );
if ( id[ 0 ] !== 0xAB || // '´'
id[ 1 ] !== 0x4B || // 'K'
id[ 2 ] !== 0x54 || // 'T'
id[ 3 ] !== 0x58 || // 'X'
id[ 4 ] !== 0x20 || // ' '
id[ 5 ] !== 0x32 || // '2'
id[ 6 ] !== 0x30 || // '0'
id[ 7 ] !== 0xBB || // 'ª'
id[ 8 ] !== 0x0D || // '\r'
id[ 9 ] !== 0x0A || // '\n'
id[ 10 ] !== 0x1A || // '\x1A'
id[ 11 ] !== 0x0A // '\n'
) {
throw new Error( 'THREE.KTX2Loader: Missing KTX 2.0 identifier.' );
}
// TODO(donmccurdy): If we need to support BE, derive this from typeSize.
var littleEndian = true;
///////////////////////////////////////////////////
// Header.
///////////////////////////////////////////////////
var headerByteLength = 17 * Uint32Array.BYTES_PER_ELEMENT;
var headerReader = new KTX2BufferReader( this.arrayBuffer, idByteLength, headerByteLength, littleEndian );
this.header = {
vkFormat: headerReader.nextUint32(),
typeSize: headerReader.nextUint32(),
pixelWidth: headerReader.nextUint32(),
pixelHeight: headerReader.nextUint32(),
pixelDepth: headerReader.nextUint32(),
arrayElementCount: headerReader.nextUint32(),
faceCount: headerReader.nextUint32(),
levelCount: headerReader.nextUint32(),
supercompressionScheme: headerReader.nextUint32(),
dfdByteOffset: headerReader.nextUint32(),
dfdByteLength: headerReader.nextUint32(),
kvdByteOffset: headerReader.nextUint32(),
kvdByteLength: headerReader.nextUint32(),
sgdByteOffset: headerReader.nextUint64(),
sgdByteLength: headerReader.nextUint64(),
};
if ( this.header.pixelDepth > 0 ) {
throw new Error( 'THREE.KTX2Loader: Only 2D textures are currently supported.' );
}
if ( this.header.arrayElementCount > 1 ) {
throw new Error( 'THREE.KTX2Loader: Array textures are not currently supported.' );
}
if ( this.header.faceCount > 1 ) {
throw new Error( 'THREE.KTX2Loader: Cube textures are not currently supported.' );
}
///////////////////////////////////////////////////
// Level index
///////////////////////////////////////////////////
var levelByteLength = this.header.levelCount * 3 * 8;
var levelReader = new KTX2BufferReader( this.arrayBuffer, idByteLength + headerByteLength, levelByteLength, littleEndian );
this.levels = [];
for ( var i = 0; i < this.header.levelCount; i ++ ) {
this.levels.push( {
byteOffset: levelReader.nextUint64(),
byteLength: levelReader.nextUint64(),
uncompressedByteLength: levelReader.nextUint64(),
} );
}
///////////////////////////////////////////////////
// Data Format Descriptor (DFD)
///////////////////////////////////////////////////
var dfdReader = new KTX2BufferReader(
this.arrayBuffer,
this.header.dfdByteOffset,
this.header.dfdByteLength,
littleEndian
);
const sampleStart = 6;
const sampleWords = 4;
this.dfd = {
vendorId: dfdReader.skip( 4 /* totalSize */ ).nextUint16(),
versionNumber: dfdReader.skip( 2 /* descriptorType */ ).nextUint16(),
descriptorBlockSize: dfdReader.nextUint16(),
colorModel: dfdReader.nextUint8(),
colorPrimaries: dfdReader.nextUint8(),
transferFunction: dfdReader.nextUint8(),
flags: dfdReader.nextUint8(),
texelBlockDimension: {
x: dfdReader.nextUint8() + 1,
y: dfdReader.nextUint8() + 1,
z: dfdReader.nextUint8() + 1,
w: dfdReader.nextUint8() + 1,
},
bytesPlane0: dfdReader.nextUint8(),
numSamples: 0,
samples: [],
};
this.dfd.numSamples = ( this.dfd.descriptorBlockSize / 4 - sampleStart ) / sampleWords;
dfdReader.skip( 7 /* bytesPlane[1-7] */ );
for ( var i = 0; i < this.dfd.numSamples; i ++ ) {
this.dfd.samples[ i ] = {
channelID: dfdReader.skip( 3 /* bitOffset + bitLength */ ).nextUint8(),
// ... remainder not implemented.
};
dfdReader.skip( 12 /* samplePosition[0-3], lower, upper */ );
}
if ( this.header.vkFormat !== 0x00 /* VK_FORMAT_UNDEFINED */ &&
! ( this.header.supercompressionScheme === 1 /* BasisLZ */ ||
this.dfd.colorModel === DFDModel.UASTC ) ) {
throw new Error( 'THREE.KTX2Loader: Only Basis Universal supercompression is currently supported.' );
}
///////////////////////////////////////////////////
// Key/Value Data (KVD)
///////////////////////////////////////////////////
// Not implemented.
this.kvd = {};
///////////////////////////////////////////////////
// Supercompression Global Data (SGD)
///////////////////////////////////////////////////
this.sgd = {};
if ( this.header.sgdByteLength <= 0 ) return;
var sgdReader = new KTX2BufferReader(
this.arrayBuffer,
this.header.sgdByteOffset,
this.header.sgdByteLength,
littleEndian
);
this.sgd.endpointCount = sgdReader.nextUint16();
this.sgd.selectorCount = sgdReader.nextUint16();
this.sgd.endpointsByteLength = sgdReader.nextUint32();
this.sgd.selectorsByteLength = sgdReader.nextUint32();
this.sgd.tablesByteLength = sgdReader.nextUint32();
this.sgd.extendedByteLength = sgdReader.nextUint32();
this.sgd.imageDescs = [];
this.sgd.endpointsData = null;
this.sgd.selectorsData = null;
this.sgd.tablesData = null;
this.sgd.extendedData = null;
for ( var i = 0; i < this.header.levelCount; i ++ ) {
this.sgd.imageDescs.push( {
imageFlags: sgdReader.nextUint32(),
rgbSliceByteOffset: sgdReader.nextUint32(),
rgbSliceByteLength: sgdReader.nextUint32(),
alphaSliceByteOffset: sgdReader.nextUint32(),
alphaSliceByteLength: sgdReader.nextUint32(),
} );
}
var endpointsByteOffset = this.header.sgdByteOffset + sgdReader.offset;
var selectorsByteOffset = endpointsByteOffset + this.sgd.endpointsByteLength;
var tablesByteOffset = selectorsByteOffset + this.sgd.selectorsByteLength;
var extendedByteOffset = tablesByteOffset + this.sgd.tablesByteLength;
this.sgd.endpointsData = new Uint8Array( this.arrayBuffer, endpointsByteOffset, this.sgd.endpointsByteLength );
this.sgd.selectorsData = new Uint8Array( this.arrayBuffer, selectorsByteOffset, this.sgd.selectorsByteLength );
this.sgd.tablesData = new Uint8Array( this.arrayBuffer, tablesByteOffset, this.sgd.tablesByteLength );
this.sgd.extendedData = new Uint8Array( this.arrayBuffer, extendedByteOffset, this.sgd.extendedByteLength );
}
async initMipmaps( transcoder, config ) {
await this.zstd.init();
var TranscodeTarget = this.basisModule.TranscodeTarget;
var TextureFormat = this.basisModule.TextureFormat;
var ImageInfo = this.basisModule.ImageInfo;
var scope = this;
var mipmaps = [];
var width = this.getWidth();
var height = this.getHeight();
var texFormat = this.getTexFormat();
var hasAlpha = this.getAlpha();
var isVideo = false;
// PVRTC1 transcoders (from both ETC1S and UASTC) only support power of 2 dimensions.
var pvrtcTranscodable = MathUtils.isPowerOfTwo( width ) && MathUtils.isPowerOfTwo( height );
if ( texFormat === TextureFormat.ETC1S ) {
var numEndpoints = this.sgd.endpointCount;
var numSelectors = this.sgd.selectorCount;
var endpoints = this.sgd.endpointsData;
var selectors = this.sgd.selectorsData;
var tables = this.sgd.tablesData;
transcoder.decodePalettes( numEndpoints, endpoints, numSelectors, selectors );
transcoder.decodeTables( tables );
}
var targetFormat;
if ( config.astcSupported ) {
targetFormat = TranscodeTarget.ASTC_4x4_RGBA;
this.transcodedFormat = RGBA_ASTC_4x4_Format;
} else if ( config.bptcSupported && texFormat === TextureFormat.UASTC4x4 ) {
targetFormat = TranscodeTarget.BC7_M5_RGBA;
this.transcodedFormat = RGBA_BPTC_Format;
} else if ( config.dxtSupported ) {
targetFormat = hasAlpha ? TranscodeTarget.BC3_RGBA : TranscodeTarget.BC1_RGB;
this.transcodedFormat = hasAlpha ? RGBA_S3TC_DXT5_Format : RGB_S3TC_DXT1_Format;
} else if ( config.pvrtcSupported && pvrtcTranscodable ) {
targetFormat = hasAlpha ? TranscodeTarget.PVRTC1_4_RGBA : TranscodeTarget.PVRTC1_4_RGB;
this.transcodedFormat = hasAlpha ? RGBA_PVRTC_4BPPV1_Format : RGB_PVRTC_4BPPV1_Format;
} else if ( config.etc2Supported ) {
targetFormat = hasAlpha ? TranscodeTarget.ETC2_RGBA : TranscodeTarget.ETC1_RGB/* subset of ETC2 */;
this.transcodedFormat = hasAlpha ? RGBA_ETC2_EAC_Format : RGB_ETC2_Format;
} else if ( config.etc1Supported ) {
targetFormat = TranscodeTarget.ETC1_RGB;
this.transcodedFormat = RGB_ETC1_Format;
} else {
console.warn( 'THREE.KTX2Loader: No suitable compressed texture format found. Decoding to RGBA32.' );
targetFormat = TranscodeTarget.RGBA32;
this.transcodedFormat = RGBAFormat;
}
if ( ! this.basisModule.isFormatSupported( targetFormat, texFormat ) ) {
throw new Error( 'THREE.KTX2Loader: Selected texture format not supported by current transcoder build.' );
}
var imageDescIndex = 0;
for ( var level = 0; level < this.header.levelCount; level ++ ) {
var levelWidth = Math.ceil( width / Math.pow( 2, level ) );
var levelHeight = Math.ceil( height / Math.pow( 2, level ) );
var numImagesInLevel = 1; // TODO(donmccurdy): Support cubemaps, arrays and 3D.
var imageOffsetInLevel = 0;
var imageInfo = new ImageInfo( texFormat, levelWidth, levelHeight, level );
var levelByteLength = this.levels[ level ].byteLength;
var levelUncompressedByteLength = this.levels[ level ].uncompressedByteLength;
for ( var imageIndex = 0; imageIndex < numImagesInLevel; imageIndex ++ ) {
var result;
var encodedData;
if ( texFormat === TextureFormat.UASTC4x4 ) {
// UASTC
imageInfo.flags = 0;
imageInfo.rgbByteOffset = 0;
imageInfo.rgbByteLength = levelUncompressedByteLength;
imageInfo.alphaByteOffset = 0;
imageInfo.alphaByteLength = 0;
encodedData = new Uint8Array( this.arrayBuffer, this.levels[ level ].byteOffset + imageOffsetInLevel, levelByteLength );
if ( this.header.supercompressionScheme === 2 /* ZSTD */ ) {
encodedData = this.zstd.decode( encodedData, levelUncompressedByteLength );
}
result = transcoder.transcodeImage( targetFormat, encodedData, imageInfo, 0, hasAlpha, isVideo );
} else {
// ETC1S
var imageDesc = this.sgd.imageDescs[ imageDescIndex ++ ];
imageInfo.flags = imageDesc.imageFlags;
imageInfo.rgbByteOffset = 0;
imageInfo.rgbByteLength = imageDesc.rgbSliceByteLength;
imageInfo.alphaByteOffset = imageDesc.alphaSliceByteOffset > 0 ? imageDesc.rgbSliceByteLength : 0;
imageInfo.alphaByteLength = imageDesc.alphaSliceByteLength;
encodedData = new Uint8Array( this.arrayBuffer, this.levels[ level ].byteOffset + imageDesc.rgbSliceByteOffset, imageDesc.rgbSliceByteLength + imageDesc.alphaSliceByteLength );
result = transcoder.transcodeImage( targetFormat, encodedData, imageInfo, 0, isVideo );
}
if ( result.transcodedImage === undefined ) {
throw new Error( 'THREE.KTX2Loader: Unable to transcode image.' );
}
// Transcoded image is written in memory allocated by WASM. We could avoid copying
// the image by waiting until the image is uploaded to the GPU, then calling
// delete(). However, (1) we don't know if the user will later need to re-upload it
// e.g. after calling texture.clone(), and (2) this code will eventually be in a
// Web Worker, and transferring WASM's memory seems like a very bad idea.
var levelData = result.transcodedImage.get_typed_memory_view().slice();
result.transcodedImage.delete();
mipmaps.push( { data: levelData, width: levelWidth, height: levelHeight } );
imageOffsetInLevel += levelByteLength;
}
}
scope.mipmaps = mipmaps;
}
getWidth() {
return this.header.pixelWidth;
}
getHeight() {
return this.header.pixelHeight;
}
getEncoding() {
return this.dfd.transferFunction === 2 /* KHR_DF_TRANSFER_SRGB */
? sRGBEncoding
: LinearEncoding;
}
getTexFormat() {
var TextureFormat = this.basisModule.TextureFormat;
return this.dfd.colorModel === DFDModel.UASTC ? TextureFormat.UASTC4x4 : TextureFormat.ETC1S;
}
getAlpha() {
var TextureFormat = this.basisModule.TextureFormat;
// TODO(donmccurdy): Handle all channelIDs (i.e. the R & R+G cases),
// choosing appropriate transcode target formats or providing queries
// for applications so they know what to do with the content.
if ( this.getTexFormat() === TextureFormat.UASTC4x4 ) {
// UASTC
if ( ( this.dfd.samples[ 0 ].channelID & 0xF ) === DFDChannel.UASTC.RGBA ) {
return true;
}
return false;
}
// ETC1S
if ( this.dfd.numSamples === 2 && ( this.dfd.samples[ 1 ].channelID & 0xF ) === DFDChannel.ETC1S.AAA ) {
return true;
}
return false;
}
getPremultiplyAlpha() {
return !! ( this.dfd.flags & 1 /* KHR_DF_FLAG_ALPHA_PREMULTIPLIED */ );
}
}
class KTX2BufferReader {
constructor( arrayBuffer, byteOffset, byteLength, littleEndian ) {
this.dataView = new DataView( arrayBuffer, byteOffset, byteLength );
this.littleEndian = littleEndian;
this.offset = 0;
}
nextUint8() {
var value = this.dataView.getUint8( this.offset, this.littleEndian );
this.offset += 1;
return value;
}
nextUint16() {
var value = this.dataView.getUint16( this.offset, this.littleEndian );
this.offset += 2;
return value;
}
nextUint32() {
var value = this.dataView.getUint32( this.offset, this.littleEndian );
this.offset += 4;
return value;
}
nextUint64() {
// https://stackoverflow.com/questions/53103695/
var left = this.dataView.getUint32( this.offset, this.littleEndian );
var right = this.dataView.getUint32( this.offset + 4, this.littleEndian );
var value = this.littleEndian ? left + ( 2 ** 32 * right ) : ( 2 ** 32 * left ) + right;
if ( ! Number.isSafeInteger( value ) ) {
console.warn( 'THREE.KTX2Loader: ' + value + ' exceeds MAX_SAFE_INTEGER. Precision may be lost.' );
}
this.offset += 8;
return value;
}
skip( bytes ) {
this.offset += bytes;
return this;
}
}
export { KTX2Loader };

View File

@@ -0,0 +1,180 @@
import {
CompressedTextureLoader
} from '../../../build/three.module.js';
/**
* for description see https://www.khronos.org/opengles/sdk/tools/KTX/
* for file layout see https://www.khronos.org/opengles/sdk/tools/KTX/file_format_spec/
*
* ported from https://github.com/BabylonJS/Babylon.js/blob/master/src/Tools/babylon.khronosTextureContainer.ts
*/
var KTXLoader = function ( manager ) {
CompressedTextureLoader.call( this, manager );
};
KTXLoader.prototype = Object.assign( Object.create( CompressedTextureLoader.prototype ), {
constructor: KTXLoader,
parse: function ( buffer, loadMipmaps ) {
var ktx = new KhronosTextureContainer( buffer, 1 );
return {
mipmaps: ktx.mipmaps( loadMipmaps ),
width: ktx.pixelWidth,
height: ktx.pixelHeight,
format: ktx.glInternalFormat,
isCubemap: ktx.numberOfFaces === 6,
mipmapCount: ktx.numberOfMipmapLevels
};
}
} );
var KhronosTextureContainer = ( function () {
/**
* @param {ArrayBuffer} arrayBuffer- contents of the KTX container file
* @param {number} facesExpected- should be either 1 or 6, based whether a cube texture or or
* @param {boolean} threeDExpected- provision for indicating that data should be a 3D texture, not implemented
* @param {boolean} textureArrayExpected- provision for indicating that data should be a texture array, not implemented
*/
function KhronosTextureContainer( arrayBuffer, facesExpected /*, threeDExpected, textureArrayExpected */ ) {
this.arrayBuffer = arrayBuffer;
// Test that it is a ktx formatted file, based on the first 12 bytes, character representation is:
// '´', 'K', 'T', 'X', ' ', '1', '1', 'ª', '\r', '\n', '\x1A', '\n'
// 0xAB, 0x4B, 0x54, 0x58, 0x20, 0x31, 0x31, 0xBB, 0x0D, 0x0A, 0x1A, 0x0A
var identifier = new Uint8Array( this.arrayBuffer, 0, 12 );
if ( identifier[ 0 ] !== 0xAB ||
identifier[ 1 ] !== 0x4B ||
identifier[ 2 ] !== 0x54 ||
identifier[ 3 ] !== 0x58 ||
identifier[ 4 ] !== 0x20 ||
identifier[ 5 ] !== 0x31 ||
identifier[ 6 ] !== 0x31 ||
identifier[ 7 ] !== 0xBB ||
identifier[ 8 ] !== 0x0D ||
identifier[ 9 ] !== 0x0A ||
identifier[ 10 ] !== 0x1A ||
identifier[ 11 ] !== 0x0A ) {
console.error( 'texture missing KTX identifier' );
return;
}
// load the reset of the header in native 32 bit uint
var dataSize = Uint32Array.BYTES_PER_ELEMENT;
var headerDataView = new DataView( this.arrayBuffer, 12, 13 * dataSize );
var endianness = headerDataView.getUint32( 0, true );
var littleEndian = endianness === 0x04030201;
this.glType = headerDataView.getUint32( 1 * dataSize, littleEndian ); // must be 0 for compressed textures
this.glTypeSize = headerDataView.getUint32( 2 * dataSize, littleEndian ); // must be 1 for compressed textures
this.glFormat = headerDataView.getUint32( 3 * dataSize, littleEndian ); // must be 0 for compressed textures
this.glInternalFormat = headerDataView.getUint32( 4 * dataSize, littleEndian ); // the value of arg passed to gl.compressedTexImage2D(,,x,,,,)
this.glBaseInternalFormat = headerDataView.getUint32( 5 * dataSize, littleEndian ); // specify GL_RGB, GL_RGBA, GL_ALPHA, etc (un-compressed only)
this.pixelWidth = headerDataView.getUint32( 6 * dataSize, littleEndian ); // level 0 value of arg passed to gl.compressedTexImage2D(,,,x,,,)
this.pixelHeight = headerDataView.getUint32( 7 * dataSize, littleEndian ); // level 0 value of arg passed to gl.compressedTexImage2D(,,,,x,,)
this.pixelDepth = headerDataView.getUint32( 8 * dataSize, littleEndian ); // level 0 value of arg passed to gl.compressedTexImage3D(,,,,,x,,)
this.numberOfArrayElements = headerDataView.getUint32( 9 * dataSize, littleEndian ); // used for texture arrays
this.numberOfFaces = headerDataView.getUint32( 10 * dataSize, littleEndian ); // used for cubemap textures, should either be 1 or 6
this.numberOfMipmapLevels = headerDataView.getUint32( 11 * dataSize, littleEndian ); // number of levels; disregard possibility of 0 for compressed textures
this.bytesOfKeyValueData = headerDataView.getUint32( 12 * dataSize, littleEndian ); // the amount of space after the header for meta-data
// Make sure we have a compressed type. Not only reduces work, but probably better to let dev know they are not compressing.
if ( this.glType !== 0 ) {
console.warn( 'only compressed formats currently supported' );
return;
} else {
// value of zero is an indication to generate mipmaps @ runtime. Not usually allowed for compressed, so disregard.
this.numberOfMipmapLevels = Math.max( 1, this.numberOfMipmapLevels );
}
if ( this.pixelHeight === 0 || this.pixelDepth !== 0 ) {
console.warn( 'only 2D textures currently supported' );
return;
}
if ( this.numberOfArrayElements !== 0 ) {
console.warn( 'texture arrays not currently supported' );
return;
}
if ( this.numberOfFaces !== facesExpected ) {
console.warn( 'number of faces expected' + facesExpected + ', but found ' + this.numberOfFaces );
return;
}
// we now have a completely validated file, so could use existence of loadType as success
// would need to make this more elaborate & adjust checks above to support more than one load type
this.loadType = KhronosTextureContainer.COMPRESSED_2D;
}
// return mipmaps for js
KhronosTextureContainer.prototype.mipmaps = function ( loadMipmaps ) {
var mipmaps = [];
// initialize width & height for level 1
var dataOffset = KhronosTextureContainer.HEADER_LEN + this.bytesOfKeyValueData;
var width = this.pixelWidth;
var height = this.pixelHeight;
var mipmapCount = loadMipmaps ? this.numberOfMipmapLevels : 1;
for ( var level = 0; level < mipmapCount; level ++ ) {
var imageSize = new Int32Array( this.arrayBuffer, dataOffset, 1 )[ 0 ]; // size per face, since not supporting array cubemaps
dataOffset += 4; // size of the image + 4 for the imageSize field
for ( var face = 0; face < this.numberOfFaces; face ++ ) {
var byteArray = new Uint8Array( this.arrayBuffer, dataOffset, imageSize );
mipmaps.push( { 'data': byteArray, 'width': width, 'height': height } );
dataOffset += imageSize;
dataOffset += 3 - ( ( imageSize + 3 ) % 4 ); // add padding for odd sized image
}
width = Math.max( 1.0, width * 0.5 );
height = Math.max( 1.0, height * 0.5 );
}
return mipmaps;
};
KhronosTextureContainer.HEADER_LEN = 12 + ( 13 * 4 ); // identifier + header elements (not including key value meta-data pairs)
// load types
KhronosTextureContainer.COMPRESSED_2D = 0; // uses a gl.compressedTexImage2D()
KhronosTextureContainer.COMPRESSED_3D = 1; // uses a gl.compressedTexImage3D()
KhronosTextureContainer.TEX_2D = 2; // uses a gl.texImage2D()
KhronosTextureContainer.TEX_3D = 3; // uses a gl.texImage3D()
return KhronosTextureContainer;
}() );
export { KTXLoader };

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,142 @@
// http://download.autodesk.com/us/systemdocs/help/2011/lustre/index.html?url=./files/WSc4e151a45a3b785a24c3d9a411df9298473-7ffd.htm,topicNumber=d0e9492
import {
Loader,
FileLoader,
DataTexture,
DataTexture3D,
RGBFormat,
UnsignedByteType,
ClampToEdgeWrapping,
LinearFilter,
} from '../../../build/three.module.js';
export class LUT3dlLoader extends Loader {
load( url, onLoad, onProgress, onError ) {
const loader = new FileLoader( this.manager );
loader.setPath( this.path );
loader.setResponseType( 'text' );
loader.load( url, text => {
try {
onLoad( this.parse( text ) );
} catch ( e ) {
if ( onError ) {
onError( e );
} else {
console.error( e );
}
this.manager.itemError( url );
}
}, onProgress, onError );
}
parse( str ) {
// remove empty lines and comment lints
str = str
.replace( /^#.*?(\n|\r)/gm, '' )
.replace( /^\s*?(\n|\r)/gm, '' )
.trim();
const lines = str.split( /[\n\r]+/g );
// first line is the positions on the grid that are provided by the LUT
const gridLines = lines[ 0 ].trim().split( /\s+/g ).map( e => parseFloat( e ) );
const gridStep = gridLines[ 1 ] - gridLines[ 0 ];
const size = gridLines.length;
for ( let i = 1, l = gridLines.length; i < l; i ++ ) {
if ( gridStep !== ( gridLines[ i ] - gridLines[ i - 1 ] ) ) {
throw new Error( 'LUT3dlLoader: Inconsistent grid size not supported.' );
}
}
const dataArray = new Array( size * size * size * 3 );
let index = 0;
let maxOutputValue = 0.0;
for ( let i = 1, l = lines.length; i < l; i ++ ) {
const line = lines[ i ].trim();
const split = line.split( /\s/g );
const r = parseFloat( split[ 0 ] );
const g = parseFloat( split[ 1 ] );
const b = parseFloat( split[ 2 ] );
maxOutputValue = Math.max( maxOutputValue, r, g, b );
const bLayer = index % size;
const gLayer = Math.floor( index / size ) % size;
const rLayer = Math.floor( index / ( size * size ) ) % size;
// b grows first, then g, then r
const pixelIndex = bLayer * size * size + gLayer * size + rLayer;
dataArray[ 3 * pixelIndex + 0 ] = r;
dataArray[ 3 * pixelIndex + 1 ] = g;
dataArray[ 3 * pixelIndex + 2 ] = b;
index += 1;
}
// Find the apparent bit depth of the stored RGB values and scale the
// values to [ 0, 255 ].
const bits = Math.ceil( Math.log2( maxOutputValue ) );
const maxBitValue = Math.pow( 2.0, bits );
for ( let i = 0, l = dataArray.length; i < l; i ++ ) {
const val = dataArray[ i ];
dataArray[ i ] = 255 * val / maxBitValue;
}
const data = new Uint8Array( dataArray );
const texture = new DataTexture();
texture.image.data = data;
texture.image.width = size;
texture.image.height = size * size;
texture.format = RGBFormat;
texture.type = UnsignedByteType;
texture.magFilter = LinearFilter;
texture.wrapS = ClampToEdgeWrapping;
texture.wrapT = ClampToEdgeWrapping;
texture.generateMipmaps = false;
const texture3D = new DataTexture3D();
texture3D.image.data = data;
texture3D.image.width = size;
texture3D.image.height = size;
texture3D.image.depth = size;
texture3D.format = RGBFormat;
texture3D.type = UnsignedByteType;
texture3D.magFilter = LinearFilter;
texture3D.wrapS = ClampToEdgeWrapping;
texture3D.wrapT = ClampToEdgeWrapping;
texture3D.wrapR = ClampToEdgeWrapping;
texture3D.generateMipmaps = false;
return {
size,
texture,
texture3D,
};
}
}

View File

@@ -0,0 +1,151 @@
// https://wwwimages2.adobe.com/content/dam/acom/en/products/speedgrade/cc/pdfs/cube-lut-specification-1.0.pdf
import {
Loader,
FileLoader,
Vector3,
DataTexture,
DataTexture3D,
RGBFormat,
UnsignedByteType,
ClampToEdgeWrapping,
LinearFilter,
} from '../../../build/three.module.js';
export class LUTCubeLoader extends Loader {
load( url, onLoad, onProgress, onError ) {
const loader = new FileLoader( this.manager );
loader.setPath( this.path );
loader.setResponseType( 'text' );
loader.load( url, text => {
try {
onLoad( this.parse( text ) );
} catch ( e ) {
if ( onError ) {
onError( e );
} else {
console.error( e );
}
this.manager.itemError( url );
}
}, onProgress, onError );
}
parse( str ) {
// Remove empty lines and comments
str = str
.replace( /^#.*?(\n|\r)/gm, '' )
.replace( /^\s*?(\n|\r)/gm, '' )
.trim();
let title = null;
let size = null;
const domainMin = new Vector3( 0, 0, 0 );
const domainMax = new Vector3( 1, 1, 1 );
const lines = str.split( /[\n\r]+/g );
let data = null;
let currIndex = 0;
for ( let i = 0, l = lines.length; i < l; i ++ ) {
const line = lines[ i ].trim();
const split = line.split( /\s/g );
switch ( split[ 0 ] ) {
case 'TITLE':
title = line.substring( 7, line.length - 1 );
break;
case 'LUT_3D_SIZE':
// TODO: A .CUBE LUT file specifies floating point values and could be represented with
// more precision than can be captured with Uint8Array.
const sizeToken = split[ 1 ];
size = parseFloat( sizeToken );
data = new Uint8Array( size * size * size * 3 );
break;
case 'DOMAIN_MIN':
domainMin.x = parseFloat( split[ 1 ] );
domainMin.y = parseFloat( split[ 2 ] );
domainMin.z = parseFloat( split[ 3 ] );
break;
case 'DOMAIN_MAX':
domainMax.x = parseFloat( split[ 1 ] );
domainMax.y = parseFloat( split[ 2 ] );
domainMax.z = parseFloat( split[ 3 ] );
break;
default:
const r = parseFloat( split[ 0 ] );
const g = parseFloat( split[ 1 ] );
const b = parseFloat( split[ 2 ] );
if (
r > 1.0 || r < 0.0 ||
g > 1.0 || g < 0.0 ||
b > 1.0 || b < 0.0
) {
throw new Error( 'LUTCubeLoader : Non normalized values not supported.' );
}
data[ currIndex + 0 ] = r * 255;
data[ currIndex + 1 ] = g * 255;
data[ currIndex + 2 ] = b * 255;
currIndex += 3;
}
}
const texture = new DataTexture();
texture.image.data = data;
texture.image.width = size;
texture.image.height = size * size;
texture.format = RGBFormat;
texture.type = UnsignedByteType;
texture.magFilter = LinearFilter;
texture.wrapS = ClampToEdgeWrapping;
texture.wrapT = ClampToEdgeWrapping;
texture.generateMipmaps = false;
const texture3D = new DataTexture3D();
texture3D.image.data = data;
texture3D.image.width = size;
texture3D.image.height = size;
texture3D.image.depth = size;
texture3D.format = RGBFormat;
texture3D.type = UnsignedByteType;
texture3D.magFilter = LinearFilter;
texture3D.wrapS = ClampToEdgeWrapping;
texture3D.wrapT = ClampToEdgeWrapping;
texture3D.wrapR = ClampToEdgeWrapping;
texture3D.generateMipmaps = false;
return {
title,
size,
domainMin,
domainMax,
texture,
texture3D,
};
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,74 @@
import {
FileLoader,
Loader,
CanvasTexture,
NearestFilter
} from '../../../build/three.module.js';
class LottieLoader extends Loader {
setQuality( value ) {
this._quality = value;
}
load( url, onLoad, onProgress, onError ) {
const quality = this._quality || 1;
const texture = new CanvasTexture();
texture.minFilter = NearestFilter;
const loader = new FileLoader( this.manager );
loader.setPath( this.path );
loader.setWithCredentials( this.withCredentials );
loader.load( url, function ( text ) {
const data = JSON.parse( text );
// bodymoving uses container.offetWidth and offsetHeight
// to define width/height
const container = document.createElement( 'div' );
container.style.width = data.w + 'px';
container.style.height = data.h + 'px';
document.body.appendChild( container );
// eslint-disable-next-line no-undef
const animation = bodymovin.loadAnimation( {
container: container,
animType: 'canvas',
loop: true,
autoplay: true,
animationData: data,
rendererSettings: { dpr: quality }
} );
texture.animation = animation;
texture.image = animation.container;
animation.addEventListener( 'enterFrame', function () {
texture.needsUpdate = true;
} );
container.style.display = 'none';
if ( onLoad !== undefined ) {
onLoad( texture );
}
}, onProgress, onError );
return texture;
}
}
export { LottieLoader };

View File

@@ -0,0 +1,404 @@
import {
AnimationClip,
BufferGeometry,
FileLoader,
Float32BufferAttribute,
Loader,
Vector3
} from '../../../build/three.module.js';
var MD2Loader = function ( manager ) {
Loader.call( this, manager );
};
MD2Loader.prototype = Object.assign( Object.create( Loader.prototype ), {
constructor: MD2Loader,
load: function ( url, onLoad, onProgress, onError ) {
var scope = this;
var loader = new FileLoader( scope.manager );
loader.setPath( scope.path );
loader.setResponseType( 'arraybuffer' );
loader.setRequestHeader( scope.requestHeader );
loader.setWithCredentials( scope.withCredentials );
loader.load( url, function ( buffer ) {
try {
onLoad( scope.parse( buffer ) );
} catch ( e ) {
if ( onError ) {
onError( e );
} else {
console.error( e );
}
scope.manager.itemError( url );
}
}, onProgress, onError );
},
parse: ( function () {
var normalData = [
[ - 0.525731, 0.000000, 0.850651 ], [ - 0.442863, 0.238856, 0.864188 ],
[ - 0.295242, 0.000000, 0.955423 ], [ - 0.309017, 0.500000, 0.809017 ],
[ - 0.162460, 0.262866, 0.951056 ], [ 0.000000, 0.000000, 1.000000 ],
[ 0.000000, 0.850651, 0.525731 ], [ - 0.147621, 0.716567, 0.681718 ],
[ 0.147621, 0.716567, 0.681718 ], [ 0.000000, 0.525731, 0.850651 ],
[ 0.309017, 0.500000, 0.809017 ], [ 0.525731, 0.000000, 0.850651 ],
[ 0.295242, 0.000000, 0.955423 ], [ 0.442863, 0.238856, 0.864188 ],
[ 0.162460, 0.262866, 0.951056 ], [ - 0.681718, 0.147621, 0.716567 ],
[ - 0.809017, 0.309017, 0.500000 ], [ - 0.587785, 0.425325, 0.688191 ],
[ - 0.850651, 0.525731, 0.000000 ], [ - 0.864188, 0.442863, 0.238856 ],
[ - 0.716567, 0.681718, 0.147621 ], [ - 0.688191, 0.587785, 0.425325 ],
[ - 0.500000, 0.809017, 0.309017 ], [ - 0.238856, 0.864188, 0.442863 ],
[ - 0.425325, 0.688191, 0.587785 ], [ - 0.716567, 0.681718, - 0.147621 ],
[ - 0.500000, 0.809017, - 0.309017 ], [ - 0.525731, 0.850651, 0.000000 ],
[ 0.000000, 0.850651, - 0.525731 ], [ - 0.238856, 0.864188, - 0.442863 ],
[ 0.000000, 0.955423, - 0.295242 ], [ - 0.262866, 0.951056, - 0.162460 ],
[ 0.000000, 1.000000, 0.000000 ], [ 0.000000, 0.955423, 0.295242 ],
[ - 0.262866, 0.951056, 0.162460 ], [ 0.238856, 0.864188, 0.442863 ],
[ 0.262866, 0.951056, 0.162460 ], [ 0.500000, 0.809017, 0.309017 ],
[ 0.238856, 0.864188, - 0.442863 ], [ 0.262866, 0.951056, - 0.162460 ],
[ 0.500000, 0.809017, - 0.309017 ], [ 0.850651, 0.525731, 0.000000 ],
[ 0.716567, 0.681718, 0.147621 ], [ 0.716567, 0.681718, - 0.147621 ],
[ 0.525731, 0.850651, 0.000000 ], [ 0.425325, 0.688191, 0.587785 ],
[ 0.864188, 0.442863, 0.238856 ], [ 0.688191, 0.587785, 0.425325 ],
[ 0.809017, 0.309017, 0.500000 ], [ 0.681718, 0.147621, 0.716567 ],
[ 0.587785, 0.425325, 0.688191 ], [ 0.955423, 0.295242, 0.000000 ],
[ 1.000000, 0.000000, 0.000000 ], [ 0.951056, 0.162460, 0.262866 ],
[ 0.850651, - 0.525731, 0.000000 ], [ 0.955423, - 0.295242, 0.000000 ],
[ 0.864188, - 0.442863, 0.238856 ], [ 0.951056, - 0.162460, 0.262866 ],
[ 0.809017, - 0.309017, 0.500000 ], [ 0.681718, - 0.147621, 0.716567 ],
[ 0.850651, 0.000000, 0.525731 ], [ 0.864188, 0.442863, - 0.238856 ],
[ 0.809017, 0.309017, - 0.500000 ], [ 0.951056, 0.162460, - 0.262866 ],
[ 0.525731, 0.000000, - 0.850651 ], [ 0.681718, 0.147621, - 0.716567 ],
[ 0.681718, - 0.147621, - 0.716567 ], [ 0.850651, 0.000000, - 0.525731 ],
[ 0.809017, - 0.309017, - 0.500000 ], [ 0.864188, - 0.442863, - 0.238856 ],
[ 0.951056, - 0.162460, - 0.262866 ], [ 0.147621, 0.716567, - 0.681718 ],
[ 0.309017, 0.500000, - 0.809017 ], [ 0.425325, 0.688191, - 0.587785 ],
[ 0.442863, 0.238856, - 0.864188 ], [ 0.587785, 0.425325, - 0.688191 ],
[ 0.688191, 0.587785, - 0.425325 ], [ - 0.147621, 0.716567, - 0.681718 ],
[ - 0.309017, 0.500000, - 0.809017 ], [ 0.000000, 0.525731, - 0.850651 ],
[ - 0.525731, 0.000000, - 0.850651 ], [ - 0.442863, 0.238856, - 0.864188 ],
[ - 0.295242, 0.000000, - 0.955423 ], [ - 0.162460, 0.262866, - 0.951056 ],
[ 0.000000, 0.000000, - 1.000000 ], [ 0.295242, 0.000000, - 0.955423 ],
[ 0.162460, 0.262866, - 0.951056 ], [ - 0.442863, - 0.238856, - 0.864188 ],
[ - 0.309017, - 0.500000, - 0.809017 ], [ - 0.162460, - 0.262866, - 0.951056 ],
[ 0.000000, - 0.850651, - 0.525731 ], [ - 0.147621, - 0.716567, - 0.681718 ],
[ 0.147621, - 0.716567, - 0.681718 ], [ 0.000000, - 0.525731, - 0.850651 ],
[ 0.309017, - 0.500000, - 0.809017 ], [ 0.442863, - 0.238856, - 0.864188 ],
[ 0.162460, - 0.262866, - 0.951056 ], [ 0.238856, - 0.864188, - 0.442863 ],
[ 0.500000, - 0.809017, - 0.309017 ], [ 0.425325, - 0.688191, - 0.587785 ],
[ 0.716567, - 0.681718, - 0.147621 ], [ 0.688191, - 0.587785, - 0.425325 ],
[ 0.587785, - 0.425325, - 0.688191 ], [ 0.000000, - 0.955423, - 0.295242 ],
[ 0.000000, - 1.000000, 0.000000 ], [ 0.262866, - 0.951056, - 0.162460 ],
[ 0.000000, - 0.850651, 0.525731 ], [ 0.000000, - 0.955423, 0.295242 ],
[ 0.238856, - 0.864188, 0.442863 ], [ 0.262866, - 0.951056, 0.162460 ],
[ 0.500000, - 0.809017, 0.309017 ], [ 0.716567, - 0.681718, 0.147621 ],
[ 0.525731, - 0.850651, 0.000000 ], [ - 0.238856, - 0.864188, - 0.442863 ],
[ - 0.500000, - 0.809017, - 0.309017 ], [ - 0.262866, - 0.951056, - 0.162460 ],
[ - 0.850651, - 0.525731, 0.000000 ], [ - 0.716567, - 0.681718, - 0.147621 ],
[ - 0.716567, - 0.681718, 0.147621 ], [ - 0.525731, - 0.850651, 0.000000 ],
[ - 0.500000, - 0.809017, 0.309017 ], [ - 0.238856, - 0.864188, 0.442863 ],
[ - 0.262866, - 0.951056, 0.162460 ], [ - 0.864188, - 0.442863, 0.238856 ],
[ - 0.809017, - 0.309017, 0.500000 ], [ - 0.688191, - 0.587785, 0.425325 ],
[ - 0.681718, - 0.147621, 0.716567 ], [ - 0.442863, - 0.238856, 0.864188 ],
[ - 0.587785, - 0.425325, 0.688191 ], [ - 0.309017, - 0.500000, 0.809017 ],
[ - 0.147621, - 0.716567, 0.681718 ], [ - 0.425325, - 0.688191, 0.587785 ],
[ - 0.162460, - 0.262866, 0.951056 ], [ 0.442863, - 0.238856, 0.864188 ],
[ 0.162460, - 0.262866, 0.951056 ], [ 0.309017, - 0.500000, 0.809017 ],
[ 0.147621, - 0.716567, 0.681718 ], [ 0.000000, - 0.525731, 0.850651 ],
[ 0.425325, - 0.688191, 0.587785 ], [ 0.587785, - 0.425325, 0.688191 ],
[ 0.688191, - 0.587785, 0.425325 ], [ - 0.955423, 0.295242, 0.000000 ],
[ - 0.951056, 0.162460, 0.262866 ], [ - 1.000000, 0.000000, 0.000000 ],
[ - 0.850651, 0.000000, 0.525731 ], [ - 0.955423, - 0.295242, 0.000000 ],
[ - 0.951056, - 0.162460, 0.262866 ], [ - 0.864188, 0.442863, - 0.238856 ],
[ - 0.951056, 0.162460, - 0.262866 ], [ - 0.809017, 0.309017, - 0.500000 ],
[ - 0.864188, - 0.442863, - 0.238856 ], [ - 0.951056, - 0.162460, - 0.262866 ],
[ - 0.809017, - 0.309017, - 0.500000 ], [ - 0.681718, 0.147621, - 0.716567 ],
[ - 0.681718, - 0.147621, - 0.716567 ], [ - 0.850651, 0.000000, - 0.525731 ],
[ - 0.688191, 0.587785, - 0.425325 ], [ - 0.587785, 0.425325, - 0.688191 ],
[ - 0.425325, 0.688191, - 0.587785 ], [ - 0.425325, - 0.688191, - 0.587785 ],
[ - 0.587785, - 0.425325, - 0.688191 ], [ - 0.688191, - 0.587785, - 0.425325 ]
];
return function ( buffer ) {
var data = new DataView( buffer );
// http://tfc.duke.free.fr/coding/md2-specs-en.html
var header = {};
var headerNames = [
'ident', 'version',
'skinwidth', 'skinheight',
'framesize',
'num_skins', 'num_vertices', 'num_st', 'num_tris', 'num_glcmds', 'num_frames',
'offset_skins', 'offset_st', 'offset_tris', 'offset_frames', 'offset_glcmds', 'offset_end'
];
for ( var i = 0; i < headerNames.length; i ++ ) {
header[ headerNames[ i ] ] = data.getInt32( i * 4, true );
}
if ( header.ident !== 844121161 || header.version !== 8 ) {
console.error( 'Not a valid MD2 file' );
return;
}
if ( header.offset_end !== data.byteLength ) {
console.error( 'Corrupted MD2 file' );
return;
}
//
var geometry = new BufferGeometry();
// uvs
var uvsTemp = [];
var offset = header.offset_st;
for ( var i = 0, l = header.num_st; i < l; i ++ ) {
var u = data.getInt16( offset + 0, true );
var v = data.getInt16( offset + 2, true );
uvsTemp.push( u / header.skinwidth, 1 - ( v / header.skinheight ) );
offset += 4;
}
// triangles
offset = header.offset_tris;
var vertexIndices = [];
var uvIndices = [];
for ( var i = 0, l = header.num_tris; i < l; i ++ ) {
vertexIndices.push(
data.getUint16( offset + 0, true ),
data.getUint16( offset + 2, true ),
data.getUint16( offset + 4, true )
);
uvIndices.push(
data.getUint16( offset + 6, true ),
data.getUint16( offset + 8, true ),
data.getUint16( offset + 10, true )
);
offset += 12;
}
// frames
var translation = new Vector3();
var scale = new Vector3();
var string = [];
var frames = [];
offset = header.offset_frames;
for ( var i = 0, l = header.num_frames; i < l; i ++ ) {
scale.set(
data.getFloat32( offset + 0, true ),
data.getFloat32( offset + 4, true ),
data.getFloat32( offset + 8, true )
);
translation.set(
data.getFloat32( offset + 12, true ),
data.getFloat32( offset + 16, true ),
data.getFloat32( offset + 20, true )
);
offset += 24;
for ( var j = 0; j < 16; j ++ ) {
var character = data.getUint8( offset + j, true );
if ( character === 0 ) break;
string[ j ] = character;
}
var frame = {
name: String.fromCharCode.apply( null, string ),
vertices: [],
normals: []
};
offset += 16;
for ( var j = 0; j < header.num_vertices; j ++ ) {
var x = data.getUint8( offset ++, true );
var y = data.getUint8( offset ++, true );
var z = data.getUint8( offset ++, true );
var n = normalData[ data.getUint8( offset ++, true ) ];
x = x * scale.x + translation.x;
y = y * scale.y + translation.y;
z = z * scale.z + translation.z;
frame.vertices.push( x, z, y ); // convert to Y-up
frame.normals.push( n[ 0 ], n[ 2 ], n[ 1 ] ); // convert to Y-up
}
frames.push( frame );
}
// static
var positions = [];
var normals = [];
var uvs = [];
var verticesTemp = frames[ 0 ].vertices;
var normalsTemp = frames[ 0 ].normals;
for ( var i = 0, l = vertexIndices.length; i < l; i ++ ) {
var vertexIndex = vertexIndices[ i ];
var stride = vertexIndex * 3;
//
var x = verticesTemp[ stride ];
var y = verticesTemp[ stride + 1 ];
var z = verticesTemp[ stride + 2 ];
positions.push( x, y, z );
//
var nx = normalsTemp[ stride ];
var ny = normalsTemp[ stride + 1 ];
var nz = normalsTemp[ stride + 2 ];
normals.push( nx, ny, nz );
//
var uvIndex = uvIndices[ i ];
stride = uvIndex * 2;
var u = uvsTemp[ stride ];
var v = uvsTemp[ stride + 1 ];
uvs.push( u, v );
}
geometry.setAttribute( 'position', new Float32BufferAttribute( positions, 3 ) );
geometry.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) );
geometry.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) );
// animation
var morphPositions = [];
var morphNormals = [];
for ( var i = 0, l = frames.length; i < l; i ++ ) {
var frame = frames[ i ];
var attributeName = frame.name;
if ( frame.vertices.length > 0 ) {
var positions = [];
for ( var j = 0, jl = vertexIndices.length; j < jl; j ++ ) {
var vertexIndex = vertexIndices[ j ];
var stride = vertexIndex * 3;
var x = frame.vertices[ stride ];
var y = frame.vertices[ stride + 1 ];
var z = frame.vertices[ stride + 2 ];
positions.push( x, y, z );
}
var positionAttribute = new Float32BufferAttribute( positions, 3 );
positionAttribute.name = attributeName;
morphPositions.push( positionAttribute );
}
if ( frame.normals.length > 0 ) {
var normals = [];
for ( var j = 0, jl = vertexIndices.length; j < jl; j ++ ) {
var vertexIndex = vertexIndices[ j ];
var stride = vertexIndex * 3;
var nx = frame.normals[ stride ];
var ny = frame.normals[ stride + 1 ];
var nz = frame.normals[ stride + 2 ];
normals.push( nx, ny, nz );
}
var normalAttribute = new Float32BufferAttribute( normals, 3 );
normalAttribute.name = attributeName;
morphNormals.push( normalAttribute );
}
}
geometry.morphAttributes.position = morphPositions;
geometry.morphAttributes.normal = morphNormals;
geometry.morphTargetsRelative = false;
geometry.animations = AnimationClip.CreateClipsFromMorphTargetSequences( frames, 10 );
return geometry;
};
} )()
} );
export { MD2Loader };

View File

@@ -0,0 +1,104 @@
/**
* MDD is a special format that stores a position for every vertex in a model for every frame in an animation.
* Similar to BVH, it can be used to transfer animation data between different 3D applications or engines.
*
* MDD stores its data in binary format (big endian) in the following way:
*
* number of frames (a single uint32)
* number of vertices (a single uint32)
* time values for each frame (sequence of float32)
* vertex data for each frame (sequence of float32)
*/
import {
AnimationClip,
BufferAttribute,
FileLoader,
Loader,
NumberKeyframeTrack
} from '../../../build/three.module.js';
var MDDLoader = function ( manager ) {
Loader.call( this, manager );
};
MDDLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
constructor: MDDLoader,
load: function ( url, onLoad, onProgress, onError ) {
var scope = this;
var loader = new FileLoader( this.manager );
loader.setPath( this.path );
loader.setResponseType( 'arraybuffer' );
loader.load( url, function ( data ) {
onLoad( scope.parse( data ) );
}, onProgress, onError );
},
parse: function ( data ) {
var view = new DataView( data );
var totalFrames = view.getUint32( 0 );
var totalPoints = view.getUint32( 4 );
var offset = 8;
// animation clip
var times = new Float32Array( totalFrames );
var values = new Float32Array( totalFrames * totalFrames ).fill( 0 );
for ( var i = 0; i < totalFrames; i ++ ) {
times[ i ] = view.getFloat32( offset ); offset += 4;
values[ ( totalFrames * i ) + i ] = 1;
}
var track = new NumberKeyframeTrack( '.morphTargetInfluences', times, values );
var clip = new AnimationClip( 'default', times[ times.length - 1 ], [ track ] );
// morph targets
var morphTargets = [];
for ( var i = 0; i < totalFrames; i ++ ) {
var morphTarget = new Float32Array( totalPoints * 3 );
for ( var j = 0; j < totalPoints; j ++ ) {
var stride = ( j * 3 );
morphTarget[ stride + 0 ] = view.getFloat32( offset ); offset += 4; // x
morphTarget[ stride + 1 ] = view.getFloat32( offset ); offset += 4; // y
morphTarget[ stride + 2 ] = view.getFloat32( offset ); offset += 4; // z
}
var attribute = new BufferAttribute( morphTarget, 3 );
attribute.name = 'morph_' + i;
morphTargets.push( attribute );
}
return {
morphTargets: morphTargets,
clip: clip
};
}
} );
export { MDDLoader };

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,564 @@
import {
Color,
DefaultLoadingManager,
FileLoader,
FrontSide,
Loader,
LoaderUtils,
MeshPhongMaterial,
RepeatWrapping,
TextureLoader,
Vector2
} from '../../../build/three.module.js';
/**
* Loads a Wavefront .mtl file specifying materials
*/
var MTLLoader = function ( manager ) {
Loader.call( this, manager );
};
MTLLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
constructor: MTLLoader,
/**
* Loads and parses a MTL asset from a URL.
*
* @param {String} url - URL to the MTL file.
* @param {Function} [onLoad] - Callback invoked with the loaded object.
* @param {Function} [onProgress] - Callback for download progress.
* @param {Function} [onError] - Callback for download errors.
*
* @see setPath setResourcePath
*
* @note In order for relative texture references to resolve correctly
* you must call setResourcePath() explicitly prior to load.
*/
load: function ( url, onLoad, onProgress, onError ) {
var scope = this;
var path = ( this.path === '' ) ? LoaderUtils.extractUrlBase( url ) : this.path;
var loader = new FileLoader( this.manager );
loader.setPath( this.path );
loader.setRequestHeader( this.requestHeader );
loader.setWithCredentials( this.withCredentials );
loader.load( url, function ( text ) {
try {
onLoad( scope.parse( text, path ) );
} catch ( e ) {
if ( onError ) {
onError( e );
} else {
console.error( e );
}
scope.manager.itemError( url );
}
}, onProgress, onError );
},
setMaterialOptions: function ( value ) {
this.materialOptions = value;
return this;
},
/**
* Parses a MTL file.
*
* @param {String} text - Content of MTL file
* @return {MTLLoader.MaterialCreator}
*
* @see setPath setResourcePath
*
* @note In order for relative texture references to resolve correctly
* you must call setResourcePath() explicitly prior to parse.
*/
parse: function ( text, path ) {
var lines = text.split( '\n' );
var info = {};
var delimiter_pattern = /\s+/;
var materialsInfo = {};
for ( var i = 0; i < lines.length; i ++ ) {
var line = lines[ i ];
line = line.trim();
if ( line.length === 0 || line.charAt( 0 ) === '#' ) {
// Blank line or comment ignore
continue;
}
var pos = line.indexOf( ' ' );
var key = ( pos >= 0 ) ? line.substring( 0, pos ) : line;
key = key.toLowerCase();
var value = ( pos >= 0 ) ? line.substring( pos + 1 ) : '';
value = value.trim();
if ( key === 'newmtl' ) {
// New material
info = { name: value };
materialsInfo[ value ] = info;
} else {
if ( key === 'ka' || key === 'kd' || key === 'ks' || key === 'ke' ) {
var ss = value.split( delimiter_pattern, 3 );
info[ key ] = [ parseFloat( ss[ 0 ] ), parseFloat( ss[ 1 ] ), parseFloat( ss[ 2 ] ) ];
} else {
info[ key ] = value;
}
}
}
var materialCreator = new MTLLoader.MaterialCreator( this.resourcePath || path, this.materialOptions );
materialCreator.setCrossOrigin( this.crossOrigin );
materialCreator.setManager( this.manager );
materialCreator.setMaterials( materialsInfo );
return materialCreator;
}
} );
/**
* Create a new MTLLoader.MaterialCreator
* @param baseUrl - Url relative to which textures are loaded
* @param options - Set of options on how to construct the materials
* side: Which side to apply the material
* FrontSide (default), THREE.BackSide, THREE.DoubleSide
* wrap: What type of wrapping to apply for textures
* RepeatWrapping (default), THREE.ClampToEdgeWrapping, THREE.MirroredRepeatWrapping
* normalizeRGB: RGBs need to be normalized to 0-1 from 0-255
* Default: false, assumed to be already normalized
* ignoreZeroRGBs: Ignore values of RGBs (Ka,Kd,Ks) that are all 0's
* Default: false
* @constructor
*/
MTLLoader.MaterialCreator = function ( baseUrl, options ) {
this.baseUrl = baseUrl || '';
this.options = options;
this.materialsInfo = {};
this.materials = {};
this.materialsArray = [];
this.nameLookup = {};
this.side = ( this.options && this.options.side ) ? this.options.side : FrontSide;
this.wrap = ( this.options && this.options.wrap ) ? this.options.wrap : RepeatWrapping;
};
MTLLoader.MaterialCreator.prototype = {
constructor: MTLLoader.MaterialCreator,
crossOrigin: 'anonymous',
setCrossOrigin: function ( value ) {
this.crossOrigin = value;
return this;
},
setManager: function ( value ) {
this.manager = value;
},
setMaterials: function ( materialsInfo ) {
this.materialsInfo = this.convert( materialsInfo );
this.materials = {};
this.materialsArray = [];
this.nameLookup = {};
},
convert: function ( materialsInfo ) {
if ( ! this.options ) return materialsInfo;
var converted = {};
for ( var mn in materialsInfo ) {
// Convert materials info into normalized form based on options
var mat = materialsInfo[ mn ];
var covmat = {};
converted[ mn ] = covmat;
for ( var prop in mat ) {
var save = true;
var value = mat[ prop ];
var lprop = prop.toLowerCase();
switch ( lprop ) {
case 'kd':
case 'ka':
case 'ks':
// Diffuse color (color under white light) using RGB values
if ( this.options && this.options.normalizeRGB ) {
value = [ value[ 0 ] / 255, value[ 1 ] / 255, value[ 2 ] / 255 ];
}
if ( this.options && this.options.ignoreZeroRGBs ) {
if ( value[ 0 ] === 0 && value[ 1 ] === 0 && value[ 2 ] === 0 ) {
// ignore
save = false;
}
}
break;
default:
break;
}
if ( save ) {
covmat[ lprop ] = value;
}
}
}
return converted;
},
preload: function () {
for ( var mn in this.materialsInfo ) {
this.create( mn );
}
},
getIndex: function ( materialName ) {
return this.nameLookup[ materialName ];
},
getAsArray: function () {
var index = 0;
for ( var mn in this.materialsInfo ) {
this.materialsArray[ index ] = this.create( mn );
this.nameLookup[ mn ] = index;
index ++;
}
return this.materialsArray;
},
create: function ( materialName ) {
if ( this.materials[ materialName ] === undefined ) {
this.createMaterial_( materialName );
}
return this.materials[ materialName ];
},
createMaterial_: function ( materialName ) {
// Create material
var scope = this;
var mat = this.materialsInfo[ materialName ];
var params = {
name: materialName,
side: this.side
};
function resolveURL( baseUrl, url ) {
if ( typeof url !== 'string' || url === '' )
return '';
// Absolute URL
if ( /^https?:\/\//i.test( url ) ) return url;
return baseUrl + url;
}
function setMapForType( mapType, value ) {
if ( params[ mapType ] ) return; // Keep the first encountered texture
var texParams = scope.getTextureParams( value, params );
var map = scope.loadTexture( resolveURL( scope.baseUrl, texParams.url ) );
map.repeat.copy( texParams.scale );
map.offset.copy( texParams.offset );
map.wrapS = scope.wrap;
map.wrapT = scope.wrap;
params[ mapType ] = map;
}
for ( var prop in mat ) {
var value = mat[ prop ];
var n;
if ( value === '' ) continue;
switch ( prop.toLowerCase() ) {
// Ns is material specular exponent
case 'kd':
// Diffuse color (color under white light) using RGB values
params.color = new Color().fromArray( value );
break;
case 'ks':
// Specular color (color when light is reflected from shiny surface) using RGB values
params.specular = new Color().fromArray( value );
break;
case 'ke':
// Emissive using RGB values
params.emissive = new Color().fromArray( value );
break;
case 'map_kd':
// Diffuse texture map
setMapForType( 'map', value );
break;
case 'map_ks':
// Specular map
setMapForType( 'specularMap', value );
break;
case 'map_ke':
// Emissive map
setMapForType( 'emissiveMap', value );
break;
case 'norm':
setMapForType( 'normalMap', value );
break;
case 'map_bump':
case 'bump':
// Bump texture map
setMapForType( 'bumpMap', value );
break;
case 'map_d':
// Alpha map
setMapForType( 'alphaMap', value );
params.transparent = true;
break;
case 'ns':
// The specular exponent (defines the focus of the specular highlight)
// A high exponent results in a tight, concentrated highlight. Ns values normally range from 0 to 1000.
params.shininess = parseFloat( value );
break;
case 'd':
n = parseFloat( value );
if ( n < 1 ) {
params.opacity = n;
params.transparent = true;
}
break;
case 'tr':
n = parseFloat( value );
if ( this.options && this.options.invertTrProperty ) n = 1 - n;
if ( n > 0 ) {
params.opacity = 1 - n;
params.transparent = true;
}
break;
default:
break;
}
}
this.materials[ materialName ] = new MeshPhongMaterial( params );
return this.materials[ materialName ];
},
getTextureParams: function ( value, matParams ) {
var texParams = {
scale: new Vector2( 1, 1 ),
offset: new Vector2( 0, 0 )
};
var items = value.split( /\s+/ );
var pos;
pos = items.indexOf( '-bm' );
if ( pos >= 0 ) {
matParams.bumpScale = parseFloat( items[ pos + 1 ] );
items.splice( pos, 2 );
}
pos = items.indexOf( '-s' );
if ( pos >= 0 ) {
texParams.scale.set( parseFloat( items[ pos + 1 ] ), parseFloat( items[ pos + 2 ] ) );
items.splice( pos, 4 ); // we expect 3 parameters here!
}
pos = items.indexOf( '-o' );
if ( pos >= 0 ) {
texParams.offset.set( parseFloat( items[ pos + 1 ] ), parseFloat( items[ pos + 2 ] ) );
items.splice( pos, 4 ); // we expect 3 parameters here!
}
texParams.url = items.join( ' ' ).trim();
return texParams;
},
loadTexture: function ( url, mapping, onLoad, onProgress, onError ) {
var texture;
var manager = ( this.manager !== undefined ) ? this.manager : DefaultLoadingManager;
var loader = manager.getHandler( url );
if ( loader === null ) {
loader = new TextureLoader( manager );
}
if ( loader.setCrossOrigin ) loader.setCrossOrigin( this.crossOrigin );
texture = loader.load( url, onLoad, onProgress, onError );
if ( mapping !== undefined ) texture.mapping = mapping;
return texture;
}
};
export { MTLLoader };

View File

@@ -0,0 +1,640 @@
import {
FileLoader,
Loader,
Matrix4,
Vector3
} from '../../../build/three.module.js';
import { Zlib } from '../libs/gunzip.module.min.js';
import { Volume } from '../misc/Volume.js';
var NRRDLoader = function ( manager ) {
Loader.call( this, manager );
};
NRRDLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
constructor: NRRDLoader,
load: function ( url, onLoad, onProgress, onError ) {
var scope = this;
var loader = new FileLoader( scope.manager );
loader.setPath( scope.path );
loader.setResponseType( 'arraybuffer' );
loader.setRequestHeader( scope.requestHeader );
loader.setWithCredentials( scope.withCredentials );
loader.load( url, function ( data ) {
try {
onLoad( scope.parse( data ) );
} catch ( e ) {
if ( onError ) {
onError( e );
} else {
console.error( e );
}
scope.manager.itemError( url );
}
}, onProgress, onError );
},
parse: function ( data ) {
// this parser is largely inspired from the XTK NRRD parser : https://github.com/xtk/X
var _data = data;
var _dataPointer = 0;
var _nativeLittleEndian = new Int8Array( new Int16Array( [ 1 ] ).buffer )[ 0 ] > 0;
var _littleEndian = true;
var headerObject = {};
function scan( type, chunks ) {
if ( chunks === undefined || chunks === null ) {
chunks = 1;
}
var _chunkSize = 1;
var _array_type = Uint8Array;
switch ( type ) {
// 1 byte data types
case 'uchar':
break;
case 'schar':
_array_type = Int8Array;
break;
// 2 byte data types
case 'ushort':
_array_type = Uint16Array;
_chunkSize = 2;
break;
case 'sshort':
_array_type = Int16Array;
_chunkSize = 2;
break;
// 4 byte data types
case 'uint':
_array_type = Uint32Array;
_chunkSize = 4;
break;
case 'sint':
_array_type = Int32Array;
_chunkSize = 4;
break;
case 'float':
_array_type = Float32Array;
_chunkSize = 4;
break;
case 'complex':
_array_type = Float64Array;
_chunkSize = 8;
break;
case 'double':
_array_type = Float64Array;
_chunkSize = 8;
break;
}
// increase the data pointer in-place
var _bytes = new _array_type( _data.slice( _dataPointer,
_dataPointer += chunks * _chunkSize ) );
// if required, flip the endianness of the bytes
if ( _nativeLittleEndian != _littleEndian ) {
// we need to flip here since the format doesn't match the native endianness
_bytes = flipEndianness( _bytes, _chunkSize );
}
if ( chunks == 1 ) {
// if only one chunk was requested, just return one value
return _bytes[ 0 ];
}
// return the byte array
return _bytes;
}
//Flips typed array endianness in-place. Based on https://github.com/kig/DataStream.js/blob/master/DataStream.js.
function flipEndianness( array, chunkSize ) {
var u8 = new Uint8Array( array.buffer, array.byteOffset, array.byteLength );
for ( var i = 0; i < array.byteLength; i += chunkSize ) {
for ( var j = i + chunkSize - 1, k = i; j > k; j --, k ++ ) {
var tmp = u8[ k ];
u8[ k ] = u8[ j ];
u8[ j ] = tmp;
}
}
return array;
}
//parse the header
function parseHeader( header ) {
var data, field, fn, i, l, lines, m, _i, _len;
lines = header.split( /\r?\n/ );
for ( _i = 0, _len = lines.length; _i < _len; _i ++ ) {
l = lines[ _i ];
if ( l.match( /NRRD\d+/ ) ) {
headerObject.isNrrd = true;
} else if ( l.match( /^#/ ) ) {
} else if ( m = l.match( /(.*):(.*)/ ) ) {
field = m[ 1 ].trim();
data = m[ 2 ].trim();
fn = NRRDLoader.prototype.fieldFunctions[ field ];
if ( fn ) {
fn.call( headerObject, data );
} else {
headerObject[ field ] = data;
}
}
}
if ( ! headerObject.isNrrd ) {
throw new Error( 'Not an NRRD file' );
}
if ( headerObject.encoding === 'bz2' || headerObject.encoding === 'bzip2' ) {
throw new Error( 'Bzip is not supported' );
}
if ( ! headerObject.vectors ) {
//if no space direction is set, let's use the identity
headerObject.vectors = [ new Vector3( 1, 0, 0 ), new Vector3( 0, 1, 0 ), new Vector3( 0, 0, 1 ) ];
//apply spacing if defined
if ( headerObject.spacings ) {
for ( i = 0; i <= 2; i ++ ) {
if ( ! isNaN( headerObject.spacings[ i ] ) ) {
headerObject.vectors[ i ].multiplyScalar( headerObject.spacings[ i ] );
}
}
}
}
}
//parse the data when registred as one of this type : 'text', 'ascii', 'txt'
function parseDataAsText( data, start, end ) {
var number = '';
start = start || 0;
end = end || data.length;
var value;
//length of the result is the product of the sizes
var lengthOfTheResult = headerObject.sizes.reduce( function ( previous, current ) {
return previous * current;
}, 1 );
var base = 10;
if ( headerObject.encoding === 'hex' ) {
base = 16;
}
var result = new headerObject.__array( lengthOfTheResult );
var resultIndex = 0;
var parsingFunction = parseInt;
if ( headerObject.__array === Float32Array || headerObject.__array === Float64Array ) {
parsingFunction = parseFloat;
}
for ( var i = start; i < end; i ++ ) {
value = data[ i ];
//if value is not a space
if ( ( value < 9 || value > 13 ) && value !== 32 ) {
number += String.fromCharCode( value );
} else {
if ( number !== '' ) {
result[ resultIndex ] = parsingFunction( number, base );
resultIndex ++;
}
number = '';
}
}
if ( number !== '' ) {
result[ resultIndex ] = parsingFunction( number, base );
resultIndex ++;
}
return result;
}
var _bytes = scan( 'uchar', data.byteLength );
var _length = _bytes.length;
var _header = null;
var _data_start = 0;
var i;
for ( i = 1; i < _length; i ++ ) {
if ( _bytes[ i - 1 ] == 10 && _bytes[ i ] == 10 ) {
// we found two line breaks in a row
// now we know what the header is
_header = this.parseChars( _bytes, 0, i - 2 );
// this is were the data starts
_data_start = i + 1;
break;
}
}
// parse the header
parseHeader( _header );
var _data = _bytes.subarray( _data_start ); // the data without header
if ( headerObject.encoding === 'gzip' || headerObject.encoding === 'gz' ) {
// we need to decompress the datastream
// here we start the unzipping and get a typed Uint8Array back
var inflate = new Zlib.Gunzip( new Uint8Array( _data ) ); // eslint-disable-line no-undef
_data = inflate.decompress();
} else if ( headerObject.encoding === 'ascii' || headerObject.encoding === 'text' || headerObject.encoding === 'txt' || headerObject.encoding === 'hex' ) {
_data = parseDataAsText( _data );
} else if ( headerObject.encoding === 'raw' ) {
//we need to copy the array to create a new array buffer, else we retrieve the original arraybuffer with the header
var _copy = new Uint8Array( _data.length );
for ( var i = 0; i < _data.length; i ++ ) {
_copy[ i ] = _data[ i ];
}
_data = _copy;
}
// .. let's use the underlying array buffer
_data = _data.buffer;
var volume = new Volume();
volume.header = headerObject;
//
// parse the (unzipped) data to a datastream of the correct type
//
volume.data = new headerObject.__array( _data );
// get the min and max intensities
var min_max = volume.computeMinMax();
var min = min_max[ 0 ];
var max = min_max[ 1 ];
// attach the scalar range to the volume
volume.windowLow = min;
volume.windowHigh = max;
// get the image dimensions
volume.dimensions = [ headerObject.sizes[ 0 ], headerObject.sizes[ 1 ], headerObject.sizes[ 2 ] ];
volume.xLength = volume.dimensions[ 0 ];
volume.yLength = volume.dimensions[ 1 ];
volume.zLength = volume.dimensions[ 2 ];
// spacing
var spacingX = ( new Vector3( headerObject.vectors[ 0 ][ 0 ], headerObject.vectors[ 0 ][ 1 ],
headerObject.vectors[ 0 ][ 2 ] ) ).length();
var spacingY = ( new Vector3( headerObject.vectors[ 1 ][ 0 ], headerObject.vectors[ 1 ][ 1 ],
headerObject.vectors[ 1 ][ 2 ] ) ).length();
var spacingZ = ( new Vector3( headerObject.vectors[ 2 ][ 0 ], headerObject.vectors[ 2 ][ 1 ],
headerObject.vectors[ 2 ][ 2 ] ) ).length();
volume.spacing = [ spacingX, spacingY, spacingZ ];
// Create IJKtoRAS matrix
volume.matrix = new Matrix4();
var _spaceX = 1;
var _spaceY = 1;
var _spaceZ = 1;
if ( headerObject.space == 'left-posterior-superior' ) {
_spaceX = - 1;
_spaceY = - 1;
} else if ( headerObject.space === 'left-anterior-superior' ) {
_spaceX = - 1;
}
if ( ! headerObject.vectors ) {
volume.matrix.set(
_spaceX, 0, 0, 0,
0, _spaceY, 0, 0,
0, 0, _spaceZ, 0,
0, 0, 0, 1 );
} else {
var v = headerObject.vectors;
volume.matrix.set(
_spaceX * v[ 0 ][ 0 ], _spaceX * v[ 1 ][ 0 ], _spaceX * v[ 2 ][ 0 ], 0,
_spaceY * v[ 0 ][ 1 ], _spaceY * v[ 1 ][ 1 ], _spaceY * v[ 2 ][ 1 ], 0,
_spaceZ * v[ 0 ][ 2 ], _spaceZ * v[ 1 ][ 2 ], _spaceZ * v[ 2 ][ 2 ], 0,
0, 0, 0, 1 );
}
volume.inverseMatrix = new Matrix4();
volume.inverseMatrix.copy( volume.matrix ).invert();
volume.RASDimensions = ( new Vector3( volume.xLength, volume.yLength, volume.zLength ) ).applyMatrix4( volume.matrix ).round().toArray().map( Math.abs );
// .. and set the default threshold
// only if the threshold was not already set
if ( volume.lowerThreshold === - Infinity ) {
volume.lowerThreshold = min;
}
if ( volume.upperThreshold === Infinity ) {
volume.upperThreshold = max;
}
return volume;
},
parseChars: function ( array, start, end ) {
// without borders, use the whole array
if ( start === undefined ) {
start = 0;
}
if ( end === undefined ) {
end = array.length;
}
var output = '';
// create and append the chars
var i = 0;
for ( i = start; i < end; ++ i ) {
output += String.fromCharCode( array[ i ] );
}
return output;
},
fieldFunctions: {
type: function ( data ) {
switch ( data ) {
case 'uchar':
case 'unsigned char':
case 'uint8':
case 'uint8_t':
this.__array = Uint8Array;
break;
case 'signed char':
case 'int8':
case 'int8_t':
this.__array = Int8Array;
break;
case 'short':
case 'short int':
case 'signed short':
case 'signed short int':
case 'int16':
case 'int16_t':
this.__array = Int16Array;
break;
case 'ushort':
case 'unsigned short':
case 'unsigned short int':
case 'uint16':
case 'uint16_t':
this.__array = Uint16Array;
break;
case 'int':
case 'signed int':
case 'int32':
case 'int32_t':
this.__array = Int32Array;
break;
case 'uint':
case 'unsigned int':
case 'uint32':
case 'uint32_t':
this.__array = Uint32Array;
break;
case 'float':
this.__array = Float32Array;
break;
case 'double':
this.__array = Float64Array;
break;
default:
throw new Error( 'Unsupported NRRD data type: ' + data );
}
return this.type = data;
},
endian: function ( data ) {
return this.endian = data;
},
encoding: function ( data ) {
return this.encoding = data;
},
dimension: function ( data ) {
return this.dim = parseInt( data, 10 );
},
sizes: function ( data ) {
var i;
return this.sizes = ( function () {
var _i, _len, _ref, _results;
_ref = data.split( /\s+/ );
_results = [];
for ( _i = 0, _len = _ref.length; _i < _len; _i ++ ) {
i = _ref[ _i ];
_results.push( parseInt( i, 10 ) );
}
return _results;
} )();
},
space: function ( data ) {
return this.space = data;
},
'space origin': function ( data ) {
return this.space_origin = data.split( '(' )[ 1 ].split( ')' )[ 0 ].split( ',' );
},
'space directions': function ( data ) {
var f, parts, v;
parts = data.match( /\(.*?\)/g );
return this.vectors = ( function () {
var _i, _len, _results;
_results = [];
for ( _i = 0, _len = parts.length; _i < _len; _i ++ ) {
v = parts[ _i ];
_results.push( ( function () {
var _j, _len2, _ref, _results2;
_ref = v.slice( 1, - 1 ).split( /,/ );
_results2 = [];
for ( _j = 0, _len2 = _ref.length; _j < _len2; _j ++ ) {
f = _ref[ _j ];
_results2.push( parseFloat( f ) );
}
return _results2;
} )() );
}
return _results;
} )();
},
spacings: function ( data ) {
var f, parts;
parts = data.split( /\s+/ );
return this.spacings = ( function () {
var _i, _len, _results = [];
for ( _i = 0, _len = parts.length; _i < _len; _i ++ ) {
f = parts[ _i ];
_results.push( parseFloat( f ) );
}
return _results;
} )();
}
}
} );
export { NRRDLoader };

View File

@@ -0,0 +1,270 @@
import {
DefaultLoadingManager,
FileLoader
} from '../../../build/three.module.js';
import * as Nodes from '../nodes/Nodes.js';
var NodeMaterialLoader = function ( manager, library ) {
this.manager = ( manager !== undefined ) ? manager : DefaultLoadingManager;
this.nodes = {};
this.materials = {};
this.passes = {};
this.names = {};
this.library = library || {};
};
var NodeMaterialLoaderUtils = {
replaceUUIDObject: function ( object, uuid, value, recursive ) {
recursive = recursive !== undefined ? recursive : true;
if ( typeof uuid === 'object' ) uuid = uuid.uuid;
if ( typeof object === 'object' ) {
var keys = Object.keys( object );
for ( var i = 0; i < keys.length; i ++ ) {
var key = keys[ i ];
if ( recursive ) {
object[ key ] = this.replaceUUIDObject( object[ key ], uuid, value );
}
if ( key === uuid ) {
object[ uuid ] = object[ key ];
delete object[ key ];
}
}
}
return object === uuid ? value : object;
},
replaceUUID: function ( json, uuid, value ) {
this.replaceUUIDObject( json, uuid, value, false );
this.replaceUUIDObject( json.nodes, uuid, value );
this.replaceUUIDObject( json.materials, uuid, value );
this.replaceUUIDObject( json.passes, uuid, value );
this.replaceUUIDObject( json.library, uuid, value, false );
return json;
}
};
Object.assign( NodeMaterialLoader.prototype, {
load: function ( url, onLoad, onProgress, onError ) {
var scope = this;
var loader = new FileLoader( scope.manager );
loader.setPath( scope.path );
loader.load( url, function ( text ) {
onLoad( scope.parse( JSON.parse( text ) ) );
}, onProgress, onError );
return this;
},
setPath: function ( value ) {
this.path = value;
return this;
},
getObjectByName: function ( uuid ) {
return this.names[ uuid ];
},
getObjectById: function ( uuid ) {
return this.library[ uuid ] ||
this.nodes[ uuid ] ||
this.materials[ uuid ] ||
this.passes[ uuid ] ||
this.names[ uuid ];
},
getNode: function ( uuid ) {
var object = this.getObjectById( uuid );
if ( ! object ) {
console.warn( 'Node "' + uuid + '" not found.' );
}
return object;
},
resolve: function ( json ) {
switch ( typeof json ) {
case 'boolean':
case 'number':
return json;
case 'string':
if ( /^\w{8}-\w{4}-\w{4}-\w{4}-\w{12}$/i.test( json ) || this.library[ json ] ) {
return this.getNode( json );
}
return json;
default:
if ( Array.isArray( json ) ) {
for ( var i = 0; i < json.length; i ++ ) {
json[ i ] = this.resolve( json[ i ] );
}
} else {
for ( var prop in json ) {
if ( prop === 'uuid' ) continue;
json[ prop ] = this.resolve( json[ prop ] );
}
}
}
return json;
},
declare: function ( json ) {
var uuid, node, object;
for ( uuid in json.nodes ) {
node = json.nodes[ uuid ];
object = new Nodes[ node.nodeType + 'Node' ]();
if ( node.name ) {
object.name = node.name;
this.names[ object.name ] = object;
}
this.nodes[ uuid ] = object;
}
for ( uuid in json.materials ) {
node = json.materials[ uuid ];
object = new Nodes[ node.type ]();
if ( node.name ) {
object.name = node.name;
this.names[ object.name ] = object;
}
this.materials[ uuid ] = object;
}
for ( uuid in json.passes ) {
node = json.passes[ uuid ];
object = new Nodes[ node.type ]();
if ( node.name ) {
object.name = node.name;
this.names[ object.name ] = object;
}
this.passes[ uuid ] = object;
}
if ( json.material ) this.material = this.materials[ json.material ];
if ( json.pass ) this.pass = this.passes[ json.pass ];
return json;
},
parse: function ( json ) {
var uuid;
json = this.resolve( this.declare( json ) );
for ( uuid in json.nodes ) {
this.nodes[ uuid ].copy( json.nodes[ uuid ] );
}
for ( uuid in json.materials ) {
this.materials[ uuid ].copy( json.materials[ uuid ] );
}
for ( uuid in json.passes ) {
this.passes[ uuid ].copy( json.passes[ uuid ] );
}
return this.material || this.pass || this;
}
} );
export { NodeMaterialLoader, NodeMaterialLoaderUtils };

View File

@@ -0,0 +1,918 @@
import {
BufferGeometry,
FileLoader,
Float32BufferAttribute,
Group,
LineBasicMaterial,
LineSegments,
Loader,
Material,
Mesh,
MeshPhongMaterial,
Points,
PointsMaterial,
Vector3
} from '../build/three.module.js';
var OBJLoader = ( function () {
// o object_name | g group_name
var object_pattern = /^[og]\s*(.+)?/;
// mtllib file_reference
var material_library_pattern = /^mtllib /;
// usemtl material_name
var material_use_pattern = /^usemtl /;
// usemap map_name
var map_use_pattern = /^usemap /;
var vA = new Vector3();
var vB = new Vector3();
var vC = new Vector3();
var ab = new Vector3();
var cb = new Vector3();
function ParserState() {
var state = {
objects: [],
object: {},
vertices: [],
normals: [],
colors: [],
uvs: [],
materials: {},
materialLibraries: [],
startObject: function ( name, fromDeclaration ) {
// If the current object (initial from reset) is not from a g/o declaration in the parsed
// file. We need to use it for the first parsed g/o to keep things in sync.
if ( this.object && this.object.fromDeclaration === false ) {
this.object.name = name;
this.object.fromDeclaration = ( fromDeclaration !== false );
return;
}
var previousMaterial = ( this.object && typeof this.object.currentMaterial === 'function' ? this.object.currentMaterial() : undefined );
if ( this.object && typeof this.object._finalize === 'function' ) {
this.object._finalize( true );
}
this.object = {
name: name || '',
fromDeclaration: ( fromDeclaration !== false ),
geometry: {
vertices: [],
normals: [],
colors: [],
uvs: [],
hasUVIndices: false
},
materials: [],
smooth: true,
startMaterial: function ( name, libraries ) {
var previous = this._finalize( false );
// New usemtl declaration overwrites an inherited material, except if faces were declared
// after the material, then it must be preserved for proper MultiMaterial continuation.
if ( previous && ( previous.inherited || previous.groupCount <= 0 ) ) {
this.materials.splice( previous.index, 1 );
}
var material = {
index: this.materials.length,
name: name || '',
mtllib: ( Array.isArray( libraries ) && libraries.length > 0 ? libraries[ libraries.length - 1 ] : '' ),
smooth: ( previous !== undefined ? previous.smooth : this.smooth ),
groupStart: ( previous !== undefined ? previous.groupEnd : 0 ),
groupEnd: - 1,
groupCount: - 1,
inherited: false,
clone: function ( index ) {
var cloned = {
index: ( typeof index === 'number' ? index : this.index ),
name: this.name,
mtllib: this.mtllib,
smooth: this.smooth,
groupStart: 0,
groupEnd: - 1,
groupCount: - 1,
inherited: false
};
cloned.clone = this.clone.bind( cloned );
return cloned;
}
};
this.materials.push( material );
return material;
},
currentMaterial: function () {
if ( this.materials.length > 0 ) {
return this.materials[ this.materials.length - 1 ];
}
return undefined;
},
_finalize: function ( end ) {
var lastMultiMaterial = this.currentMaterial();
if ( lastMultiMaterial && lastMultiMaterial.groupEnd === - 1 ) {
lastMultiMaterial.groupEnd = this.geometry.vertices.length / 3;
lastMultiMaterial.groupCount = lastMultiMaterial.groupEnd - lastMultiMaterial.groupStart;
lastMultiMaterial.inherited = false;
}
// Ignore objects tail materials if no face declarations followed them before a new o/g started.
if ( end && this.materials.length > 1 ) {
for ( var mi = this.materials.length - 1; mi >= 0; mi -- ) {
if ( this.materials[ mi ].groupCount <= 0 ) {
this.materials.splice( mi, 1 );
}
}
}
// Guarantee at least one empty material, this makes the creation later more straight forward.
if ( end && this.materials.length === 0 ) {
this.materials.push( {
name: '',
smooth: this.smooth
} );
}
return lastMultiMaterial;
}
};
// Inherit previous objects material.
// Spec tells us that a declared material must be set to all objects until a new material is declared.
// If a usemtl declaration is encountered while this new object is being parsed, it will
// overwrite the inherited material. Exception being that there was already face declarations
// to the inherited material, then it will be preserved for proper MultiMaterial continuation.
if ( previousMaterial && previousMaterial.name && typeof previousMaterial.clone === 'function' ) {
var declared = previousMaterial.clone( 0 );
declared.inherited = true;
this.object.materials.push( declared );
}
this.objects.push( this.object );
},
finalize: function () {
if ( this.object && typeof this.object._finalize === 'function' ) {
this.object._finalize( true );
}
},
parseVertexIndex: function ( value, len ) {
var index = parseInt( value, 10 );
return ( index >= 0 ? index - 1 : index + len / 3 ) * 3;
},
parseNormalIndex: function ( value, len ) {
var index = parseInt( value, 10 );
return ( index >= 0 ? index - 1 : index + len / 3 ) * 3;
},
parseUVIndex: function ( value, len ) {
var index = parseInt( value, 10 );
return ( index >= 0 ? index - 1 : index + len / 2 ) * 2;
},
addVertex: function ( a, b, c ) {
var src = this.vertices;
var dst = this.object.geometry.vertices;
dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] );
dst.push( src[ b + 0 ], src[ b + 1 ], src[ b + 2 ] );
dst.push( src[ c + 0 ], src[ c + 1 ], src[ c + 2 ] );
},
addVertexPoint: function ( a ) {
var src = this.vertices;
var dst = this.object.geometry.vertices;
dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] );
},
addVertexLine: function ( a ) {
var src = this.vertices;
var dst = this.object.geometry.vertices;
dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] );
},
addNormal: function ( a, b, c ) {
var src = this.normals;
var dst = this.object.geometry.normals;
dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] );
dst.push( src[ b + 0 ], src[ b + 1 ], src[ b + 2 ] );
dst.push( src[ c + 0 ], src[ c + 1 ], src[ c + 2 ] );
},
addFaceNormal: function ( a, b, c ) {
var src = this.vertices;
var dst = this.object.geometry.normals;
vA.fromArray( src, a );
vB.fromArray( src, b );
vC.fromArray( src, c );
cb.subVectors( vC, vB );
ab.subVectors( vA, vB );
cb.cross( ab );
cb.normalize();
dst.push( cb.x, cb.y, cb.z );
dst.push( cb.x, cb.y, cb.z );
dst.push( cb.x, cb.y, cb.z );
},
addColor: function ( a, b, c ) {
var src = this.colors;
var dst = this.object.geometry.colors;
if ( src[ a ] !== undefined ) dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] );
if ( src[ b ] !== undefined ) dst.push( src[ b + 0 ], src[ b + 1 ], src[ b + 2 ] );
if ( src[ c ] !== undefined ) dst.push( src[ c + 0 ], src[ c + 1 ], src[ c + 2 ] );
},
addUV: function ( a, b, c ) {
var src = this.uvs;
var dst = this.object.geometry.uvs;
dst.push( src[ a + 0 ], src[ a + 1 ] );
dst.push( src[ b + 0 ], src[ b + 1 ] );
dst.push( src[ c + 0 ], src[ c + 1 ] );
},
addDefaultUV: function () {
var dst = this.object.geometry.uvs;
dst.push( 0, 0 );
dst.push( 0, 0 );
dst.push( 0, 0 );
},
addUVLine: function ( a ) {
var src = this.uvs;
var dst = this.object.geometry.uvs;
dst.push( src[ a + 0 ], src[ a + 1 ] );
},
addFace: function ( a, b, c, ua, ub, uc, na, nb, nc ) {
var vLen = this.vertices.length;
var ia = this.parseVertexIndex( a, vLen );
var ib = this.parseVertexIndex( b, vLen );
var ic = this.parseVertexIndex( c, vLen );
this.addVertex( ia, ib, ic );
this.addColor( ia, ib, ic );
// normals
if ( na !== undefined && na !== '' ) {
var nLen = this.normals.length;
ia = this.parseNormalIndex( na, nLen );
ib = this.parseNormalIndex( nb, nLen );
ic = this.parseNormalIndex( nc, nLen );
this.addNormal( ia, ib, ic );
} else {
this.addFaceNormal( ia, ib, ic );
}
// uvs
if ( ua !== undefined && ua !== '' ) {
var uvLen = this.uvs.length;
ia = this.parseUVIndex( ua, uvLen );
ib = this.parseUVIndex( ub, uvLen );
ic = this.parseUVIndex( uc, uvLen );
this.addUV( ia, ib, ic );
this.object.geometry.hasUVIndices = true;
} else {
// add placeholder values (for inconsistent face definitions)
this.addDefaultUV();
}
},
addPointGeometry: function ( vertices ) {
this.object.geometry.type = 'Points';
var vLen = this.vertices.length;
for ( var vi = 0, l = vertices.length; vi < l; vi ++ ) {
var index = this.parseVertexIndex( vertices[ vi ], vLen );
this.addVertexPoint( index );
this.addColor( index );
}
},
addLineGeometry: function ( vertices, uvs ) {
this.object.geometry.type = 'Line';
var vLen = this.vertices.length;
var uvLen = this.uvs.length;
for ( var vi = 0, l = vertices.length; vi < l; vi ++ ) {
this.addVertexLine( this.parseVertexIndex( vertices[ vi ], vLen ) );
}
for ( var uvi = 0, l = uvs.length; uvi < l; uvi ++ ) {
this.addUVLine( this.parseUVIndex( uvs[ uvi ], uvLen ) );
}
}
};
state.startObject( '', false );
return state;
}
//
function OBJLoader( manager ) {
Loader.call( this, manager );
this.materials = null;
}
OBJLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
constructor: OBJLoader,
load: function ( url, onLoad, onProgress, onError ) {
var scope = this;
var loader = new FileLoader( this.manager );
loader.setPath( this.path );
loader.setRequestHeader( this.requestHeader );
loader.setWithCredentials( this.withCredentials );
loader.load( url, function ( text ) {
try {
onLoad( scope.parse( text ) );
} catch ( e ) {
if ( onError ) {
onError( e );
} else {
console.error( e );
}
scope.manager.itemError( url );
}
}, onProgress, onError );
},
setMaterials: function ( materials ) {
this.materials = materials;
return this;
},
parse: function ( text ) {
var state = new ParserState();
if ( text.indexOf( '\r\n' ) !== - 1 ) {
// This is faster than String.split with regex that splits on both
text = text.replace( /\r\n/g, '\n' );
}
if ( text.indexOf( '\\\n' ) !== - 1 ) {
// join lines separated by a line continuation character (\)
text = text.replace( /\\\n/g, '' );
}
var lines = text.split( '\n' );
var line = '', lineFirstChar = '';
var lineLength = 0;
var result = [];
// Faster to just trim left side of the line. Use if available.
var trimLeft = ( typeof ''.trimLeft === 'function' );
for ( var i = 0, l = lines.length; i < l; i ++ ) {
line = lines[ i ];
line = trimLeft ? line.trimLeft() : line.trim();
lineLength = line.length;
if ( lineLength === 0 ) continue;
lineFirstChar = line.charAt( 0 );
// @todo invoke passed in handler if any
if ( lineFirstChar === '#' ) continue;
if ( lineFirstChar === 'v' ) {
var data = line.split( /\s+/ );
switch ( data[ 0 ] ) {
case 'v':
state.vertices.push(
parseFloat( data[ 1 ] ),
parseFloat( data[ 2 ] ),
parseFloat( data[ 3 ] )
);
if ( data.length >= 7 ) {
state.colors.push(
parseFloat( data[ 4 ] ),
parseFloat( data[ 5 ] ),
parseFloat( data[ 6 ] )
);
} else {
// if no colors are defined, add placeholders so color and vertex indices match
state.colors.push( undefined, undefined, undefined );
}
break;
case 'vn':
state.normals.push(
parseFloat( data[ 1 ] ),
parseFloat( data[ 2 ] ),
parseFloat( data[ 3 ] )
);
break;
case 'vt':
state.uvs.push(
parseFloat( data[ 1 ] ),
parseFloat( data[ 2 ] )
);
break;
}
} else if ( lineFirstChar === 'f' ) {
var lineData = line.substr( 1 ).trim();
var vertexData = lineData.split( /\s+/ );
var faceVertices = [];
// Parse the face vertex data into an easy to work with format
for ( var j = 0, jl = vertexData.length; j < jl; j ++ ) {
var vertex = vertexData[ j ];
if ( vertex.length > 0 ) {
var vertexParts = vertex.split( '/' );
faceVertices.push( vertexParts );
}
}
// Draw an edge between the first vertex and all subsequent vertices to form an n-gon
var v1 = faceVertices[ 0 ];
for ( var j = 1, jl = faceVertices.length - 1; j < jl; j ++ ) {
var v2 = faceVertices[ j ];
var v3 = faceVertices[ j + 1 ];
state.addFace(
v1[ 0 ], v2[ 0 ], v3[ 0 ],
v1[ 1 ], v2[ 1 ], v3[ 1 ],
v1[ 2 ], v2[ 2 ], v3[ 2 ]
);
}
} else if ( lineFirstChar === 'l' ) {
var lineParts = line.substring( 1 ).trim().split( ' ' );
var lineVertices = [], lineUVs = [];
if ( line.indexOf( '/' ) === - 1 ) {
lineVertices = lineParts;
} else {
for ( var li = 0, llen = lineParts.length; li < llen; li ++ ) {
var parts = lineParts[ li ].split( '/' );
if ( parts[ 0 ] !== '' ) lineVertices.push( parts[ 0 ] );
if ( parts[ 1 ] !== '' ) lineUVs.push( parts[ 1 ] );
}
}
state.addLineGeometry( lineVertices, lineUVs );
} else if ( lineFirstChar === 'p' ) {
var lineData = line.substr( 1 ).trim();
var pointData = lineData.split( ' ' );
state.addPointGeometry( pointData );
} else if ( ( result = object_pattern.exec( line ) ) !== null ) {
// o object_name
// or
// g group_name
// WORKAROUND: https://bugs.chromium.org/p/v8/issues/detail?id=2869
// var name = result[ 0 ].substr( 1 ).trim();
var name = ( ' ' + result[ 0 ].substr( 1 ).trim() ).substr( 1 );
state.startObject( name );
} else if ( material_use_pattern.test( line ) ) {
// material
state.object.startMaterial( line.substring( 7 ).trim(), state.materialLibraries );
} else if ( material_library_pattern.test( line ) ) {
// mtl file
state.materialLibraries.push( line.substring( 7 ).trim() );
} else if ( map_use_pattern.test( line ) ) {
// the line is parsed but ignored since the loader assumes textures are defined MTL files
// (according to https://www.okino.com/conv/imp_wave.htm, 'usemap' is the old-style Wavefront texture reference method)
console.warn( 'THREE.OBJLoader: Rendering identifier "usemap" not supported. Textures must be defined in MTL files.' );
} else if ( lineFirstChar === 's' ) {
result = line.split( ' ' );
// smooth shading
// @todo Handle files that have varying smooth values for a set of faces inside one geometry,
// but does not define a usemtl for each face set.
// This should be detected and a dummy material created (later MultiMaterial and geometry groups).
// This requires some care to not create extra material on each smooth value for "normal" obj files.
// where explicit usemtl defines geometry groups.
// Example asset: examples/models/obj/cerberus/Cerberus.obj
/*
* http://paulbourke.net/dataformats/obj/
* or
* http://www.cs.utah.edu/~boulos/cs3505/obj_spec.pdf
*
* From chapter "Grouping" Syntax explanation "s group_number":
* "group_number is the smoothing group number. To turn off smoothing groups, use a value of 0 or off.
* Polygonal elements use group numbers to put elements in different smoothing groups. For free-form
* surfaces, smoothing groups are either turned on or off; there is no difference between values greater
* than 0."
*/
if ( result.length > 1 ) {
var value = result[ 1 ].trim().toLowerCase();
state.object.smooth = ( value !== '0' && value !== 'off' );
} else {
// ZBrush can produce "s" lines #11707
state.object.smooth = true;
}
var material = state.object.currentMaterial();
if ( material ) material.smooth = state.object.smooth;
} else {
// Handle null terminated files without exception
if ( line === '\0' ) continue;
console.warn( 'THREE.OBJLoader: Unexpected line: "' + line + '"' );
}
}
state.finalize();
var container = new Group();
container.materialLibraries = [].concat( state.materialLibraries );
var hasPrimitives = ! ( state.objects.length === 1 && state.objects[ 0 ].geometry.vertices.length === 0 );
if ( hasPrimitives === true ) {
for ( var i = 0, l = state.objects.length; i < l; i ++ ) {
var object = state.objects[ i ];
var geometry = object.geometry;
var materials = object.materials;
var isLine = ( geometry.type === 'Line' );
var isPoints = ( geometry.type === 'Points' );
var hasVertexColors = false;
// Skip o/g line declarations that did not follow with any faces
if ( geometry.vertices.length === 0 ) continue;
var buffergeometry = new BufferGeometry();
buffergeometry.setAttribute( 'position', new Float32BufferAttribute( geometry.vertices, 3 ) );
if ( geometry.normals.length > 0 ) {
buffergeometry.setAttribute( 'normal', new Float32BufferAttribute( geometry.normals, 3 ) );
}
if ( geometry.colors.length > 0 ) {
hasVertexColors = true;
buffergeometry.setAttribute( 'color', new Float32BufferAttribute( geometry.colors, 3 ) );
}
if ( geometry.hasUVIndices === true ) {
buffergeometry.setAttribute( 'uv', new Float32BufferAttribute( geometry.uvs, 2 ) );
}
// Create materials
var createdMaterials = [];
for ( var mi = 0, miLen = materials.length; mi < miLen; mi ++ ) {
var sourceMaterial = materials[ mi ];
var materialHash = sourceMaterial.name + '_' + sourceMaterial.smooth + '_' + hasVertexColors;
var material = state.materials[ materialHash ];
if ( this.materials !== null ) {
material = this.materials.create( sourceMaterial.name );
// mtl etc. loaders probably can't create line materials correctly, copy properties to a line material.
if ( isLine && material && ! ( material instanceof LineBasicMaterial ) ) {
var materialLine = new LineBasicMaterial();
Material.prototype.copy.call( materialLine, material );
materialLine.color.copy( material.color );
material = materialLine;
} else if ( isPoints && material && ! ( material instanceof PointsMaterial ) ) {
var materialPoints = new PointsMaterial( { size: 10, sizeAttenuation: false } );
Material.prototype.copy.call( materialPoints, material );
materialPoints.color.copy( material.color );
materialPoints.map = material.map;
material = materialPoints;
}
}
if ( material === undefined ) {
if ( isLine ) {
material = new LineBasicMaterial();
} else if ( isPoints ) {
material = new PointsMaterial( { size: 1, sizeAttenuation: false } );
} else {
material = new MeshPhongMaterial();
}
material.name = sourceMaterial.name;
material.flatShading = sourceMaterial.smooth ? false : true;
material.vertexColors = hasVertexColors;
state.materials[ materialHash ] = material;
}
createdMaterials.push( material );
}
// Create mesh
var mesh;
if ( createdMaterials.length > 1 ) {
for ( var mi = 0, miLen = materials.length; mi < miLen; mi ++ ) {
var sourceMaterial = materials[ mi ];
buffergeometry.addGroup( sourceMaterial.groupStart, sourceMaterial.groupCount, mi );
}
if ( isLine ) {
mesh = new LineSegments( buffergeometry, createdMaterials );
} else if ( isPoints ) {
mesh = new Points( buffergeometry, createdMaterials );
} else {
mesh = new Mesh( buffergeometry, createdMaterials );
}
} else {
if ( isLine ) {
mesh = new LineSegments( buffergeometry, createdMaterials[ 0 ] );
} else if ( isPoints ) {
mesh = new Points( buffergeometry, createdMaterials[ 0 ] );
} else {
mesh = new Mesh( buffergeometry, createdMaterials[ 0 ] );
}
}
mesh.name = object.name;
container.add( mesh );
}
} else {
// if there is only the default parser state object with no geometry data, interpret data as point cloud
if ( state.vertices.length > 0 ) {
var material = new PointsMaterial( { size: 1, sizeAttenuation: false } );
var buffergeometry = new BufferGeometry();
buffergeometry.setAttribute( 'position', new Float32BufferAttribute( state.vertices, 3 ) );
if ( state.colors.length > 0 && state.colors[ 0 ] !== undefined ) {
buffergeometry.setAttribute( 'color', new Float32BufferAttribute( state.colors, 3 ) );
material.vertexColors = true;
}
var points = new Points( buffergeometry, material );
container.add( points );
}
}
return container;
}
} );
return OBJLoader;
} )();
export { OBJLoader };

View File

@@ -0,0 +1,389 @@
/**
* Development repository: https://github.com/kaisalmen/WWOBJLoader
*/
import {
FileLoader,
Object3D,
Loader
} from '../../../build/three.module.js';
import { OBJLoader2Parser } from './obj2/OBJLoader2Parser.js';
import { MeshReceiver } from './obj2/shared/MeshReceiver.js';
import { MaterialHandler } from './obj2/shared/MaterialHandler.js';
/**
* Creates a new OBJLoader2. Use it to load OBJ data from files or to parse OBJ data from arraybuffer or text.
*
* @param {LoadingManager} [manager] The loadingManager for the loader to use. Default is {@link LoadingManager}
* @constructor
*/
const OBJLoader2 = function ( manager ) {
Loader.call( this, manager );
this.parser = new OBJLoader2Parser();
this.modelName = '';
this.instanceNo = 0;
this.baseObject3d = new Object3D();
this.materialHandler = new MaterialHandler();
this.meshReceiver = new MeshReceiver( this.materialHandler );
// as OBJLoader2 is no longer derived from OBJLoader2Parser, we need to override the default onAssetAvailable callback
const scope = this;
const defaultOnAssetAvailable = function ( payload ) {
scope._onAssetAvailable( payload );
};
this.parser.setCallbackOnAssetAvailable( defaultOnAssetAvailable );
};
OBJLoader2.OBJLOADER2_VERSION = '3.2.0';
console.info( 'Using OBJLoader2 version: ' + OBJLoader2.OBJLOADER2_VERSION );
OBJLoader2.prototype = Object.assign( Object.create( Loader.prototype ), {
constructor: OBJLoader2,
/**
* See {@link OBJLoader2Parser.setLogging}
* @return {OBJLoader2}
*/
setLogging: function ( enabled, debug ) {
this.parser.setLogging( enabled, debug );
return this;
},
/**
* See {@link OBJLoader2Parser.setMaterialPerSmoothingGroup}
* @return {OBJLoader2}
*/
setMaterialPerSmoothingGroup: function ( materialPerSmoothingGroup ) {
this.parser.setMaterialPerSmoothingGroup( materialPerSmoothingGroup );
return this;
},
/**
* See {@link OBJLoader2Parser.setUseOAsMesh}
* @return {OBJLoader2}
*/
setUseOAsMesh: function ( useOAsMesh ) {
this.parser.setUseOAsMesh( useOAsMesh );
return this;
},
/**
* See {@link OBJLoader2Parser.setUseIndices}
* @return {OBJLoader2}
*/
setUseIndices: function ( useIndices ) {
this.parser.setUseIndices( useIndices );
return this;
},
/**
* See {@link OBJLoader2Parser.setDisregardNormals}
* @return {OBJLoader2}
*/
setDisregardNormals: function ( disregardNormals ) {
this.parser.setDisregardNormals( disregardNormals );
return this;
},
/**
* Set the name of the model.
*
* @param {string} modelName
* @return {OBJLoader2}
*/
setModelName: function ( modelName ) {
this.modelName = modelName ? modelName : this.modelName;
return this;
},
/**
* Set the node where the loaded objects will be attached directly.
*
* @param {Object3D} baseObject3d Object already attached to scenegraph where new meshes will be attached to
* @return {OBJLoader2}
*/
setBaseObject3d: function ( baseObject3d ) {
this.baseObject3d = ( baseObject3d === undefined || baseObject3d === null ) ? this.baseObject3d : baseObject3d;
return this;
},
/**
* Add materials as associated array.
*
* @param {Object} materials Object with named {@link Material}
* @param overrideExisting boolean Override existing material
* @return {OBJLoader2}
*/
addMaterials: function ( materials, overrideExisting ) {
this.materialHandler.addMaterials( materials, overrideExisting );
return this;
},
/**
* See {@link OBJLoader2Parser.setCallbackOnAssetAvailable}
* @return {OBJLoader2}
*/
setCallbackOnAssetAvailable: function ( onAssetAvailable ) {
this.parser.setCallbackOnAssetAvailable( onAssetAvailable );
return this;
},
/**
* See {@link OBJLoader2Parser.setCallbackOnProgress}
* @return {OBJLoader2}
*/
setCallbackOnProgress: function ( onProgress ) {
this.parser.setCallbackOnProgress( onProgress );
return this;
},
/**
* See {@link OBJLoader2Parser.setCallbackOnError}
* @return {OBJLoader2}
*/
setCallbackOnError: function ( onError ) {
this.parser.setCallbackOnError( onError );
return this;
},
/**
* See {@link OBJLoader2Parser.setCallbackOnLoad}
* @return {OBJLoader2}
*/
setCallbackOnLoad: function ( onLoad ) {
this.parser.setCallbackOnLoad( onLoad );
return this;
},
/**
* Register a function that is called once a single mesh is available and it could be altered by the supplied function.
*
* @param {Function} [onMeshAlter]
* @return {OBJLoader2}
*/
setCallbackOnMeshAlter: function ( onMeshAlter ) {
this.meshReceiver._setCallbacks( this.parser.callbacks.onProgress, onMeshAlter );
return this;
},
/**
* Register a function that is called once all materials have been loaded and they could be altered by the supplied function.
*
* @param {Function} [onLoadMaterials]
* @return {OBJLoader2}
*/
setCallbackOnLoadMaterials: function ( onLoadMaterials ) {
this.materialHandler._setCallbacks( onLoadMaterials );
return this;
},
/**
* Use this convenient method to load a file at the given URL. By default the fileLoader uses an ArrayBuffer.
*
* @param {string} url A string containing the path/URL of the file to be loaded.
* @param {function} onLoad A function to be called after loading is successfully completed. The function receives loaded Object3D as an argument.
* @param {function} [onFileLoadProgress] A function to be called while the loading is in progress. The argument will be the XMLHttpRequest instance, which contains total and Integer bytes.
* @param {function} [onError] A function to be called if an error occurs during loading. The function receives the error as an argument.
* @param {function} [onMeshAlter] Called after every single mesh is made available by the parser
*/
load: function ( url, onLoad, onFileLoadProgress, onError, onMeshAlter ) {
const scope = this;
if ( onLoad === null || onLoad === undefined || ! ( onLoad instanceof Function ) ) {
const errorMessage = 'onLoad is not a function! Aborting...';
scope.parser.callbacks.onError( errorMessage );
throw errorMessage;
} else {
this.parser.setCallbackOnLoad( onLoad );
}
if ( onError === null || onError === undefined || ! ( onError instanceof Function ) ) {
onError = function ( event ) {
let errorMessage = event;
if ( event.currentTarget && event.currentTarget.statusText !== null ) {
errorMessage = 'Error occurred while downloading!\nurl: ' + event.currentTarget.responseURL + '\nstatus: ' + event.currentTarget.statusText;
}
scope.parser.callbacks.onError( errorMessage );
};
}
if ( ! url ) {
onError( 'An invalid url was provided. Unable to continue!' );
}
const urlFull = new URL( url, window.location.href ).href;
let filename = urlFull;
const urlParts = urlFull.split( '/' );
if ( urlParts.length > 2 ) {
filename = urlParts[ urlParts.length - 1 ];
this.path = urlParts.slice( 0, urlParts.length - 1 ).join( '/' ) + '/';
}
if ( onFileLoadProgress === null || onFileLoadProgress === undefined || ! ( onFileLoadProgress instanceof Function ) ) {
let numericalValueRef = 0;
let numericalValue = 0;
onFileLoadProgress = function ( event ) {
if ( ! event.lengthComputable ) return;
numericalValue = event.loaded / event.total;
if ( numericalValue > numericalValueRef ) {
numericalValueRef = numericalValue;
const output = 'Download of "' + url + '": ' + ( numericalValue * 100 ).toFixed( 2 ) + '%';
scope.parser.callbacks.onProgress( 'progressLoad', output, numericalValue );
}
};
}
this.setCallbackOnMeshAlter( onMeshAlter );
const fileLoaderOnLoad = function ( content ) {
scope.parser.callbacks.onLoad( scope.parse( content ), 'OBJLoader2#load: Parsing completed' );
};
const fileLoader = new FileLoader( this.manager );
fileLoader.setPath( this.path || this.resourcePath );
fileLoader.setResponseType( 'arraybuffer' );
fileLoader.load( filename, fileLoaderOnLoad, onFileLoadProgress, onError );
},
/**
* Parses OBJ data synchronously from arraybuffer or string and returns the {@link Object3D}.
*
* @param {arraybuffer|string} content OBJ data as Uint8Array or String
* @return {Object3D}
*/
parse: function ( content ) {
// fast-fail in case of illegal data
if ( content === null || content === undefined ) {
throw 'Provided content is not a valid ArrayBuffer or String. Unable to continue parsing';
}
if ( this.parser.logging.enabled ) {
console.time( 'OBJLoader parse: ' + this.modelName );
}
// Create default materials beforehand, but do not override previously set materials (e.g. during init)
this.materialHandler.createDefaultMaterials( false );
// code works directly on the material references, parser clear its materials before updating
this.parser.setMaterials( this.materialHandler.getMaterials() );
if ( content instanceof ArrayBuffer || content instanceof Uint8Array ) {
if ( this.parser.logging.enabled ) console.info( 'Parsing arrayBuffer...' );
this.parser.execute( content );
} else if ( typeof ( content ) === 'string' || content instanceof String ) {
if ( this.parser.logging.enabled ) console.info( 'Parsing text...' );
this.parser.executeLegacy( content );
} else {
this.parser.callbacks.onError( 'Provided content was neither of type String nor Uint8Array! Aborting...' );
}
if ( this.parser.logging.enabled ) {
console.timeEnd( 'OBJLoader parse: ' + this.modelName );
}
return this.baseObject3d;
},
_onAssetAvailable: function ( payload ) {
if ( payload.cmd !== 'assetAvailable' ) return;
if ( payload.type === 'mesh' ) {
const meshes = this.meshReceiver.buildMeshes( payload );
for ( const mesh of meshes ) {
this.baseObject3d.add( mesh );
}
} else if ( payload.type === 'material' ) {
this.materialHandler.addPayloadMaterials( payload );
}
}
} );
export { OBJLoader2 };

View File

@@ -0,0 +1,232 @@
/**
* Development repository: https://github.com/kaisalmen/WWOBJLoader
*/
// Imports only related to wrapper
import {
Object3D
} from '../../../build/three.module.js';
import {
CodeBuilderInstructions,
WorkerExecutionSupport
} from './obj2/worker/main/WorkerExecutionSupport.js';
import { CodeSerializer } from './obj2/utils/CodeSerializer.js';
import { OBJLoader2 } from './OBJLoader2.js';
// Imports only related to worker (when standard workers (modules aren't supported) are used)
import { OBJLoader2Parser } from './obj2/OBJLoader2Parser.js';
import {
WorkerRunner,
DefaultWorkerPayloadHandler,
ObjectManipulator
} from './obj2/worker/parallel/WorkerRunner.js';
/**
* Creates a new OBJLoader2Parallel. Use it to load OBJ data from files or to parse OBJ data from arraybuffer.
* It extends {@link OBJLoader2} with the capability to run the parser in a web worker.
*
* @param [LoadingManager] manager The loadingManager for the loader to use. Default is {@link LoadingManager}
* @constructor
*/
const OBJLoader2Parallel = function ( manager ) {
OBJLoader2.call( this, manager );
this.preferJsmWorker = false;
this.jsmWorkerUrl = null;
this.executeParallel = true;
this.workerExecutionSupport = new WorkerExecutionSupport();
};
OBJLoader2Parallel.OBJLOADER2_PARALLEL_VERSION = '3.2.0';
console.info( 'Using OBJLoader2Parallel version: ' + OBJLoader2Parallel.OBJLOADER2_PARALLEL_VERSION );
OBJLoader2Parallel.DEFAULT_JSM_WORKER_PATH = './jsm/loaders/obj2/worker/parallel/OBJLoader2JsmWorker.js';
OBJLoader2Parallel.prototype = Object.assign( Object.create( OBJLoader2.prototype ), {
constructor: OBJLoader2Parallel,
/**
* Execution of parse in parallel via Worker is default, but normal {OBJLoader2} parsing can be enforced via false here.
*
* @param {boolean} executeParallel True or False
* @return {OBJLoader2Parallel}
*/
setExecuteParallel: function ( executeParallel ) {
this.executeParallel = executeParallel === true;
return this;
},
/**
* Set whether jsm modules in workers should be used. This requires browser support which is currently only experimental.
* @param {boolean} preferJsmWorker True or False
* @param {URL} jsmWorkerUrl Provide complete jsm worker URL otherwise relative path to this module may not be correct
* @return {OBJLoader2Parallel}
*/
setJsmWorker: function ( preferJsmWorker, jsmWorkerUrl ) {
this.preferJsmWorker = preferJsmWorker === true;
if ( jsmWorkerUrl === undefined || jsmWorkerUrl === null ) {
throw 'The url to the jsm worker is not valid. Aborting...';
}
this.jsmWorkerUrl = jsmWorkerUrl;
return this;
},
/**
* Allow to get hold of {@link WorkerExecutionSupport} for configuration purposes.
* @return {WorkerExecutionSupport}
*/
getWorkerExecutionSupport: function () {
return this.workerExecutionSupport;
},
/**
* Provide instructions on what is to be contained in the worker.
* @return {CodeBuilderInstructions}
*/
buildWorkerCode: function () {
const codeBuilderInstructions = new CodeBuilderInstructions( true, true, this.preferJsmWorker );
if ( codeBuilderInstructions.isSupportsJsmWorker() ) {
codeBuilderInstructions.setJsmWorkerUrl( this.jsmWorkerUrl );
}
if ( codeBuilderInstructions.isSupportsStandardWorker() ) {
const objectManipulator = new ObjectManipulator();
const defaultWorkerPayloadHandler = new DefaultWorkerPayloadHandler( this.parser );
const workerRunner = new WorkerRunner( {} );
codeBuilderInstructions.addCodeFragment( CodeSerializer.serializeClass( OBJLoader2Parser, this.parser ) );
codeBuilderInstructions.addCodeFragment( CodeSerializer.serializeClass( ObjectManipulator, objectManipulator ) );
codeBuilderInstructions.addCodeFragment( CodeSerializer.serializeClass( DefaultWorkerPayloadHandler, defaultWorkerPayloadHandler ) );
codeBuilderInstructions.addCodeFragment( CodeSerializer.serializeClass( WorkerRunner, workerRunner ) );
const startCode = 'new ' + workerRunner.constructor.name + '( new ' + defaultWorkerPayloadHandler.constructor.name + '( new ' + this.parser.constructor.name + '() ) );';
codeBuilderInstructions.addStartCode( startCode );
}
return codeBuilderInstructions;
},
/**
* See {@link OBJLoader2.load}
*/
load: function ( content, onLoad, onFileLoadProgress, onError, onMeshAlter ) {
const scope = this;
function interceptOnLoad( object3d, message ) {
if ( object3d.name === 'OBJLoader2ParallelDummy' ) {
if ( scope.parser.logging.enabled && scope.parser.logging.debug ) {
console.debug( 'Received dummy answer from OBJLoader2Parallel#parse' );
}
} else {
onLoad( object3d, message );
}
}
OBJLoader2.prototype.load.call( this, content, interceptOnLoad, onFileLoadProgress, onError, onMeshAlter );
},
/**
* See {@link OBJLoader2.parse}
* The callback onLoad needs to be set to be able to receive the content if used in parallel mode.
* Fallback is possible via {@link OBJLoader2Parallel#setExecuteParallel}.
*/
parse: function ( content ) {
if ( this.executeParallel ) {
if ( this.parser.callbacks.onLoad === this.parser._onLoad ) {
throw 'No callback other than the default callback was provided! Aborting!';
}
// check if worker has been initialize before. If yes, skip init
if ( ! this.workerExecutionSupport.isWorkerLoaded( this.preferJsmWorker ) ) {
this.workerExecutionSupport.buildWorker( this.buildWorkerCode() );
const scope = this;
const scopedOnAssetAvailable = function ( payload ) {
scope._onAssetAvailable( payload );
};
function scopedOnLoad( message ) {
scope.parser.callbacks.onLoad( scope.baseObject3d, message );
}
this.workerExecutionSupport.updateCallbacks( scopedOnAssetAvailable, scopedOnLoad );
}
// Create default materials beforehand, but do not override previously set materials (e.g. during init)
this.materialHandler.createDefaultMaterials( false );
this.workerExecutionSupport.executeParallel(
{
params: {
modelName: this.modelName,
instanceNo: this.instanceNo,
useIndices: this.parser.useIndices,
disregardNormals: this.parser.disregardNormals,
materialPerSmoothingGroup: this.parser.materialPerSmoothingGroup,
useOAsMesh: this.parser.useOAsMesh,
materials: this.materialHandler.getMaterialsJSON()
},
data: {
input: content,
options: null
},
logging: {
enabled: this.parser.logging.enabled,
debug: this.parser.logging.debug
}
} );
const dummy = new Object3D();
dummy.name = 'OBJLoader2ParallelDummy';
return dummy;
} else {
return OBJLoader2.prototype.parse.call( this, content );
}
},
} );
export { OBJLoader2Parallel };

View File

@@ -0,0 +1,402 @@
import {
BufferGeometry,
FileLoader,
Float32BufferAttribute,
Loader,
LoaderUtils,
Points,
PointsMaterial
} from '../../../build/three.module.js';
var PCDLoader = function ( manager ) {
Loader.call( this, manager );
this.littleEndian = true;
};
PCDLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
constructor: PCDLoader,
load: function ( url, onLoad, onProgress, onError ) {
var scope = this;
var loader = new FileLoader( scope.manager );
loader.setPath( scope.path );
loader.setResponseType( 'arraybuffer' );
loader.setRequestHeader( scope.requestHeader );
loader.setWithCredentials( scope.withCredentials );
loader.load( url, function ( data ) {
try {
onLoad( scope.parse( data, url ) );
} catch ( e ) {
if ( onError ) {
onError( e );
} else {
console.error( e );
}
scope.manager.itemError( url );
}
}, onProgress, onError );
},
parse: function ( data, url ) {
// from https://gitlab.com/taketwo/three-pcd-loader/blob/master/decompress-lzf.js
function decompressLZF( inData, outLength ) {
var inLength = inData.length;
var outData = new Uint8Array( outLength );
var inPtr = 0;
var outPtr = 0;
var ctrl;
var len;
var ref;
do {
ctrl = inData[ inPtr ++ ];
if ( ctrl < ( 1 << 5 ) ) {
ctrl ++;
if ( outPtr + ctrl > outLength ) throw new Error( 'Output buffer is not large enough' );
if ( inPtr + ctrl > inLength ) throw new Error( 'Invalid compressed data' );
do {
outData[ outPtr ++ ] = inData[ inPtr ++ ];
} while ( -- ctrl );
} else {
len = ctrl >> 5;
ref = outPtr - ( ( ctrl & 0x1f ) << 8 ) - 1;
if ( inPtr >= inLength ) throw new Error( 'Invalid compressed data' );
if ( len === 7 ) {
len += inData[ inPtr ++ ];
if ( inPtr >= inLength ) throw new Error( 'Invalid compressed data' );
}
ref -= inData[ inPtr ++ ];
if ( outPtr + len + 2 > outLength ) throw new Error( 'Output buffer is not large enough' );
if ( ref < 0 ) throw new Error( 'Invalid compressed data' );
if ( ref >= outPtr ) throw new Error( 'Invalid compressed data' );
do {
outData[ outPtr ++ ] = outData[ ref ++ ];
} while ( -- len + 2 );
}
} while ( inPtr < inLength );
return outData;
}
function parseHeader( data ) {
var PCDheader = {};
var result1 = data.search( /[\r\n]DATA\s(\S*)\s/i );
var result2 = /[\r\n]DATA\s(\S*)\s/i.exec( data.substr( result1 - 1 ) );
PCDheader.data = result2[ 1 ];
PCDheader.headerLen = result2[ 0 ].length + result1;
PCDheader.str = data.substr( 0, PCDheader.headerLen );
// remove comments
PCDheader.str = PCDheader.str.replace( /\#.*/gi, '' );
// parse
PCDheader.version = /VERSION (.*)/i.exec( PCDheader.str );
PCDheader.fields = /FIELDS (.*)/i.exec( PCDheader.str );
PCDheader.size = /SIZE (.*)/i.exec( PCDheader.str );
PCDheader.type = /TYPE (.*)/i.exec( PCDheader.str );
PCDheader.count = /COUNT (.*)/i.exec( PCDheader.str );
PCDheader.width = /WIDTH (.*)/i.exec( PCDheader.str );
PCDheader.height = /HEIGHT (.*)/i.exec( PCDheader.str );
PCDheader.viewpoint = /VIEWPOINT (.*)/i.exec( PCDheader.str );
PCDheader.points = /POINTS (.*)/i.exec( PCDheader.str );
// evaluate
if ( PCDheader.version !== null )
PCDheader.version = parseFloat( PCDheader.version[ 1 ] );
if ( PCDheader.fields !== null )
PCDheader.fields = PCDheader.fields[ 1 ].split( ' ' );
if ( PCDheader.type !== null )
PCDheader.type = PCDheader.type[ 1 ].split( ' ' );
if ( PCDheader.width !== null )
PCDheader.width = parseInt( PCDheader.width[ 1 ] );
if ( PCDheader.height !== null )
PCDheader.height = parseInt( PCDheader.height[ 1 ] );
if ( PCDheader.viewpoint !== null )
PCDheader.viewpoint = PCDheader.viewpoint[ 1 ];
if ( PCDheader.points !== null )
PCDheader.points = parseInt( PCDheader.points[ 1 ], 10 );
if ( PCDheader.points === null )
PCDheader.points = PCDheader.width * PCDheader.height;
if ( PCDheader.size !== null ) {
PCDheader.size = PCDheader.size[ 1 ].split( ' ' ).map( function ( x ) {
return parseInt( x, 10 );
} );
}
if ( PCDheader.count !== null ) {
PCDheader.count = PCDheader.count[ 1 ].split( ' ' ).map( function ( x ) {
return parseInt( x, 10 );
} );
} else {
PCDheader.count = [];
for ( var i = 0, l = PCDheader.fields.length; i < l; i ++ ) {
PCDheader.count.push( 1 );
}
}
PCDheader.offset = {};
var sizeSum = 0;
for ( var i = 0, l = PCDheader.fields.length; i < l; i ++ ) {
if ( PCDheader.data === 'ascii' ) {
PCDheader.offset[ PCDheader.fields[ i ] ] = i;
} else {
PCDheader.offset[ PCDheader.fields[ i ] ] = sizeSum;
sizeSum += PCDheader.size[ i ] * PCDheader.count[ i ];
}
}
// for binary only
PCDheader.rowSize = sizeSum;
return PCDheader;
}
var textData = LoaderUtils.decodeText( new Uint8Array( data ) );
// parse header (always ascii format)
var PCDheader = parseHeader( textData );
// parse data
var position = [];
var normal = [];
var color = [];
// ascii
if ( PCDheader.data === 'ascii' ) {
var offset = PCDheader.offset;
var pcdData = textData.substr( PCDheader.headerLen );
var lines = pcdData.split( '\n' );
for ( var i = 0, l = lines.length; i < l; i ++ ) {
if ( lines[ i ] === '' ) continue;
var line = lines[ i ].split( ' ' );
if ( offset.x !== undefined ) {
position.push( parseFloat( line[ offset.x ] ) );
position.push( parseFloat( line[ offset.y ] ) );
position.push( parseFloat( line[ offset.z ] ) );
}
if ( offset.rgb !== undefined ) {
var rgb = parseFloat( line[ offset.rgb ] );
var r = ( rgb >> 16 ) & 0x0000ff;
var g = ( rgb >> 8 ) & 0x0000ff;
var b = ( rgb >> 0 ) & 0x0000ff;
color.push( r / 255, g / 255, b / 255 );
}
if ( offset.normal_x !== undefined ) {
normal.push( parseFloat( line[ offset.normal_x ] ) );
normal.push( parseFloat( line[ offset.normal_y ] ) );
normal.push( parseFloat( line[ offset.normal_z ] ) );
}
}
}
// binary-compressed
// normally data in PCD files are organized as array of structures: XYZRGBXYZRGB
// binary compressed PCD files organize their data as structure of arrays: XXYYZZRGBRGB
// that requires a totally different parsing approach compared to non-compressed data
if ( PCDheader.data === 'binary_compressed' ) {
var sizes = new Uint32Array( data.slice( PCDheader.headerLen, PCDheader.headerLen + 8 ) );
var compressedSize = sizes[ 0 ];
var decompressedSize = sizes[ 1 ];
var decompressed = decompressLZF( new Uint8Array( data, PCDheader.headerLen + 8, compressedSize ), decompressedSize );
var dataview = new DataView( decompressed.buffer );
var offset = PCDheader.offset;
for ( var i = 0; i < PCDheader.points; i ++ ) {
if ( offset.x !== undefined ) {
position.push( dataview.getFloat32( ( PCDheader.points * offset.x ) + PCDheader.size[ 0 ] * i, this.littleEndian ) );
position.push( dataview.getFloat32( ( PCDheader.points * offset.y ) + PCDheader.size[ 1 ] * i, this.littleEndian ) );
position.push( dataview.getFloat32( ( PCDheader.points * offset.z ) + PCDheader.size[ 2 ] * i, this.littleEndian ) );
}
if ( offset.rgb !== undefined ) {
color.push( dataview.getUint8( ( PCDheader.points * offset.rgb ) + PCDheader.size[ 3 ] * i + 0 ) / 255.0 );
color.push( dataview.getUint8( ( PCDheader.points * offset.rgb ) + PCDheader.size[ 3 ] * i + 1 ) / 255.0 );
color.push( dataview.getUint8( ( PCDheader.points * offset.rgb ) + PCDheader.size[ 3 ] * i + 2 ) / 255.0 );
}
if ( offset.normal_x !== undefined ) {
normal.push( dataview.getFloat32( ( PCDheader.points * offset.normal_x ) + PCDheader.size[ 4 ] * i, this.littleEndian ) );
normal.push( dataview.getFloat32( ( PCDheader.points * offset.normal_y ) + PCDheader.size[ 5 ] * i, this.littleEndian ) );
normal.push( dataview.getFloat32( ( PCDheader.points * offset.normal_z ) + PCDheader.size[ 6 ] * i, this.littleEndian ) );
}
}
}
// binary
if ( PCDheader.data === 'binary' ) {
var dataview = new DataView( data, PCDheader.headerLen );
var offset = PCDheader.offset;
for ( var i = 0, row = 0; i < PCDheader.points; i ++, row += PCDheader.rowSize ) {
if ( offset.x !== undefined ) {
position.push( dataview.getFloat32( row + offset.x, this.littleEndian ) );
position.push( dataview.getFloat32( row + offset.y, this.littleEndian ) );
position.push( dataview.getFloat32( row + offset.z, this.littleEndian ) );
}
if ( offset.rgb !== undefined ) {
color.push( dataview.getUint8( row + offset.rgb + 2 ) / 255.0 );
color.push( dataview.getUint8( row + offset.rgb + 1 ) / 255.0 );
color.push( dataview.getUint8( row + offset.rgb + 0 ) / 255.0 );
}
if ( offset.normal_x !== undefined ) {
normal.push( dataview.getFloat32( row + offset.normal_x, this.littleEndian ) );
normal.push( dataview.getFloat32( row + offset.normal_y, this.littleEndian ) );
normal.push( dataview.getFloat32( row + offset.normal_z, this.littleEndian ) );
}
}
}
// build geometry
var geometry = new BufferGeometry();
if ( position.length > 0 ) geometry.setAttribute( 'position', new Float32BufferAttribute( position, 3 ) );
if ( normal.length > 0 ) geometry.setAttribute( 'normal', new Float32BufferAttribute( normal, 3 ) );
if ( color.length > 0 ) geometry.setAttribute( 'color', new Float32BufferAttribute( color, 3 ) );
geometry.computeBoundingSphere();
// build material
var material = new PointsMaterial( { size: 0.005 } );
if ( color.length > 0 ) {
material.vertexColors = true;
} else {
material.color.setHex( Math.random() * 0xffffff );
}
// build point cloud
var mesh = new Points( geometry, material );
var name = url.split( '' ).reverse().join( '' );
name = /([^\/]*)/.exec( name );
name = name[ 1 ].split( '' ).reverse().join( '' );
mesh.name = name;
return mesh;
}
} );
export { PCDLoader };

View File

@@ -0,0 +1,234 @@
import {
BufferGeometry,
FileLoader,
Float32BufferAttribute,
Loader
} from '../../../build/three.module.js';
var PDBLoader = function ( manager ) {
Loader.call( this, manager );
};
PDBLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
constructor: PDBLoader,
load: function ( url, onLoad, onProgress, onError ) {
var scope = this;
var loader = new FileLoader( scope.manager );
loader.setPath( scope.path );
loader.setRequestHeader( scope.requestHeader );
loader.setWithCredentials( scope.withCredentials );
loader.load( url, function ( text ) {
try {
onLoad( scope.parse( text ) );
} catch ( e ) {
if ( onError ) {
onError( e );
} else {
console.error( e );
}
scope.manager.itemError( url );
}
}, onProgress, onError );
},
// Based on CanvasMol PDB parser
parse: function ( text ) {
function trim( text ) {
return text.replace( /^\s\s*/, '' ).replace( /\s\s*$/, '' );
}
function capitalize( text ) {
return text.charAt( 0 ).toUpperCase() + text.substr( 1 ).toLowerCase();
}
function hash( s, e ) {
return 's' + Math.min( s, e ) + 'e' + Math.max( s, e );
}
function parseBond( start, length ) {
var eatom = parseInt( lines[ i ].substr( start, length ) );
if ( eatom ) {
var h = hash( satom, eatom );
if ( _bhash[ h ] === undefined ) {
_bonds.push( [ satom - 1, eatom - 1, 1 ] );
_bhash[ h ] = _bonds.length - 1;
} else {
// doesn't really work as almost all PDBs
// have just normal bonds appearing multiple
// times instead of being double/triple bonds
// bonds[bhash[h]][2] += 1;
}
}
}
function buildGeometry() {
var build = {
geometryAtoms: new BufferGeometry(),
geometryBonds: new BufferGeometry(),
json: {
atoms: atoms
}
};
var geometryAtoms = build.geometryAtoms;
var geometryBonds = build.geometryBonds;
var i, l;
var x, y, z;
var verticesAtoms = [];
var colorsAtoms = [];
var verticesBonds = [];
// atoms
for ( i = 0, l = atoms.length; i < l; i ++ ) {
var atom = atoms[ i ];
x = atom[ 0 ];
y = atom[ 1 ];
z = atom[ 2 ];
verticesAtoms.push( x, y, z );
var r = atom[ 3 ][ 0 ] / 255;
var g = atom[ 3 ][ 1 ] / 255;
var b = atom[ 3 ][ 2 ] / 255;
colorsAtoms.push( r, g, b );
}
// bonds
for ( i = 0, l = _bonds.length; i < l; i ++ ) {
var bond = _bonds[ i ];
var start = bond[ 0 ];
var end = bond[ 1 ];
var startAtom = _atomMap[ start ];
var endAtom = _atomMap[ end ];
x = startAtom[ 0 ];
y = startAtom[ 1 ];
z = startAtom[ 2 ];
verticesBonds.push( x, y, z );
x = endAtom[ 0 ];
y = endAtom[ 1 ];
z = endAtom[ 2 ];
verticesBonds.push( x, y, z );
}
// build geometry
geometryAtoms.setAttribute( 'position', new Float32BufferAttribute( verticesAtoms, 3 ) );
geometryAtoms.setAttribute( 'color', new Float32BufferAttribute( colorsAtoms, 3 ) );
geometryBonds.setAttribute( 'position', new Float32BufferAttribute( verticesBonds, 3 ) );
return build;
}
var CPK = { h: [ 255, 255, 255 ], he: [ 217, 255, 255 ], li: [ 204, 128, 255 ], be: [ 194, 255, 0 ], b: [ 255, 181, 181 ], c: [ 144, 144, 144 ], n: [ 48, 80, 248 ], o: [ 255, 13, 13 ], f: [ 144, 224, 80 ], ne: [ 179, 227, 245 ], na: [ 171, 92, 242 ], mg: [ 138, 255, 0 ], al: [ 191, 166, 166 ], si: [ 240, 200, 160 ], p: [ 255, 128, 0 ], s: [ 255, 255, 48 ], cl: [ 31, 240, 31 ], ar: [ 128, 209, 227 ], k: [ 143, 64, 212 ], ca: [ 61, 255, 0 ], sc: [ 230, 230, 230 ], ti: [ 191, 194, 199 ], v: [ 166, 166, 171 ], cr: [ 138, 153, 199 ], mn: [ 156, 122, 199 ], fe: [ 224, 102, 51 ], co: [ 240, 144, 160 ], ni: [ 80, 208, 80 ], cu: [ 200, 128, 51 ], zn: [ 125, 128, 176 ], ga: [ 194, 143, 143 ], ge: [ 102, 143, 143 ], as: [ 189, 128, 227 ], se: [ 255, 161, 0 ], br: [ 166, 41, 41 ], kr: [ 92, 184, 209 ], rb: [ 112, 46, 176 ], sr: [ 0, 255, 0 ], y: [ 148, 255, 255 ], zr: [ 148, 224, 224 ], nb: [ 115, 194, 201 ], mo: [ 84, 181, 181 ], tc: [ 59, 158, 158 ], ru: [ 36, 143, 143 ], rh: [ 10, 125, 140 ], pd: [ 0, 105, 133 ], ag: [ 192, 192, 192 ], cd: [ 255, 217, 143 ], in: [ 166, 117, 115 ], sn: [ 102, 128, 128 ], sb: [ 158, 99, 181 ], te: [ 212, 122, 0 ], i: [ 148, 0, 148 ], xe: [ 66, 158, 176 ], cs: [ 87, 23, 143 ], ba: [ 0, 201, 0 ], la: [ 112, 212, 255 ], ce: [ 255, 255, 199 ], pr: [ 217, 255, 199 ], nd: [ 199, 255, 199 ], pm: [ 163, 255, 199 ], sm: [ 143, 255, 199 ], eu: [ 97, 255, 199 ], gd: [ 69, 255, 199 ], tb: [ 48, 255, 199 ], dy: [ 31, 255, 199 ], ho: [ 0, 255, 156 ], er: [ 0, 230, 117 ], tm: [ 0, 212, 82 ], yb: [ 0, 191, 56 ], lu: [ 0, 171, 36 ], hf: [ 77, 194, 255 ], ta: [ 77, 166, 255 ], w: [ 33, 148, 214 ], re: [ 38, 125, 171 ], os: [ 38, 102, 150 ], ir: [ 23, 84, 135 ], pt: [ 208, 208, 224 ], au: [ 255, 209, 35 ], hg: [ 184, 184, 208 ], tl: [ 166, 84, 77 ], pb: [ 87, 89, 97 ], bi: [ 158, 79, 181 ], po: [ 171, 92, 0 ], at: [ 117, 79, 69 ], rn: [ 66, 130, 150 ], fr: [ 66, 0, 102 ], ra: [ 0, 125, 0 ], ac: [ 112, 171, 250 ], th: [ 0, 186, 255 ], pa: [ 0, 161, 255 ], u: [ 0, 143, 255 ], np: [ 0, 128, 255 ], pu: [ 0, 107, 255 ], am: [ 84, 92, 242 ], cm: [ 120, 92, 227 ], bk: [ 138, 79, 227 ], cf: [ 161, 54, 212 ], es: [ 179, 31, 212 ], fm: [ 179, 31, 186 ], md: [ 179, 13, 166 ], no: [ 189, 13, 135 ], lr: [ 199, 0, 102 ], rf: [ 204, 0, 89 ], db: [ 209, 0, 79 ], sg: [ 217, 0, 69 ], bh: [ 224, 0, 56 ], hs: [ 230, 0, 46 ], mt: [ 235, 0, 38 ], ds: [ 235, 0, 38 ], rg: [ 235, 0, 38 ], cn: [ 235, 0, 38 ], uut: [ 235, 0, 38 ], uuq: [ 235, 0, 38 ], uup: [ 235, 0, 38 ], uuh: [ 235, 0, 38 ], uus: [ 235, 0, 38 ], uuo: [ 235, 0, 38 ] };
var atoms = [];
var _bonds = [];
var _bhash = {};
var _atomMap = {};
var x, y, z, index, e;
// parse
var lines = text.split( '\n' );
for ( var i = 0, l = lines.length; i < l; i ++ ) {
if ( lines[ i ].substr( 0, 4 ) === 'ATOM' || lines[ i ].substr( 0, 6 ) === 'HETATM' ) {
x = parseFloat( lines[ i ].substr( 30, 7 ) );
y = parseFloat( lines[ i ].substr( 38, 7 ) );
z = parseFloat( lines[ i ].substr( 46, 7 ) );
index = parseInt( lines[ i ].substr( 6, 5 ) ) - 1;
e = trim( lines[ i ].substr( 76, 2 ) ).toLowerCase();
if ( e === '' ) {
e = trim( lines[ i ].substr( 12, 2 ) ).toLowerCase();
}
var atomData = [ x, y, z, CPK[ e ], capitalize( e ) ];
atoms.push( atomData );
_atomMap[ index ] = atomData;
} else if ( lines[ i ].substr( 0, 6 ) === 'CONECT' ) {
var satom = parseInt( lines[ i ].substr( 6, 5 ) );
parseBond( 11, 5 );
parseBond( 16, 5 );
parseBond( 21, 5 );
parseBond( 26, 5 );
}
}
// build and return geometry
return buildGeometry();
}
} );
export { PDBLoader };

View File

@@ -0,0 +1,533 @@
import {
BufferGeometry,
FileLoader,
Float32BufferAttribute,
Loader,
LoaderUtils
} from '../build/three.module.js';
/**
* Description: A THREE loader for PLY ASCII files (known as the Polygon
* File Format or the Stanford Triangle Format).
*
* Limitations: ASCII decoding assumes file is UTF-8.
*
* Usage:
* var loader = new PLYLoader();
* loader.load('./models/ply/ascii/dolphins.ply', function (geometry) {
*
* scene.add( new THREE.Mesh( geometry ) );
*
* } );
*
* If the PLY file uses non standard property names, they can be mapped while
* loading. For example, the following maps the properties
* “diffuse_(red|green|blue)” in the file to standard color names.
*
* loader.setPropertyNameMapping( {
* diffuse_red: 'red',
* diffuse_green: 'green',
* diffuse_blue: 'blue'
* } );
*
*/
var PLYLoader = function ( manager ) {
Loader.call( this, manager );
this.propertyNameMapping = {};
};
PLYLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
constructor: PLYLoader,
load: function ( url, onLoad, onProgress, onError ) {
var scope = this;
var loader = new FileLoader( this.manager );
loader.setPath( this.path );
loader.setResponseType( 'arraybuffer' );
loader.setRequestHeader( this.requestHeader );
loader.setWithCredentials( this.withCredentials );
loader.load( url, function ( text ) {
try {
onLoad( scope.parse( text ) );
} catch ( e ) {
if ( onError ) {
onError( e );
} else {
console.error( e );
}
scope.manager.itemError( url );
}
}, onProgress, onError );
},
setPropertyNameMapping: function ( mapping ) {
this.propertyNameMapping = mapping;
},
parse: function ( data ) {
function parseHeader( data ) {
var patternHeader = /ply([\s\S]*)end_header\r?\n/;
var headerText = '';
var headerLength = 0;
var result = patternHeader.exec( data );
if ( result !== null ) {
headerText = result[ 1 ];
headerLength = new Blob( [ result[ 0 ] ] ).size;
}
var header = {
comments: [],
elements: [],
headerLength: headerLength,
objInfo: ''
};
var lines = headerText.split( '\n' );
var currentElement;
var lineType, lineValues;
function make_ply_element_property( propertValues, propertyNameMapping ) {
var property = { type: propertValues[ 0 ] };
if ( property.type === 'list' ) {
property.name = propertValues[ 3 ];
property.countType = propertValues[ 1 ];
property.itemType = propertValues[ 2 ];
} else {
property.name = propertValues[ 1 ];
}
if ( property.name in propertyNameMapping ) {
property.name = propertyNameMapping[ property.name ];
}
return property;
}
for ( var i = 0; i < lines.length; i ++ ) {
var line = lines[ i ];
line = line.trim();
if ( line === '' ) continue;
lineValues = line.split( /\s+/ );
lineType = lineValues.shift();
line = lineValues.join( ' ' );
switch ( lineType ) {
case 'format':
header.format = lineValues[ 0 ];
header.version = lineValues[ 1 ];
break;
case 'comment':
header.comments.push( line );
break;
case 'element':
if ( currentElement !== undefined ) {
header.elements.push( currentElement );
}
currentElement = {};
currentElement.name = lineValues[ 0 ];
currentElement.count = parseInt( lineValues[ 1 ] );
currentElement.properties = [];
break;
case 'property':
currentElement.properties.push( make_ply_element_property( lineValues, scope.propertyNameMapping ) );
break;
case 'obj_info':
header.objInfo = line;
break;
default:
console.log( 'unhandled', lineType, lineValues );
}
}
if ( currentElement !== undefined ) {
header.elements.push( currentElement );
}
return header;
}
function parseASCIINumber( n, type ) {
switch ( type ) {
case 'char': case 'uchar': case 'short': case 'ushort': case 'int': case 'uint':
case 'int8': case 'uint8': case 'int16': case 'uint16': case 'int32': case 'uint32':
return parseInt( n );
case 'float': case 'double': case 'float32': case 'float64':
return parseFloat( n );
}
}
function parseASCIIElement( properties, line ) {
var values = line.split( /\s+/ );
var element = {};
for ( var i = 0; i < properties.length; i ++ ) {
if ( properties[ i ].type === 'list' ) {
var list = [];
var n = parseASCIINumber( values.shift(), properties[ i ].countType );
for ( var j = 0; j < n; j ++ ) {
list.push( parseASCIINumber( values.shift(), properties[ i ].itemType ) );
}
element[ properties[ i ].name ] = list;
} else {
element[ properties[ i ].name ] = parseASCIINumber( values.shift(), properties[ i ].type );
}
}
return element;
}
function parseASCII( data, header ) {
// PLY ascii format specification, as per http://en.wikipedia.org/wiki/PLY_(file_format)
var buffer = {
indices: [],
vertices: [],
normals: [],
uvs: [],
faceVertexUvs: [],
colors: []
};
var result;
var patternBody = /end_header\s([\s\S]*)$/;
var body = '';
if ( ( result = patternBody.exec( data ) ) !== null ) {
body = result[ 1 ];
}
var lines = body.split( '\n' );
var currentElement = 0;
var currentElementCount = 0;
for ( var i = 0; i < lines.length; i ++ ) {
var line = lines[ i ];
line = line.trim();
if ( line === '' ) {
continue;
}
if ( currentElementCount >= header.elements[ currentElement ].count ) {
currentElement ++;
currentElementCount = 0;
}
var element = parseASCIIElement( header.elements[ currentElement ].properties, line );
handleElement( buffer, header.elements[ currentElement ].name, element );
currentElementCount ++;
}
return postProcess( buffer );
}
function postProcess( buffer ) {
var geometry = new BufferGeometry();
// mandatory buffer data
if ( buffer.indices.length > 0 ) {
geometry.setIndex( buffer.indices );
}
geometry.setAttribute( 'position', new Float32BufferAttribute( buffer.vertices, 3 ) );
// optional buffer data
if ( buffer.normals.length > 0 ) {
geometry.setAttribute( 'normal', new Float32BufferAttribute( buffer.normals, 3 ) );
}
if ( buffer.uvs.length > 0 ) {
geometry.setAttribute( 'uv', new Float32BufferAttribute( buffer.uvs, 2 ) );
}
if ( buffer.colors.length > 0 ) {
geometry.setAttribute( 'color', new Float32BufferAttribute( buffer.colors, 3 ) );
}
if ( buffer.faceVertexUvs.length > 0 ) {
geometry = geometry.toNonIndexed();
geometry.setAttribute( 'uv', new Float32BufferAttribute( buffer.faceVertexUvs, 2 ) );
}
geometry.computeBoundingSphere();
return geometry;
}
function handleElement( buffer, elementName, element ) {
if ( elementName === 'vertex' ) {
buffer.vertices.push( element.x, element.y, element.z );
if ( 'nx' in element && 'ny' in element && 'nz' in element ) {
buffer.normals.push( element.nx, element.ny, element.nz );
}
if ( 's' in element && 't' in element ) {
buffer.uvs.push( element.s, element.t );
}
if ( 'red' in element && 'green' in element && 'blue' in element ) {
buffer.colors.push( element.red / 255.0, element.green / 255.0, element.blue / 255.0 );
}
} else if ( elementName === 'face' ) {
var vertex_indices = element.vertex_indices || element.vertex_index; // issue #9338
var texcoord = element.texcoord;
if ( vertex_indices.length === 3 ) {
buffer.indices.push( vertex_indices[ 0 ], vertex_indices[ 1 ], vertex_indices[ 2 ] );
if ( texcoord && texcoord.length === 6 ) {
buffer.faceVertexUvs.push( texcoord[ 0 ], texcoord[ 1 ] );
buffer.faceVertexUvs.push( texcoord[ 2 ], texcoord[ 3 ] );
buffer.faceVertexUvs.push( texcoord[ 4 ], texcoord[ 5 ] );
}
} else if ( vertex_indices.length === 4 ) {
buffer.indices.push( vertex_indices[ 0 ], vertex_indices[ 1 ], vertex_indices[ 3 ] );
buffer.indices.push( vertex_indices[ 1 ], vertex_indices[ 2 ], vertex_indices[ 3 ] );
}
}
}
function binaryRead( dataview, at, type, little_endian ) {
switch ( type ) {
// corespondences for non-specific length types here match rply:
case 'int8': case 'char': return [ dataview.getInt8( at ), 1 ];
case 'uint8': case 'uchar': return [ dataview.getUint8( at ), 1 ];
case 'int16': case 'short': return [ dataview.getInt16( at, little_endian ), 2 ];
case 'uint16': case 'ushort': return [ dataview.getUint16( at, little_endian ), 2 ];
case 'int32': case 'int': return [ dataview.getInt32( at, little_endian ), 4 ];
case 'uint32': case 'uint': return [ dataview.getUint32( at, little_endian ), 4 ];
case 'float32': case 'float': return [ dataview.getFloat32( at, little_endian ), 4 ];
case 'float64': case 'double': return [ dataview.getFloat64( at, little_endian ), 8 ];
}
}
function binaryReadElement( dataview, at, properties, little_endian ) {
var element = {};
var result, read = 0;
for ( var i = 0; i < properties.length; i ++ ) {
if ( properties[ i ].type === 'list' ) {
var list = [];
result = binaryRead( dataview, at + read, properties[ i ].countType, little_endian );
var n = result[ 0 ];
read += result[ 1 ];
for ( var j = 0; j < n; j ++ ) {
result = binaryRead( dataview, at + read, properties[ i ].itemType, little_endian );
list.push( result[ 0 ] );
read += result[ 1 ];
}
element[ properties[ i ].name ] = list;
} else {
result = binaryRead( dataview, at + read, properties[ i ].type, little_endian );
element[ properties[ i ].name ] = result[ 0 ];
read += result[ 1 ];
}
}
return [ element, read ];
}
function parseBinary( data, header ) {
var buffer = {
indices: [],
vertices: [],
normals: [],
uvs: [],
faceVertexUvs: [],
colors: []
};
var little_endian = ( header.format === 'binary_little_endian' );
var body = new DataView( data, header.headerLength );
var result, loc = 0;
for ( var currentElement = 0; currentElement < header.elements.length; currentElement ++ ) {
for ( var currentElementCount = 0; currentElementCount < header.elements[ currentElement ].count; currentElementCount ++ ) {
result = binaryReadElement( body, loc, header.elements[ currentElement ].properties, little_endian );
loc += result[ 1 ];
var element = result[ 0 ];
handleElement( buffer, header.elements[ currentElement ].name, element );
}
}
return postProcess( buffer );
}
//
var geometry;
var scope = this;
if ( data instanceof ArrayBuffer ) {
var text = LoaderUtils.decodeText( new Uint8Array( data ) );
var header = parseHeader( text );
geometry = header.format === 'ascii' ? parseASCII( text, header ) : parseBinary( data, header );
} else {
geometry = parseASCII( data, parseHeader( data ) );
}
return geometry;
}
} );
export { PLYLoader };

View File

@@ -0,0 +1,316 @@
import {
BufferAttribute,
BufferGeometry,
FileLoader,
Loader
} from '../../../build/three.module.js';
/**
* See https://github.com/kchapelier/PRWM for more informations about this file format
*/
var PRWMLoader = ( function () {
var bigEndianPlatform = null;
/**
* Check if the endianness of the platform is big-endian (most significant bit first)
* @returns {boolean} True if big-endian, false if little-endian
*/
function isBigEndianPlatform() {
if ( bigEndianPlatform === null ) {
var buffer = new ArrayBuffer( 2 ),
uint8Array = new Uint8Array( buffer ),
uint16Array = new Uint16Array( buffer );
uint8Array[ 0 ] = 0xAA; // set first byte
uint8Array[ 1 ] = 0xBB; // set second byte
bigEndianPlatform = ( uint16Array[ 0 ] === 0xAABB );
}
return bigEndianPlatform;
}
// match the values defined in the spec to the TypedArray types
var InvertedEncodingTypes = [
null,
Float32Array,
null,
Int8Array,
Int16Array,
null,
Int32Array,
Uint8Array,
Uint16Array,
null,
Uint32Array
];
// define the method to use on a DataView, corresponding the TypedArray type
var getMethods = {
Uint16Array: 'getUint16',
Uint32Array: 'getUint32',
Int16Array: 'getInt16',
Int32Array: 'getInt32',
Float32Array: 'getFloat32',
Float64Array: 'getFloat64'
};
function copyFromBuffer( sourceArrayBuffer, viewType, position, length, fromBigEndian ) {
var bytesPerElement = viewType.BYTES_PER_ELEMENT,
result;
if ( fromBigEndian === isBigEndianPlatform() || bytesPerElement === 1 ) {
result = new viewType( sourceArrayBuffer, position, length );
} else {
var readView = new DataView( sourceArrayBuffer, position, length * bytesPerElement ),
getMethod = getMethods[ viewType.name ],
littleEndian = ! fromBigEndian,
i = 0;
result = new viewType( length );
for ( ; i < length; i ++ ) {
result[ i ] = readView[ getMethod ]( i * bytesPerElement, littleEndian );
}
}
return result;
}
function decodePrwm( buffer ) {
var array = new Uint8Array( buffer ),
version = array[ 0 ],
flags = array[ 1 ],
indexedGeometry = !! ( flags >> 7 & 0x01 ),
indicesType = flags >> 6 & 0x01,
bigEndian = ( flags >> 5 & 0x01 ) === 1,
attributesNumber = flags & 0x1F,
valuesNumber = 0,
indicesNumber = 0;
if ( bigEndian ) {
valuesNumber = ( array[ 2 ] << 16 ) + ( array[ 3 ] << 8 ) + array[ 4 ];
indicesNumber = ( array[ 5 ] << 16 ) + ( array[ 6 ] << 8 ) + array[ 7 ];
} else {
valuesNumber = array[ 2 ] + ( array[ 3 ] << 8 ) + ( array[ 4 ] << 16 );
indicesNumber = array[ 5 ] + ( array[ 6 ] << 8 ) + ( array[ 7 ] << 16 );
}
/** PRELIMINARY CHECKS **/
if ( version === 0 ) {
throw new Error( 'PRWM decoder: Invalid format version: 0' );
} else if ( version !== 1 ) {
throw new Error( 'PRWM decoder: Unsupported format version: ' + version );
}
if ( ! indexedGeometry ) {
if ( indicesType !== 0 ) {
throw new Error( 'PRWM decoder: Indices type must be set to 0 for non-indexed geometries' );
} else if ( indicesNumber !== 0 ) {
throw new Error( 'PRWM decoder: Number of indices must be set to 0 for non-indexed geometries' );
}
}
/** PARSING **/
var pos = 8;
var attributes = {},
attributeName,
char,
attributeType,
cardinality,
encodingType,
arrayType,
values,
indices,
i;
for ( i = 0; i < attributesNumber; i ++ ) {
attributeName = '';
while ( pos < array.length ) {
char = array[ pos ];
pos ++;
if ( char === 0 ) {
break;
} else {
attributeName += String.fromCharCode( char );
}
}
flags = array[ pos ];
attributeType = flags >> 7 & 0x01;
cardinality = ( flags >> 4 & 0x03 ) + 1;
encodingType = flags & 0x0F;
arrayType = InvertedEncodingTypes[ encodingType ];
pos ++;
// padding to next multiple of 4
pos = Math.ceil( pos / 4 ) * 4;
values = copyFromBuffer( buffer, arrayType, pos, cardinality * valuesNumber, bigEndian );
pos += arrayType.BYTES_PER_ELEMENT * cardinality * valuesNumber;
attributes[ attributeName ] = {
type: attributeType,
cardinality: cardinality,
values: values
};
}
pos = Math.ceil( pos / 4 ) * 4;
indices = null;
if ( indexedGeometry ) {
indices = copyFromBuffer(
buffer,
indicesType === 1 ? Uint32Array : Uint16Array,
pos,
indicesNumber,
bigEndian
);
}
return {
version: version,
attributes: attributes,
indices: indices
};
}
// Define the public interface
function PRWMLoader( manager ) {
Loader.call( this, manager );
}
PRWMLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
constructor: PRWMLoader,
load: function ( url, onLoad, onProgress, onError ) {
var scope = this;
var loader = new FileLoader( scope.manager );
loader.setPath( scope.path );
loader.setResponseType( 'arraybuffer' );
loader.setRequestHeader( scope.requestHeader );
loader.setWithCredentials( scope.withCredentials );
url = url.replace( /\*/g, isBigEndianPlatform() ? 'be' : 'le' );
loader.load( url, function ( arrayBuffer ) {
try {
onLoad( scope.parse( arrayBuffer ) );
} catch ( e ) {
if ( onError ) {
onError( e );
} else {
console.error( e );
}
scope.manager.itemError( url );
}
}, onProgress, onError );
},
parse: function ( arrayBuffer ) {
var data = decodePrwm( arrayBuffer ),
attributesKey = Object.keys( data.attributes ),
bufferGeometry = new BufferGeometry(),
attribute,
i;
for ( i = 0; i < attributesKey.length; i ++ ) {
attribute = data.attributes[ attributesKey[ i ] ];
bufferGeometry.setAttribute( attributesKey[ i ], new BufferAttribute( attribute.values, attribute.cardinality, attribute.normalized ) );
}
if ( data.indices !== null ) {
bufferGeometry.setIndex( new BufferAttribute( data.indices, 1 ) );
}
return bufferGeometry;
}
} );
PRWMLoader.isBigEndianPlatform = function () {
return isBigEndianPlatform();
};
return PRWMLoader;
} )();
export { PRWMLoader };

View File

@@ -0,0 +1,252 @@
import {
CompressedTextureLoader,
RGBA_PVRTC_2BPPV1_Format,
RGBA_PVRTC_4BPPV1_Format,
RGB_PVRTC_2BPPV1_Format,
RGB_PVRTC_4BPPV1_Format
} from '../../../build/three.module.js';
/*
* PVR v2 (legacy) parser
* TODO : Add Support for PVR v3 format
* TODO : implement loadMipmaps option
*/
var PVRLoader = function ( manager ) {
CompressedTextureLoader.call( this, manager );
};
PVRLoader.prototype = Object.assign( Object.create( CompressedTextureLoader.prototype ), {
constructor: PVRLoader,
parse: function ( buffer, loadMipmaps ) {
var headerLengthInt = 13;
var header = new Uint32Array( buffer, 0, headerLengthInt );
var pvrDatas = {
buffer: buffer,
header: header,
loadMipmaps: loadMipmaps
};
if ( header[ 0 ] === 0x03525650 ) {
// PVR v3
return PVRLoader._parseV3( pvrDatas );
} else if ( header[ 11 ] === 0x21525650 ) {
// PVR v2
return PVRLoader._parseV2( pvrDatas );
} else {
console.error( 'THREE.PVRLoader: Unknown PVR format.' );
}
}
} );
PVRLoader._parseV3 = function ( pvrDatas ) {
var header = pvrDatas.header;
var bpp, format;
var metaLen = header[ 12 ],
pixelFormat = header[ 2 ],
height = header[ 6 ],
width = header[ 7 ],
// numSurfs = header[ 9 ],
numFaces = header[ 10 ],
numMipmaps = header[ 11 ];
switch ( pixelFormat ) {
case 0 : // PVRTC 2bpp RGB
bpp = 2;
format = RGB_PVRTC_2BPPV1_Format;
break;
case 1 : // PVRTC 2bpp RGBA
bpp = 2;
format = RGBA_PVRTC_2BPPV1_Format;
break;
case 2 : // PVRTC 4bpp RGB
bpp = 4;
format = RGB_PVRTC_4BPPV1_Format;
break;
case 3 : // PVRTC 4bpp RGBA
bpp = 4;
format = RGBA_PVRTC_4BPPV1_Format;
break;
default :
console.error( 'THREE.PVRLoader: Unsupported PVR format:', pixelFormat );
}
pvrDatas.dataPtr = 52 + metaLen;
pvrDatas.bpp = bpp;
pvrDatas.format = format;
pvrDatas.width = width;
pvrDatas.height = height;
pvrDatas.numSurfaces = numFaces;
pvrDatas.numMipmaps = numMipmaps;
pvrDatas.isCubemap = ( numFaces === 6 );
return PVRLoader._extract( pvrDatas );
};
PVRLoader._parseV2 = function ( pvrDatas ) {
var header = pvrDatas.header;
var headerLength = header[ 0 ],
height = header[ 1 ],
width = header[ 2 ],
numMipmaps = header[ 3 ],
flags = header[ 4 ],
// dataLength = header[ 5 ],
// bpp = header[ 6 ],
// bitmaskRed = header[ 7 ],
// bitmaskGreen = header[ 8 ],
// bitmaskBlue = header[ 9 ],
bitmaskAlpha = header[ 10 ],
// pvrTag = header[ 11 ],
numSurfs = header[ 12 ];
var TYPE_MASK = 0xff;
var PVRTC_2 = 24,
PVRTC_4 = 25;
var formatFlags = flags & TYPE_MASK;
var bpp, format;
var _hasAlpha = bitmaskAlpha > 0;
if ( formatFlags === PVRTC_4 ) {
format = _hasAlpha ? RGBA_PVRTC_4BPPV1_Format : RGB_PVRTC_4BPPV1_Format;
bpp = 4;
} else if ( formatFlags === PVRTC_2 ) {
format = _hasAlpha ? RGBA_PVRTC_2BPPV1_Format : RGB_PVRTC_2BPPV1_Format;
bpp = 2;
} else {
console.error( 'THREE.PVRLoader: Unknown PVR format:', formatFlags );
}
pvrDatas.dataPtr = headerLength;
pvrDatas.bpp = bpp;
pvrDatas.format = format;
pvrDatas.width = width;
pvrDatas.height = height;
pvrDatas.numSurfaces = numSurfs;
pvrDatas.numMipmaps = numMipmaps + 1;
// guess cubemap type seems tricky in v2
// it juste a pvr containing 6 surface (no explicit cubemap type)
pvrDatas.isCubemap = ( numSurfs === 6 );
return PVRLoader._extract( pvrDatas );
};
PVRLoader._extract = function ( pvrDatas ) {
var pvr = {
mipmaps: [],
width: pvrDatas.width,
height: pvrDatas.height,
format: pvrDatas.format,
mipmapCount: pvrDatas.numMipmaps,
isCubemap: pvrDatas.isCubemap
};
var buffer = pvrDatas.buffer;
var dataOffset = pvrDatas.dataPtr,
bpp = pvrDatas.bpp,
numSurfs = pvrDatas.numSurfaces,
dataSize = 0,
blockSize = 0,
blockWidth = 0,
blockHeight = 0,
widthBlocks = 0,
heightBlocks = 0;
if ( bpp === 2 ) {
blockWidth = 8;
blockHeight = 4;
} else {
blockWidth = 4;
blockHeight = 4;
}
blockSize = ( blockWidth * blockHeight ) * bpp / 8;
pvr.mipmaps.length = pvrDatas.numMipmaps * numSurfs;
var mipLevel = 0;
while ( mipLevel < pvrDatas.numMipmaps ) {
var sWidth = pvrDatas.width >> mipLevel,
sHeight = pvrDatas.height >> mipLevel;
widthBlocks = sWidth / blockWidth;
heightBlocks = sHeight / blockHeight;
// Clamp to minimum number of blocks
if ( widthBlocks < 2 ) widthBlocks = 2;
if ( heightBlocks < 2 ) heightBlocks = 2;
dataSize = widthBlocks * heightBlocks * blockSize;
for ( var surfIndex = 0; surfIndex < numSurfs; surfIndex ++ ) {
var byteArray = new Uint8Array( buffer, dataOffset, dataSize );
var mipmap = {
data: byteArray,
width: sWidth,
height: sHeight
};
pvr.mipmaps[ surfIndex * pvrDatas.numMipmaps + mipLevel ] = mipmap;
dataOffset += dataSize;
}
mipLevel ++;
}
return pvr;
};
export { PVRLoader };

View File

@@ -0,0 +1,491 @@
import {
DataTextureLoader,
DataUtils,
FloatType,
HalfFloatType,
LinearEncoding,
LinearFilter,
NearestFilter,
RGBEEncoding,
RGBEFormat,
RGBFormat,
UnsignedByteType
} from '../../../build/three.module.js';
// https://github.com/mrdoob/three.js/issues/5552
// http://en.wikipedia.org/wiki/RGBE_image_format
var RGBELoader = function ( manager ) {
DataTextureLoader.call( this, manager );
this.type = UnsignedByteType;
};
RGBELoader.prototype = Object.assign( Object.create( DataTextureLoader.prototype ), {
constructor: RGBELoader,
// adapted from http://www.graphics.cornell.edu/~bjw/rgbe.html
parse: function ( buffer ) {
var
/* return codes for rgbe routines */
//RGBE_RETURN_SUCCESS = 0,
RGBE_RETURN_FAILURE = - 1,
/* default error routine. change this to change error handling */
rgbe_read_error = 1,
rgbe_write_error = 2,
rgbe_format_error = 3,
rgbe_memory_error = 4,
rgbe_error = function ( rgbe_error_code, msg ) {
switch ( rgbe_error_code ) {
case rgbe_read_error: console.error( 'THREE.RGBELoader Read Error: ' + ( msg || '' ) );
break;
case rgbe_write_error: console.error( 'THREE.RGBELoader Write Error: ' + ( msg || '' ) );
break;
case rgbe_format_error: console.error( 'THREE.RGBELoader Bad File Format: ' + ( msg || '' ) );
break;
default:
case rgbe_memory_error: console.error( 'THREE.RGBELoader: Error: ' + ( msg || '' ) );
}
return RGBE_RETURN_FAILURE;
},
/* offsets to red, green, and blue components in a data (float) pixel */
//RGBE_DATA_RED = 0,
//RGBE_DATA_GREEN = 1,
//RGBE_DATA_BLUE = 2,
/* number of floats per pixel, use 4 since stored in rgba image format */
//RGBE_DATA_SIZE = 4,
/* flags indicating which fields in an rgbe_header_info are valid */
RGBE_VALID_PROGRAMTYPE = 1,
RGBE_VALID_FORMAT = 2,
RGBE_VALID_DIMENSIONS = 4,
NEWLINE = '\n',
fgets = function ( buffer, lineLimit, consume ) {
lineLimit = ! lineLimit ? 1024 : lineLimit;
var p = buffer.pos,
i = - 1, len = 0, s = '', chunkSize = 128,
chunk = String.fromCharCode.apply( null, new Uint16Array( buffer.subarray( p, p + chunkSize ) ) )
;
while ( ( 0 > ( i = chunk.indexOf( NEWLINE ) ) ) && ( len < lineLimit ) && ( p < buffer.byteLength ) ) {
s += chunk; len += chunk.length;
p += chunkSize;
chunk += String.fromCharCode.apply( null, new Uint16Array( buffer.subarray( p, p + chunkSize ) ) );
}
if ( - 1 < i ) {
/*for (i=l-1; i>=0; i--) {
byteCode = m.charCodeAt(i);
if (byteCode > 0x7f && byteCode <= 0x7ff) byteLen++;
else if (byteCode > 0x7ff && byteCode <= 0xffff) byteLen += 2;
if (byteCode >= 0xDC00 && byteCode <= 0xDFFF) i--; //trail surrogate
}*/
if ( false !== consume ) buffer.pos += len + i + 1;
return s + chunk.slice( 0, i );
}
return false;
},
/* minimal header reading. modify if you want to parse more information */
RGBE_ReadHeader = function ( buffer ) {
var line, match,
// regexes to parse header info fields
magic_token_re = /^#\?(\S+)/,
gamma_re = /^\s*GAMMA\s*=\s*(\d+(\.\d+)?)\s*$/,
exposure_re = /^\s*EXPOSURE\s*=\s*(\d+(\.\d+)?)\s*$/,
format_re = /^\s*FORMAT=(\S+)\s*$/,
dimensions_re = /^\s*\-Y\s+(\d+)\s+\+X\s+(\d+)\s*$/,
// RGBE format header struct
header = {
valid: 0, /* indicate which fields are valid */
string: '', /* the actual header string */
comments: '', /* comments found in header */
programtype: 'RGBE', /* listed at beginning of file to identify it after "#?". defaults to "RGBE" */
format: '', /* RGBE format, default 32-bit_rle_rgbe */
gamma: 1.0, /* image has already been gamma corrected with given gamma. defaults to 1.0 (no correction) */
exposure: 1.0, /* a value of 1.0 in an image corresponds to <exposure> watts/steradian/m^2. defaults to 1.0 */
width: 0, height: 0 /* image dimensions, width/height */
};
if ( buffer.pos >= buffer.byteLength || ! ( line = fgets( buffer ) ) ) {
return rgbe_error( rgbe_read_error, 'no header found' );
}
/* if you want to require the magic token then uncomment the next line */
if ( ! ( match = line.match( magic_token_re ) ) ) {
return rgbe_error( rgbe_format_error, 'bad initial token' );
}
header.valid |= RGBE_VALID_PROGRAMTYPE;
header.programtype = match[ 1 ];
header.string += line + '\n';
while ( true ) {
line = fgets( buffer );
if ( false === line ) break;
header.string += line + '\n';
if ( '#' === line.charAt( 0 ) ) {
header.comments += line + '\n';
continue; // comment line
}
if ( match = line.match( gamma_re ) ) {
header.gamma = parseFloat( match[ 1 ], 10 );
}
if ( match = line.match( exposure_re ) ) {
header.exposure = parseFloat( match[ 1 ], 10 );
}
if ( match = line.match( format_re ) ) {
header.valid |= RGBE_VALID_FORMAT;
header.format = match[ 1 ];//'32-bit_rle_rgbe';
}
if ( match = line.match( dimensions_re ) ) {
header.valid |= RGBE_VALID_DIMENSIONS;
header.height = parseInt( match[ 1 ], 10 );
header.width = parseInt( match[ 2 ], 10 );
}
if ( ( header.valid & RGBE_VALID_FORMAT ) && ( header.valid & RGBE_VALID_DIMENSIONS ) ) break;
}
if ( ! ( header.valid & RGBE_VALID_FORMAT ) ) {
return rgbe_error( rgbe_format_error, 'missing format specifier' );
}
if ( ! ( header.valid & RGBE_VALID_DIMENSIONS ) ) {
return rgbe_error( rgbe_format_error, 'missing image size specifier' );
}
return header;
},
RGBE_ReadPixels_RLE = function ( buffer, w, h ) {
var data_rgba, offset, pos, count, byteValue,
scanline_buffer, ptr, ptr_end, i, l, off, isEncodedRun,
scanline_width = w, num_scanlines = h, rgbeStart
;
if (
// run length encoding is not allowed so read flat
( ( scanline_width < 8 ) || ( scanline_width > 0x7fff ) ) ||
// this file is not run length encoded
( ( 2 !== buffer[ 0 ] ) || ( 2 !== buffer[ 1 ] ) || ( buffer[ 2 ] & 0x80 ) )
) {
// return the flat buffer
return new Uint8Array( buffer );
}
if ( scanline_width !== ( ( buffer[ 2 ] << 8 ) | buffer[ 3 ] ) ) {
return rgbe_error( rgbe_format_error, 'wrong scanline width' );
}
data_rgba = new Uint8Array( 4 * w * h );
if ( ! data_rgba.length ) {
return rgbe_error( rgbe_memory_error, 'unable to allocate buffer space' );
}
offset = 0; pos = 0; ptr_end = 4 * scanline_width;
rgbeStart = new Uint8Array( 4 );
scanline_buffer = new Uint8Array( ptr_end );
// read in each successive scanline
while ( ( num_scanlines > 0 ) && ( pos < buffer.byteLength ) ) {
if ( pos + 4 > buffer.byteLength ) {
return rgbe_error( rgbe_read_error );
}
rgbeStart[ 0 ] = buffer[ pos ++ ];
rgbeStart[ 1 ] = buffer[ pos ++ ];
rgbeStart[ 2 ] = buffer[ pos ++ ];
rgbeStart[ 3 ] = buffer[ pos ++ ];
if ( ( 2 != rgbeStart[ 0 ] ) || ( 2 != rgbeStart[ 1 ] ) || ( ( ( rgbeStart[ 2 ] << 8 ) | rgbeStart[ 3 ] ) != scanline_width ) ) {
return rgbe_error( rgbe_format_error, 'bad rgbe scanline format' );
}
// read each of the four channels for the scanline into the buffer
// first red, then green, then blue, then exponent
ptr = 0;
while ( ( ptr < ptr_end ) && ( pos < buffer.byteLength ) ) {
count = buffer[ pos ++ ];
isEncodedRun = count > 128;
if ( isEncodedRun ) count -= 128;
if ( ( 0 === count ) || ( ptr + count > ptr_end ) ) {
return rgbe_error( rgbe_format_error, 'bad scanline data' );
}
if ( isEncodedRun ) {
// a (encoded) run of the same value
byteValue = buffer[ pos ++ ];
for ( i = 0; i < count; i ++ ) {
scanline_buffer[ ptr ++ ] = byteValue;
}
//ptr += count;
} else {
// a literal-run
scanline_buffer.set( buffer.subarray( pos, pos + count ), ptr );
ptr += count; pos += count;
}
}
// now convert data from buffer into rgba
// first red, then green, then blue, then exponent (alpha)
l = scanline_width; //scanline_buffer.byteLength;
for ( i = 0; i < l; i ++ ) {
off = 0;
data_rgba[ offset ] = scanline_buffer[ i + off ];
off += scanline_width; //1;
data_rgba[ offset + 1 ] = scanline_buffer[ i + off ];
off += scanline_width; //1;
data_rgba[ offset + 2 ] = scanline_buffer[ i + off ];
off += scanline_width; //1;
data_rgba[ offset + 3 ] = scanline_buffer[ i + off ];
offset += 4;
}
num_scanlines --;
}
return data_rgba;
};
var RGBEByteToRGBFloat = function ( sourceArray, sourceOffset, destArray, destOffset ) {
var e = sourceArray[ sourceOffset + 3 ];
var scale = Math.pow( 2.0, e - 128.0 ) / 255.0;
destArray[ destOffset + 0 ] = sourceArray[ sourceOffset + 0 ] * scale;
destArray[ destOffset + 1 ] = sourceArray[ sourceOffset + 1 ] * scale;
destArray[ destOffset + 2 ] = sourceArray[ sourceOffset + 2 ] * scale;
};
var RGBEByteToRGBHalf = function ( sourceArray, sourceOffset, destArray, destOffset ) {
var e = sourceArray[ sourceOffset + 3 ];
var scale = Math.pow( 2.0, e - 128.0 ) / 255.0;
destArray[ destOffset + 0 ] = DataUtils.toHalfFloat( sourceArray[ sourceOffset + 0 ] * scale );
destArray[ destOffset + 1 ] = DataUtils.toHalfFloat( sourceArray[ sourceOffset + 1 ] * scale );
destArray[ destOffset + 2 ] = DataUtils.toHalfFloat( sourceArray[ sourceOffset + 2 ] * scale );
};
var byteArray = new Uint8Array( buffer );
byteArray.pos = 0;
var rgbe_header_info = RGBE_ReadHeader( byteArray );
if ( RGBE_RETURN_FAILURE !== rgbe_header_info ) {
var w = rgbe_header_info.width,
h = rgbe_header_info.height,
image_rgba_data = RGBE_ReadPixels_RLE( byteArray.subarray( byteArray.pos ), w, h );
if ( RGBE_RETURN_FAILURE !== image_rgba_data ) {
switch ( this.type ) {
case UnsignedByteType:
var data = image_rgba_data;
var format = RGBEFormat; // handled as THREE.RGBAFormat in shaders
var type = UnsignedByteType;
break;
case FloatType:
var numElements = ( image_rgba_data.length / 4 ) * 3;
var floatArray = new Float32Array( numElements );
for ( var j = 0; j < numElements; j ++ ) {
RGBEByteToRGBFloat( image_rgba_data, j * 4, floatArray, j * 3 );
}
var data = floatArray;
var format = RGBFormat;
var type = FloatType;
break;
case HalfFloatType:
var numElements = ( image_rgba_data.length / 4 ) * 3;
var halfArray = new Uint16Array( numElements );
for ( var j = 0; j < numElements; j ++ ) {
RGBEByteToRGBHalf( image_rgba_data, j * 4, halfArray, j * 3 );
}
var data = halfArray;
var format = RGBFormat;
var type = HalfFloatType;
break;
default:
console.error( 'THREE.RGBELoader: unsupported type: ', this.type );
break;
}
return {
width: w, height: h,
data: data,
header: rgbe_header_info.string,
gamma: rgbe_header_info.gamma,
exposure: rgbe_header_info.exposure,
format: format,
type: type
};
}
}
return null;
},
setDataType: function ( value ) {
this.type = value;
return this;
},
load: function ( url, onLoad, onProgress, onError ) {
function onLoadCallback( texture, texData ) {
switch ( texture.type ) {
case UnsignedByteType:
texture.encoding = RGBEEncoding;
texture.minFilter = NearestFilter;
texture.magFilter = NearestFilter;
texture.generateMipmaps = false;
texture.flipY = true;
break;
case FloatType:
texture.encoding = LinearEncoding;
texture.minFilter = LinearFilter;
texture.magFilter = LinearFilter;
texture.generateMipmaps = false;
texture.flipY = true;
break;
case HalfFloatType:
texture.encoding = LinearEncoding;
texture.minFilter = LinearFilter;
texture.magFilter = LinearFilter;
texture.generateMipmaps = false;
texture.flipY = true;
break;
}
if ( onLoad ) onLoad( texture, texData );
}
return DataTextureLoader.prototype.load.call( this, url, onLoadCallback, onProgress, onError );
}
} );
export { RGBELoader };

View File

@@ -0,0 +1,402 @@
import {
BufferAttribute,
BufferGeometry,
FileLoader,
Float32BufferAttribute,
Loader,
LoaderUtils,
Vector3
} from '../../../build/three.module.js';
/**
* Description: A THREE loader for STL ASCII files, as created by Solidworks and other CAD programs.
*
* Supports both binary and ASCII encoded files, with automatic detection of type.
*
* The loader returns a non-indexed buffer geometry.
*
* Limitations:
* Binary decoding supports "Magics" color format (http://en.wikipedia.org/wiki/STL_(file_format)#Color_in_binary_STL).
* There is perhaps some question as to how valid it is to always assume little-endian-ness.
* ASCII decoding assumes file is UTF-8.
*
* Usage:
* var loader = new STLLoader();
* loader.load( './models/stl/slotted_disk.stl', function ( geometry ) {
* scene.add( new THREE.Mesh( geometry ) );
* });
*
* For binary STLs geometry might contain colors for vertices. To use it:
* // use the same code to load STL as above
* if (geometry.hasColors) {
* material = new THREE.MeshPhongMaterial({ opacity: geometry.alpha, vertexColors: true });
* } else { .... }
* var mesh = new THREE.Mesh( geometry, material );
*
* For ASCII STLs containing multiple solids, each solid is assigned to a different group.
* Groups can be used to assign a different color by defining an array of materials with the same length of
* geometry.groups and passing it to the Mesh constructor:
*
* var mesh = new THREE.Mesh( geometry, material );
*
* For example:
*
* var materials = [];
* var nGeometryGroups = geometry.groups.length;
*
* var colorMap = ...; // Some logic to index colors.
*
* for (var i = 0; i < nGeometryGroups; i++) {
*
* var material = new THREE.MeshPhongMaterial({
* color: colorMap[i],
* wireframe: false
* });
*
* }
*
* materials.push(material);
* var mesh = new THREE.Mesh(geometry, materials);
*/
var STLLoader = function ( manager ) {
Loader.call( this, manager );
};
STLLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
constructor: STLLoader,
load: function ( url, onLoad, onProgress, onError ) {
var scope = this;
var loader = new FileLoader( this.manager );
loader.setPath( this.path );
loader.setResponseType( 'arraybuffer' );
loader.setRequestHeader( this.requestHeader );
loader.setWithCredentials( this.withCredentials );
loader.load( url, function ( text ) {
try {
onLoad( scope.parse( text ) );
} catch ( e ) {
if ( onError ) {
onError( e );
} else {
console.error( e );
}
scope.manager.itemError( url );
}
}, onProgress, onError );
},
parse: function ( data ) {
function isBinary( data ) {
var expect, face_size, n_faces, reader;
reader = new DataView( data );
face_size = ( 32 / 8 * 3 ) + ( ( 32 / 8 * 3 ) * 3 ) + ( 16 / 8 );
n_faces = reader.getUint32( 80, true );
expect = 80 + ( 32 / 8 ) + ( n_faces * face_size );
if ( expect === reader.byteLength ) {
return true;
}
// An ASCII STL data must begin with 'solid ' as the first six bytes.
// However, ASCII STLs lacking the SPACE after the 'd' are known to be
// plentiful. So, check the first 5 bytes for 'solid'.
// Several encodings, such as UTF-8, precede the text with up to 5 bytes:
// https://en.wikipedia.org/wiki/Byte_order_mark#Byte_order_marks_by_encoding
// Search for "solid" to start anywhere after those prefixes.
// US-ASCII ordinal values for 's', 'o', 'l', 'i', 'd'
var solid = [ 115, 111, 108, 105, 100 ];
for ( var off = 0; off < 5; off ++ ) {
// If "solid" text is matched to the current offset, declare it to be an ASCII STL.
if ( matchDataViewAt( solid, reader, off ) ) return false;
}
// Couldn't find "solid" text at the beginning; it is binary STL.
return true;
}
function matchDataViewAt( query, reader, offset ) {
// Check if each byte in query matches the corresponding byte from the current offset
for ( var i = 0, il = query.length; i < il; i ++ ) {
if ( query[ i ] !== reader.getUint8( offset + i, false ) ) return false;
}
return true;
}
function parseBinary( data ) {
var reader = new DataView( data );
var faces = reader.getUint32( 80, true );
var r, g, b, hasColors = false, colors;
var defaultR, defaultG, defaultB, alpha;
// process STL header
// check for default color in header ("COLOR=rgba" sequence).
for ( var index = 0; index < 80 - 10; index ++ ) {
if ( ( reader.getUint32( index, false ) == 0x434F4C4F /*COLO*/ ) &&
( reader.getUint8( index + 4 ) == 0x52 /*'R'*/ ) &&
( reader.getUint8( index + 5 ) == 0x3D /*'='*/ ) ) {
hasColors = true;
colors = new Float32Array( faces * 3 * 3 );
defaultR = reader.getUint8( index + 6 ) / 255;
defaultG = reader.getUint8( index + 7 ) / 255;
defaultB = reader.getUint8( index + 8 ) / 255;
alpha = reader.getUint8( index + 9 ) / 255;
}
}
var dataOffset = 84;
var faceLength = 12 * 4 + 2;
var geometry = new BufferGeometry();
var vertices = new Float32Array( faces * 3 * 3 );
var normals = new Float32Array( faces * 3 * 3 );
for ( var face = 0; face < faces; face ++ ) {
var start = dataOffset + face * faceLength;
var normalX = reader.getFloat32( start, true );
var normalY = reader.getFloat32( start + 4, true );
var normalZ = reader.getFloat32( start + 8, true );
if ( hasColors ) {
var packedColor = reader.getUint16( start + 48, true );
if ( ( packedColor & 0x8000 ) === 0 ) {
// facet has its own unique color
r = ( packedColor & 0x1F ) / 31;
g = ( ( packedColor >> 5 ) & 0x1F ) / 31;
b = ( ( packedColor >> 10 ) & 0x1F ) / 31;
} else {
r = defaultR;
g = defaultG;
b = defaultB;
}
}
for ( var i = 1; i <= 3; i ++ ) {
var vertexstart = start + i * 12;
var componentIdx = ( face * 3 * 3 ) + ( ( i - 1 ) * 3 );
vertices[ componentIdx ] = reader.getFloat32( vertexstart, true );
vertices[ componentIdx + 1 ] = reader.getFloat32( vertexstart + 4, true );
vertices[ componentIdx + 2 ] = reader.getFloat32( vertexstart + 8, true );
normals[ componentIdx ] = normalX;
normals[ componentIdx + 1 ] = normalY;
normals[ componentIdx + 2 ] = normalZ;
if ( hasColors ) {
colors[ componentIdx ] = r;
colors[ componentIdx + 1 ] = g;
colors[ componentIdx + 2 ] = b;
}
}
}
geometry.setAttribute( 'position', new BufferAttribute( vertices, 3 ) );
geometry.setAttribute( 'normal', new BufferAttribute( normals, 3 ) );
if ( hasColors ) {
geometry.setAttribute( 'color', new BufferAttribute( colors, 3 ) );
geometry.hasColors = true;
geometry.alpha = alpha;
}
return geometry;
}
function parseASCII( data ) {
var geometry = new BufferGeometry();
var patternSolid = /solid([\s\S]*?)endsolid/g;
var patternFace = /facet([\s\S]*?)endfacet/g;
var faceCounter = 0;
var patternFloat = /[\s]+([+-]?(?:\d*)(?:\.\d*)?(?:[eE][+-]?\d+)?)/.source;
var patternVertex = new RegExp( 'vertex' + patternFloat + patternFloat + patternFloat, 'g' );
var patternNormal = new RegExp( 'normal' + patternFloat + patternFloat + patternFloat, 'g' );
var vertices = [];
var normals = [];
var normal = new Vector3();
var result;
var groupCount = 0;
var startVertex = 0;
var endVertex = 0;
while ( ( result = patternSolid.exec( data ) ) !== null ) {
startVertex = endVertex;
var solid = result[ 0 ];
while ( ( result = patternFace.exec( solid ) ) !== null ) {
var vertexCountPerFace = 0;
var normalCountPerFace = 0;
var text = result[ 0 ];
while ( ( result = patternNormal.exec( text ) ) !== null ) {
normal.x = parseFloat( result[ 1 ] );
normal.y = parseFloat( result[ 2 ] );
normal.z = parseFloat( result[ 3 ] );
normalCountPerFace ++;
}
while ( ( result = patternVertex.exec( text ) ) !== null ) {
vertices.push( parseFloat( result[ 1 ] ), parseFloat( result[ 2 ] ), parseFloat( result[ 3 ] ) );
normals.push( normal.x, normal.y, normal.z );
vertexCountPerFace ++;
endVertex ++;
}
// every face have to own ONE valid normal
if ( normalCountPerFace !== 1 ) {
console.error( 'THREE.STLLoader: Something isn\'t right with the normal of face number ' + faceCounter );
}
// each face have to own THREE valid vertices
if ( vertexCountPerFace !== 3 ) {
console.error( 'THREE.STLLoader: Something isn\'t right with the vertices of face number ' + faceCounter );
}
faceCounter ++;
}
var start = startVertex;
var count = endVertex - startVertex;
geometry.addGroup( start, count, groupCount );
groupCount ++;
}
geometry.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) );
geometry.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) );
return geometry;
}
function ensureString( buffer ) {
if ( typeof buffer !== 'string' ) {
return LoaderUtils.decodeText( new Uint8Array( buffer ) );
}
return buffer;
}
function ensureBinary( buffer ) {
if ( typeof buffer === 'string' ) {
var array_buffer = new Uint8Array( buffer.length );
for ( var i = 0; i < buffer.length; i ++ ) {
array_buffer[ i ] = buffer.charCodeAt( i ) & 0xff; // implicitly assumes little-endian
}
return array_buffer.buffer || array_buffer;
} else {
return buffer;
}
}
// start
var binData = ensureBinary( data );
return isBinary( binData ) ? parseBinary( binData ) : parseASCII( ensureString( data ) );
}
} );
export { STLLoader };

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,550 @@
import {
FileLoader,
Loader,
Texture
} from '../../../build/three.module.js';
var TGALoader = function ( manager ) {
Loader.call( this, manager );
};
TGALoader.prototype = Object.assign( Object.create( Loader.prototype ), {
constructor: TGALoader,
load: function ( url, onLoad, onProgress, onError ) {
var scope = this;
var texture = new Texture();
var loader = new FileLoader( this.manager );
loader.setResponseType( 'arraybuffer' );
loader.setPath( this.path );
loader.setWithCredentials( this.withCredentials );
loader.load( url, function ( buffer ) {
texture.image = scope.parse( buffer );
texture.needsUpdate = true;
if ( onLoad !== undefined ) {
onLoad( texture );
}
}, onProgress, onError );
return texture;
},
parse: function ( buffer ) {
// reference from vthibault, https://github.com/vthibault/roBrowser/blob/master/src/Loaders/Targa.js
function tgaCheckHeader( header ) {
switch ( header.image_type ) {
// check indexed type
case TGA_TYPE_INDEXED:
case TGA_TYPE_RLE_INDEXED:
if ( header.colormap_length > 256 || header.colormap_size !== 24 || header.colormap_type !== 1 ) {
console.error( 'THREE.TGALoader: Invalid type colormap data for indexed type.' );
}
break;
// check colormap type
case TGA_TYPE_RGB:
case TGA_TYPE_GREY:
case TGA_TYPE_RLE_RGB:
case TGA_TYPE_RLE_GREY:
if ( header.colormap_type ) {
console.error( 'THREE.TGALoader: Invalid type colormap data for colormap type.' );
}
break;
// What the need of a file without data ?
case TGA_TYPE_NO_DATA:
console.error( 'THREE.TGALoader: No data.' );
// Invalid type ?
default:
console.error( 'THREE.TGALoader: Invalid type "%s".', header.image_type );
}
// check image width and height
if ( header.width <= 0 || header.height <= 0 ) {
console.error( 'THREE.TGALoader: Invalid image size.' );
}
// check image pixel size
if ( header.pixel_size !== 8 && header.pixel_size !== 16 &&
header.pixel_size !== 24 && header.pixel_size !== 32 ) {
console.error( 'THREE.TGALoader: Invalid pixel size "%s".', header.pixel_size );
}
}
// parse tga image buffer
function tgaParse( use_rle, use_pal, header, offset, data ) {
var pixel_data,
pixel_size,
pixel_total,
palettes;
pixel_size = header.pixel_size >> 3;
pixel_total = header.width * header.height * pixel_size;
// read palettes
if ( use_pal ) {
palettes = data.subarray( offset, offset += header.colormap_length * ( header.colormap_size >> 3 ) );
}
// read RLE
if ( use_rle ) {
pixel_data = new Uint8Array( pixel_total );
var c, count, i;
var shift = 0;
var pixels = new Uint8Array( pixel_size );
while ( shift < pixel_total ) {
c = data[ offset ++ ];
count = ( c & 0x7f ) + 1;
// RLE pixels
if ( c & 0x80 ) {
// bind pixel tmp array
for ( i = 0; i < pixel_size; ++ i ) {
pixels[ i ] = data[ offset ++ ];
}
// copy pixel array
for ( i = 0; i < count; ++ i ) {
pixel_data.set( pixels, shift + i * pixel_size );
}
shift += pixel_size * count;
} else {
// raw pixels
count *= pixel_size;
for ( i = 0; i < count; ++ i ) {
pixel_data[ shift + i ] = data[ offset ++ ];
}
shift += count;
}
}
} else {
// raw pixels
pixel_data = data.subarray(
offset, offset += ( use_pal ? header.width * header.height : pixel_total )
);
}
return {
pixel_data: pixel_data,
palettes: palettes
};
}
function tgaGetImageData8bits( imageData, y_start, y_step, y_end, x_start, x_step, x_end, image, palettes ) {
var colormap = palettes;
var color, i = 0, x, y;
var width = header.width;
for ( y = y_start; y !== y_end; y += y_step ) {
for ( x = x_start; x !== x_end; x += x_step, i ++ ) {
color = image[ i ];
imageData[ ( x + width * y ) * 4 + 3 ] = 255;
imageData[ ( x + width * y ) * 4 + 2 ] = colormap[ ( color * 3 ) + 0 ];
imageData[ ( x + width * y ) * 4 + 1 ] = colormap[ ( color * 3 ) + 1 ];
imageData[ ( x + width * y ) * 4 + 0 ] = colormap[ ( color * 3 ) + 2 ];
}
}
return imageData;
}
function tgaGetImageData16bits( imageData, y_start, y_step, y_end, x_start, x_step, x_end, image ) {
var color, i = 0, x, y;
var width = header.width;
for ( y = y_start; y !== y_end; y += y_step ) {
for ( x = x_start; x !== x_end; x += x_step, i += 2 ) {
color = image[ i + 0 ] + ( image[ i + 1 ] << 8 ); // Inversed ?
imageData[ ( x + width * y ) * 4 + 0 ] = ( color & 0x7C00 ) >> 7;
imageData[ ( x + width * y ) * 4 + 1 ] = ( color & 0x03E0 ) >> 2;
imageData[ ( x + width * y ) * 4 + 2 ] = ( color & 0x001F ) >> 3;
imageData[ ( x + width * y ) * 4 + 3 ] = ( color & 0x8000 ) ? 0 : 255;
}
}
return imageData;
}
function tgaGetImageData24bits( imageData, y_start, y_step, y_end, x_start, x_step, x_end, image ) {
var i = 0, x, y;
var width = header.width;
for ( y = y_start; y !== y_end; y += y_step ) {
for ( x = x_start; x !== x_end; x += x_step, i += 3 ) {
imageData[ ( x + width * y ) * 4 + 3 ] = 255;
imageData[ ( x + width * y ) * 4 + 2 ] = image[ i + 0 ];
imageData[ ( x + width * y ) * 4 + 1 ] = image[ i + 1 ];
imageData[ ( x + width * y ) * 4 + 0 ] = image[ i + 2 ];
}
}
return imageData;
}
function tgaGetImageData32bits( imageData, y_start, y_step, y_end, x_start, x_step, x_end, image ) {
var i = 0, x, y;
var width = header.width;
for ( y = y_start; y !== y_end; y += y_step ) {
for ( x = x_start; x !== x_end; x += x_step, i += 4 ) {
imageData[ ( x + width * y ) * 4 + 2 ] = image[ i + 0 ];
imageData[ ( x + width * y ) * 4 + 1 ] = image[ i + 1 ];
imageData[ ( x + width * y ) * 4 + 0 ] = image[ i + 2 ];
imageData[ ( x + width * y ) * 4 + 3 ] = image[ i + 3 ];
}
}
return imageData;
}
function tgaGetImageDataGrey8bits( imageData, y_start, y_step, y_end, x_start, x_step, x_end, image ) {
var color, i = 0, x, y;
var width = header.width;
for ( y = y_start; y !== y_end; y += y_step ) {
for ( x = x_start; x !== x_end; x += x_step, i ++ ) {
color = image[ i ];
imageData[ ( x + width * y ) * 4 + 0 ] = color;
imageData[ ( x + width * y ) * 4 + 1 ] = color;
imageData[ ( x + width * y ) * 4 + 2 ] = color;
imageData[ ( x + width * y ) * 4 + 3 ] = 255;
}
}
return imageData;
}
function tgaGetImageDataGrey16bits( imageData, y_start, y_step, y_end, x_start, x_step, x_end, image ) {
var i = 0, x, y;
var width = header.width;
for ( y = y_start; y !== y_end; y += y_step ) {
for ( x = x_start; x !== x_end; x += x_step, i += 2 ) {
imageData[ ( x + width * y ) * 4 + 0 ] = image[ i + 0 ];
imageData[ ( x + width * y ) * 4 + 1 ] = image[ i + 0 ];
imageData[ ( x + width * y ) * 4 + 2 ] = image[ i + 0 ];
imageData[ ( x + width * y ) * 4 + 3 ] = image[ i + 1 ];
}
}
return imageData;
}
function getTgaRGBA( data, width, height, image, palette ) {
var x_start,
y_start,
x_step,
y_step,
x_end,
y_end;
switch ( ( header.flags & TGA_ORIGIN_MASK ) >> TGA_ORIGIN_SHIFT ) {
default:
case TGA_ORIGIN_UL:
x_start = 0;
x_step = 1;
x_end = width;
y_start = 0;
y_step = 1;
y_end = height;
break;
case TGA_ORIGIN_BL:
x_start = 0;
x_step = 1;
x_end = width;
y_start = height - 1;
y_step = - 1;
y_end = - 1;
break;
case TGA_ORIGIN_UR:
x_start = width - 1;
x_step = - 1;
x_end = - 1;
y_start = 0;
y_step = 1;
y_end = height;
break;
case TGA_ORIGIN_BR:
x_start = width - 1;
x_step = - 1;
x_end = - 1;
y_start = height - 1;
y_step = - 1;
y_end = - 1;
break;
}
if ( use_grey ) {
switch ( header.pixel_size ) {
case 8:
tgaGetImageDataGrey8bits( data, y_start, y_step, y_end, x_start, x_step, x_end, image );
break;
case 16:
tgaGetImageDataGrey16bits( data, y_start, y_step, y_end, x_start, x_step, x_end, image );
break;
default:
console.error( 'THREE.TGALoader: Format not supported.' );
break;
}
} else {
switch ( header.pixel_size ) {
case 8:
tgaGetImageData8bits( data, y_start, y_step, y_end, x_start, x_step, x_end, image, palette );
break;
case 16:
tgaGetImageData16bits( data, y_start, y_step, y_end, x_start, x_step, x_end, image );
break;
case 24:
tgaGetImageData24bits( data, y_start, y_step, y_end, x_start, x_step, x_end, image );
break;
case 32:
tgaGetImageData32bits( data, y_start, y_step, y_end, x_start, x_step, x_end, image );
break;
default:
console.error( 'THREE.TGALoader: Format not supported.' );
break;
}
}
// Load image data according to specific method
// var func = 'tgaGetImageData' + (use_grey ? 'Grey' : '') + (header.pixel_size) + 'bits';
// func(data, y_start, y_step, y_end, x_start, x_step, x_end, width, image, palette );
return data;
}
// TGA constants
var TGA_TYPE_NO_DATA = 0,
TGA_TYPE_INDEXED = 1,
TGA_TYPE_RGB = 2,
TGA_TYPE_GREY = 3,
TGA_TYPE_RLE_INDEXED = 9,
TGA_TYPE_RLE_RGB = 10,
TGA_TYPE_RLE_GREY = 11,
TGA_ORIGIN_MASK = 0x30,
TGA_ORIGIN_SHIFT = 0x04,
TGA_ORIGIN_BL = 0x00,
TGA_ORIGIN_BR = 0x01,
TGA_ORIGIN_UL = 0x02,
TGA_ORIGIN_UR = 0x03;
if ( buffer.length < 19 ) console.error( 'THREE.TGALoader: Not enough data to contain header.' );
var content = new Uint8Array( buffer ),
offset = 0,
header = {
id_length: content[ offset ++ ],
colormap_type: content[ offset ++ ],
image_type: content[ offset ++ ],
colormap_index: content[ offset ++ ] | content[ offset ++ ] << 8,
colormap_length: content[ offset ++ ] | content[ offset ++ ] << 8,
colormap_size: content[ offset ++ ],
origin: [
content[ offset ++ ] | content[ offset ++ ] << 8,
content[ offset ++ ] | content[ offset ++ ] << 8
],
width: content[ offset ++ ] | content[ offset ++ ] << 8,
height: content[ offset ++ ] | content[ offset ++ ] << 8,
pixel_size: content[ offset ++ ],
flags: content[ offset ++ ]
};
// check tga if it is valid format
tgaCheckHeader( header );
if ( header.id_length + offset > buffer.length ) {
console.error( 'THREE.TGALoader: No data.' );
}
// skip the needn't data
offset += header.id_length;
// get targa information about RLE compression and palette
var use_rle = false,
use_pal = false,
use_grey = false;
switch ( header.image_type ) {
case TGA_TYPE_RLE_INDEXED:
use_rle = true;
use_pal = true;
break;
case TGA_TYPE_INDEXED:
use_pal = true;
break;
case TGA_TYPE_RLE_RGB:
use_rle = true;
break;
case TGA_TYPE_RGB:
break;
case TGA_TYPE_RLE_GREY:
use_rle = true;
use_grey = true;
break;
case TGA_TYPE_GREY:
use_grey = true;
break;
}
//
var useOffscreen = typeof OffscreenCanvas !== 'undefined';
var canvas = useOffscreen ? new OffscreenCanvas( header.width, header.height ) : document.createElement( 'canvas' );
canvas.width = header.width;
canvas.height = header.height;
var context = canvas.getContext( '2d' );
var imageData = context.createImageData( header.width, header.height );
var result = tgaParse( use_rle, use_pal, header, offset, content );
getTgaRGBA( imageData.data, header.width, header.height, result.pixel_data, result.palettes );
context.putImageData( imageData, 0, 0 );
return canvas;
}
} );
export { TGALoader };

View File

@@ -0,0 +1,224 @@
import {
FileLoader,
Loader
} from '../../../build/three.module.js';
import { opentype } from '../libs/opentype.module.min.js';
/**
* Requires opentype.js to be included in the project.
* Loads TTF files and converts them into typeface JSON that can be used directly
* to create THREE.Font objects.
*/
var TTFLoader = function ( manager ) {
Loader.call( this, manager );
this.reversed = false;
};
TTFLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
constructor: TTFLoader,
load: function ( url, onLoad, onProgress, onError ) {
var scope = this;
var loader = new FileLoader( this.manager );
loader.setPath( this.path );
loader.setResponseType( 'arraybuffer' );
loader.setRequestHeader( this.requestHeader );
loader.setWithCredentials( this.withCredentials );
loader.load( url, function ( buffer ) {
try {
onLoad( scope.parse( buffer ) );
} catch ( e ) {
if ( onError ) {
onError( e );
} else {
console.error( e );
}
scope.manager.itemError( url );
}
}, onProgress, onError );
},
parse: function ( arraybuffer ) {
function convert( font, reversed ) {
var round = Math.round;
var glyphs = {};
var scale = ( 100000 ) / ( ( font.unitsPerEm || 2048 ) * 72 );
var glyphIndexMap = font.encoding.cmap.glyphIndexMap;
var unicodes = Object.keys( glyphIndexMap );
for ( var i = 0; i < unicodes.length; i ++ ) {
var unicode = unicodes[ i ];
var glyph = font.glyphs.glyphs[ glyphIndexMap[ unicode ] ];
if ( unicode !== undefined ) {
var token = {
ha: round( glyph.advanceWidth * scale ),
x_min: round( glyph.xMin * scale ),
x_max: round( glyph.xMax * scale ),
o: ''
};
if ( reversed ) {
glyph.path.commands = reverseCommands( glyph.path.commands );
}
glyph.path.commands.forEach( function ( command ) {
if ( command.type.toLowerCase() === 'c' ) {
command.type = 'b';
}
token.o += command.type.toLowerCase() + ' ';
if ( command.x !== undefined && command.y !== undefined ) {
token.o += round( command.x * scale ) + ' ' + round( command.y * scale ) + ' ';
}
if ( command.x1 !== undefined && command.y1 !== undefined ) {
token.o += round( command.x1 * scale ) + ' ' + round( command.y1 * scale ) + ' ';
}
if ( command.x2 !== undefined && command.y2 !== undefined ) {
token.o += round( command.x2 * scale ) + ' ' + round( command.y2 * scale ) + ' ';
}
} );
glyphs[ String.fromCodePoint( glyph.unicode ) ] = token;
}
}
return {
glyphs: glyphs,
familyName: font.getEnglishName( 'fullName' ),
ascender: round( font.ascender * scale ),
descender: round( font.descender * scale ),
underlinePosition: font.tables.post.underlinePosition,
underlineThickness: font.tables.post.underlineThickness,
boundingBox: {
xMin: font.tables.head.xMin,
xMax: font.tables.head.xMax,
yMin: font.tables.head.yMin,
yMax: font.tables.head.yMax
},
resolution: 1000,
original_font_information: font.tables.name
};
}
function reverseCommands( commands ) {
var paths = [];
var path;
commands.forEach( function ( c ) {
if ( c.type.toLowerCase() === 'm' ) {
path = [ c ];
paths.push( path );
} else if ( c.type.toLowerCase() !== 'z' ) {
path.push( c );
}
} );
var reversed = [];
paths.forEach( function ( p ) {
var result = {
type: 'm',
x: p[ p.length - 1 ].x,
y: p[ p.length - 1 ].y
};
reversed.push( result );
for ( var i = p.length - 1; i > 0; i -- ) {
var command = p[ i ];
var result = { type: command.type };
if ( command.x2 !== undefined && command.y2 !== undefined ) {
result.x1 = command.x2;
result.y1 = command.y2;
result.x2 = command.x1;
result.y2 = command.y1;
} else if ( command.x1 !== undefined && command.y1 !== undefined ) {
result.x1 = command.x1;
result.y1 = command.y1;
}
result.x = p[ i - 1 ].x;
result.y = p[ i - 1 ].y;
reversed.push( result );
}
} );
return reversed;
}
if ( typeof opentype === 'undefined' ) {
console.warn( 'THREE.TTFLoader: The loader requires opentype.js. Make sure it\'s included before using the loader.' );
return null;
}
return convert( opentype.parse( arraybuffer ), this.reversed ); // eslint-disable-line no-undef
}
} );
export { TTFLoader };

View File

@@ -0,0 +1,502 @@
import {
BufferAttribute,
BufferGeometry,
DoubleSide,
FileLoader,
Group,
Loader,
Mesh,
MeshBasicMaterial,
RawShaderMaterial,
TextureLoader,
Quaternion,
Vector3
} from '../../../build/three.module.js';
import { JSZip } from '../libs/jszip.module.min.js';
class TiltLoader extends Loader {
load( url, onLoad, onProgress, onError ) {
const scope = this;
const loader = new FileLoader( this.manager );
loader.setPath( this.path );
loader.setResponseType( 'arraybuffer' );
loader.setWithCredentials( this.withCredentials );
loader.load( url, function ( buffer ) {
try {
onLoad( scope.parse( buffer ) );
} catch ( e ) {
if ( onError ) {
onError( e );
} else {
console.error( e );
}
scope.manager.itemError( url );
}
}, onProgress, onError );
}
parse( buffer ) {
const group = new Group();
// https://docs.google.com/document/d/11ZsHozYn9FnWG7y3s3WAyKIACfbfwb4PbaS8cZ_xjvo/edit#
const zip = new JSZip( buffer.slice( 16 ) ); // eslint-disable-line no-undef
/*
const thumbnail = zip.files[ 'thumbnail.png' ].asArrayBuffer();
const img = document.createElement( 'img' );
img.src = URL.createObjectURL( new Blob( [ thumbnail ] ) );
document.body.appendChild( img );
*/
const metadata = JSON.parse( zip.files[ 'metadata.json' ].asText() );
/*
const blob = new Blob( [ zip.files[ 'data.sketch' ].asArrayBuffer() ], { type: 'application/octet-stream' } );
window.open( URL.createObjectURL( blob ) );
*/
const data = new DataView( zip.files[ 'data.sketch' ].asArrayBuffer() );
const num_strokes = data.getInt32( 16, true );
const brushes = {};
let offset = 20;
for ( let i = 0; i < num_strokes; i ++ ) {
const brush_index = data.getInt32( offset, true );
const brush_color = [
data.getFloat32( offset + 4, true ),
data.getFloat32( offset + 8, true ),
data.getFloat32( offset + 12, true ),
data.getFloat32( offset + 16, true )
];
const brush_size = data.getFloat32( offset + 20, true );
const stroke_mask = data.getUint32( offset + 24, true );
const controlpoint_mask = data.getUint32( offset + 28, true );
let offset_stroke_mask = 0;
let offset_controlpoint_mask = 0;
for ( let j = 0; j < 4; j ++ ) {
// TOFIX: I don't understand these masks yet
const byte = 1 << j;
if ( ( stroke_mask & byte ) > 0 ) offset_stroke_mask += 4;
if ( ( controlpoint_mask & byte ) > 0 ) offset_controlpoint_mask += 4;
}
// console.log( { brush_index, brush_color, brush_size, stroke_mask, controlpoint_mask } );
// console.log( offset_stroke_mask, offset_controlpoint_mask );
offset = offset + 28 + offset_stroke_mask + 4; // TOFIX: This is wrong
const num_control_points = data.getInt32( offset, true );
// console.log( { num_control_points } );
const positions = new Float32Array( num_control_points * 3 );
const quaternions = new Float32Array( num_control_points * 4 );
offset = offset + 4;
for ( let j = 0, k = 0; j < positions.length; j += 3, k += 4 ) {
positions[ j + 0 ] = data.getFloat32( offset + 0, true );
positions[ j + 1 ] = data.getFloat32( offset + 4, true );
positions[ j + 2 ] = data.getFloat32( offset + 8, true );
quaternions[ k + 0 ] = data.getFloat32( offset + 12, true );
quaternions[ k + 1 ] = data.getFloat32( offset + 16, true );
quaternions[ k + 2 ] = data.getFloat32( offset + 20, true );
quaternions[ k + 3 ] = data.getFloat32( offset + 24, true );
offset = offset + 28 + offset_controlpoint_mask; // TOFIX: This is wrong
}
if ( brush_index in brushes === false ) {
brushes[ brush_index ] = [];
}
brushes[ brush_index ].push( [ positions, quaternions, brush_size, brush_color ] );
}
for ( const brush_index in brushes ) {
const geometry = new StrokeGeometry( brushes[ brush_index ] );
const material = getMaterial( metadata.BrushIndex[ brush_index ] );
group.add( new Mesh( geometry, material ) );
}
return group;
}
}
class StrokeGeometry extends BufferGeometry {
constructor( strokes ) {
super();
const vertices = [];
const colors = [];
const uvs = [];
const position = new Vector3();
const prevPosition = new Vector3();
const quaternion = new Quaternion();
const prevQuaternion = new Quaternion();
const vector1 = new Vector3();
const vector2 = new Vector3();
const vector3 = new Vector3();
const vector4 = new Vector3();
// size = size / 2;
for ( const k in strokes ) {
const stroke = strokes[ k ];
const positions = stroke[ 0 ];
const quaternions = stroke[ 1 ];
const size = stroke[ 2 ];
const color = stroke[ 3 ];
prevPosition.fromArray( positions, 0 );
prevQuaternion.fromArray( quaternions, 0 );
for ( let i = 3, j = 4, l = positions.length; i < l; i += 3, j += 4 ) {
position.fromArray( positions, i );
quaternion.fromArray( quaternions, j );
vector1.set( - size, 0, 0 );
vector1.applyQuaternion( quaternion );
vector1.add( position );
vector2.set( size, 0, 0 );
vector2.applyQuaternion( quaternion );
vector2.add( position );
vector3.set( size, 0, 0 );
vector3.applyQuaternion( prevQuaternion );
vector3.add( prevPosition );
vector4.set( - size, 0, 0 );
vector4.applyQuaternion( prevQuaternion );
vector4.add( prevPosition );
vertices.push( vector1.x, vector1.y, - vector1.z );
vertices.push( vector2.x, vector2.y, - vector2.z );
vertices.push( vector4.x, vector4.y, - vector4.z );
vertices.push( vector2.x, vector2.y, - vector2.z );
vertices.push( vector3.x, vector3.y, - vector3.z );
vertices.push( vector4.x, vector4.y, - vector4.z );
prevPosition.copy( position );
prevQuaternion.copy( quaternion );
colors.push( ...color );
colors.push( ...color );
colors.push( ...color );
colors.push( ...color );
colors.push( ...color );
colors.push( ...color );
const p1 = i / l;
const p2 = ( i - 3 ) / l;
uvs.push( p1, 0 );
uvs.push( p1, 1 );
uvs.push( p2, 0 );
uvs.push( p1, 1 );
uvs.push( p2, 1 );
uvs.push( p2, 0 );
}
}
this.setAttribute( 'position', new BufferAttribute( new Float32Array( vertices ), 3 ) );
this.setAttribute( 'color', new BufferAttribute( new Float32Array( colors ), 4 ) );
this.setAttribute( 'uv', new BufferAttribute( new Float32Array( uvs ), 2 ) );
}
}
const BRUSH_LIST_ARRAY = {
'89d104cd-d012-426b-b5b3-bbaee63ac43c': 'Bubbles',
'700f3aa8-9a7c-2384-8b8a-ea028905dd8c': 'CelVinyl',
'0f0ff7b2-a677-45eb-a7d6-0cd7206f4816': 'ChromaticWave',
'1161af82-50cf-47db-9706-0c3576d43c43': 'CoarseBristles',
'79168f10-6961-464a-8be1-57ed364c5600': 'CoarseBristlesSingleSided',
'1caa6d7d-f015-3f54-3a4b-8b5354d39f81': 'Comet',
'c8313697-2563-47fc-832e-290f4c04b901': 'DiamondHull',
'4391aaaa-df73-4396-9e33-31e4e4930b27': 'Disco',
'd1d991f2-e7a0-4cf1-b328-f57e915e6260': 'DotMarker',
'6a1cf9f9-032c-45ec-9b1d-a6680bee30f7': 'Dots',
'0d3889f3-3ede-470c-8af4-f44813306126': 'DoubleTaperedFlat',
'0d3889f3-3ede-470c-8af4-de4813306126': 'DoubleTaperedMarker',
'd0262945-853c-4481-9cbd-88586bed93cb': 'DuctTape',
'3ca16e2f-bdcd-4da2-8631-dcef342f40f1': 'DuctTapeSingleSided',
'f6e85de3-6dcc-4e7f-87fd-cee8c3d25d51': 'Electricity',
'02ffb866-7fb2-4d15-b761-1012cefb1360': 'Embers',
'cb92b597-94ca-4255-b017-0e3f42f12f9e': 'Fire',
'2d35bcf0-e4d8-452c-97b1-3311be063130': 'Flat',
'55303bc4-c749-4a72-98d9-d23e68e76e18': 'FlatDeprecated',
'280c0a7a-aad8-416c-a7d2-df63d129ca70': 'FlatSingleSided',
'cf019139-d41c-4eb0-a1d0-5cf54b0a42f3': 'Highlighter',
'6a1cf9f9-032c-45ec-9b6e-a6680bee32e9': 'HyperGrid',
'dce872c2-7b49-4684-b59b-c45387949c5c': 'Hypercolor',
'e8ef32b1-baa8-460a-9c2c-9cf8506794f5': 'HypercolorSingleSided',
'2f212815-f4d3-c1a4-681a-feeaf9c6dc37': 'Icing',
'f5c336cf-5108-4b40-ade9-c687504385ab': 'Ink',
'c0012095-3ffd-4040-8ee1-fc180d346eaa': 'InkSingleSided',
'4a76a27a-44d8-4bfe-9a8c-713749a499b0': 'Leaves',
'ea19de07-d0c0-4484-9198-18489a3c1487': 'LeavesSingleSided',
'2241cd32-8ba2-48a5-9ee7-2caef7e9ed62': 'Light',
'4391aaaa-df81-4396-9e33-31e4e4930b27': 'LightWire',
'd381e0f5-3def-4a0d-8853-31e9200bcbda': 'Lofted',
'429ed64a-4e97-4466-84d3-145a861ef684': 'Marker',
'79348357-432d-4746-8e29-0e25c112e3aa': 'MatteHull',
'b2ffef01-eaaa-4ab5-aa64-95a2c4f5dbc6': 'NeonPulse',
'f72ec0e7-a844-4e38-82e3-140c44772699': 'OilPaint',
'c515dad7-4393-4681-81ad-162ef052241b': 'OilPaintSingleSided',
'f1114e2e-eb8d-4fde-915a-6e653b54e9f5': 'Paper',
'759f1ebd-20cd-4720-8d41-234e0da63716': 'PaperSingleSided',
'e0abbc80-0f80-e854-4970-8924a0863dcc': 'Petal',
'c33714d1-b2f9-412e-bd50-1884c9d46336': 'Plasma',
'ad1ad437-76e2-450d-a23a-e17f8310b960': 'Rainbow',
'faaa4d44-fcfb-4177-96be-753ac0421ba3': 'ShinyHull',
'70d79cca-b159-4f35-990c-f02193947fe8': 'Smoke',
'd902ed8b-d0d1-476c-a8de-878a79e3a34c': 'Snow',
'accb32f5-4509-454f-93f8-1df3fd31df1b': 'SoftHighlighter',
'cf7f0059-7aeb-53a4-2b67-c83d863a9ffa': 'Spikes',
'8dc4a70c-d558-4efd-a5ed-d4e860f40dc3': 'Splatter',
'7a1c8107-50c5-4b70-9a39-421576d6617e': 'SplatterSingleSided',
'0eb4db27-3f82-408d-b5a1-19ebd7d5b711': 'Stars',
'44bb800a-fbc3-4592-8426-94ecb05ddec3': 'Streamers',
'0077f88c-d93a-42f3-b59b-b31c50cdb414': 'Taffy',
'b468c1fb-f254-41ed-8ec9-57030bc5660c': 'TaperedFlat',
'c8ccb53d-ae13-45ef-8afb-b730d81394eb': 'TaperedFlatSingleSided',
'd90c6ad8-af0f-4b54-b422-e0f92abe1b3c': 'TaperedMarker',
'1a26b8c0-8a07-4f8a-9fac-d2ef36e0cad0': 'TaperedMarker_Flat',
'75b32cf0-fdd6-4d89-a64b-e2a00b247b0f': 'ThickPaint',
'fdf0326a-c0d1-4fed-b101-9db0ff6d071f': 'ThickPaintSingleSided',
'4391385a-df73-4396-9e33-31e4e4930b27': 'Toon',
'a8fea537-da7c-4d4b-817f-24f074725d6d': 'UnlitHull',
'd229d335-c334-495a-a801-660ac8a87360': 'VelvetInk',
'10201aa3-ebc2-42d8-84b7-2e63f6eeb8ab': 'Waveform',
'b67c0e81-ce6d-40a8-aeb0-ef036b081aa3': 'WetPaint',
'dea67637-cd1a-27e4-c9b1-52f4bbcb84e5': 'WetPaintSingleSided',
'5347acf0-a8e2-47b6-8346-30c70719d763': 'WigglyGraphite',
'e814fef1-97fd-7194-4a2f-50c2bb918be2': 'WigglyGraphiteSingleSided',
'4391385a-cf83-4396-9e33-31e4e4930b27': 'Wire'
};
const common = {
'colors': {
'BloomColor': `
vec3 BloomColor(vec3 color, float gain) {
// Guarantee that there's at least a little bit of all 3 channels.
// This makes fully-saturated strokes (which only have 2 non-zero
// color channels) eventually clip to white rather than to a secondary.
float cmin = length(color.rgb) * .05;
color.rgb = max(color.rgb, vec3(cmin, cmin, cmin));
// If we try to remove this pow() from .a, it brightens up
// pressure-sensitive strokes; looks better as-is.
color = pow(color, vec3(2.2));
color.rgb *= 2. * exp(gain * 10.);
return color;
}
`,
'LinearToSrgb': `
vec3 LinearToSrgb(vec3 color) {
// Approximation http://chilliant.blogspot.com/2012/08/srgb-approximations-for-hlsl.html
vec3 linearColor = color.rgb;
vec3 S1 = sqrt(linearColor);
vec3 S2 = sqrt(S1);
vec3 S3 = sqrt(S2);
color.rgb = 0.662002687 * S1 + 0.684122060 * S2 - 0.323583601 * S3 - 0.0225411470 * linearColor;
return color;
}
`,
'hsv': `
// uniform sampler2D lookupTex;
vec4 lookup(vec4 textureColor) {
return textureColor;
}
vec3 lookup(vec3 textureColor) {
return textureColor;
}
vec3 hsv2rgb( vec3 hsv ) {
vec3 rgb = clamp( abs(mod(hsv.x*6.0+vec3(0.0,4.0,2.0),6.0)-3.0)-1.0, 0.0, 1.0 );
return hsv.z * mix( vec3(1.0), rgb, hsv.y);
}
vec3 rgb2hsv( vec3 rgb ) {
vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);
vec4 p = mix(vec4(rgb.bg, K.wz), vec4(rgb.gb, K.xy), step(rgb.b, rgb.g));
vec4 q = mix(vec4(p.xyw, rgb.r), vec4(rgb.r, p.yzx), step(p.x, rgb.r));
float d = q.x - min(q.w, q.y);
float e = 1.0e-10;
return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x);
}
`,
'SrgbToLinear': `
vec3 SrgbToLinear(vec3 color) {
// Approximation http://chilliant.blogspot.com/2012/08/srgb-approximations-for-hlsl.html
vec3 sRGB = color.rgb;
color.rgb = sRGB * (sRGB * (sRGB * 0.305306011 + 0.682171111) + 0.012522878);
return color;
}
`
}
};
const loader = new TextureLoader().setPath( './textures/tiltbrush/' );
const shaders = {
'Light': {
uniforms: {
mainTex: { value: loader.load( 'Light.webp' ) },
alphaTest: { value: 0.067 },
emission_gain: { value: 0.45 },
alpha: { value: 1 },
},
vertexShader: `
precision highp float;
precision highp int;
attribute vec2 uv;
attribute vec4 color;
attribute vec3 position;
uniform mat4 modelMatrix;
uniform mat4 modelViewMatrix;
uniform mat4 projectionMatrix;
uniform mat4 viewMatrix;
uniform mat3 normalMatrix;
uniform vec3 cameraPosition;
varying vec2 vUv;
varying vec3 vColor;
${ common.colors.LinearToSrgb }
${ common.colors.hsv }
void main() {
vUv = uv;
vColor = lookup(color.rgb);
vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );
gl_Position = projectionMatrix * mvPosition;
}
`,
fragmentShader: `
precision highp float;
precision highp int;
uniform float emission_gain;
uniform sampler2D mainTex;
uniform float alphaTest;
varying vec2 vUv;
varying vec3 vColor;
${ common.colors.BloomColor }
${ common.colors.SrgbToLinear }
void main(){
vec4 col = texture2D(mainTex, vUv);
vec3 color = vColor;
color = BloomColor(color, emission_gain);
color = color * col.rgb;
color = color * col.a;
color = SrgbToLinear(color);
gl_FragColor = vec4(color, 1.0);
}
`,
side: 2,
transparent: true,
depthFunc: 2,
depthWrite: true,
depthTest: false,
blending: 5,
blendDst: 201,
blendDstAlpha: 201,
blendEquation: 100,
blendEquationAlpha: 100,
blendSrc: 201,
blendSrcAlpha: 201,
}
};
function getMaterial( GUID ) {
const name = BRUSH_LIST_ARRAY[ GUID ];
switch ( name ) {
case 'Light':
return new RawShaderMaterial( shaders.Light );
default:
return new MeshBasicMaterial( { vertexColors: true, side: DoubleSide } );
}
}
export { TiltLoader };

View File

@@ -0,0 +1,260 @@
import {
BufferGeometry,
FileLoader,
Float32BufferAttribute,
Loader,
Mesh,
MeshStandardMaterial
} from '../../../build/three.module.js';
class VOXLoader extends Loader {
load( url, onLoad, onProgress, onError ) {
const scope = this;
const loader = new FileLoader( scope.manager );
loader.setPath( scope.path );
loader.setResponseType( 'arraybuffer' );
loader.setRequestHeader( scope.requestHeader );
loader.load( url, function ( buffer ) {
try {
onLoad( scope.parse( buffer ) );
} catch ( e ) {
if ( onError ) {
onError( e );
} else {
console.error( e );
}
scope.manager.itemError( url );
}
}, onProgress, onError );
}
parse( buffer ) {
const data = new DataView( buffer );
const id = data.getInt32( 0, true );
const version = data.getUint32( 4, true );
if ( id !== 542658390 || version !== 150 ) {
console.error( 'Not a valid VOX file' );
return;
}
const DEFAULT_PALETTE = [
0x00000000, 0xffffffff, 0xffccffff, 0xff99ffff, 0xff66ffff, 0xff33ffff, 0xff00ffff, 0xffffccff,
0xffccccff, 0xff99ccff, 0xff66ccff, 0xff33ccff, 0xff00ccff, 0xffff99ff, 0xffcc99ff, 0xff9999ff,
0xff6699ff, 0xff3399ff, 0xff0099ff, 0xffff66ff, 0xffcc66ff, 0xff9966ff, 0xff6666ff, 0xff3366ff,
0xff0066ff, 0xffff33ff, 0xffcc33ff, 0xff9933ff, 0xff6633ff, 0xff3333ff, 0xff0033ff, 0xffff00ff,
0xffcc00ff, 0xff9900ff, 0xff6600ff, 0xff3300ff, 0xff0000ff, 0xffffffcc, 0xffccffcc, 0xff99ffcc,
0xff66ffcc, 0xff33ffcc, 0xff00ffcc, 0xffffcccc, 0xffcccccc, 0xff99cccc, 0xff66cccc, 0xff33cccc,
0xff00cccc, 0xffff99cc, 0xffcc99cc, 0xff9999cc, 0xff6699cc, 0xff3399cc, 0xff0099cc, 0xffff66cc,
0xffcc66cc, 0xff9966cc, 0xff6666cc, 0xff3366cc, 0xff0066cc, 0xffff33cc, 0xffcc33cc, 0xff9933cc,
0xff6633cc, 0xff3333cc, 0xff0033cc, 0xffff00cc, 0xffcc00cc, 0xff9900cc, 0xff6600cc, 0xff3300cc,
0xff0000cc, 0xffffff99, 0xffccff99, 0xff99ff99, 0xff66ff99, 0xff33ff99, 0xff00ff99, 0xffffcc99,
0xffcccc99, 0xff99cc99, 0xff66cc99, 0xff33cc99, 0xff00cc99, 0xffff9999, 0xffcc9999, 0xff999999,
0xff669999, 0xff339999, 0xff009999, 0xffff6699, 0xffcc6699, 0xff996699, 0xff666699, 0xff336699,
0xff006699, 0xffff3399, 0xffcc3399, 0xff993399, 0xff663399, 0xff333399, 0xff003399, 0xffff0099,
0xffcc0099, 0xff990099, 0xff660099, 0xff330099, 0xff000099, 0xffffff66, 0xffccff66, 0xff99ff66,
0xff66ff66, 0xff33ff66, 0xff00ff66, 0xffffcc66, 0xffcccc66, 0xff99cc66, 0xff66cc66, 0xff33cc66,
0xff00cc66, 0xffff9966, 0xffcc9966, 0xff999966, 0xff669966, 0xff339966, 0xff009966, 0xffff6666,
0xffcc6666, 0xff996666, 0xff666666, 0xff336666, 0xff006666, 0xffff3366, 0xffcc3366, 0xff993366,
0xff663366, 0xff333366, 0xff003366, 0xffff0066, 0xffcc0066, 0xff990066, 0xff660066, 0xff330066,
0xff000066, 0xffffff33, 0xffccff33, 0xff99ff33, 0xff66ff33, 0xff33ff33, 0xff00ff33, 0xffffcc33,
0xffcccc33, 0xff99cc33, 0xff66cc33, 0xff33cc33, 0xff00cc33, 0xffff9933, 0xffcc9933, 0xff999933,
0xff669933, 0xff339933, 0xff009933, 0xffff6633, 0xffcc6633, 0xff996633, 0xff666633, 0xff336633,
0xff006633, 0xffff3333, 0xffcc3333, 0xff993333, 0xff663333, 0xff333333, 0xff003333, 0xffff0033,
0xffcc0033, 0xff990033, 0xff660033, 0xff330033, 0xff000033, 0xffffff00, 0xffccff00, 0xff99ff00,
0xff66ff00, 0xff33ff00, 0xff00ff00, 0xffffcc00, 0xffcccc00, 0xff99cc00, 0xff66cc00, 0xff33cc00,
0xff00cc00, 0xffff9900, 0xffcc9900, 0xff999900, 0xff669900, 0xff339900, 0xff009900, 0xffff6600,
0xffcc6600, 0xff996600, 0xff666600, 0xff336600, 0xff006600, 0xffff3300, 0xffcc3300, 0xff993300,
0xff663300, 0xff333300, 0xff003300, 0xffff0000, 0xffcc0000, 0xff990000, 0xff660000, 0xff330000,
0xff0000ee, 0xff0000dd, 0xff0000bb, 0xff0000aa, 0xff000088, 0xff000077, 0xff000055, 0xff000044,
0xff000022, 0xff000011, 0xff00ee00, 0xff00dd00, 0xff00bb00, 0xff00aa00, 0xff008800, 0xff007700,
0xff005500, 0xff004400, 0xff002200, 0xff001100, 0xffee0000, 0xffdd0000, 0xffbb0000, 0xffaa0000,
0xff880000, 0xff770000, 0xff550000, 0xff440000, 0xff220000, 0xff110000, 0xffeeeeee, 0xffdddddd,
0xffbbbbbb, 0xffaaaaaa, 0xff888888, 0xff777777, 0xff555555, 0xff444444, 0xff222222, 0xff111111
];
let i = 8;
let chunk;
const chunks = [];
while ( i < data.byteLength ) {
let id = '';
for ( let j = 0; j < 4; j ++ ) {
id += String.fromCharCode( data.getInt8( i ++, true ) );
}
const chunkSize = data.getInt32( i, true ); i += 4;
data.getInt32( i, true ); i += 4; // childChunks
if ( id === 'SIZE' ) {
const x = data.getInt32( i, true ); i += 4;
const y = data.getInt32( i, true ); i += 4;
const z = data.getInt32( i, true ); i += 4;
chunk = {
palette: DEFAULT_PALETTE,
size: { x: x, y: y, z: z },
};
chunks.push( chunk );
i += chunkSize - ( 3 * 4 );
} else if ( id === 'XYZI' ) {
const numVoxels = data.getInt32( i, true ); i += 4;
chunk.data = new Int8Array( buffer, i, numVoxels * 4 );
i += numVoxels * 4;
} else if ( id === 'RGBA' ) {
const palette = [ 0 ];
for ( let j = 0; j < 256; j ++ ) {
palette[ j + 1 ] = data.getInt32( i, true ); i += 4;
}
chunk.palette = palette;
} else {
// console.log( id, chunkSize, childChunks );
i += chunkSize;
}
}
return chunks;
}
}
class VOXMesh extends Mesh {
constructor( chunk ) {
const data = chunk.data;
const size = chunk.size;
const palette = chunk.palette;
//
const vertices = [];
const colors = [];
const nx = [ 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1 ];
const px = [ 1, 0, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0 ];
const py = [ 0, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1 ];
const ny = [ 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0 ];
const nz = [ 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0 ];
const pz = [ 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 1, 1 ];
function add( tile, x, y, z, r, g, b ) {
x -= size.x / 2;
y -= size.z / 2;
z += size.y / 2;
for ( let i = 0; i < 18; i += 3 ) {
vertices.push( tile[ i + 0 ] + x, tile[ i + 1 ] + y, tile[ i + 2 ] + z );
colors.push( r, g, b );
}
}
// Store data in a volume for sampling
const offsety = size.x;
const offsetz = size.x * size.y;
const array = new Uint8Array( size.x * size.y * size.z );
for ( let j = 0; j < data.length; j += 4 ) {
const x = data[ j + 0 ];
const y = data[ j + 1 ];
const z = data[ j + 2 ];
const index = x + ( y * offsety ) + ( z * offsetz );
array[ index ] = 255;
}
// Construct geometry
let hasColors = false;
for ( let j = 0; j < data.length; j += 4 ) {
const x = data[ j + 0 ];
const y = data[ j + 1 ];
const z = data[ j + 2 ];
const c = data[ j + 3 ];
const hex = palette[ c ];
const r = ( hex >> 0 & 0xff ) / 0xff;
const g = ( hex >> 8 & 0xff ) / 0xff;
const b = ( hex >> 16 & 0xff ) / 0xff;
if ( r > 0 || g > 0 || b > 0 ) hasColors = true;
const index = x + ( y * offsety ) + ( z * offsetz );
if ( array[ index + 1 ] === 0 || x === size.x - 1 ) add( px, x, z, - y, r, g, b );
if ( array[ index - 1 ] === 0 || x === 0 ) add( nx, x, z, - y, r, g, b );
if ( array[ index + offsety ] === 0 || y === size.y - 1 ) add( ny, x, z, - y, r, g, b );
if ( array[ index - offsety ] === 0 || y === 0 ) add( py, x, z, - y, r, g, b );
if ( array[ index + offsetz ] === 0 || z === size.z - 1 ) add( pz, x, z, - y, r, g, b );
if ( array[ index - offsetz ] === 0 || z === 0 ) add( nz, x, z, - y, r, g, b );
}
const geometry = new BufferGeometry();
geometry.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) );
geometry.computeVertexNormals();
if ( hasColors ) geometry.setAttribute( 'color', new Float32BufferAttribute( colors, 3 ) );
const material = new MeshStandardMaterial( { vertexColors: hasColors } );
super( geometry, material );
}
}
export { VOXLoader, VOXMesh };

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,86 @@
import {
Loader
} from '../../../build/three.module.js';
import { GLTFLoader } from '../loaders/GLTFLoader.js';
// VRM Specification: https://dwango.github.io/vrm/vrm_spec/
//
// VRM is based on glTF 2.0 and VRM extension is defined
// in top-level json.extensions.VRM
var VRMLoader = ( function () {
function VRMLoader( manager ) {
if ( GLTFLoader === undefined ) {
throw new Error( 'THREE.VRMLoader: Import GLTFLoader.' );
}
Loader.call( this, manager );
this.gltfLoader = new GLTFLoader( this.manager );
}
VRMLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
constructor: VRMLoader,
load: function ( url, onLoad, onProgress, onError ) {
var scope = this;
this.gltfLoader.load( url, function ( gltf ) {
try {
scope.parse( gltf, onLoad );
} catch ( e ) {
if ( onError ) {
onError( e );
} else {
console.error( e );
}
scope.manager.itemError( url );
}
}, onProgress, onError );
},
setDRACOLoader: function ( dracoLoader ) {
this.gltfLoader.setDRACOLoader( dracoLoader );
return this;
},
parse: function ( gltf, onLoad ) {
// var gltfParser = gltf.parser;
// var gltfExtensions = gltf.userData.gltfExtensions || {};
// var vrmExtension = gltfExtensions.VRM || {};
// handle VRM Extension here
onLoad( gltf );
}
} );
return VRMLoader;
} )();
export { VRMLoader };

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,100 @@
import {
BufferGeometry,
FileLoader,
Float32BufferAttribute,
Loader
} from '../../../build/three.module.js';
class XYZLoader extends Loader {
load( url, onLoad, onProgress, onError ) {
const scope = this;
const loader = new FileLoader( this.manager );
loader.setPath( this.path );
loader.setRequestHeader( this.requestHeader );
loader.setWithCredentials( this.withCredentials );
loader.load( url, function ( text ) {
try {
onLoad( scope.parse( text ) );
} catch ( e ) {
if ( onError ) {
onError( e );
} else {
console.error( e );
}
scope.manager.itemError( url );
}
}, onProgress, onError );
}
parse( text ) {
const lines = text.split( '\n' );
const vertices = [];
const colors = [];
for ( let line of lines ) {
line = line.trim();
if ( line.charAt( 0 ) === '#' ) continue; // skip comments
const lineValues = line.split( /\s+/ );
if ( lineValues.length === 3 ) {
// XYZ
vertices.push( parseFloat( lineValues[ 0 ] ) );
vertices.push( parseFloat( lineValues[ 1 ] ) );
vertices.push( parseFloat( lineValues[ 2 ] ) );
}
if ( lineValues.length === 6 ) {
// XYZRGB
vertices.push( parseFloat( lineValues[ 0 ] ) );
vertices.push( parseFloat( lineValues[ 1 ] ) );
vertices.push( parseFloat( lineValues[ 2 ] ) );
colors.push( parseFloat( lineValues[ 3 ] ) / 255 );
colors.push( parseFloat( lineValues[ 4 ] ) / 255 );
colors.push( parseFloat( lineValues[ 5 ] ) / 255 );
}
}
const geometry = new BufferGeometry();
geometry.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) );
if ( colors.length > 0 ) {
geometry.setAttribute( 'color', new Float32BufferAttribute( colors, 3 ) );
}
return geometry;
}
}
export { XYZLoader };

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,415 @@
function LWO2Parser( IFFParser ) {
this.IFF = IFFParser;
}
LWO2Parser.prototype = {
constructor: LWO2Parser,
parseBlock: function () {
this.IFF.debugger.offset = this.IFF.reader.offset;
this.IFF.debugger.closeForms();
var blockID = this.IFF.reader.getIDTag();
var length = this.IFF.reader.getUint32(); // size of data in bytes
if ( length > this.IFF.reader.dv.byteLength - this.IFF.reader.offset ) {
this.IFF.reader.offset -= 4;
length = this.IFF.reader.getUint16();
}
this.IFF.debugger.dataOffset = this.IFF.reader.offset;
this.IFF.debugger.length = length;
// Data types may be found in either LWO2 OR LWO3 spec
switch ( blockID ) {
case 'FORM': // form blocks may consist of sub -chunks or sub-forms
this.IFF.parseForm( length );
break;
// SKIPPED CHUNKS
// if break; is called directly, the position in the lwoTree is not created
// any sub chunks and forms are added to the parent form instead
// MISC skipped
case 'ICON': // Thumbnail Icon Image
case 'VMPA': // Vertex Map Parameter
case 'BBOX': // bounding box
// case 'VMMD':
// case 'VTYP':
// normal maps can be specified, normally on models imported from other applications. Currently ignored
case 'NORM':
// ENVL FORM skipped
case 'PRE ':
case 'POST':
case 'KEY ':
case 'SPAN':
// CLIP FORM skipped
case 'TIME':
case 'CLRS':
case 'CLRA':
case 'FILT':
case 'DITH':
case 'CONT':
case 'BRIT':
case 'SATR':
case 'HUE ':
case 'GAMM':
case 'NEGA':
case 'IFLT':
case 'PFLT':
// Image Map Layer skipped
case 'PROJ':
case 'AXIS':
case 'AAST':
case 'PIXB':
case 'AUVO':
case 'STCK':
// Procedural Textures skipped
case 'PROC':
case 'VALU':
case 'FUNC':
// Gradient Textures skipped
case 'PNAM':
case 'INAM':
case 'GRST':
case 'GREN':
case 'GRPT':
case 'FKEY':
case 'IKEY':
// Texture Mapping Form skipped
case 'CSYS':
// Surface CHUNKs skipped
case 'OPAQ': // top level 'opacity' checkbox
case 'CMAP': // clip map
// Surface node CHUNKS skipped
// These mainly specify the node editor setup in LW
case 'NLOC':
case 'NZOM':
case 'NVER':
case 'NSRV':
case 'NVSK': // unknown
case 'NCRD':
case 'WRPW': // image wrap w ( for cylindrical and spherical projections)
case 'WRPH': // image wrap h
case 'NMOD':
case 'NPRW':
case 'NPLA':
case 'NODS':
case 'VERS':
case 'ENUM':
case 'TAG ':
case 'OPAC':
// Car Material CHUNKS
case 'CGMD':
case 'CGTY':
case 'CGST':
case 'CGEN':
case 'CGTS':
case 'CGTE':
case 'OSMP':
case 'OMDE':
case 'OUTR':
case 'FLAG':
case 'TRNL':
case 'GLOW':
case 'GVAL': // glow intensity
case 'SHRP':
case 'RFOP':
case 'RSAN':
case 'TROP':
case 'RBLR':
case 'TBLR':
case 'CLRH':
case 'CLRF':
case 'ADTR':
case 'LINE':
case 'ALPH':
case 'VCOL':
case 'ENAB':
this.IFF.debugger.skipped = true;
this.IFF.reader.skip( length );
break;
case 'SURF':
this.IFF.parseSurfaceLwo2( length );
break;
case 'CLIP':
this.IFF.parseClipLwo2( length );
break;
// Texture node chunks (not in spec)
case 'IPIX': // usePixelBlending
case 'IMIP': // useMipMaps
case 'IMOD': // imageBlendingMode
case 'AMOD': // unknown
case 'IINV': // imageInvertAlpha
case 'INCR': // imageInvertColor
case 'IAXS': // imageAxis ( for non-UV maps)
case 'IFOT': // imageFallofType
case 'ITIM': // timing for animated textures
case 'IWRL':
case 'IUTI':
case 'IINX':
case 'IINY':
case 'IINZ':
case 'IREF': // possibly a VX for reused texture nodes
if ( length === 4 ) this.IFF.currentNode[ blockID ] = this.IFF.reader.getInt32();
else this.IFF.reader.skip( length );
break;
case 'OTAG':
this.IFF.parseObjectTag();
break;
case 'LAYR':
this.IFF.parseLayer( length );
break;
case 'PNTS':
this.IFF.parsePoints( length );
break;
case 'VMAP':
this.IFF.parseVertexMapping( length );
break;
case 'AUVU':
case 'AUVN':
this.IFF.reader.skip( length - 1 );
this.IFF.reader.getVariableLengthIndex(); // VX
break;
case 'POLS':
this.IFF.parsePolygonList( length );
break;
case 'TAGS':
this.IFF.parseTagStrings( length );
break;
case 'PTAG':
this.IFF.parsePolygonTagMapping( length );
break;
case 'VMAD':
this.IFF.parseVertexMapping( length, true );
break;
// Misc CHUNKS
case 'DESC': // Description Line
this.IFF.currentForm.description = this.IFF.reader.getString();
break;
case 'TEXT':
case 'CMNT':
case 'NCOM':
this.IFF.currentForm.comment = this.IFF.reader.getString();
break;
// Envelope Form
case 'NAME':
this.IFF.currentForm.channelName = this.IFF.reader.getString();
break;
// Image Map Layer
case 'WRAP':
this.IFF.currentForm.wrap = { w: this.IFF.reader.getUint16(), h: this.IFF.reader.getUint16() };
break;
case 'IMAG':
var index = this.IFF.reader.getVariableLengthIndex();
this.IFF.currentForm.imageIndex = index;
break;
// Texture Mapping Form
case 'OREF':
this.IFF.currentForm.referenceObject = this.IFF.reader.getString();
break;
case 'ROID':
this.IFF.currentForm.referenceObjectID = this.IFF.reader.getUint32();
break;
// Surface Blocks
case 'SSHN':
this.IFF.currentSurface.surfaceShaderName = this.IFF.reader.getString();
break;
case 'AOVN':
this.IFF.currentSurface.surfaceCustomAOVName = this.IFF.reader.getString();
break;
// Nodal Blocks
case 'NSTA':
this.IFF.currentForm.disabled = this.IFF.reader.getUint16();
break;
case 'NRNM':
this.IFF.currentForm.realName = this.IFF.reader.getString();
break;
case 'NNME':
this.IFF.currentForm.refName = this.IFF.reader.getString();
this.IFF.currentSurface.nodes[ this.IFF.currentForm.refName ] = this.IFF.currentForm;
break;
// Nodal Blocks : connections
case 'INME':
if ( ! this.IFF.currentForm.nodeName ) this.IFF.currentForm.nodeName = [];
this.IFF.currentForm.nodeName.push( this.IFF.reader.getString() );
break;
case 'IINN':
if ( ! this.IFF.currentForm.inputNodeName ) this.IFF.currentForm.inputNodeName = [];
this.IFF.currentForm.inputNodeName.push( this.IFF.reader.getString() );
break;
case 'IINM':
if ( ! this.IFF.currentForm.inputName ) this.IFF.currentForm.inputName = [];
this.IFF.currentForm.inputName.push( this.IFF.reader.getString() );
break;
case 'IONM':
if ( ! this.IFF.currentForm.inputOutputName ) this.IFF.currentForm.inputOutputName = [];
this.IFF.currentForm.inputOutputName.push( this.IFF.reader.getString() );
break;
case 'FNAM':
this.IFF.currentForm.fileName = this.IFF.reader.getString();
break;
case 'CHAN': // NOTE: ENVL Forms may also have CHAN chunk, however ENVL is currently ignored
if ( length === 4 ) this.IFF.currentForm.textureChannel = this.IFF.reader.getIDTag();
else this.IFF.reader.skip( length );
break;
// LWO2 Spec chunks: these are needed since the SURF FORMs are often in LWO2 format
case 'SMAN':
var maxSmoothingAngle = this.IFF.reader.getFloat32();
this.IFF.currentSurface.attributes.smooth = ( maxSmoothingAngle < 0 ) ? false : true;
break;
// LWO2: Basic Surface Parameters
case 'COLR':
this.IFF.currentSurface.attributes.Color = { value: this.IFF.reader.getFloat32Array( 3 ) };
this.IFF.reader.skip( 2 ); // VX: envelope
break;
case 'LUMI':
this.IFF.currentSurface.attributes.Luminosity = { value: this.IFF.reader.getFloat32() };
this.IFF.reader.skip( 2 );
break;
case 'SPEC':
this.IFF.currentSurface.attributes.Specular = { value: this.IFF.reader.getFloat32() };
this.IFF.reader.skip( 2 );
break;
case 'DIFF':
this.IFF.currentSurface.attributes.Diffuse = { value: this.IFF.reader.getFloat32() };
this.IFF.reader.skip( 2 );
break;
case 'REFL':
this.IFF.currentSurface.attributes.Reflection = { value: this.IFF.reader.getFloat32() };
this.IFF.reader.skip( 2 );
break;
case 'GLOS':
this.IFF.currentSurface.attributes.Glossiness = { value: this.IFF.reader.getFloat32() };
this.IFF.reader.skip( 2 );
break;
case 'TRAN':
this.IFF.currentSurface.attributes.opacity = this.IFF.reader.getFloat32();
this.IFF.reader.skip( 2 );
break;
case 'BUMP':
this.IFF.currentSurface.attributes.bumpStrength = this.IFF.reader.getFloat32();
this.IFF.reader.skip( 2 );
break;
case 'SIDE':
this.IFF.currentSurface.attributes.side = this.IFF.reader.getUint16();
break;
case 'RIMG':
this.IFF.currentSurface.attributes.reflectionMap = this.IFF.reader.getVariableLengthIndex();
break;
case 'RIND':
this.IFF.currentSurface.attributes.refractiveIndex = this.IFF.reader.getFloat32();
this.IFF.reader.skip( 2 );
break;
case 'TIMG':
this.IFF.currentSurface.attributes.refractionMap = this.IFF.reader.getVariableLengthIndex();
break;
case 'IMAP':
this.IFF.reader.skip( 2 );
break;
case 'TMAP':
this.IFF.debugger.skipped = true;
this.IFF.reader.skip( length ); // needs implementing
break;
case 'IUVI': // uv channel name
this.IFF.currentNode.UVChannel = this.IFF.reader.getString( length );
break;
case 'IUTL': // widthWrappingMode: 0 = Reset, 1 = Repeat, 2 = Mirror, 3 = Edge
this.IFF.currentNode.widthWrappingMode = this.IFF.reader.getUint32();
break;
case 'IVTL': // heightWrappingMode
this.IFF.currentNode.heightWrappingMode = this.IFF.reader.getUint32();
break;
// LWO2 USE
case 'BLOK':
// skip
break;
default:
this.IFF.parseUnknownCHUNK( blockID, length );
}
if ( blockID != 'FORM' ) {
this.IFF.debugger.node = 1;
this.IFF.debugger.nodeID = blockID;
this.IFF.debugger.log();
}
if ( this.IFF.reader.offset >= this.IFF.currentFormEnd ) {
this.IFF.currentForm = this.IFF.parentForm;
}
}
};
export { LWO2Parser };

View File

@@ -0,0 +1,375 @@
function LWO3Parser( IFFParser ) {
this.IFF = IFFParser;
}
LWO3Parser.prototype = {
constructor: LWO3Parser,
parseBlock: function () {
this.IFF.debugger.offset = this.IFF.reader.offset;
this.IFF.debugger.closeForms();
var blockID = this.IFF.reader.getIDTag();
var length = this.IFF.reader.getUint32(); // size of data in bytes
this.IFF.debugger.dataOffset = this.IFF.reader.offset;
this.IFF.debugger.length = length;
// Data types may be found in either LWO2 OR LWO3 spec
switch ( blockID ) {
case 'FORM': // form blocks may consist of sub -chunks or sub-forms
this.IFF.parseForm( length );
break;
// SKIPPED CHUNKS
// MISC skipped
case 'ICON': // Thumbnail Icon Image
case 'VMPA': // Vertex Map Parameter
case 'BBOX': // bounding box
// case 'VMMD':
// case 'VTYP':
// normal maps can be specified, normally on models imported from other applications. Currently ignored
case 'NORM':
// ENVL FORM skipped
case 'PRE ':
case 'POST':
case 'KEY ':
case 'SPAN':
// CLIP FORM skipped
case 'TIME':
case 'CLRS':
case 'CLRA':
case 'FILT':
case 'DITH':
case 'CONT':
case 'BRIT':
case 'SATR':
case 'HUE ':
case 'GAMM':
case 'NEGA':
case 'IFLT':
case 'PFLT':
// Image Map Layer skipped
case 'PROJ':
case 'AXIS':
case 'AAST':
case 'PIXB':
case 'STCK':
// Procedural Textures skipped
case 'VALU':
// Gradient Textures skipped
case 'PNAM':
case 'INAM':
case 'GRST':
case 'GREN':
case 'GRPT':
case 'FKEY':
case 'IKEY':
// Texture Mapping Form skipped
case 'CSYS':
// Surface CHUNKs skipped
case 'OPAQ': // top level 'opacity' checkbox
case 'CMAP': // clip map
// Surface node CHUNKS skipped
// These mainly specify the node editor setup in LW
case 'NLOC':
case 'NZOM':
case 'NVER':
case 'NSRV':
case 'NCRD':
case 'NMOD':
case 'NSEL':
case 'NPRW':
case 'NPLA':
case 'VERS':
case 'ENUM':
case 'TAG ':
// Car Material CHUNKS
case 'CGMD':
case 'CGTY':
case 'CGST':
case 'CGEN':
case 'CGTS':
case 'CGTE':
case 'OSMP':
case 'OMDE':
case 'OUTR':
case 'FLAG':
case 'TRNL':
case 'SHRP':
case 'RFOP':
case 'RSAN':
case 'TROP':
case 'RBLR':
case 'TBLR':
case 'CLRH':
case 'CLRF':
case 'ADTR':
case 'GLOW':
case 'LINE':
case 'ALPH':
case 'VCOL':
case 'ENAB':
this.IFF.debugger.skipped = true;
this.IFF.reader.skip( length );
break;
// Texture node chunks (not in spec)
case 'IPIX': // usePixelBlending
case 'IMIP': // useMipMaps
case 'IMOD': // imageBlendingMode
case 'AMOD': // unknown
case 'IINV': // imageInvertAlpha
case 'INCR': // imageInvertColor
case 'IAXS': // imageAxis ( for non-UV maps)
case 'IFOT': // imageFallofType
case 'ITIM': // timing for animated textures
case 'IWRL':
case 'IUTI':
case 'IINX':
case 'IINY':
case 'IINZ':
case 'IREF': // possibly a VX for reused texture nodes
if ( length === 4 ) this.IFF.currentNode[ blockID ] = this.IFF.reader.getInt32();
else this.IFF.reader.skip( length );
break;
case 'OTAG':
this.IFF.parseObjectTag();
break;
case 'LAYR':
this.IFF.parseLayer( length );
break;
case 'PNTS':
this.IFF.parsePoints( length );
break;
case 'VMAP':
this.IFF.parseVertexMapping( length );
break;
case 'POLS':
this.IFF.parsePolygonList( length );
break;
case 'TAGS':
this.IFF.parseTagStrings( length );
break;
case 'PTAG':
this.IFF.parsePolygonTagMapping( length );
break;
case 'VMAD':
this.IFF.parseVertexMapping( length, true );
break;
// Misc CHUNKS
case 'DESC': // Description Line
this.IFF.currentForm.description = this.IFF.reader.getString();
break;
case 'TEXT':
case 'CMNT':
case 'NCOM':
this.IFF.currentForm.comment = this.IFF.reader.getString();
break;
// Envelope Form
case 'NAME':
this.IFF.currentForm.channelName = this.IFF.reader.getString();
break;
// Image Map Layer
case 'WRAP':
this.IFF.currentForm.wrap = { w: this.IFF.reader.getUint16(), h: this.IFF.reader.getUint16() };
break;
case 'IMAG':
var index = this.IFF.reader.getVariableLengthIndex();
this.IFF.currentForm.imageIndex = index;
break;
// Texture Mapping Form
case 'OREF':
this.IFF.currentForm.referenceObject = this.IFF.reader.getString();
break;
case 'ROID':
this.IFF.currentForm.referenceObjectID = this.IFF.reader.getUint32();
break;
// Surface Blocks
case 'SSHN':
this.IFF.currentSurface.surfaceShaderName = this.IFF.reader.getString();
break;
case 'AOVN':
this.IFF.currentSurface.surfaceCustomAOVName = this.IFF.reader.getString();
break;
// Nodal Blocks
case 'NSTA':
this.IFF.currentForm.disabled = this.IFF.reader.getUint16();
break;
case 'NRNM':
this.IFF.currentForm.realName = this.IFF.reader.getString();
break;
case 'NNME':
this.IFF.currentForm.refName = this.IFF.reader.getString();
this.IFF.currentSurface.nodes[ this.IFF.currentForm.refName ] = this.IFF.currentForm;
break;
// Nodal Blocks : connections
case 'INME':
if ( ! this.IFF.currentForm.nodeName ) this.IFF.currentForm.nodeName = [];
this.IFF.currentForm.nodeName.push( this.IFF.reader.getString() );
break;
case 'IINN':
if ( ! this.IFF.currentForm.inputNodeName ) this.IFF.currentForm.inputNodeName = [];
this.IFF.currentForm.inputNodeName.push( this.IFF.reader.getString() );
break;
case 'IINM':
if ( ! this.IFF.currentForm.inputName ) this.IFF.currentForm.inputName = [];
this.IFF.currentForm.inputName.push( this.IFF.reader.getString() );
break;
case 'IONM':
if ( ! this.IFF.currentForm.inputOutputName ) this.IFF.currentForm.inputOutputName = [];
this.IFF.currentForm.inputOutputName.push( this.IFF.reader.getString() );
break;
case 'FNAM':
this.IFF.currentForm.fileName = this.IFF.reader.getString();
break;
case 'CHAN': // NOTE: ENVL Forms may also have CHAN chunk, however ENVL is currently ignored
if ( length === 4 ) this.IFF.currentForm.textureChannel = this.IFF.reader.getIDTag();
else this.IFF.reader.skip( length );
break;
// LWO2 Spec chunks: these are needed since the SURF FORMs are often in LWO2 format
case 'SMAN':
var maxSmoothingAngle = this.IFF.reader.getFloat32();
this.IFF.currentSurface.attributes.smooth = ( maxSmoothingAngle < 0 ) ? false : true;
break;
// LWO2: Basic Surface Parameters
case 'COLR':
this.IFF.currentSurface.attributes.Color = { value: this.IFF.reader.getFloat32Array( 3 ) };
this.IFF.reader.skip( 2 ); // VX: envelope
break;
case 'LUMI':
this.IFF.currentSurface.attributes.Luminosity = { value: this.IFF.reader.getFloat32() };
this.IFF.reader.skip( 2 );
break;
case 'SPEC':
this.IFF.currentSurface.attributes.Specular = { value: this.IFF.reader.getFloat32() };
this.IFF.reader.skip( 2 );
break;
case 'DIFF':
this.IFF.currentSurface.attributes.Diffuse = { value: this.IFF.reader.getFloat32() };
this.IFF.reader.skip( 2 );
break;
case 'REFL':
this.IFF.currentSurface.attributes.Reflection = { value: this.IFF.reader.getFloat32() };
this.IFF.reader.skip( 2 );
break;
case 'GLOS':
this.IFF.currentSurface.attributes.Glossiness = { value: this.IFF.reader.getFloat32() };
this.IFF.reader.skip( 2 );
break;
case 'TRAN':
this.IFF.currentSurface.attributes.opacity = this.IFF.reader.getFloat32();
this.IFF.reader.skip( 2 );
break;
case 'BUMP':
this.IFF.currentSurface.attributes.bumpStrength = this.IFF.reader.getFloat32();
this.IFF.reader.skip( 2 );
break;
case 'SIDE':
this.IFF.currentSurface.attributes.side = this.IFF.reader.getUint16();
break;
case 'RIMG':
this.IFF.currentSurface.attributes.reflectionMap = this.IFF.reader.getVariableLengthIndex();
break;
case 'RIND':
this.IFF.currentSurface.attributes.refractiveIndex = this.IFF.reader.getFloat32();
this.IFF.reader.skip( 2 );
break;
case 'TIMG':
this.IFF.currentSurface.attributes.refractionMap = this.IFF.reader.getVariableLengthIndex();
break;
case 'IMAP':
this.IFF.currentSurface.attributes.imageMapIndex = this.IFF.reader.getUint32();
break;
case 'IUVI': // uv channel name
this.IFF.currentNode.UVChannel = this.IFF.reader.getString( length );
break;
case 'IUTL': // widthWrappingMode: 0 = Reset, 1 = Repeat, 2 = Mirror, 3 = Edge
this.IFF.currentNode.widthWrappingMode = this.IFF.reader.getUint32();
break;
case 'IVTL': // heightWrappingMode
this.IFF.currentNode.heightWrappingMode = this.IFF.reader.getUint32();
break;
default:
this.IFF.parseUnknownCHUNK( blockID, length );
}
if ( blockID != 'FORM' ) {
this.IFF.debugger.node = 1;
this.IFF.debugger.nodeID = blockID;
this.IFF.debugger.log();
}
if ( this.IFF.reader.offset >= this.IFF.currentFormEnd ) {
this.IFF.currentForm = this.IFF.parentForm;
}
}
};
export { LWO3Parser };

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,46 @@
/**
* Development repository: https://github.com/kaisalmen/WWOBJLoader
*/
import { MTLLoader } from '../../../../jsm/loaders/MTLLoader.js';
const MtlObjBridge = {
/**
*
* @param processResult
* @param assetLoader
*/
link: function ( processResult, assetLoader ) {
if ( typeof assetLoader.addMaterials === 'function' ) {
assetLoader.addMaterials( this.addMaterialsFromMtlLoader( processResult ), true );
}
},
/**
* Returns the array instance of {@link MTLLoader.MaterialCreator}.
*
* @param Instance of {@link MTLLoader.MaterialCreator}
*/
addMaterialsFromMtlLoader: function ( materialCreator ) {
let newMaterials = {};
if ( materialCreator instanceof MTLLoader.MaterialCreator ) {
materialCreator.preload();
newMaterials = materialCreator.materials;
}
return newMaterials;
}
};
export { MtlObjBridge };

View File

@@ -0,0 +1,276 @@
/**
* Development repository: https://github.com/kaisalmen/WWOBJLoader
*/
import {
LineBasicMaterial,
MaterialLoader,
MeshStandardMaterial,
PointsMaterial
} from '../../../../../build/three.module.js';
const MaterialHandler = function () {
this.logging = {
enabled: false,
debug: false
};
this.callbacks = {
onLoadMaterials: null
};
this.materials = {};
};
MaterialHandler.prototype = {
constructor: MaterialHandler,
/**
* Enable or disable logging in general (except warn and error), plus enable or disable debug logging.
*
* @param {boolean} enabled True or false.
* @param {boolean} debug True or false.
*/
setLogging: function ( enabled, debug ) {
this.logging.enabled = enabled === true;
this.logging.debug = debug === true;
},
_setCallbacks: function ( onLoadMaterials ) {
if ( onLoadMaterials !== undefined && onLoadMaterials !== null && onLoadMaterials instanceof Function ) {
this.callbacks.onLoadMaterials = onLoadMaterials;
}
},
/**
* Creates default materials and adds them to the materials object.
*
* @param overrideExisting boolean Override existing material
*/
createDefaultMaterials: function ( overrideExisting ) {
const defaultMaterial = new MeshStandardMaterial( { color: 0xDCF1FF } );
defaultMaterial.name = 'defaultMaterial';
const defaultVertexColorMaterial = new MeshStandardMaterial( { color: 0xDCF1FF } );
defaultVertexColorMaterial.name = 'defaultVertexColorMaterial';
defaultVertexColorMaterial.vertexColors = true;
const defaultLineMaterial = new LineBasicMaterial();
defaultLineMaterial.name = 'defaultLineMaterial';
const defaultPointMaterial = new PointsMaterial( { size: 0.1 } );
defaultPointMaterial.name = 'defaultPointMaterial';
const runtimeMaterials = {};
runtimeMaterials[ defaultMaterial.name ] = defaultMaterial;
runtimeMaterials[ defaultVertexColorMaterial.name ] = defaultVertexColorMaterial;
runtimeMaterials[ defaultLineMaterial.name ] = defaultLineMaterial;
runtimeMaterials[ defaultPointMaterial.name ] = defaultPointMaterial;
this.addMaterials( runtimeMaterials, overrideExisting );
},
/**
* Updates the materials with contained material objects (sync) or from alteration instructions (async).
*
* @param {Object} materialPayload Material update instructions
* @returns {Object} Map of {@link Material}
*/
addPayloadMaterials: function ( materialPayload ) {
let material, materialName;
const materialCloneInstructions = materialPayload.materials.materialCloneInstructions;
let newMaterials = {};
if ( materialCloneInstructions !== undefined && materialCloneInstructions !== null ) {
let materialNameOrg = materialCloneInstructions.materialNameOrg;
materialNameOrg = ( materialNameOrg !== undefined && materialNameOrg !== null ) ? materialNameOrg : '';
const materialOrg = this.materials[ materialNameOrg ];
if ( materialOrg ) {
material = materialOrg.clone();
materialName = materialCloneInstructions.materialName;
material.name = materialName;
Object.assign( material, materialCloneInstructions.materialProperties );
this.materials[ materialName ] = material;
newMaterials[ materialName ] = material;
} else {
if ( this.logging.enabled ) {
console.info( 'Requested material "' + materialNameOrg + '" is not available!' );
}
}
}
let materials = materialPayload.materials.serializedMaterials;
if ( materials !== undefined && materials !== null && Object.keys( materials ).length > 0 ) {
const loader = new MaterialLoader();
let materialJson;
for ( materialName in materials ) {
materialJson = materials[ materialName ];
if ( materialJson !== undefined && materialJson !== null ) {
material = loader.parse( materialJson );
if ( this.logging.enabled ) {
console.info( 'De-serialized material with name "' + materialName + '" will be added.' );
}
this.materials[ materialName ] = material;
newMaterials[ materialName ] = material;
}
}
}
materials = materialPayload.materials.runtimeMaterials;
newMaterials = this.addMaterials( materials, true, newMaterials );
return newMaterials;
},
/**
* Set materials loaded by any supplier of an Array of {@link Material}.
*
* @param materials Object with named {@link Material}
* @param overrideExisting boolean Override existing material
* @param newMaterials [Object] with named {@link Material}
*/
addMaterials: function ( materials, overrideExisting, newMaterials ) {
if ( newMaterials === undefined || newMaterials === null ) {
newMaterials = {};
}
if ( materials !== undefined && materials !== null && Object.keys( materials ).length > 0 ) {
let material;
let existingMaterial;
let add;
for ( const materialName in materials ) {
material = materials[ materialName ];
add = overrideExisting === true;
if ( ! add ) {
existingMaterial = this.materials[ materialName ];
add = ( existingMaterial === null || existingMaterial === undefined );
}
if ( add ) {
this.materials[ materialName ] = material;
newMaterials[ materialName ] = material;
}
if ( this.logging.enabled && this.logging.debug ) {
console.info( 'Material with name "' + materialName + '" was added.' );
}
}
}
if ( this.callbacks.onLoadMaterials ) {
this.callbacks.onLoadMaterials( newMaterials );
}
return newMaterials;
},
/**
* Returns the mapping object of material name and corresponding material.
*
* @returns {Object} Map of {@link Material}
*/
getMaterials: function () {
return this.materials;
},
/**
*
* @param {String} materialName
* @returns {Material}
*/
getMaterial: function ( materialName ) {
return this.materials[ materialName ];
},
/**
* Returns the mapping object of material name and corresponding jsonified material.
*
* @returns {Object} Map of Materials in JSON representation
*/
getMaterialsJSON: function () {
const materialsJSON = {};
let material;
for ( const materialName in this.materials ) {
material = this.materials[ materialName ];
materialsJSON[ materialName ] = material.toJSON();
}
return materialsJSON;
},
/**
* Removes all materials
*/
clearMaterials: function () {
this.materials = {};
}
};
export { MaterialHandler };

View File

@@ -0,0 +1,313 @@
/**
* Development repository: https://github.com/kaisalmen/WWOBJLoader
*/
import {
BufferAttribute,
BufferGeometry,
LineSegments,
Mesh,
Points
} from '../../../../../build/three.module.js';
/**
*
* @param {MaterialHandler} materialHandler
* @constructor
*/
const MeshReceiver = function ( materialHandler ) {
this.logging = {
enabled: false,
debug: false
};
this.callbacks = {
onProgress: null,
onMeshAlter: null
};
this.materialHandler = materialHandler;
};
MeshReceiver.prototype = {
constructor: MeshReceiver,
/**
* Enable or disable logging in general (except warn and error), plus enable or disable debug logging.
*
* @param {boolean} enabled True or false.
* @param {boolean} debug True or false.
*/
setLogging: function ( enabled, debug ) {
this.logging.enabled = enabled === true;
this.logging.debug = debug === true;
},
/**
*
* @param {Function} onProgress
* @param {Function} onMeshAlter
* @private
*/
_setCallbacks: function ( onProgress, onMeshAlter ) {
if ( onProgress !== null && onProgress !== undefined && onProgress instanceof Function ) {
this.callbacks.onProgress = onProgress;
}
if ( onMeshAlter !== null && onMeshAlter !== undefined && onMeshAlter instanceof Function ) {
this.callbacks.onMeshAlter = onMeshAlter;
}
},
/**
* Builds one or multiple meshes from the data described in the payload (buffers, params, material info).
*
* @param {Object} meshPayload Raw mesh description (buffers, params, materials) used to build one to many meshes.
* @returns {Mesh[]} mesh Array of {@link Mesh}
*/
buildMeshes: function ( meshPayload ) {
const meshName = meshPayload.params.meshName;
const buffers = meshPayload.buffers;
const bufferGeometry = new BufferGeometry();
if ( buffers.vertices !== undefined && buffers.vertices !== null ) {
bufferGeometry.setAttribute( 'position', new BufferAttribute( new Float32Array( buffers.vertices ), 3 ) );
}
if ( buffers.indices !== undefined && buffers.indices !== null ) {
bufferGeometry.setIndex( new BufferAttribute( new Uint32Array( buffers.indices ), 1 ) );
}
if ( buffers.colors !== undefined && buffers.colors !== null ) {
bufferGeometry.setAttribute( 'color', new BufferAttribute( new Float32Array( buffers.colors ), 3 ) );
}
if ( buffers.normals !== undefined && buffers.normals !== null ) {
bufferGeometry.setAttribute( 'normal', new BufferAttribute( new Float32Array( buffers.normals ), 3 ) );
} else {
bufferGeometry.computeVertexNormals();
}
if ( buffers.uvs !== undefined && buffers.uvs !== null ) {
bufferGeometry.setAttribute( 'uv', new BufferAttribute( new Float32Array( buffers.uvs ), 2 ) );
}
if ( buffers.skinIndex !== undefined && buffers.skinIndex !== null ) {
bufferGeometry.setAttribute( 'skinIndex', new BufferAttribute( new Uint16Array( buffers.skinIndex ), 4 ) );
}
if ( buffers.skinWeight !== undefined && buffers.skinWeight !== null ) {
bufferGeometry.setAttribute( 'skinWeight', new BufferAttribute( new Float32Array( buffers.skinWeight ), 4 ) );
}
let material, materialName, key;
const materialNames = meshPayload.materials.materialNames;
const createMultiMaterial = meshPayload.materials.multiMaterial;
const multiMaterials = [];
for ( key in materialNames ) {
materialName = materialNames[ key ];
material = this.materialHandler.getMaterial( materialName );
if ( createMultiMaterial ) multiMaterials.push( material );
}
if ( createMultiMaterial ) {
material = multiMaterials;
const materialGroups = meshPayload.materials.materialGroups;
let materialGroup;
for ( key in materialGroups ) {
materialGroup = materialGroups[ key ];
bufferGeometry.addGroup( materialGroup.start, materialGroup.count, materialGroup.index );
}
}
const meshes = [];
let mesh;
let callbackOnMeshAlterResult;
let useOrgMesh = true;
const geometryType = meshPayload.geometryType === null ? 0 : meshPayload.geometryType;
if ( this.callbacks.onMeshAlter ) {
callbackOnMeshAlterResult = this.callbacks.onMeshAlter(
{
detail: {
meshName: meshName,
bufferGeometry: bufferGeometry,
material: material,
geometryType: geometryType
}
}
);
}
// here LoadedMeshUserOverride is required to be provided by the callback used to alter the results
if ( callbackOnMeshAlterResult ) {
if ( callbackOnMeshAlterResult.isDisregardMesh() ) {
useOrgMesh = false;
} else if ( callbackOnMeshAlterResult.providesAlteredMeshes() ) {
for ( const i in callbackOnMeshAlterResult.meshes ) {
meshes.push( callbackOnMeshAlterResult.meshes[ i ] );
}
useOrgMesh = false;
}
}
if ( useOrgMesh ) {
if ( meshPayload.computeBoundingSphere ) bufferGeometry.computeBoundingSphere();
if ( geometryType === 0 ) {
mesh = new Mesh( bufferGeometry, material );
} else if ( geometryType === 1 ) {
mesh = new LineSegments( bufferGeometry, material );
} else {
mesh = new Points( bufferGeometry, material );
}
mesh.name = meshName;
meshes.push( mesh );
}
let progressMessage = meshPayload.params.meshName;
if ( meshes.length > 0 ) {
const meshNames = [];
for ( const i in meshes ) {
mesh = meshes[ i ];
meshNames[ i ] = mesh.name;
}
progressMessage += ': Adding mesh(es) (' + meshNames.length + ': ' + meshNames + ') from input mesh: ' + meshName;
progressMessage += ' (' + ( meshPayload.progress.numericalValue * 100 ).toFixed( 2 ) + '%)';
} else {
progressMessage += ': Not adding mesh: ' + meshName;
progressMessage += ' (' + ( meshPayload.progress.numericalValue * 100 ).toFixed( 2 ) + '%)';
}
if ( this.callbacks.onProgress ) {
this.callbacks.onProgress( 'progress', progressMessage, meshPayload.progress.numericalValue );
}
return meshes;
}
};
/**
* Object to return by callback onMeshAlter. Used to disregard a certain mesh or to return one to many meshes.
* @class
*
* @param {boolean} disregardMesh=false Tell implementation to completely disregard this mesh
* @param {boolean} disregardMesh=false Tell implementation that mesh(es) have been altered or added
*/
const LoadedMeshUserOverride = function ( disregardMesh, alteredMesh ) {
this.disregardMesh = disregardMesh === true;
this.alteredMesh = alteredMesh === true;
this.meshes = [];
};
LoadedMeshUserOverride.prototype = {
constructor: LoadedMeshUserOverride,
/**
* Add a mesh created within callback.
*
* @param {Mesh} mesh
*/
addMesh: function ( mesh ) {
this.meshes.push( mesh );
this.alteredMesh = true;
},
/**
* Answers if mesh shall be disregarded completely.
*
* @returns {boolean}
*/
isDisregardMesh: function () {
return this.disregardMesh;
},
/**
* Answers if new mesh(es) were created.
*
* @returns {boolean}
*/
providesAlteredMeshes: function () {
return this.alteredMesh;
}
};
export {
MeshReceiver,
LoadedMeshUserOverride
};

View File

@@ -0,0 +1,264 @@
/**
* Development repository: https://github.com/kaisalmen/WWOBJLoader
*/
const CodeSerializer = {
/**
* Serialize an object with specific prototype definition.
*
* @param {Object} targetPrototype The object that should be serialized
* @param {Object} targetPrototypeInstance An instance of the oriobject that should be serialized
* @param {String} [basePrototypeName] Name of the prototype
* @param {Object} [overrideFunctions} Array of {@Link CodeSerializationInstruction} allows to replace or remove function with provided content
*
* @returns {String}
*/
serializeClass: function ( targetPrototype, targetPrototypeInstance, basePrototypeName, overrideFunctions ) {
let objectPart, constructorString, i, funcInstructions, funcTemp;
const fullObjectName = targetPrototypeInstance.constructor.name;
const prototypeFunctions = [];
const objectProperties = [];
const objectFunctions = [];
const isExtended = ( basePrototypeName !== null && basePrototypeName !== undefined );
if ( ! Array.isArray( overrideFunctions ) ) overrideFunctions = [];
for ( const name in targetPrototype.prototype ) {
objectPart = targetPrototype.prototype[ name ];
funcInstructions = new CodeSerializationInstruction( name, fullObjectName + '.prototype.' + name );
funcInstructions.setCode( objectPart.toString() );
if ( name === 'constructor' ) {
if ( ! funcInstructions.isRemoveCode() ) {
constructorString = fullObjectName + ' = ' + funcInstructions.getCode() + ';\n\n';
}
} else if ( typeof objectPart === 'function' ) {
funcTemp = overrideFunctions[ name ];
if ( funcTemp instanceof CodeSerializationInstruction && funcTemp.getName() === funcInstructions.getName() ) {
funcInstructions = funcTemp;
}
if ( ! funcInstructions.isRemoveCode() ) {
if ( isExtended ) {
prototypeFunctions.push( funcInstructions.getFullName() + ' = ' + funcInstructions.getCode() + ';\n\n' );
} else {
prototypeFunctions.push( '\t' + funcInstructions.getName() + ': ' + funcInstructions.getCode() + ',\n\n' );
}
}
}
}
for ( const name in targetPrototype ) {
objectPart = targetPrototype[ name ];
funcInstructions = new CodeSerializationInstruction( name, fullObjectName + '.' + name );
if ( typeof objectPart === 'function' ) {
funcTemp = overrideFunctions[ name ];
if ( funcTemp instanceof CodeSerializationInstruction && funcTemp.getName() === funcInstructions.getName() ) {
funcInstructions = funcTemp;
} else {
funcInstructions.setCode( objectPart.toString() );
}
if ( ! funcInstructions.isRemoveCode() ) {
objectFunctions.push( funcInstructions.getFullName() + ' = ' + funcInstructions.getCode() + ';\n\n' );
}
} else {
if ( typeof ( objectPart ) === 'string' || objectPart instanceof String ) {
funcInstructions.setCode( '\"' + objectPart.toString() + '\"' );
} else if ( typeof objectPart === 'object' ) {
console.log( 'Omitting object "' + funcInstructions.getName() + '" and replace it with empty object.' );
funcInstructions.setCode( '{}' );
} else {
funcInstructions.setCode( objectPart );
}
if ( ! funcInstructions.isRemoveCode() ) {
objectProperties.push( funcInstructions.getFullName() + ' = ' + funcInstructions.getCode() + ';\n' );
}
}
}
let objectString = constructorString + '\n\n';
if ( isExtended ) {
objectString += fullObjectName + '.prototype = Object.create( ' + basePrototypeName + '.prototype );\n';
}
objectString += fullObjectName + '.prototype.constructor = ' + fullObjectName + ';\n';
objectString += '\n\n';
for ( i = 0; i < objectProperties.length; i ++ ) {
objectString += objectProperties[ i ];
}
objectString += '\n\n';
for ( i = 0; i < objectFunctions.length; i ++ ) {
objectString += objectFunctions[ i ];
}
objectString += '\n\n';
if ( isExtended ) {
for ( i = 0; i < prototypeFunctions.length; i ++ ) {
objectString += prototypeFunctions[ i ];
}
} else {
objectString += fullObjectName + '.prototype = {\n\n';
for ( i = 0; i < prototypeFunctions.length; i ++ ) {
objectString += prototypeFunctions[ i ];
}
objectString += '\n};';
}
objectString += '\n\n';
return objectString;
},
};
/**
* Allows to define instructions to override or remove
* @param {String} name Usually the name of a function
* @param {String} fullName The name plus full object description
* @constructor
*/
const CodeSerializationInstruction = function ( name, fullName ) {
this.name = name;
this.fullName = fullName;
this.code = null;
this.removeCode = false;
};
CodeSerializationInstruction.prototype = {
constructor: CodeSerializationInstruction,
/**
* Returns the name of the function
* @return {String}
*/
getName: function () {
return this.name;
},
/**
* Returns the full name of the function
* @return {String}
*/
getFullName: function () {
return this.fullName;
},
/**
* Set the string containing the serialized function
* @param {String} code
* @return {CodeSerializationInstruction}
*/
setCode: function ( code ) {
this.code = code;
return this;
},
/**
* Returns the serialized function code
* @return {String}
*/
getCode: function () {
return this.code;
},
/**
* Set if function should be removed
* @param {boolean} removeCode
* @return {CodeSerializationInstruction}
*/
setRemoveCode: function ( removeCode ) {
this.removeCode = removeCode;
return this;
},
/**
* If function should be completely removed
* @return {boolean}
*/
isRemoveCode: function () {
return this.removeCode;
}
};
export {
CodeSerializer,
CodeSerializationInstruction
};

View File

@@ -0,0 +1,584 @@
/**
* Development repository: https://github.com/kaisalmen/WWOBJLoader
*/
/**
* These instructions are used by {WorkerExecutionSupport} to build code for the web worker or to assign code
*
* @param {boolean} supportsStandardWorker
* @param {boolean} supportsJsmWorker
* @constructor
*/
const CodeBuilderInstructions = function ( supportsStandardWorker, supportsJsmWorker, preferJsmWorker ) {
this.supportsStandardWorker = supportsStandardWorker;
this.supportsJsmWorker = supportsJsmWorker;
this.preferJsmWorker = preferJsmWorker;
this.startCode = '';
this.codeFragments = [];
this.importStatements = [];
this.jsmWorkerUrl = null;
this.defaultGeometryType = 0;
};
CodeBuilderInstructions.prototype = {
constructor: CodeBuilderInstructions,
isSupportsStandardWorker: function () {
return this.supportsStandardWorker;
},
isSupportsJsmWorker: function () {
return this.supportsJsmWorker;
},
isPreferJsmWorker: function () {
return this.preferJsmWorker;
},
/**
* Set the full path to the module that contains the worker code.
*
* @param {String} jsmWorkerUrl
*/
setJsmWorkerUrl: function ( jsmWorkerUrl ) {
if ( jsmWorkerUrl !== undefined && jsmWorkerUrl !== null ) {
this.jsmWorkerUrl = jsmWorkerUrl;
}
},
/**
* Add code that is contained in addition to fragments and libraries
* @param {String} startCode
*/
addStartCode: function ( startCode ) {
this.startCode = startCode;
},
/**
* Add code fragment that is included in the provided order
* @param {String} code
*/
addCodeFragment: function ( code ) {
this.codeFragments.push( code );
},
/**
* Add full path to a library that is contained at the start of the worker via "importScripts"
* @param {String} libraryPath
*/
addLibraryImport: function ( libraryPath ) {
const libraryUrl = new URL( libraryPath, window.location.href ).href;
const code = 'importScripts( "' + libraryUrl + '" );';
this.importStatements.push( code );
},
getImportStatements: function () {
return this.importStatements;
},
getCodeFragments: function () {
return this.codeFragments;
},
getStartCode: function () {
return this.startCode;
}
};
/**
* This class provides means to transform existing parser code into a web worker. It defines a simple communication protocol
* which allows to configure the worker and receive raw mesh data during execution.
* @class
*/
const WorkerExecutionSupport = function () {
// check worker support first
if ( window.Worker === undefined ) throw 'This browser does not support web workers!';
if ( window.Blob === undefined ) throw 'This browser does not support Blob!';
if ( typeof window.URL.createObjectURL !== 'function' ) throw 'This browser does not support Object creation from URL!';
this._reset();
};
WorkerExecutionSupport.WORKER_SUPPORT_VERSION = '3.2.0';
console.info( 'Using WorkerSupport version: ' + WorkerExecutionSupport.WORKER_SUPPORT_VERSION );
WorkerExecutionSupport.prototype = {
constructor: WorkerExecutionSupport,
_reset: function () {
this.logging = {
enabled: false,
debug: false
};
const scope = this;
const scopeTerminate = function ( ) {
scope._terminate();
};
this.worker = {
native: null,
jsmWorker: false,
logging: true,
workerRunner: {
name: 'WorkerRunner',
usesMeshDisassembler: false,
defaultGeometryType: 0
},
terminateWorkerOnLoad: true,
forceWorkerDataCopy: false,
started: false,
queuedMessage: null,
callbacks: {
onAssetAvailable: null,
onLoad: null,
terminate: scopeTerminate
}
};
},
/**
* Enable or disable logging in general (except warn and error), plus enable or disable debug logging.
*
* @param {boolean} enabled True or false.
* @param {boolean} debug True or false.
*/
setLogging: function ( enabled, debug ) {
this.logging.enabled = enabled === true;
this.logging.debug = debug === true;
this.worker.logging = enabled === true;
return this;
},
/**
* Forces all ArrayBuffers to be transferred to worker to be copied.
*
* @param {boolean} forceWorkerDataCopy True or false.
*/
setForceWorkerDataCopy: function ( forceWorkerDataCopy ) {
this.worker.forceWorkerDataCopy = forceWorkerDataCopy === true;
return this;
},
/**
* Request termination of worker once parser is finished.
*
* @param {boolean} terminateWorkerOnLoad True or false.
*/
setTerminateWorkerOnLoad: function ( terminateWorkerOnLoad ) {
this.worker.terminateWorkerOnLoad = terminateWorkerOnLoad === true;
if ( this.worker.terminateWorkerOnLoad && this.isWorkerLoaded( this.worker.jsmWorker ) &&
this.worker.queuedMessage === null && this.worker.started ) {
if ( this.logging.enabled ) {
console.info( 'Worker is terminated immediately as it is not running!' );
}
this._terminate();
}
return this;
},
/**
* Update all callbacks.
*
* @param {Function} onAssetAvailable The function for processing the data, e.g. {@link MeshReceiver}.
* @param {Function} [onLoad] The function that is called when parsing is complete.
*/
updateCallbacks: function ( onAssetAvailable, onLoad ) {
if ( onAssetAvailable !== undefined && onAssetAvailable !== null ) {
this.worker.callbacks.onAssetAvailable = onAssetAvailable;
}
if ( onLoad !== undefined && onLoad !== null ) {
this.worker.callbacks.onLoad = onLoad;
}
this._verifyCallbacks();
},
_verifyCallbacks: function () {
if ( this.worker.callbacks.onAssetAvailable === undefined || this.worker.callbacks.onAssetAvailable === null ) {
throw 'Unable to run as no "onAssetAvailable" callback is set.';
}
},
/**
* Builds the worker code according the provided Instructions.
* If jsm worker code shall be built, then function may fall back to standard if lag is set
*
* @param {CodeBuilderInstructions} codeBuilderInstructions
*/
buildWorker: function ( codeBuilderInstructions ) {
let jsmSuccess = false;
if ( codeBuilderInstructions.isSupportsJsmWorker() && codeBuilderInstructions.isPreferJsmWorker() ) {
jsmSuccess = this._buildWorkerJsm( codeBuilderInstructions );
}
if ( ! jsmSuccess && codeBuilderInstructions.isSupportsStandardWorker() ) {
this._buildWorkerStandard( codeBuilderInstructions );
}
},
/**
*
* @param {CodeBuilderInstructions} codeBuilderInstructions
* @return {boolean} Whether loading of jsm worker was successful
* @private
*/
_buildWorkerJsm: function ( codeBuilderInstructions ) {
let jsmSuccess = true;
const timeLabel = 'buildWorkerJsm';
const workerAvailable = this._buildWorkerCheckPreconditions( true, timeLabel );
if ( ! workerAvailable ) {
try {
const worker = new Worker( codeBuilderInstructions.jsmWorkerUrl.href, { type: 'module' } );
this._configureWorkerCommunication( worker, true, codeBuilderInstructions.defaultGeometryType, timeLabel );
} catch ( e ) {
jsmSuccess = false;
// Chrome throws this exception, but Firefox currently does not complain, but can't execute the worker afterwards
if ( e instanceof TypeError || e instanceof SyntaxError ) {
console.error( 'Modules are not supported in workers.' );
}
}
}
return jsmSuccess;
},
/**
* Validate the status of worker code and the derived worker and specify functions that should be build when new raw mesh data becomes available and when the parser is finished.
*
* @param {CodeBuilderIns} buildWorkerCode The function that is invoked to create the worker code of the parser.
*/
/**
*
* @param {CodeBuilderInstructions} codeBuilderInstructions
* @private
*/
_buildWorkerStandard: function ( codeBuilderInstructions ) {
const timeLabel = 'buildWorkerStandard';
const workerAvailable = this._buildWorkerCheckPreconditions( false, timeLabel );
if ( ! workerAvailable ) {
let concatenateCode = '';
codeBuilderInstructions.getImportStatements().forEach( function ( element ) {
concatenateCode += element + '\n';
} );
concatenateCode += '\n';
codeBuilderInstructions.getCodeFragments().forEach( function ( element ) {
concatenateCode += element + '\n';
} );
concatenateCode += '\n';
concatenateCode += codeBuilderInstructions.getStartCode();
const blob = new Blob( [ concatenateCode ], { type: 'application/javascript' } );
const worker = new Worker( window.URL.createObjectURL( blob ) );
this._configureWorkerCommunication( worker, false, codeBuilderInstructions.defaultGeometryType, timeLabel );
}
},
_buildWorkerCheckPreconditions: function ( requireJsmWorker, timeLabel ) {
let workerAvailable = false;
if ( this.isWorkerLoaded( requireJsmWorker ) ) {
workerAvailable = true;
} else {
if ( this.logging.enabled ) {
console.info( 'WorkerExecutionSupport: Building ' + ( requireJsmWorker ? 'jsm' : 'standard' ) + ' worker code...' );
console.time( timeLabel );
}
}
return workerAvailable;
},
_configureWorkerCommunication: function ( worker, haveJsmWorker, defaultGeometryType, timeLabel ) {
this.worker.native = worker;
this.worker.jsmWorker = haveJsmWorker;
const scope = this;
const scopedReceiveWorkerMessage = function ( event ) {
scope._receiveWorkerMessage( event );
};
this.worker.native.onmessage = scopedReceiveWorkerMessage;
this.worker.native.onerror = scopedReceiveWorkerMessage;
if ( defaultGeometryType !== undefined && defaultGeometryType !== null ) {
this.worker.workerRunner.defaultGeometryType = defaultGeometryType;
}
if ( this.logging.enabled ) {
console.timeEnd( timeLabel );
}
},
/**
* Returns if Worker code is available and complies with expectation.
* @param {boolean} requireJsmWorker
* @return {boolean|*}
*/
isWorkerLoaded: function ( requireJsmWorker ) {
return this.worker.native !== null &&
( ( requireJsmWorker && this.worker.jsmWorker ) || ( ! requireJsmWorker && ! this.worker.jsmWorker ) );
},
/**
* Executed in worker scope
*/
_receiveWorkerMessage: function ( event ) {
// fast-fail in case of error
if ( event.type === 'error' ) {
console.error( event );
return;
}
const payload = event.data;
const workerRunnerName = this.worker.workerRunner.name;
switch ( payload.cmd ) {
case 'assetAvailable':
this.worker.callbacks.onAssetAvailable( payload );
break;
case 'completeOverall':
this.worker.queuedMessage = null;
this.worker.started = false;
if ( this.worker.callbacks.onLoad !== null ) {
this.worker.callbacks.onLoad( payload.msg );
}
if ( this.worker.terminateWorkerOnLoad ) {
if ( this.worker.logging.enabled ) {
console.info( 'WorkerSupport [' + workerRunnerName + ']: Run is complete. Terminating application on request!' );
}
this.worker.callbacks.terminate();
}
break;
case 'error':
console.error( 'WorkerSupport [' + workerRunnerName + ']: Reported error: ' + payload.msg );
this.worker.queuedMessage = null;
this.worker.started = false;
if ( this.worker.callbacks.onLoad !== null ) {
this.worker.callbacks.onLoad( payload.msg );
}
if ( this.worker.terminateWorkerOnLoad ) {
if ( this.worker.logging.enabled ) {
console.info( 'WorkerSupport [' + workerRunnerName + ']: Run reported error. Terminating application on request!' );
}
this.worker.callbacks.terminate();
}
break;
default:
console.error( 'WorkerSupport [' + workerRunnerName + ']: Received unknown command: ' + payload.cmd );
break;
}
},
/**
* Runs the parser with the provided configuration.
*
* @param {Object} payload Raw mesh description (buffers, params, materials) used to build one to many meshes.
*/
executeParallel: function ( payload, transferables ) {
payload.cmd = 'parse';
payload.usesMeshDisassembler = this.worker.workerRunner.usesMeshDisassembler;
payload.defaultGeometryType = this.worker.workerRunner.defaultGeometryType;
if ( ! this._verifyWorkerIsAvailable( payload, transferables ) ) return;
this._postMessage();
},
_verifyWorkerIsAvailable: function ( payload, transferables ) {
this._verifyCallbacks();
let ready = true;
if ( this.worker.queuedMessage !== null ) {
console.warn( 'Already processing message. Rejecting new run instruction' );
ready = false;
} else {
this.worker.queuedMessage = {
payload: payload,
transferables: ( transferables === undefined || transferables === null ) ? [] : transferables
};
this.worker.started = true;
}
return ready;
},
_postMessage: function () {
if ( this.worker.queuedMessage !== null ) {
if ( this.worker.queuedMessage.payload.data.input instanceof ArrayBuffer ) {
let transferables = [];
if ( this.worker.forceWorkerDataCopy ) {
transferables.push( this.worker.queuedMessage.payload.data.input.slice( 0 ) );
} else {
transferables.push( this.worker.queuedMessage.payload.data.input );
}
if ( this.worker.queuedMessage.transferables.length > 0 ) {
transferables = transferables.concat( this.worker.queuedMessage.transferables );
}
this.worker.native.postMessage( this.worker.queuedMessage.payload, transferables );
} else {
this.worker.native.postMessage( this.worker.queuedMessage.payload );
}
}
},
_terminate: function () {
this.worker.native.terminate();
this._reset();
}
};
export {
CodeBuilderInstructions,
WorkerExecutionSupport
};

View File

@@ -0,0 +1,12 @@
/**
* Development repository: https://github.com/kaisalmen/WWOBJLoader
*/
import { OBJLoader2Parser } from '../../OBJLoader2Parser.js';
import {
WorkerRunner,
DefaultWorkerPayloadHandler
} from './WorkerRunner.js';
new WorkerRunner( new DefaultWorkerPayloadHandler( new OBJLoader2Parser() ) );

View File

@@ -0,0 +1,165 @@
/**
* Development repository: https://github.com/kaisalmen/WWOBJLoader
*/
const ObjectManipulator = function () {
};
ObjectManipulator.prototype = {
constructor: ObjectManipulator,
/**
* Applies values from parameter object via set functions or via direct assignment.
*
* @param {Object} objToAlter The objToAlter instance
* @param {Object} params The parameter object
* @param {boolean} forceCreation Force the creation of a property
*/
applyProperties: function ( objToAlter, params, forceCreation ) {
// fast-fail
if ( objToAlter === undefined || objToAlter === null || params === undefined || params === null ) return;
let property, funcName, values;
for ( property in params ) {
funcName = 'set' + property.substring( 0, 1 ).toLocaleUpperCase() + property.substring( 1 );
values = params[ property ];
if ( typeof objToAlter[ funcName ] === 'function' ) {
objToAlter[ funcName ]( values );
} else if ( objToAlter.hasOwnProperty( property ) || forceCreation ) {
objToAlter[ property ] = values;
}
}
}
};
const DefaultWorkerPayloadHandler = function ( parser ) {
this.parser = parser;
this.logging = {
enabled: false,
debug: false
};
};
DefaultWorkerPayloadHandler.prototype = {
constructor: DefaultWorkerPayloadHandler,
handlePayload: function ( payload ) {
if ( payload.logging ) {
this.logging.enabled = payload.logging.enabled === true;
this.logging.debug = payload.logging.debug === true;
}
if ( payload.cmd === 'parse' ) {
const scope = this;
const callbacks = {
callbackOnAssetAvailable: function ( payload ) {
self.postMessage( payload );
},
callbackOnProgress: function ( text ) {
if ( scope.logging.enabled && scope.logging.debug ) console.debug( 'WorkerRunner: progress: ' + text );
}
};
const parser = this.parser;
if ( typeof parser[ 'setLogging' ] === 'function' ) {
parser.setLogging( this.logging.enabled, this.logging.debug );
}
const objectManipulator = new ObjectManipulator();
objectManipulator.applyProperties( parser, payload.params, false );
objectManipulator.applyProperties( parser, callbacks, false );
const arraybuffer = payload.data.input;
let executeFunctionName = 'execute';
if ( typeof parser.getParseFunctionName === 'function' ) executeFunctionName = parser.getParseFunctionName();
if ( payload.usesMeshDisassembler ) {
// TODO: Allow to plug and use generic MeshDisassembler
} else {
parser[ executeFunctionName ]( arraybuffer, payload.data.options );
}
if ( this.logging.enabled ) console.log( 'WorkerRunner: Run complete!' );
self.postMessage( {
cmd: 'completeOverall',
msg: 'WorkerRunner completed run.'
} );
} else {
console.error( 'WorkerRunner: Received unknown command: ' + payload.cmd );
}
}
};
/**
* Default implementation of the WorkerRunner responsible for creation and configuration of the parser within the worker.
* @constructor
*/
const WorkerRunner = function ( payloadHandler ) {
this.payloadHandler = payloadHandler;
const scope = this;
const scopedRunner = function ( event ) {
scope.processMessage( event.data );
};
self.addEventListener( 'message', scopedRunner, false );
};
WorkerRunner.prototype = {
constructor: WorkerRunner,
/**
* Configures the Parser implementation according the supplied configuration object.
*
* @param {Object} payload Raw mesh description (buffers, params, materials) used to build one to many meshes.
*/
processMessage: function ( payload ) {
this.payloadHandler.handlePayload( payload );
}
};
export {
WorkerRunner,
DefaultWorkerPayloadHandler,
ObjectManipulator
};

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

Some files were not shown because too many files have changed in this diff Show More