Skip to content

Commit ddeacf1

Browse files
committed
animations fbo depth buffer
1 parent 73e5e78 commit ddeacf1

File tree

15 files changed

+340
-36
lines changed

15 files changed

+340
-36
lines changed

apps/sandbox/src/app/app.component.ts

Lines changed: 12 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,16 @@
11
import { NgIf } from '@angular/common';
22
import { CUSTOM_ELEMENTS_SCHEMA, Component, computed, signal } from '@angular/core';
3-
import { NgtArgs, NgtBeforeRenderEvent, NgtCanvas, extend, injectNgtLoader } from 'angular-three';
3+
import { NgtArgs, NgtCanvas, extend } from 'angular-three';
4+
import { NgtsOrbitControls } from 'angular-three-soba/controls';
5+
import { injectNgtsGLTFLoader } from 'angular-three-soba/loaders';
6+
import { injectNgtsAnimations } from 'angular-three-soba/misc';
47
import * as THREE from 'three';
5-
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
68

79
extend(THREE);
810

911
@Component({
1012
standalone: true,
1113
template: `
12-
<!-- <ngt-spot-light #spotLight [position]="2" [intensity]="10" /> -->
13-
<!-- <ngt-spot-light-helper *args="[spotLight, 'blue']" /> -->
14-
<!---->
15-
<!-- <ngt-point-light #pointLight [position]="-2" [intensity]="10" /> -->
16-
<!-- <ngt-point-light-helper *args="[pointLight, 1, 'green']" /> -->
17-
18-
<!-- <ngt-mesh -->
19-
<!-- (click)="active.set(!active())" -->
20-
<!-- (pointerover)="hover.set(true)" -->
21-
<!-- (pointerout)="hover.set(false)" -->
22-
<!-- (beforeRender)="onBeforeRender($event.object)" -->
23-
<!-- [scale]="active() ? 1.5 : 1" -->
24-
<!-- > -->
25-
<!-- <ngt-box-geometry /> -->
26-
<!-- <ngt-mesh-standard-material [color]="hover() ? 'hotpink' : 'orange'" /> -->
27-
<!-- </ngt-mesh> -->
28-
2914
<ngt-spot-light [position]="3" [castShadow]="true">
3015
<ngt-vector2 *args="[512, 512]" attach="shadow.mapSize" />
3116
</ngt-spot-light>
@@ -37,13 +22,16 @@ extend(THREE);
3722
3823
<ngt-primitive
3924
*args="[cloud()]"
25+
[ref]="animations.ref"
4026
[scale]="0.01"
4127
[rotation]="[0, -Math.PI / 2, 0]"
4228
[position]="[0, -1, 0]"
43-
(beforeRender)="onBeforeRender($event)"
29+
(beforeRender)="onBeforeRender($event.object)"
4430
/>
31+
32+
<ngts-orbit-controls />
4533
`,
46-
imports: [NgtArgs],
34+
imports: [NgtArgs, NgtsOrbitControls],
4735
schemas: [CUSTOM_ELEMENTS_SCHEMA],
4836
})
4937
export class Scene {
@@ -52,32 +40,24 @@ export class Scene {
5240
active = signal(false);
5341
hover = signal(false);
5442

55-
cloudGltf = injectNgtLoader(
56-
() => GLTFLoader,
57-
() => 'assets/cloud_from_world_of_final_fantasy/scene.gltf',
58-
);
59-
60-
mixer?: THREE.AnimationMixer;
43+
private cloudGltf = injectNgtsGLTFLoader(() => 'assets/cloud_from_world_of_final_fantasy/scene.gltf');
6144

45+
animations = injectNgtsAnimations(() => this.cloudGltf()?.animations || []);
6246
cloud = computed(() => {
6347
const gltf = this.cloudGltf();
6448
if (gltf) {
6549
gltf.scene.traverse((child) => {
6650
if (child instanceof THREE.Mesh) child.castShadow = true;
6751
});
6852

69-
this.mixer = new THREE.AnimationMixer(gltf.scene);
70-
this.mixer.clipAction(gltf.animations[0]).play();
71-
7253
return gltf.scene;
7354
}
7455

7556
return null;
7657
});
7758

78-
onBeforeRender({ object: cloud, state: { delta } }: NgtBeforeRenderEvent) {
59+
onBeforeRender(cloud: THREE.Group) {
7960
cloud.rotation.y += 0.005;
80-
this.mixer?.update(delta);
8161
}
8262
}
8363

libs/core/src/lib/three-types.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import * as THREE from 'three';
22
import type { NgtEventHandlers } from './events';
33
import type { NgtAfterAttach, NgtAttachFunction, NgtInstanceNode } from './instance';
44
import type { NgtBeforeRenderEvent } from './store';
5-
import type { NgtAnyRecord } from './types';
65

76
type NoEvent<T> = Omit<T, 'addEventListener' | 'removeEventListener'>;
87

@@ -161,7 +160,7 @@ export type NgtMeshMatcapMaterial = NgtMaterial<THREE.MeshMatcapMaterial, [THREE
161160
export type NgtLineDashedMaterial = NgtMaterial<THREE.LineDashedMaterial, [THREE.LineDashedMaterialParameters]>;
162161
export type NgtLineBasicMaterial = NgtMaterial<THREE.LineBasicMaterial, [THREE.LineBasicMaterialParameters]>;
163162

164-
export type NgtPrimitive = NgtNodeElement<NgtAnyRecord, any>;
163+
export type NgtPrimitive = NgtNodeElement<any, any>;
165164
export type NgtValue = NgtNode<{ rawValue: any }, {}>;
166165

167166
export type NgtLightShadow = NgtNode<THREE.LightShadow, typeof THREE.LightShadow>;

libs/soba/loaders/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# angular-three-soba/loaders
2+
3+
Secondary entry point of `angular-three-soba`. It can be used by importing from `angular-three-soba/loaders`.

libs/soba/loaders/ng-package.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"lib": {
3+
"entryFile": "src/index.ts"
4+
}
5+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import type { Injector, Signal } from '@angular/core';
2+
import { injectNgtLoader, type NgtLoaderResults, type NgtObjectMap } from 'angular-three';
3+
// @ts-ignore
4+
import { MeshoptDecoder } from 'three-stdlib';
5+
import { DRACOLoader } from 'three-stdlib/loaders/DRACOLoader';
6+
import { GLTF, GLTFLoader } from 'three-stdlib/loaders/GLTFLoader';
7+
8+
let dracoLoader: DRACOLoader | null = null;
9+
10+
function _extensions(useDraco: boolean | string, useMeshOpt: boolean, extensions?: (loader: GLTFLoader) => void) {
11+
return (loader: THREE.Loader) => {
12+
if (extensions) {
13+
extensions(loader as GLTFLoader);
14+
}
15+
16+
if (useDraco) {
17+
if (!dracoLoader) {
18+
dracoLoader = new DRACOLoader();
19+
}
20+
21+
dracoLoader.setDecoderPath(
22+
typeof useDraco === 'string' ? useDraco : 'https://www.gstatic.com/draco/versioned/decoders/1.4.3/',
23+
);
24+
(loader as GLTFLoader).setDRACOLoader(dracoLoader);
25+
}
26+
27+
if (useMeshOpt) {
28+
(loader as GLTFLoader).setMeshoptDecoder(
29+
typeof MeshoptDecoder === 'function' ? MeshoptDecoder() : MeshoptDecoder,
30+
);
31+
}
32+
};
33+
}
34+
35+
export function injectNgtsGLTFLoader<TUrl extends string | string[] | Record<string, string>>(
36+
path: () => TUrl,
37+
{
38+
useDraco = true,
39+
useMeshOpt = true,
40+
injector,
41+
extensions,
42+
}: {
43+
useDraco?: boolean | string;
44+
useMeshOpt?: boolean;
45+
injector?: Injector;
46+
extensions?: (loader: GLTFLoader) => void;
47+
} = {},
48+
): Signal<NgtLoaderResults<TUrl, GLTF & NgtObjectMap> | null> {
49+
return injectNgtLoader(() => GLTFLoader, path, {
50+
extensions: _extensions(useDraco, useMeshOpt, extensions),
51+
injector,
52+
});
53+
}
54+
55+
injectNgtsGLTFLoader['preload'] = <TUrl extends string | string[] | Record<string, string>>(
56+
path: () => TUrl,
57+
{
58+
useDraco = true,
59+
useMeshOpt = true,
60+
extensions,
61+
}: {
62+
useDraco?: boolean | string;
63+
useMeshOpt?: boolean;
64+
extensions?: (loader: GLTFLoader) => void;
65+
} = {},
66+
) => {
67+
(injectNgtLoader as any)['preload'](() => GLTFLoader, path, _extensions(useDraco, useMeshOpt, extensions));
68+
};

libs/soba/loaders/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './gltf-loader/gltf-loader';

libs/soba/misc/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# angular-three-soba/misc
2+
3+
Secondary entry point of `angular-three-soba`. It can be used by importing from `angular-three-soba/misc`.

libs/soba/misc/ng-package.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"lib": {
3+
"entryFile": "src/index.ts"
4+
}
5+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import { DestroyRef, Injector, effect, inject, runInInjectionContext } from '@angular/core';
2+
import { assertInjectionContext, injectBeforeRender, injectNgtRef, type NgtInjectedRef } from 'angular-three';
3+
import * as THREE from 'three';
4+
5+
export function injectNgtsAnimations(
6+
animationsFactory: () => THREE.AnimationClip[],
7+
{
8+
ref,
9+
injector,
10+
playFirstClip = true,
11+
}: { ref?: NgtInjectedRef<THREE.Object3D> | THREE.Object3D; playFirstClip?: boolean; injector?: Injector } = {},
12+
) {
13+
injector = assertInjectionContext(injectNgtsAnimations, injector);
14+
return runInInjectionContext(injector, () => {
15+
let actualRef = injectNgtRef<THREE.Object3D>();
16+
17+
if (ref) {
18+
if (ref instanceof THREE.Object3D) {
19+
actualRef.nativeElement = ref;
20+
} else {
21+
actualRef = ref;
22+
}
23+
}
24+
25+
const mixer = new THREE.AnimationMixer(null!);
26+
const actions = {} as Record<string, THREE.AnimationAction>;
27+
let cached = {} as Record<string, THREE.AnimationAction>;
28+
29+
const clips = [] as THREE.AnimationClip[];
30+
const names = [] as string[];
31+
32+
inject(DestroyRef).onDestroy(() => {
33+
// clear cached
34+
cached = {};
35+
// uncache actions
36+
Object.values(actions).forEach((action) => {
37+
mixer.uncacheAction(action as unknown as THREE.AnimationClip, actualRef.untracked);
38+
});
39+
// stop all actions
40+
mixer.stopAllAction();
41+
});
42+
43+
injectBeforeRender(({ delta }) => mixer.update(delta));
44+
45+
effect(() => {
46+
const actual = actualRef.nativeElement;
47+
if (!actual) return;
48+
const animations = animationsFactory();
49+
50+
for (let i = 0; i < animations.length; i++) {
51+
const clip = animations[i];
52+
53+
names.push(clip.name);
54+
clips.push(clip);
55+
56+
Object.defineProperty(actions, clip.name, {
57+
enumerable: true,
58+
get: () => {
59+
return cached[clip.name] || (cached[clip.name] = mixer.clipAction(clip, actual));
60+
},
61+
});
62+
63+
if (i === 0 && playFirstClip) {
64+
actions[clip.name].play();
65+
}
66+
}
67+
});
68+
69+
return { ref: actualRef, actions, mixer, names, clips };
70+
});
71+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { Directive, effect } from '@angular/core';
2+
import { injectNgtStore } from 'angular-three';
3+
4+
@Directive({ selector: 'ngts-bake-shadows', standalone: true })
5+
export class NgtsBakeShadows {
6+
constructor() {
7+
const store = injectNgtStore();
8+
const glShadowMap = store.select('gl', 'shadowMap');
9+
effect((onCleanup) => {
10+
const shadowMap = glShadowMap();
11+
shadowMap.autoUpdate = false;
12+
shadowMap.needsUpdate = true;
13+
onCleanup(() => {
14+
shadowMap.autoUpdate = shadowMap.needsUpdate = true;
15+
});
16+
});
17+
}
18+
}

0 commit comments

Comments
 (0)