Skip to content

Commit da57c2f

Browse files
committed
feat(soba): add sparkles
1 parent e89e576 commit da57c2f

File tree

4 files changed

+217
-0
lines changed

4 files changed

+217
-0
lines changed
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import { ChangeDetectionStrategy, Component, CUSTOM_ELEMENTS_SCHEMA, input } from '@angular/core';
2+
import { Meta } from '@storybook/angular';
3+
import { NgtArgs, NgtVector3 } from 'angular-three';
4+
import { NgtsOrbitControls } from 'angular-three-soba/controls';
5+
import { NgtsEnvironment, NgtsShadow, NgtsSparkles } from 'angular-three-soba/staging';
6+
import * as THREE from 'three';
7+
import { storyDecorators, storyObject } from '../setup-canvas';
8+
9+
@Component({
10+
selector: 'app-sphere',
11+
template: `
12+
<ngt-mesh [position]="position()">
13+
<ngt-sphere-geometry *args="[size(), 64, 64]" />
14+
<ngt-mesh-physical-material
15+
[roughness]="0"
16+
[color]="color()"
17+
[emissive]="emissive() || color()"
18+
[envMapIntensity]="0.2"
19+
/>
20+
<ngts-sparkles [options]="{ count: amount(), scale: size() * 2, size: 6, speed: 0.4 }" />
21+
<ngts-shadow [options]="{ scale: size() * 1.5, position: [0, -size(), 0] }" />
22+
</ngt-mesh>
23+
`,
24+
imports: [NgtArgs, NgtsSparkles, NgtsShadow],
25+
changeDetection: ChangeDetectionStrategy.OnPush,
26+
schemas: [CUSTOM_ELEMENTS_SCHEMA],
27+
})
28+
class Sphere {
29+
position = input<NgtVector3>();
30+
size = input(1);
31+
amount = input(50);
32+
color = input<THREE.ColorRepresentation>('white');
33+
emissive = input<THREE.ColorRepresentation>();
34+
}
35+
36+
@Component({
37+
template: `
38+
<ngt-hemisphere-light [intensity]="0.5" color="white" groundColor="black" />
39+
<ngts-environment
40+
[options]="{
41+
files: 'https://dl.polyhaven.org/file/ph-assets/HDRIs/hdr/2k/evening_road_01_2k.hdr',
42+
ground: { height: 5, radius: 40, scale: 20 },
43+
}"
44+
/>
45+
<app-sphere [amount]="50" emissive="green" [position]="[1, 1, -1]" />
46+
<app-sphere [amount]="30" emissive="purple" [position]="[-1.5, 0.5, -2]" [size]="0.5" />
47+
<app-sphere [amount]="20" emissive="orange" [position]="[-1, 0.25, 1]" [size]="0.25" color="lightpink" />
48+
<ngts-orbit-controls
49+
[options]="{
50+
autoRotate: true,
51+
autoRotateSpeed: 0.85,
52+
zoomSpeed: 0.75,
53+
minPolarAngle: Math.PI / 2.5,
54+
maxPolarAngle: Math.PI / 2.55,
55+
}"
56+
/>
57+
`,
58+
imports: [NgtsEnvironment, Sphere, NgtsOrbitControls],
59+
changeDetection: ChangeDetectionStrategy.OnPush,
60+
schemas: [CUSTOM_ELEMENTS_SCHEMA],
61+
})
62+
class DefaultSparklesStory {
63+
protected readonly Math = Math;
64+
}
65+
66+
export default {
67+
title: 'Staging/Sparkles',
68+
decorators: storyDecorators(),
69+
} satisfies Meta;
70+
71+
export const Default = storyObject(DefaultSparklesStory, {
72+
camera: { position: [0, 0, 12], fov: 30 },
73+
lights: false,
74+
controls: null,
75+
});

libs/soba/staging/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ export * from './lib/matcap-texture';
1717
export * from './lib/normal-texture';
1818
export * from './lib/randomized-lights';
1919
export * from './lib/render-texture';
20+
export * from './lib/shadow';
2021
export * from './lib/sky';
22+
export * from './lib/sparkles';
2123
export { NgtsSpotLight, NgtsSpotLightOptions, NgtsSpotLightShadow } from './lib/spot-light';
2224
export * from './lib/stage';

libs/soba/staging/src/lib/shadow.ts

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import {
2+
ChangeDetectionStrategy,
3+
Component,
4+
computed,
5+
CUSTOM_ELEMENTS_SCHEMA,
6+
DOCUMENT,
7+
inject,
8+
input,
9+
} from '@angular/core';
10+
import { extend, NgtArgs, NgtColor, NgtThreeElements, omit, pick } from 'angular-three';
11+
import { mergeInputs } from 'ngxtension/inject-inputs';
12+
import * as THREE from 'three';
13+
import { CanvasTexture, Mesh, MeshBasicMaterial, PlaneGeometry } from 'three';
14+
15+
export interface NgtsShadowOptions extends Partial<NgtThreeElements['ngt-mesh']> {
16+
colorStop: number;
17+
fog: boolean;
18+
color: NgtColor;
19+
opacity: number;
20+
depthWrite: boolean;
21+
}
22+
23+
const defaultShadowOptions: NgtsShadowOptions = {
24+
fog: false,
25+
depthWrite: false,
26+
colorStop: 0.0,
27+
color: 'black',
28+
opacity: 0.5,
29+
};
30+
31+
@Component({
32+
selector: 'ngts-shadow',
33+
template: `
34+
<ngt-mesh [renderOrder]="renderOrder()" [rotation.x]="-Math.PI / 2" [parameters]="parameters()">
35+
<ngt-plane-geometry />
36+
<ngt-mesh-basic-material
37+
transparent
38+
[opacity]="opacity()"
39+
[fog]="fog()"
40+
[depthWrite]="depthWrite()"
41+
[side]="DoubleSide"
42+
>
43+
<ngt-canvas-texture *args="[canvas()]" attach="map" />
44+
</ngt-mesh-basic-material>
45+
<ng-content />
46+
</ngt-mesh>
47+
`,
48+
imports: [NgtArgs],
49+
changeDetection: ChangeDetectionStrategy.OnPush,
50+
schemas: [CUSTOM_ELEMENTS_SCHEMA],
51+
})
52+
export class NgtsShadow {
53+
protected readonly Math = Math;
54+
protected readonly DoubleSide = THREE.DoubleSide;
55+
56+
options = input(defaultShadowOptions, { transform: mergeInputs(defaultShadowOptions) });
57+
protected parameters = omit(this.options, ['colorStop', 'fog', 'color', 'opacity', 'depthWrite', 'renderOrder']);
58+
59+
private document = inject(DOCUMENT);
60+
61+
protected renderOrder = pick(this.options, 'renderOrder');
62+
protected opacity = pick(this.options, 'opacity');
63+
protected fog = pick(this.options, 'fog');
64+
protected depthWrite = pick(this.options, 'depthWrite');
65+
66+
private color = pick(this.options, 'color');
67+
private colorStop = pick(this.options, 'colorStop');
68+
69+
protected canvas = computed(() => {
70+
const [color, colorStop] = [this.color(), this.colorStop()];
71+
72+
const canvas = this.document.createElement('canvas');
73+
canvas.width = 128;
74+
canvas.height = 128;
75+
const context = canvas.getContext('2d') as CanvasRenderingContext2D;
76+
const gradient = context.createRadialGradient(
77+
canvas.width / 2,
78+
canvas.height / 2,
79+
0,
80+
canvas.width / 2,
81+
canvas.height / 2,
82+
canvas.width / 2,
83+
);
84+
const colorArgs = Array.isArray(color) ? color : [color];
85+
gradient.addColorStop(colorStop, new THREE.Color(...colorArgs).getStyle());
86+
gradient.addColorStop(1, 'rgba(0,0,0,0)');
87+
context.fillStyle = gradient;
88+
context.fillRect(0, 0, canvas.width, canvas.height);
89+
return canvas;
90+
});
91+
92+
constructor() {
93+
extend({ Mesh, PlaneGeometry, MeshBasicMaterial, CanvasTexture });
94+
}
95+
}

libs/soba/staging/src/lib/sparkles.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { ChangeDetectionStrategy, Component, computed, CUSTOM_ELEMENTS_SCHEMA, input } from '@angular/core';
2+
import { beforeRender, injectStore, NgtArgs, NgtThreeElements, omit, pick } from 'angular-three';
3+
import { Sparkles, type SparklesProps } from 'angular-three-soba/vanilla-exports';
4+
import { mergeInputs } from 'ngxtension/inject-inputs';
5+
6+
export type NgtsSparklesOptions = Partial<NgtThreeElements['ngt-points']> & SparklesProps;
7+
8+
const defaultSparklesOptions: NgtsSparklesOptions = {
9+
count: 100,
10+
speed: 1,
11+
opacity: 1,
12+
scale: 1,
13+
noise: 1,
14+
};
15+
16+
@Component({
17+
selector: 'ngts-sparkles',
18+
template: `
19+
<ngt-primitive *args="[sparkles()]" [parameters]="parameters()">
20+
<ng-content />
21+
</ngt-primitive>
22+
`,
23+
schemas: [CUSTOM_ELEMENTS_SCHEMA],
24+
imports: [NgtArgs],
25+
changeDetection: ChangeDetectionStrategy.OnPush,
26+
})
27+
export class NgtsSparkles {
28+
options = input(defaultSparklesOptions, { transform: mergeInputs(defaultSparklesOptions) });
29+
protected parameters = omit(this.options, ['noise', 'count', 'speed', 'opacity', 'scale', 'size', 'color']);
30+
private sparklesOptions = pick(this.options, ['noise', 'count', 'speed', 'opacity', 'scale', 'size', 'color']);
31+
32+
private store = injectStore();
33+
34+
sparkles = computed(() => {
35+
const s = new Sparkles(this.sparklesOptions());
36+
s.setPixelRatio(this.store.snapshot.viewport.dpr);
37+
return s;
38+
});
39+
40+
constructor() {
41+
beforeRender(({ clock }) => {
42+
this.sparkles().update(clock.elapsedTime);
43+
});
44+
}
45+
}

0 commit comments

Comments
 (0)