@zephyr3d/scene 0.8.2 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/animation/animationset.js +473 -60
- package/dist/animation/animationset.js.map +1 -1
- package/dist/animation/cloth/gpu_cloth_system.js +2516 -0
- package/dist/animation/cloth/gpu_cloth_system.js.map +1 -0
- package/dist/animation/fixed_geometry_cache_track.js +138 -0
- package/dist/animation/fixed_geometry_cache_track.js.map +1 -0
- package/dist/animation/geometry_cache_utils.js +120 -0
- package/dist/animation/geometry_cache_utils.js.map +1 -0
- package/dist/animation/ik_modifier.js +9 -1
- package/dist/animation/ik_modifier.js.map +1 -1
- package/dist/animation/joint_dynamics/collision.js +469 -0
- package/dist/animation/joint_dynamics/collision.js.map +1 -0
- package/dist/animation/joint_dynamics/constraints.js +329 -0
- package/dist/animation/joint_dynamics/constraints.js.map +1 -0
- package/dist/animation/joint_dynamics/controller.js +699 -0
- package/dist/animation/joint_dynamics/controller.js.map +1 -0
- package/dist/animation/joint_dynamics/joint_dynamics_system.js +393 -0
- package/dist/animation/joint_dynamics/joint_dynamics_system.js.map +1 -0
- package/dist/animation/joint_dynamics/solver.js +794 -0
- package/dist/animation/joint_dynamics/solver.js.map +1 -0
- package/dist/animation/joint_dynamics/types.js +19 -0
- package/dist/animation/joint_dynamics/types.js.map +1 -0
- package/dist/animation/joint_dynamics/vrm_spring_bone.js +46 -0
- package/dist/animation/joint_dynamics/vrm_spring_bone.js.map +1 -0
- package/dist/animation/joint_dynamics_modifier.js +51 -0
- package/dist/animation/joint_dynamics_modifier.js.map +1 -0
- package/dist/animation/pca_geometry_cache_track.js +222 -0
- package/dist/animation/pca_geometry_cache_track.js.map +1 -0
- package/dist/animation/skeleton.js +2011 -139
- package/dist/animation/skeleton.js.map +1 -1
- package/dist/animation/skeleton_modifier.js +3 -5
- package/dist/animation/skeleton_modifier.js.map +1 -1
- package/dist/animation/spring/multi_chain_spring_system.js +2 -0
- package/dist/animation/spring/multi_chain_spring_system.js.map +1 -1
- package/dist/animation/spring/spring_chain.js +3 -2
- package/dist/animation/spring/spring_chain.js.map +1 -1
- package/dist/animation/spring/spring_collider.js +35 -5
- package/dist/animation/spring/spring_collider.js.map +1 -1
- package/dist/animation/spring/spring_particle.js +1 -0
- package/dist/animation/spring/spring_particle.js.map +1 -1
- package/dist/animation/spring/spring_system.js +325 -28
- package/dist/animation/spring/spring_system.js.map +1 -1
- package/dist/animation/spring2/collision.js +469 -0
- package/dist/animation/spring2/collision.js.map +1 -0
- package/dist/animation/spring2/constraints.js +329 -0
- package/dist/animation/spring2/constraints.js.map +1 -0
- package/dist/animation/spring2/controller.js +434 -0
- package/dist/animation/spring2/controller.js.map +1 -0
- package/dist/animation/spring2/math.js +16 -0
- package/dist/animation/spring2/math.js.map +1 -0
- package/dist/animation/spring2/solver.js +624 -0
- package/dist/animation/spring2/solver.js.map +1 -0
- package/dist/animation/spring2/spring_system.js +118 -0
- package/dist/animation/spring2/spring_system.js.map +1 -0
- package/dist/animation/spring2/types.js +19 -0
- package/dist/animation/spring2/types.js.map +1 -0
- package/dist/animation/spring_modifier.js +17 -1
- package/dist/animation/spring_modifier.js.map +1 -1
- package/dist/app/engine.js +42 -15
- package/dist/app/engine.js.map +1 -1
- package/dist/app/runtimescript.js +132 -1
- package/dist/app/runtimescript.js.map +1 -1
- package/dist/app/screen.js +3 -3
- package/dist/app/screen.js.map +1 -1
- package/dist/app/scriptingsystem.js +69 -37
- package/dist/app/scriptingsystem.js.map +1 -1
- package/dist/app/scriptregistry.js +14 -3
- package/dist/app/scriptregistry.js.map +1 -1
- package/dist/asset/assetmanager.js +2 -1
- package/dist/asset/assetmanager.js.map +1 -1
- package/dist/asset/loaders/zabc/zabc_loader.js +570 -0
- package/dist/asset/loaders/zabc/zabc_loader.js.map +1 -0
- package/dist/asset/model.js +192 -0
- package/dist/asset/model.js.map +1 -1
- package/dist/camera/base.js +26 -7
- package/dist/camera/base.js.map +1 -1
- package/dist/camera/camera.js +146 -24
- package/dist/camera/camera.js.map +1 -1
- package/dist/camera/fps.js +2 -2
- package/dist/camera/fps.js.map +1 -1
- package/dist/camera/orbit.js +2 -2
- package/dist/camera/orbit.js.map +1 -1
- package/dist/index.d.ts +11728 -8535
- package/dist/index.js +29 -8
- package/dist/index.js.map +1 -1
- package/dist/material/lambert.js +1 -1
- package/dist/material/lambert.js.map +1 -1
- package/dist/material/material.js +9 -3
- package/dist/material/material.js.map +1 -1
- package/dist/material/meshmaterial.js +54 -7
- package/dist/material/meshmaterial.js.map +1 -1
- package/dist/material/mixins/lightmodel/blinnphong.js +17 -7
- package/dist/material/mixins/lightmodel/blinnphong.js.map +1 -1
- package/dist/material/mixins/lightmodel/lambert.js +7 -2
- package/dist/material/mixins/lightmodel/lambert.js.map +1 -1
- package/dist/material/mixins/lightmodel/pbrblueprintmixin.js +108 -16
- package/dist/material/mixins/lightmodel/pbrblueprintmixin.js.map +1 -1
- package/dist/material/mixins/lightmodel/pbrmetallicroughness.js +209 -14
- package/dist/material/mixins/lightmodel/pbrmetallicroughness.js.map +1 -1
- package/dist/material/mixins/lightmodel/pbrspecularglossness.js +24 -12
- package/dist/material/mixins/lightmodel/pbrspecularglossness.js.map +1 -1
- package/dist/material/mixins/lit.js +11 -8
- package/dist/material/mixins/lit.js.map +1 -1
- package/dist/material/mixins/pbr/common.js +72 -5
- package/dist/material/mixins/pbr/common.js.map +1 -1
- package/dist/material/pbrblueprint.js +148 -1
- package/dist/material/pbrblueprint.js.map +1 -1
- package/dist/material/pbrmr.js +115 -2
- package/dist/material/pbrmr.js.map +1 -1
- package/dist/material/shader/helper.js +10 -4
- package/dist/material/shader/helper.js.map +1 -1
- package/dist/material/water.js +2 -2
- package/dist/material/water.js.map +1 -1
- package/dist/posteffect/coloradjust.js +145 -0
- package/dist/posteffect/coloradjust.js.map +1 -0
- package/dist/posteffect/taa.js +68 -32
- package/dist/posteffect/taa.js.map +1 -1
- package/dist/render/cluster_light.js +17 -12
- package/dist/render/cluster_light.js.map +1 -1
- package/dist/render/deferredlightpass.js +510 -0
- package/dist/render/deferredlightpass.js.map +1 -0
- package/dist/render/deferredshadowlightpass.js +428 -0
- package/dist/render/deferredshadowlightpass.js.map +1 -0
- package/dist/render/drawable.js.map +1 -1
- package/dist/render/drawable_mixin.js +21 -12
- package/dist/render/drawable_mixin.js.map +1 -1
- package/dist/render/envlight.js +20 -22
- package/dist/render/envlight.js.map +1 -1
- package/dist/render/gbufferpass.js +50 -0
- package/dist/render/gbufferpass.js.map +1 -0
- package/dist/render/lightpass.js +1 -2
- package/dist/render/lightpass.js.map +1 -1
- package/dist/render/primitive.js +1 -1
- package/dist/render/primitive.js.map +1 -1
- package/dist/render/render_queue.js +3 -2
- package/dist/render/render_queue.js.map +1 -1
- package/dist/render/renderer.js +5 -358
- package/dist/render/renderer.js.map +1 -1
- package/dist/render/rendergraph/executor.js +15 -12
- package/dist/render/rendergraph/executor.js.map +1 -1
- package/dist/render/rendergraph/forward_plus_builder.js +144 -88
- package/dist/render/rendergraph/forward_plus_builder.js.map +1 -1
- package/dist/render/rendergraph/history_resource_manager.js +178 -0
- package/dist/render/rendergraph/history_resource_manager.js.map +1 -0
- package/dist/render/rendergraph/rendergraph.js +12 -1
- package/dist/render/rendergraph/rendergraph.js.map +1 -1
- package/dist/render/rendergraph/types.js +2 -2
- package/dist/render/rendergraph/types.js.map +1 -1
- package/dist/render/sky.js +26 -9
- package/dist/render/sky.js.map +1 -1
- package/dist/scene/basesprite.js +4 -3
- package/dist/scene/basesprite.js.map +1 -1
- package/dist/scene/batchgroup.js +4 -4
- package/dist/scene/environment.js +7 -4
- package/dist/scene/environment.js.map +1 -1
- package/dist/scene/light.js +184 -18
- package/dist/scene/light.js.map +1 -1
- package/dist/scene/mesh.js +30 -0
- package/dist/scene/mesh.js.map +1 -1
- package/dist/scene/particlesys.js +3 -2
- package/dist/scene/particlesys.js.map +1 -1
- package/dist/scene/raycast_visitor.js +29 -3
- package/dist/scene/raycast_visitor.js.map +1 -1
- package/dist/scene/scene.js +86 -19
- package/dist/scene/scene.js.map +1 -1
- package/dist/scene/scene_node.js +87 -31
- package/dist/scene/scene_node.js.map +1 -1
- package/dist/scene/script_attachment.js +59 -0
- package/dist/scene/script_attachment.js.map +1 -0
- package/dist/scene/terrain-cm/grass.js +3 -2
- package/dist/scene/terrain-cm/grass.js.map +1 -1
- package/dist/scene/terrain-cm/terrain-cm.js +4 -3
- package/dist/scene/terrain-cm/terrain-cm.js.map +1 -1
- package/dist/scene/water.js +3 -2
- package/dist/scene/water.js.map +1 -1
- package/dist/shaders/shadow.js +2 -2
- package/dist/shaders/shadow.js.map +1 -1
- package/dist/shadow/shadowmapper.js +74 -4
- package/dist/shadow/shadowmapper.js.map +1 -1
- package/dist/shapes/box.js +163 -0
- package/dist/shapes/box.js.map +1 -1
- package/dist/shapes/capsule.js +216 -0
- package/dist/shapes/capsule.js.map +1 -0
- package/dist/shapes/cylinder.js +91 -0
- package/dist/shapes/cylinder.js.map +1 -1
- package/dist/shapes/plane.js +32 -0
- package/dist/shapes/plane.js.map +1 -1
- package/dist/shapes/tetrahedron.js +211 -0
- package/dist/shapes/tetrahedron.js.map +1 -1
- package/dist/shapes/torus.js +173 -0
- package/dist/shapes/torus.js.map +1 -1
- package/dist/utility/blueprint/common/math.js +111 -1
- package/dist/utility/blueprint/common/math.js.map +1 -1
- package/dist/utility/blueprint/material/inputs.js +235 -1
- package/dist/utility/blueprint/material/inputs.js.map +1 -1
- package/dist/utility/blueprint/material/ir.js +154 -10
- package/dist/utility/blueprint/material/ir.js.map +1 -1
- package/dist/utility/blueprint/material/texture.js +273 -19
- package/dist/utility/blueprint/material/texture.js.map +1 -1
- package/dist/utility/serialization/json.js +12 -2
- package/dist/utility/serialization/json.js.map +1 -1
- package/dist/utility/serialization/manager.js +30 -11
- package/dist/utility/serialization/manager.js.map +1 -1
- package/dist/utility/serialization/scene/animation.js +250 -7
- package/dist/utility/serialization/scene/animation.js.map +1 -1
- package/dist/utility/serialization/scene/batch.js +4 -2
- package/dist/utility/serialization/scene/batch.js.map +1 -1
- package/dist/utility/serialization/scene/camera.js +154 -2
- package/dist/utility/serialization/scene/camera.js.map +1 -1
- package/dist/utility/serialization/scene/cloth_script.js +834 -0
- package/dist/utility/serialization/scene/cloth_script.js.map +1 -0
- package/dist/utility/serialization/scene/common.js +7 -0
- package/dist/utility/serialization/scene/common.js.map +1 -1
- package/dist/utility/serialization/scene/light.js +120 -4
- package/dist/utility/serialization/scene/light.js.map +1 -1
- package/dist/utility/serialization/scene/material.js +453 -1
- package/dist/utility/serialization/scene/material.js.map +1 -1
- package/dist/utility/serialization/scene/mesh.js +4 -2
- package/dist/utility/serialization/scene/mesh.js.map +1 -1
- package/dist/utility/serialization/scene/node.js +298 -10
- package/dist/utility/serialization/scene/node.js.map +1 -1
- package/dist/utility/serialization/scene/particle.js +4 -2
- package/dist/utility/serialization/scene/particle.js.map +1 -1
- package/dist/utility/serialization/scene/primitive.js +100 -3
- package/dist/utility/serialization/scene/primitive.js.map +1 -1
- package/dist/utility/serialization/scene/scene.js +130 -3
- package/dist/utility/serialization/scene/scene.js.map +1 -1
- package/dist/utility/serialization/scene/script.js +66 -0
- package/dist/utility/serialization/scene/script.js.map +1 -0
- package/dist/utility/serialization/scene/spring_script.js +596 -0
- package/dist/utility/serialization/scene/spring_script.js.map +1 -0
- package/dist/utility/serialization/scene/sprite.js +5 -3
- package/dist/utility/serialization/scene/sprite.js.map +1 -1
- package/dist/utility/serialization/scene/terrain.js +4 -2
- package/dist/utility/serialization/scene/terrain.js.map +1 -1
- package/dist/utility/serialization/scene/water.js +4 -2
- package/dist/utility/serialization/scene/water.js.map +1 -1
- package/dist/utility/serialization/types.js.map +1 -1
- package/dist/values.js +5 -1
- package/dist/values.js.map +1 -1
- package/package.json +3 -3
|
@@ -0,0 +1,794 @@
|
|
|
1
|
+
import { Vector3, Quaternion, degree2radian, clamp01, smoothStep, Matrix4x4, radian2degree } from '@zephyr3d/base';
|
|
2
|
+
import { EPSILON, ConstraintType } from './types.js';
|
|
3
|
+
import { pushoutFromSphere, pushoutFromCapsule, checkSurfaceCollision, pushInFromCapsule, pushInFromSphere, collisionDetection } from './collision.js';
|
|
4
|
+
|
|
5
|
+
// Core physics simulation 閳?direct port of JobExecuteSimulation from SPCRJointDynamicsJob.cs
|
|
6
|
+
const AXIS_Y = new Vector3(0, 1, 0);
|
|
7
|
+
const VEC3_ONE = new Vector3(1, 1, 1);
|
|
8
|
+
const _computeCapsuleAxis = new Vector3();
|
|
9
|
+
const _computeCapsuleHalfDir = new Vector3();
|
|
10
|
+
const _applySystemTransformOffset = new Vector3();
|
|
11
|
+
const _applySystemTransformRotated = new Vector3();
|
|
12
|
+
const _broadPhaseSegmentDir = new Vector3();
|
|
13
|
+
const _broadPhaseToCenter = new Vector3();
|
|
14
|
+
const _broadPhaseClosestPoint = new Vector3();
|
|
15
|
+
const _broadPhaseDelta = new Vector3();
|
|
16
|
+
/**
|
|
17
|
+
* Run the full simulation step. Mutates pointsRW and collidersRW in place.
|
|
18
|
+
* Returns { positionsToTransform, fakeWaveCounter }.
|
|
19
|
+
*/ function simulate(params, pointsR, pointsRW, constraints, collidersR, collidersRW, grabbersR, grabbersRW, movableLimitTargets, flatPlanes) {
|
|
20
|
+
const pointCount = pointsR.length;
|
|
21
|
+
const positionsToTransform = new Array(pointCount);
|
|
22
|
+
let subSteps = params.subSteps;
|
|
23
|
+
if (params.isPaused) {
|
|
24
|
+
subSteps = 1;
|
|
25
|
+
}
|
|
26
|
+
let fakeWaveFreq = params.fakeWaveCounter;
|
|
27
|
+
// Root slide offset
|
|
28
|
+
let rootSlideOffset = Vector3.zero();
|
|
29
|
+
const rootDeltaSlide = Vector3.sub(params.rootPosition, params.previousRootPosition);
|
|
30
|
+
const rootDeltaSlideLen = rootDeltaSlide.magnitude;
|
|
31
|
+
if (params.rootSlideLimit >= 0 && rootDeltaSlideLen > params.rootSlideLimit) {
|
|
32
|
+
rootSlideOffset = Vector3.scale(rootDeltaSlide, (1.0 - params.rootSlideLimit / rootDeltaSlideLen) / subSteps);
|
|
33
|
+
}
|
|
34
|
+
// Root rotation offset
|
|
35
|
+
let rootRotationOffset = Quaternion.identity();
|
|
36
|
+
const rootDeltaRot = Quaternion.multiply(params.rootRotation, Quaternion.inverse(params.previousRootRotation));
|
|
37
|
+
let rotateAngle = Math.acos(Math.max(-1, Math.min(1, rootDeltaRot.w))) * 2.0 * (180 / Math.PI);
|
|
38
|
+
if (rotateAngle > 180) {
|
|
39
|
+
rotateAngle -= 360;
|
|
40
|
+
}
|
|
41
|
+
if (params.rootRotateLimit >= 0 && Math.abs(rotateAngle) > params.rootRotateLimit) {
|
|
42
|
+
// Extract the rotation axis from the delta quaternion
|
|
43
|
+
const sinHalf = Math.sqrt(Math.max(0, 1 - rootDeltaRot.w * rootDeltaRot.w));
|
|
44
|
+
let rotateAxis;
|
|
45
|
+
if (sinHalf > 0.0001) {
|
|
46
|
+
rotateAxis = new Vector3(rootDeltaRot.x / sinHalf, rootDeltaRot.y / sinHalf, rootDeltaRot.z / sinHalf);
|
|
47
|
+
} else {
|
|
48
|
+
rotateAxis = Vector3.axisPY();
|
|
49
|
+
}
|
|
50
|
+
let angle = rotateAngle > 0 ? rotateAngle - params.rootRotateLimit : rotateAngle + params.rootRotateLimit;
|
|
51
|
+
angle /= subSteps;
|
|
52
|
+
rootRotationOffset = Quaternion.fromAxisAngle(rotateAxis, degree2radian(angle));
|
|
53
|
+
}
|
|
54
|
+
if (params.isPaused) {
|
|
55
|
+
rootSlideOffset = rootDeltaSlide;
|
|
56
|
+
rootRotationOffset = rootDeltaRot;
|
|
57
|
+
}
|
|
58
|
+
const stepTime = params.stepTime / subSteps;
|
|
59
|
+
for(let iSubStep = 1; iSubStep <= subSteps; iSubStep++){
|
|
60
|
+
const stepDelta = iSubStep / subSteps;
|
|
61
|
+
const stepTime_x2_half = stepTime * stepTime * 0.5;
|
|
62
|
+
colliderUpdate(collidersR, collidersRW, stepDelta);
|
|
63
|
+
pointUpdatePass1(pointsR, pointsRW, stepDelta, stepTime_x2_half, rootSlideOffset, rootRotationOffset, params.rootPosition, params.windForce, params.isPaused, grabbersR, grabbersRW, movableLimitTargets, flatPlanes, collidersR, collidersRW);
|
|
64
|
+
if (!params.isPaused) {
|
|
65
|
+
fakeWaveFreq += params.fakeWaveSpeed * stepTime;
|
|
66
|
+
if (params.enableSurfaceCollision) {
|
|
67
|
+
surfaceCollision(pointsRW, collidersR, collidersRW, params.surfaceConstraints);
|
|
68
|
+
}
|
|
69
|
+
// Push fixed points out of colliders before constraint solving.
|
|
70
|
+
// Fixed points follow animation and cannot be moved by the solver, but if
|
|
71
|
+
// their animated position is inside a collider, the distance constraint will
|
|
72
|
+
// pull free children inward through the surface — causing the characteristic
|
|
73
|
+
// "hair flipping outward" artifact at the back of the head.
|
|
74
|
+
// By temporarily clamping fixed-point positionCurrent to the collider surface
|
|
75
|
+
// before each substep's constraint pass, we ensure constraints operate from
|
|
76
|
+
// a geometrically valid anchor. The original animated position is restored
|
|
77
|
+
// at the start of the next substep via positionCurrentTransform.
|
|
78
|
+
fixedPointColliderPushout(pointsR, pointsRW, collidersR, collidersRW);
|
|
79
|
+
for(let iRelax = params.relaxation - 1; iRelax >= 0; --iRelax){
|
|
80
|
+
constraintUpdate(pointsR, pointsRW, constraints, collidersR, collidersRW, params.constraintShrinkLimit, params.enableBroadPhase);
|
|
81
|
+
}
|
|
82
|
+
// After all constraint/collision iterations are done, cancel any residual
|
|
83
|
+
// normal velocity that would push points back into colliders next frame.
|
|
84
|
+
// We compare the final positionCurrent (guaranteed outside all colliders)
|
|
85
|
+
// against positionPrevious (the Verlet integration start point) and zero
|
|
86
|
+
// out the component pointing into each collider.
|
|
87
|
+
postCollisionVelocityFix(pointsR, pointsRW, collidersR, collidersRW);
|
|
88
|
+
}
|
|
89
|
+
pointUpdatePass2(pointsR, pointsRW, positionsToTransform, stepDelta, fakeWaveFreq, params.blendRatio, params.isFakeWave, params.fakeWavePower);
|
|
90
|
+
}
|
|
91
|
+
return {
|
|
92
|
+
positionsToTransform,
|
|
93
|
+
fakeWaveCounter: fakeWaveFreq
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
function computeCapsule(pos, rot, height, head, direction) {
|
|
97
|
+
rot.transform(Vector3.scale(AXIS_Y, height, _computeCapsuleAxis), direction);
|
|
98
|
+
Vector3.scale(direction, 0.5, _computeCapsuleHalfDir);
|
|
99
|
+
Vector3.sub(pos, _computeCapsuleHalfDir, head);
|
|
100
|
+
}
|
|
101
|
+
function colliderUpdate(collidersR, collidersRW, stepDelta) {
|
|
102
|
+
const curPos = new Vector3();
|
|
103
|
+
const curDir = new Quaternion();
|
|
104
|
+
const corner = new Vector3();
|
|
105
|
+
const center = new Vector3();
|
|
106
|
+
const tailCorner = new Vector3();
|
|
107
|
+
const tailCenter = new Vector3();
|
|
108
|
+
for(let i = 0; i < collidersR.length; i++){
|
|
109
|
+
const colR = collidersR[i];
|
|
110
|
+
const colRW = collidersRW[i];
|
|
111
|
+
// Scale radius by the uniform world scale of the collider node.
|
|
112
|
+
// Use the average of x/y/z components to handle non-uniform scale gracefully.
|
|
113
|
+
const ws = colRW.worldScale;
|
|
114
|
+
const worldScaleUniform = (ws.x + ws.y + ws.z) / 3;
|
|
115
|
+
colRW.radius = colR.radius * worldScaleUniform;
|
|
116
|
+
Vector3.lerp(colRW.positionPreviousTransform, colRW.positionCurrentTransform, stepDelta, curPos);
|
|
117
|
+
Quaternion.slerp(colRW.directionPreviousTransform, colRW.directionCurrentTransform, stepDelta, curDir);
|
|
118
|
+
computeCapsule(curPos, curDir, colR.height, colRW.positionCurrent, colRW.directionCurrent);
|
|
119
|
+
if (colR.height > EPSILON) {
|
|
120
|
+
Vector3.scale(colRW.directionCurrent, 0.5, _computeCapsuleHalfDir);
|
|
121
|
+
Vector3.add(colRW.positionCurrent, _computeCapsuleHalfDir, colRW.boundsCenter);
|
|
122
|
+
colRW.boundsRadius = Math.sqrt(colRW.directionCurrent.magnitudeSq) * 0.5 + Math.max(colRW.radius, colRW.radius * colR.radiusTailScale);
|
|
123
|
+
} else {
|
|
124
|
+
colRW.boundsCenter.set(colRW.positionCurrent);
|
|
125
|
+
colRW.boundsRadius = colRW.radius;
|
|
126
|
+
}
|
|
127
|
+
// Update local bounds
|
|
128
|
+
Vector3.scale(VEC3_ONE, colR.radius, corner);
|
|
129
|
+
colRW.worldToLocal.transformPointAffine(colRW.positionCurrent, center);
|
|
130
|
+
Vector3.sub(center, corner, colRW.localBoundsMin);
|
|
131
|
+
Vector3.add(center, corner, colRW.localBoundsMax);
|
|
132
|
+
if (colR.height > EPSILON) {
|
|
133
|
+
Vector3.scale(VEC3_ONE, colR.radius * colR.radiusTailScale, tailCorner);
|
|
134
|
+
Vector3.add(colRW.positionCurrent, colRW.directionCurrent, tailCenter);
|
|
135
|
+
colRW.worldToLocal.transformPointAffine(tailCenter, tailCenter);
|
|
136
|
+
colRW.localBoundsMin.setXYZ(Math.min(colRW.localBoundsMin.x, tailCenter.x - tailCorner.x), Math.min(colRW.localBoundsMin.y, tailCenter.y - tailCorner.y), Math.min(colRW.localBoundsMin.z, tailCenter.z - tailCorner.z));
|
|
137
|
+
colRW.localBoundsMax.setXYZ(Math.max(colRW.localBoundsMax.x, tailCenter.x + tailCorner.x), Math.max(colRW.localBoundsMax.y, tailCenter.y + tailCorner.y), Math.max(colRW.localBoundsMax.z, tailCenter.z + tailCorner.z));
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
function applySystemTransform(point, pivot, slideOffset, rotOffset, result) {
|
|
142
|
+
const rotated = rotOffset.transform(Vector3.sub(point, pivot, _applySystemTransformOffset), result || _applySystemTransformRotated);
|
|
143
|
+
Vector3.add(rotated, pivot, rotated);
|
|
144
|
+
return Vector3.add(rotated, slideOffset, rotated);
|
|
145
|
+
}
|
|
146
|
+
function pointMayCollideBroadPhase(point, colRW, padding = 0) {
|
|
147
|
+
const radius = colRW.boundsRadius + padding;
|
|
148
|
+
const delta = Vector3.sub(point, colRW.boundsCenter, _broadPhaseDelta);
|
|
149
|
+
return delta.magnitudeSq <= radius * radius;
|
|
150
|
+
}
|
|
151
|
+
function segmentMayCollideBroadPhase(point1, point2, colRW) {
|
|
152
|
+
const segmentDir = Vector3.sub(point2, point1, _broadPhaseSegmentDir);
|
|
153
|
+
const lenSq = segmentDir.magnitudeSq;
|
|
154
|
+
if (lenSq <= EPSILON) {
|
|
155
|
+
return pointMayCollideBroadPhase(point1, colRW);
|
|
156
|
+
}
|
|
157
|
+
const toCenter = Vector3.sub(colRW.boundsCenter, point1, _broadPhaseToCenter);
|
|
158
|
+
const t = clamp01(Vector3.dot(toCenter, segmentDir) / lenSq);
|
|
159
|
+
const closestPoint = Vector3.scale(segmentDir, t, _broadPhaseClosestPoint);
|
|
160
|
+
Vector3.add(point1, closestPoint, closestPoint);
|
|
161
|
+
const delta = Vector3.sub(colRW.boundsCenter, closestPoint, _broadPhaseDelta);
|
|
162
|
+
return delta.magnitudeSq <= colRW.boundsRadius * colRW.boundsRadius;
|
|
163
|
+
}
|
|
164
|
+
// Pass 1: Verlet integration + forces
|
|
165
|
+
function pointUpdatePass1(pointsR, pointsRW, stepDelta, stepTime_x2_half, rootSlideOffset, rootRotationOffset, rootPosition, windForce, isPaused, grabbersR, grabbersRW, movableLimitTargets, flatPlanes, collidersR, collidersRW) {
|
|
166
|
+
const currentTransformPos = new Vector3();
|
|
167
|
+
const moveDir = new Vector3();
|
|
168
|
+
const extForce = new Vector3();
|
|
169
|
+
const displacement = new Vector3();
|
|
170
|
+
const restore = new Vector3();
|
|
171
|
+
const tempVec0 = new Vector3();
|
|
172
|
+
const tempVec1 = new Vector3();
|
|
173
|
+
const planeOffset = new Vector3();
|
|
174
|
+
for(let index = 0; index < pointsR.length; index++){
|
|
175
|
+
const ptR = pointsR[index];
|
|
176
|
+
const ptRW = pointsRW[index];
|
|
177
|
+
Vector3.lerp(ptRW.positionPreviousTransform, ptRW.positionCurrentTransform, stepDelta, currentTransformPos);
|
|
178
|
+
if (ptR.weight <= EPSILON) {
|
|
179
|
+
ptRW.positionPrevious.set(ptRW.positionCurrent);
|
|
180
|
+
ptRW.positionCurrent.set(currentTransformPos);
|
|
181
|
+
} else {
|
|
182
|
+
applySystemTransform(ptRW.positionPrevious, rootPosition, rootSlideOffset, rootRotationOffset, ptRW.positionPrevious);
|
|
183
|
+
applySystemTransform(ptRW.positionCurrent, rootPosition, rootSlideOffset, rootRotationOffset, ptRW.positionCurrent);
|
|
184
|
+
displacement.setXYZ(0, 0, 0);
|
|
185
|
+
if (!isPaused) {
|
|
186
|
+
Vector3.sub(ptRW.positionCurrent, ptRW.positionPrevious, moveDir);
|
|
187
|
+
Vector3.scale(windForce, ptR.windForceScale / ptR.mass, extForce);
|
|
188
|
+
Vector3.add(ptR.gravity, extForce, extForce);
|
|
189
|
+
Vector3.scale(extForce, stepTime_x2_half, extForce);
|
|
190
|
+
// Clamp the per-step force displacement to half the bone's rest length.
|
|
191
|
+
// Without this, large gravity values (e.g. -50 in world space) produce a
|
|
192
|
+
// per-step displacement comparable to the bone spacing, which exceeds what
|
|
193
|
+
// the constraint relaxation can correct in one pass and causes surface jitter.
|
|
194
|
+
if (ptR.parentLength > EPSILON) {
|
|
195
|
+
const maxForceDisp = ptR.parentLength * 0.5;
|
|
196
|
+
const forceDispSq = extForce.magnitudeSq;
|
|
197
|
+
if (forceDispSq > maxForceDisp * maxForceDisp) {
|
|
198
|
+
Vector3.scale(extForce, maxForceDisp / Math.sqrt(forceDispSq), extForce);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
// Apply resistance (damping) only to the velocity term, not to external forces.
|
|
202
|
+
// Applying resistance to gravity would incorrectly attenuate acceleration each frame,
|
|
203
|
+
// causing energy errors and instability under high gravity.
|
|
204
|
+
Vector3.scale(moveDir, ptR.resistance, moveDir);
|
|
205
|
+
Vector3.scale(moveDir, 1.0 - clamp01(ptRW.friction * ptR.frictionScale), moveDir);
|
|
206
|
+
// Clamp per-step velocity to a multiple of bone rest length.
|
|
207
|
+
// During fast root rotations the Verlet velocity can become very large,
|
|
208
|
+
// overwhelming the constraint solver and causing the hair to lose shape.
|
|
209
|
+
// Limiting velocity keeps the simulation stable regardless of how fast
|
|
210
|
+
// the root moves, while still allowing natural large-amplitude swings.
|
|
211
|
+
if (ptR.parentLength > EPSILON) {
|
|
212
|
+
const maxVelDisp = ptR.parentLength * 2.0;
|
|
213
|
+
const velDispSq = moveDir.magnitudeSq;
|
|
214
|
+
if (velDispSq > maxVelDisp * maxVelDisp) {
|
|
215
|
+
Vector3.scale(moveDir, maxVelDisp / Math.sqrt(velDispSq), moveDir);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
Vector3.add(moveDir, extForce, displacement);
|
|
219
|
+
}
|
|
220
|
+
ptRW.positionPrevious.set(ptRW.positionCurrent);
|
|
221
|
+
Vector3.add(ptRW.positionCurrent, displacement, ptRW.positionCurrent);
|
|
222
|
+
ptRW.friction = 0;
|
|
223
|
+
if (!isPaused) {
|
|
224
|
+
// Hardness restore
|
|
225
|
+
if (ptR.hardness > 0) {
|
|
226
|
+
Vector3.sub(currentTransformPos, ptRW.positionCurrent, restore);
|
|
227
|
+
Vector3.scale(restore, ptR.hardness, restore);
|
|
228
|
+
Vector3.add(ptRW.positionCurrent, restore, ptRW.positionCurrent);
|
|
229
|
+
}
|
|
230
|
+
// Force fade ratio
|
|
231
|
+
if (ptR.forceFadeRatio > 0) {
|
|
232
|
+
Vector3.lerp(ptRW.positionCurrent, currentTransformPos, ptR.forceFadeRatio, ptRW.positionCurrent);
|
|
233
|
+
}
|
|
234
|
+
// Push out of colliders immediately after hardness/fade pulls the point
|
|
235
|
+
// toward the animated position (which may be inside a collider).
|
|
236
|
+
// This prevents the hardness↔collision tug-of-war that causes jitter.
|
|
237
|
+
for(let ci = 0; ci < collidersR.length; ci++){
|
|
238
|
+
const colRci = collidersR[ci];
|
|
239
|
+
const colRWci = collidersRW[ci];
|
|
240
|
+
if (colRWci.enabled === 0 || colRci.isInverseCollider) {
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
const hRes = colRci.height <= EPSILON ? pushoutFromSphere(colRWci.positionCurrent, colRWci.radius, ptR.pointRadius, ptRW.positionCurrent) : pushoutFromCapsule(colRci, colRWci, ptRW.positionCurrent, ptR);
|
|
244
|
+
if (hRes.hit) {
|
|
245
|
+
ptRW.positionCurrent.set(hRes.point);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
// Grabber
|
|
249
|
+
if (ptRW.grabberIndex !== -1) {
|
|
250
|
+
const grR = grabbersR[ptRW.grabberIndex];
|
|
251
|
+
const grRW = grabbersRW[ptRW.grabberIndex];
|
|
252
|
+
if (grRW.enabled === 0) {
|
|
253
|
+
ptRW.grabberIndex = -1;
|
|
254
|
+
} else {
|
|
255
|
+
Vector3.sub(ptRW.positionCurrent, grRW.position, tempVec0);
|
|
256
|
+
Vector3.normalize(tempVec0, tempVec0);
|
|
257
|
+
Vector3.scale(tempVec0, ptRW.grabberDistance, tempVec1);
|
|
258
|
+
Vector3.add(grRW.position, tempVec1, tempVec1);
|
|
259
|
+
Vector3.sub(tempVec1, ptRW.positionCurrent, tempVec0);
|
|
260
|
+
Vector3.scale(tempVec0, grR.force, tempVec0);
|
|
261
|
+
Vector3.add(ptRW.positionCurrent, tempVec0, ptRW.positionCurrent);
|
|
262
|
+
}
|
|
263
|
+
} else {
|
|
264
|
+
let nearIndex = -1;
|
|
265
|
+
let sqrNearRange = 1000 * 1000;
|
|
266
|
+
for(let ig = 0; ig < grabbersR.length; ig++){
|
|
267
|
+
const grRW = grabbersRW[ig];
|
|
268
|
+
if (grRW.enabled !== 0) {
|
|
269
|
+
const sqrLen = Vector3.sub(grRW.position, ptRW.positionCurrent, tempVec0).magnitudeSq;
|
|
270
|
+
if (sqrLen < grabbersR[ig].radius * grabbersR[ig].radius && sqrLen < sqrNearRange) {
|
|
271
|
+
sqrNearRange = sqrLen;
|
|
272
|
+
nearIndex = ig;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
if (nearIndex !== -1) {
|
|
277
|
+
ptRW.grabberIndex = nearIndex;
|
|
278
|
+
ptRW.grabberDistance = Math.sqrt(sqrNearRange) / 2.0;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
// Movable limit
|
|
282
|
+
if (ptR.movableLimitIndex !== -1) {
|
|
283
|
+
const target = movableLimitTargets[ptR.movableLimitIndex];
|
|
284
|
+
const move = Vector3.sub(ptRW.positionCurrent, target, tempVec0);
|
|
285
|
+
const moveLen = move.magnitude;
|
|
286
|
+
if (moveLen > ptR.movableLimitRadius) {
|
|
287
|
+
Vector3.scale(move, ptR.movableLimitRadius / moveLen, move);
|
|
288
|
+
Vector3.add(target, move, ptRW.positionCurrent);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
// Flat plane collision
|
|
293
|
+
for(let i = 0; i < flatPlanes.length; i++){
|
|
294
|
+
const fp = flatPlanes[i];
|
|
295
|
+
const dist = Vector3.dot(fp.normal, ptRW.positionCurrent) + fp.distance;
|
|
296
|
+
if (dist < 0) {
|
|
297
|
+
Vector3.scale(fp.normal, dist, planeOffset);
|
|
298
|
+
Vector3.sub(ptRW.positionCurrent, planeOffset, ptRW.positionCurrent);
|
|
299
|
+
ptRW.friction = 0.3;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
// Surface collision (triangle-based cloth collision)
|
|
306
|
+
function surfaceCollision(pointsRW, collidersR, collidersRW, surfaceConstraints) {
|
|
307
|
+
const surfaceResult = {
|
|
308
|
+
hit: false,
|
|
309
|
+
intersectionPoint: new Vector3(),
|
|
310
|
+
pushOut: new Vector3(),
|
|
311
|
+
pointOnCollider: new Vector3(),
|
|
312
|
+
radius: 0
|
|
313
|
+
};
|
|
314
|
+
const triCenter = new Vector3();
|
|
315
|
+
const pushDir = new Vector3();
|
|
316
|
+
const centerToPt = new Vector3();
|
|
317
|
+
const intersectToPt = new Vector3();
|
|
318
|
+
const pushVec = new Vector3();
|
|
319
|
+
for(let index = 0; index < surfaceConstraints.length; index += 6){
|
|
320
|
+
for(let i = 0; i < collidersR.length; i++){
|
|
321
|
+
const colR = collidersR[i];
|
|
322
|
+
const colRW = collidersRW[i];
|
|
323
|
+
if (colRW.enabled === 0) {
|
|
324
|
+
continue;
|
|
325
|
+
}
|
|
326
|
+
const colliderPos = colRW.positionCurrent;
|
|
327
|
+
for(let j = 0; j < 6; j += 3){
|
|
328
|
+
const idxA = surfaceConstraints[index + j + 0];
|
|
329
|
+
const idxB = surfaceConstraints[index + j + 1];
|
|
330
|
+
const idxC = surfaceConstraints[index + j + 2];
|
|
331
|
+
const rwA = pointsRW[idxA];
|
|
332
|
+
const rwB = pointsRW[idxB];
|
|
333
|
+
const rwC = pointsRW[idxC];
|
|
334
|
+
const result = checkSurfaceCollision(rwA.positionCurrent, rwB.positionCurrent, rwC.positionCurrent, colliderPos, colR, colRW, surfaceResult);
|
|
335
|
+
if (result.hit) {
|
|
336
|
+
triCenter.setXYZ((rwA.positionCurrent.x + rwB.positionCurrent.x + rwC.positionCurrent.x) / 3, (rwA.positionCurrent.y + rwB.positionCurrent.y + rwC.positionCurrent.y) / 3, (rwA.positionCurrent.z + rwB.positionCurrent.z + rwC.positionCurrent.z) / 3);
|
|
337
|
+
const pushMag = result.pushOut.magnitude;
|
|
338
|
+
if (pushMag < EPSILON) {
|
|
339
|
+
continue;
|
|
340
|
+
}
|
|
341
|
+
Vector3.scale(result.pushOut, 1 / pushMag, pushDir);
|
|
342
|
+
for(let k = 0; k < 3; k++){
|
|
343
|
+
const idxR = surfaceConstraints[index + j + k];
|
|
344
|
+
const rwPt = pointsRW[idxR];
|
|
345
|
+
Vector3.sub(rwPt.positionCurrent, triCenter, centerToPt);
|
|
346
|
+
Vector3.sub(rwPt.positionCurrent, result.intersectionPoint, intersectToPt);
|
|
347
|
+
const intersectLen = intersectToPt.magnitude;
|
|
348
|
+
let rate = intersectLen > EPSILON ? centerToPt.magnitude / intersectLen : 0;
|
|
349
|
+
rate = clamp01(Math.abs(rate));
|
|
350
|
+
Vector3.scale(pushDir, (result.radius - pushMag) * rate, pushVec);
|
|
351
|
+
Vector3.add(rwPt.positionCurrent, pushVec, rwPt.positionCurrent);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
const _postFixNormal = new Vector3();
|
|
359
|
+
const _fixedPushResult = {
|
|
360
|
+
hit: false,
|
|
361
|
+
point: new Vector3()
|
|
362
|
+
};
|
|
363
|
+
/**
|
|
364
|
+
* Before constraint solving each substep, push fixed points (weight=0) out of
|
|
365
|
+
* any colliders they animate into. Fixed points normally follow animation
|
|
366
|
+
* exactly, but when their animated position lies inside a collider the distance
|
|
367
|
+
* constraint pulls free children inward through the surface, producing the
|
|
368
|
+
* classic "hair flipping outward" artifact.
|
|
369
|
+
*
|
|
370
|
+
* We clamp positionCurrent to the collider surface so that constraints operate
|
|
371
|
+
* from a geometrically valid anchor. The temporary change is overwritten at the
|
|
372
|
+
* start of the next substep when positionCurrent is refreshed from the animation
|
|
373
|
+
* transform, so the skeleton itself is never permanently altered.
|
|
374
|
+
*/ function fixedPointColliderPushout(pointsR, pointsRW, collidersR, collidersRW) {
|
|
375
|
+
for(let pi = 0; pi < pointsR.length; pi++){
|
|
376
|
+
if (pointsR[pi].weight > EPSILON) {
|
|
377
|
+
continue;
|
|
378
|
+
}
|
|
379
|
+
const ptRW = pointsRW[pi];
|
|
380
|
+
const ptR = pointsR[pi];
|
|
381
|
+
for(let ci = 0; ci < collidersR.length; ci++){
|
|
382
|
+
const colR = collidersR[ci];
|
|
383
|
+
const colRW = collidersRW[ci];
|
|
384
|
+
if (colRW.enabled === 0 || colR.isInverseCollider) {
|
|
385
|
+
continue;
|
|
386
|
+
}
|
|
387
|
+
const res = colR.height <= EPSILON ? pushoutFromSphere(colRW.positionCurrent, colRW.radius, ptR.pointRadius, ptRW.positionCurrent, _fixedPushResult) : pushoutFromCapsule(colR, colRW, ptRW.positionCurrent, ptR, _fixedPushResult);
|
|
388
|
+
if (res.hit) {
|
|
389
|
+
ptRW.positionCurrent.set(res.point);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* Called once per substep after ALL constraint + collision relaxation iterations finish.
|
|
396
|
+
* For each dynamic point that is actually touching a collider surface (within radius),
|
|
397
|
+
* cancel the normal-direction component of the Verlet velocity from positionPrevious.
|
|
398
|
+
* Points far from all colliders are untouched, preserving wind-driven velocity.
|
|
399
|
+
*/ function postCollisionVelocityFix(pointsR, pointsRW, collidersR, collidersRW) {
|
|
400
|
+
for(let pi = 0; pi < pointsR.length; pi++){
|
|
401
|
+
const ptR = pointsR[pi];
|
|
402
|
+
if (ptR.weight <= EPSILON) {
|
|
403
|
+
continue;
|
|
404
|
+
}
|
|
405
|
+
const ptRW = pointsRW[pi];
|
|
406
|
+
for(let ci = 0; ci < collidersR.length; ci++){
|
|
407
|
+
const colR = collidersR[ci];
|
|
408
|
+
const colRW = collidersRW[ci];
|
|
409
|
+
if (colRW.enabled === 0 || colR.isInverseCollider) {
|
|
410
|
+
continue;
|
|
411
|
+
}
|
|
412
|
+
let nx = 0, ny = 0, nz = 0;
|
|
413
|
+
if (colR.height <= EPSILON) {
|
|
414
|
+
nx = ptRW.positionCurrent.x - colRW.positionCurrent.x;
|
|
415
|
+
ny = ptRW.positionCurrent.y - colRW.positionCurrent.y;
|
|
416
|
+
nz = ptRW.positionCurrent.z - colRW.positionCurrent.z;
|
|
417
|
+
} else {
|
|
418
|
+
const capsuleVec = colRW.directionCurrent;
|
|
419
|
+
const capsuleVecLenSq = capsuleVec.magnitudeSq;
|
|
420
|
+
if (capsuleVecLenSq > EPSILON) {
|
|
421
|
+
const tx = ptRW.positionCurrent.x - colRW.positionCurrent.x;
|
|
422
|
+
const ty = ptRW.positionCurrent.y - colRW.positionCurrent.y;
|
|
423
|
+
const tz = ptRW.positionCurrent.z - colRW.positionCurrent.z;
|
|
424
|
+
const t = Math.max(0, Math.min(1, (tx * capsuleVec.x + ty * capsuleVec.y + tz * capsuleVec.z) / capsuleVecLenSq));
|
|
425
|
+
nx = ptRW.positionCurrent.x - (colRW.positionCurrent.x + capsuleVec.x * t);
|
|
426
|
+
ny = ptRW.positionCurrent.y - (colRW.positionCurrent.y + capsuleVec.y * t);
|
|
427
|
+
nz = ptRW.positionCurrent.z - (colRW.positionCurrent.z + capsuleVec.z * t);
|
|
428
|
+
} else {
|
|
429
|
+
nx = ptRW.positionCurrent.x - colRW.positionCurrent.x;
|
|
430
|
+
ny = ptRW.positionCurrent.y - colRW.positionCurrent.y;
|
|
431
|
+
nz = ptRW.positionCurrent.z - colRW.positionCurrent.z;
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
const nLen = Math.sqrt(nx * nx + ny * ny + nz * nz);
|
|
435
|
+
if (nLen < EPSILON) {
|
|
436
|
+
continue;
|
|
437
|
+
}
|
|
438
|
+
const surfaceDist = nLen - colRW.radius - ptR.pointRadius;
|
|
439
|
+
if (surfaceDist > colRW.radius * 0.05) {
|
|
440
|
+
continue;
|
|
441
|
+
}
|
|
442
|
+
_postFixNormal.setXYZ(nx / nLen, ny / nLen, nz / nLen);
|
|
443
|
+
const vx = ptRW.positionCurrent.x - ptRW.positionPrevious.x;
|
|
444
|
+
const vy = ptRW.positionCurrent.y - ptRW.positionPrevious.y;
|
|
445
|
+
const vz = ptRW.positionCurrent.z - ptRW.positionPrevious.z;
|
|
446
|
+
const vDotN = vx * _postFixNormal.x + vy * _postFixNormal.y + vz * _postFixNormal.z;
|
|
447
|
+
if (vDotN < 0) {
|
|
448
|
+
ptRW.positionPrevious.x += _postFixNormal.x * vDotN;
|
|
449
|
+
ptRW.positionPrevious.y += _postFixNormal.y * vDotN;
|
|
450
|
+
ptRW.positionPrevious.z += _postFixNormal.z * vDotN;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
function constraintUpdate(pointsR, pointsRW, constraints, collidersR, collidersRW, constraintShrinkLimit, enableBroadPhase) {
|
|
456
|
+
const direction = new Vector3();
|
|
457
|
+
const dirN = new Vector3();
|
|
458
|
+
const disp = new Vector3();
|
|
459
|
+
const scaledDisp = new Vector3();
|
|
460
|
+
const pointResultA = {
|
|
461
|
+
hit: false,
|
|
462
|
+
point: new Vector3()
|
|
463
|
+
};
|
|
464
|
+
const pointResultB = {
|
|
465
|
+
hit: false,
|
|
466
|
+
point: new Vector3()
|
|
467
|
+
};
|
|
468
|
+
const lineResult = {
|
|
469
|
+
hit: false,
|
|
470
|
+
pointOnLine: new Vector3(),
|
|
471
|
+
pointOnCollider: new Vector3(),
|
|
472
|
+
radius: 0
|
|
473
|
+
};
|
|
474
|
+
const pushout = new Vector3();
|
|
475
|
+
const segment = new Vector3();
|
|
476
|
+
const pointToA = new Vector3();
|
|
477
|
+
const pointToB = new Vector3();
|
|
478
|
+
const pushN = new Vector3();
|
|
479
|
+
const pushVec = new Vector3();
|
|
480
|
+
for(let index = 0; index < constraints.length; index++){
|
|
481
|
+
const c = constraints[index];
|
|
482
|
+
const ptRA = pointsR[c.indexA];
|
|
483
|
+
const ptRB = pointsR[c.indexB];
|
|
484
|
+
const weightA = ptRA.weight;
|
|
485
|
+
const weightB = ptRB.weight;
|
|
486
|
+
if (weightA <= EPSILON && weightB <= EPSILON) {
|
|
487
|
+
continue;
|
|
488
|
+
}
|
|
489
|
+
const rwA = pointsRW[c.indexA];
|
|
490
|
+
const rwB = pointsRW[c.indexB];
|
|
491
|
+
Vector3.sub(rwB.positionCurrent, rwA.positionCurrent, direction);
|
|
492
|
+
const distance = direction.magnitude;
|
|
493
|
+
let shrinkLen = c.length;
|
|
494
|
+
let stretchLen = shrinkLen;
|
|
495
|
+
if (c.type === ConstraintType.Structural_Horizontal || c.type === ConstraintType.Bending_Horizontal || c.type === ConstraintType.Shear) {
|
|
496
|
+
stretchLen += ptRA.sliderJointLength + ptRB.sliderJointLength;
|
|
497
|
+
}
|
|
498
|
+
let force = 0;
|
|
499
|
+
if (distance <= shrinkLen) {
|
|
500
|
+
force = distance - shrinkLen;
|
|
501
|
+
} else if (distance >= stretchLen) {
|
|
502
|
+
force = distance - stretchLen;
|
|
503
|
+
}
|
|
504
|
+
const isShrink = force >= 0;
|
|
505
|
+
let power = 0;
|
|
506
|
+
switch(c.type){
|
|
507
|
+
case ConstraintType.Structural_Vertical:
|
|
508
|
+
power = isShrink ? ptRA.structuralShrinkVertical + ptRB.structuralShrinkVertical : ptRA.structuralStretchVertical + ptRB.structuralStretchVertical;
|
|
509
|
+
break;
|
|
510
|
+
case ConstraintType.Structural_Horizontal:
|
|
511
|
+
power = isShrink ? Math.min(constraintShrinkLimit, ptRA.structuralShrinkHorizontal + ptRB.structuralShrinkHorizontal) : ptRA.structuralStretchHorizontal + ptRB.structuralStretchHorizontal;
|
|
512
|
+
break;
|
|
513
|
+
case ConstraintType.Shear:
|
|
514
|
+
power = isShrink ? Math.min(constraintShrinkLimit, ptRA.shearShrink + ptRB.shearShrink) : ptRA.shearStretch + ptRB.shearStretch;
|
|
515
|
+
break;
|
|
516
|
+
case ConstraintType.Bending_Vertical:
|
|
517
|
+
power = isShrink ? ptRA.bendingShrinkVertical + ptRB.bendingShrinkVertical : ptRA.bendingStretchVertical + ptRB.bendingStretchVertical;
|
|
518
|
+
break;
|
|
519
|
+
case ConstraintType.Bending_Horizontal:
|
|
520
|
+
power = isShrink ? Math.min(constraintShrinkLimit, ptRA.bendingShrinkHorizontal + ptRB.bendingShrinkHorizontal) : ptRA.bendingStretchHorizontal + ptRB.bendingStretchHorizontal;
|
|
521
|
+
break;
|
|
522
|
+
}
|
|
523
|
+
if (power > 0) {
|
|
524
|
+
if (distance > EPSILON) {
|
|
525
|
+
Vector3.scale(direction, 1 / distance, dirN);
|
|
526
|
+
} else {
|
|
527
|
+
dirN.setXYZ(0, 0, 0);
|
|
528
|
+
}
|
|
529
|
+
Vector3.scale(dirN, force * power, disp);
|
|
530
|
+
const wAB = weightA + weightB;
|
|
531
|
+
Vector3.scale(disp, weightA / wAB, scaledDisp);
|
|
532
|
+
Vector3.add(rwA.positionCurrent, scaledDisp, rwA.positionCurrent);
|
|
533
|
+
Vector3.scale(disp, weightB / wAB, scaledDisp);
|
|
534
|
+
Vector3.sub(rwB.positionCurrent, scaledDisp, rwB.positionCurrent);
|
|
535
|
+
}
|
|
536
|
+
// Per-constraint collision
|
|
537
|
+
if (c.isCollision !== 0) {
|
|
538
|
+
let friction = 0;
|
|
539
|
+
for(let i = 0; i < collidersR.length; i++){
|
|
540
|
+
const colRW = collidersRW[i];
|
|
541
|
+
if (colRW.enabled === 0) {
|
|
542
|
+
continue;
|
|
543
|
+
}
|
|
544
|
+
const colR = collidersR[i];
|
|
545
|
+
const canPointA = !enableBroadPhase || colR.isInverseCollider || pointMayCollideBroadPhase(rwA.positionCurrent, colRW, ptRA.pointRadius);
|
|
546
|
+
const canPointB = !enableBroadPhase || colR.isInverseCollider || pointMayCollideBroadPhase(rwB.positionCurrent, colRW, ptRB.pointRadius);
|
|
547
|
+
const canLine = !colR.isInverseCollider && (!enableBroadPhase || segmentMayCollideBroadPhase(rwA.positionCurrent, rwB.positionCurrent, colRW));
|
|
548
|
+
if (!canPointA && !canPointB && !canLine) {
|
|
549
|
+
continue;
|
|
550
|
+
}
|
|
551
|
+
if (colR.height > EPSILON) {
|
|
552
|
+
if (colR.isInverseCollider) {
|
|
553
|
+
if (ptRA.applyInvertCollision === 1 && canPointA) {
|
|
554
|
+
const res = pushInFromCapsule(colR, colRW, rwA.positionCurrent, pointResultA);
|
|
555
|
+
if (res.hit) {
|
|
556
|
+
rwA.positionCurrent.set(res.point);
|
|
557
|
+
friction = Math.max(friction, colR.friction * 0.25);
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
if (ptRB.applyInvertCollision === 1 && canPointB) {
|
|
561
|
+
const res = pushInFromCapsule(colR, colRW, rwB.positionCurrent, pointResultB);
|
|
562
|
+
if (res.hit) {
|
|
563
|
+
rwB.positionCurrent.set(res.point);
|
|
564
|
+
friction = Math.max(friction, colR.friction * 0.25);
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
} else {
|
|
568
|
+
if (canPointA) {
|
|
569
|
+
const res = pushoutFromCapsule(colR, colRW, rwA.positionCurrent, ptRA, pointResultA);
|
|
570
|
+
if (res.hit) {
|
|
571
|
+
rwA.positionCurrent.set(res.point);
|
|
572
|
+
friction = Math.max(friction, colR.friction * 0.25);
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
if (canPointB) {
|
|
576
|
+
const res = pushoutFromCapsule(colR, colRW, rwB.positionCurrent, ptRB, pointResultB);
|
|
577
|
+
if (res.hit) {
|
|
578
|
+
rwB.positionCurrent.set(res.point);
|
|
579
|
+
friction = Math.max(friction, colR.friction * 0.25);
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
} else {
|
|
584
|
+
if (colR.isInverseCollider) {
|
|
585
|
+
if (ptRA.applyInvertCollision === 1 && canPointA) {
|
|
586
|
+
const res = pushInFromSphere(colRW.positionCurrent, colRW.radius, rwA.positionCurrent, pointResultA);
|
|
587
|
+
if (res.hit) {
|
|
588
|
+
rwA.positionCurrent.set(res.point);
|
|
589
|
+
friction = Math.max(friction, colR.friction * 0.25);
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
if (ptRB.applyInvertCollision === 1 && canPointB) {
|
|
593
|
+
const res = pushInFromSphere(colRW.positionCurrent, colRW.radius, rwB.positionCurrent, pointResultB);
|
|
594
|
+
if (res.hit) {
|
|
595
|
+
rwB.positionCurrent.set(res.point);
|
|
596
|
+
friction = Math.max(friction, colR.friction * 0.25);
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
} else {
|
|
600
|
+
if (canPointA) {
|
|
601
|
+
const res = pushoutFromSphere(colRW.positionCurrent, colRW.radius, ptRA.pointRadius, rwA.positionCurrent, pointResultA);
|
|
602
|
+
if (res.hit) {
|
|
603
|
+
rwA.positionCurrent.set(res.point);
|
|
604
|
+
friction = Math.max(friction, colR.friction * 0.25);
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
if (canPointB) {
|
|
608
|
+
const res = pushoutFromSphere(colRW.positionCurrent, colRW.radius, ptRB.pointRadius, rwB.positionCurrent, pointResultB);
|
|
609
|
+
if (res.hit) {
|
|
610
|
+
rwB.positionCurrent.set(res.point);
|
|
611
|
+
friction = Math.max(friction, colR.friction * 0.25);
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
// Line segment collision
|
|
617
|
+
if (!colR.isInverseCollider && canLine) {
|
|
618
|
+
const lineRes = collisionDetection(colR, colRW, rwA.positionCurrent, rwB.positionCurrent, lineResult);
|
|
619
|
+
if (lineRes.hit) {
|
|
620
|
+
Vector3.sub(lineRes.pointOnLine, lineRes.pointOnCollider, pushout);
|
|
621
|
+
const pushDist = pushout.magnitude;
|
|
622
|
+
if (pushDist > EPSILON) {
|
|
623
|
+
const ptDist = Vector3.sub(rwB.positionCurrent, rwA.positionCurrent, segment).magnitude * 0.5;
|
|
624
|
+
const rateP1 = ptDist > EPSILON ? clamp01(Vector3.sub(lineRes.pointOnLine, rwA.positionCurrent, pointToA).magnitude / ptDist) : 0;
|
|
625
|
+
const rateP2 = ptDist > EPSILON ? clamp01(Vector3.sub(lineRes.pointOnLine, rwB.positionCurrent, pointToB).magnitude / ptDist) : 0;
|
|
626
|
+
Vector3.scale(pushout, 1 / pushDist, pushN);
|
|
627
|
+
Vector3.scale(pushN, Math.max(lineRes.radius - pushDist, 0), pushVec);
|
|
628
|
+
if (weightA > EPSILON) {
|
|
629
|
+
Vector3.scale(pushVec, rateP2, scaledDisp);
|
|
630
|
+
Vector3.add(rwA.positionCurrent, scaledDisp, rwA.positionCurrent);
|
|
631
|
+
}
|
|
632
|
+
if (weightB > EPSILON) {
|
|
633
|
+
Vector3.scale(pushVec, rateP1, scaledDisp);
|
|
634
|
+
Vector3.add(rwB.positionCurrent, scaledDisp, rwB.positionCurrent);
|
|
635
|
+
}
|
|
636
|
+
const dotUp = Vector3.dot(AXIS_Y, Vector3.normalize(pushout, pushN));
|
|
637
|
+
friction = Math.max(friction, colR.friction * clamp01(dotUp));
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
rwA.friction = Math.max(friction, rwA.friction);
|
|
643
|
+
rwB.friction = Math.max(friction, rwB.friction);
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
function pointUpdatePass2(pointsR, pointsRW, positionsToTransform, stepDelta, fakeWaveFreq, blendRatio, isFakeWave, fakeWavePower) {
|
|
648
|
+
let freq = fakeWaveFreq;
|
|
649
|
+
const curTransformPos = new Vector3();
|
|
650
|
+
const fakeWindOffset = new Vector3();
|
|
651
|
+
for(let index = 0; index < pointsR.length; index++){
|
|
652
|
+
const ptR = pointsR[index];
|
|
653
|
+
const ptRW = pointsRW[index];
|
|
654
|
+
Vector3.lerp(ptRW.positionPreviousTransform, ptRW.positionCurrentTransform, stepDelta, curTransformPos);
|
|
655
|
+
const blend = smoothStep(0, 1, Math.max(ptR.forceFadeRatio, blendRatio));
|
|
656
|
+
Vector3.lerp(ptRW.positionCurrent, curTransformPos, blend, ptRW.positionToTransform);
|
|
657
|
+
if (isFakeWave && ptR.child === -1 && ptR.parent !== -1) {
|
|
658
|
+
const A = pointsRW[ptR.parent].positionToTransform;
|
|
659
|
+
const B = ptRW.positionToTransform;
|
|
660
|
+
const mAxis = Matrix4x4.lookAt(A, B, AXIS_Y);
|
|
661
|
+
// Column 1 = up axis of the look-at matrix
|
|
662
|
+
const col1 = mAxis.getCol(1).xyz();
|
|
663
|
+
Vector3.lerp(ptRW.fakeWindDirection, col1, 0.5, ptRW.fakeWindDirection);
|
|
664
|
+
freq += ptR.fakeWaveFreq;
|
|
665
|
+
const power = Math.sin(freq) * fakeWavePower * ptR.fakeWavePower;
|
|
666
|
+
Vector3.scale(ptRW.fakeWindDirection, power, fakeWindOffset);
|
|
667
|
+
Vector3.add(ptRW.positionToTransform, fakeWindOffset, ptRW.positionToTransform);
|
|
668
|
+
}
|
|
669
|
+
positionsToTransform[index] = ptRW.positionToTransform.clone();
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
function applyResult(pointsR, pointsRW, positionsToTransform, blendRatio, // Current transform rotations (world and local) read from engine
|
|
673
|
+
transformRotations, transformLocalRotations, preserveTwist) {
|
|
674
|
+
const results = new Array(pointsR.length);
|
|
675
|
+
for(let index = 0; index < pointsR.length; index++){
|
|
676
|
+
const ptR = pointsR[index];
|
|
677
|
+
const ptRW = pointsRW[index];
|
|
678
|
+
let worldRot = transformRotations[index];
|
|
679
|
+
let localRot = transformLocalRotations[index];
|
|
680
|
+
if (ptR.weight >= EPSILON) {
|
|
681
|
+
// Dynamic point
|
|
682
|
+
if (ptR.parent !== -1) {
|
|
683
|
+
const direction = Vector3.sub(ptRW.positionToTransform, positionsToTransform[ptR.parent]);
|
|
684
|
+
const realLen = direction.magnitude;
|
|
685
|
+
if (realLen > EPSILON) {
|
|
686
|
+
ptRW.directionPrevious = direction;
|
|
687
|
+
} else {
|
|
688
|
+
ptRW.positionToTransform = Vector3.add(positionsToTransform[ptR.parent], ptRW.directionPrevious);
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
const pos = ptRW.positionToTransform;
|
|
692
|
+
// SetRotation with blendRatio=0 for dynamic points
|
|
693
|
+
// Step 1: Reset localRotation 閳?initialLocalRotation (blendRatio=0 閳?fully initial)
|
|
694
|
+
localRot = ptR.initialLocalRotation.clone();
|
|
695
|
+
// Step 2: Recompute worldRot from the new localRot
|
|
696
|
+
// In C#, setting localRotation immediately updates the world rotation.
|
|
697
|
+
// worldRot = parentWorldRot * localRot
|
|
698
|
+
// We approximate: the parent's current world rotation is in transformRotations[ptR.parent]
|
|
699
|
+
if (ptR.parent !== -1) {
|
|
700
|
+
// Use the parent's OUTPUT rotation (already computed if parent index < current index)
|
|
701
|
+
const parentRot = results[ptR.parent] ? results[ptR.parent].rotation : transformRotations[ptR.parent];
|
|
702
|
+
worldRot = Quaternion.multiply(parentRot, localRot);
|
|
703
|
+
} else {
|
|
704
|
+
worldRot = localRot.clone();
|
|
705
|
+
}
|
|
706
|
+
// Step 3: Aim toward child (simple shortest-arc, no twist correction here)
|
|
707
|
+
if (ptR.child !== -1) {
|
|
708
|
+
const childDir = Vector3.sub(positionsToTransform[ptR.child], ptRW.positionToTransform);
|
|
709
|
+
if (childDir.magnitudeSq > EPSILON) {
|
|
710
|
+
const aimVec = worldRot.transform(ptR.boneAxis);
|
|
711
|
+
const aimRot = Quaternion.unitVectorToUnitVector(aimVec, childDir);
|
|
712
|
+
Quaternion.multiply(aimRot, worldRot, worldRot);
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
results[index] = {
|
|
716
|
+
position: pos,
|
|
717
|
+
rotation: worldRot,
|
|
718
|
+
localRotation: localRot
|
|
719
|
+
};
|
|
720
|
+
} else {
|
|
721
|
+
const childBlend = ptR.child !== -1 ? Math.max(pointsR[ptR.child].forceFadeRatio, blendRatio) : blendRatio;
|
|
722
|
+
// Fixed joints are fully driven by the scene graph (animation + parent transforms).
|
|
723
|
+
// Use the engine's current world rotation directly as the baseline — do NOT
|
|
724
|
+
// reconstruct it from localRot, because localRot was read from the transform that
|
|
725
|
+
// was written back last frame (after aim correction), so it carries stale physics
|
|
726
|
+
// noise that would accumulate each frame and cause periodic spin artifacts.
|
|
727
|
+
// transformRotations[index] is read before applyResult runs, so it still holds
|
|
728
|
+
// the correct animated world rotation for this frame.
|
|
729
|
+
worldRot = transformRotations[index];
|
|
730
|
+
// Aim toward simulated child position, blended back toward the animated direction.
|
|
731
|
+
if (ptR.child !== -1) {
|
|
732
|
+
const childDir = Vector3.sub(positionsToTransform[ptR.child], ptRW.positionToTransform);
|
|
733
|
+
if (childDir.magnitudeSq > EPSILON) {
|
|
734
|
+
const aimVec = worldRot.transform(ptR.boneAxis);
|
|
735
|
+
const aimRot = Quaternion.unitVectorToUnitVector(aimVec, childDir);
|
|
736
|
+
worldRot = Quaternion.slerp(Quaternion.multiply(aimRot, worldRot), worldRot, childBlend);
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
results[index] = {
|
|
740
|
+
position: ptRW.positionCurrentTransform,
|
|
741
|
+
rotation: worldRot,
|
|
742
|
+
localRotation: localRot
|
|
743
|
+
};
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
// Post-process: twist correction pass (parent → child order).
|
|
747
|
+
if (preserveTwist) {
|
|
748
|
+
for(let index = 0; index < pointsR.length; index++){
|
|
749
|
+
const ptR = pointsR[index];
|
|
750
|
+
if (ptR.weight < EPSILON || ptR.child === -1) {
|
|
751
|
+
continue;
|
|
752
|
+
}
|
|
753
|
+
const result = results[index];
|
|
754
|
+
const parentWorldRot = ptR.parent !== -1 ? results[ptR.parent].rotation : Quaternion.identity();
|
|
755
|
+
const effectiveLocal = Quaternion.multiply(Quaternion.inverse(parentWorldRot), result.rotation);
|
|
756
|
+
const curTwist = new Quaternion();
|
|
757
|
+
effectiveLocal.decomposeSwingTwist(ptR.boneAxis, undefined, curTwist);
|
|
758
|
+
const swingTF = Quaternion.multiply(effectiveLocal, Quaternion.inverse(curTwist));
|
|
759
|
+
const correctedLocal = Quaternion.multiply(swingTF, ptR.initialLocalTwist);
|
|
760
|
+
Quaternion.multiply(parentWorldRot, correctedLocal, result.rotation);
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
return results;
|
|
764
|
+
}
|
|
765
|
+
function applyAngleLimits(pointsR, pointsRW, config) {
|
|
766
|
+
if (config.angleLimit < 0) {
|
|
767
|
+
return;
|
|
768
|
+
}
|
|
769
|
+
for(let index = 0; index < pointsR.length; index++){
|
|
770
|
+
const ptR = pointsR[index];
|
|
771
|
+
if (ptR.parent === -1) {
|
|
772
|
+
continue;
|
|
773
|
+
}
|
|
774
|
+
const ptRW = pointsRW[index];
|
|
775
|
+
const ptRp = pointsR[ptR.parent];
|
|
776
|
+
const ptRWp = pointsRW[ptR.parent];
|
|
777
|
+
const superParentPos = ptRp.parent !== -1 ? pointsRW[ptRp.parent].positionCurrent : ptRWp.positionCurrent;
|
|
778
|
+
const boneDir = Vector3.sub(ptRW.positionCurrent, ptRWp.positionCurrent);
|
|
779
|
+
let parentBoneDir = Vector3.sub(ptRWp.positionCurrent, superParentPos);
|
|
780
|
+
if (parentBoneDir.magnitude === 0 || config.limitFromRoot) {
|
|
781
|
+
parentBoneDir = Vector3.sub(ptRW.positionCurrentTransform, ptRWp.positionCurrentTransform);
|
|
782
|
+
}
|
|
783
|
+
const angle = Vector3.angleBetween(parentBoneDir, boneDir);
|
|
784
|
+
const remaining = radian2degree(angle) - config.angleLimit;
|
|
785
|
+
if (remaining > 0) {
|
|
786
|
+
const axis = Vector3.cross(parentBoneDir, boneDir);
|
|
787
|
+
const rotated = Quaternion.fromAxisAngle(axis, degree2radian(-remaining)).transform(boneDir);
|
|
788
|
+
ptRW.positionCurrent = Vector3.add(ptRWp.positionCurrent, rotated);
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
export { applyAngleLimits, applyResult, simulate };
|
|
794
|
+
//# sourceMappingURL=solver.js.map
|