three#Vector4 JavaScript Examples
The following examples show how to use
three#Vector4.
You can vote up the ones you like or vote down the ones you don't like,
and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: NURBSCurve.js From canvas with Apache License 2.0 | 6 votes |
NURBSCurve = function ( degree, knots /* array of reals */, controlPoints /* array of Vector(2|3|4) */, startKnot /* index in knots */, endKnot /* index in knots */ ) {
Curve.call( this );
this.degree = degree;
this.knots = knots;
this.controlPoints = [];
// Used by periodic NURBS to remove hidden spans
this.startKnot = startKnot || 0;
this.endKnot = endKnot || ( this.knots.length - 1 );
for ( var i = 0; i < controlPoints.length; ++ i ) {
// ensure Vector4 for control points
var point = controlPoints[ i ];
this.controlPoints[ i ] = new Vector4( point.x, point.y, point.z, point.w );
}
}
Example #2
Source File: NURBSSurface.js From canvas with Apache License 2.0 | 6 votes |
NURBSSurface = function ( degree1, degree2, knots1, knots2 /* arrays of reals */, controlPoints /* array^2 of Vector(2|3|4) */ ) {
this.degree1 = degree1;
this.degree2 = degree2;
this.knots1 = knots1;
this.knots2 = knots2;
this.controlPoints = [];
var len1 = knots1.length - degree1 - 1;
var len2 = knots2.length - degree2 - 1;
// ensure Vector4 for control points
for ( var i = 0; i < len1; ++ i ) {
this.controlPoints[ i ] = [];
for ( var j = 0; j < len2; ++ j ) {
var point = controlPoints[ i ][ j ];
this.controlPoints[ i ][ j ] = new Vector4( point.x, point.y, point.z, point.w );
}
}
}
Example #3
Source File: NURBSSurface.js From Computer-Graphics with MIT License | 6 votes |
constructor( degree1, degree2, knots1, knots2 /* arrays of reals */, controlPoints /* array^2 of Vector(2|3|4) */ ) {
this.degree1 = degree1;
this.degree2 = degree2;
this.knots1 = knots1;
this.knots2 = knots2;
this.controlPoints = [];
const len1 = knots1.length - degree1 - 1;
const len2 = knots2.length - degree2 - 1;
// ensure Vector4 for control points
for ( let i = 0; i < len1; ++ i ) {
this.controlPoints[ i ] = [];
for ( let j = 0; j < len2; ++ j ) {
const point = controlPoints[ i ][ j ];
this.controlPoints[ i ][ j ] = new Vector4( point.x, point.y, point.z, point.w );
}
}
}
Example #4
Source File: FlyOrbitControls.js From 3DTilesRendererJS with Apache License 2.0 | 5 votes |
tempVector = new Vector4( 0, 0, 0, 0 )
Example #5
Source File: DistanceBasedFog.js From webmc with MIT License | 5 votes |
constructor (game) {
this.game = game
this.view = new Vector3()
this.farnear = new Vector2()
this.color = new Vector4()
this.visible = true
}
Example #6
Source File: LineSegments2.js From BlueMapWeb with MIT License | 4 votes |
LineSegments2.prototype = Object.assign( Object.create( Mesh.prototype ), {
constructor: LineSegments2,
isLineSegments2: true,
computeLineDistances: ( function () { // for backwards-compatability, but could be a method of LineSegmentsGeometry...
var start = new Vector3();
var end = new Vector3();
return function computeLineDistances() {
var geometry = this.geometry;
var instanceStart = geometry.attributes.instanceStart;
var instanceEnd = geometry.attributes.instanceEnd;
var lineDistances = new Float32Array( 2 * instanceStart.data.count );
for ( var i = 0, j = 0, l = instanceStart.data.count; i < l; i ++, j += 2 ) {
start.fromBufferAttribute( instanceStart, i );
end.fromBufferAttribute( instanceEnd, i );
lineDistances[ j ] = ( j === 0 ) ? 0 : lineDistances[ j - 1 ];
lineDistances[ j + 1 ] = lineDistances[ j ] + start.distanceTo( end );
}
var instanceDistanceBuffer = new InstancedInterleavedBuffer( lineDistances, 2, 1 ); // d0, d1
geometry.setAttribute( 'instanceDistanceStart', new InterleavedBufferAttribute( instanceDistanceBuffer, 1, 0 ) ); // d0
geometry.setAttribute( 'instanceDistanceEnd', new InterleavedBufferAttribute( instanceDistanceBuffer, 1, 1 ) ); // d1
return this;
};
}() ),
raycast: ( function () {
var start = new Vector4();
var end = new Vector4();
var ssOrigin = new Vector4();
var ssOrigin3 = new Vector3();
var mvMatrix = new Matrix4();
var line = new Line3();
var closestPoint = new Vector3();
return function raycast( raycaster, intersects ) {
if ( raycaster.camera === null ) {
console.error( 'LineSegments2: "Raycaster.camera" needs to be set in order to raycast against LineSegments2.' );
}
var threshold = ( raycaster.params.Line2 !== undefined ) ? raycaster.params.Line2.threshold || 0 : 0;
var ray = raycaster.ray;
var camera = raycaster.camera;
var projectionMatrix = camera.projectionMatrix;
var geometry = this.geometry;
var material = this.material;
var resolution = material.resolution;
var lineWidth = material.linewidth + threshold;
var instanceStart = geometry.attributes.instanceStart;
var instanceEnd = geometry.attributes.instanceEnd;
// pick a point 1 unit out along the ray to avoid the ray origin
// sitting at the camera origin which will cause "w" to be 0 when
// applying the projection matrix.
ray.at( 1, ssOrigin );
// ndc space [ - 1.0, 1.0 ]
ssOrigin.w = 1;
ssOrigin.applyMatrix4( camera.matrixWorldInverse );
ssOrigin.applyMatrix4( projectionMatrix );
ssOrigin.multiplyScalar( 1 / ssOrigin.w );
// screen space
ssOrigin.x *= resolution.x / 2;
ssOrigin.y *= resolution.y / 2;
ssOrigin.z = 0;
ssOrigin3.copy( ssOrigin );
var matrixWorld = this.matrixWorld;
mvMatrix.multiplyMatrices( camera.matrixWorldInverse, matrixWorld );
for ( var i = 0, l = instanceStart.count; i < l; i ++ ) {
start.fromBufferAttribute( instanceStart, i );
end.fromBufferAttribute( instanceEnd, i );
start.w = 1;
end.w = 1;
// camera space
start.applyMatrix4( mvMatrix );
end.applyMatrix4( mvMatrix );
// clip space
start.applyMatrix4( projectionMatrix );
end.applyMatrix4( projectionMatrix );
// ndc space [ - 1.0, 1.0 ]
start.multiplyScalar( 1 / start.w );
end.multiplyScalar( 1 / end.w );
// skip the segment if it's outside the camera near and far planes
var isBehindCameraNear = start.z < - 1 && end.z < - 1;
var isPastCameraFar = start.z > 1 && end.z > 1;
if ( isBehindCameraNear || isPastCameraFar ) {
continue;
}
// screen space
start.x *= resolution.x / 2;
start.y *= resolution.y / 2;
end.x *= resolution.x / 2;
end.y *= resolution.y / 2;
// create 2d segment
line.start.copy( start );
line.start.z = 0;
line.end.copy( end );
line.end.z = 0;
// get closest point on ray to segment
var param = line.closestPointToPointParameter( ssOrigin3, true );
line.at( param, closestPoint );
// check if the intersection point is within clip space
var zPos = MathUtils.lerp( start.z, end.z, param );
var isInClipSpace = zPos >= - 1 && zPos <= 1;
var isInside = ssOrigin3.distanceTo( closestPoint ) < lineWidth * 0.5;
if ( isInClipSpace && isInside ) {
line.start.fromBufferAttribute( instanceStart, i );
line.end.fromBufferAttribute( instanceEnd, i );
line.start.applyMatrix4( matrixWorld );
line.end.applyMatrix4( matrixWorld );
var pointOnLine = new Vector3();
var point = new Vector3();
ray.distanceSqToSegment( line.start, line.end, point, pointOnLine );
intersects.push( {
point: point,
pointOnLine: pointOnLine,
distance: ray.origin.distanceTo( point ),
object: this,
face: null,
faceIndex: i,
uv: null,
uv2: null,
} );
}
}
};
}() )
} );
Example #7
Source File: Intro.jsx From portfolio with MIT License | 4 votes |
export default function Intro({ darkTheme, active, setActivePage }) {
// Use ref because need to directly manipulate DOM
const mountRef = useRef(null);
const [ requestId, setRequestId ] = useState(null)
useEffect(() => {
const curr = mountRef.current
var startTime = Date.now();
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(75, window.innerWidth/window.innerHeight, 0.1, 1000);
var renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setClearColor(darkTheme ? 0x000 : 0xf7f7f7, 1)
renderer.setSize(window.innerWidth, window.innerHeight);
mountRef.current.appendChild(renderer.domElement);
let geometry, color1, color2
let w = 12, h = 2
if (darkTheme) {
geometry = new THREE.PlaneGeometry(w, h, 70, 50);
color2 = new Color(0x00d1ff)
color1 = new Color(0x040b55)
} else {
geometry = new THREE.PlaneGeometry(w, h, 70, 50);
color2 = new Color(0x5233a8)
color1 = new Color(0x17a3db)
}
var material = new THREE.ShaderMaterial({
glslVersion: THREE.GLSL1,
wireframe: true,
vertexShader: PerlinShader.vertexShader,
fragmentShader: PerlinShader.fragmentShader,
uniforms: {
p: { value: PerlinShader.p },
time: { value: 0 },
color1: { value: new Vector4(color1.r, color1.g, color1.b, 1)},
color2: { value: new Vector4(color2.r, color2.g, color2.b, 1)},
}
})
var plane1 = new THREE.Mesh(geometry, material);
plane1.rotateX(-1.1)
plane1.position.z = 2
plane1.position.y = -1.15
scene.add(plane1)
var plane2 = new THREE.Mesh(geometry, material);
plane2.rotateX(1.1)
plane2.position.z = 2
plane2.position.y = 1.15
scene.add(plane2)
camera.position.z = 5
var animate = function () {
if (!active) {
cancelAnimationFrame(requestId)
setRequestId(null)
return
}
setRequestId(requestAnimationFrame(animate));
var elapsedMilliseconds = Date.now() - startTime;
plane1.material.uniforms.time.value = elapsedMilliseconds / 1000. / 2;
plane1.material.uniforms.time.value %= 30;
renderer.render(scene, camera);
}
let onWindowResize = function () {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
};
window.addEventListener("resize", onWindowResize, false);
animate()
return () => curr.removeChild(renderer.domElement);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [darkTheme, active]);
const updatePage = () => {
const newActivePage = [false, false, false, false]
newActivePage[1] = true
setActivePage(newActivePage)
}
return (
<div className='intro' id='intro'>
<div id='c' ref={mountRef}></div>
<div className='info'>
<h1 className='blue'>Médéric Carriat</h1>
<h4 className='blue'>Software Engineer looking for a 6-month internship</h4>
</div>
<div className='arrow-border'>
<a
className='arrow-container'
href='#experience'
onClick={updatePage}
>
<span className='arrow'>
<i class="fas fa-arrow-right blue"></i>
</span>
</a>
</div>
</div>
)
}
Example #8
Source File: NURBSUtils.js From canvas with Apache License 2.0 | 4 votes |
NURBSUtils = {
/*
Finds knot vector span.
p : degree
u : parametric value
U : knot vector
returns the span
*/
findSpan: function ( p, u, U ) {
var n = U.length - p - 1;
if ( u >= U[ n ] ) {
return n - 1;
}
if ( u <= U[ p ] ) {
return p;
}
var low = p;
var high = n;
var mid = Math.floor( ( low + high ) / 2 );
while ( u < U[ mid ] || u >= U[ mid + 1 ] ) {
if ( u < U[ mid ] ) {
high = mid;
} else {
low = mid;
}
mid = Math.floor( ( low + high ) / 2 );
}
return mid;
},
/*
Calculate basis functions. See The NURBS Book, page 70, algorithm A2.2
span : span in which u lies
u : parametric point
p : degree
U : knot vector
returns array[p+1] with basis functions values.
*/
calcBasisFunctions: function ( span, u, p, U ) {
var N = [];
var left = [];
var right = [];
N[ 0 ] = 1.0;
for ( var j = 1; j <= p; ++ j ) {
left[ j ] = u - U[ span + 1 - j ];
right[ j ] = U[ span + j ] - u;
var saved = 0.0;
for ( var r = 0; r < j; ++ r ) {
var rv = right[ r + 1 ];
var lv = left[ j - r ];
var temp = N[ r ] / ( rv + lv );
N[ r ] = saved + rv * temp;
saved = lv * temp;
}
N[ j ] = saved;
}
return N;
},
/*
Calculate B-Spline curve points. See The NURBS Book, page 82, algorithm A3.1.
p : degree of B-Spline
U : knot vector
P : control points (x, y, z, w)
u : parametric point
returns point for given u
*/
calcBSplinePoint: function ( p, U, P, u ) {
var span = this.findSpan( p, u, U );
var N = this.calcBasisFunctions( span, u, p, U );
var C = new Vector4( 0, 0, 0, 0 );
for ( var j = 0; j <= p; ++ j ) {
var point = P[ span - p + j ];
var Nj = N[ j ];
var wNj = point.w * Nj;
C.x += point.x * wNj;
C.y += point.y * wNj;
C.z += point.z * wNj;
C.w += point.w * Nj;
}
return C;
},
/*
Calculate basis functions derivatives. See The NURBS Book, page 72, algorithm A2.3.
span : span in which u lies
u : parametric point
p : degree
n : number of derivatives to calculate
U : knot vector
returns array[n+1][p+1] with basis functions derivatives
*/
calcBasisFunctionDerivatives: function ( span, u, p, n, U ) {
var zeroArr = [];
for ( var i = 0; i <= p; ++ i )
zeroArr[ i ] = 0.0;
var ders = [];
for ( var i = 0; i <= n; ++ i )
ders[ i ] = zeroArr.slice( 0 );
var ndu = [];
for ( var i = 0; i <= p; ++ i )
ndu[ i ] = zeroArr.slice( 0 );
ndu[ 0 ][ 0 ] = 1.0;
var left = zeroArr.slice( 0 );
var right = zeroArr.slice( 0 );
for ( var j = 1; j <= p; ++ j ) {
left[ j ] = u - U[ span + 1 - j ];
right[ j ] = U[ span + j ] - u;
var saved = 0.0;
for ( var r = 0; r < j; ++ r ) {
var rv = right[ r + 1 ];
var lv = left[ j - r ];
ndu[ j ][ r ] = rv + lv;
var temp = ndu[ r ][ j - 1 ] / ndu[ j ][ r ];
ndu[ r ][ j ] = saved + rv * temp;
saved = lv * temp;
}
ndu[ j ][ j ] = saved;
}
for ( var j = 0; j <= p; ++ j ) {
ders[ 0 ][ j ] = ndu[ j ][ p ];
}
for ( var r = 0; r <= p; ++ r ) {
var s1 = 0;
var s2 = 1;
var a = [];
for ( var i = 0; i <= p; ++ i ) {
a[ i ] = zeroArr.slice( 0 );
}
a[ 0 ][ 0 ] = 1.0;
for ( var k = 1; k <= n; ++ k ) {
var d = 0.0;
var rk = r - k;
var pk = p - k;
if ( r >= k ) {
a[ s2 ][ 0 ] = a[ s1 ][ 0 ] / ndu[ pk + 1 ][ rk ];
d = a[ s2 ][ 0 ] * ndu[ rk ][ pk ];
}
var j1 = ( rk >= - 1 ) ? 1 : - rk;
var j2 = ( r - 1 <= pk ) ? k - 1 : p - r;
for ( var j = j1; j <= j2; ++ j ) {
a[ s2 ][ j ] = ( a[ s1 ][ j ] - a[ s1 ][ j - 1 ] ) / ndu[ pk + 1 ][ rk + j ];
d += a[ s2 ][ j ] * ndu[ rk + j ][ pk ];
}
if ( r <= pk ) {
a[ s2 ][ k ] = - a[ s1 ][ k - 1 ] / ndu[ pk + 1 ][ r ];
d += a[ s2 ][ k ] * ndu[ r ][ pk ];
}
ders[ k ][ r ] = d;
var j = s1;
s1 = s2;
s2 = j;
}
}
var r = p;
for ( var k = 1; k <= n; ++ k ) {
for ( var j = 0; j <= p; ++ j ) {
ders[ k ][ j ] *= r;
}
r *= p - k;
}
return ders;
},
/*
Calculate derivatives of a B-Spline. See The NURBS Book, page 93, algorithm A3.2.
p : degree
U : knot vector
P : control points
u : Parametric points
nd : number of derivatives
returns array[d+1] with derivatives
*/
calcBSplineDerivatives: function ( p, U, P, u, nd ) {
var du = nd < p ? nd : p;
var CK = [];
var span = this.findSpan( p, u, U );
var nders = this.calcBasisFunctionDerivatives( span, u, p, du, U );
var Pw = [];
for ( var i = 0; i < P.length; ++ i ) {
var point = P[ i ].clone();
var w = point.w;
point.x *= w;
point.y *= w;
point.z *= w;
Pw[ i ] = point;
}
for ( var k = 0; k <= du; ++ k ) {
var point = Pw[ span - p ].clone().multiplyScalar( nders[ k ][ 0 ] );
for ( var j = 1; j <= p; ++ j ) {
point.add( Pw[ span - p + j ].clone().multiplyScalar( nders[ k ][ j ] ) );
}
CK[ k ] = point;
}
for ( var k = du + 1; k <= nd + 1; ++ k ) {
CK[ k ] = new Vector4( 0, 0, 0 );
}
return CK;
},
/*
Calculate "K over I"
returns k!/(i!(k-i)!)
*/
calcKoverI: function ( k, i ) {
var nom = 1;
for ( var j = 2; j <= k; ++ j ) {
nom *= j;
}
var denom = 1;
for ( var j = 2; j <= i; ++ j ) {
denom *= j;
}
for ( var j = 2; j <= k - i; ++ j ) {
denom *= j;
}
return nom / denom;
},
/*
Calculate derivatives (0-nd) of rational curve. See The NURBS Book, page 127, algorithm A4.2.
Pders : result of function calcBSplineDerivatives
returns array with derivatives for rational curve.
*/
calcRationalCurveDerivatives: function ( Pders ) {
var nd = Pders.length;
var Aders = [];
var wders = [];
for ( var i = 0; i < nd; ++ i ) {
var point = Pders[ i ];
Aders[ i ] = new Vector3( point.x, point.y, point.z );
wders[ i ] = point.w;
}
var CK = [];
for ( var k = 0; k < nd; ++ k ) {
var v = Aders[ k ].clone();
for ( var i = 1; i <= k; ++ i ) {
v.sub( CK[ k - i ].clone().multiplyScalar( this.calcKoverI( k, i ) * wders[ i ] ) );
}
CK[ k ] = v.divideScalar( wders[ 0 ] );
}
return CK;
},
/*
Calculate NURBS curve derivatives. See The NURBS Book, page 127, algorithm A4.2.
p : degree
U : knot vector
P : control points in homogeneous space
u : parametric points
nd : number of derivatives
returns array with derivatives.
*/
calcNURBSDerivatives: function ( p, U, P, u, nd ) {
var Pders = this.calcBSplineDerivatives( p, U, P, u, nd );
return this.calcRationalCurveDerivatives( Pders );
},
/*
Calculate rational B-Spline surface point. See The NURBS Book, page 134, algorithm A4.3.
p1, p2 : degrees of B-Spline surface
U1, U2 : knot vectors
P : control points (x, y, z, w)
u, v : parametric values
returns point for given (u, v)
*/
calcSurfacePoint: function ( p, q, U, V, P, u, v, target ) {
var uspan = this.findSpan( p, u, U );
var vspan = this.findSpan( q, v, V );
var Nu = this.calcBasisFunctions( uspan, u, p, U );
var Nv = this.calcBasisFunctions( vspan, v, q, V );
var temp = [];
for ( var l = 0; l <= q; ++ l ) {
temp[ l ] = new Vector4( 0, 0, 0, 0 );
for ( var k = 0; k <= p; ++ k ) {
var point = P[ uspan - p + k ][ vspan - q + l ].clone();
var w = point.w;
point.x *= w;
point.y *= w;
point.z *= w;
temp[ l ].add( point.multiplyScalar( Nu[ k ] ) );
}
}
var Sw = new Vector4( 0, 0, 0, 0 );
for ( var l = 0; l <= q; ++ l ) {
Sw.add( temp[ l ].multiplyScalar( Nv[ l ] ) );
}
Sw.divideScalar( Sw.w );
target.set( Sw.x, Sw.y, Sw.z );
}
}
Example #9
Source File: FBXLoader.js From canvas with Apache License 2.0 | 4 votes |
FBXLoader = ( function () {
var fbxTree;
var connections;
var sceneGraph;
function FBXLoader( manager ) {
Loader.call( this, manager );
}
FBXLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
constructor: FBXLoader,
load: function ( url, onLoad, onProgress, onError ) {
var scope = this;
var path = ( scope.path === '' ) ? LoaderUtils.extractUrlBase( url ) : scope.path;
var loader = new FileLoader( this.manager );
loader.setPath( scope.path );
loader.setResponseType( 'arraybuffer' );
loader.load( url, function ( buffer ) {
try {
onLoad( scope.parse( buffer, path ) );
} catch ( error ) {
setTimeout( function () {
if ( onError ) onError( error );
scope.manager.itemError( url );
}, 0 );
}
}, onProgress, onError );
},
parse: function ( FBXBuffer, path ) {
if ( isFbxFormatBinary( FBXBuffer ) ) {
fbxTree = new BinaryParser().parse( FBXBuffer );
} else {
var FBXText = convertArrayBufferToString( FBXBuffer );
if ( ! isFbxFormatASCII( FBXText ) ) {
throw new Error( 'THREE.FBXLoader: Unknown format.' );
}
if ( getFbxVersion( FBXText ) < 7000 ) {
throw new Error( 'THREE.FBXLoader: FBX version not supported, FileVersion: ' + getFbxVersion( FBXText ) );
}
fbxTree = new TextParser().parse( FBXText );
}
// console.log( fbxTree );
var textureLoader = new TextureLoader( this.manager ).setPath( this.resourcePath || path ).setCrossOrigin( this.crossOrigin );
return new FBXTreeParser( textureLoader, this.manager ).parse( fbxTree );
}
} );
// Parse the FBXTree object returned by the BinaryParser or TextParser and return a Group
function FBXTreeParser( textureLoader, manager ) {
this.textureLoader = textureLoader;
this.manager = manager;
}
FBXTreeParser.prototype = {
constructor: FBXTreeParser,
parse: function () {
connections = this.parseConnections();
var images = this.parseImages();
var textures = this.parseTextures( images );
var materials = this.parseMaterials( textures );
var deformers = this.parseDeformers();
var geometryMap = new GeometryParser().parse( deformers );
this.parseScene( deformers, geometryMap, materials );
return sceneGraph;
},
// Parses FBXTree.Connections which holds parent-child connections between objects (e.g. material -> texture, model->geometry )
// and details the connection type
parseConnections: function () {
var connectionMap = new Map();
if ( 'Connections' in fbxTree ) {
var rawConnections = fbxTree.Connections.connections;
rawConnections.forEach( function ( rawConnection ) {
var fromID = rawConnection[ 0 ];
var toID = rawConnection[ 1 ];
var relationship = rawConnection[ 2 ];
if ( ! connectionMap.has( fromID ) ) {
connectionMap.set( fromID, {
parents: [],
children: []
} );
}
var parentRelationship = { ID: toID, relationship: relationship };
connectionMap.get( fromID ).parents.push( parentRelationship );
if ( ! connectionMap.has( toID ) ) {
connectionMap.set( toID, {
parents: [],
children: []
} );
}
var childRelationship = { ID: fromID, relationship: relationship };
connectionMap.get( toID ).children.push( childRelationship );
} );
}
return connectionMap;
},
// Parse FBXTree.Objects.Video for embedded image data
// These images are connected to textures in FBXTree.Objects.Textures
// via FBXTree.Connections.
parseImages: function () {
var images = {};
var blobs = {};
if ( 'Video' in fbxTree.Objects ) {
var videoNodes = fbxTree.Objects.Video;
for ( var nodeID in videoNodes ) {
var videoNode = videoNodes[ nodeID ];
var id = parseInt( nodeID );
images[ id ] = videoNode.RelativeFilename || videoNode.Filename;
// raw image data is in videoNode.Content
if ( 'Content' in videoNode ) {
var arrayBufferContent = ( videoNode.Content instanceof ArrayBuffer ) && ( videoNode.Content.byteLength > 0 );
var base64Content = ( typeof videoNode.Content === 'string' ) && ( videoNode.Content !== '' );
if ( arrayBufferContent || base64Content ) {
var image = this.parseImage( videoNodes[ nodeID ] );
blobs[ videoNode.RelativeFilename || videoNode.Filename ] = image;
}
}
}
}
for ( var id in images ) {
var filename = images[ id ];
if ( blobs[ filename ] !== undefined ) images[ id ] = blobs[ filename ];
else images[ id ] = images[ id ].split( '\\' ).pop();
}
return images;
},
// Parse embedded image data in FBXTree.Video.Content
parseImage: function ( videoNode ) {
var content = videoNode.Content;
var fileName = videoNode.RelativeFilename || videoNode.Filename;
var extension = fileName.slice( fileName.lastIndexOf( '.' ) + 1 ).toLowerCase();
var type;
switch ( extension ) {
case 'bmp':
type = 'image/bmp';
break;
case 'jpg':
case 'jpeg':
type = 'image/jpeg';
break;
case 'png':
type = 'image/png';
break;
case 'tif':
type = 'image/tiff';
break;
case 'tga':
if ( this.manager.getHandler( '.tga' ) === null ) {
console.warn( 'FBXLoader: TGA loader not found, skipping ', fileName );
}
type = 'image/tga';
break;
default:
console.warn( 'FBXLoader: Image type "' + extension + '" is not supported.' );
return;
}
if ( typeof content === 'string' ) { // ASCII format
return 'data:' + type + ';base64,' + content;
} else { // Binary Format
var array = new Uint8Array( content );
return window.URL.createObjectURL( new Blob( [ array ], { type: type } ) );
}
},
// Parse nodes in FBXTree.Objects.Texture
// These contain details such as UV scaling, cropping, rotation etc and are connected
// to images in FBXTree.Objects.Video
parseTextures: function ( images ) {
var textureMap = new Map();
if ( 'Texture' in fbxTree.Objects ) {
var textureNodes = fbxTree.Objects.Texture;
for ( var nodeID in textureNodes ) {
var texture = this.parseTexture( textureNodes[ nodeID ], images );
textureMap.set( parseInt( nodeID ), texture );
}
}
return textureMap;
},
// Parse individual node in FBXTree.Objects.Texture
parseTexture: function ( textureNode, images ) {
var texture = this.loadTexture( textureNode, images );
texture.ID = textureNode.id;
texture.name = textureNode.attrName;
var wrapModeU = textureNode.WrapModeU;
var wrapModeV = textureNode.WrapModeV;
var valueU = wrapModeU !== undefined ? wrapModeU.value : 0;
var valueV = wrapModeV !== undefined ? wrapModeV.value : 0;
// http://download.autodesk.com/us/fbx/SDKdocs/FBX_SDK_Help/files/fbxsdkref/class_k_fbx_texture.html#889640e63e2e681259ea81061b85143a
// 0: repeat(default), 1: clamp
texture.wrapS = valueU === 0 ? RepeatWrapping : ClampToEdgeWrapping;
texture.wrapT = valueV === 0 ? RepeatWrapping : ClampToEdgeWrapping;
if ( 'Scaling' in textureNode ) {
var values = textureNode.Scaling.value;
texture.repeat.x = values[ 0 ];
texture.repeat.y = values[ 1 ];
}
return texture;
},
// load a texture specified as a blob or data URI, or via an external URL using TextureLoader
loadTexture: function ( textureNode, images ) {
var fileName;
var currentPath = this.textureLoader.path;
var children = connections.get( textureNode.id ).children;
if ( children !== undefined && children.length > 0 && images[ children[ 0 ].ID ] !== undefined ) {
fileName = images[ children[ 0 ].ID ];
if ( fileName.indexOf( 'blob:' ) === 0 || fileName.indexOf( 'data:' ) === 0 ) {
this.textureLoader.setPath( undefined );
}
}
var texture;
var extension = textureNode.FileName.slice( - 3 ).toLowerCase();
if ( extension === 'tga' ) {
var loader = this.manager.getHandler( '.tga' );
if ( loader === null ) {
console.warn( 'FBXLoader: TGA loader not found, creating placeholder texture for', textureNode.RelativeFilename );
texture = new Texture();
} else {
texture = loader.load( fileName );
}
} else if ( extension === 'psd' ) {
console.warn( 'FBXLoader: PSD textures are not supported, creating placeholder texture for', textureNode.RelativeFilename );
texture = new Texture();
} else {
texture = this.textureLoader.load( fileName );
}
this.textureLoader.setPath( currentPath );
return texture;
},
// Parse nodes in FBXTree.Objects.Material
parseMaterials: function ( textureMap ) {
var materialMap = new Map();
if ( 'Material' in fbxTree.Objects ) {
var materialNodes = fbxTree.Objects.Material;
for ( var nodeID in materialNodes ) {
var material = this.parseMaterial( materialNodes[ nodeID ], textureMap );
if ( material !== null ) materialMap.set( parseInt( nodeID ), material );
}
}
return materialMap;
},
// Parse single node in FBXTree.Objects.Material
// Materials are connected to texture maps in FBXTree.Objects.Textures
// FBX format currently only supports Lambert and Phong shading models
parseMaterial: function ( materialNode, textureMap ) {
var ID = materialNode.id;
var name = materialNode.attrName;
var type = materialNode.ShadingModel;
// Case where FBX wraps shading model in property object.
if ( typeof type === 'object' ) {
type = type.value;
}
// Ignore unused materials which don't have any connections.
if ( ! connections.has( ID ) ) return null;
var parameters = this.parseParameters( materialNode, textureMap, ID );
var material;
switch ( type.toLowerCase() ) {
case 'phong':
material = new MeshPhongMaterial();
break;
case 'lambert':
material = new MeshLambertMaterial();
break;
default:
console.warn( 'THREE.FBXLoader: unknown material type "%s". Defaulting to MeshPhongMaterial.', type );
material = new MeshPhongMaterial();
break;
}
material.setValues( parameters );
material.name = name;
return material;
},
// Parse FBX material and return parameters suitable for a three.js material
// Also parse the texture map and return any textures associated with the material
parseParameters: function ( materialNode, textureMap, ID ) {
var parameters = {};
if ( materialNode.BumpFactor ) {
parameters.bumpScale = materialNode.BumpFactor.value;
}
if ( materialNode.Diffuse ) {
parameters.color = new Color().fromArray( materialNode.Diffuse.value );
} else if ( materialNode.DiffuseColor && materialNode.DiffuseColor.type === 'Color' ) {
// The blender exporter exports diffuse here instead of in materialNode.Diffuse
parameters.color = new Color().fromArray( materialNode.DiffuseColor.value );
}
if ( materialNode.DisplacementFactor ) {
parameters.displacementScale = materialNode.DisplacementFactor.value;
}
if ( materialNode.Emissive ) {
parameters.emissive = new Color().fromArray( materialNode.Emissive.value );
} else if ( materialNode.EmissiveColor && materialNode.EmissiveColor.type === 'Color' ) {
// The blender exporter exports emissive color here instead of in materialNode.Emissive
parameters.emissive = new Color().fromArray( materialNode.EmissiveColor.value );
}
if ( materialNode.EmissiveFactor ) {
parameters.emissiveIntensity = parseFloat( materialNode.EmissiveFactor.value );
}
if ( materialNode.Opacity ) {
parameters.opacity = parseFloat( materialNode.Opacity.value );
}
if ( parameters.opacity < 1.0 ) {
parameters.transparent = true;
}
if ( materialNode.ReflectionFactor ) {
parameters.reflectivity = materialNode.ReflectionFactor.value;
}
if ( materialNode.Shininess ) {
parameters.shininess = materialNode.Shininess.value;
}
if ( materialNode.Specular ) {
parameters.specular = new Color().fromArray( materialNode.Specular.value );
} else if ( materialNode.SpecularColor && materialNode.SpecularColor.type === 'Color' ) {
// The blender exporter exports specular color here instead of in materialNode.Specular
parameters.specular = new Color().fromArray( materialNode.SpecularColor.value );
}
var scope = this;
connections.get( ID ).children.forEach( function ( child ) {
var type = child.relationship;
switch ( type ) {
case 'Bump':
parameters.bumpMap = scope.getTexture( textureMap, child.ID );
break;
case 'Maya|TEX_ao_map':
parameters.aoMap = scope.getTexture( textureMap, child.ID );
break;
case 'DiffuseColor':
case 'Maya|TEX_color_map':
parameters.map = scope.getTexture( textureMap, child.ID );
parameters.map.encoding = sRGBEncoding;
break;
case 'DisplacementColor':
parameters.displacementMap = scope.getTexture( textureMap, child.ID );
break;
case 'EmissiveColor':
parameters.emissiveMap = scope.getTexture( textureMap, child.ID );
parameters.emissiveMap.encoding = sRGBEncoding;
break;
case 'NormalMap':
case 'Maya|TEX_normal_map':
parameters.normalMap = scope.getTexture( textureMap, child.ID );
break;
case 'ReflectionColor':
parameters.envMap = scope.getTexture( textureMap, child.ID );
parameters.envMap.mapping = EquirectangularReflectionMapping;
parameters.envMap.encoding = sRGBEncoding;
break;
case 'SpecularColor':
parameters.specularMap = scope.getTexture( textureMap, child.ID );
parameters.specularMap.encoding = sRGBEncoding;
break;
case 'TransparentColor':
case 'TransparencyFactor':
parameters.alphaMap = scope.getTexture( textureMap, child.ID );
parameters.transparent = true;
break;
case 'AmbientColor':
case 'ShininessExponent': // AKA glossiness map
case 'SpecularFactor': // AKA specularLevel
case 'VectorDisplacementColor': // NOTE: Seems to be a copy of DisplacementColor
default:
console.warn( 'THREE.FBXLoader: %s map is not supported in three.js, skipping texture.', type );
break;
}
} );
return parameters;
},
// get a texture from the textureMap for use by a material.
getTexture: function ( textureMap, id ) {
// if the texture is a layered texture, just use the first layer and issue a warning
if ( 'LayeredTexture' in fbxTree.Objects && id in fbxTree.Objects.LayeredTexture ) {
console.warn( 'THREE.FBXLoader: layered textures are not supported in three.js. Discarding all but first layer.' );
id = connections.get( id ).children[ 0 ].ID;
}
return textureMap.get( id );
},
// Parse nodes in FBXTree.Objects.Deformer
// Deformer node can contain skinning or Vertex Cache animation data, however only skinning is supported here
// Generates map of Skeleton-like objects for use later when generating and binding skeletons.
parseDeformers: function () {
var skeletons = {};
var morphTargets = {};
if ( 'Deformer' in fbxTree.Objects ) {
var DeformerNodes = fbxTree.Objects.Deformer;
for ( var nodeID in DeformerNodes ) {
var deformerNode = DeformerNodes[ nodeID ];
var relationships = connections.get( parseInt( nodeID ) );
if ( deformerNode.attrType === 'Skin' ) {
var skeleton = this.parseSkeleton( relationships, DeformerNodes );
skeleton.ID = nodeID;
if ( relationships.parents.length > 1 ) console.warn( 'THREE.FBXLoader: skeleton attached to more than one geometry is not supported.' );
skeleton.geometryID = relationships.parents[ 0 ].ID;
skeletons[ nodeID ] = skeleton;
} else if ( deformerNode.attrType === 'BlendShape' ) {
var morphTarget = {
id: nodeID,
};
morphTarget.rawTargets = this.parseMorphTargets( relationships, DeformerNodes );
morphTarget.id = nodeID;
if ( relationships.parents.length > 1 ) console.warn( 'THREE.FBXLoader: morph target attached to more than one geometry is not supported.' );
morphTargets[ nodeID ] = morphTarget;
}
}
}
return {
skeletons: skeletons,
morphTargets: morphTargets,
};
},
// Parse single nodes in FBXTree.Objects.Deformer
// The top level skeleton node has type 'Skin' and sub nodes have type 'Cluster'
// Each skin node represents a skeleton and each cluster node represents a bone
parseSkeleton: function ( relationships, deformerNodes ) {
var rawBones = [];
relationships.children.forEach( function ( child ) {
var boneNode = deformerNodes[ child.ID ];
if ( boneNode.attrType !== 'Cluster' ) return;
var rawBone = {
ID: child.ID,
indices: [],
weights: [],
transformLink: new Matrix4().fromArray( boneNode.TransformLink.a ),
// transform: new Matrix4().fromArray( boneNode.Transform.a ),
// linkMode: boneNode.Mode,
};
if ( 'Indexes' in boneNode ) {
rawBone.indices = boneNode.Indexes.a;
rawBone.weights = boneNode.Weights.a;
}
rawBones.push( rawBone );
} );
return {
rawBones: rawBones,
bones: []
};
},
// The top level morph deformer node has type "BlendShape" and sub nodes have type "BlendShapeChannel"
parseMorphTargets: function ( relationships, deformerNodes ) {
var rawMorphTargets = [];
for ( var i = 0; i < relationships.children.length; i ++ ) {
var child = relationships.children[ i ];
var morphTargetNode = deformerNodes[ child.ID ];
var rawMorphTarget = {
name: morphTargetNode.attrName,
initialWeight: morphTargetNode.DeformPercent,
id: morphTargetNode.id,
fullWeights: morphTargetNode.FullWeights.a
};
if ( morphTargetNode.attrType !== 'BlendShapeChannel' ) return;
rawMorphTarget.geoID = connections.get( parseInt( child.ID ) ).children.filter( function ( child ) {
return child.relationship === undefined;
} )[ 0 ].ID;
rawMorphTargets.push( rawMorphTarget );
}
return rawMorphTargets;
},
// create the main Group() to be returned by the loader
parseScene: function ( deformers, geometryMap, materialMap ) {
sceneGraph = new Group();
var modelMap = this.parseModels( deformers.skeletons, geometryMap, materialMap );
var modelNodes = fbxTree.Objects.Model;
var scope = this;
modelMap.forEach( function ( model ) {
var modelNode = modelNodes[ model.ID ];
scope.setLookAtProperties( model, modelNode );
var parentConnections = connections.get( model.ID ).parents;
parentConnections.forEach( function ( connection ) {
var parent = modelMap.get( connection.ID );
if ( parent !== undefined ) parent.add( model );
} );
if ( model.parent === null ) {
sceneGraph.add( model );
}
} );
this.bindSkeleton( deformers.skeletons, geometryMap, modelMap );
this.createAmbientLight();
this.setupMorphMaterials();
sceneGraph.traverse( function ( node ) {
if ( node.userData.transformData ) {
if ( node.parent ) node.userData.transformData.parentMatrixWorld = node.parent.matrix;
var transform = generateTransform( node.userData.transformData );
node.applyMatrix4( transform );
}
} );
var animations = new AnimationParser().parse();
// if all the models where already combined in a single group, just return that
if ( sceneGraph.children.length === 1 && sceneGraph.children[ 0 ].isGroup ) {
sceneGraph.children[ 0 ].animations = animations;
sceneGraph = sceneGraph.children[ 0 ];
}
sceneGraph.animations = animations;
},
// parse nodes in FBXTree.Objects.Model
parseModels: function ( skeletons, geometryMap, materialMap ) {
var modelMap = new Map();
var modelNodes = fbxTree.Objects.Model;
for ( var nodeID in modelNodes ) {
var id = parseInt( nodeID );
var node = modelNodes[ nodeID ];
var relationships = connections.get( id );
var model = this.buildSkeleton( relationships, skeletons, id, node.attrName );
if ( ! model ) {
switch ( node.attrType ) {
case 'Camera':
model = this.createCamera( relationships );
break;
case 'Light':
model = this.createLight( relationships );
break;
case 'Mesh':
model = this.createMesh( relationships, geometryMap, materialMap );
break;
case 'NurbsCurve':
model = this.createCurve( relationships, geometryMap );
break;
case 'LimbNode':
case 'Root':
model = new Bone();
break;
case 'Null':
default:
model = new Group();
break;
}
model.name = node.attrName ? PropertyBinding.sanitizeNodeName( node.attrName ) : '';
model.ID = id;
}
this.getTransformData( model, node );
modelMap.set( id, model );
}
return modelMap;
},
buildSkeleton: function ( relationships, skeletons, id, name ) {
var bone = null;
relationships.parents.forEach( function ( parent ) {
for ( var ID in skeletons ) {
var skeleton = skeletons[ ID ];
skeleton.rawBones.forEach( function ( rawBone, i ) {
if ( rawBone.ID === parent.ID ) {
var subBone = bone;
bone = new Bone();
bone.matrixWorld.copy( rawBone.transformLink );
// set name and id here - otherwise in cases where "subBone" is created it will not have a name / id
bone.name = name ? PropertyBinding.sanitizeNodeName( name ) : '';
bone.ID = id;
skeleton.bones[ i ] = bone;
// In cases where a bone is shared between multiple meshes
// duplicate the bone here and and it as a child of the first bone
if ( subBone !== null ) {
bone.add( subBone );
}
}
} );
}
} );
return bone;
},
// create a PerspectiveCamera or OrthographicCamera
createCamera: function ( relationships ) {
var model;
var cameraAttribute;
relationships.children.forEach( function ( child ) {
var attr = fbxTree.Objects.NodeAttribute[ child.ID ];
if ( attr !== undefined ) {
cameraAttribute = attr;
}
} );
if ( cameraAttribute === undefined ) {
model = new Object3D();
} else {
var type = 0;
if ( cameraAttribute.CameraProjectionType !== undefined && cameraAttribute.CameraProjectionType.value === 1 ) {
type = 1;
}
var nearClippingPlane = 1;
if ( cameraAttribute.NearPlane !== undefined ) {
nearClippingPlane = cameraAttribute.NearPlane.value / 1000;
}
var farClippingPlane = 1000;
if ( cameraAttribute.FarPlane !== undefined ) {
farClippingPlane = cameraAttribute.FarPlane.value / 1000;
}
var width = window.innerWidth;
var height = window.innerHeight;
if ( cameraAttribute.AspectWidth !== undefined && cameraAttribute.AspectHeight !== undefined ) {
width = cameraAttribute.AspectWidth.value;
height = cameraAttribute.AspectHeight.value;
}
var aspect = width / height;
var fov = 45;
if ( cameraAttribute.FieldOfView !== undefined ) {
fov = cameraAttribute.FieldOfView.value;
}
var focalLength = cameraAttribute.FocalLength ? cameraAttribute.FocalLength.value : null;
switch ( type ) {
case 0: // Perspective
model = new PerspectiveCamera( fov, aspect, nearClippingPlane, farClippingPlane );
if ( focalLength !== null ) model.setFocalLength( focalLength );
break;
case 1: // Orthographic
model = new OrthographicCamera( - width / 2, width / 2, height / 2, - height / 2, nearClippingPlane, farClippingPlane );
break;
default:
console.warn( 'THREE.FBXLoader: Unknown camera type ' + type + '.' );
model = new Object3D();
break;
}
}
return model;
},
// Create a DirectionalLight, PointLight or SpotLight
createLight: function ( relationships ) {
var model;
var lightAttribute;
relationships.children.forEach( function ( child ) {
var attr = fbxTree.Objects.NodeAttribute[ child.ID ];
if ( attr !== undefined ) {
lightAttribute = attr;
}
} );
if ( lightAttribute === undefined ) {
model = new Object3D();
} else {
var type;
// LightType can be undefined for Point lights
if ( lightAttribute.LightType === undefined ) {
type = 0;
} else {
type = lightAttribute.LightType.value;
}
var color = 0xffffff;
if ( lightAttribute.Color !== undefined ) {
color = new Color().fromArray( lightAttribute.Color.value );
}
var intensity = ( lightAttribute.Intensity === undefined ) ? 1 : lightAttribute.Intensity.value / 100;
// light disabled
if ( lightAttribute.CastLightOnObject !== undefined && lightAttribute.CastLightOnObject.value === 0 ) {
intensity = 0;
}
var distance = 0;
if ( lightAttribute.FarAttenuationEnd !== undefined ) {
if ( lightAttribute.EnableFarAttenuation !== undefined && lightAttribute.EnableFarAttenuation.value === 0 ) {
distance = 0;
} else {
distance = lightAttribute.FarAttenuationEnd.value;
}
}
// TODO: could this be calculated linearly from FarAttenuationStart to FarAttenuationEnd?
var decay = 1;
switch ( type ) {
case 0: // Point
model = new PointLight( color, intensity, distance, decay );
break;
case 1: // Directional
model = new DirectionalLight( color, intensity );
break;
case 2: // Spot
var angle = Math.PI / 3;
if ( lightAttribute.InnerAngle !== undefined ) {
angle = MathUtils.degToRad( lightAttribute.InnerAngle.value );
}
var penumbra = 0;
if ( lightAttribute.OuterAngle !== undefined ) {
// TODO: this is not correct - FBX calculates outer and inner angle in degrees
// with OuterAngle > InnerAngle && OuterAngle <= Math.PI
// while three.js uses a penumbra between (0, 1) to attenuate the inner angle
penumbra = MathUtils.degToRad( lightAttribute.OuterAngle.value );
penumbra = Math.max( penumbra, 1 );
}
model = new SpotLight( color, intensity, distance, angle, penumbra, decay );
break;
default:
console.warn( 'THREE.FBXLoader: Unknown light type ' + lightAttribute.LightType.value + ', defaulting to a PointLight.' );
model = new PointLight( color, intensity );
break;
}
if ( lightAttribute.CastShadows !== undefined && lightAttribute.CastShadows.value === 1 ) {
model.castShadow = true;
}
}
return model;
},
createMesh: function ( relationships, geometryMap, materialMap ) {
var model;
var geometry = null;
var material = null;
var materials = [];
// get geometry and materials(s) from connections
relationships.children.forEach( function ( child ) {
if ( geometryMap.has( child.ID ) ) {
geometry = geometryMap.get( child.ID );
}
if ( materialMap.has( child.ID ) ) {
materials.push( materialMap.get( child.ID ) );
}
} );
if ( materials.length > 1 ) {
material = materials;
} else if ( materials.length > 0 ) {
material = materials[ 0 ];
} else {
material = new MeshPhongMaterial( { color: 0xcccccc } );
materials.push( material );
}
if ( 'color' in geometry.attributes ) {
materials.forEach( function ( material ) {
material.vertexColors = true;
} );
}
if ( geometry.FBX_Deformer ) {
materials.forEach( function ( material ) {
material.skinning = true;
} );
model = new SkinnedMesh( geometry, material );
model.normalizeSkinWeights();
} else {
model = new Mesh( geometry, material );
}
return model;
},
createCurve: function ( relationships, geometryMap ) {
var geometry = relationships.children.reduce( function ( geo, child ) {
if ( geometryMap.has( child.ID ) ) geo = geometryMap.get( child.ID );
return geo;
}, null );
// FBX does not list materials for Nurbs lines, so we'll just put our own in here.
var material = new LineBasicMaterial( { color: 0x3300ff, linewidth: 1 } );
return new Line( geometry, material );
},
// parse the model node for transform data
getTransformData: function ( model, modelNode ) {
var transformData = {};
if ( 'InheritType' in modelNode ) transformData.inheritType = parseInt( modelNode.InheritType.value );
if ( 'RotationOrder' in modelNode ) transformData.eulerOrder = getEulerOrder( modelNode.RotationOrder.value );
else transformData.eulerOrder = 'ZYX';
if ( 'Lcl_Translation' in modelNode ) transformData.translation = modelNode.Lcl_Translation.value;
if ( 'PreRotation' in modelNode ) transformData.preRotation = modelNode.PreRotation.value;
if ( 'Lcl_Rotation' in modelNode ) transformData.rotation = modelNode.Lcl_Rotation.value;
if ( 'PostRotation' in modelNode ) transformData.postRotation = modelNode.PostRotation.value;
if ( 'Lcl_Scaling' in modelNode ) transformData.scale = modelNode.Lcl_Scaling.value;
if ( 'ScalingOffset' in modelNode ) transformData.scalingOffset = modelNode.ScalingOffset.value;
if ( 'ScalingPivot' in modelNode ) transformData.scalingPivot = modelNode.ScalingPivot.value;
if ( 'RotationOffset' in modelNode ) transformData.rotationOffset = modelNode.RotationOffset.value;
if ( 'RotationPivot' in modelNode ) transformData.rotationPivot = modelNode.RotationPivot.value;
model.userData.transformData = transformData;
},
setLookAtProperties: function ( model, modelNode ) {
if ( 'LookAtProperty' in modelNode ) {
var children = connections.get( model.ID ).children;
children.forEach( function ( child ) {
if ( child.relationship === 'LookAtProperty' ) {
var lookAtTarget = fbxTree.Objects.Model[ child.ID ];
if ( 'Lcl_Translation' in lookAtTarget ) {
var pos = lookAtTarget.Lcl_Translation.value;
// DirectionalLight, SpotLight
if ( model.target !== undefined ) {
model.target.position.fromArray( pos );
sceneGraph.add( model.target );
} else { // Cameras and other Object3Ds
model.lookAt( new Vector3().fromArray( pos ) );
}
}
}
} );
}
},
bindSkeleton: function ( skeletons, geometryMap, modelMap ) {
var bindMatrices = this.parsePoseNodes();
for ( var ID in skeletons ) {
var skeleton = skeletons[ ID ];
var parents = connections.get( parseInt( skeleton.ID ) ).parents;
parents.forEach( function ( parent ) {
if ( geometryMap.has( parent.ID ) ) {
var geoID = parent.ID;
var geoRelationships = connections.get( geoID );
geoRelationships.parents.forEach( function ( geoConnParent ) {
if ( modelMap.has( geoConnParent.ID ) ) {
var model = modelMap.get( geoConnParent.ID );
model.bind( new Skeleton( skeleton.bones ), bindMatrices[ geoConnParent.ID ] );
}
} );
}
} );
}
},
parsePoseNodes: function () {
var bindMatrices = {};
if ( 'Pose' in fbxTree.Objects ) {
var BindPoseNode = fbxTree.Objects.Pose;
for ( var nodeID in BindPoseNode ) {
if ( BindPoseNode[ nodeID ].attrType === 'BindPose' ) {
var poseNodes = BindPoseNode[ nodeID ].PoseNode;
if ( Array.isArray( poseNodes ) ) {
poseNodes.forEach( function ( poseNode ) {
bindMatrices[ poseNode.Node ] = new Matrix4().fromArray( poseNode.Matrix.a );
} );
} else {
bindMatrices[ poseNodes.Node ] = new Matrix4().fromArray( poseNodes.Matrix.a );
}
}
}
}
return bindMatrices;
},
// Parse ambient color in FBXTree.GlobalSettings - if it's not set to black (default), create an ambient light
createAmbientLight: function () {
if ( 'GlobalSettings' in fbxTree && 'AmbientColor' in fbxTree.GlobalSettings ) {
var ambientColor = fbxTree.GlobalSettings.AmbientColor.value;
var r = ambientColor[ 0 ];
var g = ambientColor[ 1 ];
var b = ambientColor[ 2 ];
if ( r !== 0 || g !== 0 || b !== 0 ) {
var color = new Color( r, g, b );
sceneGraph.add( new AmbientLight( color, 1 ) );
}
}
},
setupMorphMaterials: function () {
var scope = this;
sceneGraph.traverse( function ( child ) {
if ( child.isMesh ) {
if ( child.geometry.morphAttributes.position && child.geometry.morphAttributes.position.length ) {
if ( Array.isArray( child.material ) ) {
child.material.forEach( function ( material, i ) {
scope.setupMorphMaterial( child, material, i );
} );
} else {
scope.setupMorphMaterial( child, child.material );
}
}
}
} );
},
setupMorphMaterial: function ( child, material, index ) {
var uuid = child.uuid;
var matUuid = material.uuid;
// if a geometry has morph targets, it cannot share the material with other geometries
var sharedMat = false;
sceneGraph.traverse( function ( node ) {
if ( node.isMesh ) {
if ( Array.isArray( node.material ) ) {
node.material.forEach( function ( mat ) {
if ( mat.uuid === matUuid && node.uuid !== uuid ) sharedMat = true;
} );
} else if ( node.material.uuid === matUuid && node.uuid !== uuid ) sharedMat = true;
}
} );
if ( sharedMat === true ) {
var clonedMat = material.clone();
clonedMat.morphTargets = true;
if ( index === undefined ) child.material = clonedMat;
else child.material[ index ] = clonedMat;
} else material.morphTargets = true;
}
};
// parse Geometry data from FBXTree and return map of BufferGeometries
function GeometryParser() {}
GeometryParser.prototype = {
constructor: GeometryParser,
// Parse nodes in FBXTree.Objects.Geometry
parse: function ( deformers ) {
var geometryMap = new Map();
if ( 'Geometry' in fbxTree.Objects ) {
var geoNodes = fbxTree.Objects.Geometry;
for ( var nodeID in geoNodes ) {
var relationships = connections.get( parseInt( nodeID ) );
var geo = this.parseGeometry( relationships, geoNodes[ nodeID ], deformers );
geometryMap.set( parseInt( nodeID ), geo );
}
}
return geometryMap;
},
// Parse single node in FBXTree.Objects.Geometry
parseGeometry: function ( relationships, geoNode, deformers ) {
switch ( geoNode.attrType ) {
case 'Mesh':
return this.parseMeshGeometry( relationships, geoNode, deformers );
break;
case 'NurbsCurve':
return this.parseNurbsGeometry( geoNode );
break;
}
},
// Parse single node mesh geometry in FBXTree.Objects.Geometry
parseMeshGeometry: function ( relationships, geoNode, deformers ) {
var skeletons = deformers.skeletons;
var morphTargets = [];
var modelNodes = relationships.parents.map( function ( parent ) {
return fbxTree.Objects.Model[ parent.ID ];
} );
// don't create geometry if it is not associated with any models
if ( modelNodes.length === 0 ) return;
var skeleton = relationships.children.reduce( function ( skeleton, child ) {
if ( skeletons[ child.ID ] !== undefined ) skeleton = skeletons[ child.ID ];
return skeleton;
}, null );
relationships.children.forEach( function ( child ) {
if ( deformers.morphTargets[ child.ID ] !== undefined ) {
morphTargets.push( deformers.morphTargets[ child.ID ] );
}
} );
// Assume one model and get the preRotation from that
// if there is more than one model associated with the geometry this may cause problems
var modelNode = modelNodes[ 0 ];
var transformData = {};
if ( 'RotationOrder' in modelNode ) transformData.eulerOrder = getEulerOrder( modelNode.RotationOrder.value );
if ( 'InheritType' in modelNode ) transformData.inheritType = parseInt( modelNode.InheritType.value );
if ( 'GeometricTranslation' in modelNode ) transformData.translation = modelNode.GeometricTranslation.value;
if ( 'GeometricRotation' in modelNode ) transformData.rotation = modelNode.GeometricRotation.value;
if ( 'GeometricScaling' in modelNode ) transformData.scale = modelNode.GeometricScaling.value;
var transform = generateTransform( transformData );
return this.genGeometry( geoNode, skeleton, morphTargets, transform );
},
// Generate a BufferGeometry from a node in FBXTree.Objects.Geometry
genGeometry: function ( geoNode, skeleton, morphTargets, preTransform ) {
var geo = new BufferGeometry();
if ( geoNode.attrName ) geo.name = geoNode.attrName;
var geoInfo = this.parseGeoNode( geoNode, skeleton );
var buffers = this.genBuffers( geoInfo );
var positionAttribute = new Float32BufferAttribute( buffers.vertex, 3 );
positionAttribute.applyMatrix4( preTransform );
geo.setAttribute( 'position', positionAttribute );
if ( buffers.colors.length > 0 ) {
geo.setAttribute( 'color', new Float32BufferAttribute( buffers.colors, 3 ) );
}
if ( skeleton ) {
geo.setAttribute( 'skinIndex', new Uint16BufferAttribute( buffers.weightsIndices, 4 ) );
geo.setAttribute( 'skinWeight', new Float32BufferAttribute( buffers.vertexWeights, 4 ) );
// used later to bind the skeleton to the model
geo.FBX_Deformer = skeleton;
}
if ( buffers.normal.length > 0 ) {
var normalMatrix = new Matrix3().getNormalMatrix( preTransform );
var normalAttribute = new Float32BufferAttribute( buffers.normal, 3 );
normalAttribute.applyNormalMatrix( normalMatrix );
geo.setAttribute( 'normal', normalAttribute );
}
buffers.uvs.forEach( function ( uvBuffer, i ) {
// subsequent uv buffers are called 'uv1', 'uv2', ...
var name = 'uv' + ( i + 1 ).toString();
// the first uv buffer is just called 'uv'
if ( i === 0 ) {
name = 'uv';
}
geo.setAttribute( name, new Float32BufferAttribute( buffers.uvs[ i ], 2 ) );
} );
if ( geoInfo.material && geoInfo.material.mappingType !== 'AllSame' ) {
// Convert the material indices of each vertex into rendering groups on the geometry.
var prevMaterialIndex = buffers.materialIndex[ 0 ];
var startIndex = 0;
buffers.materialIndex.forEach( function ( currentIndex, i ) {
if ( currentIndex !== prevMaterialIndex ) {
geo.addGroup( startIndex, i - startIndex, prevMaterialIndex );
prevMaterialIndex = currentIndex;
startIndex = i;
}
} );
// the loop above doesn't add the last group, do that here.
if ( geo.groups.length > 0 ) {
var lastGroup = geo.groups[ geo.groups.length - 1 ];
var lastIndex = lastGroup.start + lastGroup.count;
if ( lastIndex !== buffers.materialIndex.length ) {
geo.addGroup( lastIndex, buffers.materialIndex.length - lastIndex, prevMaterialIndex );
}
}
// case where there are multiple materials but the whole geometry is only
// using one of them
if ( geo.groups.length === 0 ) {
geo.addGroup( 0, buffers.materialIndex.length, buffers.materialIndex[ 0 ] );
}
}
this.addMorphTargets( geo, geoNode, morphTargets, preTransform );
return geo;
},
parseGeoNode: function ( geoNode, skeleton ) {
var geoInfo = {};
geoInfo.vertexPositions = ( geoNode.Vertices !== undefined ) ? geoNode.Vertices.a : [];
geoInfo.vertexIndices = ( geoNode.PolygonVertexIndex !== undefined ) ? geoNode.PolygonVertexIndex.a : [];
if ( geoNode.LayerElementColor ) {
geoInfo.color = this.parseVertexColors( geoNode.LayerElementColor[ 0 ] );
}
if ( geoNode.LayerElementMaterial ) {
geoInfo.material = this.parseMaterialIndices( geoNode.LayerElementMaterial[ 0 ] );
}
if ( geoNode.LayerElementNormal ) {
geoInfo.normal = this.parseNormals( geoNode.LayerElementNormal[ 0 ] );
}
if ( geoNode.LayerElementUV ) {
geoInfo.uv = [];
var i = 0;
while ( geoNode.LayerElementUV[ i ] ) {
geoInfo.uv.push( this.parseUVs( geoNode.LayerElementUV[ i ] ) );
i ++;
}
}
geoInfo.weightTable = {};
if ( skeleton !== null ) {
geoInfo.skeleton = skeleton;
skeleton.rawBones.forEach( function ( rawBone, i ) {
// loop over the bone's vertex indices and weights
rawBone.indices.forEach( function ( index, j ) {
if ( geoInfo.weightTable[ index ] === undefined ) geoInfo.weightTable[ index ] = [];
geoInfo.weightTable[ index ].push( {
id: i,
weight: rawBone.weights[ j ],
} );
} );
} );
}
return geoInfo;
},
genBuffers: function ( geoInfo ) {
var buffers = {
vertex: [],
normal: [],
colors: [],
uvs: [],
materialIndex: [],
vertexWeights: [],
weightsIndices: [],
};
var polygonIndex = 0;
var faceLength = 0;
var displayedWeightsWarning = false;
// these will hold data for a single face
var facePositionIndexes = [];
var faceNormals = [];
var faceColors = [];
var faceUVs = [];
var faceWeights = [];
var faceWeightIndices = [];
var scope = this;
geoInfo.vertexIndices.forEach( function ( vertexIndex, polygonVertexIndex ) {
var endOfFace = false;
// Face index and vertex index arrays are combined in a single array
// A cube with quad faces looks like this:
// PolygonVertexIndex: *24 {
// a: 0, 1, 3, -3, 2, 3, 5, -5, 4, 5, 7, -7, 6, 7, 1, -1, 1, 7, 5, -4, 6, 0, 2, -5
// }
// Negative numbers mark the end of a face - first face here is 0, 1, 3, -3
// to find index of last vertex bit shift the index: ^ - 1
if ( vertexIndex < 0 ) {
vertexIndex = vertexIndex ^ - 1; // equivalent to ( x * -1 ) - 1
endOfFace = true;
}
var weightIndices = [];
var weights = [];
facePositionIndexes.push( vertexIndex * 3, vertexIndex * 3 + 1, vertexIndex * 3 + 2 );
if ( geoInfo.color ) {
var data = getData( polygonVertexIndex, polygonIndex, vertexIndex, geoInfo.color );
faceColors.push( data[ 0 ], data[ 1 ], data[ 2 ] );
}
if ( geoInfo.skeleton ) {
if ( geoInfo.weightTable[ vertexIndex ] !== undefined ) {
geoInfo.weightTable[ vertexIndex ].forEach( function ( wt ) {
weights.push( wt.weight );
weightIndices.push( wt.id );
} );
}
if ( weights.length > 4 ) {
if ( ! displayedWeightsWarning ) {
console.warn( 'THREE.FBXLoader: Vertex has more than 4 skinning weights assigned to vertex. Deleting additional weights.' );
displayedWeightsWarning = true;
}
var wIndex = [ 0, 0, 0, 0 ];
var Weight = [ 0, 0, 0, 0 ];
weights.forEach( function ( weight, weightIndex ) {
var currentWeight = weight;
var currentIndex = weightIndices[ weightIndex ];
Weight.forEach( function ( comparedWeight, comparedWeightIndex, comparedWeightArray ) {
if ( currentWeight > comparedWeight ) {
comparedWeightArray[ comparedWeightIndex ] = currentWeight;
currentWeight = comparedWeight;
var tmp = wIndex[ comparedWeightIndex ];
wIndex[ comparedWeightIndex ] = currentIndex;
currentIndex = tmp;
}
} );
} );
weightIndices = wIndex;
weights = Weight;
}
// if the weight array is shorter than 4 pad with 0s
while ( weights.length < 4 ) {
weights.push( 0 );
weightIndices.push( 0 );
}
for ( var i = 0; i < 4; ++ i ) {
faceWeights.push( weights[ i ] );
faceWeightIndices.push( weightIndices[ i ] );
}
}
if ( geoInfo.normal ) {
var data = getData( polygonVertexIndex, polygonIndex, vertexIndex, geoInfo.normal );
faceNormals.push( data[ 0 ], data[ 1 ], data[ 2 ] );
}
if ( geoInfo.material && geoInfo.material.mappingType !== 'AllSame' ) {
var materialIndex = getData( polygonVertexIndex, polygonIndex, vertexIndex, geoInfo.material )[ 0 ];
}
if ( geoInfo.uv ) {
geoInfo.uv.forEach( function ( uv, i ) {
var data = getData( polygonVertexIndex, polygonIndex, vertexIndex, uv );
if ( faceUVs[ i ] === undefined ) {
faceUVs[ i ] = [];
}
faceUVs[ i ].push( data[ 0 ] );
faceUVs[ i ].push( data[ 1 ] );
} );
}
faceLength ++;
if ( endOfFace ) {
scope.genFace( buffers, geoInfo, facePositionIndexes, materialIndex, faceNormals, faceColors, faceUVs, faceWeights, faceWeightIndices, faceLength );
polygonIndex ++;
faceLength = 0;
// reset arrays for the next face
facePositionIndexes = [];
faceNormals = [];
faceColors = [];
faceUVs = [];
faceWeights = [];
faceWeightIndices = [];
}
} );
return buffers;
},
// Generate data for a single face in a geometry. If the face is a quad then split it into 2 tris
genFace: function ( buffers, geoInfo, facePositionIndexes, materialIndex, faceNormals, faceColors, faceUVs, faceWeights, faceWeightIndices, faceLength ) {
for ( var i = 2; i < faceLength; i ++ ) {
buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ 0 ] ] );
buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ 1 ] ] );
buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ 2 ] ] );
buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ ( i - 1 ) * 3 ] ] );
buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ ( i - 1 ) * 3 + 1 ] ] );
buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ ( i - 1 ) * 3 + 2 ] ] );
buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ i * 3 ] ] );
buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ i * 3 + 1 ] ] );
buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ i * 3 + 2 ] ] );
if ( geoInfo.skeleton ) {
buffers.vertexWeights.push( faceWeights[ 0 ] );
buffers.vertexWeights.push( faceWeights[ 1 ] );
buffers.vertexWeights.push( faceWeights[ 2 ] );
buffers.vertexWeights.push( faceWeights[ 3 ] );
buffers.vertexWeights.push( faceWeights[ ( i - 1 ) * 4 ] );
buffers.vertexWeights.push( faceWeights[ ( i - 1 ) * 4 + 1 ] );
buffers.vertexWeights.push( faceWeights[ ( i - 1 ) * 4 + 2 ] );
buffers.vertexWeights.push( faceWeights[ ( i - 1 ) * 4 + 3 ] );
buffers.vertexWeights.push( faceWeights[ i * 4 ] );
buffers.vertexWeights.push( faceWeights[ i * 4 + 1 ] );
buffers.vertexWeights.push( faceWeights[ i * 4 + 2 ] );
buffers.vertexWeights.push( faceWeights[ i * 4 + 3 ] );
buffers.weightsIndices.push( faceWeightIndices[ 0 ] );
buffers.weightsIndices.push( faceWeightIndices[ 1 ] );
buffers.weightsIndices.push( faceWeightIndices[ 2 ] );
buffers.weightsIndices.push( faceWeightIndices[ 3 ] );
buffers.weightsIndices.push( faceWeightIndices[ ( i - 1 ) * 4 ] );
buffers.weightsIndices.push( faceWeightIndices[ ( i - 1 ) * 4 + 1 ] );
buffers.weightsIndices.push( faceWeightIndices[ ( i - 1 ) * 4 + 2 ] );
buffers.weightsIndices.push( faceWeightIndices[ ( i - 1 ) * 4 + 3 ] );
buffers.weightsIndices.push( faceWeightIndices[ i * 4 ] );
buffers.weightsIndices.push( faceWeightIndices[ i * 4 + 1 ] );
buffers.weightsIndices.push( faceWeightIndices[ i * 4 + 2 ] );
buffers.weightsIndices.push( faceWeightIndices[ i * 4 + 3 ] );
}
if ( geoInfo.color ) {
buffers.colors.push( faceColors[ 0 ] );
buffers.colors.push( faceColors[ 1 ] );
buffers.colors.push( faceColors[ 2 ] );
buffers.colors.push( faceColors[ ( i - 1 ) * 3 ] );
buffers.colors.push( faceColors[ ( i - 1 ) * 3 + 1 ] );
buffers.colors.push( faceColors[ ( i - 1 ) * 3 + 2 ] );
buffers.colors.push( faceColors[ i * 3 ] );
buffers.colors.push( faceColors[ i * 3 + 1 ] );
buffers.colors.push( faceColors[ i * 3 + 2 ] );
}
if ( geoInfo.material && geoInfo.material.mappingType !== 'AllSame' ) {
buffers.materialIndex.push( materialIndex );
buffers.materialIndex.push( materialIndex );
buffers.materialIndex.push( materialIndex );
}
if ( geoInfo.normal ) {
buffers.normal.push( faceNormals[ 0 ] );
buffers.normal.push( faceNormals[ 1 ] );
buffers.normal.push( faceNormals[ 2 ] );
buffers.normal.push( faceNormals[ ( i - 1 ) * 3 ] );
buffers.normal.push( faceNormals[ ( i - 1 ) * 3 + 1 ] );
buffers.normal.push( faceNormals[ ( i - 1 ) * 3 + 2 ] );
buffers.normal.push( faceNormals[ i * 3 ] );
buffers.normal.push( faceNormals[ i * 3 + 1 ] );
buffers.normal.push( faceNormals[ i * 3 + 2 ] );
}
if ( geoInfo.uv ) {
geoInfo.uv.forEach( function ( uv, j ) {
if ( buffers.uvs[ j ] === undefined ) buffers.uvs[ j ] = [];
buffers.uvs[ j ].push( faceUVs[ j ][ 0 ] );
buffers.uvs[ j ].push( faceUVs[ j ][ 1 ] );
buffers.uvs[ j ].push( faceUVs[ j ][ ( i - 1 ) * 2 ] );
buffers.uvs[ j ].push( faceUVs[ j ][ ( i - 1 ) * 2 + 1 ] );
buffers.uvs[ j ].push( faceUVs[ j ][ i * 2 ] );
buffers.uvs[ j ].push( faceUVs[ j ][ i * 2 + 1 ] );
} );
}
}
},
addMorphTargets: function ( parentGeo, parentGeoNode, morphTargets, preTransform ) {
if ( morphTargets.length === 0 ) return;
parentGeo.morphTargetsRelative = true;
parentGeo.morphAttributes.position = [];
// parentGeo.morphAttributes.normal = []; // not implemented
var scope = this;
morphTargets.forEach( function ( morphTarget ) {
morphTarget.rawTargets.forEach( function ( rawTarget ) {
var morphGeoNode = fbxTree.Objects.Geometry[ rawTarget.geoID ];
if ( morphGeoNode !== undefined ) {
scope.genMorphGeometry( parentGeo, parentGeoNode, morphGeoNode, preTransform, rawTarget.name );
}
} );
} );
},
// a morph geometry node is similar to a standard node, and the node is also contained
// in FBXTree.Objects.Geometry, however it can only have attributes for position, normal
// and a special attribute Index defining which vertices of the original geometry are affected
// Normal and position attributes only have data for the vertices that are affected by the morph
genMorphGeometry: function ( parentGeo, parentGeoNode, morphGeoNode, preTransform, name ) {
var vertexIndices = ( parentGeoNode.PolygonVertexIndex !== undefined ) ? parentGeoNode.PolygonVertexIndex.a : [];
var morphPositionsSparse = ( morphGeoNode.Vertices !== undefined ) ? morphGeoNode.Vertices.a : [];
var indices = ( morphGeoNode.Indexes !== undefined ) ? morphGeoNode.Indexes.a : [];
var length = parentGeo.attributes.position.count * 3;
var morphPositions = new Float32Array( length );
for ( var i = 0; i < indices.length; i ++ ) {
var morphIndex = indices[ i ] * 3;
morphPositions[ morphIndex ] = morphPositionsSparse[ i * 3 ];
morphPositions[ morphIndex + 1 ] = morphPositionsSparse[ i * 3 + 1 ];
morphPositions[ morphIndex + 2 ] = morphPositionsSparse[ i * 3 + 2 ];
}
// TODO: add morph normal support
var morphGeoInfo = {
vertexIndices: vertexIndices,
vertexPositions: morphPositions,
};
var morphBuffers = this.genBuffers( morphGeoInfo );
var positionAttribute = new Float32BufferAttribute( morphBuffers.vertex, 3 );
positionAttribute.name = name || morphGeoNode.attrName;
positionAttribute.applyMatrix4( preTransform );
parentGeo.morphAttributes.position.push( positionAttribute );
},
// Parse normal from FBXTree.Objects.Geometry.LayerElementNormal if it exists
parseNormals: function ( NormalNode ) {
var mappingType = NormalNode.MappingInformationType;
var referenceType = NormalNode.ReferenceInformationType;
var buffer = NormalNode.Normals.a;
var indexBuffer = [];
if ( referenceType === 'IndexToDirect' ) {
if ( 'NormalIndex' in NormalNode ) {
indexBuffer = NormalNode.NormalIndex.a;
} else if ( 'NormalsIndex' in NormalNode ) {
indexBuffer = NormalNode.NormalsIndex.a;
}
}
return {
dataSize: 3,
buffer: buffer,
indices: indexBuffer,
mappingType: mappingType,
referenceType: referenceType
};
},
// Parse UVs from FBXTree.Objects.Geometry.LayerElementUV if it exists
parseUVs: function ( UVNode ) {
var mappingType = UVNode.MappingInformationType;
var referenceType = UVNode.ReferenceInformationType;
var buffer = UVNode.UV.a;
var indexBuffer = [];
if ( referenceType === 'IndexToDirect' ) {
indexBuffer = UVNode.UVIndex.a;
}
return {
dataSize: 2,
buffer: buffer,
indices: indexBuffer,
mappingType: mappingType,
referenceType: referenceType
};
},
// Parse Vertex Colors from FBXTree.Objects.Geometry.LayerElementColor if it exists
parseVertexColors: function ( ColorNode ) {
var mappingType = ColorNode.MappingInformationType;
var referenceType = ColorNode.ReferenceInformationType;
var buffer = ColorNode.Colors.a;
var indexBuffer = [];
if ( referenceType === 'IndexToDirect' ) {
indexBuffer = ColorNode.ColorIndex.a;
}
return {
dataSize: 4,
buffer: buffer,
indices: indexBuffer,
mappingType: mappingType,
referenceType: referenceType
};
},
// Parse mapping and material data in FBXTree.Objects.Geometry.LayerElementMaterial if it exists
parseMaterialIndices: function ( MaterialNode ) {
var mappingType = MaterialNode.MappingInformationType;
var referenceType = MaterialNode.ReferenceInformationType;
if ( mappingType === 'NoMappingInformation' ) {
return {
dataSize: 1,
buffer: [ 0 ],
indices: [ 0 ],
mappingType: 'AllSame',
referenceType: referenceType
};
}
var materialIndexBuffer = MaterialNode.Materials.a;
// Since materials are stored as indices, there's a bit of a mismatch between FBX and what
// we expect.So we create an intermediate buffer that points to the index in the buffer,
// for conforming with the other functions we've written for other data.
var materialIndices = [];
for ( var i = 0; i < materialIndexBuffer.length; ++ i ) {
materialIndices.push( i );
}
return {
dataSize: 1,
buffer: materialIndexBuffer,
indices: materialIndices,
mappingType: mappingType,
referenceType: referenceType
};
},
// Generate a NurbGeometry from a node in FBXTree.Objects.Geometry
parseNurbsGeometry: function ( geoNode ) {
if ( NURBSCurve === undefined ) {
console.error( 'THREE.FBXLoader: The loader relies on NURBSCurve for any nurbs present in the model. Nurbs will show up as empty geometry.' );
return new BufferGeometry();
}
var order = parseInt( geoNode.Order );
if ( isNaN( order ) ) {
console.error( 'THREE.FBXLoader: Invalid Order %s given for geometry ID: %s', geoNode.Order, geoNode.id );
return new BufferGeometry();
}
var degree = order - 1;
var knots = geoNode.KnotVector.a;
var controlPoints = [];
var pointsValues = geoNode.Points.a;
for ( var i = 0, l = pointsValues.length; i < l; i += 4 ) {
controlPoints.push( new Vector4().fromArray( pointsValues, i ) );
}
var startKnot, endKnot;
if ( geoNode.Form === 'Closed' ) {
controlPoints.push( controlPoints[ 0 ] );
} else if ( geoNode.Form === 'Periodic' ) {
startKnot = degree;
endKnot = knots.length - 1 - startKnot;
for ( var i = 0; i < degree; ++ i ) {
controlPoints.push( controlPoints[ i ] );
}
}
var curve = new NURBSCurve( degree, knots, controlPoints, startKnot, endKnot );
var vertices = curve.getPoints( controlPoints.length * 7 );
var positions = new Float32Array( vertices.length * 3 );
vertices.forEach( function ( vertex, i ) {
vertex.toArray( positions, i * 3 );
} );
var geometry = new BufferGeometry();
geometry.setAttribute( 'position', new BufferAttribute( positions, 3 ) );
return geometry;
},
};
// parse animation data from FBXTree
function AnimationParser() {}
AnimationParser.prototype = {
constructor: AnimationParser,
// take raw animation clips and turn them into three.js animation clips
parse: function () {
var animationClips = [];
var rawClips = this.parseClips();
if ( rawClips !== undefined ) {
for ( var key in rawClips ) {
var rawClip = rawClips[ key ];
var clip = this.addClip( rawClip );
animationClips.push( clip );
}
}
return animationClips;
},
parseClips: function () {
// since the actual transformation data is stored in FBXTree.Objects.AnimationCurve,
// if this is undefined we can safely assume there are no animations
if ( fbxTree.Objects.AnimationCurve === undefined ) return undefined;
var curveNodesMap = this.parseAnimationCurveNodes();
this.parseAnimationCurves( curveNodesMap );
var layersMap = this.parseAnimationLayers( curveNodesMap );
var rawClips = this.parseAnimStacks( layersMap );
return rawClips;
},
// parse nodes in FBXTree.Objects.AnimationCurveNode
// each AnimationCurveNode holds data for an animation transform for a model (e.g. left arm rotation )
// and is referenced by an AnimationLayer
parseAnimationCurveNodes: function () {
var rawCurveNodes = fbxTree.Objects.AnimationCurveNode;
var curveNodesMap = new Map();
for ( var nodeID in rawCurveNodes ) {
var rawCurveNode = rawCurveNodes[ nodeID ];
if ( rawCurveNode.attrName.match( /S|R|T|DeformPercent/ ) !== null ) {
var curveNode = {
id: rawCurveNode.id,
attr: rawCurveNode.attrName,
curves: {},
};
curveNodesMap.set( curveNode.id, curveNode );
}
}
return curveNodesMap;
},
// parse nodes in FBXTree.Objects.AnimationCurve and connect them up to
// previously parsed AnimationCurveNodes. Each AnimationCurve holds data for a single animated
// axis ( e.g. times and values of x rotation)
parseAnimationCurves: function ( curveNodesMap ) {
var rawCurves = fbxTree.Objects.AnimationCurve;
// TODO: Many values are identical up to roundoff error, but won't be optimised
// e.g. position times: [0, 0.4, 0. 8]
// position values: [7.23538335023477e-7, 93.67518615722656, -0.9982695579528809, 7.23538335023477e-7, 93.67518615722656, -0.9982695579528809, 7.235384487103147e-7, 93.67520904541016, -0.9982695579528809]
// clearly, this should be optimised to
// times: [0], positions [7.23538335023477e-7, 93.67518615722656, -0.9982695579528809]
// this shows up in nearly every FBX file, and generally time array is length > 100
for ( var nodeID in rawCurves ) {
var animationCurve = {
id: rawCurves[ nodeID ].id,
times: rawCurves[ nodeID ].KeyTime.a.map( convertFBXTimeToSeconds ),
values: rawCurves[ nodeID ].KeyValueFloat.a,
};
var relationships = connections.get( animationCurve.id );
if ( relationships !== undefined ) {
var animationCurveID = relationships.parents[ 0 ].ID;
var animationCurveRelationship = relationships.parents[ 0 ].relationship;
if ( animationCurveRelationship.match( /X/ ) ) {
curveNodesMap.get( animationCurveID ).curves[ 'x' ] = animationCurve;
} else if ( animationCurveRelationship.match( /Y/ ) ) {
curveNodesMap.get( animationCurveID ).curves[ 'y' ] = animationCurve;
} else if ( animationCurveRelationship.match( /Z/ ) ) {
curveNodesMap.get( animationCurveID ).curves[ 'z' ] = animationCurve;
} else if ( animationCurveRelationship.match( /d|DeformPercent/ ) && curveNodesMap.has( animationCurveID ) ) {
curveNodesMap.get( animationCurveID ).curves[ 'morph' ] = animationCurve;
}
}
}
},
// parse nodes in FBXTree.Objects.AnimationLayer. Each layers holds references
// to various AnimationCurveNodes and is referenced by an AnimationStack node
// note: theoretically a stack can have multiple layers, however in practice there always seems to be one per stack
parseAnimationLayers: function ( curveNodesMap ) {
var rawLayers = fbxTree.Objects.AnimationLayer;
var layersMap = new Map();
for ( var nodeID in rawLayers ) {
var layerCurveNodes = [];
var connection = connections.get( parseInt( nodeID ) );
if ( connection !== undefined ) {
// all the animationCurveNodes used in the layer
var children = connection.children;
children.forEach( function ( child, i ) {
if ( curveNodesMap.has( child.ID ) ) {
var curveNode = curveNodesMap.get( child.ID );
// check that the curves are defined for at least one axis, otherwise ignore the curveNode
if ( curveNode.curves.x !== undefined || curveNode.curves.y !== undefined || curveNode.curves.z !== undefined ) {
if ( layerCurveNodes[ i ] === undefined ) {
var modelID = connections.get( child.ID ).parents.filter( function ( parent ) {
return parent.relationship !== undefined;
} )[ 0 ].ID;
if ( modelID !== undefined ) {
var rawModel = fbxTree.Objects.Model[ modelID.toString() ];
var node = {
modelName: rawModel.attrName ? PropertyBinding.sanitizeNodeName( rawModel.attrName ) : '',
ID: rawModel.id,
initialPosition: [ 0, 0, 0 ],
initialRotation: [ 0, 0, 0 ],
initialScale: [ 1, 1, 1 ],
};
sceneGraph.traverse( function ( child ) {
if ( child.ID === rawModel.id ) {
node.transform = child.matrix;
if ( child.userData.transformData ) node.eulerOrder = child.userData.transformData.eulerOrder;
}
} );
if ( ! node.transform ) node.transform = new Matrix4();
// if the animated model is pre rotated, we'll have to apply the pre rotations to every
// animation value as well
if ( 'PreRotation' in rawModel ) node.preRotation = rawModel.PreRotation.value;
if ( 'PostRotation' in rawModel ) node.postRotation = rawModel.PostRotation.value;
layerCurveNodes[ i ] = node;
}
}
if ( layerCurveNodes[ i ] ) layerCurveNodes[ i ][ curveNode.attr ] = curveNode;
} else if ( curveNode.curves.morph !== undefined ) {
if ( layerCurveNodes[ i ] === undefined ) {
var deformerID = connections.get( child.ID ).parents.filter( function ( parent ) {
return parent.relationship !== undefined;
} )[ 0 ].ID;
var morpherID = connections.get( deformerID ).parents[ 0 ].ID;
var geoID = connections.get( morpherID ).parents[ 0 ].ID;
// assuming geometry is not used in more than one model
var modelID = connections.get( geoID ).parents[ 0 ].ID;
var rawModel = fbxTree.Objects.Model[ modelID ];
var node = {
modelName: rawModel.attrName ? PropertyBinding.sanitizeNodeName( rawModel.attrName ) : '',
morphName: fbxTree.Objects.Deformer[ deformerID ].attrName,
};
layerCurveNodes[ i ] = node;
}
layerCurveNodes[ i ][ curveNode.attr ] = curveNode;
}
}
} );
layersMap.set( parseInt( nodeID ), layerCurveNodes );
}
}
return layersMap;
},
// parse nodes in FBXTree.Objects.AnimationStack. These are the top level node in the animation
// hierarchy. Each Stack node will be used to create a AnimationClip
parseAnimStacks: function ( layersMap ) {
var rawStacks = fbxTree.Objects.AnimationStack;
// connect the stacks (clips) up to the layers
var rawClips = {};
for ( var nodeID in rawStacks ) {
var children = connections.get( parseInt( nodeID ) ).children;
if ( children.length > 1 ) {
// it seems like stacks will always be associated with a single layer. But just in case there are files
// where there are multiple layers per stack, we'll display a warning
console.warn( 'THREE.FBXLoader: Encountered an animation stack with multiple layers, this is currently not supported. Ignoring subsequent layers.' );
}
var layer = layersMap.get( children[ 0 ].ID );
rawClips[ nodeID ] = {
name: rawStacks[ nodeID ].attrName,
layer: layer,
};
}
return rawClips;
},
addClip: function ( rawClip ) {
var tracks = [];
var scope = this;
rawClip.layer.forEach( function ( rawTracks ) {
tracks = tracks.concat( scope.generateTracks( rawTracks ) );
} );
return new AnimationClip( rawClip.name, - 1, tracks );
},
generateTracks: function ( rawTracks ) {
var tracks = [];
var initialPosition = new Vector3();
var initialRotation = new Quaternion();
var initialScale = new Vector3();
if ( rawTracks.transform ) rawTracks.transform.decompose( initialPosition, initialRotation, initialScale );
initialPosition = initialPosition.toArray();
initialRotation = new Euler().setFromQuaternion( initialRotation, rawTracks.eulerOrder ).toArray();
initialScale = initialScale.toArray();
if ( rawTracks.T !== undefined && Object.keys( rawTracks.T.curves ).length > 0 ) {
var positionTrack = this.generateVectorTrack( rawTracks.modelName, rawTracks.T.curves, initialPosition, 'position' );
if ( positionTrack !== undefined ) tracks.push( positionTrack );
}
if ( rawTracks.R !== undefined && Object.keys( rawTracks.R.curves ).length > 0 ) {
var rotationTrack = this.generateRotationTrack( rawTracks.modelName, rawTracks.R.curves, initialRotation, rawTracks.preRotation, rawTracks.postRotation, rawTracks.eulerOrder );
if ( rotationTrack !== undefined ) tracks.push( rotationTrack );
}
if ( rawTracks.S !== undefined && Object.keys( rawTracks.S.curves ).length > 0 ) {
var scaleTrack = this.generateVectorTrack( rawTracks.modelName, rawTracks.S.curves, initialScale, 'scale' );
if ( scaleTrack !== undefined ) tracks.push( scaleTrack );
}
if ( rawTracks.DeformPercent !== undefined ) {
var morphTrack = this.generateMorphTrack( rawTracks );
if ( morphTrack !== undefined ) tracks.push( morphTrack );
}
return tracks;
},
generateVectorTrack: function ( modelName, curves, initialValue, type ) {
var times = this.getTimesForAllAxes( curves );
var values = this.getKeyframeTrackValues( times, curves, initialValue );
return new VectorKeyframeTrack( modelName + '.' + type, times, values );
},
generateRotationTrack: function ( modelName, curves, initialValue, preRotation, postRotation, eulerOrder ) {
if ( curves.x !== undefined ) {
this.interpolateRotations( curves.x );
curves.x.values = curves.x.values.map( MathUtils.degToRad );
}
if ( curves.y !== undefined ) {
this.interpolateRotations( curves.y );
curves.y.values = curves.y.values.map( MathUtils.degToRad );
}
if ( curves.z !== undefined ) {
this.interpolateRotations( curves.z );
curves.z.values = curves.z.values.map( MathUtils.degToRad );
}
var times = this.getTimesForAllAxes( curves );
var values = this.getKeyframeTrackValues( times, curves, initialValue );
if ( preRotation !== undefined ) {
preRotation = preRotation.map( MathUtils.degToRad );
preRotation.push( eulerOrder );
preRotation = new Euler().fromArray( preRotation );
preRotation = new Quaternion().setFromEuler( preRotation );
}
if ( postRotation !== undefined ) {
postRotation = postRotation.map( MathUtils.degToRad );
postRotation.push( eulerOrder );
postRotation = new Euler().fromArray( postRotation );
postRotation = new Quaternion().setFromEuler( postRotation ).inverse();
}
var quaternion = new Quaternion();
var euler = new Euler();
var quaternionValues = [];
for ( var i = 0; i < values.length; i += 3 ) {
euler.set( values[ i ], values[ i + 1 ], values[ i + 2 ], eulerOrder );
quaternion.setFromEuler( euler );
if ( preRotation !== undefined ) quaternion.premultiply( preRotation );
if ( postRotation !== undefined ) quaternion.multiply( postRotation );
quaternion.toArray( quaternionValues, ( i / 3 ) * 4 );
}
return new QuaternionKeyframeTrack( modelName + '.quaternion', times, quaternionValues );
},
generateMorphTrack: function ( rawTracks ) {
var curves = rawTracks.DeformPercent.curves.morph;
var values = curves.values.map( function ( val ) {
return val / 100;
} );
var morphNum = sceneGraph.getObjectByName( rawTracks.modelName ).morphTargetDictionary[ rawTracks.morphName ];
return new NumberKeyframeTrack( rawTracks.modelName + '.morphTargetInfluences[' + morphNum + ']', curves.times, values );
},
// For all animated objects, times are defined separately for each axis
// Here we'll combine the times into one sorted array without duplicates
getTimesForAllAxes: function ( curves ) {
var times = [];
// first join together the times for each axis, if defined
if ( curves.x !== undefined ) times = times.concat( curves.x.times );
if ( curves.y !== undefined ) times = times.concat( curves.y.times );
if ( curves.z !== undefined ) times = times.concat( curves.z.times );
// then sort them and remove duplicates
times = times.sort( function ( a, b ) {
return a - b;
} ).filter( function ( elem, index, array ) {
return array.indexOf( elem ) == index;
} );
return times;
},
getKeyframeTrackValues: function ( times, curves, initialValue ) {
var prevValue = initialValue;
var values = [];
var xIndex = - 1;
var yIndex = - 1;
var zIndex = - 1;
times.forEach( function ( time ) {
if ( curves.x ) xIndex = curves.x.times.indexOf( time );
if ( curves.y ) yIndex = curves.y.times.indexOf( time );
if ( curves.z ) zIndex = curves.z.times.indexOf( time );
// if there is an x value defined for this frame, use that
if ( xIndex !== - 1 ) {
var xValue = curves.x.values[ xIndex ];
values.push( xValue );
prevValue[ 0 ] = xValue;
} else {
// otherwise use the x value from the previous frame
values.push( prevValue[ 0 ] );
}
if ( yIndex !== - 1 ) {
var yValue = curves.y.values[ yIndex ];
values.push( yValue );
prevValue[ 1 ] = yValue;
} else {
values.push( prevValue[ 1 ] );
}
if ( zIndex !== - 1 ) {
var zValue = curves.z.values[ zIndex ];
values.push( zValue );
prevValue[ 2 ] = zValue;
} else {
values.push( prevValue[ 2 ] );
}
} );
return values;
},
// Rotations are defined as Euler angles which can have values of any size
// These will be converted to quaternions which don't support values greater than
// PI, so we'll interpolate large rotations
interpolateRotations: function ( curve ) {
for ( var i = 1; i < curve.values.length; i ++ ) {
var initialValue = curve.values[ i - 1 ];
var valuesSpan = curve.values[ i ] - initialValue;
var absoluteSpan = Math.abs( valuesSpan );
if ( absoluteSpan >= 180 ) {
var numSubIntervals = absoluteSpan / 180;
var step = valuesSpan / numSubIntervals;
var nextValue = initialValue + step;
var initialTime = curve.times[ i - 1 ];
var timeSpan = curve.times[ i ] - initialTime;
var interval = timeSpan / numSubIntervals;
var nextTime = initialTime + interval;
var interpolatedTimes = [];
var interpolatedValues = [];
while ( nextTime < curve.times[ i ] ) {
interpolatedTimes.push( nextTime );
nextTime += interval;
interpolatedValues.push( nextValue );
nextValue += step;
}
curve.times = inject( curve.times, i, interpolatedTimes );
curve.values = inject( curve.values, i, interpolatedValues );
}
}
},
};
// parse an FBX file in ASCII format
function TextParser() {}
TextParser.prototype = {
constructor: TextParser,
getPrevNode: function () {
return this.nodeStack[ this.currentIndent - 2 ];
},
getCurrentNode: function () {
return this.nodeStack[ this.currentIndent - 1 ];
},
getCurrentProp: function () {
return this.currentProp;
},
pushStack: function ( node ) {
this.nodeStack.push( node );
this.currentIndent += 1;
},
popStack: function () {
this.nodeStack.pop();
this.currentIndent -= 1;
},
setCurrentProp: function ( val, name ) {
this.currentProp = val;
this.currentPropName = name;
},
parse: function ( text ) {
this.currentIndent = 0;
this.allNodes = new FBXTree();
this.nodeStack = [];
this.currentProp = [];
this.currentPropName = '';
var scope = this;
var split = text.split( /[\r\n]+/ );
split.forEach( function ( line, i ) {
var matchComment = line.match( /^[\s\t]*;/ );
var matchEmpty = line.match( /^[\s\t]*$/ );
if ( matchComment || matchEmpty ) return;
var matchBeginning = line.match( '^\\t{' + scope.currentIndent + '}(\\w+):(.*){', '' );
var matchProperty = line.match( '^\\t{' + ( scope.currentIndent ) + '}(\\w+):[\\s\\t\\r\\n](.*)' );
var matchEnd = line.match( '^\\t{' + ( scope.currentIndent - 1 ) + '}}' );
if ( matchBeginning ) {
scope.parseNodeBegin( line, matchBeginning );
} else if ( matchProperty ) {
scope.parseNodeProperty( line, matchProperty, split[ ++ i ] );
} else if ( matchEnd ) {
scope.popStack();
} else if ( line.match( /^[^\s\t}]/ ) ) {
// large arrays are split over multiple lines terminated with a ',' character
// if this is encountered the line needs to be joined to the previous line
scope.parseNodePropertyContinued( line );
}
} );
return this.allNodes;
},
parseNodeBegin: function ( line, property ) {
var nodeName = property[ 1 ].trim().replace( /^"/, '' ).replace( /"$/, '' );
var nodeAttrs = property[ 2 ].split( ',' ).map( function ( attr ) {
return attr.trim().replace( /^"/, '' ).replace( /"$/, '' );
} );
var node = { name: nodeName };
var attrs = this.parseNodeAttr( nodeAttrs );
var currentNode = this.getCurrentNode();
// a top node
if ( this.currentIndent === 0 ) {
this.allNodes.add( nodeName, node );
} else { // a subnode
// if the subnode already exists, append it
if ( nodeName in currentNode ) {
// special case Pose needs PoseNodes as an array
if ( nodeName === 'PoseNode' ) {
currentNode.PoseNode.push( node );
} else if ( currentNode[ nodeName ].id !== undefined ) {
currentNode[ nodeName ] = {};
currentNode[ nodeName ][ currentNode[ nodeName ].id ] = currentNode[ nodeName ];
}
if ( attrs.id !== '' ) currentNode[ nodeName ][ attrs.id ] = node;
} else if ( typeof attrs.id === 'number' ) {
currentNode[ nodeName ] = {};
currentNode[ nodeName ][ attrs.id ] = node;
} else if ( nodeName !== 'Properties70' ) {
if ( nodeName === 'PoseNode' ) currentNode[ nodeName ] = [ node ];
else currentNode[ nodeName ] = node;
}
}
if ( typeof attrs.id === 'number' ) node.id = attrs.id;
if ( attrs.name !== '' ) node.attrName = attrs.name;
if ( attrs.type !== '' ) node.attrType = attrs.type;
this.pushStack( node );
},
parseNodeAttr: function ( attrs ) {
var id = attrs[ 0 ];
if ( attrs[ 0 ] !== '' ) {
id = parseInt( attrs[ 0 ] );
if ( isNaN( id ) ) {
id = attrs[ 0 ];
}
}
var name = '', type = '';
if ( attrs.length > 1 ) {
name = attrs[ 1 ].replace( /^(\w+)::/, '' );
type = attrs[ 2 ];
}
return { id: id, name: name, type: type };
},
parseNodeProperty: function ( line, property, contentLine ) {
var propName = property[ 1 ].replace( /^"/, '' ).replace( /"$/, '' ).trim();
var propValue = property[ 2 ].replace( /^"/, '' ).replace( /"$/, '' ).trim();
// for special case: base64 image data follows "Content: ," line
// Content: ,
// "/9j/4RDaRXhpZgAATU0A..."
if ( propName === 'Content' && propValue === ',' ) {
propValue = contentLine.replace( /"/g, '' ).replace( /,$/, '' ).trim();
}
var currentNode = this.getCurrentNode();
var parentName = currentNode.name;
if ( parentName === 'Properties70' ) {
this.parseNodeSpecialProperty( line, propName, propValue );
return;
}
// Connections
if ( propName === 'C' ) {
var connProps = propValue.split( ',' ).slice( 1 );
var from = parseInt( connProps[ 0 ] );
var to = parseInt( connProps[ 1 ] );
var rest = propValue.split( ',' ).slice( 3 );
rest = rest.map( function ( elem ) {
return elem.trim().replace( /^"/, '' );
} );
propName = 'connections';
propValue = [ from, to ];
append( propValue, rest );
if ( currentNode[ propName ] === undefined ) {
currentNode[ propName ] = [];
}
}
// Node
if ( propName === 'Node' ) currentNode.id = propValue;
// connections
if ( propName in currentNode && Array.isArray( currentNode[ propName ] ) ) {
currentNode[ propName ].push( propValue );
} else {
if ( propName !== 'a' ) currentNode[ propName ] = propValue;
else currentNode.a = propValue;
}
this.setCurrentProp( currentNode, propName );
// convert string to array, unless it ends in ',' in which case more will be added to it
if ( propName === 'a' && propValue.slice( - 1 ) !== ',' ) {
currentNode.a = parseNumberArray( propValue );
}
},
parseNodePropertyContinued: function ( line ) {
var currentNode = this.getCurrentNode();
currentNode.a += line;
// if the line doesn't end in ',' we have reached the end of the property value
// so convert the string to an array
if ( line.slice( - 1 ) !== ',' ) {
currentNode.a = parseNumberArray( currentNode.a );
}
},
// parse "Property70"
parseNodeSpecialProperty: function ( line, propName, propValue ) {
// split this
// P: "Lcl Scaling", "Lcl Scaling", "", "A",1,1,1
// into array like below
// ["Lcl Scaling", "Lcl Scaling", "", "A", "1,1,1" ]
var props = propValue.split( '",' ).map( function ( prop ) {
return prop.trim().replace( /^\"/, '' ).replace( /\s/, '_' );
} );
var innerPropName = props[ 0 ];
var innerPropType1 = props[ 1 ];
var innerPropType2 = props[ 2 ];
var innerPropFlag = props[ 3 ];
var innerPropValue = props[ 4 ];
// cast values where needed, otherwise leave as strings
switch ( innerPropType1 ) {
case 'int':
case 'enum':
case 'bool':
case 'ULongLong':
case 'double':
case 'Number':
case 'FieldOfView':
innerPropValue = parseFloat( innerPropValue );
break;
case 'Color':
case 'ColorRGB':
case 'Vector3D':
case 'Lcl_Translation':
case 'Lcl_Rotation':
case 'Lcl_Scaling':
innerPropValue = parseNumberArray( innerPropValue );
break;
}
// CAUTION: these props must append to parent's parent
this.getPrevNode()[ innerPropName ] = {
'type': innerPropType1,
'type2': innerPropType2,
'flag': innerPropFlag,
'value': innerPropValue
};
this.setCurrentProp( this.getPrevNode(), innerPropName );
},
};
// Parse an FBX file in Binary format
function BinaryParser() {}
BinaryParser.prototype = {
constructor: BinaryParser,
parse: function ( buffer ) {
var reader = new BinaryReader( buffer );
reader.skip( 23 ); // skip magic 23 bytes
var version = reader.getUint32();
console.log( 'THREE.FBXLoader: FBX binary version: ' + version );
var allNodes = new FBXTree();
while ( ! this.endOfContent( reader ) ) {
var node = this.parseNode( reader, version );
if ( node !== null ) allNodes.add( node.name, node );
}
return allNodes;
},
// Check if reader has reached the end of content.
endOfContent: function ( reader ) {
// footer size: 160bytes + 16-byte alignment padding
// - 16bytes: magic
// - padding til 16-byte alignment (at least 1byte?)
// (seems like some exporters embed fixed 15 or 16bytes?)
// - 4bytes: magic
// - 4bytes: version
// - 120bytes: zero
// - 16bytes: magic
if ( reader.size() % 16 === 0 ) {
return ( ( reader.getOffset() + 160 + 16 ) & ~ 0xf ) >= reader.size();
} else {
return reader.getOffset() + 160 + 16 >= reader.size();
}
},
// recursively parse nodes until the end of the file is reached
parseNode: function ( reader, version ) {
var node = {};
// The first three data sizes depends on version.
var endOffset = ( version >= 7500 ) ? reader.getUint64() : reader.getUint32();
var numProperties = ( version >= 7500 ) ? reader.getUint64() : reader.getUint32();
( version >= 7500 ) ? reader.getUint64() : reader.getUint32(); // the returned propertyListLen is not used
var nameLen = reader.getUint8();
var name = reader.getString( nameLen );
// Regards this node as NULL-record if endOffset is zero
if ( endOffset === 0 ) return null;
var propertyList = [];
for ( var i = 0; i < numProperties; i ++ ) {
propertyList.push( this.parseProperty( reader ) );
}
// Regards the first three elements in propertyList as id, attrName, and attrType
var id = propertyList.length > 0 ? propertyList[ 0 ] : '';
var attrName = propertyList.length > 1 ? propertyList[ 1 ] : '';
var attrType = propertyList.length > 2 ? propertyList[ 2 ] : '';
// check if this node represents just a single property
// like (name, 0) set or (name2, [0, 1, 2]) set of {name: 0, name2: [0, 1, 2]}
node.singleProperty = ( numProperties === 1 && reader.getOffset() === endOffset ) ? true : false;
while ( endOffset > reader.getOffset() ) {
var subNode = this.parseNode( reader, version );
if ( subNode !== null ) this.parseSubNode( name, node, subNode );
}
node.propertyList = propertyList; // raw property list used by parent
if ( typeof id === 'number' ) node.id = id;
if ( attrName !== '' ) node.attrName = attrName;
if ( attrType !== '' ) node.attrType = attrType;
if ( name !== '' ) node.name = name;
return node;
},
parseSubNode: function ( name, node, subNode ) {
// special case: child node is single property
if ( subNode.singleProperty === true ) {
var value = subNode.propertyList[ 0 ];
if ( Array.isArray( value ) ) {
node[ subNode.name ] = subNode;
subNode.a = value;
} else {
node[ subNode.name ] = value;
}
} else if ( name === 'Connections' && subNode.name === 'C' ) {
var array = [];
subNode.propertyList.forEach( function ( property, i ) {
// first Connection is FBX type (OO, OP, etc.). We'll discard these
if ( i !== 0 ) array.push( property );
} );
if ( node.connections === undefined ) {
node.connections = [];
}
node.connections.push( array );
} else if ( subNode.name === 'Properties70' ) {
var keys = Object.keys( subNode );
keys.forEach( function ( key ) {
node[ key ] = subNode[ key ];
} );
} else if ( name === 'Properties70' && subNode.name === 'P' ) {
var innerPropName = subNode.propertyList[ 0 ];
var innerPropType1 = subNode.propertyList[ 1 ];
var innerPropType2 = subNode.propertyList[ 2 ];
var innerPropFlag = subNode.propertyList[ 3 ];
var innerPropValue;
if ( innerPropName.indexOf( 'Lcl ' ) === 0 ) innerPropName = innerPropName.replace( 'Lcl ', 'Lcl_' );
if ( innerPropType1.indexOf( 'Lcl ' ) === 0 ) innerPropType1 = innerPropType1.replace( 'Lcl ', 'Lcl_' );
if ( innerPropType1 === 'Color' || innerPropType1 === 'ColorRGB' || innerPropType1 === 'Vector' || innerPropType1 === 'Vector3D' || innerPropType1.indexOf( 'Lcl_' ) === 0 ) {
innerPropValue = [
subNode.propertyList[ 4 ],
subNode.propertyList[ 5 ],
subNode.propertyList[ 6 ]
];
} else {
innerPropValue = subNode.propertyList[ 4 ];
}
// this will be copied to parent, see above
node[ innerPropName ] = {
'type': innerPropType1,
'type2': innerPropType2,
'flag': innerPropFlag,
'value': innerPropValue
};
} else if ( node[ subNode.name ] === undefined ) {
if ( typeof subNode.id === 'number' ) {
node[ subNode.name ] = {};
node[ subNode.name ][ subNode.id ] = subNode;
} else {
node[ subNode.name ] = subNode;
}
} else {
if ( subNode.name === 'PoseNode' ) {
if ( ! Array.isArray( node[ subNode.name ] ) ) {
node[ subNode.name ] = [ node[ subNode.name ] ];
}
node[ subNode.name ].push( subNode );
} else if ( node[ subNode.name ][ subNode.id ] === undefined ) {
node[ subNode.name ][ subNode.id ] = subNode;
}
}
},
parseProperty: function ( reader ) {
var type = reader.getString( 1 );
switch ( type ) {
case 'C':
return reader.getBoolean();
case 'D':
return reader.getFloat64();
case 'F':
return reader.getFloat32();
case 'I':
return reader.getInt32();
case 'L':
return reader.getInt64();
case 'R':
var length = reader.getUint32();
return reader.getArrayBuffer( length );
case 'S':
var length = reader.getUint32();
return reader.getString( length );
case 'Y':
return reader.getInt16();
case 'b':
case 'c':
case 'd':
case 'f':
case 'i':
case 'l':
var arrayLength = reader.getUint32();
var encoding = reader.getUint32(); // 0: non-compressed, 1: compressed
var compressedLength = reader.getUint32();
if ( encoding === 0 ) {
switch ( type ) {
case 'b':
case 'c':
return reader.getBooleanArray( arrayLength );
case 'd':
return reader.getFloat64Array( arrayLength );
case 'f':
return reader.getFloat32Array( arrayLength );
case 'i':
return reader.getInt32Array( arrayLength );
case 'l':
return reader.getInt64Array( arrayLength );
}
}
if ( typeof Zlib === 'undefined' ) {
console.error( 'THREE.FBXLoader: External library Inflate.min.js required, obtain or import from https://github.com/imaya/zlib.js' );
}
var inflate = new Zlib.Inflate( new Uint8Array( reader.getArrayBuffer( compressedLength ) ) ); // eslint-disable-line no-undef
var reader2 = new BinaryReader( inflate.decompress().buffer );
switch ( type ) {
case 'b':
case 'c':
return reader2.getBooleanArray( arrayLength );
case 'd':
return reader2.getFloat64Array( arrayLength );
case 'f':
return reader2.getFloat32Array( arrayLength );
case 'i':
return reader2.getInt32Array( arrayLength );
case 'l':
return reader2.getInt64Array( arrayLength );
}
default:
throw new Error( 'THREE.FBXLoader: Unknown property type ' + type );
}
}
};
function BinaryReader( buffer, littleEndian ) {
this.dv = new DataView( buffer );
this.offset = 0;
this.littleEndian = ( littleEndian !== undefined ) ? littleEndian : true;
}
BinaryReader.prototype = {
constructor: BinaryReader,
getOffset: function () {
return this.offset;
},
size: function () {
return this.dv.buffer.byteLength;
},
skip: function ( length ) {
this.offset += length;
},
// seems like true/false representation depends on exporter.
// true: 1 or 'Y'(=0x59), false: 0 or 'T'(=0x54)
// then sees LSB.
getBoolean: function () {
return ( this.getUint8() & 1 ) === 1;
},
getBooleanArray: function ( size ) {
var a = [];
for ( var i = 0; i < size; i ++ ) {
a.push( this.getBoolean() );
}
return a;
},
getUint8: function () {
var value = this.dv.getUint8( this.offset );
this.offset += 1;
return value;
},
getInt16: function () {
var value = this.dv.getInt16( this.offset, this.littleEndian );
this.offset += 2;
return value;
},
getInt32: function () {
var value = this.dv.getInt32( this.offset, this.littleEndian );
this.offset += 4;
return value;
},
getInt32Array: function ( size ) {
var a = [];
for ( var i = 0; i < size; i ++ ) {
a.push( this.getInt32() );
}
return a;
},
getUint32: function () {
var value = this.dv.getUint32( this.offset, this.littleEndian );
this.offset += 4;
return value;
},
// JavaScript doesn't support 64-bit integer so calculate this here
// 1 << 32 will return 1 so using multiply operation instead here.
// There's a possibility that this method returns wrong value if the value
// is out of the range between Number.MAX_SAFE_INTEGER and Number.MIN_SAFE_INTEGER.
// TODO: safely handle 64-bit integer
getInt64: function () {
var low, high;
if ( this.littleEndian ) {
low = this.getUint32();
high = this.getUint32();
} else {
high = this.getUint32();
low = this.getUint32();
}
// calculate negative value
if ( high & 0x80000000 ) {
high = ~ high & 0xFFFFFFFF;
low = ~ low & 0xFFFFFFFF;
if ( low === 0xFFFFFFFF ) high = ( high + 1 ) & 0xFFFFFFFF;
low = ( low + 1 ) & 0xFFFFFFFF;
return - ( high * 0x100000000 + low );
}
return high * 0x100000000 + low;
},
getInt64Array: function ( size ) {
var a = [];
for ( var i = 0; i < size; i ++ ) {
a.push( this.getInt64() );
}
return a;
},
// Note: see getInt64() comment
getUint64: function () {
var low, high;
if ( this.littleEndian ) {
low = this.getUint32();
high = this.getUint32();
} else {
high = this.getUint32();
low = this.getUint32();
}
return high * 0x100000000 + low;
},
getFloat32: function () {
var value = this.dv.getFloat32( this.offset, this.littleEndian );
this.offset += 4;
return value;
},
getFloat32Array: function ( size ) {
var a = [];
for ( var i = 0; i < size; i ++ ) {
a.push( this.getFloat32() );
}
return a;
},
getFloat64: function () {
var value = this.dv.getFloat64( this.offset, this.littleEndian );
this.offset += 8;
return value;
},
getFloat64Array: function ( size ) {
var a = [];
for ( var i = 0; i < size; i ++ ) {
a.push( this.getFloat64() );
}
return a;
},
getArrayBuffer: function ( size ) {
var value = this.dv.buffer.slice( this.offset, this.offset + size );
this.offset += size;
return value;
},
getString: function ( size ) {
// note: safari 9 doesn't support Uint8Array.indexOf; create intermediate array instead
var a = [];
for ( var i = 0; i < size; i ++ ) {
a[ i ] = this.getUint8();
}
var nullByte = a.indexOf( 0 );
if ( nullByte >= 0 ) a = a.slice( 0, nullByte );
return LoaderUtils.decodeText( new Uint8Array( a ) );
}
};
// FBXTree holds a representation of the FBX data, returned by the TextParser ( FBX ASCII format)
// and BinaryParser( FBX Binary format)
function FBXTree() {}
FBXTree.prototype = {
constructor: FBXTree,
add: function ( key, val ) {
this[ key ] = val;
},
};
// ************** UTILITY FUNCTIONS **************
function isFbxFormatBinary( buffer ) {
var CORRECT = 'Kaydara FBX Binary \0';
return buffer.byteLength >= CORRECT.length && CORRECT === convertArrayBufferToString( buffer, 0, CORRECT.length );
}
function isFbxFormatASCII( text ) {
var CORRECT = [ 'K', 'a', 'y', 'd', 'a', 'r', 'a', '\\', 'F', 'B', 'X', '\\', 'B', 'i', 'n', 'a', 'r', 'y', '\\', '\\' ];
var cursor = 0;
function read( offset ) {
var result = text[ offset - 1 ];
text = text.slice( cursor + offset );
cursor ++;
return result;
}
for ( var i = 0; i < CORRECT.length; ++ i ) {
var num = read( 1 );
if ( num === CORRECT[ i ] ) {
return false;
}
}
return true;
}
function getFbxVersion( text ) {
var versionRegExp = /FBXVersion: (\d+)/;
var match = text.match( versionRegExp );
if ( match ) {
var version = parseInt( match[ 1 ] );
return version;
}
throw new Error( 'THREE.FBXLoader: Cannot find the version number for the file given.' );
}
// Converts FBX ticks into real time seconds.
function convertFBXTimeToSeconds( time ) {
return time / 46186158000;
}
var dataArray = [];
// extracts the data from the correct position in the FBX array based on indexing type
function getData( polygonVertexIndex, polygonIndex, vertexIndex, infoObject ) {
var index;
switch ( infoObject.mappingType ) {
case 'ByPolygonVertex' :
index = polygonVertexIndex;
break;
case 'ByPolygon' :
index = polygonIndex;
break;
case 'ByVertice' :
index = vertexIndex;
break;
case 'AllSame' :
index = infoObject.indices[ 0 ];
break;
default :
console.warn( 'THREE.FBXLoader: unknown attribute mapping type ' + infoObject.mappingType );
}
if ( infoObject.referenceType === 'IndexToDirect' ) index = infoObject.indices[ index ];
var from = index * infoObject.dataSize;
var to = from + infoObject.dataSize;
return slice( dataArray, infoObject.buffer, from, to );
}
var tempEuler = new Euler();
var tempVec = new Vector3();
// generate transformation from FBX transform data
// ref: https://help.autodesk.com/view/FBX/2017/ENU/?guid=__files_GUID_10CDD63C_79C1_4F2D_BB28_AD2BE65A02ED_htm
// ref: http://docs.autodesk.com/FBX/2014/ENU/FBX-SDK-Documentation/index.html?url=cpp_ref/_transformations_2main_8cxx-example.html,topicNumber=cpp_ref__transformations_2main_8cxx_example_htmlfc10a1e1-b18d-4e72-9dc0-70d0f1959f5e
function generateTransform( transformData ) {
var lTranslationM = new Matrix4();
var lPreRotationM = new Matrix4();
var lRotationM = new Matrix4();
var lPostRotationM = new Matrix4();
var lScalingM = new Matrix4();
var lScalingPivotM = new Matrix4();
var lScalingOffsetM = new Matrix4();
var lRotationOffsetM = new Matrix4();
var lRotationPivotM = new Matrix4();
var lParentGX = new Matrix4();
var lGlobalT = new Matrix4();
var inheritType = ( transformData.inheritType ) ? transformData.inheritType : 0;
if ( transformData.translation ) lTranslationM.setPosition( tempVec.fromArray( transformData.translation ) );
if ( transformData.preRotation ) {
var array = transformData.preRotation.map( MathUtils.degToRad );
array.push( transformData.eulerOrder );
lPreRotationM.makeRotationFromEuler( tempEuler.fromArray( array ) );
}
if ( transformData.rotation ) {
var array = transformData.rotation.map( MathUtils.degToRad );
array.push( transformData.eulerOrder );
lRotationM.makeRotationFromEuler( tempEuler.fromArray( array ) );
}
if ( transformData.postRotation ) {
var array = transformData.postRotation.map( MathUtils.degToRad );
array.push( transformData.eulerOrder );
lPostRotationM.makeRotationFromEuler( tempEuler.fromArray( array ) );
}
if ( transformData.scale ) lScalingM.scale( tempVec.fromArray( transformData.scale ) );
// Pivots and offsets
if ( transformData.scalingOffset ) lScalingOffsetM.setPosition( tempVec.fromArray( transformData.scalingOffset ) );
if ( transformData.scalingPivot ) lScalingPivotM.setPosition( tempVec.fromArray( transformData.scalingPivot ) );
if ( transformData.rotationOffset ) lRotationOffsetM.setPosition( tempVec.fromArray( transformData.rotationOffset ) );
if ( transformData.rotationPivot ) lRotationPivotM.setPosition( tempVec.fromArray( transformData.rotationPivot ) );
// parent transform
if ( transformData.parentMatrixWorld ) lParentGX = transformData.parentMatrixWorld;
// Global Rotation
var lLRM = lPreRotationM.multiply( lRotationM ).multiply( lPostRotationM );
var lParentGRM = new Matrix4();
lParentGX.extractRotation( lParentGRM );
// Global Shear*Scaling
var lParentTM = new Matrix4();
lParentTM.copyPosition( lParentGX );
var lParentGSM = new Matrix4();
lParentGSM.getInverse( lParentGRM ).multiply( lParentGX );
var lGlobalRS = new Matrix4();
if ( inheritType === 0 ) {
lGlobalRS.copy( lParentGRM ).multiply( lLRM ).multiply( lParentGSM ).multiply( lScalingM );
} else if ( inheritType === 1 ) {
lGlobalRS.copy( lParentGRM ).multiply( lParentGSM ).multiply( lLRM ).multiply( lScalingM );
} else {
var lParentLSM_inv = new Matrix4().getInverse( lScalingM );
var lParentGSM_noLocal = new Matrix4().multiply( lParentGSM ).multiply( lParentLSM_inv );
lGlobalRS.copy( lParentGRM ).multiply( lLRM ).multiply( lParentGSM_noLocal ).multiply( lScalingM );
}
var lRotationPivotM_inv = new Matrix4().getInverse( lRotationPivotM );
var lScalingPivotM_inv = new Matrix4().getInverse( lScalingPivotM );
// Calculate the local transform matrix
var lTransform = new Matrix4();
lTransform.copy( lTranslationM ).multiply( lRotationOffsetM ).multiply( lRotationPivotM ).multiply( lPreRotationM ).multiply( lRotationM ).multiply( lPostRotationM ).multiply( lRotationPivotM_inv ).multiply( lScalingOffsetM ).multiply( lScalingPivotM ).multiply( lScalingM ).multiply( lScalingPivotM_inv );
var lLocalTWithAllPivotAndOffsetInfo = new Matrix4().copyPosition( lTransform );
var lGlobalTranslation = new Matrix4().copy( lParentGX ).multiply( lLocalTWithAllPivotAndOffsetInfo );
lGlobalT.copyPosition( lGlobalTranslation );
lTransform = new Matrix4().multiply( lGlobalT ).multiply( lGlobalRS );
return lTransform;
}
// Returns the three.js intrinsic Euler order corresponding to FBX extrinsic Euler order
// ref: http://help.autodesk.com/view/FBX/2017/ENU/?guid=__cpp_ref_class_fbx_euler_html
function getEulerOrder( order ) {
order = order || 0;
var enums = [
'ZYX', // -> XYZ extrinsic
'YZX', // -> XZY extrinsic
'XZY', // -> YZX extrinsic
'ZXY', // -> YXZ extrinsic
'YXZ', // -> ZXY extrinsic
'XYZ', // -> ZYX extrinsic
//'SphericXYZ', // not possible to support
];
if ( order === 6 ) {
console.warn( 'THREE.FBXLoader: unsupported Euler Order: Spherical XYZ. Animations and rotations may be incorrect.' );
return enums[ 0 ];
}
return enums[ order ];
}
// Parses comma separated list of numbers and returns them an array.
// Used internally by the TextParser
function parseNumberArray( value ) {
var array = value.split( ',' ).map( function ( val ) {
return parseFloat( val );
} );
return array;
}
function convertArrayBufferToString( buffer, from, to ) {
if ( from === undefined ) from = 0;
if ( to === undefined ) to = buffer.byteLength;
return LoaderUtils.decodeText( new Uint8Array( buffer, from, to ) );
}
function append( a, b ) {
for ( var i = 0, j = a.length, l = b.length; i < l; i ++, j ++ ) {
a[ j ] = b[ i ];
}
}
function slice( a, b, from, to ) {
for ( var i = from, j = 0; i < to; i ++, j ++ ) {
a[ j ] = b[ i ];
}
return a;
}
// inject array a2 into array a1 at index
function inject( a1, index, a2 ) {
return a1.slice( 0, index ).concat( a2 ).concat( a1.slice( index ) );
}
return FBXLoader;
} )()
Example #10
Source File: Water.js From canvas with Apache License 2.0 | 4 votes |
Water = function ( geometry, options ) {
Mesh.call( this, geometry );
var scope = this;
options = options || {};
var textureWidth = options.textureWidth !== undefined ? options.textureWidth : 512;
var textureHeight = options.textureHeight !== undefined ? options.textureHeight : 512;
var clipBias = options.clipBias !== undefined ? options.clipBias : 0.0;
var alpha = options.alpha !== undefined ? options.alpha : 1.0;
var time = options.time !== undefined ? options.time : 0.0;
var normalSampler = options.waterNormals !== undefined ? options.waterNormals : null;
var sunDirection = options.sunDirection !== undefined ? options.sunDirection : new Vector3( 0.70707, 0.70707, 0.0 );
var sunColor = new Color( options.sunColor !== undefined ? options.sunColor : 0xffffff );
var waterColor = new Color( options.waterColor !== undefined ? options.waterColor : 0x7F7F7F );
var eye = options.eye !== undefined ? options.eye : new Vector3( 0, 0, 0 );
var distortionScale = options.distortionScale !== undefined ? options.distortionScale : 20.0;
var side = options.side !== undefined ? options.side : FrontSide;
var fog = options.fog !== undefined ? options.fog : false;
//
var mirrorPlane = new Plane();
var normal = new Vector3();
var mirrorWorldPosition = new Vector3();
var cameraWorldPosition = new Vector3();
var rotationMatrix = new Matrix4();
var lookAtPosition = new Vector3( 0, 0, - 1 );
var clipPlane = new Vector4();
var view = new Vector3();
var target = new Vector3();
var q = new Vector4();
var textureMatrix = new Matrix4();
var mirrorCamera = new PerspectiveCamera();
var parameters = {
minFilter: LinearFilter,
magFilter: LinearFilter,
format: RGBFormat,
stencilBuffer: false
};
var renderTarget = new WebGLRenderTarget( textureWidth, textureHeight, parameters );
if ( ! MathUtils.isPowerOfTwo( textureWidth ) || ! MathUtils.isPowerOfTwo( textureHeight ) ) {
renderTarget.texture.generateMipmaps = false;
}
var mirrorShader = {
uniforms: UniformsUtils.merge( [
UniformsLib[ 'fog' ],
UniformsLib[ 'lights' ],
{
"normalSampler": { value: null },
"mirrorSampler": { value: null },
"alpha": { value: 1.0 },
"time": { value: 0.0 },
"size": { value: 1.0 },
"distortionScale": { value: 20.0 },
"textureMatrix": { value: new Matrix4() },
"sunColor": { value: new Color( 0x7F7F7F ) },
"sunDirection": { value: new Vector3( 0.70707, 0.70707, 0 ) },
"eye": { value: new Vector3() },
"waterColor": { value: new Color( 0x555555 ) }
}
] ),
vertexShader: [
'uniform mat4 textureMatrix;',
'uniform float time;',
'varying vec4 mirrorCoord;',
'varying vec4 worldPosition;',
'#include <common>',
'#include <fog_pars_vertex>',
'#include <shadowmap_pars_vertex>',
'#include <logdepthbuf_pars_vertex>',
'void main() {',
' mirrorCoord = modelMatrix * vec4( position, 1.0 );',
' worldPosition = mirrorCoord.xyzw;',
' mirrorCoord = textureMatrix * mirrorCoord;',
' vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );',
' gl_Position = projectionMatrix * mvPosition;',
'#include <logdepthbuf_vertex>',
'#include <fog_vertex>',
'#include <shadowmap_vertex>',
'}'
].join( '\n' ),
fragmentShader: [
'uniform sampler2D mirrorSampler;',
'uniform float alpha;',
'uniform float time;',
'uniform float size;',
'uniform float distortionScale;',
'uniform sampler2D normalSampler;',
'uniform vec3 sunColor;',
'uniform vec3 sunDirection;',
'uniform vec3 eye;',
'uniform vec3 waterColor;',
'varying vec4 mirrorCoord;',
'varying vec4 worldPosition;',
'vec4 getNoise( vec2 uv ) {',
' vec2 uv0 = ( uv / 103.0 ) + vec2(time / 17.0, time / 29.0);',
' vec2 uv1 = uv / 107.0-vec2( time / -19.0, time / 31.0 );',
' vec2 uv2 = uv / vec2( 8907.0, 9803.0 ) + vec2( time / 101.0, time / 97.0 );',
' vec2 uv3 = uv / vec2( 1091.0, 1027.0 ) - vec2( time / 109.0, time / -113.0 );',
' vec4 noise = texture2D( normalSampler, uv0 ) +',
' texture2D( normalSampler, uv1 ) +',
' texture2D( normalSampler, uv2 ) +',
' texture2D( normalSampler, uv3 );',
' return noise * 0.5 - 1.0;',
'}',
'void sunLight( const vec3 surfaceNormal, const vec3 eyeDirection, float shiny, float spec, float diffuse, inout vec3 diffuseColor, inout vec3 specularColor ) {',
' vec3 reflection = normalize( reflect( -sunDirection, surfaceNormal ) );',
' float direction = max( 0.0, dot( eyeDirection, reflection ) );',
' specularColor += pow( direction, shiny ) * sunColor * spec;',
' diffuseColor += max( dot( sunDirection, surfaceNormal ), 0.0 ) * sunColor * diffuse;',
'}',
'#include <common>',
'#include <packing>',
'#include <bsdfs>',
'#include <fog_pars_fragment>',
'#include <logdepthbuf_pars_fragment>',
'#include <lights_pars_begin>',
'#include <shadowmap_pars_fragment>',
'#include <shadowmask_pars_fragment>',
'void main() {',
'#include <logdepthbuf_fragment>',
' vec4 noise = getNoise( worldPosition.xz * size );',
' vec3 surfaceNormal = normalize( noise.xzy * vec3( 1.5, 1.0, 1.5 ) );',
' vec3 diffuseLight = vec3(0.0);',
' vec3 specularLight = vec3(0.0);',
' vec3 worldToEye = eye-worldPosition.xyz;',
' vec3 eyeDirection = normalize( worldToEye );',
' sunLight( surfaceNormal, eyeDirection, 100.0, 2.0, 0.5, diffuseLight, specularLight );',
' float distance = length(worldToEye);',
' vec2 distortion = surfaceNormal.xz * ( 0.001 + 1.0 / distance ) * distortionScale;',
' vec3 reflectionSample = vec3( texture2D( mirrorSampler, mirrorCoord.xy / mirrorCoord.w + distortion ) );',
' float theta = max( dot( eyeDirection, surfaceNormal ), 0.0 );',
' float rf0 = 0.3;',
' float reflectance = rf0 + ( 1.0 - rf0 ) * pow( ( 1.0 - theta ), 5.0 );',
' vec3 scatter = max( 0.0, dot( surfaceNormal, eyeDirection ) ) * waterColor;',
' vec3 albedo = mix( ( sunColor * diffuseLight * 0.3 + scatter ) * getShadowMask(), ( vec3( 0.1 ) + reflectionSample * 0.9 + reflectionSample * specularLight ), reflectance);',
' vec3 outgoingLight = albedo;',
' gl_FragColor = vec4( outgoingLight, alpha );',
'#include <tonemapping_fragment>',
'#include <fog_fragment>',
'}'
].join( '\n' )
};
var material = new ShaderMaterial( {
fragmentShader: mirrorShader.fragmentShader,
vertexShader: mirrorShader.vertexShader,
uniforms: UniformsUtils.clone( mirrorShader.uniforms ),
lights: true,
side: side,
fog: fog
} );
material.uniforms[ "mirrorSampler" ].value = renderTarget.texture;
material.uniforms[ "textureMatrix" ].value = textureMatrix;
material.uniforms[ "alpha" ].value = alpha;
material.uniforms[ "time" ].value = time;
material.uniforms[ "normalSampler" ].value = normalSampler;
material.uniforms[ "sunColor" ].value = sunColor;
material.uniforms[ "waterColor" ].value = waterColor;
material.uniforms[ "sunDirection" ].value = sunDirection;
material.uniforms[ "distortionScale" ].value = distortionScale;
material.uniforms[ "eye" ].value = eye;
scope.material = material;
scope.onBeforeRender = function ( renderer, scene, camera ) {
mirrorWorldPosition.setFromMatrixPosition( scope.matrixWorld );
cameraWorldPosition.setFromMatrixPosition( camera.matrixWorld );
rotationMatrix.extractRotation( scope.matrixWorld );
normal.set( 0, 0, 1 );
normal.applyMatrix4( rotationMatrix );
view.subVectors( mirrorWorldPosition, cameraWorldPosition );
// Avoid rendering when mirror is facing away
if ( view.dot( normal ) > 0 ) return;
view.reflect( normal ).negate();
view.add( mirrorWorldPosition );
rotationMatrix.extractRotation( camera.matrixWorld );
lookAtPosition.set( 0, 0, - 1 );
lookAtPosition.applyMatrix4( rotationMatrix );
lookAtPosition.add( cameraWorldPosition );
target.subVectors( mirrorWorldPosition, lookAtPosition );
target.reflect( normal ).negate();
target.add( mirrorWorldPosition );
mirrorCamera.position.copy( view );
mirrorCamera.up.set( 0, 1, 0 );
mirrorCamera.up.applyMatrix4( rotationMatrix );
mirrorCamera.up.reflect( normal );
mirrorCamera.lookAt( target );
mirrorCamera.far = camera.far; // Used in WebGLBackground
mirrorCamera.updateMatrixWorld();
mirrorCamera.projectionMatrix.copy( camera.projectionMatrix );
// Update the texture matrix
textureMatrix.set(
0.5, 0.0, 0.0, 0.5,
0.0, 0.5, 0.0, 0.5,
0.0, 0.0, 0.5, 0.5,
0.0, 0.0, 0.0, 1.0
);
textureMatrix.multiply( mirrorCamera.projectionMatrix );
textureMatrix.multiply( mirrorCamera.matrixWorldInverse );
// Now update projection matrix with new clip plane, implementing code from: http://www.terathon.com/code/oblique.html
// Paper explaining this technique: http://www.terathon.com/lengyel/Lengyel-Oblique.pdf
mirrorPlane.setFromNormalAndCoplanarPoint( normal, mirrorWorldPosition );
mirrorPlane.applyMatrix4( mirrorCamera.matrixWorldInverse );
clipPlane.set( mirrorPlane.normal.x, mirrorPlane.normal.y, mirrorPlane.normal.z, mirrorPlane.constant );
var projectionMatrix = mirrorCamera.projectionMatrix;
q.x = ( Math.sign( clipPlane.x ) + projectionMatrix.elements[ 8 ] ) / projectionMatrix.elements[ 0 ];
q.y = ( Math.sign( clipPlane.y ) + projectionMatrix.elements[ 9 ] ) / projectionMatrix.elements[ 5 ];
q.z = - 1.0;
q.w = ( 1.0 + projectionMatrix.elements[ 10 ] ) / projectionMatrix.elements[ 14 ];
// Calculate the scaled plane vector
clipPlane.multiplyScalar( 2.0 / clipPlane.dot( q ) );
// Replacing the third row of the projection matrix
projectionMatrix.elements[ 2 ] = clipPlane.x;
projectionMatrix.elements[ 6 ] = clipPlane.y;
projectionMatrix.elements[ 10 ] = clipPlane.z + 1.0 - clipBias;
projectionMatrix.elements[ 14 ] = clipPlane.w;
eye.setFromMatrixPosition( camera.matrixWorld );
//
var currentRenderTarget = renderer.getRenderTarget();
var currentXrEnabled = renderer.xr.enabled;
var currentShadowAutoUpdate = renderer.shadowMap.autoUpdate;
scope.visible = false;
renderer.xr.enabled = false; // Avoid camera modification and recursion
renderer.shadowMap.autoUpdate = false; // Avoid re-computing shadows
renderer.setRenderTarget( renderTarget );
renderer.state.buffers.depth.setMask( true ); // make sure the depth buffer is writable so it can be properly cleared, see #18897
if ( renderer.autoClear === false ) renderer.clear();
renderer.render( scene, mirrorCamera );
scope.visible = true;
renderer.xr.enabled = currentXrEnabled;
renderer.shadowMap.autoUpdate = currentShadowAutoUpdate;
renderer.setRenderTarget( currentRenderTarget );
// Restore viewport
var viewport = camera.viewport;
if ( viewport !== undefined ) {
renderer.state.viewport( viewport );
}
};
}
Example #11
Source File: finalizeMesh.js From HeroSaver-v2 with GNU General Public License v3.0 | 4 votes |
finalizeMesh.prototype = {
constructor: finalizeMesh,
parse: function (mesh) {
if (!mesh.isMesh) {
console.warn('Mesh type unsupported', mesh);
return;
}
var vertex = new Vector3();
var i, l = [];
var nbVertex = 0;
var geometry = mesh.geometry;
var mrot = new Matrix4().makeRotationX(90 * Math.PI / 180);
var msca = new Matrix4().makeScale(10, 10, 10);
if (geometry.isBufferGeometry) {
var newGeometry = geometry.clone(geometry);
var vertices = geometry.getAttribute('position');
// vertices
if (vertices !== undefined) {
for (i = 0, l = vertices.count; i < l; i++ , nbVertex++) {
vertex.x = vertices.getX(i);
vertex.y = vertices.getY(i);
vertex.z = vertices.getZ(i);
if (geometry.skinIndexNames == undefined
|| geometry.skinIndexNames == 0) {
vertex.applyMatrix4(mesh.matrixWorld).applyMatrix4(mrot).applyMatrix4(msca);
newGeometry.attributes.position.setXYZ(i, vertex.x, vertex.y, vertex.z);
} else {
var finalVector = new Vector4();
if (geometry.morphTargetInfluences !== undefined) {
var morphVector = new Vector4(vertex.x, vertex.y, vertex.z);
var tempMorph = new Vector4();
for (var mt = 0; mt < geometry.morphAttributes.position.length; mt++) {
if (geometry.morphTargetInfluences[mt] == 0) continue;
if (geometry.morphTargetDictionary.hide == mt) continue;
var morph = new Vector4(
geometry.morphAttributes.position[mt].getX(i),
geometry.morphAttributes.position[mt].getY(i),
geometry.morphAttributes.position[mt].getZ(i));
tempMorph.addScaledVector(morph.sub(morphVector), geometry.morphTargetInfluences[mt]);
}
morphVector.add(tempMorph);
}
for (var si = 0; si < geometry.skinIndexNames.length; si++) {
var skinIndices = geometry.getAttribute([geometry.skinIndexNames[si]])
var weights = geometry.getAttribute([geometry.skinWeightNames[si]])
var skinIndex = [];
skinIndex[0] = skinIndices.getX(i);
skinIndex[1] = skinIndices.getY(i);
skinIndex[2] = skinIndices.getZ(i);
skinIndex[3] = skinIndices.getW(i);
var skinWeight = [];
skinWeight[0] = weights.getX(i);
skinWeight[1] = weights.getY(i);
skinWeight[2] = weights.getZ(i);
skinWeight[3] = weights.getW(i);
var inverses = [];
inverses[0] = mesh.skeleton.boneInverses[skinIndex[0]];
inverses[1] = mesh.skeleton.boneInverses[skinIndex[1]];
inverses[2] = mesh.skeleton.boneInverses[skinIndex[2]];
inverses[3] = mesh.skeleton.boneInverses[skinIndex[3]];
var skinMatrices = [];
skinMatrices[0] = mesh.skeleton.bones[skinIndex[0]].matrixWorld;
skinMatrices[1] = mesh.skeleton.bones[skinIndex[1]].matrixWorld;
skinMatrices[2] = mesh.skeleton.bones[skinIndex[2]].matrixWorld;
skinMatrices[3] = mesh.skeleton.bones[skinIndex[3]].matrixWorld;
for (var k = 0; k < 4; k++) {
if (geometry.morphTargetInfluences !== undefined) {
var tempVector = new Vector4(morphVector.x, morphVector.y, morphVector.z);
} else {
var tempVector = new Vector4(vertex.x, vertex.y, vertex.z);
}
tempVector.multiplyScalar(skinWeight[k]);
//the inverse takes the vector into local bone space
//which is then transformed to the appropriate world space
tempVector.applyMatrix4(inverses[k])
.applyMatrix4(skinMatrices[k])
.applyMatrix4(mrot).applyMatrix4(msca);
finalVector.add(tempVector);
}
}
newGeometry.attributes.position.setXYZ(i, finalVector.x, finalVector.y, finalVector.z);
}
}
}
} else {
console.warn( 'Geometry type unsupported', geometry );
}
return newGeometry;
}
};
Example #12
Source File: finalizeMesh.js From threejs with GNU General Public License v3.0 | 4 votes |
finalizeMesh.prototype = {
constructor: finalizeMesh,
parse: function (mesh) {
if (!mesh.isMesh) {
console.warn('Mesh type unsupported', mesh);
return;
}
var vertex = new Vector3();
var i, l = [];
var nbVertex = 0;
var geometry = mesh.geometry;
var mrot = new Matrix4().makeRotationX(90 * Math.PI / 180);
var msca = new Matrix4().makeScale(10, 10, 10);
if (geometry.isBufferGeometry) {
var newGeometry = geometry.clone(geometry);
var vertices = geometry.getAttribute('position');
// vertices
if (vertices !== undefined) {
for (i = 0, l = vertices.count; i < l; i++ , nbVertex++) {
vertex.x = vertices.getX(i);
vertex.y = vertices.getY(i);
vertex.z = vertices.getZ(i);
if (geometry.skinIndexNames == undefined
|| geometry.skinIndexNames == 0) {
vertex.applyMatrix4(mesh.matrixWorld).applyMatrix4(mrot).applyMatrix4(msca);
newGeometry.attributes.position.setXYZ(i, vertex.x, vertex.y, vertex.z);
} else {
var finalVector = new Vector4();
var morphVector = new Vector4(vertex.x, vertex.y, vertex.z);//Lilly
if (geometry.morphTargetInfluences !== undefined) {
var tempMorph = new Vector4();
for (var mt = 0; mt < geometry.morphAttributes.position.length; mt++) {
if (geometry.morphTargetInfluences[mt] == 0) continue;
if (geometry.morphTargetDictionary.hide == mt) continue;
var morph = new Vector4(
geometry.morphAttributes.position[mt].getX(i),
geometry.morphAttributes.position[mt].getY(i),
geometry.morphAttributes.position[mt].getZ(i));
tempMorph.addScaledVector(morph, geometry.morphTargetInfluences[mt]);
}
//comment to avoid morph problems
morphVector.add(tempMorph);
}
for (var si = 0; si < geometry.skinIndexNames.length; si++) {
var skinIndices = geometry.getAttribute([geometry.skinIndexNames[si]])
var weights = geometry.getAttribute([geometry.skinWeightNames[si]])
var skinIndex = [];
skinIndex[0] = skinIndices.getX(i);
skinIndex[1] = skinIndices.getY(i);
skinIndex[2] = skinIndices.getZ(i);
skinIndex[3] = skinIndices.getW(i);
var skinWeight = [];
skinWeight[0] = weights.getX(i);
skinWeight[1] = weights.getY(i);
skinWeight[2] = weights.getZ(i);
skinWeight[3] = weights.getW(i);
var inverses = [];
inverses[0] = mesh.skeleton.boneInverses[skinIndex[0]];
inverses[1] = mesh.skeleton.boneInverses[skinIndex[1]];
inverses[2] = mesh.skeleton.boneInverses[skinIndex[2]];
inverses[3] = mesh.skeleton.boneInverses[skinIndex[3]];
var skinMatrices = [];
skinMatrices[0] = mesh.skeleton.bones[skinIndex[0]].matrixWorld;
skinMatrices[1] = mesh.skeleton.bones[skinIndex[1]].matrixWorld;
skinMatrices[2] = mesh.skeleton.bones[skinIndex[2]].matrixWorld;
skinMatrices[3] = mesh.skeleton.bones[skinIndex[3]].matrixWorld;
for (var k = 0; k < 4; k++) {
if (geometry.morphTargetInfluences !== undefined) {
var tempVector = new Vector4(morphVector.x, morphVector.y, morphVector.z);
} else {
var tempVector = new Vector4(vertex.x, vertex.y, vertex.z);
}
tempVector.multiplyScalar(skinWeight[k]);
//the inverse takes the vector into local bone space
//which is then transformed to the appropriate world space
tempVector.applyMatrix4(inverses[k])
.applyMatrix4(skinMatrices[k])
.applyMatrix4(mrot).applyMatrix4(msca);
finalVector.add(tempVector);
}
}
newGeometry.attributes.position.setXYZ(i, finalVector.x, finalVector.y, finalVector.z);
}
}
}
} else {
console.warn( 'Geometry type unsupported', geometry );
}
return newGeometry;
}
};