@multiplekex/shallot 0.2.4 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/core/component.ts +1 -1
- package/src/core/index.ts +1 -13
- package/src/core/math.ts +186 -0
- package/src/core/state.ts +1 -1
- package/src/core/xml.ts +56 -41
- package/src/extras/arrows/index.ts +3 -3
- package/src/extras/caustic.ts +37 -0
- package/src/extras/gradient/index.ts +63 -69
- package/src/extras/index.ts +3 -0
- package/src/extras/lines/index.ts +3 -3
- package/src/extras/orbit/index.ts +1 -1
- package/src/extras/skylab/index.ts +314 -0
- package/src/extras/text/font.ts +69 -14
- package/src/extras/text/index.ts +17 -69
- package/src/extras/text/sdf.ts +13 -2
- package/src/extras/water/index.ts +119 -0
- package/src/standard/defaults.ts +2 -0
- package/src/standard/index.ts +2 -0
- package/src/standard/raster/batch.ts +149 -0
- package/src/standard/raster/forward.ts +832 -0
- package/src/standard/raster/index.ts +191 -0
- package/src/standard/raster/shadow.ts +408 -0
- package/src/standard/{render → raytracing}/bvh/blas.ts +336 -88
- package/src/standard/raytracing/bvh/radix.ts +473 -0
- package/src/standard/raytracing/bvh/refit.ts +711 -0
- package/src/standard/{render → raytracing}/bvh/structs.ts +0 -55
- package/src/standard/{render → raytracing}/bvh/tlas.ts +155 -140
- package/src/standard/{render → raytracing}/bvh/traverse.ts +72 -64
- package/src/standard/{render → raytracing}/depth.ts +9 -9
- package/src/standard/raytracing/index.ts +409 -0
- package/src/standard/{render → raytracing}/instance.ts +31 -16
- package/src/standard/{render → raytracing}/ray.ts +1 -1
- package/src/standard/raytracing/shaders.ts +798 -0
- package/src/standard/{render → raytracing}/triangle.ts +1 -1
- package/src/standard/render/camera.ts +96 -106
- package/src/standard/render/data.ts +1 -1
- package/src/standard/render/index.ts +136 -220
- package/src/standard/render/indirect.ts +9 -10
- package/src/standard/render/light.ts +2 -2
- package/src/standard/render/mesh.ts +404 -0
- package/src/standard/render/overlay.ts +8 -5
- package/src/standard/render/pass.ts +1 -1
- package/src/standard/render/postprocess.ts +263 -242
- package/src/standard/render/scene.ts +28 -16
- package/src/standard/render/surface/index.ts +81 -12
- package/src/standard/render/surface/shaders.ts +511 -0
- package/src/standard/render/surface/structs.ts +23 -6
- package/src/standard/tween/tween.ts +44 -115
- package/src/standard/render/bvh/radix.ts +0 -476
- package/src/standard/render/forward/index.ts +0 -259
- package/src/standard/render/forward/raster.ts +0 -228
- package/src/standard/render/mesh/box.ts +0 -20
- package/src/standard/render/mesh/index.ts +0 -446
- package/src/standard/render/mesh/plane.ts +0 -11
- package/src/standard/render/mesh/sphere.ts +0 -40
- package/src/standard/render/mesh/unified.ts +0 -96
- package/src/standard/render/shaders.ts +0 -484
- package/src/standard/render/surface/compile.ts +0 -67
- package/src/standard/render/surface/noise.ts +0 -45
- package/src/standard/render/surface/wgsl.ts +0 -573
- /package/src/standard/{render → raytracing}/intersection.ts +0 -0
package/src/extras/text/font.ts
CHANGED
|
@@ -82,7 +82,10 @@ function parseHead(r: Reader, table: TableEntry): { unitsPerEm: number; indexToL
|
|
|
82
82
|
return { unitsPerEm, indexToLocFormat };
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
-
function parseHhea(
|
|
85
|
+
function parseHhea(
|
|
86
|
+
r: Reader,
|
|
87
|
+
table: TableEntry
|
|
88
|
+
): { ascender: number; descender: number; lineGap: number; numHMetrics: number } {
|
|
86
89
|
seek(r, table.offset + 4);
|
|
87
90
|
const ascender = i16(r);
|
|
88
91
|
const descender = i16(r);
|
|
@@ -92,7 +95,12 @@ function parseHhea(r: Reader, table: TableEntry): { ascender: number; descender:
|
|
|
92
95
|
return { ascender, descender, lineGap, numHMetrics };
|
|
93
96
|
}
|
|
94
97
|
|
|
95
|
-
function parseHmtx(
|
|
98
|
+
function parseHmtx(
|
|
99
|
+
r: Reader,
|
|
100
|
+
table: TableEntry,
|
|
101
|
+
numHMetrics: number,
|
|
102
|
+
numGlyphs: number
|
|
103
|
+
): { advances: Uint16Array } {
|
|
96
104
|
const advances = new Uint16Array(numGlyphs);
|
|
97
105
|
seek(r, table.offset);
|
|
98
106
|
|
|
@@ -114,7 +122,12 @@ function parseMaxp(r: Reader, table: TableEntry): number {
|
|
|
114
122
|
return u16(r);
|
|
115
123
|
}
|
|
116
124
|
|
|
117
|
-
function parseLoca(
|
|
125
|
+
function parseLoca(
|
|
126
|
+
r: Reader,
|
|
127
|
+
table: TableEntry,
|
|
128
|
+
numGlyphs: number,
|
|
129
|
+
indexToLocFormat: number
|
|
130
|
+
): Uint32Array {
|
|
118
131
|
const offsets = new Uint32Array(numGlyphs + 1);
|
|
119
132
|
seek(r, table.offset);
|
|
120
133
|
|
|
@@ -210,7 +223,8 @@ function parseCmap(r: Reader, table: TableEntry): Map<number, number> {
|
|
|
210
223
|
if (rangeOffset === 0) {
|
|
211
224
|
glyphId = (c + delta) & 0xffff;
|
|
212
225
|
} else {
|
|
213
|
-
const glyphIdOffset =
|
|
226
|
+
const glyphIdOffset =
|
|
227
|
+
idRangeOffsetPos + i * 2 + rangeOffset + (c - start) * 2;
|
|
214
228
|
seek(r, glyphIdOffset);
|
|
215
229
|
glyphId = u16(r);
|
|
216
230
|
if (glyphId !== 0) {
|
|
@@ -291,7 +305,12 @@ const REPEAT = 8;
|
|
|
291
305
|
const X_SAME = 16;
|
|
292
306
|
const Y_SAME = 32;
|
|
293
307
|
|
|
294
|
-
function parseGlyph(
|
|
308
|
+
function parseGlyph(
|
|
309
|
+
r: Reader,
|
|
310
|
+
glyfOffset: number,
|
|
311
|
+
loca: Uint32Array,
|
|
312
|
+
glyphId: number
|
|
313
|
+
): { path: string; bounds: [number, number, number, number] } | null {
|
|
295
314
|
const start = loca[glyphId];
|
|
296
315
|
const end = loca[glyphId + 1];
|
|
297
316
|
if (start === end) return null;
|
|
@@ -372,7 +391,11 @@ function parseGlyph(r: Reader, glyfOffset: number, loca: Uint32Array, glyphId: n
|
|
|
372
391
|
while (firstOn < points.length && !points[firstOn].on) firstOn++;
|
|
373
392
|
|
|
374
393
|
if (firstOn === points.length) {
|
|
375
|
-
const mid = {
|
|
394
|
+
const mid = {
|
|
395
|
+
x: (points[0].x + points[1].x) / 2,
|
|
396
|
+
y: (points[0].y + points[1].y) / 2,
|
|
397
|
+
on: true,
|
|
398
|
+
};
|
|
376
399
|
points.unshift(mid);
|
|
377
400
|
firstOn = 0;
|
|
378
401
|
}
|
|
@@ -412,17 +435,28 @@ function parseGlyph(r: Reader, glyfOffset: number, loca: Uint32Array, glyphId: n
|
|
|
412
435
|
return { path, bounds: [xMin, yMin, xMax, yMax] };
|
|
413
436
|
}
|
|
414
437
|
|
|
415
|
-
function parseCompositeGlyph(
|
|
438
|
+
function parseCompositeGlyph(
|
|
439
|
+
r: Reader,
|
|
440
|
+
glyfOffset: number,
|
|
441
|
+
loca: Uint32Array
|
|
442
|
+
): { path: string; bounds: [number, number, number, number] } | null {
|
|
416
443
|
let path = "";
|
|
417
|
-
let xMin = Infinity,
|
|
444
|
+
let xMin = Infinity,
|
|
445
|
+
yMin = Infinity,
|
|
446
|
+
xMax = -Infinity,
|
|
447
|
+
yMax = -Infinity;
|
|
418
448
|
let hasMore = true;
|
|
419
449
|
|
|
420
450
|
while (hasMore) {
|
|
421
451
|
const flags = u16(r);
|
|
422
452
|
const glyphIndex = u16(r);
|
|
423
453
|
|
|
424
|
-
let dx = 0,
|
|
425
|
-
|
|
454
|
+
let dx = 0,
|
|
455
|
+
dy = 0;
|
|
456
|
+
let a = 1,
|
|
457
|
+
b = 0,
|
|
458
|
+
c = 0,
|
|
459
|
+
d = 1;
|
|
426
460
|
|
|
427
461
|
if (flags & 1) {
|
|
428
462
|
dx = i16(r);
|
|
@@ -464,7 +498,15 @@ function parseCompositeGlyph(r: Reader, glyfOffset: number, loca: Uint32Array):
|
|
|
464
498
|
return { path, bounds: [xMin, yMin, xMax, yMax] };
|
|
465
499
|
}
|
|
466
500
|
|
|
467
|
-
function transformPath(
|
|
501
|
+
function transformPath(
|
|
502
|
+
path: string,
|
|
503
|
+
a: number,
|
|
504
|
+
b: number,
|
|
505
|
+
c: number,
|
|
506
|
+
d: number,
|
|
507
|
+
dx: number,
|
|
508
|
+
dy: number
|
|
509
|
+
): string {
|
|
468
510
|
return path.replace(/(-?\d+(?:\.\d+)?),(-?\d+(?:\.\d+)?)/g, (_, x, y) => {
|
|
469
511
|
const nx = parseFloat(x) * a + parseFloat(y) * b + dx;
|
|
470
512
|
const ny = parseFloat(x) * c + parseFloat(y) * d + dy;
|
|
@@ -485,7 +527,15 @@ export function parseFont(buffer: ArrayBuffer): Font {
|
|
|
485
527
|
const glyfTable = tables.get("glyf");
|
|
486
528
|
const kernTable = tables.get("kern");
|
|
487
529
|
|
|
488
|
-
if (
|
|
530
|
+
if (
|
|
531
|
+
!headTable ||
|
|
532
|
+
!hheaTable ||
|
|
533
|
+
!hmtxTable ||
|
|
534
|
+
!maxpTable ||
|
|
535
|
+
!cmapTable ||
|
|
536
|
+
!locaTable ||
|
|
537
|
+
!glyfTable
|
|
538
|
+
) {
|
|
489
539
|
throw new Error("Missing required font tables");
|
|
490
540
|
}
|
|
491
541
|
|
|
@@ -497,14 +547,19 @@ export function parseFont(buffer: ArrayBuffer): Font {
|
|
|
497
547
|
const cmap = parseCmap(r, cmapTable);
|
|
498
548
|
const kern = kernTable ? parseKern(r, kernTable) : new Map<number, number>();
|
|
499
549
|
|
|
500
|
-
const glyphCache = new Map<
|
|
550
|
+
const glyphCache = new Map<
|
|
551
|
+
number,
|
|
552
|
+
{ path: string; bounds: [number, number, number, number] } | null
|
|
553
|
+
>();
|
|
501
554
|
const glyfOffset = glyfTable.offset;
|
|
502
555
|
|
|
503
556
|
function getGlyphId(char: string): number {
|
|
504
557
|
return cmap.get(char.codePointAt(0) ?? 0) ?? 0;
|
|
505
558
|
}
|
|
506
559
|
|
|
507
|
-
function getGlyph(
|
|
560
|
+
function getGlyph(
|
|
561
|
+
glyphId: number
|
|
562
|
+
): { path: string; bounds: [number, number, number, number] } | null {
|
|
508
563
|
if (glyphCache.has(glyphId)) return glyphCache.get(glyphId)!;
|
|
509
564
|
const glyph = parseGlyph(r, glyfOffset, loca, glyphId);
|
|
510
565
|
glyphCache.set(glyphId, glyph);
|
package/src/extras/text/index.ts
CHANGED
|
@@ -3,12 +3,10 @@ import { SDFGenerator } from "./sdf";
|
|
|
3
3
|
import {
|
|
4
4
|
MAX_ENTITIES,
|
|
5
5
|
resource,
|
|
6
|
-
registerPostLoadHook,
|
|
7
6
|
createFieldProxy,
|
|
8
7
|
type Plugin,
|
|
9
8
|
type State,
|
|
10
9
|
type System,
|
|
11
|
-
type PostLoadContext,
|
|
12
10
|
type FieldProxy,
|
|
13
11
|
} from "../../core";
|
|
14
12
|
import { setTraits } from "../../core/component";
|
|
@@ -21,8 +19,8 @@ import {
|
|
|
21
19
|
type Draw,
|
|
22
20
|
type SharedPassContext,
|
|
23
21
|
} from "../../standard/render";
|
|
24
|
-
import {
|
|
25
|
-
import { SCENE_STRUCT_WGSL } from "../../standard/render/
|
|
22
|
+
import { Z_FORMAT } from "../../standard/render/scene";
|
|
23
|
+
import { SCENE_STRUCT_WGSL } from "../../standard/render/surface/structs";
|
|
26
24
|
import { Transform } from "../../standard/transforms";
|
|
27
25
|
|
|
28
26
|
const MAX_GLYPHS = 50000;
|
|
@@ -31,14 +29,16 @@ const SDF_SIZE = 96;
|
|
|
31
29
|
const SDF_EXPONENT = 9;
|
|
32
30
|
const fontUrls: string[] = [];
|
|
33
31
|
const loadedFonts: (Font | null)[] = [];
|
|
32
|
+
const fontNames = new Map<string, number>();
|
|
34
33
|
|
|
35
34
|
export const DEFAULT_FONT =
|
|
36
35
|
"https://fonts.gstatic.com/s/inter/v20/UcCO3FwrK3iLTeHuS_nVMrMxCp50SjIw2boKoduKmMEVuLyfMZg.ttf";
|
|
37
36
|
|
|
38
|
-
export function font(url: string): number {
|
|
37
|
+
export function font(url: string, name?: string): number {
|
|
39
38
|
const id = fontUrls.length;
|
|
40
39
|
fontUrls.push(url);
|
|
41
40
|
loadedFonts.push(null);
|
|
41
|
+
if (name) fontNames.set(name, id);
|
|
42
42
|
return id;
|
|
43
43
|
}
|
|
44
44
|
|
|
@@ -46,9 +46,14 @@ export function getFont(id: number): Font | null {
|
|
|
46
46
|
return loadedFonts[id] ?? null;
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
export function getFontByName(name: string): number | undefined {
|
|
50
|
+
return fontNames.get(name);
|
|
51
|
+
}
|
|
52
|
+
|
|
49
53
|
export function resetFonts(): void {
|
|
50
54
|
fontUrls.length = 0;
|
|
51
55
|
loadedFonts.length = 0;
|
|
56
|
+
fontNames.clear();
|
|
52
57
|
}
|
|
53
58
|
|
|
54
59
|
async function loadFonts(): Promise<void> {
|
|
@@ -141,35 +146,6 @@ export const Text = {
|
|
|
141
146
|
colorB: createFieldProxy(data, 12, 10),
|
|
142
147
|
};
|
|
143
148
|
|
|
144
|
-
interface PendingText {
|
|
145
|
-
readonly eid: number;
|
|
146
|
-
readonly content: string;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
let pendingTextContent: PendingText[] = [];
|
|
150
|
-
|
|
151
|
-
function parseTextAttrs(attrs: Record<string, string>): Record<string, string> {
|
|
152
|
-
if (attrs._value) {
|
|
153
|
-
const parsed: Record<string, string> = {};
|
|
154
|
-
for (const part of attrs._value.split(";")) {
|
|
155
|
-
const colonIdx = part.indexOf(":");
|
|
156
|
-
if (colonIdx === -1) continue;
|
|
157
|
-
const key = part.slice(0, colonIdx).trim();
|
|
158
|
-
const value = part.slice(colonIdx + 1).trim();
|
|
159
|
-
if (key && value) parsed[key] = value;
|
|
160
|
-
}
|
|
161
|
-
return parsed;
|
|
162
|
-
}
|
|
163
|
-
return attrs;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
function finalizePendingText(_state: State, _context: PostLoadContext): void {
|
|
167
|
-
for (const pending of pendingTextContent) {
|
|
168
|
-
Text.content[pending.eid] = pending.content;
|
|
169
|
-
}
|
|
170
|
-
pendingTextContent = [];
|
|
171
|
-
}
|
|
172
|
-
|
|
173
149
|
setTraits(Text, {
|
|
174
150
|
defaults: () => ({
|
|
175
151
|
font: 0,
|
|
@@ -180,36 +156,7 @@ setTraits(Text, {
|
|
|
180
156
|
anchorY: 0,
|
|
181
157
|
color: 0xffffff,
|
|
182
158
|
}),
|
|
183
|
-
|
|
184
|
-
const parsed = parseTextAttrs(attrs);
|
|
185
|
-
const result: Record<string, number> = {};
|
|
186
|
-
|
|
187
|
-
if (parsed.content) {
|
|
188
|
-
pendingTextContent.push({ eid, content: parsed.content });
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
if (parsed.font) result.font = parseInt(parsed.font, 10);
|
|
192
|
-
if (parsed["font-size"]) result.fontSize = parseFloat(parsed["font-size"]);
|
|
193
|
-
if (parsed.fontSize) result.fontSize = parseFloat(parsed.fontSize);
|
|
194
|
-
if (parsed.opacity) result.opacity = parseFloat(parsed.opacity);
|
|
195
|
-
if (parsed.visible) result.visible = parseFloat(parsed.visible);
|
|
196
|
-
if (parsed["anchor-x"]) result.anchorX = parseFloat(parsed["anchor-x"]);
|
|
197
|
-
if (parsed.anchorX) result.anchorX = parseFloat(parsed.anchorX);
|
|
198
|
-
if (parsed["anchor-y"]) result.anchorY = parseFloat(parsed["anchor-y"]);
|
|
199
|
-
if (parsed.anchorY) result.anchorY = parseFloat(parsed.anchorY);
|
|
200
|
-
if (parsed.color) {
|
|
201
|
-
const colorStr = parsed.color;
|
|
202
|
-
if (colorStr.startsWith("0x") || colorStr.startsWith("0X")) {
|
|
203
|
-
result.color = parseInt(colorStr, 16);
|
|
204
|
-
} else if (colorStr.startsWith("#")) {
|
|
205
|
-
result.color = parseInt(colorStr.slice(1), 16);
|
|
206
|
-
} else {
|
|
207
|
-
result.color = parseInt(colorStr, 10);
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
return result;
|
|
212
|
-
},
|
|
159
|
+
parse: { font: getFontByName },
|
|
213
160
|
});
|
|
214
161
|
|
|
215
162
|
interface GlyphMetrics {
|
|
@@ -366,7 +313,7 @@ function layoutText(text: string, atlas: GlyphAtlas, fontSize: number): LayoutRe
|
|
|
366
313
|
if (!metrics) continue;
|
|
367
314
|
|
|
368
315
|
if (prevChar) {
|
|
369
|
-
cursorX += atlas.font.kerning(prevChar, char) / atlas.font.unitsPerEm * scale;
|
|
316
|
+
cursorX += (atlas.font.kerning(prevChar, char) / atlas.font.unitsPerEm) * scale;
|
|
370
317
|
}
|
|
371
318
|
|
|
372
319
|
const glyphW = metrics.glyphWidth * scale;
|
|
@@ -579,7 +526,7 @@ function createTextPipeline(
|
|
|
579
526
|
cullMode: "none",
|
|
580
527
|
},
|
|
581
528
|
depthStencil: {
|
|
582
|
-
format:
|
|
529
|
+
format: Z_FORMAT,
|
|
583
530
|
depthCompare: "less",
|
|
584
531
|
depthWriteEnabled: false,
|
|
585
532
|
},
|
|
@@ -669,6 +616,8 @@ interface PendingGlyph {
|
|
|
669
616
|
a: number;
|
|
670
617
|
}
|
|
671
618
|
|
|
619
|
+
const glyphsByFont: PendingGlyph[][] = [];
|
|
620
|
+
|
|
672
621
|
const TextSystem: System = {
|
|
673
622
|
group: "draw",
|
|
674
623
|
|
|
@@ -681,7 +630,8 @@ const TextSystem: System = {
|
|
|
681
630
|
const { atlases, staging, ranges } = text;
|
|
682
631
|
const stagingU32 = new Uint32Array(staging.buffer);
|
|
683
632
|
|
|
684
|
-
|
|
633
|
+
while (glyphsByFont.length < atlases.length) glyphsByFont.push([]);
|
|
634
|
+
for (let i = 0; i < atlases.length; i++) glyphsByFont[i].length = 0;
|
|
685
635
|
|
|
686
636
|
for (const eid of state.query([Text, Transform])) {
|
|
687
637
|
if (!Text.visible[eid]) continue;
|
|
@@ -785,8 +735,6 @@ export const TextPlugin: Plugin = {
|
|
|
785
735
|
dependencies: [ComputePlugin, RenderPlugin],
|
|
786
736
|
|
|
787
737
|
async initialize(state: State) {
|
|
788
|
-
registerPostLoadHook(finalizePendingText);
|
|
789
|
-
|
|
790
738
|
const compute = Compute.from(state);
|
|
791
739
|
const render = Render.from(state);
|
|
792
740
|
if (!compute || !render) return;
|
package/src/extras/text/sdf.ts
CHANGED
|
@@ -340,14 +340,25 @@ export class SDFGenerator {
|
|
|
340
340
|
const segments = segmentPath(path, this.curveSubdivisions);
|
|
341
341
|
if (segments.length === 0) return;
|
|
342
342
|
if (segments.length > this.maxSegments) {
|
|
343
|
-
console.warn(
|
|
343
|
+
console.warn(
|
|
344
|
+
`Too many segments (${segments.length}), truncating to ${this.maxSegments}`
|
|
345
|
+
);
|
|
344
346
|
segments.length = this.maxSegments;
|
|
345
347
|
}
|
|
346
348
|
|
|
347
349
|
const [xMin, yMin, xMax, yMax] = bounds;
|
|
348
350
|
const maxDist = Math.max(xMax - xMin, yMax - yMin) / 2;
|
|
349
351
|
|
|
350
|
-
const uniformData = new Float32Array([
|
|
352
|
+
const uniformData = new Float32Array([
|
|
353
|
+
xMin,
|
|
354
|
+
yMin,
|
|
355
|
+
xMax,
|
|
356
|
+
yMax,
|
|
357
|
+
maxDist,
|
|
358
|
+
this.exponent,
|
|
359
|
+
0,
|
|
360
|
+
0,
|
|
361
|
+
]);
|
|
351
362
|
this.device.queue.writeBuffer(this.uniformBuffer, 0, uniformData);
|
|
352
363
|
|
|
353
364
|
const segmentData = new Float32Array(segments.length * 4);
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import type { Plugin, State, System } from "../../core";
|
|
2
|
+
import { setTraits } from "../../core/component";
|
|
3
|
+
import { Transform } from "../../standard/transforms";
|
|
4
|
+
import { Mesh, Volume, mesh, Surface, surface, RenderPlugin } from "../../standard/render";
|
|
5
|
+
import type { MeshData } from "../../standard/render";
|
|
6
|
+
|
|
7
|
+
const SEED1 = "vec2(127.1, 311.7)";
|
|
8
|
+
const SEED2 = "vec2(269.5, 183.3)";
|
|
9
|
+
|
|
10
|
+
const waterSurface = surface({
|
|
11
|
+
fragment: `
|
|
12
|
+
let p = (*surface).worldPos.xz * 3.0;
|
|
13
|
+
let t = scene.time;
|
|
14
|
+
let t1 = vec2(t * 0.6, t * 0.4);
|
|
15
|
+
let t2 = vec2(t * 0.5, t * 0.7);
|
|
16
|
+
let eps = 0.1;
|
|
17
|
+
|
|
18
|
+
let h = (value2d(p + t1, ${SEED1}) + value2d(p - t2, ${SEED2})) * 0.5;
|
|
19
|
+
let hx = (value2d(p + vec2(eps, 0.0) + t1, ${SEED1}) + value2d(p + vec2(eps, 0.0) - t2, ${SEED2})) * 0.5;
|
|
20
|
+
let hz = (value2d(p + vec2(0.0, eps) + t1, ${SEED1}) + value2d(p + vec2(0.0, eps) - t2, ${SEED2})) * 0.5;
|
|
21
|
+
|
|
22
|
+
let dx = (hx - h) / eps;
|
|
23
|
+
let dz = (hz - h) / eps;
|
|
24
|
+
(*surface).normal = normalize(vec3(-dx * 0.015, 1.0, -dz * 0.015));`,
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
function createSubdividedPlane(subdivisions: number = 32): MeshData {
|
|
28
|
+
const segments = subdivisions;
|
|
29
|
+
const vertexCount = (segments + 1) * (segments + 1);
|
|
30
|
+
const indexCount = segments * segments * 6;
|
|
31
|
+
|
|
32
|
+
const vertices = new Float32Array(vertexCount * 6);
|
|
33
|
+
const indices = new Uint16Array(indexCount);
|
|
34
|
+
|
|
35
|
+
let vi = 0;
|
|
36
|
+
for (let z = 0; z <= segments; z++) {
|
|
37
|
+
for (let x = 0; x <= segments; x++) {
|
|
38
|
+
const u = x / segments;
|
|
39
|
+
const v = z / segments;
|
|
40
|
+
vertices[vi++] = u - 0.5;
|
|
41
|
+
vertices[vi++] = 0;
|
|
42
|
+
vertices[vi++] = v - 0.5;
|
|
43
|
+
vertices[vi++] = 0;
|
|
44
|
+
vertices[vi++] = 1;
|
|
45
|
+
vertices[vi++] = 0;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
let ii = 0;
|
|
50
|
+
for (let z = 0; z < segments; z++) {
|
|
51
|
+
for (let x = 0; x < segments; x++) {
|
|
52
|
+
const topLeft = z * (segments + 1) + x;
|
|
53
|
+
const topRight = topLeft + 1;
|
|
54
|
+
const bottomLeft = (z + 1) * (segments + 1) + x;
|
|
55
|
+
const bottomRight = bottomLeft + 1;
|
|
56
|
+
|
|
57
|
+
indices[ii++] = topLeft;
|
|
58
|
+
indices[ii++] = bottomLeft;
|
|
59
|
+
indices[ii++] = topRight;
|
|
60
|
+
indices[ii++] = topRight;
|
|
61
|
+
indices[ii++] = bottomLeft;
|
|
62
|
+
indices[ii++] = bottomRight;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return { vertices, indices, vertexCount, indexCount };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const waterMesh = mesh(createSubdividedPlane(64));
|
|
70
|
+
|
|
71
|
+
export const Water = {
|
|
72
|
+
color: [] as number[],
|
|
73
|
+
opacity: [] as number[],
|
|
74
|
+
roughness: [] as number[],
|
|
75
|
+
metallic: [] as number[],
|
|
76
|
+
ior: [] as number[],
|
|
77
|
+
level: [] as number[],
|
|
78
|
+
};
|
|
79
|
+
setTraits(Water, {
|
|
80
|
+
defaults: () => ({
|
|
81
|
+
color: 0x4090a0,
|
|
82
|
+
opacity: 0.5,
|
|
83
|
+
roughness: 0.05,
|
|
84
|
+
metallic: 0.8,
|
|
85
|
+
ior: 1.33,
|
|
86
|
+
level: 0,
|
|
87
|
+
}),
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
const WaterSystem: System = {
|
|
91
|
+
group: "simulation",
|
|
92
|
+
|
|
93
|
+
update(state: State) {
|
|
94
|
+
for (const eid of state.query([Water])) {
|
|
95
|
+
if (!state.hasComponent(eid, Transform)) state.addComponent(eid, Transform);
|
|
96
|
+
if (!state.hasComponent(eid, Mesh)) state.addComponent(eid, Mesh);
|
|
97
|
+
if (!state.hasComponent(eid, Surface)) state.addComponent(eid, Surface);
|
|
98
|
+
|
|
99
|
+
Transform.posY[eid] = Water.level[eid];
|
|
100
|
+
Mesh.shape[eid] = waterMesh;
|
|
101
|
+
Mesh.color[eid] = Water.color[eid];
|
|
102
|
+
Mesh.opacity[eid] = Water.opacity[eid];
|
|
103
|
+
Mesh.roughness[eid] = Water.roughness[eid];
|
|
104
|
+
Mesh.metallic[eid] = Water.metallic[eid];
|
|
105
|
+
Mesh.ior[eid] = Water.ior[eid];
|
|
106
|
+
Mesh.sizeX[eid] = 40;
|
|
107
|
+
Mesh.sizeY[eid] = 1;
|
|
108
|
+
Mesh.sizeZ[eid] = 40;
|
|
109
|
+
Mesh.volume[eid] = Volume.HalfSpace;
|
|
110
|
+
Surface.type[eid] = waterSurface;
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
export const WaterPlugin: Plugin = {
|
|
116
|
+
systems: [WaterSystem],
|
|
117
|
+
components: { Water },
|
|
118
|
+
dependencies: [RenderPlugin],
|
|
119
|
+
};
|
package/src/standard/defaults.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { TransformsPlugin } from "./transforms";
|
|
|
3
3
|
import { InputPlugin } from "./input";
|
|
4
4
|
import { ComputePlugin, initCanvas } from "./compute";
|
|
5
5
|
import { RenderPlugin } from "./render";
|
|
6
|
+
import { RasterPlugin } from "./raster";
|
|
6
7
|
import { ActivityPlugin, spinnerDark } from "./activity";
|
|
7
8
|
import { shallotDark } from "./loading";
|
|
8
9
|
|
|
@@ -12,6 +13,7 @@ export const DEFAULT_PLUGINS: readonly Plugin[] = [
|
|
|
12
13
|
InputPlugin,
|
|
13
14
|
ComputePlugin,
|
|
14
15
|
RenderPlugin,
|
|
16
|
+
RasterPlugin,
|
|
15
17
|
];
|
|
16
18
|
|
|
17
19
|
StateBuilder.defaultPlugins = DEFAULT_PLUGINS;
|
package/src/standard/index.ts
CHANGED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { MAX_ENTITIES } from "../../core";
|
|
2
|
+
import { MAX_SURFACES, MAX_BATCH_SLOTS, Mesh, getMesh, createMeshBuffers } from "../render/mesh";
|
|
3
|
+
import type { MeshBuffers } from "../render/mesh";
|
|
4
|
+
|
|
5
|
+
const INVALID_SHAPE = 0xffffffff;
|
|
6
|
+
const SLOT_STRIDE = 4;
|
|
7
|
+
|
|
8
|
+
export interface Batch {
|
|
9
|
+
entityIds: GPUBuffer;
|
|
10
|
+
buffers: Map<number, MeshBuffers>;
|
|
11
|
+
opaque: Uint32Array;
|
|
12
|
+
transparent: Uint32Array;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function createBatch(device: GPUDevice): Batch {
|
|
16
|
+
return {
|
|
17
|
+
entityIds: device.createBuffer({
|
|
18
|
+
label: "raster-entity-ids",
|
|
19
|
+
size: MAX_ENTITIES * 4,
|
|
20
|
+
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
|
|
21
|
+
}),
|
|
22
|
+
buffers: new Map(),
|
|
23
|
+
opaque: new Uint32Array(MAX_BATCH_SLOTS * SLOT_STRIDE),
|
|
24
|
+
transparent: new Uint32Array(MAX_BATCH_SLOTS * SLOT_STRIDE),
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function slotInstanceCount(slots: Uint32Array, i: number): number {
|
|
29
|
+
return slots[i * SLOT_STRIDE + 1];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function slotIndexCount(slots: Uint32Array, i: number): number {
|
|
33
|
+
return slots[i * SLOT_STRIDE];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function slotFirstInstance(slots: Uint32Array, i: number): number {
|
|
37
|
+
return slots[i * SLOT_STRIDE + 2];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function slotShapeId(slots: Uint32Array, i: number): number {
|
|
41
|
+
return slots[i * SLOT_STRIDE + 3];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const entityIdsData = new Uint32Array(MAX_ENTITIES);
|
|
45
|
+
const opaqueCounts = new Uint32Array(MAX_BATCH_SLOTS);
|
|
46
|
+
const transparentCounts = new Uint32Array(MAX_BATCH_SLOTS);
|
|
47
|
+
const opaqueOffsets = new Uint32Array(MAX_BATCH_SLOTS);
|
|
48
|
+
const transparentOffsets = new Uint32Array(MAX_BATCH_SLOTS);
|
|
49
|
+
const opaqueWriteOffsets = new Uint32Array(MAX_BATCH_SLOTS);
|
|
50
|
+
const transparentWriteOffsets = new Uint32Array(MAX_BATCH_SLOTS);
|
|
51
|
+
|
|
52
|
+
const opaqueEids = new Uint32Array(MAX_ENTITIES);
|
|
53
|
+
const opaqueBatchIds = new Uint32Array(MAX_ENTITIES);
|
|
54
|
+
const transparentEids = new Uint32Array(MAX_ENTITIES);
|
|
55
|
+
const transparentBatchIds = new Uint32Array(MAX_ENTITIES);
|
|
56
|
+
|
|
57
|
+
export function uploadBatch(
|
|
58
|
+
device: GPUDevice,
|
|
59
|
+
entities: Iterable<number>,
|
|
60
|
+
getSurface: (eid: number) => number,
|
|
61
|
+
getOpacity: (eid: number) => number,
|
|
62
|
+
state: Batch
|
|
63
|
+
): void {
|
|
64
|
+
opaqueCounts.fill(0);
|
|
65
|
+
transparentCounts.fill(0);
|
|
66
|
+
let opaqueLen = 0;
|
|
67
|
+
let transparentLen = 0;
|
|
68
|
+
|
|
69
|
+
for (const eid of entities) {
|
|
70
|
+
const shape = Mesh.shape[eid];
|
|
71
|
+
if (shape === INVALID_SHAPE) continue;
|
|
72
|
+
|
|
73
|
+
const surface = getSurface(eid);
|
|
74
|
+
const batchIndex = shape * MAX_SURFACES + surface;
|
|
75
|
+
if (batchIndex >= MAX_BATCH_SLOTS) continue;
|
|
76
|
+
|
|
77
|
+
if (getOpacity(eid) < 1.0) {
|
|
78
|
+
transparentEids[transparentLen] = eid;
|
|
79
|
+
transparentBatchIds[transparentLen] = batchIndex;
|
|
80
|
+
transparentLen++;
|
|
81
|
+
transparentCounts[batchIndex]++;
|
|
82
|
+
} else {
|
|
83
|
+
opaqueEids[opaqueLen] = eid;
|
|
84
|
+
opaqueBatchIds[opaqueLen] = batchIndex;
|
|
85
|
+
opaqueLen++;
|
|
86
|
+
opaqueCounts[batchIndex]++;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
let offset = 0;
|
|
91
|
+
for (let i = 0; i < MAX_BATCH_SLOTS; i++) {
|
|
92
|
+
opaqueOffsets[i] = offset;
|
|
93
|
+
offset += opaqueCounts[i];
|
|
94
|
+
}
|
|
95
|
+
for (let i = 0; i < MAX_BATCH_SLOTS; i++) {
|
|
96
|
+
transparentOffsets[i] = offset;
|
|
97
|
+
offset += transparentCounts[i];
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
opaqueWriteOffsets.fill(0);
|
|
101
|
+
for (let i = 0; i < opaqueLen; i++) {
|
|
102
|
+
const batchIndex = opaqueBatchIds[i];
|
|
103
|
+
const idx = opaqueOffsets[batchIndex] + opaqueWriteOffsets[batchIndex];
|
|
104
|
+
entityIdsData[idx] = opaqueEids[i];
|
|
105
|
+
opaqueWriteOffsets[batchIndex]++;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
transparentWriteOffsets.fill(0);
|
|
109
|
+
for (let i = 0; i < transparentLen; i++) {
|
|
110
|
+
const batchIndex = transparentBatchIds[i];
|
|
111
|
+
const idx = transparentOffsets[batchIndex] + transparentWriteOffsets[batchIndex];
|
|
112
|
+
entityIdsData[idx] = transparentEids[i];
|
|
113
|
+
transparentWriteOffsets[batchIndex]++;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const totalEntities = offset;
|
|
117
|
+
if (totalEntities > 0) {
|
|
118
|
+
device.queue.writeBuffer(state.entityIds, 0, entityIdsData, 0, totalEntities);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
for (let batchIndex = 0; batchIndex < MAX_BATCH_SLOTS; batchIndex++) {
|
|
122
|
+
const shapeId = Math.floor(batchIndex / MAX_SURFACES);
|
|
123
|
+
const o = batchIndex * SLOT_STRIDE;
|
|
124
|
+
|
|
125
|
+
const opaqueCount = opaqueCounts[batchIndex];
|
|
126
|
+
const transparentCount = transparentCounts[batchIndex];
|
|
127
|
+
|
|
128
|
+
let indexCount = 0;
|
|
129
|
+
if (opaqueCount > 0 || transparentCount > 0) {
|
|
130
|
+
const mesh = getMesh(shapeId);
|
|
131
|
+
if (mesh) {
|
|
132
|
+
indexCount = mesh.indexCount;
|
|
133
|
+
if (!state.buffers.has(shapeId)) {
|
|
134
|
+
state.buffers.set(shapeId, createMeshBuffers(device, mesh));
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
state.opaque[o] = opaqueCount > 0 ? indexCount : 0;
|
|
140
|
+
state.opaque[o + 1] = opaqueCount;
|
|
141
|
+
state.opaque[o + 2] = opaqueOffsets[batchIndex];
|
|
142
|
+
state.opaque[o + 3] = shapeId;
|
|
143
|
+
|
|
144
|
+
state.transparent[o] = transparentCount > 0 ? indexCount : 0;
|
|
145
|
+
state.transparent[o + 1] = transparentCount;
|
|
146
|
+
state.transparent[o + 2] = transparentOffsets[batchIndex];
|
|
147
|
+
state.transparent[o + 3] = shapeId;
|
|
148
|
+
}
|
|
149
|
+
}
|