Skip to content

Commit 8337da6

Browse files
Chau TranChau Tran
Chau Tran
authored and
Chau Tran
committed
wip
1 parent e346e09 commit 8337da6

16 files changed

+17655
-20219
lines changed

libs/core/src/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +0,0 @@
1-
export * from './lib/core/core.component';

libs/core/src/lib/core/core.component.spec.ts

Lines changed: 0 additions & 21 deletions
This file was deleted.

libs/core/src/lib/core/core.component.ts

Lines changed: 0 additions & 11 deletions
This file was deleted.

libs/core/src/lib/di.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { createInjectionToken } from './utils';
2+
3+
const catalogue: Record<string, new (...args: any[]) => any> = {};
4+
5+
export function extend(objects: object): void {
6+
Object.assign(catalogue, objects);
7+
}
8+
9+
export const [injectNgtCatalogue, provideNgtCatalogue, NGT_CATALOGUE] = createInjectionToken(() => catalogue);

libs/core/src/lib/events.ts

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import type { NgtCameraManual, NgtState } from './store';
2+
import type { NgtProperties } from './types';
3+
import type { NgtSignalStore } from './utils';
4+
5+
export const supportedDomEvents = [
6+
'click',
7+
'contextmenu',
8+
'dblclick',
9+
'pointerup',
10+
'pointerdown',
11+
'pointerover',
12+
'pointerout',
13+
'pointerenter',
14+
'pointerleave',
15+
'pointermove',
16+
'pointermissed',
17+
'pointercancel',
18+
'wheel',
19+
] as const;
20+
21+
export type NgtIntersection = THREE.Intersection & {
22+
/** The event source (the object which registered the handler) */
23+
eventObject: THREE.Object3D;
24+
};
25+
26+
export type NgtIntersectionEvent<TSourceEvent> = NgtIntersection & {
27+
/** The event source (the object which registered the handler) */
28+
eventObject: THREE.Object3D;
29+
/** An array of intersections */
30+
intersections: NgtIntersection[];
31+
/** vec3.set(pointer.x, pointer.y, 0).unproject(camera) */
32+
unprojectedPoint: THREE.Vector3;
33+
/** Normalized event coordinates */
34+
pointer: THREE.Vector2;
35+
/** Delta between first click and this event */
36+
delta: number;
37+
/** The ray that pierced it */
38+
ray: THREE.Ray;
39+
/** The camera that was used by the raycaster */
40+
camera: NgtCameraManual;
41+
/** stopPropagation will stop underlying handlers from firing */
42+
stopPropagation: () => void;
43+
/** The original host event */
44+
nativeEvent: TSourceEvent;
45+
/** If the event was stopped by calling stopPropagation */
46+
stopped: boolean;
47+
};
48+
49+
export type NgtThreeEvent<TEvent> = NgtIntersectionEvent<TEvent> & NgtProperties<TEvent>;
50+
export type NgtDomEvent = PointerEvent | MouseEvent | WheelEvent;
51+
52+
export type NgtEventHandlers = {
53+
click?: (event: NgtThreeEvent<MouseEvent>) => void;
54+
contextmenu?: (event: NgtThreeEvent<MouseEvent>) => void;
55+
dblclick?: (event: NgtThreeEvent<MouseEvent>) => void;
56+
pointerup?: (event: NgtThreeEvent<PointerEvent>) => void;
57+
pointerdown?: (event: NgtThreeEvent<PointerEvent>) => void;
58+
pointerover?: (event: NgtThreeEvent<PointerEvent>) => void;
59+
pointerout?: (event: NgtThreeEvent<PointerEvent>) => void;
60+
pointerenter?: (event: NgtThreeEvent<PointerEvent>) => void;
61+
pointerleave?: (event: NgtThreeEvent<PointerEvent>) => void;
62+
pointermove?: (event: NgtThreeEvent<PointerEvent>) => void;
63+
pointermissed?: (event: MouseEvent) => void;
64+
pointercancel?: (event: NgtThreeEvent<PointerEvent>) => void;
65+
wheel?: (event: NgtThreeEvent<WheelEvent>) => void;
66+
};
67+
68+
export type NgtEvents = {
69+
[TEvent in keyof NgtEventHandlers]-?: EventListener;
70+
};
71+
72+
export type NgtPointerCaptureTarget = {
73+
intersection: NgtIntersection;
74+
target: Element;
75+
};
76+
77+
export type NgtFilterFunction = (items: THREE.Intersection[], store: NgtSignalStore<NgtState>) => THREE.Intersection[];
78+
export type NgtComputeFunction = (
79+
event: NgtDomEvent,
80+
store: NgtSignalStore<NgtState>,
81+
previous?: NgtSignalStore<NgtState>
82+
) => void;
83+
84+
export interface NgtEventManager<TTarget> {
85+
/** Determines if the event layer is active */
86+
enabled: boolean;
87+
/** Event layer priority, higher prioritized layers come first and may stop(-propagate) lower layer */
88+
priority: number;
89+
/** The compute function needs to set up the raycaster and an xy- pointer */
90+
compute?: NgtComputeFunction;
91+
/** The filter can re-order or re-structure the intersections */
92+
filter?: NgtFilterFunction;
93+
/** The target node the event layer is tied to */
94+
connected?: TTarget;
95+
/** All the pointer event handlers through which the host forwards native events */
96+
handlers?: NgtEvents;
97+
/** Allows re-connecting to another target */
98+
connect?: (target: TTarget) => void;
99+
/** Removes all existing events handlers from the target */
100+
disconnect?: () => void;
101+
/** Triggers a onPointerMove with the last known event. This can be useful to enable raycasting without
102+
* explicit user interaction, for instance when the camera moves a hoverable object underneath the cursor.
103+
*/
104+
update?: () => void;
105+
}

libs/core/src/lib/instance.ts

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
import { EventEmitter, WritableSignal, signal, untracked } from '@angular/core';
2+
import { type NgtEventHandlers } from './events';
3+
import { type NgtState } from './store';
4+
import type { NgtAnyRecord } from './types';
5+
import { checkUpdate, signalStore, type NgtSignalStore } from './utils';
6+
7+
export type NgtAttachFunction<TChild = any, TParent = any> = (
8+
parent: TParent,
9+
child: TChild,
10+
store: NgtSignalStore<NgtState>
11+
) => void | (() => void);
12+
13+
export type NgtAfterAttach<
14+
TParent extends NgtInstanceNode = NgtInstanceNode,
15+
TChild extends NgtInstanceNode = NgtInstanceNode
16+
> = { parent: TParent; node: TChild };
17+
18+
export type NgtInstanceLocalState = {
19+
/** the state getter of the canvas that the instance is being rendered to */
20+
store: NgtSignalStore<NgtState>;
21+
// objects and parent are used when children are added with `attach` instead of being added to the Object3D scene graph
22+
nonObjects: WritableSignal<NgtInstanceNode[]>;
23+
// objects that are Object3D
24+
objects: WritableSignal<NgtInstanceNode[]>;
25+
// shortcut to add/remove object to list
26+
add: (instance: NgtInstanceNode, type: 'objects' | 'nonObjects') => void;
27+
remove: (instance: NgtInstanceNode, type: 'objects' | 'nonObjects') => void;
28+
// native props signal
29+
nativeProps: NgtSignalStore<NgtAnyRecord>;
30+
// parent based on attach three instance
31+
parent: WritableSignal<NgtInstanceNode | null>;
32+
// if this THREE instance is a ngt-primitive
33+
primitive?: boolean;
34+
// if this THREE object has any events bound to it
35+
eventCount: number;
36+
// list of handlers to handle the events
37+
handlers: Partial<NgtEventHandlers>;
38+
// previous args
39+
args?: unknown[];
40+
// attach information so that we can detach as well as reset
41+
attach?: string[] | NgtAttachFunction;
42+
// previously attach information so we can reset as well as clean up
43+
previousAttach?: unknown | (() => void);
44+
// is raw value
45+
isRaw?: boolean;
46+
// priority for before render
47+
priority?: number;
48+
// emitter after props update
49+
afterUpdate?: EventEmitter<NgtInstanceNode>;
50+
// emitter after attaching to parent
51+
afterAttach?: EventEmitter<NgtAfterAttach>;
52+
};
53+
54+
export type NgtInstanceNode<TNode = any> = {
55+
__ngt__: NgtInstanceLocalState;
56+
} & NgtAnyRecord &
57+
TNode;
58+
59+
export function getLocalState<TInstance extends object = NgtAnyRecord>(
60+
obj: TInstance | undefined
61+
): NgtInstanceLocalState {
62+
if (!obj) return {} as unknown as NgtInstanceLocalState;
63+
return (obj as NgtAnyRecord)['__ngt__'] || ({} as NgtInstanceLocalState);
64+
}
65+
66+
export function invalidateInstance<TInstance extends object>(instance: TInstance) {
67+
const state = getLocalState(instance).store?.get();
68+
if (state && state.internal.frames === 0) state.invalidate();
69+
checkUpdate(instance);
70+
}
71+
72+
export function prepare<TInstance extends object = NgtAnyRecord>(
73+
object: TInstance,
74+
localState?: Partial<NgtInstanceLocalState>
75+
): NgtInstanceNode<TInstance> {
76+
const instance = object as unknown as NgtInstanceNode<TInstance>;
77+
78+
if (localState?.primitive || !instance.__ngt__) {
79+
const {
80+
objects = signal<NgtInstanceNode[]>([]),
81+
nonObjects = signal<NgtInstanceNode[]>([]),
82+
...rest
83+
} = localState || {};
84+
85+
instance.__ngt__ = {
86+
previousAttach: null,
87+
store: null,
88+
parent: signal(null),
89+
memoized: {},
90+
eventCount: 0,
91+
handlers: {},
92+
objects,
93+
nonObjects,
94+
nativeProps: signalStore<NgtAnyRecord>(),
95+
add: (object, type) => {
96+
untracked(() => {
97+
const current = untracked(instance.__ngt__[type]);
98+
const foundIndex = current.indexOf((obj: NgtInstanceNode) => obj === object);
99+
if (foundIndex > -1) {
100+
// if we add an object with the same reference, then we switch it out
101+
// and update the BehaviorSubject
102+
current.splice(foundIndex, 1, object);
103+
instance.__ngt__[type].set(current);
104+
} else {
105+
instance.__ngt__[type].update((prev) => [...prev, object]);
106+
}
107+
notifyAncestors(untracked(instance.__ngt__.parent));
108+
});
109+
},
110+
remove: (object, type) => {
111+
untracked(() => {
112+
instance.__ngt__[type].update((prev) => prev.filter((o) => o !== object));
113+
notifyAncestors(untracked(instance.__ngt__.parent));
114+
});
115+
},
116+
...rest,
117+
} as NgtInstanceLocalState;
118+
}
119+
120+
return instance;
121+
}
122+
123+
function notifyAncestors(instance: NgtInstanceNode | null) {
124+
if (!instance) return;
125+
const localState = getLocalState(instance);
126+
if (localState.objects) localState.objects.update((prev) => prev);
127+
if (localState.nonObjects) localState.nonObjects.update((prev) => prev);
128+
notifyAncestors(untracked(localState.parent));
129+
}

0 commit comments

Comments
 (0)