大屏添加cesium
This commit is contained in:
1764
public/sdk/three/jsm/loaders/3DMLoader.js
Normal file
1764
public/sdk/three/jsm/loaders/3DMLoader.js
Normal file
File diff suppressed because it is too large
Load Diff
1480
public/sdk/three/jsm/loaders/3MFLoader.js
Normal file
1480
public/sdk/three/jsm/loaders/3MFLoader.js
Normal file
File diff suppressed because it is too large
Load Diff
521
public/sdk/three/jsm/loaders/AMFLoader.js
Normal file
521
public/sdk/three/jsm/loaders/AMFLoader.js
Normal file
@ -0,0 +1,521 @@
|
||||
import {
|
||||
BufferGeometry,
|
||||
Color,
|
||||
FileLoader,
|
||||
Float32BufferAttribute,
|
||||
Group,
|
||||
Loader,
|
||||
Mesh,
|
||||
MeshPhongMaterial
|
||||
} from 'three';
|
||||
import * as fflate from '../libs/fflate.module.js';
|
||||
|
||||
/**
|
||||
* Description: Early release of an AMF Loader following the pattern of the
|
||||
* example loaders in the three.js project.
|
||||
*
|
||||
* Usage:
|
||||
* const loader = new AMFLoader();
|
||||
* loader.load('/path/to/project.amf', function(objecttree) {
|
||||
* scene.add(objecttree);
|
||||
* });
|
||||
*
|
||||
* Materials now supported, material colors supported
|
||||
* Zip support, requires fflate
|
||||
* No constellation support (yet)!
|
||||
*
|
||||
*/
|
||||
|
||||
class AMFLoader extends Loader {
|
||||
|
||||
constructor( manager ) {
|
||||
|
||||
super( manager );
|
||||
|
||||
}
|
||||
|
||||
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.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( data ) {
|
||||
|
||||
function loadDocument( data ) {
|
||||
|
||||
let view = new DataView( data );
|
||||
const magic = String.fromCharCode( view.getUint8( 0 ), view.getUint8( 1 ) );
|
||||
|
||||
if ( magic === 'PK' ) {
|
||||
|
||||
let zip = null;
|
||||
let file = null;
|
||||
|
||||
console.log( 'THREE.AMFLoader: Loading Zip' );
|
||||
|
||||
try {
|
||||
|
||||
zip = fflate.unzipSync( new Uint8Array( data ) );
|
||||
|
||||
} catch ( e ) {
|
||||
|
||||
if ( e instanceof ReferenceError ) {
|
||||
|
||||
console.log( 'THREE.AMFLoader: fflate missing and file is compressed.' );
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
for ( file in zip ) {
|
||||
|
||||
if ( file.toLowerCase().slice( - 4 ) === '.amf' ) {
|
||||
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
console.log( 'THREE.AMFLoader: Trying to load file asset: ' + file );
|
||||
view = new DataView( zip[ file ].buffer );
|
||||
|
||||
}
|
||||
|
||||
const fileText = new TextDecoder().decode( view );
|
||||
const 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 ) {
|
||||
|
||||
let scale = 1.0;
|
||||
let unit = 'millimeter';
|
||||
|
||||
if ( node.documentElement.attributes.unit !== undefined ) {
|
||||
|
||||
unit = node.documentElement.attributes.unit.value.toLowerCase();
|
||||
|
||||
}
|
||||
|
||||
const 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 ) {
|
||||
|
||||
let matName = 'AMF Material';
|
||||
const matId = node.attributes.id.textContent;
|
||||
let color = { r: 1.0, g: 1.0, b: 1.0, a: 1.0 };
|
||||
|
||||
let loadedMaterial = null;
|
||||
|
||||
for ( let i = 0; i < node.childNodes.length; i ++ ) {
|
||||
|
||||
const 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 ) {
|
||||
|
||||
const color = { r: 1.0, g: 1.0, b: 1.0, a: 1.0 };
|
||||
|
||||
for ( let i = 0; i < node.childNodes.length; i ++ ) {
|
||||
|
||||
const 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 ) {
|
||||
|
||||
const volume = { name: '', triangles: [], materialid: null };
|
||||
|
||||
let 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' ) {
|
||||
|
||||
const v1 = currVolumeNode.getElementsByTagName( 'v1' )[ 0 ].textContent;
|
||||
const v2 = currVolumeNode.getElementsByTagName( 'v2' )[ 0 ].textContent;
|
||||
const v3 = currVolumeNode.getElementsByTagName( 'v3' )[ 0 ].textContent;
|
||||
|
||||
volume.triangles.push( v1, v2, v3 );
|
||||
|
||||
}
|
||||
|
||||
currVolumeNode = currVolumeNode.nextElementSibling;
|
||||
|
||||
}
|
||||
|
||||
return volume;
|
||||
|
||||
}
|
||||
|
||||
function loadMeshVertices( node ) {
|
||||
|
||||
const vertArray = [];
|
||||
const normalArray = [];
|
||||
let currVerticesNode = node.firstElementChild;
|
||||
|
||||
while ( currVerticesNode ) {
|
||||
|
||||
if ( currVerticesNode.nodeName === 'vertex' ) {
|
||||
|
||||
let vNode = currVerticesNode.firstElementChild;
|
||||
|
||||
while ( vNode ) {
|
||||
|
||||
if ( vNode.nodeName === 'coordinates' ) {
|
||||
|
||||
const x = vNode.getElementsByTagName( 'x' )[ 0 ].textContent;
|
||||
const y = vNode.getElementsByTagName( 'y' )[ 0 ].textContent;
|
||||
const z = vNode.getElementsByTagName( 'z' )[ 0 ].textContent;
|
||||
|
||||
vertArray.push( x, y, z );
|
||||
|
||||
} else if ( vNode.nodeName === 'normal' ) {
|
||||
|
||||
const nx = vNode.getElementsByTagName( 'nx' )[ 0 ].textContent;
|
||||
const ny = vNode.getElementsByTagName( 'ny' )[ 0 ].textContent;
|
||||
const 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 ) {
|
||||
|
||||
const objId = node.attributes.id.textContent;
|
||||
const loadedObject = { name: 'amfobject', meshes: [] };
|
||||
let currColor = null;
|
||||
let 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' ) {
|
||||
|
||||
let currMeshNode = currObjNode.firstElementChild;
|
||||
const mesh = { vertices: [], normals: [], volumes: [], color: currColor };
|
||||
|
||||
while ( currMeshNode ) {
|
||||
|
||||
if ( currMeshNode.nodeName === 'vertices' ) {
|
||||
|
||||
const 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 };
|
||||
|
||||
}
|
||||
|
||||
const xmlData = loadDocument( data );
|
||||
let amfName = '';
|
||||
let amfAuthor = '';
|
||||
const amfScale = loadDocumentScale( xmlData );
|
||||
const amfMaterials = {};
|
||||
const amfObjects = {};
|
||||
const childNodes = xmlData.documentElement.childNodes;
|
||||
|
||||
let i, j;
|
||||
|
||||
for ( i = 0; i < childNodes.length; i ++ ) {
|
||||
|
||||
const 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' ) {
|
||||
|
||||
const loadedMaterial = loadMaterials( child );
|
||||
|
||||
amfMaterials[ loadedMaterial.id ] = loadedMaterial.material;
|
||||
|
||||
} else if ( child.nodeName === 'object' ) {
|
||||
|
||||
const loadedObject = loadObject( child );
|
||||
|
||||
amfObjects[ loadedObject.id ] = loadedObject.obj;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const sceneObject = new Group();
|
||||
const defaultMaterial = new MeshPhongMaterial( {
|
||||
name: Loader.DEFAULT_MATERIAL_NAME,
|
||||
color: 0xaaaaff,
|
||||
flatShading: true
|
||||
} );
|
||||
|
||||
sceneObject.name = amfName;
|
||||
sceneObject.userData.author = amfAuthor;
|
||||
sceneObject.userData.loader = 'AMF';
|
||||
|
||||
for ( const id in amfObjects ) {
|
||||
|
||||
const part = amfObjects[ id ];
|
||||
const meshes = part.meshes;
|
||||
const newObject = new Group();
|
||||
newObject.name = part.name || '';
|
||||
|
||||
for ( i = 0; i < meshes.length; i ++ ) {
|
||||
|
||||
let objDefaultMaterial = defaultMaterial;
|
||||
const mesh = meshes[ i ];
|
||||
const vertices = new Float32BufferAttribute( mesh.vertices, 3 );
|
||||
let normals = null;
|
||||
|
||||
if ( mesh.normals.length ) {
|
||||
|
||||
normals = new Float32BufferAttribute( mesh.normals, 3 );
|
||||
|
||||
}
|
||||
|
||||
if ( mesh.color ) {
|
||||
|
||||
const 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;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const volumes = mesh.volumes;
|
||||
|
||||
for ( j = 0; j < volumes.length; j ++ ) {
|
||||
|
||||
const volume = volumes[ j ];
|
||||
const newGeometry = new BufferGeometry();
|
||||
let 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 };
|
||||
437
public/sdk/three/jsm/loaders/BVHLoader.js
Normal file
437
public/sdk/three/jsm/loaders/BVHLoader.js
Normal file
@ -0,0 +1,437 @@
|
||||
import {
|
||||
AnimationClip,
|
||||
Bone,
|
||||
FileLoader,
|
||||
Loader,
|
||||
Quaternion,
|
||||
QuaternionKeyframeTrack,
|
||||
Skeleton,
|
||||
Vector3,
|
||||
VectorKeyframeTrack
|
||||
} from 'three';
|
||||
|
||||
/**
|
||||
* Description: reads BVH files and outputs a single Skeleton and an AnimationClip
|
||||
*
|
||||
* Currently only supports bvh files containing a single root.
|
||||
*
|
||||
*/
|
||||
|
||||
class BVHLoader extends Loader {
|
||||
|
||||
constructor( manager ) {
|
||||
|
||||
super( manager );
|
||||
|
||||
this.animateBonePositions = true;
|
||||
this.animateBoneRotations = true;
|
||||
|
||||
}
|
||||
|
||||
load( url, onLoad, onProgress, onError ) {
|
||||
|
||||
const scope = this;
|
||||
|
||||
const 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( 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.' );
|
||||
|
||||
}
|
||||
|
||||
const list = []; // collects flat array of all bones
|
||||
const root = readNode( lines, nextLine( lines ), list );
|
||||
|
||||
// read motion data
|
||||
|
||||
if ( nextLine( lines ) !== 'MOTION' ) {
|
||||
|
||||
console.error( 'THREE.BVHLoader: MOTION expected.' );
|
||||
|
||||
}
|
||||
|
||||
// number of frames
|
||||
|
||||
let tokens = nextLine( lines ).split( /[\s]+/ );
|
||||
const numFrames = parseInt( tokens[ 1 ] );
|
||||
|
||||
if ( isNaN( numFrames ) ) {
|
||||
|
||||
console.error( 'THREE.BVHLoader: Failed to read number of frames.' );
|
||||
|
||||
}
|
||||
|
||||
// frame time
|
||||
|
||||
tokens = nextLine( lines ).split( /[\s]+/ );
|
||||
const frameTime = parseFloat( tokens[ 2 ] );
|
||||
|
||||
if ( isNaN( frameTime ) ) {
|
||||
|
||||
console.error( 'THREE.BVHLoader: Failed to read frame time.' );
|
||||
|
||||
}
|
||||
|
||||
// read frame data line by line
|
||||
|
||||
for ( let 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
|
||||
|
||||
const keyframe = {
|
||||
time: frameTime,
|
||||
position: new Vector3(),
|
||||
rotation: new Quaternion()
|
||||
};
|
||||
|
||||
bone.frames.push( keyframe );
|
||||
|
||||
const quat = new Quaternion();
|
||||
|
||||
const vx = new Vector3( 1, 0, 0 );
|
||||
const vy = new Vector3( 0, 1, 0 );
|
||||
const vz = new Vector3( 0, 0, 1 );
|
||||
|
||||
// parse values for each channel in node
|
||||
|
||||
for ( let 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 ( let 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 ) {
|
||||
|
||||
const node = { name: '', type: '', frames: [] };
|
||||
list.push( node );
|
||||
|
||||
// parse node type and name
|
||||
|
||||
let 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.' );
|
||||
|
||||
}
|
||||
|
||||
const 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.' );
|
||||
|
||||
}
|
||||
|
||||
const numChannels = parseInt( tokens[ 1 ] );
|
||||
node.channels = tokens.splice( 2, numChannels );
|
||||
node.children = [];
|
||||
|
||||
}
|
||||
|
||||
// read children
|
||||
|
||||
while ( true ) {
|
||||
|
||||
const 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 ) {
|
||||
|
||||
const bone = new Bone();
|
||||
list.push( bone );
|
||||
|
||||
bone.position.add( source.offset );
|
||||
bone.name = source.name;
|
||||
|
||||
if ( source.type !== 'ENDSITE' ) {
|
||||
|
||||
for ( let 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 ) {
|
||||
|
||||
const tracks = [];
|
||||
|
||||
// create a position and quaternion animation track for each node
|
||||
|
||||
for ( let i = 0; i < bones.length; i ++ ) {
|
||||
|
||||
const bone = bones[ i ];
|
||||
|
||||
if ( bone.type === 'ENDSITE' )
|
||||
continue;
|
||||
|
||||
// track data
|
||||
|
||||
const times = [];
|
||||
const positions = [];
|
||||
const rotations = [];
|
||||
|
||||
for ( let j = 0; j < bone.frames.length; j ++ ) {
|
||||
|
||||
const 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( bone.name + '.position', times, positions ) );
|
||||
|
||||
}
|
||||
|
||||
if ( scope.animateBoneRotations ) {
|
||||
|
||||
tracks.push( new QuaternionKeyframeTrack( bone.name + '.quaternion', times, rotations ) );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return new AnimationClip( 'animation', - 1, tracks );
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
returns the next non-empty line in lines
|
||||
*/
|
||||
function nextLine( lines ) {
|
||||
|
||||
let line;
|
||||
// skip empty lines
|
||||
while ( ( line = lines.shift().trim() ).length === 0 ) { }
|
||||
|
||||
return line;
|
||||
|
||||
}
|
||||
|
||||
const scope = this;
|
||||
|
||||
const lines = text.split( /[\r\n]+/g );
|
||||
|
||||
const bones = readBvh( lines );
|
||||
|
||||
const threeBones = [];
|
||||
toTHREEBone( bones[ 0 ], threeBones );
|
||||
|
||||
const threeClip = toTHREEAnimation( bones );
|
||||
|
||||
return {
|
||||
skeleton: new Skeleton( threeBones ),
|
||||
clip: threeClip
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export { BVHLoader };
|
||||
4116
public/sdk/three/jsm/loaders/ColladaLoader.js
Normal file
4116
public/sdk/three/jsm/loaders/ColladaLoader.js
Normal file
File diff suppressed because it is too large
Load Diff
318
public/sdk/three/jsm/loaders/DDSLoader.js
Normal file
318
public/sdk/three/jsm/loaders/DDSLoader.js
Normal file
@ -0,0 +1,318 @@
|
||||
import {
|
||||
CompressedTextureLoader,
|
||||
RGBAFormat,
|
||||
RGBA_S3TC_DXT3_Format,
|
||||
RGBA_S3TC_DXT5_Format,
|
||||
RGB_ETC1_Format,
|
||||
RGB_S3TC_DXT1_Format,
|
||||
RGB_BPTC_SIGNED_Format,
|
||||
RGB_BPTC_UNSIGNED_Format
|
||||
} from 'three';
|
||||
|
||||
class DDSLoader extends CompressedTextureLoader {
|
||||
|
||||
constructor( manager ) {
|
||||
|
||||
super( manager );
|
||||
|
||||
}
|
||||
|
||||
parse( buffer, loadMipmaps ) {
|
||||
|
||||
const 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/
|
||||
|
||||
const DDS_MAGIC = 0x20534444;
|
||||
|
||||
// const DDSD_CAPS = 0x1;
|
||||
// const DDSD_HEIGHT = 0x2;
|
||||
// const DDSD_WIDTH = 0x4;
|
||||
// const DDSD_PITCH = 0x8;
|
||||
// const DDSD_PIXELFORMAT = 0x1000;
|
||||
const DDSD_MIPMAPCOUNT = 0x20000;
|
||||
// const DDSD_LINEARSIZE = 0x80000;
|
||||
// const DDSD_DEPTH = 0x800000;
|
||||
|
||||
// const DDSCAPS_COMPLEX = 0x8;
|
||||
// const DDSCAPS_MIPMAP = 0x400000;
|
||||
// const DDSCAPS_TEXTURE = 0x1000;
|
||||
|
||||
const DDSCAPS2_CUBEMAP = 0x200;
|
||||
const DDSCAPS2_CUBEMAP_POSITIVEX = 0x400;
|
||||
const DDSCAPS2_CUBEMAP_NEGATIVEX = 0x800;
|
||||
const DDSCAPS2_CUBEMAP_POSITIVEY = 0x1000;
|
||||
const DDSCAPS2_CUBEMAP_NEGATIVEY = 0x2000;
|
||||
const DDSCAPS2_CUBEMAP_POSITIVEZ = 0x4000;
|
||||
const DDSCAPS2_CUBEMAP_NEGATIVEZ = 0x8000;
|
||||
// const DDSCAPS2_VOLUME = 0x200000;
|
||||
|
||||
// const DDPF_ALPHAPIXELS = 0x1;
|
||||
// const DDPF_ALPHA = 0x2;
|
||||
// const DDPF_FOURCC = 0x4;
|
||||
// const DDPF_RGB = 0x40;
|
||||
// const DDPF_YUV = 0x200;
|
||||
// const DDPF_LUMINANCE = 0x20000;
|
||||
|
||||
const DXGI_FORMAT_BC6H_UF16 = 95;
|
||||
const DXGI_FORMAT_BC6H_SF16 = 96;
|
||||
|
||||
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 ) {
|
||||
|
||||
const dataLength = width * height * 4;
|
||||
const srcBuffer = new Uint8Array( buffer, dataOffset, dataLength );
|
||||
const byteArray = new Uint8Array( dataLength );
|
||||
let dst = 0;
|
||||
let src = 0;
|
||||
for ( let y = 0; y < height; y ++ ) {
|
||||
|
||||
for ( let x = 0; x < width; x ++ ) {
|
||||
|
||||
const b = srcBuffer[ src ]; src ++;
|
||||
const g = srcBuffer[ src ]; src ++;
|
||||
const r = srcBuffer[ src ]; src ++;
|
||||
const 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;
|
||||
|
||||
}
|
||||
|
||||
const FOURCC_DXT1 = fourCCToInt32( 'DXT1' );
|
||||
const FOURCC_DXT3 = fourCCToInt32( 'DXT3' );
|
||||
const FOURCC_DXT5 = fourCCToInt32( 'DXT5' );
|
||||
const FOURCC_ETC1 = fourCCToInt32( 'ETC1' );
|
||||
const FOURCC_DX10 = fourCCToInt32( 'DX10' );
|
||||
|
||||
const headerLengthInt = 31; // The header length in 32 bit ints
|
||||
const extendedHeaderLengthInt = 5; // The extended header length in 32 bit ints
|
||||
|
||||
// Offsets into the header array
|
||||
|
||||
const off_magic = 0;
|
||||
|
||||
const off_size = 1;
|
||||
const off_flags = 2;
|
||||
const off_height = 3;
|
||||
const off_width = 4;
|
||||
|
||||
const off_mipmapCount = 7;
|
||||
|
||||
// const off_pfFlags = 20;
|
||||
const off_pfFourCC = 21;
|
||||
const off_RGBBitCount = 22;
|
||||
const off_RBitMask = 23;
|
||||
const off_GBitMask = 24;
|
||||
const off_BBitMask = 25;
|
||||
const off_ABitMask = 26;
|
||||
|
||||
// const off_caps = 27;
|
||||
const off_caps2 = 28;
|
||||
// const off_caps3 = 29;
|
||||
// const off_caps4 = 30;
|
||||
|
||||
// If fourCC = DX10, the extended header starts after 32
|
||||
const off_dxgiFormat = 0;
|
||||
|
||||
// Parse header
|
||||
|
||||
const 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;
|
||||
|
||||
}
|
||||
|
||||
let blockBytes;
|
||||
|
||||
const fourCC = header[ off_pfFourCC ];
|
||||
|
||||
let isRGBAUncompressed = false;
|
||||
|
||||
let dataOffset = header[ off_size ] + 4;
|
||||
|
||||
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;
|
||||
|
||||
case FOURCC_DX10:
|
||||
|
||||
dataOffset += extendedHeaderLengthInt * 4;
|
||||
const extendedHeader = new Int32Array( buffer, ( headerLengthInt + 1 ) * 4, extendedHeaderLengthInt );
|
||||
const dxgiFormat = extendedHeader[ off_dxgiFormat ];
|
||||
switch ( dxgiFormat ) {
|
||||
|
||||
case DXGI_FORMAT_BC6H_SF16: {
|
||||
|
||||
blockBytes = 16;
|
||||
dds.format = RGB_BPTC_SIGNED_Format;
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
case DXGI_FORMAT_BC6H_UF16: {
|
||||
|
||||
blockBytes = 16;
|
||||
dds.format = RGB_BPTC_UNSIGNED_Format;
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
default: {
|
||||
|
||||
console.error( 'THREE.DDSLoader.parse: Unsupported DXGI_FORMAT code ', dxgiFormat );
|
||||
return dds;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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 ] );
|
||||
|
||||
}
|
||||
|
||||
const 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 ];
|
||||
|
||||
// Extract mipmaps buffers
|
||||
|
||||
const faces = dds.isCubemap ? 6 : 1;
|
||||
|
||||
for ( let face = 0; face < faces; face ++ ) {
|
||||
|
||||
let width = dds.width;
|
||||
let height = dds.height;
|
||||
|
||||
for ( let i = 0; i < dds.mipmapCount; i ++ ) {
|
||||
|
||||
let byteArray, dataLength;
|
||||
|
||||
if ( isRGBAUncompressed ) {
|
||||
|
||||
byteArray = loadARGBMip( buffer, dataOffset, width, height );
|
||||
dataLength = byteArray.length;
|
||||
|
||||
} else {
|
||||
|
||||
dataLength = Math.max( 4, width ) / 4 * Math.max( 4, height ) / 4 * blockBytes;
|
||||
byteArray = new Uint8Array( buffer, dataOffset, dataLength );
|
||||
|
||||
}
|
||||
|
||||
const 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 };
|
||||
613
public/sdk/three/jsm/loaders/DRACOLoader.js
Normal file
613
public/sdk/three/jsm/loaders/DRACOLoader.js
Normal file
@ -0,0 +1,613 @@
|
||||
import {
|
||||
BufferAttribute,
|
||||
BufferGeometry,
|
||||
Color,
|
||||
FileLoader,
|
||||
Loader,
|
||||
LinearSRGBColorSpace,
|
||||
SRGBColorSpace
|
||||
} from 'three';
|
||||
|
||||
const _taskCache = new WeakMap();
|
||||
|
||||
class DRACOLoader extends Loader {
|
||||
|
||||
constructor( manager ) {
|
||||
|
||||
super( 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'
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
setDecoderPath( path ) {
|
||||
|
||||
this.decoderPath = path;
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
setDecoderConfig( config ) {
|
||||
|
||||
this.decoderConfig = config;
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
setWorkerLimit( workerLimit ) {
|
||||
|
||||
this.workerLimit = workerLimit;
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
load( url, onLoad, onProgress, onError ) {
|
||||
|
||||
const loader = new FileLoader( this.manager );
|
||||
|
||||
loader.setPath( this.path );
|
||||
loader.setResponseType( 'arraybuffer' );
|
||||
loader.setRequestHeader( this.requestHeader );
|
||||
loader.setWithCredentials( this.withCredentials );
|
||||
|
||||
loader.load( url, ( buffer ) => {
|
||||
|
||||
this.parse( buffer, onLoad, onError );
|
||||
|
||||
}, onProgress, onError );
|
||||
|
||||
}
|
||||
|
||||
|
||||
parse( buffer, onLoad, onError = ()=>{} ) {
|
||||
|
||||
this.decodeDracoFile( buffer, onLoad, null, null, SRGBColorSpace ).catch( onError );
|
||||
|
||||
}
|
||||
|
||||
decodeDracoFile( buffer, callback, attributeIDs, attributeTypes, vertexColorSpace = LinearSRGBColorSpace, onError = () => {} ) {
|
||||
|
||||
const taskConfig = {
|
||||
attributeIDs: attributeIDs || this.defaultAttributeIDs,
|
||||
attributeTypes: attributeTypes || this.defaultAttributeTypes,
|
||||
useUniqueIDs: !! attributeIDs,
|
||||
vertexColorSpace: vertexColorSpace,
|
||||
};
|
||||
|
||||
return this.decodeGeometry( buffer, taskConfig ).then( callback ).catch( onError );
|
||||
|
||||
}
|
||||
|
||||
decodeGeometry( buffer, taskConfig ) {
|
||||
|
||||
const taskKey = JSON.stringify( taskConfig );
|
||||
|
||||
// Check for an existing task using this buffer. A transferred buffer cannot be transferred
|
||||
// again from this thread.
|
||||
if ( _taskCache.has( buffer ) ) {
|
||||
|
||||
const cachedTask = _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.'
|
||||
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
let worker;
|
||||
const taskID = this.workerNextTaskID ++;
|
||||
const taskCost = buffer.byteLength;
|
||||
|
||||
// Obtain a worker and assign a task, and construct a geometry instance
|
||||
// when the task completes.
|
||||
const 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.
|
||||
_taskCache.set( buffer, {
|
||||
|
||||
key: taskKey,
|
||||
promise: geometryPending
|
||||
|
||||
} );
|
||||
|
||||
return geometryPending;
|
||||
|
||||
}
|
||||
|
||||
_createGeometry( geometryData ) {
|
||||
|
||||
const geometry = new BufferGeometry();
|
||||
|
||||
if ( geometryData.index ) {
|
||||
|
||||
geometry.setIndex( new BufferAttribute( geometryData.index.array, 1 ) );
|
||||
|
||||
}
|
||||
|
||||
for ( let i = 0; i < geometryData.attributes.length; i ++ ) {
|
||||
|
||||
const result = geometryData.attributes[ i ];
|
||||
const name = result.name;
|
||||
const array = result.array;
|
||||
const itemSize = result.itemSize;
|
||||
|
||||
const attribute = new BufferAttribute( array, itemSize );
|
||||
|
||||
if ( name === 'color' ) {
|
||||
|
||||
this._assignVertexColorSpace( attribute, result.vertexColorSpace );
|
||||
|
||||
attribute.normalized = ( array instanceof Float32Array ) === false;
|
||||
|
||||
}
|
||||
|
||||
geometry.setAttribute( name, attribute );
|
||||
|
||||
}
|
||||
|
||||
return geometry;
|
||||
|
||||
}
|
||||
|
||||
_assignVertexColorSpace( attribute, inputColorSpace ) {
|
||||
|
||||
// While .drc files do not specify colorspace, the only 'official' tooling
|
||||
// is PLY and OBJ converters, which use sRGB. We'll assume sRGB when a .drc
|
||||
// file is passed into .load() or .parse(). GLTFLoader uses internal APIs
|
||||
// to decode geometry, and vertex colors are already Linear-sRGB in there.
|
||||
|
||||
if ( inputColorSpace !== SRGBColorSpace ) return;
|
||||
|
||||
const _color = new Color();
|
||||
|
||||
for ( let i = 0, il = attribute.count; i < il; i ++ ) {
|
||||
|
||||
_color.fromBufferAttribute( attribute, i ).convertSRGBToLinear();
|
||||
attribute.setXYZ( i, _color.r, _color.g, _color.b );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
_loadLibrary( url, responseType ) {
|
||||
|
||||
const 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() {
|
||||
|
||||
this._initDecoder();
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
_initDecoder() {
|
||||
|
||||
if ( this.decoderPending ) return this.decoderPending;
|
||||
|
||||
const useJS = typeof WebAssembly !== 'object' || this.decoderConfig.type === 'js';
|
||||
const 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 ) => {
|
||||
|
||||
const jsContent = libraries[ 0 ];
|
||||
|
||||
if ( ! useJS ) {
|
||||
|
||||
this.decoderConfig.wasmBinary = libraries[ 1 ];
|
||||
|
||||
}
|
||||
|
||||
const fn = DRACOWorker.toString();
|
||||
|
||||
const 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( taskID, taskCost ) {
|
||||
|
||||
return this._initDecoder().then( () => {
|
||||
|
||||
if ( this.workerPool.length < this.workerLimit ) {
|
||||
|
||||
const worker = new Worker( this.workerSourceURL );
|
||||
|
||||
worker._callbacks = {};
|
||||
worker._taskCosts = {};
|
||||
worker._taskLoad = 0;
|
||||
|
||||
worker.postMessage( { type: 'init', decoderConfig: this.decoderConfig } );
|
||||
|
||||
worker.onmessage = function ( e ) {
|
||||
|
||||
const 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;
|
||||
|
||||
} );
|
||||
|
||||
}
|
||||
|
||||
const worker = this.workerPool[ this.workerPool.length - 1 ];
|
||||
worker._taskCosts[ taskID ] = taskCost;
|
||||
worker._taskLoad += taskCost;
|
||||
return worker;
|
||||
|
||||
} );
|
||||
|
||||
}
|
||||
|
||||
_releaseTask( worker, taskID ) {
|
||||
|
||||
worker._taskLoad -= worker._taskCosts[ taskID ];
|
||||
delete worker._callbacks[ taskID ];
|
||||
delete worker._taskCosts[ taskID ];
|
||||
|
||||
}
|
||||
|
||||
debug() {
|
||||
|
||||
console.log( 'Task load: ', this.workerPool.map( ( worker ) => worker._taskLoad ) );
|
||||
|
||||
}
|
||||
|
||||
dispose() {
|
||||
|
||||
for ( let i = 0; i < this.workerPool.length; ++ i ) {
|
||||
|
||||
this.workerPool[ i ].terminate();
|
||||
|
||||
}
|
||||
|
||||
this.workerPool.length = 0;
|
||||
|
||||
if ( this.workerSourceURL !== '' ) {
|
||||
|
||||
URL.revokeObjectURL( this.workerSourceURL );
|
||||
|
||||
}
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* WEB WORKER */
|
||||
|
||||
function DRACOWorker() {
|
||||
|
||||
let decoderConfig;
|
||||
let decoderPending;
|
||||
|
||||
onmessage = function ( e ) {
|
||||
|
||||
const 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':
|
||||
const buffer = message.buffer;
|
||||
const taskConfig = message.taskConfig;
|
||||
decoderPending.then( ( module ) => {
|
||||
|
||||
const draco = module.draco;
|
||||
const decoder = new draco.Decoder();
|
||||
|
||||
try {
|
||||
|
||||
const geometry = decodeGeometry( draco, decoder, new Int8Array( buffer ), taskConfig );
|
||||
|
||||
const 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( decoder );
|
||||
|
||||
}
|
||||
|
||||
} );
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
function decodeGeometry( draco, decoder, array, taskConfig ) {
|
||||
|
||||
const attributeIDs = taskConfig.attributeIDs;
|
||||
const attributeTypes = taskConfig.attributeTypes;
|
||||
|
||||
let dracoGeometry;
|
||||
let decodingStatus;
|
||||
|
||||
const geometryType = decoder.GetEncodedGeometryType( array );
|
||||
|
||||
if ( geometryType === draco.TRIANGULAR_MESH ) {
|
||||
|
||||
dracoGeometry = new draco.Mesh();
|
||||
decodingStatus = decoder.DecodeArrayToMesh( array, array.byteLength, dracoGeometry );
|
||||
|
||||
} else if ( geometryType === draco.POINT_CLOUD ) {
|
||||
|
||||
dracoGeometry = new draco.PointCloud();
|
||||
decodingStatus = decoder.DecodeArrayToPointCloud( array, array.byteLength, 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() );
|
||||
|
||||
}
|
||||
|
||||
const geometry = { index: null, attributes: [] };
|
||||
|
||||
// Gather all vertex attributes.
|
||||
for ( const attributeName in attributeIDs ) {
|
||||
|
||||
const attributeType = self[ attributeTypes[ attributeName ] ];
|
||||
|
||||
let attribute;
|
||||
let 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 );
|
||||
|
||||
}
|
||||
|
||||
const attributeResult = decodeAttribute( draco, decoder, dracoGeometry, attributeName, attributeType, attribute );
|
||||
|
||||
if ( attributeName === 'color' ) {
|
||||
|
||||
attributeResult.vertexColorSpace = taskConfig.vertexColorSpace;
|
||||
|
||||
}
|
||||
|
||||
geometry.attributes.push( attributeResult );
|
||||
|
||||
}
|
||||
|
||||
// Add index.
|
||||
if ( geometryType === draco.TRIANGULAR_MESH ) {
|
||||
|
||||
geometry.index = decodeIndex( draco, decoder, dracoGeometry );
|
||||
|
||||
}
|
||||
|
||||
draco.destroy( dracoGeometry );
|
||||
|
||||
return geometry;
|
||||
|
||||
}
|
||||
|
||||
function decodeIndex( draco, decoder, dracoGeometry ) {
|
||||
|
||||
const numFaces = dracoGeometry.num_faces();
|
||||
const numIndices = numFaces * 3;
|
||||
const byteLength = numIndices * 4;
|
||||
|
||||
const ptr = draco._malloc( byteLength );
|
||||
decoder.GetTrianglesUInt32Array( dracoGeometry, byteLength, ptr );
|
||||
const 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 ) {
|
||||
|
||||
const numComponents = attribute.num_components();
|
||||
const numPoints = dracoGeometry.num_points();
|
||||
const numValues = numPoints * numComponents;
|
||||
const byteLength = numValues * attributeType.BYTES_PER_ELEMENT;
|
||||
const dataType = getDracoDataType( draco, attributeType );
|
||||
|
||||
const ptr = draco._malloc( byteLength );
|
||||
decoder.GetAttributeDataArrayForAllPoints( dracoGeometry, attribute, dataType, byteLength, ptr );
|
||||
const 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;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export { DRACOLoader };
|
||||
2563
public/sdk/three/jsm/loaders/EXRLoader.js
Normal file
2563
public/sdk/three/jsm/loaders/EXRLoader.js
Normal file
File diff suppressed because it is too large
Load Diff
4314
public/sdk/three/jsm/loaders/FBXLoader.js
Normal file
4314
public/sdk/three/jsm/loaders/FBXLoader.js
Normal file
File diff suppressed because it is too large
Load Diff
183
public/sdk/three/jsm/loaders/FontLoader.js
Normal file
183
public/sdk/three/jsm/loaders/FontLoader.js
Normal file
@ -0,0 +1,183 @@
|
||||
import {
|
||||
FileLoader,
|
||||
Loader,
|
||||
ShapePath
|
||||
} from '../../three.module.min.js';
|
||||
|
||||
class FontLoader extends Loader {
|
||||
|
||||
constructor( manager ) {
|
||||
|
||||
super( manager );
|
||||
|
||||
}
|
||||
|
||||
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 ) {
|
||||
|
||||
const font = scope.parse( JSON.parse( text ) );
|
||||
|
||||
if ( onLoad ) onLoad( font );
|
||||
|
||||
}, onProgress, onError );
|
||||
|
||||
}
|
||||
|
||||
parse( json ) {
|
||||
|
||||
return new Font( json );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
class Font {
|
||||
|
||||
constructor( data ) {
|
||||
|
||||
this.isFont = true;
|
||||
|
||||
this.type = 'Font';
|
||||
|
||||
this.data = data;
|
||||
|
||||
}
|
||||
|
||||
generateShapes( text, size = 100 ) {
|
||||
|
||||
const shapes = [];
|
||||
const paths = createPaths( text, size, this.data );
|
||||
|
||||
for ( let p = 0, pl = paths.length; p < pl; p ++ ) {
|
||||
|
||||
shapes.push( ...paths[ p ].toShapes() );
|
||||
|
||||
}
|
||||
|
||||
return shapes;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function createPaths( text, size, data ) {
|
||||
|
||||
const chars = Array.from( text );
|
||||
const scale = size / data.resolution;
|
||||
const line_height = ( data.boundingBox.yMax - data.boundingBox.yMin + data.underlineThickness ) * scale;
|
||||
|
||||
const paths = [];
|
||||
|
||||
let offsetX = 0, offsetY = 0;
|
||||
|
||||
for ( let i = 0; i < chars.length; i ++ ) {
|
||||
|
||||
const char = chars[ i ];
|
||||
|
||||
if ( char === '\n' ) {
|
||||
|
||||
offsetX = 0;
|
||||
offsetY -= line_height;
|
||||
|
||||
} else {
|
||||
|
||||
const ret = createPath( char, scale, offsetX, offsetY, data );
|
||||
offsetX += ret.offsetX;
|
||||
paths.push( ret.path );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return paths;
|
||||
|
||||
}
|
||||
|
||||
function createPath( char, scale, offsetX, offsetY, data ) {
|
||||
|
||||
const glyph = data.glyphs[ char ] || data.glyphs[ '?' ];
|
||||
|
||||
if ( ! glyph ) {
|
||||
|
||||
console.error( 'THREE.Font: character "' + char + '" does not exists in font family ' + data.familyName + '.' );
|
||||
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
const path = new ShapePath();
|
||||
|
||||
let x, y, cpx, cpy, cpx1, cpy1, cpx2, cpy2;
|
||||
|
||||
if ( glyph.o ) {
|
||||
|
||||
const outline = glyph._cachedOutline || ( glyph._cachedOutline = glyph.o.split( ' ' ) );
|
||||
|
||||
for ( let i = 0, l = outline.length; i < l; ) {
|
||||
|
||||
const action = outline[ i ++ ];
|
||||
|
||||
switch ( action ) {
|
||||
|
||||
case 'm': // moveTo
|
||||
|
||||
x = outline[ i ++ ] * scale + offsetX;
|
||||
y = outline[ i ++ ] * scale + offsetY;
|
||||
|
||||
path.moveTo( x, y );
|
||||
|
||||
break;
|
||||
|
||||
case 'l': // lineTo
|
||||
|
||||
x = outline[ i ++ ] * scale + offsetX;
|
||||
y = outline[ i ++ ] * scale + offsetY;
|
||||
|
||||
path.lineTo( x, y );
|
||||
|
||||
break;
|
||||
|
||||
case 'q': // quadraticCurveTo
|
||||
|
||||
cpx = outline[ i ++ ] * scale + offsetX;
|
||||
cpy = outline[ i ++ ] * scale + offsetY;
|
||||
cpx1 = outline[ i ++ ] * scale + offsetX;
|
||||
cpy1 = outline[ i ++ ] * scale + offsetY;
|
||||
|
||||
path.quadraticCurveTo( cpx1, cpy1, cpx, cpy );
|
||||
|
||||
break;
|
||||
|
||||
case 'b': // bezierCurveTo
|
||||
|
||||
cpx = outline[ i ++ ] * scale + offsetX;
|
||||
cpy = outline[ i ++ ] * scale + offsetY;
|
||||
cpx1 = outline[ i ++ ] * scale + offsetX;
|
||||
cpy1 = outline[ i ++ ] * scale + offsetY;
|
||||
cpx2 = outline[ i ++ ] * scale + offsetX;
|
||||
cpy2 = outline[ i ++ ] * scale + offsetY;
|
||||
|
||||
path.bezierCurveTo( cpx1, cpy1, cpx2, cpy2, cpx, cpy );
|
||||
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return { offsetX: glyph.ha * scale, path: path };
|
||||
|
||||
}
|
||||
|
||||
export { FontLoader, Font };
|
||||
261
public/sdk/three/jsm/loaders/GCodeLoader.js
Normal file
261
public/sdk/three/jsm/loaders/GCodeLoader.js
Normal file
@ -0,0 +1,261 @@
|
||||
import {
|
||||
BufferGeometry,
|
||||
FileLoader,
|
||||
Float32BufferAttribute,
|
||||
Group,
|
||||
LineBasicMaterial,
|
||||
LineSegments,
|
||||
Loader
|
||||
} from 'three';
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
class GCodeLoader extends Loader {
|
||||
|
||||
constructor( manager ) {
|
||||
|
||||
super( manager );
|
||||
|
||||
this.splitLayer = false;
|
||||
|
||||
}
|
||||
|
||||
load( url, onLoad, onProgress, onError ) {
|
||||
|
||||
const scope = this;
|
||||
|
||||
const 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( data ) {
|
||||
|
||||
let state = { x: 0, y: 0, z: 0, e: 0, f: 0, extruding: false, relative: false };
|
||||
const layers = [];
|
||||
|
||||
let currentLayer = undefined;
|
||||
|
||||
const pathMaterial = new LineBasicMaterial( { color: 0xFF0000 } );
|
||||
pathMaterial.name = 'path';
|
||||
|
||||
const 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 ( state.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;
|
||||
|
||||
}
|
||||
|
||||
const lines = data.replace( /;.+/g, '' ).split( '\n' );
|
||||
|
||||
for ( let i = 0; i < lines.length; i ++ ) {
|
||||
|
||||
const tokens = lines[ i ].split( ' ' );
|
||||
const cmd = tokens[ 0 ].toUpperCase();
|
||||
|
||||
//Argumments
|
||||
const args = {};
|
||||
tokens.splice( 1 ).forEach( function ( token ) {
|
||||
|
||||
if ( token[ 0 ] !== undefined ) {
|
||||
|
||||
const key = token[ 0 ].toLowerCase();
|
||||
const value = parseFloat( token.substring( 1 ) );
|
||||
args[ key ] = value;
|
||||
|
||||
}
|
||||
|
||||
} );
|
||||
|
||||
//Process commands
|
||||
//G0/G1 – Linear Movement
|
||||
if ( cmd === 'G0' || cmd === 'G1' ) {
|
||||
|
||||
const 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 ) {
|
||||
|
||||
state.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
|
||||
const 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;
|
||||
|
||||
} else {
|
||||
|
||||
//console.warn( 'THREE.GCodeLoader: Command not supported:' + cmd );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function addObject( vertex, extruding, i ) {
|
||||
|
||||
const geometry = new BufferGeometry();
|
||||
geometry.setAttribute( 'position', new Float32BufferAttribute( vertex, 3 ) );
|
||||
const segments = new LineSegments( geometry, extruding ? extrudingMaterial : pathMaterial );
|
||||
segments.name = 'layer' + i;
|
||||
object.add( segments );
|
||||
|
||||
}
|
||||
|
||||
const object = new Group();
|
||||
object.name = 'gcode';
|
||||
|
||||
if ( this.splitLayer ) {
|
||||
|
||||
for ( let i = 0; i < layers.length; i ++ ) {
|
||||
|
||||
const layer = layers[ i ];
|
||||
addObject( layer.vertex, true, i );
|
||||
addObject( layer.pathVertex, false, i );
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
const vertex = [],
|
||||
pathVertex = [];
|
||||
|
||||
for ( let i = 0; i < layers.length; i ++ ) {
|
||||
|
||||
const layer = layers[ i ];
|
||||
const layerVertex = layer.vertex;
|
||||
const layerPathVertex = layer.pathVertex;
|
||||
|
||||
for ( let j = 0; j < layerVertex.length; j ++ ) {
|
||||
|
||||
vertex.push( layerVertex[ j ] );
|
||||
|
||||
}
|
||||
|
||||
for ( let j = 0; j < layerPathVertex.length; j ++ ) {
|
||||
|
||||
pathVertex.push( layerPathVertex[ j ] );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
addObject( vertex, true, layers.length );
|
||||
addObject( pathVertex, false, layers.length );
|
||||
|
||||
}
|
||||
|
||||
object.rotation.set( - Math.PI / 2, 0, 0 );
|
||||
|
||||
return object;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export { GCodeLoader };
|
||||
4722
public/sdk/three/jsm/loaders/GLTFLoader.js
Normal file
4722
public/sdk/three/jsm/loaders/GLTFLoader.js
Normal file
File diff suppressed because it is too large
Load Diff
115
public/sdk/three/jsm/loaders/HDRCubeTextureLoader.js
Normal file
115
public/sdk/three/jsm/loaders/HDRCubeTextureLoader.js
Normal file
@ -0,0 +1,115 @@
|
||||
import {
|
||||
CubeTexture,
|
||||
DataTexture,
|
||||
FileLoader,
|
||||
FloatType,
|
||||
HalfFloatType,
|
||||
LinearFilter,
|
||||
LinearSRGBColorSpace,
|
||||
Loader
|
||||
} from 'three';
|
||||
import { RGBELoader } from '../loaders/RGBELoader.js';
|
||||
|
||||
class HDRCubeTextureLoader extends Loader {
|
||||
|
||||
constructor( manager ) {
|
||||
|
||||
super( manager );
|
||||
|
||||
this.hdrLoader = new RGBELoader();
|
||||
this.type = HalfFloatType;
|
||||
|
||||
}
|
||||
|
||||
load( urls, onLoad, onProgress, onError ) {
|
||||
|
||||
const texture = new CubeTexture();
|
||||
|
||||
texture.type = this.type;
|
||||
|
||||
switch ( texture.type ) {
|
||||
|
||||
case FloatType:
|
||||
|
||||
texture.colorSpace = LinearSRGBColorSpace;
|
||||
texture.minFilter = LinearFilter;
|
||||
texture.magFilter = LinearFilter;
|
||||
texture.generateMipmaps = false;
|
||||
break;
|
||||
|
||||
case HalfFloatType:
|
||||
|
||||
texture.colorSpace = LinearSRGBColorSpace;
|
||||
texture.minFilter = LinearFilter;
|
||||
texture.magFilter = LinearFilter;
|
||||
texture.generateMipmaps = false;
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
const scope = this;
|
||||
|
||||
let 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 ++;
|
||||
|
||||
const texData = scope.hdrLoader.parse( buffer );
|
||||
|
||||
if ( ! texData ) return;
|
||||
|
||||
if ( texData.data !== undefined ) {
|
||||
|
||||
const dataTexture = new DataTexture( texData.data, texData.width, texData.height );
|
||||
|
||||
dataTexture.type = texture.type;
|
||||
dataTexture.colorSpace = texture.colorSpace;
|
||||
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 ( let i = 0; i < urls.length; i ++ ) {
|
||||
|
||||
loadHDRData( i, onLoad, onProgress, onError );
|
||||
|
||||
}
|
||||
|
||||
return texture;
|
||||
|
||||
}
|
||||
|
||||
setDataType( value ) {
|
||||
|
||||
this.type = value;
|
||||
this.hdrLoader.setDataType( value );
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export { HDRCubeTextureLoader };
|
||||
337
public/sdk/three/jsm/loaders/IESLoader.js
Normal file
337
public/sdk/three/jsm/loaders/IESLoader.js
Normal file
@ -0,0 +1,337 @@
|
||||
import {
|
||||
DataTexture,
|
||||
FileLoader,
|
||||
FloatType,
|
||||
RedFormat,
|
||||
MathUtils,
|
||||
Loader,
|
||||
UnsignedByteType,
|
||||
LinearFilter,
|
||||
HalfFloatType,
|
||||
DataUtils
|
||||
} from 'three';
|
||||
|
||||
class IESLoader extends Loader {
|
||||
|
||||
constructor( manager ) {
|
||||
|
||||
super( manager );
|
||||
|
||||
this.type = HalfFloatType;
|
||||
|
||||
}
|
||||
|
||||
_getIESValues( iesLamp, type ) {
|
||||
|
||||
const width = 360;
|
||||
const height = 180;
|
||||
const size = width * height;
|
||||
|
||||
const data = new Array( size );
|
||||
|
||||
function interpolateCandelaValues( phi, theta ) {
|
||||
|
||||
let phiIndex = 0, thetaIndex = 0;
|
||||
let startTheta = 0, endTheta = 0, startPhi = 0, endPhi = 0;
|
||||
|
||||
for ( let i = 0; i < iesLamp.numHorAngles - 1; ++ i ) { // numHorAngles = horAngles.length-1 because of extra padding, so this wont cause an out of bounds error
|
||||
|
||||
if ( theta < iesLamp.horAngles[ i + 1 ] || i == iesLamp.numHorAngles - 2 ) {
|
||||
|
||||
thetaIndex = i;
|
||||
startTheta = iesLamp.horAngles[ i ];
|
||||
endTheta = iesLamp.horAngles[ i + 1 ];
|
||||
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
for ( let i = 0; i < iesLamp.numVerAngles - 1; ++ i ) {
|
||||
|
||||
if ( phi < iesLamp.verAngles[ i + 1 ] || i == iesLamp.numVerAngles - 2 ) {
|
||||
|
||||
phiIndex = i;
|
||||
startPhi = iesLamp.verAngles[ i ];
|
||||
endPhi = iesLamp.verAngles[ i + 1 ];
|
||||
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const deltaTheta = endTheta - startTheta;
|
||||
const deltaPhi = endPhi - startPhi;
|
||||
|
||||
if ( deltaPhi === 0 ) // Outside range
|
||||
return 0;
|
||||
|
||||
const t1 = deltaTheta === 0 ? 0 : ( theta - startTheta ) / deltaTheta;
|
||||
const t2 = ( phi - startPhi ) / deltaPhi;
|
||||
|
||||
const nextThetaIndex = deltaTheta === 0 ? thetaIndex : thetaIndex + 1;
|
||||
|
||||
const v1 = MathUtils.lerp( iesLamp.candelaValues[ thetaIndex ][ phiIndex ], iesLamp.candelaValues[ nextThetaIndex ][ phiIndex ], t1 );
|
||||
const v2 = MathUtils.lerp( iesLamp.candelaValues[ thetaIndex ][ phiIndex + 1 ], iesLamp.candelaValues[ nextThetaIndex ][ phiIndex + 1 ], t1 );
|
||||
const v = MathUtils.lerp( v1, v2, t2 );
|
||||
|
||||
return v;
|
||||
|
||||
}
|
||||
|
||||
const startTheta = iesLamp.horAngles[ 0 ], endTheta = iesLamp.horAngles[ iesLamp.numHorAngles - 1 ];
|
||||
|
||||
for ( let i = 0; i < size; ++ i ) {
|
||||
|
||||
let theta = i % width;
|
||||
const phi = Math.floor( i / width );
|
||||
|
||||
if ( endTheta - startTheta !== 0 && ( theta < startTheta || theta >= endTheta ) ) { // Handle symmetry for hor angles
|
||||
|
||||
theta %= endTheta * 2;
|
||||
|
||||
if ( theta > endTheta )
|
||||
theta = endTheta * 2 - theta;
|
||||
|
||||
}
|
||||
|
||||
data[ phi + theta * height ] = interpolateCandelaValues( phi, theta );
|
||||
|
||||
}
|
||||
|
||||
let result = null;
|
||||
|
||||
if ( type === UnsignedByteType ) result = Uint8Array.from( data.map( v => Math.min( v * 0xFF, 0xFF ) ) );
|
||||
else if ( type === HalfFloatType ) result = Uint16Array.from( data.map( v => DataUtils.toHalfFloat( v ) ) );
|
||||
else if ( type === FloatType ) result = Float32Array.from( data );
|
||||
else console.error( 'IESLoader: Unsupported type:', type );
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
load( url, onLoad, onProgress, onError ) {
|
||||
|
||||
const loader = new FileLoader( this.manager );
|
||||
loader.setResponseType( 'text' );
|
||||
loader.setCrossOrigin( this.crossOrigin );
|
||||
loader.setWithCredentials( this.withCredentials );
|
||||
loader.setPath( this.path );
|
||||
loader.setRequestHeader( this.requestHeader );
|
||||
|
||||
loader.load( url, text => {
|
||||
|
||||
onLoad( this.parse( text ) );
|
||||
|
||||
}, onProgress, onError );
|
||||
|
||||
}
|
||||
|
||||
parse( text ) {
|
||||
|
||||
const type = this.type;
|
||||
|
||||
const iesLamp = new IESLamp( text );
|
||||
const data = this._getIESValues( iesLamp, type );
|
||||
|
||||
const texture = new DataTexture( data, 180, 1, RedFormat, type );
|
||||
texture.minFilter = LinearFilter;
|
||||
texture.magFilter = LinearFilter;
|
||||
texture.needsUpdate = true;
|
||||
|
||||
return texture;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
function IESLamp( text ) {
|
||||
|
||||
const _self = this;
|
||||
|
||||
const textArray = text.split( '\n' );
|
||||
|
||||
let lineNumber = 0;
|
||||
let line;
|
||||
|
||||
_self.verAngles = [ ];
|
||||
_self.horAngles = [ ];
|
||||
|
||||
_self.candelaValues = [ ];
|
||||
|
||||
_self.tiltData = { };
|
||||
_self.tiltData.angles = [ ];
|
||||
_self.tiltData.mulFactors = [ ];
|
||||
|
||||
function textToArray( text ) {
|
||||
|
||||
text = text.replace( /^\s+|\s+$/g, '' ); // remove leading or trailing spaces
|
||||
text = text.replace( /,/g, ' ' ); // replace commas with spaces
|
||||
text = text.replace( /\s\s+/g, ' ' ); // replace white space/tabs etc by single whitespace
|
||||
|
||||
const array = text.split( ' ' );
|
||||
|
||||
return array;
|
||||
|
||||
}
|
||||
|
||||
function readArray( count, array ) {
|
||||
|
||||
while ( true ) {
|
||||
|
||||
const line = textArray[ lineNumber ++ ];
|
||||
const lineData = textToArray( line );
|
||||
|
||||
for ( let i = 0; i < lineData.length; ++ i ) {
|
||||
|
||||
array.push( Number( lineData[ i ] ) );
|
||||
|
||||
}
|
||||
|
||||
if ( array.length === count )
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function readTilt() {
|
||||
|
||||
let line = textArray[ lineNumber ++ ];
|
||||
let lineData = textToArray( line );
|
||||
|
||||
_self.tiltData.lampToLumGeometry = Number( lineData[ 0 ] );
|
||||
|
||||
line = textArray[ lineNumber ++ ];
|
||||
lineData = textToArray( line );
|
||||
|
||||
_self.tiltData.numAngles = Number( lineData[ 0 ] );
|
||||
|
||||
readArray( _self.tiltData.numAngles, _self.tiltData.angles );
|
||||
readArray( _self.tiltData.numAngles, _self.tiltData.mulFactors );
|
||||
|
||||
}
|
||||
|
||||
function readLampValues() {
|
||||
|
||||
const values = [ ];
|
||||
readArray( 10, values );
|
||||
|
||||
_self.count = Number( values[ 0 ] );
|
||||
_self.lumens = Number( values[ 1 ] );
|
||||
_self.multiplier = Number( values[ 2 ] );
|
||||
_self.numVerAngles = Number( values[ 3 ] );
|
||||
_self.numHorAngles = Number( values[ 4 ] );
|
||||
_self.gonioType = Number( values[ 5 ] );
|
||||
_self.units = Number( values[ 6 ] );
|
||||
_self.width = Number( values[ 7 ] );
|
||||
_self.length = Number( values[ 8 ] );
|
||||
_self.height = Number( values[ 9 ] );
|
||||
|
||||
}
|
||||
|
||||
function readLampFactors() {
|
||||
|
||||
const values = [ ];
|
||||
readArray( 3, values );
|
||||
|
||||
_self.ballFactor = Number( values[ 0 ] );
|
||||
_self.blpFactor = Number( values[ 1 ] );
|
||||
_self.inputWatts = Number( values[ 2 ] );
|
||||
|
||||
}
|
||||
|
||||
while ( true ) {
|
||||
|
||||
line = textArray[ lineNumber ++ ];
|
||||
|
||||
if ( line.includes( 'TILT' ) ) {
|
||||
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if ( ! line.includes( 'NONE' ) ) {
|
||||
|
||||
if ( line.includes( 'INCLUDE' ) ) {
|
||||
|
||||
readTilt();
|
||||
|
||||
} else {
|
||||
|
||||
// TODO:: Read tilt data from a file
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
readLampValues();
|
||||
|
||||
readLampFactors();
|
||||
|
||||
// Initialize candela value array
|
||||
for ( let i = 0; i < _self.numHorAngles; ++ i ) {
|
||||
|
||||
_self.candelaValues.push( [ ] );
|
||||
|
||||
}
|
||||
|
||||
// Parse Angles
|
||||
readArray( _self.numVerAngles, _self.verAngles );
|
||||
readArray( _self.numHorAngles, _self.horAngles );
|
||||
|
||||
// Parse Candela values
|
||||
for ( let i = 0; i < _self.numHorAngles; ++ i ) {
|
||||
|
||||
readArray( _self.numVerAngles, _self.candelaValues[ i ] );
|
||||
|
||||
}
|
||||
|
||||
// Calculate actual candela values, and normalize.
|
||||
for ( let i = 0; i < _self.numHorAngles; ++ i ) {
|
||||
|
||||
for ( let j = 0; j < _self.numVerAngles; ++ j ) {
|
||||
|
||||
_self.candelaValues[ i ][ j ] *= _self.candelaValues[ i ][ j ] * _self.multiplier
|
||||
* _self.ballFactor * _self.blpFactor;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
let maxVal = - 1;
|
||||
for ( let i = 0; i < _self.numHorAngles; ++ i ) {
|
||||
|
||||
for ( let j = 0; j < _self.numVerAngles; ++ j ) {
|
||||
|
||||
const value = _self.candelaValues[ i ][ j ];
|
||||
maxVal = maxVal < value ? value : maxVal;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const bNormalize = true;
|
||||
if ( bNormalize && maxVal > 0 ) {
|
||||
|
||||
for ( let i = 0; i < _self.numHorAngles; ++ i ) {
|
||||
|
||||
for ( let j = 0; j < _self.numVerAngles; ++ j ) {
|
||||
|
||||
_self.candelaValues[ i ][ j ] /= maxVal;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
export { IESLoader };
|
||||
130
public/sdk/three/jsm/loaders/KMZLoader.js
Normal file
130
public/sdk/three/jsm/loaders/KMZLoader.js
Normal file
@ -0,0 +1,130 @@
|
||||
import {
|
||||
FileLoader,
|
||||
Group,
|
||||
Loader,
|
||||
LoadingManager
|
||||
} from 'three';
|
||||
import { ColladaLoader } from '../loaders/ColladaLoader.js';
|
||||
import * as fflate from '../libs/fflate.module.js';
|
||||
|
||||
class KMZLoader extends Loader {
|
||||
|
||||
constructor( manager ) {
|
||||
|
||||
super( manager );
|
||||
|
||||
}
|
||||
|
||||
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.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( data ) {
|
||||
|
||||
function findFile( url ) {
|
||||
|
||||
for ( const path in zip ) {
|
||||
|
||||
if ( path.slice( - url.length ) === url ) {
|
||||
|
||||
return zip[ path ];
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const manager = new LoadingManager();
|
||||
manager.setURLModifier( function ( url ) {
|
||||
|
||||
const image = findFile( url );
|
||||
|
||||
if ( image ) {
|
||||
|
||||
console.log( 'Loading', url );
|
||||
|
||||
const blob = new Blob( [ image.buffer ], { type: 'application/octet-stream' } );
|
||||
return URL.createObjectURL( blob );
|
||||
|
||||
}
|
||||
|
||||
return url;
|
||||
|
||||
} );
|
||||
|
||||
//
|
||||
|
||||
const zip = fflate.unzipSync( new Uint8Array( data ) );
|
||||
|
||||
if ( zip[ 'doc.kml' ] ) {
|
||||
|
||||
const xml = new DOMParser().parseFromString( fflate.strFromU8( zip[ 'doc.kml' ] ), 'application/xml' );
|
||||
|
||||
const model = xml.querySelector( 'Placemark Model Link href' );
|
||||
|
||||
if ( model ) {
|
||||
|
||||
const loader = new ColladaLoader( manager );
|
||||
return loader.parse( fflate.strFromU8( zip[ model.textContent ] ) );
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
console.warn( 'KMZLoader: Missing doc.kml file.' );
|
||||
|
||||
for ( const path in zip ) {
|
||||
|
||||
const extension = path.split( '.' ).pop().toLowerCase();
|
||||
|
||||
if ( extension === 'dae' ) {
|
||||
|
||||
const loader = new ColladaLoader( manager );
|
||||
return loader.parse( fflate.strFromU8( zip[ path ] ) );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
console.error( 'KMZLoader: Couldn\'t find .dae file.' );
|
||||
return { scene: new Group() };
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export { KMZLoader };
|
||||
925
public/sdk/three/jsm/loaders/KTX2Loader.js
Normal file
925
public/sdk/three/jsm/loaders/KTX2Loader.js
Normal file
@ -0,0 +1,925 @@
|
||||
/**
|
||||
* Loader for KTX 2.0 GPU Texture containers.
|
||||
*
|
||||
* KTX 2.0 is a container format for various GPU texture formats. The loader
|
||||
* supports Basis Universal GPU textures, which can be quickly transcoded to
|
||||
* a wide variety of GPU texture compression formats, as well as some
|
||||
* uncompressed DataTexture and Data3DTexture formats.
|
||||
*
|
||||
* References:
|
||||
* - KTX: http://github.khronos.org/KTX-Specification/
|
||||
* - DFD: https://www.khronos.org/registry/DataFormat/specs/1.3/dataformat.1.3.html#basicdescriptor
|
||||
*/
|
||||
|
||||
import {
|
||||
CompressedTexture,
|
||||
CompressedArrayTexture,
|
||||
CompressedCubeTexture,
|
||||
Data3DTexture,
|
||||
DataTexture,
|
||||
DisplayP3ColorSpace,
|
||||
FileLoader,
|
||||
FloatType,
|
||||
HalfFloatType,
|
||||
NoColorSpace,
|
||||
LinearFilter,
|
||||
LinearMipmapLinearFilter,
|
||||
LinearDisplayP3ColorSpace,
|
||||
LinearSRGBColorSpace,
|
||||
Loader,
|
||||
RedFormat,
|
||||
RGB_ETC1_Format,
|
||||
RGB_ETC2_Format,
|
||||
RGB_PVRTC_4BPPV1_Format,
|
||||
RGBA_ASTC_4x4_Format,
|
||||
RGBA_ASTC_6x6_Format,
|
||||
RGBA_BPTC_Format,
|
||||
RGBA_ETC2_EAC_Format,
|
||||
RGBA_PVRTC_4BPPV1_Format,
|
||||
RGBA_S3TC_DXT5_Format,
|
||||
RGBA_S3TC_DXT1_Format,
|
||||
RGBAFormat,
|
||||
RGFormat,
|
||||
SRGBColorSpace,
|
||||
UnsignedByteType,
|
||||
} from 'three';
|
||||
import { WorkerPool } from '../utils/WorkerPool.js';
|
||||
import {
|
||||
read,
|
||||
KHR_DF_FLAG_ALPHA_PREMULTIPLIED,
|
||||
KHR_DF_TRANSFER_SRGB,
|
||||
KHR_SUPERCOMPRESSION_NONE,
|
||||
KHR_SUPERCOMPRESSION_ZSTD,
|
||||
VK_FORMAT_UNDEFINED,
|
||||
VK_FORMAT_R16_SFLOAT,
|
||||
VK_FORMAT_R16G16_SFLOAT,
|
||||
VK_FORMAT_R16G16B16A16_SFLOAT,
|
||||
VK_FORMAT_R32_SFLOAT,
|
||||
VK_FORMAT_R32G32_SFLOAT,
|
||||
VK_FORMAT_R32G32B32A32_SFLOAT,
|
||||
VK_FORMAT_R8_SRGB,
|
||||
VK_FORMAT_R8_UNORM,
|
||||
VK_FORMAT_R8G8_SRGB,
|
||||
VK_FORMAT_R8G8_UNORM,
|
||||
VK_FORMAT_R8G8B8A8_SRGB,
|
||||
VK_FORMAT_R8G8B8A8_UNORM,
|
||||
VK_FORMAT_ASTC_6x6_SRGB_BLOCK,
|
||||
VK_FORMAT_ASTC_6x6_UNORM_BLOCK,
|
||||
KHR_DF_PRIMARIES_UNSPECIFIED,
|
||||
KHR_DF_PRIMARIES_BT709,
|
||||
KHR_DF_PRIMARIES_DISPLAYP3
|
||||
} from '../libs/ktx-parse.module.js';
|
||||
import { ZSTDDecoder } from '../libs/zstddec.module.js';
|
||||
|
||||
const _taskCache = new WeakMap();
|
||||
|
||||
let _activeLoaders = 0;
|
||||
|
||||
let _zstd;
|
||||
|
||||
class KTX2Loader extends Loader {
|
||||
|
||||
constructor( manager ) {
|
||||
|
||||
super( manager );
|
||||
|
||||
this.transcoderPath = '';
|
||||
this.transcoderBinary = null;
|
||||
this.transcoderPending = null;
|
||||
|
||||
this.workerPool = new WorkerPool();
|
||||
this.workerSourceURL = '';
|
||||
this.workerConfig = null;
|
||||
|
||||
if ( typeof MSC_TRANSCODER !== 'undefined' ) {
|
||||
|
||||
console.warn(
|
||||
|
||||
'THREE.KTX2Loader: Please update to latest "basis_transcoder".'
|
||||
+ ' "msc_basis_transcoder" is no longer supported in three.js r125+.'
|
||||
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
setTranscoderPath( path ) {
|
||||
|
||||
this.transcoderPath = path;
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
setWorkerLimit( num ) {
|
||||
|
||||
this.workerPool.setWorkerLimit( num );
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
async detectSupportAsync( renderer ) {
|
||||
|
||||
this.workerConfig = {
|
||||
astcSupported: await renderer.hasFeatureAsync( 'texture-compression-astc' ),
|
||||
etc1Supported: await renderer.hasFeatureAsync( 'texture-compression-etc1' ),
|
||||
etc2Supported: await renderer.hasFeatureAsync( 'texture-compression-etc2' ),
|
||||
dxtSupported: await renderer.hasFeatureAsync( 'texture-compression-bc' ),
|
||||
bptcSupported: await renderer.hasFeatureAsync( 'texture-compression-bptc' ),
|
||||
pvrtcSupported: await renderer.hasFeatureAsync( 'texture-compression-pvrtc' )
|
||||
};
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
detectSupport( renderer ) {
|
||||
|
||||
if ( renderer.isWebGPURenderer === true ) {
|
||||
|
||||
this.workerConfig = {
|
||||
astcSupported: renderer.hasFeature( 'texture-compression-astc' ),
|
||||
etc1Supported: renderer.hasFeature( 'texture-compression-etc1' ),
|
||||
etc2Supported: renderer.hasFeature( 'texture-compression-etc2' ),
|
||||
dxtSupported: renderer.hasFeature( 'texture-compression-bc' ),
|
||||
bptcSupported: renderer.hasFeature( 'texture-compression-bptc' ),
|
||||
pvrtcSupported: renderer.hasFeature( 'texture-compression-pvrtc' )
|
||||
};
|
||||
|
||||
} else {
|
||||
|
||||
this.workerConfig = {
|
||||
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;
|
||||
|
||||
}
|
||||
|
||||
init() {
|
||||
|
||||
if ( ! this.transcoderPending ) {
|
||||
|
||||
// Load transcoder wrapper.
|
||||
const jsLoader = new FileLoader( this.manager );
|
||||
jsLoader.setPath( this.transcoderPath );
|
||||
jsLoader.setWithCredentials( this.withCredentials );
|
||||
const jsContent = jsLoader.loadAsync( 'basis_transcoder.js' );
|
||||
|
||||
// Load transcoder WASM binary.
|
||||
const binaryLoader = new FileLoader( this.manager );
|
||||
binaryLoader.setPath( this.transcoderPath );
|
||||
binaryLoader.setResponseType( 'arraybuffer' );
|
||||
binaryLoader.setWithCredentials( this.withCredentials );
|
||||
const binaryContent = binaryLoader.loadAsync( 'basis_transcoder.wasm' );
|
||||
|
||||
this.transcoderPending = Promise.all( [ jsContent, binaryContent ] )
|
||||
.then( ( [ jsContent, binaryContent ] ) => {
|
||||
|
||||
const fn = KTX2Loader.BasisWorker.toString();
|
||||
|
||||
const body = [
|
||||
'/* constants */',
|
||||
'let _EngineFormat = ' + JSON.stringify( KTX2Loader.EngineFormat ),
|
||||
'let _TranscoderFormat = ' + JSON.stringify( KTX2Loader.TranscoderFormat ),
|
||||
'let _BasisFormat = ' + JSON.stringify( KTX2Loader.BasisFormat ),
|
||||
'/* basis_transcoder.js */',
|
||||
jsContent,
|
||||
'/* worker */',
|
||||
fn.substring( fn.indexOf( '{' ) + 1, fn.lastIndexOf( '}' ) )
|
||||
].join( '\n' );
|
||||
|
||||
this.workerSourceURL = URL.createObjectURL( new Blob( [ body ] ) );
|
||||
this.transcoderBinary = binaryContent;
|
||||
|
||||
this.workerPool.setWorkerCreator( () => {
|
||||
|
||||
const worker = new Worker( this.workerSourceURL );
|
||||
const transcoderBinary = this.transcoderBinary.slice( 0 );
|
||||
|
||||
worker.postMessage( { type: 'init', config: this.workerConfig, transcoderBinary }, [ transcoderBinary ] );
|
||||
|
||||
return worker;
|
||||
|
||||
} );
|
||||
|
||||
} );
|
||||
|
||||
if ( _activeLoaders > 0 ) {
|
||||
|
||||
// Each instance loads a transcoder and allocates workers, increasing network and memory cost.
|
||||
|
||||
console.warn(
|
||||
|
||||
'THREE.KTX2Loader: Multiple active KTX2 loaders may cause performance issues.'
|
||||
+ ' Use a single KTX2Loader instance, or call .dispose() on old instances.'
|
||||
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
_activeLoaders ++;
|
||||
|
||||
}
|
||||
|
||||
return this.transcoderPending;
|
||||
|
||||
}
|
||||
|
||||
load( url, onLoad, onProgress, onError ) {
|
||||
|
||||
if ( this.workerConfig === null ) {
|
||||
|
||||
throw new Error( 'THREE.KTX2Loader: Missing initialization with `.detectSupport( renderer )`.' );
|
||||
|
||||
}
|
||||
|
||||
const 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 ( _taskCache.has( buffer ) ) {
|
||||
|
||||
const cachedTask = _taskCache.get( buffer );
|
||||
|
||||
return cachedTask.promise.then( onLoad ).catch( onError );
|
||||
|
||||
}
|
||||
|
||||
this._createTexture( buffer )
|
||||
.then( ( texture ) => onLoad ? onLoad( texture ) : null )
|
||||
.catch( onError );
|
||||
|
||||
}, onProgress, onError );
|
||||
|
||||
}
|
||||
|
||||
_createTextureFrom( transcodeResult, container ) {
|
||||
|
||||
const { faces, width, height, format, type, error, dfdFlags } = transcodeResult;
|
||||
|
||||
if ( type === 'error' ) return Promise.reject( error );
|
||||
|
||||
let texture;
|
||||
|
||||
if ( container.faceCount === 6 ) {
|
||||
|
||||
texture = new CompressedCubeTexture( faces, format, UnsignedByteType );
|
||||
|
||||
} else {
|
||||
|
||||
const mipmaps = faces[ 0 ].mipmaps;
|
||||
|
||||
texture = container.layerCount > 1
|
||||
? new CompressedArrayTexture( mipmaps, width, height, container.layerCount, format, UnsignedByteType )
|
||||
: new CompressedTexture( mipmaps, width, height, format, UnsignedByteType );
|
||||
|
||||
}
|
||||
|
||||
texture.minFilter = faces[ 0 ].mipmaps.length === 1 ? LinearFilter : LinearMipmapLinearFilter;
|
||||
texture.magFilter = LinearFilter;
|
||||
texture.generateMipmaps = false;
|
||||
|
||||
texture.needsUpdate = true;
|
||||
texture.colorSpace = parseColorSpace( container );
|
||||
texture.premultiplyAlpha = !! ( dfdFlags & KHR_DF_FLAG_ALPHA_PREMULTIPLIED );
|
||||
|
||||
return texture;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {ArrayBuffer} buffer
|
||||
* @param {object?} config
|
||||
* @return {Promise<CompressedTexture|CompressedArrayTexture|DataTexture|Data3DTexture>}
|
||||
*/
|
||||
async _createTexture( buffer, config = {} ) {
|
||||
|
||||
const container = read( new Uint8Array( buffer ) );
|
||||
|
||||
if ( container.vkFormat !== VK_FORMAT_UNDEFINED ) {
|
||||
|
||||
return createRawTexture( container );
|
||||
|
||||
}
|
||||
|
||||
//
|
||||
const taskConfig = config;
|
||||
const texturePending = this.init().then( () => {
|
||||
|
||||
return this.workerPool.postMessage( { type: 'transcode', buffer, taskConfig: taskConfig }, [ buffer ] );
|
||||
|
||||
} ).then( ( e ) => this._createTextureFrom( e.data, container ) );
|
||||
|
||||
// Cache the task result.
|
||||
_taskCache.set( buffer, { promise: texturePending } );
|
||||
|
||||
return texturePending;
|
||||
|
||||
}
|
||||
|
||||
dispose() {
|
||||
|
||||
this.workerPool.dispose();
|
||||
if ( this.workerSourceURL ) URL.revokeObjectURL( this.workerSourceURL );
|
||||
|
||||
_activeLoaders --;
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/* CONSTANTS */
|
||||
|
||||
KTX2Loader.BasisFormat = {
|
||||
ETC1S: 0,
|
||||
UASTC_4x4: 1,
|
||||
};
|
||||
|
||||
KTX2Loader.TranscoderFormat = {
|
||||
ETC1: 0,
|
||||
ETC2: 1,
|
||||
BC1: 2,
|
||||
BC3: 3,
|
||||
BC4: 4,
|
||||
BC5: 5,
|
||||
BC7_M6_OPAQUE_ONLY: 6,
|
||||
BC7_M5: 7,
|
||||
PVRTC1_4_RGB: 8,
|
||||
PVRTC1_4_RGBA: 9,
|
||||
ASTC_4x4: 10,
|
||||
ATC_RGB: 11,
|
||||
ATC_RGBA_INTERPOLATED_ALPHA: 12,
|
||||
RGBA32: 13,
|
||||
RGB565: 14,
|
||||
BGR565: 15,
|
||||
RGBA4444: 16,
|
||||
};
|
||||
|
||||
KTX2Loader.EngineFormat = {
|
||||
RGBAFormat: RGBAFormat,
|
||||
RGBA_ASTC_4x4_Format: RGBA_ASTC_4x4_Format,
|
||||
RGBA_BPTC_Format: RGBA_BPTC_Format,
|
||||
RGBA_ETC2_EAC_Format: RGBA_ETC2_EAC_Format,
|
||||
RGBA_PVRTC_4BPPV1_Format: RGBA_PVRTC_4BPPV1_Format,
|
||||
RGBA_S3TC_DXT5_Format: RGBA_S3TC_DXT5_Format,
|
||||
RGB_ETC1_Format: RGB_ETC1_Format,
|
||||
RGB_ETC2_Format: RGB_ETC2_Format,
|
||||
RGB_PVRTC_4BPPV1_Format: RGB_PVRTC_4BPPV1_Format,
|
||||
RGBA_S3TC_DXT1_Format: RGBA_S3TC_DXT1_Format,
|
||||
};
|
||||
|
||||
|
||||
/* WEB WORKER */
|
||||
|
||||
KTX2Loader.BasisWorker = function () {
|
||||
|
||||
let config;
|
||||
let transcoderPending;
|
||||
let BasisModule;
|
||||
|
||||
const EngineFormat = _EngineFormat; // eslint-disable-line no-undef
|
||||
const TranscoderFormat = _TranscoderFormat; // eslint-disable-line no-undef
|
||||
const BasisFormat = _BasisFormat; // eslint-disable-line no-undef
|
||||
|
||||
self.addEventListener( 'message', function ( e ) {
|
||||
|
||||
const message = e.data;
|
||||
|
||||
switch ( message.type ) {
|
||||
|
||||
case 'init':
|
||||
config = message.config;
|
||||
init( message.transcoderBinary );
|
||||
break;
|
||||
|
||||
case 'transcode':
|
||||
transcoderPending.then( () => {
|
||||
|
||||
try {
|
||||
|
||||
const { faces, buffers, width, height, hasAlpha, format, dfdFlags } = transcode( message.buffer );
|
||||
|
||||
self.postMessage( { type: 'transcode', id: message.id, faces, width, height, hasAlpha, format, dfdFlags }, buffers );
|
||||
|
||||
} catch ( error ) {
|
||||
|
||||
console.error( error );
|
||||
|
||||
self.postMessage( { type: 'error', id: message.id, error: error.message } );
|
||||
|
||||
}
|
||||
|
||||
} );
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
} );
|
||||
|
||||
function init( wasmBinary ) {
|
||||
|
||||
transcoderPending = new Promise( ( resolve ) => {
|
||||
|
||||
BasisModule = { wasmBinary, onRuntimeInitialized: resolve };
|
||||
BASIS( BasisModule ); // eslint-disable-line no-undef
|
||||
|
||||
} ).then( () => {
|
||||
|
||||
BasisModule.initializeBasis();
|
||||
|
||||
if ( BasisModule.KTX2File === undefined ) {
|
||||
|
||||
console.warn( 'THREE.KTX2Loader: Please update Basis Universal transcoder.' );
|
||||
|
||||
}
|
||||
|
||||
} );
|
||||
|
||||
}
|
||||
|
||||
function transcode( buffer ) {
|
||||
|
||||
const ktx2File = new BasisModule.KTX2File( new Uint8Array( buffer ) );
|
||||
|
||||
function cleanup() {
|
||||
|
||||
ktx2File.close();
|
||||
ktx2File.delete();
|
||||
|
||||
}
|
||||
|
||||
if ( ! ktx2File.isValid() ) {
|
||||
|
||||
cleanup();
|
||||
throw new Error( 'THREE.KTX2Loader: Invalid or unsupported .ktx2 file' );
|
||||
|
||||
}
|
||||
|
||||
const basisFormat = ktx2File.isUASTC() ? BasisFormat.UASTC_4x4 : BasisFormat.ETC1S;
|
||||
const width = ktx2File.getWidth();
|
||||
const height = ktx2File.getHeight();
|
||||
const layerCount = ktx2File.getLayers() || 1;
|
||||
const levelCount = ktx2File.getLevels();
|
||||
const faceCount = ktx2File.getFaces();
|
||||
const hasAlpha = ktx2File.getHasAlpha();
|
||||
const dfdFlags = ktx2File.getDFDFlags();
|
||||
|
||||
const { transcoderFormat, engineFormat } = getTranscoderFormat( basisFormat, width, height, hasAlpha );
|
||||
|
||||
if ( ! width || ! height || ! levelCount ) {
|
||||
|
||||
cleanup();
|
||||
throw new Error( 'THREE.KTX2Loader: Invalid texture' );
|
||||
|
||||
}
|
||||
|
||||
if ( ! ktx2File.startTranscoding() ) {
|
||||
|
||||
cleanup();
|
||||
throw new Error( 'THREE.KTX2Loader: .startTranscoding failed' );
|
||||
|
||||
}
|
||||
|
||||
const faces = [];
|
||||
const buffers = [];
|
||||
|
||||
for ( let face = 0; face < faceCount; face ++ ) {
|
||||
|
||||
const mipmaps = [];
|
||||
|
||||
for ( let mip = 0; mip < levelCount; mip ++ ) {
|
||||
|
||||
const layerMips = [];
|
||||
|
||||
let mipWidth, mipHeight;
|
||||
|
||||
for ( let layer = 0; layer < layerCount; layer ++ ) {
|
||||
|
||||
const levelInfo = ktx2File.getImageLevelInfo( mip, layer, face );
|
||||
|
||||
if ( face === 0 && mip === 0 && layer === 0 && ( levelInfo.origWidth % 4 !== 0 || levelInfo.origHeight % 4 !== 0 ) ) {
|
||||
|
||||
console.warn( 'THREE.KTX2Loader: ETC1S and UASTC textures should use multiple-of-four dimensions.' );
|
||||
|
||||
}
|
||||
|
||||
if ( levelCount > 1 ) {
|
||||
|
||||
mipWidth = levelInfo.origWidth;
|
||||
mipHeight = levelInfo.origHeight;
|
||||
|
||||
} else {
|
||||
|
||||
// Handles non-multiple-of-four dimensions in textures without mipmaps. Textures with
|
||||
// mipmaps must use multiple-of-four dimensions, for some texture formats and APIs.
|
||||
// See mrdoob/three.js#25908.
|
||||
mipWidth = levelInfo.width;
|
||||
mipHeight = levelInfo.height;
|
||||
|
||||
}
|
||||
|
||||
const dst = new Uint8Array( ktx2File.getImageTranscodedSizeInBytes( mip, layer, 0, transcoderFormat ) );
|
||||
const status = ktx2File.transcodeImage( dst, mip, layer, face, transcoderFormat, 0, - 1, - 1 );
|
||||
|
||||
if ( ! status ) {
|
||||
|
||||
cleanup();
|
||||
throw new Error( 'THREE.KTX2Loader: .transcodeImage failed.' );
|
||||
|
||||
}
|
||||
|
||||
layerMips.push( dst );
|
||||
|
||||
}
|
||||
|
||||
const mipData = concat( layerMips );
|
||||
|
||||
mipmaps.push( { data: mipData, width: mipWidth, height: mipHeight } );
|
||||
buffers.push( mipData.buffer );
|
||||
|
||||
}
|
||||
|
||||
faces.push( { mipmaps, width, height, format: engineFormat } );
|
||||
|
||||
}
|
||||
|
||||
cleanup();
|
||||
|
||||
return { faces, buffers, width, height, hasAlpha, format: engineFormat, dfdFlags };
|
||||
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
// Optimal choice of a transcoder target format depends on the Basis format (ETC1S or UASTC),
|
||||
// device capabilities, and texture dimensions. The list below ranks the formats separately
|
||||
// for ETC1S and UASTC.
|
||||
//
|
||||
// In some cases, transcoding UASTC to RGBA32 might be preferred for higher quality (at
|
||||
// significant memory cost) compared to ETC1/2, BC1/3, and PVRTC. The transcoder currently
|
||||
// chooses RGBA32 only as a last resort and does not expose that option to the caller.
|
||||
const FORMAT_OPTIONS = [
|
||||
{
|
||||
if: 'astcSupported',
|
||||
basisFormat: [ BasisFormat.UASTC_4x4 ],
|
||||
transcoderFormat: [ TranscoderFormat.ASTC_4x4, TranscoderFormat.ASTC_4x4 ],
|
||||
engineFormat: [ EngineFormat.RGBA_ASTC_4x4_Format, EngineFormat.RGBA_ASTC_4x4_Format ],
|
||||
priorityETC1S: Infinity,
|
||||
priorityUASTC: 1,
|
||||
needsPowerOfTwo: false,
|
||||
},
|
||||
{
|
||||
if: 'bptcSupported',
|
||||
basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC_4x4 ],
|
||||
transcoderFormat: [ TranscoderFormat.BC7_M5, TranscoderFormat.BC7_M5 ],
|
||||
engineFormat: [ EngineFormat.RGBA_BPTC_Format, EngineFormat.RGBA_BPTC_Format ],
|
||||
priorityETC1S: 3,
|
||||
priorityUASTC: 2,
|
||||
needsPowerOfTwo: false,
|
||||
},
|
||||
{
|
||||
if: 'dxtSupported',
|
||||
basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC_4x4 ],
|
||||
transcoderFormat: [ TranscoderFormat.BC1, TranscoderFormat.BC3 ],
|
||||
engineFormat: [ EngineFormat.RGBA_S3TC_DXT1_Format, EngineFormat.RGBA_S3TC_DXT5_Format ],
|
||||
priorityETC1S: 4,
|
||||
priorityUASTC: 5,
|
||||
needsPowerOfTwo: false,
|
||||
},
|
||||
{
|
||||
if: 'etc2Supported',
|
||||
basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC_4x4 ],
|
||||
transcoderFormat: [ TranscoderFormat.ETC1, TranscoderFormat.ETC2 ],
|
||||
engineFormat: [ EngineFormat.RGB_ETC2_Format, EngineFormat.RGBA_ETC2_EAC_Format ],
|
||||
priorityETC1S: 1,
|
||||
priorityUASTC: 3,
|
||||
needsPowerOfTwo: false,
|
||||
},
|
||||
{
|
||||
if: 'etc1Supported',
|
||||
basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC_4x4 ],
|
||||
transcoderFormat: [ TranscoderFormat.ETC1 ],
|
||||
engineFormat: [ EngineFormat.RGB_ETC1_Format ],
|
||||
priorityETC1S: 2,
|
||||
priorityUASTC: 4,
|
||||
needsPowerOfTwo: false,
|
||||
},
|
||||
{
|
||||
if: 'pvrtcSupported',
|
||||
basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC_4x4 ],
|
||||
transcoderFormat: [ TranscoderFormat.PVRTC1_4_RGB, TranscoderFormat.PVRTC1_4_RGBA ],
|
||||
engineFormat: [ EngineFormat.RGB_PVRTC_4BPPV1_Format, EngineFormat.RGBA_PVRTC_4BPPV1_Format ],
|
||||
priorityETC1S: 5,
|
||||
priorityUASTC: 6,
|
||||
needsPowerOfTwo: true,
|
||||
},
|
||||
];
|
||||
|
||||
const ETC1S_OPTIONS = FORMAT_OPTIONS.sort( function ( a, b ) {
|
||||
|
||||
return a.priorityETC1S - b.priorityETC1S;
|
||||
|
||||
} );
|
||||
const UASTC_OPTIONS = FORMAT_OPTIONS.sort( function ( a, b ) {
|
||||
|
||||
return a.priorityUASTC - b.priorityUASTC;
|
||||
|
||||
} );
|
||||
|
||||
function getTranscoderFormat( basisFormat, width, height, hasAlpha ) {
|
||||
|
||||
let transcoderFormat;
|
||||
let engineFormat;
|
||||
|
||||
const options = basisFormat === BasisFormat.ETC1S ? ETC1S_OPTIONS : UASTC_OPTIONS;
|
||||
|
||||
for ( let i = 0; i < options.length; i ++ ) {
|
||||
|
||||
const opt = options[ i ];
|
||||
|
||||
if ( ! config[ opt.if ] ) continue;
|
||||
if ( ! opt.basisFormat.includes( basisFormat ) ) continue;
|
||||
if ( hasAlpha && opt.transcoderFormat.length < 2 ) continue;
|
||||
if ( opt.needsPowerOfTwo && ! ( isPowerOfTwo( width ) && isPowerOfTwo( height ) ) ) continue;
|
||||
|
||||
transcoderFormat = opt.transcoderFormat[ hasAlpha ? 1 : 0 ];
|
||||
engineFormat = opt.engineFormat[ hasAlpha ? 1 : 0 ];
|
||||
|
||||
return { transcoderFormat, engineFormat };
|
||||
|
||||
}
|
||||
|
||||
console.warn( 'THREE.KTX2Loader: No suitable compressed texture format found. Decoding to RGBA32.' );
|
||||
|
||||
transcoderFormat = TranscoderFormat.RGBA32;
|
||||
engineFormat = EngineFormat.RGBAFormat;
|
||||
|
||||
return { transcoderFormat, engineFormat };
|
||||
|
||||
}
|
||||
|
||||
function isPowerOfTwo( value ) {
|
||||
|
||||
if ( value <= 2 ) return true;
|
||||
|
||||
return ( value & ( value - 1 ) ) === 0 && value !== 0;
|
||||
|
||||
}
|
||||
|
||||
/** Concatenates N byte arrays. */
|
||||
function concat( arrays ) {
|
||||
|
||||
if ( arrays.length === 1 ) return arrays[ 0 ];
|
||||
|
||||
let totalByteLength = 0;
|
||||
|
||||
for ( let i = 0; i < arrays.length; i ++ ) {
|
||||
|
||||
const array = arrays[ i ];
|
||||
totalByteLength += array.byteLength;
|
||||
|
||||
}
|
||||
|
||||
const result = new Uint8Array( totalByteLength );
|
||||
|
||||
let byteOffset = 0;
|
||||
|
||||
for ( let i = 0; i < arrays.length; i ++ ) {
|
||||
|
||||
const array = arrays[ i ];
|
||||
result.set( array, byteOffset );
|
||||
|
||||
byteOffset += array.byteLength;
|
||||
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
//
|
||||
// Parsing for non-Basis textures. These textures are may have supercompression
|
||||
// like Zstd, but they do not require transcoding.
|
||||
|
||||
const UNCOMPRESSED_FORMATS = new Set( [ RGBAFormat, RGFormat, RedFormat ] );
|
||||
|
||||
const FORMAT_MAP = {
|
||||
|
||||
[ VK_FORMAT_R32G32B32A32_SFLOAT ]: RGBAFormat,
|
||||
[ VK_FORMAT_R16G16B16A16_SFLOAT ]: RGBAFormat,
|
||||
[ VK_FORMAT_R8G8B8A8_UNORM ]: RGBAFormat,
|
||||
[ VK_FORMAT_R8G8B8A8_SRGB ]: RGBAFormat,
|
||||
|
||||
[ VK_FORMAT_R32G32_SFLOAT ]: RGFormat,
|
||||
[ VK_FORMAT_R16G16_SFLOAT ]: RGFormat,
|
||||
[ VK_FORMAT_R8G8_UNORM ]: RGFormat,
|
||||
[ VK_FORMAT_R8G8_SRGB ]: RGFormat,
|
||||
|
||||
[ VK_FORMAT_R32_SFLOAT ]: RedFormat,
|
||||
[ VK_FORMAT_R16_SFLOAT ]: RedFormat,
|
||||
[ VK_FORMAT_R8_SRGB ]: RedFormat,
|
||||
[ VK_FORMAT_R8_UNORM ]: RedFormat,
|
||||
|
||||
[ VK_FORMAT_ASTC_6x6_SRGB_BLOCK ]: RGBA_ASTC_6x6_Format,
|
||||
[ VK_FORMAT_ASTC_6x6_UNORM_BLOCK ]: RGBA_ASTC_6x6_Format,
|
||||
|
||||
};
|
||||
|
||||
const TYPE_MAP = {
|
||||
|
||||
[ VK_FORMAT_R32G32B32A32_SFLOAT ]: FloatType,
|
||||
[ VK_FORMAT_R16G16B16A16_SFLOAT ]: HalfFloatType,
|
||||
[ VK_FORMAT_R8G8B8A8_UNORM ]: UnsignedByteType,
|
||||
[ VK_FORMAT_R8G8B8A8_SRGB ]: UnsignedByteType,
|
||||
|
||||
[ VK_FORMAT_R32G32_SFLOAT ]: FloatType,
|
||||
[ VK_FORMAT_R16G16_SFLOAT ]: HalfFloatType,
|
||||
[ VK_FORMAT_R8G8_UNORM ]: UnsignedByteType,
|
||||
[ VK_FORMAT_R8G8_SRGB ]: UnsignedByteType,
|
||||
|
||||
[ VK_FORMAT_R32_SFLOAT ]: FloatType,
|
||||
[ VK_FORMAT_R16_SFLOAT ]: HalfFloatType,
|
||||
[ VK_FORMAT_R8_SRGB ]: UnsignedByteType,
|
||||
[ VK_FORMAT_R8_UNORM ]: UnsignedByteType,
|
||||
|
||||
[ VK_FORMAT_ASTC_6x6_SRGB_BLOCK ]: UnsignedByteType,
|
||||
[ VK_FORMAT_ASTC_6x6_UNORM_BLOCK ]: UnsignedByteType,
|
||||
|
||||
};
|
||||
|
||||
async function createRawTexture( container ) {
|
||||
|
||||
const { vkFormat } = container;
|
||||
|
||||
if ( FORMAT_MAP[ vkFormat ] === undefined ) {
|
||||
|
||||
throw new Error( 'THREE.KTX2Loader: Unsupported vkFormat.' );
|
||||
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
let zstd;
|
||||
|
||||
if ( container.supercompressionScheme === KHR_SUPERCOMPRESSION_ZSTD ) {
|
||||
|
||||
if ( ! _zstd ) {
|
||||
|
||||
_zstd = new Promise( async ( resolve ) => {
|
||||
|
||||
const zstd = new ZSTDDecoder();
|
||||
await zstd.init();
|
||||
resolve( zstd );
|
||||
|
||||
} );
|
||||
|
||||
}
|
||||
|
||||
zstd = await _zstd;
|
||||
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
const mipmaps = [];
|
||||
|
||||
|
||||
for ( let levelIndex = 0; levelIndex < container.levels.length; levelIndex ++ ) {
|
||||
|
||||
const levelWidth = Math.max( 1, container.pixelWidth >> levelIndex );
|
||||
const levelHeight = Math.max( 1, container.pixelHeight >> levelIndex );
|
||||
const levelDepth = container.pixelDepth ? Math.max( 1, container.pixelDepth >> levelIndex ) : 0;
|
||||
|
||||
const level = container.levels[ levelIndex ];
|
||||
|
||||
let levelData;
|
||||
|
||||
if ( container.supercompressionScheme === KHR_SUPERCOMPRESSION_NONE ) {
|
||||
|
||||
levelData = level.levelData;
|
||||
|
||||
} else if ( container.supercompressionScheme === KHR_SUPERCOMPRESSION_ZSTD ) {
|
||||
|
||||
levelData = zstd.decode( level.levelData, level.uncompressedByteLength );
|
||||
|
||||
} else {
|
||||
|
||||
throw new Error( 'THREE.KTX2Loader: Unsupported supercompressionScheme.' );
|
||||
|
||||
}
|
||||
|
||||
let data;
|
||||
|
||||
if ( TYPE_MAP[ vkFormat ] === FloatType ) {
|
||||
|
||||
data = new Float32Array(
|
||||
|
||||
levelData.buffer,
|
||||
levelData.byteOffset,
|
||||
levelData.byteLength / Float32Array.BYTES_PER_ELEMENT
|
||||
|
||||
);
|
||||
|
||||
} else if ( TYPE_MAP[ vkFormat ] === HalfFloatType ) {
|
||||
|
||||
data = new Uint16Array(
|
||||
|
||||
levelData.buffer,
|
||||
levelData.byteOffset,
|
||||
levelData.byteLength / Uint16Array.BYTES_PER_ELEMENT
|
||||
|
||||
);
|
||||
|
||||
} else {
|
||||
|
||||
data = levelData;
|
||||
|
||||
}
|
||||
|
||||
mipmaps.push( {
|
||||
|
||||
data: data,
|
||||
width: levelWidth,
|
||||
height: levelHeight,
|
||||
depth: levelDepth,
|
||||
|
||||
} );
|
||||
|
||||
}
|
||||
|
||||
let texture;
|
||||
|
||||
if ( UNCOMPRESSED_FORMATS.has( FORMAT_MAP[ vkFormat ] ) ) {
|
||||
|
||||
texture = container.pixelDepth === 0
|
||||
? new DataTexture( mipmaps[ 0 ].data, container.pixelWidth, container.pixelHeight )
|
||||
: new Data3DTexture( mipmaps[ 0 ].data, container.pixelWidth, container.pixelHeight, container.pixelDepth );
|
||||
|
||||
} else {
|
||||
|
||||
if ( container.pixelDepth > 0 ) throw new Error( 'THREE.KTX2Loader: Unsupported pixelDepth.' );
|
||||
|
||||
texture = new CompressedTexture( mipmaps, container.pixelWidth, container.pixelHeight );
|
||||
|
||||
}
|
||||
|
||||
texture.mipmaps = mipmaps;
|
||||
|
||||
texture.type = TYPE_MAP[ vkFormat ];
|
||||
texture.format = FORMAT_MAP[ vkFormat ];
|
||||
texture.colorSpace = parseColorSpace( container );
|
||||
texture.needsUpdate = true;
|
||||
|
||||
//
|
||||
|
||||
return Promise.resolve( texture );
|
||||
|
||||
}
|
||||
|
||||
function parseColorSpace( container ) {
|
||||
|
||||
const dfd = container.dataFormatDescriptor[ 0 ];
|
||||
|
||||
if ( dfd.colorPrimaries === KHR_DF_PRIMARIES_BT709 ) {
|
||||
|
||||
return dfd.transferFunction === KHR_DF_TRANSFER_SRGB ? SRGBColorSpace : LinearSRGBColorSpace;
|
||||
|
||||
} else if ( dfd.colorPrimaries === KHR_DF_PRIMARIES_DISPLAYP3 ) {
|
||||
|
||||
return dfd.transferFunction === KHR_DF_TRANSFER_SRGB ? DisplayP3ColorSpace : LinearDisplayP3ColorSpace;
|
||||
|
||||
} else if ( dfd.colorPrimaries === KHR_DF_PRIMARIES_UNSPECIFIED ) {
|
||||
|
||||
return NoColorSpace;
|
||||
|
||||
} else {
|
||||
|
||||
console.warn( `THREE.KTX2Loader: Unsupported color primaries, "${ dfd.colorPrimaries }"` );
|
||||
return NoColorSpace;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export { KTX2Loader };
|
||||
176
public/sdk/three/jsm/loaders/KTXLoader.js
Normal file
176
public/sdk/three/jsm/loaders/KTXLoader.js
Normal file
@ -0,0 +1,176 @@
|
||||
import {
|
||||
CompressedTextureLoader
|
||||
} from 'three';
|
||||
|
||||
/**
|
||||
* 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/Misc/khronosTextureContainer.ts
|
||||
*/
|
||||
|
||||
|
||||
class KTXLoader extends CompressedTextureLoader {
|
||||
|
||||
constructor( manager ) {
|
||||
|
||||
super( manager );
|
||||
|
||||
}
|
||||
|
||||
parse( buffer, loadMipmaps ) {
|
||||
|
||||
const 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
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
const HEADER_LEN = 12 + ( 13 * 4 ); // identifier + header elements (not including key value meta-data pairs)
|
||||
// load types
|
||||
const COMPRESSED_2D = 0; // uses a gl.compressedTexImage2D()
|
||||
//const COMPRESSED_3D = 1; // uses a gl.compressedTexImage3D()
|
||||
//const TEX_2D = 2; // uses a gl.texImage2D()
|
||||
//const TEX_3D = 3; // uses a gl.texImage3D()
|
||||
|
||||
class KhronosTextureContainer {
|
||||
|
||||
/**
|
||||
* @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
|
||||
*/
|
||||
constructor( 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
|
||||
const 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
|
||||
const dataSize = Uint32Array.BYTES_PER_ELEMENT;
|
||||
const headerDataView = new DataView( this.arrayBuffer, 12, 13 * dataSize );
|
||||
const endianness = headerDataView.getUint32( 0, true );
|
||||
const 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 = COMPRESSED_2D;
|
||||
|
||||
}
|
||||
|
||||
mipmaps( loadMipmaps ) {
|
||||
|
||||
const mipmaps = [];
|
||||
|
||||
// initialize width & height for level 1
|
||||
let dataOffset = HEADER_LEN + this.bytesOfKeyValueData;
|
||||
let width = this.pixelWidth;
|
||||
let height = this.pixelHeight;
|
||||
const mipmapCount = loadMipmaps ? this.numberOfMipmapLevels : 1;
|
||||
|
||||
for ( let level = 0; level < mipmapCount; level ++ ) {
|
||||
|
||||
const 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 ( let face = 0; face < this.numberOfFaces; face ++ ) {
|
||||
|
||||
const 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;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export { KTXLoader };
|
||||
2470
public/sdk/three/jsm/loaders/LDrawLoader.js
Normal file
2470
public/sdk/three/jsm/loaders/LDrawLoader.js
Normal file
File diff suppressed because it is too large
Load Diff
168
public/sdk/three/jsm/loaders/LUT3dlLoader.js
Normal file
168
public/sdk/three/jsm/loaders/LUT3dlLoader.js
Normal file
@ -0,0 +1,168 @@
|
||||
// http://download.autodesk.com/us/systemdocs/help/2011/lustre/index.html?url=./files/WSc4e151a45a3b785a24c3d9a411df9298473-7ffd.htm,topicNumber=d0e9492
|
||||
// https://community.foundry.com/discuss/topic/103636/format-spec-for-3dl?mode=Post&postID=895258
|
||||
|
||||
import {
|
||||
ClampToEdgeWrapping,
|
||||
Data3DTexture,
|
||||
FileLoader,
|
||||
FloatType,
|
||||
LinearFilter,
|
||||
Loader,
|
||||
RGBAFormat,
|
||||
UnsignedByteType,
|
||||
} from 'three';
|
||||
|
||||
export class LUT3dlLoader extends Loader {
|
||||
|
||||
constructor( manager ) {
|
||||
|
||||
super( manager );
|
||||
|
||||
this.type = UnsignedByteType;
|
||||
|
||||
}
|
||||
|
||||
setType( type ) {
|
||||
|
||||
if ( type !== UnsignedByteType && type !== FloatType ) {
|
||||
|
||||
throw new Error( 'LUT3dlLoader: Unsupported type' );
|
||||
|
||||
}
|
||||
|
||||
this.type = type;
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
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( input ) {
|
||||
|
||||
const regExpGridInfo = /^[\d ]+$/m;
|
||||
const regExpDataPoints = /^([\d.e+-]+) +([\d.e+-]+) +([\d.e+-]+) *$/gm;
|
||||
|
||||
// The first line describes the positions of values on the LUT grid.
|
||||
let result = regExpGridInfo.exec( input );
|
||||
|
||||
if ( result === null ) {
|
||||
|
||||
throw new Error( 'LUT3dlLoader: Missing grid information' );
|
||||
|
||||
}
|
||||
|
||||
const gridLines = result[ 0 ].trim().split( /\s+/g ).map( Number );
|
||||
const gridStep = gridLines[ 1 ] - gridLines[ 0 ];
|
||||
const size = gridLines.length;
|
||||
const sizeSq = size ** 2;
|
||||
|
||||
for ( let i = 1, l = gridLines.length; i < l; ++ i ) {
|
||||
|
||||
if ( gridStep !== ( gridLines[ i ] - gridLines[ i - 1 ] ) ) {
|
||||
|
||||
throw new Error( 'LUT3dlLoader: Inconsistent grid size' );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const dataFloat = new Float32Array( size ** 3 * 4 );
|
||||
let maxValue = 0.0;
|
||||
let index = 0;
|
||||
|
||||
while ( ( result = regExpDataPoints.exec( input ) ) !== null ) {
|
||||
|
||||
const r = Number( result[ 1 ] );
|
||||
const g = Number( result[ 2 ] );
|
||||
const b = Number( result[ 3 ] );
|
||||
|
||||
maxValue = Math.max( maxValue, r, g, b );
|
||||
|
||||
const bLayer = index % size;
|
||||
const gLayer = Math.floor( index / size ) % size;
|
||||
const rLayer = Math.floor( index / ( sizeSq ) ) % size;
|
||||
|
||||
// b grows first, then g, then r.
|
||||
const d4 = ( bLayer * sizeSq + gLayer * size + rLayer ) * 4;
|
||||
dataFloat[ d4 + 0 ] = r;
|
||||
dataFloat[ d4 + 1 ] = g;
|
||||
dataFloat[ d4 + 2 ] = b;
|
||||
|
||||
++ index;
|
||||
|
||||
}
|
||||
|
||||
// Determine the bit depth to scale the values to [0.0, 1.0].
|
||||
const bits = Math.ceil( Math.log2( maxValue ) );
|
||||
const maxBitValue = Math.pow( 2, bits );
|
||||
|
||||
const data = this.type === UnsignedByteType ? new Uint8Array( dataFloat.length ) : dataFloat;
|
||||
const scale = this.type === UnsignedByteType ? 255 : 1;
|
||||
|
||||
for ( let i = 0, l = data.length; i < l; i += 4 ) {
|
||||
|
||||
const i1 = i + 1;
|
||||
const i2 = i + 2;
|
||||
const i3 = i + 3;
|
||||
|
||||
// Note: data is dataFloat when type is FloatType.
|
||||
data[ i ] = dataFloat[ i ] / maxBitValue * scale;
|
||||
data[ i1 ] = dataFloat[ i1 ] / maxBitValue * scale;
|
||||
data[ i2 ] = dataFloat[ i2 ] / maxBitValue * scale;
|
||||
data[ i3 ] = scale;
|
||||
|
||||
}
|
||||
|
||||
const texture3D = new Data3DTexture();
|
||||
texture3D.image.data = data;
|
||||
texture3D.image.width = size;
|
||||
texture3D.image.height = size;
|
||||
texture3D.image.depth = size;
|
||||
texture3D.format = RGBAFormat;
|
||||
texture3D.type = this.type;
|
||||
texture3D.magFilter = LinearFilter;
|
||||
texture3D.minFilter = LinearFilter;
|
||||
texture3D.wrapS = ClampToEdgeWrapping;
|
||||
texture3D.wrapT = ClampToEdgeWrapping;
|
||||
texture3D.wrapR = ClampToEdgeWrapping;
|
||||
texture3D.generateMipmaps = false;
|
||||
texture3D.needsUpdate = true;
|
||||
|
||||
return {
|
||||
size,
|
||||
texture3D,
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
153
public/sdk/three/jsm/loaders/LUTCubeLoader.js
Normal file
153
public/sdk/three/jsm/loaders/LUTCubeLoader.js
Normal file
@ -0,0 +1,153 @@
|
||||
// https://wwwimages2.adobe.com/content/dam/acom/en/products/speedgrade/cc/pdfs/cube-lut-specification-1.0.pdf
|
||||
|
||||
import {
|
||||
ClampToEdgeWrapping,
|
||||
Data3DTexture,
|
||||
FileLoader,
|
||||
FloatType,
|
||||
LinearFilter,
|
||||
Loader,
|
||||
UnsignedByteType,
|
||||
Vector3,
|
||||
} from 'three';
|
||||
|
||||
export class LUTCubeLoader extends Loader {
|
||||
|
||||
constructor( manager ) {
|
||||
|
||||
super( manager );
|
||||
|
||||
this.type = UnsignedByteType;
|
||||
|
||||
}
|
||||
|
||||
setType( type ) {
|
||||
|
||||
if ( type !== UnsignedByteType && type !== FloatType ) {
|
||||
|
||||
throw new Error( 'LUTCubeLoader: Unsupported type' );
|
||||
|
||||
}
|
||||
|
||||
this.type = type;
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
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( input ) {
|
||||
|
||||
const regExpTitle = /TITLE +"([^"]*)"/;
|
||||
const regExpSize = /LUT_3D_SIZE +(\d+)/;
|
||||
const regExpDomainMin = /DOMAIN_MIN +([\d.]+) +([\d.]+) +([\d.]+)/;
|
||||
const regExpDomainMax = /DOMAIN_MAX +([\d.]+) +([\d.]+) +([\d.]+)/;
|
||||
const regExpDataPoints = /^([\d.e+-]+) +([\d.e+-]+) +([\d.e+-]+) *$/gm;
|
||||
|
||||
let result = regExpTitle.exec( input );
|
||||
const title = ( result !== null ) ? result[ 1 ] : null;
|
||||
|
||||
result = regExpSize.exec( input );
|
||||
|
||||
if ( result === null ) {
|
||||
|
||||
throw new Error( 'LUTCubeLoader: Missing LUT_3D_SIZE information' );
|
||||
|
||||
}
|
||||
|
||||
const size = Number( result[ 1 ] );
|
||||
const length = size ** 3 * 4;
|
||||
const data = this.type === UnsignedByteType ? new Uint8Array( length ) : new Float32Array( length );
|
||||
|
||||
const domainMin = new Vector3( 0, 0, 0 );
|
||||
const domainMax = new Vector3( 1, 1, 1 );
|
||||
|
||||
result = regExpDomainMin.exec( input );
|
||||
|
||||
if ( result !== null ) {
|
||||
|
||||
domainMin.set( Number( result[ 1 ] ), Number( result[ 2 ] ), Number( result[ 3 ] ) );
|
||||
|
||||
}
|
||||
|
||||
result = regExpDomainMax.exec( input );
|
||||
|
||||
if ( result !== null ) {
|
||||
|
||||
domainMax.set( Number( result[ 1 ] ), Number( result[ 2 ] ), Number( result[ 3 ] ) );
|
||||
|
||||
}
|
||||
|
||||
if ( domainMin.x > domainMax.x || domainMin.y > domainMax.y || domainMin.z > domainMax.z ) {
|
||||
|
||||
throw new Error( 'LUTCubeLoader: Invalid input domain' );
|
||||
|
||||
}
|
||||
|
||||
const scale = this.type === UnsignedByteType ? 255 : 1;
|
||||
let i = 0;
|
||||
|
||||
while ( ( result = regExpDataPoints.exec( input ) ) !== null ) {
|
||||
|
||||
data[ i ++ ] = Number( result[ 1 ] ) * scale;
|
||||
data[ i ++ ] = Number( result[ 2 ] ) * scale;
|
||||
data[ i ++ ] = Number( result[ 3 ] ) * scale;
|
||||
data[ i ++ ] = scale;
|
||||
|
||||
}
|
||||
|
||||
const texture3D = new Data3DTexture();
|
||||
texture3D.image.data = data;
|
||||
texture3D.image.width = size;
|
||||
texture3D.image.height = size;
|
||||
texture3D.image.depth = size;
|
||||
texture3D.type = this.type;
|
||||
texture3D.magFilter = LinearFilter;
|
||||
texture3D.minFilter = LinearFilter;
|
||||
texture3D.wrapS = ClampToEdgeWrapping;
|
||||
texture3D.wrapT = ClampToEdgeWrapping;
|
||||
texture3D.wrapR = ClampToEdgeWrapping;
|
||||
texture3D.generateMipmaps = false;
|
||||
texture3D.needsUpdate = true;
|
||||
|
||||
return {
|
||||
title,
|
||||
size,
|
||||
domainMin,
|
||||
domainMax,
|
||||
texture3D,
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
149
public/sdk/three/jsm/loaders/LUTImageLoader.js
Normal file
149
public/sdk/three/jsm/loaders/LUTImageLoader.js
Normal file
@ -0,0 +1,149 @@
|
||||
import {
|
||||
Loader,
|
||||
TextureLoader,
|
||||
Data3DTexture,
|
||||
RGBAFormat,
|
||||
UnsignedByteType,
|
||||
ClampToEdgeWrapping,
|
||||
LinearFilter,
|
||||
} from 'three';
|
||||
|
||||
export class LUTImageLoader extends Loader {
|
||||
|
||||
constructor( flipVertical = false ) {
|
||||
|
||||
//The NeutralLUT.png has green at the bottom for Unreal ang green at the top for Unity URP Color Lookup
|
||||
//post-processing. If you're using lut image strips from a Unity pipeline then pass true to the constructor
|
||||
|
||||
super();
|
||||
|
||||
this.flip = flipVertical;
|
||||
|
||||
}
|
||||
|
||||
load( url, onLoad, onProgress, onError ) {
|
||||
|
||||
const loader = new TextureLoader( this.manager );
|
||||
|
||||
loader.setCrossOrigin( this.crossOrigin );
|
||||
|
||||
loader.setPath( this.path );
|
||||
loader.load( url, texture => {
|
||||
|
||||
try {
|
||||
|
||||
let imageData;
|
||||
|
||||
if ( texture.image.width < texture.image.height ) {
|
||||
|
||||
imageData = this.getImageData( texture );
|
||||
|
||||
} else {
|
||||
|
||||
imageData = this.horz2Vert( texture );
|
||||
|
||||
}
|
||||
|
||||
onLoad( this.parse( imageData.data, Math.min( texture.image.width, texture.image.height ) ) );
|
||||
|
||||
} catch ( e ) {
|
||||
|
||||
if ( onError ) {
|
||||
|
||||
onError( e );
|
||||
|
||||
} else {
|
||||
|
||||
console.error( e );
|
||||
|
||||
}
|
||||
|
||||
this.manager.itemError( url );
|
||||
|
||||
}
|
||||
|
||||
}, onProgress, onError );
|
||||
|
||||
}
|
||||
|
||||
getImageData( texture ) {
|
||||
|
||||
const width = texture.image.width;
|
||||
const height = texture.image.height;
|
||||
|
||||
const canvas = document.createElement( 'canvas' );
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
|
||||
const context = canvas.getContext( '2d' );
|
||||
|
||||
if ( this.flip === true ) {
|
||||
|
||||
context.scale( 1, - 1 );
|
||||
context.translate( 0, - height );
|
||||
|
||||
}
|
||||
|
||||
context.drawImage( texture.image, 0, 0 );
|
||||
|
||||
return context.getImageData( 0, 0, width, height );
|
||||
|
||||
}
|
||||
|
||||
horz2Vert( texture ) {
|
||||
|
||||
const width = texture.image.height;
|
||||
const height = texture.image.width;
|
||||
|
||||
const canvas = document.createElement( 'canvas' );
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
|
||||
const context = canvas.getContext( '2d' );
|
||||
|
||||
if ( this.flip === true ) {
|
||||
|
||||
context.scale( 1, - 1 );
|
||||
context.translate( 0, - height );
|
||||
|
||||
}
|
||||
|
||||
for ( let i = 0; i < width; i ++ ) {
|
||||
|
||||
const sy = i * width;
|
||||
const dy = ( this.flip ) ? height - i * width : i * width;
|
||||
context.drawImage( texture.image, sy, 0, width, width, 0, dy, width, width );
|
||||
|
||||
}
|
||||
|
||||
return context.getImageData( 0, 0, width, height );
|
||||
|
||||
}
|
||||
|
||||
parse( dataArray, size ) {
|
||||
|
||||
const data = new Uint8Array( dataArray );
|
||||
|
||||
const texture3D = new Data3DTexture();
|
||||
texture3D.image.data = data;
|
||||
texture3D.image.width = size;
|
||||
texture3D.image.height = size;
|
||||
texture3D.image.depth = size;
|
||||
texture3D.format = RGBAFormat;
|
||||
texture3D.type = UnsignedByteType;
|
||||
texture3D.magFilter = LinearFilter;
|
||||
texture3D.minFilter = LinearFilter;
|
||||
texture3D.wrapS = ClampToEdgeWrapping;
|
||||
texture3D.wrapT = ClampToEdgeWrapping;
|
||||
texture3D.wrapR = ClampToEdgeWrapping;
|
||||
texture3D.generateMipmaps = false;
|
||||
texture3D.needsUpdate = true;
|
||||
|
||||
return {
|
||||
size,
|
||||
texture3D,
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
1052
public/sdk/three/jsm/loaders/LWOLoader.js
Normal file
1052
public/sdk/three/jsm/loaders/LWOLoader.js
Normal file
File diff suppressed because it is too large
Load Diff
606
public/sdk/three/jsm/loaders/LogLuvLoader.js
Normal file
606
public/sdk/three/jsm/loaders/LogLuvLoader.js
Normal file
@ -0,0 +1,606 @@
|
||||
import {
|
||||
DataUtils,
|
||||
DataTextureLoader,
|
||||
FloatType,
|
||||
HalfFloatType,
|
||||
RGBAFormat
|
||||
} from 'three';
|
||||
|
||||
class LogLuvLoader extends DataTextureLoader {
|
||||
|
||||
constructor( manager ) {
|
||||
|
||||
super( manager );
|
||||
|
||||
this.type = HalfFloatType;
|
||||
|
||||
}
|
||||
|
||||
parse( buffer ) {
|
||||
|
||||
const ifds = UTIF.decode( buffer );
|
||||
UTIF.decodeImage( buffer, ifds[ 0 ] );
|
||||
const rgba = UTIF.toRGBA( ifds[ 0 ], this.type );
|
||||
|
||||
return {
|
||||
width: ifds[ 0 ].width,
|
||||
height: ifds[ 0 ].height,
|
||||
data: rgba,
|
||||
format: RGBAFormat,
|
||||
type: this.type,
|
||||
flipY: true
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
setDataType( value ) {
|
||||
|
||||
this.type = value;
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// from https://github.com/photopea/UTIF.js (MIT License)
|
||||
|
||||
const UTIF = {};
|
||||
|
||||
UTIF.decode = function ( buff, prm ) {
|
||||
|
||||
if ( prm == null ) prm = { parseMN: true, debug: false }; // read MakerNote, debug
|
||||
var data = new Uint8Array( buff ), offset = 0;
|
||||
|
||||
var id = UTIF._binBE.readASCII( data, offset, 2 ); offset += 2;
|
||||
var bin = id == 'II' ? UTIF._binLE : UTIF._binBE;
|
||||
bin.readUshort( data, offset ); offset += 2;
|
||||
|
||||
var ifdo = bin.readUint( data, offset );
|
||||
var ifds = [];
|
||||
while ( true ) {
|
||||
|
||||
var cnt = bin.readUshort( data, ifdo ), typ = bin.readUshort( data, ifdo + 4 ); if ( cnt != 0 ) if ( typ < 1 || 13 < typ ) {
|
||||
|
||||
console.log( 'error in TIFF' ); break;
|
||||
|
||||
}
|
||||
|
||||
|
||||
UTIF._readIFD( bin, data, ifdo, ifds, 0, prm );
|
||||
|
||||
ifdo = bin.readUint( data, ifdo + 2 + cnt * 12 );
|
||||
if ( ifdo == 0 ) break;
|
||||
|
||||
}
|
||||
|
||||
return ifds;
|
||||
|
||||
};
|
||||
|
||||
UTIF.decodeImage = function ( buff, img, ifds ) {
|
||||
|
||||
if ( img.data ) return;
|
||||
var data = new Uint8Array( buff );
|
||||
var id = UTIF._binBE.readASCII( data, 0, 2 );
|
||||
|
||||
if ( img[ 't256' ] == null ) return; // No width => probably not an image
|
||||
img.isLE = id == 'II';
|
||||
img.width = img[ 't256' ][ 0 ]; //delete img["t256"];
|
||||
img.height = img[ 't257' ][ 0 ]; //delete img["t257"];
|
||||
|
||||
var cmpr = img[ 't259' ] ? img[ 't259' ][ 0 ] : 1; //delete img["t259"];
|
||||
var fo = img[ 't266' ] ? img[ 't266' ][ 0 ] : 1; //delete img["t266"];
|
||||
if ( img[ 't284' ] && img[ 't284' ][ 0 ] == 2 ) console.log( 'PlanarConfiguration 2 should not be used!' );
|
||||
if ( cmpr == 7 && img[ 't258' ] && img[ 't258' ].length > 3 ) img[ 't258' ] = img[ 't258' ].slice( 0, 3 );
|
||||
|
||||
var bipp; // bits per pixel
|
||||
if ( img[ 't258' ] ) bipp = Math.min( 32, img[ 't258' ][ 0 ] ) * img[ 't258' ].length;
|
||||
else bipp = ( img[ 't277' ] ? img[ 't277' ][ 0 ] : 1 );
|
||||
// Some .NEF files have t258==14, even though they use 16 bits per pixel
|
||||
if ( cmpr == 1 && img[ 't279' ] != null && img[ 't278' ] && img[ 't262' ][ 0 ] == 32803 ) {
|
||||
|
||||
bipp = Math.round( ( img[ 't279' ][ 0 ] * 8 ) / ( img.width * img[ 't278' ][ 0 ] ) );
|
||||
|
||||
}
|
||||
|
||||
var bipl = Math.ceil( img.width * bipp / 8 ) * 8;
|
||||
var soff = img[ 't273' ]; if ( soff == null ) soff = img[ 't324' ];
|
||||
var bcnt = img[ 't279' ]; if ( cmpr == 1 && soff.length == 1 ) bcnt = [ img.height * ( bipl >>> 3 ) ]; if ( bcnt == null ) bcnt = img[ 't325' ];
|
||||
//bcnt[0] = Math.min(bcnt[0], data.length); // Hasselblad, "RAW_HASSELBLAD_H3D39II.3FR"
|
||||
var bytes = new Uint8Array( img.height * ( bipl >>> 3 ) ), bilen = 0;
|
||||
|
||||
if ( img[ 't322' ] != null ) {
|
||||
|
||||
var tw = img[ 't322' ][ 0 ], th = img[ 't323' ][ 0 ];
|
||||
var tx = Math.floor( ( img.width + tw - 1 ) / tw );
|
||||
var ty = Math.floor( ( img.height + th - 1 ) / th );
|
||||
var tbuff = new Uint8Array( Math.ceil( tw * th * bipp / 8 ) | 0 );
|
||||
for ( var y = 0; y < ty; y ++ )
|
||||
for ( var x = 0; x < tx; x ++ ) {
|
||||
|
||||
var i = y * tx + x; for ( var j = 0; j < tbuff.length; j ++ ) tbuff[ j ] = 0;
|
||||
UTIF.decode._decompress( img, ifds, data, soff[ i ], bcnt[ i ], cmpr, tbuff, 0, fo );
|
||||
// Might be required for 7 too. Need to check
|
||||
if ( cmpr == 6 ) bytes = tbuff;
|
||||
else UTIF._copyTile( tbuff, Math.ceil( tw * bipp / 8 ) | 0, th, bytes, Math.ceil( img.width * bipp / 8 ) | 0, img.height, Math.ceil( x * tw * bipp / 8 ) | 0, y * th );
|
||||
|
||||
}
|
||||
|
||||
bilen = bytes.length * 8;
|
||||
|
||||
} else {
|
||||
|
||||
var rps = img[ 't278' ] ? img[ 't278' ][ 0 ] : img.height; rps = Math.min( rps, img.height );
|
||||
for ( var i = 0; i < soff.length; i ++ ) {
|
||||
|
||||
UTIF.decode._decompress( img, ifds, data, soff[ i ], bcnt[ i ], cmpr, bytes, Math.ceil( bilen / 8 ) | 0, fo );
|
||||
bilen += bipl * rps;
|
||||
|
||||
}
|
||||
|
||||
bilen = Math.min( bilen, bytes.length * 8 );
|
||||
|
||||
}
|
||||
|
||||
img.data = new Uint8Array( bytes.buffer, 0, Math.ceil( bilen / 8 ) | 0 );
|
||||
|
||||
};
|
||||
|
||||
UTIF.decode._decompress = function ( img, ifds, data, off, len, cmpr, tgt, toff ) {
|
||||
|
||||
//console.log("compression", cmpr);
|
||||
//var time = Date.now();
|
||||
if ( cmpr == 34676 ) UTIF.decode._decodeLogLuv32( img, data, off, len, tgt, toff );
|
||||
else console.log( 'Unsupported compression', cmpr );
|
||||
|
||||
//console.log(Date.now()-time);
|
||||
|
||||
var bps = ( img[ 't258' ] ? Math.min( 32, img[ 't258' ][ 0 ] ) : 1 );
|
||||
var noc = ( img[ 't277' ] ? img[ 't277' ][ 0 ] : 1 ), bpp = ( bps * noc ) >>> 3, h = ( img[ 't278' ] ? img[ 't278' ][ 0 ] : img.height ), bpl = Math.ceil( bps * noc * img.width / 8 );
|
||||
|
||||
// convert to Little Endian /*
|
||||
if ( bps == 16 && ! img.isLE && img[ 't33422' ] == null ) // not DNG
|
||||
for ( var y = 0; y < h; y ++ ) {
|
||||
|
||||
//console.log("fixing endianity");
|
||||
var roff = toff + y * bpl;
|
||||
for ( var x = 1; x < bpl; x += 2 ) {
|
||||
|
||||
var t = tgt[ roff + x ]; tgt[ roff + x ] = tgt[ roff + x - 1 ]; tgt[ roff + x - 1 ] = t;
|
||||
|
||||
}
|
||||
|
||||
} //*/
|
||||
|
||||
if ( img[ 't317' ] && img[ 't317' ][ 0 ] == 2 ) {
|
||||
|
||||
for ( var y = 0; y < h; y ++ ) {
|
||||
|
||||
var ntoff = toff + y * bpl;
|
||||
if ( bps == 16 ) for ( var j = bpp; j < bpl; j += 2 ) {
|
||||
|
||||
var nv = ( ( tgt[ ntoff + j + 1 ] << 8 ) | tgt[ ntoff + j ] ) + ( ( tgt[ ntoff + j - bpp + 1 ] << 8 ) | tgt[ ntoff + j - bpp ] );
|
||||
tgt[ ntoff + j ] = nv & 255; tgt[ ntoff + j + 1 ] = ( nv >>> 8 ) & 255;
|
||||
|
||||
}
|
||||
else if ( noc == 3 ) for ( var j = 3; j < bpl; j += 3 ) {
|
||||
|
||||
tgt[ ntoff + j ] = ( tgt[ ntoff + j ] + tgt[ ntoff + j - 3 ] ) & 255;
|
||||
tgt[ ntoff + j + 1 ] = ( tgt[ ntoff + j + 1 ] + tgt[ ntoff + j - 2 ] ) & 255;
|
||||
tgt[ ntoff + j + 2 ] = ( tgt[ ntoff + j + 2 ] + tgt[ ntoff + j - 1 ] ) & 255;
|
||||
|
||||
}
|
||||
else for ( var j = bpp; j < bpl; j ++ ) tgt[ ntoff + j ] = ( tgt[ ntoff + j ] + tgt[ ntoff + j - bpp ] ) & 255;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
UTIF.decode._decodeLogLuv32 = function ( img, data, off, len, tgt, toff ) {
|
||||
|
||||
var w = img.width, qw = w * 4;
|
||||
var io = 0, out = new Uint8Array( qw );
|
||||
|
||||
while ( io < len ) {
|
||||
|
||||
var oo = 0;
|
||||
while ( oo < qw ) {
|
||||
|
||||
var c = data[ off + io ]; io ++;
|
||||
if ( c < 128 ) {
|
||||
|
||||
for ( var j = 0; j < c; j ++ ) out[ oo + j ] = data[ off + io + j ]; oo += c; io += c;
|
||||
|
||||
} else {
|
||||
|
||||
c = c - 126; for ( var j = 0; j < c; j ++ ) out[ oo + j ] = data[ off + io ]; oo += c; io ++;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
for ( var x = 0; x < w; x ++ ) {
|
||||
|
||||
tgt[ toff + 0 ] = out[ x ];
|
||||
tgt[ toff + 1 ] = out[ x + w ];
|
||||
tgt[ toff + 2 ] = out[ x + w * 2 ];
|
||||
tgt[ toff + 4 ] = out[ x + w * 3 ];
|
||||
toff += 6;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
UTIF.tags = {};
|
||||
//UTIF.ttypes = { 256:3,257:3,258:3, 259:3, 262:3, 273:4, 274:3, 277:3,278:4,279:4, 282:5, 283:5, 284:3, 286:5,287:5, 296:3, 305:2, 306:2, 338:3, 513:4, 514:4, 34665:4 };
|
||||
// start at tag 250
|
||||
UTIF._types = function () {
|
||||
|
||||
var main = new Array( 250 ); main.fill( 0 );
|
||||
main = main.concat( [ 0, 0, 0, 0, 4, 3, 3, 3, 3, 3, 0, 0, 3, 0, 0, 0, 3, 0, 0, 2, 2, 2, 2, 4, 3, 0, 0, 3, 4, 4, 3, 3, 5, 5, 3, 2, 5, 5, 0, 0, 0, 0, 4, 4, 0, 0, 3, 3, 0, 0, 0, 0, 0, 0, 0, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 3, 5, 5, 3, 0, 3, 3, 4, 4, 4, 3, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ] );
|
||||
var rest = { 33432: 2, 33434: 5, 33437: 5, 34665: 4, 34850: 3, 34853: 4, 34855: 3, 34864: 3, 34866: 4, 36864: 7, 36867: 2, 36868: 2, 37121: 7, 37377: 10, 37378: 5, 37380: 10, 37381: 5, 37383: 3, 37384: 3, 37385: 3, 37386: 5, 37510: 7, 37520: 2, 37521: 2, 37522: 2, 40960: 7, 40961: 3, 40962: 4, 40963: 4, 40965: 4, 41486: 5, 41487: 5, 41488: 3, 41985: 3, 41986: 3, 41987: 3, 41988: 5, 41989: 3, 41990: 3, 41993: 3, 41994: 3, 41995: 7, 41996: 3, 42032: 2, 42033: 2, 42034: 5, 42036: 2, 42037: 2, 59932: 7 };
|
||||
return {
|
||||
basic: {
|
||||
main: main,
|
||||
rest: rest
|
||||
},
|
||||
gps: {
|
||||
main: [ 1, 2, 5, 2, 5, 1, 5, 5, 0, 9 ],
|
||||
rest: { 18: 2, 29: 2 }
|
||||
}
|
||||
};
|
||||
|
||||
}();
|
||||
|
||||
UTIF._readIFD = function ( bin, data, offset, ifds, depth, prm ) {
|
||||
|
||||
var cnt = bin.readUshort( data, offset ); offset += 2;
|
||||
var ifd = {};
|
||||
|
||||
if ( prm.debug ) console.log( ' '.repeat( depth ), ifds.length - 1, '>>>----------------' );
|
||||
for ( var i = 0; i < cnt; i ++ ) {
|
||||
|
||||
var tag = bin.readUshort( data, offset ); offset += 2;
|
||||
var type = bin.readUshort( data, offset ); offset += 2;
|
||||
var num = bin.readUint( data, offset ); offset += 4;
|
||||
var voff = bin.readUint( data, offset ); offset += 4;
|
||||
|
||||
var arr = [];
|
||||
//ifd["t"+tag+"-"+UTIF.tags[tag]] = arr;
|
||||
if ( type == 1 || type == 7 ) {
|
||||
|
||||
arr = new Uint8Array( data.buffer, ( num < 5 ? offset - 4 : voff ), num );
|
||||
|
||||
}
|
||||
|
||||
if ( type == 2 ) {
|
||||
|
||||
var o0 = ( num < 5 ? offset - 4 : voff ), c = data[ o0 ], len = Math.max( 0, Math.min( num - 1, data.length - o0 ) );
|
||||
if ( c < 128 || len == 0 ) arr.push( bin.readASCII( data, o0, len ) );
|
||||
else arr = new Uint8Array( data.buffer, o0, len );
|
||||
|
||||
}
|
||||
|
||||
if ( type == 3 ) {
|
||||
|
||||
for ( var j = 0; j < num; j ++ ) arr.push( bin.readUshort( data, ( num < 3 ? offset - 4 : voff ) + 2 * j ) );
|
||||
|
||||
}
|
||||
|
||||
if ( type == 4
|
||||
|| type == 13 ) {
|
||||
|
||||
for ( var j = 0; j < num; j ++ ) arr.push( bin.readUint( data, ( num < 2 ? offset - 4 : voff ) + 4 * j ) );
|
||||
|
||||
}
|
||||
|
||||
if ( type == 5 || type == 10 ) {
|
||||
|
||||
var ri = type == 5 ? bin.readUint : bin.readInt;
|
||||
for ( var j = 0; j < num; j ++ ) arr.push( [ ri( data, voff + j * 8 ), ri( data, voff + j * 8 + 4 ) ] );
|
||||
|
||||
}
|
||||
|
||||
if ( type == 8 ) {
|
||||
|
||||
for ( var j = 0; j < num; j ++ ) arr.push( bin.readShort( data, ( num < 3 ? offset - 4 : voff ) + 2 * j ) );
|
||||
|
||||
}
|
||||
|
||||
if ( type == 9 ) {
|
||||
|
||||
for ( var j = 0; j < num; j ++ ) arr.push( bin.readInt( data, ( num < 2 ? offset - 4 : voff ) + 4 * j ) );
|
||||
|
||||
}
|
||||
|
||||
if ( type == 11 ) {
|
||||
|
||||
for ( var j = 0; j < num; j ++ ) arr.push( bin.readFloat( data, voff + j * 4 ) );
|
||||
|
||||
}
|
||||
|
||||
if ( type == 12 ) {
|
||||
|
||||
for ( var j = 0; j < num; j ++ ) arr.push( bin.readDouble( data, voff + j * 8 ) );
|
||||
|
||||
}
|
||||
|
||||
if ( num != 0 && arr.length == 0 ) {
|
||||
|
||||
console.log( tag, 'unknown TIFF tag type: ', type, 'num:', num ); if ( i == 0 ) return; continue;
|
||||
|
||||
}
|
||||
|
||||
if ( prm.debug ) console.log( ' '.repeat( depth ), tag, type, UTIF.tags[ tag ], arr );
|
||||
|
||||
ifd[ 't' + tag ] = arr;
|
||||
|
||||
if ( tag == 330 || tag == 34665 || tag == 34853 || ( tag == 50740 && bin.readUshort( data, bin.readUint( arr, 0 ) ) < 300 ) || tag == 61440 ) {
|
||||
|
||||
var oarr = tag == 50740 ? [ bin.readUint( arr, 0 ) ] : arr;
|
||||
var subfd = [];
|
||||
for ( var j = 0; j < oarr.length; j ++ ) UTIF._readIFD( bin, data, oarr[ j ], subfd, depth + 1, prm );
|
||||
if ( tag == 330 ) ifd.subIFD = subfd;
|
||||
if ( tag == 34665 ) ifd.exifIFD = subfd[ 0 ];
|
||||
if ( tag == 34853 ) ifd.gpsiIFD = subfd[ 0 ]; //console.log("gps", subfd[0]); }
|
||||
if ( tag == 50740 ) ifd.dngPrvt = subfd[ 0 ];
|
||||
if ( tag == 61440 ) ifd.fujiIFD = subfd[ 0 ];
|
||||
|
||||
}
|
||||
|
||||
if ( tag == 37500 && prm.parseMN ) {
|
||||
|
||||
var mn = arr;
|
||||
//console.log(bin.readASCII(mn,0,mn.length), mn);
|
||||
if ( bin.readASCII( mn, 0, 5 ) == 'Nikon' ) ifd.makerNote = UTIF[ 'decode' ]( mn.slice( 10 ).buffer )[ 0 ];
|
||||
else if ( bin.readUshort( data, voff ) < 300 && bin.readUshort( data, voff + 4 ) <= 12 ) {
|
||||
|
||||
var subsub = []; UTIF._readIFD( bin, data, voff, subsub, depth + 1, prm );
|
||||
ifd.makerNote = subsub[ 0 ];
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ifds.push( ifd );
|
||||
if ( prm.debug ) console.log( ' '.repeat( depth ), '<<<---------------' );
|
||||
return offset;
|
||||
|
||||
};
|
||||
|
||||
UTIF.toRGBA = function ( out, type ) {
|
||||
|
||||
const w = out.width, h = out.height, area = w * h, data = out.data;
|
||||
|
||||
let img;
|
||||
|
||||
switch ( type ) {
|
||||
|
||||
case HalfFloatType:
|
||||
|
||||
img = new Uint16Array( area * 4 );
|
||||
break;
|
||||
|
||||
case FloatType:
|
||||
|
||||
img = new Float32Array( area * 4 );
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Error( 'THREE.LogLuvLoader: Unsupported texture data type: ' + type );
|
||||
|
||||
}
|
||||
|
||||
let intp = out[ 't262' ] ? out[ 't262' ][ 0 ] : 2;
|
||||
const bps = out[ 't258' ] ? Math.min( 32, out[ 't258' ][ 0 ] ) : 1;
|
||||
|
||||
if ( out[ 't262' ] == null && bps == 1 ) intp = 0;
|
||||
|
||||
if ( intp == 32845 ) {
|
||||
|
||||
for ( let y = 0; y < h; y ++ ) {
|
||||
|
||||
for ( let x = 0; x < w; x ++ ) {
|
||||
|
||||
const si = ( y * w + x ) * 6, qi = ( y * w + x ) * 4;
|
||||
let L = ( data[ si + 1 ] << 8 ) | data[ si ];
|
||||
|
||||
L = Math.pow( 2, ( L + 0.5 ) / 256 - 64 );
|
||||
const u = ( data[ si + 3 ] + 0.5 ) / 410;
|
||||
const v = ( data[ si + 5 ] + 0.5 ) / 410;
|
||||
|
||||
// Luv to xyY
|
||||
const sX = ( 9 * u ) / ( 6 * u - 16 * v + 12 );
|
||||
const sY = ( 4 * v ) / ( 6 * u - 16 * v + 12 );
|
||||
const bY = L;
|
||||
|
||||
// xyY to XYZ
|
||||
const X = ( sX * bY ) / sY, Y = bY, Z = ( 1 - sX - sY ) * bY / sY;
|
||||
|
||||
// XYZ to linear RGB
|
||||
const r = 2.690 * X - 1.276 * Y - 0.414 * Z;
|
||||
const g = - 1.022 * X + 1.978 * Y + 0.044 * Z;
|
||||
const b = 0.061 * X - 0.224 * Y + 1.163 * Z;
|
||||
|
||||
if ( type === HalfFloatType ) {
|
||||
|
||||
img[ qi ] = DataUtils.toHalfFloat( Math.min( r, 65504 ) );
|
||||
img[ qi + 1 ] = DataUtils.toHalfFloat( Math.min( g, 65504 ) );
|
||||
img[ qi + 2 ] = DataUtils.toHalfFloat( Math.min( b, 65504 ) );
|
||||
img[ qi + 3 ] = DataUtils.toHalfFloat( 1 );
|
||||
|
||||
|
||||
} else {
|
||||
|
||||
img[ qi ] = r;
|
||||
img[ qi + 1 ] = g;
|
||||
img[ qi + 2 ] = b;
|
||||
img[ qi + 3 ] = 1;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
throw new Error( 'THREE.LogLuvLoader: Unsupported Photometric interpretation: ' + intp );
|
||||
|
||||
}
|
||||
|
||||
return img;
|
||||
|
||||
};
|
||||
|
||||
UTIF._binBE =
|
||||
{
|
||||
nextZero: function ( data, o ) {
|
||||
|
||||
while ( data[ o ] != 0 ) o ++; return o;
|
||||
|
||||
},
|
||||
readUshort: function ( buff, p ) {
|
||||
|
||||
return ( buff[ p ] << 8 ) | buff[ p + 1 ];
|
||||
|
||||
},
|
||||
readShort: function ( buff, p ) {
|
||||
|
||||
var a = UTIF._binBE.ui8; a[ 0 ] = buff[ p + 1 ]; a[ 1 ] = buff[ p + 0 ]; return UTIF._binBE.i16[ 0 ];
|
||||
|
||||
},
|
||||
readInt: function ( buff, p ) {
|
||||
|
||||
var a = UTIF._binBE.ui8; a[ 0 ] = buff[ p + 3 ]; a[ 1 ] = buff[ p + 2 ]; a[ 2 ] = buff[ p + 1 ]; a[ 3 ] = buff[ p + 0 ]; return UTIF._binBE.i32[ 0 ];
|
||||
|
||||
},
|
||||
readUint: function ( buff, p ) {
|
||||
|
||||
var a = UTIF._binBE.ui8; a[ 0 ] = buff[ p + 3 ]; a[ 1 ] = buff[ p + 2 ]; a[ 2 ] = buff[ p + 1 ]; a[ 3 ] = buff[ p + 0 ]; return UTIF._binBE.ui32[ 0 ];
|
||||
|
||||
},
|
||||
readASCII: function ( buff, p, l ) {
|
||||
|
||||
var s = ''; for ( var i = 0; i < l; i ++ ) s += String.fromCharCode( buff[ p + i ] ); return s;
|
||||
|
||||
},
|
||||
readFloat: function ( buff, p ) {
|
||||
|
||||
var a = UTIF._binBE.ui8; for ( var i = 0; i < 4; i ++ ) a[ i ] = buff[ p + 3 - i ]; return UTIF._binBE.fl32[ 0 ];
|
||||
|
||||
},
|
||||
readDouble: function ( buff, p ) {
|
||||
|
||||
var a = UTIF._binBE.ui8; for ( var i = 0; i < 8; i ++ ) a[ i ] = buff[ p + 7 - i ]; return UTIF._binBE.fl64[ 0 ];
|
||||
|
||||
},
|
||||
|
||||
writeUshort: function ( buff, p, n ) {
|
||||
|
||||
buff[ p ] = ( n >> 8 ) & 255; buff[ p + 1 ] = n & 255;
|
||||
|
||||
},
|
||||
writeInt: function ( buff, p, n ) {
|
||||
|
||||
var a = UTIF._binBE.ui8; UTIF._binBE.i32[ 0 ] = n; buff[ p + 3 ] = a[ 0 ]; buff[ p + 2 ] = a[ 1 ]; buff[ p + 1 ] = a[ 2 ]; buff[ p + 0 ] = a[ 3 ];
|
||||
|
||||
},
|
||||
writeUint: function ( buff, p, n ) {
|
||||
|
||||
buff[ p ] = ( n >> 24 ) & 255; buff[ p + 1 ] = ( n >> 16 ) & 255; buff[ p + 2 ] = ( n >> 8 ) & 255; buff[ p + 3 ] = ( n >> 0 ) & 255;
|
||||
|
||||
},
|
||||
writeASCII: function ( buff, p, s ) {
|
||||
|
||||
for ( var i = 0; i < s.length; i ++ ) buff[ p + i ] = s.charCodeAt( i );
|
||||
|
||||
},
|
||||
writeDouble: function ( buff, p, n ) {
|
||||
|
||||
UTIF._binBE.fl64[ 0 ] = n;
|
||||
for ( var i = 0; i < 8; i ++ ) buff[ p + i ] = UTIF._binBE.ui8[ 7 - i ];
|
||||
|
||||
}
|
||||
};
|
||||
UTIF._binBE.ui8 = new Uint8Array( 8 );
|
||||
UTIF._binBE.i16 = new Int16Array( UTIF._binBE.ui8.buffer );
|
||||
UTIF._binBE.i32 = new Int32Array( UTIF._binBE.ui8.buffer );
|
||||
UTIF._binBE.ui32 = new Uint32Array( UTIF._binBE.ui8.buffer );
|
||||
UTIF._binBE.fl32 = new Float32Array( UTIF._binBE.ui8.buffer );
|
||||
UTIF._binBE.fl64 = new Float64Array( UTIF._binBE.ui8.buffer );
|
||||
|
||||
UTIF._binLE =
|
||||
{
|
||||
nextZero: UTIF._binBE.nextZero,
|
||||
readUshort: function ( buff, p ) {
|
||||
|
||||
return ( buff[ p + 1 ] << 8 ) | buff[ p ];
|
||||
|
||||
},
|
||||
readShort: function ( buff, p ) {
|
||||
|
||||
var a = UTIF._binBE.ui8; a[ 0 ] = buff[ p + 0 ]; a[ 1 ] = buff[ p + 1 ]; return UTIF._binBE.i16[ 0 ];
|
||||
|
||||
},
|
||||
readInt: function ( buff, p ) {
|
||||
|
||||
var a = UTIF._binBE.ui8; a[ 0 ] = buff[ p + 0 ]; a[ 1 ] = buff[ p + 1 ]; a[ 2 ] = buff[ p + 2 ]; a[ 3 ] = buff[ p + 3 ]; return UTIF._binBE.i32[ 0 ];
|
||||
|
||||
},
|
||||
readUint: function ( buff, p ) {
|
||||
|
||||
var a = UTIF._binBE.ui8; a[ 0 ] = buff[ p + 0 ]; a[ 1 ] = buff[ p + 1 ]; a[ 2 ] = buff[ p + 2 ]; a[ 3 ] = buff[ p + 3 ]; return UTIF._binBE.ui32[ 0 ];
|
||||
|
||||
},
|
||||
readASCII: UTIF._binBE.readASCII,
|
||||
readFloat: function ( buff, p ) {
|
||||
|
||||
var a = UTIF._binBE.ui8; for ( var i = 0; i < 4; i ++ ) a[ i ] = buff[ p + i ]; return UTIF._binBE.fl32[ 0 ];
|
||||
|
||||
},
|
||||
readDouble: function ( buff, p ) {
|
||||
|
||||
var a = UTIF._binBE.ui8; for ( var i = 0; i < 8; i ++ ) a[ i ] = buff[ p + i ]; return UTIF._binBE.fl64[ 0 ];
|
||||
|
||||
},
|
||||
|
||||
writeUshort: function ( buff, p, n ) {
|
||||
|
||||
buff[ p ] = ( n ) & 255; buff[ p + 1 ] = ( n >> 8 ) & 255;
|
||||
|
||||
},
|
||||
writeInt: function ( buff, p, n ) {
|
||||
|
||||
var a = UTIF._binBE.ui8; UTIF._binBE.i32[ 0 ] = n; buff[ p + 0 ] = a[ 0 ]; buff[ p + 1 ] = a[ 1 ]; buff[ p + 2 ] = a[ 2 ]; buff[ p + 3 ] = a[ 3 ];
|
||||
|
||||
},
|
||||
writeUint: function ( buff, p, n ) {
|
||||
|
||||
buff[ p ] = ( n >>> 0 ) & 255; buff[ p + 1 ] = ( n >>> 8 ) & 255; buff[ p + 2 ] = ( n >>> 16 ) & 255; buff[ p + 3 ] = ( n >>> 24 ) & 255;
|
||||
|
||||
},
|
||||
writeASCII: UTIF._binBE.writeASCII
|
||||
};
|
||||
UTIF._copyTile = function ( tb, tw, th, b, w, h, xoff, yoff ) {
|
||||
|
||||
//log("copyTile", tw, th, w, h, xoff, yoff);
|
||||
var xlim = Math.min( tw, w - xoff );
|
||||
var ylim = Math.min( th, h - yoff );
|
||||
for ( var y = 0; y < ylim; y ++ ) {
|
||||
|
||||
var tof = ( yoff + y ) * w + xoff;
|
||||
var sof = y * tw;
|
||||
for ( var x = 0; x < xlim; x ++ ) b[ tof + x ] = tb[ sof + x ];
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
export { LogLuvLoader };
|
||||
77
public/sdk/three/jsm/loaders/LottieLoader.js
Normal file
77
public/sdk/three/jsm/loaders/LottieLoader.js
Normal file
@ -0,0 +1,77 @@
|
||||
import {
|
||||
FileLoader,
|
||||
Loader,
|
||||
CanvasTexture,
|
||||
NearestFilter,
|
||||
SRGBColorSpace
|
||||
} from 'three';
|
||||
|
||||
import lottie from '../libs/lottie_canvas.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;
|
||||
texture.colorSpace = SRGBColorSpace;
|
||||
|
||||
const loader = new FileLoader( this.manager );
|
||||
loader.setPath( this.path );
|
||||
loader.setWithCredentials( this.withCredentials );
|
||||
|
||||
loader.load( url, function ( text ) {
|
||||
|
||||
const data = JSON.parse( text );
|
||||
|
||||
// lottie 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 );
|
||||
|
||||
const animation = lottie.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 };
|
||||
399
public/sdk/three/jsm/loaders/MD2Loader.js
Normal file
399
public/sdk/three/jsm/loaders/MD2Loader.js
Normal file
@ -0,0 +1,399 @@
|
||||
import {
|
||||
AnimationClip,
|
||||
BufferGeometry,
|
||||
FileLoader,
|
||||
Float32BufferAttribute,
|
||||
Loader,
|
||||
Vector3
|
||||
} from 'three';
|
||||
|
||||
const _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 ]
|
||||
];
|
||||
|
||||
class MD2Loader extends Loader {
|
||||
|
||||
constructor( manager ) {
|
||||
|
||||
super( manager );
|
||||
|
||||
}
|
||||
|
||||
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.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( buffer ) {
|
||||
|
||||
const data = new DataView( buffer );
|
||||
|
||||
// http://tfc.duke.free.fr/coding/md2-specs-en.html
|
||||
|
||||
const header = {};
|
||||
const 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 ( let 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;
|
||||
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
const geometry = new BufferGeometry();
|
||||
|
||||
// uvs
|
||||
|
||||
const uvsTemp = [];
|
||||
let offset = header.offset_st;
|
||||
|
||||
for ( let i = 0, l = header.num_st; i < l; i ++ ) {
|
||||
|
||||
const u = data.getInt16( offset + 0, true );
|
||||
const v = data.getInt16( offset + 2, true );
|
||||
|
||||
uvsTemp.push( u / header.skinwidth, 1 - ( v / header.skinheight ) );
|
||||
|
||||
offset += 4;
|
||||
|
||||
}
|
||||
|
||||
// triangles
|
||||
|
||||
offset = header.offset_tris;
|
||||
|
||||
const vertexIndices = [];
|
||||
const uvIndices = [];
|
||||
|
||||
for ( let 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
|
||||
|
||||
const translation = new Vector3();
|
||||
const scale = new Vector3();
|
||||
|
||||
const frames = [];
|
||||
|
||||
offset = header.offset_frames;
|
||||
|
||||
for ( let 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;
|
||||
|
||||
const string = [];
|
||||
|
||||
for ( let j = 0; j < 16; j ++ ) {
|
||||
|
||||
const character = data.getUint8( offset + j );
|
||||
if ( character === 0 ) break;
|
||||
|
||||
string[ j ] = character;
|
||||
|
||||
}
|
||||
|
||||
const frame = {
|
||||
name: String.fromCharCode.apply( null, string ),
|
||||
vertices: [],
|
||||
normals: []
|
||||
};
|
||||
|
||||
offset += 16;
|
||||
|
||||
for ( let j = 0; j < header.num_vertices; j ++ ) {
|
||||
|
||||
let x = data.getUint8( offset ++ );
|
||||
let y = data.getUint8( offset ++ );
|
||||
let z = data.getUint8( offset ++ );
|
||||
const n = _normalData[ data.getUint8( offset ++ ) ];
|
||||
|
||||
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
|
||||
|
||||
const positions = [];
|
||||
const normals = [];
|
||||
const uvs = [];
|
||||
|
||||
const verticesTemp = frames[ 0 ].vertices;
|
||||
const normalsTemp = frames[ 0 ].normals;
|
||||
|
||||
for ( let i = 0, l = vertexIndices.length; i < l; i ++ ) {
|
||||
|
||||
const vertexIndex = vertexIndices[ i ];
|
||||
let stride = vertexIndex * 3;
|
||||
|
||||
//
|
||||
|
||||
const x = verticesTemp[ stride ];
|
||||
const y = verticesTemp[ stride + 1 ];
|
||||
const z = verticesTemp[ stride + 2 ];
|
||||
|
||||
positions.push( x, y, z );
|
||||
|
||||
//
|
||||
|
||||
const nx = normalsTemp[ stride ];
|
||||
const ny = normalsTemp[ stride + 1 ];
|
||||
const nz = normalsTemp[ stride + 2 ];
|
||||
|
||||
normals.push( nx, ny, nz );
|
||||
|
||||
//
|
||||
|
||||
const uvIndex = uvIndices[ i ];
|
||||
stride = uvIndex * 2;
|
||||
|
||||
const u = uvsTemp[ stride ];
|
||||
const 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
|
||||
|
||||
const morphPositions = [];
|
||||
const morphNormals = [];
|
||||
|
||||
for ( let i = 0, l = frames.length; i < l; i ++ ) {
|
||||
|
||||
const frame = frames[ i ];
|
||||
const attributeName = frame.name;
|
||||
|
||||
if ( frame.vertices.length > 0 ) {
|
||||
|
||||
const positions = [];
|
||||
|
||||
for ( let j = 0, jl = vertexIndices.length; j < jl; j ++ ) {
|
||||
|
||||
const vertexIndex = vertexIndices[ j ];
|
||||
const stride = vertexIndex * 3;
|
||||
|
||||
const x = frame.vertices[ stride ];
|
||||
const y = frame.vertices[ stride + 1 ];
|
||||
const z = frame.vertices[ stride + 2 ];
|
||||
|
||||
positions.push( x, y, z );
|
||||
|
||||
}
|
||||
|
||||
const positionAttribute = new Float32BufferAttribute( positions, 3 );
|
||||
positionAttribute.name = attributeName;
|
||||
|
||||
morphPositions.push( positionAttribute );
|
||||
|
||||
}
|
||||
|
||||
if ( frame.normals.length > 0 ) {
|
||||
|
||||
const normals = [];
|
||||
|
||||
for ( let j = 0, jl = vertexIndices.length; j < jl; j ++ ) {
|
||||
|
||||
const vertexIndex = vertexIndices[ j ];
|
||||
const stride = vertexIndex * 3;
|
||||
|
||||
const nx = frame.normals[ stride ];
|
||||
const ny = frame.normals[ stride + 1 ];
|
||||
const nz = frame.normals[ stride + 2 ];
|
||||
|
||||
normals.push( nx, ny, nz );
|
||||
|
||||
}
|
||||
|
||||
const 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 };
|
||||
102
public/sdk/three/jsm/loaders/MDDLoader.js
Normal file
102
public/sdk/three/jsm/loaders/MDDLoader.js
Normal file
@ -0,0 +1,102 @@
|
||||
/**
|
||||
* 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 'three';
|
||||
|
||||
class MDDLoader extends Loader {
|
||||
|
||||
constructor( manager ) {
|
||||
|
||||
super( manager );
|
||||
|
||||
}
|
||||
|
||||
load( url, onLoad, onProgress, onError ) {
|
||||
|
||||
const scope = this;
|
||||
|
||||
const loader = new FileLoader( this.manager );
|
||||
loader.setPath( this.path );
|
||||
loader.setResponseType( 'arraybuffer' );
|
||||
loader.load( url, function ( data ) {
|
||||
|
||||
onLoad( scope.parse( data ) );
|
||||
|
||||
}, onProgress, onError );
|
||||
|
||||
}
|
||||
|
||||
parse( data ) {
|
||||
|
||||
const view = new DataView( data );
|
||||
|
||||
const totalFrames = view.getUint32( 0 );
|
||||
const totalPoints = view.getUint32( 4 );
|
||||
|
||||
let offset = 8;
|
||||
|
||||
// animation clip
|
||||
|
||||
const times = new Float32Array( totalFrames );
|
||||
const values = new Float32Array( totalFrames * totalFrames ).fill( 0 );
|
||||
|
||||
for ( let i = 0; i < totalFrames; i ++ ) {
|
||||
|
||||
times[ i ] = view.getFloat32( offset ); offset += 4;
|
||||
values[ ( totalFrames * i ) + i ] = 1;
|
||||
|
||||
}
|
||||
|
||||
const track = new NumberKeyframeTrack( '.morphTargetInfluences', times, values );
|
||||
const clip = new AnimationClip( 'default', times[ times.length - 1 ], [ track ] );
|
||||
|
||||
// morph targets
|
||||
|
||||
const morphTargets = [];
|
||||
|
||||
for ( let i = 0; i < totalFrames; i ++ ) {
|
||||
|
||||
const morphTarget = new Float32Array( totalPoints * 3 );
|
||||
|
||||
for ( let j = 0; j < totalPoints; j ++ ) {
|
||||
|
||||
const 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
|
||||
|
||||
}
|
||||
|
||||
const attribute = new BufferAttribute( morphTarget, 3 );
|
||||
attribute.name = 'morph_' + i;
|
||||
|
||||
morphTargets.push( attribute );
|
||||
|
||||
}
|
||||
|
||||
return {
|
||||
morphTargets: morphTargets,
|
||||
clip: clip
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export { MDDLoader };
|
||||
2276
public/sdk/three/jsm/loaders/MMDLoader.js
Normal file
2276
public/sdk/three/jsm/loaders/MMDLoader.js
Normal file
File diff suppressed because it is too large
Load Diff
567
public/sdk/three/jsm/loaders/MTLLoader.js
Normal file
567
public/sdk/three/jsm/loaders/MTLLoader.js
Normal file
@ -0,0 +1,567 @@
|
||||
import {
|
||||
Color,
|
||||
DefaultLoadingManager,
|
||||
FileLoader,
|
||||
FrontSide,
|
||||
Loader,
|
||||
LoaderUtils,
|
||||
MeshPhongMaterial,
|
||||
RepeatWrapping,
|
||||
TextureLoader,
|
||||
Vector2,
|
||||
SRGBColorSpace
|
||||
} from 'three';
|
||||
|
||||
/**
|
||||
* Loads a Wavefront .mtl file specifying materials
|
||||
*/
|
||||
|
||||
class MTLLoader extends Loader {
|
||||
|
||||
constructor( manager ) {
|
||||
|
||||
super( manager );
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 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( url, onLoad, onProgress, onError ) {
|
||||
|
||||
const scope = this;
|
||||
|
||||
const path = ( this.path === '' ) ? LoaderUtils.extractUrlBase( url ) : this.path;
|
||||
|
||||
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, path ) );
|
||||
|
||||
} catch ( e ) {
|
||||
|
||||
if ( onError ) {
|
||||
|
||||
onError( e );
|
||||
|
||||
} else {
|
||||
|
||||
console.error( e );
|
||||
|
||||
}
|
||||
|
||||
scope.manager.itemError( url );
|
||||
|
||||
}
|
||||
|
||||
}, onProgress, onError );
|
||||
|
||||
}
|
||||
|
||||
setMaterialOptions( value ) {
|
||||
|
||||
this.materialOptions = value;
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a MTL file.
|
||||
*
|
||||
* @param {String} text - Content of MTL file
|
||||
* @return {MaterialCreator}
|
||||
*
|
||||
* @see setPath setResourcePath
|
||||
*
|
||||
* @note In order for relative texture references to resolve correctly
|
||||
* you must call setResourcePath() explicitly prior to parse.
|
||||
*/
|
||||
parse( text, path ) {
|
||||
|
||||
const lines = text.split( '\n' );
|
||||
let info = {};
|
||||
const delimiter_pattern = /\s+/;
|
||||
const materialsInfo = {};
|
||||
|
||||
for ( let i = 0; i < lines.length; i ++ ) {
|
||||
|
||||
let line = lines[ i ];
|
||||
line = line.trim();
|
||||
|
||||
if ( line.length === 0 || line.charAt( 0 ) === '#' ) {
|
||||
|
||||
// Blank line or comment ignore
|
||||
continue;
|
||||
|
||||
}
|
||||
|
||||
const pos = line.indexOf( ' ' );
|
||||
|
||||
let key = ( pos >= 0 ) ? line.substring( 0, pos ) : line;
|
||||
key = key.toLowerCase();
|
||||
|
||||
let 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' ) {
|
||||
|
||||
const ss = value.split( delimiter_pattern, 3 );
|
||||
info[ key ] = [ parseFloat( ss[ 0 ] ), parseFloat( ss[ 1 ] ), parseFloat( ss[ 2 ] ) ];
|
||||
|
||||
} else {
|
||||
|
||||
info[ key ] = value;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const materialCreator = new 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
|
||||
*/
|
||||
|
||||
class MaterialCreator {
|
||||
|
||||
constructor( baseUrl = '', options = {} ) {
|
||||
|
||||
this.baseUrl = baseUrl;
|
||||
this.options = options;
|
||||
this.materialsInfo = {};
|
||||
this.materials = {};
|
||||
this.materialsArray = [];
|
||||
this.nameLookup = {};
|
||||
|
||||
this.crossOrigin = 'anonymous';
|
||||
|
||||
this.side = ( this.options.side !== undefined ) ? this.options.side : FrontSide;
|
||||
this.wrap = ( this.options.wrap !== undefined ) ? this.options.wrap : RepeatWrapping;
|
||||
|
||||
}
|
||||
|
||||
setCrossOrigin( value ) {
|
||||
|
||||
this.crossOrigin = value;
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
setManager( value ) {
|
||||
|
||||
this.manager = value;
|
||||
|
||||
}
|
||||
|
||||
setMaterials( materialsInfo ) {
|
||||
|
||||
this.materialsInfo = this.convert( materialsInfo );
|
||||
this.materials = {};
|
||||
this.materialsArray = [];
|
||||
this.nameLookup = {};
|
||||
|
||||
}
|
||||
|
||||
convert( materialsInfo ) {
|
||||
|
||||
if ( ! this.options ) return materialsInfo;
|
||||
|
||||
const converted = {};
|
||||
|
||||
for ( const mn in materialsInfo ) {
|
||||
|
||||
// Convert materials info into normalized form based on options
|
||||
|
||||
const mat = materialsInfo[ mn ];
|
||||
|
||||
const covmat = {};
|
||||
|
||||
converted[ mn ] = covmat;
|
||||
|
||||
for ( const prop in mat ) {
|
||||
|
||||
let save = true;
|
||||
let value = mat[ prop ];
|
||||
const 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() {
|
||||
|
||||
for ( const mn in this.materialsInfo ) {
|
||||
|
||||
this.create( mn );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
getIndex( materialName ) {
|
||||
|
||||
return this.nameLookup[ materialName ];
|
||||
|
||||
}
|
||||
|
||||
getAsArray() {
|
||||
|
||||
let index = 0;
|
||||
|
||||
for ( const mn in this.materialsInfo ) {
|
||||
|
||||
this.materialsArray[ index ] = this.create( mn );
|
||||
this.nameLookup[ mn ] = index;
|
||||
index ++;
|
||||
|
||||
}
|
||||
|
||||
return this.materialsArray;
|
||||
|
||||
}
|
||||
|
||||
create( materialName ) {
|
||||
|
||||
if ( this.materials[ materialName ] === undefined ) {
|
||||
|
||||
this.createMaterial_( materialName );
|
||||
|
||||
}
|
||||
|
||||
return this.materials[ materialName ];
|
||||
|
||||
}
|
||||
|
||||
createMaterial_( materialName ) {
|
||||
|
||||
// Create material
|
||||
|
||||
const scope = this;
|
||||
const mat = this.materialsInfo[ materialName ];
|
||||
const 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
|
||||
|
||||
const texParams = scope.getTextureParams( value, params );
|
||||
const 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;
|
||||
|
||||
if ( mapType === 'map' || mapType === 'emissiveMap' ) {
|
||||
|
||||
map.colorSpace = SRGBColorSpace;
|
||||
|
||||
}
|
||||
|
||||
params[ mapType ] = map;
|
||||
|
||||
}
|
||||
|
||||
for ( const prop in mat ) {
|
||||
|
||||
const value = mat[ prop ];
|
||||
let 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 ).convertSRGBToLinear();
|
||||
|
||||
break;
|
||||
|
||||
case 'ks':
|
||||
|
||||
// Specular color (color when light is reflected from shiny surface) using RGB values
|
||||
params.specular = new Color().fromArray( value ).convertSRGBToLinear();
|
||||
|
||||
break;
|
||||
|
||||
case 'ke':
|
||||
|
||||
// Emissive using RGB values
|
||||
params.emissive = new Color().fromArray( value ).convertSRGBToLinear();
|
||||
|
||||
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( value, matParams ) {
|
||||
|
||||
const texParams = {
|
||||
|
||||
scale: new Vector2( 1, 1 ),
|
||||
offset: new Vector2( 0, 0 )
|
||||
|
||||
};
|
||||
|
||||
const items = value.split( /\s+/ );
|
||||
let 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( url, mapping, onLoad, onProgress, onError ) {
|
||||
|
||||
const manager = ( this.manager !== undefined ) ? this.manager : DefaultLoadingManager;
|
||||
let loader = manager.getHandler( url );
|
||||
|
||||
if ( loader === null ) {
|
||||
|
||||
loader = new TextureLoader( manager );
|
||||
|
||||
}
|
||||
|
||||
if ( loader.setCrossOrigin ) loader.setCrossOrigin( this.crossOrigin );
|
||||
|
||||
const texture = loader.load( url, onLoad, onProgress, onError );
|
||||
|
||||
if ( mapping !== undefined ) texture.mapping = mapping;
|
||||
|
||||
return texture;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export { MTLLoader };
|
||||
852
public/sdk/three/jsm/loaders/MaterialXLoader.js
Normal file
852
public/sdk/three/jsm/loaders/MaterialXLoader.js
Normal file
@ -0,0 +1,852 @@
|
||||
import {
|
||||
FileLoader,
|
||||
Loader,
|
||||
TextureLoader,
|
||||
RepeatWrapping
|
||||
} from 'three';
|
||||
|
||||
import {
|
||||
MeshBasicNodeMaterial, MeshPhysicalNodeMaterial,
|
||||
float, bool, int, vec2, vec3, vec4, color, texture,
|
||||
positionLocal, positionWorld, uv, vertexColor,
|
||||
normalLocal, normalWorld, tangentLocal, tangentWorld,
|
||||
add, sub, mul, div, mod, abs, sign, floor, ceil, round, pow, sin, cos, tan,
|
||||
asin, acos, atan2, sqrt, exp, clamp, min, max, normalize, length, dot, cross, normalMap,
|
||||
remap, smoothstep, luminance, mx_rgbtohsv, mx_hsvtorgb,
|
||||
mix,
|
||||
mx_ramplr, mx_ramptb, mx_splitlr, mx_splittb,
|
||||
mx_fractal_noise_float, mx_noise_float, mx_cell_noise_float, mx_worley_noise_float,
|
||||
mx_transform_uv,
|
||||
mx_safepower, mx_contrast,
|
||||
mx_srgb_texture_to_lin_rec709,
|
||||
saturation
|
||||
} from '../nodes/Nodes.js';
|
||||
|
||||
const colorSpaceLib = {
|
||||
mx_srgb_texture_to_lin_rec709
|
||||
};
|
||||
|
||||
class MXElement {
|
||||
|
||||
constructor( name, nodeFunc, params = null ) {
|
||||
|
||||
this.name = name;
|
||||
this.nodeFunc = nodeFunc;
|
||||
this.params = params;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Ref: https://github.com/mrdoob/three.js/issues/24674
|
||||
|
||||
const mx_add = ( in1, in2 = float( 0 ) ) => add( in1, in2 );
|
||||
const mx_subtract = ( in1, in2 = float( 0 ) ) => sub( in1, in2 );
|
||||
const mx_multiply = ( in1, in2 = float( 1 ) ) => mul( in1, in2 );
|
||||
const mx_divide = ( in1, in2 = float( 1 ) ) => div( in1, in2 );
|
||||
const mx_modulo = ( in1, in2 = float( 1 ) ) => mod( in1, in2 );
|
||||
const mx_power = ( in1, in2 = float( 1 ) ) => pow( in1, in2 );
|
||||
const mx_atan2 = ( in1 = float( 0 ), in2 = float( 1 ) ) => atan2( in1, in2 );
|
||||
|
||||
const MXElements = [
|
||||
|
||||
// << Math >>
|
||||
new MXElement( 'add', mx_add, [ 'in1', 'in2' ] ),
|
||||
new MXElement( 'subtract', mx_subtract, [ 'in1', 'in2' ] ),
|
||||
new MXElement( 'multiply', mx_multiply, [ 'in1', 'in2' ] ),
|
||||
new MXElement( 'divide', mx_divide, [ 'in1', 'in2' ] ),
|
||||
new MXElement( 'modulo', mx_modulo, [ 'in1', 'in2' ] ),
|
||||
new MXElement( 'absval', abs, [ 'in1', 'in2' ] ),
|
||||
new MXElement( 'sign', sign, [ 'in1', 'in2' ] ),
|
||||
new MXElement( 'floor', floor, [ 'in1', 'in2' ] ),
|
||||
new MXElement( 'ceil', ceil, [ 'in1', 'in2' ] ),
|
||||
new MXElement( 'round', round, [ 'in1', 'in2' ] ),
|
||||
new MXElement( 'power', mx_power, [ 'in1', 'in2' ] ),
|
||||
new MXElement( 'sin', sin, [ 'in' ] ),
|
||||
new MXElement( 'cos', cos, [ 'in' ] ),
|
||||
new MXElement( 'tan', tan, [ 'in' ] ),
|
||||
new MXElement( 'asin', asin, [ 'in' ] ),
|
||||
new MXElement( 'acos', acos, [ 'in' ] ),
|
||||
new MXElement( 'atan2', mx_atan2, [ 'in1', 'in2' ] ),
|
||||
new MXElement( 'sqrt', sqrt, [ 'in' ] ),
|
||||
//new MtlXElement( 'ln', ... ),
|
||||
new MXElement( 'exp', exp, [ 'in' ] ),
|
||||
new MXElement( 'clamp', clamp, [ 'in', 'low', 'high' ] ),
|
||||
new MXElement( 'min', min, [ 'in1', 'in2' ] ),
|
||||
new MXElement( 'max', max, [ 'in1', 'in2' ] ),
|
||||
new MXElement( 'normalize', normalize, [ 'in' ] ),
|
||||
new MXElement( 'magnitude', length, [ 'in1', 'in2' ] ),
|
||||
new MXElement( 'dotproduct', dot, [ 'in1', 'in2' ] ),
|
||||
new MXElement( 'crossproduct', cross, [ 'in' ] ),
|
||||
//new MtlXElement( 'transformpoint', ... ),
|
||||
//new MtlXElement( 'transformvector', ... ),
|
||||
//new MtlXElement( 'transformnormal', ... ),
|
||||
//new MtlXElement( 'transformmatrix', ... ),
|
||||
new MXElement( 'normalmap', normalMap, [ 'in', 'scale' ] ),
|
||||
//new MtlXElement( 'transpose', ... ),
|
||||
//new MtlXElement( 'determinant', ... ),
|
||||
//new MtlXElement( 'invertmatrix', ... ),
|
||||
//new MtlXElement( 'rotate2d', rotateUV, [ 'in', radians( 'amount' )** ] ),
|
||||
//new MtlXElement( 'rotate3d', ... ),
|
||||
//new MtlXElement( 'arrayappend', ... ),
|
||||
//new MtlXElement( 'dot', ... ),
|
||||
|
||||
// << Adjustment >>
|
||||
new MXElement( 'remap', remap, [ 'in', 'inlow', 'inhigh', 'outlow', 'outhigh' ] ),
|
||||
new MXElement( 'smoothstep', smoothstep, [ 'in', 'low', 'high' ] ),
|
||||
//new MtlXElement( 'curveadjust', ... ),
|
||||
//new MtlXElement( 'curvelookup', ... ),
|
||||
new MXElement( 'luminance', luminance, [ 'in', 'lumacoeffs' ] ),
|
||||
new MXElement( 'rgbtohsv', mx_rgbtohsv, [ 'in' ] ),
|
||||
new MXElement( 'hsvtorgb', mx_hsvtorgb, [ 'in' ] ),
|
||||
|
||||
// << Mix >>
|
||||
new MXElement( 'mix', mix, [ 'bg', 'fg', 'mix' ] ),
|
||||
|
||||
// << Channel >>
|
||||
new MXElement( 'combine2', vec2, [ 'in1', 'in2' ] ),
|
||||
new MXElement( 'combine3', vec3, [ 'in1', 'in2', 'in3' ] ),
|
||||
new MXElement( 'combine4', vec4, [ 'in1', 'in2', 'in3', 'in4' ] ),
|
||||
|
||||
// << Procedural >>
|
||||
new MXElement( 'ramplr', mx_ramplr, [ 'valuel', 'valuer', 'texcoord' ] ),
|
||||
new MXElement( 'ramptb', mx_ramptb, [ 'valuet', 'valueb', 'texcoord' ] ),
|
||||
new MXElement( 'splitlr', mx_splitlr, [ 'valuel', 'valuer', 'texcoord' ] ),
|
||||
new MXElement( 'splittb', mx_splittb, [ 'valuet', 'valueb', 'texcoord' ] ),
|
||||
new MXElement( 'noise2d', mx_noise_float, [ 'texcoord', 'amplitude', 'pivot' ] ),
|
||||
new MXElement( 'noise3d', mx_noise_float, [ 'texcoord', 'amplitude', 'pivot' ] ),
|
||||
new MXElement( 'fractal3d', mx_fractal_noise_float, [ 'position', 'octaves', 'lacunarity', 'diminish', 'amplitude' ] ),
|
||||
new MXElement( 'cellnoise2d', mx_cell_noise_float, [ 'texcoord' ] ),
|
||||
new MXElement( 'cellnoise3d', mx_cell_noise_float, [ 'texcoord' ] ),
|
||||
new MXElement( 'worleynoise2d', mx_worley_noise_float, [ 'texcoord', 'jitter' ] ),
|
||||
new MXElement( 'worleynoise3d', mx_worley_noise_float, [ 'texcoord', 'jitter' ] ),
|
||||
|
||||
// << Supplemental >>
|
||||
//new MtlXElement( 'tiledimage', ... ),
|
||||
//new MtlXElement( 'triplanarprojection', triplanarTextures, [ 'filex', 'filey', 'filez' ] ),
|
||||
//new MtlXElement( 'ramp4', ... ),
|
||||
//new MtlXElement( 'place2d', mx_place2d, [ 'texcoord', 'pivot', 'scale', 'rotate', 'offset' ] ),
|
||||
new MXElement( 'safepower', mx_safepower, [ 'in1', 'in2' ] ),
|
||||
new MXElement( 'contrast', mx_contrast, [ 'in', 'amount', 'pivot' ] ),
|
||||
//new MtlXElement( 'hsvadjust', ... ),
|
||||
new MXElement( 'saturate', saturation, [ 'in', 'amount' ] ),
|
||||
//new MtlXElement( 'extract', ... ),
|
||||
//new MtlXElement( 'separate2', ... ),
|
||||
//new MtlXElement( 'separate3', ... ),
|
||||
//new MtlXElement( 'separate4', ... )
|
||||
|
||||
];
|
||||
|
||||
const MtlXLibrary = {};
|
||||
MXElements.forEach( element => MtlXLibrary[ element.name ] = element );
|
||||
|
||||
class MaterialXLoader extends Loader {
|
||||
|
||||
constructor( manager ) {
|
||||
|
||||
super( manager );
|
||||
|
||||
}
|
||||
|
||||
load( url, onLoad, onProgress, onError ) {
|
||||
|
||||
const _onError = function ( e ) {
|
||||
|
||||
if ( onError ) {
|
||||
|
||||
onError( e );
|
||||
|
||||
} else {
|
||||
|
||||
console.error( e );
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
new FileLoader( this.manager )
|
||||
.setPath( this.path )
|
||||
.load( url, async ( text ) => {
|
||||
|
||||
try {
|
||||
|
||||
onLoad( this.parse( text ) );
|
||||
|
||||
} catch ( e ) {
|
||||
|
||||
_onError( e );
|
||||
|
||||
}
|
||||
|
||||
}, onProgress, _onError );
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
parse( text ) {
|
||||
|
||||
return new MaterialX( this.manager, this.path ).parse( text );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class MaterialXNode {
|
||||
|
||||
constructor( materialX, nodeXML, nodePath = '' ) {
|
||||
|
||||
this.materialX = materialX;
|
||||
this.nodeXML = nodeXML;
|
||||
this.nodePath = nodePath ? nodePath + '/' + this.name : this.name;
|
||||
|
||||
this.parent = null;
|
||||
|
||||
this.node = null;
|
||||
|
||||
this.children = [];
|
||||
|
||||
}
|
||||
|
||||
get element() {
|
||||
|
||||
return this.nodeXML.nodeName;
|
||||
|
||||
}
|
||||
|
||||
get nodeGraph() {
|
||||
|
||||
return this.getAttribute( 'nodegraph' );
|
||||
|
||||
}
|
||||
|
||||
get nodeName() {
|
||||
|
||||
return this.getAttribute( 'nodename' );
|
||||
|
||||
}
|
||||
|
||||
get interfaceName() {
|
||||
|
||||
return this.getAttribute( 'interfacename' );
|
||||
|
||||
}
|
||||
|
||||
get output() {
|
||||
|
||||
return this.getAttribute( 'output' );
|
||||
|
||||
}
|
||||
|
||||
get name() {
|
||||
|
||||
return this.getAttribute( 'name' );
|
||||
|
||||
}
|
||||
|
||||
get type() {
|
||||
|
||||
return this.getAttribute( 'type' );
|
||||
|
||||
}
|
||||
|
||||
get value() {
|
||||
|
||||
return this.getAttribute( 'value' );
|
||||
|
||||
}
|
||||
|
||||
getNodeGraph() {
|
||||
|
||||
let nodeX = this;
|
||||
|
||||
while ( nodeX !== null ) {
|
||||
|
||||
if ( nodeX.element === 'nodegraph' ) {
|
||||
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
nodeX = nodeX.parent;
|
||||
|
||||
}
|
||||
|
||||
return nodeX;
|
||||
|
||||
}
|
||||
|
||||
getRoot() {
|
||||
|
||||
let nodeX = this;
|
||||
|
||||
while ( nodeX.parent !== null ) {
|
||||
|
||||
nodeX = nodeX.parent;
|
||||
|
||||
}
|
||||
|
||||
return nodeX;
|
||||
|
||||
}
|
||||
|
||||
get referencePath() {
|
||||
|
||||
let referencePath = null;
|
||||
|
||||
if ( this.nodeGraph !== null && this.output !== null ) {
|
||||
|
||||
referencePath = this.nodeGraph + '/' + this.output;
|
||||
|
||||
} else if ( this.nodeName !== null || this.interfaceName !== null ) {
|
||||
|
||||
referencePath = this.getNodeGraph().nodePath + '/' + ( this.nodeName || this.interfaceName );
|
||||
|
||||
}
|
||||
|
||||
return referencePath;
|
||||
|
||||
}
|
||||
|
||||
get hasReference() {
|
||||
|
||||
return this.referencePath !== null;
|
||||
|
||||
}
|
||||
|
||||
get isConst() {
|
||||
|
||||
return this.element === 'input' && this.value !== null && this.type !== 'filename';
|
||||
|
||||
}
|
||||
|
||||
getColorSpaceNode() {
|
||||
|
||||
const csSource = this.getAttribute( 'colorspace' );
|
||||
const csTarget = this.getRoot().getAttribute( 'colorspace' );
|
||||
|
||||
const nodeName = `mx_${ csSource }_to_${ csTarget }`;
|
||||
|
||||
return colorSpaceLib[ nodeName ];
|
||||
|
||||
}
|
||||
|
||||
getTexture() {
|
||||
|
||||
const filePrefix = this.getRecursiveAttribute( 'fileprefix' ) || '';
|
||||
|
||||
let loader = this.materialX.textureLoader;
|
||||
const uri = filePrefix + this.value;
|
||||
|
||||
if ( uri ) {
|
||||
|
||||
const handler = this.materialX.manager.getHandler( uri );
|
||||
if ( handler !== null ) loader = handler;
|
||||
|
||||
}
|
||||
|
||||
const texture = loader.load( uri );
|
||||
texture.wrapS = texture.wrapT = RepeatWrapping;
|
||||
texture.flipY = false;
|
||||
|
||||
return texture;
|
||||
|
||||
}
|
||||
|
||||
getClassFromType( type ) {
|
||||
|
||||
let nodeClass = null;
|
||||
|
||||
if ( type === 'integer' ) nodeClass = int;
|
||||
else if ( type === 'float' ) nodeClass = float;
|
||||
else if ( type === 'vector2' ) nodeClass = vec2;
|
||||
else if ( type === 'vector3' ) nodeClass = vec3;
|
||||
else if ( type === 'vector4' || type === 'color4' ) nodeClass = vec4;
|
||||
else if ( type === 'color3' ) nodeClass = color;
|
||||
else if ( type === 'boolean' ) nodeClass = bool;
|
||||
|
||||
return nodeClass;
|
||||
|
||||
}
|
||||
|
||||
getNode() {
|
||||
|
||||
let node = this.node;
|
||||
|
||||
if ( node !== null ) {
|
||||
|
||||
return node;
|
||||
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
const type = this.type;
|
||||
|
||||
if ( this.isConst ) {
|
||||
|
||||
const nodeClass = this.getClassFromType( type );
|
||||
|
||||
node = nodeClass( ...this.getVector() );
|
||||
|
||||
} else if ( this.hasReference ) {
|
||||
|
||||
node = this.materialX.getMaterialXNode( this.referencePath ).getNode();
|
||||
|
||||
} else {
|
||||
|
||||
const element = this.element;
|
||||
|
||||
if ( element === 'convert' ) {
|
||||
|
||||
const nodeClass = this.getClassFromType( type );
|
||||
|
||||
node = nodeClass( this.getNodeByName( 'in' ) );
|
||||
|
||||
} else if ( element === 'constant' ) {
|
||||
|
||||
node = this.getNodeByName( 'value' );
|
||||
|
||||
} else if ( element === 'position' ) {
|
||||
|
||||
const space = this.getAttribute( 'space' );
|
||||
node = space === 'world' ? positionWorld : positionLocal;
|
||||
|
||||
} else if ( element === 'normal' ) {
|
||||
|
||||
const space = this.getAttribute( 'space' );
|
||||
node = space === 'world' ? normalWorld : normalLocal;
|
||||
|
||||
} else if ( element === 'tangent' ) {
|
||||
|
||||
const space = this.getAttribute( 'space' );
|
||||
node = space === 'world' ? tangentWorld : tangentLocal;
|
||||
|
||||
} else if ( element === 'texcoord' ) {
|
||||
|
||||
const indexNode = this.getChildByName( 'index' );
|
||||
const index = indexNode ? parseInt( indexNode.value ) : 0;
|
||||
|
||||
node = uv( index );
|
||||
|
||||
} else if ( element === 'geomcolor' ) {
|
||||
|
||||
const indexNode = this.getChildByName( 'index' );
|
||||
const index = indexNode ? parseInt( indexNode.value ) : 0;
|
||||
|
||||
node = vertexColor( index );
|
||||
|
||||
} else if ( element === 'tiledimage' ) {
|
||||
|
||||
const file = this.getChildByName( 'file' );
|
||||
|
||||
const textureFile = file.getTexture();
|
||||
const uvTiling = mx_transform_uv( ...this.getNodesByNames( [ 'uvtiling', 'uvoffset' ] ) );
|
||||
|
||||
node = texture( textureFile, uvTiling );
|
||||
|
||||
const colorSpaceNode = file.getColorSpaceNode();
|
||||
|
||||
if ( colorSpaceNode ) {
|
||||
|
||||
node = colorSpaceNode( node );
|
||||
|
||||
}
|
||||
|
||||
} else if ( element === 'image' ) {
|
||||
|
||||
const file = this.getChildByName( 'file' );
|
||||
const uvNode = this.getNodeByName( 'texcoord' );
|
||||
|
||||
const textureFile = file.getTexture();
|
||||
|
||||
node = texture( textureFile, uvNode );
|
||||
|
||||
const colorSpaceNode = file.getColorSpaceNode();
|
||||
|
||||
if ( colorSpaceNode ) {
|
||||
|
||||
node = colorSpaceNode( node );
|
||||
|
||||
}
|
||||
|
||||
} else if ( MtlXLibrary[ element ] !== undefined ) {
|
||||
|
||||
const nodeElement = MtlXLibrary[ element ];
|
||||
|
||||
node = nodeElement.nodeFunc( ...this.getNodesByNames( ...nodeElement.params ) );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
if ( node === null ) {
|
||||
|
||||
console.warn( `THREE.MaterialXLoader: Unexpected node ${ new XMLSerializer().serializeToString( this.nodeXML ) }.` );
|
||||
|
||||
node = float( 0 );
|
||||
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
const nodeToTypeClass = this.getClassFromType( type );
|
||||
|
||||
if ( nodeToTypeClass !== null ) {
|
||||
|
||||
node = nodeToTypeClass( node );
|
||||
|
||||
}
|
||||
|
||||
node.name = this.name;
|
||||
|
||||
this.node = node;
|
||||
|
||||
return node;
|
||||
|
||||
}
|
||||
|
||||
getChildByName( name ) {
|
||||
|
||||
for ( const input of this.children ) {
|
||||
|
||||
if ( input.name === name ) {
|
||||
|
||||
return input;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
getNodes() {
|
||||
|
||||
const nodes = {};
|
||||
|
||||
for ( const input of this.children ) {
|
||||
|
||||
const node = input.getNode();
|
||||
|
||||
nodes[ node.name ] = node;
|
||||
|
||||
}
|
||||
|
||||
return nodes;
|
||||
|
||||
}
|
||||
|
||||
getNodeByName( name ) {
|
||||
|
||||
const child = this.getChildByName( name );
|
||||
|
||||
return child ? child.getNode() : undefined;
|
||||
|
||||
}
|
||||
|
||||
getNodesByNames( ...names ) {
|
||||
|
||||
const nodes = [];
|
||||
|
||||
for ( const name of names ) {
|
||||
|
||||
const node = this.getNodeByName( name );
|
||||
|
||||
if ( node ) nodes.push( node );
|
||||
|
||||
}
|
||||
|
||||
return nodes;
|
||||
|
||||
}
|
||||
|
||||
getValue() {
|
||||
|
||||
return this.value.trim();
|
||||
|
||||
}
|
||||
|
||||
getVector() {
|
||||
|
||||
const vector = [];
|
||||
|
||||
for ( const val of this.getValue().split( /[,|\s]/ ) ) {
|
||||
|
||||
if ( val !== '' ) {
|
||||
|
||||
vector.push( Number( val.trim() ) );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return vector;
|
||||
|
||||
}
|
||||
|
||||
getAttribute( name ) {
|
||||
|
||||
return this.nodeXML.getAttribute( name );
|
||||
|
||||
}
|
||||
|
||||
getRecursiveAttribute( name ) {
|
||||
|
||||
let attribute = this.nodeXML.getAttribute( name );
|
||||
|
||||
if ( attribute === null && this.parent !== null ) {
|
||||
|
||||
attribute = this.parent.getRecursiveAttribute( name );
|
||||
|
||||
}
|
||||
|
||||
return attribute;
|
||||
|
||||
}
|
||||
|
||||
setStandardSurfaceToGltfPBR( material ) {
|
||||
|
||||
const inputs = this.getNodes();
|
||||
|
||||
//
|
||||
|
||||
let colorNode = null;
|
||||
|
||||
if ( inputs.base && inputs.base_color ) colorNode = mul( inputs.base, inputs.base_color );
|
||||
else if ( inputs.base ) colorNode = inputs.base;
|
||||
else if ( inputs.base_color ) colorNode = inputs.base_color;
|
||||
|
||||
//
|
||||
|
||||
let roughnessNode = null;
|
||||
|
||||
if ( inputs.specular_roughness ) roughnessNode = inputs.specular_roughness;
|
||||
|
||||
//
|
||||
|
||||
let metalnessNode = null;
|
||||
|
||||
if ( inputs.metalness ) metalnessNode = inputs.metalness;
|
||||
|
||||
//
|
||||
|
||||
let clearcoatNode = null;
|
||||
let clearcoatRoughnessNode = null;
|
||||
|
||||
if ( inputs.coat ) clearcoatNode = inputs.coat;
|
||||
if ( inputs.coat_roughness ) clearcoatRoughnessNode = inputs.coat_roughness;
|
||||
|
||||
if ( inputs.coat_color ) {
|
||||
|
||||
colorNode = colorNode ? mul( colorNode, inputs.coat_color ) : colorNode;
|
||||
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
let normalNode = null;
|
||||
|
||||
if ( inputs.normal ) normalNode = inputs.normal;
|
||||
|
||||
//
|
||||
|
||||
let emissiveNode = null;
|
||||
|
||||
if ( inputs.emission ) emissiveNode = inputs.emission;
|
||||
if ( inputs.emissionColor ) {
|
||||
|
||||
emissiveNode = emissiveNode ? mul( emissiveNode, inputs.emissionColor ) : emissiveNode;
|
||||
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
material.colorNode = colorNode || color( 0.8, 0.8, 0.8 );
|
||||
material.roughnessNode = roughnessNode || float( 0.2 );
|
||||
material.metalnessNode = metalnessNode || float( 0 );
|
||||
material.clearcoatNode = clearcoatNode || float( 0 );
|
||||
material.clearcoatRoughnessNode = clearcoatRoughnessNode || float( 0 );
|
||||
if ( normalNode ) material.normalNode = normalNode;
|
||||
if ( emissiveNode ) material.emissiveNode = emissiveNode;
|
||||
|
||||
}
|
||||
|
||||
/*setGltfPBR( material ) {
|
||||
|
||||
const inputs = this.getNodes();
|
||||
|
||||
console.log( inputs );
|
||||
|
||||
}*/
|
||||
|
||||
setMaterial( material ) {
|
||||
|
||||
const element = this.element;
|
||||
|
||||
if ( element === 'gltf_pbr' ) {
|
||||
|
||||
//this.setGltfPBR( material );
|
||||
|
||||
} else if ( element === 'standard_surface' ) {
|
||||
|
||||
this.setStandardSurfaceToGltfPBR( material );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
toBasicMaterial() {
|
||||
|
||||
const material = new MeshBasicNodeMaterial();
|
||||
material.name = this.name;
|
||||
|
||||
for ( const nodeX of this.children.toReversed() ) {
|
||||
|
||||
if ( nodeX.name === 'out' ) {
|
||||
|
||||
material.colorNode = nodeX.getNode();
|
||||
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return material;
|
||||
|
||||
}
|
||||
|
||||
toPhysicalMaterial() {
|
||||
|
||||
const material = new MeshPhysicalNodeMaterial();
|
||||
material.name = this.name;
|
||||
|
||||
for ( const nodeX of this.children ) {
|
||||
|
||||
const shaderProperties = this.materialX.getMaterialXNode( nodeX.nodeName );
|
||||
shaderProperties.setMaterial( material );
|
||||
|
||||
}
|
||||
|
||||
return material;
|
||||
|
||||
}
|
||||
|
||||
toMaterials() {
|
||||
|
||||
const materials = {};
|
||||
|
||||
let isUnlit = true;
|
||||
|
||||
for ( const nodeX of this.children ) {
|
||||
|
||||
if ( nodeX.element === 'surfacematerial' ) {
|
||||
|
||||
const material = nodeX.toPhysicalMaterial();
|
||||
|
||||
materials[ material.name ] = material;
|
||||
|
||||
isUnlit = false;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if ( isUnlit ) {
|
||||
|
||||
for ( const nodeX of this.children ) {
|
||||
|
||||
if ( nodeX.element === 'nodegraph' ) {
|
||||
|
||||
const material = nodeX.toBasicMaterial();
|
||||
|
||||
materials[ material.name ] = material;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return materials;
|
||||
|
||||
}
|
||||
|
||||
add( materialXNode ) {
|
||||
|
||||
materialXNode.parent = this;
|
||||
|
||||
this.children.push( materialXNode );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class MaterialX {
|
||||
|
||||
constructor( manager, path ) {
|
||||
|
||||
this.manager = manager;
|
||||
this.path = path;
|
||||
this.resourcePath = '';
|
||||
|
||||
this.nodesXLib = new Map();
|
||||
//this.nodesXRefLib = new WeakMap();
|
||||
|
||||
this.textureLoader = new TextureLoader( manager );
|
||||
|
||||
}
|
||||
|
||||
addMaterialXNode( materialXNode ) {
|
||||
|
||||
this.nodesXLib.set( materialXNode.nodePath, materialXNode );
|
||||
|
||||
}
|
||||
|
||||
/*getMaterialXNodeFromXML( xmlNode ) {
|
||||
|
||||
return this.nodesXRefLib.get( xmlNode );
|
||||
|
||||
}*/
|
||||
|
||||
getMaterialXNode( ...names ) {
|
||||
|
||||
return this.nodesXLib.get( names.join( '/' ) );
|
||||
|
||||
}
|
||||
|
||||
parseNode( nodeXML, nodePath = '' ) {
|
||||
|
||||
const materialXNode = new MaterialXNode( this, nodeXML, nodePath );
|
||||
if ( materialXNode.nodePath ) this.addMaterialXNode( materialXNode );
|
||||
|
||||
for ( const childNodeXML of nodeXML.children ) {
|
||||
|
||||
const childMXNode = this.parseNode( childNodeXML, materialXNode.nodePath );
|
||||
materialXNode.add( childMXNode );
|
||||
|
||||
}
|
||||
|
||||
return materialXNode;
|
||||
|
||||
}
|
||||
|
||||
parse( text ) {
|
||||
|
||||
const rootXML = new DOMParser().parseFromString( text, 'application/xml' ).documentElement;
|
||||
|
||||
this.textureLoader.setPath( this.path );
|
||||
|
||||
//
|
||||
|
||||
const materials = this.parseNode( rootXML ).toMaterials();
|
||||
|
||||
return { materials };
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export { MaterialXLoader };
|
||||
686
public/sdk/three/jsm/loaders/NRRDLoader.js
Normal file
686
public/sdk/three/jsm/loaders/NRRDLoader.js
Normal file
@ -0,0 +1,686 @@
|
||||
import {
|
||||
FileLoader,
|
||||
Loader,
|
||||
Matrix4,
|
||||
Vector3
|
||||
} from 'three';
|
||||
import * as fflate from '../libs/fflate.module.js';
|
||||
import { Volume } from '../misc/Volume.js';
|
||||
|
||||
class NRRDLoader extends Loader {
|
||||
|
||||
constructor( manager ) {
|
||||
|
||||
super( manager );
|
||||
|
||||
}
|
||||
|
||||
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.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 );
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {boolean} segmentation is a option for user to choose
|
||||
*/
|
||||
setSegmentation( segmentation ) {
|
||||
|
||||
this.segmentation = segmentation;
|
||||
|
||||
}
|
||||
|
||||
parse( data ) {
|
||||
|
||||
// this parser is largely inspired from the XTK NRRD parser : https://github.com/xtk/X
|
||||
|
||||
let _data = data;
|
||||
|
||||
let _dataPointer = 0;
|
||||
|
||||
const _nativeLittleEndian = new Int8Array( new Int16Array( [ 1 ] ).buffer )[ 0 ] > 0;
|
||||
|
||||
const _littleEndian = true;
|
||||
|
||||
const headerObject = {};
|
||||
|
||||
function scan( type, chunks ) {
|
||||
|
||||
let _chunkSize = 1;
|
||||
let _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
|
||||
let _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 );
|
||||
|
||||
}
|
||||
|
||||
// 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 ) {
|
||||
|
||||
const u8 = new Uint8Array( array.buffer, array.byteOffset, array.byteLength );
|
||||
for ( let i = 0; i < array.byteLength; i += chunkSize ) {
|
||||
|
||||
for ( let j = i + chunkSize - 1, k = i; j > k; j --, k ++ ) {
|
||||
|
||||
const tmp = u8[ k ];
|
||||
u8[ k ] = u8[ j ];
|
||||
u8[ j ] = tmp;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return array;
|
||||
|
||||
}
|
||||
|
||||
//parse the header
|
||||
function parseHeader( header ) {
|
||||
|
||||
let data, field, fn, i, l, m, _i, _len;
|
||||
const 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( /^#/ ) && ( m = l.match( /(.*):(.*)/ ) ) ) {
|
||||
|
||||
field = m[ 1 ].trim();
|
||||
data = m[ 2 ].trim();
|
||||
fn = _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 = [ ];
|
||||
headerObject.vectors.push( [ 1, 0, 0 ] );
|
||||
headerObject.vectors.push( [ 0, 1, 0 ] );
|
||||
headerObject.vectors.push( [ 0, 0, 1 ] );
|
||||
|
||||
//apply spacing if defined
|
||||
if ( headerObject.spacings ) {
|
||||
|
||||
for ( i = 0; i <= 2; i ++ ) {
|
||||
|
||||
if ( ! isNaN( headerObject.spacings[ i ] ) ) {
|
||||
|
||||
for ( let j = 0; j <= 2; j ++ ) {
|
||||
|
||||
headerObject.vectors[ i ][ j ] *= headerObject.spacings[ i ];
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//parse the data when registred as one of this type : 'text', 'ascii', 'txt'
|
||||
function parseDataAsText( data, start, end ) {
|
||||
|
||||
let number = '';
|
||||
start = start || 0;
|
||||
end = end || data.length;
|
||||
let value;
|
||||
//length of the result is the product of the sizes
|
||||
const lengthOfTheResult = headerObject.sizes.reduce( function ( previous, current ) {
|
||||
|
||||
return previous * current;
|
||||
|
||||
}, 1 );
|
||||
|
||||
let base = 10;
|
||||
if ( headerObject.encoding === 'hex' ) {
|
||||
|
||||
base = 16;
|
||||
|
||||
}
|
||||
|
||||
const result = new headerObject.__array( lengthOfTheResult );
|
||||
let resultIndex = 0;
|
||||
let parsingFunction = parseInt;
|
||||
if ( headerObject.__array === Float32Array || headerObject.__array === Float64Array ) {
|
||||
|
||||
parsingFunction = parseFloat;
|
||||
|
||||
}
|
||||
|
||||
for ( let 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;
|
||||
|
||||
}
|
||||
|
||||
const _bytes = scan( 'uchar', data.byteLength );
|
||||
const _length = _bytes.length;
|
||||
let _header = null;
|
||||
let _data_start = 0;
|
||||
let 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 );
|
||||
|
||||
_data = _bytes.subarray( _data_start ); // the data without header
|
||||
if ( headerObject.encoding.substring( 0, 2 ) === 'gz' ) {
|
||||
|
||||
// we need to decompress the datastream
|
||||
// here we start the unzipping and get a typed Uint8Array back
|
||||
_data = fflate.gunzipSync( new Uint8Array( _data ) );
|
||||
|
||||
} 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
|
||||
const _copy = new Uint8Array( _data.length );
|
||||
|
||||
for ( let i = 0; i < _data.length; i ++ ) {
|
||||
|
||||
_copy[ i ] = _data[ i ];
|
||||
|
||||
}
|
||||
|
||||
_data = _copy;
|
||||
|
||||
}
|
||||
|
||||
// .. let's use the underlying array buffer
|
||||
_data = _data.buffer;
|
||||
|
||||
const volume = new Volume();
|
||||
volume.header = headerObject;
|
||||
volume.segmentation = this.segmentation;
|
||||
//
|
||||
// parse the (unzipped) data to a datastream of the correct type
|
||||
//
|
||||
volume.data = new headerObject.__array( _data );
|
||||
// get the min and max intensities
|
||||
const min_max = volume.computeMinMax();
|
||||
const min = min_max[ 0 ];
|
||||
const 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 ];
|
||||
|
||||
// Identify axis order in the space-directions matrix from the header if possible.
|
||||
if ( headerObject.vectors ) {
|
||||
|
||||
const xIndex = headerObject.vectors.findIndex( vector => vector[ 0 ] !== 0 );
|
||||
const yIndex = headerObject.vectors.findIndex( vector => vector[ 1 ] !== 0 );
|
||||
const zIndex = headerObject.vectors.findIndex( vector => vector[ 2 ] !== 0 );
|
||||
|
||||
const axisOrder = [];
|
||||
|
||||
if ( xIndex !== yIndex && xIndex !== zIndex && yIndex !== zIndex ) {
|
||||
|
||||
axisOrder[ xIndex ] = 'x';
|
||||
axisOrder[ yIndex ] = 'y';
|
||||
axisOrder[ zIndex ] = 'z';
|
||||
|
||||
} else {
|
||||
|
||||
axisOrder[ 0 ] = 'x';
|
||||
axisOrder[ 1 ] = 'y';
|
||||
axisOrder[ 2 ] = 'z';
|
||||
|
||||
}
|
||||
|
||||
volume.axisOrder = axisOrder;
|
||||
|
||||
} else {
|
||||
|
||||
volume.axisOrder = [ 'x', 'y', 'z' ];
|
||||
|
||||
}
|
||||
|
||||
// spacing
|
||||
const spacingX = new Vector3().fromArray( headerObject.vectors[ 0 ] ).length();
|
||||
const spacingY = new Vector3().fromArray( headerObject.vectors[ 1 ] ).length();
|
||||
const spacingZ = new Vector3().fromArray( headerObject.vectors[ 2 ] ).length();
|
||||
volume.spacing = [ spacingX, spacingY, spacingZ ];
|
||||
|
||||
|
||||
// Create IJKtoRAS matrix
|
||||
volume.matrix = new Matrix4();
|
||||
|
||||
const transitionMatrix = new Matrix4();
|
||||
|
||||
if ( headerObject.space === 'left-posterior-superior' ) {
|
||||
|
||||
transitionMatrix.set(
|
||||
- 1, 0, 0, 0,
|
||||
0, - 1, 0, 0,
|
||||
0, 0, 1, 0,
|
||||
0, 0, 0, 1
|
||||
);
|
||||
|
||||
} else if ( headerObject.space === 'left-anterior-superior' ) {
|
||||
|
||||
transitionMatrix.set(
|
||||
1, 0, 0, 0,
|
||||
0, 1, 0, 0,
|
||||
0, 0, - 1, 0,
|
||||
0, 0, 0, 1
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
|
||||
if ( ! headerObject.vectors ) {
|
||||
|
||||
volume.matrix.set(
|
||||
1, 0, 0, 0,
|
||||
0, 1, 0, 0,
|
||||
0, 0, 1, 0,
|
||||
0, 0, 0, 1 );
|
||||
|
||||
} else {
|
||||
|
||||
const v = headerObject.vectors;
|
||||
|
||||
const ijk_to_transition = new Matrix4().set(
|
||||
v[ 0 ][ 0 ], v[ 1 ][ 0 ], v[ 2 ][ 0 ], 0,
|
||||
v[ 0 ][ 1 ], v[ 1 ][ 1 ], v[ 2 ][ 1 ], 0,
|
||||
v[ 0 ][ 2 ], v[ 1 ][ 2 ], v[ 2 ][ 2 ], 0,
|
||||
0, 0, 0, 1
|
||||
);
|
||||
|
||||
const transition_to_ras = new Matrix4().multiplyMatrices( ijk_to_transition, transitionMatrix );
|
||||
|
||||
volume.matrix = transition_to_ras;
|
||||
|
||||
}
|
||||
|
||||
volume.inverseMatrix = new Matrix4();
|
||||
volume.inverseMatrix.copy( volume.matrix ).invert();
|
||||
|
||||
volume.RASDimensions = [
|
||||
Math.floor( volume.xLength * spacingX ),
|
||||
Math.floor( volume.yLength * spacingY ),
|
||||
Math.floor( volume.zLength * spacingZ )
|
||||
];
|
||||
|
||||
// .. 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( array, start, end ) {
|
||||
|
||||
// without borders, use the whole array
|
||||
if ( start === undefined ) {
|
||||
|
||||
start = 0;
|
||||
|
||||
}
|
||||
|
||||
if ( end === undefined ) {
|
||||
|
||||
end = array.length;
|
||||
|
||||
}
|
||||
|
||||
let output = '';
|
||||
// create and append the chars
|
||||
let i = 0;
|
||||
for ( i = start; i < end; ++ i ) {
|
||||
|
||||
output += String.fromCharCode( array[ i ] );
|
||||
|
||||
}
|
||||
|
||||
return output;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const _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 ) {
|
||||
|
||||
let i;
|
||||
return this.sizes = ( function () {
|
||||
|
||||
const _ref = data.split( /\s+/ );
|
||||
const _results = [];
|
||||
|
||||
for ( let _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 ) {
|
||||
|
||||
let f, v;
|
||||
const parts = data.match( /\(.*?\)/g );
|
||||
return this.vectors = ( function () {
|
||||
|
||||
const _results = [];
|
||||
|
||||
for ( let _i = 0, _len = parts.length; _i < _len; _i ++ ) {
|
||||
|
||||
v = parts[ _i ];
|
||||
_results.push( ( function () {
|
||||
|
||||
const _ref = v.slice( 1, - 1 ).split( /,/ );
|
||||
const _results2 = [];
|
||||
|
||||
for ( let _j = 0, _len2 = _ref.length; _j < _len2; _j ++ ) {
|
||||
|
||||
f = _ref[ _j ];
|
||||
_results2.push( parseFloat( f ) );
|
||||
|
||||
}
|
||||
|
||||
return _results2;
|
||||
|
||||
} )() );
|
||||
|
||||
}
|
||||
|
||||
return _results;
|
||||
|
||||
} )();
|
||||
|
||||
},
|
||||
|
||||
spacings: function ( data ) {
|
||||
|
||||
let f;
|
||||
const parts = data.split( /\s+/ );
|
||||
return this.spacings = ( function () {
|
||||
|
||||
const _results = [];
|
||||
|
||||
for ( let _i = 0, _len = parts.length; _i < _len; _i ++ ) {
|
||||
|
||||
f = parts[ _i ];
|
||||
_results.push( parseFloat( f ) );
|
||||
|
||||
}
|
||||
|
||||
return _results;
|
||||
|
||||
} )();
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
export { NRRDLoader };
|
||||
905
public/sdk/three/jsm/loaders/OBJLoader.js
Normal file
905
public/sdk/three/jsm/loaders/OBJLoader.js
Normal file
@ -0,0 +1,905 @@
|
||||
import {
|
||||
BufferGeometry,
|
||||
FileLoader,
|
||||
Float32BufferAttribute,
|
||||
Group,
|
||||
LineBasicMaterial,
|
||||
LineSegments,
|
||||
Loader,
|
||||
Material,
|
||||
Mesh,
|
||||
MeshPhongMaterial,
|
||||
Points,
|
||||
PointsMaterial,
|
||||
Vector3,
|
||||
Color
|
||||
} from 'three';
|
||||
|
||||
// o object_name | g group_name
|
||||
const _object_pattern = /^[og]\s*(.+)?/;
|
||||
// mtllib file_reference
|
||||
const _material_library_pattern = /^mtllib /;
|
||||
// usemtl material_name
|
||||
const _material_use_pattern = /^usemtl /;
|
||||
// usemap map_name
|
||||
const _map_use_pattern = /^usemap /;
|
||||
const _face_vertex_data_separator_pattern = /\s+/;
|
||||
|
||||
const _vA = new Vector3();
|
||||
const _vB = new Vector3();
|
||||
const _vC = new Vector3();
|
||||
|
||||
const _ab = new Vector3();
|
||||
const _cb = new Vector3();
|
||||
|
||||
const _color = new Color();
|
||||
|
||||
function ParserState() {
|
||||
|
||||
const 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;
|
||||
|
||||
}
|
||||
|
||||
const 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 ) {
|
||||
|
||||
const 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 );
|
||||
|
||||
}
|
||||
|
||||
const 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 ) {
|
||||
|
||||
const 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 ) {
|
||||
|
||||
const 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 ( let 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' ) {
|
||||
|
||||
const 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 ) {
|
||||
|
||||
const index = parseInt( value, 10 );
|
||||
return ( index >= 0 ? index - 1 : index + len / 3 ) * 3;
|
||||
|
||||
},
|
||||
|
||||
parseNormalIndex: function ( value, len ) {
|
||||
|
||||
const index = parseInt( value, 10 );
|
||||
return ( index >= 0 ? index - 1 : index + len / 3 ) * 3;
|
||||
|
||||
},
|
||||
|
||||
parseUVIndex: function ( value, len ) {
|
||||
|
||||
const index = parseInt( value, 10 );
|
||||
return ( index >= 0 ? index - 1 : index + len / 2 ) * 2;
|
||||
|
||||
},
|
||||
|
||||
addVertex: function ( a, b, c ) {
|
||||
|
||||
const src = this.vertices;
|
||||
const 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 ) {
|
||||
|
||||
const src = this.vertices;
|
||||
const dst = this.object.geometry.vertices;
|
||||
|
||||
dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] );
|
||||
|
||||
},
|
||||
|
||||
addVertexLine: function ( a ) {
|
||||
|
||||
const src = this.vertices;
|
||||
const dst = this.object.geometry.vertices;
|
||||
|
||||
dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] );
|
||||
|
||||
},
|
||||
|
||||
addNormal: function ( a, b, c ) {
|
||||
|
||||
const src = this.normals;
|
||||
const 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 ) {
|
||||
|
||||
const src = this.vertices;
|
||||
const 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 ) {
|
||||
|
||||
const src = this.colors;
|
||||
const 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 ) {
|
||||
|
||||
const src = this.uvs;
|
||||
const 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 () {
|
||||
|
||||
const dst = this.object.geometry.uvs;
|
||||
|
||||
dst.push( 0, 0 );
|
||||
dst.push( 0, 0 );
|
||||
dst.push( 0, 0 );
|
||||
|
||||
},
|
||||
|
||||
addUVLine: function ( a ) {
|
||||
|
||||
const src = this.uvs;
|
||||
const dst = this.object.geometry.uvs;
|
||||
|
||||
dst.push( src[ a + 0 ], src[ a + 1 ] );
|
||||
|
||||
},
|
||||
|
||||
addFace: function ( a, b, c, ua, ub, uc, na, nb, nc ) {
|
||||
|
||||
const vLen = this.vertices.length;
|
||||
|
||||
let ia = this.parseVertexIndex( a, vLen );
|
||||
let ib = this.parseVertexIndex( b, vLen );
|
||||
let ic = this.parseVertexIndex( c, vLen );
|
||||
|
||||
this.addVertex( ia, ib, ic );
|
||||
this.addColor( ia, ib, ic );
|
||||
|
||||
// normals
|
||||
|
||||
if ( na !== undefined && na !== '' ) {
|
||||
|
||||
const 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 !== '' ) {
|
||||
|
||||
const 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';
|
||||
|
||||
const vLen = this.vertices.length;
|
||||
|
||||
for ( let vi = 0, l = vertices.length; vi < l; vi ++ ) {
|
||||
|
||||
const index = this.parseVertexIndex( vertices[ vi ], vLen );
|
||||
|
||||
this.addVertexPoint( index );
|
||||
this.addColor( index );
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
addLineGeometry: function ( vertices, uvs ) {
|
||||
|
||||
this.object.geometry.type = 'Line';
|
||||
|
||||
const vLen = this.vertices.length;
|
||||
const uvLen = this.uvs.length;
|
||||
|
||||
for ( let vi = 0, l = vertices.length; vi < l; vi ++ ) {
|
||||
|
||||
this.addVertexLine( this.parseVertexIndex( vertices[ vi ], vLen ) );
|
||||
|
||||
}
|
||||
|
||||
for ( let uvi = 0, l = uvs.length; uvi < l; uvi ++ ) {
|
||||
|
||||
this.addUVLine( this.parseUVIndex( uvs[ uvi ], uvLen ) );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
state.startObject( '', false );
|
||||
|
||||
return state;
|
||||
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
class OBJLoader extends Loader {
|
||||
|
||||
constructor( manager ) {
|
||||
|
||||
super( manager );
|
||||
|
||||
this.materials = null;
|
||||
|
||||
}
|
||||
|
||||
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 );
|
||||
|
||||
}
|
||||
|
||||
setMaterials( materials ) {
|
||||
|
||||
this.materials = materials;
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
parse( text ) {
|
||||
|
||||
const 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, '' );
|
||||
|
||||
}
|
||||
|
||||
const lines = text.split( '\n' );
|
||||
let result = [];
|
||||
|
||||
for ( let i = 0, l = lines.length; i < l; i ++ ) {
|
||||
|
||||
const line = lines[ i ].trimStart();
|
||||
|
||||
if ( line.length === 0 ) continue;
|
||||
|
||||
const lineFirstChar = line.charAt( 0 );
|
||||
|
||||
// @todo invoke passed in handler if any
|
||||
if ( lineFirstChar === '#' ) continue; // skip comments
|
||||
|
||||
if ( lineFirstChar === 'v' ) {
|
||||
|
||||
const data = line.split( _face_vertex_data_separator_pattern );
|
||||
|
||||
switch ( data[ 0 ] ) {
|
||||
|
||||
case 'v':
|
||||
state.vertices.push(
|
||||
parseFloat( data[ 1 ] ),
|
||||
parseFloat( data[ 2 ] ),
|
||||
parseFloat( data[ 3 ] )
|
||||
);
|
||||
if ( data.length >= 7 ) {
|
||||
|
||||
_color.setRGB(
|
||||
parseFloat( data[ 4 ] ),
|
||||
parseFloat( data[ 5 ] ),
|
||||
parseFloat( data[ 6 ] )
|
||||
).convertSRGBToLinear();
|
||||
|
||||
state.colors.push( _color.r, _color.g, _color.b );
|
||||
|
||||
} 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' ) {
|
||||
|
||||
const lineData = line.slice( 1 ).trim();
|
||||
const vertexData = lineData.split( _face_vertex_data_separator_pattern );
|
||||
const faceVertices = [];
|
||||
|
||||
// Parse the face vertex data into an easy to work with format
|
||||
|
||||
for ( let j = 0, jl = vertexData.length; j < jl; j ++ ) {
|
||||
|
||||
const vertex = vertexData[ j ];
|
||||
|
||||
if ( vertex.length > 0 ) {
|
||||
|
||||
const vertexParts = vertex.split( '/' );
|
||||
faceVertices.push( vertexParts );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Draw an edge between the first vertex and all subsequent vertices to form an n-gon
|
||||
|
||||
const v1 = faceVertices[ 0 ];
|
||||
|
||||
for ( let j = 1, jl = faceVertices.length - 1; j < jl; j ++ ) {
|
||||
|
||||
const v2 = faceVertices[ j ];
|
||||
const 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' ) {
|
||||
|
||||
const lineParts = line.substring( 1 ).trim().split( ' ' );
|
||||
let lineVertices = [];
|
||||
const lineUVs = [];
|
||||
|
||||
if ( line.indexOf( '/' ) === - 1 ) {
|
||||
|
||||
lineVertices = lineParts;
|
||||
|
||||
} else {
|
||||
|
||||
for ( let li = 0, llen = lineParts.length; li < llen; li ++ ) {
|
||||
|
||||
const 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' ) {
|
||||
|
||||
const lineData = line.slice( 1 ).trim();
|
||||
const 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
|
||||
// let name = result[ 0 ].slice( 1 ).trim();
|
||||
const name = ( ' ' + result[ 0 ].slice( 1 ).trim() ).slice( 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/
|
||||
*
|
||||
* 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 ) {
|
||||
|
||||
const value = result[ 1 ].trim().toLowerCase();
|
||||
state.object.smooth = ( value !== '0' && value !== 'off' );
|
||||
|
||||
} else {
|
||||
|
||||
// ZBrush can produce "s" lines #11707
|
||||
state.object.smooth = true;
|
||||
|
||||
}
|
||||
|
||||
const 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();
|
||||
|
||||
const container = new Group();
|
||||
container.materialLibraries = [].concat( state.materialLibraries );
|
||||
|
||||
const hasPrimitives = ! ( state.objects.length === 1 && state.objects[ 0 ].geometry.vertices.length === 0 );
|
||||
|
||||
if ( hasPrimitives === true ) {
|
||||
|
||||
for ( let i = 0, l = state.objects.length; i < l; i ++ ) {
|
||||
|
||||
const object = state.objects[ i ];
|
||||
const geometry = object.geometry;
|
||||
const materials = object.materials;
|
||||
const isLine = ( geometry.type === 'Line' );
|
||||
const isPoints = ( geometry.type === 'Points' );
|
||||
let hasVertexColors = false;
|
||||
|
||||
// Skip o/g line declarations that did not follow with any faces
|
||||
if ( geometry.vertices.length === 0 ) continue;
|
||||
|
||||
const 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
|
||||
|
||||
const createdMaterials = [];
|
||||
|
||||
for ( let mi = 0, miLen = materials.length; mi < miLen; mi ++ ) {
|
||||
|
||||
const sourceMaterial = materials[ mi ];
|
||||
const materialHash = sourceMaterial.name + '_' + sourceMaterial.smooth + '_' + hasVertexColors;
|
||||
let 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 ) ) {
|
||||
|
||||
const materialLine = new LineBasicMaterial();
|
||||
Material.prototype.copy.call( materialLine, material );
|
||||
materialLine.color.copy( material.color );
|
||||
material = materialLine;
|
||||
|
||||
} else if ( isPoints && material && ! ( material instanceof PointsMaterial ) ) {
|
||||
|
||||
const 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
|
||||
|
||||
let mesh;
|
||||
|
||||
if ( createdMaterials.length > 1 ) {
|
||||
|
||||
for ( let mi = 0, miLen = materials.length; mi < miLen; mi ++ ) {
|
||||
|
||||
const 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 ) {
|
||||
|
||||
const material = new PointsMaterial( { size: 1, sizeAttenuation: false } );
|
||||
|
||||
const 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;
|
||||
|
||||
}
|
||||
|
||||
const points = new Points( buffergeometry, material );
|
||||
container.add( points );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return container;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export { OBJLoader };
|
||||
467
public/sdk/three/jsm/loaders/PCDLoader.js
Normal file
467
public/sdk/three/jsm/loaders/PCDLoader.js
Normal file
@ -0,0 +1,467 @@
|
||||
import {
|
||||
BufferGeometry,
|
||||
Color,
|
||||
FileLoader,
|
||||
Float32BufferAttribute,
|
||||
Int32BufferAttribute,
|
||||
Loader,
|
||||
Points,
|
||||
PointsMaterial
|
||||
} from 'three';
|
||||
|
||||
class PCDLoader extends Loader {
|
||||
|
||||
constructor( manager ) {
|
||||
|
||||
super( manager );
|
||||
|
||||
this.littleEndian = true;
|
||||
|
||||
}
|
||||
|
||||
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.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( data ) {
|
||||
|
||||
// from https://gitlab.com/taketwo/three-pcd-loader/blob/master/decompress-lzf.js
|
||||
|
||||
function decompressLZF( inData, outLength ) {
|
||||
|
||||
const inLength = inData.length;
|
||||
const outData = new Uint8Array( outLength );
|
||||
let inPtr = 0;
|
||||
let outPtr = 0;
|
||||
let ctrl;
|
||||
let len;
|
||||
let 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 ) {
|
||||
|
||||
const PCDheader = {};
|
||||
const result1 = data.search( /[\r\n]DATA\s(\S*)\s/i );
|
||||
const result2 = /[\r\n]DATA\s(\S*)\s/i.exec( data.slice( result1 - 1 ) );
|
||||
|
||||
PCDheader.data = result2[ 1 ];
|
||||
PCDheader.headerLen = result2[ 0 ].length + result1;
|
||||
PCDheader.str = data.slice( 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 ] );
|
||||
|
||||
PCDheader.fields = ( PCDheader.fields !== null ) ? 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 ( let i = 0, l = PCDheader.fields.length; i < l; i ++ ) {
|
||||
|
||||
PCDheader.count.push( 1 );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
PCDheader.offset = {};
|
||||
|
||||
let sizeSum = 0;
|
||||
|
||||
for ( let 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;
|
||||
|
||||
}
|
||||
|
||||
const textData = new TextDecoder().decode( data );
|
||||
|
||||
// parse header (always ascii format)
|
||||
|
||||
const PCDheader = parseHeader( textData );
|
||||
|
||||
// parse data
|
||||
|
||||
const position = [];
|
||||
const normal = [];
|
||||
const color = [];
|
||||
const intensity = [];
|
||||
const label = [];
|
||||
|
||||
const c = new Color();
|
||||
|
||||
// ascii
|
||||
|
||||
if ( PCDheader.data === 'ascii' ) {
|
||||
|
||||
const offset = PCDheader.offset;
|
||||
const pcdData = textData.slice( PCDheader.headerLen );
|
||||
const lines = pcdData.split( '\n' );
|
||||
|
||||
for ( let i = 0, l = lines.length; i < l; i ++ ) {
|
||||
|
||||
if ( lines[ i ] === '' ) continue;
|
||||
|
||||
const 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 ) {
|
||||
|
||||
const rgb_field_index = PCDheader.fields.findIndex( ( field ) => field === 'rgb' );
|
||||
const rgb_type = PCDheader.type[ rgb_field_index ];
|
||||
|
||||
const float = parseFloat( line[ offset.rgb ] );
|
||||
let rgb = float;
|
||||
|
||||
if ( rgb_type === 'F' ) {
|
||||
|
||||
// treat float values as int
|
||||
// https://github.com/daavoo/pyntcloud/pull/204/commits/7b4205e64d5ed09abe708b2e91b615690c24d518
|
||||
const farr = new Float32Array( 1 );
|
||||
farr[ 0 ] = float;
|
||||
rgb = new Int32Array( farr.buffer )[ 0 ];
|
||||
|
||||
}
|
||||
|
||||
const r = ( ( rgb >> 16 ) & 0x0000ff ) / 255;
|
||||
const g = ( ( rgb >> 8 ) & 0x0000ff ) / 255;
|
||||
const b = ( ( rgb >> 0 ) & 0x0000ff ) / 255;
|
||||
|
||||
c.set( r, g, b ).convertSRGBToLinear();
|
||||
|
||||
color.push( c.r, c.g, c.b );
|
||||
|
||||
}
|
||||
|
||||
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 ] ) );
|
||||
|
||||
}
|
||||
|
||||
if ( offset.intensity !== undefined ) {
|
||||
|
||||
intensity.push( parseFloat( line[ offset.intensity ] ) );
|
||||
|
||||
}
|
||||
|
||||
if ( offset.label !== undefined ) {
|
||||
|
||||
label.push( parseInt( line[ offset.label ] ) );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 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' ) {
|
||||
|
||||
const sizes = new Uint32Array( data.slice( PCDheader.headerLen, PCDheader.headerLen + 8 ) );
|
||||
const compressedSize = sizes[ 0 ];
|
||||
const decompressedSize = sizes[ 1 ];
|
||||
const decompressed = decompressLZF( new Uint8Array( data, PCDheader.headerLen + 8, compressedSize ), decompressedSize );
|
||||
const dataview = new DataView( decompressed.buffer );
|
||||
|
||||
const offset = PCDheader.offset;
|
||||
|
||||
for ( let i = 0; i < PCDheader.points; i ++ ) {
|
||||
|
||||
if ( offset.x !== undefined ) {
|
||||
|
||||
const xIndex = PCDheader.fields.indexOf( 'x' );
|
||||
const yIndex = PCDheader.fields.indexOf( 'y' );
|
||||
const zIndex = PCDheader.fields.indexOf( 'z' );
|
||||
position.push( dataview.getFloat32( ( PCDheader.points * offset.x ) + PCDheader.size[ xIndex ] * i, this.littleEndian ) );
|
||||
position.push( dataview.getFloat32( ( PCDheader.points * offset.y ) + PCDheader.size[ yIndex ] * i, this.littleEndian ) );
|
||||
position.push( dataview.getFloat32( ( PCDheader.points * offset.z ) + PCDheader.size[ zIndex ] * i, this.littleEndian ) );
|
||||
|
||||
}
|
||||
|
||||
if ( offset.rgb !== undefined ) {
|
||||
|
||||
const rgbIndex = PCDheader.fields.indexOf( 'rgb' );
|
||||
|
||||
const r = dataview.getUint8( ( PCDheader.points * offset.rgb ) + PCDheader.size[ rgbIndex ] * i + 2 ) / 255.0;
|
||||
const g = dataview.getUint8( ( PCDheader.points * offset.rgb ) + PCDheader.size[ rgbIndex ] * i + 1 ) / 255.0;
|
||||
const b = dataview.getUint8( ( PCDheader.points * offset.rgb ) + PCDheader.size[ rgbIndex ] * i + 0 ) / 255.0;
|
||||
|
||||
c.set( r, g, b ).convertSRGBToLinear();
|
||||
|
||||
color.push( c.r, c.g, c.b );
|
||||
|
||||
}
|
||||
|
||||
if ( offset.normal_x !== undefined ) {
|
||||
|
||||
const xIndex = PCDheader.fields.indexOf( 'normal_x' );
|
||||
const yIndex = PCDheader.fields.indexOf( 'normal_y' );
|
||||
const zIndex = PCDheader.fields.indexOf( 'normal_z' );
|
||||
normal.push( dataview.getFloat32( ( PCDheader.points * offset.normal_x ) + PCDheader.size[ xIndex ] * i, this.littleEndian ) );
|
||||
normal.push( dataview.getFloat32( ( PCDheader.points * offset.normal_y ) + PCDheader.size[ yIndex ] * i, this.littleEndian ) );
|
||||
normal.push( dataview.getFloat32( ( PCDheader.points * offset.normal_z ) + PCDheader.size[ zIndex ] * i, this.littleEndian ) );
|
||||
|
||||
}
|
||||
|
||||
if ( offset.intensity !== undefined ) {
|
||||
|
||||
const intensityIndex = PCDheader.fields.indexOf( 'intensity' );
|
||||
intensity.push( dataview.getFloat32( ( PCDheader.points * offset.intensity ) + PCDheader.size[ intensityIndex ] * i, this.littleEndian ) );
|
||||
|
||||
}
|
||||
|
||||
if ( offset.label !== undefined ) {
|
||||
|
||||
const labelIndex = PCDheader.fields.indexOf( 'label' );
|
||||
label.push( dataview.getInt32( ( PCDheader.points * offset.label ) + PCDheader.size[ labelIndex ] * i, this.littleEndian ) );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// binary
|
||||
|
||||
if ( PCDheader.data === 'binary' ) {
|
||||
|
||||
const dataview = new DataView( data, PCDheader.headerLen );
|
||||
const offset = PCDheader.offset;
|
||||
|
||||
for ( let 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 ) {
|
||||
|
||||
const r = dataview.getUint8( row + offset.rgb + 2 ) / 255.0;
|
||||
const g = dataview.getUint8( row + offset.rgb + 1 ) / 255.0;
|
||||
const b = dataview.getUint8( row + offset.rgb + 0 ) / 255.0;
|
||||
|
||||
c.set( r, g, b ).convertSRGBToLinear();
|
||||
|
||||
color.push( c.r, c.g, c.b );
|
||||
|
||||
}
|
||||
|
||||
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 ) );
|
||||
|
||||
}
|
||||
|
||||
if ( offset.intensity !== undefined ) {
|
||||
|
||||
intensity.push( dataview.getFloat32( row + offset.intensity, this.littleEndian ) );
|
||||
|
||||
}
|
||||
|
||||
if ( offset.label !== undefined ) {
|
||||
|
||||
label.push( dataview.getInt32( row + offset.label, this.littleEndian ) );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// build geometry
|
||||
|
||||
const 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 ) );
|
||||
if ( intensity.length > 0 ) geometry.setAttribute( 'intensity', new Float32BufferAttribute( intensity, 1 ) );
|
||||
if ( label.length > 0 ) geometry.setAttribute( 'label', new Int32BufferAttribute( label, 1 ) );
|
||||
|
||||
geometry.computeBoundingSphere();
|
||||
|
||||
// build material
|
||||
|
||||
const material = new PointsMaterial( { size: 0.005 } );
|
||||
|
||||
if ( color.length > 0 ) {
|
||||
|
||||
material.vertexColors = true;
|
||||
|
||||
}
|
||||
|
||||
// build point cloud
|
||||
|
||||
return new Points( geometry, material );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export { PCDLoader };
|
||||
232
public/sdk/three/jsm/loaders/PDBLoader.js
Normal file
232
public/sdk/three/jsm/loaders/PDBLoader.js
Normal file
@ -0,0 +1,232 @@
|
||||
import {
|
||||
BufferGeometry,
|
||||
FileLoader,
|
||||
Float32BufferAttribute,
|
||||
Loader,
|
||||
Color
|
||||
} from 'three';
|
||||
|
||||
class PDBLoader extends Loader {
|
||||
|
||||
constructor( manager ) {
|
||||
|
||||
super( manager );
|
||||
|
||||
}
|
||||
|
||||
load( url, onLoad, onProgress, onError ) {
|
||||
|
||||
const scope = this;
|
||||
|
||||
const 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( text ) {
|
||||
|
||||
function trim( text ) {
|
||||
|
||||
return text.replace( /^\s\s*/, '' ).replace( /\s\s*$/, '' );
|
||||
|
||||
}
|
||||
|
||||
function capitalize( text ) {
|
||||
|
||||
return text.charAt( 0 ).toUpperCase() + text.slice( 1 ).toLowerCase();
|
||||
|
||||
}
|
||||
|
||||
function hash( s, e ) {
|
||||
|
||||
return 's' + Math.min( s, e ) + 'e' + Math.max( s, e );
|
||||
|
||||
}
|
||||
|
||||
function parseBond( start, length, satom, i ) {
|
||||
|
||||
const eatom = parseInt( lines[ i ].slice( start, start + length ) );
|
||||
|
||||
if ( eatom ) {
|
||||
|
||||
const 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() {
|
||||
|
||||
const build = {
|
||||
geometryAtoms: new BufferGeometry(),
|
||||
geometryBonds: new BufferGeometry(),
|
||||
json: {
|
||||
atoms: atoms
|
||||
}
|
||||
};
|
||||
|
||||
const geometryAtoms = build.geometryAtoms;
|
||||
const geometryBonds = build.geometryBonds;
|
||||
|
||||
const verticesAtoms = [];
|
||||
const colorsAtoms = [];
|
||||
const verticesBonds = [];
|
||||
|
||||
// atoms
|
||||
|
||||
const c = new Color();
|
||||
|
||||
for ( let i = 0, l = atoms.length; i < l; i ++ ) {
|
||||
|
||||
const atom = atoms[ i ];
|
||||
|
||||
const x = atom[ 0 ];
|
||||
const y = atom[ 1 ];
|
||||
const z = atom[ 2 ];
|
||||
|
||||
verticesAtoms.push( x, y, z );
|
||||
|
||||
const r = atom[ 3 ][ 0 ] / 255;
|
||||
const g = atom[ 3 ][ 1 ] / 255;
|
||||
const b = atom[ 3 ][ 2 ] / 255;
|
||||
|
||||
c.set( r, g, b ).convertSRGBToLinear();
|
||||
|
||||
colorsAtoms.push( c.r, c.g, c.b );
|
||||
|
||||
}
|
||||
|
||||
// bonds
|
||||
|
||||
for ( let i = 0, l = _bonds.length; i < l; i ++ ) {
|
||||
|
||||
const bond = _bonds[ i ];
|
||||
|
||||
const start = bond[ 0 ];
|
||||
const end = bond[ 1 ];
|
||||
|
||||
const startAtom = _atomMap[ start ];
|
||||
const endAtom = _atomMap[ end ];
|
||||
|
||||
let x = startAtom[ 0 ];
|
||||
let y = startAtom[ 1 ];
|
||||
let 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;
|
||||
|
||||
}
|
||||
|
||||
const 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 ] };
|
||||
|
||||
const atoms = [];
|
||||
|
||||
const _bonds = [];
|
||||
const _bhash = {};
|
||||
const _atomMap = {};
|
||||
|
||||
// parse
|
||||
|
||||
const lines = text.split( '\n' );
|
||||
|
||||
for ( let i = 0, l = lines.length; i < l; i ++ ) {
|
||||
|
||||
if ( lines[ i ].slice( 0, 4 ) === 'ATOM' || lines[ i ].slice( 0, 6 ) === 'HETATM' ) {
|
||||
|
||||
const x = parseFloat( lines[ i ].slice( 30, 37 ) );
|
||||
const y = parseFloat( lines[ i ].slice( 38, 45 ) );
|
||||
const z = parseFloat( lines[ i ].slice( 46, 53 ) );
|
||||
const index = parseInt( lines[ i ].slice( 6, 11 ) ) - 1;
|
||||
|
||||
let e = trim( lines[ i ].slice( 76, 78 ) ).toLowerCase();
|
||||
|
||||
if ( e === '' ) {
|
||||
|
||||
e = trim( lines[ i ].slice( 12, 14 ) ).toLowerCase();
|
||||
|
||||
}
|
||||
|
||||
const atomData = [ x, y, z, CPK[ e ], capitalize( e ) ];
|
||||
|
||||
atoms.push( atomData );
|
||||
_atomMap[ index ] = atomData;
|
||||
|
||||
} else if ( lines[ i ].slice( 0, 6 ) === 'CONECT' ) {
|
||||
|
||||
const satom = parseInt( lines[ i ].slice( 6, 11 ) );
|
||||
|
||||
parseBond( 11, 5, satom, i );
|
||||
parseBond( 16, 5, satom, i );
|
||||
parseBond( 21, 5, satom, i );
|
||||
parseBond( 26, 5, satom, i );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// build and return geometry
|
||||
|
||||
return buildGeometry();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export { PDBLoader };
|
||||
771
public/sdk/three/jsm/loaders/PLYLoader.js
Normal file
771
public/sdk/three/jsm/loaders/PLYLoader.js
Normal file
@ -0,0 +1,771 @@
|
||||
import {
|
||||
BufferGeometry,
|
||||
FileLoader,
|
||||
Float32BufferAttribute,
|
||||
Loader,
|
||||
Color
|
||||
} from 'three';
|
||||
|
||||
/**
|
||||
* 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:
|
||||
* const 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'
|
||||
* } );
|
||||
*
|
||||
* Custom properties outside of the defaults for position, uv, normal
|
||||
* and color attributes can be added using the setCustomPropertyNameMapping method.
|
||||
* For example, the following maps the element properties “custom_property_a”
|
||||
* and “custom_property_b” to an attribute “customAttribute” with an item size of 2.
|
||||
* Attribute item sizes are set from the number of element properties in the property array.
|
||||
*
|
||||
* loader.setCustomPropertyNameMapping( {
|
||||
* customAttribute: ['custom_property_a', 'custom_property_b'],
|
||||
* } );
|
||||
*
|
||||
*/
|
||||
|
||||
const _color = new Color();
|
||||
|
||||
class PLYLoader extends Loader {
|
||||
|
||||
constructor( manager ) {
|
||||
|
||||
super( manager );
|
||||
|
||||
this.propertyNameMapping = {};
|
||||
this.customPropertyMapping = {};
|
||||
|
||||
}
|
||||
|
||||
load( url, onLoad, onProgress, onError ) {
|
||||
|
||||
const scope = this;
|
||||
|
||||
const 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( mapping ) {
|
||||
|
||||
this.propertyNameMapping = mapping;
|
||||
|
||||
}
|
||||
|
||||
setCustomPropertyNameMapping( mapping ) {
|
||||
|
||||
this.customPropertyMapping = mapping;
|
||||
|
||||
}
|
||||
|
||||
parse( data ) {
|
||||
|
||||
function parseHeader( data, headerLength = 0 ) {
|
||||
|
||||
const patternHeader = /^ply([\s\S]*)end_header(\r\n|\r|\n)/;
|
||||
let headerText = '';
|
||||
const result = patternHeader.exec( data );
|
||||
|
||||
if ( result !== null ) {
|
||||
|
||||
headerText = result[ 1 ];
|
||||
|
||||
}
|
||||
|
||||
const header = {
|
||||
comments: [],
|
||||
elements: [],
|
||||
headerLength: headerLength,
|
||||
objInfo: ''
|
||||
};
|
||||
|
||||
const lines = headerText.split( /\r\n|\r|\n/ );
|
||||
let currentElement;
|
||||
|
||||
function make_ply_element_property( propertValues, propertyNameMapping ) {
|
||||
|
||||
const 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 ( let i = 0; i < lines.length; i ++ ) {
|
||||
|
||||
let line = lines[ i ];
|
||||
line = line.trim();
|
||||
|
||||
if ( line === '' ) continue;
|
||||
|
||||
const lineValues = line.split( /\s+/ );
|
||||
const 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, tokens ) {
|
||||
|
||||
const element = {};
|
||||
|
||||
for ( let i = 0; i < properties.length; i ++ ) {
|
||||
|
||||
if ( tokens.empty() ) return null;
|
||||
|
||||
if ( properties[ i ].type === 'list' ) {
|
||||
|
||||
const list = [];
|
||||
const n = parseASCIINumber( tokens.next(), properties[ i ].countType );
|
||||
|
||||
for ( let j = 0; j < n; j ++ ) {
|
||||
|
||||
if ( tokens.empty() ) return null;
|
||||
|
||||
list.push( parseASCIINumber( tokens.next(), properties[ i ].itemType ) );
|
||||
|
||||
}
|
||||
|
||||
element[ properties[ i ].name ] = list;
|
||||
|
||||
} else {
|
||||
|
||||
element[ properties[ i ].name ] = parseASCIINumber( tokens.next(), properties[ i ].type );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return element;
|
||||
|
||||
}
|
||||
|
||||
function createBuffer() {
|
||||
|
||||
const buffer = {
|
||||
indices: [],
|
||||
vertices: [],
|
||||
normals: [],
|
||||
uvs: [],
|
||||
faceVertexUvs: [],
|
||||
colors: [],
|
||||
faceVertexColors: []
|
||||
};
|
||||
|
||||
for ( const customProperty of Object.keys( scope.customPropertyMapping ) ) {
|
||||
|
||||
buffer[ customProperty ] = [];
|
||||
|
||||
}
|
||||
|
||||
return buffer;
|
||||
|
||||
}
|
||||
|
||||
function mapElementAttributes( properties ) {
|
||||
|
||||
const elementNames = properties.map( property => {
|
||||
|
||||
return property.name;
|
||||
|
||||
} );
|
||||
|
||||
function findAttrName( names ) {
|
||||
|
||||
for ( let i = 0, l = names.length; i < l; i ++ ) {
|
||||
|
||||
const name = names[ i ];
|
||||
|
||||
if ( elementNames.includes( name ) ) return name;
|
||||
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
return {
|
||||
attrX: findAttrName( [ 'x', 'px', 'posx' ] ) || 'x',
|
||||
attrY: findAttrName( [ 'y', 'py', 'posy' ] ) || 'y',
|
||||
attrZ: findAttrName( [ 'z', 'pz', 'posz' ] ) || 'z',
|
||||
attrNX: findAttrName( [ 'nx', 'normalx' ] ),
|
||||
attrNY: findAttrName( [ 'ny', 'normaly' ] ),
|
||||
attrNZ: findAttrName( [ 'nz', 'normalz' ] ),
|
||||
attrS: findAttrName( [ 's', 'u', 'texture_u', 'tx' ] ),
|
||||
attrT: findAttrName( [ 't', 'v', 'texture_v', 'ty' ] ),
|
||||
attrR: findAttrName( [ 'red', 'diffuse_red', 'r', 'diffuse_r' ] ),
|
||||
attrG: findAttrName( [ 'green', 'diffuse_green', 'g', 'diffuse_g' ] ),
|
||||
attrB: findAttrName( [ 'blue', 'diffuse_blue', 'b', 'diffuse_b' ] ),
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
function parseASCII( data, header ) {
|
||||
|
||||
// PLY ascii format specification, as per http://en.wikipedia.org/wiki/PLY_(file_format)
|
||||
|
||||
const buffer = createBuffer();
|
||||
|
||||
const patternBody = /end_header\s+(\S[\s\S]*\S|\S)\s*$/;
|
||||
let body, matches;
|
||||
|
||||
if ( ( matches = patternBody.exec( data ) ) !== null ) {
|
||||
|
||||
body = matches[ 1 ].split( /\s+/ );
|
||||
|
||||
} else {
|
||||
|
||||
body = [ ];
|
||||
|
||||
}
|
||||
|
||||
const tokens = new ArrayStream( body );
|
||||
|
||||
loop: for ( let i = 0; i < header.elements.length; i ++ ) {
|
||||
|
||||
const elementDesc = header.elements[ i ];
|
||||
const attributeMap = mapElementAttributes( elementDesc.properties );
|
||||
|
||||
for ( let j = 0; j < elementDesc.count; j ++ ) {
|
||||
|
||||
const element = parseASCIIElement( elementDesc.properties, tokens );
|
||||
|
||||
if ( ! element ) break loop;
|
||||
|
||||
handleElement( buffer, elementDesc.name, element, attributeMap );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return postProcess( buffer );
|
||||
|
||||
}
|
||||
|
||||
function postProcess( buffer ) {
|
||||
|
||||
let 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 || buffer.faceVertexColors.length > 0 ) {
|
||||
|
||||
geometry = geometry.toNonIndexed();
|
||||
|
||||
if ( buffer.faceVertexUvs.length > 0 ) geometry.setAttribute( 'uv', new Float32BufferAttribute( buffer.faceVertexUvs, 2 ) );
|
||||
if ( buffer.faceVertexColors.length > 0 ) geometry.setAttribute( 'color', new Float32BufferAttribute( buffer.faceVertexColors, 3 ) );
|
||||
|
||||
}
|
||||
|
||||
// custom buffer data
|
||||
|
||||
for ( const customProperty of Object.keys( scope.customPropertyMapping ) ) {
|
||||
|
||||
if ( buffer[ customProperty ].length > 0 ) {
|
||||
|
||||
geometry.setAttribute(
|
||||
customProperty,
|
||||
new Float32BufferAttribute(
|
||||
buffer[ customProperty ],
|
||||
scope.customPropertyMapping[ customProperty ].length
|
||||
)
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
geometry.computeBoundingSphere();
|
||||
|
||||
return geometry;
|
||||
|
||||
}
|
||||
|
||||
function handleElement( buffer, elementName, element, cacheEntry ) {
|
||||
|
||||
if ( elementName === 'vertex' ) {
|
||||
|
||||
buffer.vertices.push( element[ cacheEntry.attrX ], element[ cacheEntry.attrY ], element[ cacheEntry.attrZ ] );
|
||||
|
||||
if ( cacheEntry.attrNX !== null && cacheEntry.attrNY !== null && cacheEntry.attrNZ !== null ) {
|
||||
|
||||
buffer.normals.push( element[ cacheEntry.attrNX ], element[ cacheEntry.attrNY ], element[ cacheEntry.attrNZ ] );
|
||||
|
||||
}
|
||||
|
||||
if ( cacheEntry.attrS !== null && cacheEntry.attrT !== null ) {
|
||||
|
||||
buffer.uvs.push( element[ cacheEntry.attrS ], element[ cacheEntry.attrT ] );
|
||||
|
||||
}
|
||||
|
||||
if ( cacheEntry.attrR !== null && cacheEntry.attrG !== null && cacheEntry.attrB !== null ) {
|
||||
|
||||
_color.setRGB(
|
||||
element[ cacheEntry.attrR ] / 255.0,
|
||||
element[ cacheEntry.attrG ] / 255.0,
|
||||
element[ cacheEntry.attrB ] / 255.0
|
||||
).convertSRGBToLinear();
|
||||
|
||||
buffer.colors.push( _color.r, _color.g, _color.b );
|
||||
|
||||
}
|
||||
|
||||
for ( const customProperty of Object.keys( scope.customPropertyMapping ) ) {
|
||||
|
||||
for ( const elementProperty of scope.customPropertyMapping[ customProperty ] ) {
|
||||
|
||||
buffer[ customProperty ].push( element[ elementProperty ] );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} else if ( elementName === 'face' ) {
|
||||
|
||||
const vertex_indices = element.vertex_indices || element.vertex_index; // issue #9338
|
||||
const 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 ] );
|
||||
|
||||
}
|
||||
|
||||
// face colors
|
||||
|
||||
if ( cacheEntry.attrR !== null && cacheEntry.attrG !== null && cacheEntry.attrB !== null ) {
|
||||
|
||||
_color.setRGB(
|
||||
element[ cacheEntry.attrR ] / 255.0,
|
||||
element[ cacheEntry.attrG ] / 255.0,
|
||||
element[ cacheEntry.attrB ] / 255.0
|
||||
).convertSRGBToLinear();
|
||||
buffer.faceVertexColors.push( _color.r, _color.g, _color.b );
|
||||
buffer.faceVertexColors.push( _color.r, _color.g, _color.b );
|
||||
buffer.faceVertexColors.push( _color.r, _color.g, _color.b );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function binaryReadElement( at, properties ) {
|
||||
|
||||
const element = {};
|
||||
let read = 0;
|
||||
|
||||
for ( let i = 0; i < properties.length; i ++ ) {
|
||||
|
||||
const property = properties[ i ];
|
||||
const valueReader = property.valueReader;
|
||||
|
||||
if ( property.type === 'list' ) {
|
||||
|
||||
const list = [];
|
||||
|
||||
const n = property.countReader.read( at + read );
|
||||
read += property.countReader.size;
|
||||
|
||||
for ( let j = 0; j < n; j ++ ) {
|
||||
|
||||
list.push( valueReader.read( at + read ) );
|
||||
read += valueReader.size;
|
||||
|
||||
}
|
||||
|
||||
element[ property.name ] = list;
|
||||
|
||||
} else {
|
||||
|
||||
element[ property.name ] = valueReader.read( at + read );
|
||||
read += valueReader.size;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return [ element, read ];
|
||||
|
||||
}
|
||||
|
||||
function setPropertyBinaryReaders( properties, body, little_endian ) {
|
||||
|
||||
function getBinaryReader( dataview, type, little_endian ) {
|
||||
|
||||
switch ( type ) {
|
||||
|
||||
// corespondences for non-specific length types here match rply:
|
||||
case 'int8': case 'char': return { read: ( at ) => {
|
||||
|
||||
return dataview.getInt8( at );
|
||||
|
||||
}, size: 1 };
|
||||
case 'uint8': case 'uchar': return { read: ( at ) => {
|
||||
|
||||
return dataview.getUint8( at );
|
||||
|
||||
}, size: 1 };
|
||||
case 'int16': case 'short': return { read: ( at ) => {
|
||||
|
||||
return dataview.getInt16( at, little_endian );
|
||||
|
||||
}, size: 2 };
|
||||
case 'uint16': case 'ushort': return { read: ( at ) => {
|
||||
|
||||
return dataview.getUint16( at, little_endian );
|
||||
|
||||
}, size: 2 };
|
||||
case 'int32': case 'int': return { read: ( at ) => {
|
||||
|
||||
return dataview.getInt32( at, little_endian );
|
||||
|
||||
}, size: 4 };
|
||||
case 'uint32': case 'uint': return { read: ( at ) => {
|
||||
|
||||
return dataview.getUint32( at, little_endian );
|
||||
|
||||
}, size: 4 };
|
||||
case 'float32': case 'float': return { read: ( at ) => {
|
||||
|
||||
return dataview.getFloat32( at, little_endian );
|
||||
|
||||
}, size: 4 };
|
||||
case 'float64': case 'double': return { read: ( at ) => {
|
||||
|
||||
return dataview.getFloat64( at, little_endian );
|
||||
|
||||
}, size: 8 };
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
for ( let i = 0, l = properties.length; i < l; i ++ ) {
|
||||
|
||||
const property = properties[ i ];
|
||||
|
||||
if ( property.type === 'list' ) {
|
||||
|
||||
property.countReader = getBinaryReader( body, property.countType, little_endian );
|
||||
property.valueReader = getBinaryReader( body, property.itemType, little_endian );
|
||||
|
||||
} else {
|
||||
|
||||
property.valueReader = getBinaryReader( body, property.type, little_endian );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function parseBinary( data, header ) {
|
||||
|
||||
const buffer = createBuffer();
|
||||
|
||||
const little_endian = ( header.format === 'binary_little_endian' );
|
||||
const body = new DataView( data, header.headerLength );
|
||||
let result, loc = 0;
|
||||
|
||||
for ( let currentElement = 0; currentElement < header.elements.length; currentElement ++ ) {
|
||||
|
||||
const elementDesc = header.elements[ currentElement ];
|
||||
const properties = elementDesc.properties;
|
||||
const attributeMap = mapElementAttributes( properties );
|
||||
|
||||
setPropertyBinaryReaders( properties, body, little_endian );
|
||||
|
||||
for ( let currentElementCount = 0; currentElementCount < elementDesc.count; currentElementCount ++ ) {
|
||||
|
||||
result = binaryReadElement( loc, properties );
|
||||
loc += result[ 1 ];
|
||||
const element = result[ 0 ];
|
||||
|
||||
handleElement( buffer, elementDesc.name, element, attributeMap );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return postProcess( buffer );
|
||||
|
||||
}
|
||||
|
||||
function extractHeaderText( bytes ) {
|
||||
|
||||
let i = 0;
|
||||
let cont = true;
|
||||
|
||||
let line = '';
|
||||
const lines = [];
|
||||
|
||||
const startLine = new TextDecoder().decode( bytes.subarray( 0, 5 ) );
|
||||
const hasCRNL = /^ply\r\n/.test( startLine );
|
||||
|
||||
do {
|
||||
|
||||
const c = String.fromCharCode( bytes[ i ++ ] );
|
||||
|
||||
if ( c !== '\n' && c !== '\r' ) {
|
||||
|
||||
line += c;
|
||||
|
||||
} else {
|
||||
|
||||
if ( line === 'end_header' ) cont = false;
|
||||
if ( line !== '' ) {
|
||||
|
||||
lines.push( line );
|
||||
line = '';
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} while ( cont && i < bytes.length );
|
||||
|
||||
// ascii section using \r\n as line endings
|
||||
if ( hasCRNL === true ) i ++;
|
||||
|
||||
return { headerText: lines.join( '\r' ) + '\r', headerLength: i };
|
||||
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
let geometry;
|
||||
const scope = this;
|
||||
|
||||
if ( data instanceof ArrayBuffer ) {
|
||||
|
||||
const bytes = new Uint8Array( data );
|
||||
const { headerText, headerLength } = extractHeaderText( bytes );
|
||||
const header = parseHeader( headerText, headerLength );
|
||||
|
||||
if ( header.format === 'ascii' ) {
|
||||
|
||||
const text = new TextDecoder().decode( bytes );
|
||||
|
||||
geometry = parseASCII( text, header );
|
||||
|
||||
} else {
|
||||
|
||||
geometry = parseBinary( data, header );
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
geometry = parseASCII( data, parseHeader( data ) );
|
||||
|
||||
}
|
||||
|
||||
return geometry;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class ArrayStream {
|
||||
|
||||
constructor( arr ) {
|
||||
|
||||
this.arr = arr;
|
||||
this.i = 0;
|
||||
|
||||
}
|
||||
|
||||
empty() {
|
||||
|
||||
return this.i >= this.arr.length;
|
||||
|
||||
}
|
||||
|
||||
next() {
|
||||
|
||||
return this.arr[ this.i ++ ];
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export { PLYLoader };
|
||||
251
public/sdk/three/jsm/loaders/PVRLoader.js
Normal file
251
public/sdk/three/jsm/loaders/PVRLoader.js
Normal file
@ -0,0 +1,251 @@
|
||||
import {
|
||||
CompressedTextureLoader,
|
||||
RGBA_PVRTC_2BPPV1_Format,
|
||||
RGBA_PVRTC_4BPPV1_Format,
|
||||
RGB_PVRTC_2BPPV1_Format,
|
||||
RGB_PVRTC_4BPPV1_Format
|
||||
} from 'three';
|
||||
|
||||
/*
|
||||
* PVR v2 (legacy) parser
|
||||
* TODO : Add Support for PVR v3 format
|
||||
* TODO : implement loadMipmaps option
|
||||
*/
|
||||
|
||||
class PVRLoader extends CompressedTextureLoader {
|
||||
|
||||
constructor( manager ) {
|
||||
|
||||
super( manager );
|
||||
|
||||
}
|
||||
|
||||
parse( buffer, loadMipmaps ) {
|
||||
|
||||
const headerLengthInt = 13;
|
||||
const header = new Uint32Array( buffer, 0, headerLengthInt );
|
||||
|
||||
const pvrDatas = {
|
||||
buffer: buffer,
|
||||
header: header,
|
||||
loadMipmaps: loadMipmaps
|
||||
};
|
||||
|
||||
if ( header[ 0 ] === 0x03525650 ) {
|
||||
|
||||
// PVR v3
|
||||
|
||||
return _parseV3( pvrDatas );
|
||||
|
||||
} else if ( header[ 11 ] === 0x21525650 ) {
|
||||
|
||||
// PVR v2
|
||||
|
||||
return _parseV2( pvrDatas );
|
||||
|
||||
} else {
|
||||
|
||||
console.error( 'THREE.PVRLoader: Unknown PVR format.' );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function _parseV3( pvrDatas ) {
|
||||
|
||||
const header = pvrDatas.header;
|
||||
let bpp, format;
|
||||
|
||||
|
||||
const 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 _extract( pvrDatas );
|
||||
|
||||
}
|
||||
|
||||
function _parseV2( pvrDatas ) {
|
||||
|
||||
const header = pvrDatas.header;
|
||||
|
||||
const 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 ];
|
||||
|
||||
|
||||
const TYPE_MASK = 0xff;
|
||||
const PVRTC_2 = 24,
|
||||
PVRTC_4 = 25;
|
||||
|
||||
const formatFlags = flags & TYPE_MASK;
|
||||
|
||||
let bpp, format;
|
||||
const _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 _extract( pvrDatas );
|
||||
|
||||
}
|
||||
|
||||
|
||||
function _extract( pvrDatas ) {
|
||||
|
||||
const pvr = {
|
||||
mipmaps: [],
|
||||
width: pvrDatas.width,
|
||||
height: pvrDatas.height,
|
||||
format: pvrDatas.format,
|
||||
mipmapCount: pvrDatas.numMipmaps,
|
||||
isCubemap: pvrDatas.isCubemap
|
||||
};
|
||||
|
||||
const buffer = pvrDatas.buffer;
|
||||
|
||||
let dataOffset = pvrDatas.dataPtr,
|
||||
dataSize = 0,
|
||||
blockSize = 0,
|
||||
blockWidth = 0,
|
||||
blockHeight = 0,
|
||||
widthBlocks = 0,
|
||||
heightBlocks = 0;
|
||||
|
||||
const bpp = pvrDatas.bpp,
|
||||
numSurfs = pvrDatas.numSurfaces;
|
||||
|
||||
if ( bpp === 2 ) {
|
||||
|
||||
blockWidth = 8;
|
||||
blockHeight = 4;
|
||||
|
||||
} else {
|
||||
|
||||
blockWidth = 4;
|
||||
blockHeight = 4;
|
||||
|
||||
}
|
||||
|
||||
blockSize = ( blockWidth * blockHeight ) * bpp / 8;
|
||||
|
||||
pvr.mipmaps.length = pvrDatas.numMipmaps * numSurfs;
|
||||
|
||||
let mipLevel = 0;
|
||||
|
||||
while ( mipLevel < pvrDatas.numMipmaps ) {
|
||||
|
||||
const 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 ( let surfIndex = 0; surfIndex < numSurfs; surfIndex ++ ) {
|
||||
|
||||
const byteArray = new Uint8Array( buffer, dataOffset, dataSize );
|
||||
|
||||
const mipmap = {
|
||||
data: byteArray,
|
||||
width: sWidth,
|
||||
height: sHeight
|
||||
};
|
||||
|
||||
pvr.mipmaps[ surfIndex * pvrDatas.numMipmaps + mipLevel ] = mipmap;
|
||||
|
||||
dataOffset += dataSize;
|
||||
|
||||
}
|
||||
|
||||
mipLevel ++;
|
||||
|
||||
}
|
||||
|
||||
return pvr;
|
||||
|
||||
}
|
||||
|
||||
export { PVRLoader };
|
||||
450
public/sdk/three/jsm/loaders/RGBELoader.js
Normal file
450
public/sdk/three/jsm/loaders/RGBELoader.js
Normal file
@ -0,0 +1,450 @@
|
||||
import {
|
||||
DataTextureLoader,
|
||||
DataUtils,
|
||||
FloatType,
|
||||
HalfFloatType,
|
||||
LinearFilter,
|
||||
LinearSRGBColorSpace
|
||||
} from 'three';
|
||||
|
||||
// https://github.com/mrdoob/three.js/issues/5552
|
||||
// http://en.wikipedia.org/wiki/RGBE_image_format
|
||||
|
||||
class RGBELoader extends DataTextureLoader {
|
||||
|
||||
constructor( manager ) {
|
||||
|
||||
super( manager );
|
||||
|
||||
this.type = HalfFloatType;
|
||||
|
||||
}
|
||||
|
||||
// adapted from http://www.graphics.cornell.edu/~bjw/rgbe.html
|
||||
|
||||
parse( buffer ) {
|
||||
|
||||
const
|
||||
/* 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: throw new Error( 'THREE.RGBELoader: Read Error: ' + ( msg || '' ) );
|
||||
case rgbe_write_error: throw new Error( 'THREE.RGBELoader: Write Error: ' + ( msg || '' ) );
|
||||
case rgbe_format_error: throw new Error( 'THREE.RGBELoader: Bad File Format: ' + ( msg || '' ) );
|
||||
default:
|
||||
case rgbe_memory_error: throw new Error( 'THREE.RGBELoader: Memory Error: ' + ( msg || '' ) );
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
/* 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 ) {
|
||||
|
||||
const chunkSize = 128;
|
||||
|
||||
lineLimit = ! lineLimit ? 1024 : lineLimit;
|
||||
let p = buffer.pos,
|
||||
i = - 1, len = 0, s = '',
|
||||
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 ) {
|
||||
|
||||
|
||||
// regexes to parse header info fields
|
||||
const 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 */
|
||||
|
||||
};
|
||||
|
||||
let line, match;
|
||||
|
||||
if ( buffer.pos >= buffer.byteLength || ! ( line = fgets( buffer ) ) ) {
|
||||
|
||||
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 ) ) ) {
|
||||
|
||||
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 ] );
|
||||
|
||||
}
|
||||
|
||||
if ( match = line.match( exposure_re ) ) {
|
||||
|
||||
header.exposure = parseFloat( match[ 1 ] );
|
||||
|
||||
}
|
||||
|
||||
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 ) ) {
|
||||
|
||||
rgbe_error( rgbe_format_error, 'missing format specifier' );
|
||||
|
||||
}
|
||||
|
||||
if ( ! ( header.valid & RGBE_VALID_DIMENSIONS ) ) {
|
||||
|
||||
rgbe_error( rgbe_format_error, 'missing image size specifier' );
|
||||
|
||||
}
|
||||
|
||||
return header;
|
||||
|
||||
},
|
||||
|
||||
RGBE_ReadPixels_RLE = function ( buffer, w, h ) {
|
||||
|
||||
const scanline_width = w;
|
||||
|
||||
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 ] ) ) {
|
||||
|
||||
rgbe_error( rgbe_format_error, 'wrong scanline width' );
|
||||
|
||||
}
|
||||
|
||||
const data_rgba = new Uint8Array( 4 * w * h );
|
||||
|
||||
if ( ! data_rgba.length ) {
|
||||
|
||||
rgbe_error( rgbe_memory_error, 'unable to allocate buffer space' );
|
||||
|
||||
}
|
||||
|
||||
let offset = 0, pos = 0;
|
||||
|
||||
const ptr_end = 4 * scanline_width;
|
||||
const rgbeStart = new Uint8Array( 4 );
|
||||
const scanline_buffer = new Uint8Array( ptr_end );
|
||||
let num_scanlines = h;
|
||||
|
||||
// read in each successive scanline
|
||||
while ( ( num_scanlines > 0 ) && ( pos < buffer.byteLength ) ) {
|
||||
|
||||
if ( pos + 4 > buffer.byteLength ) {
|
||||
|
||||
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 ) ) {
|
||||
|
||||
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
|
||||
let ptr = 0, count;
|
||||
|
||||
while ( ( ptr < ptr_end ) && ( pos < buffer.byteLength ) ) {
|
||||
|
||||
count = buffer[ pos ++ ];
|
||||
const isEncodedRun = count > 128;
|
||||
if ( isEncodedRun ) count -= 128;
|
||||
|
||||
if ( ( 0 === count ) || ( ptr + count > ptr_end ) ) {
|
||||
|
||||
rgbe_error( rgbe_format_error, 'bad scanline data' );
|
||||
|
||||
}
|
||||
|
||||
if ( isEncodedRun ) {
|
||||
|
||||
// a (encoded) run of the same value
|
||||
const byteValue = buffer[ pos ++ ];
|
||||
for ( let 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)
|
||||
const l = scanline_width; //scanline_buffer.byteLength;
|
||||
for ( let i = 0; i < l; i ++ ) {
|
||||
|
||||
let 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;
|
||||
|
||||
};
|
||||
|
||||
const RGBEByteToRGBFloat = function ( sourceArray, sourceOffset, destArray, destOffset ) {
|
||||
|
||||
const e = sourceArray[ sourceOffset + 3 ];
|
||||
const 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;
|
||||
destArray[ destOffset + 3 ] = 1;
|
||||
|
||||
};
|
||||
|
||||
const RGBEByteToRGBHalf = function ( sourceArray, sourceOffset, destArray, destOffset ) {
|
||||
|
||||
const e = sourceArray[ sourceOffset + 3 ];
|
||||
const scale = Math.pow( 2.0, e - 128.0 ) / 255.0;
|
||||
|
||||
// clamping to 65504, the maximum representable value in float16
|
||||
destArray[ destOffset + 0 ] = DataUtils.toHalfFloat( Math.min( sourceArray[ sourceOffset + 0 ] * scale, 65504 ) );
|
||||
destArray[ destOffset + 1 ] = DataUtils.toHalfFloat( Math.min( sourceArray[ sourceOffset + 1 ] * scale, 65504 ) );
|
||||
destArray[ destOffset + 2 ] = DataUtils.toHalfFloat( Math.min( sourceArray[ sourceOffset + 2 ] * scale, 65504 ) );
|
||||
destArray[ destOffset + 3 ] = DataUtils.toHalfFloat( 1 );
|
||||
|
||||
};
|
||||
|
||||
const byteArray = new Uint8Array( buffer );
|
||||
byteArray.pos = 0;
|
||||
const rgbe_header_info = RGBE_ReadHeader( byteArray );
|
||||
|
||||
const w = rgbe_header_info.width,
|
||||
h = rgbe_header_info.height,
|
||||
image_rgba_data = RGBE_ReadPixels_RLE( byteArray.subarray( byteArray.pos ), w, h );
|
||||
|
||||
|
||||
let data, type;
|
||||
let numElements;
|
||||
|
||||
switch ( this.type ) {
|
||||
|
||||
case FloatType:
|
||||
|
||||
numElements = image_rgba_data.length / 4;
|
||||
const floatArray = new Float32Array( numElements * 4 );
|
||||
|
||||
for ( let j = 0; j < numElements; j ++ ) {
|
||||
|
||||
RGBEByteToRGBFloat( image_rgba_data, j * 4, floatArray, j * 4 );
|
||||
|
||||
}
|
||||
|
||||
data = floatArray;
|
||||
type = FloatType;
|
||||
break;
|
||||
|
||||
case HalfFloatType:
|
||||
|
||||
numElements = image_rgba_data.length / 4;
|
||||
const halfArray = new Uint16Array( numElements * 4 );
|
||||
|
||||
for ( let j = 0; j < numElements; j ++ ) {
|
||||
|
||||
RGBEByteToRGBHalf( image_rgba_data, j * 4, halfArray, j * 4 );
|
||||
|
||||
}
|
||||
|
||||
data = halfArray;
|
||||
type = HalfFloatType;
|
||||
break;
|
||||
|
||||
default:
|
||||
|
||||
throw new 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,
|
||||
type: type
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
setDataType( value ) {
|
||||
|
||||
this.type = value;
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
load( url, onLoad, onProgress, onError ) {
|
||||
|
||||
function onLoadCallback( texture, texData ) {
|
||||
|
||||
switch ( texture.type ) {
|
||||
|
||||
case FloatType:
|
||||
case HalfFloatType:
|
||||
|
||||
texture.colorSpace = LinearSRGBColorSpace;
|
||||
texture.minFilter = LinearFilter;
|
||||
texture.magFilter = LinearFilter;
|
||||
texture.generateMipmaps = false;
|
||||
texture.flipY = true;
|
||||
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
if ( onLoad ) onLoad( texture, texData );
|
||||
|
||||
}
|
||||
|
||||
return super.load( url, onLoadCallback, onProgress, onError );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export { RGBELoader };
|
||||
1081
public/sdk/three/jsm/loaders/RGBMLoader.js
Normal file
1081
public/sdk/three/jsm/loaders/RGBMLoader.js
Normal file
File diff suppressed because it is too large
Load Diff
410
public/sdk/three/jsm/loaders/STLLoader.js
Normal file
410
public/sdk/three/jsm/loaders/STLLoader.js
Normal file
@ -0,0 +1,410 @@
|
||||
import {
|
||||
BufferAttribute,
|
||||
BufferGeometry,
|
||||
Color,
|
||||
FileLoader,
|
||||
Float32BufferAttribute,
|
||||
Loader,
|
||||
Vector3
|
||||
} from 'three';
|
||||
|
||||
/**
|
||||
* 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:
|
||||
* const 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 { .... }
|
||||
* const 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:
|
||||
*
|
||||
* const mesh = new THREE.Mesh( geometry, material );
|
||||
*
|
||||
* For example:
|
||||
*
|
||||
* const materials = [];
|
||||
* const nGeometryGroups = geometry.groups.length;
|
||||
*
|
||||
* const colorMap = ...; // Some logic to index colors.
|
||||
*
|
||||
* for (let i = 0; i < nGeometryGroups; i++) {
|
||||
*
|
||||
* const material = new THREE.MeshPhongMaterial({
|
||||
* color: colorMap[i],
|
||||
* wireframe: false
|
||||
* });
|
||||
*
|
||||
* }
|
||||
*
|
||||
* materials.push(material);
|
||||
* const mesh = new THREE.Mesh(geometry, materials);
|
||||
*/
|
||||
|
||||
|
||||
class STLLoader extends Loader {
|
||||
|
||||
constructor( manager ) {
|
||||
|
||||
super( manager );
|
||||
|
||||
}
|
||||
|
||||
load( url, onLoad, onProgress, onError ) {
|
||||
|
||||
const scope = this;
|
||||
|
||||
const 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( data ) {
|
||||
|
||||
function isBinary( data ) {
|
||||
|
||||
const reader = new DataView( data );
|
||||
const face_size = ( 32 / 8 * 3 ) + ( ( 32 / 8 * 3 ) * 3 ) + ( 16 / 8 );
|
||||
const n_faces = reader.getUint32( 80, true );
|
||||
const 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'
|
||||
|
||||
const solid = [ 115, 111, 108, 105, 100 ];
|
||||
|
||||
for ( let 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 ( let i = 0, il = query.length; i < il; i ++ ) {
|
||||
|
||||
if ( query[ i ] !== reader.getUint8( offset + i ) ) return false;
|
||||
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
function parseBinary( data ) {
|
||||
|
||||
const reader = new DataView( data );
|
||||
const faces = reader.getUint32( 80, true );
|
||||
|
||||
let r, g, b, hasColors = false, colors;
|
||||
let defaultR, defaultG, defaultB, alpha;
|
||||
|
||||
// process STL header
|
||||
// check for default color in header ("COLOR=rgba" sequence).
|
||||
|
||||
for ( let 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;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const dataOffset = 84;
|
||||
const faceLength = 12 * 4 + 2;
|
||||
|
||||
const geometry = new BufferGeometry();
|
||||
|
||||
const vertices = new Float32Array( faces * 3 * 3 );
|
||||
const normals = new Float32Array( faces * 3 * 3 );
|
||||
|
||||
const color = new Color();
|
||||
|
||||
for ( let face = 0; face < faces; face ++ ) {
|
||||
|
||||
const start = dataOffset + face * faceLength;
|
||||
const normalX = reader.getFloat32( start, true );
|
||||
const normalY = reader.getFloat32( start + 4, true );
|
||||
const normalZ = reader.getFloat32( start + 8, true );
|
||||
|
||||
if ( hasColors ) {
|
||||
|
||||
const 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 ( let i = 1; i <= 3; i ++ ) {
|
||||
|
||||
const vertexstart = start + i * 12;
|
||||
const 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 ) {
|
||||
|
||||
color.set( r, g, b ).convertSRGBToLinear();
|
||||
|
||||
colors[ componentIdx ] = color.r;
|
||||
colors[ componentIdx + 1 ] = color.g;
|
||||
colors[ componentIdx + 2 ] = color.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 ) {
|
||||
|
||||
const geometry = new BufferGeometry();
|
||||
const patternSolid = /solid([\s\S]*?)endsolid/g;
|
||||
const patternFace = /facet([\s\S]*?)endfacet/g;
|
||||
const patternName = /solid\s(.+)/;
|
||||
let faceCounter = 0;
|
||||
|
||||
const patternFloat = /[\s]+([+-]?(?:\d*)(?:\.\d*)?(?:[eE][+-]?\d+)?)/.source;
|
||||
const patternVertex = new RegExp( 'vertex' + patternFloat + patternFloat + patternFloat, 'g' );
|
||||
const patternNormal = new RegExp( 'normal' + patternFloat + patternFloat + patternFloat, 'g' );
|
||||
|
||||
const vertices = [];
|
||||
const normals = [];
|
||||
const groupNames = [];
|
||||
|
||||
const normal = new Vector3();
|
||||
|
||||
let result;
|
||||
|
||||
let groupCount = 0;
|
||||
let startVertex = 0;
|
||||
let endVertex = 0;
|
||||
|
||||
while ( ( result = patternSolid.exec( data ) ) !== null ) {
|
||||
|
||||
startVertex = endVertex;
|
||||
|
||||
const solid = result[ 0 ];
|
||||
|
||||
const name = ( result = patternName.exec( solid ) ) !== null ? result[ 1 ] : '';
|
||||
groupNames.push( name );
|
||||
|
||||
while ( ( result = patternFace.exec( solid ) ) !== null ) {
|
||||
|
||||
let vertexCountPerFace = 0;
|
||||
let normalCountPerFace = 0;
|
||||
|
||||
const 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 ++;
|
||||
|
||||
}
|
||||
|
||||
const start = startVertex;
|
||||
const count = endVertex - startVertex;
|
||||
|
||||
geometry.userData.groupNames = groupNames;
|
||||
|
||||
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 new TextDecoder().decode( buffer );
|
||||
|
||||
}
|
||||
|
||||
return buffer;
|
||||
|
||||
}
|
||||
|
||||
function ensureBinary( buffer ) {
|
||||
|
||||
if ( typeof buffer === 'string' ) {
|
||||
|
||||
const array_buffer = new Uint8Array( buffer.length );
|
||||
for ( let 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
|
||||
|
||||
const binData = ensureBinary( data );
|
||||
|
||||
return isBinary( binData ) ? parseBinary( binData ) : parseASCII( ensureString( data ) );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export { STLLoader };
|
||||
3173
public/sdk/three/jsm/loaders/SVGLoader.js
Normal file
3173
public/sdk/three/jsm/loaders/SVGLoader.js
Normal file
File diff suppressed because it is too large
Load Diff
1124
public/sdk/three/jsm/loaders/TDSLoader.js
Normal file
1124
public/sdk/three/jsm/loaders/TDSLoader.js
Normal file
File diff suppressed because it is too large
Load Diff
517
public/sdk/three/jsm/loaders/TGALoader.js
Normal file
517
public/sdk/three/jsm/loaders/TGALoader.js
Normal file
@ -0,0 +1,517 @@
|
||||
import {
|
||||
DataTextureLoader,
|
||||
LinearMipmapLinearFilter
|
||||
} from 'three';
|
||||
|
||||
class TGALoader extends DataTextureLoader {
|
||||
|
||||
constructor( manager ) {
|
||||
|
||||
super( manager );
|
||||
|
||||
}
|
||||
|
||||
parse( 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 ) {
|
||||
|
||||
throw new 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 ) {
|
||||
|
||||
throw new Error( 'THREE.TGALoader: Invalid type colormap data for colormap type.' );
|
||||
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
// What the need of a file without data ?
|
||||
|
||||
case TGA_TYPE_NO_DATA:
|
||||
throw new Error( 'THREE.TGALoader: No data.' );
|
||||
|
||||
// Invalid type ?
|
||||
|
||||
default:
|
||||
throw new Error( 'THREE.TGALoader: Invalid type ' + header.image_type );
|
||||
|
||||
}
|
||||
|
||||
// check image width and height
|
||||
|
||||
if ( header.width <= 0 || header.height <= 0 ) {
|
||||
|
||||
throw new 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 ) {
|
||||
|
||||
throw new Error( 'THREE.TGALoader: Invalid pixel size ' + header.pixel_size );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// parse tga image buffer
|
||||
|
||||
function tgaParse( use_rle, use_pal, header, offset, data ) {
|
||||
|
||||
let pixel_data,
|
||||
palettes;
|
||||
|
||||
const pixel_size = header.pixel_size >> 3;
|
||||
const 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 );
|
||||
|
||||
let c, count, i;
|
||||
let shift = 0;
|
||||
const 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 ) {
|
||||
|
||||
const colormap = palettes;
|
||||
let color, i = 0, x, y;
|
||||
const 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 ) {
|
||||
|
||||
let color, i = 0, x, y;
|
||||
const 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 );
|
||||
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 ) {
|
||||
|
||||
let i = 0, x, y;
|
||||
const 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 ) {
|
||||
|
||||
let i = 0, x, y;
|
||||
const 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 ) {
|
||||
|
||||
let color, i = 0, x, y;
|
||||
const 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 ) {
|
||||
|
||||
let i = 0, x, y;
|
||||
const 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 ) {
|
||||
|
||||
let 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:
|
||||
throw new 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:
|
||||
throw new Error( 'THREE.TGALoader: Format not supported.' );
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Load image data according to specific method
|
||||
// let 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
|
||||
|
||||
const 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 ) throw new Error( 'THREE.TGALoader: Not enough data to contain header.' );
|
||||
|
||||
let offset = 0;
|
||||
|
||||
const content = new Uint8Array( buffer ),
|
||||
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 ) {
|
||||
|
||||
throw new Error( 'THREE.TGALoader: No data.' );
|
||||
|
||||
}
|
||||
|
||||
// skip the needn't data
|
||||
|
||||
offset += header.id_length;
|
||||
|
||||
// get targa information about RLE compression and palette
|
||||
|
||||
let 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;
|
||||
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
const imageData = new Uint8Array( header.width * header.height * 4 );
|
||||
const result = tgaParse( use_rle, use_pal, header, offset, content );
|
||||
getTgaRGBA( imageData, header.width, header.height, result.pixel_data, result.palettes );
|
||||
|
||||
return {
|
||||
|
||||
data: imageData,
|
||||
width: header.width,
|
||||
height: header.height,
|
||||
flipY: true,
|
||||
generateMipmaps: true,
|
||||
minFilter: LinearMipmapLinearFilter,
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export { TGALoader };
|
||||
36
public/sdk/three/jsm/loaders/TIFFLoader.js
Normal file
36
public/sdk/three/jsm/loaders/TIFFLoader.js
Normal file
@ -0,0 +1,36 @@
|
||||
import {
|
||||
DataTextureLoader,
|
||||
LinearFilter,
|
||||
LinearMipmapLinearFilter
|
||||
} from 'three';
|
||||
|
||||
import UTIF from '../libs/utif.module.js';
|
||||
|
||||
class TIFFLoader extends DataTextureLoader {
|
||||
|
||||
constructor( manager ) {
|
||||
|
||||
super( manager );
|
||||
|
||||
}
|
||||
|
||||
parse( buffer ) {
|
||||
|
||||
const ifds = UTIF.decode( buffer );
|
||||
UTIF.decodeImage( buffer, ifds[ 0 ] );
|
||||
const rgba = UTIF.toRGBA8( ifds[ 0 ] );
|
||||
|
||||
return {
|
||||
width: ifds[ 0 ].width,
|
||||
height: ifds[ 0 ].height,
|
||||
data: rgba,
|
||||
flipY: true,
|
||||
magFilter: LinearFilter,
|
||||
minFilter: LinearMipmapLinearFilter
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export { TIFFLoader };
|
||||
214
public/sdk/three/jsm/loaders/TTFLoader.js
Normal file
214
public/sdk/three/jsm/loaders/TTFLoader.js
Normal file
@ -0,0 +1,214 @@
|
||||
import {
|
||||
FileLoader,
|
||||
Loader
|
||||
} from 'three';
|
||||
import opentype from '../libs/opentype.module.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.
|
||||
*/
|
||||
|
||||
class TTFLoader extends Loader {
|
||||
|
||||
constructor( manager ) {
|
||||
|
||||
super( manager );
|
||||
|
||||
this.reversed = false;
|
||||
|
||||
}
|
||||
|
||||
load( url, onLoad, onProgress, onError ) {
|
||||
|
||||
const scope = this;
|
||||
|
||||
const 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( arraybuffer ) {
|
||||
|
||||
function convert( font, reversed ) {
|
||||
|
||||
const round = Math.round;
|
||||
|
||||
const glyphs = {};
|
||||
const scale = ( 100000 ) / ( ( font.unitsPerEm || 2048 ) * 72 );
|
||||
|
||||
const glyphIndexMap = font.encoding.cmap.glyphIndexMap;
|
||||
const unicodes = Object.keys( glyphIndexMap );
|
||||
|
||||
for ( let i = 0; i < unicodes.length; i ++ ) {
|
||||
|
||||
const unicode = unicodes[ i ];
|
||||
const glyph = font.glyphs.glyphs[ glyphIndexMap[ unicode ] ];
|
||||
|
||||
if ( unicode !== undefined ) {
|
||||
|
||||
const 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 ) {
|
||||
|
||||
const paths = [];
|
||||
let path;
|
||||
|
||||
commands.forEach( function ( c ) {
|
||||
|
||||
if ( c.type.toLowerCase() === 'm' ) {
|
||||
|
||||
path = [ c ];
|
||||
paths.push( path );
|
||||
|
||||
} else if ( c.type.toLowerCase() !== 'z' ) {
|
||||
|
||||
path.push( c );
|
||||
|
||||
}
|
||||
|
||||
} );
|
||||
|
||||
const reversed = [];
|
||||
|
||||
paths.forEach( function ( p ) {
|
||||
|
||||
const result = {
|
||||
type: 'm',
|
||||
x: p[ p.length - 1 ].x,
|
||||
y: p[ p.length - 1 ].y
|
||||
};
|
||||
|
||||
reversed.push( result );
|
||||
|
||||
for ( let i = p.length - 1; i > 0; i -- ) {
|
||||
|
||||
const command = p[ i ];
|
||||
const 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;
|
||||
|
||||
}
|
||||
|
||||
return convert( opentype.parse( arraybuffer ), this.reversed );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export { TTFLoader };
|
||||
520
public/sdk/three/jsm/loaders/TiltLoader.js
Normal file
520
public/sdk/three/jsm/loaders/TiltLoader.js
Normal file
@ -0,0 +1,520 @@
|
||||
import {
|
||||
BufferAttribute,
|
||||
BufferGeometry,
|
||||
Color,
|
||||
DoubleSide,
|
||||
FileLoader,
|
||||
Group,
|
||||
Loader,
|
||||
Mesh,
|
||||
MeshBasicMaterial,
|
||||
RawShaderMaterial,
|
||||
TextureLoader,
|
||||
Quaternion,
|
||||
Vector3
|
||||
} from 'three';
|
||||
import * as fflate from '../libs/fflate.module.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 = fflate.unzipSync( new Uint8Array( buffer.slice( 16 ) ) );
|
||||
|
||||
/*
|
||||
const thumbnail = zip[ 'thumbnail.png' ].buffer;
|
||||
const img = document.createElement( 'img' );
|
||||
img.src = URL.createObjectURL( new Blob( [ thumbnail ] ) );
|
||||
document.body.appendChild( img );
|
||||
*/
|
||||
|
||||
const metadata = JSON.parse( fflate.strFromU8( zip[ 'metadata.json' ] ) );
|
||||
|
||||
/*
|
||||
const blob = new Blob( [ zip[ 'data.sketch' ].buffer ], { type: 'application/octet-stream' } );
|
||||
window.open( URL.createObjectURL( blob ) );
|
||||
*/
|
||||
|
||||
const data = new DataView( zip[ 'data.sketch' ].buffer );
|
||||
|
||||
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();
|
||||
|
||||
const color = new Color();
|
||||
|
||||
// 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 rgba = stroke[ 3 ];
|
||||
const alpha = stroke[ 3 ][ 3 ];
|
||||
|
||||
color.fromArray( rgba ).convertSRGBToLinear();
|
||||
|
||||
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, alpha );
|
||||
colors.push( ...color, alpha );
|
||||
colors.push( ...color, alpha );
|
||||
|
||||
colors.push( ...color, alpha );
|
||||
colors.push( ...color, alpha );
|
||||
colors.push( ...color, alpha );
|
||||
|
||||
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;
|
||||
}
|
||||
`
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
let shaders = null;
|
||||
|
||||
function getShaders() {
|
||||
|
||||
if ( shaders === null ) {
|
||||
|
||||
const loader = new TextureLoader().setPath( './textures/tiltbrush/' );
|
||||
|
||||
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,
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
return shaders;
|
||||
|
||||
}
|
||||
|
||||
function getMaterial( GUID ) {
|
||||
|
||||
const name = BRUSH_LIST_ARRAY[ GUID ];
|
||||
|
||||
switch ( name ) {
|
||||
|
||||
case 'Light':
|
||||
return new RawShaderMaterial( getShaders().Light );
|
||||
|
||||
default:
|
||||
return new MeshBasicMaterial( { vertexColors: true, side: DoubleSide } );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export { TiltLoader };
|
||||
822
public/sdk/three/jsm/loaders/USDZLoader.js
Normal file
822
public/sdk/three/jsm/loaders/USDZLoader.js
Normal file
@ -0,0 +1,822 @@
|
||||
import {
|
||||
BufferAttribute,
|
||||
BufferGeometry,
|
||||
ClampToEdgeWrapping,
|
||||
FileLoader,
|
||||
Group,
|
||||
NoColorSpace,
|
||||
Loader,
|
||||
Mesh,
|
||||
MeshPhysicalMaterial,
|
||||
MirroredRepeatWrapping,
|
||||
RepeatWrapping,
|
||||
SRGBColorSpace,
|
||||
TextureLoader,
|
||||
Object3D,
|
||||
Vector2
|
||||
} from 'three';
|
||||
|
||||
import * as fflate from '../libs/fflate.module.js';
|
||||
|
||||
class USDAParser {
|
||||
|
||||
parse( text ) {
|
||||
|
||||
const data = {};
|
||||
|
||||
const lines = text.split( '\n' );
|
||||
|
||||
let string = null;
|
||||
let target = data;
|
||||
|
||||
const stack = [ data ];
|
||||
|
||||
// debugger;
|
||||
|
||||
for ( const line of lines ) {
|
||||
|
||||
// console.log( line );
|
||||
|
||||
if ( line.includes( '=' ) ) {
|
||||
|
||||
const assignment = line.split( '=' );
|
||||
|
||||
const lhs = assignment[ 0 ].trim();
|
||||
const rhs = assignment[ 1 ].trim();
|
||||
|
||||
if ( rhs.endsWith( '{' ) ) {
|
||||
|
||||
const group = {};
|
||||
stack.push( group );
|
||||
|
||||
target[ lhs ] = group;
|
||||
target = group;
|
||||
|
||||
} else {
|
||||
|
||||
target[ lhs ] = rhs;
|
||||
|
||||
}
|
||||
|
||||
} else if ( line.endsWith( '{' ) ) {
|
||||
|
||||
const group = target[ string ] || {};
|
||||
stack.push( group );
|
||||
|
||||
target[ string ] = group;
|
||||
target = group;
|
||||
|
||||
} else if ( line.endsWith( '}' ) ) {
|
||||
|
||||
stack.pop();
|
||||
|
||||
if ( stack.length === 0 ) continue;
|
||||
|
||||
target = stack[ stack.length - 1 ];
|
||||
|
||||
} else if ( line.endsWith( '(' ) ) {
|
||||
|
||||
const meta = {};
|
||||
stack.push( meta );
|
||||
|
||||
string = line.split( '(' )[ 0 ].trim() || string;
|
||||
|
||||
target[ string ] = meta;
|
||||
target = meta;
|
||||
|
||||
} else if ( line.endsWith( ')' ) ) {
|
||||
|
||||
stack.pop();
|
||||
|
||||
target = stack[ stack.length - 1 ];
|
||||
|
||||
} else {
|
||||
|
||||
string = line.trim();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return data;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class USDZLoader extends Loader {
|
||||
|
||||
constructor( manager ) {
|
||||
|
||||
super( manager );
|
||||
|
||||
}
|
||||
|
||||
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.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( buffer ) {
|
||||
|
||||
const parser = new USDAParser();
|
||||
|
||||
function parseAssets( zip ) {
|
||||
|
||||
const data = {};
|
||||
const loader = new FileLoader();
|
||||
loader.setResponseType( 'arraybuffer' );
|
||||
|
||||
for ( const filename in zip ) {
|
||||
|
||||
if ( filename.endsWith( 'png' ) ) {
|
||||
|
||||
const blob = new Blob( [ zip[ filename ] ], { type: { type: 'image/png' } } );
|
||||
data[ filename ] = URL.createObjectURL( blob );
|
||||
|
||||
}
|
||||
|
||||
if ( filename.endsWith( 'usd' ) || filename.endsWith( 'usda' ) ) {
|
||||
|
||||
if ( isCrateFile( zip[ filename ] ) ) {
|
||||
|
||||
console.warn( 'THREE.USDZLoader: Crate files (.usdc or binary .usd) are not supported.' );
|
||||
continue;
|
||||
|
||||
}
|
||||
|
||||
const text = fflate.strFromU8( zip[ filename ] );
|
||||
data[ filename ] = parser.parse( text );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return data;
|
||||
|
||||
}
|
||||
|
||||
function isCrateFile( buffer ) {
|
||||
|
||||
// Check if this a crate file. First 7 bytes of a crate file are "PXR-USDC".
|
||||
const fileHeader = buffer.slice( 0, 7 );
|
||||
const crateHeader = new Uint8Array( [ 0x50, 0x58, 0x52, 0x2D, 0x55, 0x53, 0x44, 0x43 ] );
|
||||
|
||||
// If this is not a crate file, we assume it is a plain USDA file.
|
||||
return fileHeader.every( ( value, index ) => value === crateHeader[ index ] );
|
||||
|
||||
}
|
||||
|
||||
function findUSD( zip ) {
|
||||
|
||||
if ( zip.length < 1 ) return undefined;
|
||||
|
||||
const firstFileName = Object.keys( zip )[ 0 ];
|
||||
let isCrate = false;
|
||||
|
||||
// As per the USD specification, the first entry in the zip archive is used as the main file ("UsdStage").
|
||||
// ASCII files can end in either .usda or .usd.
|
||||
// See https://openusd.org/release/spec_usdz.html#layout
|
||||
if ( firstFileName.endsWith( 'usda' ) ) return zip[ firstFileName ];
|
||||
|
||||
if ( firstFileName.endsWith( 'usdc' ) ) {
|
||||
|
||||
isCrate = true;
|
||||
|
||||
} else if ( firstFileName.endsWith( 'usd' ) ) {
|
||||
|
||||
// If this is not a crate file, we assume it is a plain USDA file.
|
||||
if ( ! isCrateFile( zip[ firstFileName ] ) ) {
|
||||
|
||||
return zip[ firstFileName ];
|
||||
|
||||
} else {
|
||||
|
||||
isCrate = true;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if ( isCrate ) {
|
||||
|
||||
console.warn( 'THREE.USDZLoader: Crate files (.usdc or binary .usd) are not supported.' );
|
||||
|
||||
}
|
||||
|
||||
return undefined;
|
||||
|
||||
}
|
||||
|
||||
const zip = fflate.unzipSync( new Uint8Array( buffer ) );
|
||||
|
||||
// console.log( zip );
|
||||
|
||||
const assets = parseAssets( zip );
|
||||
|
||||
// console.log( assets )
|
||||
|
||||
const file = findUSD( zip );
|
||||
|
||||
if ( file === undefined ) {
|
||||
|
||||
console.warn( 'THREE.USDZLoader: No usda file found.' );
|
||||
|
||||
return new Group();
|
||||
|
||||
}
|
||||
|
||||
|
||||
// Parse file
|
||||
|
||||
const text = fflate.strFromU8( file );
|
||||
const root = parser.parse( text );
|
||||
|
||||
// Build scene
|
||||
|
||||
function findMeshGeometry( data ) {
|
||||
|
||||
if ( ! data ) return undefined;
|
||||
|
||||
if ( 'prepend references' in data ) {
|
||||
|
||||
const reference = data[ 'prepend references' ];
|
||||
const parts = reference.split( '@' );
|
||||
const path = parts[ 1 ].replace( /^.\//, '' );
|
||||
const id = parts[ 2 ].replace( /^<\//, '' ).replace( />$/, '' );
|
||||
|
||||
return findGeometry( assets[ path ], id );
|
||||
|
||||
}
|
||||
|
||||
return findGeometry( data );
|
||||
|
||||
}
|
||||
|
||||
function findGeometry( data, id ) {
|
||||
|
||||
if ( ! data ) return undefined;
|
||||
|
||||
if ( id !== undefined ) {
|
||||
|
||||
const def = `def Mesh "${id}"`;
|
||||
|
||||
if ( def in data ) {
|
||||
|
||||
return data[ def ];
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
for ( const name in data ) {
|
||||
|
||||
const object = data[ name ];
|
||||
|
||||
if ( name.startsWith( 'def Mesh' ) ) {
|
||||
|
||||
// Move points to Mesh
|
||||
|
||||
if ( 'point3f[] points' in data ) {
|
||||
|
||||
object[ 'point3f[] points' ] = data[ 'point3f[] points' ];
|
||||
|
||||
}
|
||||
|
||||
// Move st to Mesh
|
||||
|
||||
if ( 'texCoord2f[] primvars:st' in data ) {
|
||||
|
||||
object[ 'texCoord2f[] primvars:st' ] = data[ 'texCoord2f[] primvars:st' ];
|
||||
|
||||
}
|
||||
|
||||
// Move st indices to Mesh
|
||||
|
||||
if ( 'int[] primvars:st:indices' in data ) {
|
||||
|
||||
object[ 'int[] primvars:st:indices' ] = data[ 'int[] primvars:st:indices' ];
|
||||
|
||||
}
|
||||
|
||||
return object;
|
||||
|
||||
}
|
||||
|
||||
|
||||
if ( typeof object === 'object' ) {
|
||||
|
||||
const geometry = findGeometry( object );
|
||||
|
||||
if ( geometry ) return geometry;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function buildGeometry( data ) {
|
||||
|
||||
if ( ! data ) return undefined;
|
||||
|
||||
let geometry = new BufferGeometry();
|
||||
|
||||
if ( 'int[] faceVertexIndices' in data ) {
|
||||
|
||||
const indices = JSON.parse( data[ 'int[] faceVertexIndices' ] );
|
||||
geometry.setIndex( indices );
|
||||
|
||||
}
|
||||
|
||||
if ( 'point3f[] points' in data ) {
|
||||
|
||||
const positions = JSON.parse( data[ 'point3f[] points' ].replace( /[()]*/g, '' ) );
|
||||
const attribute = new BufferAttribute( new Float32Array( positions ), 3 );
|
||||
geometry.setAttribute( 'position', attribute );
|
||||
|
||||
}
|
||||
|
||||
if ( 'normal3f[] normals' in data ) {
|
||||
|
||||
const normals = JSON.parse( data[ 'normal3f[] normals' ].replace( /[()]*/g, '' ) );
|
||||
const attribute = new BufferAttribute( new Float32Array( normals ), 3 );
|
||||
geometry.setAttribute( 'normal', attribute );
|
||||
|
||||
} else {
|
||||
|
||||
geometry.computeVertexNormals();
|
||||
|
||||
}
|
||||
|
||||
if ( 'float2[] primvars:st' in data ) {
|
||||
|
||||
data[ 'texCoord2f[] primvars:st' ] = data[ 'float2[] primvars:st' ];
|
||||
|
||||
}
|
||||
|
||||
if ( 'texCoord2f[] primvars:st' in data ) {
|
||||
|
||||
const uvs = JSON.parse( data[ 'texCoord2f[] primvars:st' ].replace( /[()]*/g, '' ) );
|
||||
const attribute = new BufferAttribute( new Float32Array( uvs ), 2 );
|
||||
|
||||
if ( 'int[] primvars:st:indices' in data ) {
|
||||
|
||||
geometry = geometry.toNonIndexed();
|
||||
|
||||
const indices = JSON.parse( data[ 'int[] primvars:st:indices' ] );
|
||||
geometry.setAttribute( 'uv', toFlatBufferAttribute( attribute, indices ) );
|
||||
|
||||
} else {
|
||||
|
||||
geometry.setAttribute( 'uv', attribute );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return geometry;
|
||||
|
||||
}
|
||||
|
||||
function toFlatBufferAttribute( attribute, indices ) {
|
||||
|
||||
const array = attribute.array;
|
||||
const itemSize = attribute.itemSize;
|
||||
|
||||
const array2 = new array.constructor( indices.length * itemSize );
|
||||
|
||||
let index = 0, index2 = 0;
|
||||
|
||||
for ( let i = 0, l = indices.length; i < l; i ++ ) {
|
||||
|
||||
index = indices[ i ] * itemSize;
|
||||
|
||||
for ( let j = 0; j < itemSize; j ++ ) {
|
||||
|
||||
array2[ index2 ++ ] = array[ index ++ ];
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return new BufferAttribute( array2, itemSize );
|
||||
|
||||
}
|
||||
|
||||
function findMeshMaterial( data ) {
|
||||
|
||||
if ( ! data ) return undefined;
|
||||
|
||||
if ( 'rel material:binding' in data ) {
|
||||
|
||||
const reference = data[ 'rel material:binding' ];
|
||||
const id = reference.replace( /^<\//, '' ).replace( />$/, '' );
|
||||
const parts = id.split( '/' );
|
||||
|
||||
return findMaterial( root, ` "${ parts[ 1 ] }"` );
|
||||
|
||||
}
|
||||
|
||||
return findMaterial( data );
|
||||
|
||||
}
|
||||
|
||||
function findMaterial( data, id = '' ) {
|
||||
|
||||
for ( const name in data ) {
|
||||
|
||||
const object = data[ name ];
|
||||
|
||||
if ( name.startsWith( 'def Material' + id ) ) {
|
||||
|
||||
return object;
|
||||
|
||||
}
|
||||
|
||||
if ( typeof object === 'object' ) {
|
||||
|
||||
const material = findMaterial( object, id );
|
||||
|
||||
if ( material ) return material;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function setTextureParams( map, data_value ) {
|
||||
|
||||
// rotation, scale and translation
|
||||
|
||||
if ( data_value[ 'float inputs:rotation' ] ) {
|
||||
|
||||
map.rotation = parseFloat( data_value[ 'float inputs:rotation' ] );
|
||||
|
||||
}
|
||||
|
||||
if ( data_value[ 'float2 inputs:scale' ] ) {
|
||||
|
||||
map.repeat = new Vector2().fromArray( JSON.parse( '[' + data_value[ 'float2 inputs:scale' ].replace( /[()]*/g, '' ) + ']' ) );
|
||||
|
||||
}
|
||||
|
||||
if ( data_value[ 'float2 inputs:translation' ] ) {
|
||||
|
||||
map.offset = new Vector2().fromArray( JSON.parse( '[' + data_value[ 'float2 inputs:translation' ].replace( /[()]*/g, '' ) + ']' ) );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function buildMaterial( data ) {
|
||||
|
||||
const material = new MeshPhysicalMaterial();
|
||||
|
||||
if ( data !== undefined ) {
|
||||
|
||||
if ( 'def Shader "PreviewSurface"' in data ) {
|
||||
|
||||
const surface = data[ 'def Shader "PreviewSurface"' ];
|
||||
|
||||
if ( 'color3f inputs:diffuseColor.connect' in surface ) {
|
||||
|
||||
const path = surface[ 'color3f inputs:diffuseColor.connect' ];
|
||||
const sampler = findTexture( root, /(\w+).output/.exec( path )[ 1 ] );
|
||||
|
||||
material.map = buildTexture( sampler );
|
||||
material.map.colorSpace = SRGBColorSpace;
|
||||
|
||||
if ( 'def Shader "Transform2d_diffuse"' in data ) {
|
||||
|
||||
setTextureParams( material.map, data[ 'def Shader "Transform2d_diffuse"' ] );
|
||||
|
||||
}
|
||||
|
||||
} else if ( 'color3f inputs:diffuseColor' in surface ) {
|
||||
|
||||
const color = surface[ 'color3f inputs:diffuseColor' ].replace( /[()]*/g, '' );
|
||||
material.color.fromArray( JSON.parse( '[' + color + ']' ) );
|
||||
|
||||
}
|
||||
|
||||
if ( 'color3f inputs:emissiveColor.connect' in surface ) {
|
||||
|
||||
const path = surface[ 'color3f inputs:emissiveColor.connect' ];
|
||||
const sampler = findTexture( root, /(\w+).output/.exec( path )[ 1 ] );
|
||||
|
||||
material.emissiveMap = buildTexture( sampler );
|
||||
material.emissiveMap.colorSpace = SRGBColorSpace;
|
||||
material.emissive.set( 0xffffff );
|
||||
|
||||
if ( 'def Shader "Transform2d_emissive"' in data ) {
|
||||
|
||||
setTextureParams( material.emissiveMap, data[ 'def Shader "Transform2d_emissive"' ] );
|
||||
|
||||
}
|
||||
|
||||
} else if ( 'color3f inputs:emissiveColor' in surface ) {
|
||||
|
||||
const color = surface[ 'color3f inputs:emissiveColor' ].replace( /[()]*/g, '' );
|
||||
material.emissive.fromArray( JSON.parse( '[' + color + ']' ) );
|
||||
|
||||
}
|
||||
|
||||
if ( 'normal3f inputs:normal.connect' in surface ) {
|
||||
|
||||
const path = surface[ 'normal3f inputs:normal.connect' ];
|
||||
const sampler = findTexture( root, /(\w+).output/.exec( path )[ 1 ] );
|
||||
|
||||
material.normalMap = buildTexture( sampler );
|
||||
material.normalMap.colorSpace = NoColorSpace;
|
||||
|
||||
if ( 'def Shader "Transform2d_normal"' in data ) {
|
||||
|
||||
setTextureParams( material.normalMap, data[ 'def Shader "Transform2d_normal"' ] );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if ( 'float inputs:roughness.connect' in surface ) {
|
||||
|
||||
const path = surface[ 'float inputs:roughness.connect' ];
|
||||
const sampler = findTexture( root, /(\w+).output/.exec( path )[ 1 ] );
|
||||
|
||||
material.roughness = 1.0;
|
||||
material.roughnessMap = buildTexture( sampler );
|
||||
material.roughnessMap.colorSpace = NoColorSpace;
|
||||
|
||||
if ( 'def Shader "Transform2d_roughness"' in data ) {
|
||||
|
||||
setTextureParams( material.roughnessMap, data[ 'def Shader "Transform2d_roughness"' ] );
|
||||
|
||||
}
|
||||
|
||||
} else if ( 'float inputs:roughness' in surface ) {
|
||||
|
||||
material.roughness = parseFloat( surface[ 'float inputs:roughness' ] );
|
||||
|
||||
}
|
||||
|
||||
if ( 'float inputs:metallic.connect' in surface ) {
|
||||
|
||||
const path = surface[ 'float inputs:metallic.connect' ];
|
||||
const sampler = findTexture( root, /(\w+).output/.exec( path )[ 1 ] );
|
||||
|
||||
material.metalness = 1.0;
|
||||
material.metalnessMap = buildTexture( sampler );
|
||||
material.metalnessMap.colorSpace = NoColorSpace;
|
||||
|
||||
if ( 'def Shader "Transform2d_metallic"' in data ) {
|
||||
|
||||
setTextureParams( material.metalnessMap, data[ 'def Shader "Transform2d_metallic"' ] );
|
||||
|
||||
}
|
||||
|
||||
} else if ( 'float inputs:metallic' in surface ) {
|
||||
|
||||
material.metalness = parseFloat( surface[ 'float inputs:metallic' ] );
|
||||
|
||||
}
|
||||
|
||||
if ( 'float inputs:clearcoat.connect' in surface ) {
|
||||
|
||||
const path = surface[ 'float inputs:clearcoat.connect' ];
|
||||
const sampler = findTexture( root, /(\w+).output/.exec( path )[ 1 ] );
|
||||
|
||||
material.clearcoat = 1.0;
|
||||
material.clearcoatMap = buildTexture( sampler );
|
||||
material.clearcoatMap.colorSpace = NoColorSpace;
|
||||
|
||||
if ( 'def Shader "Transform2d_clearcoat"' in data ) {
|
||||
|
||||
setTextureParams( material.clearcoatMap, data[ 'def Shader "Transform2d_clearcoat"' ] );
|
||||
|
||||
}
|
||||
|
||||
} else if ( 'float inputs:clearcoat' in surface ) {
|
||||
|
||||
material.clearcoat = parseFloat( surface[ 'float inputs:clearcoat' ] );
|
||||
|
||||
}
|
||||
|
||||
if ( 'float inputs:clearcoatRoughness.connect' in surface ) {
|
||||
|
||||
const path = surface[ 'float inputs:clearcoatRoughness.connect' ];
|
||||
const sampler = findTexture( root, /(\w+).output/.exec( path )[ 1 ] );
|
||||
|
||||
material.clearcoatRoughness = 1.0;
|
||||
material.clearcoatRoughnessMap = buildTexture( sampler );
|
||||
material.clearcoatRoughnessMap.colorSpace = NoColorSpace;
|
||||
|
||||
if ( 'def Shader "Transform2d_clearcoatRoughness"' in data ) {
|
||||
|
||||
setTextureParams( material.clearcoatRoughnessMap, data[ 'def Shader "Transform2d_clearcoatRoughness"' ] );
|
||||
|
||||
}
|
||||
|
||||
} else if ( 'float inputs:clearcoatRoughness' in surface ) {
|
||||
|
||||
material.clearcoatRoughness = parseFloat( surface[ 'float inputs:clearcoatRoughness' ] );
|
||||
|
||||
}
|
||||
|
||||
if ( 'float inputs:ior' in surface ) {
|
||||
|
||||
material.ior = parseFloat( surface[ 'float inputs:ior' ] );
|
||||
|
||||
}
|
||||
|
||||
if ( 'float inputs:occlusion.connect' in surface ) {
|
||||
|
||||
const path = surface[ 'float inputs:occlusion.connect' ];
|
||||
const sampler = findTexture( root, /(\w+).output/.exec( path )[ 1 ] );
|
||||
|
||||
material.aoMap = buildTexture( sampler );
|
||||
material.aoMap.colorSpace = NoColorSpace;
|
||||
|
||||
if ( 'def Shader "Transform2d_occlusion"' in data ) {
|
||||
|
||||
setTextureParams( material.aoMap, data[ 'def Shader "Transform2d_occlusion"' ] );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if ( 'def Shader "diffuseColor_texture"' in data ) {
|
||||
|
||||
const sampler = data[ 'def Shader "diffuseColor_texture"' ];
|
||||
|
||||
material.map = buildTexture( sampler );
|
||||
material.map.colorSpace = SRGBColorSpace;
|
||||
|
||||
}
|
||||
|
||||
if ( 'def Shader "normal_texture"' in data ) {
|
||||
|
||||
const sampler = data[ 'def Shader "normal_texture"' ];
|
||||
|
||||
material.normalMap = buildTexture( sampler );
|
||||
material.normalMap.colorSpace = NoColorSpace;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return material;
|
||||
|
||||
}
|
||||
|
||||
function findTexture( data, id ) {
|
||||
|
||||
for ( const name in data ) {
|
||||
|
||||
const object = data[ name ];
|
||||
|
||||
if ( name.startsWith( `def Shader "${ id }"` ) ) {
|
||||
|
||||
return object;
|
||||
|
||||
}
|
||||
|
||||
if ( typeof object === 'object' ) {
|
||||
|
||||
const texture = findTexture( object, id );
|
||||
|
||||
if ( texture ) return texture;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function buildTexture( data ) {
|
||||
|
||||
if ( 'asset inputs:file' in data ) {
|
||||
|
||||
const path = data[ 'asset inputs:file' ].replace( /@*/g, '' );
|
||||
|
||||
const loader = new TextureLoader();
|
||||
|
||||
const texture = loader.load( assets[ path ] );
|
||||
|
||||
const map = {
|
||||
'"clamp"': ClampToEdgeWrapping,
|
||||
'"mirror"': MirroredRepeatWrapping,
|
||||
'"repeat"': RepeatWrapping
|
||||
};
|
||||
|
||||
if ( 'token inputs:wrapS' in data ) {
|
||||
|
||||
texture.wrapS = map[ data[ 'token inputs:wrapS' ] ];
|
||||
|
||||
}
|
||||
|
||||
if ( 'token inputs:wrapT' in data ) {
|
||||
|
||||
texture.wrapT = map[ data[ 'token inputs:wrapT' ] ];
|
||||
|
||||
}
|
||||
|
||||
return texture;
|
||||
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
function buildObject( data ) {
|
||||
|
||||
const geometry = buildGeometry( findMeshGeometry( data ) );
|
||||
const material = buildMaterial( findMeshMaterial( data ) );
|
||||
|
||||
const mesh = geometry ? new Mesh( geometry, material ) : new Object3D();
|
||||
|
||||
if ( 'matrix4d xformOp:transform' in data ) {
|
||||
|
||||
const array = JSON.parse( '[' + data[ 'matrix4d xformOp:transform' ].replace( /[()]*/g, '' ) + ']' );
|
||||
|
||||
mesh.matrix.fromArray( array );
|
||||
mesh.matrix.decompose( mesh.position, mesh.quaternion, mesh.scale );
|
||||
|
||||
}
|
||||
|
||||
return mesh;
|
||||
|
||||
}
|
||||
|
||||
function buildHierarchy( data, group ) {
|
||||
|
||||
for ( const name in data ) {
|
||||
|
||||
if ( name.startsWith( 'def Scope' ) ) {
|
||||
|
||||
buildHierarchy( data[ name ], group );
|
||||
|
||||
} else if ( name.startsWith( 'def Xform' ) ) {
|
||||
|
||||
const mesh = buildObject( data[ name ] );
|
||||
|
||||
if ( /def Xform "(\w+)"/.test( name ) ) {
|
||||
|
||||
mesh.name = /def Xform "(\w+)"/.exec( name )[ 1 ];
|
||||
|
||||
}
|
||||
|
||||
group.add( mesh );
|
||||
|
||||
buildHierarchy( data[ name ], mesh );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const group = new Group();
|
||||
|
||||
buildHierarchy( root, group );
|
||||
|
||||
return group;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export { USDZLoader };
|
||||
318
public/sdk/three/jsm/loaders/VOXLoader.js
Normal file
318
public/sdk/three/jsm/loaders/VOXLoader.js
Normal file
@ -0,0 +1,318 @@
|
||||
import {
|
||||
BufferGeometry,
|
||||
Color,
|
||||
Data3DTexture,
|
||||
FileLoader,
|
||||
Float32BufferAttribute,
|
||||
Loader,
|
||||
LinearFilter,
|
||||
Mesh,
|
||||
MeshStandardMaterial,
|
||||
NearestFilter,
|
||||
RedFormat,
|
||||
SRGBColorSpace
|
||||
} from 'three';
|
||||
|
||||
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.getUint32( 0, true );
|
||||
const version = data.getUint32( 4, true );
|
||||
|
||||
if ( id !== 542658390 ) {
|
||||
|
||||
console.error( 'THREE.VOXLoader: Invalid VOX file.' );
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
if ( version !== 150 ) {
|
||||
|
||||
console.error( 'THREE.VOXLoader: Invalid VOX file. Unsupported version:', version );
|
||||
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.getUint8( i ++ ) );
|
||||
|
||||
}
|
||||
|
||||
const chunkSize = data.getUint32( i, true ); i += 4;
|
||||
i += 4; // childChunks
|
||||
|
||||
if ( id === 'SIZE' ) {
|
||||
|
||||
const x = data.getUint32( i, true ); i += 4;
|
||||
const y = data.getUint32( i, true ); i += 4;
|
||||
const z = data.getUint32( 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.getUint32( i, true ); i += 4;
|
||||
chunk.data = new Uint8Array( 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.getUint32( 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 ];
|
||||
|
||||
const _color = new Color();
|
||||
|
||||
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 ) {
|
||||
|
||||
_color.setRGB( r, g, b, SRGBColorSpace );
|
||||
|
||||
vertices.push( tile[ i + 0 ] + x, tile[ i + 1 ] + y, tile[ i + 2 ] + z );
|
||||
colors.push( _color.r, _color.g, _color.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();
|
||||
|
||||
const material = new MeshStandardMaterial();
|
||||
|
||||
if ( hasColors ) {
|
||||
|
||||
geometry.setAttribute( 'color', new Float32BufferAttribute( colors, 3 ) );
|
||||
material.vertexColors = true;
|
||||
|
||||
}
|
||||
|
||||
super( geometry, material );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class VOXData3DTexture extends Data3DTexture {
|
||||
|
||||
constructor( chunk ) {
|
||||
|
||||
const data = chunk.data;
|
||||
const size = chunk.size;
|
||||
|
||||
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;
|
||||
|
||||
}
|
||||
|
||||
super( array, size.x, size.y, size.z );
|
||||
|
||||
this.format = RedFormat;
|
||||
this.minFilter = NearestFilter;
|
||||
this.magFilter = LinearFilter;
|
||||
this.unpackAlignment = 1;
|
||||
this.needsUpdate = true;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export { VOXLoader, VOXMesh, VOXData3DTexture };
|
||||
3537
public/sdk/three/jsm/loaders/VRMLLoader.js
Normal file
3537
public/sdk/three/jsm/loaders/VRMLLoader.js
Normal file
File diff suppressed because it is too large
Load Diff
1163
public/sdk/three/jsm/loaders/VTKLoader.js
Normal file
1163
public/sdk/three/jsm/loaders/VTKLoader.js
Normal file
File diff suppressed because it is too large
Load Diff
106
public/sdk/three/jsm/loaders/XYZLoader.js
Normal file
106
public/sdk/three/jsm/loaders/XYZLoader.js
Normal file
@ -0,0 +1,106 @@
|
||||
import {
|
||||
BufferGeometry,
|
||||
Color,
|
||||
FileLoader,
|
||||
Float32BufferAttribute,
|
||||
Loader
|
||||
} from 'three';
|
||||
|
||||
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 = [];
|
||||
const color = new Color();
|
||||
|
||||
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 ] ) );
|
||||
|
||||
const r = parseFloat( lineValues[ 3 ] ) / 255;
|
||||
const g = parseFloat( lineValues[ 4 ] ) / 255;
|
||||
const b = parseFloat( lineValues[ 5 ] ) / 255;
|
||||
|
||||
color.set( r, g, b ).convertSRGBToLinear();
|
||||
|
||||
colors.push( color.r, color.g, color.b );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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 };
|
||||
1217
public/sdk/three/jsm/loaders/lwo/IFFParser.js
Normal file
1217
public/sdk/three/jsm/loaders/lwo/IFFParser.js
Normal file
File diff suppressed because it is too large
Load Diff
414
public/sdk/three/jsm/loaders/lwo/LWO2Parser.js
Normal file
414
public/sdk/three/jsm/loaders/lwo/LWO2Parser.js
Normal file
@ -0,0 +1,414 @@
|
||||
class LWO2Parser {
|
||||
|
||||
constructor( IFFParser ) {
|
||||
|
||||
this.IFF = IFFParser;
|
||||
|
||||
}
|
||||
|
||||
parseBlock() {
|
||||
|
||||
this.IFF.debugger.offset = this.IFF.reader.offset;
|
||||
this.IFF.debugger.closeForms();
|
||||
|
||||
const blockID = this.IFF.reader.getIDTag();
|
||||
let 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 'NSEL':
|
||||
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':
|
||||
const 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':
|
||||
const 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 };
|
||||
373
public/sdk/three/jsm/loaders/lwo/LWO3Parser.js
Normal file
373
public/sdk/three/jsm/loaders/lwo/LWO3Parser.js
Normal file
@ -0,0 +1,373 @@
|
||||
class LWO3Parser {
|
||||
|
||||
constructor( IFFParser ) {
|
||||
|
||||
this.IFF = IFFParser;
|
||||
|
||||
}
|
||||
|
||||
parseBlock() {
|
||||
|
||||
this.IFF.debugger.offset = this.IFF.reader.offset;
|
||||
this.IFF.debugger.closeForms();
|
||||
|
||||
const blockID = this.IFF.reader.getIDTag();
|
||||
const 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 ': // Pre-loop behavior for the keyframe
|
||||
case 'POST': // Post-loop behavior for the keyframe
|
||||
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':
|
||||
const 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':
|
||||
const 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 };
|
||||
Reference in New Issue
Block a user