Skip to content

Commit e943869

Browse files
Chau TranChau Tran
Chau Tran
authored and
Chau Tran
committed
feat(soba): migrate cloud
1 parent adac197 commit e943869

File tree

5 files changed

+224
-0
lines changed

5 files changed

+224
-0
lines changed

libs/soba/loaders/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
export * from './gltf-loader/gltf-loader';
22
export * from './loader/loader';
33
export * from './progress/progress';
4+
export * from './texture-loader/texture-loader';
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { effect, inject, Injector, runInInjectionContext, type Signal } from '@angular/core';
2+
import { assertInjectionContext, injectNgtLoader, NgtStore, type NgtLoaderResults } from 'angular-three';
3+
import * as THREE from 'three';
4+
5+
export function injectNgtsTextureLoader<TInput extends string[] | string | Record<string, string>>(
6+
input: () => TInput,
7+
{
8+
onLoad,
9+
injector,
10+
}: {
11+
onLoad?: (texture: THREE.Texture | THREE.Texture[]) => void;
12+
injector?: Injector;
13+
} = {}
14+
): Signal<NgtLoaderResults<TInput, THREE.Texture>> {
15+
injector = assertInjectionContext(injectNgtsTextureLoader, injector);
16+
return runInInjectionContext(injector, () => {
17+
const store = inject(NgtStore);
18+
const result = injectNgtLoader(() => THREE.TextureLoader, input, { injector });
19+
20+
effect(() => {
21+
const textures = result();
22+
if (!textures) return;
23+
const array = Array.isArray(textures)
24+
? textures
25+
: textures instanceof THREE.Texture
26+
? [textures]
27+
: Object.values(textures);
28+
if (onLoad) onLoad(array);
29+
array.forEach(store.get('gl').initTexture);
30+
});
31+
32+
return result;
33+
});
34+
}
35+
36+
injectNgtsTextureLoader['preload'] = <TInput extends string[] | string | Record<string, string>>(
37+
input: () => TInput
38+
) => {
39+
injectNgtLoader.preload(() => THREE.TextureLoader, input);
40+
};
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
2+
import { Meta, moduleMetadata, StoryFn } from '@storybook/angular';
3+
import { NgtsOrbitControls } from 'angular-three-soba/controls';
4+
import { NgtsCloud } from 'angular-three-soba/staging';
5+
import { makeRenderFunction, StorybookSetup } from '../setup-canvas';
6+
7+
@Component({
8+
standalone: true,
9+
template: `
10+
<ngts-cloud [position]="[-4, -2, 0]" />
11+
<ngts-cloud [position]="[-4, 2, 0]" />
12+
<ngts-cloud />
13+
<ngts-cloud [position]="[4, -2, 0]" />
14+
<ngts-cloud [position]="[4, 2, 0]" />
15+
<ngts-orbit-controls [enablePan]="false" [zoomSpeed]="0.5" />
16+
`,
17+
imports: [NgtsCloud, NgtsOrbitControls],
18+
schemas: [CUSTOM_ELEMENTS_SCHEMA],
19+
})
20+
class DefaultCloudStory {}
21+
22+
export default {
23+
title: 'Staging/Cloud',
24+
decorators: [moduleMetadata({ imports: [StorybookSetup] })],
25+
} as Meta;
26+
27+
export const Default: StoryFn = makeRenderFunction(DefaultCloudStory, {
28+
camera: { position: [0, 0, 10] },
29+
controls: false,
30+
});

libs/soba/staging/src/cloud/cloud.ts

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
import { NgFor, NgIf } from '@angular/common';
2+
import { CUSTOM_ELEMENTS_SCHEMA, Component, Input, computed, inject } from '@angular/core';
3+
import {
4+
NgtSignalStore,
5+
NgtStore,
6+
extend,
7+
injectNgtRef,
8+
type NgtBeforeRenderEvent,
9+
type NgtGroup,
10+
} from 'angular-three';
11+
import { NgtsBillboard } from 'angular-three-soba/abstractions';
12+
import { injectNgtsTextureLoader } from 'angular-three-soba/loaders';
13+
import { Group, Mesh, MeshStandardMaterial, PlaneGeometry } from 'three';
14+
15+
const CLOUD_URL = 'https://rawcdn.githack.com/pmndrs/drei-assets/9225a9f1fbd449d9411125c2f419b843d0308c9f/cloud.png';
16+
injectNgtsTextureLoader.preload(() => CLOUD_URL);
17+
18+
extend({ Group, Mesh, PlaneGeometry, MeshStandardMaterial });
19+
20+
export interface NgtsCloudState {
21+
opacity: number;
22+
speed: number;
23+
width: number;
24+
depth: number;
25+
segments: number;
26+
texture: string;
27+
color: THREE.ColorRepresentation;
28+
depthTest: boolean;
29+
}
30+
31+
declare global {
32+
interface HTMLElementTagNameMap {
33+
'ngts-cloud': NgtsCloudState & NgtGroup;
34+
}
35+
}
36+
37+
@Component({
38+
selector: 'ngts-cloud',
39+
standalone: true,
40+
template: `
41+
<ngt-group ngtCompound [ref]="groupRef">
42+
<ngt-group
43+
[position]="[0, 0, (cloudSegments() / 2) * cloudDepth()]"
44+
(beforeRender)="onBeforeRender($event)"
45+
>
46+
<ngts-billboard
47+
*ngFor="let cloud of clouds(); let i = index"
48+
[position]="[cloud.x, cloud.y, -i * cloudDepth()]"
49+
>
50+
<ngt-mesh [scale]="cloud.scale" [rotation]="[0, 0, 0]">
51+
<ngt-plane-geometry />
52+
<!-- we use ngIf here for texture because by the time ngt-value is initialized -->
53+
<!-- [map] has not been resolved yet. we ngIf it so that texture is available before hand -->
54+
<ngt-mesh-standard-material
55+
*ngIf="cloudTexture() as cloudTexture"
56+
[transparent]="true"
57+
[map]="cloudTexture"
58+
[depthTest]="cloudDepthTest()"
59+
[opacity]="(cloud.scale / 6) * cloud.density * cloudOpacity()"
60+
[color]="cloudColor()"
61+
>
62+
<ngt-value [rawValue]="encoding()" attach="map.encoding" />
63+
</ngt-mesh-standard-material>
64+
</ngt-mesh>
65+
</ngts-billboard>
66+
</ngt-group>
67+
</ngt-group>
68+
`,
69+
imports: [NgFor, NgtsBillboard, NgIf],
70+
schemas: [CUSTOM_ELEMENTS_SCHEMA],
71+
})
72+
export class NgtsCloud extends NgtSignalStore<NgtsCloudState> {
73+
@Input() groupRef = injectNgtRef<Group>();
74+
75+
@Input() set opacity(opacity: number) {
76+
this.set({ opacity });
77+
}
78+
79+
@Input() set speed(speed: number) {
80+
this.set({ speed });
81+
}
82+
83+
@Input() set width(width: number) {
84+
this.set({ width });
85+
}
86+
87+
@Input() set depth(depth: number) {
88+
this.set({ depth });
89+
}
90+
91+
@Input() set segments(segments: number) {
92+
this.set({ segments });
93+
}
94+
95+
@Input() set texture(texture: string) {
96+
this.set({ texture });
97+
}
98+
99+
@Input() set color(color: THREE.ColorRepresentation) {
100+
this.set({ color });
101+
}
102+
103+
@Input() set depthTest(depthTest: boolean) {
104+
this.set({ depthTest });
105+
}
106+
107+
readonly #store = inject(NgtStore);
108+
109+
readonly #width = this.select('width');
110+
readonly #speed = this.select('speed');
111+
112+
readonly cloudSegments = this.select('segments');
113+
readonly cloudDepth = this.select('depth');
114+
readonly cloudDepthTest = this.select('depthTest');
115+
readonly cloudOpacity = this.select('opacity');
116+
readonly cloudColor = this.select('color');
117+
readonly encoding = this.#store.select('gl', 'outputEncoding');
118+
119+
readonly cloudTexture = injectNgtsTextureLoader(this.select('texture'));
120+
readonly clouds = computed(() =>
121+
[...new Array(this.cloudSegments())].map((_, index) => ({
122+
x: this.#width() / 2 - Math.random() * this.#width(),
123+
y: this.#width() / 2 - Math.random() * this.#width(),
124+
scale: 0.4 + Math.sin(((index + 1) / this.cloudSegments()) * Math.PI) * ((0.2 + Math.random()) * 10),
125+
density: Math.max(0.2, Math.random()),
126+
rotation: Math.max(0.002, 0.005 * Math.random()) * this.#speed(),
127+
}))
128+
);
129+
130+
constructor() {
131+
super({
132+
opacity: 0.5,
133+
speed: 0.4,
134+
width: 10,
135+
depth: 1.5,
136+
segments: 20,
137+
texture: CLOUD_URL,
138+
color: '#ffffff',
139+
depthTest: true,
140+
});
141+
}
142+
143+
onBeforeRender({ state, object }: NgtBeforeRenderEvent<Group>) {
144+
const clouds = this.clouds();
145+
object.children.forEach((cloud, index) => {
146+
cloud.children[0].rotation.z += clouds[index].rotation;
147+
cloud.children[0].scale.setScalar(
148+
clouds[index].scale + (((1 + Math.sin(state.clock.getElapsedTime() / 10)) / 2) * index) / 10
149+
);
150+
});
151+
}
152+
}

libs/soba/staging/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
export * from './center/center';
2+
export * from './cloud/cloud';
23
export * from './float/float';

0 commit comments

Comments
 (0)