添加关照、全局等高线、修改图层问题
This commit is contained in:
519
dist/electron/static/sdk/three/jsm/misc/ConvexObjectBreaker.js
vendored
Normal file
519
dist/electron/static/sdk/three/jsm/misc/ConvexObjectBreaker.js
vendored
Normal file
@ -0,0 +1,519 @@
|
||||
import {
|
||||
Line3,
|
||||
Mesh,
|
||||
Plane,
|
||||
Vector3
|
||||
} from 'three';
|
||||
import { ConvexGeometry } from '../geometries/ConvexGeometry.js';
|
||||
|
||||
/**
|
||||
* @fileoverview This class can be used to subdivide a convex Geometry object into pieces.
|
||||
*
|
||||
* Usage:
|
||||
*
|
||||
* Use the function prepareBreakableObject to prepare a Mesh object to be broken.
|
||||
*
|
||||
* Then, call the various functions to subdivide the object (subdivideByImpact, cutByPlane)
|
||||
*
|
||||
* Sub-objects that are product of subdivision don't need prepareBreakableObject to be called on them.
|
||||
*
|
||||
* Requisites for the object:
|
||||
*
|
||||
* - Mesh object must have a buffer geometry and a material
|
||||
*
|
||||
* - Vertex normals must be planar (not smoothed)
|
||||
*
|
||||
* - The geometry must be convex (this is not checked in the library). You can create convex
|
||||
* geometries with ConvexGeometry. The BoxGeometry, SphereGeometry and other convex primitives
|
||||
* can also be used.
|
||||
*
|
||||
* Note: This lib adds member variables to object's userData member (see prepareBreakableObject function)
|
||||
* Use with caution and read the code when using with other libs.
|
||||
*
|
||||
* @param {double} minSizeForBreak Min size a debris can have to break.
|
||||
* @param {double} smallDelta Max distance to consider that a point belongs to a plane.
|
||||
*
|
||||
*/
|
||||
|
||||
const _v1 = new Vector3();
|
||||
|
||||
class ConvexObjectBreaker {
|
||||
|
||||
constructor( minSizeForBreak = 1.4, smallDelta = 0.0001 ) {
|
||||
|
||||
this.minSizeForBreak = minSizeForBreak;
|
||||
this.smallDelta = smallDelta;
|
||||
|
||||
this.tempLine1 = new Line3();
|
||||
this.tempPlane1 = new Plane();
|
||||
this.tempPlane2 = new Plane();
|
||||
this.tempPlane_Cut = new Plane();
|
||||
this.tempCM1 = new Vector3();
|
||||
this.tempCM2 = new Vector3();
|
||||
this.tempVector3 = new Vector3();
|
||||
this.tempVector3_2 = new Vector3();
|
||||
this.tempVector3_3 = new Vector3();
|
||||
this.tempVector3_P0 = new Vector3();
|
||||
this.tempVector3_P1 = new Vector3();
|
||||
this.tempVector3_P2 = new Vector3();
|
||||
this.tempVector3_N0 = new Vector3();
|
||||
this.tempVector3_N1 = new Vector3();
|
||||
this.tempVector3_AB = new Vector3();
|
||||
this.tempVector3_CB = new Vector3();
|
||||
this.tempResultObjects = { object1: null, object2: null };
|
||||
|
||||
this.segments = [];
|
||||
const n = 30 * 30;
|
||||
for ( let i = 0; i < n; i ++ ) this.segments[ i ] = false;
|
||||
|
||||
}
|
||||
|
||||
prepareBreakableObject( object, mass, velocity, angularVelocity, breakable ) {
|
||||
|
||||
// object is a Object3d (normally a Mesh), must have a buffer geometry, and it must be convex.
|
||||
// Its material property is propagated to its children (sub-pieces)
|
||||
// mass must be > 0
|
||||
|
||||
const userData = object.userData;
|
||||
userData.mass = mass;
|
||||
userData.velocity = velocity.clone();
|
||||
userData.angularVelocity = angularVelocity.clone();
|
||||
userData.breakable = breakable;
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* @param {int} maxRadialIterations Iterations for radial cuts.
|
||||
* @param {int} maxRandomIterations Max random iterations for not-radial cuts
|
||||
*
|
||||
* Returns the array of pieces
|
||||
*/
|
||||
subdivideByImpact( object, pointOfImpact, normal, maxRadialIterations, maxRandomIterations ) {
|
||||
|
||||
const debris = [];
|
||||
|
||||
const tempPlane1 = this.tempPlane1;
|
||||
const tempPlane2 = this.tempPlane2;
|
||||
|
||||
this.tempVector3.addVectors( pointOfImpact, normal );
|
||||
tempPlane1.setFromCoplanarPoints( pointOfImpact, object.position, this.tempVector3 );
|
||||
|
||||
const maxTotalIterations = maxRandomIterations + maxRadialIterations;
|
||||
|
||||
const scope = this;
|
||||
|
||||
function subdivideRadial( subObject, startAngle, endAngle, numIterations ) {
|
||||
|
||||
if ( Math.random() < numIterations * 0.05 || numIterations > maxTotalIterations ) {
|
||||
|
||||
debris.push( subObject );
|
||||
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
let angle = Math.PI;
|
||||
|
||||
if ( numIterations === 0 ) {
|
||||
|
||||
tempPlane2.normal.copy( tempPlane1.normal );
|
||||
tempPlane2.constant = tempPlane1.constant;
|
||||
|
||||
} else {
|
||||
|
||||
if ( numIterations <= maxRadialIterations ) {
|
||||
|
||||
angle = ( endAngle - startAngle ) * ( 0.2 + 0.6 * Math.random() ) + startAngle;
|
||||
|
||||
// Rotate tempPlane2 at impact point around normal axis and the angle
|
||||
scope.tempVector3_2.copy( object.position ).sub( pointOfImpact ).applyAxisAngle( normal, angle ).add( pointOfImpact );
|
||||
tempPlane2.setFromCoplanarPoints( pointOfImpact, scope.tempVector3, scope.tempVector3_2 );
|
||||
|
||||
} else {
|
||||
|
||||
angle = ( ( 0.5 * ( numIterations & 1 ) ) + 0.2 * ( 2 - Math.random() ) ) * Math.PI;
|
||||
|
||||
// Rotate tempPlane2 at object position around normal axis and the angle
|
||||
scope.tempVector3_2.copy( pointOfImpact ).sub( subObject.position ).applyAxisAngle( normal, angle ).add( subObject.position );
|
||||
scope.tempVector3_3.copy( normal ).add( subObject.position );
|
||||
tempPlane2.setFromCoplanarPoints( subObject.position, scope.tempVector3_3, scope.tempVector3_2 );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Perform the cut
|
||||
scope.cutByPlane( subObject, tempPlane2, scope.tempResultObjects );
|
||||
|
||||
const obj1 = scope.tempResultObjects.object1;
|
||||
const obj2 = scope.tempResultObjects.object2;
|
||||
|
||||
if ( obj1 ) {
|
||||
|
||||
subdivideRadial( obj1, startAngle, angle, numIterations + 1 );
|
||||
|
||||
}
|
||||
|
||||
if ( obj2 ) {
|
||||
|
||||
subdivideRadial( obj2, angle, endAngle, numIterations + 1 );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
subdivideRadial( object, 0, 2 * Math.PI, 0 );
|
||||
|
||||
return debris;
|
||||
|
||||
}
|
||||
|
||||
cutByPlane( object, plane, output ) {
|
||||
|
||||
// Returns breakable objects in output.object1 and output.object2 members, the resulting 2 pieces of the cut.
|
||||
// object2 can be null if the plane doesn't cut the object.
|
||||
// object1 can be null only in case of internal error
|
||||
// Returned value is number of pieces, 0 for error.
|
||||
|
||||
const geometry = object.geometry;
|
||||
const coords = geometry.attributes.position.array;
|
||||
const normals = geometry.attributes.normal.array;
|
||||
|
||||
const numPoints = coords.length / 3;
|
||||
let numFaces = numPoints / 3;
|
||||
|
||||
let indices = geometry.getIndex();
|
||||
|
||||
if ( indices ) {
|
||||
|
||||
indices = indices.array;
|
||||
numFaces = indices.length / 3;
|
||||
|
||||
}
|
||||
|
||||
function getVertexIndex( faceIdx, vert ) {
|
||||
|
||||
// vert = 0, 1 or 2.
|
||||
|
||||
const idx = faceIdx * 3 + vert;
|
||||
|
||||
return indices ? indices[ idx ] : idx;
|
||||
|
||||
}
|
||||
|
||||
const points1 = [];
|
||||
const points2 = [];
|
||||
|
||||
const delta = this.smallDelta;
|
||||
|
||||
// Reset segments mark
|
||||
const numPointPairs = numPoints * numPoints;
|
||||
for ( let i = 0; i < numPointPairs; i ++ ) this.segments[ i ] = false;
|
||||
|
||||
const p0 = this.tempVector3_P0;
|
||||
const p1 = this.tempVector3_P1;
|
||||
const n0 = this.tempVector3_N0;
|
||||
const n1 = this.tempVector3_N1;
|
||||
|
||||
// Iterate through the faces to mark edges shared by coplanar faces
|
||||
for ( let i = 0; i < numFaces - 1; i ++ ) {
|
||||
|
||||
const a1 = getVertexIndex( i, 0 );
|
||||
const b1 = getVertexIndex( i, 1 );
|
||||
const c1 = getVertexIndex( i, 2 );
|
||||
|
||||
// Assuming all 3 vertices have the same normal
|
||||
n0.set( normals[ a1 ], normals[ a1 ] + 1, normals[ a1 ] + 2 );
|
||||
|
||||
for ( let j = i + 1; j < numFaces; j ++ ) {
|
||||
|
||||
const a2 = getVertexIndex( j, 0 );
|
||||
const b2 = getVertexIndex( j, 1 );
|
||||
const c2 = getVertexIndex( j, 2 );
|
||||
|
||||
// Assuming all 3 vertices have the same normal
|
||||
n1.set( normals[ a2 ], normals[ a2 ] + 1, normals[ a2 ] + 2 );
|
||||
|
||||
const coplanar = 1 - n0.dot( n1 ) < delta;
|
||||
|
||||
if ( coplanar ) {
|
||||
|
||||
if ( a1 === a2 || a1 === b2 || a1 === c2 ) {
|
||||
|
||||
if ( b1 === a2 || b1 === b2 || b1 === c2 ) {
|
||||
|
||||
this.segments[ a1 * numPoints + b1 ] = true;
|
||||
this.segments[ b1 * numPoints + a1 ] = true;
|
||||
|
||||
} else {
|
||||
|
||||
this.segments[ c1 * numPoints + a1 ] = true;
|
||||
this.segments[ a1 * numPoints + c1 ] = true;
|
||||
|
||||
}
|
||||
|
||||
} else if ( b1 === a2 || b1 === b2 || b1 === c2 ) {
|
||||
|
||||
this.segments[ c1 * numPoints + b1 ] = true;
|
||||
this.segments[ b1 * numPoints + c1 ] = true;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Transform the plane to object local space
|
||||
const localPlane = this.tempPlane_Cut;
|
||||
object.updateMatrix();
|
||||
ConvexObjectBreaker.transformPlaneToLocalSpace( plane, object.matrix, localPlane );
|
||||
|
||||
// Iterate through the faces adding points to both pieces
|
||||
for ( let i = 0; i < numFaces; i ++ ) {
|
||||
|
||||
const va = getVertexIndex( i, 0 );
|
||||
const vb = getVertexIndex( i, 1 );
|
||||
const vc = getVertexIndex( i, 2 );
|
||||
|
||||
for ( let segment = 0; segment < 3; segment ++ ) {
|
||||
|
||||
const i0 = segment === 0 ? va : ( segment === 1 ? vb : vc );
|
||||
const i1 = segment === 0 ? vb : ( segment === 1 ? vc : va );
|
||||
|
||||
const segmentState = this.segments[ i0 * numPoints + i1 ];
|
||||
|
||||
if ( segmentState ) continue; // The segment already has been processed in another face
|
||||
|
||||
// Mark segment as processed (also inverted segment)
|
||||
this.segments[ i0 * numPoints + i1 ] = true;
|
||||
this.segments[ i1 * numPoints + i0 ] = true;
|
||||
|
||||
p0.set( coords[ 3 * i0 ], coords[ 3 * i0 + 1 ], coords[ 3 * i0 + 2 ] );
|
||||
p1.set( coords[ 3 * i1 ], coords[ 3 * i1 + 1 ], coords[ 3 * i1 + 2 ] );
|
||||
|
||||
// mark: 1 for negative side, 2 for positive side, 3 for coplanar point
|
||||
let mark0 = 0;
|
||||
|
||||
let d = localPlane.distanceToPoint( p0 );
|
||||
|
||||
if ( d > delta ) {
|
||||
|
||||
mark0 = 2;
|
||||
points2.push( p0.clone() );
|
||||
|
||||
} else if ( d < - delta ) {
|
||||
|
||||
mark0 = 1;
|
||||
points1.push( p0.clone() );
|
||||
|
||||
} else {
|
||||
|
||||
mark0 = 3;
|
||||
points1.push( p0.clone() );
|
||||
points2.push( p0.clone() );
|
||||
|
||||
}
|
||||
|
||||
// mark: 1 for negative side, 2 for positive side, 3 for coplanar point
|
||||
let mark1 = 0;
|
||||
|
||||
d = localPlane.distanceToPoint( p1 );
|
||||
|
||||
if ( d > delta ) {
|
||||
|
||||
mark1 = 2;
|
||||
points2.push( p1.clone() );
|
||||
|
||||
} else if ( d < - delta ) {
|
||||
|
||||
mark1 = 1;
|
||||
points1.push( p1.clone() );
|
||||
|
||||
} else {
|
||||
|
||||
mark1 = 3;
|
||||
points1.push( p1.clone() );
|
||||
points2.push( p1.clone() );
|
||||
|
||||
}
|
||||
|
||||
if ( ( mark0 === 1 && mark1 === 2 ) || ( mark0 === 2 && mark1 === 1 ) ) {
|
||||
|
||||
// Intersection of segment with the plane
|
||||
|
||||
this.tempLine1.start.copy( p0 );
|
||||
this.tempLine1.end.copy( p1 );
|
||||
|
||||
let intersection = new Vector3();
|
||||
intersection = localPlane.intersectLine( this.tempLine1, intersection );
|
||||
|
||||
if ( intersection === null ) {
|
||||
|
||||
// Shouldn't happen
|
||||
console.error( 'Internal error: segment does not intersect plane.' );
|
||||
output.segmentedObject1 = null;
|
||||
output.segmentedObject2 = null;
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
points1.push( intersection );
|
||||
points2.push( intersection.clone() );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Calculate debris mass (very fast and imprecise):
|
||||
const newMass = object.userData.mass * 0.5;
|
||||
|
||||
// Calculate debris Center of Mass (again fast and imprecise)
|
||||
this.tempCM1.set( 0, 0, 0 );
|
||||
let radius1 = 0;
|
||||
const numPoints1 = points1.length;
|
||||
|
||||
if ( numPoints1 > 0 ) {
|
||||
|
||||
for ( let i = 0; i < numPoints1; i ++ ) this.tempCM1.add( points1[ i ] );
|
||||
|
||||
this.tempCM1.divideScalar( numPoints1 );
|
||||
for ( let i = 0; i < numPoints1; i ++ ) {
|
||||
|
||||
const p = points1[ i ];
|
||||
p.sub( this.tempCM1 );
|
||||
radius1 = Math.max( radius1, p.x, p.y, p.z );
|
||||
|
||||
}
|
||||
|
||||
this.tempCM1.add( object.position );
|
||||
|
||||
}
|
||||
|
||||
this.tempCM2.set( 0, 0, 0 );
|
||||
let radius2 = 0;
|
||||
const numPoints2 = points2.length;
|
||||
if ( numPoints2 > 0 ) {
|
||||
|
||||
for ( let i = 0; i < numPoints2; i ++ ) this.tempCM2.add( points2[ i ] );
|
||||
|
||||
this.tempCM2.divideScalar( numPoints2 );
|
||||
for ( let i = 0; i < numPoints2; i ++ ) {
|
||||
|
||||
const p = points2[ i ];
|
||||
p.sub( this.tempCM2 );
|
||||
radius2 = Math.max( radius2, p.x, p.y, p.z );
|
||||
|
||||
}
|
||||
|
||||
this.tempCM2.add( object.position );
|
||||
|
||||
}
|
||||
|
||||
let object1 = null;
|
||||
let object2 = null;
|
||||
|
||||
let numObjects = 0;
|
||||
|
||||
if ( numPoints1 > 4 ) {
|
||||
|
||||
object1 = new Mesh( new ConvexGeometry( points1 ), object.material );
|
||||
object1.position.copy( this.tempCM1 );
|
||||
object1.quaternion.copy( object.quaternion );
|
||||
|
||||
this.prepareBreakableObject( object1, newMass, object.userData.velocity, object.userData.angularVelocity, 2 * radius1 > this.minSizeForBreak );
|
||||
|
||||
numObjects ++;
|
||||
|
||||
}
|
||||
|
||||
if ( numPoints2 > 4 ) {
|
||||
|
||||
object2 = new Mesh( new ConvexGeometry( points2 ), object.material );
|
||||
object2.position.copy( this.tempCM2 );
|
||||
object2.quaternion.copy( object.quaternion );
|
||||
|
||||
this.prepareBreakableObject( object2, newMass, object.userData.velocity, object.userData.angularVelocity, 2 * radius2 > this.minSizeForBreak );
|
||||
|
||||
numObjects ++;
|
||||
|
||||
}
|
||||
|
||||
output.object1 = object1;
|
||||
output.object2 = object2;
|
||||
|
||||
return numObjects;
|
||||
|
||||
}
|
||||
|
||||
static transformFreeVector( v, m ) {
|
||||
|
||||
// input:
|
||||
// vector interpreted as a free vector
|
||||
// THREE.Matrix4 orthogonal matrix (matrix without scale)
|
||||
|
||||
const x = v.x, y = v.y, z = v.z;
|
||||
const e = m.elements;
|
||||
|
||||
v.x = e[ 0 ] * x + e[ 4 ] * y + e[ 8 ] * z;
|
||||
v.y = e[ 1 ] * x + e[ 5 ] * y + e[ 9 ] * z;
|
||||
v.z = e[ 2 ] * x + e[ 6 ] * y + e[ 10 ] * z;
|
||||
|
||||
return v;
|
||||
|
||||
}
|
||||
|
||||
static transformFreeVectorInverse( v, m ) {
|
||||
|
||||
// input:
|
||||
// vector interpreted as a free vector
|
||||
// THREE.Matrix4 orthogonal matrix (matrix without scale)
|
||||
|
||||
const x = v.x, y = v.y, z = v.z;
|
||||
const e = m.elements;
|
||||
|
||||
v.x = e[ 0 ] * x + e[ 1 ] * y + e[ 2 ] * z;
|
||||
v.y = e[ 4 ] * x + e[ 5 ] * y + e[ 6 ] * z;
|
||||
v.z = e[ 8 ] * x + e[ 9 ] * y + e[ 10 ] * z;
|
||||
|
||||
return v;
|
||||
|
||||
}
|
||||
|
||||
static transformTiedVectorInverse( v, m ) {
|
||||
|
||||
// input:
|
||||
// vector interpreted as a tied (ordinary) vector
|
||||
// THREE.Matrix4 orthogonal matrix (matrix without scale)
|
||||
|
||||
const x = v.x, y = v.y, z = v.z;
|
||||
const e = m.elements;
|
||||
|
||||
v.x = e[ 0 ] * x + e[ 1 ] * y + e[ 2 ] * z - e[ 12 ];
|
||||
v.y = e[ 4 ] * x + e[ 5 ] * y + e[ 6 ] * z - e[ 13 ];
|
||||
v.z = e[ 8 ] * x + e[ 9 ] * y + e[ 10 ] * z - e[ 14 ];
|
||||
|
||||
return v;
|
||||
|
||||
}
|
||||
|
||||
static transformPlaneToLocalSpace( plane, m, resultPlane ) {
|
||||
|
||||
resultPlane.normal.copy( plane.normal );
|
||||
resultPlane.constant = plane.constant;
|
||||
|
||||
const referencePoint = ConvexObjectBreaker.transformTiedVectorInverse( plane.coplanarPoint( _v1 ), m );
|
||||
|
||||
ConvexObjectBreaker.transformFreeVectorInverse( resultPlane.normal, m );
|
||||
|
||||
// recalculate constant (like in setFromNormalAndCoplanarPoint)
|
||||
resultPlane.constant = - referencePoint.dot( resultPlane.normal );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export { ConvexObjectBreaker };
|
440
dist/electron/static/sdk/three/jsm/misc/GPUComputationRenderer.js
vendored
Normal file
440
dist/electron/static/sdk/three/jsm/misc/GPUComputationRenderer.js
vendored
Normal file
@ -0,0 +1,440 @@
|
||||
import {
|
||||
Camera,
|
||||
ClampToEdgeWrapping,
|
||||
DataTexture,
|
||||
FloatType,
|
||||
Mesh,
|
||||
NearestFilter,
|
||||
PlaneGeometry,
|
||||
RGBAFormat,
|
||||
Scene,
|
||||
ShaderMaterial,
|
||||
WebGLRenderTarget
|
||||
} from 'three';
|
||||
|
||||
/**
|
||||
* GPUComputationRenderer, based on SimulationRenderer by zz85
|
||||
*
|
||||
* The GPUComputationRenderer uses the concept of variables. These variables are RGBA float textures that hold 4 floats
|
||||
* for each compute element (texel)
|
||||
*
|
||||
* Each variable has a fragment shader that defines the computation made to obtain the variable in question.
|
||||
* You can use as many variables you need, and make dependencies so you can use textures of other variables in the shader
|
||||
* (the sampler uniforms are added automatically) Most of the variables will need themselves as dependency.
|
||||
*
|
||||
* The renderer has actually two render targets per variable, to make ping-pong. Textures from the current frame are used
|
||||
* as inputs to render the textures of the next frame.
|
||||
*
|
||||
* The render targets of the variables can be used as input textures for your visualization shaders.
|
||||
*
|
||||
* Variable names should be valid identifiers and should not collide with THREE GLSL used identifiers.
|
||||
* a common approach could be to use 'texture' prefixing the variable name; i.e texturePosition, textureVelocity...
|
||||
*
|
||||
* The size of the computation (sizeX * sizeY) is defined as 'resolution' automatically in the shader. For example:
|
||||
* #DEFINE resolution vec2( 1024.0, 1024.0 )
|
||||
*
|
||||
* -------------
|
||||
*
|
||||
* Basic use:
|
||||
*
|
||||
* // Initialization...
|
||||
*
|
||||
* // Create computation renderer
|
||||
* const gpuCompute = new GPUComputationRenderer( 1024, 1024, renderer );
|
||||
*
|
||||
* // Create initial state float textures
|
||||
* const pos0 = gpuCompute.createTexture();
|
||||
* const vel0 = gpuCompute.createTexture();
|
||||
* // and fill in here the texture data...
|
||||
*
|
||||
* // Add texture variables
|
||||
* const velVar = gpuCompute.addVariable( "textureVelocity", fragmentShaderVel, pos0 );
|
||||
* const posVar = gpuCompute.addVariable( "texturePosition", fragmentShaderPos, vel0 );
|
||||
*
|
||||
* // Add variable dependencies
|
||||
* gpuCompute.setVariableDependencies( velVar, [ velVar, posVar ] );
|
||||
* gpuCompute.setVariableDependencies( posVar, [ velVar, posVar ] );
|
||||
*
|
||||
* // Add custom uniforms
|
||||
* velVar.material.uniforms.time = { value: 0.0 };
|
||||
*
|
||||
* // Check for completeness
|
||||
* const error = gpuCompute.init();
|
||||
* if ( error !== null ) {
|
||||
* console.error( error );
|
||||
* }
|
||||
*
|
||||
*
|
||||
* // In each frame...
|
||||
*
|
||||
* // Compute!
|
||||
* gpuCompute.compute();
|
||||
*
|
||||
* // Update texture uniforms in your visualization materials with the gpu renderer output
|
||||
* myMaterial.uniforms.myTexture.value = gpuCompute.getCurrentRenderTarget( posVar ).texture;
|
||||
*
|
||||
* // Do your rendering
|
||||
* renderer.render( myScene, myCamera );
|
||||
*
|
||||
* -------------
|
||||
*
|
||||
* Also, you can use utility functions to create ShaderMaterial and perform computations (rendering between textures)
|
||||
* Note that the shaders can have multiple input textures.
|
||||
*
|
||||
* const myFilter1 = gpuCompute.createShaderMaterial( myFilterFragmentShader1, { theTexture: { value: null } } );
|
||||
* const myFilter2 = gpuCompute.createShaderMaterial( myFilterFragmentShader2, { theTexture: { value: null } } );
|
||||
*
|
||||
* const inputTexture = gpuCompute.createTexture();
|
||||
*
|
||||
* // Fill in here inputTexture...
|
||||
*
|
||||
* myFilter1.uniforms.theTexture.value = inputTexture;
|
||||
*
|
||||
* const myRenderTarget = gpuCompute.createRenderTarget();
|
||||
* myFilter2.uniforms.theTexture.value = myRenderTarget.texture;
|
||||
*
|
||||
* const outputRenderTarget = gpuCompute.createRenderTarget();
|
||||
*
|
||||
* // Now use the output texture where you want:
|
||||
* myMaterial.uniforms.map.value = outputRenderTarget.texture;
|
||||
*
|
||||
* // And compute each frame, before rendering to screen:
|
||||
* gpuCompute.doRenderTarget( myFilter1, myRenderTarget );
|
||||
* gpuCompute.doRenderTarget( myFilter2, outputRenderTarget );
|
||||
*
|
||||
*
|
||||
*
|
||||
* @param {int} sizeX Computation problem size is always 2d: sizeX * sizeY elements.
|
||||
* @param {int} sizeY Computation problem size is always 2d: sizeX * sizeY elements.
|
||||
* @param {WebGLRenderer} renderer The renderer
|
||||
*/
|
||||
|
||||
class GPUComputationRenderer {
|
||||
|
||||
constructor( sizeX, sizeY, renderer ) {
|
||||
|
||||
this.variables = [];
|
||||
|
||||
this.currentTextureIndex = 0;
|
||||
|
||||
let dataType = FloatType;
|
||||
|
||||
const scene = new Scene();
|
||||
|
||||
const camera = new Camera();
|
||||
camera.position.z = 1;
|
||||
|
||||
const passThruUniforms = {
|
||||
passThruTexture: { value: null }
|
||||
};
|
||||
|
||||
const passThruShader = createShaderMaterial( getPassThroughFragmentShader(), passThruUniforms );
|
||||
|
||||
const mesh = new Mesh( new PlaneGeometry( 2, 2 ), passThruShader );
|
||||
scene.add( mesh );
|
||||
|
||||
|
||||
this.setDataType = function ( type ) {
|
||||
|
||||
dataType = type;
|
||||
return this;
|
||||
|
||||
};
|
||||
|
||||
this.addVariable = function ( variableName, computeFragmentShader, initialValueTexture ) {
|
||||
|
||||
const material = this.createShaderMaterial( computeFragmentShader );
|
||||
|
||||
const variable = {
|
||||
name: variableName,
|
||||
initialValueTexture: initialValueTexture,
|
||||
material: material,
|
||||
dependencies: null,
|
||||
renderTargets: [],
|
||||
wrapS: null,
|
||||
wrapT: null,
|
||||
minFilter: NearestFilter,
|
||||
magFilter: NearestFilter
|
||||
};
|
||||
|
||||
this.variables.push( variable );
|
||||
|
||||
return variable;
|
||||
|
||||
};
|
||||
|
||||
this.setVariableDependencies = function ( variable, dependencies ) {
|
||||
|
||||
variable.dependencies = dependencies;
|
||||
|
||||
};
|
||||
|
||||
this.init = function () {
|
||||
|
||||
if ( renderer.capabilities.maxVertexTextures === 0 ) {
|
||||
|
||||
return 'No support for vertex shader textures.';
|
||||
|
||||
}
|
||||
|
||||
for ( let i = 0; i < this.variables.length; i ++ ) {
|
||||
|
||||
const variable = this.variables[ i ];
|
||||
|
||||
// Creates rendertargets and initialize them with input texture
|
||||
variable.renderTargets[ 0 ] = this.createRenderTarget( sizeX, sizeY, variable.wrapS, variable.wrapT, variable.minFilter, variable.magFilter );
|
||||
variable.renderTargets[ 1 ] = this.createRenderTarget( sizeX, sizeY, variable.wrapS, variable.wrapT, variable.minFilter, variable.magFilter );
|
||||
this.renderTexture( variable.initialValueTexture, variable.renderTargets[ 0 ] );
|
||||
this.renderTexture( variable.initialValueTexture, variable.renderTargets[ 1 ] );
|
||||
|
||||
// Adds dependencies uniforms to the ShaderMaterial
|
||||
const material = variable.material;
|
||||
const uniforms = material.uniforms;
|
||||
|
||||
if ( variable.dependencies !== null ) {
|
||||
|
||||
for ( let d = 0; d < variable.dependencies.length; d ++ ) {
|
||||
|
||||
const depVar = variable.dependencies[ d ];
|
||||
|
||||
if ( depVar.name !== variable.name ) {
|
||||
|
||||
// Checks if variable exists
|
||||
let found = false;
|
||||
|
||||
for ( let j = 0; j < this.variables.length; j ++ ) {
|
||||
|
||||
if ( depVar.name === this.variables[ j ].name ) {
|
||||
|
||||
found = true;
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if ( ! found ) {
|
||||
|
||||
return 'Variable dependency not found. Variable=' + variable.name + ', dependency=' + depVar.name;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
uniforms[ depVar.name ] = { value: null };
|
||||
|
||||
material.fragmentShader = '\nuniform sampler2D ' + depVar.name + ';\n' + material.fragmentShader;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
this.currentTextureIndex = 0;
|
||||
|
||||
return null;
|
||||
|
||||
};
|
||||
|
||||
this.compute = function () {
|
||||
|
||||
const currentTextureIndex = this.currentTextureIndex;
|
||||
const nextTextureIndex = this.currentTextureIndex === 0 ? 1 : 0;
|
||||
|
||||
for ( let i = 0, il = this.variables.length; i < il; i ++ ) {
|
||||
|
||||
const variable = this.variables[ i ];
|
||||
|
||||
// Sets texture dependencies uniforms
|
||||
if ( variable.dependencies !== null ) {
|
||||
|
||||
const uniforms = variable.material.uniforms;
|
||||
|
||||
for ( let d = 0, dl = variable.dependencies.length; d < dl; d ++ ) {
|
||||
|
||||
const depVar = variable.dependencies[ d ];
|
||||
|
||||
uniforms[ depVar.name ].value = depVar.renderTargets[ currentTextureIndex ].texture;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Performs the computation for this variable
|
||||
this.doRenderTarget( variable.material, variable.renderTargets[ nextTextureIndex ] );
|
||||
|
||||
}
|
||||
|
||||
this.currentTextureIndex = nextTextureIndex;
|
||||
|
||||
};
|
||||
|
||||
this.getCurrentRenderTarget = function ( variable ) {
|
||||
|
||||
return variable.renderTargets[ this.currentTextureIndex ];
|
||||
|
||||
};
|
||||
|
||||
this.getAlternateRenderTarget = function ( variable ) {
|
||||
|
||||
return variable.renderTargets[ this.currentTextureIndex === 0 ? 1 : 0 ];
|
||||
|
||||
};
|
||||
|
||||
this.dispose = function () {
|
||||
|
||||
mesh.geometry.dispose();
|
||||
mesh.material.dispose();
|
||||
|
||||
const variables = this.variables;
|
||||
|
||||
for ( let i = 0; i < variables.length; i ++ ) {
|
||||
|
||||
const variable = variables[ i ];
|
||||
|
||||
if ( variable.initialValueTexture ) variable.initialValueTexture.dispose();
|
||||
|
||||
const renderTargets = variable.renderTargets;
|
||||
|
||||
for ( let j = 0; j < renderTargets.length; j ++ ) {
|
||||
|
||||
const renderTarget = renderTargets[ j ];
|
||||
renderTarget.dispose();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
function addResolutionDefine( materialShader ) {
|
||||
|
||||
materialShader.defines.resolution = 'vec2( ' + sizeX.toFixed( 1 ) + ', ' + sizeY.toFixed( 1 ) + ' )';
|
||||
|
||||
}
|
||||
|
||||
this.addResolutionDefine = addResolutionDefine;
|
||||
|
||||
|
||||
// The following functions can be used to compute things manually
|
||||
|
||||
function createShaderMaterial( computeFragmentShader, uniforms ) {
|
||||
|
||||
uniforms = uniforms || {};
|
||||
|
||||
const material = new ShaderMaterial( {
|
||||
name: 'GPUComputationShader',
|
||||
uniforms: uniforms,
|
||||
vertexShader: getPassThroughVertexShader(),
|
||||
fragmentShader: computeFragmentShader
|
||||
} );
|
||||
|
||||
addResolutionDefine( material );
|
||||
|
||||
return material;
|
||||
|
||||
}
|
||||
|
||||
this.createShaderMaterial = createShaderMaterial;
|
||||
|
||||
this.createRenderTarget = function ( sizeXTexture, sizeYTexture, wrapS, wrapT, minFilter, magFilter ) {
|
||||
|
||||
sizeXTexture = sizeXTexture || sizeX;
|
||||
sizeYTexture = sizeYTexture || sizeY;
|
||||
|
||||
wrapS = wrapS || ClampToEdgeWrapping;
|
||||
wrapT = wrapT || ClampToEdgeWrapping;
|
||||
|
||||
minFilter = minFilter || NearestFilter;
|
||||
magFilter = magFilter || NearestFilter;
|
||||
|
||||
const renderTarget = new WebGLRenderTarget( sizeXTexture, sizeYTexture, {
|
||||
wrapS: wrapS,
|
||||
wrapT: wrapT,
|
||||
minFilter: minFilter,
|
||||
magFilter: magFilter,
|
||||
format: RGBAFormat,
|
||||
type: dataType,
|
||||
depthBuffer: false
|
||||
} );
|
||||
|
||||
return renderTarget;
|
||||
|
||||
};
|
||||
|
||||
this.createTexture = function () {
|
||||
|
||||
const data = new Float32Array( sizeX * sizeY * 4 );
|
||||
const texture = new DataTexture( data, sizeX, sizeY, RGBAFormat, FloatType );
|
||||
texture.needsUpdate = true;
|
||||
return texture;
|
||||
|
||||
};
|
||||
|
||||
this.renderTexture = function ( input, output ) {
|
||||
|
||||
// Takes a texture, and render out in rendertarget
|
||||
// input = Texture
|
||||
// output = RenderTarget
|
||||
|
||||
passThruUniforms.passThruTexture.value = input;
|
||||
|
||||
this.doRenderTarget( passThruShader, output );
|
||||
|
||||
passThruUniforms.passThruTexture.value = null;
|
||||
|
||||
};
|
||||
|
||||
this.doRenderTarget = function ( material, output ) {
|
||||
|
||||
const currentRenderTarget = renderer.getRenderTarget();
|
||||
|
||||
const currentXrEnabled = renderer.xr.enabled;
|
||||
const currentShadowAutoUpdate = renderer.shadowMap.autoUpdate;
|
||||
|
||||
renderer.xr.enabled = false; // Avoid camera modification
|
||||
renderer.shadowMap.autoUpdate = false; // Avoid re-computing shadows
|
||||
mesh.material = material;
|
||||
renderer.setRenderTarget( output );
|
||||
renderer.render( scene, camera );
|
||||
mesh.material = passThruShader;
|
||||
|
||||
renderer.xr.enabled = currentXrEnabled;
|
||||
renderer.shadowMap.autoUpdate = currentShadowAutoUpdate;
|
||||
|
||||
renderer.setRenderTarget( currentRenderTarget );
|
||||
|
||||
};
|
||||
|
||||
// Shaders
|
||||
|
||||
function getPassThroughVertexShader() {
|
||||
|
||||
return 'void main() {\n' +
|
||||
'\n' +
|
||||
' gl_Position = vec4( position, 1.0 );\n' +
|
||||
'\n' +
|
||||
'}\n';
|
||||
|
||||
}
|
||||
|
||||
function getPassThroughFragmentShader() {
|
||||
|
||||
return 'uniform sampler2D passThruTexture;\n' +
|
||||
'\n' +
|
||||
'void main() {\n' +
|
||||
'\n' +
|
||||
' vec2 uv = gl_FragCoord.xy / resolution.xy;\n' +
|
||||
'\n' +
|
||||
' gl_FragColor = texture2D( passThruTexture, uv );\n' +
|
||||
'\n' +
|
||||
'}\n';
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export { GPUComputationRenderer };
|
66
dist/electron/static/sdk/three/jsm/misc/Gyroscope.js
vendored
Normal file
66
dist/electron/static/sdk/three/jsm/misc/Gyroscope.js
vendored
Normal file
@ -0,0 +1,66 @@
|
||||
import {
|
||||
Object3D,
|
||||
Quaternion,
|
||||
Vector3
|
||||
} from 'three';
|
||||
|
||||
const _translationObject = new Vector3();
|
||||
const _quaternionObject = new Quaternion();
|
||||
const _scaleObject = new Vector3();
|
||||
|
||||
const _translationWorld = new Vector3();
|
||||
const _quaternionWorld = new Quaternion();
|
||||
const _scaleWorld = new Vector3();
|
||||
|
||||
class Gyroscope extends Object3D {
|
||||
|
||||
constructor() {
|
||||
|
||||
super();
|
||||
|
||||
}
|
||||
|
||||
updateMatrixWorld( force ) {
|
||||
|
||||
this.matrixAutoUpdate && this.updateMatrix();
|
||||
|
||||
// update matrixWorld
|
||||
|
||||
if ( this.matrixWorldNeedsUpdate || force ) {
|
||||
|
||||
if ( this.parent !== null ) {
|
||||
|
||||
this.matrixWorld.multiplyMatrices( this.parent.matrixWorld, this.matrix );
|
||||
|
||||
this.matrixWorld.decompose( _translationWorld, _quaternionWorld, _scaleWorld );
|
||||
this.matrix.decompose( _translationObject, _quaternionObject, _scaleObject );
|
||||
|
||||
this.matrixWorld.compose( _translationWorld, _quaternionObject, _scaleWorld );
|
||||
|
||||
|
||||
} else {
|
||||
|
||||
this.matrixWorld.copy( this.matrix );
|
||||
|
||||
}
|
||||
|
||||
|
||||
this.matrixWorldNeedsUpdate = false;
|
||||
|
||||
force = true;
|
||||
|
||||
}
|
||||
|
||||
// update children
|
||||
|
||||
for ( let i = 0, l = this.children.length; i < l; i ++ ) {
|
||||
|
||||
this.children[ i ].updateMatrixWorld( force );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export { Gyroscope };
|
276
dist/electron/static/sdk/three/jsm/misc/MD2Character.js
vendored
Normal file
276
dist/electron/static/sdk/three/jsm/misc/MD2Character.js
vendored
Normal file
@ -0,0 +1,276 @@
|
||||
import {
|
||||
AnimationMixer,
|
||||
Box3,
|
||||
Mesh,
|
||||
MeshLambertMaterial,
|
||||
Object3D,
|
||||
TextureLoader,
|
||||
UVMapping,
|
||||
SRGBColorSpace
|
||||
} from 'three';
|
||||
import { MD2Loader } from '../loaders/MD2Loader.js';
|
||||
|
||||
class MD2Character {
|
||||
|
||||
constructor() {
|
||||
|
||||
this.scale = 1;
|
||||
this.animationFPS = 6;
|
||||
|
||||
this.root = new Object3D();
|
||||
|
||||
this.meshBody = null;
|
||||
this.meshWeapon = null;
|
||||
|
||||
this.skinsBody = [];
|
||||
this.skinsWeapon = [];
|
||||
|
||||
this.weapons = [];
|
||||
|
||||
this.activeAnimation = null;
|
||||
|
||||
this.mixer = null;
|
||||
|
||||
this.onLoadComplete = function () {};
|
||||
|
||||
this.loadCounter = 0;
|
||||
|
||||
}
|
||||
|
||||
loadParts( config ) {
|
||||
|
||||
const scope = this;
|
||||
|
||||
function createPart( geometry, skinMap ) {
|
||||
|
||||
const materialWireframe = new MeshLambertMaterial( { color: 0xffaa00, wireframe: true } );
|
||||
const materialTexture = new MeshLambertMaterial( { color: 0xffffff, wireframe: false, map: skinMap } );
|
||||
|
||||
//
|
||||
|
||||
const mesh = new Mesh( geometry, materialTexture );
|
||||
mesh.rotation.y = - Math.PI / 2;
|
||||
|
||||
mesh.castShadow = true;
|
||||
mesh.receiveShadow = true;
|
||||
|
||||
//
|
||||
|
||||
mesh.materialTexture = materialTexture;
|
||||
mesh.materialWireframe = materialWireframe;
|
||||
|
||||
return mesh;
|
||||
|
||||
}
|
||||
|
||||
function loadTextures( baseUrl, textureUrls ) {
|
||||
|
||||
const textureLoader = new TextureLoader();
|
||||
const textures = [];
|
||||
|
||||
for ( let i = 0; i < textureUrls.length; i ++ ) {
|
||||
|
||||
textures[ i ] = textureLoader.load( baseUrl + textureUrls[ i ], checkLoadingComplete );
|
||||
textures[ i ].mapping = UVMapping;
|
||||
textures[ i ].name = textureUrls[ i ];
|
||||
textures[ i ].colorSpace = SRGBColorSpace;
|
||||
|
||||
}
|
||||
|
||||
return textures;
|
||||
|
||||
}
|
||||
|
||||
function checkLoadingComplete() {
|
||||
|
||||
scope.loadCounter -= 1;
|
||||
|
||||
if ( scope.loadCounter === 0 ) scope.onLoadComplete();
|
||||
|
||||
}
|
||||
|
||||
this.loadCounter = config.weapons.length * 2 + config.skins.length + 1;
|
||||
|
||||
const weaponsTextures = [];
|
||||
for ( let i = 0; i < config.weapons.length; i ++ ) weaponsTextures[ i ] = config.weapons[ i ][ 1 ];
|
||||
// SKINS
|
||||
|
||||
this.skinsBody = loadTextures( config.baseUrl + 'skins/', config.skins );
|
||||
this.skinsWeapon = loadTextures( config.baseUrl + 'skins/', weaponsTextures );
|
||||
|
||||
// BODY
|
||||
|
||||
const loader = new MD2Loader();
|
||||
|
||||
loader.load( config.baseUrl + config.body, function ( geo ) {
|
||||
|
||||
const boundingBox = new Box3();
|
||||
boundingBox.setFromBufferAttribute( geo.attributes.position );
|
||||
|
||||
scope.root.position.y = - scope.scale * boundingBox.min.y;
|
||||
|
||||
const mesh = createPart( geo, scope.skinsBody[ 0 ] );
|
||||
mesh.scale.set( scope.scale, scope.scale, scope.scale );
|
||||
|
||||
scope.root.add( mesh );
|
||||
|
||||
scope.meshBody = mesh;
|
||||
|
||||
scope.meshBody.clipOffset = 0;
|
||||
scope.activeAnimationClipName = mesh.geometry.animations[ 0 ].name;
|
||||
|
||||
scope.mixer = new AnimationMixer( mesh );
|
||||
|
||||
checkLoadingComplete();
|
||||
|
||||
} );
|
||||
|
||||
// WEAPONS
|
||||
|
||||
const generateCallback = function ( index, name ) {
|
||||
|
||||
return function ( geo ) {
|
||||
|
||||
const mesh = createPart( geo, scope.skinsWeapon[ index ] );
|
||||
mesh.scale.set( scope.scale, scope.scale, scope.scale );
|
||||
mesh.visible = false;
|
||||
|
||||
mesh.name = name;
|
||||
|
||||
scope.root.add( mesh );
|
||||
|
||||
scope.weapons[ index ] = mesh;
|
||||
scope.meshWeapon = mesh;
|
||||
|
||||
checkLoadingComplete();
|
||||
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
for ( let i = 0; i < config.weapons.length; i ++ ) {
|
||||
|
||||
loader.load( config.baseUrl + config.weapons[ i ][ 0 ], generateCallback( i, config.weapons[ i ][ 0 ] ) );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
setPlaybackRate( rate ) {
|
||||
|
||||
if ( rate !== 0 ) {
|
||||
|
||||
this.mixer.timeScale = 1 / rate;
|
||||
|
||||
} else {
|
||||
|
||||
this.mixer.timeScale = 0;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
setWireframe( wireframeEnabled ) {
|
||||
|
||||
if ( wireframeEnabled ) {
|
||||
|
||||
if ( this.meshBody ) this.meshBody.material = this.meshBody.materialWireframe;
|
||||
if ( this.meshWeapon ) this.meshWeapon.material = this.meshWeapon.materialWireframe;
|
||||
|
||||
} else {
|
||||
|
||||
if ( this.meshBody ) this.meshBody.material = this.meshBody.materialTexture;
|
||||
if ( this.meshWeapon ) this.meshWeapon.material = this.meshWeapon.materialTexture;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
setSkin( index ) {
|
||||
|
||||
if ( this.meshBody && this.meshBody.material.wireframe === false ) {
|
||||
|
||||
this.meshBody.material.map = this.skinsBody[ index ];
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
setWeapon( index ) {
|
||||
|
||||
for ( let i = 0; i < this.weapons.length; i ++ ) this.weapons[ i ].visible = false;
|
||||
|
||||
const activeWeapon = this.weapons[ index ];
|
||||
|
||||
if ( activeWeapon ) {
|
||||
|
||||
activeWeapon.visible = true;
|
||||
this.meshWeapon = activeWeapon;
|
||||
|
||||
this.syncWeaponAnimation();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
setAnimation( clipName ) {
|
||||
|
||||
if ( this.meshBody ) {
|
||||
|
||||
if ( this.meshBody.activeAction ) {
|
||||
|
||||
this.meshBody.activeAction.stop();
|
||||
this.meshBody.activeAction = null;
|
||||
|
||||
}
|
||||
|
||||
const action = this.mixer.clipAction( clipName, this.meshBody );
|
||||
|
||||
if ( action ) {
|
||||
|
||||
this.meshBody.activeAction = action.play();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
this.activeClipName = clipName;
|
||||
|
||||
this.syncWeaponAnimation();
|
||||
|
||||
}
|
||||
|
||||
syncWeaponAnimation() {
|
||||
|
||||
const clipName = this.activeClipName;
|
||||
|
||||
if ( this.meshWeapon ) {
|
||||
|
||||
if ( this.meshWeapon.activeAction ) {
|
||||
|
||||
this.meshWeapon.activeAction.stop();
|
||||
this.meshWeapon.activeAction = null;
|
||||
|
||||
}
|
||||
|
||||
const action = this.mixer.clipAction( clipName, this.meshWeapon );
|
||||
|
||||
if ( action ) {
|
||||
|
||||
this.meshWeapon.activeAction = action.syncWith( this.meshBody.activeAction ).play();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
update( delta ) {
|
||||
|
||||
if ( this.mixer ) this.mixer.update( delta );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export { MD2Character };
|
576
dist/electron/static/sdk/three/jsm/misc/MD2CharacterComplex.js
vendored
Normal file
576
dist/electron/static/sdk/three/jsm/misc/MD2CharacterComplex.js
vendored
Normal file
@ -0,0 +1,576 @@
|
||||
import {
|
||||
Box3,
|
||||
MathUtils,
|
||||
MeshLambertMaterial,
|
||||
Object3D,
|
||||
TextureLoader,
|
||||
UVMapping,
|
||||
SRGBColorSpace
|
||||
} from 'three';
|
||||
import { MD2Loader } from '../loaders/MD2Loader.js';
|
||||
import { MorphBlendMesh } from '../misc/MorphBlendMesh.js';
|
||||
|
||||
class MD2CharacterComplex {
|
||||
|
||||
constructor() {
|
||||
|
||||
this.scale = 1;
|
||||
|
||||
// animation parameters
|
||||
|
||||
this.animationFPS = 6;
|
||||
this.transitionFrames = 15;
|
||||
|
||||
// movement model parameters
|
||||
|
||||
this.maxSpeed = 275;
|
||||
this.maxReverseSpeed = - 275;
|
||||
|
||||
this.frontAcceleration = 600;
|
||||
this.backAcceleration = 600;
|
||||
|
||||
this.frontDecceleration = 600;
|
||||
|
||||
this.angularSpeed = 2.5;
|
||||
|
||||
// rig
|
||||
|
||||
this.root = new Object3D();
|
||||
|
||||
this.meshBody = null;
|
||||
this.meshWeapon = null;
|
||||
|
||||
this.controls = null;
|
||||
|
||||
// skins
|
||||
|
||||
this.skinsBody = [];
|
||||
this.skinsWeapon = [];
|
||||
|
||||
this.weapons = [];
|
||||
|
||||
this.currentSkin = undefined;
|
||||
|
||||
//
|
||||
|
||||
this.onLoadComplete = function () {};
|
||||
|
||||
// internals
|
||||
|
||||
this.meshes = [];
|
||||
this.animations = {};
|
||||
|
||||
this.loadCounter = 0;
|
||||
|
||||
// internal movement control variables
|
||||
|
||||
this.speed = 0;
|
||||
this.bodyOrientation = 0;
|
||||
|
||||
this.walkSpeed = this.maxSpeed;
|
||||
this.crouchSpeed = this.maxSpeed * 0.5;
|
||||
|
||||
// internal animation parameters
|
||||
|
||||
this.activeAnimation = null;
|
||||
this.oldAnimation = null;
|
||||
|
||||
// API
|
||||
|
||||
}
|
||||
|
||||
enableShadows( enable ) {
|
||||
|
||||
for ( let i = 0; i < this.meshes.length; i ++ ) {
|
||||
|
||||
this.meshes[ i ].castShadow = enable;
|
||||
this.meshes[ i ].receiveShadow = enable;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
setVisible( enable ) {
|
||||
|
||||
for ( let i = 0; i < this.meshes.length; i ++ ) {
|
||||
|
||||
this.meshes[ i ].visible = enable;
|
||||
this.meshes[ i ].visible = enable;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
shareParts( original ) {
|
||||
|
||||
this.animations = original.animations;
|
||||
this.walkSpeed = original.walkSpeed;
|
||||
this.crouchSpeed = original.crouchSpeed;
|
||||
|
||||
this.skinsBody = original.skinsBody;
|
||||
this.skinsWeapon = original.skinsWeapon;
|
||||
|
||||
// BODY
|
||||
|
||||
const mesh = this._createPart( original.meshBody.geometry, this.skinsBody[ 0 ] );
|
||||
mesh.scale.set( this.scale, this.scale, this.scale );
|
||||
|
||||
this.root.position.y = original.root.position.y;
|
||||
this.root.add( mesh );
|
||||
|
||||
this.meshBody = mesh;
|
||||
|
||||
this.meshes.push( mesh );
|
||||
|
||||
// WEAPONS
|
||||
|
||||
for ( let i = 0; i < original.weapons.length; i ++ ) {
|
||||
|
||||
const meshWeapon = this._createPart( original.weapons[ i ].geometry, this.skinsWeapon[ i ] );
|
||||
meshWeapon.scale.set( this.scale, this.scale, this.scale );
|
||||
meshWeapon.visible = false;
|
||||
|
||||
meshWeapon.name = original.weapons[ i ].name;
|
||||
|
||||
this.root.add( meshWeapon );
|
||||
|
||||
this.weapons[ i ] = meshWeapon;
|
||||
this.meshWeapon = meshWeapon;
|
||||
|
||||
this.meshes.push( meshWeapon );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
loadParts( config ) {
|
||||
|
||||
const scope = this;
|
||||
|
||||
function loadTextures( baseUrl, textureUrls ) {
|
||||
|
||||
const textureLoader = new TextureLoader();
|
||||
const textures = [];
|
||||
|
||||
for ( let i = 0; i < textureUrls.length; i ++ ) {
|
||||
|
||||
textures[ i ] = textureLoader.load( baseUrl + textureUrls[ i ], checkLoadingComplete );
|
||||
textures[ i ].mapping = UVMapping;
|
||||
textures[ i ].name = textureUrls[ i ];
|
||||
textures[ i ].colorSpace = SRGBColorSpace;
|
||||
|
||||
}
|
||||
|
||||
return textures;
|
||||
|
||||
}
|
||||
|
||||
function checkLoadingComplete() {
|
||||
|
||||
scope.loadCounter -= 1;
|
||||
if ( scope.loadCounter === 0 ) scope.onLoadComplete();
|
||||
|
||||
}
|
||||
|
||||
this.animations = config.animations;
|
||||
this.walkSpeed = config.walkSpeed;
|
||||
this.crouchSpeed = config.crouchSpeed;
|
||||
|
||||
this.loadCounter = config.weapons.length * 2 + config.skins.length + 1;
|
||||
|
||||
const weaponsTextures = [];
|
||||
for ( let i = 0; i < config.weapons.length; i ++ ) weaponsTextures[ i ] = config.weapons[ i ][ 1 ];
|
||||
|
||||
// SKINS
|
||||
|
||||
this.skinsBody = loadTextures( config.baseUrl + 'skins/', config.skins );
|
||||
this.skinsWeapon = loadTextures( config.baseUrl + 'skins/', weaponsTextures );
|
||||
|
||||
// BODY
|
||||
|
||||
const loader = new MD2Loader();
|
||||
|
||||
loader.load( config.baseUrl + config.body, function ( geo ) {
|
||||
|
||||
const boundingBox = new Box3();
|
||||
boundingBox.setFromBufferAttribute( geo.attributes.position );
|
||||
|
||||
scope.root.position.y = - scope.scale * boundingBox.min.y;
|
||||
|
||||
const mesh = scope._createPart( geo, scope.skinsBody[ 0 ] );
|
||||
mesh.scale.set( scope.scale, scope.scale, scope.scale );
|
||||
|
||||
scope.root.add( mesh );
|
||||
|
||||
scope.meshBody = mesh;
|
||||
scope.meshes.push( mesh );
|
||||
|
||||
checkLoadingComplete();
|
||||
|
||||
} );
|
||||
|
||||
// WEAPONS
|
||||
|
||||
const generateCallback = function ( index, name ) {
|
||||
|
||||
return function ( geo ) {
|
||||
|
||||
const mesh = scope._createPart( geo, scope.skinsWeapon[ index ] );
|
||||
mesh.scale.set( scope.scale, scope.scale, scope.scale );
|
||||
mesh.visible = false;
|
||||
|
||||
mesh.name = name;
|
||||
|
||||
scope.root.add( mesh );
|
||||
|
||||
scope.weapons[ index ] = mesh;
|
||||
scope.meshWeapon = mesh;
|
||||
scope.meshes.push( mesh );
|
||||
|
||||
checkLoadingComplete();
|
||||
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
for ( let i = 0; i < config.weapons.length; i ++ ) {
|
||||
|
||||
loader.load( config.baseUrl + config.weapons[ i ][ 0 ], generateCallback( i, config.weapons[ i ][ 0 ] ) );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
setPlaybackRate( rate ) {
|
||||
|
||||
if ( this.meshBody ) this.meshBody.duration = this.meshBody.baseDuration / rate;
|
||||
if ( this.meshWeapon ) this.meshWeapon.duration = this.meshWeapon.baseDuration / rate;
|
||||
|
||||
}
|
||||
|
||||
setWireframe( wireframeEnabled ) {
|
||||
|
||||
if ( wireframeEnabled ) {
|
||||
|
||||
if ( this.meshBody ) this.meshBody.material = this.meshBody.materialWireframe;
|
||||
if ( this.meshWeapon ) this.meshWeapon.material = this.meshWeapon.materialWireframe;
|
||||
|
||||
} else {
|
||||
|
||||
if ( this.meshBody ) this.meshBody.material = this.meshBody.materialTexture;
|
||||
if ( this.meshWeapon ) this.meshWeapon.material = this.meshWeapon.materialTexture;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
setSkin( index ) {
|
||||
|
||||
if ( this.meshBody && this.meshBody.material.wireframe === false ) {
|
||||
|
||||
this.meshBody.material.map = this.skinsBody[ index ];
|
||||
this.currentSkin = index;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
setWeapon( index ) {
|
||||
|
||||
for ( let i = 0; i < this.weapons.length; i ++ ) this.weapons[ i ].visible = false;
|
||||
|
||||
const activeWeapon = this.weapons[ index ];
|
||||
|
||||
if ( activeWeapon ) {
|
||||
|
||||
activeWeapon.visible = true;
|
||||
this.meshWeapon = activeWeapon;
|
||||
|
||||
if ( this.activeAnimation ) {
|
||||
|
||||
activeWeapon.playAnimation( this.activeAnimation );
|
||||
this.meshWeapon.setAnimationTime( this.activeAnimation, this.meshBody.getAnimationTime( this.activeAnimation ) );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
setAnimation( animationName ) {
|
||||
|
||||
if ( animationName === this.activeAnimation || ! animationName ) return;
|
||||
|
||||
if ( this.meshBody ) {
|
||||
|
||||
this.meshBody.setAnimationWeight( animationName, 0 );
|
||||
this.meshBody.playAnimation( animationName );
|
||||
|
||||
this.oldAnimation = this.activeAnimation;
|
||||
this.activeAnimation = animationName;
|
||||
|
||||
this.blendCounter = this.transitionFrames;
|
||||
|
||||
}
|
||||
|
||||
if ( this.meshWeapon ) {
|
||||
|
||||
this.meshWeapon.setAnimationWeight( animationName, 0 );
|
||||
this.meshWeapon.playAnimation( animationName );
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
update( delta ) {
|
||||
|
||||
if ( this.controls ) this.updateMovementModel( delta );
|
||||
|
||||
if ( this.animations ) {
|
||||
|
||||
this.updateBehaviors();
|
||||
this.updateAnimations( delta );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
updateAnimations( delta ) {
|
||||
|
||||
let mix = 1;
|
||||
|
||||
if ( this.blendCounter > 0 ) {
|
||||
|
||||
mix = ( this.transitionFrames - this.blendCounter ) / this.transitionFrames;
|
||||
this.blendCounter -= 1;
|
||||
|
||||
}
|
||||
|
||||
if ( this.meshBody ) {
|
||||
|
||||
this.meshBody.update( delta );
|
||||
|
||||
this.meshBody.setAnimationWeight( this.activeAnimation, mix );
|
||||
this.meshBody.setAnimationWeight( this.oldAnimation, 1 - mix );
|
||||
|
||||
}
|
||||
|
||||
if ( this.meshWeapon ) {
|
||||
|
||||
this.meshWeapon.update( delta );
|
||||
|
||||
this.meshWeapon.setAnimationWeight( this.activeAnimation, mix );
|
||||
this.meshWeapon.setAnimationWeight( this.oldAnimation, 1 - mix );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
updateBehaviors() {
|
||||
|
||||
const controls = this.controls;
|
||||
const animations = this.animations;
|
||||
|
||||
let moveAnimation, idleAnimation;
|
||||
|
||||
// crouch vs stand
|
||||
|
||||
if ( controls.crouch ) {
|
||||
|
||||
moveAnimation = animations[ 'crouchMove' ];
|
||||
idleAnimation = animations[ 'crouchIdle' ];
|
||||
|
||||
} else {
|
||||
|
||||
moveAnimation = animations[ 'move' ];
|
||||
idleAnimation = animations[ 'idle' ];
|
||||
|
||||
}
|
||||
|
||||
// actions
|
||||
|
||||
if ( controls.jump ) {
|
||||
|
||||
moveAnimation = animations[ 'jump' ];
|
||||
idleAnimation = animations[ 'jump' ];
|
||||
|
||||
}
|
||||
|
||||
if ( controls.attack ) {
|
||||
|
||||
if ( controls.crouch ) {
|
||||
|
||||
moveAnimation = animations[ 'crouchAttack' ];
|
||||
idleAnimation = animations[ 'crouchAttack' ];
|
||||
|
||||
} else {
|
||||
|
||||
moveAnimation = animations[ 'attack' ];
|
||||
idleAnimation = animations[ 'attack' ];
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// set animations
|
||||
|
||||
if ( controls.moveForward || controls.moveBackward || controls.moveLeft || controls.moveRight ) {
|
||||
|
||||
if ( this.activeAnimation !== moveAnimation ) {
|
||||
|
||||
this.setAnimation( moveAnimation );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
if ( Math.abs( this.speed ) < 0.2 * this.maxSpeed && ! ( controls.moveLeft || controls.moveRight || controls.moveForward || controls.moveBackward ) ) {
|
||||
|
||||
if ( this.activeAnimation !== idleAnimation ) {
|
||||
|
||||
this.setAnimation( idleAnimation );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// set animation direction
|
||||
|
||||
if ( controls.moveForward ) {
|
||||
|
||||
if ( this.meshBody ) {
|
||||
|
||||
this.meshBody.setAnimationDirectionForward( this.activeAnimation );
|
||||
this.meshBody.setAnimationDirectionForward( this.oldAnimation );
|
||||
|
||||
}
|
||||
|
||||
if ( this.meshWeapon ) {
|
||||
|
||||
this.meshWeapon.setAnimationDirectionForward( this.activeAnimation );
|
||||
this.meshWeapon.setAnimationDirectionForward( this.oldAnimation );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if ( controls.moveBackward ) {
|
||||
|
||||
if ( this.meshBody ) {
|
||||
|
||||
this.meshBody.setAnimationDirectionBackward( this.activeAnimation );
|
||||
this.meshBody.setAnimationDirectionBackward( this.oldAnimation );
|
||||
|
||||
}
|
||||
|
||||
if ( this.meshWeapon ) {
|
||||
|
||||
this.meshWeapon.setAnimationDirectionBackward( this.activeAnimation );
|
||||
this.meshWeapon.setAnimationDirectionBackward( this.oldAnimation );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
updateMovementModel( delta ) {
|
||||
|
||||
function exponentialEaseOut( k ) {
|
||||
|
||||
return k === 1 ? 1 : - Math.pow( 2, - 10 * k ) + 1;
|
||||
|
||||
}
|
||||
|
||||
const controls = this.controls;
|
||||
|
||||
// speed based on controls
|
||||
|
||||
if ( controls.crouch ) this.maxSpeed = this.crouchSpeed;
|
||||
else this.maxSpeed = this.walkSpeed;
|
||||
|
||||
this.maxReverseSpeed = - this.maxSpeed;
|
||||
|
||||
if ( controls.moveForward ) this.speed = MathUtils.clamp( this.speed + delta * this.frontAcceleration, this.maxReverseSpeed, this.maxSpeed );
|
||||
if ( controls.moveBackward ) this.speed = MathUtils.clamp( this.speed - delta * this.backAcceleration, this.maxReverseSpeed, this.maxSpeed );
|
||||
|
||||
// orientation based on controls
|
||||
// (don't just stand while turning)
|
||||
|
||||
const dir = 1;
|
||||
|
||||
if ( controls.moveLeft ) {
|
||||
|
||||
this.bodyOrientation += delta * this.angularSpeed;
|
||||
this.speed = MathUtils.clamp( this.speed + dir * delta * this.frontAcceleration, this.maxReverseSpeed, this.maxSpeed );
|
||||
|
||||
}
|
||||
|
||||
if ( controls.moveRight ) {
|
||||
|
||||
this.bodyOrientation -= delta * this.angularSpeed;
|
||||
this.speed = MathUtils.clamp( this.speed + dir * delta * this.frontAcceleration, this.maxReverseSpeed, this.maxSpeed );
|
||||
|
||||
}
|
||||
|
||||
// speed decay
|
||||
|
||||
if ( ! ( controls.moveForward || controls.moveBackward ) ) {
|
||||
|
||||
if ( this.speed > 0 ) {
|
||||
|
||||
const k = exponentialEaseOut( this.speed / this.maxSpeed );
|
||||
this.speed = MathUtils.clamp( this.speed - k * delta * this.frontDecceleration, 0, this.maxSpeed );
|
||||
|
||||
} else {
|
||||
|
||||
const k = exponentialEaseOut( this.speed / this.maxReverseSpeed );
|
||||
this.speed = MathUtils.clamp( this.speed + k * delta * this.backAcceleration, this.maxReverseSpeed, 0 );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// displacement
|
||||
|
||||
const forwardDelta = this.speed * delta;
|
||||
|
||||
this.root.position.x += Math.sin( this.bodyOrientation ) * forwardDelta;
|
||||
this.root.position.z += Math.cos( this.bodyOrientation ) * forwardDelta;
|
||||
|
||||
// steering
|
||||
|
||||
this.root.rotation.y = this.bodyOrientation;
|
||||
|
||||
}
|
||||
|
||||
// internal
|
||||
|
||||
_createPart( geometry, skinMap ) {
|
||||
|
||||
const materialWireframe = new MeshLambertMaterial( { color: 0xffaa00, wireframe: true } );
|
||||
const materialTexture = new MeshLambertMaterial( { color: 0xffffff, wireframe: false, map: skinMap } );
|
||||
|
||||
//
|
||||
|
||||
const mesh = new MorphBlendMesh( geometry, materialTexture );
|
||||
mesh.rotation.y = - Math.PI / 2;
|
||||
|
||||
//
|
||||
|
||||
mesh.materialTexture = materialTexture;
|
||||
mesh.materialWireframe = materialWireframe;
|
||||
|
||||
//
|
||||
|
||||
mesh.autoCreateAnimations( this.animationFPS );
|
||||
|
||||
return mesh;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export { MD2CharacterComplex };
|
75
dist/electron/static/sdk/three/jsm/misc/MorphAnimMesh.js
vendored
Normal file
75
dist/electron/static/sdk/three/jsm/misc/MorphAnimMesh.js
vendored
Normal file
@ -0,0 +1,75 @@
|
||||
import {
|
||||
AnimationClip,
|
||||
AnimationMixer,
|
||||
Mesh
|
||||
} from 'three';
|
||||
|
||||
class MorphAnimMesh extends Mesh {
|
||||
|
||||
constructor( geometry, material ) {
|
||||
|
||||
super( geometry, material );
|
||||
|
||||
this.type = 'MorphAnimMesh';
|
||||
|
||||
this.mixer = new AnimationMixer( this );
|
||||
this.activeAction = null;
|
||||
|
||||
}
|
||||
|
||||
setDirectionForward() {
|
||||
|
||||
this.mixer.timeScale = 1.0;
|
||||
|
||||
}
|
||||
|
||||
setDirectionBackward() {
|
||||
|
||||
this.mixer.timeScale = - 1.0;
|
||||
|
||||
}
|
||||
|
||||
playAnimation( label, fps ) {
|
||||
|
||||
if ( this.activeAction ) {
|
||||
|
||||
this.activeAction.stop();
|
||||
this.activeAction = null;
|
||||
|
||||
}
|
||||
|
||||
const clip = AnimationClip.findByName( this, label );
|
||||
|
||||
if ( clip ) {
|
||||
|
||||
const action = this.mixer.clipAction( clip );
|
||||
action.timeScale = ( clip.tracks.length * fps ) / clip.duration;
|
||||
this.activeAction = action.play();
|
||||
|
||||
} else {
|
||||
|
||||
throw new Error( 'THREE.MorphAnimMesh: animations[' + label + '] undefined in .playAnimation()' );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
updateAnimation( delta ) {
|
||||
|
||||
this.mixer.update( delta );
|
||||
|
||||
}
|
||||
|
||||
copy( source, recursive ) {
|
||||
|
||||
super.copy( source, recursive );
|
||||
|
||||
this.mixer = new AnimationMixer( this );
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export { MorphAnimMesh };
|
322
dist/electron/static/sdk/three/jsm/misc/MorphBlendMesh.js
vendored
Normal file
322
dist/electron/static/sdk/three/jsm/misc/MorphBlendMesh.js
vendored
Normal file
@ -0,0 +1,322 @@
|
||||
import {
|
||||
MathUtils,
|
||||
Mesh
|
||||
} from 'three';
|
||||
|
||||
class MorphBlendMesh extends Mesh {
|
||||
|
||||
constructor( geometry, material ) {
|
||||
|
||||
super( geometry, material );
|
||||
|
||||
this.animationsMap = {};
|
||||
this.animationsList = [];
|
||||
|
||||
// prepare default animation
|
||||
// (all frames played together in 1 second)
|
||||
|
||||
const numFrames = Object.keys( this.morphTargetDictionary ).length;
|
||||
|
||||
const name = '__default';
|
||||
|
||||
const startFrame = 0;
|
||||
const endFrame = numFrames - 1;
|
||||
|
||||
const fps = numFrames / 1;
|
||||
|
||||
this.createAnimation( name, startFrame, endFrame, fps );
|
||||
this.setAnimationWeight( name, 1 );
|
||||
|
||||
}
|
||||
|
||||
createAnimation( name, start, end, fps ) {
|
||||
|
||||
const animation = {
|
||||
|
||||
start: start,
|
||||
end: end,
|
||||
|
||||
length: end - start + 1,
|
||||
|
||||
fps: fps,
|
||||
duration: ( end - start ) / fps,
|
||||
|
||||
lastFrame: 0,
|
||||
currentFrame: 0,
|
||||
|
||||
active: false,
|
||||
|
||||
time: 0,
|
||||
direction: 1,
|
||||
weight: 1,
|
||||
|
||||
directionBackwards: false,
|
||||
mirroredLoop: false
|
||||
|
||||
};
|
||||
|
||||
this.animationsMap[ name ] = animation;
|
||||
this.animationsList.push( animation );
|
||||
|
||||
}
|
||||
|
||||
autoCreateAnimations( fps ) {
|
||||
|
||||
const pattern = /([a-z]+)_?(\d+)/i;
|
||||
|
||||
let firstAnimation;
|
||||
|
||||
const frameRanges = {};
|
||||
|
||||
let i = 0;
|
||||
|
||||
for ( const key in this.morphTargetDictionary ) {
|
||||
|
||||
const chunks = key.match( pattern );
|
||||
|
||||
if ( chunks && chunks.length > 1 ) {
|
||||
|
||||
const name = chunks[ 1 ];
|
||||
|
||||
if ( ! frameRanges[ name ] ) frameRanges[ name ] = { start: Infinity, end: - Infinity };
|
||||
|
||||
const range = frameRanges[ name ];
|
||||
|
||||
if ( i < range.start ) range.start = i;
|
||||
if ( i > range.end ) range.end = i;
|
||||
|
||||
if ( ! firstAnimation ) firstAnimation = name;
|
||||
|
||||
}
|
||||
|
||||
i ++;
|
||||
|
||||
}
|
||||
|
||||
for ( const name in frameRanges ) {
|
||||
|
||||
const range = frameRanges[ name ];
|
||||
this.createAnimation( name, range.start, range.end, fps );
|
||||
|
||||
}
|
||||
|
||||
this.firstAnimation = firstAnimation;
|
||||
|
||||
}
|
||||
|
||||
setAnimationDirectionForward( name ) {
|
||||
|
||||
const animation = this.animationsMap[ name ];
|
||||
|
||||
if ( animation ) {
|
||||
|
||||
animation.direction = 1;
|
||||
animation.directionBackwards = false;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
setAnimationDirectionBackward( name ) {
|
||||
|
||||
const animation = this.animationsMap[ name ];
|
||||
|
||||
if ( animation ) {
|
||||
|
||||
animation.direction = - 1;
|
||||
animation.directionBackwards = true;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
setAnimationFPS( name, fps ) {
|
||||
|
||||
const animation = this.animationsMap[ name ];
|
||||
|
||||
if ( animation ) {
|
||||
|
||||
animation.fps = fps;
|
||||
animation.duration = ( animation.end - animation.start ) / animation.fps;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
setAnimationDuration( name, duration ) {
|
||||
|
||||
const animation = this.animationsMap[ name ];
|
||||
|
||||
if ( animation ) {
|
||||
|
||||
animation.duration = duration;
|
||||
animation.fps = ( animation.end - animation.start ) / animation.duration;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
setAnimationWeight( name, weight ) {
|
||||
|
||||
const animation = this.animationsMap[ name ];
|
||||
|
||||
if ( animation ) {
|
||||
|
||||
animation.weight = weight;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
setAnimationTime( name, time ) {
|
||||
|
||||
const animation = this.animationsMap[ name ];
|
||||
|
||||
if ( animation ) {
|
||||
|
||||
animation.time = time;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
getAnimationTime( name ) {
|
||||
|
||||
let time = 0;
|
||||
|
||||
const animation = this.animationsMap[ name ];
|
||||
|
||||
if ( animation ) {
|
||||
|
||||
time = animation.time;
|
||||
|
||||
}
|
||||
|
||||
return time;
|
||||
|
||||
}
|
||||
|
||||
getAnimationDuration( name ) {
|
||||
|
||||
let duration = - 1;
|
||||
|
||||
const animation = this.animationsMap[ name ];
|
||||
|
||||
if ( animation ) {
|
||||
|
||||
duration = animation.duration;
|
||||
|
||||
}
|
||||
|
||||
return duration;
|
||||
|
||||
}
|
||||
|
||||
playAnimation( name ) {
|
||||
|
||||
const animation = this.animationsMap[ name ];
|
||||
|
||||
if ( animation ) {
|
||||
|
||||
animation.time = 0;
|
||||
animation.active = true;
|
||||
|
||||
} else {
|
||||
|
||||
console.warn( 'THREE.MorphBlendMesh: animation[' + name + '] undefined in .playAnimation()' );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
stopAnimation( name ) {
|
||||
|
||||
const animation = this.animationsMap[ name ];
|
||||
|
||||
if ( animation ) {
|
||||
|
||||
animation.active = false;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
update( delta ) {
|
||||
|
||||
for ( let i = 0, il = this.animationsList.length; i < il; i ++ ) {
|
||||
|
||||
const animation = this.animationsList[ i ];
|
||||
|
||||
if ( ! animation.active ) continue;
|
||||
|
||||
const frameTime = animation.duration / animation.length;
|
||||
|
||||
animation.time += animation.direction * delta;
|
||||
|
||||
if ( animation.mirroredLoop ) {
|
||||
|
||||
if ( animation.time > animation.duration || animation.time < 0 ) {
|
||||
|
||||
animation.direction *= - 1;
|
||||
|
||||
if ( animation.time > animation.duration ) {
|
||||
|
||||
animation.time = animation.duration;
|
||||
animation.directionBackwards = true;
|
||||
|
||||
}
|
||||
|
||||
if ( animation.time < 0 ) {
|
||||
|
||||
animation.time = 0;
|
||||
animation.directionBackwards = false;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
animation.time = animation.time % animation.duration;
|
||||
|
||||
if ( animation.time < 0 ) animation.time += animation.duration;
|
||||
|
||||
}
|
||||
|
||||
const keyframe = animation.start + MathUtils.clamp( Math.floor( animation.time / frameTime ), 0, animation.length - 1 );
|
||||
const weight = animation.weight;
|
||||
|
||||
if ( keyframe !== animation.currentFrame ) {
|
||||
|
||||
this.morphTargetInfluences[ animation.lastFrame ] = 0;
|
||||
this.morphTargetInfluences[ animation.currentFrame ] = 1 * weight;
|
||||
|
||||
this.morphTargetInfluences[ keyframe ] = 0;
|
||||
|
||||
animation.lastFrame = animation.currentFrame;
|
||||
animation.currentFrame = keyframe;
|
||||
|
||||
}
|
||||
|
||||
let mix = ( animation.time % frameTime ) / frameTime;
|
||||
|
||||
if ( animation.directionBackwards ) mix = 1 - mix;
|
||||
|
||||
if ( animation.currentFrame !== animation.lastFrame ) {
|
||||
|
||||
this.morphTargetInfluences[ animation.currentFrame ] = mix * weight;
|
||||
this.morphTargetInfluences[ animation.lastFrame ] = ( 1 - mix ) * weight;
|
||||
|
||||
} else {
|
||||
|
||||
this.morphTargetInfluences[ animation.currentFrame ] = weight;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export { MorphBlendMesh };
|
324
dist/electron/static/sdk/three/jsm/misc/ProgressiveLightMap.js
vendored
Normal file
324
dist/electron/static/sdk/three/jsm/misc/ProgressiveLightMap.js
vendored
Normal file
@ -0,0 +1,324 @@
|
||||
import * as THREE from 'three';
|
||||
import { potpack } from '../libs/potpack.module.js';
|
||||
|
||||
/**
|
||||
* Progressive Light Map Accumulator, by [zalo](https://github.com/zalo/)
|
||||
*
|
||||
* To use, simply construct a `ProgressiveLightMap` object,
|
||||
* `plmap.addObjectsToLightMap(object)` an array of semi-static
|
||||
* objects and lights to the class once, and then call
|
||||
* `plmap.update(camera)` every frame to begin accumulating
|
||||
* lighting samples.
|
||||
*
|
||||
* This should begin accumulating lightmaps which apply to
|
||||
* your objects, so you can start jittering lighting to achieve
|
||||
* the texture-space effect you're looking for.
|
||||
*
|
||||
* @param {WebGLRenderer} renderer A WebGL Rendering Context
|
||||
* @param {number} res The side-long dimension of you total lightmap
|
||||
*/
|
||||
class ProgressiveLightMap {
|
||||
|
||||
constructor( renderer, res = 1024 ) {
|
||||
|
||||
this.renderer = renderer;
|
||||
this.res = res;
|
||||
this.lightMapContainers = [];
|
||||
this.compiled = false;
|
||||
this.scene = new THREE.Scene();
|
||||
this.scene.background = null;
|
||||
this.tinyTarget = new THREE.WebGLRenderTarget( 1, 1 );
|
||||
this.buffer1Active = false;
|
||||
this.firstUpdate = true;
|
||||
this.warned = false;
|
||||
|
||||
// Create the Progressive LightMap Texture
|
||||
const format = /(Android|iPad|iPhone|iPod)/g.test( navigator.userAgent ) ? THREE.HalfFloatType : THREE.FloatType;
|
||||
this.progressiveLightMap1 = new THREE.WebGLRenderTarget( this.res, this.res, { type: format } );
|
||||
this.progressiveLightMap2 = new THREE.WebGLRenderTarget( this.res, this.res, { type: format } );
|
||||
this.progressiveLightMap2.texture.channel = 1;
|
||||
|
||||
// Inject some spicy new logic into a standard phong material
|
||||
this.uvMat = new THREE.MeshPhongMaterial();
|
||||
this.uvMat.uniforms = {};
|
||||
this.uvMat.onBeforeCompile = ( shader ) => {
|
||||
|
||||
// Vertex Shader: Set Vertex Positions to the Unwrapped UV Positions
|
||||
shader.vertexShader =
|
||||
'attribute vec2 uv1;\n' +
|
||||
'#define USE_LIGHTMAP\n' +
|
||||
'#define LIGHTMAP_UV uv1\n' +
|
||||
shader.vertexShader.slice( 0, - 1 ) +
|
||||
' gl_Position = vec4((LIGHTMAP_UV - 0.5) * 2.0, 1.0, 1.0); }';
|
||||
|
||||
// Fragment Shader: Set Pixels to average in the Previous frame's Shadows
|
||||
const bodyStart = shader.fragmentShader.indexOf( 'void main() {' );
|
||||
shader.fragmentShader =
|
||||
'#define USE_LIGHTMAP\n' +
|
||||
shader.fragmentShader.slice( 0, bodyStart ) +
|
||||
' uniform sampler2D previousShadowMap;\n uniform float averagingWindow;\n' +
|
||||
shader.fragmentShader.slice( bodyStart - 1, - 1 ) +
|
||||
`\nvec3 texelOld = texture2D(previousShadowMap, vLightMapUv).rgb;
|
||||
gl_FragColor.rgb = mix(texelOld, gl_FragColor.rgb, 1.0/averagingWindow);
|
||||
}`;
|
||||
|
||||
// Set the Previous Frame's Texture Buffer and Averaging Window
|
||||
shader.uniforms.previousShadowMap = { value: this.progressiveLightMap1.texture };
|
||||
shader.uniforms.averagingWindow = { value: 100 };
|
||||
|
||||
this.uvMat.uniforms = shader.uniforms;
|
||||
|
||||
// Set the new Shader to this
|
||||
this.uvMat.userData.shader = shader;
|
||||
|
||||
this.compiled = true;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets these objects' materials' lightmaps and modifies their uv1's.
|
||||
* @param {Object3D} objects An array of objects and lights to set up your lightmap.
|
||||
*/
|
||||
addObjectsToLightMap( objects ) {
|
||||
|
||||
// Prepare list of UV bounding boxes for packing later...
|
||||
this.uv_boxes = []; const padding = 3 / this.res;
|
||||
|
||||
for ( let ob = 0; ob < objects.length; ob ++ ) {
|
||||
|
||||
const object = objects[ ob ];
|
||||
|
||||
// If this object is a light, simply add it to the internal scene
|
||||
if ( object.isLight ) {
|
||||
|
||||
this.scene.attach( object ); continue;
|
||||
|
||||
}
|
||||
|
||||
if ( ! object.geometry.hasAttribute( 'uv' ) ) {
|
||||
|
||||
console.warn( 'All lightmap objects need UVs!' ); continue;
|
||||
|
||||
}
|
||||
|
||||
if ( this.blurringPlane == null ) {
|
||||
|
||||
this._initializeBlurPlane( this.res, this.progressiveLightMap1 );
|
||||
|
||||
}
|
||||
|
||||
// Apply the lightmap to the object
|
||||
object.material.lightMap = this.progressiveLightMap2.texture;
|
||||
object.material.dithering = true;
|
||||
object.castShadow = true;
|
||||
object.receiveShadow = true;
|
||||
object.renderOrder = 1000 + ob;
|
||||
|
||||
// Prepare UV boxes for potpack
|
||||
// TODO: Size these by object surface area
|
||||
this.uv_boxes.push( { w: 1 + ( padding * 2 ),
|
||||
h: 1 + ( padding * 2 ), index: ob } );
|
||||
|
||||
this.lightMapContainers.push( { basicMat: object.material, object: object } );
|
||||
|
||||
this.compiled = false;
|
||||
|
||||
}
|
||||
|
||||
// Pack the objects' lightmap UVs into the same global space
|
||||
const dimensions = potpack( this.uv_boxes );
|
||||
this.uv_boxes.forEach( ( box ) => {
|
||||
|
||||
const uv1 = objects[ box.index ].geometry.getAttribute( 'uv' ).clone();
|
||||
for ( let i = 0; i < uv1.array.length; i += uv1.itemSize ) {
|
||||
|
||||
uv1.array[ i ] = ( uv1.array[ i ] + box.x + padding ) / dimensions.w;
|
||||
uv1.array[ i + 1 ] = ( uv1.array[ i + 1 ] + box.y + padding ) / dimensions.h;
|
||||
|
||||
}
|
||||
|
||||
objects[ box.index ].geometry.setAttribute( 'uv1', uv1 );
|
||||
objects[ box.index ].geometry.getAttribute( 'uv1' ).needsUpdate = true;
|
||||
|
||||
} );
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* This function renders each mesh one at a time into their respective surface maps
|
||||
* @param {Camera} camera Standard Rendering Camera
|
||||
* @param {number} blendWindow When >1, samples will accumulate over time.
|
||||
* @param {boolean} blurEdges Whether to fix UV Edges via blurring
|
||||
*/
|
||||
update( camera, blendWindow = 100, blurEdges = true ) {
|
||||
|
||||
if ( this.blurringPlane == null ) {
|
||||
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
// Store the original Render Target
|
||||
const oldTarget = this.renderer.getRenderTarget();
|
||||
|
||||
// The blurring plane applies blur to the seams of the lightmap
|
||||
this.blurringPlane.visible = blurEdges;
|
||||
|
||||
// Steal the Object3D from the real world to our special dimension
|
||||
for ( let l = 0; l < this.lightMapContainers.length; l ++ ) {
|
||||
|
||||
this.lightMapContainers[ l ].object.oldScene =
|
||||
this.lightMapContainers[ l ].object.parent;
|
||||
this.scene.attach( this.lightMapContainers[ l ].object );
|
||||
|
||||
}
|
||||
|
||||
// Render once normally to initialize everything
|
||||
if ( this.firstUpdate ) {
|
||||
|
||||
this.renderer.setRenderTarget( this.tinyTarget ); // Tiny for Speed
|
||||
this.renderer.render( this.scene, camera );
|
||||
this.firstUpdate = false;
|
||||
|
||||
}
|
||||
|
||||
// Set each object's material to the UV Unwrapped Surface Mapping Version
|
||||
for ( let l = 0; l < this.lightMapContainers.length; l ++ ) {
|
||||
|
||||
this.uvMat.uniforms.averagingWindow = { value: blendWindow };
|
||||
this.lightMapContainers[ l ].object.material = this.uvMat;
|
||||
this.lightMapContainers[ l ].object.oldFrustumCulled =
|
||||
this.lightMapContainers[ l ].object.frustumCulled;
|
||||
this.lightMapContainers[ l ].object.frustumCulled = false;
|
||||
|
||||
}
|
||||
|
||||
// Ping-pong two surface buffers for reading/writing
|
||||
const activeMap = this.buffer1Active ? this.progressiveLightMap1 : this.progressiveLightMap2;
|
||||
const inactiveMap = this.buffer1Active ? this.progressiveLightMap2 : this.progressiveLightMap1;
|
||||
|
||||
// Render the object's surface maps
|
||||
this.renderer.setRenderTarget( activeMap );
|
||||
this.uvMat.uniforms.previousShadowMap = { value: inactiveMap.texture };
|
||||
this.blurringPlane.material.uniforms.previousShadowMap = { value: inactiveMap.texture };
|
||||
this.buffer1Active = ! this.buffer1Active;
|
||||
this.renderer.render( this.scene, camera );
|
||||
|
||||
// Restore the object's Real-time Material and add it back to the original world
|
||||
for ( let l = 0; l < this.lightMapContainers.length; l ++ ) {
|
||||
|
||||
this.lightMapContainers[ l ].object.frustumCulled =
|
||||
this.lightMapContainers[ l ].object.oldFrustumCulled;
|
||||
this.lightMapContainers[ l ].object.material = this.lightMapContainers[ l ].basicMat;
|
||||
this.lightMapContainers[ l ].object.oldScene.attach( this.lightMapContainers[ l ].object );
|
||||
|
||||
}
|
||||
|
||||
// Restore the original Render Target
|
||||
this.renderer.setRenderTarget( oldTarget );
|
||||
|
||||
}
|
||||
|
||||
/** DEBUG
|
||||
* Draw the lightmap in the main scene. Call this after adding the objects to it.
|
||||
* @param {boolean} visible Whether the debug plane should be visible
|
||||
* @param {Vector3} position Where the debug plane should be drawn
|
||||
*/
|
||||
showDebugLightmap( visible, position = undefined ) {
|
||||
|
||||
if ( this.lightMapContainers.length == 0 ) {
|
||||
|
||||
if ( ! this.warned ) {
|
||||
|
||||
console.warn( 'Call this after adding the objects!' ); this.warned = true;
|
||||
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
if ( this.labelMesh == null ) {
|
||||
|
||||
this.labelMaterial = new THREE.MeshBasicMaterial(
|
||||
{ map: this.progressiveLightMap1.texture, side: THREE.DoubleSide } );
|
||||
this.labelPlane = new THREE.PlaneGeometry( 100, 100 );
|
||||
this.labelMesh = new THREE.Mesh( this.labelPlane, this.labelMaterial );
|
||||
this.labelMesh.position.y = 250;
|
||||
this.lightMapContainers[ 0 ].object.parent.add( this.labelMesh );
|
||||
|
||||
}
|
||||
|
||||
if ( position != undefined ) {
|
||||
|
||||
this.labelMesh.position.copy( position );
|
||||
|
||||
}
|
||||
|
||||
this.labelMesh.visible = visible;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* INTERNAL Creates the Blurring Plane
|
||||
* @param {number} res The square resolution of this object's lightMap.
|
||||
* @param {WebGLRenderTexture} lightMap The lightmap to initialize the plane with.
|
||||
*/
|
||||
_initializeBlurPlane( res, lightMap = null ) {
|
||||
|
||||
const blurMaterial = new THREE.MeshBasicMaterial();
|
||||
blurMaterial.uniforms = { previousShadowMap: { value: null },
|
||||
pixelOffset: { value: 1.0 / res },
|
||||
polygonOffset: true, polygonOffsetFactor: - 1, polygonOffsetUnits: 3.0 };
|
||||
blurMaterial.onBeforeCompile = ( shader ) => {
|
||||
|
||||
// Vertex Shader: Set Vertex Positions to the Unwrapped UV Positions
|
||||
shader.vertexShader =
|
||||
'#define USE_UV\n' +
|
||||
shader.vertexShader.slice( 0, - 1 ) +
|
||||
' gl_Position = vec4((uv - 0.5) * 2.0, 1.0, 1.0); }';
|
||||
|
||||
// Fragment Shader: Set Pixels to 9-tap box blur the current frame's Shadows
|
||||
const bodyStart = shader.fragmentShader.indexOf( 'void main() {' );
|
||||
shader.fragmentShader =
|
||||
'#define USE_UV\n' +
|
||||
shader.fragmentShader.slice( 0, bodyStart ) +
|
||||
' uniform sampler2D previousShadowMap;\n uniform float pixelOffset;\n' +
|
||||
shader.fragmentShader.slice( bodyStart - 1, - 1 ) +
|
||||
` gl_FragColor.rgb = (
|
||||
texture2D(previousShadowMap, vUv + vec2( pixelOffset, 0.0 )).rgb +
|
||||
texture2D(previousShadowMap, vUv + vec2( 0.0 , pixelOffset)).rgb +
|
||||
texture2D(previousShadowMap, vUv + vec2( 0.0 , -pixelOffset)).rgb +
|
||||
texture2D(previousShadowMap, vUv + vec2(-pixelOffset, 0.0 )).rgb +
|
||||
texture2D(previousShadowMap, vUv + vec2( pixelOffset, pixelOffset)).rgb +
|
||||
texture2D(previousShadowMap, vUv + vec2(-pixelOffset, pixelOffset)).rgb +
|
||||
texture2D(previousShadowMap, vUv + vec2( pixelOffset, -pixelOffset)).rgb +
|
||||
texture2D(previousShadowMap, vUv + vec2(-pixelOffset, -pixelOffset)).rgb)/8.0;
|
||||
}`;
|
||||
|
||||
// Set the LightMap Accumulation Buffer
|
||||
shader.uniforms.previousShadowMap = { value: lightMap.texture };
|
||||
shader.uniforms.pixelOffset = { value: 0.5 / res };
|
||||
blurMaterial.uniforms = shader.uniforms;
|
||||
|
||||
// Set the new Shader to this
|
||||
blurMaterial.userData.shader = shader;
|
||||
|
||||
this.compiled = true;
|
||||
|
||||
};
|
||||
|
||||
this.blurringPlane = new THREE.Mesh( new THREE.PlaneGeometry( 1, 1 ), blurMaterial );
|
||||
this.blurringPlane.name = 'Blurring Plane';
|
||||
this.blurringPlane.frustumCulled = false;
|
||||
this.blurringPlane.renderOrder = 0;
|
||||
this.blurringPlane.material.depthWrite = false;
|
||||
this.scene.add( this.blurringPlane );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export { ProgressiveLightMap };
|
566
dist/electron/static/sdk/three/jsm/misc/RollerCoaster.js
vendored
Normal file
566
dist/electron/static/sdk/three/jsm/misc/RollerCoaster.js
vendored
Normal file
@ -0,0 +1,566 @@
|
||||
import {
|
||||
BufferAttribute,
|
||||
BufferGeometry,
|
||||
Color,
|
||||
Quaternion,
|
||||
Raycaster,
|
||||
SRGBColorSpace,
|
||||
Vector3
|
||||
} from 'three';
|
||||
|
||||
class RollerCoasterGeometry extends BufferGeometry {
|
||||
|
||||
constructor( curve, divisions ) {
|
||||
|
||||
super();
|
||||
|
||||
const vertices = [];
|
||||
const normals = [];
|
||||
const colors = [];
|
||||
|
||||
const color1 = [ 1, 1, 1 ];
|
||||
const color2 = [ 1, 1, 0 ];
|
||||
|
||||
const up = new Vector3( 0, 1, 0 );
|
||||
const forward = new Vector3();
|
||||
const right = new Vector3();
|
||||
|
||||
const quaternion = new Quaternion();
|
||||
const prevQuaternion = new Quaternion();
|
||||
prevQuaternion.setFromAxisAngle( up, Math.PI / 2 );
|
||||
|
||||
const point = new Vector3();
|
||||
const prevPoint = new Vector3();
|
||||
prevPoint.copy( curve.getPointAt( 0 ) );
|
||||
|
||||
// shapes
|
||||
|
||||
const step = [
|
||||
new Vector3( - 0.225, 0, 0 ),
|
||||
new Vector3( 0, - 0.050, 0 ),
|
||||
new Vector3( 0, - 0.175, 0 ),
|
||||
|
||||
new Vector3( 0, - 0.050, 0 ),
|
||||
new Vector3( 0.225, 0, 0 ),
|
||||
new Vector3( 0, - 0.175, 0 )
|
||||
];
|
||||
|
||||
const PI2 = Math.PI * 2;
|
||||
|
||||
let sides = 5;
|
||||
const tube1 = [];
|
||||
|
||||
for ( let i = 0; i < sides; i ++ ) {
|
||||
|
||||
const angle = ( i / sides ) * PI2;
|
||||
tube1.push( new Vector3( Math.sin( angle ) * 0.06, Math.cos( angle ) * 0.06, 0 ) );
|
||||
|
||||
}
|
||||
|
||||
sides = 6;
|
||||
const tube2 = [];
|
||||
|
||||
for ( let i = 0; i < sides; i ++ ) {
|
||||
|
||||
const angle = ( i / sides ) * PI2;
|
||||
tube2.push( new Vector3( Math.sin( angle ) * 0.025, Math.cos( angle ) * 0.025, 0 ) );
|
||||
|
||||
}
|
||||
|
||||
const vector = new Vector3();
|
||||
const normal = new Vector3();
|
||||
|
||||
function drawShape( shape, color ) {
|
||||
|
||||
normal.set( 0, 0, - 1 ).applyQuaternion( quaternion );
|
||||
|
||||
for ( let j = 0; j < shape.length; j ++ ) {
|
||||
|
||||
vector.copy( shape[ j ] );
|
||||
vector.applyQuaternion( quaternion );
|
||||
vector.add( point );
|
||||
|
||||
vertices.push( vector.x, vector.y, vector.z );
|
||||
normals.push( normal.x, normal.y, normal.z );
|
||||
colors.push( color[ 0 ], color[ 1 ], color[ 2 ] );
|
||||
|
||||
}
|
||||
|
||||
normal.set( 0, 0, 1 ).applyQuaternion( quaternion );
|
||||
|
||||
for ( let j = shape.length - 1; j >= 0; j -- ) {
|
||||
|
||||
vector.copy( shape[ j ] );
|
||||
vector.applyQuaternion( quaternion );
|
||||
vector.add( point );
|
||||
|
||||
vertices.push( vector.x, vector.y, vector.z );
|
||||
normals.push( normal.x, normal.y, normal.z );
|
||||
colors.push( color[ 0 ], color[ 1 ], color[ 2 ] );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const vector1 = new Vector3();
|
||||
const vector2 = new Vector3();
|
||||
const vector3 = new Vector3();
|
||||
const vector4 = new Vector3();
|
||||
|
||||
const normal1 = new Vector3();
|
||||
const normal2 = new Vector3();
|
||||
const normal3 = new Vector3();
|
||||
const normal4 = new Vector3();
|
||||
|
||||
function extrudeShape( shape, offset, color ) {
|
||||
|
||||
for ( let j = 0, jl = shape.length; j < jl; j ++ ) {
|
||||
|
||||
const point1 = shape[ j ];
|
||||
const point2 = shape[ ( j + 1 ) % jl ];
|
||||
|
||||
vector1.copy( point1 ).add( offset );
|
||||
vector1.applyQuaternion( quaternion );
|
||||
vector1.add( point );
|
||||
|
||||
vector2.copy( point2 ).add( offset );
|
||||
vector2.applyQuaternion( quaternion );
|
||||
vector2.add( point );
|
||||
|
||||
vector3.copy( point2 ).add( offset );
|
||||
vector3.applyQuaternion( prevQuaternion );
|
||||
vector3.add( prevPoint );
|
||||
|
||||
vector4.copy( point1 ).add( offset );
|
||||
vector4.applyQuaternion( prevQuaternion );
|
||||
vector4.add( prevPoint );
|
||||
|
||||
vertices.push( vector1.x, vector1.y, vector1.z );
|
||||
vertices.push( vector2.x, vector2.y, vector2.z );
|
||||
vertices.push( vector4.x, vector4.y, vector4.z );
|
||||
|
||||
vertices.push( vector2.x, vector2.y, vector2.z );
|
||||
vertices.push( vector3.x, vector3.y, vector3.z );
|
||||
vertices.push( vector4.x, vector4.y, vector4.z );
|
||||
|
||||
//
|
||||
|
||||
normal1.copy( point1 );
|
||||
normal1.applyQuaternion( quaternion );
|
||||
normal1.normalize();
|
||||
|
||||
normal2.copy( point2 );
|
||||
normal2.applyQuaternion( quaternion );
|
||||
normal2.normalize();
|
||||
|
||||
normal3.copy( point2 );
|
||||
normal3.applyQuaternion( prevQuaternion );
|
||||
normal3.normalize();
|
||||
|
||||
normal4.copy( point1 );
|
||||
normal4.applyQuaternion( prevQuaternion );
|
||||
normal4.normalize();
|
||||
|
||||
normals.push( normal1.x, normal1.y, normal1.z );
|
||||
normals.push( normal2.x, normal2.y, normal2.z );
|
||||
normals.push( normal4.x, normal4.y, normal4.z );
|
||||
|
||||
normals.push( normal2.x, normal2.y, normal2.z );
|
||||
normals.push( normal3.x, normal3.y, normal3.z );
|
||||
normals.push( normal4.x, normal4.y, normal4.z );
|
||||
|
||||
colors.push( color[ 0 ], color[ 1 ], color[ 2 ] );
|
||||
colors.push( color[ 0 ], color[ 1 ], color[ 2 ] );
|
||||
colors.push( color[ 0 ], color[ 1 ], color[ 2 ] );
|
||||
|
||||
colors.push( color[ 0 ], color[ 1 ], color[ 2 ] );
|
||||
colors.push( color[ 0 ], color[ 1 ], color[ 2 ] );
|
||||
colors.push( color[ 0 ], color[ 1 ], color[ 2 ] );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const offset = new Vector3();
|
||||
|
||||
for ( let i = 1; i <= divisions; i ++ ) {
|
||||
|
||||
point.copy( curve.getPointAt( i / divisions ) );
|
||||
|
||||
up.set( 0, 1, 0 );
|
||||
|
||||
forward.subVectors( point, prevPoint ).normalize();
|
||||
right.crossVectors( up, forward ).normalize();
|
||||
up.crossVectors( forward, right );
|
||||
|
||||
const angle = Math.atan2( forward.x, forward.z );
|
||||
|
||||
quaternion.setFromAxisAngle( up, angle );
|
||||
|
||||
if ( i % 2 === 0 ) {
|
||||
|
||||
drawShape( step, color2 );
|
||||
|
||||
}
|
||||
|
||||
extrudeShape( tube1, offset.set( 0, - 0.125, 0 ), color2 );
|
||||
extrudeShape( tube2, offset.set( 0.2, 0, 0 ), color1 );
|
||||
extrudeShape( tube2, offset.set( - 0.2, 0, 0 ), color1 );
|
||||
|
||||
prevPoint.copy( point );
|
||||
prevQuaternion.copy( quaternion );
|
||||
|
||||
}
|
||||
|
||||
// console.log( vertices.length );
|
||||
|
||||
this.setAttribute( 'position', new BufferAttribute( new Float32Array( vertices ), 3 ) );
|
||||
this.setAttribute( 'normal', new BufferAttribute( new Float32Array( normals ), 3 ) );
|
||||
this.setAttribute( 'color', new BufferAttribute( new Float32Array( colors ), 3 ) );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class RollerCoasterLiftersGeometry extends BufferGeometry {
|
||||
|
||||
constructor( curve, divisions ) {
|
||||
|
||||
super();
|
||||
|
||||
const vertices = [];
|
||||
const normals = [];
|
||||
|
||||
const quaternion = new Quaternion();
|
||||
|
||||
const up = new Vector3( 0, 1, 0 );
|
||||
|
||||
const point = new Vector3();
|
||||
const tangent = new Vector3();
|
||||
|
||||
// shapes
|
||||
|
||||
const tube1 = [
|
||||
new Vector3( 0, 0.05, - 0.05 ),
|
||||
new Vector3( 0, 0.05, 0.05 ),
|
||||
new Vector3( 0, - 0.05, 0 )
|
||||
];
|
||||
|
||||
const tube2 = [
|
||||
new Vector3( - 0.05, 0, 0.05 ),
|
||||
new Vector3( - 0.05, 0, - 0.05 ),
|
||||
new Vector3( 0.05, 0, 0 )
|
||||
];
|
||||
|
||||
const tube3 = [
|
||||
new Vector3( 0.05, 0, - 0.05 ),
|
||||
new Vector3( 0.05, 0, 0.05 ),
|
||||
new Vector3( - 0.05, 0, 0 )
|
||||
];
|
||||
|
||||
const vector1 = new Vector3();
|
||||
const vector2 = new Vector3();
|
||||
const vector3 = new Vector3();
|
||||
const vector4 = new Vector3();
|
||||
|
||||
const normal1 = new Vector3();
|
||||
const normal2 = new Vector3();
|
||||
const normal3 = new Vector3();
|
||||
const normal4 = new Vector3();
|
||||
|
||||
function extrudeShape( shape, fromPoint, toPoint ) {
|
||||
|
||||
for ( let j = 0, jl = shape.length; j < jl; j ++ ) {
|
||||
|
||||
const point1 = shape[ j ];
|
||||
const point2 = shape[ ( j + 1 ) % jl ];
|
||||
|
||||
vector1.copy( point1 );
|
||||
vector1.applyQuaternion( quaternion );
|
||||
vector1.add( fromPoint );
|
||||
|
||||
vector2.copy( point2 );
|
||||
vector2.applyQuaternion( quaternion );
|
||||
vector2.add( fromPoint );
|
||||
|
||||
vector3.copy( point2 );
|
||||
vector3.applyQuaternion( quaternion );
|
||||
vector3.add( toPoint );
|
||||
|
||||
vector4.copy( point1 );
|
||||
vector4.applyQuaternion( quaternion );
|
||||
vector4.add( toPoint );
|
||||
|
||||
vertices.push( vector1.x, vector1.y, vector1.z );
|
||||
vertices.push( vector2.x, vector2.y, vector2.z );
|
||||
vertices.push( vector4.x, vector4.y, vector4.z );
|
||||
|
||||
vertices.push( vector2.x, vector2.y, vector2.z );
|
||||
vertices.push( vector3.x, vector3.y, vector3.z );
|
||||
vertices.push( vector4.x, vector4.y, vector4.z );
|
||||
|
||||
//
|
||||
|
||||
normal1.copy( point1 );
|
||||
normal1.applyQuaternion( quaternion );
|
||||
normal1.normalize();
|
||||
|
||||
normal2.copy( point2 );
|
||||
normal2.applyQuaternion( quaternion );
|
||||
normal2.normalize();
|
||||
|
||||
normal3.copy( point2 );
|
||||
normal3.applyQuaternion( quaternion );
|
||||
normal3.normalize();
|
||||
|
||||
normal4.copy( point1 );
|
||||
normal4.applyQuaternion( quaternion );
|
||||
normal4.normalize();
|
||||
|
||||
normals.push( normal1.x, normal1.y, normal1.z );
|
||||
normals.push( normal2.x, normal2.y, normal2.z );
|
||||
normals.push( normal4.x, normal4.y, normal4.z );
|
||||
|
||||
normals.push( normal2.x, normal2.y, normal2.z );
|
||||
normals.push( normal3.x, normal3.y, normal3.z );
|
||||
normals.push( normal4.x, normal4.y, normal4.z );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const fromPoint = new Vector3();
|
||||
const toPoint = new Vector3();
|
||||
|
||||
for ( let i = 1; i <= divisions; i ++ ) {
|
||||
|
||||
point.copy( curve.getPointAt( i / divisions ) );
|
||||
tangent.copy( curve.getTangentAt( i / divisions ) );
|
||||
|
||||
const angle = Math.atan2( tangent.x, tangent.z );
|
||||
|
||||
quaternion.setFromAxisAngle( up, angle );
|
||||
|
||||
//
|
||||
|
||||
if ( point.y > 10 ) {
|
||||
|
||||
fromPoint.set( - 0.75, - 0.35, 0 );
|
||||
fromPoint.applyQuaternion( quaternion );
|
||||
fromPoint.add( point );
|
||||
|
||||
toPoint.set( 0.75, - 0.35, 0 );
|
||||
toPoint.applyQuaternion( quaternion );
|
||||
toPoint.add( point );
|
||||
|
||||
extrudeShape( tube1, fromPoint, toPoint );
|
||||
|
||||
fromPoint.set( - 0.7, - 0.3, 0 );
|
||||
fromPoint.applyQuaternion( quaternion );
|
||||
fromPoint.add( point );
|
||||
|
||||
toPoint.set( - 0.7, - point.y, 0 );
|
||||
toPoint.applyQuaternion( quaternion );
|
||||
toPoint.add( point );
|
||||
|
||||
extrudeShape( tube2, fromPoint, toPoint );
|
||||
|
||||
fromPoint.set( 0.7, - 0.3, 0 );
|
||||
fromPoint.applyQuaternion( quaternion );
|
||||
fromPoint.add( point );
|
||||
|
||||
toPoint.set( 0.7, - point.y, 0 );
|
||||
toPoint.applyQuaternion( quaternion );
|
||||
toPoint.add( point );
|
||||
|
||||
extrudeShape( tube3, fromPoint, toPoint );
|
||||
|
||||
} else {
|
||||
|
||||
fromPoint.set( 0, - 0.2, 0 );
|
||||
fromPoint.applyQuaternion( quaternion );
|
||||
fromPoint.add( point );
|
||||
|
||||
toPoint.set( 0, - point.y, 0 );
|
||||
toPoint.applyQuaternion( quaternion );
|
||||
toPoint.add( point );
|
||||
|
||||
extrudeShape( tube3, fromPoint, toPoint );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
this.setAttribute( 'position', new BufferAttribute( new Float32Array( vertices ), 3 ) );
|
||||
this.setAttribute( 'normal', new BufferAttribute( new Float32Array( normals ), 3 ) );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class RollerCoasterShadowGeometry extends BufferGeometry {
|
||||
|
||||
constructor( curve, divisions ) {
|
||||
|
||||
super();
|
||||
|
||||
const vertices = [];
|
||||
|
||||
const up = new Vector3( 0, 1, 0 );
|
||||
const forward = new Vector3();
|
||||
|
||||
const quaternion = new Quaternion();
|
||||
const prevQuaternion = new Quaternion();
|
||||
prevQuaternion.setFromAxisAngle( up, Math.PI / 2 );
|
||||
|
||||
const point = new Vector3();
|
||||
|
||||
const prevPoint = new Vector3();
|
||||
prevPoint.copy( curve.getPointAt( 0 ) );
|
||||
prevPoint.y = 0;
|
||||
|
||||
const vector1 = new Vector3();
|
||||
const vector2 = new Vector3();
|
||||
const vector3 = new Vector3();
|
||||
const vector4 = new Vector3();
|
||||
|
||||
for ( let i = 1; i <= divisions; i ++ ) {
|
||||
|
||||
point.copy( curve.getPointAt( i / divisions ) );
|
||||
point.y = 0;
|
||||
|
||||
forward.subVectors( point, prevPoint );
|
||||
|
||||
const angle = Math.atan2( forward.x, forward.z );
|
||||
|
||||
quaternion.setFromAxisAngle( up, angle );
|
||||
|
||||
vector1.set( - 0.3, 0, 0 );
|
||||
vector1.applyQuaternion( quaternion );
|
||||
vector1.add( point );
|
||||
|
||||
vector2.set( 0.3, 0, 0 );
|
||||
vector2.applyQuaternion( quaternion );
|
||||
vector2.add( point );
|
||||
|
||||
vector3.set( 0.3, 0, 0 );
|
||||
vector3.applyQuaternion( prevQuaternion );
|
||||
vector3.add( prevPoint );
|
||||
|
||||
vector4.set( - 0.3, 0, 0 );
|
||||
vector4.applyQuaternion( prevQuaternion );
|
||||
vector4.add( prevPoint );
|
||||
|
||||
vertices.push( vector1.x, vector1.y, vector1.z );
|
||||
vertices.push( vector2.x, vector2.y, vector2.z );
|
||||
vertices.push( vector4.x, vector4.y, vector4.z );
|
||||
|
||||
vertices.push( vector2.x, vector2.y, vector2.z );
|
||||
vertices.push( vector3.x, vector3.y, vector3.z );
|
||||
vertices.push( vector4.x, vector4.y, vector4.z );
|
||||
|
||||
prevPoint.copy( point );
|
||||
prevQuaternion.copy( quaternion );
|
||||
|
||||
}
|
||||
|
||||
this.setAttribute( 'position', new BufferAttribute( new Float32Array( vertices ), 3 ) );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class SkyGeometry extends BufferGeometry {
|
||||
|
||||
constructor() {
|
||||
|
||||
super();
|
||||
|
||||
const vertices = [];
|
||||
|
||||
for ( let i = 0; i < 100; i ++ ) {
|
||||
|
||||
const x = Math.random() * 800 - 400;
|
||||
const y = Math.random() * 50 + 50;
|
||||
const z = Math.random() * 800 - 400;
|
||||
|
||||
const size = Math.random() * 40 + 20;
|
||||
|
||||
vertices.push( x - size, y, z - size );
|
||||
vertices.push( x + size, y, z - size );
|
||||
vertices.push( x - size, y, z + size );
|
||||
|
||||
vertices.push( x + size, y, z - size );
|
||||
vertices.push( x + size, y, z + size );
|
||||
vertices.push( x - size, y, z + size );
|
||||
|
||||
}
|
||||
|
||||
|
||||
this.setAttribute( 'position', new BufferAttribute( new Float32Array( vertices ), 3 ) );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class TreesGeometry extends BufferGeometry {
|
||||
|
||||
constructor( landscape ) {
|
||||
|
||||
super();
|
||||
|
||||
const vertices = [];
|
||||
const colors = [];
|
||||
|
||||
const raycaster = new Raycaster();
|
||||
raycaster.ray.direction.set( 0, - 1, 0 );
|
||||
|
||||
const _color = new Color();
|
||||
|
||||
for ( let i = 0; i < 2000; i ++ ) {
|
||||
|
||||
const x = Math.random() * 500 - 250;
|
||||
const z = Math.random() * 500 - 250;
|
||||
|
||||
raycaster.ray.origin.set( x, 50, z );
|
||||
|
||||
const intersections = raycaster.intersectObject( landscape );
|
||||
|
||||
if ( intersections.length === 0 ) continue;
|
||||
|
||||
const y = intersections[ 0 ].point.y;
|
||||
|
||||
const height = Math.random() * 5 + 0.5;
|
||||
|
||||
let angle = Math.random() * Math.PI * 2;
|
||||
|
||||
vertices.push( x + Math.sin( angle ), y, z + Math.cos( angle ) );
|
||||
vertices.push( x, y + height, z );
|
||||
vertices.push( x + Math.sin( angle + Math.PI ), y, z + Math.cos( angle + Math.PI ) );
|
||||
|
||||
angle += Math.PI / 2;
|
||||
|
||||
vertices.push( x + Math.sin( angle ), y, z + Math.cos( angle ) );
|
||||
vertices.push( x, y + height, z );
|
||||
vertices.push( x + Math.sin( angle + Math.PI ), y, z + Math.cos( angle + Math.PI ) );
|
||||
|
||||
const random = Math.random() * 0.1;
|
||||
|
||||
for ( let j = 0; j < 6; j ++ ) {
|
||||
|
||||
_color.setRGB( 0.2 + random, 0.4 + random, 0, SRGBColorSpace );
|
||||
|
||||
colors.push( _color.r, _color.g, _color.b );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
this.setAttribute( 'position', new BufferAttribute( new Float32Array( vertices ), 3 ) );
|
||||
this.setAttribute( 'color', new BufferAttribute( new Float32Array( colors ), 3 ) );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export { RollerCoasterGeometry, RollerCoasterLiftersGeometry, RollerCoasterShadowGeometry, SkyGeometry, TreesGeometry };
|
128
dist/electron/static/sdk/three/jsm/misc/Timer.js
vendored
Normal file
128
dist/electron/static/sdk/three/jsm/misc/Timer.js
vendored
Normal file
@ -0,0 +1,128 @@
|
||||
class Timer {
|
||||
|
||||
constructor() {
|
||||
|
||||
this._previousTime = 0;
|
||||
this._currentTime = 0;
|
||||
this._startTime = now();
|
||||
|
||||
this._delta = 0;
|
||||
this._elapsed = 0;
|
||||
|
||||
this._timescale = 1;
|
||||
|
||||
// use Page Visibility API to avoid large time delta values
|
||||
|
||||
this._usePageVisibilityAPI = ( typeof document !== 'undefined' && document.hidden !== undefined );
|
||||
|
||||
if ( this._usePageVisibilityAPI === true ) {
|
||||
|
||||
this._pageVisibilityHandler = handleVisibilityChange.bind( this );
|
||||
|
||||
document.addEventListener( 'visibilitychange', this._pageVisibilityHandler, false );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
getDelta() {
|
||||
|
||||
return this._delta / 1000;
|
||||
|
||||
}
|
||||
|
||||
getElapsed() {
|
||||
|
||||
return this._elapsed / 1000;
|
||||
|
||||
}
|
||||
|
||||
getTimescale() {
|
||||
|
||||
return this._timescale;
|
||||
|
||||
}
|
||||
|
||||
setTimescale( timescale ) {
|
||||
|
||||
this._timescale = timescale;
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
reset() {
|
||||
|
||||
this._currentTime = now() - this._startTime;
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
dispose() {
|
||||
|
||||
if ( this._usePageVisibilityAPI === true ) {
|
||||
|
||||
document.removeEventListener( 'visibilitychange', this._pageVisibilityHandler );
|
||||
|
||||
}
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
update( timestamp ) {
|
||||
|
||||
|
||||
if ( this._usePageVisibilityAPI === true && document.hidden === true ) {
|
||||
|
||||
this._delta = 0;
|
||||
|
||||
} else {
|
||||
|
||||
this._previousTime = this._currentTime;
|
||||
this._currentTime = ( timestamp !== undefined ? timestamp : now() ) - this._startTime;
|
||||
|
||||
this._delta = ( this._currentTime - this._previousTime ) * this._timescale;
|
||||
this._elapsed += this._delta; // _elapsed is the accumulation of all previous deltas
|
||||
|
||||
}
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class FixedTimer extends Timer {
|
||||
|
||||
constructor( fps = 60 ) {
|
||||
|
||||
super();
|
||||
this._delta = ( 1 / fps ) * 1000;
|
||||
|
||||
}
|
||||
|
||||
update() {
|
||||
|
||||
this._elapsed += ( this._delta * this._timescale ); // _elapsed is the accumulation of all previous deltas
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function now() {
|
||||
|
||||
return ( typeof performance === 'undefined' ? Date : performance ).now();
|
||||
|
||||
}
|
||||
|
||||
function handleVisibilityChange() {
|
||||
|
||||
if ( document.hidden === false ) this.reset();
|
||||
|
||||
}
|
||||
|
||||
export { Timer, FixedTimer };
|
202
dist/electron/static/sdk/three/jsm/misc/TubePainter.js
vendored
Normal file
202
dist/electron/static/sdk/three/jsm/misc/TubePainter.js
vendored
Normal file
@ -0,0 +1,202 @@
|
||||
import {
|
||||
BufferAttribute,
|
||||
BufferGeometry,
|
||||
Color,
|
||||
DynamicDrawUsage,
|
||||
Matrix4,
|
||||
Mesh,
|
||||
MeshStandardMaterial,
|
||||
Vector3
|
||||
} from 'three';
|
||||
|
||||
function TubePainter() {
|
||||
|
||||
const BUFFER_SIZE = 1000000 * 3;
|
||||
|
||||
const positions = new BufferAttribute( new Float32Array( BUFFER_SIZE ), 3 );
|
||||
positions.usage = DynamicDrawUsage;
|
||||
|
||||
const normals = new BufferAttribute( new Float32Array( BUFFER_SIZE ), 3 );
|
||||
normals.usage = DynamicDrawUsage;
|
||||
|
||||
const colors = new BufferAttribute( new Float32Array( BUFFER_SIZE ), 3 );
|
||||
colors.usage = DynamicDrawUsage;
|
||||
|
||||
const geometry = new BufferGeometry();
|
||||
geometry.setAttribute( 'position', positions );
|
||||
geometry.setAttribute( 'normal', normals );
|
||||
geometry.setAttribute( 'color', colors );
|
||||
geometry.drawRange.count = 0;
|
||||
|
||||
const material = new MeshStandardMaterial( {
|
||||
vertexColors: true
|
||||
} );
|
||||
|
||||
const mesh = new Mesh( geometry, material );
|
||||
mesh.frustumCulled = false;
|
||||
|
||||
//
|
||||
|
||||
function getPoints( size ) {
|
||||
|
||||
const PI2 = Math.PI * 2;
|
||||
|
||||
const sides = 10;
|
||||
const array = [];
|
||||
const radius = 0.01 * size;
|
||||
|
||||
for ( let i = 0; i < sides; i ++ ) {
|
||||
|
||||
const angle = ( i / sides ) * PI2;
|
||||
array.push( new Vector3( Math.sin( angle ) * radius, Math.cos( angle ) * radius, 0 ) );
|
||||
|
||||
}
|
||||
|
||||
return array;
|
||||
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
const vector1 = new Vector3();
|
||||
const vector2 = new Vector3();
|
||||
const vector3 = new Vector3();
|
||||
const vector4 = new Vector3();
|
||||
|
||||
const color = new Color( 0xffffff );
|
||||
let size = 1;
|
||||
|
||||
function stroke( position1, position2, matrix1, matrix2 ) {
|
||||
|
||||
if ( position1.distanceToSquared( position2 ) === 0 ) return;
|
||||
|
||||
let count = geometry.drawRange.count;
|
||||
|
||||
const points = getPoints( size );
|
||||
|
||||
for ( let i = 0, il = points.length; i < il; i ++ ) {
|
||||
|
||||
const vertex1 = points[ i ];
|
||||
const vertex2 = points[ ( i + 1 ) % il ];
|
||||
|
||||
// positions
|
||||
|
||||
vector1.copy( vertex1 ).applyMatrix4( matrix2 ).add( position2 );
|
||||
vector2.copy( vertex2 ).applyMatrix4( matrix2 ).add( position2 );
|
||||
vector3.copy( vertex2 ).applyMatrix4( matrix1 ).add( position1 );
|
||||
vector4.copy( vertex1 ).applyMatrix4( matrix1 ).add( position1 );
|
||||
|
||||
vector1.toArray( positions.array, ( count + 0 ) * 3 );
|
||||
vector2.toArray( positions.array, ( count + 1 ) * 3 );
|
||||
vector4.toArray( positions.array, ( count + 2 ) * 3 );
|
||||
|
||||
vector2.toArray( positions.array, ( count + 3 ) * 3 );
|
||||
vector3.toArray( positions.array, ( count + 4 ) * 3 );
|
||||
vector4.toArray( positions.array, ( count + 5 ) * 3 );
|
||||
|
||||
// normals
|
||||
|
||||
vector1.copy( vertex1 ).applyMatrix4( matrix2 ).normalize();
|
||||
vector2.copy( vertex2 ).applyMatrix4( matrix2 ).normalize();
|
||||
vector3.copy( vertex2 ).applyMatrix4( matrix1 ).normalize();
|
||||
vector4.copy( vertex1 ).applyMatrix4( matrix1 ).normalize();
|
||||
|
||||
vector1.toArray( normals.array, ( count + 0 ) * 3 );
|
||||
vector2.toArray( normals.array, ( count + 1 ) * 3 );
|
||||
vector4.toArray( normals.array, ( count + 2 ) * 3 );
|
||||
|
||||
vector2.toArray( normals.array, ( count + 3 ) * 3 );
|
||||
vector3.toArray( normals.array, ( count + 4 ) * 3 );
|
||||
vector4.toArray( normals.array, ( count + 5 ) * 3 );
|
||||
|
||||
// colors
|
||||
|
||||
color.toArray( colors.array, ( count + 0 ) * 3 );
|
||||
color.toArray( colors.array, ( count + 1 ) * 3 );
|
||||
color.toArray( colors.array, ( count + 2 ) * 3 );
|
||||
|
||||
color.toArray( colors.array, ( count + 3 ) * 3 );
|
||||
color.toArray( colors.array, ( count + 4 ) * 3 );
|
||||
color.toArray( colors.array, ( count + 5 ) * 3 );
|
||||
|
||||
count += 6;
|
||||
|
||||
}
|
||||
|
||||
geometry.drawRange.count = count;
|
||||
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
const up = new Vector3( 0, 1, 0 );
|
||||
|
||||
const point1 = new Vector3();
|
||||
const point2 = new Vector3();
|
||||
|
||||
const matrix1 = new Matrix4();
|
||||
const matrix2 = new Matrix4();
|
||||
|
||||
function moveTo( position ) {
|
||||
|
||||
point1.copy( position );
|
||||
matrix1.lookAt( point2, point1, up );
|
||||
|
||||
point2.copy( position );
|
||||
matrix2.copy( matrix1 );
|
||||
|
||||
}
|
||||
|
||||
function lineTo( position ) {
|
||||
|
||||
point1.copy( position );
|
||||
matrix1.lookAt( point2, point1, up );
|
||||
|
||||
stroke( point1, point2, matrix1, matrix2 );
|
||||
|
||||
point2.copy( point1 );
|
||||
matrix2.copy( matrix1 );
|
||||
|
||||
}
|
||||
|
||||
function setSize( value ) {
|
||||
|
||||
size = value;
|
||||
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
let count = 0;
|
||||
|
||||
function update() {
|
||||
|
||||
const start = count;
|
||||
const end = geometry.drawRange.count;
|
||||
|
||||
if ( start === end ) return;
|
||||
|
||||
positions.addUpdateRange( start * 3, ( end - start ) * 3 );
|
||||
positions.needsUpdate = true;
|
||||
|
||||
normals.addUpdateRange( start * 3, ( end - start ) * 3 );
|
||||
normals.needsUpdate = true;
|
||||
|
||||
colors.addUpdateRange( start * 3, ( end - start ) * 3 );
|
||||
colors.needsUpdate = true;
|
||||
|
||||
count = geometry.drawRange.count;
|
||||
|
||||
}
|
||||
|
||||
return {
|
||||
mesh: mesh,
|
||||
moveTo: moveTo,
|
||||
lineTo: lineTo,
|
||||
setSize: setSize,
|
||||
update: update
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
export { TubePainter };
|
473
dist/electron/static/sdk/three/jsm/misc/Volume.js
vendored
Normal file
473
dist/electron/static/sdk/three/jsm/misc/Volume.js
vendored
Normal file
@ -0,0 +1,473 @@
|
||||
import {
|
||||
Matrix3,
|
||||
Matrix4,
|
||||
Vector3
|
||||
} from 'three';
|
||||
import { VolumeSlice } from '../misc/VolumeSlice.js';
|
||||
|
||||
/**
|
||||
* This class had been written to handle the output of the NRRD loader.
|
||||
* It contains a volume of data and informations about it.
|
||||
* For now it only handles 3 dimensional data.
|
||||
* See the webgl_loader_nrrd.html example and the loaderNRRD.js file to see how to use this class.
|
||||
* @class
|
||||
* @param {number} xLength Width of the volume
|
||||
* @param {number} yLength Length of the volume
|
||||
* @param {number} zLength Depth of the volume
|
||||
* @param {string} type The type of data (uint8, uint16, ...)
|
||||
* @param {ArrayBuffer} arrayBuffer The buffer with volume data
|
||||
*/
|
||||
class Volume {
|
||||
|
||||
constructor( xLength, yLength, zLength, type, arrayBuffer ) {
|
||||
|
||||
if ( xLength !== undefined ) {
|
||||
|
||||
/**
|
||||
* @member {number} xLength Width of the volume in the IJK coordinate system
|
||||
*/
|
||||
this.xLength = Number( xLength ) || 1;
|
||||
/**
|
||||
* @member {number} yLength Height of the volume in the IJK coordinate system
|
||||
*/
|
||||
this.yLength = Number( yLength ) || 1;
|
||||
/**
|
||||
* @member {number} zLength Depth of the volume in the IJK coordinate system
|
||||
*/
|
||||
this.zLength = Number( zLength ) || 1;
|
||||
/**
|
||||
* @member {Array<string>} The order of the Axis dictated by the NRRD header
|
||||
*/
|
||||
this.axisOrder = [ 'x', 'y', 'z' ];
|
||||
/**
|
||||
* @member {TypedArray} data Data of the volume
|
||||
*/
|
||||
|
||||
switch ( type ) {
|
||||
|
||||
case 'Uint8' :
|
||||
case 'uint8' :
|
||||
case 'uchar' :
|
||||
case 'unsigned char' :
|
||||
case 'uint8_t' :
|
||||
this.data = new Uint8Array( arrayBuffer );
|
||||
break;
|
||||
case 'Int8' :
|
||||
case 'int8' :
|
||||
case 'signed char' :
|
||||
case 'int8_t' :
|
||||
this.data = new Int8Array( arrayBuffer );
|
||||
break;
|
||||
case 'Int16' :
|
||||
case 'int16' :
|
||||
case 'short' :
|
||||
case 'short int' :
|
||||
case 'signed short' :
|
||||
case 'signed short int' :
|
||||
case 'int16_t' :
|
||||
this.data = new Int16Array( arrayBuffer );
|
||||
break;
|
||||
case 'Uint16' :
|
||||
case 'uint16' :
|
||||
case 'ushort' :
|
||||
case 'unsigned short' :
|
||||
case 'unsigned short int' :
|
||||
case 'uint16_t' :
|
||||
this.data = new Uint16Array( arrayBuffer );
|
||||
break;
|
||||
case 'Int32' :
|
||||
case 'int32' :
|
||||
case 'int' :
|
||||
case 'signed int' :
|
||||
case 'int32_t' :
|
||||
this.data = new Int32Array( arrayBuffer );
|
||||
break;
|
||||
case 'Uint32' :
|
||||
case 'uint32' :
|
||||
case 'uint' :
|
||||
case 'unsigned int' :
|
||||
case 'uint32_t' :
|
||||
this.data = new Uint32Array( arrayBuffer );
|
||||
break;
|
||||
case 'longlong' :
|
||||
case 'long long' :
|
||||
case 'long long int' :
|
||||
case 'signed long long' :
|
||||
case 'signed long long int' :
|
||||
case 'int64' :
|
||||
case 'int64_t' :
|
||||
case 'ulonglong' :
|
||||
case 'unsigned long long' :
|
||||
case 'unsigned long long int' :
|
||||
case 'uint64' :
|
||||
case 'uint64_t' :
|
||||
throw new Error( 'Error in Volume constructor : this type is not supported in JavaScript' );
|
||||
break;
|
||||
case 'Float32' :
|
||||
case 'float32' :
|
||||
case 'float' :
|
||||
this.data = new Float32Array( arrayBuffer );
|
||||
break;
|
||||
case 'Float64' :
|
||||
case 'float64' :
|
||||
case 'double' :
|
||||
this.data = new Float64Array( arrayBuffer );
|
||||
break;
|
||||
default :
|
||||
this.data = new Uint8Array( arrayBuffer );
|
||||
|
||||
}
|
||||
|
||||
if ( this.data.length !== this.xLength * this.yLength * this.zLength ) {
|
||||
|
||||
throw new Error( 'Error in Volume constructor, lengths are not matching arrayBuffer size' );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @member {Array} spacing Spacing to apply to the volume from IJK to RAS coordinate system
|
||||
*/
|
||||
this.spacing = [ 1, 1, 1 ];
|
||||
/**
|
||||
* @member {Array} offset Offset of the volume in the RAS coordinate system
|
||||
*/
|
||||
this.offset = [ 0, 0, 0 ];
|
||||
/**
|
||||
* @member {Martrix3} matrix The IJK to RAS matrix
|
||||
*/
|
||||
this.matrix = new Matrix3();
|
||||
this.matrix.identity();
|
||||
/**
|
||||
* @member {Martrix3} inverseMatrix The RAS to IJK matrix
|
||||
*/
|
||||
/**
|
||||
* @member {number} lowerThreshold The voxels with values under this threshold won't appear in the slices.
|
||||
* If changed, geometryNeedsUpdate is automatically set to true on all the slices associated to this volume
|
||||
*/
|
||||
let lowerThreshold = - Infinity;
|
||||
Object.defineProperty( this, 'lowerThreshold', {
|
||||
get: function () {
|
||||
|
||||
return lowerThreshold;
|
||||
|
||||
},
|
||||
set: function ( value ) {
|
||||
|
||||
lowerThreshold = value;
|
||||
this.sliceList.forEach( function ( slice ) {
|
||||
|
||||
slice.geometryNeedsUpdate = true;
|
||||
|
||||
} );
|
||||
|
||||
}
|
||||
} );
|
||||
/**
|
||||
* @member {number} upperThreshold The voxels with values over this threshold won't appear in the slices.
|
||||
* If changed, geometryNeedsUpdate is automatically set to true on all the slices associated to this volume
|
||||
*/
|
||||
let upperThreshold = Infinity;
|
||||
Object.defineProperty( this, 'upperThreshold', {
|
||||
get: function () {
|
||||
|
||||
return upperThreshold;
|
||||
|
||||
},
|
||||
set: function ( value ) {
|
||||
|
||||
upperThreshold = value;
|
||||
this.sliceList.forEach( function ( slice ) {
|
||||
|
||||
slice.geometryNeedsUpdate = true;
|
||||
|
||||
} );
|
||||
|
||||
}
|
||||
} );
|
||||
|
||||
|
||||
/**
|
||||
* @member {Array} sliceList The list of all the slices associated to this volume
|
||||
*/
|
||||
this.sliceList = [];
|
||||
|
||||
|
||||
/**
|
||||
* @member {boolean} segmentation in segmentation mode, it can load 16-bits nrrds correctly
|
||||
*/
|
||||
this.segmentation = false;
|
||||
|
||||
|
||||
/**
|
||||
* @member {Array} RASDimensions This array holds the dimensions of the volume in the RAS space
|
||||
*/
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @member {Function} getData Shortcut for data[access(i,j,k)]
|
||||
* @memberof Volume
|
||||
* @param {number} i First coordinate
|
||||
* @param {number} j Second coordinate
|
||||
* @param {number} k Third coordinate
|
||||
* @returns {number} value in the data array
|
||||
*/
|
||||
getData( i, j, k ) {
|
||||
|
||||
return this.data[ k * this.xLength * this.yLength + j * this.xLength + i ];
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @member {Function} access compute the index in the data array corresponding to the given coordinates in IJK system
|
||||
* @memberof Volume
|
||||
* @param {number} i First coordinate
|
||||
* @param {number} j Second coordinate
|
||||
* @param {number} k Third coordinate
|
||||
* @returns {number} index
|
||||
*/
|
||||
access( i, j, k ) {
|
||||
|
||||
return k * this.xLength * this.yLength + j * this.xLength + i;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @member {Function} reverseAccess Retrieve the IJK coordinates of the voxel corresponding of the given index in the data
|
||||
* @memberof Volume
|
||||
* @param {number} index index of the voxel
|
||||
* @returns {Array} [x,y,z]
|
||||
*/
|
||||
reverseAccess( index ) {
|
||||
|
||||
const z = Math.floor( index / ( this.yLength * this.xLength ) );
|
||||
const y = Math.floor( ( index - z * this.yLength * this.xLength ) / this.xLength );
|
||||
const x = index - z * this.yLength * this.xLength - y * this.xLength;
|
||||
return [ x, y, z ];
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @member {Function} map Apply a function to all the voxels, be careful, the value will be replaced
|
||||
* @memberof Volume
|
||||
* @param {Function} functionToMap A function to apply to every voxel, will be called with the following parameters :
|
||||
* value of the voxel
|
||||
* index of the voxel
|
||||
* the data (TypedArray)
|
||||
* @param {Object} context You can specify a context in which call the function, default if this Volume
|
||||
* @returns {Volume} this
|
||||
*/
|
||||
map( functionToMap, context ) {
|
||||
|
||||
const length = this.data.length;
|
||||
context = context || this;
|
||||
|
||||
for ( let i = 0; i < length; i ++ ) {
|
||||
|
||||
this.data[ i ] = functionToMap.call( context, this.data[ i ], i, this.data );
|
||||
|
||||
}
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @member {Function} extractPerpendicularPlane Compute the orientation of the slice and returns all the information relative to the geometry such as sliceAccess, the plane matrix (orientation and position in RAS coordinate) and the dimensions of the plane in both coordinate system.
|
||||
* @memberof Volume
|
||||
* @param {string} axis the normal axis to the slice 'x' 'y' or 'z'
|
||||
* @param {number} index the index of the slice
|
||||
* @returns {Object} an object containing all the usefull information on the geometry of the slice
|
||||
*/
|
||||
extractPerpendicularPlane( axis, RASIndex ) {
|
||||
|
||||
let firstSpacing,
|
||||
secondSpacing,
|
||||
positionOffset,
|
||||
IJKIndex;
|
||||
|
||||
const axisInIJK = new Vector3(),
|
||||
firstDirection = new Vector3(),
|
||||
secondDirection = new Vector3(),
|
||||
planeMatrix = ( new Matrix4() ).identity(),
|
||||
volume = this;
|
||||
|
||||
const dimensions = new Vector3( this.xLength, this.yLength, this.zLength );
|
||||
|
||||
|
||||
switch ( axis ) {
|
||||
|
||||
case 'x' :
|
||||
axisInIJK.set( 1, 0, 0 );
|
||||
firstDirection.set( 0, 0, - 1 );
|
||||
secondDirection.set( 0, - 1, 0 );
|
||||
firstSpacing = this.spacing[ this.axisOrder.indexOf( 'z' ) ];
|
||||
secondSpacing = this.spacing[ this.axisOrder.indexOf( 'y' ) ];
|
||||
IJKIndex = new Vector3( RASIndex, 0, 0 );
|
||||
|
||||
planeMatrix.multiply( ( new Matrix4() ).makeRotationY( Math.PI / 2 ) );
|
||||
positionOffset = ( volume.RASDimensions[ 0 ] - 1 ) / 2;
|
||||
planeMatrix.setPosition( new Vector3( RASIndex - positionOffset, 0, 0 ) );
|
||||
break;
|
||||
case 'y' :
|
||||
axisInIJK.set( 0, 1, 0 );
|
||||
firstDirection.set( 1, 0, 0 );
|
||||
secondDirection.set( 0, 0, 1 );
|
||||
firstSpacing = this.spacing[ this.axisOrder.indexOf( 'x' ) ];
|
||||
secondSpacing = this.spacing[ this.axisOrder.indexOf( 'z' ) ];
|
||||
IJKIndex = new Vector3( 0, RASIndex, 0 );
|
||||
|
||||
planeMatrix.multiply( ( new Matrix4() ).makeRotationX( - Math.PI / 2 ) );
|
||||
positionOffset = ( volume.RASDimensions[ 1 ] - 1 ) / 2;
|
||||
planeMatrix.setPosition( new Vector3( 0, RASIndex - positionOffset, 0 ) );
|
||||
break;
|
||||
case 'z' :
|
||||
default :
|
||||
axisInIJK.set( 0, 0, 1 );
|
||||
firstDirection.set( 1, 0, 0 );
|
||||
secondDirection.set( 0, - 1, 0 );
|
||||
firstSpacing = this.spacing[ this.axisOrder.indexOf( 'x' ) ];
|
||||
secondSpacing = this.spacing[ this.axisOrder.indexOf( 'y' ) ];
|
||||
IJKIndex = new Vector3( 0, 0, RASIndex );
|
||||
|
||||
positionOffset = ( volume.RASDimensions[ 2 ] - 1 ) / 2;
|
||||
planeMatrix.setPosition( new Vector3( 0, 0, RASIndex - positionOffset ) );
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
if ( ! this.segmentation ) {
|
||||
|
||||
firstDirection.applyMatrix4( volume.inverseMatrix ).normalize();
|
||||
secondDirection.applyMatrix4( volume.inverseMatrix ).normalize();
|
||||
axisInIJK.applyMatrix4( volume.inverseMatrix ).normalize();
|
||||
|
||||
}
|
||||
|
||||
firstDirection.arglet = 'i';
|
||||
secondDirection.arglet = 'j';
|
||||
const iLength = Math.floor( Math.abs( firstDirection.dot( dimensions ) ) );
|
||||
const jLength = Math.floor( Math.abs( secondDirection.dot( dimensions ) ) );
|
||||
const planeWidth = Math.abs( iLength * firstSpacing );
|
||||
const planeHeight = Math.abs( jLength * secondSpacing );
|
||||
|
||||
IJKIndex = Math.abs( Math.round( IJKIndex.applyMatrix4( volume.inverseMatrix ).dot( axisInIJK ) ) );
|
||||
const base = [ new Vector3( 1, 0, 0 ), new Vector3( 0, 1, 0 ), new Vector3( 0, 0, 1 ) ];
|
||||
const iDirection = [ firstDirection, secondDirection, axisInIJK ].find( function ( x ) {
|
||||
|
||||
return Math.abs( x.dot( base[ 0 ] ) ) > 0.9;
|
||||
|
||||
} );
|
||||
const jDirection = [ firstDirection, secondDirection, axisInIJK ].find( function ( x ) {
|
||||
|
||||
return Math.abs( x.dot( base[ 1 ] ) ) > 0.9;
|
||||
|
||||
} );
|
||||
const kDirection = [ firstDirection, secondDirection, axisInIJK ].find( function ( x ) {
|
||||
|
||||
return Math.abs( x.dot( base[ 2 ] ) ) > 0.9;
|
||||
|
||||
} );
|
||||
|
||||
function sliceAccess( i, j ) {
|
||||
|
||||
const si = ( iDirection === axisInIJK ) ? IJKIndex : ( iDirection.arglet === 'i' ? i : j );
|
||||
const sj = ( jDirection === axisInIJK ) ? IJKIndex : ( jDirection.arglet === 'i' ? i : j );
|
||||
const sk = ( kDirection === axisInIJK ) ? IJKIndex : ( kDirection.arglet === 'i' ? i : j );
|
||||
|
||||
// invert indices if necessary
|
||||
|
||||
const accessI = ( iDirection.dot( base[ 0 ] ) > 0 ) ? si : ( volume.xLength - 1 ) - si;
|
||||
const accessJ = ( jDirection.dot( base[ 1 ] ) > 0 ) ? sj : ( volume.yLength - 1 ) - sj;
|
||||
const accessK = ( kDirection.dot( base[ 2 ] ) > 0 ) ? sk : ( volume.zLength - 1 ) - sk;
|
||||
|
||||
return volume.access( accessI, accessJ, accessK );
|
||||
|
||||
}
|
||||
|
||||
return {
|
||||
iLength: iLength,
|
||||
jLength: jLength,
|
||||
sliceAccess: sliceAccess,
|
||||
matrix: planeMatrix,
|
||||
planeWidth: planeWidth,
|
||||
planeHeight: planeHeight
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @member {Function} extractSlice Returns a slice corresponding to the given axis and index
|
||||
* The coordinate are given in the Right Anterior Superior coordinate format
|
||||
* @memberof Volume
|
||||
* @param {string} axis the normal axis to the slice 'x' 'y' or 'z'
|
||||
* @param {number} index the index of the slice
|
||||
* @returns {VolumeSlice} the extracted slice
|
||||
*/
|
||||
extractSlice( axis, index ) {
|
||||
|
||||
const slice = new VolumeSlice( this, index, axis );
|
||||
this.sliceList.push( slice );
|
||||
return slice;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @member {Function} repaintAllSlices Call repaint on all the slices extracted from this volume
|
||||
* @see VolumeSlice.repaint
|
||||
* @memberof Volume
|
||||
* @returns {Volume} this
|
||||
*/
|
||||
repaintAllSlices() {
|
||||
|
||||
this.sliceList.forEach( function ( slice ) {
|
||||
|
||||
slice.repaint();
|
||||
|
||||
} );
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @member {Function} computeMinMax Compute the minimum and the maximum of the data in the volume
|
||||
* @memberof Volume
|
||||
* @returns {Array} [min,max]
|
||||
*/
|
||||
computeMinMax() {
|
||||
|
||||
let min = Infinity;
|
||||
let max = - Infinity;
|
||||
|
||||
// buffer the length
|
||||
const datasize = this.data.length;
|
||||
|
||||
let i = 0;
|
||||
|
||||
for ( i = 0; i < datasize; i ++ ) {
|
||||
|
||||
if ( ! isNaN( this.data[ i ] ) ) {
|
||||
|
||||
const value = this.data[ i ];
|
||||
min = Math.min( min, value );
|
||||
max = Math.max( max, value );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
this.min = min;
|
||||
this.max = max;
|
||||
|
||||
return [ min, max ];
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export { Volume };
|
229
dist/electron/static/sdk/three/jsm/misc/VolumeSlice.js
vendored
Normal file
229
dist/electron/static/sdk/three/jsm/misc/VolumeSlice.js
vendored
Normal file
@ -0,0 +1,229 @@
|
||||
import {
|
||||
ClampToEdgeWrapping,
|
||||
DoubleSide,
|
||||
LinearFilter,
|
||||
Mesh,
|
||||
MeshBasicMaterial,
|
||||
PlaneGeometry,
|
||||
Texture,
|
||||
SRGBColorSpace
|
||||
} from 'three';
|
||||
|
||||
/**
|
||||
* This class has been made to hold a slice of a volume data
|
||||
* @class
|
||||
* @param {Volume} volume The associated volume
|
||||
* @param {number} [index=0] The index of the slice
|
||||
* @param {string} [axis='z'] For now only 'x', 'y' or 'z' but later it will change to a normal vector
|
||||
* @see Volume
|
||||
*/
|
||||
class VolumeSlice {
|
||||
|
||||
constructor( volume, index, axis ) {
|
||||
|
||||
const slice = this;
|
||||
/**
|
||||
* @member {Volume} volume The associated volume
|
||||
*/
|
||||
this.volume = volume;
|
||||
/**
|
||||
* @member {Number} index The index of the slice, if changed, will automatically call updateGeometry at the next repaint
|
||||
*/
|
||||
index = index || 0;
|
||||
Object.defineProperty( this, 'index', {
|
||||
get: function () {
|
||||
|
||||
return index;
|
||||
|
||||
},
|
||||
set: function ( value ) {
|
||||
|
||||
index = value;
|
||||
slice.geometryNeedsUpdate = true;
|
||||
return index;
|
||||
|
||||
}
|
||||
} );
|
||||
/**
|
||||
* @member {String} axis The normal axis
|
||||
*/
|
||||
this.axis = axis || 'z';
|
||||
|
||||
/**
|
||||
* @member {HTMLCanvasElement} canvas The final canvas used for the texture
|
||||
*/
|
||||
/**
|
||||
* @member {CanvasRenderingContext2D} ctx Context of the canvas
|
||||
*/
|
||||
this.canvas = document.createElement( 'canvas' );
|
||||
/**
|
||||
* @member {HTMLCanvasElement} canvasBuffer The intermediary canvas used to paint the data
|
||||
*/
|
||||
/**
|
||||
* @member {CanvasRenderingContext2D} ctxBuffer Context of the canvas buffer
|
||||
*/
|
||||
this.canvasBuffer = document.createElement( 'canvas' );
|
||||
this.updateGeometry();
|
||||
|
||||
|
||||
const canvasMap = new Texture( this.canvas );
|
||||
canvasMap.minFilter = LinearFilter;
|
||||
canvasMap.wrapS = canvasMap.wrapT = ClampToEdgeWrapping;
|
||||
canvasMap.colorSpace = SRGBColorSpace;
|
||||
const material = new MeshBasicMaterial( { map: canvasMap, side: DoubleSide, transparent: true } );
|
||||
/**
|
||||
* @member {Mesh} mesh The mesh ready to get used in the scene
|
||||
*/
|
||||
this.mesh = new Mesh( this.geometry, material );
|
||||
this.mesh.matrixAutoUpdate = false;
|
||||
/**
|
||||
* @member {Boolean} geometryNeedsUpdate If set to true, updateGeometry will be triggered at the next repaint
|
||||
*/
|
||||
this.geometryNeedsUpdate = true;
|
||||
this.repaint();
|
||||
|
||||
/**
|
||||
* @member {Number} iLength Width of slice in the original coordinate system, corresponds to the width of the buffer canvas
|
||||
*/
|
||||
|
||||
/**
|
||||
* @member {Number} jLength Height of slice in the original coordinate system, corresponds to the height of the buffer canvas
|
||||
*/
|
||||
|
||||
/**
|
||||
* @member {Function} sliceAccess Function that allow the slice to access right data
|
||||
* @see Volume.extractPerpendicularPlane
|
||||
* @param {Number} i The first coordinate
|
||||
* @param {Number} j The second coordinate
|
||||
* @returns {Number} the index corresponding to the voxel in volume.data of the given position in the slice
|
||||
*/
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @member {Function} repaint Refresh the texture and the geometry if geometryNeedsUpdate is set to true
|
||||
* @memberof VolumeSlice
|
||||
*/
|
||||
repaint() {
|
||||
|
||||
if ( this.geometryNeedsUpdate ) {
|
||||
|
||||
this.updateGeometry();
|
||||
|
||||
}
|
||||
|
||||
const iLength = this.iLength,
|
||||
jLength = this.jLength,
|
||||
sliceAccess = this.sliceAccess,
|
||||
volume = this.volume,
|
||||
canvas = this.canvasBuffer,
|
||||
ctx = this.ctxBuffer;
|
||||
|
||||
|
||||
// get the imageData and pixel array from the canvas
|
||||
const imgData = ctx.getImageData( 0, 0, iLength, jLength );
|
||||
const data = imgData.data;
|
||||
const volumeData = volume.data;
|
||||
const upperThreshold = volume.upperThreshold;
|
||||
const lowerThreshold = volume.lowerThreshold;
|
||||
const windowLow = volume.windowLow;
|
||||
const windowHigh = volume.windowHigh;
|
||||
|
||||
// manipulate some pixel elements
|
||||
let pixelCount = 0;
|
||||
|
||||
if ( volume.dataType === 'label' ) {
|
||||
|
||||
//this part is currently useless but will be used when colortables will be handled
|
||||
for ( let j = 0; j < jLength; j ++ ) {
|
||||
|
||||
for ( let i = 0; i < iLength; i ++ ) {
|
||||
|
||||
let label = volumeData[ sliceAccess( i, j ) ];
|
||||
label = label >= this.colorMap.length ? ( label % this.colorMap.length ) + 1 : label;
|
||||
const color = this.colorMap[ label ];
|
||||
data[ 4 * pixelCount ] = ( color >> 24 ) & 0xff;
|
||||
data[ 4 * pixelCount + 1 ] = ( color >> 16 ) & 0xff;
|
||||
data[ 4 * pixelCount + 2 ] = ( color >> 8 ) & 0xff;
|
||||
data[ 4 * pixelCount + 3 ] = color & 0xff;
|
||||
pixelCount ++;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
for ( let j = 0; j < jLength; j ++ ) {
|
||||
|
||||
for ( let i = 0; i < iLength; i ++ ) {
|
||||
|
||||
let value = volumeData[ sliceAccess( i, j ) ];
|
||||
let alpha = 0xff;
|
||||
//apply threshold
|
||||
alpha = upperThreshold >= value ? ( lowerThreshold <= value ? alpha : 0 ) : 0;
|
||||
//apply window level
|
||||
value = Math.floor( 255 * ( value - windowLow ) / ( windowHigh - windowLow ) );
|
||||
value = value > 255 ? 255 : ( value < 0 ? 0 : value | 0 );
|
||||
|
||||
data[ 4 * pixelCount ] = value;
|
||||
data[ 4 * pixelCount + 1 ] = value;
|
||||
data[ 4 * pixelCount + 2 ] = value;
|
||||
data[ 4 * pixelCount + 3 ] = alpha;
|
||||
pixelCount ++;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ctx.putImageData( imgData, 0, 0 );
|
||||
this.ctx.drawImage( canvas, 0, 0, iLength, jLength, 0, 0, this.canvas.width, this.canvas.height );
|
||||
|
||||
|
||||
this.mesh.material.map.needsUpdate = true;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @member {Function} Refresh the geometry according to axis and index
|
||||
* @see Volume.extractPerpendicularPlane
|
||||
* @memberof VolumeSlice
|
||||
*/
|
||||
updateGeometry() {
|
||||
|
||||
const extracted = this.volume.extractPerpendicularPlane( this.axis, this.index );
|
||||
this.sliceAccess = extracted.sliceAccess;
|
||||
this.jLength = extracted.jLength;
|
||||
this.iLength = extracted.iLength;
|
||||
this.matrix = extracted.matrix;
|
||||
|
||||
this.canvas.width = extracted.planeWidth;
|
||||
this.canvas.height = extracted.planeHeight;
|
||||
this.canvasBuffer.width = this.iLength;
|
||||
this.canvasBuffer.height = this.jLength;
|
||||
this.ctx = this.canvas.getContext( '2d' );
|
||||
this.ctxBuffer = this.canvasBuffer.getContext( '2d' );
|
||||
|
||||
if ( this.geometry ) this.geometry.dispose(); // dispose existing geometry
|
||||
|
||||
this.geometry = new PlaneGeometry( extracted.planeWidth, extracted.planeHeight );
|
||||
|
||||
if ( this.mesh ) {
|
||||
|
||||
this.mesh.geometry = this.geometry;
|
||||
//reset mesh matrix
|
||||
this.mesh.matrix.identity();
|
||||
this.mesh.applyMatrix4( this.matrix );
|
||||
|
||||
}
|
||||
|
||||
this.geometryNeedsUpdate = false;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export { VolumeSlice };
|
Reference in New Issue
Block a user