@woosh/meep-engine 3.40.0 → 3.41.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/package.json +1 -1
- package/src/core/geom/3d/quaternion/swing_twist_clamp.d.ts +24 -0
- package/src/core/geom/3d/quaternion/swing_twist_clamp.d.ts.map +1 -0
- package/src/core/geom/3d/quaternion/swing_twist_clamp.js +62 -0
- package/src/core/geom/3d/quaternion/swing_twist_compose.d.ts +15 -0
- package/src/core/geom/3d/quaternion/swing_twist_compose.d.ts.map +1 -0
- package/src/core/geom/3d/quaternion/swing_twist_compose.js +36 -0
- package/src/core/geom/3d/quaternion/swing_twist_error.d.ts +39 -0
- package/src/core/geom/3d/quaternion/swing_twist_error.d.ts.map +1 -0
- package/src/core/geom/3d/quaternion/swing_twist_error.js +85 -0
- package/src/core/geom/vec3/v3_rigid_align_paired_unit_vectors.d.ts +11 -6
- package/src/core/geom/vec3/v3_rigid_align_paired_unit_vectors.d.ts.map +1 -1
- package/src/core/geom/vec3/v3_rigid_align_paired_unit_vectors.js +27 -13
- package/src/core/math/linalg/eigen/matrix_symmetric_4x4_eigen.d.ts +25 -0
- package/src/core/math/linalg/eigen/matrix_symmetric_4x4_eigen.d.ts.map +1 -0
- package/src/core/math/linalg/eigen/matrix_symmetric_4x4_eigen.js +166 -0
- package/src/core/math/linalg/eigen/matrix_top_eigenvector_power_iteration.d.ts +5 -3
- package/src/core/math/linalg/eigen/matrix_top_eigenvector_power_iteration.d.ts.map +1 -1
- package/src/core/math/linalg/eigen/matrix_top_eigenvector_power_iteration.js +5 -3
- package/src/engine/animation/curve/AnimationCurve.d.ts +3 -0
- package/src/engine/animation/curve/AnimationCurve.d.ts.map +1 -1
- package/src/engine/animation/curve/AnimationCurve.js +6 -1
- package/src/engine/ecs/EntityManager.d.ts +9 -1
- package/src/engine/ecs/EntityManager.d.ts.map +1 -1
- package/src/engine/ecs/EntityManager.js +26 -27
- package/src/engine/ecs/gpu/GPUStateAuthority.d.ts +20 -0
- package/src/engine/ecs/gpu/GPUStateAuthority.d.ts.map +1 -1
- package/src/engine/ecs/gpu/GPUStateAuthority.js +21 -0
- package/src/engine/ecs/gpu/GPU_AUTHORITY_EVENT_HOLD_CHANGE.d.ts +13 -0
- package/src/engine/ecs/gpu/GPU_AUTHORITY_EVENT_HOLD_CHANGE.d.ts.map +1 -0
- package/src/engine/ecs/gpu/GPU_AUTHORITY_EVENT_HOLD_CHANGE.js +12 -0
- package/src/engine/ecs/gpu/gpu_authority_grant.d.ts +6 -0
- package/src/engine/ecs/gpu/gpu_authority_grant.d.ts.map +1 -1
- package/src/engine/ecs/gpu/gpu_authority_grant.js +7 -1
- package/src/engine/ecs/gpu/gpu_authority_held.d.ts +14 -0
- package/src/engine/ecs/gpu/gpu_authority_held.d.ts.map +1 -0
- package/src/engine/ecs/gpu/gpu_authority_held.js +23 -0
- package/src/engine/ecs/gpu/gpu_authority_hold.d.ts +23 -0
- package/src/engine/ecs/gpu/gpu_authority_hold.d.ts.map +1 -0
- package/src/engine/ecs/gpu/gpu_authority_hold.js +41 -0
- package/src/engine/ecs/gpu/gpu_authority_unhold.d.ts +14 -0
- package/src/engine/ecs/gpu/gpu_authority_unhold.d.ts.map +1 -0
- package/src/engine/ecs/gpu/gpu_authority_unhold.js +32 -0
- package/src/engine/ecs/ik/IKGoal.d.ts +59 -0
- package/src/engine/ecs/ik/IKGoal.d.ts.map +1 -0
- package/src/engine/ecs/ik/IKGoal.js +83 -0
- package/src/engine/ecs/ik/IKGoalSystem.d.ts +75 -0
- package/src/engine/ecs/ik/IKGoalSystem.d.ts.map +1 -0
- package/src/engine/ecs/ik/IKGoalSystem.js +1456 -0
- package/src/engine/ecs/ik/PLAN.md +179 -0
- package/src/engine/ecs/speaker/VoiceSystem.d.ts +2 -2
- package/src/engine/ecs/speaker/VoiceSystem.d.ts.map +1 -1
- package/src/engine/ecs/storage/BinaryBufferSerializer.d.ts.map +1 -1
- package/src/engine/ecs/storage/BinaryBufferSerializer.js +4 -5
- package/src/engine/ecs/storage/binary/SERIALIZATION_COVERAGE.md +7 -3
- package/src/engine/ecs/storage/populateEngineSerializationRegistry.d.ts.map +1 -1
- package/src/engine/ecs/storage/populateEngineSerializationRegistry.js +1 -2
- package/src/engine/ecs/system/orderSystemsByDeclaredAccess.d.ts +33 -0
- package/src/engine/ecs/system/orderSystemsByDeclaredAccess.d.ts.map +1 -0
- package/src/engine/ecs/system/orderSystemsByDeclaredAccess.js +282 -0
- package/src/engine/ecs/transform/T64WorldCache.js +139 -139
- package/src/engine/ecs/transform/t64_evaluate_local.d.ts +25 -0
- package/src/engine/ecs/transform/t64_evaluate_local.d.ts.map +1 -0
- package/src/engine/ecs/transform/t64_evaluate_local.js +35 -0
- package/src/engine/ecs/transform/t64_evaluate_world.d.ts +5 -3
- package/src/engine/ecs/transform/t64_evaluate_world.d.ts.map +1 -1
- package/src/engine/ecs/transform/t64_evaluate_world.js +128 -126
- package/src/engine/graphics3/AnimationGraphSystem.d.ts +2 -0
- package/src/engine/graphics3/AnimationGraphSystem.d.ts.map +1 -1
- package/src/engine/graphics3/AnimationGraphSystem.js +337 -329
- package/src/engine/graphics3/AnimationSystem.d.ts +2 -0
- package/src/engine/graphics3/AnimationSystem.d.ts.map +1 -1
- package/src/engine/graphics3/AnimationSystem.js +360 -352
- package/src/engine/physics/cloth/MEASUREMENTS.md +130 -0
- package/src/engine/physics/cloth/PLAN.md +16 -1
- package/src/engine/physics/cloth/ecs/ClothColliderSystem.d.ts +5 -6
- package/src/engine/physics/cloth/ecs/ClothColliderSystem.d.ts.map +1 -1
- package/src/engine/physics/cloth/ecs/ClothColliderSystem.js +5 -6
- package/src/engine/physics/cloth/ecs/ClothInstance.d.ts +4 -7
- package/src/engine/physics/cloth/ecs/ClothInstance.d.ts.map +1 -1
- package/src/engine/physics/cloth/ecs/ClothInstance.js +4 -7
- package/src/engine/physics/cloth/ecs/ClothSystem.d.ts +4 -3
- package/src/engine/physics/cloth/ecs/ClothSystem.d.ts.map +1 -1
- package/src/engine/physics/cloth/ecs/ClothSystem.js +4 -3
- package/src/engine/physics/cloth/ecs/ClothWorld.d.ts.map +1 -1
- package/src/engine/physics/cloth/ecs/ClothWorld.js +10 -0
- package/src/engine/physics/cloth/ecs/cloth_seed_rig.d.ts +24 -24
- package/src/engine/physics/cloth/ecs/cloth_seed_rig.d.ts.map +1 -1
- package/src/engine/physics/cloth/ecs/cloth_seed_rig.js +46 -43
- package/src/engine/physics/cloth/ecs/cloth_write_back.d.ts +1 -1
- package/src/engine/physics/cloth/ecs/cloth_write_back.js +1 -1
- package/src/engine/physics/constraint/solve_constraints.d.ts +0 -35
- package/src/engine/physics/constraint/solve_constraints.d.ts.map +1 -1
- package/src/engine/physics/constraint/solve_constraints.js +1 -83
- package/src/engine/physics/ecs/ContactProbe.d.ts +73 -0
- package/src/engine/physics/ecs/ContactProbe.d.ts.map +1 -0
- package/src/engine/physics/ecs/ContactProbe.js +103 -0
- package/src/engine/physics/ecs/ContactProbeSystem.d.ts +44 -0
- package/src/engine/physics/ecs/ContactProbeSystem.d.ts.map +1 -0
- package/src/engine/physics/ecs/ContactProbeSystem.js +397 -0
- package/src/engine/physics/fluid/ecs/FluidComponent.d.ts +1 -1
- package/src/engine/physics/fluid/ecs/FluidComponent.js +1 -1
- package/src/engine/physics/fluid/ecs/FluidObstacleSystem.d.ts +4 -4
- package/src/engine/physics/inverse_kinematics/frame_fabrik/FrameFabrikProblem.d.ts +205 -0
- package/src/engine/physics/inverse_kinematics/frame_fabrik/FrameFabrikProblem.d.ts.map +1 -0
- package/src/engine/physics/inverse_kinematics/frame_fabrik/FrameFabrikProblem.js +245 -0
- package/src/engine/physics/inverse_kinematics/frame_fabrik/frame_fabrik_limit_from_joint.d.ts +21 -0
- package/src/engine/physics/inverse_kinematics/frame_fabrik/frame_fabrik_limit_from_joint.d.ts.map +1 -0
- package/src/engine/physics/inverse_kinematics/frame_fabrik/frame_fabrik_limit_from_joint.js +63 -0
- package/src/engine/physics/inverse_kinematics/frame_fabrik/frame_fabrik_solve.d.ts +33 -0
- package/src/engine/physics/inverse_kinematics/frame_fabrik/frame_fabrik_solve.d.ts.map +1 -0
- package/src/engine/physics/inverse_kinematics/frame_fabrik/frame_fabrik_solve.js +874 -0
- package/src/engine/physics/inverse_kinematics/joint_hinge_mid_axis.d.ts +27 -0
- package/src/engine/physics/inverse_kinematics/joint_hinge_mid_axis.d.ts.map +1 -0
- package/src/engine/physics/inverse_kinematics/joint_hinge_mid_axis.js +77 -0
- package/src/engine/physics/inverse_kinematics/two_bone_path_solve.d.ts +36 -0
- package/src/engine/physics/inverse_kinematics/two_bone_path_solve.d.ts.map +1 -0
- package/src/engine/physics/inverse_kinematics/two_bone_path_solve.js +97 -0
- package/src/engine/physics/inverse_kinematics/two_joint_ik.d.ts +33 -11
- package/src/engine/physics/inverse_kinematics/two_joint_ik.d.ts.map +1 -1
- package/src/engine/physics/inverse_kinematics/two_joint_ik.js +158 -67
- package/src/shade/renderer/animation/ANIMATION_TRACK_BINDING_STRUCT.d.ts.map +1 -1
- package/src/shade/renderer/animation/ANIMATION_TRACK_BINDING_STRUCT.js +10 -1
- package/src/shade/renderer/animation/GPUAnimationManager.d.ts +10 -4
- package/src/shade/renderer/animation/GPUAnimationManager.d.ts.map +1 -1
- package/src/shade/renderer/animation/GPUAnimationManager.js +251 -23
- package/src/shade/renderer/animation/POSE_ACCUMULATOR_LAYOUT.d.ts +12 -4
- package/src/shade/renderer/animation/POSE_ACCUMULATOR_LAYOUT.d.ts.map +1 -1
- package/src/shade/renderer/animation/POSE_ACCUMULATOR_LAYOUT.js +12 -4
- package/src/shade/renderer/animation/chunk_pose_accumulator.d.ts.map +1 -1
- package/src/shade/renderer/animation/chunk_pose_accumulator.js +14 -1
- package/src/shade/renderer/animation/pose/PoseChannelIndex.d.ts +78 -0
- package/src/shade/renderer/animation/pose/PoseChannelIndex.d.ts.map +1 -0
- package/src/shade/renderer/animation/pose/PoseChannelIndex.js +274 -0
- package/src/shade/renderer/animation/pose/PosePlayback.d.ts +40 -3
- package/src/shade/renderer/animation/pose/PosePlayback.d.ts.map +1 -1
- package/src/shade/renderer/animation/pose/PosePlayback.js +77 -3
- package/src/shade/renderer/animation/pose/pose_evaluate_local.d.ts +21 -3
- package/src/shade/renderer/animation/pose/pose_evaluate_local.d.ts.map +1 -1
- package/src/shade/renderer/animation/pose/pose_evaluate_local.js +79 -11
- package/src/shade/renderer/animation/shader_animation_apply.js +1 -1
- package/src/shade/renderer/animation/shader_animation_pose_resolve.d.ts +3 -2
- package/src/shade/renderer/animation/shader_animation_pose_resolve.d.ts.map +1 -1
- package/src/shade/renderer/animation/shader_animation_pose_resolve.js +187 -176
- package/src/shade/renderer/loader/gltf/tiny-gltf.d.ts +1 -1
- package/src/shade/renderer/object_property/chunk_write_object_property.d.ts +3 -1
- package/src/shade/renderer/object_property/chunk_write_object_property.d.ts.map +1 -1
- package/src/shade/renderer/object_property/chunk_write_object_property.js +5 -2
- package/src/shade/renderer/object_property/chunk_write_object_property_node3d.d.ts +4 -0
- package/src/shade/renderer/object_property/chunk_write_object_property_node3d.d.ts.map +1 -1
- package/src/shade/renderer/object_property/chunk_write_object_property_node3d.js +6 -1
- package/src/shade/renderer/postprocess/nss/NSS.d.ts +1 -1
- package/src/shade/renderer/scene/optimization/geometry/geometry_deduplicate.d.ts +1 -1
- package/src/shade/renderer/scene/optimization/geometry/geometry_deduplicate.d.ts.map +1 -1
- package/src/shade/renderer/scene/optimization/geometry/geometry_deduplicate.js +135 -73
- package/src/shade/renderer/scene/optimization/geometry/internal/cover_greedy.d.ts +7 -0
- package/src/shade/renderer/scene/optimization/geometry/internal/cover_greedy.d.ts.map +1 -1
- package/src/shade/renderer/scene/optimization/geometry/internal/cover_greedy.js +29 -1
- package/src/shade/renderer/scene/optimization/geometry/internal/face_neighbourhood_hash.d.ts +19 -0
- package/src/shade/renderer/scene/optimization/geometry/internal/face_neighbourhood_hash.d.ts.map +1 -0
- package/src/shade/renderer/scene/optimization/geometry/internal/face_neighbourhood_hash.js +167 -0
- package/src/shade/renderer/scene/optimization/geometry/internal/neighbourhood_anchor_index.d.ts +25 -0
- package/src/shade/renderer/scene/optimization/geometry/internal/neighbourhood_anchor_index.d.ts.map +1 -0
- package/src/shade/renderer/scene/optimization/geometry/internal/neighbourhood_anchor_index.js +124 -0
- package/src/shade/renderer/scene/optimization/geometry/internal/seed_correspondence_from_face_pair.d.ts +19 -0
- package/src/shade/renderer/scene/optimization/geometry/internal/seed_correspondence_from_face_pair.d.ts.map +1 -0
- package/src/shade/renderer/scene/optimization/geometry/internal/seed_correspondence_from_face_pair.js +48 -0
- package/src/shade/renderer/scene/rows/GPUSceneRows.d.ts +17 -1
- package/src/shade/renderer/scene/rows/GPUSceneRows.d.ts.map +1 -1
- package/src/shade/renderer/scene/rows/GPUSceneRows.js +29 -3
- package/src/engine/ecs/ik/IKConstraint.d.ts +0 -52
- package/src/engine/ecs/ik/IKConstraint.d.ts.map +0 -1
- package/src/engine/ecs/ik/IKConstraint.js +0 -95
- package/src/engine/ecs/ik/IKProblem.d.ts +0 -45
- package/src/engine/ecs/ik/IKProblem.d.ts.map +0 -1
- package/src/engine/ecs/ik/IKProblem.js +0 -58
- package/src/engine/ecs/ik/IKSolver.d.ts +0 -8
- package/src/engine/ecs/ik/IKSolver.d.ts.map +0 -1
- package/src/engine/ecs/ik/IKSolver.js +0 -9
- package/src/engine/ecs/ik/InverseKinematics.d.ts +0 -48
- package/src/engine/ecs/ik/InverseKinematics.d.ts.map +0 -1
- package/src/engine/ecs/ik/InverseKinematics.js +0 -119
- package/src/engine/ecs/ik/InverseKinematicsSerializationAdapter.d.ts +0 -19
- package/src/engine/ecs/ik/InverseKinematicsSerializationAdapter.d.ts.map +0 -1
- package/src/engine/ecs/ik/InverseKinematicsSerializationAdapter.js +0 -72
- package/src/engine/ecs/ik/InverseKinematicsSystem.d.ts +0 -54
- package/src/engine/ecs/ik/InverseKinematicsSystem.d.ts.map +0 -1
- package/src/engine/ecs/ik/InverseKinematicsSystem.js +0 -212
- package/src/engine/ecs/ik/OneBoneSurfaceAlignmentSolver.d.ts +0 -8
- package/src/engine/ecs/ik/OneBoneSurfaceAlignmentSolver.d.ts.map +0 -1
- package/src/engine/ecs/ik/OneBoneSurfaceAlignmentSolver.js +0 -153
- package/src/engine/ecs/ik/TwoBoneInverseKinematicsSolver.d.ts +0 -5
- package/src/engine/ecs/ik/TwoBoneInverseKinematicsSolver.d.ts.map +0 -1
- package/src/engine/ecs/ik/TwoBoneInverseKinematicsSolver.js +0 -120
- package/src/engine/ecs/ik/ik_bone_chain.d.ts +0 -64
- package/src/engine/ecs/ik/ik_bone_chain.d.ts.map +0 -1
- package/src/engine/ecs/ik/ik_bone_chain.js +0 -142
- package/src/engine/ecs/ik/ik_probe_terrain_contact.d.ts +0 -42
- package/src/engine/ecs/ik/ik_probe_terrain_contact.d.ts.map +0 -1
- package/src/engine/ecs/ik/ik_probe_terrain_contact.js +0 -123
- package/src/engine/ecs/ik/ik_write_local_rotation.d.ts +0 -18
- package/src/engine/ecs/ik/ik_write_local_rotation.d.ts.map +0 -1
- package/src/engine/ecs/ik/ik_write_local_rotation.js +0 -32
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
import { UINT32_MAP_ABSENT, Uint32Map } from "../../../../core/collection/Uint32Map.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @typedef {import("../ShadeAnimationChannel.js").ShadeAnimationChannel} ShadeAnimationChannel
|
|
5
|
+
* @typedef {import("../ShadeAnimationClip.js").ShadeAnimationClip} ShadeAnimationClip
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* What {@link PoseChannelIndex#group_of} answers for an entity the clip has no track for.
|
|
10
|
+
*
|
|
11
|
+
* @type {number}
|
|
12
|
+
*/
|
|
13
|
+
export const POSE_CHANNEL_GROUP_NONE = UINT32_MAP_ABSENT;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* The largest entity {@link Uint32Map} can hold as a key; it stores a key as `key + 1`.
|
|
17
|
+
*
|
|
18
|
+
* @type {number}
|
|
19
|
+
*/
|
|
20
|
+
const LARGEST_ENTITY = 0xFFFFFFFE;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Each clip's index, built the first time a playback takes the clip and shared by every playback
|
|
24
|
+
* that takes it after — a released joint's seed takes the same clip once per joint.
|
|
25
|
+
*
|
|
26
|
+
* @type {WeakMap<ShadeAnimationClip, PoseChannelIndex>}
|
|
27
|
+
*/
|
|
28
|
+
const INDICES = new WeakMap();
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Per-group cursors for {@link PoseChannelIndex#holds}, grown to the largest clip seen.
|
|
32
|
+
*
|
|
33
|
+
* @type {Uint32Array}
|
|
34
|
+
*/
|
|
35
|
+
let CURSORS = new Uint32Array(64);
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Whether a channel names an entity — the only thing the renderer binds a channel to, so a channel
|
|
39
|
+
* naming anything else plays on nothing there either.
|
|
40
|
+
*
|
|
41
|
+
* @param {*} target
|
|
42
|
+
* @returns {boolean}
|
|
43
|
+
*/
|
|
44
|
+
function is_entity(target) {
|
|
45
|
+
return Number.isInteger(target) && target >= 0 && target <= LARGEST_ENTITY;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* A clip's channels grouped by the entity each names, for the CPU pose twin to look one entity up
|
|
50
|
+
* in, rather than scan the whole clip for it once per level of every query.
|
|
51
|
+
*
|
|
52
|
+
* The groups are laid end to end in one array, each in the clip's own order, with a table of where
|
|
53
|
+
* each starts and a {@link Uint32Map} from entity to group: on a 70-joint clip of 210 channels it
|
|
54
|
+
* holds a few KiB, against the ~25 KiB of the clip itself, and nothing in it is handed out to be
|
|
55
|
+
* written. Read it with {@link group_of}, {@link first}, {@link end} and {@link channel}.
|
|
56
|
+
*
|
|
57
|
+
* Built once per clip ({@link of}) and checked, not rebuilt, each time a playback takes the clip
|
|
58
|
+
* again.
|
|
59
|
+
*
|
|
60
|
+
* @author Alex Goldring
|
|
61
|
+
* @copyright Company Named Limited (c) 2026
|
|
62
|
+
*/
|
|
63
|
+
export class PoseChannelIndex {
|
|
64
|
+
/**
|
|
65
|
+
* Entity → group.
|
|
66
|
+
*
|
|
67
|
+
* @type {Uint32Map}
|
|
68
|
+
*/
|
|
69
|
+
#groups;
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Group `g` is `#channels[#starts[g] .. #starts[g + 1])`.
|
|
73
|
+
*
|
|
74
|
+
* @type {Uint32Array}
|
|
75
|
+
*/
|
|
76
|
+
#starts;
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* The clip's channels that name an entity, grouped by it, each group in the clip's order.
|
|
80
|
+
*
|
|
81
|
+
* @type {ShadeAnimationChannel[]}
|
|
82
|
+
*/
|
|
83
|
+
#channels;
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* How many channels the clip held when this was built, those naming no entity included.
|
|
87
|
+
*
|
|
88
|
+
* @type {number}
|
|
89
|
+
*/
|
|
90
|
+
#source_count;
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* @param {ShadeAnimationChannel[]} channels a clip's channels
|
|
94
|
+
*/
|
|
95
|
+
constructor(channels) {
|
|
96
|
+
const count = channels.length;
|
|
97
|
+
const groups = new Uint32Map(8);
|
|
98
|
+
const sizes = [];
|
|
99
|
+
|
|
100
|
+
let indexed = 0;
|
|
101
|
+
|
|
102
|
+
for (let i = 0; i < count; i++) {
|
|
103
|
+
const target = channels[i].target;
|
|
104
|
+
|
|
105
|
+
if (!is_entity(target)) {
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
let group = groups.get(target);
|
|
110
|
+
|
|
111
|
+
if (group === UINT32_MAP_ABSENT) {
|
|
112
|
+
group = sizes.length;
|
|
113
|
+
|
|
114
|
+
groups.set(target, group);
|
|
115
|
+
sizes.push(0);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
sizes[group]++;
|
|
119
|
+
indexed++;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const group_count = sizes.length;
|
|
123
|
+
const starts = new Uint32Array(group_count + 1);
|
|
124
|
+
|
|
125
|
+
for (let g = 0; g < group_count; g++) {
|
|
126
|
+
starts[g + 1] = starts[g] + sizes[g];
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// a counting sort: each channel's slot, walking the clip in order so each group keeps it
|
|
130
|
+
const slots = new Uint32Array(indexed);
|
|
131
|
+
const cursors = starts.slice(0, group_count);
|
|
132
|
+
|
|
133
|
+
for (let i = 0; i < count; i++) {
|
|
134
|
+
const target = channels[i].target;
|
|
135
|
+
|
|
136
|
+
if (is_entity(target)) {
|
|
137
|
+
slots[cursors[groups.get(target)]++] = i;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const grouped = [];
|
|
142
|
+
|
|
143
|
+
for (let s = 0; s < indexed; s++) {
|
|
144
|
+
grouped.push(channels[slots[s]]);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
this.#groups = groups;
|
|
148
|
+
this.#starts = starts;
|
|
149
|
+
this.#channels = grouped;
|
|
150
|
+
this.#source_count = count;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* The group of channels naming `entity`, or {@link POSE_CHANNEL_GROUP_NONE} where the clip has no
|
|
155
|
+
* track for it — which is most entities for most clips.
|
|
156
|
+
*
|
|
157
|
+
* @param {number} entity
|
|
158
|
+
* @returns {number}
|
|
159
|
+
*/
|
|
160
|
+
group_of(entity) {
|
|
161
|
+
return this.#groups.get(entity);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* @param {number} group
|
|
166
|
+
* @returns {number} the position of the group's first channel
|
|
167
|
+
*/
|
|
168
|
+
first(group) {
|
|
169
|
+
return this.#starts[group];
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* @param {number} group
|
|
174
|
+
* @returns {number} one past the position of the group's last channel
|
|
175
|
+
*/
|
|
176
|
+
end(group) {
|
|
177
|
+
return this.#starts[group + 1];
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* @param {number} position from {@link first} up to {@link end}
|
|
182
|
+
* @returns {ShadeAnimationChannel}
|
|
183
|
+
*/
|
|
184
|
+
channel(position) {
|
|
185
|
+
return this.#channels[position];
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Whether this still describes `channels`: every channel naming an entity is where this put it
|
|
190
|
+
* — the same channel, naming the same entity, in the same order among that entity's — and there
|
|
191
|
+
* are as many as there were. A walk of the clip without allocation.
|
|
192
|
+
*
|
|
193
|
+
* @param {ShadeAnimationChannel[]} channels
|
|
194
|
+
* @returns {boolean}
|
|
195
|
+
*/
|
|
196
|
+
holds(channels) {
|
|
197
|
+
const count = channels.length;
|
|
198
|
+
|
|
199
|
+
if (count !== this.#source_count) {
|
|
200
|
+
return false;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const starts = this.#starts;
|
|
204
|
+
const indexed = this.#channels;
|
|
205
|
+
const groups = this.#groups;
|
|
206
|
+
const group_count = starts.length - 1;
|
|
207
|
+
|
|
208
|
+
if (CURSORS.length < group_count) {
|
|
209
|
+
CURSORS = new Uint32Array(group_count);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const cursors = CURSORS;
|
|
213
|
+
|
|
214
|
+
cursors.fill(0, 0, group_count);
|
|
215
|
+
|
|
216
|
+
let matched = 0;
|
|
217
|
+
|
|
218
|
+
for (let i = 0; i < count; i++) {
|
|
219
|
+
const channel = channels[i];
|
|
220
|
+
const target = channel.target;
|
|
221
|
+
|
|
222
|
+
if (!is_entity(target)) {
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const group = groups.get(target);
|
|
227
|
+
|
|
228
|
+
if (group === UINT32_MAP_ABSENT) {
|
|
229
|
+
return false;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const slot = starts[group] + cursors[group];
|
|
233
|
+
|
|
234
|
+
if (slot >= starts[group + 1] || indexed[slot] !== channel) {
|
|
235
|
+
return false;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
cursors[group]++;
|
|
239
|
+
matched++;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
return matched === indexed.length;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* The clip's index, as the clip holds its channels now: the one built before when it still
|
|
247
|
+
* holds, a new one otherwise.
|
|
248
|
+
*
|
|
249
|
+
* @param {ShadeAnimationClip} clip
|
|
250
|
+
* @returns {PoseChannelIndex}
|
|
251
|
+
*/
|
|
252
|
+
static of(clip) {
|
|
253
|
+
const channels = clip.channels;
|
|
254
|
+
const cached = INDICES.get(clip);
|
|
255
|
+
|
|
256
|
+
if (cached !== undefined && cached.holds(channels)) {
|
|
257
|
+
return cached;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const index = new PoseChannelIndex(channels);
|
|
261
|
+
|
|
262
|
+
INDICES.set(clip, index);
|
|
263
|
+
|
|
264
|
+
return index;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* What a playback with no clip holds: no groups at all.
|
|
270
|
+
*
|
|
271
|
+
* @readonly
|
|
272
|
+
* @type {PoseChannelIndex}
|
|
273
|
+
*/
|
|
274
|
+
PoseChannelIndex.EMPTY = new PoseChannelIndex([]);
|
|
@@ -17,11 +17,46 @@ export class PosePlayback {
|
|
|
17
17
|
*/
|
|
18
18
|
static from(clip: ShadeAnimationClip, time: number, weight: number): PosePlayback;
|
|
19
19
|
/**
|
|
20
|
-
* @
|
|
20
|
+
* Assigning a clip reads its {@link start_time} once, here: `ShadeAnimationClip.start_time`
|
|
21
|
+
* walks every channel and every curve, and the evaluator runs per node of every query — reading
|
|
22
|
+
* it there tripled the cost of a whole-skeleton pass. Assigning the clip already held reads
|
|
23
|
+
* nothing, so a producer that refreshes its playbacks per query pays for it once per clip.
|
|
24
|
+
*
|
|
25
|
+
* It reads the clip's channels here too, grouped by the entity each names
|
|
26
|
+
* ({@link channel_index}), for the same reason: the evaluator asks once per level of every
|
|
27
|
+
* query, and scanning the whole clip for the few channels of one joint made its cost grow with
|
|
28
|
+
* every clip a model has bound. The grouping is built once per clip and shared, and taking the
|
|
29
|
+
* clip again only checks that it still holds.
|
|
30
|
+
*
|
|
31
|
+
* A clip's channels are taken as settled once it plays, as the renderer takes them when it
|
|
32
|
+
* registers the clip.
|
|
33
|
+
*
|
|
34
|
+
* @param {ShadeAnimationClip|null} clip
|
|
35
|
+
*/
|
|
36
|
+
set clip(clip: any);
|
|
37
|
+
/**
|
|
38
|
+
* @returns {ShadeAnimationClip|null}
|
|
39
|
+
*/
|
|
40
|
+
get clip(): any;
|
|
41
|
+
/**
|
|
42
|
+
* The clip's channels grouped by the entity each names, as they were when the clip was assigned;
|
|
43
|
+
* {@link PoseChannelIndex.EMPTY} while there is no clip.
|
|
44
|
+
*
|
|
45
|
+
* @returns {PoseChannelIndex}
|
|
46
|
+
*/
|
|
47
|
+
get channel_index(): PoseChannelIndex;
|
|
48
|
+
/**
|
|
49
|
+
* Where {@link time} 0 sits on the clip's curves: its earliest key. The renderer's clip record
|
|
50
|
+
* takes the same origin at registration (`time_start`), which is what has the two sample one
|
|
51
|
+
* instant — a glTF clip keeps its file's key times, and its first key need not be at zero.
|
|
52
|
+
*
|
|
53
|
+
* @returns {number}
|
|
21
54
|
*/
|
|
22
|
-
|
|
55
|
+
get start_time(): number;
|
|
23
56
|
/**
|
|
24
|
-
* Seconds into the clip
|
|
57
|
+
* Seconds into the clip, from its {@link start_time} — the renderer's clip record measures its
|
|
58
|
+
* `time` the same way, and both players run their clocks over `[0, duration]`. Not wrapped for
|
|
59
|
+
* you — a caller that loops decides what looping means.
|
|
25
60
|
*
|
|
26
61
|
* @type {number}
|
|
27
62
|
*/
|
|
@@ -36,5 +71,7 @@ export class PosePlayback {
|
|
|
36
71
|
* @type {number}
|
|
37
72
|
*/
|
|
38
73
|
weight: number;
|
|
74
|
+
#private;
|
|
39
75
|
}
|
|
76
|
+
import { PoseChannelIndex } from "./PoseChannelIndex.js";
|
|
40
77
|
//# sourceMappingURL=PosePlayback.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PosePlayback.d.ts","sourceRoot":"","sources":["../../../../../../src/shade/renderer/animation/pose/PosePlayback.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"PosePlayback.d.ts","sourceRoot":"","sources":["../../../../../../src/shade/renderer/animation/pose/PosePlayback.js"],"names":[],"mappings":"AAGA;;;;;;;;;GASG;AACH;IAiGI;;;;;OAKG;IACH,4CAJW,MAAM,UACN,MAAM,GACJ,YAAY,CAcxB;IA5FD;;;;;;;;;;;;;;;;OAgBG;IACH,oBAcC;IAtCD;;OAEG;IACH,gBAEC;IAmCD;;;;;OAKG;IACH,sCAEC;IAED;;;;;;OAMG;IACH,yBAEC;IAED;;;;;;OAMG;IACH,MAFU,MAAM,CAEP;IAET;;;;;;;;OAQG;IACH,QAFU,MAAM,CAEL;;CAqBd;iCAhIgC,uBAAuB"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { assert } from "../../../../core/assert.js";
|
|
2
|
+
import { PoseChannelIndex } from "./PoseChannelIndex.js";
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* One clip contributing to a pose: which clip, where in it, and how much of it.
|
|
@@ -12,12 +13,85 @@ import { assert } from "../../../../core/assert.js";
|
|
|
12
13
|
*/
|
|
13
14
|
export class PosePlayback {
|
|
14
15
|
/**
|
|
15
|
-
* @type {ShadeAnimationClip}
|
|
16
|
+
* @type {ShadeAnimationClip|null}
|
|
16
17
|
*/
|
|
17
|
-
clip = null;
|
|
18
|
+
#clip = null;
|
|
18
19
|
|
|
19
20
|
/**
|
|
20
|
-
*
|
|
21
|
+
* @type {number}
|
|
22
|
+
*/
|
|
23
|
+
#start_time = 0;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @type {PoseChannelIndex}
|
|
27
|
+
*/
|
|
28
|
+
#channel_index = PoseChannelIndex.EMPTY;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @returns {ShadeAnimationClip|null}
|
|
32
|
+
*/
|
|
33
|
+
get clip() {
|
|
34
|
+
return this.#clip;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Assigning a clip reads its {@link start_time} once, here: `ShadeAnimationClip.start_time`
|
|
39
|
+
* walks every channel and every curve, and the evaluator runs per node of every query — reading
|
|
40
|
+
* it there tripled the cost of a whole-skeleton pass. Assigning the clip already held reads
|
|
41
|
+
* nothing, so a producer that refreshes its playbacks per query pays for it once per clip.
|
|
42
|
+
*
|
|
43
|
+
* It reads the clip's channels here too, grouped by the entity each names
|
|
44
|
+
* ({@link channel_index}), for the same reason: the evaluator asks once per level of every
|
|
45
|
+
* query, and scanning the whole clip for the few channels of one joint made its cost grow with
|
|
46
|
+
* every clip a model has bound. The grouping is built once per clip and shared, and taking the
|
|
47
|
+
* clip again only checks that it still holds.
|
|
48
|
+
*
|
|
49
|
+
* A clip's channels are taken as settled once it plays, as the renderer takes them when it
|
|
50
|
+
* registers the clip.
|
|
51
|
+
*
|
|
52
|
+
* @param {ShadeAnimationClip|null} clip
|
|
53
|
+
*/
|
|
54
|
+
set clip(clip) {
|
|
55
|
+
if (clip === this.#clip) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
this.#clip = clip;
|
|
60
|
+
|
|
61
|
+
if (clip === null) {
|
|
62
|
+
this.#start_time = 0;
|
|
63
|
+
this.#channel_index = PoseChannelIndex.EMPTY;
|
|
64
|
+
} else {
|
|
65
|
+
this.#start_time = clip.start_time;
|
|
66
|
+
this.#channel_index = PoseChannelIndex.of(clip);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* The clip's channels grouped by the entity each names, as they were when the clip was assigned;
|
|
72
|
+
* {@link PoseChannelIndex.EMPTY} while there is no clip.
|
|
73
|
+
*
|
|
74
|
+
* @returns {PoseChannelIndex}
|
|
75
|
+
*/
|
|
76
|
+
get channel_index() {
|
|
77
|
+
return this.#channel_index;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Where {@link time} 0 sits on the clip's curves: its earliest key. The renderer's clip record
|
|
82
|
+
* takes the same origin at registration (`time_start`), which is what has the two sample one
|
|
83
|
+
* instant — a glTF clip keeps its file's key times, and its first key need not be at zero.
|
|
84
|
+
*
|
|
85
|
+
* @returns {number}
|
|
86
|
+
*/
|
|
87
|
+
get start_time() {
|
|
88
|
+
return this.#start_time;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Seconds into the clip, from its {@link start_time} — the renderer's clip record measures its
|
|
93
|
+
* `time` the same way, and both players run their clocks over `[0, duration]`. Not wrapped for
|
|
94
|
+
* you — a caller that loops decides what looping means.
|
|
21
95
|
*
|
|
22
96
|
* @type {number}
|
|
23
97
|
*/
|
|
@@ -6,26 +6,44 @@
|
|
|
6
6
|
* into a per-row slot (`POSE_ACCUMULATOR_LAYOUT`) and then drains it
|
|
7
7
|
* (`shader_animation_pose_resolve`); the rules reproduced here are that drain's, exactly:
|
|
8
8
|
*
|
|
9
|
+
* - **A clip is sampled at its start plus its time** — {@link PosePlayback.start_time}, its earliest
|
|
10
|
+
* key, is the apply pass's `clip.time_start`.
|
|
9
11
|
* - **A channel that contributed replaces its axis; one that did not leaves the rest pose alone.**
|
|
10
12
|
* Translation and scale track this per axis, because a channel may animate only one of them.
|
|
11
13
|
* - **Rotation is a weighted sum, normalised once at the end** — nlerp, not slerp, and normalising
|
|
12
14
|
* is what turns the sum back into a rotation. It is tracked with a single dirty bit because all
|
|
13
15
|
* four components always move together.
|
|
14
16
|
* - **Weights are not normalised.** See {@link PosePlayback.weight}.
|
|
17
|
+
* - **A clip at weight zero still writes**: the accumulator takes a bound clip's contribution
|
|
18
|
+
* whatever its weight, so the axes it keys resolve to the sum of what was added. It adds its value
|
|
19
|
+
* times zero, which leaves any finite sum where it was, so here its curves are not sampled — only
|
|
20
|
+
* its axes are marked. The two part only where a silent clip's curve would sample to something
|
|
21
|
+
* that is not a finite number — an infinite key, or keys whose interpolation overflows — which
|
|
22
|
+
* the GPU multiplies by zero for no number at all, and which adds nothing here.
|
|
15
23
|
*
|
|
16
24
|
* Where the two disagree, the GPU is right by definition — it is what is on screen — and this is
|
|
17
25
|
* the thing to fix. `pose_evaluator_lockstep.spec.js` fails when the WGSL moves, so that the pair
|
|
18
26
|
* cannot drift silently.
|
|
19
27
|
*
|
|
28
|
+
* A call costs a lookup per playback and the curves of the channels that name `target` in the
|
|
29
|
+
* audible ones — not a scan of every clip: each playback holds its clip's channels grouped by
|
|
30
|
+
* entity ({@link PosePlayback#channel_index}), read once when it took the clip. A model with many
|
|
31
|
+
* clips bound and few playing pays little more than one with only the few.
|
|
32
|
+
*
|
|
20
33
|
* `rest` supplies the rest pose: the node's or the entity's authored local offset. That is sound
|
|
21
34
|
* because the CPU never writes a GPU-authoritative row, so what the authored offset holds is what
|
|
22
|
-
* the row started from, and undirty axes on the GPU hold the same value for the same reason.
|
|
35
|
+
* the row started from, and undirty axes on the GPU hold the same value for the same reason. A CPU
|
|
36
|
+
* writer's hold is how the offset may move under a bound clip, and its end hands the offset the
|
|
37
|
+
* holder left to the row (`GPUSceneRows#mark_handover`) and to every binding as its rest
|
|
38
|
+
* (`GPUAnimationManager#unmute`), so the same holds after it. Anything else writing a driven
|
|
39
|
+
* joint's offset breaks this, and the twin then answers a rest the GPU does not have.
|
|
23
40
|
*
|
|
24
41
|
* @param {Transform64} result written with the evaluated local TRS; its matrix is left stale
|
|
25
|
-
* @param {
|
|
42
|
+
* @param {number} target the entity the channels name
|
|
26
43
|
* @param {Transform64} rest the target's authored local pose
|
|
27
44
|
* @param {PosePlayback[]} playbacks
|
|
28
45
|
* @returns {Transform64} `result`
|
|
29
46
|
*/
|
|
30
|
-
export function pose_evaluate_local(result: Transform64, target:
|
|
47
|
+
export function pose_evaluate_local(result: Transform64, target: number, rest: Transform64, playbacks: PosePlayback[]): Transform64;
|
|
48
|
+
export type ShadeAnimationChannel = import("../ShadeAnimationChannel.js").ShadeAnimationChannel;
|
|
31
49
|
//# sourceMappingURL=pose_evaluate_local.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pose_evaluate_local.d.ts","sourceRoot":"","sources":["../../../../../../src/shade/renderer/animation/pose/pose_evaluate_local.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"pose_evaluate_local.d.ts","sourceRoot":"","sources":["../../../../../../src/shade/renderer/animation/pose/pose_evaluate_local.js"],"names":[],"mappings":"AAqDA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AACH,iEALW,MAAM,gCAEN,cAAc,eAsJxB;oCAjPY,OAAO,6BAA6B,EAAE,qBAAqB"}
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { assert } from "../../../../core/assert.js";
|
|
2
2
|
import { Node3DProperty } from "../../object_property/Node3DProperty.js";
|
|
3
|
+
import { POSE_CHANNEL_GROUP_NONE } from "./PoseChannelIndex.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @typedef {import("../ShadeAnimationChannel.js").ShadeAnimationChannel} ShadeAnimationChannel
|
|
7
|
+
*/
|
|
3
8
|
|
|
4
9
|
/**
|
|
5
10
|
* Per-axis accumulators, reused across calls. The evaluator is a query on a gameplay path and can
|
|
@@ -17,6 +22,35 @@ const DIRTY_SX = 16;
|
|
|
17
22
|
const DIRTY_SY = 32;
|
|
18
23
|
const DIRTY_SZ = 64;
|
|
19
24
|
|
|
25
|
+
/**
|
|
26
|
+
* What a channel writes, as dirty bits: each translation or scale axis it has a curve for, and the
|
|
27
|
+
* rotation whole — what the accumulator marks for it at any weight.
|
|
28
|
+
*
|
|
29
|
+
* @param {ShadeAnimationChannel} channel
|
|
30
|
+
* @returns {number}
|
|
31
|
+
*/
|
|
32
|
+
function channel_dirty_bits(channel) {
|
|
33
|
+
const curves = channel.curves;
|
|
34
|
+
|
|
35
|
+
switch (channel.property) {
|
|
36
|
+
case Node3DProperty.Translation:
|
|
37
|
+
return (curves.x !== undefined ? DIRTY_TX : 0)
|
|
38
|
+
| (curves.y !== undefined ? DIRTY_TY : 0)
|
|
39
|
+
| (curves.z !== undefined ? DIRTY_TZ : 0);
|
|
40
|
+
|
|
41
|
+
case Node3DProperty.Rotation:
|
|
42
|
+
return DIRTY_R;
|
|
43
|
+
|
|
44
|
+
case Node3DProperty.Scale:
|
|
45
|
+
return (curves.x !== undefined ? DIRTY_SX : 0)
|
|
46
|
+
| (curves.y !== undefined ? DIRTY_SY : 0)
|
|
47
|
+
| (curves.z !== undefined ? DIRTY_SZ : 0);
|
|
48
|
+
|
|
49
|
+
default:
|
|
50
|
+
return 0;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
20
54
|
/**
|
|
21
55
|
* Evaluate one node's local pose from a set of playing clips, into `result`.
|
|
22
56
|
*
|
|
@@ -25,23 +59,40 @@ const DIRTY_SZ = 64;
|
|
|
25
59
|
* into a per-row slot (`POSE_ACCUMULATOR_LAYOUT`) and then drains it
|
|
26
60
|
* (`shader_animation_pose_resolve`); the rules reproduced here are that drain's, exactly:
|
|
27
61
|
*
|
|
62
|
+
* - **A clip is sampled at its start plus its time** — {@link PosePlayback.start_time}, its earliest
|
|
63
|
+
* key, is the apply pass's `clip.time_start`.
|
|
28
64
|
* - **A channel that contributed replaces its axis; one that did not leaves the rest pose alone.**
|
|
29
65
|
* Translation and scale track this per axis, because a channel may animate only one of them.
|
|
30
66
|
* - **Rotation is a weighted sum, normalised once at the end** — nlerp, not slerp, and normalising
|
|
31
67
|
* is what turns the sum back into a rotation. It is tracked with a single dirty bit because all
|
|
32
68
|
* four components always move together.
|
|
33
69
|
* - **Weights are not normalised.** See {@link PosePlayback.weight}.
|
|
70
|
+
* - **A clip at weight zero still writes**: the accumulator takes a bound clip's contribution
|
|
71
|
+
* whatever its weight, so the axes it keys resolve to the sum of what was added. It adds its value
|
|
72
|
+
* times zero, which leaves any finite sum where it was, so here its curves are not sampled — only
|
|
73
|
+
* its axes are marked. The two part only where a silent clip's curve would sample to something
|
|
74
|
+
* that is not a finite number — an infinite key, or keys whose interpolation overflows — which
|
|
75
|
+
* the GPU multiplies by zero for no number at all, and which adds nothing here.
|
|
34
76
|
*
|
|
35
77
|
* Where the two disagree, the GPU is right by definition — it is what is on screen — and this is
|
|
36
78
|
* the thing to fix. `pose_evaluator_lockstep.spec.js` fails when the WGSL moves, so that the pair
|
|
37
79
|
* cannot drift silently.
|
|
38
80
|
*
|
|
81
|
+
* A call costs a lookup per playback and the curves of the channels that name `target` in the
|
|
82
|
+
* audible ones — not a scan of every clip: each playback holds its clip's channels grouped by
|
|
83
|
+
* entity ({@link PosePlayback#channel_index}), read once when it took the clip. A model with many
|
|
84
|
+
* clips bound and few playing pays little more than one with only the few.
|
|
85
|
+
*
|
|
39
86
|
* `rest` supplies the rest pose: the node's or the entity's authored local offset. That is sound
|
|
40
87
|
* because the CPU never writes a GPU-authoritative row, so what the authored offset holds is what
|
|
41
|
-
* the row started from, and undirty axes on the GPU hold the same value for the same reason.
|
|
88
|
+
* the row started from, and undirty axes on the GPU hold the same value for the same reason. A CPU
|
|
89
|
+
* writer's hold is how the offset may move under a bound clip, and its end hands the offset the
|
|
90
|
+
* holder left to the row (`GPUSceneRows#mark_handover`) and to every binding as its rest
|
|
91
|
+
* (`GPUAnimationManager#unmute`), so the same holds after it. Anything else writing a driven
|
|
92
|
+
* joint's offset breaks this, and the twin then answers a rest the GPU does not have.
|
|
42
93
|
*
|
|
43
94
|
* @param {Transform64} result written with the evaluated local TRS; its matrix is left stale
|
|
44
|
-
* @param {
|
|
95
|
+
* @param {number} target the entity the channels name
|
|
45
96
|
* @param {Transform64} rest the target's authored local pose
|
|
46
97
|
* @param {PosePlayback[]} playbacks
|
|
47
98
|
* @returns {Transform64} `result`
|
|
@@ -69,17 +120,33 @@ export function pose_evaluate_local(result, target, rest, playbacks) {
|
|
|
69
120
|
|
|
70
121
|
for (let p = 0; p < playbacks.length; p++) {
|
|
71
122
|
const playback = playbacks[p];
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
const
|
|
123
|
+
const index = playback.channel_index;
|
|
124
|
+
// only the channels that name this target, which most clips have none of
|
|
125
|
+
const group = index.group_of(target);
|
|
126
|
+
|
|
127
|
+
if (group === POSE_CHANNEL_GROUP_NONE) {
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
75
130
|
|
|
76
|
-
|
|
77
|
-
|
|
131
|
+
const first = index.first(group);
|
|
132
|
+
const end = index.end(group);
|
|
78
133
|
|
|
79
|
-
|
|
80
|
-
|
|
134
|
+
const weight = playback.weight;
|
|
135
|
+
|
|
136
|
+
if (weight === 0) {
|
|
137
|
+
// it writes, and adds nothing to what it writes: mark its axes, sample nothing
|
|
138
|
+
for (let c = first; c < end; c++) {
|
|
139
|
+
dirty |= channel_dirty_bits(index.channel(c));
|
|
81
140
|
}
|
|
82
141
|
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// the apply pass's `clip.time + clip.time_start`
|
|
146
|
+
const time = playback.start_time + playback.time;
|
|
147
|
+
|
|
148
|
+
for (let c = first; c < end; c++) {
|
|
149
|
+
const channel = index.channel(c);
|
|
83
150
|
const curves = channel.curves;
|
|
84
151
|
|
|
85
152
|
switch (channel.property) {
|
|
@@ -160,8 +227,9 @@ export function pose_evaluate_local(result, target, rest, playbacks) {
|
|
|
160
227
|
// A quaternion sum of length zero has no orientation in it to recover. The ordinary way in
|
|
161
228
|
// is a joint whose rotation only clips at weight zero write — the accumulator takes a bound
|
|
162
229
|
// clip's contribution whatever its weight — and the other is contributions that cancelled
|
|
163
|
-
// exactly. The
|
|
164
|
-
//
|
|
230
|
+
// exactly. The answer is the rest pose: the GPU writes the rest rotation every
|
|
231
|
+
// contribution carried from its binding, which was taken from this same rest when the clip
|
|
232
|
+
// bound the target.
|
|
165
233
|
const length = Math.sqrt(x * x + y * y + z * z + w * w);
|
|
166
234
|
|
|
167
235
|
if (length > 0) {
|
|
@@ -86,7 +86,7 @@ fn main(
|
|
|
86
86
|
|
|
87
87
|
let value = animation_track_evaluate(effective_time, track, &animation_database) * weight;
|
|
88
88
|
|
|
89
|
-
write_object_property(binding.instance, binding.property, track.mask, value, weight);
|
|
89
|
+
write_object_property(binding.instance, binding.property, track.mask, value, weight, binding.rest_rotation);
|
|
90
90
|
}
|
|
91
91
|
`, [
|
|
92
92
|
BINDING_TABLE.chunk_iterate(WORKGROUP_SIZE),
|
|
@@ -11,8 +11,9 @@ export const WORKGROUP_SIZE: 64;
|
|
|
11
11
|
* thread per slot. For each occupied row whose accumulator slot has
|
|
12
12
|
* any dirty bits set, reads the contributed components (atomic
|
|
13
13
|
* loads, bitcast back to f32), writes them through the per-field
|
|
14
|
-
* scene helpers, normalises rotation,
|
|
15
|
-
*
|
|
14
|
+
* scene helpers, normalises rotation — or, for a rotation summing to
|
|
15
|
+
* nothing, writes the rest rotation the contributions carried — and
|
|
16
|
+
* zeros the sums and the dirty mask for next frame.
|
|
16
17
|
*
|
|
17
18
|
* The accumulator buffer must be sized to at least
|
|
18
19
|
* `transforms.dispatch_page_count * elements_per_page` rows —
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shader_animation_pose_resolve.d.ts","sourceRoot":"","sources":["../../../../../src/shade/renderer/animation/shader_animation_pose_resolve.js"],"names":[],"mappings":"AAWA;;;GAGG;AACH,gCAAiC;
|
|
1
|
+
{"version":3,"file":"shader_animation_pose_resolve.d.ts","sourceRoot":"","sources":["../../../../../src/shade/renderer/animation/shader_animation_pose_resolve.js"],"names":[],"mappings":"AAWA;;;GAGG;AACH,gCAAiC;AAmJjC;;;;;;;;;;;;;;;;;;;GAmBG;AACH,0DAIG;8BArL2B,4BAA4B"}
|