Skip to content

Commit 2f8466b

Browse files
committed
feat: add transformer for selected shape
1 parent d505c38 commit 2f8466b

File tree

25 files changed

+717
-450
lines changed

25 files changed

+717
-450
lines changed

__tests__/ecs/hierarchy.spec.ts

+11-6
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import _gl from 'gl';
2+
import '../useSnapshotMatchers';
23
import {
34
App,
45
Camera,
@@ -31,6 +32,7 @@ describe('Hierarchy', () => {
3132
it('should create a hierarchy', async () => {
3233
const app = new App();
3334

35+
let $canvas: HTMLCanvasElement;
3436
let canvasEntity: Entity | undefined;
3537
let cameraEntity: Entity | undefined;
3638
let parentEntity: Entity | undefined;
@@ -62,10 +64,7 @@ describe('Hierarchy', () => {
6264
);
6365

6466
initialize(): void {
65-
const $canvas = DOMAdapter.get().createCanvas(
66-
200,
67-
200,
68-
) as HTMLCanvasElement;
67+
$canvas = DOMAdapter.get().createCanvas(200, 200) as HTMLCanvasElement;
6968

7069
const canvas = this.commands.spawn(
7170
new Canvas({
@@ -87,7 +86,7 @@ describe('Hierarchy', () => {
8786
new Transform(),
8887
new Renderable(),
8988
new FillSolid('red'),
90-
new Circle({ cx: 0, cy: 0, r: 100 }),
89+
new Circle({ cx: 100, cy: 100, r: 100 }),
9190
new Visibility(),
9291
);
9392

@@ -103,7 +102,7 @@ describe('Hierarchy', () => {
103102
alignment: 'center',
104103
dasharray: [10, 10],
105104
}),
106-
new Circle({ cx: 0, cy: 0, r: 50 }),
105+
new Circle({ cx: 100, cy: 100, r: 50 }),
107106
new Visibility(),
108107
);
109108
parent.appendChild(child);
@@ -147,6 +146,12 @@ describe('Hierarchy', () => {
147146
expect(child.parent.isSame(parentEntity)).toBeTruthy();
148147
}
149148

149+
const dir = `${__dirname}/snapshots`;
150+
expect($canvas!.getContext('webgl1')).toMatchWebGLSnapshot(
151+
dir,
152+
'hierarchy',
153+
);
154+
150155
await app.exit();
151156
});
152157
});

__tests__/ecs/snapshots/hierarchy.png

8.47 KB
Loading

packages/ecs/src/components/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,4 @@ export * from './hierarchy';
1515
export * from './renderable';
1616
export * from './math';
1717
export * from './geometry';
18+
export * from './pen';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export class Selected {}

packages/ecs/src/components/pen/UI.ts

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
/**
2+
* UI should be rendered on top of everything else.
3+
* UI should not be exported.
4+
*/
5+
export class UI {}
+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from './UI';
2+
export * from './Selected';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export class SizeAttenuation {}

packages/ecs/src/components/renderable/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,4 @@ export * from './Rough';
1212
export * from './ComputedBounds';
1313
export * from './Visibility';
1414
export * from './ToBeDeleted';
15+
export * from './SizeAttenuation';

packages/ecs/src/drawcalls/SDF.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import {
3838
Mat3,
3939
Opacity,
4040
Rect,
41+
SizeAttenuation,
4142
Stroke,
4243
} from '../components';
4344

@@ -469,8 +470,6 @@ export class SDF extends Drawcall {
469470
index: number,
470471
total: number,
471472
): [number[], Record<string, unknown>] {
472-
// TODO
473-
const sizeAttenuation = 0;
474473
const globalRenderOrder = shape.has(GlobalRenderOrder)
475474
? shape.read(GlobalRenderOrder).value
476475
: 0;
@@ -548,7 +547,8 @@ export class SDF extends Drawcall {
548547
];
549548

550549
const LEFT_SHIFT23 = 8388608.0;
551-
const compressed = (sizeAttenuation ? 1 : 0) * LEFT_SHIFT23 + type;
550+
const compressed =
551+
(shape.has(SizeAttenuation) ? 1 : 0) * LEFT_SHIFT23 + type;
552552
const u_Opacity = [opacity, fillOpacity, strokeOpacity, compressed];
553553
const u_InnerShadowColor = [isr / 255, isg / 255, isb / 255, iso];
554554
const u_InnerShadow = [offsetX, offsetY, blurRadius, 0];

packages/ecs/src/plugins/Pen.ts

+9-1
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,22 @@
1-
import { system } from '@lastolivegames/becsy';
1+
import { component, system } from '@lastolivegames/becsy';
22
import { Plugin } from './types';
33
import {
44
ComputeBounds,
55
EventWriter,
66
PropagateTransforms,
7+
RenderTransformer,
78
Select,
9+
SetCursor,
810
SetupDevice,
911
Sort,
1012
SyncSimpleTransforms,
1113
} from '../systems';
14+
import { Selected, UI } from '../components';
1215

1316
export const PenPlugin: Plugin = () => {
17+
component(UI);
18+
component(Selected);
19+
1420
system((s) =>
1521
s.after(
1622
ComputeBounds,
@@ -19,6 +25,8 @@ export const PenPlugin: Plugin = () => {
1925
SyncSimpleTransforms,
2026
PropagateTransforms,
2127
Sort,
28+
SetCursor,
2229
),
2330
)(Select);
31+
system((s) => s.afterWritersOf(Selected))(RenderTransformer);
2432
};

packages/ecs/src/plugins/Renderer.ts

+2
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ import {
5151
GPUResource,
5252
Name,
5353
ToBeDeleted,
54+
SizeAttenuation,
5455
} from '../components';
5556

5657
export const RendererPlugin: Plugin = () => {
@@ -64,6 +65,7 @@ export const RendererPlugin: Plugin = () => {
6465
component(GlobalRenderOrder);
6566
component(Visibility);
6667
component(ToBeDeleted);
68+
component(SizeAttenuation);
6769
/**
6870
* Style
6971
*/

packages/ecs/src/systems/BatchManager.ts

+8-7
Original file line numberDiff line numberDiff line change
@@ -198,15 +198,14 @@ export class BatchManager {
198198
}
199199

200200
add(shape: Entity) {
201+
let drawcalls: Drawcall[];
201202
if (shape.read(Renderable).batchable) {
202-
const drawcalls = this.getOrCreateBatchableDrawcalls(shape);
203-
if (this.#drawcallsToFlush.indexOf(drawcalls[0]) === -1) {
204-
this.#drawcallsToFlush.push(...drawcalls);
205-
}
203+
drawcalls = this.getOrCreateBatchableDrawcalls(shape);
206204
} else {
207-
this.#drawcallsToFlush.push(
208-
...this.getOrCreateNonBatchableDrawcalls(shape),
209-
);
205+
drawcalls = this.getOrCreateNonBatchableDrawcalls(shape);
206+
}
207+
if (this.#drawcallsToFlush.indexOf(drawcalls[0]) === -1) {
208+
this.#drawcallsToFlush.push(...drawcalls);
210209
}
211210
}
212211

@@ -306,6 +305,8 @@ export class BatchManager {
306305
this.#drawcallsToFlush.forEach((drawcall) => {
307306
drawcall.submit(renderPass, uniformBuffer, uniformLegacyObject);
308307
});
308+
309+
// console.log('flush', this.#drawcallsToFlush);
309310
}
310311

311312
stats() {

packages/ecs/src/systems/ExportSVG.ts

+7-5
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import {
2424
Text,
2525
Theme,
2626
Transform,
27+
UI,
2728
VectorScreenshotRequest,
2829
Visibility,
2930
} from '../components';
@@ -65,6 +66,7 @@ export class ExportSVG extends System {
6566
Theme,
6667
Grid,
6768
Camera,
69+
UI,
6870
Name,
6971
Parent,
7072
Children,
@@ -128,11 +130,11 @@ export class ExportSVG extends System {
128130
}
129131
}
130132

131-
serializeNodesToSVGElements(entityToSerializedNodes(cameras[0])).forEach(
132-
(element) => {
133-
$namespace.appendChild(element);
134-
},
135-
);
133+
serializeNodesToSVGElements(
134+
entityToSerializedNodes(cameras[0], (entity) => !entity.has(UI)),
135+
).forEach((element) => {
136+
$namespace.appendChild(element);
137+
});
136138
return $namespace;
137139
}
138140

packages/ecs/src/systems/MeshPipeline.ts

+2
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import {
4040
Renderable,
4141
Rough,
4242
Screenshot,
43+
SizeAttenuation,
4344
Stroke,
4445
Text,
4546
Theme,
@@ -151,6 +152,7 @@ export class MeshPipeline extends System {
151152
FillSolid,
152153
FillTexture,
153154
FractionalIndex,
155+
SizeAttenuation,
154156
)
155157
.read.and.using(RasterScreenshotRequest, Screenshot).write,
156158
);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
import { Entity, System } from '@lastolivegames/becsy';
2+
import {
3+
Children,
4+
Circle,
5+
ComputedBounds,
6+
FillSolid,
7+
Opacity,
8+
Parent,
9+
Rect,
10+
Renderable,
11+
Selected,
12+
SizeAttenuation,
13+
Stroke,
14+
ToBeDeleted,
15+
Transform,
16+
UI,
17+
ZIndex,
18+
} from '../components';
19+
import { Commands } from '../commands';
20+
import { getDescendants, getSceneRoot } from './Transform';
21+
22+
/**
23+
* @see https://github.com/konvajs/konva/blob/master/src/shapes/Transformer.ts
24+
*/
25+
export class RenderTransformer extends System {
26+
private readonly commands = new Commands(this);
27+
28+
private readonly selected = this.query((q) =>
29+
q.added.and.removed.with(Selected),
30+
);
31+
32+
#transformers = new WeakMap<Entity, Entity>();
33+
34+
constructor() {
35+
super();
36+
37+
this.query(
38+
(q) =>
39+
q
40+
.using(ComputedBounds)
41+
.read.and.using(
42+
UI,
43+
Selected,
44+
Transform,
45+
Parent,
46+
Children,
47+
Renderable,
48+
FillSolid,
49+
Opacity,
50+
Stroke,
51+
Rect,
52+
Circle,
53+
ZIndex,
54+
SizeAttenuation,
55+
ToBeDeleted,
56+
).write,
57+
);
58+
}
59+
60+
execute() {
61+
this.selected.added.forEach((entity) => {
62+
console.log('add', entity);
63+
64+
const { geometryBounds } = entity.read(ComputedBounds);
65+
const { minX, minY, maxX, maxY } = geometryBounds;
66+
const width = maxX - minX;
67+
const height = maxY - minY;
68+
const { rotation } = entity.read(Transform);
69+
70+
const transformer = this.commands
71+
.spawn(
72+
new UI(),
73+
new Transform({
74+
rotation,
75+
}),
76+
new Renderable(),
77+
new FillSolid('white'), // --spectrum-blue-100
78+
new Opacity({ fillOpacity: 0 }),
79+
new Stroke({ width: 1, color: '#147af3' }), // --spectrum-thumbnail-border-color-selected
80+
new Rect({
81+
x: minX,
82+
y: minY,
83+
width,
84+
height,
85+
}),
86+
new ZIndex(Infinity),
87+
// new SizeAttenuation(),
88+
)
89+
.id()
90+
.hold();
91+
92+
const tlAnchor = this.createAnchor(minX, minY);
93+
const trAnchor = this.createAnchor(maxX, minY);
94+
const blAnchor = this.createAnchor(minX, maxY);
95+
const brAnchor = this.createAnchor(maxX, maxY);
96+
97+
const transformEntity = this.commands.entity(transformer);
98+
transformEntity.appendChild(this.commands.entity(tlAnchor));
99+
transformEntity.appendChild(this.commands.entity(trAnchor));
100+
transformEntity.appendChild(this.commands.entity(blAnchor));
101+
transformEntity.appendChild(this.commands.entity(brAnchor));
102+
103+
this.commands.execute();
104+
105+
const camera = this.commands.entity(getSceneRoot(entity));
106+
camera.appendChild(this.commands.entity(transformer));
107+
108+
this.commands.execute();
109+
110+
this.#transformers.set(entity, transformer);
111+
});
112+
113+
this.selected.removed.forEach((entity) => {
114+
console.log('remove', entity);
115+
116+
if (this.#transformers.has(entity)) {
117+
const transformer = this.#transformers.get(entity);
118+
119+
transformer.add(ToBeDeleted);
120+
getDescendants(transformer).forEach((child) => {
121+
child.add(ToBeDeleted);
122+
});
123+
124+
this.#transformers.delete(entity);
125+
}
126+
});
127+
}
128+
129+
private createAnchor(cx: number, cy: number) {
130+
return this.commands
131+
.spawn(
132+
new UI(),
133+
new Transform(),
134+
new Renderable(),
135+
new FillSolid('#fff'),
136+
new Stroke({ width: 1, color: '#147af3' }),
137+
new Circle({
138+
cx,
139+
cy,
140+
r: 5,
141+
}),
142+
new SizeAttenuation(),
143+
)
144+
.id()
145+
.hold();
146+
}
147+
}

0 commit comments

Comments
 (0)