add Potree (version 1.8)
This commit is contained in:
1411
PointCloudWeb.Web/public/Potree/libs/three.js/loaders/3DMLoader.js
Normal file
1411
PointCloudWeb.Web/public/Potree/libs/three.js/loaders/3DMLoader.js
Normal file
File diff suppressed because it is too large
Load Diff
1472
PointCloudWeb.Web/public/Potree/libs/three.js/loaders/3MFLoader.js
Normal file
1472
PointCloudWeb.Web/public/Potree/libs/three.js/loaders/3MFLoader.js
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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
@@ -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 };
|
||||
@@ -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
@@ -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 };
|
||||
@@ -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 };
|
||||
2418
PointCloudWeb.Web/public/Potree/libs/three.js/loaders/EXRLoader.js
Normal file
2418
PointCloudWeb.Web/public/Potree/libs/three.js/loaders/EXRLoader.js
Normal file
File diff suppressed because it is too large
Load Diff
4205
PointCloudWeb.Web/public/Potree/libs/three.js/loaders/FBXLoader.js
Normal file
4205
PointCloudWeb.Web/public/Potree/libs/three.js/loaders/FBXLoader.js
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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 };
|
||||
3939
PointCloudWeb.Web/public/Potree/libs/three.js/loaders/GLTFLoader.js
Normal file
3939
PointCloudWeb.Web/public/Potree/libs/three.js/loaders/GLTFLoader.js
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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 };
|
||||
@@ -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 };
|
||||
@@ -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 };
|
||||
@@ -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 };
|
||||
1972
PointCloudWeb.Web/public/Potree/libs/three.js/loaders/LDrawLoader.js
Normal file
1972
PointCloudWeb.Web/public/Potree/libs/three.js/loaders/LDrawLoader.js
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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,
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
1079
PointCloudWeb.Web/public/Potree/libs/three.js/loaders/LWOLoader.js
Normal file
1079
PointCloudWeb.Web/public/Potree/libs/three.js/loaders/LWOLoader.js
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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 };
|
||||
@@ -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 };
|
||||
@@ -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 };
|
||||
2021
PointCloudWeb.Web/public/Potree/libs/three.js/loaders/MMDLoader.js
Normal file
2021
PointCloudWeb.Web/public/Potree/libs/three.js/loaders/MMDLoader.js
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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 };
|
||||
@@ -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 };
|
||||
@@ -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 };
|
||||
@@ -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 };
|
||||
@@ -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 };
|
||||
@@ -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 };
|
||||
@@ -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 };
|
||||
@@ -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 };
|
||||
@@ -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 };
|
||||
@@ -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 };
|
||||
@@ -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 };
|
||||
@@ -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 };
|
||||
@@ -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 };
|
||||
2239
PointCloudWeb.Web/public/Potree/libs/three.js/loaders/SVGLoader.js
Normal file
2239
PointCloudWeb.Web/public/Potree/libs/three.js/loaders/SVGLoader.js
Normal file
File diff suppressed because it is too large
Load Diff
1166
PointCloudWeb.Web/public/Potree/libs/three.js/loaders/TDSLoader.js
Normal file
1166
PointCloudWeb.Web/public/Potree/libs/three.js/loaders/TDSLoader.js
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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 };
|
||||
@@ -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 };
|
||||
@@ -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 };
|
||||
@@ -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 };
|
||||
3494
PointCloudWeb.Web/public/Potree/libs/three.js/loaders/VRMLLoader.js
Normal file
3494
PointCloudWeb.Web/public/Potree/libs/three.js/loaders/VRMLLoader.js
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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 };
|
||||
1185
PointCloudWeb.Web/public/Potree/libs/three.js/loaders/VTKLoader.js
Normal file
1185
PointCloudWeb.Web/public/Potree/libs/three.js/loaders/VTKLoader.js
Normal file
File diff suppressed because it is too large
Load Diff
1759
PointCloudWeb.Web/public/Potree/libs/three.js/loaders/XLoader.js
Normal file
1759
PointCloudWeb.Web/public/Potree/libs/three.js/loaders/XLoader.js
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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
@@ -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 };
|
||||
@@ -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
@@ -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 };
|
||||
@@ -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 };
|
||||
@@ -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
|
||||
};
|
||||
@@ -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
|
||||
};
|
||||
@@ -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
|
||||
};
|
||||
@@ -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() ) );
|
||||
@@ -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
|
||||
};
|
||||
Reference in New Issue
Block a user