@zephyr3d/scene 0.8.2 → 0.9.0
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 +157 -146
- 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 +222 -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 +125 -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
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import { Vector3, Quaternion } from '@zephyr3d/base';
|
|
2
2
|
import { IKUtils } from '../ik/ik_utils.js';
|
|
3
|
-
import { updateColliderFromNode,
|
|
3
|
+
import { updateColliderFromNode, resolveSphereCollision, resolveCapsuleCollision, resolvePlaneCollision } from './spring_collider.js';
|
|
4
4
|
|
|
5
|
+
const FIXED_SIMULATION_TIME_STEP = 1 / 60;
|
|
6
|
+
const MAX_ACCUMULATED_SIMULATION_TIME = 1 / 20;
|
|
7
|
+
const MAX_SIMULATION_STEPS_PER_UPDATE = Math.max(1, Math.ceil(MAX_ACCUMULATED_SIMULATION_TIME / FIXED_SIMULATION_TIME_STEP));
|
|
8
|
+
const DEFAULT_PARTICLE_TARGET_SMOOTHING_TIME = 1 / 30;
|
|
9
|
+
const DEFAULT_COLLIDER_SMOOTHING_TIME = 1 / 30;
|
|
5
10
|
/**
|
|
6
11
|
* Physics engine for spring-based particle simulation
|
|
7
12
|
* Uses Verlet integration and iterative constraint solving
|
|
@@ -17,6 +22,18 @@ import { updateColliderFromNode, resolvePlaneCollision, resolveCapsuleCollision,
|
|
|
17
22
|
_coriolisScale;
|
|
18
23
|
_colliders;
|
|
19
24
|
_solver;
|
|
25
|
+
_poseFollow;
|
|
26
|
+
_maxPoseOffset;
|
|
27
|
+
_poseFollowRoot;
|
|
28
|
+
_poseFollowTip;
|
|
29
|
+
_poseFollowExponent;
|
|
30
|
+
_maxPoseOffsetRoot;
|
|
31
|
+
_maxPoseOffsetTip;
|
|
32
|
+
_timeAccumulator;
|
|
33
|
+
_smoothedParticleTargets;
|
|
34
|
+
_smoothedSphereCenters;
|
|
35
|
+
_smoothedCapsuleEndpoints;
|
|
36
|
+
_smoothedPlaneData;
|
|
20
37
|
constructor(chain, options){
|
|
21
38
|
this._chain = chain;
|
|
22
39
|
this._iterations = options?.iterations ?? 5;
|
|
@@ -27,13 +44,38 @@ import { updateColliderFromNode, resolvePlaneCollision, resolveCapsuleCollision,
|
|
|
27
44
|
this._coriolisScale = options?.coriolisScale ?? 1.0;
|
|
28
45
|
this._colliders = [];
|
|
29
46
|
this._solver = options?.solver ?? 'verlet';
|
|
47
|
+
this._poseFollow = Math.max(0, Math.min(1, options?.poseFollow ?? 0.35));
|
|
48
|
+
this._maxPoseOffset = Math.max(0, options?.maxPoseOffset ?? 0);
|
|
49
|
+
this._poseFollowRoot = Math.max(0, Math.min(1, options?.poseFollowRoot ?? this._poseFollow));
|
|
50
|
+
this._poseFollowTip = Math.max(0, Math.min(1, options?.poseFollowTip ?? this._poseFollow));
|
|
51
|
+
this._poseFollowExponent = Math.max(0.1, options?.poseFollowExponent ?? 1.6);
|
|
52
|
+
this._maxPoseOffsetRoot = Math.max(0, options?.maxPoseOffsetRoot ?? this._maxPoseOffset);
|
|
53
|
+
this._maxPoseOffsetTip = Math.max(0, options?.maxPoseOffsetTip ?? this._maxPoseOffset);
|
|
54
|
+
this._timeAccumulator = 0;
|
|
55
|
+
this._smoothedParticleTargets = new WeakMap();
|
|
56
|
+
this._smoothedSphereCenters = new WeakMap();
|
|
57
|
+
this._smoothedCapsuleEndpoints = new WeakMap();
|
|
58
|
+
this._smoothedPlaneData = new WeakMap();
|
|
30
59
|
}
|
|
31
60
|
/**
|
|
32
61
|
* Updates the physics simulation
|
|
33
62
|
* @param deltaTime - Time step in seconds
|
|
34
63
|
*/ update(deltaTime) {
|
|
35
|
-
|
|
36
|
-
|
|
64
|
+
const frameDt = Math.min(Math.max(Number(deltaTime) || 0, 0), MAX_ACCUMULATED_SIMULATION_TIME);
|
|
65
|
+
if (frameDt <= 0) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
this._timeAccumulator = Math.min(this._timeAccumulator + frameDt, MAX_ACCUMULATED_SIMULATION_TIME);
|
|
69
|
+
const stepCount = Math.min(MAX_SIMULATION_STEPS_PER_UPDATE, Math.floor((this._timeAccumulator + 1e-8) / FIXED_SIMULATION_TIME_STEP));
|
|
70
|
+
if (stepCount <= 0) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
this._timeAccumulator = Math.max(0, this._timeAccumulator - stepCount * FIXED_SIMULATION_TIME_STEP);
|
|
74
|
+
for(let i = 0; i < stepCount; i++){
|
|
75
|
+
this.simulateStep(FIXED_SIMULATION_TIME_STEP, i === 0 ? frameDt : 0);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
simulateStep(dt, inputDeltaTime) {
|
|
37
79
|
// Step 1: Save all particle positions before updating
|
|
38
80
|
if (this._enableInertialForces) {
|
|
39
81
|
for (const p of this._chain.particles){
|
|
@@ -41,7 +83,7 @@ import { updateColliderFromNode, resolvePlaneCollision, resolveCapsuleCollision,
|
|
|
41
83
|
}
|
|
42
84
|
}
|
|
43
85
|
// Step 2: Update fixed particles from their scene nodes
|
|
44
|
-
this.updateFixedParticles();
|
|
86
|
+
this.updateFixedParticles(inputDeltaTime);
|
|
45
87
|
// Step 3: Calculate global rotation parameters
|
|
46
88
|
let rotationCenter = null;
|
|
47
89
|
let angularVelocity = null;
|
|
@@ -87,25 +129,34 @@ import { updateColliderFromNode, resolvePlaneCollision, resolveCapsuleCollision,
|
|
|
87
129
|
this.solveConstraint(constraint);
|
|
88
130
|
}
|
|
89
131
|
}
|
|
132
|
+
// Pull particles back toward the animated pose to preserve hair silhouette.
|
|
133
|
+
// Normalize follow strength across solver iterations so tuning stays intuitive.
|
|
134
|
+
this.solvePosePreservation(this._iterations);
|
|
90
135
|
// Apply collision constraints
|
|
91
|
-
this.solveCollisions();
|
|
136
|
+
this.solveCollisions(inputDeltaTime);
|
|
92
137
|
}
|
|
93
138
|
}
|
|
94
139
|
/**
|
|
95
140
|
* Updates fixed particles to match their scene node positions
|
|
96
|
-
*/ updateFixedParticles() {
|
|
141
|
+
*/ updateFixedParticles(deltaTime) {
|
|
142
|
+
const blend = this.getTemporalBlendFactor(deltaTime, DEFAULT_PARTICLE_TARGET_SMOOTHING_TIME);
|
|
97
143
|
for (const particle of this._chain.particles){
|
|
98
|
-
if (particle.node
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
144
|
+
if (!particle.node) {
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
const worldMatrix = particle.node.worldMatrix;
|
|
148
|
+
const worldPos = new Vector3(worldMatrix.m03, worldMatrix.m13, worldMatrix.m23);
|
|
149
|
+
const smoothedTarget = this.getSmoothedParticleTarget(particle, worldPos, blend);
|
|
150
|
+
particle.animPosition.set(smoothedTarget);
|
|
151
|
+
if (particle.fixed) {
|
|
152
|
+
particle.position.set(smoothedTarget);
|
|
153
|
+
particle.prevPosition.set(smoothedTarget);
|
|
103
154
|
// Maintain position history for rotation center estimation
|
|
104
155
|
if (this._enableInertialForces) {
|
|
105
156
|
if (!particle.positionHistory) {
|
|
106
157
|
particle.positionHistory = [];
|
|
107
158
|
}
|
|
108
|
-
particle.positionHistory.push(
|
|
159
|
+
particle.positionHistory.push(smoothedTarget.clone());
|
|
109
160
|
// Keep only last 5 frames
|
|
110
161
|
if (particle.positionHistory.length > 5) {
|
|
111
162
|
particle.positionHistory.shift();
|
|
@@ -115,6 +166,41 @@ import { updateColliderFromNode, resolvePlaneCollision, resolveCapsuleCollision,
|
|
|
115
166
|
}
|
|
116
167
|
}
|
|
117
168
|
/**
|
|
169
|
+
* Solves pose preservation (long-range attachment to animated pose).
|
|
170
|
+
* This keeps strands close to authored shape while preserving dynamic movement.
|
|
171
|
+
*/ solvePosePreservation(totalIterations) {
|
|
172
|
+
if (this._poseFollowRoot <= 0 && this._poseFollowTip <= 0) {
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
const lastIndex = Math.max(1, this._chain.particles.length - 1);
|
|
176
|
+
for(let i = 0; i < this._chain.particles.length; i++){
|
|
177
|
+
const particle = this._chain.particles[i];
|
|
178
|
+
if (particle.fixed) {
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
const t = Math.pow(i / lastIndex, this._poseFollowExponent);
|
|
182
|
+
const particlePoseFollow = this.lerp(this._poseFollowRoot, this._poseFollowTip, t);
|
|
183
|
+
// Convert user-facing per-frame follow into per-iteration follow:
|
|
184
|
+
// effective = 1 - (1 - follow)^iterations
|
|
185
|
+
const iterationFollow = totalIterations > 1 ? 1 - Math.pow(Math.max(0, 1 - particlePoseFollow), 1 / totalIterations) : particlePoseFollow;
|
|
186
|
+
const toAnim = Vector3.sub(particle.animPosition, particle.position, new Vector3());
|
|
187
|
+
const correction = Vector3.scale(toAnim, iterationFollow, new Vector3());
|
|
188
|
+
Vector3.add(particle.position, correction, particle.position);
|
|
189
|
+
const particleMaxPoseOffset = this.lerp(this._maxPoseOffsetRoot, this._maxPoseOffsetTip, t);
|
|
190
|
+
if (particleMaxPoseOffset > 0) {
|
|
191
|
+
const offset = Vector3.sub(particle.position, particle.animPosition, new Vector3());
|
|
192
|
+
const offsetLen = offset.magnitude;
|
|
193
|
+
if (offsetLen > particleMaxPoseOffset && offsetLen > 1e-6) {
|
|
194
|
+
offset.scaleBy(particleMaxPoseOffset / offsetLen);
|
|
195
|
+
Vector3.add(particle.animPosition, offset, particle.position);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
lerp(a, b, t) {
|
|
201
|
+
return a + (b - a) * t;
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
118
204
|
* Calculates global rotation parameters from fixed particle movements
|
|
119
205
|
* Uses position history to estimate rotation center
|
|
120
206
|
*/ calculateGlobalRotation(dt) {
|
|
@@ -337,33 +423,75 @@ import { updateColliderFromNode, resolvePlaneCollision, resolveCapsuleCollision,
|
|
|
337
423
|
}
|
|
338
424
|
/**
|
|
339
425
|
* Solves collisions for all particles
|
|
340
|
-
*/ solveCollisions() {
|
|
426
|
+
*/ solveCollisions(deltaTime) {
|
|
341
427
|
// Update dynamic colliders from their nodes
|
|
428
|
+
const blend = this.getTemporalBlendFactor(deltaTime, DEFAULT_COLLIDER_SMOOTHING_TIME);
|
|
429
|
+
const spheres = [];
|
|
430
|
+
const capsules = [];
|
|
431
|
+
const planes = [];
|
|
342
432
|
for (const collider of this._colliders){
|
|
343
433
|
if (collider.node) {
|
|
344
434
|
updateColliderFromNode(collider);
|
|
345
435
|
}
|
|
436
|
+
if (!collider.enabled) {
|
|
437
|
+
continue;
|
|
438
|
+
}
|
|
439
|
+
switch(collider.type){
|
|
440
|
+
case 'sphere':
|
|
441
|
+
{
|
|
442
|
+
const source = collider;
|
|
443
|
+
spheres.push({
|
|
444
|
+
particleCollider: source,
|
|
445
|
+
collider: {
|
|
446
|
+
...source,
|
|
447
|
+
center: this.getSmoothedSphereCenter(source, blend)
|
|
448
|
+
}
|
|
449
|
+
});
|
|
450
|
+
break;
|
|
451
|
+
}
|
|
452
|
+
case 'capsule':
|
|
453
|
+
{
|
|
454
|
+
const source = collider;
|
|
455
|
+
const endpoints = this.getSmoothedCapsuleEndpoints(source, blend);
|
|
456
|
+
capsules.push({
|
|
457
|
+
particleCollider: source,
|
|
458
|
+
collider: {
|
|
459
|
+
...source,
|
|
460
|
+
start: endpoints.start,
|
|
461
|
+
end: endpoints.end
|
|
462
|
+
}
|
|
463
|
+
});
|
|
464
|
+
break;
|
|
465
|
+
}
|
|
466
|
+
case 'plane':
|
|
467
|
+
{
|
|
468
|
+
const source = collider;
|
|
469
|
+
const plane = this.getSmoothedPlaneData(source, blend);
|
|
470
|
+
planes.push({
|
|
471
|
+
particleCollider: source,
|
|
472
|
+
collider: {
|
|
473
|
+
...source,
|
|
474
|
+
point: plane.point,
|
|
475
|
+
normal: plane.normal
|
|
476
|
+
}
|
|
477
|
+
});
|
|
478
|
+
break;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
346
481
|
}
|
|
347
482
|
// Check each particle against all colliders
|
|
348
483
|
for (const particle of this._chain.particles){
|
|
349
484
|
if (particle.fixed) {
|
|
350
485
|
continue; // Skip fixed particles
|
|
351
486
|
}
|
|
352
|
-
for (const collider of
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
case 'capsule':
|
|
361
|
-
resolveCapsuleCollision(particle.position, collider);
|
|
362
|
-
break;
|
|
363
|
-
case 'plane':
|
|
364
|
-
resolvePlaneCollision(particle.position, collider);
|
|
365
|
-
break;
|
|
366
|
-
}
|
|
487
|
+
for (const collider of spheres){
|
|
488
|
+
resolveSphereCollision(particle.position, collider.collider);
|
|
489
|
+
}
|
|
490
|
+
for (const collider of capsules){
|
|
491
|
+
resolveCapsuleCollision(particle.position, collider.collider);
|
|
492
|
+
}
|
|
493
|
+
for (const collider of planes){
|
|
494
|
+
resolvePlaneCollision(particle.position, collider.collider);
|
|
367
495
|
}
|
|
368
496
|
}
|
|
369
497
|
}
|
|
@@ -421,6 +549,18 @@ import { updateColliderFromNode, resolvePlaneCollision, resolveCapsuleCollision,
|
|
|
421
549
|
* Resets the simulation to initial state
|
|
422
550
|
*/ reset() {
|
|
423
551
|
this._chain.reset();
|
|
552
|
+
for (const particle of this._chain.particles){
|
|
553
|
+
particle.animPosition.set(particle.originalPosition);
|
|
554
|
+
particle.lastFramePosition.set(particle.originalPosition);
|
|
555
|
+
if (particle.positionHistory) {
|
|
556
|
+
particle.positionHistory.length = 0;
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
this._timeAccumulator = 0;
|
|
560
|
+
this._smoothedParticleTargets = new WeakMap();
|
|
561
|
+
this._smoothedSphereCenters = new WeakMap();
|
|
562
|
+
this._smoothedCapsuleEndpoints = new WeakMap();
|
|
563
|
+
this._smoothedPlaneData = new WeakMap();
|
|
424
564
|
}
|
|
425
565
|
/**
|
|
426
566
|
* Gets the spring chain
|
|
@@ -496,9 +636,88 @@ import { updateColliderFromNode, resolvePlaneCollision, resolveCapsuleCollision,
|
|
|
496
636
|
}
|
|
497
637
|
}
|
|
498
638
|
/**
|
|
639
|
+
* Gets pose preservation strength [0-1]
|
|
640
|
+
*/ get poseFollow() {
|
|
641
|
+
return this._poseFollow;
|
|
642
|
+
}
|
|
643
|
+
/**
|
|
644
|
+
* Sets pose preservation strength [0-1]
|
|
645
|
+
*/ set poseFollow(value) {
|
|
646
|
+
const v = Math.max(0, Math.min(1, value));
|
|
647
|
+
this._poseFollow = v;
|
|
648
|
+
this._poseFollowRoot = v;
|
|
649
|
+
this._poseFollowTip = v;
|
|
650
|
+
}
|
|
651
|
+
/**
|
|
652
|
+
* Gets max allowed deviation from animated pose
|
|
653
|
+
*/ get maxPoseOffset() {
|
|
654
|
+
return this._maxPoseOffset;
|
|
655
|
+
}
|
|
656
|
+
/**
|
|
657
|
+
* Sets max allowed deviation from animated pose. 0 disables clamping.
|
|
658
|
+
*/ set maxPoseOffset(value) {
|
|
659
|
+
const v = Math.max(0, value);
|
|
660
|
+
this._maxPoseOffset = v;
|
|
661
|
+
this._maxPoseOffsetRoot = v;
|
|
662
|
+
this._maxPoseOffsetTip = v;
|
|
663
|
+
}
|
|
664
|
+
/**
|
|
665
|
+
* Gets root pose follow strength [0-1]
|
|
666
|
+
*/ get poseFollowRoot() {
|
|
667
|
+
return this._poseFollowRoot;
|
|
668
|
+
}
|
|
669
|
+
/**
|
|
670
|
+
* Sets root pose follow strength [0-1]
|
|
671
|
+
*/ set poseFollowRoot(value) {
|
|
672
|
+
this._poseFollowRoot = Math.max(0, Math.min(1, value));
|
|
673
|
+
}
|
|
674
|
+
/**
|
|
675
|
+
* Gets tip pose follow strength [0-1]
|
|
676
|
+
*/ get poseFollowTip() {
|
|
677
|
+
return this._poseFollowTip;
|
|
678
|
+
}
|
|
679
|
+
/**
|
|
680
|
+
* Sets tip pose follow strength [0-1]
|
|
681
|
+
*/ set poseFollowTip(value) {
|
|
682
|
+
this._poseFollowTip = Math.max(0, Math.min(1, value));
|
|
683
|
+
}
|
|
684
|
+
/**
|
|
685
|
+
* Gets exponent for root->tip interpolation
|
|
686
|
+
*/ get poseFollowExponent() {
|
|
687
|
+
return this._poseFollowExponent;
|
|
688
|
+
}
|
|
689
|
+
/**
|
|
690
|
+
* Sets exponent for root->tip interpolation
|
|
691
|
+
*/ set poseFollowExponent(value) {
|
|
692
|
+
this._poseFollowExponent = Math.max(0.1, value);
|
|
693
|
+
}
|
|
694
|
+
/**
|
|
695
|
+
* Gets root max allowed deviation from animated pose
|
|
696
|
+
*/ get maxPoseOffsetRoot() {
|
|
697
|
+
return this._maxPoseOffsetRoot;
|
|
698
|
+
}
|
|
699
|
+
/**
|
|
700
|
+
* Sets root max allowed deviation from animated pose
|
|
701
|
+
*/ set maxPoseOffsetRoot(value) {
|
|
702
|
+
this._maxPoseOffsetRoot = Math.max(0, value);
|
|
703
|
+
}
|
|
704
|
+
/**
|
|
705
|
+
* Gets tip max allowed deviation from animated pose
|
|
706
|
+
*/ get maxPoseOffsetTip() {
|
|
707
|
+
return this._maxPoseOffsetTip;
|
|
708
|
+
}
|
|
709
|
+
/**
|
|
710
|
+
* Sets tip max allowed deviation from animated pose
|
|
711
|
+
*/ set maxPoseOffsetTip(value) {
|
|
712
|
+
this._maxPoseOffsetTip = Math.max(0, value);
|
|
713
|
+
}
|
|
714
|
+
/**
|
|
499
715
|
* Adds a collider to the system
|
|
500
716
|
*/ addCollider(collider) {
|
|
501
717
|
this._colliders.push(collider);
|
|
718
|
+
this._smoothedSphereCenters = new WeakMap();
|
|
719
|
+
this._smoothedCapsuleEndpoints = new WeakMap();
|
|
720
|
+
this._smoothedPlaneData = new WeakMap();
|
|
502
721
|
}
|
|
503
722
|
/**
|
|
504
723
|
* Removes a collider from the system
|
|
@@ -506,6 +725,9 @@ import { updateColliderFromNode, resolvePlaneCollision, resolveCapsuleCollision,
|
|
|
506
725
|
const index = this._colliders.indexOf(collider);
|
|
507
726
|
if (index >= 0) {
|
|
508
727
|
this._colliders.splice(index, 1);
|
|
728
|
+
this._smoothedSphereCenters = new WeakMap();
|
|
729
|
+
this._smoothedCapsuleEndpoints = new WeakMap();
|
|
730
|
+
this._smoothedPlaneData = new WeakMap();
|
|
509
731
|
return true;
|
|
510
732
|
}
|
|
511
733
|
return false;
|
|
@@ -514,12 +736,87 @@ import { updateColliderFromNode, resolvePlaneCollision, resolveCapsuleCollision,
|
|
|
514
736
|
* Clears all colliders
|
|
515
737
|
*/ clearColliders() {
|
|
516
738
|
this._colliders = [];
|
|
739
|
+
this._smoothedSphereCenters = new WeakMap();
|
|
740
|
+
this._smoothedCapsuleEndpoints = new WeakMap();
|
|
741
|
+
this._smoothedPlaneData = new WeakMap();
|
|
517
742
|
}
|
|
518
743
|
/**
|
|
519
744
|
* Gets all colliders
|
|
520
745
|
*/ get colliders() {
|
|
521
746
|
return this._colliders;
|
|
522
747
|
}
|
|
748
|
+
getTemporalBlendFactor(deltaTime, smoothingTime) {
|
|
749
|
+
const dt = Math.min(Math.max(Number(deltaTime) || 0, 0), MAX_ACCUMULATED_SIMULATION_TIME);
|
|
750
|
+
if (smoothingTime <= 0) {
|
|
751
|
+
return 1;
|
|
752
|
+
}
|
|
753
|
+
if (dt <= 0) {
|
|
754
|
+
return 0;
|
|
755
|
+
}
|
|
756
|
+
return 1 - Math.exp(-dt / smoothingTime);
|
|
757
|
+
}
|
|
758
|
+
getSmoothedParticleTarget(target, current, blend) {
|
|
759
|
+
const cached = this._smoothedParticleTargets.get(target);
|
|
760
|
+
if (!cached || blend >= 1) {
|
|
761
|
+
const next = current.clone();
|
|
762
|
+
this._smoothedParticleTargets.set(target, next);
|
|
763
|
+
return next;
|
|
764
|
+
}
|
|
765
|
+
cached.setXYZ(cached.x + (current.x - cached.x) * blend, cached.y + (current.y - cached.y) * blend, cached.z + (current.z - cached.z) * blend);
|
|
766
|
+
return cached;
|
|
767
|
+
}
|
|
768
|
+
getSmoothedSphereCenter(collider, blend) {
|
|
769
|
+
const current = collider.center?.clone() ?? new Vector3();
|
|
770
|
+
const cached = this._smoothedSphereCenters.get(collider);
|
|
771
|
+
if (!cached || blend >= 1) {
|
|
772
|
+
this._smoothedSphereCenters.set(collider, current);
|
|
773
|
+
return current;
|
|
774
|
+
}
|
|
775
|
+
cached.setXYZ(cached.x + (current.x - cached.x) * blend, cached.y + (current.y - cached.y) * blend, cached.z + (current.z - cached.z) * blend);
|
|
776
|
+
return cached;
|
|
777
|
+
}
|
|
778
|
+
getSmoothedCapsuleEndpoints(collider, blend) {
|
|
779
|
+
const currentStart = collider.start?.clone() ?? new Vector3();
|
|
780
|
+
const currentEnd = collider.end?.clone() ?? new Vector3();
|
|
781
|
+
const cached = this._smoothedCapsuleEndpoints.get(collider);
|
|
782
|
+
if (!cached || blend >= 1) {
|
|
783
|
+
const next = {
|
|
784
|
+
start: currentStart,
|
|
785
|
+
end: currentEnd
|
|
786
|
+
};
|
|
787
|
+
this._smoothedCapsuleEndpoints.set(collider, next);
|
|
788
|
+
return next;
|
|
789
|
+
}
|
|
790
|
+
cached.start.setXYZ(cached.start.x + (currentStart.x - cached.start.x) * blend, cached.start.y + (currentStart.y - cached.start.y) * blend, cached.start.z + (currentStart.z - cached.start.z) * blend);
|
|
791
|
+
cached.end.setXYZ(cached.end.x + (currentEnd.x - cached.end.x) * blend, cached.end.y + (currentEnd.y - cached.end.y) * blend, cached.end.z + (currentEnd.z - cached.end.z) * blend);
|
|
792
|
+
return cached;
|
|
793
|
+
}
|
|
794
|
+
getSmoothedPlaneData(collider, blend) {
|
|
795
|
+
const currentPoint = collider.point?.clone() ?? new Vector3();
|
|
796
|
+
const currentNormal = collider.normal?.clone() ?? Vector3.axisPY();
|
|
797
|
+
if (currentNormal.magnitudeSq > 1e-8) {
|
|
798
|
+
currentNormal.inplaceNormalize();
|
|
799
|
+
} else {
|
|
800
|
+
currentNormal.setXYZ(0, 1, 0);
|
|
801
|
+
}
|
|
802
|
+
const cached = this._smoothedPlaneData.get(collider);
|
|
803
|
+
if (!cached || blend >= 1) {
|
|
804
|
+
const next = {
|
|
805
|
+
point: currentPoint,
|
|
806
|
+
normal: currentNormal
|
|
807
|
+
};
|
|
808
|
+
this._smoothedPlaneData.set(collider, next);
|
|
809
|
+
return next;
|
|
810
|
+
}
|
|
811
|
+
cached.point.setXYZ(cached.point.x + (currentPoint.x - cached.point.x) * blend, cached.point.y + (currentPoint.y - cached.point.y) * blend, cached.point.z + (currentPoint.z - cached.point.z) * blend);
|
|
812
|
+
cached.normal.setXYZ(cached.normal.x + (currentNormal.x - cached.normal.x) * blend, cached.normal.y + (currentNormal.y - cached.normal.y) * blend, cached.normal.z + (currentNormal.z - cached.normal.z) * blend);
|
|
813
|
+
if (cached.normal.magnitudeSq > 1e-8) {
|
|
814
|
+
cached.normal.inplaceNormalize();
|
|
815
|
+
} else {
|
|
816
|
+
cached.normal.setXYZ(0, 1, 0);
|
|
817
|
+
}
|
|
818
|
+
return cached;
|
|
819
|
+
}
|
|
523
820
|
}
|
|
524
821
|
|
|
525
822
|
export { SpringSystem };
|