Skip to content

Commit 25d69b6

Browse files
Chau TranChau Tran
Chau Tran
authored and
Chau Tran
committed
feat(soba): migrate cameras
1 parent c7109ca commit 25d69b6

File tree

13 files changed

+312
-1
lines changed

13 files changed

+312
-1
lines changed

libs/soba/cameras/README.md

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

libs/soba/cameras/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: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { Directive, inject, Input, TemplateRef } from '@angular/core';
2+
import * as THREE from 'three';
3+
4+
@Directive({ selector: 'ng-template[ngtsCameraContent]', standalone: true })
5+
export class NgtsCameraContent {
6+
readonly template = inject(TemplateRef);
7+
@Input() ngtsCameraContent: true | '' = '';
8+
9+
static ngTemplateContextGuard(
10+
_: NgtsCameraContent,
11+
ctx: unknown
12+
): ctx is { fbo: THREE.WebGLRenderTarget; group?: THREE.Group } {
13+
return true;
14+
}
15+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import { Directive, effect, inject, Input } from '@angular/core';
2+
import { injectNgtRef, NgtSignalStore, NgtStore, type NgtCamera } from 'angular-three';
3+
import { injectNgtsFBO } from 'angular-three-soba/misc';
4+
5+
export interface NgtsCameraState {
6+
makeDefault: boolean;
7+
manual: boolean;
8+
frames: number;
9+
resolution: number;
10+
envMap?: THREE.Texture;
11+
}
12+
13+
@Directive()
14+
export abstract class NgtsCamera<TCamera extends NgtCamera> extends NgtSignalStore<NgtsCameraState> {
15+
@Input() set makeDefault(makeDefault: boolean) {
16+
this.set({ makeDefault });
17+
}
18+
19+
@Input() set manual(manual: boolean) {
20+
this.set({ manual });
21+
}
22+
23+
@Input() set frames(frames: number) {
24+
this.set({ frames });
25+
}
26+
27+
@Input() set resolution(resolution: number) {
28+
this.set({ resolution });
29+
}
30+
31+
@Input() set envMap(envMap: THREE.Texture) {
32+
this.set({ envMap });
33+
}
34+
35+
@Input() cameraRef = injectNgtRef<TCamera>();
36+
37+
protected readonly store = inject(NgtStore);
38+
readonly fboRef = injectNgtsFBO(() => {
39+
const resolution = this.select('resolution');
40+
return { width: resolution() };
41+
});
42+
43+
constructor() {
44+
super({ resolution: 256, frames: Infinity, makeDefault: false, manual: false });
45+
this.#setDefaultCamera();
46+
this.#updateProjectionMatrix();
47+
}
48+
49+
#setDefaultCamera() {
50+
effect(
51+
(onCleanup) => {
52+
const camera = this.cameraRef.nativeElement;
53+
const makeDefault = this.select('makeDefault');
54+
if (camera && makeDefault()) {
55+
const { camera: oldCamera } = this.store.get();
56+
this.store.set({ camera });
57+
onCleanup(() => this.store.set({ camera: oldCamera }));
58+
}
59+
},
60+
{ allowSignalWrites: true }
61+
);
62+
}
63+
64+
#updateProjectionMatrix() {
65+
effect(() => {
66+
const camera = this.cameraRef.nativeElement;
67+
const manual = this.select('manual');
68+
if (!manual() && camera) camera.updateProjectionMatrix();
69+
});
70+
}
71+
}

libs/soba/cameras/src/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export * from './camera/camera';
2+
export * from './camera/camera-content';
3+
export * from './orthographic-camera/orthographic-camera';
4+
export * from './perspective-camera/perspective-camera';
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import { NgIf, NgTemplateOutlet } from '@angular/common';
2+
import { CUSTOM_ELEMENTS_SCHEMA, Component, ContentChild, Input, computed } from '@angular/core';
3+
import { extend } from 'angular-three';
4+
import { Group, OrthographicCamera } from 'three';
5+
import { NgtsCamera } from '../camera/camera';
6+
import { NgtsCameraContent } from '../camera/camera-content';
7+
8+
extend({ OrthographicCamera, Group });
9+
10+
declare module '../camera/camera' {
11+
interface NgtsCameraState {
12+
left: number;
13+
top: number;
14+
right: number;
15+
bottom: number;
16+
}
17+
}
18+
19+
@Component({
20+
selector: 'ngts-orthographic-camera',
21+
standalone: true,
22+
template: `
23+
<ngt-orthographic-camera
24+
ngtCompound
25+
[ref]="cameraRef"
26+
[left]="cameraLeft()"
27+
[right]="cameraRight()"
28+
[top]="cameraTop()"
29+
[bottom]="cameraBottom()"
30+
>
31+
<ng-container
32+
*ngIf="cameraContent && !cameraContent.ngtsCameraContent"
33+
[ngTemplateOutlet]="cameraContent.template"
34+
/>
35+
</ngt-orthographic-camera>
36+
<ngt-group #group *ngIf="cameraContent && cameraContent.ngtsCameraContent">
37+
<ng-container *ngTemplateOutlet="cameraContent.template; context: { fbo: fboRef.nativeElement, group }" />
38+
</ngt-group>
39+
`,
40+
imports: [NgIf, NgTemplateOutlet],
41+
schemas: [CUSTOM_ELEMENTS_SCHEMA],
42+
})
43+
export class NgtsOrthographicCamera extends NgtsCamera<THREE.OrthographicCamera> {
44+
@ContentChild(NgtsCameraContent) cameraContent?: NgtsCameraContent;
45+
46+
@Input() set left(left: number) {
47+
this.set({ left });
48+
}
49+
50+
@Input() set right(right: number) {
51+
this.set({ right });
52+
}
53+
54+
@Input() set top(top: number) {
55+
this.set({ top });
56+
}
57+
58+
@Input() set bottom(bottom: number) {
59+
this.set({ bottom });
60+
}
61+
62+
readonly cameraLeft = computed(() => {
63+
const left = this.select('left');
64+
const size = this.store.select('size');
65+
return left() || size().width / -2;
66+
});
67+
68+
readonly cameraRight = computed(() => {
69+
const right = this.select('right');
70+
const size = this.store.select('size');
71+
return right() || size().width / 2;
72+
});
73+
74+
readonly cameraTop = computed(() => {
75+
const top = this.select('top');
76+
const size = this.store.select('size');
77+
return top() || size().height / 2;
78+
});
79+
80+
readonly cameraBottom = computed(() => {
81+
const bottom = this.select('bottom');
82+
const size = this.store.select('size');
83+
return bottom() || size().height / -2;
84+
});
85+
86+
constructor() {
87+
super();
88+
this.set({ left: 0, right: 0, top: 0, bottom: 0 });
89+
}
90+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { NgIf, NgTemplateOutlet } from '@angular/common';
2+
import { CUSTOM_ELEMENTS_SCHEMA, Component, ContentChild } from '@angular/core';
3+
import { extend } from 'angular-three';
4+
import { Group, PerspectiveCamera } from 'three';
5+
import { NgtsCamera } from '../camera/camera';
6+
import { NgtsCameraContent } from '../camera/camera-content';
7+
8+
extend({ PerspectiveCamera, Group });
9+
10+
@Component({
11+
selector: 'ngts-perspective-camera',
12+
standalone: true,
13+
template: `
14+
<ngt-perspective-camera [ref]="cameraRef" ngtCompound>
15+
<ng-container
16+
*ngIf="cameraContent && !cameraContent.ngtsCameraContent"
17+
[ngTemplateOutlet]="cameraContent.template"
18+
/>
19+
</ngt-perspective-camera>
20+
<ngt-group #group *ngIf="cameraContent && cameraContent.ngtsCameraContent">
21+
<ng-container *ngTemplateOutlet="cameraContent.template; context: { fbo: fboRef.nativeElement, group }" />
22+
</ngt-group>
23+
`,
24+
imports: [NgIf, NgTemplateOutlet],
25+
schemas: [CUSTOM_ELEMENTS_SCHEMA],
26+
})
27+
export class NgtsPerspectiveCamera extends NgtsCamera<PerspectiveCamera> {
28+
@ContentChild(NgtsCameraContent) cameraContent?: NgtsCameraContent;
29+
}

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+
}

libs/soba/misc/src/fbo/fbo.ts

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import {
2+
assertInInjectionContext,
3+
ChangeDetectorRef,
4+
computed,
5+
DestroyRef,
6+
effect,
7+
inject,
8+
Injector,
9+
runInInjectionContext,
10+
} from '@angular/core';
11+
import { injectNgtRef, NgtStore, safeDetectChanges } from 'angular-three';
12+
import * as THREE from 'three';
13+
14+
interface FBOSettings extends THREE.WebGLRenderTargetOptions {
15+
/** Defines the count of MSAA samples. Can only be used with WebGL 2. Default: 0 */
16+
samples?: number;
17+
/** If set, the scene depth will be rendered into buffer.depthTexture. Default: false */
18+
depth?: boolean;
19+
}
20+
21+
export interface NgtsFBOParams {
22+
width?: number | FBOSettings;
23+
height?: number;
24+
settings?: FBOSettings;
25+
}
26+
27+
export function injectNgtsFBO(fboParams: () => NgtsFBOParams, { injector }: { injector?: Injector } = {}) {
28+
assertInInjectionContext(injectNgtsFBO);
29+
30+
if (!injector) {
31+
injector = inject(Injector);
32+
}
33+
34+
return runInInjectionContext(injector, () => {
35+
const store = inject(NgtStore);
36+
const cdr = inject(ChangeDetectorRef);
37+
38+
const targetRef = injectNgtRef<THREE.WebGLRenderTarget>();
39+
40+
inject(DestroyRef).onDestroy(() => targetRef.untracked.dispose());
41+
42+
const trigger = computed(() => {
43+
const size = store.select('size');
44+
const viewport = store.select('viewport');
45+
const { width, height, settings } = fboParams();
46+
return { size: size(), viewport: viewport(), width, height, settings };
47+
});
48+
49+
effect(
50+
() => {
51+
const { size, width, height, settings, viewport } = trigger();
52+
const _width = typeof width === 'number' ? width : size.width * viewport.dpr;
53+
const _height = typeof height === 'number' ? height : size.height * viewport.dpr;
54+
const _settings = (typeof width === 'number' ? settings : (width as FBOSettings)) || {};
55+
const { samples = 0, depth, ...targetSettings } = _settings;
56+
57+
if (!targetRef.untracked) {
58+
const target = new THREE.WebGLRenderTarget(_width, _height, {
59+
minFilter: THREE.LinearFilter,
60+
magFilter: THREE.LinearFilter,
61+
type: THREE.HalfFloatType,
62+
...targetSettings,
63+
});
64+
if (depth) target.depthTexture = new THREE.DepthTexture(_width, _height, THREE.FloatType);
65+
66+
target.samples = samples;
67+
targetRef.nativeElement = target;
68+
}
69+
70+
targetRef.untracked.setSize(_width, _height);
71+
if (samples) targetRef.nativeElement.samples = samples;
72+
safeDetectChanges(cdr);
73+
},
74+
{ allowSignalWrites: true }
75+
);
76+
77+
return targetRef;
78+
});
79+
}

libs/soba/misc/src/index.ts

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

libs/soba/project.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,11 @@
4848
"libs/soba/controls/**/*.ts",
4949
"libs/soba/controls/**/*.html",
5050
"libs/soba/staging/**/*.ts",
51-
"libs/soba/staging/**/*.html"
51+
"libs/soba/staging/**/*.html",
52+
"libs/soba/cameras/**/*.ts",
53+
"libs/soba/cameras/**/*.html",
54+
"libs/soba/misc/**/*.ts",
55+
"libs/soba/misc/**/*.html"
5256
]
5357
}
5458
},

tsconfig.base.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@
2222
"angular-three-postprocessing": ["libs/postprocessing/src/index.ts"],
2323
"angular-three-soba": ["libs/soba/src/index.ts"],
2424
"angular-three-soba/abstractions": ["libs/soba/abstractions/src/index.ts"],
25+
"angular-three-soba/cameras": ["libs/soba/cameras/src/index.ts"],
2526
"angular-three-soba/controls": ["libs/soba/controls/src/index.ts"],
27+
"angular-three-soba/misc": ["libs/soba/misc/src/index.ts"],
2628
"angular-three-soba/staging": ["libs/soba/staging/src/index.ts"],
2729
"angular-three-suspense": ["libs/suspense/src/index.ts"]
2830
}

0 commit comments

Comments
 (0)