@needle-tools/engine 5.1.0-alpha.3 → 5.1.0-alpha.4

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 (178) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/SKILL.md +4 -1
  3. package/components.needle.json +1 -1
  4. package/dist/needle-engine.bundle-AjVIot3d.min.js +1733 -0
  5. package/dist/{needle-engine.bundle-DF01sSGQ.js → needle-engine.bundle-B7cqsI4c.js} +9149 -8867
  6. package/dist/{needle-engine.bundle-C-ixARur.umd.cjs → needle-engine.bundle-DQCuBTVp.umd.cjs} +154 -154
  7. package/dist/needle-engine.d.ts +754 -199
  8. package/dist/needle-engine.js +583 -584
  9. package/dist/needle-engine.min.js +1 -1
  10. package/dist/needle-engine.umd.cjs +1 -1
  11. package/lib/engine/api.d.ts +3 -0
  12. package/lib/engine/api.js +2 -0
  13. package/lib/engine/api.js.map +1 -1
  14. package/lib/engine/codegen/register_types.js +2 -10
  15. package/lib/engine/codegen/register_types.js.map +1 -1
  16. package/lib/engine/engine_context.js +2 -1
  17. package/lib/engine/engine_context.js.map +1 -1
  18. package/lib/engine/engine_disposable.d.ts +172 -0
  19. package/lib/engine/engine_disposable.js +136 -0
  20. package/lib/engine/engine_disposable.js.map +1 -0
  21. package/lib/engine/engine_gameobject.d.ts +1 -10
  22. package/lib/engine/engine_gameobject.js +20 -118
  23. package/lib/engine/engine_gameobject.js.map +1 -1
  24. package/lib/engine/engine_gltf_builtin_components.js +7 -69
  25. package/lib/engine/engine_gltf_builtin_components.js.map +1 -1
  26. package/lib/engine/engine_instantiate_resolve.d.ts +42 -0
  27. package/lib/engine/engine_instantiate_resolve.js +372 -0
  28. package/lib/engine/engine_instantiate_resolve.js.map +1 -0
  29. package/lib/engine/engine_mainloop_utils.js +2 -2
  30. package/lib/engine/engine_mainloop_utils.js.map +1 -1
  31. package/lib/engine/engine_networking.d.ts +51 -37
  32. package/lib/engine/engine_networking.js +132 -82
  33. package/lib/engine/engine_networking.js.map +1 -1
  34. package/lib/engine/engine_networking.transport.websocket.d.ts +15 -0
  35. package/lib/engine/engine_networking.transport.websocket.js +38 -0
  36. package/lib/engine/engine_networking.transport.websocket.js.map +1 -0
  37. package/lib/engine/engine_networking_instantiate.js +2 -2
  38. package/lib/engine/engine_networking_instantiate.js.map +1 -1
  39. package/lib/engine/engine_networking_types.d.ts +39 -1
  40. package/lib/engine/engine_networking_types.js +7 -0
  41. package/lib/engine/engine_networking_types.js.map +1 -1
  42. package/lib/engine/engine_physics_rapier.d.ts +11 -3
  43. package/lib/engine/engine_physics_rapier.js +88 -25
  44. package/lib/engine/engine_physics_rapier.js.map +1 -1
  45. package/lib/engine/engine_serialization_builtin_serializer.js +1 -5
  46. package/lib/engine/engine_serialization_builtin_serializer.js.map +1 -1
  47. package/lib/engine/engine_serialization_core.d.ts +1 -0
  48. package/lib/engine/engine_serialization_core.js +7 -0
  49. package/lib/engine/engine_serialization_core.js.map +1 -1
  50. package/lib/engine/engine_types.d.ts +19 -11
  51. package/lib/engine/engine_types.js +1 -1
  52. package/lib/engine/engine_types.js.map +1 -1
  53. package/lib/engine/engine_util_decorator.js +7 -2
  54. package/lib/engine/engine_util_decorator.js.map +1 -1
  55. package/lib/engine/engine_utils.d.ts +1 -1
  56. package/lib/engine/engine_utils.js +19 -5
  57. package/lib/engine/engine_utils.js.map +1 -1
  58. package/lib/engine-components/Animator.d.ts +6 -0
  59. package/lib/engine-components/Animator.js +17 -12
  60. package/lib/engine-components/Animator.js.map +1 -1
  61. package/lib/engine-components/AnimatorController.builder.d.ts +113 -0
  62. package/lib/engine-components/AnimatorController.builder.js +195 -0
  63. package/lib/engine-components/AnimatorController.builder.js.map +1 -0
  64. package/lib/engine-components/AnimatorController.d.ts +2 -119
  65. package/lib/engine-components/AnimatorController.js +31 -232
  66. package/lib/engine-components/AnimatorController.js.map +1 -1
  67. package/lib/engine-components/Collider.d.ts +18 -9
  68. package/lib/engine-components/Collider.js +61 -14
  69. package/lib/engine-components/Collider.js.map +1 -1
  70. package/lib/engine-components/Component.d.ts +72 -9
  71. package/lib/engine-components/Component.js +114 -10
  72. package/lib/engine-components/Component.js.map +1 -1
  73. package/lib/engine-components/DragControls.js +0 -7
  74. package/lib/engine-components/DragControls.js.map +1 -1
  75. package/lib/engine-components/EventList.d.ts +31 -9
  76. package/lib/engine-components/EventList.js +37 -76
  77. package/lib/engine-components/EventList.js.map +1 -1
  78. package/lib/engine-components/Joints.d.ts +4 -2
  79. package/lib/engine-components/Joints.js +19 -3
  80. package/lib/engine-components/Joints.js.map +1 -1
  81. package/lib/engine-components/Light.js +9 -1
  82. package/lib/engine-components/Light.js.map +1 -1
  83. package/lib/engine-components/RigidBody.d.ts +12 -4
  84. package/lib/engine-components/RigidBody.js +18 -4
  85. package/lib/engine-components/RigidBody.js.map +1 -1
  86. package/lib/engine-components/api.d.ts +1 -1
  87. package/lib/engine-components/api.js +1 -1
  88. package/lib/engine-components/api.js.map +1 -1
  89. package/lib/engine-components/codegen/components.d.ts +3 -9
  90. package/lib/engine-components/codegen/components.js +3 -9
  91. package/lib/engine-components/codegen/components.js.map +1 -1
  92. package/lib/engine-components/timeline/PlayableDirector.d.ts +16 -6
  93. package/lib/engine-components/timeline/PlayableDirector.js +70 -62
  94. package/lib/engine-components/timeline/PlayableDirector.js.map +1 -1
  95. package/lib/engine-components/timeline/SignalAsset.d.ts +3 -1
  96. package/lib/engine-components/timeline/SignalAsset.js +1 -0
  97. package/lib/engine-components/timeline/SignalAsset.js.map +1 -1
  98. package/lib/engine-components/timeline/TimelineBuilder.d.ts +247 -0
  99. package/lib/engine-components/timeline/TimelineBuilder.js +400 -0
  100. package/lib/engine-components/timeline/TimelineBuilder.js.map +1 -0
  101. package/lib/engine-components/timeline/TimelineModels.d.ts +2 -1
  102. package/lib/engine-components/timeline/TimelineModels.js +3 -0
  103. package/lib/engine-components/timeline/TimelineModels.js.map +1 -1
  104. package/lib/engine-components/timeline/TimelineTracks.d.ts +23 -0
  105. package/lib/engine-components/timeline/TimelineTracks.js +71 -13
  106. package/lib/engine-components/timeline/TimelineTracks.js.map +1 -1
  107. package/lib/engine-components/timeline/index.d.ts +2 -1
  108. package/lib/engine-components/timeline/index.js +2 -0
  109. package/lib/engine-components/timeline/index.js.map +1 -1
  110. package/package.json +2 -83
  111. package/plugins/common/license.js +5 -2
  112. package/plugins/common/worker.js +9 -4
  113. package/plugins/vite/dependencies.js +1 -11
  114. package/plugins/vite/dependency-watcher.js +2 -2
  115. package/plugins/vite/editor-connection.js +3 -3
  116. package/plugins/vite/reload.js +1 -1
  117. package/plugins/vite/server.js +2 -1
  118. package/src/engine/api.ts +4 -0
  119. package/src/engine/codegen/register_types.ts +2 -10
  120. package/src/engine/engine_context.ts +2 -1
  121. package/src/engine/engine_disposable.ts +214 -0
  122. package/src/engine/engine_gameobject.ts +52 -157
  123. package/src/engine/engine_gltf_builtin_components.ts +7 -76
  124. package/src/engine/engine_instantiate_resolve.ts +407 -0
  125. package/src/engine/engine_mainloop_utils.ts +2 -2
  126. package/src/engine/engine_networking.transport.websocket.ts +45 -0
  127. package/src/engine/engine_networking.ts +161 -137
  128. package/src/engine/engine_networking_instantiate.ts +2 -2
  129. package/src/engine/engine_networking_types.ts +41 -1
  130. package/src/engine/engine_physics_rapier.ts +82 -27
  131. package/src/engine/engine_serialization_builtin_serializer.ts +1 -6
  132. package/src/engine/engine_serialization_core.ts +9 -0
  133. package/src/engine/engine_types.ts +24 -15
  134. package/src/engine/engine_util_decorator.ts +7 -2
  135. package/src/engine/engine_utils.ts +16 -5
  136. package/src/engine-components/Animator.ts +18 -11
  137. package/src/engine-components/AnimatorController.builder.ts +261 -0
  138. package/src/engine-components/AnimatorController.ts +19 -291
  139. package/src/engine-components/Collider.ts +66 -18
  140. package/src/engine-components/Component.ts +118 -20
  141. package/src/engine-components/DragControls.ts +0 -9
  142. package/src/engine-components/EventList.ts +45 -83
  143. package/src/engine-components/Joints.ts +20 -4
  144. package/src/engine-components/Light.ts +10 -2
  145. package/src/engine-components/RigidBody.ts +18 -4
  146. package/src/engine-components/api.ts +1 -1
  147. package/src/engine-components/codegen/components.ts +3 -9
  148. package/src/engine-components/timeline/PlayableDirector.ts +67 -65
  149. package/src/engine-components/timeline/SignalAsset.ts +4 -1
  150. package/src/engine-components/timeline/TimelineBuilder.ts +564 -0
  151. package/src/engine-components/timeline/TimelineModels.ts +5 -1
  152. package/src/engine-components/timeline/TimelineTracks.ts +74 -13
  153. package/src/engine-components/timeline/index.ts +2 -1
  154. package/dist/needle-engine.bundle-CHmXdnE1.min.js +0 -1733
  155. package/lib/engine-components/AvatarLoader.d.ts +0 -80
  156. package/lib/engine-components/AvatarLoader.js +0 -232
  157. package/lib/engine-components/AvatarLoader.js.map +0 -1
  158. package/lib/engine-components/avatar/AvatarBlink_Simple.d.ts +0 -11
  159. package/lib/engine-components/avatar/AvatarBlink_Simple.js +0 -77
  160. package/lib/engine-components/avatar/AvatarBlink_Simple.js.map +0 -1
  161. package/lib/engine-components/avatar/AvatarEyeLook_Rotation.d.ts +0 -14
  162. package/lib/engine-components/avatar/AvatarEyeLook_Rotation.js +0 -69
  163. package/lib/engine-components/avatar/AvatarEyeLook_Rotation.js.map +0 -1
  164. package/lib/engine-components/avatar/Avatar_Brain_LookAt.d.ts +0 -29
  165. package/lib/engine-components/avatar/Avatar_Brain_LookAt.js +0 -122
  166. package/lib/engine-components/avatar/Avatar_Brain_LookAt.js.map +0 -1
  167. package/lib/engine-components/avatar/Avatar_MouthShapes.d.ts +0 -15
  168. package/lib/engine-components/avatar/Avatar_MouthShapes.js +0 -80
  169. package/lib/engine-components/avatar/Avatar_MouthShapes.js.map +0 -1
  170. package/lib/engine-components/avatar/Avatar_MustacheShake.d.ts +0 -9
  171. package/lib/engine-components/avatar/Avatar_MustacheShake.js +0 -30
  172. package/lib/engine-components/avatar/Avatar_MustacheShake.js.map +0 -1
  173. package/src/engine-components/AvatarLoader.ts +0 -264
  174. package/src/engine-components/avatar/AvatarBlink_Simple.ts +0 -70
  175. package/src/engine-components/avatar/AvatarEyeLook_Rotation.ts +0 -64
  176. package/src/engine-components/avatar/Avatar_Brain_LookAt.ts +0 -140
  177. package/src/engine-components/avatar/Avatar_MouthShapes.ts +0 -84
  178. package/src/engine-components/avatar/Avatar_MustacheShake.ts +0 -32
@@ -0,0 +1,261 @@
1
+ import { AnimationClip } from "three";
2
+
3
+ import { AnimatorConditionMode, AnimatorControllerParameterType } from "../engine/extensions/NEEDLE_animator_controller_model.js";
4
+ import type { AnimatorControllerModel, Condition, Parameter, State, Transition } from "../engine/extensions/NEEDLE_animator_controller_model.js";
5
+ import { InstantiateIdProvider } from "../engine/engine_networking_instantiate.js";
6
+ import { AnimatorController } from "./AnimatorController.js";
7
+
8
+
9
+ /**
10
+ * Configuration for an animation state in the builder
11
+ */
12
+ export declare type StateOptions = {
13
+ /** The animation clip for this state */
14
+ clip: AnimationClip;
15
+ /** Whether the animation should loop (default: false) */
16
+ loop?: boolean;
17
+ /** Base speed multiplier (default: 1) */
18
+ speed?: number;
19
+ /** Name of a float parameter to multiply with speed */
20
+ speedParameter?: string;
21
+ /** Normalized cycle offset 0-1 (default: 0) */
22
+ cycleOffset?: number;
23
+ /** Name of a float parameter to use as cycle offset */
24
+ cycleOffsetParameter?: string;
25
+ }
26
+
27
+ /**
28
+ * Configuration for a transition in the builder
29
+ */
30
+ export declare type TransitionOptions = {
31
+ /** Duration of the crossfade in seconds (default: 0) */
32
+ duration?: number;
33
+ /** Normalized exit time 0-1 (default: 1). Only used when hasExitTime is true */
34
+ exitTime?: number;
35
+ /** Whether the transition waits for exitTime before transitioning (default: false) */
36
+ hasExitTime?: boolean;
37
+ /** Normalized offset into the destination state's animation (default: 0) */
38
+ offset?: number;
39
+ /** Whether duration is in seconds (true) or normalized (false) (default: false) */
40
+ hasFixedDuration?: boolean;
41
+ }
42
+
43
+ /** String condition modes for the builder, mapped to {@link AnimatorConditionMode} */
44
+ export type ConditionMode = "if" | "ifNot" | "greater" | "less" | "equals" | "notEqual";
45
+
46
+ function conditionModeToEnum(mode: ConditionMode): AnimatorConditionMode {
47
+ switch (mode) {
48
+ case "if": return AnimatorConditionMode.If;
49
+ case "ifNot": return AnimatorConditionMode.IfNot;
50
+ case "greater": return AnimatorConditionMode.Greater;
51
+ case "less": return AnimatorConditionMode.Less;
52
+ case "equals": return AnimatorConditionMode.Equals;
53
+ case "notEqual": return AnimatorConditionMode.NotEqual;
54
+ }
55
+ }
56
+
57
+ type BuilderTransition = {
58
+ to: string;
59
+ options: TransitionOptions;
60
+ conditions: Array<{ parameter: string; mode: ConditionMode; threshold: number }>;
61
+ };
62
+
63
+ type BuilderState = {
64
+ name: string;
65
+ options: StateOptions;
66
+ transitions: BuilderTransition[];
67
+ };
68
+
69
+ /**
70
+ * A fluent builder for creating {@link AnimatorController} instances from code.
71
+ *
72
+ * Use {@link AnimatorController.build} to create a new builder.
73
+ *
74
+ * @example
75
+ * ```ts
76
+ * const controller = AnimatorController.build("CharacterController")
77
+ * .floatParameter("Speed", 0)
78
+ * .triggerParameter("Jump")
79
+ * .state("Idle", { clip: idleClip, loop: true })
80
+ * .state("Walk", { clip: walkClip, loop: true })
81
+ * .state("Jump", { clip: jumpClip })
82
+ * .transition("Idle", "Walk", { duration: 0.25 })
83
+ * .condition("Speed", "greater", 0.1)
84
+ * .transition("Walk", "Idle", { duration: 0.25 })
85
+ * .condition("Speed", "less", 0.1)
86
+ * .transition("*", "Jump", { duration: 0.1 })
87
+ * .condition("Jump", "if")
88
+ * .transition("Jump", "Idle", { hasExitTime: true, exitTime: 0.9, duration: 0.25 })
89
+ * .build();
90
+ * ```
91
+ *
92
+ * @category Animation and Sequencing
93
+ * @group Utilities
94
+ */
95
+ export class AnimatorControllerBuilder {
96
+ private _name: string;
97
+ private _parameters: Parameter[] = [];
98
+ private _states: BuilderState[] = [];
99
+ private _anyStateTransitions: BuilderTransition[] = [];
100
+ private _defaultStateName: string | null = null;
101
+ private _lastTransition: BuilderTransition | null = null;
102
+
103
+ constructor(name?: string) {
104
+ this._name = name ?? "AnimatorController";
105
+ }
106
+
107
+ /** Adds a float parameter */
108
+ floatParameter(name: string, defaultValue: number = 0): this {
109
+ this._parameters.push({ name, hash: this._parameters.length, type: AnimatorControllerParameterType.Float, value: defaultValue });
110
+ return this;
111
+ }
112
+
113
+ /** Adds an integer parameter */
114
+ intParameter(name: string, defaultValue: number = 0): this {
115
+ this._parameters.push({ name, hash: this._parameters.length, type: AnimatorControllerParameterType.Int, value: defaultValue });
116
+ return this;
117
+ }
118
+
119
+ /** Adds a boolean parameter */
120
+ boolParameter(name: string, defaultValue: boolean = false): this {
121
+ this._parameters.push({ name, hash: this._parameters.length, type: AnimatorControllerParameterType.Bool, value: defaultValue });
122
+ return this;
123
+ }
124
+
125
+ /** Adds a trigger parameter */
126
+ triggerParameter(name: string): this {
127
+ this._parameters.push({ name, hash: this._parameters.length, type: AnimatorControllerParameterType.Trigger, value: false });
128
+ return this;
129
+ }
130
+
131
+ /**
132
+ * Adds a state to the controller. The first state added becomes the default state.
133
+ * @param name - Unique name for the state
134
+ * @param options - State configuration including clip, loop, speed
135
+ */
136
+ state(name: string, options: StateOptions): this {
137
+ this._states.push({ name, options, transitions: [] });
138
+ return this;
139
+ }
140
+
141
+ /**
142
+ * Adds a transition between two states.
143
+ * Use `"*"` as the source to create a transition from any state.
144
+ * Chain `.condition()` calls after this to add conditions.
145
+ * @param from - Source state name, or `"*"` for any-state transition
146
+ * @param to - Destination state name
147
+ * @param options - Transition configuration
148
+ */
149
+ transition(from: string, to: string, options?: TransitionOptions): this {
150
+ const t: BuilderTransition = { to, options: options ?? {}, conditions: [] };
151
+ if (from === "*") {
152
+ this._anyStateTransitions.push(t);
153
+ }
154
+ else {
155
+ const state = this._states.find(s => s.name === from);
156
+ if (!state) throw new Error(`AnimatorControllerBuilder: source state "${from}" not found. Add it with .state() first.`);
157
+ state.transitions.push(t);
158
+ }
159
+ this._lastTransition = t;
160
+ return this;
161
+ }
162
+
163
+ /**
164
+ * Adds a condition to the most recently added transition.
165
+ * Multiple conditions on the same transition are AND-ed together.
166
+ * @param parameter - Name of the parameter to evaluate
167
+ * @param mode - Condition mode: `"if"`, `"ifNot"`, `"greater"`, `"less"`, `"equals"`, `"notEqual"`
168
+ * @param threshold - Comparison threshold for numeric conditions (default: 0)
169
+ */
170
+ condition(parameter: string, mode: ConditionMode, threshold: number = 0): this {
171
+ if (!this._lastTransition) throw new Error("AnimatorControllerBuilder: .condition() must be called after .transition()");
172
+ this._lastTransition.conditions.push({ parameter, mode, threshold });
173
+ return this;
174
+ }
175
+
176
+ /**
177
+ * Sets which state is the default/entry state.
178
+ * If not called, the first added state is used.
179
+ * @param name - Name of the state
180
+ */
181
+ defaultState(name: string): this {
182
+ this._defaultStateName = name;
183
+ return this;
184
+ }
185
+
186
+ /**
187
+ * Builds and returns the {@link AnimatorController}.
188
+ * Resolves all state name references to indices.
189
+ */
190
+ build(): AnimatorController {
191
+ const stateIndexMap = new Map<string, number>();
192
+ this._states.forEach((s, i) => stateIndexMap.set(s.name, i));
193
+
194
+ let defaultStateIndex = 0;
195
+ if (this._defaultStateName !== null) {
196
+ const idx = stateIndexMap.get(this._defaultStateName);
197
+ if (idx === undefined) throw new Error(`AnimatorControllerBuilder: default state "${this._defaultStateName}" not found`);
198
+ defaultStateIndex = idx;
199
+ }
200
+
201
+ const resolveTransition = (t: BuilderTransition): Transition => {
202
+ const destIndex = stateIndexMap.get(t.to);
203
+ if (destIndex === undefined) throw new Error(`AnimatorControllerBuilder: transition target "${t.to}" not found`);
204
+ return {
205
+ exitTime: t.options.exitTime ?? 1,
206
+ hasExitTime: t.options.hasExitTime ?? false,
207
+ duration: t.options.duration ?? 0,
208
+ offset: t.options.offset ?? 0,
209
+ hasFixedDuration: t.options.hasFixedDuration ?? false,
210
+ destinationState: destIndex,
211
+ conditions: t.conditions.map(c => ({
212
+ parameter: c.parameter,
213
+ mode: conditionModeToEnum(c.mode),
214
+ threshold: c.threshold,
215
+ })),
216
+ };
217
+ };
218
+
219
+ const states: State[] = this._states.map((s, index) => {
220
+ const transitions: Transition[] = s.transitions.map(resolveTransition);
221
+
222
+ // Replicate any-state transitions onto every state (except self-targeting)
223
+ for (const anyT of this._anyStateTransitions) {
224
+ const destIndex = stateIndexMap.get(anyT.to);
225
+ if (destIndex === index) continue;
226
+ transitions.push(resolveTransition(anyT));
227
+ }
228
+
229
+ return {
230
+ name: s.name,
231
+ hash: index,
232
+ motion: {
233
+ name: s.options.clip.name,
234
+ clip: s.options.clip,
235
+ isLooping: s.options.loop ?? false,
236
+ },
237
+ transitions,
238
+ behaviours: [],
239
+ speed: s.options.speed,
240
+ speedParameter: s.options.speedParameter,
241
+ cycleOffset: s.options.cycleOffset,
242
+ cycleOffsetParameter: s.options.cycleOffsetParameter,
243
+ };
244
+ });
245
+
246
+ const model: AnimatorControllerModel = {
247
+ name: this._name,
248
+ guid: new InstantiateIdProvider(Date.now()).generateUUID(),
249
+ parameters: this._parameters,
250
+ layers: [{
251
+ name: "Base Layer",
252
+ stateMachine: {
253
+ defaultState: defaultStateIndex,
254
+ states,
255
+ }
256
+ }],
257
+ };
258
+
259
+ return new AnimatorController(model);
260
+ }
261
+ }
@@ -5,6 +5,7 @@ import { AnimationUtils } from "../engine/engine_animation.js";
5
5
  import { Mathf } from "../engine/engine_math.js";
6
6
  import { InstantiateIdProvider } from "../engine/engine_networking_instantiate.js";
7
7
  import { assign, SerializationContext, TypeSerializer } from "../engine/engine_serialization_core.js";
8
+ import { serializable } from "../engine/engine_serialization_decorator.js";
8
9
  import { Context } from "../engine/engine_setup.js";
9
10
  import { isAnimationAction } from "../engine/engine_three_utils.js";
10
11
  import { TypeStore } from "../engine/engine_typestore.js";
@@ -12,6 +13,9 @@ import { deepClone, getParam } from "../engine/engine_utils.js";
12
13
  import type { AnimatorControllerModel, Condition, Parameter, State, Transition } from "../engine/extensions/NEEDLE_animator_controller_model.js";
13
14
  import { AnimatorConditionMode, AnimatorControllerParameterType, AnimatorStateInfo, createMotion, StateMachineBehaviour } from "../engine/extensions/NEEDLE_animator_controller_model.js";
14
15
  import type { Animator } from "./Animator.js";
16
+ import { AnimatorControllerBuilder } from "./AnimatorController.builder.js";
17
+
18
+ export { AnimatorControllerBuilder, type ConditionMode, type StateOptions, type TransitionOptions } from "./AnimatorController.builder.js";
15
19
 
16
20
  const debug = getParam("debuganimatorcontroller");
17
21
  const debugRootMotion = getParam("debugrootmotion");
@@ -30,263 +34,6 @@ declare type CreateAnimatorControllerOptions = {
30
34
  }
31
35
 
32
36
 
33
- // #region AnimatorControllerBuilder
34
-
35
- /**
36
- * Configuration for an animation state in the builder
37
- */
38
- export declare type StateOptions = {
39
- /** The animation clip for this state */
40
- clip: AnimationClip;
41
- /** Whether the animation should loop (default: false) */
42
- loop?: boolean;
43
- /** Base speed multiplier (default: 1) */
44
- speed?: number;
45
- /** Name of a float parameter to multiply with speed */
46
- speedParameter?: string;
47
- /** Normalized cycle offset 0-1 (default: 0) */
48
- cycleOffset?: number;
49
- /** Name of a float parameter to use as cycle offset */
50
- cycleOffsetParameter?: string;
51
- }
52
-
53
- /**
54
- * Configuration for a transition in the builder
55
- */
56
- export declare type TransitionOptions = {
57
- /** Duration of the crossfade in seconds (default: 0) */
58
- duration?: number;
59
- /** Normalized exit time 0-1 (default: 1). Only used when hasExitTime is true */
60
- exitTime?: number;
61
- /** Whether the transition waits for exitTime before transitioning (default: false) */
62
- hasExitTime?: boolean;
63
- /** Normalized offset into the destination state's animation (default: 0) */
64
- offset?: number;
65
- /** Whether duration is in seconds (true) or normalized (false) (default: false) */
66
- hasFixedDuration?: boolean;
67
- }
68
-
69
- /** String condition modes for the builder, mapped to {@link AnimatorConditionMode} */
70
- export type ConditionMode = "if" | "ifNot" | "greater" | "less" | "equals" | "notEqual";
71
-
72
- function conditionModeToEnum(mode: ConditionMode): AnimatorConditionMode {
73
- switch (mode) {
74
- case "if": return AnimatorConditionMode.If;
75
- case "ifNot": return AnimatorConditionMode.IfNot;
76
- case "greater": return AnimatorConditionMode.Greater;
77
- case "less": return AnimatorConditionMode.Less;
78
- case "equals": return AnimatorConditionMode.Equals;
79
- case "notEqual": return AnimatorConditionMode.NotEqual;
80
- }
81
- }
82
-
83
- type BuilderTransition = {
84
- to: string;
85
- options: TransitionOptions;
86
- conditions: Array<{ parameter: string; mode: ConditionMode; threshold: number }>;
87
- };
88
-
89
- type BuilderState = {
90
- name: string;
91
- options: StateOptions;
92
- transitions: BuilderTransition[];
93
- };
94
-
95
- /**
96
- * A fluent builder for creating {@link AnimatorController} instances from code.
97
- *
98
- * Use {@link AnimatorController.build} to create a new builder.
99
- *
100
- * @example
101
- * ```ts
102
- * const controller = AnimatorController.build("CharacterController")
103
- * .floatParameter("Speed", 0)
104
- * .triggerParameter("Jump")
105
- * .state("Idle", { clip: idleClip, loop: true })
106
- * .state("Walk", { clip: walkClip, loop: true })
107
- * .state("Jump", { clip: jumpClip })
108
- * .transition("Idle", "Walk", { duration: 0.25 })
109
- * .condition("Speed", "greater", 0.1)
110
- * .transition("Walk", "Idle", { duration: 0.25 })
111
- * .condition("Speed", "less", 0.1)
112
- * .transition("*", "Jump", { duration: 0.1 })
113
- * .condition("Jump", "if")
114
- * .transition("Jump", "Idle", { hasExitTime: true, exitTime: 0.9, duration: 0.25 })
115
- * .build();
116
- * ```
117
- *
118
- * @category Animation and Sequencing
119
- * @group Utilities
120
- */
121
- export class AnimatorControllerBuilder {
122
- private _name: string;
123
- private _parameters: Parameter[] = [];
124
- private _states: BuilderState[] = [];
125
- private _anyStateTransitions: BuilderTransition[] = [];
126
- private _defaultStateName: string | null = null;
127
- private _lastTransition: BuilderTransition | null = null;
128
-
129
- constructor(name?: string) {
130
- this._name = name ?? "AnimatorController";
131
- }
132
-
133
- /** Adds a float parameter */
134
- floatParameter(name: string, defaultValue: number = 0): this {
135
- this._parameters.push({ name, hash: this._parameters.length, type: AnimatorControllerParameterType.Float, value: defaultValue });
136
- return this;
137
- }
138
-
139
- /** Adds an integer parameter */
140
- intParameter(name: string, defaultValue: number = 0): this {
141
- this._parameters.push({ name, hash: this._parameters.length, type: AnimatorControllerParameterType.Int, value: defaultValue });
142
- return this;
143
- }
144
-
145
- /** Adds a boolean parameter */
146
- boolParameter(name: string, defaultValue: boolean = false): this {
147
- this._parameters.push({ name, hash: this._parameters.length, type: AnimatorControllerParameterType.Bool, value: defaultValue });
148
- return this;
149
- }
150
-
151
- /** Adds a trigger parameter */
152
- triggerParameter(name: string): this {
153
- this._parameters.push({ name, hash: this._parameters.length, type: AnimatorControllerParameterType.Trigger, value: false });
154
- return this;
155
- }
156
-
157
- /**
158
- * Adds a state to the controller. The first state added becomes the default state.
159
- * @param name - Unique name for the state
160
- * @param options - State configuration including clip, loop, speed
161
- */
162
- state(name: string, options: StateOptions): this {
163
- this._states.push({ name, options, transitions: [] });
164
- return this;
165
- }
166
-
167
- /**
168
- * Adds a transition between two states.
169
- * Use `"*"` as the source to create a transition from any state.
170
- * Chain `.condition()` calls after this to add conditions.
171
- * @param from - Source state name, or `"*"` for any-state transition
172
- * @param to - Destination state name
173
- * @param options - Transition configuration
174
- */
175
- transition(from: string, to: string, options?: TransitionOptions): this {
176
- const t: BuilderTransition = { to, options: options ?? {}, conditions: [] };
177
- if (from === "*") {
178
- this._anyStateTransitions.push(t);
179
- }
180
- else {
181
- const state = this._states.find(s => s.name === from);
182
- if (!state) throw new Error(`AnimatorControllerBuilder: source state "${from}" not found. Add it with .state() first.`);
183
- state.transitions.push(t);
184
- }
185
- this._lastTransition = t;
186
- return this;
187
- }
188
-
189
- /**
190
- * Adds a condition to the most recently added transition.
191
- * Multiple conditions on the same transition are AND-ed together.
192
- * @param parameter - Name of the parameter to evaluate
193
- * @param mode - Condition mode: `"if"`, `"ifNot"`, `"greater"`, `"less"`, `"equals"`, `"notEqual"`
194
- * @param threshold - Comparison threshold for numeric conditions (default: 0)
195
- */
196
- condition(parameter: string, mode: ConditionMode, threshold: number = 0): this {
197
- if (!this._lastTransition) throw new Error("AnimatorControllerBuilder: .condition() must be called after .transition()");
198
- this._lastTransition.conditions.push({ parameter, mode, threshold });
199
- return this;
200
- }
201
-
202
- /**
203
- * Sets which state is the default/entry state.
204
- * If not called, the first added state is used.
205
- * @param name - Name of the state
206
- */
207
- defaultState(name: string): this {
208
- this._defaultStateName = name;
209
- return this;
210
- }
211
-
212
- /**
213
- * Builds and returns the {@link AnimatorController}.
214
- * Resolves all state name references to indices.
215
- */
216
- build(): AnimatorController {
217
- const stateIndexMap = new Map<string, number>();
218
- this._states.forEach((s, i) => stateIndexMap.set(s.name, i));
219
-
220
- let defaultStateIndex = 0;
221
- if (this._defaultStateName !== null) {
222
- const idx = stateIndexMap.get(this._defaultStateName);
223
- if (idx === undefined) throw new Error(`AnimatorControllerBuilder: default state "${this._defaultStateName}" not found`);
224
- defaultStateIndex = idx;
225
- }
226
-
227
- const resolveTransition = (t: BuilderTransition): Transition => {
228
- const destIndex = stateIndexMap.get(t.to);
229
- if (destIndex === undefined) throw new Error(`AnimatorControllerBuilder: transition target "${t.to}" not found`);
230
- return {
231
- exitTime: t.options.exitTime ?? 1,
232
- hasExitTime: t.options.hasExitTime ?? false,
233
- duration: t.options.duration ?? 0,
234
- offset: t.options.offset ?? 0,
235
- hasFixedDuration: t.options.hasFixedDuration ?? false,
236
- destinationState: destIndex,
237
- conditions: t.conditions.map(c => ({
238
- parameter: c.parameter,
239
- mode: conditionModeToEnum(c.mode),
240
- threshold: c.threshold,
241
- })),
242
- };
243
- };
244
-
245
- const states: State[] = this._states.map((s, index) => {
246
- const transitions: Transition[] = s.transitions.map(resolveTransition);
247
-
248
- // Replicate any-state transitions onto every state (except self-targeting)
249
- for (const anyT of this._anyStateTransitions) {
250
- const destIndex = stateIndexMap.get(anyT.to);
251
- if (destIndex === index) continue;
252
- transitions.push(resolveTransition(anyT));
253
- }
254
-
255
- return {
256
- name: s.name,
257
- hash: index,
258
- motion: {
259
- name: s.options.clip.name,
260
- clip: s.options.clip,
261
- isLooping: s.options.loop ?? false,
262
- },
263
- transitions,
264
- behaviours: [],
265
- speed: s.options.speed,
266
- speedParameter: s.options.speedParameter,
267
- cycleOffset: s.options.cycleOffset,
268
- cycleOffsetParameter: s.options.cycleOffsetParameter,
269
- };
270
- });
271
-
272
- const model: AnimatorControllerModel = {
273
- name: this._name,
274
- guid: new InstantiateIdProvider(Date.now()).generateUUID(),
275
- parameters: this._parameters,
276
- layers: [{
277
- name: "Base Layer",
278
- stateMachine: {
279
- defaultState: defaultStateIndex,
280
- states,
281
- }
282
- }],
283
- };
284
-
285
- return new AnimatorController(model);
286
- }
287
- }
288
- // #endregion
289
-
290
37
  // #region AnimatorController
291
38
  /**
292
39
  * Controls the playback of animations using a state machine architecture.
@@ -628,6 +375,9 @@ export class AnimatorController {
628
375
  /**
629
376
  * The data model describing the animation states and transitions.
630
377
  */
378
+ // @serializable marks this for the instantiate resolve system so it traverses into the model
379
+ // and remaps Object3D references (e.g. motion.clips[].node) to cloned objects.
380
+ @serializable()
631
381
  model: AnimatorControllerModel;
632
382
 
633
383
  /**
@@ -682,38 +432,6 @@ export class AnimatorController {
682
432
  }
683
433
  }
684
434
 
685
- /**
686
- * Creates a deep copy of this controller.
687
- * Clones the model data but does not copy runtime state.
688
- *
689
- * @returns A new AnimatorController instance with the same configuration
690
- */
691
- clone() {
692
- if (typeof this.model === "string") {
693
- console.warn("AnimatorController has not been resolved, can not create model from string", this.model);
694
- return null;
695
- }
696
- if (debug) console.warn("AnimatorController clone()", this.model);
697
- // clone runtime controller but dont clone clip or action
698
- const clonedModel = deepClone(this.model, (_owner, _key, _value) => {
699
- if (_value === null || _value === undefined) return true;
700
- // dont clone three Objects
701
- if (_value.type === "Object3D" || _value.isObject3D === true) return false;
702
- // dont clone AnimationAction
703
- if (isAnimationAction(_value)) { //.constructor.name === "AnimationAction") {
704
- // console.log(_value);
705
- return false;
706
- }
707
- // dont clone AnimationClip
708
- if (_value["tracks"] !== undefined) return false;
709
- // when assigned __concreteInstance during serialization
710
- if (_value instanceof AnimatorController) return false;
711
- return true;
712
- }) as AnimatorControllerModel;
713
- console.assert(clonedModel !== this.model);
714
- const controller = new AnimatorController(clonedModel);
715
- return controller;
716
- }
717
435
 
718
436
  /**
719
437
  * Updates the controller's state machine and animations.
@@ -749,8 +467,18 @@ export class AnimatorController {
749
467
  get activeState(): State | undefined { return this._activeState; }
750
468
 
751
469
  constructor(model: AnimatorControllerModel) {
752
- this.model = model;
753
- if (debug) console.log(this);
470
+ // Deep-clone the model so each AnimatorController owns its own copy.
471
+ // This ensures independent parameter state and allows the instantiate
472
+ // resolve system to remap Object3D refs (e.g. motion.clips[].node) per instance.
473
+ // The predicate skips Object3D (keeps references) and AnimationClip/AnimationAction (shared resources).
474
+ this.model = deepClone(model, (_owner, _key, _value) => {
475
+ if (_value === null || _value === undefined) return true;
476
+ if (_value.isObject3D === true) return false;
477
+ if (isAnimationAction(_value)) return false;
478
+ if (_value["tracks"] !== undefined) return false; // AnimationClip
479
+ if (_value instanceof AnimatorController) return false;
480
+ return true;
481
+ }) as AnimatorControllerModel;
754
482
  }
755
483
 
756
484
  private _activeStates: State[] = [];