@zephyr3d/scene 0.8.1 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (241) hide show
  1. package/dist/animation/animationset.js +563 -128
  2. package/dist/animation/animationset.js.map +1 -1
  3. package/dist/animation/cloth/gpu_cloth_system.js +2516 -0
  4. package/dist/animation/cloth/gpu_cloth_system.js.map +1 -0
  5. package/dist/animation/fixed_geometry_cache_track.js +138 -0
  6. package/dist/animation/fixed_geometry_cache_track.js.map +1 -0
  7. package/dist/animation/geometry_cache_utils.js +120 -0
  8. package/dist/animation/geometry_cache_utils.js.map +1 -0
  9. package/dist/animation/ik_modifier.js +9 -1
  10. package/dist/animation/ik_modifier.js.map +1 -1
  11. package/dist/animation/joint_dynamics/collision.js +469 -0
  12. package/dist/animation/joint_dynamics/collision.js.map +1 -0
  13. package/dist/animation/joint_dynamics/constraints.js +329 -0
  14. package/dist/animation/joint_dynamics/constraints.js.map +1 -0
  15. package/dist/animation/joint_dynamics/controller.js +699 -0
  16. package/dist/animation/joint_dynamics/controller.js.map +1 -0
  17. package/dist/animation/joint_dynamics/joint_dynamics_system.js +393 -0
  18. package/dist/animation/joint_dynamics/joint_dynamics_system.js.map +1 -0
  19. package/dist/animation/joint_dynamics/solver.js +794 -0
  20. package/dist/animation/joint_dynamics/solver.js.map +1 -0
  21. package/dist/animation/joint_dynamics/types.js +19 -0
  22. package/dist/animation/joint_dynamics/types.js.map +1 -0
  23. package/dist/animation/joint_dynamics/vrm_spring_bone.js +46 -0
  24. package/dist/animation/joint_dynamics/vrm_spring_bone.js.map +1 -0
  25. package/dist/animation/joint_dynamics_modifier.js +51 -0
  26. package/dist/animation/joint_dynamics_modifier.js.map +1 -0
  27. package/dist/animation/pca_geometry_cache_track.js +222 -0
  28. package/dist/animation/pca_geometry_cache_track.js.map +1 -0
  29. package/dist/animation/skeleton.js +1907 -3
  30. package/dist/animation/skeleton.js.map +1 -1
  31. package/dist/animation/skeleton_modifier.js +3 -5
  32. package/dist/animation/skeleton_modifier.js.map +1 -1
  33. package/dist/animation/spring/multi_chain_spring_system.js +2 -0
  34. package/dist/animation/spring/multi_chain_spring_system.js.map +1 -1
  35. package/dist/animation/spring/spring_chain.js +3 -2
  36. package/dist/animation/spring/spring_chain.js.map +1 -1
  37. package/dist/animation/spring/spring_collider.js +35 -5
  38. package/dist/animation/spring/spring_collider.js.map +1 -1
  39. package/dist/animation/spring/spring_particle.js +1 -0
  40. package/dist/animation/spring/spring_particle.js.map +1 -1
  41. package/dist/animation/spring/spring_system.js +325 -28
  42. package/dist/animation/spring/spring_system.js.map +1 -1
  43. package/dist/animation/spring2/collision.js +469 -0
  44. package/dist/animation/spring2/collision.js.map +1 -0
  45. package/dist/animation/spring2/constraints.js +329 -0
  46. package/dist/animation/spring2/constraints.js.map +1 -0
  47. package/dist/animation/spring2/controller.js +434 -0
  48. package/dist/animation/spring2/controller.js.map +1 -0
  49. package/dist/animation/spring2/math.js +16 -0
  50. package/dist/animation/spring2/math.js.map +1 -0
  51. package/dist/animation/spring2/solver.js +624 -0
  52. package/dist/animation/spring2/solver.js.map +1 -0
  53. package/dist/animation/spring2/spring_system.js +118 -0
  54. package/dist/animation/spring2/spring_system.js.map +1 -0
  55. package/dist/animation/spring2/types.js +19 -0
  56. package/dist/animation/spring2/types.js.map +1 -0
  57. package/dist/animation/spring_modifier.js +17 -1
  58. package/dist/animation/spring_modifier.js.map +1 -1
  59. package/dist/app/engine.js +42 -15
  60. package/dist/app/engine.js.map +1 -1
  61. package/dist/app/runtimescript.js +132 -1
  62. package/dist/app/runtimescript.js.map +1 -1
  63. package/dist/app/screen.js +3 -3
  64. package/dist/app/screen.js.map +1 -1
  65. package/dist/app/scriptingsystem.js +69 -37
  66. package/dist/app/scriptingsystem.js.map +1 -1
  67. package/dist/app/scriptregistry.js +157 -146
  68. package/dist/app/scriptregistry.js.map +1 -1
  69. package/dist/asset/assetmanager.js +2 -1
  70. package/dist/asset/assetmanager.js.map +1 -1
  71. package/dist/asset/loaders/zabc/zabc_loader.js +570 -0
  72. package/dist/asset/loaders/zabc/zabc_loader.js.map +1 -0
  73. package/dist/asset/model.js +192 -0
  74. package/dist/asset/model.js.map +1 -1
  75. package/dist/camera/base.js +26 -7
  76. package/dist/camera/base.js.map +1 -1
  77. package/dist/camera/camera.js +146 -24
  78. package/dist/camera/camera.js.map +1 -1
  79. package/dist/camera/fps.js +2 -2
  80. package/dist/camera/fps.js.map +1 -1
  81. package/dist/camera/orbit.js +2 -2
  82. package/dist/camera/orbit.js.map +1 -1
  83. package/dist/index.d.ts +11701 -8506
  84. package/dist/index.js +29 -8
  85. package/dist/index.js.map +1 -1
  86. package/dist/material/lambert.js +1 -1
  87. package/dist/material/lambert.js.map +1 -1
  88. package/dist/material/material.js +9 -3
  89. package/dist/material/material.js.map +1 -1
  90. package/dist/material/meshmaterial.js +54 -7
  91. package/dist/material/meshmaterial.js.map +1 -1
  92. package/dist/material/mixins/lightmodel/blinnphong.js +17 -7
  93. package/dist/material/mixins/lightmodel/blinnphong.js.map +1 -1
  94. package/dist/material/mixins/lightmodel/lambert.js +7 -2
  95. package/dist/material/mixins/lightmodel/lambert.js.map +1 -1
  96. package/dist/material/mixins/lightmodel/pbrblueprintmixin.js +108 -16
  97. package/dist/material/mixins/lightmodel/pbrblueprintmixin.js.map +1 -1
  98. package/dist/material/mixins/lightmodel/pbrmetallicroughness.js +222 -14
  99. package/dist/material/mixins/lightmodel/pbrmetallicroughness.js.map +1 -1
  100. package/dist/material/mixins/lightmodel/pbrspecularglossness.js +24 -12
  101. package/dist/material/mixins/lightmodel/pbrspecularglossness.js.map +1 -1
  102. package/dist/material/mixins/lit.js +11 -8
  103. package/dist/material/mixins/lit.js.map +1 -1
  104. package/dist/material/mixins/pbr/common.js +72 -5
  105. package/dist/material/mixins/pbr/common.js.map +1 -1
  106. package/dist/material/pbrblueprint.js +148 -1
  107. package/dist/material/pbrblueprint.js.map +1 -1
  108. package/dist/material/pbrmr.js +125 -2
  109. package/dist/material/pbrmr.js.map +1 -1
  110. package/dist/material/shader/helper.js +10 -4
  111. package/dist/material/shader/helper.js.map +1 -1
  112. package/dist/material/water.js +2 -2
  113. package/dist/material/water.js.map +1 -1
  114. package/dist/posteffect/coloradjust.js +145 -0
  115. package/dist/posteffect/coloradjust.js.map +1 -0
  116. package/dist/posteffect/taa.js +68 -32
  117. package/dist/posteffect/taa.js.map +1 -1
  118. package/dist/render/cluster_light.js +17 -12
  119. package/dist/render/cluster_light.js.map +1 -1
  120. package/dist/render/deferredlightpass.js +510 -0
  121. package/dist/render/deferredlightpass.js.map +1 -0
  122. package/dist/render/deferredshadowlightpass.js +428 -0
  123. package/dist/render/deferredshadowlightpass.js.map +1 -0
  124. package/dist/render/drawable.js.map +1 -1
  125. package/dist/render/drawable_mixin.js +21 -12
  126. package/dist/render/drawable_mixin.js.map +1 -1
  127. package/dist/render/envlight.js +20 -22
  128. package/dist/render/envlight.js.map +1 -1
  129. package/dist/render/gbufferpass.js +50 -0
  130. package/dist/render/gbufferpass.js.map +1 -0
  131. package/dist/render/lightpass.js +1 -2
  132. package/dist/render/lightpass.js.map +1 -1
  133. package/dist/render/primitive.js +1 -1
  134. package/dist/render/primitive.js.map +1 -1
  135. package/dist/render/render_queue.js +3 -2
  136. package/dist/render/render_queue.js.map +1 -1
  137. package/dist/render/renderer.js +5 -358
  138. package/dist/render/renderer.js.map +1 -1
  139. package/dist/render/rendergraph/executor.js +15 -12
  140. package/dist/render/rendergraph/executor.js.map +1 -1
  141. package/dist/render/rendergraph/forward_plus_builder.js +144 -88
  142. package/dist/render/rendergraph/forward_plus_builder.js.map +1 -1
  143. package/dist/render/rendergraph/history_resource_manager.js +178 -0
  144. package/dist/render/rendergraph/history_resource_manager.js.map +1 -0
  145. package/dist/render/rendergraph/rendergraph.js +12 -1
  146. package/dist/render/rendergraph/rendergraph.js.map +1 -1
  147. package/dist/render/rendergraph/types.js +2 -2
  148. package/dist/render/rendergraph/types.js.map +1 -1
  149. package/dist/render/sky.js +26 -9
  150. package/dist/render/sky.js.map +1 -1
  151. package/dist/scene/basesprite.js +4 -3
  152. package/dist/scene/basesprite.js.map +1 -1
  153. package/dist/scene/batchgroup.js +4 -4
  154. package/dist/scene/environment.js +7 -4
  155. package/dist/scene/environment.js.map +1 -1
  156. package/dist/scene/light.js +184 -18
  157. package/dist/scene/light.js.map +1 -1
  158. package/dist/scene/mesh.js +30 -0
  159. package/dist/scene/mesh.js.map +1 -1
  160. package/dist/scene/particlesys.js +3 -2
  161. package/dist/scene/particlesys.js.map +1 -1
  162. package/dist/scene/raycast_visitor.js +29 -3
  163. package/dist/scene/raycast_visitor.js.map +1 -1
  164. package/dist/scene/scene.js +86 -19
  165. package/dist/scene/scene.js.map +1 -1
  166. package/dist/scene/scene_node.js +87 -31
  167. package/dist/scene/scene_node.js.map +1 -1
  168. package/dist/scene/script_attachment.js +59 -0
  169. package/dist/scene/script_attachment.js.map +1 -0
  170. package/dist/scene/terrain-cm/grass.js +3 -2
  171. package/dist/scene/terrain-cm/grass.js.map +1 -1
  172. package/dist/scene/terrain-cm/terrain-cm.js +4 -3
  173. package/dist/scene/terrain-cm/terrain-cm.js.map +1 -1
  174. package/dist/scene/water.js +3 -2
  175. package/dist/scene/water.js.map +1 -1
  176. package/dist/shaders/shadow.js +2 -2
  177. package/dist/shaders/shadow.js.map +1 -1
  178. package/dist/shadow/shadowmapper.js +74 -4
  179. package/dist/shadow/shadowmapper.js.map +1 -1
  180. package/dist/shapes/box.js +163 -0
  181. package/dist/shapes/box.js.map +1 -1
  182. package/dist/shapes/capsule.js +216 -0
  183. package/dist/shapes/capsule.js.map +1 -0
  184. package/dist/shapes/cylinder.js +91 -0
  185. package/dist/shapes/cylinder.js.map +1 -1
  186. package/dist/shapes/plane.js +32 -0
  187. package/dist/shapes/plane.js.map +1 -1
  188. package/dist/shapes/tetrahedron.js +211 -0
  189. package/dist/shapes/tetrahedron.js.map +1 -1
  190. package/dist/shapes/torus.js +173 -0
  191. package/dist/shapes/torus.js.map +1 -1
  192. package/dist/utility/blueprint/common/math.js +111 -1
  193. package/dist/utility/blueprint/common/math.js.map +1 -1
  194. package/dist/utility/blueprint/material/inputs.js +235 -1
  195. package/dist/utility/blueprint/material/inputs.js.map +1 -1
  196. package/dist/utility/blueprint/material/ir.js +154 -10
  197. package/dist/utility/blueprint/material/ir.js.map +1 -1
  198. package/dist/utility/blueprint/material/texture.js +273 -19
  199. package/dist/utility/blueprint/material/texture.js.map +1 -1
  200. package/dist/utility/serialization/json.js +12 -2
  201. package/dist/utility/serialization/json.js.map +1 -1
  202. package/dist/utility/serialization/manager.js +30 -11
  203. package/dist/utility/serialization/manager.js.map +1 -1
  204. package/dist/utility/serialization/scene/animation.js +250 -7
  205. package/dist/utility/serialization/scene/animation.js.map +1 -1
  206. package/dist/utility/serialization/scene/batch.js +4 -2
  207. package/dist/utility/serialization/scene/batch.js.map +1 -1
  208. package/dist/utility/serialization/scene/camera.js +154 -2
  209. package/dist/utility/serialization/scene/camera.js.map +1 -1
  210. package/dist/utility/serialization/scene/cloth_script.js +834 -0
  211. package/dist/utility/serialization/scene/cloth_script.js.map +1 -0
  212. package/dist/utility/serialization/scene/common.js +7 -0
  213. package/dist/utility/serialization/scene/common.js.map +1 -1
  214. package/dist/utility/serialization/scene/light.js +120 -4
  215. package/dist/utility/serialization/scene/light.js.map +1 -1
  216. package/dist/utility/serialization/scene/material.js +453 -1
  217. package/dist/utility/serialization/scene/material.js.map +1 -1
  218. package/dist/utility/serialization/scene/mesh.js +4 -2
  219. package/dist/utility/serialization/scene/mesh.js.map +1 -1
  220. package/dist/utility/serialization/scene/node.js +298 -10
  221. package/dist/utility/serialization/scene/node.js.map +1 -1
  222. package/dist/utility/serialization/scene/particle.js +4 -2
  223. package/dist/utility/serialization/scene/particle.js.map +1 -1
  224. package/dist/utility/serialization/scene/primitive.js +100 -3
  225. package/dist/utility/serialization/scene/primitive.js.map +1 -1
  226. package/dist/utility/serialization/scene/scene.js +130 -3
  227. package/dist/utility/serialization/scene/scene.js.map +1 -1
  228. package/dist/utility/serialization/scene/script.js +66 -0
  229. package/dist/utility/serialization/scene/script.js.map +1 -0
  230. package/dist/utility/serialization/scene/spring_script.js +596 -0
  231. package/dist/utility/serialization/scene/spring_script.js.map +1 -0
  232. package/dist/utility/serialization/scene/sprite.js +5 -3
  233. package/dist/utility/serialization/scene/sprite.js.map +1 -1
  234. package/dist/utility/serialization/scene/terrain.js +4 -2
  235. package/dist/utility/serialization/scene/terrain.js.map +1 -1
  236. package/dist/utility/serialization/scene/water.js +4 -2
  237. package/dist/utility/serialization/scene/water.js.map +1 -1
  238. package/dist/utility/serialization/types.js.map +1 -1
  239. package/dist/values.js +5 -1
  240. package/dist/values.js.map +1 -1
  241. package/package.json +3 -3
@@ -0,0 +1,2516 @@
1
+ import { PBPrimitiveType } from '@zephyr3d/device';
2
+ import { Vector3, retainObject, releaseObject, Matrix4x4, uint8ArrayToBase64, base64ToUint8Array } from '@zephyr3d/base';
3
+ import { getDevice } from '../../app/api.js';
4
+ import { BoundingBox } from '../../utility/bounding_volume.js';
5
+ import { updateColliderFromNode } from '../spring/spring_collider.js';
6
+
7
+ function encodeTypedArrayBase64(view) {
8
+ return uint8ArrayToBase64(new Uint8Array(view.buffer, view.byteOffset, view.byteLength));
9
+ }
10
+ function decodeTypedArrayFromBase64(ctor, base64, expectedLength) {
11
+ const bytes = base64ToUint8Array(base64);
12
+ const byteLength = bytes.byteLength;
13
+ if (byteLength % ctor.BYTES_PER_ELEMENT !== 0) {
14
+ throw new Error('GPU cloth wrap failed: binding cache data is corrupted.');
15
+ }
16
+ const buffer = bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
17
+ const result = new ctor(buffer);
18
+ if (expectedLength >= 0 && result.length !== expectedLength) {
19
+ throw new Error('GPU cloth wrap failed: binding cache size does not match target mesh.');
20
+ }
21
+ return result;
22
+ }
23
+ function isWrapBindingDataCompatible(data, sourceRestPositions, targetVertexCount) {
24
+ return data?.version === 4 && data.vertexCount === targetVertexCount && data.sourceVertexCount === sourceRestPositions.length / 3 >> 0 && data.influenceCount === WRAP_TRIANGLE_VERTEX_COUNT;
25
+ }
26
+ function resolveDevice(explicitDevice) {
27
+ if (explicitDevice) {
28
+ return explicitDevice;
29
+ }
30
+ try {
31
+ return getDevice();
32
+ } catch {
33
+ return null;
34
+ }
35
+ }
36
+ /**
37
+ * Returns whether GPU cloth is supported on the given device.
38
+ *
39
+ * GPU cloth in this engine is WebGPU-only. WebGL backends are explicitly unsupported.
40
+ */ function isGPUClothSupported(device) {
41
+ const resolved = resolveDevice(device);
42
+ return resolved?.type === 'webgpu';
43
+ }
44
+ const DEFAULT_DAMPING = 0.02;
45
+ const DEFAULT_DYNAMIC_FRICTION = 0.15;
46
+ const DEFAULT_STATIC_FRICTION = 0.3;
47
+ const DEFAULT_STIFFNESS = 0.3;
48
+ const DEFAULT_POSE_FOLLOW = 0;
49
+ const DEFAULT_SUBSTEPS = 2;
50
+ const DEFAULT_SOLVER_ITERATIONS = 5;
51
+ const DEFAULT_MAX_NEIGHBORS = 8;
52
+ const DEFAULT_WORKGROUP_SIZE = 64;
53
+ const DEFAULT_MAX_TRIANGLES_PER_VERTEX = 16;
54
+ const DEFAULT_REST_POSITION_SMOOTHING_TIME = 1 / 25;
55
+ const DEFAULT_COLLIDER_SMOOTHING_TIME = 1 / 30;
56
+ const FIXED_SIMULATION_TIME_STEP = 1 / 60;
57
+ const MAX_ACCUMULATED_SIMULATION_TIME = 1 / 20;
58
+ const MAX_SIMULATION_STEPS_PER_UPDATE = Math.max(1, Math.ceil(MAX_ACCUMULATED_SIMULATION_TIME / FIXED_SIMULATION_TIME_STEP));
59
+ const WRAP_TRIANGLE_VERTEX_COUNT = 3;
60
+ const WRAP_MIN_DISTANCE = 1e-8;
61
+ function clamp(value, min, max) {
62
+ return Math.min(Math.max(value, min), max);
63
+ }
64
+ function getInitialColliderBufferFloatCount(colliders, type, stride) {
65
+ const count = (colliders ?? []).reduce((total, collider)=>total + (collider?.type === type ? 1 : 0), 0);
66
+ return Math.max(1, count) * stride;
67
+ }
68
+ function getPrimitiveScalarByteSize(type) {
69
+ switch(type){
70
+ case PBPrimitiveType.I16:
71
+ case PBPrimitiveType.I16_NORM:
72
+ case PBPrimitiveType.U16:
73
+ case PBPrimitiveType.U16_NORM:
74
+ case PBPrimitiveType.F16:
75
+ return 2;
76
+ case PBPrimitiveType.I32:
77
+ case PBPrimitiveType.I32_NORM:
78
+ case PBPrimitiveType.U32:
79
+ case PBPrimitiveType.U32_NORM:
80
+ case PBPrimitiveType.F32:
81
+ return 4;
82
+ default:
83
+ return 1;
84
+ }
85
+ }
86
+ function readPrimitiveScalar(view, byteOffset, scalarType, normalized) {
87
+ switch(scalarType){
88
+ case PBPrimitiveType.I8:
89
+ case PBPrimitiveType.I8_NORM:
90
+ {
91
+ const value = view.getInt8(byteOffset);
92
+ return normalized ? Math.max(-1, value / 127) : value;
93
+ }
94
+ case PBPrimitiveType.U8:
95
+ case PBPrimitiveType.U8_NORM:
96
+ {
97
+ const value = view.getUint8(byteOffset);
98
+ return normalized ? value / 255 : value;
99
+ }
100
+ case PBPrimitiveType.I16:
101
+ case PBPrimitiveType.I16_NORM:
102
+ {
103
+ const value = view.getInt16(byteOffset, true);
104
+ return normalized ? Math.max(-1, value / 32767) : value;
105
+ }
106
+ case PBPrimitiveType.U16:
107
+ case PBPrimitiveType.U16_NORM:
108
+ {
109
+ const value = view.getUint16(byteOffset, true);
110
+ return normalized ? value / 65535 : value;
111
+ }
112
+ case PBPrimitiveType.I32:
113
+ case PBPrimitiveType.I32_NORM:
114
+ {
115
+ const value = view.getInt32(byteOffset, true);
116
+ return normalized ? Math.max(-1, value / 2147483647) : value;
117
+ }
118
+ case PBPrimitiveType.U32:
119
+ case PBPrimitiveType.U32_NORM:
120
+ {
121
+ const value = view.getUint32(byteOffset, true);
122
+ return normalized ? value / 4294967295 : value;
123
+ }
124
+ case PBPrimitiveType.F32:
125
+ return view.getFloat32(byteOffset, true);
126
+ default:
127
+ throw new Error(`Unsupported vertex scalar type: ${scalarType}`);
128
+ }
129
+ }
130
+ function distance3(positions, a, b) {
131
+ const a0 = a * 3;
132
+ const b0 = b * 3;
133
+ const dx = positions[a0] - positions[b0];
134
+ const dy = positions[a0 + 1] - positions[b0 + 1];
135
+ const dz = positions[a0 + 2] - positions[b0 + 2];
136
+ return Math.sqrt(dx * dx + dy * dy + dz * dz);
137
+ }
138
+ function dot3(ax, ay, az, bx, by, bz) {
139
+ return ax * bx + ay * by + az * bz;
140
+ }
141
+ function normalize3(x, y, z, fallback) {
142
+ const length = Math.hypot(x, y, z);
143
+ if (length <= WRAP_MIN_DISTANCE) {
144
+ return fallback;
145
+ }
146
+ return [
147
+ x / length,
148
+ y / length,
149
+ z / length
150
+ ];
151
+ }
152
+ function buildWrapTriangleFrame(p0x, p0y, p0z, p1x, p1y, p1z, p2x, p2y, p2z, out) {
153
+ const edge01x = p1x - p0x;
154
+ const edge01y = p1y - p0y;
155
+ const edge01z = p1z - p0z;
156
+ const edge02x = p2x - p0x;
157
+ const edge02y = p2y - p0y;
158
+ const edge02z = p2z - p0z;
159
+ let tangent = normalize3(edge01x, edge01y, edge01z, [
160
+ 0,
161
+ 0,
162
+ 0
163
+ ]);
164
+ if (tangent[0] === 0 && tangent[1] === 0 && tangent[2] === 0) {
165
+ tangent = normalize3(edge02x, edge02y, edge02z, [
166
+ 1,
167
+ 0,
168
+ 0
169
+ ]);
170
+ }
171
+ let normal = normalize3(edge01y * edge02z - edge01z * edge02y, edge01z * edge02x - edge01x * edge02z, edge01x * edge02y - edge01y * edge02x, [
172
+ 0,
173
+ 0,
174
+ 0
175
+ ]);
176
+ if (normal[0] === 0 && normal[1] === 0 && normal[2] === 0) {
177
+ const fallbackAxis = Math.abs(tangent[1]) < 0.999 ? [
178
+ 0,
179
+ 1,
180
+ 0
181
+ ] : [
182
+ 1,
183
+ 0,
184
+ 0
185
+ ];
186
+ normal = normalize3(tangent[1] * fallbackAxis[2] - tangent[2] * fallbackAxis[1], tangent[2] * fallbackAxis[0] - tangent[0] * fallbackAxis[2], tangent[0] * fallbackAxis[1] - tangent[1] * fallbackAxis[0], [
187
+ 0,
188
+ 0,
189
+ 1
190
+ ]);
191
+ }
192
+ let bitangent = normalize3(normal[1] * tangent[2] - normal[2] * tangent[1], normal[2] * tangent[0] - normal[0] * tangent[2], normal[0] * tangent[1] - normal[1] * tangent[0], [
193
+ 0,
194
+ 0,
195
+ 0
196
+ ]);
197
+ if (bitangent[0] === 0 && bitangent[1] === 0 && bitangent[2] === 0) {
198
+ bitangent = Math.abs(tangent[1]) < 0.999 ? [
199
+ 0,
200
+ 1,
201
+ 0
202
+ ] : [
203
+ 1,
204
+ 0,
205
+ 0
206
+ ];
207
+ }
208
+ normal = normalize3(tangent[1] * bitangent[2] - tangent[2] * bitangent[1], tangent[2] * bitangent[0] - tangent[0] * bitangent[2], tangent[0] * bitangent[1] - tangent[1] * bitangent[0], [
209
+ 0,
210
+ 0,
211
+ 1
212
+ ]);
213
+ out[0] = tangent[0];
214
+ out[1] = tangent[1];
215
+ out[2] = tangent[2];
216
+ out[3] = bitangent[0];
217
+ out[4] = bitangent[1];
218
+ out[5] = bitangent[2];
219
+ out[6] = normal[0];
220
+ out[7] = normal[1];
221
+ out[8] = normal[2];
222
+ }
223
+ function closestPointOnTriangle(px, py, pz, ax, ay, az, bx, by, bz, cx, cy, cz, closestOut, baryOut) {
224
+ const abx = bx - ax;
225
+ const aby = by - ay;
226
+ const abz = bz - az;
227
+ const acx = cx - ax;
228
+ const acy = cy - ay;
229
+ const acz = cz - az;
230
+ const apx = px - ax;
231
+ const apy = py - ay;
232
+ const apz = pz - az;
233
+ const d1 = dot3(abx, aby, abz, apx, apy, apz);
234
+ const d2 = dot3(acx, acy, acz, apx, apy, apz);
235
+ if (d1 <= 0 && d2 <= 0) {
236
+ closestOut[0] = ax;
237
+ closestOut[1] = ay;
238
+ closestOut[2] = az;
239
+ baryOut[0] = 1;
240
+ baryOut[1] = 0;
241
+ baryOut[2] = 0;
242
+ } else {
243
+ const bpx = px - bx;
244
+ const bpy = py - by;
245
+ const bpz = pz - bz;
246
+ const d3 = dot3(abx, aby, abz, bpx, bpy, bpz);
247
+ const d4 = dot3(acx, acy, acz, bpx, bpy, bpz);
248
+ if (d3 >= 0 && d4 <= d3) {
249
+ closestOut[0] = bx;
250
+ closestOut[1] = by;
251
+ closestOut[2] = bz;
252
+ baryOut[0] = 0;
253
+ baryOut[1] = 1;
254
+ baryOut[2] = 0;
255
+ } else {
256
+ const vc = d1 * d4 - d3 * d2;
257
+ if (vc <= 0 && d1 >= 0 && d3 <= 0) {
258
+ const v = d1 / Math.max(WRAP_MIN_DISTANCE, d1 - d3);
259
+ closestOut[0] = ax + abx * v;
260
+ closestOut[1] = ay + aby * v;
261
+ closestOut[2] = az + abz * v;
262
+ baryOut[0] = 1 - v;
263
+ baryOut[1] = v;
264
+ baryOut[2] = 0;
265
+ } else {
266
+ const cpx = px - cx;
267
+ const cpy = py - cy;
268
+ const cpz = pz - cz;
269
+ const d5 = dot3(abx, aby, abz, cpx, cpy, cpz);
270
+ const d6 = dot3(acx, acy, acz, cpx, cpy, cpz);
271
+ if (d6 >= 0 && d5 <= d6) {
272
+ closestOut[0] = cx;
273
+ closestOut[1] = cy;
274
+ closestOut[2] = cz;
275
+ baryOut[0] = 0;
276
+ baryOut[1] = 0;
277
+ baryOut[2] = 1;
278
+ } else {
279
+ const vb = d5 * d2 - d1 * d6;
280
+ if (vb <= 0 && d2 >= 0 && d6 <= 0) {
281
+ const w = d2 / Math.max(WRAP_MIN_DISTANCE, d2 - d6);
282
+ closestOut[0] = ax + acx * w;
283
+ closestOut[1] = ay + acy * w;
284
+ closestOut[2] = az + acz * w;
285
+ baryOut[0] = 1 - w;
286
+ baryOut[1] = 0;
287
+ baryOut[2] = w;
288
+ } else {
289
+ const va = d3 * d6 - d5 * d4;
290
+ if (va <= 0 && d4 - d3 >= 0 && d5 - d6 >= 0) {
291
+ const w = (d4 - d3) / Math.max(WRAP_MIN_DISTANCE, d4 - d3 + d5 - d6);
292
+ closestOut[0] = bx + (cx - bx) * w;
293
+ closestOut[1] = by + (cy - by) * w;
294
+ closestOut[2] = bz + (cz - bz) * w;
295
+ baryOut[0] = 0;
296
+ baryOut[1] = 1 - w;
297
+ baryOut[2] = w;
298
+ } else {
299
+ const denom = 1 / Math.max(WRAP_MIN_DISTANCE, va + vb + vc);
300
+ const v = vb * denom;
301
+ const w = vc * denom;
302
+ const u = 1 - v - w;
303
+ closestOut[0] = ax * u + bx * v + cx * w;
304
+ closestOut[1] = ay * u + by * v + cy * w;
305
+ closestOut[2] = az * u + bz * v + cz * w;
306
+ baryOut[0] = u;
307
+ baryOut[1] = v;
308
+ baryOut[2] = w;
309
+ }
310
+ }
311
+ }
312
+ }
313
+ }
314
+ }
315
+ const dx = px - closestOut[0];
316
+ const dy = py - closestOut[1];
317
+ const dz = pz - closestOut[2];
318
+ return dx * dx + dy * dy + dz * dz;
319
+ }
320
+ function buildPinWeightArray(positions, explicitWeights, explicitPinned) {
321
+ const vertexCount = positions.length / 3 >> 0;
322
+ const result = new Float32Array(vertexCount);
323
+ if (explicitWeights && explicitWeights.length > 0) {
324
+ const len = Math.min(vertexCount, explicitWeights.length);
325
+ for(let i = 0; i < len; i++){
326
+ result[i] = clamp(Number(explicitWeights[i]) || 0, 0, 1);
327
+ }
328
+ return result;
329
+ }
330
+ if (explicitPinned && explicitPinned.length > 0) {
331
+ for (const i of explicitPinned){
332
+ if (Number.isFinite(i) && i >= 0 && i < vertexCount) {
333
+ result[i >> 0] = 1;
334
+ }
335
+ }
336
+ }
337
+ return result;
338
+ }
339
+ function buildNeighborData(positions, indexData, maxNeighbors) {
340
+ const vertexCount = positions.length / 3 >> 0;
341
+ const neighborMap = Array.from({
342
+ length: vertexCount
343
+ }, ()=>new Map());
344
+ const addEdge = (a, b)=>{
345
+ if (a < 0 || b < 0 || a >= vertexCount || b >= vertexCount || a === b) {
346
+ return;
347
+ }
348
+ const len = distance3(positions, a, b);
349
+ const mapA = neighborMap[a];
350
+ const mapB = neighborMap[b];
351
+ const oldAB = mapA.get(b);
352
+ if (oldAB === undefined || len < oldAB) {
353
+ mapA.set(b, len);
354
+ }
355
+ const oldBA = mapB.get(a);
356
+ if (oldBA === undefined || len < oldBA) {
357
+ mapB.set(a, len);
358
+ }
359
+ };
360
+ for(let i = 0; i + 2 < indexData.length; i += 3){
361
+ const a = indexData[i];
362
+ const b = indexData[i + 1];
363
+ const c = indexData[i + 2];
364
+ addEdge(a, b);
365
+ addEdge(b, c);
366
+ addEdge(c, a);
367
+ }
368
+ const totalEntries = vertexCount * maxNeighbors;
369
+ const neighborIndices = new Int32Array(totalEntries);
370
+ const restLengths = new Float32Array(totalEntries);
371
+ neighborIndices.fill(-1);
372
+ let hasConstraint = false;
373
+ for(let i = 0; i < vertexCount; i++){
374
+ const sorted = [
375
+ ...neighborMap[i].entries()
376
+ ].sort((a, b)=>a[1] - b[1]).slice(0, maxNeighbors);
377
+ for(let j = 0; j < sorted.length; j++){
378
+ const slot = i * maxNeighbors + j;
379
+ neighborIndices[slot] = sorted[j][0];
380
+ restLengths[slot] = sorted[j][1];
381
+ hasConstraint = true;
382
+ }
383
+ }
384
+ return {
385
+ neighborIndices,
386
+ restLengths,
387
+ hasConstraint
388
+ };
389
+ }
390
+ function toUInt32Indices(indexData) {
391
+ if (indexData instanceof Uint32Array) {
392
+ return indexData;
393
+ }
394
+ const out = new Uint32Array(indexData.length);
395
+ for(let i = 0; i < indexData.length; i++){
396
+ out[i] = indexData[i];
397
+ }
398
+ return out;
399
+ }
400
+ function buildVertexTriangleAdjacency(vertexCount, indexData, maxTrianglesPerVertex) {
401
+ const triangleCount = indexData.length / 3 >> 0;
402
+ const trianglesPerVertex = Array.from({
403
+ length: vertexCount
404
+ }, ()=>[]);
405
+ for(let tri = 0; tri < triangleCount; tri++){
406
+ const base = tri * 3;
407
+ const i0 = indexData[base];
408
+ const i1 = indexData[base + 1];
409
+ const i2 = indexData[base + 2];
410
+ if (i0 < vertexCount) {
411
+ trianglesPerVertex[i0].push(tri);
412
+ }
413
+ if (i1 < vertexCount) {
414
+ trianglesPerVertex[i1].push(tri);
415
+ }
416
+ if (i2 < vertexCount) {
417
+ trianglesPerVertex[i2].push(tri);
418
+ }
419
+ }
420
+ const adjacency = new Int32Array(vertexCount * maxTrianglesPerVertex);
421
+ adjacency.fill(-1);
422
+ for(let i = 0; i < vertexCount; i++){
423
+ const tris = trianglesPerVertex[i];
424
+ const len = Math.min(maxTrianglesPerVertex, tris.length);
425
+ for(let t = 0; t < len; t++){
426
+ adjacency[i * maxTrianglesPerVertex + t] = tris[t];
427
+ }
428
+ }
429
+ return {
430
+ triangleCount,
431
+ adjacency
432
+ };
433
+ }
434
+ function buildInitialSimulationPositions(options) {
435
+ const sourcePositions = options?.positionData;
436
+ if (!sourcePositions) {
437
+ return null;
438
+ }
439
+ const target = options?.collisionSpaceNode;
440
+ const blendIndices = options?.skinningBlendIndices;
441
+ const blendWeights = options?.skinningBlendWeights;
442
+ if (!target?.invWorldMatrix || !blendIndices || !blendWeights || typeof target?.findSkeletonById !== 'function') {
443
+ return new Float32Array(sourcePositions);
444
+ }
445
+ const skeletonId = String(target.skeletonName ?? '');
446
+ const skeleton = skeletonId ? target.findSkeletonById(skeletonId) : null;
447
+ if (!skeleton?.skinPositionsToLocal) {
448
+ return new Float32Array(sourcePositions);
449
+ }
450
+ return skeleton.skinPositionsToLocal(sourcePositions, blendIndices, blendWeights, target.invWorldMatrix);
451
+ }
452
+ async function resolveWrapBindPositions(mesh, basePositions) {
453
+ if (!basePositions || basePositions.length === 0) {
454
+ return new Float32Array(0);
455
+ }
456
+ const primitive = mesh?.primitive;
457
+ if (!primitive) {
458
+ return new Float32Array(basePositions);
459
+ }
460
+ const blendData = await readSkinningDataFromPrimitive(primitive);
461
+ if (!mesh?.invWorldMatrix || !blendData?.blendIndices || !blendData?.blendWeights || typeof mesh?.findSkeletonById !== 'function') {
462
+ return new Float32Array(basePositions);
463
+ }
464
+ const skeletonId = String(mesh.skeletonName ?? '');
465
+ const skeleton = skeletonId ? mesh.findSkeletonById(skeletonId) : null;
466
+ if (!skeleton?.skinPositionsToLocal) {
467
+ return new Float32Array(basePositions);
468
+ }
469
+ return skeleton.skinPositionsToLocal(basePositions, blendData.blendIndices, blendData.blendWeights, mesh.invWorldMatrix);
470
+ }
471
+ function createIntegrateProgram(device, workgroupSize) {
472
+ const program = device.buildComputeProgram({
473
+ workgroupSize: [
474
+ workgroupSize,
475
+ 1,
476
+ 1
477
+ ],
478
+ compute (pb) {
479
+ this.positions = pb.float[0]().storageBuffer(0);
480
+ this.prevPositions = pb.float[0]().storageBuffer(0);
481
+ this.restPositions = pb.float[0]().storageBufferReadonly(0);
482
+ this.prevRestPositions = pb.float[0]().storageBufferReadonly(0);
483
+ this.invMass = pb.float[0]().storageBufferReadonly(0);
484
+ this.sphereData = pb.float[0]().storageBufferReadonly(0);
485
+ this.capsuleData = pb.float[0]().storageBufferReadonly(0);
486
+ this.planeData = pb.float[0]().storageBufferReadonly(0);
487
+ this.vertexCount = pb.uint().uniform(0);
488
+ this.deltaTime = pb.float().uniform(0);
489
+ this.damping = pb.float().uniform(0);
490
+ this.gravity = pb.vec3().uniform(0);
491
+ this.sphereCount = pb.uint().uniform(0);
492
+ this.capsuleCount = pb.uint().uniform(0);
493
+ this.planeCount = pb.uint().uniform(0);
494
+ this.dynamicFriction = pb.float().uniform(0);
495
+ this.staticFriction = pb.float().uniform(0);
496
+ this.minDistance = pb.float().uniform(0);
497
+ pb.main(function() {
498
+ this.$l.index = this.$builtins.globalInvocationId.x;
499
+ this.$if(pb.lessThan(this.index, this.vertexCount), function() {
500
+ this.$l.base = pb.mul(this.index, 3);
501
+ this.$l.x = this.positions.at(this.base);
502
+ this.$l.y = this.positions.at(pb.add(this.base, 1));
503
+ this.$l.z = this.positions.at(pb.add(this.base, 2));
504
+ this.$l.px = this.prevPositions.at(this.base);
505
+ this.$l.py = this.prevPositions.at(pb.add(this.base, 1));
506
+ this.$l.pz = this.prevPositions.at(pb.add(this.base, 2));
507
+ this.$l.restX = this.restPositions.at(this.base);
508
+ this.$l.restY = this.restPositions.at(pb.add(this.base, 1));
509
+ this.$l.restZ = this.restPositions.at(pb.add(this.base, 2));
510
+ this.$l.prevRestX = this.prevRestPositions.at(this.base);
511
+ this.$l.prevRestY = this.prevRestPositions.at(pb.add(this.base, 1));
512
+ this.$l.prevRestZ = this.prevRestPositions.at(pb.add(this.base, 2));
513
+ this.$l.freeWeight = this.invMass.at(this.index);
514
+ this.$l.pinWeight = pb.sub(1, this.freeWeight);
515
+ this.$l.current = pb.vec3(this.x, this.y, this.z);
516
+ this.$l.previous = pb.vec3(this.px, this.py, this.pz);
517
+ this.$l.rest = pb.vec3(this.restX, this.restY, this.restZ);
518
+ this.$l.prevRest = pb.vec3(this.prevRestX, this.prevRestY, this.prevRestZ);
519
+ this.$l.restDelta = pb.sub(this.rest, this.prevRest);
520
+ this.current = pb.add(this.current, this.restDelta);
521
+ this.previous = pb.add(this.previous, this.restDelta);
522
+ this.$l.dt2 = pb.mul(this.deltaTime, this.deltaTime);
523
+ // Damping is modeled as velocity loss strength: 0 keeps full inertia, 1 removes it entirely.
524
+ this.$l.velocity = pb.mul(pb.sub(this.current, this.previous), pb.mul(pb.sub(1, this.damping), this.freeWeight));
525
+ this.$l.next = pb.add(this.current, this.velocity, pb.mul(this.gravity, pb.mul(this.dt2, this.freeWeight)));
526
+ this.$if(pb.greaterThan(this.freeWeight, this.minDistance), function() {
527
+ this.$for(pb.uint('i'), 0, this.sphereCount, function() {
528
+ this.$l.sphereBase = pb.mul(this.i, 4);
529
+ this.$l.center = pb.vec3(this.sphereData.at(this.sphereBase), this.sphereData.at(pb.add(this.sphereBase, 1)), this.sphereData.at(pb.add(this.sphereBase, 2)));
530
+ this.$l.radius = this.sphereData.at(pb.add(this.sphereBase, 3));
531
+ this.$l.deltaCol = pb.sub(this.next, this.center);
532
+ this.$l.lenCol = pb.length(this.deltaCol);
533
+ this.$if(pb.lessThan(this.lenCol, this.radius), function() {
534
+ this.$l.contactNormal = pb.vec3(0, 1, 0);
535
+ this.$if(pb.greaterThan(this.lenCol, this.minDistance), function() {
536
+ this.contactNormal = pb.div(this.deltaCol, this.lenCol);
537
+ this.next = pb.add(this.center, pb.mul(this.contactNormal, this.radius));
538
+ }).$else(function() {
539
+ this.next = pb.add(this.center, pb.vec3(0, this.radius, 0));
540
+ });
541
+ this.$l.correctionDelta = pb.sub(this.next, pb.add(this.center, this.deltaCol));
542
+ this.$l.correctionLen = pb.length(this.correctionDelta);
543
+ this.$l.surfaceDelta = pb.sub(this.next, this.current);
544
+ this.$l.normalStep = pb.mul(this.contactNormal, pb.dot(this.surfaceDelta, this.contactNormal));
545
+ this.$l.tangentStep = pb.sub(this.surfaceDelta, this.normalStep);
546
+ this.$l.normalTravel = pb.length(this.normalStep);
547
+ this.$l.tangentLen = pb.length(this.tangentStep);
548
+ this.$if(pb.greaterThan(this.tangentLen, this.minDistance), function() {
549
+ this.$l.dynamicBasis = pb.max(this.correctionLen, pb.max(this.normalTravel, pb.mul(this.tangentLen, 0.5)));
550
+ this.$l.staticBasis = pb.max(this.correctionLen, pb.max(this.normalTravel, this.tangentLen));
551
+ this.$l.staticLimit = pb.max(this.minDistance, pb.mul(this.staticBasis, pb.mul(this.staticFriction, 2)));
552
+ this.$if(pb.lessThanEqual(this.tangentLen, this.staticLimit), function() {
553
+ this.next = pb.sub(this.next, this.tangentStep);
554
+ }).$else(function() {
555
+ this.$l.dynamicRemove = pb.min(this.tangentLen, pb.mul(this.dynamicBasis, this.dynamicFriction));
556
+ this.next = pb.sub(this.next, pb.mul(pb.div(this.tangentStep, this.tangentLen), this.dynamicRemove));
557
+ });
558
+ });
559
+ });
560
+ });
561
+ this.$for(pb.uint('j'), 0, this.capsuleCount, function() {
562
+ this.$l.capsuleBase = pb.mul(this.j, 8);
563
+ this.$l.cStart = pb.vec3(this.capsuleData.at(this.capsuleBase), this.capsuleData.at(pb.add(this.capsuleBase, 1)), this.capsuleData.at(pb.add(this.capsuleBase, 2)));
564
+ this.$l.cRadius = this.capsuleData.at(pb.add(this.capsuleBase, 3));
565
+ this.$l.cEnd = pb.vec3(this.capsuleData.at(pb.add(this.capsuleBase, 4)), this.capsuleData.at(pb.add(this.capsuleBase, 5)), this.capsuleData.at(pb.add(this.capsuleBase, 6)));
566
+ this.$l.ab = pb.sub(this.cEnd, this.cStart);
567
+ this.$l.abLen2 = pb.dot(this.ab, this.ab);
568
+ this.$l.closest = this.cStart;
569
+ this.$if(pb.greaterThan(this.abLen2, this.minDistance), function() {
570
+ this.$l.ap = pb.sub(this.next, this.cStart);
571
+ this.$l.t = pb.clamp(pb.div(pb.dot(this.ap, this.ab), this.abLen2), 0, 1);
572
+ this.closest = pb.add(this.cStart, pb.mul(this.ab, this.t));
573
+ });
574
+ this.$l.deltaCap = pb.sub(this.next, this.closest);
575
+ this.$l.lenCap = pb.length(this.deltaCap);
576
+ this.$if(pb.lessThan(this.lenCap, this.cRadius), function() {
577
+ this.$l.contactNormal = pb.vec3(0, 1, 0);
578
+ this.$if(pb.greaterThan(this.lenCap, this.minDistance), function() {
579
+ this.contactNormal = pb.div(this.deltaCap, this.lenCap);
580
+ this.next = pb.add(this.closest, pb.mul(this.contactNormal, this.cRadius));
581
+ }).$else(function() {
582
+ this.next = pb.add(this.closest, pb.vec3(0, this.cRadius, 0));
583
+ });
584
+ this.$l.correctionDelta = pb.sub(this.next, pb.add(this.closest, this.deltaCap));
585
+ this.$l.correctionLen = pb.length(this.correctionDelta);
586
+ this.$l.surfaceDelta = pb.sub(this.next, this.current);
587
+ this.$l.normalStep = pb.mul(this.contactNormal, pb.dot(this.surfaceDelta, this.contactNormal));
588
+ this.$l.tangentStep = pb.sub(this.surfaceDelta, this.normalStep);
589
+ this.$l.normalTravel = pb.length(this.normalStep);
590
+ this.$l.tangentLen = pb.length(this.tangentStep);
591
+ this.$if(pb.greaterThan(this.tangentLen, this.minDistance), function() {
592
+ this.$l.dynamicBasis = pb.max(this.correctionLen, pb.max(this.normalTravel, pb.mul(this.tangentLen, 0.5)));
593
+ this.$l.staticBasis = pb.max(this.correctionLen, pb.max(this.normalTravel, this.tangentLen));
594
+ this.$l.staticLimit = pb.max(this.minDistance, pb.mul(this.staticBasis, pb.mul(this.staticFriction, 2)));
595
+ this.$if(pb.lessThanEqual(this.tangentLen, this.staticLimit), function() {
596
+ this.next = pb.sub(this.next, this.tangentStep);
597
+ }).$else(function() {
598
+ this.$l.dynamicRemove = pb.min(this.tangentLen, pb.mul(this.dynamicBasis, this.dynamicFriction));
599
+ this.next = pb.sub(this.next, pb.mul(pb.div(this.tangentStep, this.tangentLen), this.dynamicRemove));
600
+ });
601
+ });
602
+ });
603
+ });
604
+ this.$for(pb.uint('k'), 0, this.planeCount, function() {
605
+ this.$l.planeBase = pb.mul(this.k, 8);
606
+ this.$l.planePoint = pb.vec3(this.planeData.at(this.planeBase), this.planeData.at(pb.add(this.planeBase, 1)), this.planeData.at(pb.add(this.planeBase, 2)));
607
+ this.$l.planeNormal = pb.vec3(this.planeData.at(pb.add(this.planeBase, 4)), this.planeData.at(pb.add(this.planeBase, 5)), this.planeData.at(pb.add(this.planeBase, 6)));
608
+ this.$l.planeDistance = pb.dot(pb.sub(this.next, this.planePoint), this.planeNormal);
609
+ this.$if(pb.lessThan(this.planeDistance, 0), function() {
610
+ this.$l.contactNormal = this.planeNormal;
611
+ this.$l.preProject = this.next;
612
+ this.next = pb.sub(this.next, pb.mul(this.contactNormal, this.planeDistance));
613
+ this.$l.correctionDelta = pb.sub(this.next, this.preProject);
614
+ this.$l.correctionLen = pb.length(this.correctionDelta);
615
+ this.$l.surfaceDelta = pb.sub(this.next, this.current);
616
+ this.$l.normalStep = pb.mul(this.contactNormal, pb.dot(this.surfaceDelta, this.contactNormal));
617
+ this.$l.tangentStep = pb.sub(this.surfaceDelta, this.normalStep);
618
+ this.$l.normalTravel = pb.length(this.normalStep);
619
+ this.$l.tangentLen = pb.length(this.tangentStep);
620
+ this.$if(pb.greaterThan(this.tangentLen, this.minDistance), function() {
621
+ this.$l.dynamicBasis = pb.max(this.correctionLen, pb.max(this.normalTravel, pb.mul(this.tangentLen, 0.5)));
622
+ this.$l.staticBasis = pb.max(this.correctionLen, pb.max(this.normalTravel, this.tangentLen));
623
+ this.$l.staticLimit = pb.max(this.minDistance, pb.mul(this.staticBasis, pb.mul(this.staticFriction, 2)));
624
+ this.$if(pb.lessThanEqual(this.tangentLen, this.staticLimit), function() {
625
+ this.next = pb.sub(this.next, this.tangentStep);
626
+ }).$else(function() {
627
+ this.$l.dynamicRemove = pb.min(this.tangentLen, pb.mul(this.dynamicBasis, this.dynamicFriction));
628
+ this.next = pb.sub(this.next, pb.mul(pb.div(this.tangentStep, this.tangentLen), this.dynamicRemove));
629
+ });
630
+ });
631
+ });
632
+ });
633
+ });
634
+ this.next = pb.add(pb.mul(this.next, this.freeWeight), pb.mul(this.rest, this.pinWeight));
635
+ this.$l.prevOut = pb.add(pb.mul(this.current, this.freeWeight), pb.mul(this.rest, this.pinWeight));
636
+ this.prevPositions.setAt(this.base, this.prevOut.x);
637
+ this.prevPositions.setAt(pb.add(this.base, 1), this.prevOut.y);
638
+ this.prevPositions.setAt(pb.add(this.base, 2), this.prevOut.z);
639
+ this.positions.setAt(this.base, this.next.x);
640
+ this.positions.setAt(pb.add(this.base, 1), this.next.y);
641
+ this.positions.setAt(pb.add(this.base, 2), this.next.z);
642
+ });
643
+ });
644
+ }
645
+ });
646
+ if (program) {
647
+ program.name = '@GPUCloth_Integrate';
648
+ }
649
+ return program;
650
+ }
651
+ function createConstraintProgram(device, workgroupSize) {
652
+ const program = device.buildComputeProgram({
653
+ workgroupSize: [
654
+ workgroupSize,
655
+ 1,
656
+ 1
657
+ ],
658
+ compute (pb) {
659
+ this.positions = pb.float[0]().storageBuffer(0);
660
+ this.restPositions = pb.float[0]().storageBufferReadonly(0);
661
+ this.invMass = pb.float[0]().storageBufferReadonly(0);
662
+ this.neighborIndices = pb.int[0]().storageBufferReadonly(0);
663
+ this.restLengths = pb.float[0]().storageBufferReadonly(0);
664
+ this.sphereData = pb.float[0]().storageBufferReadonly(0);
665
+ this.capsuleData = pb.float[0]().storageBufferReadonly(0);
666
+ this.planeData = pb.float[0]().storageBufferReadonly(0);
667
+ this.vertexCount = pb.uint().uniform(0);
668
+ this.maxNeighbors = pb.uint().uniform(0);
669
+ this.stiffness = pb.float().uniform(0);
670
+ this.poseFollow = pb.float().uniform(0);
671
+ this.sphereCount = pb.uint().uniform(0);
672
+ this.capsuleCount = pb.uint().uniform(0);
673
+ this.planeCount = pb.uint().uniform(0);
674
+ this.dynamicFriction = pb.float().uniform(0);
675
+ this.staticFriction = pb.float().uniform(0);
676
+ this.minDistance = pb.float().uniform(0);
677
+ pb.main(function() {
678
+ this.$l.index = this.$builtins.globalInvocationId.x;
679
+ this.$if(pb.lessThan(this.index, this.vertexCount), function() {
680
+ this.$l.freeWeight = this.invMass.at(this.index);
681
+ this.$l.pinWeight = pb.sub(1, this.freeWeight);
682
+ this.$l.base = pb.mul(this.index, 3);
683
+ this.$l.rest = pb.vec3(this.restPositions.at(this.base), this.restPositions.at(pb.add(this.base, 1)), this.restPositions.at(pb.add(this.base, 2)));
684
+ this.$if(pb.greaterThan(this.freeWeight, this.minDistance), function() {
685
+ this.$l.pos = pb.vec3(this.positions.at(this.base), this.positions.at(pb.add(this.base, 1)), this.positions.at(pb.add(this.base, 2)));
686
+ this.$l.correction = pb.vec3(0);
687
+ this.$l.validCount = pb.uint(0);
688
+ this.$l.neighborStart = pb.mul(this.index, this.maxNeighbors);
689
+ this.$for(pb.uint('i'), 0, this.maxNeighbors, function() {
690
+ this.$l.slot = pb.add(this.neighborStart, this.i);
691
+ this.$l.neighbor = this.neighborIndices.at(this.slot);
692
+ this.$if(pb.greaterThanEqual(this.neighbor, 0), function() {
693
+ this.$l.neighborBase = pb.mul(pb.uint(this.neighbor), 3);
694
+ this.$l.neighborPos = pb.vec3(this.positions.at(this.neighborBase), this.positions.at(pb.add(this.neighborBase, 1)), this.positions.at(pb.add(this.neighborBase, 2)));
695
+ this.$l.restLength = this.restLengths.at(this.slot);
696
+ this.$l.delta = pb.sub(this.pos, this.neighborPos);
697
+ this.$l.len = pb.length(this.delta);
698
+ this.$if(pb.greaterThan(this.len, this.minDistance), function() {
699
+ this.$l.offset = pb.mul(pb.div(this.delta, this.len), pb.sub(this.len, this.restLength));
700
+ this.correction = pb.add(this.correction, this.offset);
701
+ this.validCount = pb.add(this.validCount, 1);
702
+ });
703
+ });
704
+ });
705
+ this.$l.corrected = this.pos;
706
+ this.$if(pb.greaterThan(this.validCount, 0), function() {
707
+ this.$l.scale = pb.div(pb.mul(this.stiffness, this.freeWeight), pb.float(this.validCount));
708
+ this.corrected = pb.sub(this.pos, pb.mul(this.correction, this.scale));
709
+ });
710
+ this.$if(pb.greaterThan(this.poseFollow, this.minDistance), function() {
711
+ this.$l.followStrength = pb.mul(this.poseFollow, this.freeWeight);
712
+ this.corrected = pb.add(this.corrected, pb.mul(pb.sub(this.rest, this.corrected), this.followStrength));
713
+ });
714
+ this.corrected = pb.add(pb.mul(this.corrected, this.freeWeight), pb.mul(this.rest, this.pinWeight));
715
+ this.$for(pb.uint('i'), 0, this.sphereCount, function() {
716
+ this.$l.sphereBase = pb.mul(this.i, 4);
717
+ this.$l.center = pb.vec3(this.sphereData.at(this.sphereBase), this.sphereData.at(pb.add(this.sphereBase, 1)), this.sphereData.at(pb.add(this.sphereBase, 2)));
718
+ this.$l.radius = this.sphereData.at(pb.add(this.sphereBase, 3));
719
+ this.$l.deltaCol = pb.sub(this.corrected, this.center);
720
+ this.$l.lenCol = pb.length(this.deltaCol);
721
+ this.$if(pb.lessThan(this.lenCol, this.radius), function() {
722
+ this.$if(pb.greaterThan(this.lenCol, this.minDistance), function() {
723
+ this.corrected = pb.add(this.center, pb.mul(pb.div(this.deltaCol, this.lenCol), this.radius));
724
+ }).$else(function() {
725
+ this.corrected = pb.add(this.center, pb.vec3(0, this.radius, 0));
726
+ });
727
+ });
728
+ });
729
+ this.$for(pb.uint('j'), 0, this.capsuleCount, function() {
730
+ this.$l.capsuleBase = pb.mul(this.j, 8);
731
+ this.$l.cStart = pb.vec3(this.capsuleData.at(this.capsuleBase), this.capsuleData.at(pb.add(this.capsuleBase, 1)), this.capsuleData.at(pb.add(this.capsuleBase, 2)));
732
+ this.$l.cRadius = this.capsuleData.at(pb.add(this.capsuleBase, 3));
733
+ this.$l.cEnd = pb.vec3(this.capsuleData.at(pb.add(this.capsuleBase, 4)), this.capsuleData.at(pb.add(this.capsuleBase, 5)), this.capsuleData.at(pb.add(this.capsuleBase, 6)));
734
+ this.$l.ab = pb.sub(this.cEnd, this.cStart);
735
+ this.$l.abLen2 = pb.dot(this.ab, this.ab);
736
+ this.$l.closest = this.cStart;
737
+ this.$if(pb.greaterThan(this.abLen2, this.minDistance), function() {
738
+ this.$l.ap = pb.sub(this.corrected, this.cStart);
739
+ this.$l.t = pb.clamp(pb.div(pb.dot(this.ap, this.ab), this.abLen2), 0, 1);
740
+ this.closest = pb.add(this.cStart, pb.mul(this.ab, this.t));
741
+ });
742
+ this.$l.deltaCap = pb.sub(this.corrected, this.closest);
743
+ this.$l.lenCap = pb.length(this.deltaCap);
744
+ this.$if(pb.lessThan(this.lenCap, this.cRadius), function() {
745
+ this.$if(pb.greaterThan(this.lenCap, this.minDistance), function() {
746
+ this.corrected = pb.add(this.closest, pb.mul(pb.div(this.deltaCap, this.lenCap), this.cRadius));
747
+ }).$else(function() {
748
+ this.corrected = pb.add(this.closest, pb.vec3(0, this.cRadius, 0));
749
+ });
750
+ });
751
+ });
752
+ this.$for(pb.uint('k'), 0, this.planeCount, function() {
753
+ this.$l.planeBase = pb.mul(this.k, 8);
754
+ this.$l.planePoint = pb.vec3(this.planeData.at(this.planeBase), this.planeData.at(pb.add(this.planeBase, 1)), this.planeData.at(pb.add(this.planeBase, 2)));
755
+ this.$l.planeNormal = pb.vec3(this.planeData.at(pb.add(this.planeBase, 4)), this.planeData.at(pb.add(this.planeBase, 5)), this.planeData.at(pb.add(this.planeBase, 6)));
756
+ this.$l.planeDistance = pb.dot(pb.sub(this.corrected, this.planePoint), this.planeNormal);
757
+ this.$if(pb.lessThan(this.planeDistance, 0), function() {
758
+ this.corrected = pb.sub(this.corrected, pb.mul(this.planeNormal, this.planeDistance));
759
+ });
760
+ });
761
+ this.positions.setAt(this.base, this.corrected.x);
762
+ this.positions.setAt(pb.add(this.base, 1), this.corrected.y);
763
+ this.positions.setAt(pb.add(this.base, 2), this.corrected.z);
764
+ }).$else(function() {
765
+ this.positions.setAt(this.base, this.rest.x);
766
+ this.positions.setAt(pb.add(this.base, 1), this.rest.y);
767
+ this.positions.setAt(pb.add(this.base, 2), this.rest.z);
768
+ });
769
+ });
770
+ });
771
+ }
772
+ });
773
+ if (program) {
774
+ program.name = '@GPUCloth_Constraint';
775
+ }
776
+ return program;
777
+ }
778
+ function createFaceNormalProgram(device, workgroupSize) {
779
+ const program = device.buildComputeProgram({
780
+ workgroupSize: [
781
+ workgroupSize,
782
+ 1,
783
+ 1
784
+ ],
785
+ compute (pb) {
786
+ this.positions = pb.float[0]().storageBufferReadonly(0);
787
+ this.triangleIndices = pb.uint[0]().storageBufferReadonly(0);
788
+ this.triangleNormals = pb.float[0]().storageBuffer(0);
789
+ this.triangleCount = pb.uint().uniform(0);
790
+ this.minDistance = pb.float().uniform(0);
791
+ pb.main(function() {
792
+ this.$l.tri = this.$builtins.globalInvocationId.x;
793
+ this.$if(pb.lessThan(this.tri, this.triangleCount), function() {
794
+ this.$l.indexBase = pb.mul(this.tri, 3);
795
+ this.$l.i0 = this.triangleIndices.at(this.indexBase);
796
+ this.$l.i1 = this.triangleIndices.at(pb.add(this.indexBase, 1));
797
+ this.$l.i2 = this.triangleIndices.at(pb.add(this.indexBase, 2));
798
+ this.$l.p0Base = pb.mul(this.i0, 3);
799
+ this.$l.p1Base = pb.mul(this.i1, 3);
800
+ this.$l.p2Base = pb.mul(this.i2, 3);
801
+ this.$l.p0 = pb.vec3(this.positions.at(this.p0Base), this.positions.at(pb.add(this.p0Base, 1)), this.positions.at(pb.add(this.p0Base, 2)));
802
+ this.$l.p1 = pb.vec3(this.positions.at(this.p1Base), this.positions.at(pb.add(this.p1Base, 1)), this.positions.at(pb.add(this.p1Base, 2)));
803
+ this.$l.p2 = pb.vec3(this.positions.at(this.p2Base), this.positions.at(pb.add(this.p2Base, 1)), this.positions.at(pb.add(this.p2Base, 2)));
804
+ this.$l.n = pb.cross(pb.sub(this.p1, this.p0), pb.sub(this.p2, this.p0));
805
+ this.$l.nLen = pb.length(this.n);
806
+ this.$if(pb.greaterThan(this.nLen, this.minDistance), function() {
807
+ this.n = pb.div(this.n, this.nLen);
808
+ }).$else(function() {
809
+ this.n = pb.vec3(0, 1, 0);
810
+ });
811
+ this.$l.outBase = pb.mul(this.tri, 3);
812
+ this.triangleNormals.setAt(this.outBase, this.n.x);
813
+ this.triangleNormals.setAt(pb.add(this.outBase, 1), this.n.y);
814
+ this.triangleNormals.setAt(pb.add(this.outBase, 2), this.n.z);
815
+ });
816
+ });
817
+ }
818
+ });
819
+ if (program) {
820
+ program.name = '@GPUCloth_FaceNormal';
821
+ }
822
+ return program;
823
+ }
824
+ function createVertexNormalProgram(device, workgroupSize) {
825
+ const program = device.buildComputeProgram({
826
+ workgroupSize: [
827
+ workgroupSize,
828
+ 1,
829
+ 1
830
+ ],
831
+ compute (pb) {
832
+ this.triangleNormals = pb.float[0]().storageBufferReadonly(0);
833
+ this.vertexTriangleAdjacency = pb.int[0]().storageBufferReadonly(0);
834
+ this.vertexNormals = pb.float[0]().storageBuffer(0);
835
+ this.vertexCount = pb.uint().uniform(0);
836
+ this.maxTrianglesPerVertex = pb.uint().uniform(0);
837
+ this.minDistance = pb.float().uniform(0);
838
+ pb.main(function() {
839
+ this.$l.vertex = this.$builtins.globalInvocationId.x;
840
+ this.$if(pb.lessThan(this.vertex, this.vertexCount), function() {
841
+ this.$l.sum = pb.vec3(0);
842
+ this.$l.valid = pb.uint(0);
843
+ this.$l.start = pb.mul(this.vertex, this.maxTrianglesPerVertex);
844
+ this.$for(pb.uint('i'), 0, this.maxTrianglesPerVertex, function() {
845
+ this.$l.tri = this.vertexTriangleAdjacency.at(pb.add(this.start, this.i));
846
+ this.$if(pb.greaterThanEqual(this.tri, 0), function() {
847
+ this.$l.nBase = pb.mul(pb.uint(this.tri), 3);
848
+ this.sum = pb.add(this.sum, pb.vec3(this.triangleNormals.at(this.nBase), this.triangleNormals.at(pb.add(this.nBase, 1)), this.triangleNormals.at(pb.add(this.nBase, 2))));
849
+ this.valid = pb.add(this.valid, 1);
850
+ });
851
+ });
852
+ this.$if(pb.greaterThan(this.valid, 0), function() {
853
+ this.sum = pb.div(this.sum, pb.float(this.valid));
854
+ }).$else(function() {
855
+ this.sum = pb.vec3(0, 1, 0);
856
+ });
857
+ this.$l.len = pb.length(this.sum);
858
+ this.$if(pb.greaterThan(this.len, this.minDistance), function() {
859
+ this.sum = pb.div(this.sum, this.len);
860
+ }).$else(function() {
861
+ this.sum = pb.vec3(0, 1, 0);
862
+ });
863
+ this.$l.outBase = pb.mul(this.vertex, 3);
864
+ this.vertexNormals.setAt(this.outBase, this.sum.x);
865
+ this.vertexNormals.setAt(pb.add(this.outBase, 1), this.sum.y);
866
+ this.vertexNormals.setAt(pb.add(this.outBase, 2), this.sum.z);
867
+ });
868
+ });
869
+ }
870
+ });
871
+ if (program) {
872
+ program.name = '@GPUCloth_VertexNormal';
873
+ }
874
+ return program;
875
+ }
876
+ async function readPositionDataFromPrimitive(primitive) {
877
+ const positions = await readVertexAttributeDataFromPrimitive(primitive, 'position', 3);
878
+ if (!positions) {
879
+ throw new Error('GPU cloth initialization failed: primitive has no position buffer.');
880
+ }
881
+ return positions;
882
+ }
883
+ async function readVertexAttributeDataFromPrimitive(primitive, semantic, componentCount) {
884
+ const info = primitive.getVertexBufferInfo(semantic);
885
+ if (!info || !info.type.isPrimitiveType() || info.type.cols < componentCount) {
886
+ return null;
887
+ }
888
+ const vertexCount = primitive.getNumVertices();
889
+ if (vertexCount <= 0) {
890
+ return null;
891
+ }
892
+ const bytes = await info.buffer.getBufferSubData();
893
+ const result = new Float32Array(vertexCount * componentCount);
894
+ const scalarType = info.type.scalarType;
895
+ const normalized = info.type.normalized;
896
+ const componentByteSize = getPrimitiveScalarByteSize(scalarType);
897
+ const baseByteOffset = info.drawOffset + info.offset;
898
+ if (scalarType === PBPrimitiveType.F32 && !normalized && baseByteOffset % 4 === 0 && info.stride % 4 === 0) {
899
+ const raw = new Float32Array(bytes.buffer, bytes.byteOffset, bytes.byteLength >> 2);
900
+ const stride = info.stride >> 2;
901
+ const srcOffset = baseByteOffset >> 2;
902
+ for(let i = 0; i < vertexCount; i++){
903
+ const src = srcOffset + i * stride;
904
+ const dst = i * componentCount;
905
+ for(let c = 0; c < componentCount; c++){
906
+ result[dst + c] = raw[src + c];
907
+ }
908
+ }
909
+ return result;
910
+ }
911
+ if (scalarType === PBPrimitiveType.F16) {
912
+ throw new Error(`GPU cloth initialization failed: unsupported ${semantic} attribute format.`);
913
+ }
914
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
915
+ for(let i = 0; i < vertexCount; i++){
916
+ const src = baseByteOffset + i * info.stride;
917
+ const dst = i * componentCount;
918
+ for(let c = 0; c < componentCount; c++){
919
+ result[dst + c] = readPrimitiveScalar(view, src + c * componentByteSize, scalarType, normalized);
920
+ }
921
+ }
922
+ return result;
923
+ }
924
+ async function readSkinningDataFromPrimitive(primitive) {
925
+ try {
926
+ const [blendIndices, blendWeights] = await Promise.all([
927
+ readVertexAttributeDataFromPrimitive(primitive, 'blendIndices', 4),
928
+ readVertexAttributeDataFromPrimitive(primitive, 'blendWeights', 4)
929
+ ]);
930
+ if (!blendIndices || !blendWeights) {
931
+ return null;
932
+ }
933
+ return {
934
+ blendIndices,
935
+ blendWeights
936
+ };
937
+ } catch {
938
+ return null;
939
+ }
940
+ }
941
+ function buildNonIndexedTriangles(primitive, vertexCount) {
942
+ if (primitive.primitiveType !== 'triangle-list') {
943
+ throw new Error('GPU cloth initialization failed: only triangle-list primitive is supported.');
944
+ }
945
+ const start = primitive.indexStart;
946
+ const count = primitive.indexCount;
947
+ if (count <= 0 || count % 3 !== 0 || start < 0 || start + count > vertexCount) {
948
+ throw new Error('GPU cloth initialization failed: invalid non-indexed triangle range.');
949
+ }
950
+ const indices = new Uint32Array(count);
951
+ for(let i = 0; i < count; i++){
952
+ indices[i] = start + i;
953
+ }
954
+ return indices;
955
+ }
956
+ async function readIndexDataFromPrimitive(primitive, vertexCount) {
957
+ const indexBuffer = primitive.getIndexBuffer();
958
+ if (!indexBuffer) {
959
+ return buildNonIndexedTriangles(primitive, vertexCount);
960
+ }
961
+ if (primitive.primitiveType !== 'triangle-list') {
962
+ throw new Error('GPU cloth initialization failed: only triangle-list primitive is supported.');
963
+ }
964
+ const bytes = await indexBuffer.getBufferSubData();
965
+ if (indexBuffer.indexType.primitiveType === PBPrimitiveType.U16) {
966
+ const src = new Uint16Array(bytes.buffer, bytes.byteOffset, bytes.byteLength >> 1);
967
+ return new Uint16Array(src);
968
+ }
969
+ const src = new Uint32Array(bytes.buffer, bytes.byteOffset, bytes.byteLength >> 2);
970
+ return new Uint32Array(src);
971
+ }
972
+ function createWrapDeformerProgram(device, workgroupSize) {
973
+ const program = device.buildComputeProgram({
974
+ workgroupSize: [
975
+ workgroupSize,
976
+ 1,
977
+ 1
978
+ ],
979
+ compute (pb) {
980
+ this.sourcePositions = pb.float[0]().storageBufferReadonly(0);
981
+ this.sourceTriangleIndices = pb.uint[0]().storageBufferReadonly(0);
982
+ this.sourceBarycentrics = pb.float[0]().storageBufferReadonly(0);
983
+ this.targetLocalOffsets = pb.float[0]().storageBufferReadonly(0);
984
+ this.targetPositions = pb.float[0]().storageBuffer(0);
985
+ this.targetNormals = pb.float[0]().storageBuffer(0);
986
+ this.vertexCount = pb.uint().uniform(0);
987
+ this.sourceToTargetMatrix = pb.mat4().uniform(0);
988
+ this.minDistance = pb.float().uniform(0);
989
+ pb.main(function() {
990
+ this.$l.vertex = this.$builtins.globalInvocationId.x;
991
+ this.$if(pb.lessThan(this.vertex, this.vertexCount), function() {
992
+ this.$l.targetBase = pb.mul(this.vertex, 3);
993
+ this.$l.influenceBase = pb.mul(this.vertex, WRAP_TRIANGLE_VERTEX_COUNT);
994
+ this.$l.i0 = this.sourceTriangleIndices.at(this.influenceBase);
995
+ this.$l.i1 = this.sourceTriangleIndices.at(pb.add(this.influenceBase, 1));
996
+ this.$l.i2 = this.sourceTriangleIndices.at(pb.add(this.influenceBase, 2));
997
+ this.$l.base0 = pb.mul(this.i0, 3);
998
+ this.$l.base1 = pb.mul(this.i1, 3);
999
+ this.$l.base2 = pb.mul(this.i2, 3);
1000
+ this.$l.w0 = this.sourceBarycentrics.at(this.influenceBase);
1001
+ this.$l.w1 = this.sourceBarycentrics.at(pb.add(this.influenceBase, 1));
1002
+ this.$l.w2 = this.sourceBarycentrics.at(pb.add(this.influenceBase, 2));
1003
+ this.$l.p0 = pb.mul(this.sourceToTargetMatrix, pb.vec4(this.sourcePositions.at(this.base0), this.sourcePositions.at(pb.add(this.base0, 1)), this.sourcePositions.at(pb.add(this.base0, 2)), 1)).xyz;
1004
+ this.$l.p1 = pb.mul(this.sourceToTargetMatrix, pb.vec4(this.sourcePositions.at(this.base1), this.sourcePositions.at(pb.add(this.base1, 1)), this.sourcePositions.at(pb.add(this.base1, 2)), 1)).xyz;
1005
+ this.$l.p2 = pb.mul(this.sourceToTargetMatrix, pb.vec4(this.sourcePositions.at(this.base2), this.sourcePositions.at(pb.add(this.base2, 1)), this.sourcePositions.at(pb.add(this.base2, 2)), 1)).xyz;
1006
+ this.$l.basePoint = pb.add(pb.mul(this.p0, this.w0), pb.mul(this.p1, this.w1), pb.mul(this.p2, this.w2));
1007
+ this.$l.edge01 = pb.sub(this.p1, this.p0);
1008
+ this.$l.edge02 = pb.sub(this.p2, this.p0);
1009
+ this.$l.tangent = this.edge01;
1010
+ this.$l.tangentLen = pb.length(this.tangent);
1011
+ this.$if(pb.lessThanEqual(this.tangentLen, this.minDistance), function() {
1012
+ this.tangent = this.edge02;
1013
+ this.tangentLen = pb.length(this.tangent);
1014
+ });
1015
+ this.$if(pb.greaterThan(this.tangentLen, this.minDistance), function() {
1016
+ this.tangent = pb.div(this.tangent, this.tangentLen);
1017
+ }).$else(function() {
1018
+ this.tangent = pb.vec3(1, 0, 0);
1019
+ });
1020
+ this.$l.normal = pb.cross(this.edge01, this.edge02);
1021
+ this.$l.normalLen = pb.length(this.normal);
1022
+ this.$if(pb.greaterThan(this.normalLen, this.minDistance), function() {
1023
+ this.normal = pb.div(this.normal, this.normalLen);
1024
+ }).$else(function() {
1025
+ this.$l.fallbackAxis = pb.vec3(0, 1, 0);
1026
+ this.$if(pb.or(pb.greaterThan(this.tangent.y, 0.999), pb.lessThan(this.tangent.y, -0.999)), function() {
1027
+ this.fallbackAxis = pb.vec3(1, 0, 0);
1028
+ });
1029
+ this.normal = pb.cross(this.tangent, this.fallbackAxis);
1030
+ this.normalLen = pb.length(this.normal);
1031
+ this.$if(pb.greaterThan(this.normalLen, this.minDistance), function() {
1032
+ this.normal = pb.div(this.normal, this.normalLen);
1033
+ }).$else(function() {
1034
+ this.normal = pb.vec3(0, 0, 1);
1035
+ });
1036
+ });
1037
+ this.$l.bitangent = pb.cross(this.normal, this.tangent);
1038
+ this.$l.bitangentLen = pb.length(this.bitangent);
1039
+ this.$if(pb.greaterThan(this.bitangentLen, this.minDistance), function() {
1040
+ this.bitangent = pb.div(this.bitangent, this.bitangentLen);
1041
+ }).$else(function() {
1042
+ this.bitangent = pb.vec3(0, 1, 0);
1043
+ });
1044
+ this.normal = pb.cross(this.tangent, this.bitangent);
1045
+ this.normalLen = pb.length(this.normal);
1046
+ this.$if(pb.greaterThan(this.normalLen, this.minDistance), function() {
1047
+ this.normal = pb.div(this.normal, this.normalLen);
1048
+ }).$else(function() {
1049
+ this.normal = pb.vec3(0, 0, 1);
1050
+ });
1051
+ this.$l.localOffset = pb.vec3(this.targetLocalOffsets.at(this.targetBase), this.targetLocalOffsets.at(pb.add(this.targetBase, 1)), this.targetLocalOffsets.at(pb.add(this.targetBase, 2)));
1052
+ this.$l.deformedPosition = pb.add(this.basePoint, pb.add(pb.mul(this.tangent, this.localOffset.x), pb.mul(this.bitangent, this.localOffset.y), pb.mul(this.normal, this.localOffset.z)));
1053
+ this.targetPositions.setAt(this.targetBase, this.deformedPosition.x);
1054
+ this.targetPositions.setAt(pb.add(this.targetBase, 1), this.deformedPosition.y);
1055
+ this.targetPositions.setAt(pb.add(this.targetBase, 2), this.deformedPosition.z);
1056
+ this.targetNormals.setAt(this.targetBase, this.normal.x);
1057
+ this.targetNormals.setAt(pb.add(this.targetBase, 1), this.normal.y);
1058
+ this.targetNormals.setAt(pb.add(this.targetBase, 2), this.normal.z);
1059
+ });
1060
+ });
1061
+ }
1062
+ });
1063
+ if (program) {
1064
+ program.name = '@GPUCloth_DisplacementWrapTarget';
1065
+ }
1066
+ return program;
1067
+ }
1068
+ function transformPointArrayByMatrix(matrix, source, out) {
1069
+ const target = new Float32Array(source.length);
1070
+ for(let i = 0; i + 2 < source.length; i += 3){
1071
+ const x = source[i];
1072
+ const y = source[i + 1];
1073
+ const z = source[i + 2];
1074
+ target[i] = matrix[0] * x + matrix[4] * y + matrix[8] * z + matrix[12];
1075
+ target[i + 1] = matrix[1] * x + matrix[5] * y + matrix[9] * z + matrix[13];
1076
+ target[i + 2] = matrix[2] * x + matrix[6] * y + matrix[10] * z + matrix[14];
1077
+ }
1078
+ return target;
1079
+ }
1080
+ function getWrapSourceToTargetMatrix(source, target, out) {
1081
+ if (!source?.worldMatrix || !target?.invWorldMatrix) {
1082
+ return (out ?? new Matrix4x4()).identity();
1083
+ }
1084
+ return Matrix4x4.multiplyAffine(target.invWorldMatrix, source.worldMatrix, out);
1085
+ }
1086
+ class GPUClothWrapBinding {
1087
+ _device;
1088
+ _source;
1089
+ _target;
1090
+ _targetPrimitive;
1091
+ _wrapTriangleIndexBuffer;
1092
+ _wrapBarycentricBuffer;
1093
+ _wrapLocalOffsetBuffer;
1094
+ _program;
1095
+ _bindGroup;
1096
+ _workgroupCount;
1097
+ _maxOffsetDistance;
1098
+ _sourceToTargetMatrix;
1099
+ _positionBuffer;
1100
+ _normalBuffer;
1101
+ _originalPositionBuffer;
1102
+ _originalNormalBuffer;
1103
+ _restoreSkinning;
1104
+ constructor(device, source, target, targetPrimitive, wrapTriangleIndexBuffer, wrapBarycentricBuffer, wrapLocalOffsetBuffer, program, bindGroup, positionBuffer, normalBuffer, originalPositionBuffer, originalNormalBuffer, workgroupCount, maxOffsetDistance, restoreSkinning){
1105
+ this._device = device;
1106
+ this._source = source;
1107
+ this._target = target;
1108
+ this._targetPrimitive = targetPrimitive;
1109
+ this._wrapTriangleIndexBuffer = wrapTriangleIndexBuffer;
1110
+ this._wrapBarycentricBuffer = wrapBarycentricBuffer;
1111
+ this._wrapLocalOffsetBuffer = wrapLocalOffsetBuffer;
1112
+ this._program = program;
1113
+ this._bindGroup = bindGroup;
1114
+ this._workgroupCount = workgroupCount;
1115
+ this._maxOffsetDistance = maxOffsetDistance;
1116
+ this._sourceToTargetMatrix = new Matrix4x4();
1117
+ this._positionBuffer = positionBuffer;
1118
+ this._normalBuffer = normalBuffer;
1119
+ this._originalPositionBuffer = originalPositionBuffer;
1120
+ this._originalNormalBuffer = originalNormalBuffer;
1121
+ this._restoreSkinning = restoreSkinning;
1122
+ }
1123
+ static async create(device, source, sourcePositionBuffer, sourceRestPositions, sourceIndexData, target, workgroupSize) {
1124
+ const data = await GPUClothWrapBinding.createBindingData(source, sourceRestPositions, sourceIndexData, target);
1125
+ return GPUClothWrapBinding.createFromData(device, source, sourcePositionBuffer, sourceRestPositions, sourceIndexData, target, workgroupSize, data);
1126
+ }
1127
+ static async createBindingData(source, sourceRestPositions, sourceIndexData, target) {
1128
+ if (!target?.primitive) {
1129
+ throw new Error('GPU cloth wrap failed: target mesh has no primitive.');
1130
+ }
1131
+ const targetPrimitive = target.primitive;
1132
+ const targetBasePositions = await readPositionDataFromPrimitive(targetPrimitive);
1133
+ const sourceToTargetBindMatrix = getWrapSourceToTargetMatrix(source, target);
1134
+ const [resolvedSourcePositions, targetPositions] = await Promise.all([
1135
+ resolveWrapBindPositions(source, sourceRestPositions),
1136
+ resolveWrapBindPositions(target, targetBasePositions)
1137
+ ]);
1138
+ const sourcePositionsInTargetSpace = transformPointArrayByMatrix(sourceToTargetBindMatrix, resolvedSourcePositions);
1139
+ const sourceVertexCount = sourcePositionsInTargetSpace.length / 3 >> 0;
1140
+ const sourceTriangleCount = sourceIndexData.length / 3 >> 0;
1141
+ if (sourceVertexCount <= 0) {
1142
+ throw new Error('GPU cloth wrap failed: source mesh has no vertices.');
1143
+ }
1144
+ if (sourceTriangleCount <= 0) {
1145
+ throw new Error('GPU cloth wrap failed: source mesh has no triangles.');
1146
+ }
1147
+ const vertexCount = targetPositions.length / 3 >> 0;
1148
+ const sourceTriangleIndices = new Uint32Array(vertexCount * WRAP_TRIANGLE_VERTEX_COUNT);
1149
+ const sourceBarycentrics = new Float32Array(vertexCount * WRAP_TRIANGLE_VERTEX_COUNT);
1150
+ const targetLocalOffsets = new Float32Array(vertexCount * 3);
1151
+ let maxOffsetDistance = 0;
1152
+ const closestPoint = new Float32Array(3);
1153
+ const barycentrics = new Float32Array(3);
1154
+ const frame = new Float32Array(9);
1155
+ for(let vertex = 0; vertex < vertexCount; vertex++){
1156
+ const targetBase = vertex * 3;
1157
+ const vx = targetPositions[targetBase];
1158
+ const vy = targetPositions[targetBase + 1];
1159
+ const vz = targetPositions[targetBase + 2];
1160
+ let bestDistanceSq = Number.POSITIVE_INFINITY;
1161
+ let bestI0 = 0;
1162
+ let bestI1 = 0;
1163
+ let bestI2 = 0;
1164
+ let bestClosestX = vx;
1165
+ let bestClosestY = vy;
1166
+ let bestClosestZ = vz;
1167
+ let bestBary0 = 1;
1168
+ let bestBary1 = 0;
1169
+ let bestBary2 = 0;
1170
+ for(let tri = 0; tri < sourceTriangleCount; tri++){
1171
+ const triBase = tri * 3;
1172
+ const i0 = sourceIndexData[triBase];
1173
+ const i1 = sourceIndexData[triBase + 1];
1174
+ const i2 = sourceIndexData[triBase + 2];
1175
+ if (i0 >= sourceVertexCount || i1 >= sourceVertexCount || i2 >= sourceVertexCount) {
1176
+ continue;
1177
+ }
1178
+ const base0 = i0 * 3;
1179
+ const base1 = i1 * 3;
1180
+ const base2 = i2 * 3;
1181
+ const distanceSq = closestPointOnTriangle(vx, vy, vz, sourcePositionsInTargetSpace[base0], sourcePositionsInTargetSpace[base0 + 1], sourcePositionsInTargetSpace[base0 + 2], sourcePositionsInTargetSpace[base1], sourcePositionsInTargetSpace[base1 + 1], sourcePositionsInTargetSpace[base1 + 2], sourcePositionsInTargetSpace[base2], sourcePositionsInTargetSpace[base2 + 1], sourcePositionsInTargetSpace[base2 + 2], closestPoint, barycentrics);
1182
+ if (distanceSq < bestDistanceSq) {
1183
+ bestDistanceSq = distanceSq;
1184
+ bestI0 = i0;
1185
+ bestI1 = i1;
1186
+ bestI2 = i2;
1187
+ bestClosestX = closestPoint[0];
1188
+ bestClosestY = closestPoint[1];
1189
+ bestClosestZ = closestPoint[2];
1190
+ bestBary0 = barycentrics[0];
1191
+ bestBary1 = barycentrics[1];
1192
+ bestBary2 = barycentrics[2];
1193
+ }
1194
+ }
1195
+ if (!Number.isFinite(bestDistanceSq)) {
1196
+ throw new Error('GPU cloth wrap failed: source mesh triangle data is invalid.');
1197
+ }
1198
+ const influenceBase = vertex * WRAP_TRIANGLE_VERTEX_COUNT;
1199
+ sourceTriangleIndices[influenceBase] = bestI0;
1200
+ sourceTriangleIndices[influenceBase + 1] = bestI1;
1201
+ sourceTriangleIndices[influenceBase + 2] = bestI2;
1202
+ sourceBarycentrics[influenceBase] = bestBary0;
1203
+ sourceBarycentrics[influenceBase + 1] = bestBary1;
1204
+ sourceBarycentrics[influenceBase + 2] = bestBary2;
1205
+ const bestBase0 = bestI0 * 3;
1206
+ const bestBase1 = bestI1 * 3;
1207
+ const bestBase2 = bestI2 * 3;
1208
+ buildWrapTriangleFrame(sourcePositionsInTargetSpace[bestBase0], sourcePositionsInTargetSpace[bestBase0 + 1], sourcePositionsInTargetSpace[bestBase0 + 2], sourcePositionsInTargetSpace[bestBase1], sourcePositionsInTargetSpace[bestBase1 + 1], sourcePositionsInTargetSpace[bestBase1 + 2], sourcePositionsInTargetSpace[bestBase2], sourcePositionsInTargetSpace[bestBase2 + 1], sourcePositionsInTargetSpace[bestBase2 + 2], frame);
1209
+ const deltaX = vx - bestClosestX;
1210
+ const deltaY = vy - bestClosestY;
1211
+ const deltaZ = vz - bestClosestZ;
1212
+ targetLocalOffsets[targetBase] = dot3(deltaX, deltaY, deltaZ, frame[0], frame[1], frame[2]);
1213
+ targetLocalOffsets[targetBase + 1] = dot3(deltaX, deltaY, deltaZ, frame[3], frame[4], frame[5]);
1214
+ targetLocalOffsets[targetBase + 2] = dot3(deltaX, deltaY, deltaZ, frame[6], frame[7], frame[8]);
1215
+ maxOffsetDistance = Math.max(maxOffsetDistance, Math.sqrt(bestDistanceSq));
1216
+ }
1217
+ return {
1218
+ version: 4,
1219
+ vertexCount,
1220
+ sourceVertexCount,
1221
+ influenceCount: WRAP_TRIANGLE_VERTEX_COUNT,
1222
+ maxOffsetDistance,
1223
+ sourceTriangleIndices: encodeTypedArrayBase64(sourceTriangleIndices),
1224
+ sourceBarycentrics: encodeTypedArrayBase64(sourceBarycentrics),
1225
+ targetLocalOffsets: encodeTypedArrayBase64(targetLocalOffsets)
1226
+ };
1227
+ }
1228
+ static createFromData(device, source, sourcePositionBuffer, sourceRestPositions, _sourceIndexData, target, workgroupSize, data) {
1229
+ if (!target?.primitive) {
1230
+ throw new Error('GPU cloth wrap failed: target mesh has no primitive.');
1231
+ }
1232
+ const targetPrimitive = target.primitive;
1233
+ const vertexCount = targetPrimitive.getNumVertices();
1234
+ if (!isWrapBindingDataCompatible(data, sourceRestPositions, vertexCount)) {
1235
+ throw new Error('GPU cloth wrap failed: binding cache is incompatible with current meshes.');
1236
+ }
1237
+ const expectedElementCount = vertexCount * 3;
1238
+ const influenceElementCount = vertexCount * data.influenceCount;
1239
+ const wrapSourceTriangleIndices = decodeTypedArrayFromBase64(Uint32Array, data.sourceTriangleIndices, influenceElementCount);
1240
+ const wrapSourceBarycentrics = decodeTypedArrayFromBase64(Float32Array, data.sourceBarycentrics, influenceElementCount);
1241
+ const wrapTargetLocalOffsets = decodeTypedArrayFromBase64(Float32Array, data.targetLocalOffsets, expectedElementCount);
1242
+ const positionBuffer = device.createVertexBuffer('position_f32x3', new Float32Array(expectedElementCount), {
1243
+ storage: true,
1244
+ managed: false
1245
+ });
1246
+ if (!positionBuffer) {
1247
+ throw new Error('GPU cloth wrap failed: could not create target position buffer.');
1248
+ }
1249
+ const normalBuffer = device.createVertexBuffer('normal_f32x3', new Float32Array(expectedElementCount), {
1250
+ storage: true,
1251
+ managed: false
1252
+ });
1253
+ if (!normalBuffer) {
1254
+ throw new Error('GPU cloth wrap failed: could not create target normal buffer.');
1255
+ }
1256
+ const wrapTriangleIndexBuffer = device.createBuffer(wrapSourceTriangleIndices.byteLength, {
1257
+ usage: 'uniform',
1258
+ storage: true,
1259
+ dynamic: false,
1260
+ managed: false
1261
+ });
1262
+ wrapTriangleIndexBuffer.bufferSubData(0, wrapSourceTriangleIndices);
1263
+ const wrapBarycentricBuffer = device.createBuffer(wrapSourceBarycentrics.byteLength, {
1264
+ usage: 'uniform',
1265
+ storage: true,
1266
+ dynamic: false,
1267
+ managed: false
1268
+ });
1269
+ wrapBarycentricBuffer.bufferSubData(0, wrapSourceBarycentrics);
1270
+ const wrapLocalOffsetBuffer = device.createBuffer(wrapTargetLocalOffsets.byteLength, {
1271
+ usage: 'uniform',
1272
+ storage: true,
1273
+ dynamic: false,
1274
+ managed: false
1275
+ });
1276
+ wrapLocalOffsetBuffer.bufferSubData(0, wrapTargetLocalOffsets);
1277
+ const program = createWrapDeformerProgram(device, workgroupSize);
1278
+ if (!program) {
1279
+ throw new Error('GPU cloth wrap failed: could not create compute program.');
1280
+ }
1281
+ const bindGroup = device.createBindGroup(program.bindGroupLayouts[0]);
1282
+ bindGroup.setBuffer('sourcePositions', sourcePositionBuffer);
1283
+ bindGroup.setBuffer('sourceTriangleIndices', wrapTriangleIndexBuffer);
1284
+ bindGroup.setBuffer('sourceBarycentrics', wrapBarycentricBuffer);
1285
+ bindGroup.setBuffer('targetLocalOffsets', wrapLocalOffsetBuffer);
1286
+ bindGroup.setBuffer('targetPositions', positionBuffer);
1287
+ bindGroup.setBuffer('targetNormals', normalBuffer);
1288
+ const sourceToTargetBindMatrix = getWrapSourceToTargetMatrix(source, target);
1289
+ bindGroup.setValue('vertexCount', vertexCount);
1290
+ bindGroup.setValue('sourceToTargetMatrix', sourceToTargetBindMatrix);
1291
+ bindGroup.setValue('minDistance', 1e-5);
1292
+ const originalPositionBuffer = targetPrimitive.getVertexBuffer('position');
1293
+ const originalNormalBuffer = targetPrimitive.getVertexBuffer('normal');
1294
+ retainObject(originalPositionBuffer);
1295
+ retainObject(originalNormalBuffer);
1296
+ targetPrimitive.removeVertexBuffer('position');
1297
+ targetPrimitive.setVertexBuffer(positionBuffer);
1298
+ targetPrimitive.removeVertexBuffer('normal');
1299
+ targetPrimitive.setVertexBuffer(normalBuffer);
1300
+ let restoreSkinning = null;
1301
+ if (typeof target?.suspendSkinning === 'boolean') {
1302
+ restoreSkinning = target.suspendSkinning;
1303
+ target.suspendSkinning = true;
1304
+ target.setBoneMatrices?.(null);
1305
+ target.setAnimatedBoundingBox?.(null);
1306
+ }
1307
+ const binding = new GPUClothWrapBinding(device, source, target, targetPrimitive, wrapTriangleIndexBuffer, wrapBarycentricBuffer, wrapLocalOffsetBuffer, program, bindGroup, positionBuffer, normalBuffer, originalPositionBuffer, originalNormalBuffer, Math.max(1, Math.ceil(vertexCount / workgroupSize)), Math.max(0, Number(data.maxOffsetDistance) || 0), restoreSkinning);
1308
+ binding.update();
1309
+ return binding;
1310
+ }
1311
+ update() {
1312
+ getWrapSourceToTargetMatrix(this._source, this._target, this._sourceToTargetMatrix);
1313
+ this._bindGroup.setValue('sourceToTargetMatrix', this._sourceToTargetMatrix);
1314
+ this._device.setProgram(this._program);
1315
+ this._device.setBindGroup(0, this._bindGroup);
1316
+ this._device.compute(this._workgroupCount, 1, 1);
1317
+ this.updateBoundingBox();
1318
+ }
1319
+ dispose() {
1320
+ this._bindGroup.dispose();
1321
+ this._program.dispose();
1322
+ this._wrapTriangleIndexBuffer.dispose();
1323
+ this._wrapBarycentricBuffer.dispose();
1324
+ this._wrapLocalOffsetBuffer.dispose();
1325
+ if (this._targetPrimitive) {
1326
+ this._targetPrimitive.removeVertexBuffer('position');
1327
+ if (this._originalPositionBuffer) {
1328
+ this._targetPrimitive.setVertexBuffer(this._originalPositionBuffer);
1329
+ }
1330
+ this._targetPrimitive.removeVertexBuffer('normal');
1331
+ if (this._originalNormalBuffer) {
1332
+ this._targetPrimitive.setVertexBuffer(this._originalNormalBuffer);
1333
+ }
1334
+ }
1335
+ releaseObject(this._originalPositionBuffer);
1336
+ releaseObject(this._originalNormalBuffer);
1337
+ this._positionBuffer.dispose();
1338
+ this._normalBuffer.dispose();
1339
+ if (this._target && this._restoreSkinning !== null) {
1340
+ this._target.setAnimatedBoundingBox?.(null);
1341
+ this._target.suspendSkinning = this._restoreSkinning;
1342
+ }
1343
+ }
1344
+ updateBoundingBox() {
1345
+ const sourceBBox = this._source?.getAnimatedBoundingBox?.() ?? this._source?.primitive?.getBoundingVolume?.()?.toAABB?.() ?? null;
1346
+ if (!sourceBBox) {
1347
+ return;
1348
+ }
1349
+ let minX = Number.POSITIVE_INFINITY;
1350
+ let minY = Number.POSITIVE_INFINITY;
1351
+ let minZ = Number.POSITIVE_INFINITY;
1352
+ let maxX = Number.NEGATIVE_INFINITY;
1353
+ let maxY = Number.NEGATIVE_INFINITY;
1354
+ let maxZ = Number.NEGATIVE_INFINITY;
1355
+ getWrapSourceToTargetMatrix(this._source, this._target, this._sourceToTargetMatrix);
1356
+ const corners = [
1357
+ [
1358
+ sourceBBox.minPoint.x,
1359
+ sourceBBox.minPoint.y,
1360
+ sourceBBox.minPoint.z
1361
+ ],
1362
+ [
1363
+ sourceBBox.minPoint.x,
1364
+ sourceBBox.minPoint.y,
1365
+ sourceBBox.maxPoint.z
1366
+ ],
1367
+ [
1368
+ sourceBBox.minPoint.x,
1369
+ sourceBBox.maxPoint.y,
1370
+ sourceBBox.minPoint.z
1371
+ ],
1372
+ [
1373
+ sourceBBox.minPoint.x,
1374
+ sourceBBox.maxPoint.y,
1375
+ sourceBBox.maxPoint.z
1376
+ ],
1377
+ [
1378
+ sourceBBox.maxPoint.x,
1379
+ sourceBBox.minPoint.y,
1380
+ sourceBBox.minPoint.z
1381
+ ],
1382
+ [
1383
+ sourceBBox.maxPoint.x,
1384
+ sourceBBox.minPoint.y,
1385
+ sourceBBox.maxPoint.z
1386
+ ],
1387
+ [
1388
+ sourceBBox.maxPoint.x,
1389
+ sourceBBox.maxPoint.y,
1390
+ sourceBBox.minPoint.z
1391
+ ],
1392
+ [
1393
+ sourceBBox.maxPoint.x,
1394
+ sourceBBox.maxPoint.y,
1395
+ sourceBBox.maxPoint.z
1396
+ ]
1397
+ ];
1398
+ for (const corner of corners){
1399
+ const x = this._sourceToTargetMatrix[0] * corner[0] + this._sourceToTargetMatrix[4] * corner[1] + this._sourceToTargetMatrix[8] * corner[2] + this._sourceToTargetMatrix[12];
1400
+ const y = this._sourceToTargetMatrix[1] * corner[0] + this._sourceToTargetMatrix[5] * corner[1] + this._sourceToTargetMatrix[9] * corner[2] + this._sourceToTargetMatrix[13];
1401
+ const z = this._sourceToTargetMatrix[2] * corner[0] + this._sourceToTargetMatrix[6] * corner[1] + this._sourceToTargetMatrix[10] * corner[2] + this._sourceToTargetMatrix[14];
1402
+ minX = Math.min(minX, x);
1403
+ minY = Math.min(minY, y);
1404
+ minZ = Math.min(minZ, z);
1405
+ maxX = Math.max(maxX, x);
1406
+ maxY = Math.max(maxY, y);
1407
+ maxZ = Math.max(maxZ, z);
1408
+ }
1409
+ const padding = Math.max(0.001, this._maxOffsetDistance);
1410
+ this._target?.setAnimatedBoundingBox?.(new BoundingBox(new Vector3(minX - padding, minY - padding, minZ - padding), new Vector3(maxX + padding, maxY + padding, maxZ + padding)));
1411
+ }
1412
+ }
1413
+ async function createGPUClothWrapBindingData(source, target) {
1414
+ if (!source?.primitive) {
1415
+ throw new Error('GPU cloth wrap failed: source mesh has no primitive.');
1416
+ }
1417
+ const sourcePrimitive = source.primitive;
1418
+ const sourcePositions = await readPositionDataFromPrimitive(sourcePrimitive);
1419
+ const sourceIndices = toUInt32Indices(await readIndexDataFromPrimitive(sourcePrimitive, sourcePositions.length / 3 >> 0));
1420
+ return GPUClothWrapBinding.createBindingData(source, sourcePositions, sourceIndices, target);
1421
+ }
1422
+ /**
1423
+ * WebGPU-only cloth simulation entry.
1424
+ *
1425
+ * On non-WebGPU backends (including WebGL), the system is always disabled.
1426
+ */ class GPUClothSystem {
1427
+ _enabled;
1428
+ _disabledReason;
1429
+ _device;
1430
+ _primitive;
1431
+ _originalPositionBuffer;
1432
+ _originalNormalBuffer;
1433
+ _positionBuffer;
1434
+ _sourceIndexData;
1435
+ _prevPositionBuffer;
1436
+ _restPositionBuffer;
1437
+ _prevRestPositionBuffer;
1438
+ _invMassBuffer;
1439
+ _neighborIndexBuffer;
1440
+ _restLengthBuffer;
1441
+ _integrateProgram;
1442
+ _constraintProgram;
1443
+ _faceNormalProgram;
1444
+ _vertexNormalProgram;
1445
+ _integrateBindGroup;
1446
+ _constraintBindGroup;
1447
+ _faceNormalBindGroup;
1448
+ _vertexNormalBindGroup;
1449
+ _vertexCount;
1450
+ _workgroupCount;
1451
+ _triangleCount;
1452
+ _triangleWorkgroupCount;
1453
+ _maxNeighbors;
1454
+ _maxTrianglesPerVertex;
1455
+ _gravity;
1456
+ _damping;
1457
+ _dynamicFriction;
1458
+ _staticFriction;
1459
+ _stiffness;
1460
+ _poseFollow;
1461
+ _substeps;
1462
+ _solverIterations;
1463
+ _colliders;
1464
+ _sphereColliderBuffer;
1465
+ _capsuleColliderBuffer;
1466
+ _planeColliderBuffer;
1467
+ _sphereColliderData;
1468
+ _capsuleColliderData;
1469
+ _planeColliderData;
1470
+ _triangleIndexBuffer;
1471
+ _triangleNormalBuffer;
1472
+ _vertexTriangleAdjacencyBuffer;
1473
+ _normalBuffer;
1474
+ _rebuildNormals;
1475
+ _collisionSpaceNode;
1476
+ _sourcePositionData;
1477
+ _skinningBlendIndices;
1478
+ _skinningBlendWeights;
1479
+ _dynamicRestPositionData;
1480
+ _smoothedRestPositionData;
1481
+ _usingDynamicRestPose;
1482
+ _skinAnimationTarget;
1483
+ _restoreSkinAnimation;
1484
+ _boundScene;
1485
+ _autoUpdate;
1486
+ _workgroupSize;
1487
+ _wrapBindings;
1488
+ _latestInputDeltaTime;
1489
+ _smoothedSphereCenters;
1490
+ _smoothedCapsuleEndpoints;
1491
+ _smoothedPlaneData;
1492
+ _timeAccumulator;
1493
+ _pendingAutoUpdateDeltaTime;
1494
+ _meshPostUpdateCallback;
1495
+ constructor(options){
1496
+ this._device = resolveDevice(options?.device ?? null);
1497
+ const supported = isGPUClothSupported(this._device);
1498
+ const wantEnabled = options?.enabled ?? true;
1499
+ this._enabled = false;
1500
+ this._disabledReason = null;
1501
+ this._primitive = options?.primitive ?? null;
1502
+ this._originalPositionBuffer = null;
1503
+ this._originalNormalBuffer = null;
1504
+ this._positionBuffer = null;
1505
+ this._sourceIndexData = null;
1506
+ this._prevPositionBuffer = null;
1507
+ this._restPositionBuffer = null;
1508
+ this._prevRestPositionBuffer = null;
1509
+ this._invMassBuffer = null;
1510
+ this._neighborIndexBuffer = null;
1511
+ this._restLengthBuffer = null;
1512
+ this._integrateProgram = null;
1513
+ this._constraintProgram = null;
1514
+ this._faceNormalProgram = null;
1515
+ this._vertexNormalProgram = null;
1516
+ this._integrateBindGroup = null;
1517
+ this._constraintBindGroup = null;
1518
+ this._faceNormalBindGroup = null;
1519
+ this._vertexNormalBindGroup = null;
1520
+ this._vertexCount = 0;
1521
+ this._workgroupCount = 0;
1522
+ this._triangleCount = 0;
1523
+ this._triangleWorkgroupCount = 0;
1524
+ this._maxNeighbors = clamp(options?.maxNeighbors ?? DEFAULT_MAX_NEIGHBORS, 1, 64) | 0;
1525
+ this._maxTrianglesPerVertex = clamp(options?.maxTrianglesPerVertex ?? DEFAULT_MAX_TRIANGLES_PER_VERTEX, 1, 64);
1526
+ this._gravity = options?.gravity?.clone() ?? new Vector3(0, -9.8, 0);
1527
+ this._damping = clamp(options?.damping ?? DEFAULT_DAMPING, 0, 1);
1528
+ this._dynamicFriction = clamp(options?.dynamicFriction ?? DEFAULT_DYNAMIC_FRICTION, 0, 1);
1529
+ this._staticFriction = clamp(options?.staticFriction ?? DEFAULT_STATIC_FRICTION, 0, 1);
1530
+ this._stiffness = clamp(options?.stiffness ?? DEFAULT_STIFFNESS, 0, 1);
1531
+ this._poseFollow = clamp(options?.poseFollow ?? DEFAULT_POSE_FOLLOW, 0, 1);
1532
+ this._substeps = clamp(options?.substeps ?? DEFAULT_SUBSTEPS, 1, 8) | 0;
1533
+ this._solverIterations = Math.max(1, (options?.solverIterations ?? DEFAULT_SOLVER_ITERATIONS) | 0);
1534
+ this._colliders = [
1535
+ ...options?.colliders ?? []
1536
+ ];
1537
+ this._sphereColliderBuffer = null;
1538
+ this._capsuleColliderBuffer = null;
1539
+ this._planeColliderBuffer = null;
1540
+ this._sphereColliderData = new Float32Array(getInitialColliderBufferFloatCount(this._colliders, 'sphere', 4));
1541
+ this._capsuleColliderData = new Float32Array(getInitialColliderBufferFloatCount(this._colliders, 'capsule', 8));
1542
+ this._planeColliderData = new Float32Array(getInitialColliderBufferFloatCount(this._colliders, 'plane', 8));
1543
+ this._triangleIndexBuffer = null;
1544
+ this._triangleNormalBuffer = null;
1545
+ this._vertexTriangleAdjacencyBuffer = null;
1546
+ this._normalBuffer = null;
1547
+ this._rebuildNormals = options?.rebuildNormals ?? true;
1548
+ this._collisionSpaceNode = options?.collisionSpaceNode ?? null;
1549
+ this._sourcePositionData = options?.positionData ? new Float32Array(options.positionData) : null;
1550
+ this._skinningBlendIndices = options?.skinningBlendIndices ? new Float32Array(options.skinningBlendIndices) : null;
1551
+ this._skinningBlendWeights = options?.skinningBlendWeights ? new Float32Array(options.skinningBlendWeights) : null;
1552
+ this._dynamicRestPositionData = null;
1553
+ this._smoothedRestPositionData = null;
1554
+ this._usingDynamicRestPose = false;
1555
+ this._skinAnimationTarget = null;
1556
+ this._restoreSkinAnimation = null;
1557
+ this._boundScene = null;
1558
+ this._autoUpdate = options?.autoUpdate ?? true;
1559
+ this._workgroupSize = clamp(options?.workgroupSize ?? DEFAULT_WORKGROUP_SIZE, 1, 256) | 0;
1560
+ this._wrapBindings = [];
1561
+ this._latestInputDeltaTime = 0;
1562
+ this._smoothedSphereCenters = new WeakMap();
1563
+ this._smoothedCapsuleEndpoints = new WeakMap();
1564
+ this._smoothedPlaneData = new WeakMap();
1565
+ this._timeAccumulator = 0;
1566
+ this._pendingAutoUpdateDeltaTime = 0;
1567
+ this._meshPostUpdateCallback = (_frameId, _elapsedInSeconds, deltaInSeconds)=>{
1568
+ const dt = this._pendingAutoUpdateDeltaTime || deltaInSeconds;
1569
+ if (dt > 0) {
1570
+ this._pendingAutoUpdateDeltaTime = 0;
1571
+ this.update(dt);
1572
+ }
1573
+ };
1574
+ if (!supported) {
1575
+ this._disabledReason = 'GPU cloth is disabled: current backend is not WebGPU.';
1576
+ return;
1577
+ }
1578
+ if (!this._device) {
1579
+ this._disabledReason = 'GPU cloth is disabled: no active rendering device.';
1580
+ return;
1581
+ }
1582
+ if (!this._primitive) {
1583
+ this._disabledReason = 'GPU cloth is disabled: primitive is required.';
1584
+ return;
1585
+ }
1586
+ if (!options?.positionData || !options?.indexData) {
1587
+ this._disabledReason = 'GPU cloth is disabled: positionData and indexData are required. Use createFromPrimitive(...) to build them automatically.';
1588
+ return;
1589
+ }
1590
+ const vertexCount = options.positionData.length / 3 >> 0;
1591
+ if (vertexCount <= 0 || options.positionData.length % 3 !== 0) {
1592
+ this._disabledReason = 'GPU cloth is disabled: invalid positionData.';
1593
+ return;
1594
+ }
1595
+ if (options.indexData.length < 3 || options.indexData.length % 3 !== 0) {
1596
+ this._disabledReason = 'GPU cloth is disabled: indexData must be triangle indices.';
1597
+ return;
1598
+ }
1599
+ const initialSimulationPositions = buildInitialSimulationPositions(options) ?? new Float32Array(options.positionData);
1600
+ const workgroupSize = this._workgroupSize;
1601
+ const indexDataU32 = toUInt32Indices(options.indexData);
1602
+ this._sourceIndexData = new Uint32Array(indexDataU32);
1603
+ const pinWeightArray = buildPinWeightArray(initialSimulationPositions, options.pinnedVertexWeights, options.pinnedVertexIndices);
1604
+ const neighbors = buildNeighborData(initialSimulationPositions, options.indexData, this._maxNeighbors);
1605
+ const adjacency = buildVertexTriangleAdjacency(vertexCount, indexDataU32, this._maxTrianglesPerVertex);
1606
+ if (!neighbors.hasConstraint) {
1607
+ this._disabledReason = 'GPU cloth is disabled: could not build cloth constraints from indexData.';
1608
+ return;
1609
+ }
1610
+ try {
1611
+ this._positionBuffer = this._device.createVertexBuffer('position_f32x3', initialSimulationPositions, {
1612
+ storage: true,
1613
+ managed: false
1614
+ });
1615
+ if (!this._positionBuffer) {
1616
+ this._disabledReason = 'GPU cloth is disabled: failed to create cloth position buffer.';
1617
+ return;
1618
+ }
1619
+ this._prevPositionBuffer = this._device.createBuffer(options.positionData.byteLength, {
1620
+ usage: 'uniform',
1621
+ storage: true,
1622
+ dynamic: false,
1623
+ managed: false
1624
+ });
1625
+ this._prevPositionBuffer.bufferSubData(0, initialSimulationPositions);
1626
+ this._restPositionBuffer = this._device.createBuffer(options.positionData.byteLength, {
1627
+ usage: 'uniform',
1628
+ storage: true,
1629
+ dynamic: false,
1630
+ managed: false
1631
+ });
1632
+ this._restPositionBuffer.bufferSubData(0, initialSimulationPositions);
1633
+ this._prevRestPositionBuffer = this._device.createBuffer(options.positionData.byteLength, {
1634
+ usage: 'uniform',
1635
+ storage: true,
1636
+ dynamic: false,
1637
+ managed: false
1638
+ });
1639
+ this._prevRestPositionBuffer.bufferSubData(0, initialSimulationPositions);
1640
+ const invMassArray = new Float32Array(vertexCount);
1641
+ for(let i = 0; i < vertexCount; i++){
1642
+ invMassArray[i] = 1 - pinWeightArray[i];
1643
+ }
1644
+ this._invMassBuffer = this._device.createBuffer(invMassArray.byteLength, {
1645
+ usage: 'uniform',
1646
+ storage: true,
1647
+ dynamic: false,
1648
+ managed: false
1649
+ });
1650
+ this._invMassBuffer.bufferSubData(0, invMassArray);
1651
+ this._neighborIndexBuffer = this._device.createBuffer(neighbors.neighborIndices.byteLength, {
1652
+ usage: 'uniform',
1653
+ storage: true,
1654
+ dynamic: false,
1655
+ managed: false
1656
+ });
1657
+ this._neighborIndexBuffer.bufferSubData(0, neighbors.neighborIndices);
1658
+ this._restLengthBuffer = this._device.createBuffer(neighbors.restLengths.byteLength, {
1659
+ usage: 'uniform',
1660
+ storage: true,
1661
+ dynamic: false,
1662
+ managed: false
1663
+ });
1664
+ this._restLengthBuffer.bufferSubData(0, neighbors.restLengths);
1665
+ this._triangleIndexBuffer = this._device.createBuffer(indexDataU32.byteLength, {
1666
+ usage: 'uniform',
1667
+ storage: true,
1668
+ dynamic: false,
1669
+ managed: false
1670
+ });
1671
+ this._triangleIndexBuffer.bufferSubData(0, indexDataU32);
1672
+ this._triangleNormalBuffer = this._device.createBuffer(adjacency.triangleCount * 3 * 4, {
1673
+ usage: 'uniform',
1674
+ storage: true,
1675
+ dynamic: false,
1676
+ managed: false
1677
+ });
1678
+ this._vertexTriangleAdjacencyBuffer = this._device.createBuffer(adjacency.adjacency.byteLength, {
1679
+ usage: 'uniform',
1680
+ storage: true,
1681
+ dynamic: false,
1682
+ managed: false
1683
+ });
1684
+ this._vertexTriangleAdjacencyBuffer.bufferSubData(0, adjacency.adjacency);
1685
+ this._sphereColliderBuffer = this._device.createBuffer(this._sphereColliderData.byteLength, {
1686
+ usage: 'uniform',
1687
+ storage: true,
1688
+ dynamic: false,
1689
+ managed: false
1690
+ });
1691
+ this._sphereColliderBuffer.bufferSubData(0, this._sphereColliderData);
1692
+ this._capsuleColliderBuffer = this._device.createBuffer(this._capsuleColliderData.byteLength, {
1693
+ usage: 'uniform',
1694
+ storage: true,
1695
+ dynamic: false,
1696
+ managed: false
1697
+ });
1698
+ this._capsuleColliderBuffer.bufferSubData(0, this._capsuleColliderData);
1699
+ this._planeColliderBuffer = this._device.createBuffer(this._planeColliderData.byteLength, {
1700
+ usage: 'uniform',
1701
+ storage: true,
1702
+ dynamic: false,
1703
+ managed: false
1704
+ });
1705
+ this._planeColliderBuffer.bufferSubData(0, this._planeColliderData);
1706
+ this._integrateProgram = createIntegrateProgram(this._device, workgroupSize);
1707
+ this._constraintProgram = createConstraintProgram(this._device, workgroupSize);
1708
+ if (!this._integrateProgram || !this._constraintProgram) {
1709
+ this._disabledReason = 'GPU cloth is disabled: failed to create compute programs.';
1710
+ return;
1711
+ }
1712
+ this._integrateBindGroup = this._device.createBindGroup(this._integrateProgram.bindGroupLayouts[0]);
1713
+ this._integrateBindGroup.setBuffer('positions', this._positionBuffer);
1714
+ this._integrateBindGroup.setBuffer('prevPositions', this._prevPositionBuffer);
1715
+ this._integrateBindGroup.setBuffer('restPositions', this._restPositionBuffer);
1716
+ this._integrateBindGroup.setBuffer('prevRestPositions', this._prevRestPositionBuffer);
1717
+ this._integrateBindGroup.setBuffer('invMass', this._invMassBuffer);
1718
+ this._integrateBindGroup.setBuffer('sphereData', this._sphereColliderBuffer);
1719
+ this._integrateBindGroup.setBuffer('capsuleData', this._capsuleColliderBuffer);
1720
+ this._integrateBindGroup.setBuffer('planeData', this._planeColliderBuffer);
1721
+ this._integrateBindGroup.setValue('vertexCount', vertexCount);
1722
+ this._integrateBindGroup.setValue('damping', this._damping);
1723
+ this._integrateBindGroup.setValue('gravity', this._gravity);
1724
+ this._integrateBindGroup.setValue('sphereCount', 0);
1725
+ this._integrateBindGroup.setValue('capsuleCount', 0);
1726
+ this._integrateBindGroup.setValue('planeCount', 0);
1727
+ this._integrateBindGroup.setValue('dynamicFriction', this._dynamicFriction);
1728
+ this._integrateBindGroup.setValue('staticFriction', this._staticFriction);
1729
+ this._integrateBindGroup.setValue('minDistance', 1e-5);
1730
+ this._constraintBindGroup = this._device.createBindGroup(this._constraintProgram.bindGroupLayouts[0]);
1731
+ this._constraintBindGroup.setBuffer('positions', this._positionBuffer);
1732
+ this._constraintBindGroup.setBuffer('restPositions', this._restPositionBuffer);
1733
+ this._constraintBindGroup.setBuffer('invMass', this._invMassBuffer);
1734
+ this._constraintBindGroup.setBuffer('neighborIndices', this._neighborIndexBuffer);
1735
+ this._constraintBindGroup.setBuffer('restLengths', this._restLengthBuffer);
1736
+ this._constraintBindGroup.setBuffer('sphereData', this._sphereColliderBuffer);
1737
+ this._constraintBindGroup.setBuffer('capsuleData', this._capsuleColliderBuffer);
1738
+ this._constraintBindGroup.setBuffer('planeData', this._planeColliderBuffer);
1739
+ this._constraintBindGroup.setValue('vertexCount', vertexCount);
1740
+ this._constraintBindGroup.setValue('maxNeighbors', this._maxNeighbors);
1741
+ this._constraintBindGroup.setValue('stiffness', this._stiffness);
1742
+ this._constraintBindGroup.setValue('poseFollow', this.getSubstepPoseFollow());
1743
+ this._constraintBindGroup.setValue('sphereCount', 0);
1744
+ this._constraintBindGroup.setValue('capsuleCount', 0);
1745
+ this._constraintBindGroup.setValue('planeCount', 0);
1746
+ this._constraintBindGroup.setValue('dynamicFriction', this._dynamicFriction);
1747
+ this._constraintBindGroup.setValue('staticFriction', this._staticFriction);
1748
+ this._constraintBindGroup.setValue('minDistance', 1e-5);
1749
+ if (this._rebuildNormals) {
1750
+ this._normalBuffer = this._device.createVertexBuffer('normal_f32x3', new Float32Array(vertexCount * 3), {
1751
+ storage: true,
1752
+ managed: false
1753
+ });
1754
+ if (this._normalBuffer) {
1755
+ this._faceNormalProgram = createFaceNormalProgram(this._device, workgroupSize);
1756
+ this._vertexNormalProgram = createVertexNormalProgram(this._device, workgroupSize);
1757
+ if (!this._faceNormalProgram || !this._vertexNormalProgram) {
1758
+ this._disabledReason = 'GPU cloth is disabled: failed to create normal reconstruction programs.';
1759
+ return;
1760
+ }
1761
+ this._faceNormalBindGroup = this._device.createBindGroup(this._faceNormalProgram.bindGroupLayouts[0]);
1762
+ this._faceNormalBindGroup.setBuffer('positions', this._positionBuffer);
1763
+ this._faceNormalBindGroup.setBuffer('triangleIndices', this._triangleIndexBuffer);
1764
+ this._faceNormalBindGroup.setBuffer('triangleNormals', this._triangleNormalBuffer);
1765
+ this._faceNormalBindGroup.setValue('triangleCount', adjacency.triangleCount);
1766
+ this._faceNormalBindGroup.setValue('minDistance', 1e-5);
1767
+ this._vertexNormalBindGroup = this._device.createBindGroup(this._vertexNormalProgram.bindGroupLayouts[0]);
1768
+ this._vertexNormalBindGroup.setBuffer('triangleNormals', this._triangleNormalBuffer);
1769
+ this._vertexNormalBindGroup.setBuffer('vertexTriangleAdjacency', this._vertexTriangleAdjacencyBuffer);
1770
+ this._vertexNormalBindGroup.setBuffer('vertexNormals', this._normalBuffer);
1771
+ this._vertexNormalBindGroup.setValue('vertexCount', vertexCount);
1772
+ this._vertexNormalBindGroup.setValue('maxTrianglesPerVertex', this._maxTrianglesPerVertex);
1773
+ this._vertexNormalBindGroup.setValue('minDistance', 1e-5);
1774
+ }
1775
+ }
1776
+ this._originalPositionBuffer = this._primitive.getVertexBuffer('position');
1777
+ retainObject(this._originalPositionBuffer);
1778
+ this._primitive.removeVertexBuffer('position');
1779
+ this._primitive.setVertexBuffer(this._positionBuffer);
1780
+ if (this._normalBuffer) {
1781
+ this._originalNormalBuffer = this._primitive.getVertexBuffer('normal');
1782
+ retainObject(this._originalNormalBuffer);
1783
+ this._primitive.removeVertexBuffer('normal');
1784
+ this._primitive.setVertexBuffer(this._normalBuffer);
1785
+ }
1786
+ this._vertexCount = vertexCount;
1787
+ this._workgroupCount = Math.max(1, Math.ceil(vertexCount / workgroupSize));
1788
+ this._triangleCount = adjacency.triangleCount;
1789
+ this._triangleWorkgroupCount = Math.max(1, Math.ceil(this._triangleCount / workgroupSize));
1790
+ this.disableTargetSkinning();
1791
+ this.updateTargetBoundingBox(initialSimulationPositions);
1792
+ this.updateColliderBuffers();
1793
+ if (this._autoUpdate && options?.scene) {
1794
+ this.bindToScene(options.scene);
1795
+ }
1796
+ this._enabled = !!wantEnabled;
1797
+ this._disabledReason = null;
1798
+ } catch (err) {
1799
+ this._disabledReason = `GPU cloth initialization failed: ${err instanceof Error ? err.message : String(err)}`;
1800
+ this._enabled = false;
1801
+ }
1802
+ }
1803
+ static async createFromPrimitive(primitive, options) {
1804
+ const positionData = await readPositionDataFromPrimitive(primitive);
1805
+ const skinningData = await readSkinningDataFromPrimitive(primitive);
1806
+ const indexData = await readIndexDataFromPrimitive(primitive, positionData.length / 3 >> 0);
1807
+ return new GPUClothSystem({
1808
+ ...options,
1809
+ primitive,
1810
+ positionData,
1811
+ indexData,
1812
+ skinningBlendIndices: skinningData?.blendIndices ?? undefined,
1813
+ skinningBlendWeights: skinningData?.blendWeights ?? undefined
1814
+ });
1815
+ }
1816
+ static async createFromMesh(mesh, options) {
1817
+ if (!mesh.primitive) {
1818
+ throw new Error('GPU cloth initialization failed: mesh has no primitive.');
1819
+ }
1820
+ return GPUClothSystem.createFromPrimitive(mesh.primitive, {
1821
+ ...options,
1822
+ collisionSpaceNode: mesh,
1823
+ scene: mesh.scene ?? null
1824
+ });
1825
+ }
1826
+ async setWrapTargets(targets) {
1827
+ this.clearWrapTargets();
1828
+ if (!this.supported || !this._device || !this._positionBuffer || !this._sourcePositionData || !this._sourceIndexData) {
1829
+ return;
1830
+ }
1831
+ const uniqueTargets = [
1832
+ ...new Set((targets ?? []).filter((target)=>target?.primitive && target !== this._collisionSpaceNode))
1833
+ ];
1834
+ for (const target of uniqueTargets){
1835
+ try {
1836
+ const binding = await GPUClothWrapBinding.create(this._device, this._collisionSpaceNode, this._positionBuffer, this._sourcePositionData, this._sourceIndexData, target, this._workgroupSize);
1837
+ this._wrapBindings.push(binding);
1838
+ } catch (err) {
1839
+ console.error('GPU cloth wrap target initialization failed:', err);
1840
+ }
1841
+ }
1842
+ }
1843
+ setWrapTargetsFromBindingData(targets) {
1844
+ this.clearWrapTargets();
1845
+ if (!this.supported || !this._device || !this._positionBuffer || !this._sourcePositionData || !this._sourceIndexData) {
1846
+ return;
1847
+ }
1848
+ const seen = new Set();
1849
+ for (const entry of targets ?? []){
1850
+ const target = entry?.target;
1851
+ if (!target?.primitive || target === this._collisionSpaceNode || seen.has(target)) {
1852
+ continue;
1853
+ }
1854
+ seen.add(target);
1855
+ try {
1856
+ const binding = GPUClothWrapBinding.createFromData(this._device, this._collisionSpaceNode, this._positionBuffer, this._sourcePositionData, this._sourceIndexData, target, this._workgroupSize, entry.data);
1857
+ this._wrapBindings.push(binding);
1858
+ } catch (err) {
1859
+ console.error('GPU cloth wrap target binding cache initialization failed:', err);
1860
+ }
1861
+ }
1862
+ }
1863
+ clearWrapTargets() {
1864
+ if (this._wrapBindings.length === 0) {
1865
+ return;
1866
+ }
1867
+ for (const binding of this._wrapBindings){
1868
+ binding.dispose();
1869
+ }
1870
+ this._wrapBindings = [];
1871
+ }
1872
+ get enabled() {
1873
+ return this._enabled;
1874
+ }
1875
+ set enabled(value) {
1876
+ const enabled = this.supported && !!value;
1877
+ if (this._enabled !== enabled) {
1878
+ this.resetSimulationTiming();
1879
+ }
1880
+ this._enabled = enabled;
1881
+ }
1882
+ get supported() {
1883
+ return this._disabledReason === null;
1884
+ }
1885
+ get disabledReason() {
1886
+ return this._disabledReason;
1887
+ }
1888
+ get colliders() {
1889
+ return this._colliders;
1890
+ }
1891
+ set colliders(value) {
1892
+ this._colliders = [
1893
+ ...value ?? []
1894
+ ];
1895
+ this.resetColliderSmoothing();
1896
+ this.updateColliderBuffers();
1897
+ }
1898
+ addCollider(collider) {
1899
+ this._colliders.push(collider);
1900
+ this.updateColliderBuffers();
1901
+ }
1902
+ removeCollider(collider) {
1903
+ const idx = this._colliders.indexOf(collider);
1904
+ if (idx >= 0) {
1905
+ this._colliders.splice(idx, 1);
1906
+ this.updateColliderBuffers();
1907
+ return true;
1908
+ }
1909
+ return false;
1910
+ }
1911
+ clearColliders() {
1912
+ this._colliders.length = 0;
1913
+ this.resetColliderSmoothing();
1914
+ this.updateColliderBuffers();
1915
+ }
1916
+ get gravity() {
1917
+ return this._gravity;
1918
+ }
1919
+ set gravity(value) {
1920
+ this._gravity.set(value);
1921
+ }
1922
+ get damping() {
1923
+ return this._damping;
1924
+ }
1925
+ set damping(value) {
1926
+ this._damping = clamp(value, 0, 1);
1927
+ }
1928
+ get dynamicFriction() {
1929
+ return this._dynamicFriction;
1930
+ }
1931
+ set dynamicFriction(value) {
1932
+ this._dynamicFriction = clamp(value, 0, 1);
1933
+ }
1934
+ get staticFriction() {
1935
+ return this._staticFriction;
1936
+ }
1937
+ set staticFriction(value) {
1938
+ this._staticFriction = clamp(value, 0, 1);
1939
+ }
1940
+ get stiffness() {
1941
+ return this._stiffness;
1942
+ }
1943
+ set stiffness(value) {
1944
+ this._stiffness = clamp(value, 0, 1);
1945
+ }
1946
+ get poseFollow() {
1947
+ return this._poseFollow;
1948
+ }
1949
+ set poseFollow(value) {
1950
+ this._poseFollow = clamp(value, 0, 1);
1951
+ }
1952
+ get solverIterations() {
1953
+ return this._solverIterations;
1954
+ }
1955
+ set solverIterations(value) {
1956
+ this._solverIterations = Math.max(1, value | 0);
1957
+ }
1958
+ get substeps() {
1959
+ return this._substeps;
1960
+ }
1961
+ set substeps(value) {
1962
+ this._substeps = clamp(value, 1, 8) | 0;
1963
+ }
1964
+ get vertexCount() {
1965
+ return this._vertexCount;
1966
+ }
1967
+ bindToScene(scene) {
1968
+ if (this._boundScene === scene) {
1969
+ return;
1970
+ }
1971
+ this.detachAutoUpdateTarget();
1972
+ if (this._boundScene) {
1973
+ this._boundScene.off('update', this._onSceneUpdate, this);
1974
+ }
1975
+ this._boundScene = scene ?? null;
1976
+ if (this._boundScene) {
1977
+ this._boundScene.on('update', this._onSceneUpdate, this);
1978
+ this.attachAutoUpdateTarget();
1979
+ }
1980
+ }
1981
+ unbindFromScene() {
1982
+ this.bindToScene(null);
1983
+ }
1984
+ update(deltaTime) {
1985
+ if (!this._enabled) {
1986
+ return;
1987
+ }
1988
+ if (!this._device || !this._integrateProgram || !this._constraintProgram || !this._integrateBindGroup || !this._constraintBindGroup) {
1989
+ return;
1990
+ }
1991
+ const frameDt = clamp(Number(deltaTime) || 0, 0, MAX_ACCUMULATED_SIMULATION_TIME);
1992
+ this._latestInputDeltaTime = frameDt;
1993
+ this.updateSkinnedRestPositions(frameDt);
1994
+ this.updateColliderBuffers(frameDt);
1995
+ if (frameDt <= 0) {
1996
+ return;
1997
+ }
1998
+ this._timeAccumulator = Math.min(this._timeAccumulator + frameDt, MAX_ACCUMULATED_SIMULATION_TIME);
1999
+ const stepCount = Math.min(MAX_SIMULATION_STEPS_PER_UPDATE, Math.floor((this._timeAccumulator + 1e-8) / FIXED_SIMULATION_TIME_STEP));
2000
+ if (stepCount <= 0) {
2001
+ return;
2002
+ }
2003
+ this._timeAccumulator = Math.max(0, this._timeAccumulator - stepCount * FIXED_SIMULATION_TIME_STEP);
2004
+ this._device.pushDeviceStates();
2005
+ try {
2006
+ for(let step = 0; step < stepCount; step++){
2007
+ this.simulateStep(FIXED_SIMULATION_TIME_STEP);
2008
+ }
2009
+ this.updateSimulationOutputs();
2010
+ } finally{
2011
+ this._device.popDeviceStates();
2012
+ }
2013
+ }
2014
+ dispose() {
2015
+ this.unbindFromScene();
2016
+ this.clearWrapTargets();
2017
+ this.restoreTargetSkinning();
2018
+ if (this._primitive) {
2019
+ if (this._positionBuffer) {
2020
+ this._primitive.removeVertexBuffer('position');
2021
+ if (this._originalPositionBuffer) {
2022
+ this._primitive.setVertexBuffer(this._originalPositionBuffer);
2023
+ }
2024
+ }
2025
+ if (this._normalBuffer || this._originalNormalBuffer) {
2026
+ this._primitive.removeVertexBuffer('normal');
2027
+ if (this._originalNormalBuffer) {
2028
+ this._primitive.setVertexBuffer(this._originalNormalBuffer);
2029
+ }
2030
+ }
2031
+ }
2032
+ this._integrateBindGroup?.dispose();
2033
+ this._integrateBindGroup = null;
2034
+ this._constraintBindGroup?.dispose();
2035
+ this._constraintBindGroup = null;
2036
+ this._faceNormalBindGroup?.dispose();
2037
+ this._faceNormalBindGroup = null;
2038
+ this._vertexNormalBindGroup?.dispose();
2039
+ this._vertexNormalBindGroup = null;
2040
+ this._integrateProgram?.dispose();
2041
+ this._integrateProgram = null;
2042
+ this._constraintProgram?.dispose();
2043
+ this._constraintProgram = null;
2044
+ this._faceNormalProgram?.dispose();
2045
+ this._faceNormalProgram = null;
2046
+ this._vertexNormalProgram?.dispose();
2047
+ this._vertexNormalProgram = null;
2048
+ this._prevPositionBuffer?.dispose();
2049
+ this._prevPositionBuffer = null;
2050
+ this._restPositionBuffer?.dispose();
2051
+ this._restPositionBuffer = null;
2052
+ this._prevRestPositionBuffer?.dispose();
2053
+ this._prevRestPositionBuffer = null;
2054
+ this._invMassBuffer?.dispose();
2055
+ this._invMassBuffer = null;
2056
+ this._neighborIndexBuffer?.dispose();
2057
+ this._neighborIndexBuffer = null;
2058
+ this._restLengthBuffer?.dispose();
2059
+ this._restLengthBuffer = null;
2060
+ this._triangleIndexBuffer?.dispose();
2061
+ this._triangleIndexBuffer = null;
2062
+ this._triangleNormalBuffer?.dispose();
2063
+ this._triangleNormalBuffer = null;
2064
+ this._vertexTriangleAdjacencyBuffer?.dispose();
2065
+ this._vertexTriangleAdjacencyBuffer = null;
2066
+ this._sphereColliderBuffer?.dispose();
2067
+ this._sphereColliderBuffer = null;
2068
+ this._capsuleColliderBuffer?.dispose();
2069
+ this._capsuleColliderBuffer = null;
2070
+ this._planeColliderBuffer?.dispose();
2071
+ this._planeColliderBuffer = null;
2072
+ releaseObject(this._originalPositionBuffer);
2073
+ this._originalPositionBuffer = null;
2074
+ releaseObject(this._originalNormalBuffer);
2075
+ this._originalNormalBuffer = null;
2076
+ this._enabled = false;
2077
+ this.resetSimulationTiming();
2078
+ }
2079
+ simulateStep(deltaTime) {
2080
+ const substeps = this._substeps;
2081
+ const substepDt = deltaTime / substeps;
2082
+ try {
2083
+ for(let substep = 0; substep < substeps; substep++){
2084
+ this._integrateBindGroup.setValue('deltaTime', substepDt);
2085
+ this._integrateBindGroup.setValue('damping', this._damping);
2086
+ this._integrateBindGroup.setValue('gravity', this._gravity);
2087
+ this._integrateBindGroup.setValue('dynamicFriction', this._dynamicFriction);
2088
+ this._integrateBindGroup.setValue('staticFriction', this._staticFriction);
2089
+ this._device.setProgram(this._integrateProgram);
2090
+ this._device.setBindGroup(0, this._integrateBindGroup);
2091
+ this._device.compute(this._workgroupCount, 1, 1);
2092
+ this._constraintBindGroup.setValue('stiffness', this._stiffness);
2093
+ this._constraintBindGroup.setValue('dynamicFriction', this._dynamicFriction);
2094
+ this._constraintBindGroup.setValue('staticFriction', this._staticFriction);
2095
+ this._device.setProgram(this._constraintProgram);
2096
+ this._device.setBindGroup(0, this._constraintBindGroup);
2097
+ for(let i = 0; i < this._solverIterations; i++){
2098
+ this._constraintBindGroup.setValue('poseFollow', i === this._solverIterations - 1 ? this.getSubstepPoseFollow() : 0);
2099
+ this._device.compute(this._workgroupCount, 1, 1);
2100
+ }
2101
+ }
2102
+ } finally{
2103
+ // Advance the animated rest pose once per fixed simulation step so a
2104
+ // single dropped frame does not inject the same pose delta repeatedly.
2105
+ this.commitRestPositions();
2106
+ }
2107
+ }
2108
+ updateSimulationOutputs() {
2109
+ if (this._rebuildNormals && this._faceNormalProgram && this._vertexNormalProgram && this._faceNormalBindGroup && this._vertexNormalBindGroup) {
2110
+ this._device.setProgram(this._faceNormalProgram);
2111
+ this._device.setBindGroup(0, this._faceNormalBindGroup);
2112
+ this._device.compute(this._triangleWorkgroupCount, 1, 1);
2113
+ this._device.setProgram(this._vertexNormalProgram);
2114
+ this._device.setBindGroup(0, this._vertexNormalBindGroup);
2115
+ this._device.compute(this._workgroupCount, 1, 1);
2116
+ }
2117
+ for (const binding of this._wrapBindings){
2118
+ binding.update();
2119
+ }
2120
+ }
2121
+ resetSimulationTiming() {
2122
+ this._timeAccumulator = 0;
2123
+ this._latestInputDeltaTime = 0;
2124
+ this._pendingAutoUpdateDeltaTime = 0;
2125
+ this._smoothedRestPositionData = null;
2126
+ this.resetColliderSmoothing();
2127
+ }
2128
+ resetColliderSmoothing() {
2129
+ this._smoothedSphereCenters = new WeakMap();
2130
+ this._smoothedCapsuleEndpoints = new WeakMap();
2131
+ this._smoothedPlaneData = new WeakMap();
2132
+ }
2133
+ updateColliderBuffers(deltaTime = this._latestInputDeltaTime) {
2134
+ if (!this._device || !this._integrateBindGroup) {
2135
+ return;
2136
+ }
2137
+ const spheres = [];
2138
+ const capsules = [];
2139
+ const planes = [];
2140
+ const blend = this.getTemporalBlendFactor(deltaTime, DEFAULT_COLLIDER_SMOOTHING_TIME);
2141
+ for (const collider of this._colliders){
2142
+ if (!collider?.enabled) {
2143
+ continue;
2144
+ }
2145
+ if (collider.node) {
2146
+ updateColliderFromNode(collider);
2147
+ }
2148
+ if (collider.type === 'sphere') {
2149
+ const sphere = collider;
2150
+ spheres.push({
2151
+ collider: sphere,
2152
+ center: this.getSmoothedSphereCenter(sphere, blend)
2153
+ });
2154
+ } else if (collider.type === 'capsule') {
2155
+ const capsule = collider;
2156
+ const smoothed = this.getSmoothedCapsuleEndpoints(capsule, blend);
2157
+ capsules.push({
2158
+ collider: capsule,
2159
+ start: smoothed.start,
2160
+ end: smoothed.end
2161
+ });
2162
+ } else if (collider.type === 'plane') {
2163
+ const plane = collider;
2164
+ const smoothed = this.getSmoothedPlaneData(plane, blend);
2165
+ planes.push({
2166
+ collider: plane,
2167
+ point: smoothed.point,
2168
+ normal: smoothed.normal
2169
+ });
2170
+ }
2171
+ }
2172
+ const sphereData = new Float32Array(Math.max(1, spheres.length) * 4);
2173
+ for(let i = 0; i < spheres.length; i++){
2174
+ const base = i * 4;
2175
+ const center = this.toCollisionSpacePoint(spheres[i].center);
2176
+ sphereData[base] = center.x;
2177
+ sphereData[base + 1] = center.y;
2178
+ sphereData[base + 2] = center.z;
2179
+ sphereData[base + 3] = this.toCollisionSpaceRadius(spheres[i].collider.radius);
2180
+ }
2181
+ const capsuleData = new Float32Array(Math.max(1, capsules.length) * 8);
2182
+ for(let i = 0; i < capsules.length; i++){
2183
+ const base = i * 8;
2184
+ const start = this.toCollisionSpacePoint(capsules[i].start);
2185
+ const end = this.toCollisionSpacePoint(capsules[i].end);
2186
+ const axis = Vector3.sub(capsules[i].end, capsules[i].start, new Vector3());
2187
+ capsuleData[base] = start.x;
2188
+ capsuleData[base + 1] = start.y;
2189
+ capsuleData[base + 2] = start.z;
2190
+ capsuleData[base + 3] = this.toCollisionSpaceRadius(capsules[i].collider.radius, axis);
2191
+ capsuleData[base + 4] = end.x;
2192
+ capsuleData[base + 5] = end.y;
2193
+ capsuleData[base + 6] = end.z;
2194
+ capsuleData[base + 7] = 0;
2195
+ }
2196
+ const planeData = new Float32Array(Math.max(1, planes.length) * 8);
2197
+ for(let i = 0; i < planes.length; i++){
2198
+ const base = i * 8;
2199
+ const point = this.toCollisionSpacePoint(planes[i].point);
2200
+ const normal = this.toCollisionSpaceVector(planes[i].normal);
2201
+ if (normal.magnitudeSq > 1e-8) {
2202
+ normal.inplaceNormalize();
2203
+ } else {
2204
+ normal.setXYZ(0, 1, 0);
2205
+ }
2206
+ planeData[base] = point.x;
2207
+ planeData[base + 1] = point.y;
2208
+ planeData[base + 2] = point.z;
2209
+ planeData[base + 3] = 0;
2210
+ planeData[base + 4] = normal.x;
2211
+ planeData[base + 5] = normal.y;
2212
+ planeData[base + 6] = normal.z;
2213
+ planeData[base + 7] = 0;
2214
+ }
2215
+ if (!this._sphereColliderBuffer || this._sphereColliderBuffer.byteLength < sphereData.byteLength) {
2216
+ if (this._sphereColliderBuffer) {
2217
+ // Drain queued uploads for the old buffer before replacing it.
2218
+ this._device.flush();
2219
+ this._sphereColliderBuffer.dispose();
2220
+ }
2221
+ this._sphereColliderBuffer = this._device.createBuffer(sphereData.byteLength, {
2222
+ usage: 'uniform',
2223
+ storage: true,
2224
+ dynamic: false,
2225
+ managed: false
2226
+ });
2227
+ this._integrateBindGroup.setBuffer('sphereData', this._sphereColliderBuffer);
2228
+ this._constraintBindGroup?.setBuffer('sphereData', this._sphereColliderBuffer);
2229
+ }
2230
+ this._sphereColliderBuffer.bufferSubData(0, sphereData);
2231
+ if (!this._capsuleColliderBuffer || this._capsuleColliderBuffer.byteLength < capsuleData.byteLength) {
2232
+ if (this._capsuleColliderBuffer) {
2233
+ // Drain queued uploads for the old buffer before replacing it.
2234
+ this._device.flush();
2235
+ this._capsuleColliderBuffer.dispose();
2236
+ }
2237
+ this._capsuleColliderBuffer = this._device.createBuffer(capsuleData.byteLength, {
2238
+ usage: 'uniform',
2239
+ storage: true,
2240
+ dynamic: false,
2241
+ managed: false
2242
+ });
2243
+ this._integrateBindGroup.setBuffer('capsuleData', this._capsuleColliderBuffer);
2244
+ this._constraintBindGroup?.setBuffer('capsuleData', this._capsuleColliderBuffer);
2245
+ }
2246
+ this._capsuleColliderBuffer.bufferSubData(0, capsuleData);
2247
+ if (!this._planeColliderBuffer || this._planeColliderBuffer.byteLength < planeData.byteLength) {
2248
+ if (this._planeColliderBuffer) {
2249
+ // Drain queued uploads for the old buffer before replacing it.
2250
+ this._device.flush();
2251
+ this._planeColliderBuffer.dispose();
2252
+ }
2253
+ this._planeColliderBuffer = this._device.createBuffer(planeData.byteLength, {
2254
+ usage: 'uniform',
2255
+ storage: true,
2256
+ dynamic: false,
2257
+ managed: false
2258
+ });
2259
+ this._integrateBindGroup.setBuffer('planeData', this._planeColliderBuffer);
2260
+ this._constraintBindGroup?.setBuffer('planeData', this._planeColliderBuffer);
2261
+ }
2262
+ this._planeColliderBuffer.bufferSubData(0, planeData);
2263
+ this._integrateBindGroup.setValue('sphereCount', spheres.length);
2264
+ this._integrateBindGroup.setValue('capsuleCount', capsules.length);
2265
+ this._integrateBindGroup.setValue('planeCount', planes.length);
2266
+ this._constraintBindGroup?.setValue('sphereCount', spheres.length);
2267
+ this._constraintBindGroup?.setValue('capsuleCount', capsules.length);
2268
+ this._constraintBindGroup?.setValue('planeCount', planes.length);
2269
+ }
2270
+ getSubstepPoseFollow() {
2271
+ const effectivePoseFollow = this.getEffectivePoseFollow();
2272
+ if (effectivePoseFollow <= 0) {
2273
+ return 0;
2274
+ }
2275
+ const totalPoseFollowApplications = Math.max(1, this._substeps);
2276
+ return totalPoseFollowApplications > 1 ? 1 - Math.pow(Math.max(0, 1 - effectivePoseFollow), 1 / totalPoseFollowApplications) : effectivePoseFollow;
2277
+ }
2278
+ getEffectivePoseFollow() {
2279
+ // Cloth uses a dense per-vertex animated target, so the same numeric follow value
2280
+ // feels much stronger than sparse spring chains. Square the UI value to keep the
2281
+ // low range usable while preserving 1 => full follow.
2282
+ return this._poseFollow * this._poseFollow;
2283
+ }
2284
+ toCollisionSpacePoint(worldPoint) {
2285
+ const invWorldMatrix = this._collisionSpaceNode?.invWorldMatrix;
2286
+ if (!invWorldMatrix?.transformPointAffine) {
2287
+ return worldPoint;
2288
+ }
2289
+ return invWorldMatrix.transformPointAffine(worldPoint, new Vector3());
2290
+ }
2291
+ toCollisionSpaceVector(worldVector) {
2292
+ const invWorldMatrix = this._collisionSpaceNode?.invWorldMatrix;
2293
+ if (!invWorldMatrix?.transformVectorAffine) {
2294
+ return worldVector;
2295
+ }
2296
+ return invWorldMatrix.transformVectorAffine(worldVector, new Vector3());
2297
+ }
2298
+ toCollisionSpaceRadius(worldRadius, axisWorld) {
2299
+ const radius = Math.max(0, Number(worldRadius) || 0);
2300
+ if (radius <= 0) {
2301
+ return 0;
2302
+ }
2303
+ const invWorldMatrix = this._collisionSpaceNode?.invWorldMatrix;
2304
+ if (!invWorldMatrix?.transformVectorAffine) {
2305
+ return radius;
2306
+ }
2307
+ if (axisWorld && axisWorld.magnitudeSq > 1e-6) {
2308
+ const axis = axisWorld.clone().inplaceNormalize();
2309
+ let tangent = Vector3.cross(Math.abs(axis.y) < 0.999 ? Vector3.axisPY() : Vector3.axisPX(), axis, new Vector3());
2310
+ if (tangent.magnitudeSq <= 1e-6) {
2311
+ tangent = Vector3.cross(Vector3.axisPZ(), axis, tangent);
2312
+ }
2313
+ tangent.inplaceNormalize();
2314
+ const bitangent = Vector3.cross(axis, tangent, new Vector3()).inplaceNormalize();
2315
+ const localTangent = this.toCollisionSpaceVector(Vector3.scale(tangent, radius, new Vector3())).magnitude;
2316
+ const localBitangent = this.toCollisionSpaceVector(Vector3.scale(bitangent, radius, new Vector3())).magnitude;
2317
+ return Math.max(1e-6, (localTangent + localBitangent) * 0.5);
2318
+ }
2319
+ const localX = this.toCollisionSpaceVector(new Vector3(radius, 0, 0)).magnitude;
2320
+ const localY = this.toCollisionSpaceVector(new Vector3(0, radius, 0)).magnitude;
2321
+ const localZ = this.toCollisionSpaceVector(new Vector3(0, 0, radius)).magnitude;
2322
+ return Math.max(1e-6, (localX + localY + localZ) / 3);
2323
+ }
2324
+ _onSceneUpdate() {
2325
+ const dt = (getDevice().frameInfo.elapsedFrame || 16.6667) * 0.001;
2326
+ if (this.hasAutoUpdateTarget()) {
2327
+ this._pendingAutoUpdateDeltaTime = dt;
2328
+ this._collisionSpaceNode?.scene?.queueUpdateNode?.(this._collisionSpaceNode);
2329
+ return;
2330
+ }
2331
+ this.update(dt);
2332
+ }
2333
+ hasAutoUpdateTarget() {
2334
+ const target = this._collisionSpaceNode;
2335
+ return !!(target?.addPostUpdateCallback && target?.removePostUpdateCallback);
2336
+ }
2337
+ attachAutoUpdateTarget() {
2338
+ if (this.hasAutoUpdateTarget()) {
2339
+ this._collisionSpaceNode.addPostUpdateCallback(this._meshPostUpdateCallback);
2340
+ }
2341
+ }
2342
+ detachAutoUpdateTarget() {
2343
+ if (this.hasAutoUpdateTarget()) {
2344
+ this._collisionSpaceNode.removePostUpdateCallback(this._meshPostUpdateCallback);
2345
+ }
2346
+ this._pendingAutoUpdateDeltaTime = 0;
2347
+ }
2348
+ updateSkinnedRestPositions(deltaTime = 0) {
2349
+ if (!this._sourcePositionData || !this._skinningBlendIndices || !this._skinningBlendWeights || !this._restPositionBuffer || !this._prevRestPositionBuffer) {
2350
+ return;
2351
+ }
2352
+ const target = this._collisionSpaceNode;
2353
+ const skeletonId = String(target?.skeletonName ?? '');
2354
+ const skeleton = skeletonId && typeof target?.findSkeletonById === 'function' ? target.findSkeletonById(skeletonId) : null;
2355
+ if (skeleton?.skinPositionsToLocal && target?.invWorldMatrix) {
2356
+ const restPositions = this._dynamicRestPositionData && this._dynamicRestPositionData.length === this._sourcePositionData.length ? this._dynamicRestPositionData : new Float32Array(this._sourcePositionData.length);
2357
+ skeleton.skinPositionsToLocal(this._sourcePositionData, this._skinningBlendIndices, this._skinningBlendWeights, target.invWorldMatrix, restPositions);
2358
+ const smoothedRestPositions = this.smoothRestPositions(restPositions, deltaTime, restPositions);
2359
+ this._dynamicRestPositionData = restPositions;
2360
+ this._restPositionBuffer.bufferSubData(0, smoothedRestPositions);
2361
+ this._usingDynamicRestPose = true;
2362
+ this.updateTargetBoundingBox(smoothedRestPositions);
2363
+ return;
2364
+ }
2365
+ if (this._usingDynamicRestPose) {
2366
+ this._restPositionBuffer.bufferSubData(0, this._sourcePositionData);
2367
+ this._prevRestPositionBuffer.bufferSubData(0, this._sourcePositionData);
2368
+ this._usingDynamicRestPose = false;
2369
+ this._dynamicRestPositionData = null;
2370
+ this._smoothedRestPositionData = null;
2371
+ this.updateTargetBoundingBox(this._sourcePositionData);
2372
+ }
2373
+ }
2374
+ smoothRestPositions(rawRestPositions, deltaTime, out) {
2375
+ const blend = this.getTemporalBlendFactor(deltaTime, DEFAULT_REST_POSITION_SMOOTHING_TIME);
2376
+ const smoothed = this._smoothedRestPositionData && this._smoothedRestPositionData.length === rawRestPositions.length ? this._smoothedRestPositionData : new Float32Array(rawRestPositions.length);
2377
+ if (!this._smoothedRestPositionData || blend >= 1) {
2378
+ smoothed.set(rawRestPositions);
2379
+ } else {
2380
+ const retain = 1 - blend;
2381
+ for(let i = 0; i < rawRestPositions.length; i++){
2382
+ smoothed[i] = smoothed[i] * retain + rawRestPositions[i] * blend;
2383
+ }
2384
+ }
2385
+ out.set(smoothed);
2386
+ this._smoothedRestPositionData = smoothed;
2387
+ return out;
2388
+ }
2389
+ getTemporalBlendFactor(deltaTime, smoothingTime) {
2390
+ const dt = clamp(Number(deltaTime) || 0, 0, MAX_ACCUMULATED_SIMULATION_TIME);
2391
+ if (dt <= 0 || smoothingTime <= 0) {
2392
+ return 1;
2393
+ }
2394
+ return 1 - Math.exp(-dt / smoothingTime);
2395
+ }
2396
+ getSmoothedSphereCenter(collider, blend) {
2397
+ const current = collider.center?.clone() ?? new Vector3();
2398
+ const cached = this._smoothedSphereCenters.get(collider);
2399
+ if (!cached || blend >= 1) {
2400
+ this._smoothedSphereCenters.set(collider, current);
2401
+ return current;
2402
+ }
2403
+ cached.setXYZ(cached.x + (current.x - cached.x) * blend, cached.y + (current.y - cached.y) * blend, cached.z + (current.z - cached.z) * blend);
2404
+ return cached;
2405
+ }
2406
+ getSmoothedCapsuleEndpoints(collider, blend) {
2407
+ const currentStart = collider.start?.clone() ?? new Vector3();
2408
+ const currentEnd = collider.end?.clone() ?? new Vector3();
2409
+ const cached = this._smoothedCapsuleEndpoints.get(collider);
2410
+ if (!cached || blend >= 1) {
2411
+ const next = {
2412
+ start: currentStart,
2413
+ end: currentEnd
2414
+ };
2415
+ this._smoothedCapsuleEndpoints.set(collider, next);
2416
+ return next;
2417
+ }
2418
+ 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);
2419
+ 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);
2420
+ return cached;
2421
+ }
2422
+ getSmoothedPlaneData(collider, blend) {
2423
+ const currentPoint = collider.point?.clone() ?? new Vector3();
2424
+ const currentNormal = collider.normal?.clone() ?? Vector3.axisPY();
2425
+ if (currentNormal.magnitudeSq > 1e-8) {
2426
+ currentNormal.inplaceNormalize();
2427
+ } else {
2428
+ currentNormal.setXYZ(0, 1, 0);
2429
+ }
2430
+ const cached = this._smoothedPlaneData.get(collider);
2431
+ if (!cached || blend >= 1) {
2432
+ const next = {
2433
+ point: currentPoint,
2434
+ normal: currentNormal
2435
+ };
2436
+ this._smoothedPlaneData.set(collider, next);
2437
+ return next;
2438
+ }
2439
+ 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);
2440
+ 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);
2441
+ if (cached.normal.magnitudeSq > 1e-8) {
2442
+ cached.normal.inplaceNormalize();
2443
+ } else {
2444
+ cached.normal.setXYZ(0, 1, 0);
2445
+ }
2446
+ return cached;
2447
+ }
2448
+ updateTargetBoundingBox(positions) {
2449
+ if (!positions || positions.length < 3) {
2450
+ return;
2451
+ }
2452
+ let minX = Number.POSITIVE_INFINITY;
2453
+ let minY = Number.POSITIVE_INFINITY;
2454
+ let minZ = Number.POSITIVE_INFINITY;
2455
+ let maxX = Number.NEGATIVE_INFINITY;
2456
+ let maxY = Number.NEGATIVE_INFINITY;
2457
+ let maxZ = Number.NEGATIVE_INFINITY;
2458
+ for(let i = 0; i + 2 < positions.length; i += 3){
2459
+ const x = positions[i];
2460
+ const y = positions[i + 1];
2461
+ const z = positions[i + 2];
2462
+ if (!Number.isFinite(x) || !Number.isFinite(y) || !Number.isFinite(z)) {
2463
+ continue;
2464
+ }
2465
+ minX = Math.min(minX, x);
2466
+ minY = Math.min(minY, y);
2467
+ minZ = Math.min(minZ, z);
2468
+ maxX = Math.max(maxX, x);
2469
+ maxY = Math.max(maxY, y);
2470
+ maxZ = Math.max(maxZ, z);
2471
+ }
2472
+ if (!Number.isFinite(minX)) {
2473
+ return;
2474
+ }
2475
+ const padding = Math.max(maxX - minX, maxY - minY, maxZ - minZ) * 0.15;
2476
+ const bbox = new BoundingBox(new Vector3(minX - padding, minY - padding, minZ - padding), new Vector3(maxX + padding, maxY + padding, maxZ + padding));
2477
+ const target = this._collisionSpaceNode;
2478
+ if (typeof target?.setAnimatedBoundingBox === 'function') {
2479
+ target.setAnimatedBoundingBox(bbox);
2480
+ } else {
2481
+ this._primitive?.setBoundingVolume(bbox);
2482
+ }
2483
+ }
2484
+ commitRestPositions() {
2485
+ if (!this._prevRestPositionBuffer || !this._usingDynamicRestPose) {
2486
+ return;
2487
+ }
2488
+ if (this._dynamicRestPositionData) {
2489
+ this._prevRestPositionBuffer.bufferSubData(0, this._dynamicRestPositionData);
2490
+ }
2491
+ }
2492
+ disableTargetSkinning() {
2493
+ const target = this._collisionSpaceNode;
2494
+ if (!target || typeof target.suspendSkinning !== 'boolean' || this._restoreSkinAnimation !== null) {
2495
+ return;
2496
+ }
2497
+ this._skinAnimationTarget = target;
2498
+ this._restoreSkinAnimation = target.suspendSkinning;
2499
+ target.suspendSkinning = true;
2500
+ target.setBoneMatrices?.(null);
2501
+ target.setAnimatedBoundingBox?.(null);
2502
+ }
2503
+ restoreTargetSkinning() {
2504
+ if (!this._skinAnimationTarget || this._restoreSkinAnimation === null) {
2505
+ return;
2506
+ }
2507
+ this.detachAutoUpdateTarget();
2508
+ this._skinAnimationTarget.setAnimatedBoundingBox?.(null);
2509
+ this._skinAnimationTarget.suspendSkinning = this._restoreSkinAnimation;
2510
+ this._skinAnimationTarget = null;
2511
+ this._restoreSkinAnimation = null;
2512
+ }
2513
+ }
2514
+
2515
+ export { GPUClothSystem, createGPUClothWrapBindingData, isGPUClothSupported };
2516
+ //# sourceMappingURL=gpu_cloth_system.js.map