phy-engine
Version:
JavaScript 3D Physics for three.js
1,786 lines (1,283 loc) • 1.36 MB
JavaScript
/**
* @license
* Copyright 2010-2025 Phy.js Authors
* SPDX-License-Identifier: MIT
*/
'use strict';
var three = require('three');
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
const PI = Math.PI;
const torad$3 = PI / 180;
const todeg$1 = 180 / PI;
const EPSILON = Number.EPSILON;//0.00001;
const PI90 = PI*0.5;
const M$2 = {
//-----------------------
// LIGHT
//-----------------------
luminousPowers : {
'110000 lm (1000W)': 110000,
'3500 lm (300W)': 3500,
'1700 lm (100W)': 1700,
'800 lm (60W)': 800,
'400 lm (40W)': 400,
'180 lm (25W)': 180,
'20 lm (4W)': 20,
'Off': 0
},
// ref for solar irradiances: https://en.wikipedia.org/wiki/Lux
luminousIrradiances : {
'0.0001 lx (Moonless Night)': 0.0001,
'0.002 lx (Night Airglow)': 0.002,
'0.5 lx (Full Moon)': 0.5,
'3.4 lx (City Twilight)': 3.4,
'50 lx (Living Room)': 50,
'100 lx (Very Overcast)': 100,
'350 lx (Office Room)': 350,
'400 lx (Sunrise/Sunset)': 400,
'1000 lx (Overcast)': 1000,
'18000 lx (Daylight)': 18000,
'50000 lx (Direct Sun)': 50000
},
exposure : (v) => ( Math.pow( v, 5.0 ) ),
//Candela is default three light intensity
candelaToLumens : (v) => ( v * 4 * Math.PI ),
lumensToCandela : (v) => ( v / ( 4 * Math.PI ) ),
//-----------------------
// MATH
//-----------------------
todeg:todeg$1,
torad:torad$3,
toFixed: ( x, n = 3 ) => ( x.toFixed(n) * 1 ),
toRound: ( x, n = 3 ) => ( Math.trunc(x) ),
clamp: ( v, min = 0, max = 1 ) => {
v = v < min ? min : v;
v = v > max ? max : v;
return v;
},
clampA: ( v, min, max ) => ( Math.max( min, Math.min( max, v ) ) ),
smoothstep: ( min, max, t ) => {
t = M$2.clamp(t);
t = -2 * t * t * t + 3.0 * t * t;
return min * t + max * (1 - t);
},
remap: ( f, fmin, fmax, min, max) => {
return min + (f - fmin) * (max - min) / (fmax - fmin);
},
lerp: ( x, y, t ) => ( ( 1 - t ) * x + t * y ),
damp: ( x, y, lambda, dt ) => ( M$2.lerp( x, y, 1 - Math.exp( - lambda * dt ) ) ),
nearAngle: ( s1, s2, deg = false ) => ( s2 + Math.atan2(Math.sin(s1-s2), Math.cos(s1-s2)) * (deg ? todeg$1 : 1) ),
unwrapDeg: ( r ) => ( r - (Math.floor((r + 180)/360))*360 ),
//unwrapRad: ( r ) => (r - (Math.floor((r + Math.PI)/(2*Math.PI)))*2*Math.PI),
unwrapRad: ( r ) => ( Math.atan2(Math.sin(r), Math.cos(r)) ),
nearEquals: ( a, b, t = 1e-4 ) => ( Math.abs(a - b) <= t ? true : false ),
autoSize: ( s = [ 1, 1, 1 ], type = 'box' ) => {
if ( s.length === 1 ) s[ 1 ] = s[ 0 ];
let radius = s[0];
let height = s[1];
if( type === 'sphere' ) s = [ radius, radius, radius ];
if( type === 'cylinder' || type === 'wheel' || type === 'capsule' ) s = [ radius, height, radius ];
if( type === 'cone' || type === 'pyramid' ) s = [ radius, height, radius ];
if ( s.length === 2 ) s[ 2 ] = s[ 0 ];
return s;
},
shuffle: (array) => {
let shuffled = array
.map(value => ({ value, sort: Math.random() }))
.sort((a, b) => a.sort - b.sort)
.map(({ value }) => value);
return shuffled
},
/*distance: ( a, b = { x:0, y:0, z:0 } ) => { // rotation array in degree
const dx = a.x ? a.x - b.x : 0
const dy = a.y ? a.y - b.y : 0
const dz = a.z ? a.z - b.z : 0
return Math.sqrt( dx * dx + dy * dy + dz * dz );
},*/
//-----------------------
// RANDOM
//-----------------------
randomSign: () => ( Math.random() < 0.5 ? -1 : 1 ),
randSpread: ( range ) => ( range * ( 0.5 - Math.random() ) ),
rand: ( low = 0, high = 1 ) => ( low + Math.random() * ( high - low ) ),
randInt: ( low, high ) => ( low + Math.floor( Math.random() * ( high - low + 1 ) ) ),
randIntUnic: ( low, high, num ) => {
var arr = [];
while( arr.length < num ){
var r = M$2.randInt(low, high);
if( arr.indexOf(r) === -1 ) arr.push(r);
}
return arr;
},
//-----------------------
// EXTRA
//-----------------------
fromTransform: ( p1, q1, p2, q2 = [0,0,0,1], inv = false ) => {
let m1 = M$2.composeMatrixArray( p1, q1 );
let m2 = M$2.composeMatrixArray( p2, q2 );
if( inv ) m1 = M$2.invertMatrixArray(m1);
m1 = M$2.multiplyMatrixArray( m1, m2 );
return [ m1[12],m1[13],m1[14]]
},
fromTransformToQ: ( p, q, inv = false ) => {
let m = M$2.composeMatrixArray( p, q );
let res = M$2.decomposeFullMatrixArray( m );
let q1 = res.q;
if(inv) q1 = M$2.quatInvert(q1);
return q1
},
// special Havok motion !!!
lerpTransform: ( oar, ar, t ) => {
let op = oar[0];
let oq = oar[1];
let p = ar[0];
let q = ar[1];
p = M$2.lerpArray(op, p, t);
q = M$2.slerpQuatArray(oq, q, t);
return [p,q]
},
//-----------------------
// MATRIX
//-----------------------
composeMatrixArray: ( p, q, s = [1,1,1] ) => {
const x = q[0], y = q[1], z = q[2], w = q[3];
const x2 = x + x, y2 = y + y, z2 = z + z;
const xx = x * x2, xy = x * y2, xz = x * z2;
const yy = y * y2, yz = y * z2, zz = z * z2;
const wx = w * x2, wy = w * y2, wz = w * z2;
const sx = s[0], sy = s[1], sz = s[2];
return [
( 1 - ( yy + zz ) ) * sx, ( xy + wz ) * sx, ( xz - wy ) * sx, 0,
( xy - wz ) * sy, ( 1 - ( xx + zz ) ) * sy, ( yz + wx ) * sy, 0,
( xz + wy ) * sz, ( yz - wx ) * sz, ( 1 - ( xx + yy ) ) * sz, 0,
p[0], p[1], p[2], 1
]
},
multiplyMatrixArray: ( a, b ) => {
const ae = a;
const be = b;
const te = [];
const a11 = ae[ 0 ], a12 = ae[ 4 ], a13 = ae[ 8 ], a14 = ae[ 12 ];
const a21 = ae[ 1 ], a22 = ae[ 5 ], a23 = ae[ 9 ], a24 = ae[ 13 ];
const a31 = ae[ 2 ], a32 = ae[ 6 ], a33 = ae[ 10 ], a34 = ae[ 14 ];
const a41 = ae[ 3 ], a42 = ae[ 7 ], a43 = ae[ 11 ], a44 = ae[ 15 ];
const b11 = be[ 0 ], b12 = be[ 4 ], b13 = be[ 8 ], b14 = be[ 12 ];
const b21 = be[ 1 ], b22 = be[ 5 ], b23 = be[ 9 ], b24 = be[ 13 ];
const b31 = be[ 2 ], b32 = be[ 6 ], b33 = be[ 10 ], b34 = be[ 14 ];
const b41 = be[ 3 ], b42 = be[ 7 ], b43 = be[ 11 ], b44 = be[ 15 ];
te[ 0 ] = a11 * b11 + a12 * b21 + a13 * b31 + a14 * b41;
te[ 4 ] = a11 * b12 + a12 * b22 + a13 * b32 + a14 * b42;
te[ 8 ] = a11 * b13 + a12 * b23 + a13 * b33 + a14 * b43;
te[ 12 ] = a11 * b14 + a12 * b24 + a13 * b34 + a14 * b44;
te[ 1 ] = a21 * b11 + a22 * b21 + a23 * b31 + a24 * b41;
te[ 5 ] = a21 * b12 + a22 * b22 + a23 * b32 + a24 * b42;
te[ 9 ] = a21 * b13 + a22 * b23 + a23 * b33 + a24 * b43;
te[ 13 ] = a21 * b14 + a22 * b24 + a23 * b34 + a24 * b44;
te[ 2 ] = a31 * b11 + a32 * b21 + a33 * b31 + a34 * b41;
te[ 6 ] = a31 * b12 + a32 * b22 + a33 * b32 + a34 * b42;
te[ 10 ] = a31 * b13 + a32 * b23 + a33 * b33 + a34 * b43;
te[ 14 ] = a31 * b14 + a32 * b24 + a33 * b34 + a34 * b44;
te[ 3 ] = a41 * b11 + a42 * b21 + a43 * b31 + a44 * b41;
te[ 7 ] = a41 * b12 + a42 * b22 + a43 * b32 + a44 * b42;
te[ 11 ] = a41 * b13 + a42 * b23 + a43 * b33 + a44 * b43;
te[ 15 ] = a41 * b14 + a42 * b24 + a43 * b34 + a44 * b44;
return te;
},
invertMatrixArray: ( m ) => {
const te = m,
n11 = te[ 0 ], n21 = te[ 1 ], n31 = te[ 2 ], n41 = te[ 3 ],
n12 = te[ 4 ], n22 = te[ 5 ], n32 = te[ 6 ], n42 = te[ 7 ],
n13 = te[ 8 ], n23 = te[ 9 ], n33 = te[ 10 ], n43 = te[ 11 ],
n14 = te[ 12 ], n24 = te[ 13 ], n34 = te[ 14 ], n44 = te[ 15 ],
t11 = n23 * n34 * n42 - n24 * n33 * n42 + n24 * n32 * n43 - n22 * n34 * n43 - n23 * n32 * n44 + n22 * n33 * n44,
t12 = n14 * n33 * n42 - n13 * n34 * n42 - n14 * n32 * n43 + n12 * n34 * n43 + n13 * n32 * n44 - n12 * n33 * n44,
t13 = n13 * n24 * n42 - n14 * n23 * n42 + n14 * n22 * n43 - n12 * n24 * n43 - n13 * n22 * n44 + n12 * n23 * n44,
t14 = n14 * n23 * n32 - n13 * n24 * n32 - n14 * n22 * n33 + n12 * n24 * n33 + n13 * n22 * n34 - n12 * n23 * n34;
const det = n11 * t11 + n21 * t12 + n31 * t13 + n41 * t14;
if ( det === 0 ) return [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ];
const detInv = 1 / det;
te[ 0 ] = t11 * detInv;
te[ 1 ] = ( n24 * n33 * n41 - n23 * n34 * n41 - n24 * n31 * n43 + n21 * n34 * n43 + n23 * n31 * n44 - n21 * n33 * n44 ) * detInv;
te[ 2 ] = ( n22 * n34 * n41 - n24 * n32 * n41 + n24 * n31 * n42 - n21 * n34 * n42 - n22 * n31 * n44 + n21 * n32 * n44 ) * detInv;
te[ 3 ] = ( n23 * n32 * n41 - n22 * n33 * n41 - n23 * n31 * n42 + n21 * n33 * n42 + n22 * n31 * n43 - n21 * n32 * n43 ) * detInv;
te[ 4 ] = t12 * detInv;
te[ 5 ] = ( n13 * n34 * n41 - n14 * n33 * n41 + n14 * n31 * n43 - n11 * n34 * n43 - n13 * n31 * n44 + n11 * n33 * n44 ) * detInv;
te[ 6 ] = ( n14 * n32 * n41 - n12 * n34 * n41 - n14 * n31 * n42 + n11 * n34 * n42 + n12 * n31 * n44 - n11 * n32 * n44 ) * detInv;
te[ 7 ] = ( n12 * n33 * n41 - n13 * n32 * n41 + n13 * n31 * n42 - n11 * n33 * n42 - n12 * n31 * n43 + n11 * n32 * n43 ) * detInv;
te[ 8 ] = t13 * detInv;
te[ 9 ] = ( n14 * n23 * n41 - n13 * n24 * n41 - n14 * n21 * n43 + n11 * n24 * n43 + n13 * n21 * n44 - n11 * n23 * n44 ) * detInv;
te[ 10 ] = ( n12 * n24 * n41 - n14 * n22 * n41 + n14 * n21 * n42 - n11 * n24 * n42 - n12 * n21 * n44 + n11 * n22 * n44 ) * detInv;
te[ 11 ] = ( n13 * n22 * n41 - n12 * n23 * n41 - n13 * n21 * n42 + n11 * n23 * n42 + n12 * n21 * n43 - n11 * n22 * n43 ) * detInv;
te[ 12 ] = t14 * detInv;
te[ 13 ] = ( n13 * n24 * n31 - n14 * n23 * n31 + n14 * n21 * n33 - n11 * n24 * n33 - n13 * n21 * n34 + n11 * n23 * n34 ) * detInv;
te[ 14 ] = ( n14 * n22 * n31 - n12 * n24 * n31 - n14 * n21 * n32 + n11 * n24 * n32 + n12 * n21 * n34 - n11 * n22 * n34 ) * detInv;
te[ 15 ] = ( n12 * n23 * n31 - n13 * n22 * n31 + n13 * n21 * n32 - n11 * n23 * n32 - n12 * n21 * n33 + n11 * n22 * n33 ) * detInv;
return te;
},
matrixArrayDeterminant: ( m ) => {
const te = m;
const n11 = te[ 0 ], n12 = te[ 4 ], n13 = te[ 8 ], n14 = te[ 12 ];
const n21 = te[ 1 ], n22 = te[ 5 ], n23 = te[ 9 ], n24 = te[ 13 ];
const n31 = te[ 2 ], n32 = te[ 6 ], n33 = te[ 10 ], n34 = te[ 14 ];
const n41 = te[ 3 ], n42 = te[ 7 ], n43 = te[ 11 ], n44 = te[ 15 ];
//TODO: make this more efficient
//( based on http://www.euclideanspace.com/maths/algebra/matrix/functions/inverse/fourD/index.htm )
return (
n41 * (
+ n14 * n23 * n32
- n13 * n24 * n32
- n14 * n22 * n33
+ n12 * n24 * n33
+ n13 * n22 * n34
- n12 * n23 * n34
) +
n42 * (
+ n11 * n23 * n34
- n11 * n24 * n33
+ n14 * n21 * n33
- n13 * n21 * n34
+ n13 * n24 * n31
- n14 * n23 * n31
) +
n43 * (
+ n11 * n24 * n32
- n11 * n22 * n34
- n14 * n21 * n32
+ n12 * n21 * n34
+ n14 * n22 * n31
- n12 * n24 * n31
) +
n44 * (
- n13 * n22 * n31
- n11 * n23 * n32
+ n11 * n22 * n33
+ n13 * n21 * n32
- n12 * n21 * n33
+ n12 * n23 * n31
)
);
},
decomposeMatrixArray: ( m ) => {
return [
m[12],m[13],m[14],
]
},
decomposeFullMatrixArray: ( m ) => {
const te = m;
let sx = M$2.lengthArray( [te[ 0 ], te[ 1 ], te[ 2 ]] );//_v1.set( te[ 0 ], te[ 1 ], te[ 2 ] ).length();
const sy = M$2.lengthArray( [te[ 4 ], te[ 5 ], te[ 6 ]] );//_v1.set( te[ 4 ], te[ 5 ], te[ 6 ] ).length();
const sz = M$2.lengthArray( [te[ 8 ], te[ 9 ], te[ 10 ]] );//_v1.set( te[ 8 ], te[ 9 ], te[ 10 ] ).length();
// if determine is negative, we need to invert one scale
const det = M$2.matrixArrayDeterminant(m);
if ( det < 0 ) sx = - sx;
let m1 = [...m];
const invSX = 1 / sx;
const invSY = 1 / sy;
const invSZ = 1 / sz;
m1[ 0 ] *= invSX;
m1[ 1 ] *= invSX;
m1[ 2 ] *= invSX;
m1[ 4 ] *= invSY;
m1[ 5 ] *= invSY;
m1[ 6 ] *= invSY;
m1[ 8 ] *= invSZ;
m1[ 9 ] *= invSZ;
m1[ 10 ] *= invSZ;
let q = M$2.quatFromRotationMatrix(m1);
return {
p:[m[12],m[13],m[14]],
q:q,
s:[sx,sy,sz]
}
},
// for physx substep
applyTransformArray: ( v, p, q, s = [1,1,1] ) => {
const e = M$2.composeMatrixArray( p, q, s );
const x = v[0], y = v[1], z = v[2];
const w = 1 / ( e[ 3 ] * x + e[ 7 ] * y + e[ 11 ] * z + e[ 15 ] );
return [
( e[ 0 ] * x + e[ 4 ] * y + e[ 8 ] * z + e[ 12 ] ) * w,
( e[ 1 ] * x + e[ 5 ] * y + e[ 9 ] * z + e[ 13 ] ) * w,
( e[ 2 ] * x + e[ 6 ] * y + e[ 10 ] * z + e[ 14 ] ) * w
]
},
slerpQuatArray: ( a, b, t ) => {
if ( t === 0 ) return a;
if ( t === 1 ) return b;
let r = [...a];
const x = a[0], y = a[1], z = a[2], w = a[3];
const qx = b[0], qy = b[1], qz = b[2], qw = b[3];
let cosHalfTheta = w * qw + x * qx + y * qy + z * qz;
if ( cosHalfTheta < 0 ) {
r = [ -qx, -qy, -qz, -qw ];
cosHalfTheta = - cosHalfTheta;
} else {
r = [...b];
}
if ( cosHalfTheta >= 1.0 ) return a
const sqrSinHalfTheta = 1.0 - cosHalfTheta * cosHalfTheta;
if ( sqrSinHalfTheta <= EPSILON ) {
const s = 1 - t;
r[3] = s * w + t * r[3];
r[0] = s * x + t * r[0];
r[1] = s * y + t * r[1];
r[2] = s * z + t * r[2];
return M$2.quatNomalize(r);
}
const sinHalfTheta = Math.sqrt( sqrSinHalfTheta );
const halfTheta = Math.atan2( sinHalfTheta, cosHalfTheta );
const ratioA = Math.sin( ( 1 - t ) * halfTheta ) / sinHalfTheta, ratioB = Math.sin( t * halfTheta ) / sinHalfTheta;
r[3] = ( w * ratioA + r[3] * ratioB );
r[0] = ( x * ratioA + r[0] * ratioB );
r[1] = ( y * ratioA + r[1] * ratioB );
r[2] = ( z * ratioA + r[2] * ratioB );
return r;
},
//-----------------------
// QUAT
//-----------------------
toLocalQuatArray: ( rot = [0,0,0], b ) => { // rotation array in degree
let q1 = M$2.quatFromEuler( rot );
let q2 = M$2.quatInvert( b.quaternion.toArray() );
return M$2.quatMultiply( q2, q1 )
/*quat.setFromEuler( euler.fromArray( math.vectorad( rot ) ) )
quat.premultiply( b.quaternion.invert() );
return quat.toArray();*/
},
quatFromRotationMatrix: ( m ) => {
let q = [0,0,0,1];
const te = m,
m11 = te[ 0 ], m12 = te[ 4 ], m13 = te[ 8 ],
m21 = te[ 1 ], m22 = te[ 5 ], m23 = te[ 9 ],
m31 = te[ 2 ], m32 = te[ 6 ], m33 = te[ 10 ],
trace = m11 + m22 + m33;
if ( trace > 0 ) {
const s = 0.5 / Math.sqrt( trace + 1.0 );
q[3] = 0.25 / s;
q[0] = ( m32 - m23 ) * s;
q[1] = ( m13 - m31 ) * s;
q[2] = ( m21 - m12 ) * s;
} else if ( m11 > m22 && m11 > m33 ) {
const s = 2.0 * Math.sqrt( 1.0 + m11 - m22 - m33 );
q[3] = ( m32 - m23 ) / s;
q[0] = 0.25 * s;
q[1] = ( m12 + m21 ) / s;
q[2] = ( m13 + m31 ) / s;
} else if ( m22 > m33 ) {
const s = 2.0 * Math.sqrt( 1.0 + m22 - m11 - m33 );
q[3] = ( m13 - m31 ) / s;
q[0] = ( m12 + m21 ) / s;
q[1] = 0.25 * s;
q[2] = ( m23 + m32 ) / s;
} else {
const s = 2.0 * Math.sqrt( 1.0 + m33 - m11 - m22 );
q[3] = ( m21 - m12 ) / s;
q[0] = ( m13 + m31 ) / s;
q[1] = ( m23 + m32 ) / s;
q[2] = 0.25 * s;
}
return q;
},
quatFromEuler: ( r = [0,0,0], isDeg = true ) => {
const cos = Math.cos;
const sin = Math.sin;
const n = isDeg ? torad$3 : 1;
const x = (r[0]*n) * 0.5, y = (r[1]*n) * 0.5, z = (r[2]*n) * 0.5;
const c1 = cos( x ), c2 = cos( y ), c3 = cos( z );
const s1 = sin( x ), s2 = sin( y ), s3 = sin( z );
return [
s1 * c2 * c3 + c1 * s2 * s3,
c1 * s2 * c3 - s1 * c2 * s3,
c1 * c2 * s3 + s1 * s2 * c3,
c1 * c2 * c3 - s1 * s2 * s3
]
},
quatFromAxis: ( r = [0,0,0], angle, isDeg = true ) => {
const n = isDeg ? torad$3 : 1;
const halfAngle = (angle * 0.5) * n, s = Math.sin( halfAngle );
return [
r[0] * s,
r[1] * s,
r[2] * s,
Math.cos( halfAngle )
]
},
quatNomalize: ( q ) => {
let l = M$2.lengthArray( q );
if ( l === 0 ) {
return [0,0,0,1]
} else {
l = 1 / l;
return M$2.scaleArray(q, l, 4)
}
},
quatInvert: ( q ) => {
return [-q[0],-q[1],-q[2], q[3]]
},
quatMultiply:( a, b ) => {
const qax = a[0], qay = a[1], qaz = a[2], qaw = a[3];
const qbx = b[0], qby = b[1], qbz = b[2], qbw = b[3];
return [
qax * qbw + qaw * qbx + qay * qbz - qaz * qby,
qay * qbw + qaw * qby + qaz * qbx - qax * qbz,
qaz * qbw + qaw * qbz + qax * qby - qay * qbx,
qaw * qbw - qax * qbx - qay * qby - qaz * qbz
]
},
quatToAxis:( q ) => {
let w = 2 * Math.acos( q[3] );
const s = Math.sqrt( 1 - q[3] * q[3] );
if ( s < 0.0001 ) {
return [1,0,0]
} else {
return [ q[0] / s, q[1] / s, q[2] / s, w ]
}
},
eulerFromMatrix: (te) => {
const m11 = te[ 0 ], m12 = te[ 4 ], m13 = te[ 8 ];
te[ 1 ]; const m22 = te[ 5 ], m23 = te[ 9 ];
te[ 2 ]; const m32 = te[ 6 ], m33 = te[ 10 ];
let ar = [0,0,0];
ar[1] = Math.asin( M$2.clamp( m13, -1, 1 ) );
if ( Math.abs( m13 ) < 0.9999999 ) {
ar[0] = Math.atan2( - m23, m33 );
ar[2] = Math.atan2( - m12, m11 );
} else {
ar[0] = Math.atan2( m32, m22 );
ar[2] = 0;
}
return ar
},
angleTo: ( a, b ) => {
return 2 * Math.acos( Math.abs( M$2.clamp( M$2.dotArray(a,b), -1, 1 ) ) );
},
//-----------------------
// ARRAY
//-----------------------
fixedArray: ( a, p ) => {
let i = a.length;
let r = [];
while(i--){ r[i] = M$2.toFixed(a[i], p); }
return r;
},
getSize: ( r ) => ( ( r.byteLength * 0.001 ) +'kb' ),
// Creates a vector normal (perpendicular) to the current Vector3
perpendicularArray: ( v ) => {
const radius = Math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
let theta = Math.acos(v[1] / radius);
const phi = Math.atan2(v[2], v[0]);
//makes angle 90 degs to current vector
if( theta > PI90 ) theta -= PI90;
else theta += PI90;
//Calculates resutant normal vector from spherical coordinate of perpendicular vector
const x = radius * Math.sin(theta) * Math.cos(phi);
const y = radius * Math.cos(theta);
const z = radius * Math.sin(theta) * Math.sin(phi);
return [x, y, z];
},
crossArray: ( a, b ) => {
const ax = a[0], ay = a[1], az = a[2];
const bx = b[0], by = b[1], bz = b[2];
let x = ay * bz - az * by;
let y = az * bx - ax * bz;
let z = ax * by - ay * bx;
return [x, y, z];
},
applyQuaternion: ( v, q ) => {
// quaternion q is assumed to have unit length
const vx = v[0], vy = v[1], vz = v[2];
const qx = q[0], qy = q[1], qz = q[2], qw = q[3];
// t = 2 * cross( q.xyz, v );
const tx = 2 * ( qy * vz - qz * vy );
const ty = 2 * ( qz * vx - qx * vz );
const tz = 2 * ( qx * vy - qy * vx );
// v + q.w * t + cross( q.xyz, t );
let x = vx + qw * tx + qy * tz - qz * ty;
let y = vy + qw * ty + qz * tx - qx * tz;
let z = vz + qw * tz + qx * ty - qy * tx;
return [x,y,z];
},
// ARRAY OPERATION
nullArray: ( a, n, i ) => {
let j = 0;
while( i-- ) j += a[n+i];
return j;
},
equalArray: ( a, b ) => {
let i = a.length;
while(i--){ if(a[i]!==b[i]) return false; }
return true;
},
lerpArray: ( a, b, t ) => {
if ( t === 0 ) return a;
if ( t === 1 ) return b;
let i = a.length;
let r = [];
while(i--){ r[i] = a[i]; r[i] += ( b[i] - r[i] ) * t; }
return r;
},
zeroArray: ( a, n = 0, i ) => {
i = i ?? a.length;
while ( i-- ) a[n+i] = 0;
return a;
},
lengthArray: ( r ) => {
let i = r.length, l=0;
while( i-- ) l += r[i] * r[i];
return Math.sqrt( l );
},
dotArray: ( a, b ) => {
let i = a.length, r = 0;
while ( i-- ) r += a[ i ] * b[ i ];
return r;
},
addArray: ( a, b, i ) => {
i = i ?? a.length;
let r = [];
while ( i-- ) r[i] = a[i] + b[i];
return r;
},
subArray: ( a, b, i ) => {
i = i ?? a.length;
let r = [];
while ( i-- ) r[i] = a[i] - b[i];
return r;
},
mulArray: ( a, b, i ) => {
let ar = b instanceof Array;
if( !ar ){
return a.map((x) => x * b);
} else {
let r = [];
i = i ?? a.length;
while ( i-- ) r[i] = a[i] * b[i];
return r;
}
},
worldscale: ( a, b ) => {
return a.map((x) => x * b);
},
divArray: ( r, s, i ) => ( M$2.mulArray( r, 1/s, i ) ),
scaleArray: ( r, s, i ) => ( M$2.mulArray( r, s, i ) ),
fillArray: ( a, b, n = 0, i ) => {
i = i ?? a.length;
while( i-- ) b[n+i] = a[i];
},
copyArray: ( a, b ) => { [...b]; },
cloneArray: ( a ) => ( [...a] ),
distanceArray: ( a, b = [0,0,0] ) => ( M$2.lengthArray( M$2.subArray( a, b ) ) ),
normalizeArray: ( a ) => ( M$2.divArray( a, M$2.lengthArray(a) || 1 ) ),
normalArray: ( a, b = [0,0,0] ) => ( M$2.normalizeArray( M$2.subArray( b, a ) ) ),
//-----------------------
// VOLUME
//-----------------------
getCenter:( g, center ) => {
g.computeBoundingBox();
return g.boundingBox.getCenter( center );
},
getVolume: ( type, size, vertex = null ) => {
let volume = 1;
let s = size;
switch(type){
case 'sphere' : volume = (4*Math.PI*s[0]*s[0]*s[0])/3; break;
case 'cone' : volume = Math.PI * s[0] * (s[1] * 0.5) * 2; break;
case 'box' : volume = 8 * (s[0]*0.5)*(s[1]*0.5)*(s[2]*0.5); break;
case 'cylinder' : volume = Math.PI * s[0] * s[0] * (s[1] * 0.5) * 2; break;
case 'capsule' : volume = ( (4*Math.PI*s[0]*s[0]*s[0])/3) + ( Math.PI * s[0] * s[0] * (s[1] * 0.5) * 2 ); break;
case 'convex' : case 'mesh' : volume = M$2.getConvexVolume( vertex ); break;
}
return volume;
},
getConvexVolume: ( v ) => {
let i = v.length / 3, n;
let min = [0, 0, 0];
let max = [0, 0, 0];
while(i--){
n = i*3;
if ( v[n] < min[0] ) min[0] = v[n];
else if (v[n] > max[0]) max[0] = v[n];
if ( v[n+1] < min[1] ) min[1] = v[n+1];
else if (v[n+1] > max[1]) max[1] = v[n+1];
if ( v[n+2] < min[2] ) min[2] = v[n+2];
else if (v[n+2] > max[2]) max[2] = v[n+2];
}
let s = [ max[0]-min[0], max[1]-min[1], max[2]-min[2] ];
return 8 * (s[0]*0.5)*(s[1]*0.5)*(s[2]*0.5);
//return (max[0]-min[0])*(max[1]-min[1])*(max[2]-min[2])
},
massFromDensity: ( density, volume ) => ( density * volume ),
densityFromMass: ( mass, volume ) => ( mass / volume ),
//-----------------------
// GEOMETRY
//-----------------------
toNonIndexed: ( g ) => ( !g.index ? g : g.clone().toNonIndexed() ),
getIndex: ( g, noIndex ) => {
if( !g.index || noIndex ) return null
return g.index.array || null
},
getSameVertex: ( g ) => {
const positionAttribute = g.getAttribute( 'position' );
const ar = positionAttribute.array;
const tmppos = [];
const pos = [];
const sameId = {};
new THREE.Vector3();
let n = 0, jcount;
M$2.getHash(g);
let p1, p2, same = false;
let idx = 0;
for ( let i = 0; i < positionAttribute.count; i ++ ) {
n = i*3;
p1 = { x:ar[n], y:ar[n+1], z:ar[n+2], id:i };
same = false;
jcount = tmppos.length;
for ( let j = 0; j < jcount; j ++ ) {
p2 = tmppos[j];
if( p1.x === p2.x && p1.y === p2.y && p1.z === p2.z ){
same = true;
sameId[i] = p2.id;
//console.log(i+' have same index than '+p2.id)
}
}
if(!same){
//if(!sameId[i])sameId[i] = i
p1.id = idx++;
tmppos.push(p1);
pos.push([ p1.x, p1.y, p1.z ]);
}
}
//console.log(tmppos)
return [pos, sameId]
},
getVertex: ( g, noIndex ) => {
let c = g.attributes.position.array;
if( noIndex && g.index ){
g = g.clone().toNonIndexed();
c = g.attributes.position.array;
}
return c;
},
getNormal: ( g ) => {
//if( noIndex && g.index ) g = g.clone().toNonIndexed();
let c = g.attributes.normal.array;
//console.log(c)
return c;
},
getFaces: ( g ) => {
let faces = [];
if( g.index ){
let index = g.getIndex();
for ( let i = 0; i < index.count; i += 3 ) {
faces.push( [index.getX(i), index.getX(i+1), index.getX(i+2)] );
}
}else {
let lng = g.getAttribute( 'position' ).count;
for ( let i = 0; i < lng; i += 3 ) {
faces.push([i, i+1, i+2] );
}
}
return faces;
},
getConnectedFaces: ( faces ) => {
const connected = [];
let lng = faces.length;
let i = lng, j, fa, fb, common;
let tmp, nx, final;
while(i--){
fa = faces[i];
j = lng;
while(j--){
if(j !== i){
//d = 0
fb = faces[j];
common = fa.filter(item => fb.includes(item));
if(common.length>1){
final = [];
tmp = [...fa];
nx = tmp.indexOf(common[0]);
tmp.splice(nx, 1);
nx = tmp.indexOf(common[1]);
tmp.splice(nx, 1);
final.push(tmp[0]);
tmp = [...fb];
nx = tmp.indexOf(common[0]);
tmp.splice(nx, 1);
nx = tmp.indexOf(common[1]);
tmp.splice(nx, 1);
final.push(tmp[0]);
connected.push( final );
}
}
}
}
return connected
},
reduce: ( x ) => {
},
barycentric: ( simplex, point ) => {
},
solve: ( simplex, point ) => {
},
getHash: (geometry, tolerance = 1e-4) => {
tolerance = Math.max( tolerance, Number.EPSILON );
const hashToIndex = {};
const hashTable = {};
const positions = geometry.getAttribute( 'position' );
const vertexCount = positions.count;
//const vertexCount = positions.count;
const ar = positions.array;
const halfTolerance = tolerance * 0.5;
const exponent = Math.log10( 1 / tolerance );
const mul = Math.pow( 10, exponent );
const add = halfTolerance * mul;
let n;
for ( let i = 0; i < vertexCount; i ++ ) {
const index = i;
n = index*3;
// Generate a hash for the vertex attributes at the current index 'i'
let hash = `${ ~ ~ ( ar[n] * mul + add ) },${ ~ ~ ( ar[n+1] * mul + add ) },${ ~ ~ ( ar[n+2] * mul + add ) }`;
if(hashToIndex[hash]) hashToIndex[hash].push(i);
else hashToIndex[hash] = [i];
}
let id = 0;
for(let h in hashToIndex){
hashTable[id++] = hashToIndex[h];
}
//console.log(hashTable)
return hashTable
},
/*mergeVertices:( geometry, tolerance = 1e-4 ) => {
tolerance = Math.max( tolerance, Number.EPSILON );
// Generate an index buffer if the geometry doesn't have one, or optimize it
// if it's already available.
const hashToIndex = {};
const indices = geometry.getIndex();
const positions = geometry.getAttribute( 'position' );
const vertexCount = indices ? indices.count : positions.count;
// next value for triangle indices
let nextIndex = 0;
// attributes and new attribute arrays
const attributeNames = Object.keys( geometry.attributes );
const tmpAttributes = {};
const tmpMorphAttributes = {};
const newIndices = [];
const getters = [ 'getX', 'getY', 'getZ', 'getW' ];
const setters = [ 'setX', 'setY', 'setZ', 'setW' ];
// Initialize the arrays, allocating space conservatively. Extra
// space will be trimmed in the last step.
for ( let i = 0, l = attributeNames.length; i < l; i ++ ) {
const name = attributeNames[ i ];
const attr = geometry.attributes[ name ];
tmpAttributes[ name ] = new attr.constructor(
new attr.array.constructor( attr.count * attr.itemSize ),
attr.itemSize,
attr.normalized
);
const morphAttributes = geometry.morphAttributes[ name ];
if ( morphAttributes ) {
if ( ! tmpMorphAttributes[ name ] ) tmpMorphAttributes[ name ] = [];
morphAttributes.forEach( ( morphAttr, i ) => {
const array = new morphAttr.array.constructor( morphAttr.count * morphAttr.itemSize );
tmpMorphAttributes[ name ][ i ] = new morphAttr.constructor( array, morphAttr.itemSize, morphAttr.normalized );
} );
}
}
// convert the error tolerance to an amount of decimal places to truncate to
const halfTolerance = tolerance * 0.5;
const exponent = Math.log10( 1 / tolerance );
const mul = Math.pow( 10, exponent );
const add = halfTolerance * mul;
for ( let i = 0; i < vertexCount; i ++ ) {
const index = indices ? indices.getX( i ) : i;
// Generate a hash for the vertex attributes at the current index 'i'
let hash = '';
for ( let j = 0, l = attributeNames.length; j < l; j ++ ) {
const name = attributeNames[ j ];
const attribute = geometry.getAttribute( name );
const itemSize = attribute.itemSize;
for ( let k = 0; k < itemSize; k ++ ) {
// double tilde truncates the decimal value
hash += `${ ~ ~ ( attribute[ getters[ k ] ]( index ) * mul + add ) },`;
}
}
// Add another reference to the vertex if it's already
// used by another index
if ( hash in hashToIndex ) {
newIndices.push( hashToIndex[ hash ] );
} else {
// copy data to the new index in the temporary attributes
for ( let j = 0, l = attributeNames.length; j < l; j ++ ) {
const name = attributeNames[ j ];
const attribute = geometry.getAttribute( name );
const morphAttributes = geometry.morphAttributes[ name ];
const itemSize = attribute.itemSize;
const newArray = tmpAttributes[ name ];
const newMorphArrays = tmpMorphAttributes[ name ];
for ( let k = 0; k < itemSize; k ++ ) {
const getterFunc = getters[ k ];
const setterFunc = setters[ k ];
newArray[ setterFunc ]( nextIndex, attribute[ getterFunc ]( index ) );
if ( morphAttributes ) {
for ( let m = 0, ml = morphAttributes.length; m < ml; m ++ ) {
newMorphArrays[ m ][ setterFunc ]( nextIndex, morphAttributes[ m ][ getterFunc ]( index ) );
}
}
}
}
hashToIndex[ hash ] = nextIndex;
newIndices.push( nextIndex );
nextIndex ++;
}
}
// generate result BufferGeometry
const result = geometry.clone();
for ( const name in geometry.attributes ) {
const tmpAttribute = tmpAttributes[ name ];
result.setAttribute( name, new tmpAttribute.constructor(
tmpAttribute.array.slice( 0, nextIndex * tmpAttribute.itemSize ),
tmpAttribute.itemSize,
tmpAttribute.normalized,
) );
if ( ! ( name in tmpMorphAttributes ) ) continue;
for ( let j = 0; j < tmpMorphAttributes[ name ].length; j ++ ) {
const tmpMorphAttribute = tmpMorphAttributes[ name ][ j ];
result.morphAttributes[ name ][ j ] = new tmpMorphAttribute.constructor(
tmpMorphAttribute.array.slice( 0, nextIndex * tmpMorphAttribute.itemSize ),
tmpMorphAttribute.itemSize,
tmpMorphAttribute.normalized,
);
}
}
// indices
result.setIndex( newIndices );
return result;
}*/
};
const MathTool = M$2;
// point weight blend space javascript
/*
get_blend_space_2d_node_influences :: (using space : *Blend_Space_2d, position : Vec2) -> []f32 #must
{
weights := alloc_array (f32, nodes.count, temp_allocator);
sqrd_distances := alloc_array (f32, nodes.count, temp_allocator);
angular_distances := alloc_array (f32, nodes.count, temp_allocator);
total_sqrd_distance, total_angular_distance := 0.0;
for nodes
{
sqrd_distance := dot (position - it.position, position - it.position);
if sqrd_distance > 0
{
angular_distance := -(clamp (dot (normalize (position), normalize (it.position)), -1, 1) - 1) * 0.5;
total_sqrd_distance += 1 / sqrd_distance;
if angular_distance > 0 then total_angular_distance += 1 / angular_distance;
sqrd_distances[it_index] = sqrd_distance;
angular_distances[it_index] = angular_distance;
}
else // The distance is 0 so it.position == position
{
// Weights are already initialized to 0
weights[it_index] = 1;
return weights;
}
}
for i : 0..nodes.count - 1
{
sqrd_distance := total_sqrd_distance * sqrd_distances[i];
angular_distance := total_angular_distance * angular_distances[i];
if sqrd_distance > 0 && angular_distance > 0
weights[i] = (1 / sqrd_distance) * 0.5 + (1 / angular_distance) * 0.5;
else if sqrd_distance > 0
weights[i] = (1 / sqrd_distance) * 0.5 + 0.5;
else
weight = 0;
}
return weights;
}
*/
const WithMassCenter = ['PHYSX', 'HAVOK'];
const Max = {
body:4000,
joint:1000,
contact:4000,
ray:100,
character:100,
vehicle:50,
solver:20,
};
const Num = {
bodyFull:14,
body:8,
joint:16,
contact:1,
ray:11,
character:16,
vehicle:72,//max 8 wheels
solver:128,
};
// Define how many body phy can manage
const getArray = function ( engine, full = false ){
const ArPos = {};
let counts = {
body: Max.body * ( full ? Num.bodyFull : Num.body ),
joint: Max.joint * Num.joint,
ray: Max.ray * Num.ray,
contact: Max.contact * Num.contact,
character: Max.character * Num.character
};
if( engine === 'PHYSX' || engine === 'AMMO' ){
counts['vehicle'] = Max.vehicle * Num.vehicle;
}
if( engine === 'PHYSX' ){
counts['solver'] = Max.solver * Num.solver;
}
if( engine === 'HAVOK' || engine === 'RAPIER' || engine === 'JOLT' ){
Num.joint = 0;
}
let prev = 0;
for( let m in counts ){
ArPos[m] = prev;
prev += counts[m];
}
ArPos['total'] = prev;
return ArPos;
};
// Convert type for all engine
const getType = function ( o ) {
switch( o.type ){
case 'plane': case 'box': case 'sphere': case 'highSphere': case 'customSphere': case 'cylinder': case 'stair': case 'particle':
case 'cone': case 'capsule': case 'mesh': case 'convex': case 'compound': case 'null':
//if ( ( !o.mass || !o.density ) && !o.kinematic ) return 'solid'
if ( !o.mass && !o.density && !o.kinematic ) return 'solid'
else return 'body'
case 'fixe':
case 'generic': case 'universal': case "dof": case "d6":
case 'hinge': case 'revolute':
case "prismatic":
case 'cylindrical': case 'slider':
case 'spherical':
case 'ragdoll':
case "distance":
return 'joint'
default:
return o.type;
}
};
class CircleHelper extends three.LineSegments {
constructor( box, color = 0xffff00 ) {
let size=0.6;
const indices = new Uint16Array( [
0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 0,
6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 6,
12, 13, 13, 14, 14, 15, 15, 16, 16, 17, 17, 12,
18,19, 20,21, 22, 23,
] );
const positions = [
0.5, 0.0, 0.0,
0.25, 0.433, 0.0,
-0.25, 0.433, 0.0,
-0.5, 0.0, 0.0,
-0.25, -0.433, 0.0,
0.25, -0.433, 0.0,
0.5, 0.0,0.0,
0.25, 0.0,0.433,
-0.25, 0.0,0.433,
-0.5, 0.0, 0.0,
-0.25,0.0, -0.433,
0.25, 0.0, -0.433,
0.0,0.5, 0.0,
0.0,0.25, 0.433,
0.0,-0.25, 0.433,
0.0,-0.5, 0.0,
0.0,-0.25, -0.433,
0.0,0.25, -0.433,
0, 0, 0, size, 0, 0,
0, 0, 0, 0, size, 0,
0, 0, 0, 0, 0, size,
];
const colors = [
0, 0, 1,
0, 0, 1,
0, 0, 1,
0, 0, 1,
0, 0, 1,
0, 0, 1,
0, 1, 0,
0, 1, 0,
0, 1, 0,
0, 1, 0,
0, 1, 0,
0, 1, 0,
1, 0, 0,
1, 0, 0,
1, 0, 0,
1, 0, 0,
1, 0, 0,
1, 0, 0,
1, 0, 0, 1, 0, 0,
0, 1, 0, 0, 1, 0,
0, 0, 1, 0, 0, 1,
];
const geometry = new three.BufferGeometry();
geometry.setIndex( new three.BufferAttribute( indices, 1 ) );
geometry.setAttribute( 'position', new three.Float32BufferAttribute( positions, 3 ) );
geometry.setAttribute( 'color', new three.Float32BufferAttribute( colors, 3 ) );
super( geometry, new three.LineBasicMaterial( { color: color, depthTest: false, depthWrite: false, toneMapped: false, transparent: true } ) );
this.box = box;
this.type = 'CircleHelper';
this.geometry.computeBoundingSphere();
}
updateMatrixWorld( force ) {
const box = this.box;
if ( box.isEmpty() ) return;
box.getCenter( this.position );
box.getSize( this.scale );
this.scale.multiplyScalar( 0.5 );
super.updateMatrixWorld( force );
}
dispose() {
this.geometry.dispose();
this.material.dispose();
}
}
//-------------------
//
// GEOMETRY POOL
//
//-------------------
let Geo$1 = class Geo {
constructor(){
this.geoN = 0;
this.geo = {};
}
unic ( g ) {
//console.log(g)
this.geo[ 'geo' + this.geoN++ ] = g;
}
set( g ) {
this.geo[g.name] = g;
}
get( name, o = {} ) {
if( !this.geo[name] ){
let g;
switch( name ){
case 'plane': g = new three.PlaneGeometry(1,1); g.rotateX( -Math.PI * 0.5 ); break
case 'box': g = new three.BoxGeometry(1,1,1); break
case 'sphere': g = new three.SphereGeometry( 1, 16, 12 ); break
case 'cylinder': g = new three.CylinderGeometry( 1, 1, 1 , 16 ); break
//case 'wheel': g = new CylinderGeometry( 1, 1, 1 , 16 ); g.rotateX( -Math.PI * 0.5 ); break
case 'cone': g = new three.CylinderGeometry( 0.001, 1, 1 , 16 ); break
//case 'joint': g = new Box3Helper().geometry; g.scale( 0.05,0.05,0.05 ); break
case 'particle': g = new three.SphereGeometry( 1.0, 6, 4 ); break
case 'joint': g = new CircleHelper().geometry; break
default: return null;
}
this.geo[name] = g;
}
return this.geo[name]
}
dispose () {
// TODO BUG with Start demo and HAVOK !!!
//console.log( geo )
for( let n in this.geo ){
if( this.geo[n].isBufferGeometry ) this.geo[n].dispose();
else console.log(this.geo[n]);
}
this.geo = {};
this.geoN = 0;
}
};
class CarbonTexture {
constructor( normal, c1='rgb(69,69,69)', c2='rgb(39,39,39)' ) {
let s = 128;
const canvas = document.createElement( 'canvas' );
canvas.width = canvas.height = s;
const ctx = canvas.getContext( '2d' );
ctx.fillStyle = c1;
ctx.fillRect( 0, 0, s, s );
if( !normal ){
ctx.beginPath();
ctx.fillStyle = c2;
ctx.rect(0, 0, 32, 64);
ctx.rect(32, 32, 32, 64);
ctx.rect(64, 64, 32, 64);
ctx.rect(96, 96, 32, 64);
ctx.rect(96, -32, 32, 64);
ctx.fill();
} else {
let i, j, n, d;
let pos = [ [0, 0], [32, 32],[64, 64],[96, 96],[96, -32] ];
let deg = [ [0, 64], [32, 96],[64, 128],[96, 160],[-32, 32] ];
let f1 = normal ? 'rgb(128,128,255)' : c1;
let f2 = normal ? 'rgb(160,100,255)' : c2;
let f3 = normal ? 'rgba(100,160,255, 0.5)' : 'rgba(0,0,0, 0.1)';
ctx.strokeStyle = f3;
ctx.lineWidth = 1;
for( i = 0; i<5; i++ ){
d = ctx.createLinearGradient(0, deg[i][0], 0, deg[i][1]);
d.addColorStop(0, f2);
d.addColorStop(1, f1);
ctx.beginPath();
ctx.fillStyle = d;
ctx.rect(pos[i][0], pos[i][1], 32, 64);
ctx.fill();
for( let j = 0; j<8; j++ ){
n = (Math.random()-0.5) * 2;
ctx.beginPath();
ctx.moveTo(pos[i][0]+n+2+j*4, pos[i][1]);
ctx.lineTo(pos[i][0]+n+2+j*4, pos[i][1]+64);
ctx.stroke();
}
}
pos = [ [32, 0], [64, 32],[96, 64],[-32, 64],[0, 96] ];
deg = [ [32, 96], [64, 128],[96, 160],[-32, 32],[0, 64] ];
for( i = 0; i<5; i++ ){
d = ctx.createLinearGradient(deg[i][0], 0, deg[i][1], 0);
d.addColorStop(0, f1);
d.addColorStop(1, f2);
ctx.beginPath();
ctx.fillStyle = d;
ctx.rect(pos[i][0], pos[i][1], 64, 32);
ctx.fill();
for( j = 0; j<8; j++ ){
n = (Math.random()-0.5) * 2;
ctx.beginPath();
ctx.moveTo(pos[i][0], pos[i][1]+n+2+j*4);
ctx.lineTo(pos[i][0]+64, pos[i][1]+n+2+j*4);
ctx.stroke();
}
}
}
//return canvas;
const texture = new three.CanvasTexture( canvas ); //new CarbonTexture('#ffffff', '#CCCCCC') )
texture.wrapS = texture.wrapT = three.RepeatWrapping;
texture.repeat.x = texture.repeat.y = 60;
if(!normal) texture.colorSpace = three.SRGBColorSpace;
return texture;
}
}
/**
* ------------------------------------------------------------------------------------------
* Subsurface Scattering shader
* Based on GDC 2011 – Approximating Translucency for a Fast, Cheap and Convincing Subsurface Scattering Look
* https://colinbarrebrisebois.com/2011/03/07/gdc-2011-approximating-translucency-for-a-fast-cheap-and-convincing-subsurface-scattering-look/
*------------------------------------------------------------------------------------------
*/
class MeshSssMaterial extends three.MeshPhysicalMaterial {
constructor( parameters ) {
super();
this.defines = {
'STANDARD': '',
'PHYSICAL': '',
'SUBSURFACE': '',
'USE_UV': '',
};
this.extra = {};
this.addParametre( 'sssMap', null );
this.addParametre( 'sssColor', new three.Color( 0,0,0 ) );
this.addParametre( 'sssAmbient', 0.5 );
this.addParametre( 'sssDistortion', 0.6 );
this.addParametre( 'sssAttenuation', 0.1 );
this.addParametre( 'sssPower', 1.0 );
this.addParametre( 'sssScale', 6.0 );
this.setValues( parameters );
let self = this;
self.onBeforeCompile = function ( shader ) {
for(let name in self.extra ) {
shader.uniforms[ name ] = { value: self.extra[name] };
}
shader.fragmentShader = shader.fragmentShader.replace( '#include <common>', shaderChange.common );
shader.fragmentShader = shader.fragmentShader.replace( '#include <lights_fragment_begin>',
self.replaceAll(
lights_fragment_begin,
'RE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );',
shaderChange.light
)
);
self.userData.shader = shader;
};
}
addParametre( name, value ){
this.extra[ name ] = value;
Object.defineProperty( this, name, {
get: () => ( this.extra[ name ] ),
set: ( v ) => {
this.extra[ name ] = v;
if( this.userData.shader ) this.userData.shader.uniforms[name].value = this.extra[ name ];
}
});
}
replaceAll( string, find, replace ) {
return string.split( find ).join( replace );
}
/*customProgramCacheKey(){
return self
} */
/*onBeforeCompile( shader ){
for(let name in this.extra ) {
shader.uniforms[ name ] = { value: this.extra[name] };
}
shader.fragmentShader = shader.fragmentShader.replace( '#include <common>', shaderChange.common );
shader.fragmentShader = shader.fragmentShader.replace( '#include <lights_fragment_begin>',
this.replaceAll(
THREE.ShaderChunk[ 'lights_fragment_begin' ],
'RE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );',
shaderChange.light
)
);
this.userData.shader = shader;
}*/
}
const shaderChange = {
common : /* glsl */`
#include <common>
uniform sampler2D sssMap;
uniform float sssPower;
uniform float sssScale;
uniform float sssDistortion;
uniform float sssAmbient;
uniform float sssAttenuation;
uniform vec3 sssColor;
void RE_Direct_Scattering(const in IncidentLight directLight, const in vec2 uv, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, inout ReflectedLight reflectedLight) {
vec3 thickness = sssColor * texture2D(sssMap, uv).r;
vec3 scatteringHalf = normalize(directLight.direction + (geometryNormal * sssDistortion));
float scatteringDot = pow(saturate(dot(geometryViewDir, -scatteringHalf)), sssPower) * sssScale;
vec3 scatteringIllu = (scatteringDot + sssAmbient) * thickness;
reflectedLight.directDiffuse += scatteringIllu * sssAttenuation * directLight.color;
}
`,
light : /* glsl */`
RE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );
#if defined( SUBSURFACE ) && defined( USE_UV )
RE_Direct_Scattering(directLight, vUv, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, reflectedLight);
#endif
`
};
const lights_fragment_begin = /* glsl */`
vec3 geometryPosition = - vViewPosition;
vec3 geometryNormal = normal;
vec3 geometryViewDir = ( isOrthographic ) ? vec3( 0, 0, 1 ) : normalize( vViewPosition );
vec3 geometryClearcoatNormal = vec3( 0.0 );
#ifdef USE_CLEARCOAT
geometryClearcoatNormal = clearcoatNormal;
#endif
#ifdef USE_IRIDESCENCE
float dotNVi = saturate( dot( normal, geometryViewDir ) );
if ( material.iridescenceThickness == 0.0 ) {
material.iridescence = 0.0;
} else {
material.iridescence = saturate( material.iridescence );
}
if ( material.iridescence > 0.0 ) {
material.iridescenceFresnel = evalIridescence( 1.0, material.iridescenceIOR, dotNVi, material.iridescenceThickness, material.specularColor );
// Iridescence F0 approximation
material.iridescenceF0 = Schlick_to_F0( material.iridescenceFresnel, 1.0, dotNVi );
}
#endif
IncidentLight directLight;
#if ( NUM_POINT_LIGHTS > 0 ) && defined( RE_Direct )
PointLight pointLight;
#if defined( USE_SHADOWMAP ) && NUM_POINT_LIGHT_SHADOWS > 0
PointLightShadow pointLightShadow;
#endif
#pragma unroll_loop_start
for ( int i = 0;