@needle-tools/engine 5.1.0-alpha.4 → 5.1.0-alpha.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +39 -0
- package/components.needle.json +1 -1
- package/dist/{needle-engine.bundle-DQCuBTVp.umd.cjs → needle-engine.bundle-5avtTUMM.umd.cjs} +149 -148
- package/dist/{needle-engine.bundle-AjVIot3d.min.js → needle-engine.bundle-BHcw4C8f.min.js} +187 -186
- package/dist/{needle-engine.bundle-B7cqsI4c.js → needle-engine.bundle-C0gPOq4m.js} +7522 -7092
- package/dist/needle-engine.d.ts +715 -176
- package/dist/needle-engine.js +595 -593
- package/dist/needle-engine.min.js +1 -1
- package/dist/needle-engine.umd.cjs +1 -1
- package/dist/three.js +1 -0
- package/dist/three.min.js +21 -21
- package/dist/three.umd.cjs +16 -16
- package/lib/engine/api.d.ts +3 -1
- package/lib/engine/api.js +3 -1
- package/lib/engine/api.js.map +1 -1
- package/lib/engine/codegen/register_types.js +10 -10
- package/lib/engine/codegen/register_types.js.map +1 -1
- package/lib/engine/engine_camera.fit.js +16 -4
- package/lib/engine/engine_camera.fit.js.map +1 -1
- package/lib/engine/engine_context.d.ts +20 -7
- package/lib/engine/engine_context.js +36 -14
- package/lib/engine/engine_context.js.map +1 -1
- package/lib/engine/engine_context_eventbus.d.ts +47 -0
- package/lib/engine/engine_context_eventbus.js +47 -0
- package/lib/engine/engine_context_eventbus.js.map +1 -0
- package/lib/engine/engine_init.js +2 -2
- package/lib/engine/engine_init.js.map +1 -1
- package/lib/engine/engine_input.d.ts +23 -4
- package/lib/engine/engine_input.js +2 -1
- package/lib/engine/engine_input.js.map +1 -1
- package/lib/engine/engine_license.d.ts +7 -7
- package/lib/engine/engine_license.js +185 -57
- package/lib/engine/engine_license.js.map +1 -1
- package/lib/engine/engine_networking_blob.js +3 -3
- package/lib/engine/engine_networking_blob.js.map +1 -1
- package/lib/engine/engine_physics_rapier.d.ts +10 -0
- package/lib/engine/engine_physics_rapier.js +6 -0
- package/lib/engine/engine_physics_rapier.js.map +1 -1
- package/lib/engine/engine_types.d.ts +10 -0
- package/lib/engine/engine_utils_qrcode.js +2 -2
- package/lib/engine/engine_utils_qrcode.js.map +1 -1
- package/lib/engine/webcomponents/needle menu/needle-menu-spatial.js +2 -2
- package/lib/engine/webcomponents/needle menu/needle-menu-spatial.js.map +1 -1
- package/lib/engine/webcomponents/needle menu/needle-menu.js +5 -5
- package/lib/engine/webcomponents/needle menu/needle-menu.js.map +1 -1
- package/lib/engine/webcomponents/needle-engine.js +2 -2
- package/lib/engine/webcomponents/needle-engine.js.map +1 -1
- package/lib/engine/webcomponents/needle-engine.loading.js +2 -2
- package/lib/engine/webcomponents/needle-engine.loading.js.map +1 -1
- package/lib/engine/xr/TempXRContext.js +2 -2
- package/lib/engine/xr/TempXRContext.js.map +1 -1
- package/lib/engine-components/AnimationBuilder.d.ts +158 -0
- package/lib/engine-components/AnimationBuilder.js +305 -0
- package/lib/engine-components/AnimationBuilder.js.map +1 -0
- package/lib/engine-components/Animator.js +6 -1
- package/lib/engine-components/Animator.js.map +1 -1
- package/lib/engine-components/AnimatorController.builder.d.ts +101 -23
- package/lib/engine-components/AnimatorController.builder.js +88 -20
- package/lib/engine-components/AnimatorController.builder.js.map +1 -1
- package/lib/engine-components/AnimatorController.js +2 -0
- package/lib/engine-components/AnimatorController.js.map +1 -1
- package/lib/engine-components/ContactShadows.d.ts +1 -0
- package/lib/engine-components/ContactShadows.js +14 -1
- package/lib/engine-components/ContactShadows.js.map +1 -1
- package/lib/engine-components/DropListener.js +3 -0
- package/lib/engine-components/DropListener.js.map +1 -1
- package/lib/engine-components/OrbitControls.d.ts +0 -2
- package/lib/engine-components/OrbitControls.js +14 -1
- package/lib/engine-components/OrbitControls.js.map +1 -1
- package/lib/engine-components/SceneSwitcher.js +3 -0
- package/lib/engine-components/SceneSwitcher.js.map +1 -1
- package/lib/engine-components/api.d.ts +1 -0
- package/lib/engine-components/api.js +1 -0
- package/lib/engine-components/api.js.map +1 -1
- package/lib/engine-components/codegen/components.d.ts +6 -6
- package/lib/engine-components/codegen/components.js +6 -6
- package/lib/engine-components/codegen/components.js.map +1 -1
- package/lib/engine-components/export/usdz/USDZExporter.js +4 -4
- package/lib/engine-components/export/usdz/USDZExporter.js.map +1 -1
- package/lib/engine-components/postprocessing/Effects/Tonemapping.utils.d.ts +1 -1
- package/lib/engine-components/timeline/PlayableDirector.d.ts +7 -7
- package/lib/engine-components/timeline/PlayableDirector.js +6 -6
- package/lib/engine-components/timeline/PlayableDirector.js.map +1 -1
- package/lib/engine-components/timeline/TimelineBuilder.d.ts +175 -9
- package/lib/engine-components/timeline/TimelineBuilder.js +108 -2
- package/lib/engine-components/timeline/TimelineBuilder.js.map +1 -1
- package/lib/engine-components/timeline/TimelineTracks.d.ts +15 -7
- package/lib/engine-components/timeline/TimelineTracks.js +22 -14
- package/lib/engine-components/timeline/TimelineTracks.js.map +1 -1
- package/lib/engine-components/web/CursorFollow.d.ts +0 -1
- package/lib/engine-components/web/CursorFollow.js +0 -1
- package/lib/engine-components/web/CursorFollow.js.map +1 -1
- package/lib/engine-components/webxr/WebXRImageTracking.d.ts +62 -1
- package/lib/engine-components/webxr/WebXRImageTracking.js +55 -2
- package/lib/engine-components/webxr/WebXRImageTracking.js.map +1 -1
- package/package.json +1 -1
- package/plugins/common/cloud.js +6 -1
- package/plugins/common/license.js +26 -8
- package/plugins/vite/license.js +42 -7
- package/src/engine/api.ts +4 -1
- package/src/engine/codegen/register_types.ts +10 -10
- package/src/engine/engine_camera.fit.ts +15 -4
- package/src/engine/engine_context.ts +41 -16
- package/src/engine/engine_context_eventbus.ts +73 -0
- package/src/engine/engine_init.ts +2 -2
- package/src/engine/engine_input.ts +27 -6
- package/src/engine/engine_license.ts +201 -55
- package/src/engine/engine_networking_blob.ts +3 -3
- package/src/engine/engine_physics_rapier.ts +20 -6
- package/src/engine/engine_types.ts +22 -12
- package/src/engine/engine_utils_qrcode.ts +2 -2
- package/src/engine/webcomponents/needle menu/needle-menu-spatial.ts +2 -2
- package/src/engine/webcomponents/needle menu/needle-menu.ts +5 -5
- package/src/engine/webcomponents/needle-engine.loading.ts +6 -6
- package/src/engine/webcomponents/needle-engine.ts +2 -2
- package/src/engine/xr/TempXRContext.ts +2 -2
- package/src/engine-components/AnimationBuilder.ts +472 -0
- package/src/engine-components/Animator.ts +6 -1
- package/src/engine-components/AnimatorController.builder.ts +163 -37
- package/src/engine-components/AnimatorController.ts +1 -0
- package/src/engine-components/ContactShadows.ts +15 -1
- package/src/engine-components/DropListener.ts +3 -0
- package/src/engine-components/OrbitControls.ts +16 -5
- package/src/engine-components/SceneSwitcher.ts +3 -0
- package/src/engine-components/api.ts +1 -0
- package/src/engine-components/codegen/components.ts +6 -6
- package/src/engine-components/export/usdz/USDZExporter.ts +4 -4
- package/src/engine-components/timeline/PlayableDirector.ts +20 -20
- package/src/engine-components/timeline/TimelineBuilder.ts +277 -17
- package/src/engine-components/timeline/TimelineTracks.ts +24 -16
- package/src/engine-components/web/CursorFollow.ts +0 -1
- package/src/engine-components/webxr/WebXRImageTracking.ts +77 -7
- package/src/vite-env.d.ts +0 -16
|
@@ -1,17 +1,34 @@
|
|
|
1
|
-
import { AnimationClip } from "three";
|
|
1
|
+
import { AnimationClip, Object3D } from "three";
|
|
2
|
+
import type { Light, Material, PerspectiveCamera } from "three";
|
|
2
3
|
|
|
3
4
|
import { AnimatorConditionMode, AnimatorControllerParameterType } from "../engine/extensions/NEEDLE_animator_controller_model.js";
|
|
4
5
|
import type { AnimatorControllerModel, Condition, Parameter, State, Transition } from "../engine/extensions/NEEDLE_animator_controller_model.js";
|
|
5
6
|
import { InstantiateIdProvider } from "../engine/engine_networking_instantiate.js";
|
|
6
7
|
import { AnimatorController } from "./AnimatorController.js";
|
|
8
|
+
import { resolveClipSource, track as trackFn, type TrackDescriptor, type TrackOptions, type AnimationKeyframe, type Tween, type Vec3Value, type QuatValue, type EulerValue, type ColorValue } from "./AnimationBuilder.js";
|
|
9
|
+
|
|
10
|
+
/** Keyframe array or tween shorthand */
|
|
11
|
+
type KF<V> = AnimationKeyframe<V>[] | Tween<V>;
|
|
12
|
+
|
|
13
|
+
/** Extracts parameter names of a given type from the builder's tracked parameter map */
|
|
14
|
+
type ParamNamesOfType<TParams, PType extends string> = {
|
|
15
|
+
[K in keyof TParams & string]: TParams[K] extends PType ? K : never
|
|
16
|
+
}[keyof TParams & string];
|
|
7
17
|
|
|
8
18
|
|
|
9
19
|
/**
|
|
10
20
|
* Configuration for an animation state in the builder
|
|
11
21
|
*/
|
|
12
22
|
export declare type StateOptions = {
|
|
13
|
-
/**
|
|
14
|
-
|
|
23
|
+
/**
|
|
24
|
+
* The animation clip for this state. Accepts:
|
|
25
|
+
* - A pre-built `AnimationClip`
|
|
26
|
+
* - A single {@link TrackDescriptor} from {@link track}
|
|
27
|
+
* - An array of {@link TrackDescriptor}s (multiple tracks combined into one clip)
|
|
28
|
+
*
|
|
29
|
+
* When omitted, use {@link AnimatorControllerBuilder.track .track()} to define animation tracks inline.
|
|
30
|
+
*/
|
|
31
|
+
clip?: AnimationClip | TrackDescriptor | TrackDescriptor[];
|
|
15
32
|
/** Whether the animation should loop (default: false) */
|
|
16
33
|
loop?: boolean;
|
|
17
34
|
/** Base speed multiplier (default: 1) */
|
|
@@ -30,10 +47,8 @@ export declare type StateOptions = {
|
|
|
30
47
|
export declare type TransitionOptions = {
|
|
31
48
|
/** Duration of the crossfade in seconds (default: 0) */
|
|
32
49
|
duration?: number;
|
|
33
|
-
/** Normalized exit time 0-1
|
|
50
|
+
/** Normalized exit time 0-1. When set, the transition waits until the source animation reaches this point before transitioning. */
|
|
34
51
|
exitTime?: number;
|
|
35
|
-
/** Whether the transition waits for exitTime before transitioning (default: false) */
|
|
36
|
-
hasExitTime?: boolean;
|
|
37
52
|
/** Normalized offset into the destination state's animation (default: 0) */
|
|
38
53
|
offset?: number;
|
|
39
54
|
/** Whether duration is in seconds (true) or normalized (false) (default: false) */
|
|
@@ -63,17 +78,22 @@ type BuilderTransition = {
|
|
|
63
78
|
type BuilderState = {
|
|
64
79
|
name: string;
|
|
65
80
|
options: StateOptions;
|
|
81
|
+
inlineTracks: TrackDescriptor[];
|
|
66
82
|
transitions: BuilderTransition[];
|
|
67
83
|
};
|
|
68
84
|
|
|
69
85
|
/**
|
|
70
86
|
* A fluent builder for creating {@link AnimatorController} instances from code.
|
|
71
87
|
*
|
|
72
|
-
* Use {@link AnimatorController.build} to create a new builder.
|
|
88
|
+
* Use {@link AnimatorControllerBuilder.create} or {@link AnimatorController.build} to create a new builder.
|
|
73
89
|
*
|
|
74
|
-
*
|
|
90
|
+
* The builder tracks state names and parameter types through the fluent chain,
|
|
91
|
+
* providing autocomplete for state names in `.transition()` and type-aware
|
|
92
|
+
* `.condition()` calls (e.g., trigger parameters don't require a mode argument).
|
|
93
|
+
*
|
|
94
|
+
* @example With pre-built AnimationClips
|
|
75
95
|
* ```ts
|
|
76
|
-
* const controller =
|
|
96
|
+
* const controller = AnimatorControllerBuilder.create("CharacterController")
|
|
77
97
|
* .floatParameter("Speed", 0)
|
|
78
98
|
* .triggerParameter("Jump")
|
|
79
99
|
* .state("Idle", { clip: idleClip, loop: true })
|
|
@@ -84,58 +104,100 @@ type BuilderState = {
|
|
|
84
104
|
* .transition("Walk", "Idle", { duration: 0.25 })
|
|
85
105
|
* .condition("Speed", "less", 0.1)
|
|
86
106
|
* .transition("*", "Jump", { duration: 0.1 })
|
|
87
|
-
* .condition("Jump"
|
|
107
|
+
* .condition("Jump")
|
|
88
108
|
* .transition("Jump", "Idle", { hasExitTime: true, exitTime: 0.9, duration: 0.25 })
|
|
89
109
|
* .build();
|
|
90
110
|
* ```
|
|
91
111
|
*
|
|
112
|
+
* @example With inline tracks (no pre-built clips needed)
|
|
113
|
+
* ```ts
|
|
114
|
+
* const controller = AnimatorControllerBuilder.create("Door")
|
|
115
|
+
* .boolParameter("Open", false)
|
|
116
|
+
* .state("Closed", { loop: true })
|
|
117
|
+
* .track(door, "position", { from: [0, 0, 0], to: [0, 0, 0], duration: 1 })
|
|
118
|
+
* .state("Open", { loop: true })
|
|
119
|
+
* .track(door, "position", { from: [0, 0, 0], to: [2, 0, 0], duration: 1 })
|
|
120
|
+
* .track(light, "intensity", { from: 0, to: 5, duration: 1 })
|
|
121
|
+
* .transition("Closed", "Open", { duration: 0.25 })
|
|
122
|
+
* .condition("Open", "if")
|
|
123
|
+
* .transition("Open", "Closed", { duration: 0.25 })
|
|
124
|
+
* .condition("Open", "ifNot")
|
|
125
|
+
* .build(room);
|
|
126
|
+
* ```
|
|
127
|
+
*
|
|
128
|
+
* @typeParam TStates - Union of state names added via `.state()`. Used for autocomplete and validation in `.transition()` and `.defaultState()`.
|
|
129
|
+
* @typeParam TParams - Record mapping parameter names to their types (`"trigger"`, `"bool"`, `"float"`, `"int"`). Used for type-aware `.condition()` overloads.
|
|
130
|
+
*
|
|
92
131
|
* @category Animation and Sequencing
|
|
93
132
|
* @group Utilities
|
|
94
133
|
*/
|
|
95
|
-
export class AnimatorControllerBuilder
|
|
134
|
+
export class AnimatorControllerBuilder<
|
|
135
|
+
TStates extends string = never,
|
|
136
|
+
TParams extends Record<string, "trigger" | "bool" | "float" | "int"> = {},
|
|
137
|
+
> {
|
|
96
138
|
private _name: string;
|
|
97
139
|
private _parameters: Parameter[] = [];
|
|
98
140
|
private _states: BuilderState[] = [];
|
|
99
141
|
private _anyStateTransitions: BuilderTransition[] = [];
|
|
100
142
|
private _defaultStateName: string | null = null;
|
|
101
143
|
private _lastTransition: BuilderTransition | null = null;
|
|
144
|
+
private _lastState: BuilderState | null = null;
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Creates a new AnimatorControllerBuilder instance.
|
|
148
|
+
* @param name - Optional name for the controller
|
|
149
|
+
*/
|
|
150
|
+
static create(name?: string): AnimatorControllerBuilder {
|
|
151
|
+
return new AnimatorControllerBuilder(name);
|
|
152
|
+
}
|
|
102
153
|
|
|
103
154
|
constructor(name?: string) {
|
|
104
155
|
this._name = name ?? "AnimatorController";
|
|
105
156
|
}
|
|
106
157
|
|
|
107
158
|
/** Adds a float parameter */
|
|
108
|
-
floatParameter(name:
|
|
159
|
+
floatParameter<N extends string>(name: N, defaultValue: number = 0): AnimatorControllerBuilder<TStates, TParams & Record<N, "float">> {
|
|
109
160
|
this._parameters.push({ name, hash: this._parameters.length, type: AnimatorControllerParameterType.Float, value: defaultValue });
|
|
110
|
-
return this;
|
|
161
|
+
return this as any;
|
|
111
162
|
}
|
|
112
163
|
|
|
113
164
|
/** Adds an integer parameter */
|
|
114
|
-
intParameter(name:
|
|
165
|
+
intParameter<N extends string>(name: N, defaultValue: number = 0): AnimatorControllerBuilder<TStates, TParams & Record<N, "int">> {
|
|
115
166
|
this._parameters.push({ name, hash: this._parameters.length, type: AnimatorControllerParameterType.Int, value: defaultValue });
|
|
116
|
-
return this;
|
|
167
|
+
return this as any;
|
|
117
168
|
}
|
|
118
169
|
|
|
119
170
|
/** Adds a boolean parameter */
|
|
120
|
-
boolParameter(name:
|
|
171
|
+
boolParameter<N extends string>(name: N, defaultValue: boolean = false): AnimatorControllerBuilder<TStates, TParams & Record<N, "bool">> {
|
|
121
172
|
this._parameters.push({ name, hash: this._parameters.length, type: AnimatorControllerParameterType.Bool, value: defaultValue });
|
|
122
|
-
return this;
|
|
173
|
+
return this as any;
|
|
123
174
|
}
|
|
124
175
|
|
|
125
176
|
/** Adds a trigger parameter */
|
|
126
|
-
triggerParameter(name:
|
|
177
|
+
triggerParameter<N extends string>(name: N): AnimatorControllerBuilder<TStates, TParams & Record<N, "trigger">> {
|
|
127
178
|
this._parameters.push({ name, hash: this._parameters.length, type: AnimatorControllerParameterType.Trigger, value: false });
|
|
128
|
-
return this;
|
|
179
|
+
return this as any;
|
|
129
180
|
}
|
|
130
181
|
|
|
131
182
|
/**
|
|
132
183
|
* Adds a state to the controller. The first state added becomes the default state.
|
|
184
|
+
*
|
|
185
|
+
* When `options.clip` is provided, the state uses that clip directly.
|
|
186
|
+
* When omitted, chain `.track()` calls to define animation tracks inline:
|
|
187
|
+
* ```ts
|
|
188
|
+
* .state("Open", { loop: true })
|
|
189
|
+
* .track(door, "position", { from: [0,0,0], to: [2,0,0], duration: 1 })
|
|
190
|
+
* .track(light, "intensity", { from: 0, to: 5, duration: 1 })
|
|
191
|
+
* ```
|
|
192
|
+
*
|
|
133
193
|
* @param name - Unique name for the state
|
|
134
|
-
* @param options - State configuration including clip, loop, speed
|
|
194
|
+
* @param options - State configuration including clip, loop, speed. When omitted, use `.track()` to add animation data.
|
|
135
195
|
*/
|
|
136
|
-
state(name:
|
|
137
|
-
|
|
138
|
-
|
|
196
|
+
state<N extends string>(name: N, options?: StateOptions): AnimatorControllerBuilder<TStates | N, TParams> {
|
|
197
|
+
const state: BuilderState = { name, options: options ?? {}, inlineTracks: [], transitions: [] };
|
|
198
|
+
this._states.push(state);
|
|
199
|
+
this._lastState = state;
|
|
200
|
+
return this as any;
|
|
139
201
|
}
|
|
140
202
|
|
|
141
203
|
/**
|
|
@@ -146,8 +208,9 @@ export class AnimatorControllerBuilder {
|
|
|
146
208
|
* @param to - Destination state name
|
|
147
209
|
* @param options - Transition configuration
|
|
148
210
|
*/
|
|
149
|
-
transition(from:
|
|
150
|
-
|
|
211
|
+
transition(from: TStates | "*", to: TStates, options?: TransitionOptions): AnimatorControllerBuilder<TStates, TParams> {
|
|
212
|
+
this._lastState = null;
|
|
213
|
+
const t: BuilderTransition = { to: to as string, options: options ?? {}, conditions: [] };
|
|
151
214
|
if (from === "*") {
|
|
152
215
|
this._anyStateTransitions.push(t);
|
|
153
216
|
}
|
|
@@ -157,19 +220,68 @@ export class AnimatorControllerBuilder {
|
|
|
157
220
|
state.transitions.push(t);
|
|
158
221
|
}
|
|
159
222
|
this._lastTransition = t;
|
|
160
|
-
return this;
|
|
223
|
+
return this as any;
|
|
161
224
|
}
|
|
162
225
|
|
|
163
226
|
/**
|
|
164
227
|
* Adds a condition to the most recently added transition.
|
|
165
228
|
* Multiple conditions on the same transition are AND-ed together.
|
|
229
|
+
*
|
|
230
|
+
* The required arguments depend on the parameter type:
|
|
231
|
+
* - **Trigger**: `.condition("Jump")` — mode defaults to `"if"`, no threshold needed
|
|
232
|
+
* - **Bool**: `.condition("Open", "if")` or `.condition("Open", "ifNot")`
|
|
233
|
+
* - **Float/Int**: `.condition("Speed", "greater", 0.1)`
|
|
234
|
+
*
|
|
166
235
|
* @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
236
|
*/
|
|
170
|
-
|
|
237
|
+
// Trigger parameters: mode is optional (defaults to "if")
|
|
238
|
+
condition(parameter: ParamNamesOfType<TParams, "trigger">, mode?: "if" | "ifNot"): AnimatorControllerBuilder<TStates, TParams>;
|
|
239
|
+
// Bool parameters: mode is required
|
|
240
|
+
condition(parameter: ParamNamesOfType<TParams, "bool">, mode: "if" | "ifNot"): AnimatorControllerBuilder<TStates, TParams>;
|
|
241
|
+
// Float/Int parameters: mode and optional threshold
|
|
242
|
+
condition(parameter: ParamNamesOfType<TParams, "float" | "int">, mode: "greater" | "less" | "equals" | "notEqual", threshold?: number): AnimatorControllerBuilder<TStates, TParams>;
|
|
243
|
+
condition(parameter: string, mode?: ConditionMode, threshold?: number): AnimatorControllerBuilder<TStates, TParams> {
|
|
171
244
|
if (!this._lastTransition) throw new Error("AnimatorControllerBuilder: .condition() must be called after .transition()");
|
|
172
|
-
this._lastTransition.conditions.push({ parameter, mode, threshold });
|
|
245
|
+
this._lastTransition.conditions.push({ parameter, mode: mode ?? "if", threshold: threshold ?? 0 });
|
|
246
|
+
return this as any;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// --- Object3D ---
|
|
250
|
+
/** Adds an animation track for an Object3D's position or scale to the current state */
|
|
251
|
+
track(target: Object3D, property: "position" | "scale", keyframes: KF<Vec3Value>, options?: TrackOptions): this;
|
|
252
|
+
/** Adds an animation track for an Object3D's quaternion to the current state */
|
|
253
|
+
track(target: Object3D, property: "quaternion", keyframes: KF<QuatValue>, options?: TrackOptions): this;
|
|
254
|
+
/** Adds an animation track for an Object3D's rotation (Euler, converted to quaternion) to the current state */
|
|
255
|
+
track(target: Object3D, property: "rotation", keyframes: KF<EulerValue>, options?: TrackOptions): this;
|
|
256
|
+
/** Adds an animation track for an Object3D's visibility to the current state */
|
|
257
|
+
track(target: Object3D, property: "visible", keyframes: KF<boolean>, options?: TrackOptions): this;
|
|
258
|
+
// --- Material ---
|
|
259
|
+
/** Adds an animation track for a material's numeric property to the current state */
|
|
260
|
+
track(target: Material, property: "opacity" | "roughness" | "metalness" | "alphaTest" | "emissiveIntensity" | "envMapIntensity" | "bumpScale" | "displacementScale" | "displacementBias", keyframes: KF<number>, options?: TrackOptions): this;
|
|
261
|
+
/** Adds an animation track for a material's color property to the current state */
|
|
262
|
+
track(target: Material, property: "color" | "emissive", keyframes: KF<ColorValue>, options?: TrackOptions): this;
|
|
263
|
+
// --- Light ---
|
|
264
|
+
/** Adds an animation track for a light's numeric property to the current state */
|
|
265
|
+
track(target: Light, property: "intensity" | "distance" | "angle" | "penumbra" | "decay", keyframes: KF<number>, options?: TrackOptions): this;
|
|
266
|
+
/** Adds an animation track for a light's color to the current state */
|
|
267
|
+
track(target: Light, property: "color", keyframes: KF<ColorValue>, options?: TrackOptions): this;
|
|
268
|
+
// --- Camera ---
|
|
269
|
+
/** Adds an animation track for a camera's numeric property to the current state */
|
|
270
|
+
track(target: PerspectiveCamera, property: "fov" | "near" | "far" | "zoom", keyframes: KF<number>, options?: TrackOptions): this;
|
|
271
|
+
/**
|
|
272
|
+
* Adds an animation track to the most recently added state.
|
|
273
|
+
* Must be called after `.state()`. The track has the same type-safe overloads
|
|
274
|
+
* as the standalone {@link track} function.
|
|
275
|
+
*
|
|
276
|
+
* @param target - The object whose type determines valid properties and value types
|
|
277
|
+
* @param property - The property to animate
|
|
278
|
+
* @param keyframes - Keyframe array or {@link Tween} shorthand
|
|
279
|
+
* @param options - Optional {@link TrackOptions} with a `root` for named targeting
|
|
280
|
+
*/
|
|
281
|
+
track(target: object, property: string, keyframes: KF<any>, options?: TrackOptions): this {
|
|
282
|
+
if (!this._lastState) throw new Error("AnimatorControllerBuilder: .track() must be called after .state()");
|
|
283
|
+
if (this._lastState.options.clip) throw new Error(`AnimatorControllerBuilder: state "${this._lastState.name}" already has a clip. Use either .track() or { clip: ... }, not both.`);
|
|
284
|
+
this._lastState.inlineTracks.push(trackFn(target as Object3D, property as "position", keyframes, options));
|
|
173
285
|
return this;
|
|
174
286
|
}
|
|
175
287
|
|
|
@@ -178,16 +290,18 @@ export class AnimatorControllerBuilder {
|
|
|
178
290
|
* If not called, the first added state is used.
|
|
179
291
|
* @param name - Name of the state
|
|
180
292
|
*/
|
|
181
|
-
defaultState(name:
|
|
182
|
-
this._defaultStateName = name;
|
|
183
|
-
return this;
|
|
293
|
+
defaultState(name: TStates): AnimatorControllerBuilder<TStates, TParams> {
|
|
294
|
+
this._defaultStateName = name as string;
|
|
295
|
+
return this as any;
|
|
184
296
|
}
|
|
185
297
|
|
|
186
298
|
/**
|
|
187
299
|
* Builds and returns the {@link AnimatorController}.
|
|
188
300
|
* Resolves all state name references to indices.
|
|
301
|
+
* @param root - Optional root Object3D for resolving {@link TrackDescriptor} track paths.
|
|
302
|
+
* When provided, tracks targeting a different object use `target.name` for named resolution.
|
|
189
303
|
*/
|
|
190
|
-
build(): AnimatorController {
|
|
304
|
+
build(root?: Object3D): AnimatorController {
|
|
191
305
|
const stateIndexMap = new Map<string, number>();
|
|
192
306
|
this._states.forEach((s, i) => stateIndexMap.set(s.name, i));
|
|
193
307
|
|
|
@@ -203,7 +317,7 @@ export class AnimatorControllerBuilder {
|
|
|
203
317
|
if (destIndex === undefined) throw new Error(`AnimatorControllerBuilder: transition target "${t.to}" not found`);
|
|
204
318
|
return {
|
|
205
319
|
exitTime: t.options.exitTime ?? 1,
|
|
206
|
-
hasExitTime: t.options.
|
|
320
|
+
hasExitTime: t.options.exitTime !== undefined,
|
|
207
321
|
duration: t.options.duration ?? 0,
|
|
208
322
|
offset: t.options.offset ?? 0,
|
|
209
323
|
hasFixedDuration: t.options.hasFixedDuration ?? false,
|
|
@@ -226,12 +340,24 @@ export class AnimatorControllerBuilder {
|
|
|
226
340
|
transitions.push(resolveTransition(anyT));
|
|
227
341
|
}
|
|
228
342
|
|
|
343
|
+
// Resolve clip: from options.clip, inline .track() calls, or error
|
|
344
|
+
let clip: AnimationClip;
|
|
345
|
+
if (s.options.clip) {
|
|
346
|
+
clip = resolveClipSource(s.options.clip, root);
|
|
347
|
+
}
|
|
348
|
+
else if (s.inlineTracks.length > 0) {
|
|
349
|
+
clip = resolveClipSource(s.inlineTracks, root);
|
|
350
|
+
}
|
|
351
|
+
else {
|
|
352
|
+
throw new Error(`AnimatorControllerBuilder: state "${s.name}" has no clip and no inline tracks. Provide { clip } or chain .track() calls.`);
|
|
353
|
+
}
|
|
354
|
+
|
|
229
355
|
return {
|
|
230
356
|
name: s.name,
|
|
231
357
|
hash: index,
|
|
232
358
|
motion: {
|
|
233
|
-
name:
|
|
234
|
-
clip:
|
|
359
|
+
name: clip.name,
|
|
360
|
+
clip: clip,
|
|
235
361
|
isLooping: s.options.loop ?? false,
|
|
236
362
|
},
|
|
237
363
|
transitions,
|
|
@@ -397,6 +397,7 @@ export class AnimatorController {
|
|
|
397
397
|
* Stops all animations and unregisters the mixer from the animation system.
|
|
398
398
|
*/
|
|
399
399
|
dispose() {
|
|
400
|
+
if (!this._mixer) return;
|
|
400
401
|
this._mixer.stopAllAction();
|
|
401
402
|
if (this.animator) {
|
|
402
403
|
this._mixer.uncacheRoot(this.animator.gameObject);
|
|
@@ -100,7 +100,7 @@ export class ContactShadows extends Behaviour {
|
|
|
100
100
|
const obj = new Object3D();
|
|
101
101
|
obj.name = "ContactShadows";
|
|
102
102
|
instance = addComponent(obj, ContactShadows, {
|
|
103
|
-
autoFit:
|
|
103
|
+
autoFit: true,
|
|
104
104
|
occludeBelowGround: false
|
|
105
105
|
});
|
|
106
106
|
this._instances.set(context, instance);
|
|
@@ -169,6 +169,10 @@ export class ContactShadows extends Behaviour {
|
|
|
169
169
|
return this._needsUpdate;
|
|
170
170
|
}
|
|
171
171
|
private _needsUpdate: boolean = false;
|
|
172
|
+
private _needsFit: boolean = false;
|
|
173
|
+
|
|
174
|
+
// TODO: support auto-refit for moveable/animated objects (e.g. via mesh-bvh / scene BVH).
|
|
175
|
+
// Currently there's no reliable way to detect object position/scale changes.
|
|
172
176
|
|
|
173
177
|
/** All shadow objects are parented to this object.
|
|
174
178
|
* The gameObject itself should not be transformed because we want the ContactShadows object e.g. also have a GroundProjectedEnv component
|
|
@@ -366,6 +370,11 @@ export class ContactShadows extends Behaviour {
|
|
|
366
370
|
|
|
367
371
|
onEnable(): void {
|
|
368
372
|
this._needsUpdate = true;
|
|
373
|
+
this.autoCleanup(this.context.events.on("scene-content-changed", () => {
|
|
374
|
+
if (!this.autoFit) return;
|
|
375
|
+
this._needsFit = true;
|
|
376
|
+
this._needsUpdate = true;
|
|
377
|
+
}));
|
|
369
378
|
}
|
|
370
379
|
|
|
371
380
|
/** @internal */
|
|
@@ -393,6 +402,11 @@ export class ContactShadows extends Behaviour {
|
|
|
393
402
|
/** @internal */
|
|
394
403
|
onBeforeRender(_frame: XRFrame | null): void {
|
|
395
404
|
|
|
405
|
+
if (this._needsFit && this.autoFit) {
|
|
406
|
+
this._needsFit = false;
|
|
407
|
+
this.fitShadows();
|
|
408
|
+
}
|
|
409
|
+
|
|
396
410
|
if (this.manualUpdate) {
|
|
397
411
|
if (!this._needsUpdate) return;
|
|
398
412
|
}
|
|
@@ -597,6 +597,9 @@ export class DropListener extends Behaviour {
|
|
|
597
597
|
});
|
|
598
598
|
this.dispatchEvent(evt);
|
|
599
599
|
this.onDropped?.invoke(evt.detail);
|
|
600
|
+
if (obj) {
|
|
601
|
+
this.context.events.emit("scene-content-changed", { source: this, object: obj });
|
|
602
|
+
}
|
|
600
603
|
|
|
601
604
|
// send network event
|
|
602
605
|
if (!isRemote && ctx.url?.startsWith("http") && this.context.connection.isConnected && obj) {
|
|
@@ -663,7 +663,15 @@ export class OrbitControls extends Behaviour implements ICameraController {
|
|
|
663
663
|
}
|
|
664
664
|
this._controls.enabled = true;
|
|
665
665
|
|
|
666
|
-
|
|
666
|
+
// Interrupt programmatic transitions on meaningful new user interaction:
|
|
667
|
+
// - Middle/right button down (always intentional camera control)
|
|
668
|
+
// - Mouse wheel (zoom intent)
|
|
669
|
+
// - Left button drag start: getPointerDown(0) with a position delta — a bare click
|
|
670
|
+
// without movement shouldn't cancel an animation, but starting to drag should.
|
|
671
|
+
// Using getPointerDown (not getPointerPressed) ensures we only interrupt once at
|
|
672
|
+
// drag onset, not continuously every frame during a drag.
|
|
673
|
+
const leftDragStart = this.context.input.getPointerDown(0) && (this.context.input.getPointerPositionDelta(0)?.length() || 0) > .1;
|
|
674
|
+
if (this.context.input.getPointerDown(1) || this.context.input.getPointerDown(2) || this.context.input.mouseWheelChanged || leftDragStart) {
|
|
667
675
|
this._inputs += 1;
|
|
668
676
|
}
|
|
669
677
|
if (this._inputs > 0 && this.allowInterrupt) {
|
|
@@ -1095,13 +1103,16 @@ export class OrbitControls extends Behaviour implements ICameraController {
|
|
|
1095
1103
|
// Adapted from https://discourse.threejs.org/t/camera-zoom-to-fit-object/936/24
|
|
1096
1104
|
// Slower but better implementation that takes bones and exact vertex positions into account: https://github.com/google/model-viewer/blob/04e900c5027de8c5306fe1fe9627707f42811b05/packages/model-viewer/src/three-components/ModelScene.ts#L321
|
|
1097
1105
|
|
|
1098
|
-
/**
|
|
1099
|
-
* Fits the camera to show the objects provided (defaults to the scene if no objects are passed in)
|
|
1106
|
+
/**
|
|
1107
|
+
* Fits the camera to show the objects provided (defaults to the scene if no objects are passed in)
|
|
1100
1108
|
* @param options The options for fitting the camera. Use to provide objects to fit to, fit direction and size and other settings.
|
|
1101
1109
|
*/
|
|
1102
1110
|
fitCamera(options?: OrbitFitCameraOptions);
|
|
1103
|
-
|
|
1104
|
-
|
|
1111
|
+
// Deprecated overload commented out: it accepted Object3D as first arg, which caused
|
|
1112
|
+
// TypeScript autocomplete to show Object3D properties (position, worldPosition, etc.)
|
|
1113
|
+
// instead of OrbitFitCameraOptions. The implementation still handles Object3D at runtime
|
|
1114
|
+
// for backwards-compat — use fitCamera({ objects: [...] }) instead.
|
|
1115
|
+
// fitCamera(objects?: Object3D | Array<Object3D>, options?: Omit<OrbitFitCameraOptions, "objects">);
|
|
1105
1116
|
fitCamera(objectsOrOptions?: Object3D | Array<Object3D> | OrbitFitCameraOptions, options?: OrbitFitCameraOptions): void {
|
|
1106
1117
|
|
|
1107
1118
|
|
|
@@ -747,6 +747,9 @@ export class SceneSwitcher extends Behaviour {
|
|
|
747
747
|
const openedEvt = new CustomEvent<LoadSceneEvent>("scene-opened", { detail: { scene: scene, switcher: this, index: index } });
|
|
748
748
|
this.dispatchEvent(openedEvt);
|
|
749
749
|
this.sceneLoaded?.invoke(this);
|
|
750
|
+
if (this._currentSceneAsset) {
|
|
751
|
+
this.context.events.emit("scene-content-changed", { source: this, object: this._currentSceneAsset });
|
|
752
|
+
}
|
|
750
753
|
return true;
|
|
751
754
|
}
|
|
752
755
|
}
|
|
@@ -34,6 +34,7 @@
|
|
|
34
34
|
* @module Built-in Components
|
|
35
35
|
*/
|
|
36
36
|
|
|
37
|
+
export { AnimationBuilder, type AnimationKeyframe, type Tween, type AnimationInterpolation } from "./AnimationBuilder.js";
|
|
37
38
|
export { AnimatorControllerBuilder, type ConditionMode, type StateOptions, type TransitionOptions } from "./AnimatorController.builder.js";
|
|
38
39
|
export * from "./codegen/components.js";
|
|
39
40
|
export { Collider } from "./Collider.js"; // export abstract type
|
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
export class __Ignore {}
|
|
4
4
|
export { AlignmentConstraint } from "../AlignmentConstraint.js";
|
|
5
5
|
export { Animation } from "../Animation.js";
|
|
6
|
+
export { AnimationBuilder } from "../AnimationBuilder.js";
|
|
6
7
|
export { Keyframe } from "../AnimationCurve.js";
|
|
7
8
|
export { AnimationCurve } from "../AnimationCurve.js";
|
|
8
9
|
export { Animator } from "../Animator.js";
|
|
9
|
-
export { AnimatorControllerBuilder } from "../AnimatorController.builder.js";
|
|
10
10
|
export { AnimatorController } from "../AnimatorController.js";
|
|
11
11
|
export { AudioListener } from "../AudioListener.js";
|
|
12
12
|
export { AudioSource } from "../AudioSource.js";
|
|
@@ -161,12 +161,12 @@ export { SignalAsset } from "../timeline/SignalAsset.js";
|
|
|
161
161
|
export { SignalReceiverEvent } from "../timeline/SignalAsset.js";
|
|
162
162
|
export { SignalReceiver } from "../timeline/SignalAsset.js";
|
|
163
163
|
export { TimelineBuilder } from "../timeline/TimelineBuilder.js";
|
|
164
|
-
export {
|
|
165
|
-
export {
|
|
166
|
-
export {
|
|
164
|
+
export { TimelineAnimationTrack } from "../timeline/TimelineTracks.js";
|
|
165
|
+
export { TimelineAudioTrack } from "../timeline/TimelineTracks.js";
|
|
166
|
+
export { TimelineMarkerTrack } from "../timeline/TimelineTracks.js";
|
|
167
167
|
export { SignalTrackHandler } from "../timeline/TimelineTracks.js";
|
|
168
|
-
export {
|
|
169
|
-
export {
|
|
168
|
+
export { TimelineActivationTrack } from "../timeline/TimelineTracks.js";
|
|
169
|
+
export { TimelineControlTrack } from "../timeline/TimelineTracks.js";
|
|
170
170
|
export { TransformGizmo } from "../TransformGizmo.js";
|
|
171
171
|
export { BaseUIComponent } from "../ui/BaseUIComponent.js";
|
|
172
172
|
export { UIRootComponent } from "../ui/BaseUIComponent.js";
|
|
@@ -3,7 +3,7 @@ import { Euler, Material, Matrix4, Mesh, Object3D, Quaternion, Vector3 } from "t
|
|
|
3
3
|
|
|
4
4
|
import { isDevEnvironment, showBalloonMessage, showBalloonWarning } from "../../../engine/debug/index.js";
|
|
5
5
|
import { findObjectOfType } from "../../../engine/engine_components.js";
|
|
6
|
-
import {
|
|
6
|
+
import { _cxKhKwDL } from "../../../engine/engine_license.js";
|
|
7
7
|
import { serializable } from "../../../engine/engine_serialization.js";
|
|
8
8
|
import { getFormattedDate, Progress } from "../../../engine/engine_time_utils.js";
|
|
9
9
|
import { DeviceUtilities, getParam } from "../../../engine/engine_utils.js";
|
|
@@ -277,7 +277,7 @@ export class USDZExporter extends Behaviour {
|
|
|
277
277
|
let name = this.exportFileName ?? this.objectToExport?.name ?? this.name;
|
|
278
278
|
name += "-" + getFormattedDate(); // seems iOS caches the file in some cases, this ensures we always have a fresh file
|
|
279
279
|
|
|
280
|
-
if (!
|
|
280
|
+
if (!_cxKhKwDL()) {
|
|
281
281
|
if (name !== "") name += "-";
|
|
282
282
|
name += "MadeWithNeedle";
|
|
283
283
|
}
|
|
@@ -682,7 +682,7 @@ export class USDZExporter extends Behaviour {
|
|
|
682
682
|
if (debug)
|
|
683
683
|
showBalloonMessage("Quicklook url: " + callToActionURL);
|
|
684
684
|
if (callToActionURL) {
|
|
685
|
-
if (!
|
|
685
|
+
if (!_cxKhKwDL()) {
|
|
686
686
|
console.warn("Quicklook closed: custom redirects require a Needle Engine Pro license: https://needle.tools/pricing", callToActionURL)
|
|
687
687
|
}
|
|
688
688
|
else {
|
|
@@ -697,7 +697,7 @@ export class USDZExporter extends Behaviour {
|
|
|
697
697
|
private buildQuicklookOverlay(): CustomBranding {
|
|
698
698
|
const obj: CustomBranding = {};
|
|
699
699
|
if (this.customBranding) Object.assign(obj, this.customBranding);
|
|
700
|
-
if (!
|
|
700
|
+
if (!_cxKhKwDL()) {
|
|
701
701
|
console.log("Custom Quicklook banner text requires pro license: https://needle.tools/pricing");
|
|
702
702
|
obj.callToAction = "Close";
|
|
703
703
|
obj.checkoutTitle = "🌵 Made with Needle";
|