@needle-tools/engine 4.14.0-next.b2e3b1a → 4.15.0-next.cecd8e7
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 +9 -1
- package/components.needle.json +1 -1
- package/dist/{needle-engine.bundle-Bhgt3W8p.min.js → needle-engine.bundle-CuAiLb-d.min.js} +134 -126
- package/dist/{needle-engine.bundle-BC1QDiuv.umd.cjs → needle-engine.bundle-JQGIFVRm.umd.cjs} +128 -120
- package/dist/{needle-engine.bundle-CeQXs7Hh.js → needle-engine.bundle-VZVrVbc3.js} +3521 -3315
- package/dist/needle-engine.d.ts +450 -107
- package/dist/needle-engine.js +570 -569
- package/dist/needle-engine.min.js +1 -1
- package/dist/needle-engine.umd.cjs +1 -1
- package/lib/engine/api.d.ts +202 -17
- package/lib/engine/api.js +270 -17
- package/lib/engine/api.js.map +1 -1
- package/lib/engine/engine_accessibility.d.ts +77 -0
- package/lib/engine/engine_accessibility.js +162 -0
- package/lib/engine/engine_accessibility.js.map +1 -0
- package/lib/engine/engine_components.d.ts +1 -1
- package/lib/engine/engine_components.js +3 -7
- package/lib/engine/engine_components.js.map +1 -1
- package/lib/engine/engine_context.d.ts +2 -12
- package/lib/engine/engine_context.js +7 -29
- package/lib/engine/engine_context.js.map +1 -1
- package/lib/engine/engine_gltf_builtin_components.js +1 -16
- package/lib/engine/engine_gltf_builtin_components.js.map +1 -1
- package/lib/engine/engine_materialpropertyblock.d.ts +90 -4
- package/lib/engine/engine_materialpropertyblock.js +97 -7
- package/lib/engine/engine_materialpropertyblock.js.map +1 -1
- package/lib/engine/engine_math.d.ts +34 -1
- package/lib/engine/engine_math.js +34 -1
- package/lib/engine/engine_math.js.map +1 -1
- package/lib/engine/engine_networking.js +1 -1
- package/lib/engine/engine_networking.js.map +1 -1
- package/lib/engine/engine_types.d.ts +2 -0
- package/lib/engine/engine_types.js +2 -0
- package/lib/engine/engine_types.js.map +1 -1
- package/lib/engine/webcomponents/icons.js +3 -0
- package/lib/engine/webcomponents/icons.js.map +1 -1
- package/lib/engine/webcomponents/logo-element.d.ts +1 -0
- package/lib/engine/webcomponents/logo-element.js +3 -1
- package/lib/engine/webcomponents/logo-element.js.map +1 -1
- package/lib/engine/webcomponents/needle-button.d.ts +37 -11
- package/lib/engine/webcomponents/needle-button.js +42 -11
- package/lib/engine/webcomponents/needle-button.js.map +1 -1
- package/lib/engine/webcomponents/needle-engine.d.ts +10 -2
- package/lib/engine/webcomponents/needle-engine.js +13 -3
- package/lib/engine/webcomponents/needle-engine.js.map +1 -1
- package/lib/engine-components/Component.d.ts +1 -2
- package/lib/engine-components/Component.js +1 -2
- package/lib/engine-components/Component.js.map +1 -1
- package/lib/engine-components/DragControls.d.ts +1 -0
- package/lib/engine-components/DragControls.js +21 -0
- package/lib/engine-components/DragControls.js.map +1 -1
- package/lib/engine-components/NeedleMenu.d.ts +2 -0
- package/lib/engine-components/NeedleMenu.js +2 -0
- package/lib/engine-components/NeedleMenu.js.map +1 -1
- package/lib/engine-components/Networking.d.ts +28 -3
- package/lib/engine-components/Networking.js +28 -3
- package/lib/engine-components/Networking.js.map +1 -1
- package/lib/engine-components/ReflectionProbe.d.ts +1 -0
- package/lib/engine-components/ReflectionProbe.js +20 -2
- package/lib/engine-components/ReflectionProbe.js.map +1 -1
- package/lib/engine-components/export/usdz/extensions/behavior/BehaviourComponents.d.ts +107 -13
- package/lib/engine-components/export/usdz/extensions/behavior/BehaviourComponents.js +167 -30
- package/lib/engine-components/export/usdz/extensions/behavior/BehaviourComponents.js.map +1 -1
- package/lib/engine-components/postprocessing/Effects/Tonemapping.utils.d.ts +1 -1
- package/lib/engine-components/ui/Button.d.ts +1 -0
- package/lib/engine-components/ui/Button.js +11 -0
- package/lib/engine-components/ui/Button.js.map +1 -1
- package/lib/engine-components/ui/Text.d.ts +1 -0
- package/lib/engine-components/ui/Text.js +11 -0
- package/lib/engine-components/ui/Text.js.map +1 -1
- package/package.json +1 -1
- package/src/engine/api.ts +370 -18
- package/src/engine/engine_accessibility.ts +198 -0
- package/src/engine/engine_components.ts +3 -7
- package/src/engine/engine_context.ts +11 -28
- package/src/engine/engine_gltf_builtin_components.ts +1 -17
- package/src/engine/engine_materialpropertyblock.ts +102 -11
- package/src/engine/engine_math.ts +34 -1
- package/src/engine/engine_networking.ts +1 -1
- package/src/engine/engine_types.ts +5 -0
- package/src/engine/webcomponents/icons.ts +3 -0
- package/src/engine/webcomponents/logo-element.ts +4 -1
- package/src/engine/webcomponents/needle-button.ts +44 -13
- package/src/engine/webcomponents/needle-engine.ts +18 -7
- package/src/engine-components/Component.ts +1 -3
- package/src/engine-components/DragControls.ts +29 -4
- package/src/engine-components/NeedleMenu.ts +5 -3
- package/src/engine-components/Networking.ts +29 -4
- package/src/engine-components/ReflectionProbe.ts +21 -2
- package/src/engine-components/export/usdz/extensions/behavior/BehaviourComponents.ts +198 -65
- package/src/engine-components/ui/Button.ts +12 -0
- package/src/engine-components/ui/Text.ts +13 -0
|
@@ -15,6 +15,7 @@ import { nodeFrame } from "three/examples/jsm/renderers/webgl-legacy/nodes/WebGL
|
|
|
15
15
|
|
|
16
16
|
import { initSpectorIfAvailable } from './debug/debug_spector.js';
|
|
17
17
|
import { isDevEnvironment, LogType, showBalloonError, showBalloonMessage } from './debug/index.js';
|
|
18
|
+
import { AccessibilityManager } from './engine_accessibility.js';
|
|
18
19
|
import { Addressables } from './engine_addressables.js';
|
|
19
20
|
import { AnimationsRegistry } from './engine_animation.js';
|
|
20
21
|
import { Application } from './engine_application.js';
|
|
@@ -522,6 +523,8 @@ export class Context implements IContext {
|
|
|
522
523
|
/** Access the needle menu to add or remove buttons to the menu element */
|
|
523
524
|
readonly menu: NeedleMenu;
|
|
524
525
|
|
|
526
|
+
readonly accessibility: AccessibilityManager;
|
|
527
|
+
|
|
525
528
|
/**
|
|
526
529
|
* Checks if the context is fully created and ready
|
|
527
530
|
* @returns true if the context is fully created and ready
|
|
@@ -571,6 +574,7 @@ export class Context implements IContext {
|
|
|
571
574
|
this.menu = new NeedleMenu(this);
|
|
572
575
|
this.lodsManager = new LODsManager(this);
|
|
573
576
|
this.animations = new AnimationsRegistry(this);
|
|
577
|
+
this.accessibility = new AccessibilityManager(this);
|
|
574
578
|
|
|
575
579
|
|
|
576
580
|
const resizeCallback = () => this._needsUpdateSize = true;
|
|
@@ -614,6 +618,9 @@ export class Context implements IContext {
|
|
|
614
618
|
|
|
615
619
|
this.renderer = new WebGLRenderer(params);
|
|
616
620
|
|
|
621
|
+
this.renderer.domElement.setAttribute("aria-label", "3D rendering");
|
|
622
|
+
this.renderer.domElement.setAttribute("role", "img");
|
|
623
|
+
|
|
617
624
|
this.renderer.debug.checkShaderErrors = isDevEnvironment() || getParam("checkshadererrors") === true;
|
|
618
625
|
|
|
619
626
|
// some tonemapping other than "NONE" is required for adjusting exposure with EXR environments
|
|
@@ -753,34 +760,8 @@ export class Context implements IContext {
|
|
|
753
760
|
}
|
|
754
761
|
}
|
|
755
762
|
|
|
756
|
-
/**
|
|
757
|
-
*
|
|
758
|
-
* Components that have been added to objects (e.g. via `addComponent`) but are not yet registered
|
|
759
|
-
* with a context will be collected and queued for initialization.
|
|
760
|
-
* On the next frame, these components will go through the full lifecycle: `awake` → `onEnable` → `start` → `update`.
|
|
761
|
-
*
|
|
762
|
-
* This is useful when components are created outside of the normal glTF loading pipeline,
|
|
763
|
-
* for example in an editor that adds components during edit mode and then needs to activate them for play mode.
|
|
764
|
-
* @param root The root object to search for components. Defaults to the context's scene.
|
|
765
|
-
* @returns The number of components that were newly registered.
|
|
766
|
-
*/
|
|
767
|
-
registerSceneComponents(root?: Object3D): number {
|
|
768
|
-
const searchRoot = root ?? this.scene;
|
|
769
|
-
if (!searchRoot) return 0;
|
|
770
|
-
let count = 0;
|
|
771
|
-
foreachComponent(searchRoot, (comp) => {
|
|
772
|
-
// Skip components that are already registered with this context
|
|
773
|
-
if (this.scripts.includes(comp)) return;
|
|
774
|
-
if (this.new_scripts.includes(comp)) return;
|
|
775
|
-
if (this.new_script_start.includes(comp)) return;
|
|
776
|
-
this.new_scripts.push(comp);
|
|
777
|
-
count++;
|
|
778
|
-
}, true);
|
|
779
|
-
return count;
|
|
780
|
-
}
|
|
781
|
-
|
|
782
|
-
/** This will recreate the whole needle engine context and dispose the whole scene content
|
|
783
|
-
* All content will be reloaded (loading times might be faster due to browser caches)
|
|
763
|
+
/** This will recreate the whole needle engine context and dispose the whole scene content
|
|
764
|
+
* All content will be reloaded (loading times might be faster due to browser caches)
|
|
784
765
|
* All scripts will be recreated */
|
|
785
766
|
recreate() {
|
|
786
767
|
this.clear();
|
|
@@ -836,6 +817,7 @@ export class Context implements IContext {
|
|
|
836
817
|
this.lightmaps?.clear();
|
|
837
818
|
this.physics?.engine?.clearCaches();
|
|
838
819
|
this.lodsManager.disable();
|
|
820
|
+
this.accessibility?.clear();
|
|
839
821
|
|
|
840
822
|
this._onBeforeRenderListeners.clear();
|
|
841
823
|
this._onAfterRenderListeners.clear();
|
|
@@ -857,6 +839,7 @@ export class Context implements IContext {
|
|
|
857
839
|
*/
|
|
858
840
|
dispose() {
|
|
859
841
|
this.internalOnDestroy();
|
|
842
|
+
this.accessibility.dispose();
|
|
860
843
|
}
|
|
861
844
|
|
|
862
845
|
/**@deprecated use dispose() */
|
|
@@ -113,20 +113,6 @@ export async function createBuiltinComponents(context: Context, gltfId: SourceId
|
|
|
113
113
|
}
|
|
114
114
|
}
|
|
115
115
|
});
|
|
116
|
-
|
|
117
|
-
// Batch-register all components that were created above with the context.
|
|
118
|
-
// This uses the same codepath as Context.registerSceneComponents() which is also used
|
|
119
|
-
// by editors to activate components that were added outside the glTF pipeline.
|
|
120
|
-
if (gltf.scenes) {
|
|
121
|
-
for (const scene of gltf.scenes) {
|
|
122
|
-
context.registerSceneComponents(scene);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
if (gltf.children) {
|
|
126
|
-
for (const ch of gltf.children) {
|
|
127
|
-
context.registerSceneComponents(ch);
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
116
|
}
|
|
131
117
|
|
|
132
118
|
declare type IHasResolveGuids = {
|
|
@@ -250,10 +236,8 @@ async function onCreateBuiltinComponents(context: SerializationContext, obj: Obj
|
|
|
250
236
|
// Object.assign(instance, compData);
|
|
251
237
|
// dont call awake here because some references might not be resolved yet and components that access those fields in awake will throw
|
|
252
238
|
// for example Duplicatable reference to object might still be { node: id }
|
|
253
|
-
// dont register here - components are batch-registered via context.registerSceneComponents below
|
|
254
239
|
const callAwake = false;
|
|
255
|
-
|
|
256
|
-
addNewComponent(obj, instance, callAwake, register);
|
|
240
|
+
addNewComponent(obj, instance, callAwake);
|
|
257
241
|
deserialize.push({ instance, compData, obj });
|
|
258
242
|
|
|
259
243
|
// if the component instance is a camera and we dont have a main camera yet
|
|
@@ -122,17 +122,26 @@ class PropertyBlockRegistry {
|
|
|
122
122
|
const registry = new PropertyBlockRegistry();
|
|
123
123
|
|
|
124
124
|
/**
|
|
125
|
-
* MaterialPropertyBlock allows per-object material property overrides without creating new material instances.
|
|
126
|
-
* This is useful for rendering multiple objects with the same base material but different properties
|
|
127
|
-
* (e.g., different colors, textures, or shader parameters).
|
|
125
|
+
* MaterialPropertyBlock allows per-object material property overrides without creating new material instances.
|
|
126
|
+
* This is useful for rendering multiple objects with the same base material but different properties
|
|
127
|
+
* (e.g., different colors, textures, or shader parameters).
|
|
128
128
|
*
|
|
129
|
-
*
|
|
129
|
+
* ## How Property Blocks Work
|
|
130
|
+
*
|
|
131
|
+
* **Important**: Overrides are registered on the **Object3D**, not on the material.
|
|
132
|
+
* This means:
|
|
133
|
+
* - If you change the object's material, the overrides will still be applied to the new material
|
|
134
|
+
* - Multiple objects can share the same material but have different property overrides
|
|
135
|
+
* - If you don't want overrides applied after changing a material, you must remove them using {@link removeOveride}, {@link clearAllOverrides}, or {@link dispose}
|
|
136
|
+
*
|
|
137
|
+
* The property block system works by:
|
|
130
138
|
* - Temporarily applying overrides in onBeforeRender
|
|
131
139
|
* - Restoring original values in onAfterRender
|
|
132
140
|
* - Managing shader defines and program cache keys for correct shader compilation
|
|
133
141
|
* - Supporting texture coordinate transforms per object
|
|
134
142
|
*
|
|
135
|
-
* Common
|
|
143
|
+
* ## Common Use Cases
|
|
144
|
+
*
|
|
136
145
|
* - **Lightmaps**: Apply unique lightmap textures to individual objects sharing the same material
|
|
137
146
|
* - **Reflection Probes**: Apply different environment maps per object for localized reflections
|
|
138
147
|
* - **See-through effects**: Temporarily override transparency/transmission properties for X-ray effects
|
|
@@ -170,6 +179,26 @@ const registry = new PropertyBlockRegistry();
|
|
|
170
179
|
* block.setDefine("USE_CUSTOM_FEATURE", 1);
|
|
171
180
|
* ```
|
|
172
181
|
*
|
|
182
|
+
* @example Material swapping behavior
|
|
183
|
+
* ```typescript
|
|
184
|
+
* const mesh = new Mesh(geometry, materialA);
|
|
185
|
+
* const block = MaterialPropertyBlock.get(mesh);
|
|
186
|
+
* block.setOverride("color", new Color(1, 0, 0));
|
|
187
|
+
*
|
|
188
|
+
* // The color override is red for materialA
|
|
189
|
+
*
|
|
190
|
+
* // Swap the material - overrides persist and apply to the new material!
|
|
191
|
+
* mesh.material = materialB;
|
|
192
|
+
* // The color override is now red for materialB too
|
|
193
|
+
*
|
|
194
|
+
* // If you don't want overrides on the new material, remove them:
|
|
195
|
+
* block.clearAllOverrides(); // Remove all overrides
|
|
196
|
+
* // or
|
|
197
|
+
* block.removeOveride("color"); // Remove specific override
|
|
198
|
+
* // or
|
|
199
|
+
* block.dispose(); // Remove the entire property block
|
|
200
|
+
* ```
|
|
201
|
+
*
|
|
173
202
|
* @example Lightmap usage
|
|
174
203
|
* ```typescript
|
|
175
204
|
* const block = MaterialPropertyBlock.get(mesh);
|
|
@@ -317,8 +346,25 @@ export class MaterialPropertyBlock<T extends Material = Material> {
|
|
|
317
346
|
}
|
|
318
347
|
|
|
319
348
|
/**
|
|
320
|
-
* Removes a specific property override
|
|
321
|
-
*
|
|
349
|
+
* Removes a specific property override.
|
|
350
|
+
* After removal, the material will use its original property value for this property.
|
|
351
|
+
*
|
|
352
|
+
* @param name The property name to remove the override for
|
|
353
|
+
*
|
|
354
|
+
* @example
|
|
355
|
+
* ```typescript
|
|
356
|
+
* const block = MaterialPropertyBlock.get(mesh);
|
|
357
|
+
*
|
|
358
|
+
* // Set some overrides
|
|
359
|
+
* block.setOverride("color", new Color(1, 0, 0));
|
|
360
|
+
* block.setOverride("roughness", 0.5);
|
|
361
|
+
* block.setOverride("lightMap", lightmapTexture);
|
|
362
|
+
*
|
|
363
|
+
* // Remove a specific override - the material will now use its original color
|
|
364
|
+
* block.removeOveride("color");
|
|
365
|
+
*
|
|
366
|
+
* // Other overrides (roughness, lightMap) remain active
|
|
367
|
+
* ```
|
|
322
368
|
*/
|
|
323
369
|
removeOveride<K extends NonFunctionPropertyNames<T>>(name: K | ({} & string)): void {
|
|
324
370
|
const index = this._overrides.findIndex(o => o.name === name);
|
|
@@ -328,7 +374,47 @@ export class MaterialPropertyBlock<T extends Material = Material> {
|
|
|
328
374
|
}
|
|
329
375
|
|
|
330
376
|
/**
|
|
331
|
-
* Removes all property overrides from this block
|
|
377
|
+
* Removes all property overrides from this block.
|
|
378
|
+
* After calling this, the material will use its original values for all properties.
|
|
379
|
+
*
|
|
380
|
+
* **Note**: This does NOT remove shader defines. Use {@link clearDefine} or {@link dispose} for that.
|
|
381
|
+
*
|
|
382
|
+
* @example Remove all overrides but keep the property block
|
|
383
|
+
* ```typescript
|
|
384
|
+
* const block = MaterialPropertyBlock.get(mesh);
|
|
385
|
+
*
|
|
386
|
+
* // Set multiple overrides
|
|
387
|
+
* block.setOverride("color", new Color(1, 0, 0));
|
|
388
|
+
* block.setOverride("roughness", 0.5);
|
|
389
|
+
* block.setOverride("lightMap", lightmapTexture);
|
|
390
|
+
*
|
|
391
|
+
* // Later, remove all overrides at once
|
|
392
|
+
* block.clearAllOverrides();
|
|
393
|
+
*
|
|
394
|
+
* // The material now uses its original values
|
|
395
|
+
* // The property block still exists and can be reused with new overrides
|
|
396
|
+
* ```
|
|
397
|
+
*
|
|
398
|
+
* @example Temporarily disable all overrides
|
|
399
|
+
* ```typescript
|
|
400
|
+
* const block = MaterialPropertyBlock.get(mesh);
|
|
401
|
+
*
|
|
402
|
+
* // Save current overrides if you want to restore them later
|
|
403
|
+
* const savedOverrides = [...block.overrides];
|
|
404
|
+
*
|
|
405
|
+
* // Clear all overrides temporarily
|
|
406
|
+
* block.clearAllOverrides();
|
|
407
|
+
*
|
|
408
|
+
* // Do some rendering without overrides...
|
|
409
|
+
*
|
|
410
|
+
* // Restore overrides
|
|
411
|
+
* savedOverrides.forEach(override => {
|
|
412
|
+
* block.setOverride(override.name, override.value, override.textureTransform);
|
|
413
|
+
* });
|
|
414
|
+
* ```
|
|
415
|
+
*
|
|
416
|
+
* @see {@link removeOveride} - To remove a single override
|
|
417
|
+
* @see {@link dispose} - To completely remove the property block and clean up resources
|
|
332
418
|
*/
|
|
333
419
|
clearAllOverrides(): void {
|
|
334
420
|
this._overrides = [];
|
|
@@ -797,11 +883,11 @@ function attachPropertyBlockToObject(object: Object3D, _propertyBlock: MaterialP
|
|
|
797
883
|
if (object.type === "Group") {
|
|
798
884
|
object.children.forEach(child => {
|
|
799
885
|
if (child.type === "Mesh" || child.type === "SkinnedMesh") {
|
|
800
|
-
attachCallbacksToMesh(child, object);
|
|
886
|
+
attachCallbacksToMesh(child, object, _propertyBlock);
|
|
801
887
|
}
|
|
802
888
|
});
|
|
803
889
|
} else if (object.type === "Mesh" || object.type === "SkinnedMesh") {
|
|
804
|
-
attachCallbacksToMesh(object, object);
|
|
890
|
+
attachCallbacksToMesh(object, object, _propertyBlock);
|
|
805
891
|
}
|
|
806
892
|
}
|
|
807
893
|
/**
|
|
@@ -811,7 +897,7 @@ function attachPropertyBlockToObject(object: Object3D, _propertyBlock: MaterialP
|
|
|
811
897
|
* @param propertyBlockOwner The object that owns the property block (may be the mesh itself or its parent Group)
|
|
812
898
|
* @internal
|
|
813
899
|
*/
|
|
814
|
-
function attachCallbacksToMesh(mesh: Object3D, propertyBlockOwner: Object3D): void {
|
|
900
|
+
function attachCallbacksToMesh(mesh: Object3D, propertyBlockOwner: Object3D, _propertyBlock: MaterialPropertyBlock): void {
|
|
815
901
|
// Check if this specific mesh already has our callbacks attached for this property block owner
|
|
816
902
|
if (registry.isHooked(mesh, propertyBlockOwner)) {
|
|
817
903
|
// Already hooked for this property block owner
|
|
@@ -820,6 +906,11 @@ function attachCallbacksToMesh(mesh: Object3D, propertyBlockOwner: Object3D): vo
|
|
|
820
906
|
|
|
821
907
|
registry.addHook(mesh, propertyBlockOwner);
|
|
822
908
|
|
|
909
|
+
/**
|
|
910
|
+
* Expose the property block for e.g. Needle Inspector
|
|
911
|
+
*/
|
|
912
|
+
mesh["needle:materialPropertyBlock"] = _propertyBlock;
|
|
913
|
+
|
|
823
914
|
if (!mesh.onBeforeRender) {
|
|
824
915
|
mesh.onBeforeRender = onBeforeRender_MaterialBlock;
|
|
825
916
|
} else {
|
|
@@ -254,7 +254,40 @@ class LowPassFilter {
|
|
|
254
254
|
}
|
|
255
255
|
|
|
256
256
|
/**
|
|
257
|
-
* OneEuroFilter is a
|
|
257
|
+
* [OneEuroFilter](https://engine.needle.tools/docs/api/OneEuroFilter) is a low-pass filter designed to reduce jitter in noisy signals while maintaining low latency.
|
|
258
|
+
* It's particularly useful for smoothing tracking data from XR controllers, hand tracking, or other input devices where the signal contains noise but responsiveness is important.
|
|
259
|
+
*
|
|
260
|
+
* The filter automatically adapts its smoothing strength based on the signal's velocity:
|
|
261
|
+
* - When the signal moves slowly, it applies strong smoothing to reduce jitter
|
|
262
|
+
* - When the signal moves quickly, it reduces smoothing to maintain responsiveness
|
|
263
|
+
*
|
|
264
|
+
* Based on the research paper: [1€ Filter: A Simple Speed-based Low-pass Filter for Noisy Input](http://cristal.univ-lille.fr/~casiez/1euro/)
|
|
265
|
+
*
|
|
266
|
+
* @example Basic usage with timestamp
|
|
267
|
+
* ```ts
|
|
268
|
+
* const filter = new OneEuroFilter(120, 1.0, 0.0);
|
|
269
|
+
*
|
|
270
|
+
* // In your update loop:
|
|
271
|
+
* const smoothedValue = filter.filter(noisyValue, this.context.time.time);
|
|
272
|
+
* ```
|
|
273
|
+
*
|
|
274
|
+
* @example Without timestamps (using frequency estimate)
|
|
275
|
+
* ```ts
|
|
276
|
+
* // Assuming 60 FPS update rate
|
|
277
|
+
* const filter = new OneEuroFilter(60, 1.0, 0.5);
|
|
278
|
+
*
|
|
279
|
+
* // Call without timestamp - uses the frequency estimate
|
|
280
|
+
* const smoothedValue = filter.filter(noisyValue);
|
|
281
|
+
* ```
|
|
282
|
+
*
|
|
283
|
+
* @example Smoothing 3D positions
|
|
284
|
+
* ```ts
|
|
285
|
+
* const posFilter = new OneEuroFilterXYZ(90, 0.5, 0.0);
|
|
286
|
+
*
|
|
287
|
+
* posFilter.filter(trackedPosition, smoothedPosition, this.context.time.time);
|
|
288
|
+
* ```
|
|
289
|
+
*
|
|
290
|
+
* @see {@link OneEuroFilterXYZ} for filtering 3D vectors
|
|
258
291
|
*/
|
|
259
292
|
export class OneEuroFilter {
|
|
260
293
|
/**
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const defaultNetworkingBackendUrlProvider = "https://urls.needle.tools/default-networking-backend/index";
|
|
2
|
-
let networkingServerUrl: string | undefined = "wss://networking.needle.tools/socket";
|
|
2
|
+
let networkingServerUrl: string | undefined = "wss://networking-2.needle.tools/socket";
|
|
3
3
|
|
|
4
4
|
import * as flatbuffers from 'flatbuffers';
|
|
5
5
|
import { type Websocket } from 'websocket-ts';
|
|
@@ -164,10 +164,15 @@ export interface IHasGuid {
|
|
|
164
164
|
guid: string;
|
|
165
165
|
}
|
|
166
166
|
|
|
167
|
+
// DO NOT CHANGE THE SYMBOL NAME
|
|
168
|
+
export const $componentName = Symbol("component-name");
|
|
169
|
+
|
|
167
170
|
// TODO: we might want to separate the IComponent and IBehaviour where the Behaviour is the one with custom methods and the component only has e.g. the gameobject reference
|
|
168
171
|
export interface IComponent extends IHasGuid {
|
|
169
172
|
get isComponent(): boolean;
|
|
170
173
|
|
|
174
|
+
get [$componentName](): string | undefined;
|
|
175
|
+
|
|
171
176
|
/** the object this component is attached to */
|
|
172
177
|
gameObject: IGameObject;
|
|
173
178
|
// guid: string;
|
|
@@ -17,6 +17,9 @@ export function getIconElement(str: string): HTMLElement {
|
|
|
17
17
|
span.innerText = str;
|
|
18
18
|
span.style.visibility = "hidden";
|
|
19
19
|
span.style.userSelect = "none";
|
|
20
|
+
span.setAttribute("role", "img");
|
|
21
|
+
span.setAttribute("aria-label", str + " icon");
|
|
22
|
+
span.setAttribute("aria-hidden", "true");
|
|
20
23
|
fontReady(fontname).then(res => {
|
|
21
24
|
if (res) span.style.visibility = "";
|
|
22
25
|
else {
|
|
@@ -84,8 +84,11 @@ export class NeedleLogoElement extends HTMLElement {
|
|
|
84
84
|
globalThis.open("https://needle.tools", "_blank");
|
|
85
85
|
});
|
|
86
86
|
|
|
87
|
-
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
connectedCallback() {
|
|
88
90
|
this.wrapper.setAttribute("title", "Made with Needle Engine");
|
|
91
|
+
this.setAttribute("aria-label", "Needle Engine logo. Click to open the Needle Engine website.");
|
|
89
92
|
}
|
|
90
93
|
|
|
91
94
|
private readonly _root: ShadowRoot;
|
|
@@ -13,32 +13,58 @@ const htmlTagName = "needle-button";
|
|
|
13
13
|
const isDev = isDevEnvironment();
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
|
-
*
|
|
17
|
-
*
|
|
16
|
+
* [<needle-button>](https://engine.needle.tools/docs/api/NeedleButtonElement) is a web component for easily adding AR, VR, Quicklook, or QR code buttons to your website without writing JavaScript code.
|
|
17
|
+
*
|
|
18
|
+
* The button automatically handles session management and displays appropriate UI based on device capabilities.
|
|
19
|
+
* It comes with default styling (glassmorphism design) but can be fully customized with CSS.
|
|
20
|
+
*
|
|
21
|
+
* **Supported button types:**
|
|
22
|
+
* - `ar` - WebXR AR session button
|
|
23
|
+
* - `vr` - WebXR VR session button
|
|
24
|
+
* - `quicklook` - Apple AR Quick Look button (iOS only)
|
|
25
|
+
* - `qrcode` - QR code sharing button
|
|
26
|
+
*
|
|
27
|
+
* @example Basic AR/VR buttons
|
|
18
28
|
* ```html
|
|
29
|
+
* <needle-engine src="scene.glb"></needle-engine>
|
|
19
30
|
* <needle-button ar></needle-button>
|
|
20
31
|
* <needle-button vr></needle-button>
|
|
21
32
|
* <needle-button quicklook></needle-button>
|
|
22
33
|
* ```
|
|
23
|
-
*
|
|
24
|
-
* @example
|
|
34
|
+
*
|
|
35
|
+
* @example Custom button labels
|
|
25
36
|
* ```html
|
|
26
|
-
* <needle-button ar>Start AR</needle-button>
|
|
27
|
-
* <needle-button vr>
|
|
37
|
+
* <needle-button ar>Start AR Experience</needle-button>
|
|
38
|
+
* <needle-button vr>Enter VR Mode</needle-button>
|
|
28
39
|
* <needle-button quicklook>View in AR</needle-button>
|
|
29
40
|
* ```
|
|
30
|
-
*
|
|
31
|
-
* @example
|
|
41
|
+
*
|
|
42
|
+
* @example Custom styling
|
|
32
43
|
* ```html
|
|
33
|
-
* <!-- You can either style the element directly or use a CSS stylesheet -->
|
|
34
44
|
* <style>
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
45
|
+
* needle-button {
|
|
46
|
+
* background-color: #ff6b6b;
|
|
47
|
+
* color: white;
|
|
48
|
+
* border-radius: 8px;
|
|
49
|
+
* padding: 1rem 2rem;
|
|
50
|
+
* }
|
|
51
|
+
* needle-button:hover {
|
|
52
|
+
* background-color: #ff5252;
|
|
53
|
+
* }
|
|
39
54
|
* </style>
|
|
40
55
|
* <needle-button ar>Start AR</needle-button>
|
|
41
56
|
* ```
|
|
57
|
+
*
|
|
58
|
+
* @example Unstyled button (for complete custom styling)
|
|
59
|
+
* ```html
|
|
60
|
+
* <needle-button ar unstyled>
|
|
61
|
+
* <span class="my-icon">🥽</span>
|
|
62
|
+
* Launch AR
|
|
63
|
+
* </needle-button>
|
|
64
|
+
* ```
|
|
65
|
+
*
|
|
66
|
+
* @see {@link NeedleEngineWebComponent} for the main <needle-engine> element
|
|
67
|
+
* @see {@link NeedleMenu} for the built-in menu component that can display similar buttons
|
|
42
68
|
*/
|
|
43
69
|
export class NeedleButtonElement extends HTMLElement {
|
|
44
70
|
|
|
@@ -73,18 +99,22 @@ export class NeedleButtonElement extends HTMLElement {
|
|
|
73
99
|
if (this.getAttribute("ar") != null) {
|
|
74
100
|
this.#webxrfactory ??= new WebXRButtonFactory()
|
|
75
101
|
this.#button = this.#webxrfactory.createARButton();
|
|
102
|
+
this.setAttribute("aria-label", "Enter augmented reality mode");
|
|
76
103
|
}
|
|
77
104
|
else if (this.getAttribute("vr") != null) {
|
|
78
105
|
this.#webxrfactory ??= new WebXRButtonFactory()
|
|
79
106
|
this.#button = this.#webxrfactory.createVRButton();
|
|
107
|
+
this.setAttribute("aria-label", "Enter virtual reality mode");
|
|
80
108
|
}
|
|
81
109
|
else if (this.getAttribute("quicklook") != null) {
|
|
82
110
|
this.#webxrfactory ??= new WebXRButtonFactory()
|
|
83
111
|
this.#button = this.#webxrfactory.createQuicklookButton();
|
|
112
|
+
this.setAttribute("aria-label", "View in AR with Apple Quick Look");
|
|
84
113
|
}
|
|
85
114
|
else if (this.getAttribute("qrcode") != null) {
|
|
86
115
|
this.#buttonfactory ??= new ButtonsFactory();
|
|
87
116
|
this.#button = this.#buttonfactory.createQRCode({ anchorElement: this });
|
|
117
|
+
this.setAttribute("aria-label", "Share application with QR code");
|
|
88
118
|
}
|
|
89
119
|
else {
|
|
90
120
|
if (isDev) {
|
|
@@ -93,6 +123,7 @@ export class NeedleButtonElement extends HTMLElement {
|
|
|
93
123
|
else {
|
|
94
124
|
console.debug("No button type specified for <needle-button>. Use either ar, vr or quicklook attribute.")
|
|
95
125
|
}
|
|
126
|
+
this.setAttribute("aria-label", "Needle Button with no specified type");
|
|
96
127
|
return;
|
|
97
128
|
}
|
|
98
129
|
|
|
@@ -87,17 +87,25 @@ const observedAttributes = [
|
|
|
87
87
|
|
|
88
88
|
// https://developers.google.com/web/fundamentals/web-components/customelements
|
|
89
89
|
|
|
90
|
-
/**
|
|
91
|
-
* The `<needle-engine>` web component. See {@link NeedleEngineAttributes} attributes for supported attributes
|
|
92
|
-
* The web component creates and manages a Needle Engine context, which is responsible for rendering a 3D scene using threejs.
|
|
93
|
-
* The context is created when the `src` attribute is set, and disposed when the element is removed from the DOM. You can prevent cleanup by setting the `keep-alive` attribute to `true`.
|
|
90
|
+
/**
|
|
91
|
+
* The `<needle-engine>` web component. See {@link NeedleEngineAttributes} attributes for supported attributes
|
|
92
|
+
* The web component creates and manages a Needle Engine context, which is responsible for rendering a 3D scene using threejs.
|
|
93
|
+
* The context is created when the `src` attribute is set, and disposed when the element is removed from the DOM. You can prevent cleanup by setting the `keep-alive` attribute to `true`.
|
|
94
94
|
* The context is accessible from the `<needle-engine>` element: `document.querySelector("needle-engine").context`.
|
|
95
95
|
* See {@link https://engine.needle.tools/docs/reference/needle-engine-attributes}
|
|
96
96
|
*
|
|
97
|
-
* @example
|
|
97
|
+
* @example Basic usage
|
|
98
|
+
* ```html
|
|
98
99
|
* <needle-engine src="https://example.com/scene.glb"></needle-engine>
|
|
99
|
-
*
|
|
100
|
+
* ```
|
|
101
|
+
*
|
|
102
|
+
* @example With camera controls disabled
|
|
103
|
+
* ```html
|
|
100
104
|
* <needle-engine src="https://example.com/scene.glb" camera-controls="false"></needle-engine>
|
|
105
|
+
* ```
|
|
106
|
+
*
|
|
107
|
+
* @see {@link NeedleButtonElement} for adding AR/VR/Quicklook buttons via <needle-button>
|
|
108
|
+
* @see {@link NeedleMenu} for the built-in menu configuration component
|
|
101
109
|
*/
|
|
102
110
|
export class NeedleEngineWebComponent extends HTMLElement implements INeedleEngineComponent {
|
|
103
111
|
|
|
@@ -176,7 +184,9 @@ export class NeedleEngineWebComponent extends HTMLElement implements INeedleEngi
|
|
|
176
184
|
|
|
177
185
|
ensureFonts();
|
|
178
186
|
|
|
179
|
-
this.attachShadow({ mode: 'open' });
|
|
187
|
+
this.attachShadow({ mode: 'open', delegatesFocus: true });
|
|
188
|
+
this.setAttribute("role", "application");
|
|
189
|
+
this.setAttribute("aria-label", "Needle Engine 3D scene");
|
|
180
190
|
const template = document.createElement('template');
|
|
181
191
|
// #region CSS
|
|
182
192
|
template.innerHTML = `<style>
|
|
@@ -282,6 +292,7 @@ export class NeedleEngineWebComponent extends HTMLElement implements INeedleEngi
|
|
|
282
292
|
if (this.getAttribute("tabindex") === null || this.getAttribute("tabindex") === undefined)
|
|
283
293
|
this.setAttribute("tabindex", "0");
|
|
284
294
|
|
|
295
|
+
|
|
285
296
|
this.addEventListener("xr-session-started", this.onXRSessionStarted);
|
|
286
297
|
this.onSetupDesktop();
|
|
287
298
|
|
|
@@ -10,7 +10,7 @@ import * as main from "../engine/engine_mainloop_utils.js";
|
|
|
10
10
|
import { syncDestroy, syncInstantiate, SyncInstantiateOptions } from "../engine/engine_networking_instantiate.js";
|
|
11
11
|
import { Context, FrameEvent } from "../engine/engine_setup.js";
|
|
12
12
|
import * as threeutils from "../engine/engine_three_utils.js";
|
|
13
|
-
import type
|
|
13
|
+
import { $componentName, type Collision, type ComponentInit, type Constructor, type ConstructorConcrete, type GuidsMap, type ICollider, type IComponent, type IGameObject, type SourceIdentifier } from "../engine/engine_types.js";
|
|
14
14
|
import { TypeStore } from "../engine/engine_typestore.js";
|
|
15
15
|
import type { INeedleXRSessionEventReceiver, NeedleXRControllerEventArgs, NeedleXREventArgs } from "../engine/engine_xr.js";
|
|
16
16
|
import { type IPointerEventHandler, PointerEventData } from "./ui/PointerEvents.js";
|
|
@@ -542,8 +542,6 @@ export abstract class GameObject extends Object3D implements Object3D, IGameObje
|
|
|
542
542
|
}
|
|
543
543
|
}
|
|
544
544
|
|
|
545
|
-
// DO NOT CHANGE THE SYMBOL NAME
|
|
546
|
-
const $componentName = Symbol("component-name");
|
|
547
545
|
|
|
548
546
|
/**
|
|
549
547
|
* Needle Engine component's are the main building blocks of the Needle Engine.
|
|
@@ -193,7 +193,7 @@ export class DragControls extends Behaviour implements IPointerEventHandler {
|
|
|
193
193
|
}
|
|
194
194
|
}
|
|
195
195
|
}
|
|
196
|
-
|
|
196
|
+
|
|
197
197
|
private _rigidbody: Rigidbody | null = null;
|
|
198
198
|
|
|
199
199
|
// future:
|
|
@@ -235,12 +235,22 @@ export class DragControls extends Behaviour implements IPointerEventHandler {
|
|
|
235
235
|
/** @internal */
|
|
236
236
|
onEnable(): void {
|
|
237
237
|
DragControls._instances.push(this);
|
|
238
|
+
this.context.accessibility.updateElement(this, {
|
|
239
|
+
role: "button",
|
|
240
|
+
label: "Drag " + (this.gameObject.name || "object"),
|
|
241
|
+
hidden: false,
|
|
242
|
+
});
|
|
238
243
|
}
|
|
239
244
|
/** @internal */
|
|
240
245
|
onDisable(): void {
|
|
246
|
+
this.context.accessibility.updateElement(this, { hidden: true });
|
|
241
247
|
DragControls._instances = DragControls._instances.filter(i => i !== this);
|
|
242
248
|
}
|
|
243
249
|
|
|
250
|
+
onDestroy(): void {
|
|
251
|
+
this.context.accessibility.removeElement(this);
|
|
252
|
+
}
|
|
253
|
+
|
|
244
254
|
/**
|
|
245
255
|
* Checks if editing is allowed for the current networking connection.
|
|
246
256
|
* @param _obj Optional object to check edit permissions for
|
|
@@ -268,6 +278,8 @@ export class DragControls extends Behaviour implements IPointerEventHandler {
|
|
|
268
278
|
if (!dc || dc !== this) return;
|
|
269
279
|
DragControls.lastHovered = evt.object;
|
|
270
280
|
this.context.domElement.style.cursor = 'pointer';
|
|
281
|
+
|
|
282
|
+
this.context.accessibility.hover(this, `Draggable ${evt.object?.name}`);
|
|
271
283
|
}
|
|
272
284
|
|
|
273
285
|
/**
|
|
@@ -339,6 +351,14 @@ export class DragControls extends Behaviour implements IPointerEventHandler {
|
|
|
339
351
|
}
|
|
340
352
|
|
|
341
353
|
args.use();
|
|
354
|
+
|
|
355
|
+
this.context.accessibility.updateElement(this, {
|
|
356
|
+
role: "button",
|
|
357
|
+
label: "Dragging " + (this.gameObject.name || "object"),
|
|
358
|
+
hidden: false,
|
|
359
|
+
busy: true,
|
|
360
|
+
});
|
|
361
|
+
this.context.accessibility.focus(this);
|
|
342
362
|
}
|
|
343
363
|
}
|
|
344
364
|
|
|
@@ -375,6 +395,11 @@ export class DragControls extends Behaviour implements IPointerEventHandler {
|
|
|
375
395
|
}
|
|
376
396
|
args.use();
|
|
377
397
|
}
|
|
398
|
+
|
|
399
|
+
this.context.accessibility.unfocus(this);
|
|
400
|
+
this.context.accessibility.updateElement(this, {
|
|
401
|
+
busy: false,
|
|
402
|
+
});
|
|
378
403
|
}
|
|
379
404
|
|
|
380
405
|
/**
|
|
@@ -779,12 +804,12 @@ class DragPointerHandler implements IDragHandler {
|
|
|
779
804
|
* Used for determining if enough motion has occurred to start a drag.
|
|
780
805
|
*/
|
|
781
806
|
getTotalMovement(): Vector3 { return this._totalMovement; }
|
|
782
|
-
|
|
807
|
+
|
|
783
808
|
/**
|
|
784
809
|
* Returns the object that follows the pointer during dragging operations.
|
|
785
810
|
*/
|
|
786
811
|
get followObject(): GameObject { return this._followObject; }
|
|
787
|
-
|
|
812
|
+
|
|
788
813
|
/**
|
|
789
814
|
* Returns the point where the pointer initially hit the object in local space.
|
|
790
815
|
*/
|
|
@@ -1377,7 +1402,7 @@ class LegacyDragVisualsHelper {
|
|
|
1377
1402
|
|
|
1378
1403
|
/** Controls whether visual helpers like lines and markers are displayed */
|
|
1379
1404
|
showGizmo: boolean = true;
|
|
1380
|
-
|
|
1405
|
+
|
|
1381
1406
|
/** When true, drag plane alignment changes based on view angle */
|
|
1382
1407
|
useViewAngle: boolean = true;
|
|
1383
1408
|
|