Skip to content

Commit aed72ab

Browse files
committed
rounded-box
1 parent 5af9afb commit aed72ab

File tree

6 files changed

+222
-26
lines changed

6 files changed

+222
-26
lines changed

libs/soba/abstractions/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,6 @@ export * from './lib/helper';
77
export * from './lib/line';
88
export * from './lib/prism-geometry';
99
export * from './lib/quadratic-bezier-line';
10+
export * from './lib/rounded-box';
1011
export * from './lib/text';
1112
export * from './lib/text-3d';
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
import {
2+
afterNextRender,
3+
ChangeDetectionStrategy,
4+
Component,
5+
computed,
6+
CUSTOM_ELEMENTS_SCHEMA,
7+
ElementRef,
8+
input,
9+
untracked,
10+
viewChild,
11+
} from '@angular/core';
12+
import { extend, NgtArgs, NgtMesh, omit, pick } from 'angular-three';
13+
import { injectAutoEffect } from 'ngxtension/auto-effect';
14+
import { mergeInputs } from 'ngxtension/inject-inputs';
15+
import { ExtrudeGeometry, Mesh, Shape } from 'three';
16+
import { toCreasedNormals } from 'three-stdlib';
17+
18+
const eps = 0.00001;
19+
20+
function createShape(width: number, height: number, radius0: number) {
21+
const shape = new Shape();
22+
const radius = radius0 - eps;
23+
shape.absarc(eps, eps, eps, -Math.PI / 2, -Math.PI, true);
24+
shape.absarc(eps, height - radius * 2, eps, Math.PI, Math.PI / 2, true);
25+
shape.absarc(width - radius * 2, height - radius * 2, eps, Math.PI / 2, 0, true);
26+
shape.absarc(width - radius * 2, eps, eps, 0, -Math.PI / 2, true);
27+
return shape;
28+
}
29+
30+
export interface NgtsRoundedBoxOptions extends Partial<NgtMesh> {
31+
width: number;
32+
height: number;
33+
depth: number;
34+
radius: number;
35+
smoothness: number;
36+
bevelSegments: number;
37+
steps: number;
38+
creaseAngle: number;
39+
}
40+
41+
const defaultOptions: NgtsRoundedBoxOptions = {
42+
width: 1,
43+
height: 1,
44+
depth: 1,
45+
radius: 0.05,
46+
smoothness: 4,
47+
bevelSegments: 4,
48+
creaseAngle: 0.4,
49+
steps: 1,
50+
};
51+
52+
@Component({
53+
selector: 'ngts-rounded-box',
54+
standalone: true,
55+
template: `
56+
<ngt-mesh #mesh [parameters]="parameters()">
57+
<ngt-extrude-geometry #geometry *args="[shape(), params()]" />
58+
<ng-content />
59+
</ngt-mesh>
60+
`,
61+
schemas: [CUSTOM_ELEMENTS_SCHEMA],
62+
changeDetection: ChangeDetectionStrategy.OnPush,
63+
imports: [NgtArgs],
64+
})
65+
export class NgtsRoundedBox {
66+
options = input(defaultOptions, { transform: mergeInputs(defaultOptions) });
67+
parameters = omit(this.options, [
68+
'width',
69+
'height',
70+
'depth',
71+
'radius',
72+
'smoothness',
73+
'bevelSegments',
74+
'steps',
75+
'creaseAngle',
76+
]);
77+
78+
private width = pick(this.options, 'width');
79+
private height = pick(this.options, 'height');
80+
private depth = pick(this.options, 'depth');
81+
private radius = pick(this.options, 'radius');
82+
private smoothness = pick(this.options, 'smoothness');
83+
private bevelSegments = pick(this.options, 'bevelSegments');
84+
private steps = pick(this.options, 'steps');
85+
private creaseAngle = pick(this.options, 'creaseAngle');
86+
87+
meshRef = viewChild.required<ElementRef<Mesh>>('mesh');
88+
geometryRef = viewChild<ElementRef<ExtrudeGeometry>>('geometry');
89+
90+
shape = computed(() => {
91+
const [width, height, radius] = [this.width(), this.height(), this.radius()];
92+
return createShape(width, height, radius);
93+
});
94+
params = computed(() => {
95+
const [depth, radius, smoothness, bevelSegments, steps] = [
96+
this.depth(),
97+
this.radius(),
98+
this.smoothness(),
99+
untracked(this.bevelSegments),
100+
untracked(this.steps),
101+
];
102+
103+
return {
104+
depth: depth - radius * 2,
105+
bevelEnabled: true,
106+
bevelSegments: bevelSegments * 2,
107+
steps,
108+
bevelSize: radius - eps,
109+
bevelThickness: radius,
110+
curveSegments: smoothness,
111+
};
112+
});
113+
114+
constructor() {
115+
extend({ ExtrudeGeometry, Mesh });
116+
117+
const autoEffect = injectAutoEffect();
118+
afterNextRender(() => {
119+
autoEffect(() => {
120+
const geometry = this.geometryRef()?.nativeElement;
121+
if (!geometry) return;
122+
123+
this.shape();
124+
this.params();
125+
126+
geometry.center();
127+
toCreasedNormals(geometry, untracked(this.creaseAngle));
128+
});
129+
});
130+
}
131+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import { ChangeDetectionStrategy, Component, CUSTOM_ELEMENTS_SCHEMA, input, viewChild } from '@angular/core';
2+
3+
@Component({
4+
standalone: true,
5+
template: `
6+
@if (solid()) {
7+
<ngt-spot-light [position]="35" [intensity]="2 * Math.PI" [decay]="0" />
8+
}
9+
10+
<ngts-rounded-box
11+
[options]="{ width: width(), height: height(), depth: depth(), radius: radius(), smoothness: smoothness() }"
12+
>
13+
<ngt-mesh-phong-material color="#f3f3f3" [wireframe]="!solid()" />
14+
</ngts-rounded-box>
15+
`,
16+
schemas: [CUSTOM_ELEMENTS_SCHEMA],
17+
changeDetection: ChangeDetectionStrategy.OnPush,
18+
imports: [NgtsRoundedBox],
19+
})
20+
class DefaultRoundedBoxStory {
21+
width = input(25);
22+
height = input(25);
23+
depth = input(25);
24+
radius = input(1);
25+
smoothness = input(5);
26+
solid = input(false);
27+
28+
roundedBox = viewChild.required(NgtsRoundedBox);
29+
30+
constructor() {
31+
injectTurnable(() => this.roundedBox().meshRef());
32+
}
33+
34+
protected readonly Math = Math;
35+
}
36+
37+
import { Meta } from '@storybook/angular';
38+
import { NgtsRoundedBox } from 'angular-three-soba/abstractions';
39+
import { injectTurnable, makeDecorators, makeStoryObject, number } from '../setup-canvas';
40+
41+
export default {
42+
title: 'Abstractions/RoundedBox',
43+
decorators: makeDecorators(),
44+
} as Meta;
45+
46+
export const Default = makeStoryObject(DefaultRoundedBoxStory, {
47+
canvasOptions: { camera: { position: [-30, 30, 30] } },
48+
argsOptions: {
49+
width: number(25, { range: true, min: 1, max: 100, step: 1 }),
50+
height: number(25, { range: true, min: 1, max: 100, step: 1 }),
51+
depth: number(25, { range: true, min: 1, max: 100, step: 1 }),
52+
radius: number(1, { range: true, min: 0, max: 10, step: 0.1 }),
53+
smoothness: number(5, { range: true, min: 0, max: 10, step: 0.1 }),
54+
solid: false,
55+
},
56+
});
57+
58+
export const Solid = makeStoryObject(DefaultRoundedBoxStory, {
59+
canvasOptions: { camera: { position: [-30, 30, 30] } },
60+
argsOptions: {
61+
width: number(25, { range: true, min: 1, max: 100, step: 1 }),
62+
height: number(25, { range: true, min: 1, max: 100, step: 1 }),
63+
depth: number(25, { range: true, min: 1, max: 100, step: 1 }),
64+
radius: number(8, { range: true, min: 0, max: 10, step: 0.1 }),
65+
smoothness: number(5, { range: true, min: 0, max: 10, step: 0.1 }),
66+
solid: true,
67+
},
68+
});

libs/soba/src/materials/mesh-refraction-materials.stories.ts

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -108,21 +108,12 @@ class Diamond {
108108
color: 'orange',
109109
colorBlend: 2,
110110
toneMapped: true,
111-
alphaTest: 0.8,
111+
alphaTest: 0.7,
112112
opacity: 1,
113113
scale: 12,
114114
}"
115115
>
116-
<ngts-randomized-lights
117-
[options]="{
118-
intensity: Math.PI,
119-
position: [5, 5, -10],
120-
amount: 8,
121-
radius: 10,
122-
ambient: 0.5,
123-
bias: 0.001,
124-
}"
125-
/>
116+
<ngts-randomized-lights [options]="{ position: [5, 5, -10], amount: 8, radius: 10, ambient: 0.5, bias: 0.001 }" />
126117
</ngts-accumulative-shadows>
127118
128119
<ngts-environment

libs/soba/src/setup-canvas.ts

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,15 @@ import {
2222
viewChild,
2323
} from '@angular/core';
2424
import { Args, Decorator, moduleMetadata } from '@storybook/angular';
25-
import { NgtAnyRecord, NgtArgs, NgtCanvas, NgtPerformance, extend, injectBeforeRender } from 'angular-three';
25+
import {
26+
NgtAnyRecord,
27+
NgtArgs,
28+
NgtCanvas,
29+
NgtPerformance,
30+
extend,
31+
injectBeforeRender,
32+
resolveRef,
33+
} from 'angular-three';
2634
import { injectAutoEffect } from 'ngxtension/auto-effect';
2735
import { mergeInputs } from 'ngxtension/inject-inputs';
2836
import * as THREE from 'three';
@@ -306,10 +314,14 @@ export function makeDecorators(providers: Provider[] = [], ...decoratorFns: Deco
306314
export class Turnable {
307315
constructor() {
308316
const element = inject<ElementRef<Object3D>>(ElementRef);
309-
injectBeforeRender(() => {
310-
const object = element.nativeElement;
311-
if (!object.isObject3D) return;
312-
object.rotation.y += 0.01;
313-
});
317+
injectTurnable(() => element);
314318
}
315319
}
320+
321+
export function injectTurnable(object: () => Object3D | ElementRef<Object3D> | undefined | null) {
322+
return injectBeforeRender(() => {
323+
const obj = resolveRef(object());
324+
if (!obj) return;
325+
obj.rotation.y += 0.01;
326+
});
327+
}

libs/soba/src/staging/caustics.stories.ts

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -182,17 +182,10 @@ class Env {
182182
<ngt-group [position]="[0, -0.5, 0]" [rotation]="[0, -0.75, 0]">
183183
<caustics-scene />
184184
<ngts-accumulative-shadows
185-
[options]="{ frames: 100, alphaTest: 0.85, opacity: 0.8, color: 'red', scale: 20, position: [0, -0.005, 0] }"
185+
[options]="{ frames: 100, alphaTest: 0.75, opacity: 0.8, color: 'red', scale: 20, position: [0, -0.005, 0] }"
186186
>
187187
<ngts-randomized-lights
188-
[options]="{
189-
amount: 8,
190-
radius: 6,
191-
ambient: 0.5,
192-
intensity: Math.PI,
193-
position: [-1.5, 2.5, -2.5],
194-
bias: 0.001,
195-
}"
188+
[options]="{ amount: 8, radius: 6, ambient: 0.5, position: [-1.5, 2.5, -2.5], bias: 0.001 }"
196189
/>
197190
</ngts-accumulative-shadows>
198191
<caustics-env />

0 commit comments

Comments
 (0)