@zephyr3d/scene 0.9.25 → 0.9.27
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 +92 -30
- package/dist/animation/animationset.js.map +1 -1
- package/dist/animation/hair/gpu_hair_simulation.js +1090 -0
- package/dist/animation/hair/gpu_hair_simulation.js.map +1 -0
- package/dist/animation/joint_dynamics/controller.js +7 -5
- package/dist/animation/joint_dynamics/controller.js.map +1 -1
- package/dist/animation/joint_dynamics/convex_collider.js +320 -0
- package/dist/animation/joint_dynamics/convex_collider.js.map +1 -0
- package/dist/animation/joint_dynamics/joint_dynamics_system.js +81 -5
- package/dist/animation/joint_dynamics/joint_dynamics_system.js.map +1 -1
- package/dist/animation/morphtrack.js +3 -4
- package/dist/animation/morphtrack.js.map +1 -1
- package/dist/animation/skeleton.js +79 -25
- package/dist/animation/skeleton.js.map +1 -1
- package/dist/animation/spring/multi_chain_spring_system.js +577 -179
- package/dist/animation/spring/multi_chain_spring_system.js.map +1 -1
- package/dist/animation/spring/spring_collider.js +4 -3
- package/dist/animation/spring/spring_collider.js.map +1 -1
- package/dist/animation/spring/spring_node_pose_tracker.js +40 -0
- package/dist/animation/spring/spring_node_pose_tracker.js.map +1 -0
- package/dist/animation/spring/spring_particle.js +2 -0
- package/dist/animation/spring/spring_particle.js.map +1 -1
- package/dist/animation/spring/spring_system.js +11 -3
- package/dist/animation/spring/spring_system.js.map +1 -1
- package/dist/animation/spring_modifier.js +10 -0
- package/dist/animation/spring_modifier.js.map +1 -1
- package/dist/app/engine.js +53 -43
- package/dist/app/engine.js.map +1 -1
- package/dist/app/scriptingsystem.js +119 -6
- package/dist/app/scriptingsystem.js.map +1 -1
- package/dist/app/scriptregistry.js +3 -2
- package/dist/app/scriptregistry.js.map +1 -1
- package/dist/asset/assetmanager.js +284 -67
- package/dist/asset/assetmanager.js.map +1 -1
- package/dist/asset/loaders/zhair/zhair_format.js +288 -0
- package/dist/asset/loaders/zhair/zhair_format.js.map +1 -0
- package/dist/asset/loaders/zhair/zhair_loader.js +221 -0
- package/dist/asset/loaders/zhair/zhair_loader.js.map +1 -0
- package/dist/asset/model.js +184 -69
- package/dist/asset/model.js.map +1 -1
- package/dist/camera/camera.js +268 -44
- package/dist/camera/camera.js.map +1 -1
- package/dist/camera/perspectivecamera.js +3 -1
- package/dist/camera/perspectivecamera.js.map +1 -1
- package/dist/index.d.ts +9044 -5139
- package/dist/index.js +17 -6
- package/dist/index.js.map +1 -1
- package/dist/material/eye.js +932 -0
- package/dist/material/eye.js.map +1 -0
- package/dist/material/hair.js +917 -0
- package/dist/material/hair.js.map +1 -0
- package/dist/material/hairstrand.js +842 -0
- package/dist/material/hairstrand.js.map +1 -0
- package/dist/material/hairstrand_data.js +162 -0
- package/dist/material/hairstrand_data.js.map +1 -0
- package/dist/material/lambert.js +5 -2
- package/dist/material/lambert.js.map +1 -1
- package/dist/material/material.js +48 -10
- package/dist/material/material.js.map +1 -1
- package/dist/material/meshmaterial.js +120 -19
- package/dist/material/meshmaterial.js.map +1 -1
- package/dist/material/mixins/albedocolor.js +1 -1
- package/dist/material/mixins/albedocolor.js.map +1 -1
- package/dist/material/mixins/lightmodel/blinnphong.js +5 -3
- package/dist/material/mixins/lightmodel/blinnphong.js.map +1 -1
- package/dist/material/mixins/lightmodel/lambert.js +5 -4
- package/dist/material/mixins/lightmodel/lambert.js.map +1 -1
- package/dist/material/mixins/lightmodel/pbrblueprintmixin.js +3 -3
- package/dist/material/mixins/lightmodel/pbrblueprintmixin.js.map +1 -1
- package/dist/material/mixins/lightmodel/pbrmetallicroughness.js +5 -5
- package/dist/material/mixins/lightmodel/pbrmetallicroughness.js.map +1 -1
- package/dist/material/mixins/lightmodel/pbrspecularglossness.js +3 -3
- package/dist/material/mixins/lightmodel/pbrspecularglossness.js.map +1 -1
- package/dist/material/mixins/lit.js +106 -19
- package/dist/material/mixins/lit.js.map +1 -1
- package/dist/material/mixins/pbr/common.js +4 -4
- package/dist/material/mixins/pbr/common.js.map +1 -1
- package/dist/material/mixins/texture.js +48 -11
- package/dist/material/mixins/texture.js.map +1 -1
- package/dist/material/mtoon.js +6 -5
- package/dist/material/mtoon.js.map +1 -1
- package/dist/material/pbrblueprint.js +146 -28
- package/dist/material/pbrblueprint.js.map +1 -1
- package/dist/material/pbrblueprint_instance.js +501 -0
- package/dist/material/pbrblueprint_instance.js.map +1 -0
- package/dist/material/pbrmr.js +15 -16
- package/dist/material/pbrmr.js.map +1 -1
- package/dist/material/shader/helper.js +734 -23
- package/dist/material/shader/helper.js.map +1 -1
- package/dist/material/shader/terrain_blend.js +55 -0
- package/dist/material/shader/terrain_blend.js.map +1 -0
- package/dist/material/skin.js +192 -32
- package/dist/material/skin.js.map +1 -1
- package/dist/material/spriteblueprint.js +16 -1
- package/dist/material/spriteblueprint.js.map +1 -1
- package/dist/material/subsurfaceprofile.js +86 -23
- package/dist/material/subsurfaceprofile.js.map +1 -1
- package/dist/material/water.js +2059 -72
- package/dist/material/water.js.map +1 -1
- package/dist/posteffect/bloom.js +167 -7
- package/dist/posteffect/bloom.js.map +1 -1
- package/dist/posteffect/coloradjust.js +2 -1
- package/dist/posteffect/coloradjust.js.map +1 -1
- package/dist/posteffect/compositor.js.map +1 -1
- package/dist/posteffect/fxaa.js +2 -1
- package/dist/posteffect/fxaa.js.map +1 -1
- package/dist/posteffect/grayscale.js +3 -1
- package/dist/posteffect/grayscale.js.map +1 -1
- package/dist/posteffect/motionblur.js +664 -56
- package/dist/posteffect/motionblur.js.map +1 -1
- package/dist/posteffect/posteffect.js +12 -1
- package/dist/posteffect/posteffect.js.map +1 -1
- package/dist/posteffect/sao.js +46 -44
- package/dist/posteffect/sao.js.map +1 -1
- package/dist/posteffect/skinsss.js +368 -69
- package/dist/posteffect/skinsss.js.map +1 -1
- package/dist/posteffect/ssgi.js +16 -9
- package/dist/posteffect/ssgi.js.map +1 -1
- package/dist/posteffect/ssr.js +3 -0
- package/dist/posteffect/ssr.js.map +1 -1
- package/dist/posteffect/sss.js +121 -36
- package/dist/posteffect/sss.js.map +1 -1
- package/dist/posteffect/taa.js.map +1 -1
- package/dist/posteffect/tonemap.js +44 -1
- package/dist/posteffect/tonemap.js.map +1 -1
- package/dist/render/abuffer_oit.js +8 -2
- package/dist/render/abuffer_oit.js.map +1 -1
- package/dist/render/clipmap.js +156 -5
- package/dist/render/clipmap.js.map +1 -1
- package/dist/render/cull_visitor.js +20 -0
- package/dist/render/cull_visitor.js.map +1 -1
- package/dist/render/depthpass.js +36 -2
- package/dist/render/depthpass.js.map +1 -1
- package/dist/render/drawable_mixin.js +26 -1
- package/dist/render/drawable_mixin.js.map +1 -1
- package/dist/render/dualdepthpeeling_oit.js +3 -2
- package/dist/render/dualdepthpeeling_oit.js.map +1 -1
- package/dist/render/envlight.js +1 -1
- package/dist/render/envlight.js.map +1 -1
- package/dist/render/fbm_wavegenerator.js +6 -3
- package/dist/render/fbm_wavegenerator.js.map +1 -1
- package/dist/render/fft_wavegenerator.js +57 -15
- package/dist/render/fft_wavegenerator.js.map +1 -1
- package/dist/render/gerstner_wavegenerator.js +130 -20
- package/dist/render/gerstner_wavegenerator.js.map +1 -1
- package/dist/render/globalbindgroup_allocator.js +3 -0
- package/dist/render/globalbindgroup_allocator.js.map +1 -1
- package/dist/render/hzb.js +80 -22
- package/dist/render/hzb.js.map +1 -1
- package/dist/render/lightpass.js +6 -3
- package/dist/render/lightpass.js.map +1 -1
- package/dist/render/oit.js +29 -0
- package/dist/render/oit.js.map +1 -0
- package/dist/render/render_queue.js +39 -0
- package/dist/render/render_queue.js.map +1 -1
- package/dist/render/renderer.js +4 -0
- package/dist/render/renderer.js.map +1 -1
- package/dist/render/rendergraph/blackboard.js +1 -0
- package/dist/render/rendergraph/blackboard.js.map +1 -1
- package/dist/render/rendergraph/device_pool_allocator.js.map +1 -1
- package/dist/render/rendergraph/executor.js +2 -0
- package/dist/render/rendergraph/executor.js.map +1 -1
- package/dist/render/rendergraph/forward_plus_builder.js +379 -10
- package/dist/render/rendergraph/forward_plus_builder.js.map +1 -1
- package/dist/render/rendergraph/frame_resource_requirements.js +1 -0
- package/dist/render/rendergraph/frame_resource_requirements.js.map +1 -1
- package/dist/render/rendergraph/history_resources.js +8 -1
- package/dist/render/rendergraph/history_resources.js.map +1 -1
- package/dist/render/rendergraph/rendergraph.js.map +1 -1
- package/dist/render/rendergraph/texture_affinity_cache.js +1 -1
- package/dist/render/rendergraph/texture_affinity_cache.js.map +1 -1
- package/dist/render/rendergraph/types.js.map +1 -1
- package/dist/render/shadow_mask_pass.js +73 -8
- package/dist/render/shadow_mask_pass.js.map +1 -1
- package/dist/render/sky.js +166 -126
- package/dist/render/sky.js.map +1 -1
- package/dist/render/underwater.js +624 -0
- package/dist/render/underwater.js.map +1 -0
- package/dist/render/water_caustics.js +1380 -0
- package/dist/render/water_caustics.js.map +1 -0
- package/dist/render/weightedblended_oit.js +3 -2
- package/dist/render/weightedblended_oit.js.map +1 -1
- package/dist/scene/basesprite.js +8 -0
- package/dist/scene/basesprite.js.map +1 -1
- package/dist/scene/batchgroup.js +3 -0
- package/dist/scene/batchgroup.js.map +1 -1
- package/dist/scene/environment.js +4 -1
- package/dist/scene/environment.js.map +1 -1
- package/dist/scene/hair.js +963 -0
- package/dist/scene/hair.js.map +1 -0
- package/dist/scene/light.js +5 -1
- package/dist/scene/light.js.map +1 -1
- package/dist/scene/mesh.js +481 -47
- package/dist/scene/mesh.js.map +1 -1
- package/dist/scene/meshdrawable.js +6 -0
- package/dist/scene/meshdrawable.js.map +1 -1
- package/dist/scene/msdftext.js +3 -0
- package/dist/scene/msdftext.js.map +1 -1
- package/dist/scene/msdftextsprite.js +3 -0
- package/dist/scene/msdftextsprite.js.map +1 -1
- package/dist/scene/particlesys.js +6 -0
- package/dist/scene/particlesys.js.map +1 -1
- package/dist/scene/scene.js +3 -0
- package/dist/scene/scene.js.map +1 -1
- package/dist/scene/scene_node.js +6 -1
- package/dist/scene/scene_node.js.map +1 -1
- package/dist/scene/terrain-cm/grass.js +3 -0
- package/dist/scene/terrain-cm/grass.js.map +1 -1
- package/dist/scene/terrain-cm/grassmaterial.js +3 -0
- package/dist/scene/terrain-cm/grassmaterial.js.map +1 -1
- package/dist/scene/terrain-cm/terrain-cm.js +6 -0
- package/dist/scene/terrain-cm/terrain-cm.js.map +1 -1
- package/dist/scene/terrain-cm/texture_cache.js +309 -0
- package/dist/scene/terrain-cm/texture_cache.js.map +1 -0
- package/dist/scene/water.js +546 -4
- package/dist/scene/water.js.map +1 -1
- package/dist/shaders/atmosphere.js.map +1 -1
- package/dist/shaders/diffusion.js +54 -0
- package/dist/shaders/diffusion.js.map +1 -0
- package/dist/shaders/shadow.js +33 -21
- package/dist/shaders/shadow.js.map +1 -1
- package/dist/shaders/temporal.js +71 -33
- package/dist/shaders/temporal.js.map +1 -1
- package/dist/shaders/water.js +22 -19
- package/dist/shaders/water.js.map +1 -1
- package/dist/shaders/water_medium.js +56 -0
- package/dist/shaders/water_medium.js.map +1 -0
- package/dist/shadow/dom.js +546 -0
- package/dist/shadow/dom.js.map +1 -0
- package/dist/shadow/pcf_opt.js +3 -5
- package/dist/shadow/pcf_opt.js.map +1 -1
- package/dist/shadow/pcf_pd.js +1 -1
- package/dist/shadow/pcf_pd.js.map +1 -1
- package/dist/shadow/receiver_bias.js +41 -0
- package/dist/shadow/receiver_bias.js.map +1 -0
- package/dist/shadow/shader.js +20 -60
- package/dist/shadow/shader.js.map +1 -1
- package/dist/shadow/shadow_impl.js +89 -0
- package/dist/shadow/shadow_impl.js.map +1 -1
- package/dist/shadow/shadow_region.js +1 -1
- package/dist/shadow/shadow_region.js.map +1 -1
- package/dist/shadow/shadowmapper.js +150 -37
- package/dist/shadow/shadowmapper.js.map +1 -1
- package/dist/shapes/shape.js +11 -2
- package/dist/shapes/shape.js.map +1 -1
- package/dist/shapes/sphere.js +50 -9
- package/dist/shapes/sphere.js.map +1 -1
- package/dist/utility/blueprint/common/constants.js +112 -2
- package/dist/utility/blueprint/common/constants.js.map +1 -1
- package/dist/utility/blueprint/material/func.js +142 -49
- package/dist/utility/blueprint/material/func.js.map +1 -1
- package/dist/utility/blueprint/material/ir.js +490 -25
- package/dist/utility/blueprint/material/ir.js.map +1 -1
- package/dist/utility/blueprint/material/texture.js +898 -4
- package/dist/utility/blueprint/material/texture.js.map +1 -1
- package/dist/utility/serialization/manager.js +434 -101
- package/dist/utility/serialization/manager.js.map +1 -1
- package/dist/utility/serialization/scene/animation.js +401 -5
- package/dist/utility/serialization/scene/animation.js.map +1 -1
- package/dist/utility/serialization/scene/batch.js +4 -0
- package/dist/utility/serialization/scene/batch.js.map +1 -1
- package/dist/utility/serialization/scene/camera.js +186 -26
- package/dist/utility/serialization/scene/camera.js.map +1 -1
- package/dist/utility/serialization/scene/common.js +26 -5
- package/dist/utility/serialization/scene/common.js.map +1 -1
- package/dist/utility/serialization/scene/hair.js +1141 -0
- package/dist/utility/serialization/scene/hair.js.map +1 -0
- package/dist/utility/serialization/scene/light.js +84 -4
- package/dist/utility/serialization/scene/light.js.map +1 -1
- package/dist/utility/serialization/scene/material.js +1867 -166
- package/dist/utility/serialization/scene/material.js.map +1 -1
- package/dist/utility/serialization/scene/mesh.js +231 -14
- package/dist/utility/serialization/scene/mesh.js.map +1 -1
- package/dist/utility/serialization/scene/node.js +141 -7
- package/dist/utility/serialization/scene/node.js.map +1 -1
- package/dist/utility/serialization/scene/particle.js +4 -0
- package/dist/utility/serialization/scene/particle.js.map +1 -1
- package/dist/utility/serialization/scene/primitive.js +18 -0
- package/dist/utility/serialization/scene/primitive.js.map +1 -1
- package/dist/utility/serialization/scene/sprite.js +4 -0
- package/dist/utility/serialization/scene/sprite.js.map +1 -1
- package/dist/utility/serialization/scene/terrain.js +4 -0
- package/dist/utility/serialization/scene/terrain.js.map +1 -1
- package/dist/utility/serialization/scene/text.js +4 -0
- package/dist/utility/serialization/scene/text.js.map +1 -1
- package/dist/utility/serialization/scene/water.js +1105 -21
- package/dist/utility/serialization/scene/water.js.map +1 -1
- package/dist/utility/serialization/types.js.map +1 -1
- package/dist/utility/shprojector.js +9 -8
- package/dist/utility/shprojector.js.map +1 -1
- package/dist/values.js +49 -2
- package/dist/values.js.map +1 -1
- package/package.json +3 -3
- package/dist/render/rendergraph/frame_graph_context.js +0 -41
- package/dist/render/rendergraph/frame_graph_context.js.map +0 -1
- package/dist/shaders/ssgi.js +0 -63
- package/dist/shaders/ssgi.js.map +0 -1
- package/dist/shaders/ssgi_reprojection.js +0 -118
- package/dist/shaders/ssgi_reprojection.js.map +0 -1
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { Vector3, Quaternion } from '@zephyr3d/base';
|
|
2
2
|
import { IKUtils } from '../ik/ik_utils.js';
|
|
3
|
+
import { updateColliderFromNode, resolveSphereCollision, resolveCapsuleCollision, resolvePlaneCollision } from './spring_collider.js';
|
|
4
|
+
import { SpringNodePoseTracker } from './spring_node_pose_tracker.js';
|
|
3
5
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
* @public
|
|
11
|
-
*/ class MultiChainSpringSystem {
|
|
6
|
+
const FIXED_SIMULATION_TIME_STEP = 1 / 60;
|
|
7
|
+
const MAX_ACCUMULATED_SIMULATION_TIME = 1 / 20;
|
|
8
|
+
const MAX_SIMULATION_STEPS_PER_UPDATE = Math.max(1, Math.ceil(MAX_ACCUMULATED_SIMULATION_TIME / FIXED_SIMULATION_TIME_STEP));
|
|
9
|
+
const DEFAULT_PARTICLE_TARGET_SMOOTHING_TIME = 1 / 30;
|
|
10
|
+
const DEFAULT_COLLIDER_SMOOTHING_TIME = 1 / 30;
|
|
11
|
+
class MultiChainSpringSystem {
|
|
12
12
|
_chains;
|
|
13
13
|
_interChainConstraints;
|
|
14
14
|
_iterations;
|
|
@@ -18,6 +18,23 @@ import { IKUtils } from '../ik/ik_utils.js';
|
|
|
18
18
|
_centrifugalScale;
|
|
19
19
|
_coriolisScale;
|
|
20
20
|
_solver;
|
|
21
|
+
_poseFollow;
|
|
22
|
+
_maxPoseOffset;
|
|
23
|
+
_poseFollowRoot;
|
|
24
|
+
_poseFollowTip;
|
|
25
|
+
_poseFollowExponent;
|
|
26
|
+
_maxPoseOffsetRoot;
|
|
27
|
+
_maxPoseOffsetTip;
|
|
28
|
+
_colliders;
|
|
29
|
+
_timeAccumulator;
|
|
30
|
+
_smoothedParticleTargets;
|
|
31
|
+
_smoothedSphereCenters;
|
|
32
|
+
_smoothedCapsuleEndpoints;
|
|
33
|
+
_smoothedPlaneData;
|
|
34
|
+
_runtimeNodeMap;
|
|
35
|
+
_runtimeAnchorOffsets;
|
|
36
|
+
_runtimeRestLengths;
|
|
37
|
+
_nodePoseTracker;
|
|
21
38
|
constructor(options){
|
|
22
39
|
this._chains = [];
|
|
23
40
|
this._interChainConstraints = [];
|
|
@@ -28,26 +45,32 @@ import { IKUtils } from '../ik/ik_utils.js';
|
|
|
28
45
|
this._centrifugalScale = options?.centrifugalScale ?? 1.0;
|
|
29
46
|
this._coriolisScale = options?.coriolisScale ?? 1.0;
|
|
30
47
|
this._solver = options?.solver ?? 'verlet';
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
48
|
+
this._poseFollow = Math.max(0, Math.min(1, options?.poseFollow ?? 0.35));
|
|
49
|
+
this._maxPoseOffset = Math.max(0, options?.maxPoseOffset ?? 0);
|
|
50
|
+
this._poseFollowRoot = Math.max(0, Math.min(1, options?.poseFollowRoot ?? this._poseFollow));
|
|
51
|
+
this._poseFollowTip = Math.max(0, Math.min(1, options?.poseFollowTip ?? this._poseFollow));
|
|
52
|
+
this._poseFollowExponent = Math.max(0.1, options?.poseFollowExponent ?? 1.6);
|
|
53
|
+
this._maxPoseOffsetRoot = Math.max(0, options?.maxPoseOffsetRoot ?? this._maxPoseOffset);
|
|
54
|
+
this._maxPoseOffsetTip = Math.max(0, options?.maxPoseOffsetTip ?? this._maxPoseOffset);
|
|
55
|
+
this._colliders = [];
|
|
56
|
+
this._timeAccumulator = 0;
|
|
57
|
+
this._smoothedParticleTargets = new WeakMap();
|
|
58
|
+
this._smoothedSphereCenters = new WeakMap();
|
|
59
|
+
this._smoothedCapsuleEndpoints = new WeakMap();
|
|
60
|
+
this._smoothedPlaneData = new WeakMap();
|
|
61
|
+
this._runtimeNodeMap = null;
|
|
62
|
+
this._runtimeAnchorOffsets = new WeakMap();
|
|
63
|
+
this._runtimeRestLengths = new WeakMap();
|
|
64
|
+
this._nodePoseTracker = new SpringNodePoseTracker();
|
|
65
|
+
}
|
|
66
|
+
addChain(chain) {
|
|
37
67
|
this._chains.push(chain);
|
|
38
68
|
return this._chains.length - 1;
|
|
39
69
|
}
|
|
40
|
-
|
|
41
|
-
* Adds an inter-chain constraint
|
|
42
|
-
* @param constraint - The constraint to add
|
|
43
|
-
*/ addInterChainConstraint(constraint) {
|
|
70
|
+
addInterChainConstraint(constraint) {
|
|
44
71
|
this._interChainConstraints.push(constraint);
|
|
45
72
|
}
|
|
46
|
-
|
|
47
|
-
* Creates radial constraints between adjacent chains
|
|
48
|
-
* Useful for skirts, capes, and other radial multi-chain structures
|
|
49
|
-
* @param options - Configuration options
|
|
50
|
-
*/ createRadialConstraints(options) {
|
|
73
|
+
createRadialConstraints(options) {
|
|
51
74
|
const skipRows = options.skipRows ?? 0;
|
|
52
75
|
const connectDistance = options.connectDistance ?? 1;
|
|
53
76
|
const compliance = options.compliance ?? 0;
|
|
@@ -77,21 +100,272 @@ import { IKUtils } from '../ik/ik_utils.js';
|
|
|
77
100
|
}
|
|
78
101
|
}
|
|
79
102
|
}
|
|
103
|
+
update(deltaTime) {
|
|
104
|
+
this._nodePoseTracker.restoreInputPose();
|
|
105
|
+
const frameDt = Math.min(Math.max(Number(deltaTime) || 0, 0), MAX_ACCUMULATED_SIMULATION_TIME);
|
|
106
|
+
if (frameDt <= 0) {
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
this._timeAccumulator = Math.min(this._timeAccumulator + frameDt, MAX_ACCUMULATED_SIMULATION_TIME);
|
|
110
|
+
const stepCount = Math.min(MAX_SIMULATION_STEPS_PER_UPDATE, Math.floor((this._timeAccumulator + 1e-8) / FIXED_SIMULATION_TIME_STEP));
|
|
111
|
+
if (stepCount <= 0) {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
this._timeAccumulator = Math.max(0, this._timeAccumulator - stepCount * FIXED_SIMULATION_TIME_STEP);
|
|
115
|
+
for(let i = 0; i < stepCount; i++){
|
|
116
|
+
this.simulateStep(FIXED_SIMULATION_TIME_STEP, i === 0 ? frameDt : 0);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
applyToNodes(weight = 1.0) {
|
|
120
|
+
const baseNodeStates = new Map();
|
|
121
|
+
const desiredWorldRotations = new Map();
|
|
122
|
+
for (const chain of this._chains){
|
|
123
|
+
this.collectChainNodeRotations(chain, weight, baseNodeStates, desiredWorldRotations);
|
|
124
|
+
}
|
|
125
|
+
const nodes = Array.from(desiredWorldRotations.keys());
|
|
126
|
+
nodes.sort((a, b)=>this.getNodeDepth(a) - this.getNodeDepth(b));
|
|
127
|
+
for (const node of nodes){
|
|
128
|
+
const worldRotation = desiredWorldRotations.get(node);
|
|
129
|
+
if (!worldRotation) {
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
const parent = node.parent;
|
|
133
|
+
const inputRotation = node.rotation.clone();
|
|
134
|
+
if (parent) {
|
|
135
|
+
const parentWorldRotation = new Quaternion();
|
|
136
|
+
parent.worldMatrix.decompose(null, parentWorldRotation, null);
|
|
137
|
+
const parentInvRotation = Quaternion.conjugate(parentWorldRotation, new Quaternion());
|
|
138
|
+
const localRotation = Quaternion.multiply(parentInvRotation, worldRotation, new Quaternion());
|
|
139
|
+
node.rotation = localRotation;
|
|
140
|
+
this._nodePoseTracker.recordAppliedRotation(node, inputRotation, localRotation);
|
|
141
|
+
} else {
|
|
142
|
+
node.rotation = worldRotation;
|
|
143
|
+
this._nodePoseTracker.recordAppliedRotation(node, inputRotation, worldRotation);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
reset() {
|
|
148
|
+
this._nodePoseTracker.clear(true);
|
|
149
|
+
for (const chain of this._chains){
|
|
150
|
+
chain.reset();
|
|
151
|
+
for (const particle of chain.particles){
|
|
152
|
+
particle.animPosition.set(particle.originalPosition);
|
|
153
|
+
particle.lastFramePosition.set(particle.originalPosition);
|
|
154
|
+
if (particle.positionHistory) {
|
|
155
|
+
particle.positionHistory.length = 0;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
for (const constraint of this._interChainConstraints){
|
|
160
|
+
constraint.lambda = 0;
|
|
161
|
+
}
|
|
162
|
+
this._timeAccumulator = 0;
|
|
163
|
+
this._smoothedParticleTargets = new WeakMap();
|
|
164
|
+
this._smoothedSphereCenters = new WeakMap();
|
|
165
|
+
this._smoothedCapsuleEndpoints = new WeakMap();
|
|
166
|
+
this._smoothedPlaneData = new WeakMap();
|
|
167
|
+
}
|
|
168
|
+
get chains() {
|
|
169
|
+
return this._chains;
|
|
170
|
+
}
|
|
171
|
+
get interChainConstraints() {
|
|
172
|
+
return this._interChainConstraints;
|
|
173
|
+
}
|
|
174
|
+
get gravity() {
|
|
175
|
+
return this._gravity;
|
|
176
|
+
}
|
|
177
|
+
set gravity(gravity) {
|
|
178
|
+
this._gravity.set(gravity);
|
|
179
|
+
}
|
|
180
|
+
get wind() {
|
|
181
|
+
return this._wind;
|
|
182
|
+
}
|
|
183
|
+
set wind(wind) {
|
|
184
|
+
this._wind.set(wind);
|
|
185
|
+
}
|
|
186
|
+
get iterations() {
|
|
187
|
+
return this._iterations;
|
|
188
|
+
}
|
|
189
|
+
set iterations(count) {
|
|
190
|
+
this._iterations = Math.max(1, count);
|
|
191
|
+
}
|
|
192
|
+
get enableInertialForces() {
|
|
193
|
+
return this._enableInertialForces;
|
|
194
|
+
}
|
|
195
|
+
set enableInertialForces(enabled) {
|
|
196
|
+
this._enableInertialForces = enabled;
|
|
197
|
+
}
|
|
198
|
+
get centrifugalScale() {
|
|
199
|
+
return this._centrifugalScale;
|
|
200
|
+
}
|
|
201
|
+
set centrifugalScale(scale) {
|
|
202
|
+
this._centrifugalScale = Math.max(0, scale);
|
|
203
|
+
}
|
|
204
|
+
get coriolisScale() {
|
|
205
|
+
return this._coriolisScale;
|
|
206
|
+
}
|
|
207
|
+
set coriolisScale(scale) {
|
|
208
|
+
this._coriolisScale = Math.max(0, scale);
|
|
209
|
+
}
|
|
210
|
+
get solver() {
|
|
211
|
+
return this._solver;
|
|
212
|
+
}
|
|
213
|
+
set solver(type) {
|
|
214
|
+
if (this._solver !== type) {
|
|
215
|
+
this._solver = type;
|
|
216
|
+
if (type === 'xpbd') {
|
|
217
|
+
this.resetConstraintLambdas();
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
get poseFollow() {
|
|
222
|
+
return this._poseFollow;
|
|
223
|
+
}
|
|
224
|
+
set poseFollow(value) {
|
|
225
|
+
const v = Math.max(0, Math.min(1, value));
|
|
226
|
+
this._poseFollow = v;
|
|
227
|
+
this._poseFollowRoot = v;
|
|
228
|
+
this._poseFollowTip = v;
|
|
229
|
+
}
|
|
230
|
+
get poseFollowRoot() {
|
|
231
|
+
return this._poseFollowRoot;
|
|
232
|
+
}
|
|
233
|
+
set poseFollowRoot(value) {
|
|
234
|
+
this._poseFollowRoot = Math.max(0, Math.min(1, value));
|
|
235
|
+
}
|
|
236
|
+
get poseFollowTip() {
|
|
237
|
+
return this._poseFollowTip;
|
|
238
|
+
}
|
|
239
|
+
set poseFollowTip(value) {
|
|
240
|
+
this._poseFollowTip = Math.max(0, Math.min(1, value));
|
|
241
|
+
}
|
|
242
|
+
get poseFollowExponent() {
|
|
243
|
+
return this._poseFollowExponent;
|
|
244
|
+
}
|
|
245
|
+
set poseFollowExponent(value) {
|
|
246
|
+
this._poseFollowExponent = Math.max(0.1, value);
|
|
247
|
+
}
|
|
248
|
+
get maxPoseOffset() {
|
|
249
|
+
return this._maxPoseOffset;
|
|
250
|
+
}
|
|
251
|
+
set maxPoseOffset(value) {
|
|
252
|
+
const v = Math.max(0, value);
|
|
253
|
+
this._maxPoseOffset = v;
|
|
254
|
+
this._maxPoseOffsetRoot = v;
|
|
255
|
+
this._maxPoseOffsetTip = v;
|
|
256
|
+
}
|
|
257
|
+
get maxPoseOffsetRoot() {
|
|
258
|
+
return this._maxPoseOffsetRoot;
|
|
259
|
+
}
|
|
260
|
+
set maxPoseOffsetRoot(value) {
|
|
261
|
+
this._maxPoseOffsetRoot = Math.max(0, value);
|
|
262
|
+
}
|
|
263
|
+
get maxPoseOffsetTip() {
|
|
264
|
+
return this._maxPoseOffsetTip;
|
|
265
|
+
}
|
|
266
|
+
set maxPoseOffsetTip(value) {
|
|
267
|
+
this._maxPoseOffsetTip = Math.max(0, value);
|
|
268
|
+
}
|
|
269
|
+
addCollider(collider) {
|
|
270
|
+
this._colliders.push(collider);
|
|
271
|
+
this._smoothedSphereCenters = new WeakMap();
|
|
272
|
+
this._smoothedCapsuleEndpoints = new WeakMap();
|
|
273
|
+
this._smoothedPlaneData = new WeakMap();
|
|
274
|
+
}
|
|
275
|
+
removeCollider(collider) {
|
|
276
|
+
const index = this._colliders.indexOf(collider);
|
|
277
|
+
if (index >= 0) {
|
|
278
|
+
this._colliders.splice(index, 1);
|
|
279
|
+
this._smoothedSphereCenters = new WeakMap();
|
|
280
|
+
this._smoothedCapsuleEndpoints = new WeakMap();
|
|
281
|
+
this._smoothedPlaneData = new WeakMap();
|
|
282
|
+
return true;
|
|
283
|
+
}
|
|
284
|
+
return false;
|
|
285
|
+
}
|
|
286
|
+
clearColliders() {
|
|
287
|
+
this._colliders = [];
|
|
288
|
+
this._smoothedSphereCenters = new WeakMap();
|
|
289
|
+
this._smoothedCapsuleEndpoints = new WeakMap();
|
|
290
|
+
this._smoothedPlaneData = new WeakMap();
|
|
291
|
+
}
|
|
292
|
+
get colliders() {
|
|
293
|
+
return this._colliders;
|
|
294
|
+
}
|
|
80
295
|
/**
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
*/
|
|
84
|
-
|
|
85
|
-
|
|
296
|
+
* Sets a runtime-only node remapping used by particles, anchors and colliders.
|
|
297
|
+
* The authored node references remain unchanged and therefore serialize normally.
|
|
298
|
+
*/ setRuntimeNodeMap(nodeMap) {
|
|
299
|
+
this._nodePoseTracker.clear(false);
|
|
300
|
+
this._runtimeNodeMap = nodeMap;
|
|
301
|
+
this._runtimeAnchorOffsets = new WeakMap();
|
|
302
|
+
this._runtimeRestLengths = new WeakMap();
|
|
303
|
+
this.clearRuntimeCaches();
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* Rebuilds particle history from the current runtime-remapped node pose.
|
|
307
|
+
* This must be called after a hierarchy graft or skeleton rebind.
|
|
308
|
+
*/ reinitializeFromCurrentPose(options) {
|
|
309
|
+
this._nodePoseTracker.clear(false);
|
|
310
|
+
const recomputeAnchorOffsets = options?.recomputeAnchorOffsets ?? true;
|
|
311
|
+
const recalculateRestLengths = options?.recalculateRestLengths ?? true;
|
|
312
|
+
this._runtimeAnchorOffsets = new WeakMap();
|
|
313
|
+
this._runtimeRestLengths = new WeakMap();
|
|
314
|
+
for (const chain of this._chains){
|
|
315
|
+
for (const particle of chain.particles){
|
|
316
|
+
const particleNode = this.resolveRuntimeNode(particle.node);
|
|
317
|
+
let position = particleNode ? new Vector3(particleNode.worldMatrix.m03, particleNode.worldMatrix.m13, particleNode.worldMatrix.m23) : particle.position.clone();
|
|
318
|
+
const anchorNode = this.resolveRuntimeNode(particle.anchorNode);
|
|
319
|
+
if (particle.fixed && anchorNode) {
|
|
320
|
+
let anchorOffset = particle.anchorOffset;
|
|
321
|
+
if (recomputeAnchorOffsets && anchorOffset && particleNode) {
|
|
322
|
+
anchorOffset = anchorNode.worldToThis(position, new Vector3());
|
|
323
|
+
this._runtimeAnchorOffsets.set(particle, anchorOffset);
|
|
324
|
+
}
|
|
325
|
+
position = anchorOffset ? anchorNode.worldMatrix.transformPointAffine(anchorOffset, new Vector3()) : new Vector3(anchorNode.worldMatrix.m03, anchorNode.worldMatrix.m13, anchorNode.worldMatrix.m23);
|
|
326
|
+
}
|
|
327
|
+
particle.position.set(position);
|
|
328
|
+
particle.prevPosition.set(position);
|
|
329
|
+
particle.animPosition.set(position);
|
|
330
|
+
particle.lastFramePosition.set(position);
|
|
331
|
+
if (particle.positionHistory) {
|
|
332
|
+
particle.positionHistory.length = 0;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
for (const constraint of chain.constraints){
|
|
336
|
+
constraint.lambda = 0;
|
|
337
|
+
if (recalculateRestLengths) {
|
|
338
|
+
this._runtimeRestLengths.set(constraint, Vector3.distance(chain.particles[constraint.particleA].position, chain.particles[constraint.particleB].position));
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
for (const constraint of this._interChainConstraints){
|
|
343
|
+
constraint.lambda = 0;
|
|
344
|
+
if (recalculateRestLengths) {
|
|
345
|
+
const particleA = this._chains[constraint.chainAIndex]?.particles[constraint.particleAIndex];
|
|
346
|
+
const particleB = this._chains[constraint.chainBIndex]?.particles[constraint.particleBIndex];
|
|
347
|
+
if (particleA && particleB) {
|
|
348
|
+
this._runtimeRestLengths.set(constraint, Vector3.distance(particleA.position, particleB.position));
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
for (const collider of this._colliders){
|
|
353
|
+
const colliderNode = this.resolveRuntimeNode(collider.node);
|
|
354
|
+
if (colliderNode) {
|
|
355
|
+
updateColliderFromNode(collider, colliderNode);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
this.clearRuntimeCaches();
|
|
359
|
+
}
|
|
360
|
+
simulateStep(dt, inputDeltaTime) {
|
|
86
361
|
if (this._enableInertialForces) {
|
|
87
362
|
for (const chain of this._chains){
|
|
88
|
-
for (const
|
|
89
|
-
|
|
363
|
+
for (const particle of chain.particles){
|
|
364
|
+
particle.lastFramePosition.set(particle.position);
|
|
90
365
|
}
|
|
91
366
|
}
|
|
92
367
|
}
|
|
93
|
-
this.updateFixedParticles();
|
|
94
|
-
// Calculate global rotation parameters
|
|
368
|
+
this.updateFixedParticles(inputDeltaTime);
|
|
95
369
|
let rotationCenter = null;
|
|
96
370
|
let angularVelocity = null;
|
|
97
371
|
if (this._enableInertialForces && dt > 0.0001) {
|
|
@@ -107,7 +381,6 @@ import { IKUtils } from '../ik/ik_utils.js';
|
|
|
107
381
|
const velocity = Vector3.sub(particle.position, particle.prevPosition, new Vector3());
|
|
108
382
|
velocity.scaleBy(particle.damping);
|
|
109
383
|
const acceleration = Vector3.add(this._gravity, this._wind, new Vector3());
|
|
110
|
-
// Apply inertial forces
|
|
111
384
|
if (this._enableInertialForces && rotationCenter && angularVelocity) {
|
|
112
385
|
const inertialAccel = this.calculateInertialAcceleration(particle, rotationCenter, angularVelocity, velocity, this._centrifugalScale, this._coriolisScale);
|
|
113
386
|
Vector3.add(acceleration, inertialAccel, acceleration);
|
|
@@ -119,14 +392,7 @@ import { IKUtils } from '../ik/ik_utils.js';
|
|
|
119
392
|
}
|
|
120
393
|
}
|
|
121
394
|
if (this._solver === 'xpbd') {
|
|
122
|
-
|
|
123
|
-
for (const c of chain.constraints){
|
|
124
|
-
c.lambda = 0;
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
for (const c of this._interChainConstraints){
|
|
128
|
-
c.lambda = 0;
|
|
129
|
-
}
|
|
395
|
+
this.resetConstraintLambdas();
|
|
130
396
|
}
|
|
131
397
|
for(let iter = 0; iter < this._iterations; iter++){
|
|
132
398
|
for (const chain of this._chains){
|
|
@@ -145,21 +411,30 @@ import { IKUtils } from '../ik/ik_utils.js';
|
|
|
145
411
|
this.solveInterChainConstraint(constraint);
|
|
146
412
|
}
|
|
147
413
|
}
|
|
414
|
+
this.solvePosePreservation(this._iterations);
|
|
415
|
+
this.solveCollisions(inputDeltaTime);
|
|
148
416
|
}
|
|
149
417
|
}
|
|
150
|
-
updateFixedParticles() {
|
|
418
|
+
updateFixedParticles(deltaTime) {
|
|
419
|
+
const blend = this.getTemporalBlendFactor(deltaTime, DEFAULT_PARTICLE_TARGET_SMOOTHING_TIME);
|
|
151
420
|
for (const chain of this._chains){
|
|
152
421
|
for (const particle of chain.particles){
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
422
|
+
const sourceNode = this.resolveRuntimeNode(particle.anchorNode ?? particle.node);
|
|
423
|
+
if (!sourceNode) {
|
|
424
|
+
continue;
|
|
425
|
+
}
|
|
426
|
+
const anchorOffset = this._runtimeAnchorOffsets.get(particle) ?? particle.anchorOffset;
|
|
427
|
+
const worldPos = anchorOffset ? sourceNode.worldMatrix.transformPointAffine(anchorOffset) : new Vector3(sourceNode.worldMatrix.m03, sourceNode.worldMatrix.m13, sourceNode.worldMatrix.m23);
|
|
428
|
+
const smoothedTarget = this.getSmoothedParticleTarget(particle, worldPos, blend);
|
|
429
|
+
particle.animPosition.set(smoothedTarget);
|
|
430
|
+
if (particle.fixed) {
|
|
431
|
+
particle.position.set(smoothedTarget);
|
|
432
|
+
particle.prevPosition.set(smoothedTarget);
|
|
158
433
|
if (this._enableInertialForces) {
|
|
159
434
|
if (!particle.positionHistory) {
|
|
160
435
|
particle.positionHistory = [];
|
|
161
436
|
}
|
|
162
|
-
particle.positionHistory.push(
|
|
437
|
+
particle.positionHistory.push(smoothedTarget.clone());
|
|
163
438
|
if (particle.positionHistory.length > 5) {
|
|
164
439
|
particle.positionHistory.shift();
|
|
165
440
|
}
|
|
@@ -168,18 +443,125 @@ import { IKUtils } from '../ik/ik_utils.js';
|
|
|
168
443
|
}
|
|
169
444
|
}
|
|
170
445
|
}
|
|
446
|
+
solvePosePreservation(totalIterations) {
|
|
447
|
+
if (this._poseFollowRoot <= 0 && this._poseFollowTip <= 0) {
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
for (const chain of this._chains){
|
|
451
|
+
const lastIndex = Math.max(1, chain.particles.length - 1);
|
|
452
|
+
for(let i = 0; i < chain.particles.length; i++){
|
|
453
|
+
const particle = chain.particles[i];
|
|
454
|
+
if (particle.fixed) {
|
|
455
|
+
continue;
|
|
456
|
+
}
|
|
457
|
+
const t = Math.pow(i / lastIndex, this._poseFollowExponent);
|
|
458
|
+
const particlePoseFollow = this.lerp(this._poseFollowRoot, this._poseFollowTip, t);
|
|
459
|
+
const iterationFollow = totalIterations > 1 ? 1 - Math.pow(Math.max(0, 1 - particlePoseFollow), 1 / totalIterations) : particlePoseFollow;
|
|
460
|
+
const toAnim = Vector3.sub(particle.animPosition, particle.position, new Vector3());
|
|
461
|
+
const correction = Vector3.scale(toAnim, iterationFollow, new Vector3());
|
|
462
|
+
Vector3.add(particle.position, correction, particle.position);
|
|
463
|
+
const particleMaxPoseOffset = this.lerp(this._maxPoseOffsetRoot, this._maxPoseOffsetTip, t);
|
|
464
|
+
if (particleMaxPoseOffset > 0) {
|
|
465
|
+
const offset = Vector3.sub(particle.position, particle.animPosition, new Vector3());
|
|
466
|
+
const offsetLen = offset.magnitude;
|
|
467
|
+
if (offsetLen > particleMaxPoseOffset && offsetLen > 1e-6) {
|
|
468
|
+
offset.scaleBy(particleMaxPoseOffset / offsetLen);
|
|
469
|
+
Vector3.add(particle.animPosition, offset, particle.position);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
solveCollisions(deltaTime) {
|
|
476
|
+
const blend = this.getTemporalBlendFactor(deltaTime, DEFAULT_COLLIDER_SMOOTHING_TIME);
|
|
477
|
+
const spheres = [];
|
|
478
|
+
const capsules = [];
|
|
479
|
+
const planes = [];
|
|
480
|
+
for (const collider of this._colliders){
|
|
481
|
+
const colliderNode = this.resolveRuntimeNode(collider.node);
|
|
482
|
+
if (colliderNode) {
|
|
483
|
+
updateColliderFromNode(collider, colliderNode);
|
|
484
|
+
}
|
|
485
|
+
if (!collider.enabled) {
|
|
486
|
+
continue;
|
|
487
|
+
}
|
|
488
|
+
switch(collider.type){
|
|
489
|
+
case 'sphere':
|
|
490
|
+
{
|
|
491
|
+
const source = collider;
|
|
492
|
+
spheres.push({
|
|
493
|
+
...source,
|
|
494
|
+
center: this.getSmoothedSphereCenter(source, blend)
|
|
495
|
+
});
|
|
496
|
+
break;
|
|
497
|
+
}
|
|
498
|
+
case 'capsule':
|
|
499
|
+
{
|
|
500
|
+
const source = collider;
|
|
501
|
+
const endpoints = this.getSmoothedCapsuleEndpoints(source, blend);
|
|
502
|
+
capsules.push({
|
|
503
|
+
...source,
|
|
504
|
+
start: endpoints.start,
|
|
505
|
+
end: endpoints.end
|
|
506
|
+
});
|
|
507
|
+
break;
|
|
508
|
+
}
|
|
509
|
+
case 'plane':
|
|
510
|
+
{
|
|
511
|
+
const source = collider;
|
|
512
|
+
const plane = this.getSmoothedPlaneData(source, blend);
|
|
513
|
+
planes.push({
|
|
514
|
+
...source,
|
|
515
|
+
point: plane.point,
|
|
516
|
+
normal: plane.normal
|
|
517
|
+
});
|
|
518
|
+
break;
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
for (const chain of this._chains){
|
|
523
|
+
for (const particle of chain.particles){
|
|
524
|
+
if (particle.fixed) {
|
|
525
|
+
continue;
|
|
526
|
+
}
|
|
527
|
+
const positionBeforeCollision = particle.position.clone();
|
|
528
|
+
let collided = false;
|
|
529
|
+
for (const collider of spheres){
|
|
530
|
+
collided = resolveSphereCollision(particle.position, collider) || collided;
|
|
531
|
+
}
|
|
532
|
+
for (const collider of capsules){
|
|
533
|
+
collided = resolveCapsuleCollision(particle.position, collider) || collided;
|
|
534
|
+
}
|
|
535
|
+
for (const collider of planes){
|
|
536
|
+
collided = resolvePlaneCollision(particle.position, collider) || collided;
|
|
537
|
+
}
|
|
538
|
+
if (collided) {
|
|
539
|
+
Vector3.add(particle.prevPosition, Vector3.sub(particle.position, positionBeforeCollision, new Vector3()), particle.prevPosition);
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
}
|
|
171
544
|
calculateGlobalRotation(dt) {
|
|
172
545
|
const fixedParticles = [];
|
|
173
546
|
const velocities = [];
|
|
547
|
+
const drivenNodes = new Set();
|
|
174
548
|
for (const chain of this._chains){
|
|
175
|
-
for (const
|
|
176
|
-
|
|
549
|
+
for (const particle of chain.particles){
|
|
550
|
+
const node = this.resolveRuntimeNode(particle.node);
|
|
551
|
+
if (node) {
|
|
552
|
+
drivenNodes.add(node);
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
for (const chain of this._chains){
|
|
557
|
+
for (const particle of chain.particles){
|
|
558
|
+
if (!particle.fixed || !this.isExternalInertialAnchor(particle, drivenNodes)) {
|
|
177
559
|
continue;
|
|
178
560
|
}
|
|
179
|
-
const velocity = Vector3.sub(
|
|
561
|
+
const velocity = Vector3.sub(particle.position, particle.lastFramePosition, new Vector3());
|
|
180
562
|
velocity.scaleBy(1.0 / dt);
|
|
181
563
|
if (velocity.magnitudeSq > 0.001) {
|
|
182
|
-
fixedParticles.push(
|
|
564
|
+
fixedParticles.push(particle);
|
|
183
565
|
velocities.push(velocity);
|
|
184
566
|
}
|
|
185
567
|
}
|
|
@@ -190,18 +572,16 @@ import { IKUtils } from '../ik/ik_utils.js';
|
|
|
190
572
|
omega: new Vector3(0, 0, 0)
|
|
191
573
|
};
|
|
192
574
|
}
|
|
193
|
-
// Estimate rotation center
|
|
194
575
|
let center;
|
|
195
576
|
if (fixedParticles.length === 1) {
|
|
196
577
|
center = this.estimateRotationCenterFromHistory(fixedParticles[0], velocities[0]);
|
|
197
578
|
} else {
|
|
198
579
|
center = new Vector3(0, 0, 0);
|
|
199
|
-
for (const
|
|
200
|
-
Vector3.add(center,
|
|
580
|
+
for (const particle of fixedParticles){
|
|
581
|
+
Vector3.add(center, particle.position, center);
|
|
201
582
|
}
|
|
202
583
|
center.scaleBy(1.0 / fixedParticles.length);
|
|
203
584
|
}
|
|
204
|
-
// Estimate angular velocity
|
|
205
585
|
let sumOmega = new Vector3(0, 0, 0);
|
|
206
586
|
let count = 0;
|
|
207
587
|
for(let i = 0; i < fixedParticles.length; i++){
|
|
@@ -236,16 +616,14 @@ import { IKUtils } from '../ik/ik_utils.js';
|
|
|
236
616
|
if (perpDir.magnitudeSq < 0.0001) {
|
|
237
617
|
perpDir.set(new Vector3(1, 0, 0));
|
|
238
618
|
} else {
|
|
239
|
-
|
|
619
|
+
perpDir.inplaceNormalize();
|
|
240
620
|
}
|
|
241
|
-
|
|
242
|
-
return center;
|
|
621
|
+
return Vector3.add(particle.position, Vector3.scale(perpDir, estimatedRadius, new Vector3()), new Vector3());
|
|
243
622
|
}
|
|
244
623
|
const p1 = history[0];
|
|
245
624
|
const p2 = history[Math.floor(history.length / 2)];
|
|
246
625
|
const p3 = history[history.length - 1];
|
|
247
|
-
|
|
248
|
-
return center;
|
|
626
|
+
return this.calculateCircleCenter(p1, p2, p3);
|
|
249
627
|
}
|
|
250
628
|
calculateCircleCenter(p1, p2, p3) {
|
|
251
629
|
const mid12 = Vector3.scale(Vector3.add(p1, p2, new Vector3()), 0.5, new Vector3());
|
|
@@ -256,17 +634,14 @@ import { IKUtils } from '../ik/ik_utils.js';
|
|
|
256
634
|
if (normal.magnitudeSq < 0.0001) {
|
|
257
635
|
return Vector3.scale(Vector3.add(Vector3.add(p1, p2, new Vector3()), p3, new Vector3()), 1.0 / 3.0, new Vector3());
|
|
258
636
|
}
|
|
259
|
-
|
|
260
|
-
const perp12 = Vector3.cross(dir12, normal, new Vector3());
|
|
261
|
-
Vector3.
|
|
262
|
-
const perp23 = Vector3.cross(dir23, normal, new Vector3());
|
|
263
|
-
Vector3.normalize(perp23, perp23);
|
|
637
|
+
normal.inplaceNormalize();
|
|
638
|
+
const perp12 = Vector3.cross(dir12, normal, new Vector3()).inplaceNormalize();
|
|
639
|
+
const perp23 = Vector3.cross(dir23, normal, new Vector3()).inplaceNormalize();
|
|
264
640
|
const diff = Vector3.sub(mid23, mid12, new Vector3());
|
|
265
641
|
const det = perp12.x * perp23.y - perp12.y * perp23.x;
|
|
266
642
|
if (Math.abs(det) > 0.0001) {
|
|
267
643
|
const t = (diff.x * perp23.y - diff.y * perp23.x) / det;
|
|
268
|
-
|
|
269
|
-
return center;
|
|
644
|
+
return Vector3.add(mid12, Vector3.scale(perp12, t, new Vector3()), new Vector3());
|
|
270
645
|
}
|
|
271
646
|
return Vector3.scale(Vector3.add(Vector3.add(p1, p2, new Vector3()), p3, new Vector3()), 1.0 / 3.0, new Vector3());
|
|
272
647
|
}
|
|
@@ -277,8 +652,7 @@ import { IKUtils } from '../ik/ik_utils.js';
|
|
|
277
652
|
centrifugalAccel.scaleBy(centrifugalScale);
|
|
278
653
|
const coriolisAccel = Vector3.cross(angularVelocity, particleVelocity, new Vector3());
|
|
279
654
|
coriolisAccel.scaleBy(-2 * coriolisScale);
|
|
280
|
-
|
|
281
|
-
return totalAccel;
|
|
655
|
+
return Vector3.add(centrifugalAccel, coriolisAccel, new Vector3());
|
|
282
656
|
}
|
|
283
657
|
solveConstraint(chain, constraint) {
|
|
284
658
|
const pA = chain.particles[constraint.particleA];
|
|
@@ -288,7 +662,7 @@ import { IKUtils } from '../ik/ik_utils.js';
|
|
|
288
662
|
if (currentLength < 0.0001) {
|
|
289
663
|
return;
|
|
290
664
|
}
|
|
291
|
-
const diff = (currentLength - constraint
|
|
665
|
+
const diff = (currentLength - this.getRuntimeRestLength(constraint)) / currentLength;
|
|
292
666
|
const correction = Vector3.scale(delta, diff * constraint.stiffness * 0.5, new Vector3());
|
|
293
667
|
if (!pA.fixed) {
|
|
294
668
|
Vector3.add(pA.position, correction, pA.position);
|
|
@@ -307,7 +681,7 @@ import { IKUtils } from '../ik/ik_utils.js';
|
|
|
307
681
|
if (currentLength < 0.0001) {
|
|
308
682
|
return;
|
|
309
683
|
}
|
|
310
|
-
const diff = (currentLength - constraint
|
|
684
|
+
const diff = (currentLength - this.getRuntimeRestLength(constraint)) / currentLength;
|
|
311
685
|
const correction = Vector3.scale(delta, diff * constraint.stiffness * 0.5, new Vector3());
|
|
312
686
|
if (!pA.fixed) {
|
|
313
687
|
Vector3.add(pA.position, correction, pA.position);
|
|
@@ -330,7 +704,7 @@ import { IKUtils } from '../ik/ik_utils.js';
|
|
|
330
704
|
if (currentLength < 0.0001) {
|
|
331
705
|
return;
|
|
332
706
|
}
|
|
333
|
-
const C = currentLength - constraint
|
|
707
|
+
const C = currentLength - this.getRuntimeRestLength(constraint);
|
|
334
708
|
const alphaTilde = constraint.compliance / (dt * dt);
|
|
335
709
|
const deltaLambda = (-C - alphaTilde * constraint.lambda) / (wSum + alphaTilde);
|
|
336
710
|
constraint.lambda += deltaLambda;
|
|
@@ -358,7 +732,7 @@ import { IKUtils } from '../ik/ik_utils.js';
|
|
|
358
732
|
if (currentLength < 0.0001) {
|
|
359
733
|
return;
|
|
360
734
|
}
|
|
361
|
-
const C = currentLength - constraint
|
|
735
|
+
const C = currentLength - this.getRuntimeRestLength(constraint);
|
|
362
736
|
const alphaTilde = constraint.compliance / (dt * dt);
|
|
363
737
|
const deltaLambda = (-C - alphaTilde * constraint.lambda) / (wSum + alphaTilde);
|
|
364
738
|
constraint.lambda += deltaLambda;
|
|
@@ -370,33 +744,40 @@ import { IKUtils } from '../ik/ik_utils.js';
|
|
|
370
744
|
Vector3.add(pB.position, Vector3.scale(n, wB * deltaLambda, new Vector3()), pB.position);
|
|
371
745
|
}
|
|
372
746
|
}
|
|
373
|
-
|
|
374
|
-
* Applies simulation results to scene nodes
|
|
375
|
-
* @param weight - Blend weight [0-1] (default: 1.0)
|
|
376
|
-
*/ applyToNodes(weight = 1.0) {
|
|
377
|
-
for (const chain of this._chains){
|
|
378
|
-
this.applyChainToNodes(chain, weight);
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
applyChainToNodes(chain, weight) {
|
|
747
|
+
collectChainNodeRotations(chain, weight, baseNodeStates, desiredWorldRotations) {
|
|
382
748
|
for(let i = 0; i < chain.particles.length - 1; i++){
|
|
383
749
|
const particle = chain.particles[i];
|
|
384
750
|
const nextParticle = chain.particles[i + 1];
|
|
385
|
-
|
|
751
|
+
const node = this.resolveRuntimeNode(particle.node);
|
|
752
|
+
if (!node) {
|
|
386
753
|
continue;
|
|
387
754
|
}
|
|
388
|
-
|
|
389
|
-
const currentBonePos = new Vector3(particle.node.worldMatrix.m03, particle.node.worldMatrix.m13, particle.node.worldMatrix.m23);
|
|
390
|
-
const nextNode = nextParticle.node;
|
|
755
|
+
const nextNode = this.resolveRuntimeNode(nextParticle.node);
|
|
391
756
|
if (!nextNode) {
|
|
392
757
|
continue;
|
|
393
758
|
}
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
759
|
+
let nodeState = baseNodeStates.get(node);
|
|
760
|
+
if (!nodeState) {
|
|
761
|
+
const currentBoneRotation = new Quaternion();
|
|
762
|
+
node.worldMatrix.decompose(null, currentBoneRotation, null);
|
|
763
|
+
nodeState = {
|
|
764
|
+
position: new Vector3(node.worldMatrix.m03, node.worldMatrix.m13, node.worldMatrix.m23),
|
|
765
|
+
rotation: currentBoneRotation
|
|
766
|
+
};
|
|
767
|
+
baseNodeStates.set(node, nodeState);
|
|
768
|
+
}
|
|
769
|
+
let nextNodeState = baseNodeStates.get(nextNode);
|
|
770
|
+
if (!nextNodeState) {
|
|
771
|
+
const nextBoneRotation = new Quaternion();
|
|
772
|
+
nextNode.worldMatrix.decompose(null, nextBoneRotation, null);
|
|
773
|
+
nextNodeState = {
|
|
774
|
+
position: new Vector3(nextNode.worldMatrix.m03, nextNode.worldMatrix.m13, nextNode.worldMatrix.m23),
|
|
775
|
+
rotation: nextBoneRotation
|
|
776
|
+
};
|
|
777
|
+
baseNodeStates.set(nextNode, nextNodeState);
|
|
778
|
+
}
|
|
779
|
+
const originalDir = Vector3.sub(nextNodeState.position, nodeState.position, new Vector3());
|
|
780
|
+
const currentBoneRotation = nodeState.rotation;
|
|
400
781
|
const newDir = Vector3.sub(nextParticle.position, particle.position, new Vector3());
|
|
401
782
|
const deltaRotation = new Quaternion();
|
|
402
783
|
IKUtils.fromToRotation(originalDir, newDir, deltaRotation);
|
|
@@ -404,100 +785,117 @@ import { IKUtils } from '../ik/ik_utils.js';
|
|
|
404
785
|
if (weight < 1) {
|
|
405
786
|
Quaternion.slerp(currentBoneRotation, worldRotation, weight, worldRotation);
|
|
406
787
|
}
|
|
407
|
-
|
|
408
|
-
if (parent) {
|
|
409
|
-
const parentWorldRotation = new Quaternion();
|
|
410
|
-
parent.worldMatrix.decompose(null, parentWorldRotation, null);
|
|
411
|
-
const parentInvRotation = Quaternion.conjugate(parentWorldRotation, new Quaternion());
|
|
412
|
-
const localRotation = Quaternion.multiply(parentInvRotation, worldRotation, new Quaternion());
|
|
413
|
-
particle.node.rotation = localRotation;
|
|
414
|
-
} else {
|
|
415
|
-
particle.node.rotation = worldRotation;
|
|
416
|
-
}
|
|
788
|
+
desiredWorldRotations.set(node, worldRotation);
|
|
417
789
|
}
|
|
418
790
|
}
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
791
|
+
getNodeDepth(node) {
|
|
792
|
+
let depth = 0;
|
|
793
|
+
let current = node?.parent;
|
|
794
|
+
while(current){
|
|
795
|
+
depth++;
|
|
796
|
+
current = current.parent;
|
|
424
797
|
}
|
|
798
|
+
return depth;
|
|
425
799
|
}
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
}
|
|
436
|
-
/**
|
|
437
|
-
* Gets the current gravity
|
|
438
|
-
*/ get gravity() {
|
|
439
|
-
return this._gravity;
|
|
440
|
-
}
|
|
441
|
-
set gravity(gravity) {
|
|
442
|
-
this._gravity.set(gravity);
|
|
443
|
-
}
|
|
444
|
-
/**
|
|
445
|
-
* Gets the current wind
|
|
446
|
-
*/ get wind() {
|
|
447
|
-
return this._wind;
|
|
448
|
-
}
|
|
449
|
-
set wind(wind) {
|
|
450
|
-
this._wind.set(wind);
|
|
451
|
-
}
|
|
452
|
-
/**
|
|
453
|
-
* Gets the number of iterations
|
|
454
|
-
*/ get iterations() {
|
|
455
|
-
return this._iterations;
|
|
456
|
-
}
|
|
457
|
-
set iterations(count) {
|
|
458
|
-
this._iterations = Math.max(1, count);
|
|
459
|
-
}
|
|
460
|
-
/**
|
|
461
|
-
* Gets whether inertial forces are enabled
|
|
462
|
-
*/ get enableInertialForces() {
|
|
463
|
-
return this._enableInertialForces;
|
|
464
|
-
}
|
|
465
|
-
set enableInertialForces(enabled) {
|
|
466
|
-
this._enableInertialForces = enabled;
|
|
467
|
-
}
|
|
468
|
-
/**
|
|
469
|
-
* Gets the centrifugal force scale
|
|
470
|
-
*/ get centrifugalScale() {
|
|
471
|
-
return this._centrifugalScale;
|
|
472
|
-
}
|
|
473
|
-
set centrifugalScale(scale) {
|
|
474
|
-
this._centrifugalScale = Math.max(0, scale);
|
|
475
|
-
}
|
|
476
|
-
/**
|
|
477
|
-
* Gets the Coriolis force scale
|
|
478
|
-
*/ get coriolisScale() {
|
|
479
|
-
return this._coriolisScale;
|
|
480
|
-
}
|
|
481
|
-
set coriolisScale(scale) {
|
|
482
|
-
this._coriolisScale = Math.max(0, scale);
|
|
800
|
+
resetConstraintLambdas() {
|
|
801
|
+
for (const chain of this._chains){
|
|
802
|
+
for (const constraint of chain.constraints){
|
|
803
|
+
constraint.lambda = 0;
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
for (const constraint of this._interChainConstraints){
|
|
807
|
+
constraint.lambda = 0;
|
|
808
|
+
}
|
|
483
809
|
}
|
|
484
|
-
|
|
485
|
-
return this.
|
|
810
|
+
resolveRuntimeNode(node) {
|
|
811
|
+
return node ? this._runtimeNodeMap?.get(node) ?? node : null;
|
|
486
812
|
}
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
c.lambda = 0;
|
|
494
|
-
}
|
|
495
|
-
}
|
|
496
|
-
for (const c of this._interChainConstraints){
|
|
497
|
-
c.lambda = 0;
|
|
498
|
-
}
|
|
813
|
+
isExternalInertialAnchor(particle, drivenNodes) {
|
|
814
|
+
const particleNode = this.resolveRuntimeNode(particle.node);
|
|
815
|
+
let current = this.resolveRuntimeNode(particle.anchorNode ?? particle.node);
|
|
816
|
+
while(current){
|
|
817
|
+
if (current !== particleNode && drivenNodes.has(current)) {
|
|
818
|
+
return false;
|
|
499
819
|
}
|
|
820
|
+
current = current.parent;
|
|
821
|
+
}
|
|
822
|
+
return true;
|
|
823
|
+
}
|
|
824
|
+
getRuntimeRestLength(constraint) {
|
|
825
|
+
return this._runtimeRestLengths.get(constraint) ?? constraint.restLength;
|
|
826
|
+
}
|
|
827
|
+
clearRuntimeCaches() {
|
|
828
|
+
this._timeAccumulator = 0;
|
|
829
|
+
this._smoothedParticleTargets = new WeakMap();
|
|
830
|
+
this._smoothedSphereCenters = new WeakMap();
|
|
831
|
+
this._smoothedCapsuleEndpoints = new WeakMap();
|
|
832
|
+
this._smoothedPlaneData = new WeakMap();
|
|
833
|
+
}
|
|
834
|
+
lerp(a, b, t) {
|
|
835
|
+
return a + (b - a) * t;
|
|
836
|
+
}
|
|
837
|
+
getTemporalBlendFactor(deltaTime, smoothingTime) {
|
|
838
|
+
const dt = Math.min(Math.max(Number(deltaTime) || 0, 0), MAX_ACCUMULATED_SIMULATION_TIME);
|
|
839
|
+
if (smoothingTime <= 0) {
|
|
840
|
+
return 1;
|
|
841
|
+
}
|
|
842
|
+
if (dt <= 0) {
|
|
843
|
+
return 0;
|
|
844
|
+
}
|
|
845
|
+
return 1 - Math.exp(-dt / smoothingTime);
|
|
846
|
+
}
|
|
847
|
+
getSmoothedParticleTarget(target, current, blend) {
|
|
848
|
+
const cached = this._smoothedParticleTargets.get(target);
|
|
849
|
+
if (!cached || blend >= 1) {
|
|
850
|
+
const next = current.clone();
|
|
851
|
+
this._smoothedParticleTargets.set(target, next);
|
|
852
|
+
return next;
|
|
853
|
+
}
|
|
854
|
+
cached.setXYZ(cached.x + (current.x - cached.x) * blend, cached.y + (current.y - cached.y) * blend, cached.z + (current.z - cached.z) * blend);
|
|
855
|
+
return cached;
|
|
856
|
+
}
|
|
857
|
+
getSmoothedSphereCenter(collider, blend) {
|
|
858
|
+
const current = collider.center?.clone() ?? new Vector3();
|
|
859
|
+
const cached = this._smoothedSphereCenters.get(collider);
|
|
860
|
+
if (!cached || blend >= 1) {
|
|
861
|
+
this._smoothedSphereCenters.set(collider, current);
|
|
862
|
+
return current;
|
|
863
|
+
}
|
|
864
|
+
cached.setXYZ(cached.x + (current.x - cached.x) * blend, cached.y + (current.y - cached.y) * blend, cached.z + (current.z - cached.z) * blend);
|
|
865
|
+
return cached;
|
|
866
|
+
}
|
|
867
|
+
getSmoothedCapsuleEndpoints(collider, blend) {
|
|
868
|
+
const currentStart = collider.start?.clone() ?? new Vector3();
|
|
869
|
+
const currentEnd = collider.end?.clone() ?? new Vector3();
|
|
870
|
+
const cached = this._smoothedCapsuleEndpoints.get(collider);
|
|
871
|
+
if (!cached || blend >= 1) {
|
|
872
|
+
const next = {
|
|
873
|
+
start: currentStart,
|
|
874
|
+
end: currentEnd
|
|
875
|
+
};
|
|
876
|
+
this._smoothedCapsuleEndpoints.set(collider, next);
|
|
877
|
+
return next;
|
|
878
|
+
}
|
|
879
|
+
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);
|
|
880
|
+
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);
|
|
881
|
+
return cached;
|
|
882
|
+
}
|
|
883
|
+
getSmoothedPlaneData(collider, blend) {
|
|
884
|
+
const currentPoint = collider.point?.clone() ?? new Vector3();
|
|
885
|
+
const currentNormal = collider.normal?.clone() ?? new Vector3(0, 1, 0);
|
|
886
|
+
const cached = this._smoothedPlaneData.get(collider);
|
|
887
|
+
if (!cached || blend >= 1) {
|
|
888
|
+
const next = {
|
|
889
|
+
point: currentPoint,
|
|
890
|
+
normal: currentNormal
|
|
891
|
+
};
|
|
892
|
+
this._smoothedPlaneData.set(collider, next);
|
|
893
|
+
return next;
|
|
500
894
|
}
|
|
895
|
+
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);
|
|
896
|
+
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);
|
|
897
|
+
cached.normal.inplaceNormalize();
|
|
898
|
+
return cached;
|
|
501
899
|
}
|
|
502
900
|
}
|
|
503
901
|
|