@dcl/ecs 7.18.2-21450088960.commit-3c16004 → 7.18.2-21457748765.commit-7ae2e38
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/dist/components/extended/Material.d.ts +203 -2
- package/dist/components/extended/Material.js +475 -0
- package/dist/components/generated/pb/decentraland/sdk/components/pointer_events.gen.d.ts +25 -0
- package/dist/components/generated/pb/decentraland/sdk/components/pointer_events.gen.js +10 -0
- package/dist/components/types.d.ts +1 -1
- package/dist/runtime/helpers/index.d.ts +1 -0
- package/dist/runtime/helpers/index.js +1 -0
- package/dist/runtime/helpers/timers.d.ts +85 -0
- package/dist/runtime/helpers/timers.js +69 -0
- package/dist/runtime/helpers/tree.d.ts +61 -0
- package/dist/runtime/helpers/tree.js +238 -0
- package/dist/runtime/initialization/index.d.ts +7 -0
- package/dist/runtime/initialization/index.js +11 -0
- package/dist/systems/events.d.ts +1 -0
- package/dist/systems/events.js +2 -1
- package/dist-cjs/components/extended/Material.d.ts +203 -2
- package/dist-cjs/components/extended/Material.js +475 -0
- package/dist-cjs/components/generated/pb/decentraland/sdk/components/pointer_events.gen.d.ts +25 -0
- package/dist-cjs/components/generated/pb/decentraland/sdk/components/pointer_events.gen.js +10 -0
- package/dist-cjs/components/types.d.ts +1 -1
- package/dist-cjs/runtime/helpers/index.d.ts +1 -0
- package/dist-cjs/runtime/helpers/index.js +3 -0
- package/dist-cjs/runtime/helpers/timers.d.ts +85 -0
- package/dist-cjs/runtime/helpers/timers.js +73 -0
- package/dist-cjs/runtime/helpers/tree.d.ts +61 -0
- package/dist-cjs/runtime/helpers/tree.js +242 -1
- package/dist-cjs/runtime/initialization/index.d.ts +7 -0
- package/dist-cjs/runtime/initialization/index.js +12 -1
- package/dist-cjs/systems/events.d.ts +1 -0
- package/dist-cjs/systems/events.js +2 -1
- package/package.json +2 -2
|
@@ -14,6 +14,29 @@ import { InputAction, PointerEventType } from "./common/input_action.gen";
|
|
|
14
14
|
*
|
|
15
15
|
* It also supports simple visual feedback when interactions occur, by showing floating text.
|
|
16
16
|
* More sophisticated feedback requires the use of other components.
|
|
17
|
+
*
|
|
18
|
+
* Distance rules
|
|
19
|
+
* --------------
|
|
20
|
+
* PointerEvents can enforce interaction range using two independent distance checks:
|
|
21
|
+
*
|
|
22
|
+
* - Camera distance (`max_distance`): distance from the active camera to the target entity.
|
|
23
|
+
* - Player distance (`max_player_distance`): distance from the avatar/player position to the target entity.
|
|
24
|
+
*
|
|
25
|
+
* How the interaction checks are combined:
|
|
26
|
+
*
|
|
27
|
+
* 1) Only `max_distance` is present
|
|
28
|
+
* - The interaction is allowed only if the camera distance is <= `max_distance`.
|
|
29
|
+
*
|
|
30
|
+
* 2) Only `max_player_distance` is present
|
|
31
|
+
* - The interaction is allowed only if the player distance is <= `max_player_distance`.
|
|
32
|
+
*
|
|
33
|
+
* 3) Both `max_distance` and `max_player_distance` are present
|
|
34
|
+
* - The interaction is allowed if ANY of the checks passes (OR logic):
|
|
35
|
+
* (camera distance <= `max_distance`) OR (player distance <= `max_player_distance`).
|
|
36
|
+
*
|
|
37
|
+
* 4) Neither `max_distance` nor `max_player_distance` is present
|
|
38
|
+
* - The system behaves as if `max_distance` were set to its default value (10),
|
|
39
|
+
* i.e., it uses the camera distance check with a threshold of 10.
|
|
17
40
|
*/
|
|
18
41
|
/**
|
|
19
42
|
* @public
|
|
@@ -36,6 +59,8 @@ export interface PBPointerEvents_Info {
|
|
|
36
59
|
showFeedback?: boolean | undefined;
|
|
37
60
|
/** enable or disable hover highlight (default true) */
|
|
38
61
|
showHighlight?: boolean | undefined;
|
|
62
|
+
/** range of interaction from the avatar's position (default 0) */
|
|
63
|
+
maxPlayerDistance?: number | undefined;
|
|
39
64
|
}
|
|
40
65
|
/**
|
|
41
66
|
* @public
|
|
@@ -46,6 +46,7 @@ function createBasePBPointerEvents_Info() {
|
|
|
46
46
|
maxDistance: undefined,
|
|
47
47
|
showFeedback: undefined,
|
|
48
48
|
showHighlight: undefined,
|
|
49
|
+
maxPlayerDistance: undefined,
|
|
49
50
|
};
|
|
50
51
|
}
|
|
51
52
|
/**
|
|
@@ -69,6 +70,9 @@ export var PBPointerEvents_Info;
|
|
|
69
70
|
if (message.showHighlight !== undefined) {
|
|
70
71
|
writer.uint32(40).bool(message.showHighlight);
|
|
71
72
|
}
|
|
73
|
+
if (message.maxPlayerDistance !== undefined) {
|
|
74
|
+
writer.uint32(53).float(message.maxPlayerDistance);
|
|
75
|
+
}
|
|
72
76
|
return writer;
|
|
73
77
|
}
|
|
74
78
|
PBPointerEvents_Info.encode = encode;
|
|
@@ -109,6 +113,12 @@ export var PBPointerEvents_Info;
|
|
|
109
113
|
}
|
|
110
114
|
message.showHighlight = reader.bool();
|
|
111
115
|
continue;
|
|
116
|
+
case 6:
|
|
117
|
+
if (tag !== 53) {
|
|
118
|
+
break;
|
|
119
|
+
}
|
|
120
|
+
message.maxPlayerDistance = reader.float();
|
|
121
|
+
continue;
|
|
112
122
|
}
|
|
113
123
|
if ((tag & 7) === 4 || tag === 0) {
|
|
114
124
|
break;
|
|
@@ -3,7 +3,7 @@ export type { AudioSourceComponentDefinitionExtended } from './extended/AudioSou
|
|
|
3
3
|
export type { AudioStreamComponentDefinitionExtended } from './extended/AudioStream';
|
|
4
4
|
export type { MeshRendererComponentDefinitionExtended } from './extended/MeshRenderer';
|
|
5
5
|
export type { MeshColliderComponentDefinitionExtended } from './extended/MeshCollider';
|
|
6
|
-
export type { TextureHelper, MaterialComponentDefinitionExtended } from './extended/Material';
|
|
6
|
+
export type { TextureHelper, MaterialComponentDefinitionExtended, FlatTexture, ReadonlyFlatMaterial, ReadonlyFlatTexture, FlatMaterial } from './extended/Material';
|
|
7
7
|
export type { TweenHelper, TweenComponentDefinitionExtended } from './extended/Tween';
|
|
8
8
|
export type { CameraTransitionHelper, VirtualCameraComponentDefinitionExtended } from './extended/VirtualCamera';
|
|
9
9
|
export type { TransformComponentExtended, TransformTypeWithOptionals } from './manual/Transform';
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { IEngine } from '../../engine/types';
|
|
2
|
+
export type TimerId = number;
|
|
3
|
+
export type TimerCallback = () => void;
|
|
4
|
+
export type Timers = {
|
|
5
|
+
/**
|
|
6
|
+
* Delays the execution of a function by a given amount of milliseconds.
|
|
7
|
+
*
|
|
8
|
+
* @param callback - The function to execute after the delay
|
|
9
|
+
* @param ms - The delay in milliseconds
|
|
10
|
+
* @returns A TimerId that can be used to cancel the timeout
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```ts
|
|
14
|
+
* const timeoutId = timers.setTimeout(() => {
|
|
15
|
+
* console.log('1 second passed')
|
|
16
|
+
* }, 1000)
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
setTimeout(callback: TimerCallback, ms: number): TimerId;
|
|
20
|
+
/**
|
|
21
|
+
* Cancels a timeout previously established by setTimeout.
|
|
22
|
+
*
|
|
23
|
+
* @param timerId - The TimerId returned by setTimeout
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```ts
|
|
27
|
+
* const timeoutId = timers.setTimeout(() => {
|
|
28
|
+
* console.log('This will not run')
|
|
29
|
+
* }, 1000)
|
|
30
|
+
*
|
|
31
|
+
* timers.clearTimeout(timeoutId)
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
clearTimeout(timerId: TimerId): void;
|
|
35
|
+
/**
|
|
36
|
+
* Repeatedly executes a function at specified intervals.
|
|
37
|
+
*
|
|
38
|
+
* @param callback - The function to execute at each interval
|
|
39
|
+
* @param ms - The interval in milliseconds
|
|
40
|
+
* @returns A TimerId that can be used to cancel the interval
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* ```ts
|
|
44
|
+
* const intervalId = timers.setInterval(() => {
|
|
45
|
+
* console.log('Printing this every 1 second')
|
|
46
|
+
* }, 1000)
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
setInterval(callback: TimerCallback, ms: number): TimerId;
|
|
50
|
+
/**
|
|
51
|
+
* Cancels an interval previously established by setInterval.
|
|
52
|
+
*
|
|
53
|
+
* @param timerId - The TimerId returned by setInterval
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* ```ts
|
|
57
|
+
* const intervalId = timers.setInterval(() => {
|
|
58
|
+
* console.log('This will stop')
|
|
59
|
+
* }, 1000)
|
|
60
|
+
*
|
|
61
|
+
* timers.clearInterval(intervalId)
|
|
62
|
+
* ```
|
|
63
|
+
*/
|
|
64
|
+
clearInterval(timerId: TimerId): void;
|
|
65
|
+
};
|
|
66
|
+
/**
|
|
67
|
+
* Creates a timer system bound to a specific engine instance.
|
|
68
|
+
*
|
|
69
|
+
* @param targetEngine - The engine instance to bind timers to
|
|
70
|
+
* @returns A Timers object with setTimeout, clearTimeout, setInterval, and clearInterval methods
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* ```ts
|
|
74
|
+
* import { Engine } from '@dcl/sdk/ecs'
|
|
75
|
+
* import { createTimers } from '@dcl/sdk/ecs'
|
|
76
|
+
*
|
|
77
|
+
* const engine = Engine()
|
|
78
|
+
* const timers = createTimers(engine)
|
|
79
|
+
*
|
|
80
|
+
* timers.setTimeout(() => console.log('done'), 1000)
|
|
81
|
+
* ```
|
|
82
|
+
*
|
|
83
|
+
* @public
|
|
84
|
+
*/
|
|
85
|
+
export declare function createTimers(targetEngine: IEngine): Timers;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Creates a timer system bound to a specific engine instance.
|
|
3
|
+
*
|
|
4
|
+
* @param targetEngine - The engine instance to bind timers to
|
|
5
|
+
* @returns A Timers object with setTimeout, clearTimeout, setInterval, and clearInterval methods
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* import { Engine } from '@dcl/sdk/ecs'
|
|
10
|
+
* import { createTimers } from '@dcl/sdk/ecs'
|
|
11
|
+
*
|
|
12
|
+
* const engine = Engine()
|
|
13
|
+
* const timers = createTimers(engine)
|
|
14
|
+
*
|
|
15
|
+
* timers.setTimeout(() => console.log('done'), 1000)
|
|
16
|
+
* ```
|
|
17
|
+
*
|
|
18
|
+
* @public
|
|
19
|
+
*/
|
|
20
|
+
export function createTimers(targetEngine) {
|
|
21
|
+
const timers = new Map();
|
|
22
|
+
let timerIdCounter = 0;
|
|
23
|
+
function system(dt) {
|
|
24
|
+
for (const [timerId, timerData] of timers) {
|
|
25
|
+
timerData.accumulatedTime += 1000 * dt;
|
|
26
|
+
if (timerData.accumulatedTime < timerData.interval) {
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
if (timerData.recurrent) {
|
|
30
|
+
// For intervals, subtract full interval periods to handle accumulated time
|
|
31
|
+
const fullIntervals = Math.floor(timerData.accumulatedTime / timerData.interval);
|
|
32
|
+
timerData.accumulatedTime -= fullIntervals * timerData.interval;
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
timers.delete(timerId);
|
|
36
|
+
}
|
|
37
|
+
timerData.callback();
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
targetEngine.addSystem(system, Number.MAX_SAFE_INTEGER, '@dcl/ecs/timers');
|
|
41
|
+
return {
|
|
42
|
+
setTimeout(callback, ms) {
|
|
43
|
+
const timerId = timerIdCounter++;
|
|
44
|
+
timers.set(timerId, {
|
|
45
|
+
callback,
|
|
46
|
+
interval: ms,
|
|
47
|
+
recurrent: false,
|
|
48
|
+
accumulatedTime: 0
|
|
49
|
+
});
|
|
50
|
+
return timerId;
|
|
51
|
+
},
|
|
52
|
+
clearTimeout(timerId) {
|
|
53
|
+
timers.delete(timerId);
|
|
54
|
+
},
|
|
55
|
+
setInterval(callback, ms) {
|
|
56
|
+
const timerId = timerIdCounter++;
|
|
57
|
+
timers.set(timerId, {
|
|
58
|
+
callback,
|
|
59
|
+
interval: ms,
|
|
60
|
+
recurrent: true,
|
|
61
|
+
accumulatedTime: 0
|
|
62
|
+
});
|
|
63
|
+
return timerId;
|
|
64
|
+
},
|
|
65
|
+
clearInterval(timerId) {
|
|
66
|
+
timers.delete(timerId);
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { Entity } from '../../engine/entity';
|
|
2
2
|
import { ComponentDefinition, IEngine } from '../../engine';
|
|
3
|
+
import { Vector3Type } from '../../schemas/custom/Vector3';
|
|
4
|
+
import { QuaternionType } from '../../schemas/custom/Quaternion';
|
|
3
5
|
/**
|
|
4
6
|
* Get an iterator of entities that follow a tree structure for a component
|
|
5
7
|
* @public
|
|
@@ -30,3 +32,62 @@ export declare function getComponentEntityTree<T>(engine: Pick<IEngine, 'getEnti
|
|
|
30
32
|
* @public
|
|
31
33
|
*/
|
|
32
34
|
export declare function removeEntityWithChildren(engine: Pick<IEngine, 'getEntitiesWith' | 'defineComponentFromSchema' | 'removeEntity' | 'defineComponent'>, entity: Entity): void;
|
|
35
|
+
/**
|
|
36
|
+
* Get all entities that have the given entity as their parent
|
|
37
|
+
* @public
|
|
38
|
+
* @param engine - the engine running the entities
|
|
39
|
+
* @param parent - the parent entity to find children for
|
|
40
|
+
* @returns An array of entities that have the given parent
|
|
41
|
+
*
|
|
42
|
+
* Example:
|
|
43
|
+
* ```ts
|
|
44
|
+
* const children = getEntitiesWithParent(engine, myEntity)
|
|
45
|
+
* for (const child of children) {
|
|
46
|
+
* // process each child entity
|
|
47
|
+
* }
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
export declare function getEntitiesWithParent(engine: Pick<IEngine, 'getEntitiesWith' | 'defineComponentFromSchema'>, parent: Entity): Entity[];
|
|
51
|
+
/** @public Engine type for world transform functions */
|
|
52
|
+
export type WorldTransformEngine = Pick<IEngine, 'getEntitiesWith' | 'defineComponentFromSchema' | 'PlayerEntity'>;
|
|
53
|
+
/**
|
|
54
|
+
* Get the world position of an entity, taking into account the full parent hierarchy.
|
|
55
|
+
* This computes the world-space position by accumulating all parent transforms
|
|
56
|
+
* (position, rotation, and scale).
|
|
57
|
+
*
|
|
58
|
+
* When the entity has AvatarAttach and Transform, the renderer updates the Transform
|
|
59
|
+
* with avatar-relative values (including the exact anchor point offset for hand, head, etc.).
|
|
60
|
+
* This function combines the player's transform with those values to compute the world position.
|
|
61
|
+
*
|
|
62
|
+
* @public
|
|
63
|
+
* @param engine - the engine running the entities
|
|
64
|
+
* @param entity - the entity to get the world position for
|
|
65
|
+
* @returns The entity's position in world space. Returns `{x: 0, y: 0, z: 0}` if the entity has no Transform.
|
|
66
|
+
*
|
|
67
|
+
* Example:
|
|
68
|
+
* ```ts
|
|
69
|
+
* const worldPos = getWorldPosition(engine, childEntity)
|
|
70
|
+
* console.log(`World position: ${worldPos.x}, ${worldPos.y}, ${worldPos.z}`)
|
|
71
|
+
* ```
|
|
72
|
+
*/
|
|
73
|
+
export declare function getWorldPosition(engine: WorldTransformEngine, entity: Entity): Vector3Type;
|
|
74
|
+
/**
|
|
75
|
+
* Get the world rotation of an entity, taking into account the full parent hierarchy.
|
|
76
|
+
* This computes the world-space rotation by combining all parent rotations.
|
|
77
|
+
*
|
|
78
|
+
* When the entity has AvatarAttach and Transform, the renderer updates the Transform
|
|
79
|
+
* with avatar-relative values (including the exact anchor point rotation for hand, head, etc.).
|
|
80
|
+
* This function combines the player's rotation with those values to compute the world rotation.
|
|
81
|
+
*
|
|
82
|
+
* @public
|
|
83
|
+
* @param engine - the engine running the entities
|
|
84
|
+
* @param entity - the entity to get the world rotation for
|
|
85
|
+
* @returns The entity's rotation in world space as a quaternion. Returns identity quaternion `{x: 0, y: 0, z: 0, w: 1}` if the entity has no Transform.
|
|
86
|
+
*
|
|
87
|
+
* Example:
|
|
88
|
+
* ```ts
|
|
89
|
+
* const worldRot = getWorldRotation(engine, childEntity)
|
|
90
|
+
* console.log(`World rotation: ${worldRot.x}, ${worldRot.y}, ${worldRot.z}, ${worldRot.w}`)
|
|
91
|
+
* ```
|
|
92
|
+
*/
|
|
93
|
+
export declare function getWorldRotation(engine: WorldTransformEngine, entity: Entity): QuaternionType;
|
|
@@ -1,4 +1,161 @@
|
|
|
1
1
|
import * as components from '../../components';
|
|
2
|
+
/**
|
|
3
|
+
* @internal
|
|
4
|
+
* Add two Vector3 values
|
|
5
|
+
*/
|
|
6
|
+
function addVectors(v1, v2) {
|
|
7
|
+
return {
|
|
8
|
+
x: v1.x + v2.x,
|
|
9
|
+
y: v1.y + v2.y,
|
|
10
|
+
z: v1.z + v2.z
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* @internal
|
|
15
|
+
* Multiply two Vector3 values element-wise (used for scaling)
|
|
16
|
+
*/
|
|
17
|
+
function multiplyVectors(v1, v2) {
|
|
18
|
+
return {
|
|
19
|
+
x: v1.x * v2.x,
|
|
20
|
+
y: v1.y * v2.y,
|
|
21
|
+
z: v1.z * v2.z
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* @internal
|
|
26
|
+
* Multiply two quaternions (combines rotations)
|
|
27
|
+
* Result represents applying q1 first, then q2
|
|
28
|
+
*/
|
|
29
|
+
function multiplyQuaternions(q1, q2) {
|
|
30
|
+
return {
|
|
31
|
+
x: q1.w * q2.x + q1.x * q2.w + q1.y * q2.z - q1.z * q2.y,
|
|
32
|
+
y: q1.w * q2.y - q1.x * q2.z + q1.y * q2.w + q1.z * q2.x,
|
|
33
|
+
z: q1.w * q2.z + q1.x * q2.y - q1.y * q2.x + q1.z * q2.w,
|
|
34
|
+
w: q1.w * q2.w - q1.x * q2.x - q1.y * q2.y - q1.z * q2.z
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* @internal
|
|
39
|
+
* Rotate a vector by a quaternion
|
|
40
|
+
* Uses the formula: v' = q * v * q^(-1), optimized version
|
|
41
|
+
*/
|
|
42
|
+
function rotateVectorByQuaternion(v, q) {
|
|
43
|
+
// Extract quaternion components
|
|
44
|
+
const qx = q.x, qy = q.y, qz = q.z, qw = q.w;
|
|
45
|
+
// Calculate cross product terms (q.xyz × v) * 2
|
|
46
|
+
const ix = qw * v.x + qy * v.z - qz * v.y;
|
|
47
|
+
const iy = qw * v.y + qz * v.x - qx * v.z;
|
|
48
|
+
const iz = qw * v.z + qx * v.y - qy * v.x;
|
|
49
|
+
const iw = -qx * v.x - qy * v.y - qz * v.z;
|
|
50
|
+
// Calculate final rotated vector
|
|
51
|
+
return {
|
|
52
|
+
x: ix * qw + iw * -qx + iy * -qz - iz * -qy,
|
|
53
|
+
y: iy * qw + iw * -qy + iz * -qx - ix * -qz,
|
|
54
|
+
z: iz * qw + iw * -qz + ix * -qy - iy * -qx
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
/** @internal Identity transform values */
|
|
58
|
+
const IDENTITY_POSITION = { x: 0, y: 0, z: 0 };
|
|
59
|
+
const IDENTITY_ROTATION = { x: 0, y: 0, z: 0, w: 1 };
|
|
60
|
+
const IDENTITY_SCALE = { x: 1, y: 1, z: 1 };
|
|
61
|
+
/**
|
|
62
|
+
* @internal
|
|
63
|
+
* Computes the world transform for an entity with AvatarAttach.
|
|
64
|
+
* If the entity has a Transform, the avatar-relative values (set by the renderer)
|
|
65
|
+
* are combined with the player's transform. Otherwise, returns the player's transform
|
|
66
|
+
* with identity scale.
|
|
67
|
+
*/
|
|
68
|
+
function computeAvatarAttachedWorldTransform(playerTransform, entityTransform) {
|
|
69
|
+
if (!entityTransform) {
|
|
70
|
+
return {
|
|
71
|
+
position: { ...playerTransform.position },
|
|
72
|
+
rotation: { ...playerTransform.rotation },
|
|
73
|
+
scale: { ...IDENTITY_SCALE }
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
const rotatedPosition = rotateVectorByQuaternion(entityTransform.position, playerTransform.rotation);
|
|
77
|
+
return {
|
|
78
|
+
position: addVectors(playerTransform.position, rotatedPosition),
|
|
79
|
+
rotation: multiplyQuaternions(playerTransform.rotation, entityTransform.rotation),
|
|
80
|
+
scale: entityTransform.scale
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* @internal
|
|
85
|
+
* Finds the transform of a player by their avatar ID.
|
|
86
|
+
* Returns the local player's transform if avatarId is undefined,
|
|
87
|
+
* or searches for a remote player by matching their address.
|
|
88
|
+
*/
|
|
89
|
+
function findPlayerTransform(Transform, PlayerIdentityData, localPlayerEntity, avatarId) {
|
|
90
|
+
// Local player (avatarId undefined)
|
|
91
|
+
if (avatarId === undefined) {
|
|
92
|
+
return Transform.getOrNull(localPlayerEntity);
|
|
93
|
+
}
|
|
94
|
+
// Remote player - find their entity by matching address
|
|
95
|
+
if (!PlayerIdentityData) {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
for (const [playerEntity, identityData] of PlayerIdentityData.iterator()) {
|
|
99
|
+
if (identityData.address === avatarId) {
|
|
100
|
+
return Transform.getOrNull(playerEntity);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* @internal
|
|
107
|
+
* Computes world position, rotation, and scale in a single hierarchy traversal.
|
|
108
|
+
* This is O(n) where n is the depth of the hierarchy.
|
|
109
|
+
*
|
|
110
|
+
* When an entity has AvatarAttach and Transform, the renderer updates the Transform
|
|
111
|
+
* with avatar-relative values (including the exact anchor point offset). This function
|
|
112
|
+
* combines the player's transform with the entity's avatar-relative transform to
|
|
113
|
+
* compute the world-space position.
|
|
114
|
+
*
|
|
115
|
+
* @throws Error if a circular dependency is detected in the entity hierarchy
|
|
116
|
+
*/
|
|
117
|
+
function getWorldTransformInternal(Transform, AvatarAttach, PlayerIdentityData, PlayerEntity, entity, visited = new Set()) {
|
|
118
|
+
const transform = Transform.getOrNull(entity);
|
|
119
|
+
const avatarAttach = AvatarAttach?.getOrNull(entity);
|
|
120
|
+
// Handle AvatarAttach: combine player's transform with the entity's avatar-relative transform
|
|
121
|
+
// (which the renderer updates with the exact anchor point offset for hand, head, etc.)
|
|
122
|
+
if (avatarAttach) {
|
|
123
|
+
const playerTransform = findPlayerTransform(Transform, PlayerIdentityData, PlayerEntity, avatarAttach.avatarId);
|
|
124
|
+
if (playerTransform) {
|
|
125
|
+
return computeAvatarAttachedWorldTransform(playerTransform, transform);
|
|
126
|
+
}
|
|
127
|
+
// Player's Transform not available, fall through to normal Transform handling
|
|
128
|
+
}
|
|
129
|
+
if (!transform) {
|
|
130
|
+
return {
|
|
131
|
+
position: { ...IDENTITY_POSITION },
|
|
132
|
+
rotation: { ...IDENTITY_ROTATION },
|
|
133
|
+
scale: { ...IDENTITY_SCALE }
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
if (!transform.parent) {
|
|
137
|
+
return {
|
|
138
|
+
position: { ...transform.position },
|
|
139
|
+
rotation: { ...transform.rotation },
|
|
140
|
+
scale: { ...transform.scale }
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
visited.add(entity);
|
|
144
|
+
if (visited.has(transform.parent)) {
|
|
145
|
+
throw new Error(`Circular dependency detected in entity hierarchy: entity ${entity} has ancestor ${transform.parent} which creates a cycle`);
|
|
146
|
+
}
|
|
147
|
+
const parentWorld = getWorldTransformInternal(Transform, AvatarAttach, PlayerIdentityData, PlayerEntity, transform.parent, visited);
|
|
148
|
+
const worldScale = multiplyVectors(parentWorld.scale, transform.scale);
|
|
149
|
+
const worldRotation = multiplyQuaternions(parentWorld.rotation, transform.rotation);
|
|
150
|
+
const scaledPosition = multiplyVectors(transform.position, parentWorld.scale);
|
|
151
|
+
const rotatedPosition = rotateVectorByQuaternion(scaledPosition, parentWorld.rotation);
|
|
152
|
+
const worldPosition = addVectors(parentWorld.position, rotatedPosition);
|
|
153
|
+
return {
|
|
154
|
+
position: worldPosition,
|
|
155
|
+
rotation: worldRotation,
|
|
156
|
+
scale: worldScale
|
|
157
|
+
};
|
|
158
|
+
}
|
|
2
159
|
function* genEntityTree(entity, entities) {
|
|
3
160
|
// This avoid infinite loop when there is a cyclic parenting
|
|
4
161
|
if (!entities.has(entity))
|
|
@@ -70,3 +227,84 @@ export function removeEntityWithChildren(engine, entity) {
|
|
|
70
227
|
engine.removeEntity(ent);
|
|
71
228
|
}
|
|
72
229
|
}
|
|
230
|
+
/**
|
|
231
|
+
* Get all entities that have the given entity as their parent
|
|
232
|
+
* @public
|
|
233
|
+
* @param engine - the engine running the entities
|
|
234
|
+
* @param parent - the parent entity to find children for
|
|
235
|
+
* @returns An array of entities that have the given parent
|
|
236
|
+
*
|
|
237
|
+
* Example:
|
|
238
|
+
* ```ts
|
|
239
|
+
* const children = getEntitiesWithParent(engine, myEntity)
|
|
240
|
+
* for (const child of children) {
|
|
241
|
+
* // process each child entity
|
|
242
|
+
* }
|
|
243
|
+
* ```
|
|
244
|
+
*/
|
|
245
|
+
export function getEntitiesWithParent(engine, parent) {
|
|
246
|
+
const Transform = components.Transform(engine);
|
|
247
|
+
const entitiesWithParent = [];
|
|
248
|
+
for (const [entity, transform] of engine.getEntitiesWith(Transform)) {
|
|
249
|
+
if (transform.parent === parent) {
|
|
250
|
+
entitiesWithParent.push(entity);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
return entitiesWithParent;
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* @internal
|
|
257
|
+
* Computes the world transform for an entity using the provided engine.
|
|
258
|
+
* This is a convenience wrapper that initializes the required components.
|
|
259
|
+
*/
|
|
260
|
+
function getWorldTransform(engine, entity) {
|
|
261
|
+
const Transform = components.Transform(engine);
|
|
262
|
+
const AvatarAttach = components.AvatarAttach(engine);
|
|
263
|
+
const PlayerIdentityData = components.PlayerIdentityData(engine);
|
|
264
|
+
return getWorldTransformInternal(Transform, AvatarAttach, PlayerIdentityData, engine.PlayerEntity, entity);
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Get the world position of an entity, taking into account the full parent hierarchy.
|
|
268
|
+
* This computes the world-space position by accumulating all parent transforms
|
|
269
|
+
* (position, rotation, and scale).
|
|
270
|
+
*
|
|
271
|
+
* When the entity has AvatarAttach and Transform, the renderer updates the Transform
|
|
272
|
+
* with avatar-relative values (including the exact anchor point offset for hand, head, etc.).
|
|
273
|
+
* This function combines the player's transform with those values to compute the world position.
|
|
274
|
+
*
|
|
275
|
+
* @public
|
|
276
|
+
* @param engine - the engine running the entities
|
|
277
|
+
* @param entity - the entity to get the world position for
|
|
278
|
+
* @returns The entity's position in world space. Returns `{x: 0, y: 0, z: 0}` if the entity has no Transform.
|
|
279
|
+
*
|
|
280
|
+
* Example:
|
|
281
|
+
* ```ts
|
|
282
|
+
* const worldPos = getWorldPosition(engine, childEntity)
|
|
283
|
+
* console.log(`World position: ${worldPos.x}, ${worldPos.y}, ${worldPos.z}`)
|
|
284
|
+
* ```
|
|
285
|
+
*/
|
|
286
|
+
export function getWorldPosition(engine, entity) {
|
|
287
|
+
return getWorldTransform(engine, entity).position;
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Get the world rotation of an entity, taking into account the full parent hierarchy.
|
|
291
|
+
* This computes the world-space rotation by combining all parent rotations.
|
|
292
|
+
*
|
|
293
|
+
* When the entity has AvatarAttach and Transform, the renderer updates the Transform
|
|
294
|
+
* with avatar-relative values (including the exact anchor point rotation for hand, head, etc.).
|
|
295
|
+
* This function combines the player's rotation with those values to compute the world rotation.
|
|
296
|
+
*
|
|
297
|
+
* @public
|
|
298
|
+
* @param engine - the engine running the entities
|
|
299
|
+
* @param entity - the entity to get the world rotation for
|
|
300
|
+
* @returns The entity's rotation in world space as a quaternion. Returns identity quaternion `{x: 0, y: 0, z: 0, w: 1}` if the entity has no Transform.
|
|
301
|
+
*
|
|
302
|
+
* Example:
|
|
303
|
+
* ```ts
|
|
304
|
+
* const worldRot = getWorldRotation(engine, childEntity)
|
|
305
|
+
* console.log(`World rotation: ${worldRot.x}, ${worldRot.y}, ${worldRot.z}, ${worldRot.w}`)
|
|
306
|
+
* ```
|
|
307
|
+
*/
|
|
308
|
+
export function getWorldRotation(engine, entity) {
|
|
309
|
+
return getWorldTransform(engine, entity).rotation;
|
|
310
|
+
}
|
|
@@ -10,6 +10,7 @@ import { RaycastSystem } from '../../systems/raycast';
|
|
|
10
10
|
import { VideoEventsSystem } from '../../systems/videoEvents';
|
|
11
11
|
import { TweenSystem } from '../../systems/tween';
|
|
12
12
|
import { TriggerAreaEventsSystem } from '../../systems/triggerArea';
|
|
13
|
+
import { createTimers, Timers } from '../helpers/timers';
|
|
13
14
|
/**
|
|
14
15
|
* @public
|
|
15
16
|
* The engine is the part of the scene that sits in the middle and manages all of the other parts.
|
|
@@ -63,6 +64,12 @@ export { TweenSystem };
|
|
|
63
64
|
*/
|
|
64
65
|
export declare const triggerAreaEventsSystem: TriggerAreaEventsSystem;
|
|
65
66
|
export { TriggerAreaEventsSystem };
|
|
67
|
+
/**
|
|
68
|
+
* @public
|
|
69
|
+
* Timer utilities for delayed and repeated execution.
|
|
70
|
+
*/
|
|
71
|
+
export declare const timers: Timers;
|
|
72
|
+
export { Timers, createTimers };
|
|
66
73
|
/**
|
|
67
74
|
* @public
|
|
68
75
|
* Runs an async function
|
|
@@ -11,6 +11,7 @@ import { createVideoEventsSystem } from '../../systems/videoEvents';
|
|
|
11
11
|
import { createTweenSystem } from '../../systems/tween';
|
|
12
12
|
import { pointerEventColliderChecker } from '../../systems/pointer-event-collider-checker';
|
|
13
13
|
import { createTriggerAreaEventsSystem } from '../../systems/triggerArea';
|
|
14
|
+
import { createTimers } from '../helpers/timers';
|
|
14
15
|
/**
|
|
15
16
|
* @public
|
|
16
17
|
* The engine is the part of the scene that sits in the middle and manages all of the other parts.
|
|
@@ -58,6 +59,16 @@ export const tweenSystem = createTweenSystem(engine);
|
|
|
58
59
|
* Register callback functions for trigger area results.
|
|
59
60
|
*/
|
|
60
61
|
export const triggerAreaEventsSystem = /* @__PURE__ */ createTriggerAreaEventsSystem(engine);
|
|
62
|
+
/**
|
|
63
|
+
* @public
|
|
64
|
+
* Timer utilities for delayed and repeated execution.
|
|
65
|
+
*/
|
|
66
|
+
export const timers = /* @__PURE__ */ createTimers(engine);
|
|
67
|
+
export { createTimers };
|
|
68
|
+
globalThis.setTimeout = globalThis.setTimeout ?? timers.setTimeout;
|
|
69
|
+
globalThis.clearTimeout = globalThis.clearTimeout ?? timers.clearTimeout;
|
|
70
|
+
globalThis.setInterval = globalThis.setInterval ?? timers.setInterval;
|
|
71
|
+
globalThis.clearInterval = globalThis.clearInterval ?? timers.clearInterval;
|
|
61
72
|
/**
|
|
62
73
|
* Adds pointer event collider system only in DEV env
|
|
63
74
|
*/
|
package/dist/systems/events.d.ts
CHANGED
package/dist/systems/events.js
CHANGED
|
@@ -32,7 +32,8 @@ export function createPointerEventsSystem(engine, inputSystem) {
|
|
|
32
32
|
showFeedback: opts.showFeedback,
|
|
33
33
|
showHighlight: opts.showHighlight,
|
|
34
34
|
hoverText: opts.hoverText,
|
|
35
|
-
maxDistance: opts.maxDistance
|
|
35
|
+
maxDistance: opts.maxDistance,
|
|
36
|
+
maxPlayerDistance: opts.maxPlayerDistance
|
|
36
37
|
}
|
|
37
38
|
});
|
|
38
39
|
}
|