@needle-tools/engine 4.9.0 → 4.9.1
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 +4 -0
- package/dist/{needle-engine.bundle-DrlDKOar.umd.cjs → needle-engine.bundle-BuTUhZAc.umd.cjs} +89 -89
- package/dist/{needle-engine.bundle-B1gr_nQ0.min.js → needle-engine.bundle-DoywABnJ.min.js} +95 -95
- package/dist/{needle-engine.bundle-BikYBC35.js → needle-engine.bundle-O7rlGMn7.js} +1988 -1948
- package/dist/needle-engine.js +2 -2
- package/dist/needle-engine.min.js +1 -1
- package/dist/needle-engine.umd.cjs +1 -1
- package/lib/engine/engine_gameobject.d.ts +7 -7
- package/lib/engine/engine_gameobject.js +88 -27
- package/lib/engine/engine_gameobject.js.map +1 -1
- package/lib/engine/engine_networking_instantiate.js +23 -6
- package/lib/engine/engine_networking_instantiate.js.map +1 -1
- package/lib/engine/engine_serialization_core.d.ts +2 -2
- package/lib/engine/engine_serialization_core.js +7 -7
- package/lib/engine/engine_serialization_core.js.map +1 -1
- package/lib/engine/engine_serialization_decorator.js +2 -1
- package/lib/engine/engine_serialization_decorator.js.map +1 -1
- package/lib/engine-components/Renderer.d.ts +7 -6
- package/lib/engine-components/Renderer.js.map +1 -1
- package/lib/engine-components/web/ScrollFollow.d.ts +19 -1
- package/lib/engine-components/web/ScrollFollow.js +20 -1
- package/lib/engine-components/web/ScrollFollow.js.map +1 -1
- package/package.json +1 -1
- package/plugins/vite/build.js +3 -0
- package/src/engine/engine_gameobject.ts +105 -38
- package/src/engine/engine_networking_instantiate.ts +21 -6
- package/src/engine/engine_serialization_core.ts +9 -9
- package/src/engine/engine_serialization_decorator.ts +2 -1
- package/src/engine-components/Renderer.ts +12 -10
- package/src/engine-components/web/ScrollFollow.ts +20 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@needle-tools/engine",
|
|
3
|
-
"version": "4.9.
|
|
3
|
+
"version": "4.9.1",
|
|
4
4
|
"description": "Needle Engine is a web-based runtime for 3D apps. It runs on your machine for development with great integrations into editors like Unity or Blender - and can be deployed onto any device! It is flexible, extensible and networking and XR are built-in.",
|
|
5
5
|
"main": "dist/needle-engine.min.js",
|
|
6
6
|
"exports": {
|
package/plugins/vite/build.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Bone, Object3D, Quaternion, SkinnedMesh, Vector3 } from "three";
|
|
1
|
+
import { Bone, Euler, Object3D, Quaternion, SkinnedMesh, Vector3 } from "three";
|
|
2
2
|
|
|
3
3
|
import { $shadowDomOwner } from "../engine-components/ui/Symbols.js";
|
|
4
4
|
import { type AssetReference } from "./engine_addressables.js";
|
|
@@ -11,7 +11,7 @@ import { processNewScripts } from "./engine_mainloop_utils.js";
|
|
|
11
11
|
import { InstantiateIdProvider } from "./engine_networking_instantiate.js";
|
|
12
12
|
import { assign, ISerializable } from "./engine_serialization_core.js";
|
|
13
13
|
import { Context, registerComponent } from "./engine_setup.js";
|
|
14
|
-
import { logHierarchy, setWorldPosition, setWorldQuaternion } from "./engine_three_utils.js";
|
|
14
|
+
import { getTempQuaternion, logHierarchy, setWorldPosition, setWorldQuaternion } from "./engine_three_utils.js";
|
|
15
15
|
import { type Constructor, type GuidsMap, type IComponent as Component, type IComponent, IEventList, type IGameObject as GameObject, type UIDProvider } from "./engine_types.js";
|
|
16
16
|
import { deepClone, getParam, tryFindObject } from "./engine_utils.js";
|
|
17
17
|
import { apply } from "./js-extensions/index.js";
|
|
@@ -24,12 +24,12 @@ export type IInstantiateOptions = {
|
|
|
24
24
|
//** parent guid or object */
|
|
25
25
|
parent?: string | Object3D;
|
|
26
26
|
/** position in local space. Set `keepWorldPosition` to true if this is world space */
|
|
27
|
-
position?: Vector3;
|
|
27
|
+
position?: Vector3 | [number, number, number];
|
|
28
28
|
/** for duplicatable parenting */
|
|
29
29
|
keepWorldPosition?: boolean;
|
|
30
30
|
/** rotation in local space. Set `keepWorldPosition` to true if this is world space */
|
|
31
|
-
rotation?: Quaternion;
|
|
32
|
-
scale?: Vector3;
|
|
31
|
+
rotation?: Quaternion | Euler | [number, number, number];
|
|
32
|
+
scale?: Vector3 | [number, number, number];
|
|
33
33
|
/** if the instantiated object should be visible */
|
|
34
34
|
visible?: boolean;
|
|
35
35
|
context?: Context;
|
|
@@ -46,9 +46,9 @@ export class InstantiateOptions implements IInstantiateOptions {
|
|
|
46
46
|
idProvider?: UIDProvider | undefined;
|
|
47
47
|
parent?: string | undefined | Object3D;
|
|
48
48
|
keepWorldPosition?: boolean
|
|
49
|
-
position?: Vector3 | undefined;
|
|
50
|
-
rotation?: Quaternion | undefined;
|
|
51
|
-
scale?: Vector3 | undefined;
|
|
49
|
+
position?: Vector3 | [number, number, number] | undefined;
|
|
50
|
+
rotation?: Quaternion | Euler | [number, number, number] | undefined;
|
|
51
|
+
scale?: Vector3 | [number, number, number] | undefined;
|
|
52
52
|
visible?: boolean | undefined;
|
|
53
53
|
context?: Context | undefined;
|
|
54
54
|
components?: boolean | undefined;
|
|
@@ -58,9 +58,9 @@ export class InstantiateOptions implements IInstantiateOptions {
|
|
|
58
58
|
clone.idProvider = this.idProvider;
|
|
59
59
|
clone.parent = this.parent;
|
|
60
60
|
clone.keepWorldPosition = this.keepWorldPosition;
|
|
61
|
-
clone.position = this.position?.clone();
|
|
62
|
-
clone.rotation = this.rotation?.clone();
|
|
63
|
-
clone.scale = this.scale?.clone();
|
|
61
|
+
clone.position = Array.isArray(this.position) ? [...this.position] : this.position?.clone();
|
|
62
|
+
clone.rotation = Array.isArray(this.rotation) ? [...this.rotation] : this.rotation?.clone();
|
|
63
|
+
clone.scale = Array.isArray(this.scale) ? [...this.scale] : this.scale?.clone();
|
|
64
64
|
clone.visible = this.visible;
|
|
65
65
|
clone.context = this.context;
|
|
66
66
|
clone.components = this.components;
|
|
@@ -72,9 +72,9 @@ export class InstantiateOptions implements IInstantiateOptions {
|
|
|
72
72
|
this.idProvider = other.idProvider;
|
|
73
73
|
this.parent = other.parent;
|
|
74
74
|
this.keepWorldPosition = other.keepWorldPosition;
|
|
75
|
-
this.position = other.position?.clone();
|
|
76
|
-
this.rotation = other.rotation?.clone();
|
|
77
|
-
this.scale = other.scale?.clone();
|
|
75
|
+
this.position = Array.isArray(other.position) ? [...other.position] : other.position?.clone();
|
|
76
|
+
this.rotation = Array.isArray(other.rotation) ? [...other.rotation] : other.rotation?.clone();
|
|
77
|
+
this.scale = Array.isArray(other.scale) ? [...other.scale] : other.scale?.clone();
|
|
78
78
|
this.visible = other.visible;
|
|
79
79
|
this.context = other.context;
|
|
80
80
|
this.components = other.components;
|
|
@@ -287,6 +287,8 @@ declare type ObjectCloneReference = {
|
|
|
287
287
|
|
|
288
288
|
|
|
289
289
|
declare type InstantiateReferenceMap = Record<string, ObjectCloneReference>;
|
|
290
|
+
declare type NewObjectReferenceMap = Record<string, { target: object, key: string }>;
|
|
291
|
+
|
|
290
292
|
/**
|
|
291
293
|
* Provides access to the instantiated object and its clone
|
|
292
294
|
*/
|
|
@@ -325,13 +327,13 @@ export function instantiate(instance: AssetReference | GameObject | Object3D, op
|
|
|
325
327
|
}
|
|
326
328
|
|
|
327
329
|
const components: Array<Component> = [];
|
|
328
|
-
const
|
|
330
|
+
const referencemap: InstantiateReferenceMap = {}; // used to resolve references on components to components on other gameobjects to their new counterpart
|
|
329
331
|
const skinnedMeshes: InstantiateReferenceMap = {}; // used to resolve skinned mesh bones
|
|
330
|
-
const clone = internalInstantiate(context, instance, options, components,
|
|
332
|
+
const clone = internalInstantiate(context, instance, options, components, referencemap, skinnedMeshes);
|
|
331
333
|
|
|
332
334
|
if (clone) {
|
|
333
|
-
resolveReferences(
|
|
334
|
-
resolveAndBindSkinnedMeshBones(skinnedMeshes,
|
|
335
|
+
resolveReferences(clone, referencemap);
|
|
336
|
+
resolveAndBindSkinnedMeshBones(skinnedMeshes, referencemap);
|
|
335
337
|
}
|
|
336
338
|
|
|
337
339
|
if (debug) {
|
|
@@ -426,19 +428,45 @@ function internalInstantiate(
|
|
|
426
428
|
parent.add(clone);
|
|
427
429
|
}
|
|
428
430
|
|
|
429
|
-
//
|
|
431
|
+
// POSITION
|
|
430
432
|
if (opts?.position) {
|
|
431
|
-
|
|
433
|
+
if (Array.isArray(opts.position)) {
|
|
434
|
+
const vec = new Vector3();
|
|
435
|
+
vec.fromArray(opts.position);
|
|
436
|
+
clone.worldPosition = vec;
|
|
437
|
+
}
|
|
438
|
+
else {
|
|
439
|
+
clone.worldPosition = opts.position;
|
|
440
|
+
}
|
|
432
441
|
}
|
|
433
442
|
else clone.position.copy(instance.position);
|
|
443
|
+
|
|
444
|
+
// ROTATION
|
|
434
445
|
if (opts?.rotation) {
|
|
435
|
-
|
|
446
|
+
if (opts.rotation instanceof Quaternion)
|
|
447
|
+
clone.worldQuaternion = opts.rotation;
|
|
448
|
+
else if (opts.rotation instanceof Euler)
|
|
449
|
+
clone.worldQuaternion = getTempQuaternion().setFromEuler(opts.rotation);
|
|
450
|
+
else if (Array.isArray(opts.rotation)) {
|
|
451
|
+
const euler = new Euler();
|
|
452
|
+
euler.fromArray(opts.rotation);
|
|
453
|
+
clone.worldQuaternion = getTempQuaternion().setFromEuler(euler);
|
|
454
|
+
}
|
|
436
455
|
}
|
|
437
456
|
else clone.quaternion.copy(instance.quaternion);
|
|
457
|
+
|
|
458
|
+
// SCALE
|
|
438
459
|
if (opts?.scale) {
|
|
439
|
-
clone.scale.copy(opts.scale);
|
|
440
460
|
// TODO MAJOR: replace with worldscale
|
|
441
461
|
// clone.worldScale = opts.scale;
|
|
462
|
+
if (Array.isArray(opts.scale)) {
|
|
463
|
+
const vec = new Vector3();
|
|
464
|
+
vec.fromArray(opts.scale);
|
|
465
|
+
opts.scale = vec;
|
|
466
|
+
}
|
|
467
|
+
else {
|
|
468
|
+
clone.scale.copy(opts.scale);
|
|
469
|
+
}
|
|
442
470
|
}
|
|
443
471
|
else clone.scale.copy(instance.scale);
|
|
444
472
|
|
|
@@ -470,17 +498,7 @@ function internalInstantiate(
|
|
|
470
498
|
for (let i = 0; i < components.length; i++) {
|
|
471
499
|
const comp = components[i];
|
|
472
500
|
const copy = new comp.constructor();
|
|
473
|
-
|
|
474
|
-
// onAssign: (source, key, value) => {
|
|
475
|
-
// if (typeof value === "object") {
|
|
476
|
-
// const serializable = source as ISerializable;
|
|
477
|
-
// if (serializable?.$serializedTypes?.[key]) {
|
|
478
|
-
// console.debug("TODO CLONE", key, value);
|
|
479
|
-
// }
|
|
480
|
-
// }
|
|
481
|
-
// return value;
|
|
482
|
-
// }
|
|
483
|
-
});
|
|
501
|
+
onAssignComponent(comp, copy, objectsMap);
|
|
484
502
|
// make sure the original guid stays intact
|
|
485
503
|
if (comp[editorGuidKeyName] !== undefined)
|
|
486
504
|
copy[editorGuidKeyName] = comp[editorGuidKeyName];
|
|
@@ -501,7 +519,6 @@ function internalInstantiate(
|
|
|
501
519
|
opts.parent = undefined;
|
|
502
520
|
opts.visible = undefined;
|
|
503
521
|
}
|
|
504
|
-
|
|
505
522
|
for (const ch in instance.children) {
|
|
506
523
|
const child = instance.children[ch];
|
|
507
524
|
const newChild = internalInstantiate(context, child as GameObject, opts, componentsList, objectsMap, skinnedMeshesMap);
|
|
@@ -510,11 +527,59 @@ function internalInstantiate(
|
|
|
510
527
|
clone.add(newChild);
|
|
511
528
|
}
|
|
512
529
|
}
|
|
513
|
-
|
|
514
530
|
return clone;
|
|
531
|
+
}
|
|
532
|
+
|
|
515
533
|
|
|
534
|
+
function onAssignComponent(source: any, target: any, _newObjectsMap: InstantiateReferenceMap) {
|
|
535
|
+
assign(target, source, undefined, {
|
|
536
|
+
// onAssigned: (target, key, _oldValue, value) => {
|
|
537
|
+
// if (value !== null && typeof value === "object") {
|
|
538
|
+
// const serializable = target as ISerializable;
|
|
539
|
+
// if (serializable?.$serializedTypes?.[key]) {
|
|
540
|
+
// if (!(value instanceof Object3D)) {
|
|
541
|
+
// // let clone = null;
|
|
542
|
+
// // if ("clone" in value) {
|
|
543
|
+
// // if (canClone(value)) clone = (value as any).clone();
|
|
544
|
+
// // }
|
|
545
|
+
// // else {
|
|
546
|
+
// // clone = Object.assign(Object.create(Object.getPrototypeOf(value)), value);
|
|
547
|
+
// // }
|
|
548
|
+
// // if (clone) {
|
|
549
|
+
// // console.debug(key, { target, value, clone })
|
|
550
|
+
// // target[key] = clone;
|
|
551
|
+
// // findNestedReferences(clone, objectsMap);
|
|
552
|
+
// // }
|
|
553
|
+
// // else console.debug("Could not clone value for key", key, value);
|
|
554
|
+
// }
|
|
555
|
+
// else {
|
|
556
|
+
// console.log("ASSIGNED", value)
|
|
557
|
+
// }
|
|
558
|
+
|
|
559
|
+
// recursiveAssign(target, target[key], newObjectsMap);
|
|
560
|
+
// }
|
|
561
|
+
|
|
562
|
+
// }
|
|
563
|
+
// }
|
|
564
|
+
});
|
|
516
565
|
}
|
|
517
566
|
|
|
567
|
+
// function findNestedReferences(object: object, map: InstantiateReferenceMap) {
|
|
568
|
+
// const keys = Object.keys(object);
|
|
569
|
+
// for (const key of keys) {
|
|
570
|
+
// const val = (object as any)[key];
|
|
571
|
+
// if (val instanceof Object3D) {
|
|
572
|
+
// if ("guid" in val && val.guid) {
|
|
573
|
+
// console.log("FOUND ", val.guid, val)
|
|
574
|
+
// map[val.guid] = { original: val, clone: null };
|
|
575
|
+
// }
|
|
576
|
+
// }
|
|
577
|
+
// else if (typeof val === "object" && val !== null) {
|
|
578
|
+
// findNestedReferences(val, map);
|
|
579
|
+
// }
|
|
580
|
+
// }
|
|
581
|
+
// }
|
|
582
|
+
|
|
518
583
|
function resolveAndBindSkinnedMeshBones(
|
|
519
584
|
skinnedMeshes: { [key: string]: ObjectCloneReference },
|
|
520
585
|
newObjectsMap: { [key: string]: ObjectCloneReference }
|
|
@@ -599,13 +664,13 @@ function resolveAndBindSkinnedMeshBones(
|
|
|
599
664
|
|
|
600
665
|
// }
|
|
601
666
|
|
|
602
|
-
function resolveReferences(newObjectsMap: InstantiateReferenceMap) {
|
|
667
|
+
function resolveReferences(_newInstance: Object3D, newObjectsMap: InstantiateReferenceMap) {
|
|
603
668
|
// for every object that is newly created we want to update references to their newly created counterparts
|
|
604
669
|
// e.g. a collider instance referencing a rigidbody instance should be updated so that
|
|
605
670
|
// the cloned collider does not reference the cloned rigidbody (instead of the original rigidbody)
|
|
606
671
|
for (const key in newObjectsMap) {
|
|
607
672
|
const val = newObjectsMap[key];
|
|
608
|
-
const clone = val.clone as Object3D;
|
|
673
|
+
const clone = val.clone as Object3D | null;
|
|
609
674
|
// resolve references
|
|
610
675
|
if (clone?.isObject3D && clone?.userData?.components) {
|
|
611
676
|
for (let i = 0; i < clone.userData.components.length; i++) {
|
|
@@ -706,4 +771,6 @@ function postProcessNewInstance(copy: Object3D, key: string, value: IComponent |
|
|
|
706
771
|
return copy;
|
|
707
772
|
}
|
|
708
773
|
}
|
|
709
|
-
}
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
// const canClone = (value: any) => value.isVector4 || value.isVector3 || value.isVector2 || value.isQuaternion || value.isEuler || value.isColor === true;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Object3D, Quaternion, Vector3 } from "three";
|
|
1
|
+
import { Euler, Object3D, Quaternion, Vector3 } from "three";
|
|
2
2
|
// https://github.com/uuidjs/uuid
|
|
3
3
|
// v5 takes string and namespace
|
|
4
4
|
import { v5 } from 'uuid';
|
|
@@ -254,12 +254,27 @@ export function syncInstantiate(object: GameObject | Object3D, opts: SyncInstant
|
|
|
254
254
|
if (opts.deleteOnDisconnect === true)
|
|
255
255
|
model.deleteStateOnDisconnect = true;
|
|
256
256
|
if (originalOpts) {
|
|
257
|
-
if (originalOpts.position)
|
|
258
|
-
|
|
259
|
-
|
|
257
|
+
if (originalOpts.position) {
|
|
258
|
+
if (Array.isArray(originalOpts.position)) {
|
|
259
|
+
model.position = { x: originalOpts.position[0], y: originalOpts.position[1], z: originalOpts.position[2] };
|
|
260
|
+
}
|
|
261
|
+
else model.position = { x: originalOpts.position.x, y: originalOpts.position.y, z: originalOpts.position.z };
|
|
262
|
+
}
|
|
263
|
+
if (originalOpts.rotation) {
|
|
264
|
+
if (originalOpts.rotation instanceof Euler) {
|
|
265
|
+
originalOpts.rotation = new Quaternion().setFromEuler(originalOpts.rotation);
|
|
266
|
+
}
|
|
267
|
+
else if (originalOpts.rotation instanceof Array) {
|
|
268
|
+
originalOpts.rotation = new Quaternion().fromArray(originalOpts.rotation);
|
|
269
|
+
}
|
|
260
270
|
model.rotation = { x: originalOpts.rotation.x, y: originalOpts.rotation.y, z: originalOpts.rotation.z, w: originalOpts.rotation.w };
|
|
261
|
-
|
|
262
|
-
|
|
271
|
+
}
|
|
272
|
+
if (originalOpts.scale) {
|
|
273
|
+
if (Array.isArray(originalOpts.scale)) {
|
|
274
|
+
model.scale = { x: originalOpts.scale[0], y: originalOpts.scale[1], z: originalOpts.scale[2] };
|
|
275
|
+
}
|
|
276
|
+
else model.scale = { x: originalOpts.scale.x, y: originalOpts.scale.y, z: originalOpts.scale.z };
|
|
277
|
+
}
|
|
263
278
|
}
|
|
264
279
|
if (!model.position)
|
|
265
280
|
model.position = { x: go.position.x, y: go.position.y, z: go.position.z };
|
|
@@ -658,10 +658,10 @@ export const $isAssigningProperties = Symbol("assigned component properties");
|
|
|
658
658
|
* @param key the key that is being assigned
|
|
659
659
|
* @param value the value that is being assigned
|
|
660
660
|
*/
|
|
661
|
-
type
|
|
661
|
+
type AssignedCallback = (source: object, key: string, oldValue: any, newValue: any) => any;
|
|
662
662
|
|
|
663
663
|
/** Object.assign behaviour but check if property is writeable (e.g. getter only properties are skipped) */
|
|
664
|
-
export function assign(target: any, source: any, info?: ImplementationInformation, opts?: {
|
|
664
|
+
export function assign(target: any, source: any, info?: ImplementationInformation, opts?: { onAssigned?: AssignedCallback }) {
|
|
665
665
|
if (source === undefined || source === null) return;
|
|
666
666
|
if (target === undefined || target === null) return;
|
|
667
667
|
|
|
@@ -699,13 +699,13 @@ export function assign(target: any, source: any, info?: ImplementationInformatio
|
|
|
699
699
|
// arrow functions are defined as properties on the object
|
|
700
700
|
continue;
|
|
701
701
|
}
|
|
702
|
-
if (!desc || desc.writable === true) {
|
|
703
|
-
const
|
|
704
|
-
target[key]
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
702
|
+
if (!desc || desc.writable === true || desc.set !== undefined) {
|
|
703
|
+
const newValue = source[key];
|
|
704
|
+
const oldValue = target[key];
|
|
705
|
+
target[key] = newValue;
|
|
706
|
+
if (opts?.onAssigned) {
|
|
707
|
+
opts.onAssigned(target, key, oldValue, newValue);
|
|
708
|
+
}
|
|
709
709
|
}
|
|
710
710
|
}
|
|
711
711
|
delete target[$isAssigningProperties];
|
|
@@ -27,7 +27,8 @@ export const serializable = function <T>(type?: Constructor<T> | null | Array<Co
|
|
|
27
27
|
|
|
28
28
|
return function (_target: any, _propertyKey: string | { name: string }) {
|
|
29
29
|
if (!_target) {
|
|
30
|
-
|
|
30
|
+
const propertyName = typeof _propertyKey === 'string' ? _propertyKey : _propertyKey.name;
|
|
31
|
+
console.warn(`@serializable without a target at '${propertyName}'.`);
|
|
31
32
|
return;
|
|
32
33
|
}
|
|
33
34
|
// The _propertyKey parameter is a string in TS4 with experimentalDecorators
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { getRaycastMesh } from "@needle-tools/gltf-progressive";
|
|
2
|
-
import { AxesHelper, Material, Mesh, Object3D, SkinnedMesh, Texture, Vector4 } from "three";
|
|
2
|
+
import { AxesHelper, Material, Mesh, MeshBasicMaterial, MeshPhysicalMaterial, MeshStandardMaterial, Object3D, RawShaderMaterial, ShaderMaterial, SkinnedMesh, Texture, Vector4 } from "three";
|
|
3
3
|
|
|
4
4
|
import { showBalloonWarning } from "../engine/debug/index.js";
|
|
5
5
|
import { getComponent, getOrAddComponent } from "../engine/engine_components.js";
|
|
@@ -48,6 +48,8 @@ export enum RenderState {
|
|
|
48
48
|
Front = 2,
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
type SharedMaterial = (Material & Partial<MeshStandardMaterial> & Partial<MeshPhysicalMaterial> & Partial<ShaderMaterial> & Partial<RawShaderMaterial>);
|
|
52
|
+
|
|
51
53
|
|
|
52
54
|
// support sharedMaterials[index] assigning materials directly to the objects
|
|
53
55
|
class SharedMaterialArray implements ISharedMaterials {
|
|
@@ -185,7 +187,7 @@ class SharedMaterialArray implements ISharedMaterials {
|
|
|
185
187
|
this.changed = true;
|
|
186
188
|
}
|
|
187
189
|
|
|
188
|
-
private getMaterial(index: number):
|
|
190
|
+
private getMaterial(index: number): SharedMaterial | null {
|
|
189
191
|
index = this.resolveIndex(index);
|
|
190
192
|
if (index < 0) return null;
|
|
191
193
|
const obj = this._targets;
|
|
@@ -302,11 +304,11 @@ export class Renderer extends Behaviour implements IRenderer {
|
|
|
302
304
|
return this._sharedMeshes;
|
|
303
305
|
}
|
|
304
306
|
|
|
305
|
-
get sharedMaterial():
|
|
306
|
-
return this.sharedMaterials[0];
|
|
307
|
+
get sharedMaterial(): SharedMaterial {
|
|
308
|
+
return this.sharedMaterials[0] as SharedMaterial;
|
|
307
309
|
}
|
|
308
310
|
|
|
309
|
-
set sharedMaterial(mat:
|
|
311
|
+
set sharedMaterial(mat: SharedMaterial) {
|
|
310
312
|
const cur = this.sharedMaterials[0];
|
|
311
313
|
if (cur === mat) return;
|
|
312
314
|
this.sharedMaterials[0] = mat;
|
|
@@ -314,12 +316,12 @@ export class Renderer extends Behaviour implements IRenderer {
|
|
|
314
316
|
}
|
|
315
317
|
|
|
316
318
|
/**@deprecated please use sharedMaterial */
|
|
317
|
-
get material():
|
|
318
|
-
return this.sharedMaterials[0];
|
|
319
|
+
get material(): SharedMaterial {
|
|
320
|
+
return this.sharedMaterials[0] as SharedMaterial;
|
|
319
321
|
}
|
|
320
322
|
|
|
321
323
|
/**@deprecated please use sharedMaterial */
|
|
322
|
-
set material(mat:
|
|
324
|
+
set material(mat: SharedMaterial) {
|
|
323
325
|
this.sharedMaterial = mat;
|
|
324
326
|
}
|
|
325
327
|
|
|
@@ -329,10 +331,10 @@ export class Renderer extends Behaviour implements IRenderer {
|
|
|
329
331
|
private _probeAnchorLastFrame?: Object3D;
|
|
330
332
|
|
|
331
333
|
// this is just available during deserialization
|
|
332
|
-
private set sharedMaterials(_val: Array<
|
|
334
|
+
private set sharedMaterials(_val: Array<SharedMaterial | null>) {
|
|
333
335
|
// TODO: elements in the array might be missing at the moment which leads to problems if an index is serialized
|
|
334
336
|
if (!this._originalMaterials) {
|
|
335
|
-
this._originalMaterials = _val as
|
|
337
|
+
this._originalMaterials = _val as SharedMaterial[];
|
|
336
338
|
}
|
|
337
339
|
else if (_val) {
|
|
338
340
|
let didWarn = false;
|
|
@@ -28,18 +28,36 @@ type ScrollFollowEvent = {
|
|
|
28
28
|
* The ScrollFollow component allows you to link the scroll position of the page (or a specific element) to one or more target objects.
|
|
29
29
|
* This can be used to create scroll-based animations, audio playback, or other effects. For example you can link the scroll position to a timeline (PlayableDirector) to create scroll-based storytelling effects or to an Animator component to change the animation state based on scroll.
|
|
30
30
|
*
|
|
31
|
+
* Assign {@link target} objects to the component to have them updated based on the current scroll position (check the 'target' property for supported types).
|
|
32
|
+
*
|
|
31
33
|
* @link Example at https://scrollytelling-2-z23hmxby7c6x-u30ld.needle.run/
|
|
34
|
+
* @link Template at https://github.com/needle-engine/scrollytelling-template
|
|
35
|
+
*
|
|
36
|
+
* ## How to use with an Animator
|
|
37
|
+
* 1. Create an Animator component and set up a float parameter named "scroll".
|
|
38
|
+
* 2. Create transitions between animation states based on the "scroll" parameter (e.g. from 0 to 1).
|
|
39
|
+
* 3. Add a ScrollFollow component to the same GameObject or another GameObject in the scene.
|
|
40
|
+
* 4. Assign the Animator component to the ScrollFollow's target property.
|
|
41
|
+
*
|
|
42
|
+
* ## How to use with a PlayableDirector (timeline)
|
|
43
|
+
* 1. Create a PlayableDirector component and set up a timeline asset.
|
|
44
|
+
* 2. Add a ScrollFollow component to the same GameObject or another GameObject in the scene.
|
|
45
|
+
* 3. Assign the PlayableDirector component to the ScrollFollow's target property.
|
|
46
|
+
* 4. The timeline will now scrub based on the scroll position of the page.
|
|
32
47
|
*/
|
|
33
48
|
export class ScrollFollow extends Behaviour {
|
|
34
49
|
|
|
35
50
|
/**
|
|
36
|
-
* Target object(s) to follow the scroll position of the page.
|
|
51
|
+
* Target object(s) to follow the scroll position of the page.
|
|
37
52
|
*
|
|
38
53
|
* Supported target types:
|
|
39
54
|
* - PlayableDirector (timeline), the scroll position will be mapped to the timeline time
|
|
40
55
|
* - Animator, the scroll position will be set to a float parameter named "scroll"
|
|
41
56
|
* - Animation, the scroll position will be mapped to the animation time
|
|
42
57
|
* - AudioSource, the scroll position will be mapped to the audio time
|
|
58
|
+
* - SplineWalker, the scroll position will be mapped to the position01 property
|
|
59
|
+
* - Light, the scroll position will be mapped to the intensity property
|
|
60
|
+
* - Object3D, the object will move vertically based on the scroll position
|
|
43
61
|
* - Any object with a `scroll` property (number or function)
|
|
44
62
|
*/
|
|
45
63
|
@serializable([Behaviour, Object3D])
|
|
@@ -204,6 +222,7 @@ export class ScrollFollow extends Behaviour {
|
|
|
204
222
|
}
|
|
205
223
|
const bounds = target["needle:scrollbounds"] as Box3;
|
|
206
224
|
if (bounds) {
|
|
225
|
+
// TODO: remap position to use upper screen edge and lower edge instead of center
|
|
207
226
|
target.position.y = -bounds.min.y - value * (bounds.max.y - bounds.min.y);
|
|
208
227
|
}
|
|
209
228
|
}
|