vgpu-render 0.1.0 → 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/README.md +101 -34
- package/dist/curve.d.ts +26 -1
- package/dist/curve.js +53 -0
- package/dist/debug-draw-3d.d.ts +3 -3
- package/dist/debug-draw-3d.js +10 -10
- package/dist/frame-draw-list-2d.d.ts +286 -0
- package/dist/frame-draw-list-2d.js +668 -0
- package/dist/{frame-recorder.d.ts → frame-draw-list.d.ts} +60 -8
- package/dist/{frame-recorder.js → frame-draw-list.js} +51 -10
- package/dist/geometry-generators.d.ts +11 -2
- package/dist/geometry-generators.js +37 -1
- package/dist/gpu-debug-draw-2d.d.ts +1 -1
- package/dist/gpu-debug-draw-2d.js +10 -2
- package/dist/gpu-debug-draw-3d.d.ts +1 -1
- package/dist/gpu-debug-draw-3d.js +4 -2
- package/dist/gpu-text-3d.d.ts +1 -1
- package/dist/gpu-text-3d.js +49 -2
- package/dist/index.d.ts +3 -3
- package/dist/index.js +2 -3
- package/dist/mesh-batch-3d.d.ts +1 -1
- package/dist/shape2d-shader.js +295 -7
- package/dist/text-atlas.d.ts +6 -4
- package/dist/text-atlas.js +9 -6
- package/dist/text-font.d.ts +48 -7
- package/dist/text-font.js +63 -11
- package/dist/text-layout.d.ts +63 -4
- package/dist/text-layout.js +58 -8
- package/dist/text-msdf-baked.d.ts +9 -4
- package/dist/text-msdf-baked.js +9 -4
- package/dist/text-msdf-font.d.ts +10 -4
- package/dist/text-msdf-font.js +9 -3
- package/dist/vector-math.d.ts +19 -0
- package/dist/vector-math.js +37 -0
- package/package.json +1 -1
- package/dist/frame-recorder-2d.d.ts +0 -177
- package/dist/frame-recorder-2d.js +0 -378
- package/dist/layout-3d.d.ts +0 -227
- package/dist/layout-3d.js +0 -226
- package/dist/scene-examples.d.ts +0 -39
- package/dist/scene-examples.js +0 -200
- package/dist/scenes-2d.d.ts +0 -18
- package/dist/scenes-2d.js +0 -160
- package/dist/scenes-3d.d.ts +0 -46
- package/dist/scenes-3d.js +0 -1994
- package/dist/text-scenes.d.ts +0 -85
- package/dist/text-scenes.js +0 -338
package/dist/text-layout.d.ts
CHANGED
|
@@ -40,12 +40,62 @@ export type GlyphMetrics = {
|
|
|
40
40
|
/** Ink box top edge relative to the baseline, y down: positive is below the baseline. */
|
|
41
41
|
readonly bearingTop: number;
|
|
42
42
|
};
|
|
43
|
+
/**
|
|
44
|
+
* reference `FontStyle`, minus `Oblique(angle)`.
|
|
45
|
+
*
|
|
46
|
+
* Canvas2D accepts the `italic` keyword in its font shorthand and silently drops the slant angle —
|
|
47
|
+
* `oblique 14deg` reads back as `italic` — so an oblique angle could not be honoured and is left out
|
|
48
|
+
* rather than accepted and ignored. A face without an italic face is slanted by the browser.
|
|
49
|
+
*/
|
|
50
|
+
export type TextFontStyle = "normal" | "italic";
|
|
51
|
+
/**
|
|
52
|
+
* reference `FontWidth` stops, spelled as the CSS `font-stretch` keywords Canvas2D understands.
|
|
53
|
+
*
|
|
54
|
+
* reference takes a percentage (`FontWidth::SEMI_CONDENSED` is 87.5), but Canvas2D rejects a percentage in
|
|
55
|
+
* the font shorthand and only accepts these nine keywords. The axis is inert unless the face actually
|
|
56
|
+
* carries a `wdth` axis: a static face like the bundled subset keeps the keyword out of its resolved
|
|
57
|
+
* font, so every stop draws the same width there.
|
|
58
|
+
*/
|
|
59
|
+
export type TextFontWidth = "ultra-condensed" | "extra-condensed" | "condensed" | "semi-condensed" | "normal" | "semi-expanded" | "expanded" | "extra-expanded" | "ultra-expanded";
|
|
60
|
+
/** Every `FontWidth` stop, in reference order. */
|
|
61
|
+
export declare const TEXT_FONT_WIDTHS: readonly TextFontWidth[];
|
|
62
|
+
/** Every `FontStyle` variant this implementation can honour. */
|
|
63
|
+
export declare const TEXT_FONT_STYLES: readonly TextFontStyle[];
|
|
64
|
+
/** reference `FontWeight::NORMAL`, and the value the CSS font shorthand defaults to. */
|
|
65
|
+
export declare const DEFAULT_FONT_WEIGHT = 400;
|
|
66
|
+
/** reference `FontWeight` range, 1 to 1000. */
|
|
67
|
+
export declare const MIN_FONT_WEIGHT = 1;
|
|
68
|
+
export declare const MAX_FONT_WEIGHT = 1000;
|
|
69
|
+
/**
|
|
70
|
+
* One face request: everything that decides which glyphs a character rasterises to.
|
|
71
|
+
*
|
|
72
|
+
* This is the unit the glyph atlas keys on, so two blocks that differ only in weight cannot share a
|
|
73
|
+
* rasterised glyph. A source with a single fixed face reports the parts it cannot change through
|
|
74
|
+
* `GlyphRasterizer.faceKey`.
|
|
75
|
+
*/
|
|
76
|
+
export type FontFace = {
|
|
77
|
+
readonly family: string;
|
|
78
|
+
readonly weight: number;
|
|
79
|
+
readonly style: TextFontStyle;
|
|
80
|
+
readonly width: TextFontWidth;
|
|
81
|
+
};
|
|
82
|
+
/** A face with the axes left optional, which is how callers and spans express one. */
|
|
83
|
+
export type FontFaceOptions = {
|
|
84
|
+
readonly family: string;
|
|
85
|
+
readonly weight?: number | undefined;
|
|
86
|
+
readonly style?: TextFontStyle | undefined;
|
|
87
|
+
readonly width?: TextFontWidth | undefined;
|
|
88
|
+
};
|
|
89
|
+
/** Folds a face's optional axes into the complete request the measurer and the atlas take. */
|
|
90
|
+
export declare function resolveFontFace(options: FontFaceOptions): FontFace;
|
|
91
|
+
/** Every field that can change which glyphs are rasterised, joined into one cache key. */
|
|
92
|
+
export declare function fontFaceKey(face: FontFace): string;
|
|
43
93
|
/** Source of glyph metrics. The browser implementation measures through Canvas2D. */
|
|
44
94
|
export interface TextMeasurer {
|
|
45
|
-
/** Vertical metrics for a `fontSize`-sized
|
|
46
|
-
verticalMetrics(
|
|
95
|
+
/** Vertical metrics for a `fontSize`-sized instance of `face`. */
|
|
96
|
+
verticalMetrics(face: FontFace, fontSize: number): VerticalMetrics;
|
|
47
97
|
/** Metrics for one character. Whitespace returns a zero-size ink box. */
|
|
48
|
-
glyphMetrics(character: string,
|
|
98
|
+
glyphMetrics(character: string, face: FontFace, fontSize: number): GlyphMetrics;
|
|
49
99
|
}
|
|
50
100
|
/** Reference `Justify`. `start`/`end` resolve to left/right in this LTR-only layout. */
|
|
51
101
|
export type TextJustify = "left" | "center" | "right" | "justified" | "start" | "end";
|
|
@@ -62,6 +112,9 @@ export type TextSpanInput = {
|
|
|
62
112
|
readonly color?: Rgba;
|
|
63
113
|
readonly fontSize?: number;
|
|
64
114
|
readonly fontFamily?: string;
|
|
115
|
+
readonly weight?: number;
|
|
116
|
+
readonly style?: TextFontStyle;
|
|
117
|
+
readonly width?: TextFontWidth;
|
|
65
118
|
};
|
|
66
119
|
/** reference `LineHeight` default, `RelativeToFont(1.2)`. */
|
|
67
120
|
export declare const DEFAULT_LINE_HEIGHT_SCALE = 1.2;
|
|
@@ -69,6 +122,12 @@ export type TextLayoutOptions = {
|
|
|
69
122
|
readonly color: Rgba;
|
|
70
123
|
readonly fontSize: number;
|
|
71
124
|
readonly fontFamily: string;
|
|
125
|
+
/** Block default for `FontFace.weight`; spans override it. */
|
|
126
|
+
readonly weight?: number | undefined;
|
|
127
|
+
/** Block default for `FontFace.style`; spans override it. */
|
|
128
|
+
readonly style?: TextFontStyle | undefined;
|
|
129
|
+
/** Block default for `FontFace.width`; spans override it. */
|
|
130
|
+
readonly width?: TextFontWidth | undefined;
|
|
72
131
|
/**
|
|
73
132
|
* Absolute line height in layout units, reference `LineHeight::Px`. When omitted the line height
|
|
74
133
|
* is `lineHeightScale` times the line's own largest font size, which is reference
|
|
@@ -95,7 +154,7 @@ export type TextLayoutOptions = {
|
|
|
95
154
|
/** One laid-out glyph, ready to become an instance once its atlas rectangle is known. */
|
|
96
155
|
export type PositionedGlyph = {
|
|
97
156
|
readonly character: string;
|
|
98
|
-
readonly
|
|
157
|
+
readonly face: FontFace;
|
|
99
158
|
readonly fontSize: number;
|
|
100
159
|
readonly color: Rgba;
|
|
101
160
|
/** Ink box centre relative to the block's anchor point, y down. */
|
package/dist/text-layout.js
CHANGED
|
@@ -18,6 +18,38 @@
|
|
|
18
18
|
* Layout happens in one local space: origin at the block's anchor point, x to the right and
|
|
19
19
|
* **y down**, matching Canvas2D. The renderer flips y for spaces where up is +y.
|
|
20
20
|
*/
|
|
21
|
+
/** Every `FontWidth` stop, in reference order. */
|
|
22
|
+
export const TEXT_FONT_WIDTHS = [
|
|
23
|
+
"ultra-condensed",
|
|
24
|
+
"extra-condensed",
|
|
25
|
+
"condensed",
|
|
26
|
+
"semi-condensed",
|
|
27
|
+
"normal",
|
|
28
|
+
"semi-expanded",
|
|
29
|
+
"expanded",
|
|
30
|
+
"extra-expanded",
|
|
31
|
+
"ultra-expanded",
|
|
32
|
+
];
|
|
33
|
+
/** Every `FontStyle` variant this implementation can honour. */
|
|
34
|
+
export const TEXT_FONT_STYLES = ["normal", "italic"];
|
|
35
|
+
/** reference `FontWeight::NORMAL`, and the value the CSS font shorthand defaults to. */
|
|
36
|
+
export const DEFAULT_FONT_WEIGHT = 400;
|
|
37
|
+
/** reference `FontWeight` range, 1 to 1000. */
|
|
38
|
+
export const MIN_FONT_WEIGHT = 1;
|
|
39
|
+
export const MAX_FONT_WEIGHT = 1000;
|
|
40
|
+
/** Folds a face's optional axes into the complete request the measurer and the atlas take. */
|
|
41
|
+
export function resolveFontFace(options) {
|
|
42
|
+
return {
|
|
43
|
+
family: options.family,
|
|
44
|
+
weight: options.weight ?? DEFAULT_FONT_WEIGHT,
|
|
45
|
+
style: options.style ?? "normal",
|
|
46
|
+
width: options.width ?? "normal",
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
/** Every field that can change which glyphs are rasterised, joined into one cache key. */
|
|
50
|
+
export function fontFaceKey(face) {
|
|
51
|
+
return `${face.family}|${face.weight}|${face.width}|${face.style}`;
|
|
52
|
+
}
|
|
21
53
|
const TEXT_ANCHORS = {
|
|
22
54
|
center: [0.5, 0.5],
|
|
23
55
|
"top-left": [0, 0],
|
|
@@ -144,13 +176,20 @@ function measureLine(characters, letterSpacing) {
|
|
|
144
176
|
while (end > 0 && content[end - 1].isSpace)
|
|
145
177
|
end--;
|
|
146
178
|
let fontSize = 0;
|
|
147
|
-
|
|
148
|
-
|
|
179
|
+
let face;
|
|
180
|
+
// The line box follows the line's own largest run, so the face recorded here is that run's.
|
|
181
|
+
for (const character of content) {
|
|
182
|
+
if (character.fontSize > fontSize) {
|
|
183
|
+
fontSize = character.fontSize;
|
|
184
|
+
face = character.face;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
149
187
|
return {
|
|
150
188
|
characters: content.slice(0, end),
|
|
151
189
|
advance: runWidth(content.slice(0, end), letterSpacing),
|
|
152
190
|
fullAdvance: runWidth(content, letterSpacing),
|
|
153
191
|
fontSize,
|
|
192
|
+
face,
|
|
154
193
|
isEmpty: content.length === 0,
|
|
155
194
|
};
|
|
156
195
|
}
|
|
@@ -241,20 +280,31 @@ function countSpaces(characters) {
|
|
|
241
280
|
*/
|
|
242
281
|
export function layoutText(spans, options, measurer) {
|
|
243
282
|
const items = [];
|
|
283
|
+
const blockFace = resolveFontFace({
|
|
284
|
+
family: options.fontFamily,
|
|
285
|
+
weight: options.weight,
|
|
286
|
+
style: options.style,
|
|
287
|
+
width: options.width,
|
|
288
|
+
});
|
|
244
289
|
for (const span of spans) {
|
|
245
290
|
const fontSize = span.fontSize ?? options.fontSize;
|
|
246
|
-
const
|
|
291
|
+
const face = resolveFontFace({
|
|
292
|
+
family: span.fontFamily ?? options.fontFamily,
|
|
293
|
+
weight: span.weight ?? options.weight,
|
|
294
|
+
style: span.style ?? options.style,
|
|
295
|
+
width: span.width ?? options.width,
|
|
296
|
+
});
|
|
247
297
|
const color = span.color ?? options.color;
|
|
248
298
|
// Iterating the string yields code points, so surrogate pairs stay whole.
|
|
249
299
|
for (const character of span.text) {
|
|
250
300
|
if (character === "\n") {
|
|
251
|
-
items.push({ character,
|
|
301
|
+
items.push({ character, face, fontSize, color, advance: 0, width: 0, height: 0, bearingLeft: 0, bearingTop: 0, isSpace: false });
|
|
252
302
|
continue;
|
|
253
303
|
}
|
|
254
|
-
const metrics = measurer.glyphMetrics(character,
|
|
304
|
+
const metrics = measurer.glyphMetrics(character, face, fontSize);
|
|
255
305
|
items.push({
|
|
256
306
|
character,
|
|
257
|
-
|
|
307
|
+
face,
|
|
258
308
|
fontSize,
|
|
259
309
|
color,
|
|
260
310
|
advance: metrics.advance,
|
|
@@ -273,7 +323,7 @@ export function layoutText(spans, options, measurer) {
|
|
|
273
323
|
// instead of being held to the block's default size.
|
|
274
324
|
const boxes = lines.map((line) => {
|
|
275
325
|
const fontSize = line.fontSize > 0 ? line.fontSize : options.fontSize;
|
|
276
|
-
const base = measurer.verticalMetrics(
|
|
326
|
+
const base = measurer.verticalMetrics(line.face ?? blockFace, fontSize);
|
|
277
327
|
const height = options.lineHeight ?? (options.lineHeightScale ?? DEFAULT_LINE_HEIGHT_SCALE) * fontSize;
|
|
278
328
|
return { base, height, halfLeading: (height - (base.ascent + base.descent)) / 2 };
|
|
279
329
|
});
|
|
@@ -310,7 +360,7 @@ export function layoutText(spans, options, measurer) {
|
|
|
310
360
|
if (character.width > 0 && character.height > 0) {
|
|
311
361
|
glyphs.push({
|
|
312
362
|
character: character.character,
|
|
313
|
-
|
|
363
|
+
face: character.face,
|
|
314
364
|
fontSize: character.fontSize,
|
|
315
365
|
color: character.color,
|
|
316
366
|
center: [offsetX + penX + character.bearingLeft + character.width / 2, offsetY + baseline + character.bearingTop + character.height / 2],
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* baking: one set of texels serves every size.
|
|
16
16
|
*/
|
|
17
17
|
import { type GlyphRasterizer, type RasterizedGlyph } from "./text-font.js";
|
|
18
|
-
import type { GlyphMetrics, TextMeasurer, VerticalMetrics } from "./text-layout.js";
|
|
18
|
+
import type { FontFace, GlyphMetrics, TextMeasurer, VerticalMetrics } from "./text-layout.js";
|
|
19
19
|
export declare class BakedMsdfRasterizer implements GlyphRasterizer, TextMeasurer {
|
|
20
20
|
#private;
|
|
21
21
|
readonly encoding: "msdf";
|
|
@@ -30,7 +30,12 @@ export declare class BakedMsdfRasterizer implements GlyphRasterizer, TextMeasure
|
|
|
30
30
|
get ready(): boolean;
|
|
31
31
|
/** Decompresses the inlined page. Idempotent, so calling it per frame is harmless. */
|
|
32
32
|
load(): Promise<void>;
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
/**
|
|
34
|
+
* The page stores one face, so the axes cannot change a sliced glyph. Keying on the family alone
|
|
35
|
+
* keeps the atlas from packing the same texels once per requested weight.
|
|
36
|
+
*/
|
|
37
|
+
readonly faceKey: (face: FontFace) => string;
|
|
38
|
+
verticalMetrics(_face: FontFace, fontSize: number): VerticalMetrics;
|
|
39
|
+
glyphMetrics(character: string, _face: FontFace, fontSize: number): GlyphMetrics;
|
|
40
|
+
rasterize(character: string, face: FontFace, fontSize: number): RasterizedGlyph | undefined;
|
|
36
41
|
}
|
package/dist/text-msdf-baked.js
CHANGED
|
@@ -48,11 +48,16 @@ export class BakedMsdfRasterizer {
|
|
|
48
48
|
}
|
|
49
49
|
this.#page = page;
|
|
50
50
|
}
|
|
51
|
-
|
|
51
|
+
/**
|
|
52
|
+
* The page stores one face, so the axes cannot change a sliced glyph. Keying on the family alone
|
|
53
|
+
* keeps the atlas from packing the same texels once per requested weight.
|
|
54
|
+
*/
|
|
55
|
+
faceKey = (face) => face.family;
|
|
56
|
+
verticalMetrics(_face, fontSize) {
|
|
52
57
|
const scale = fontSize / BAKED_MSDF_BAKE_SIZE;
|
|
53
58
|
return { ascent: BAKED_MSDF_ASCENT * scale, descent: BAKED_MSDF_DESCENT * scale };
|
|
54
59
|
}
|
|
55
|
-
glyphMetrics(character,
|
|
60
|
+
glyphMetrics(character, _face, fontSize) {
|
|
56
61
|
const key = `${fontSize}|${character}`;
|
|
57
62
|
const cached = this.#metrics.get(key);
|
|
58
63
|
if (cached !== undefined)
|
|
@@ -71,7 +76,7 @@ export class BakedMsdfRasterizer {
|
|
|
71
76
|
this.#metrics.set(key, value);
|
|
72
77
|
return value;
|
|
73
78
|
}
|
|
74
|
-
rasterize(character,
|
|
79
|
+
rasterize(character, face, fontSize) {
|
|
75
80
|
const page = this.#page;
|
|
76
81
|
if (page === undefined)
|
|
77
82
|
throw new Error("BakedMsdfRasterizer.load() must resolve before glyphs are requested");
|
|
@@ -96,7 +101,7 @@ export class BakedMsdfRasterizer {
|
|
|
96
101
|
}
|
|
97
102
|
// The texels stay at their baked size; only the placement scales with the requested font size.
|
|
98
103
|
const scale = fontSize / BAKED_MSDF_BAKE_SIZE;
|
|
99
|
-
const metrics = this.glyphMetrics(character,
|
|
104
|
+
const metrics = this.glyphMetrics(character, face, fontSize);
|
|
100
105
|
return {
|
|
101
106
|
width,
|
|
102
107
|
height,
|
package/dist/text-msdf-font.d.ts
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
import type { Font } from "opentype.js";
|
|
14
14
|
import { type GlyphRasterizer, type RasterizedGlyph } from "./text-font.js";
|
|
15
15
|
import { type OutlineSegment } from "./text-msdf.js";
|
|
16
|
-
import type { GlyphMetrics, TextMeasurer, VerticalMetrics } from "./text-layout.js";
|
|
16
|
+
import type { FontFace, GlyphMetrics, TextMeasurer, VerticalMetrics } from "./text-layout.js";
|
|
17
17
|
/** The subset of `opentype.js`'s path commands this source understands. */
|
|
18
18
|
type OutlineCommand = {
|
|
19
19
|
readonly type: "M";
|
|
@@ -72,8 +72,14 @@ export declare class OutlineGlyphRasterizer implements GlyphRasterizer, TextMeas
|
|
|
72
72
|
constructor(options?: OutlineGlyphRasterizerOptions);
|
|
73
73
|
/** The face this source reads, for callers that want to name it. */
|
|
74
74
|
get font(): Font;
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
75
|
+
/**
|
|
76
|
+
* This source holds one parsed buffer, so a weight, width or slant it was not built with cannot
|
|
77
|
+
* change the outlines it returns. Keying on the family alone keeps the atlas from caching the same
|
|
78
|
+
* glyph once per axis value.
|
|
79
|
+
*/
|
|
80
|
+
readonly faceKey: (face: FontFace) => string;
|
|
81
|
+
verticalMetrics(_face: FontFace, fontSize: number): VerticalMetrics;
|
|
82
|
+
glyphMetrics(character: string, _face: FontFace, fontSize: number): GlyphMetrics;
|
|
83
|
+
rasterize(character: string, _face: FontFace, rasterSize: number): RasterizedGlyph | undefined;
|
|
78
84
|
}
|
|
79
85
|
export {};
|
package/dist/text-msdf-font.js
CHANGED
|
@@ -105,7 +105,13 @@ export class OutlineGlyphRasterizer {
|
|
|
105
105
|
const bottom = Number.isFinite(box.y2) ? box.y2 : top;
|
|
106
106
|
return { left, top, width: Math.max(0, right - left), height: Math.max(0, bottom - top) };
|
|
107
107
|
}
|
|
108
|
-
|
|
108
|
+
/**
|
|
109
|
+
* This source holds one parsed buffer, so a weight, width or slant it was not built with cannot
|
|
110
|
+
* change the outlines it returns. Keying on the family alone keeps the atlas from caching the same
|
|
111
|
+
* glyph once per axis value.
|
|
112
|
+
*/
|
|
113
|
+
faceKey = (face) => face.family;
|
|
114
|
+
verticalMetrics(_face, fontSize) {
|
|
109
115
|
const cached = this.#vertical.get(String(fontSize));
|
|
110
116
|
if (cached !== undefined)
|
|
111
117
|
return cached;
|
|
@@ -114,7 +120,7 @@ export class OutlineGlyphRasterizer {
|
|
|
114
120
|
this.#vertical.set(String(fontSize), value);
|
|
115
121
|
return value;
|
|
116
122
|
}
|
|
117
|
-
glyphMetrics(character,
|
|
123
|
+
glyphMetrics(character, _face, fontSize) {
|
|
118
124
|
const key = `${fontSize}|${character}`;
|
|
119
125
|
const cached = this.#metrics.get(key);
|
|
120
126
|
if (cached !== undefined)
|
|
@@ -131,7 +137,7 @@ export class OutlineGlyphRasterizer {
|
|
|
131
137
|
this.#metrics.set(key, value);
|
|
132
138
|
return value;
|
|
133
139
|
}
|
|
134
|
-
rasterize(character,
|
|
140
|
+
rasterize(character, _face, rasterSize) {
|
|
135
141
|
const ink = this.#inkBox(character, rasterSize);
|
|
136
142
|
if (!(ink.width > 0) || !(ink.height > 0))
|
|
137
143
|
return undefined;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Small vector helpers shared by the geometry generators and the example layouts.
|
|
3
|
+
*
|
|
4
|
+
* These live in the library rather than next to the gallery scenes because
|
|
5
|
+
* `geometry-generators.ts` needs them, and a library module must not reach into
|
|
6
|
+
* the example code.
|
|
7
|
+
*/
|
|
8
|
+
import type { Vec3 } from "./frame-draw-list.js";
|
|
9
|
+
export declare function normalizeVector(p: Vec3): Vec3;
|
|
10
|
+
export declare function crossVector(a: Vec3, b: Vec3): Vec3;
|
|
11
|
+
/** Right-handed Rodrigues rotation, equivalent to reference `Vector3.rotated(axis, angle)`. */
|
|
12
|
+
export declare function rotateAroundAxis(p: Vec3, axis: Vec3, angle: number): Vec3;
|
|
13
|
+
export type CircleBasis = {
|
|
14
|
+
readonly x: Vec3;
|
|
15
|
+
readonly y: Vec3;
|
|
16
|
+
readonly z: Vec3;
|
|
17
|
+
};
|
|
18
|
+
/** Orthonormal basis for a circle in the plane whose normal is `normal`. */
|
|
19
|
+
export declare function circleBasis(normal: Vec3, angle: number): CircleBasis;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Small vector helpers shared by the geometry generators and the example layouts.
|
|
3
|
+
*
|
|
4
|
+
* These live in the library rather than next to the gallery scenes because
|
|
5
|
+
* `geometry-generators.ts` needs them, and a library module must not reach into
|
|
6
|
+
* the example code.
|
|
7
|
+
*/
|
|
8
|
+
export function normalizeVector(p) {
|
|
9
|
+
const length = Math.hypot(p[0], p[1], p[2]) || 1;
|
|
10
|
+
return [p[0] / length, p[1] / length, p[2] / length];
|
|
11
|
+
}
|
|
12
|
+
export function crossVector(a, b) {
|
|
13
|
+
return [a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0]];
|
|
14
|
+
}
|
|
15
|
+
/** Right-handed Rodrigues rotation, equivalent to reference `Vector3.rotated(axis, angle)`. */
|
|
16
|
+
export function rotateAroundAxis(p, axis, angle) {
|
|
17
|
+
const a = normalizeVector(axis);
|
|
18
|
+
const cos = Math.cos(angle);
|
|
19
|
+
const sin = Math.sin(angle);
|
|
20
|
+
const dot = p[0] * a[0] + p[1] * a[1] + p[2] * a[2];
|
|
21
|
+
const cross = crossVector(a, p);
|
|
22
|
+
return [
|
|
23
|
+
p[0] * cos + cross[0] * sin + a[0] * dot * (1 - cos),
|
|
24
|
+
p[1] * cos + cross[1] * sin + a[1] * dot * (1 - cos),
|
|
25
|
+
p[2] * cos + cross[2] * sin + a[2] * dot * (1 - cos),
|
|
26
|
+
];
|
|
27
|
+
}
|
|
28
|
+
/** Orthonormal basis for a circle in the plane whose normal is `normal`. */
|
|
29
|
+
export function circleBasis(normal, angle) {
|
|
30
|
+
const n = normalizeVector(normal);
|
|
31
|
+
const reference = Math.abs(n[0]) > 0.95 ? [0, 0, 1] : [1, 0, 0];
|
|
32
|
+
const projection = reference[0] * n[0] + reference[1] * n[1] + reference[2] * n[2];
|
|
33
|
+
const planar = [reference[0] - n[0] * projection, reference[1] - n[1] * projection, reference[2] - n[2] * projection];
|
|
34
|
+
const widthAxis = normalizeVector(rotateAroundAxis(normalizeVector(planar), n, angle));
|
|
35
|
+
const heightAxis = normalizeVector(crossVector(n, widthAxis));
|
|
36
|
+
return { x: widthAxis, y: heightAxis, z: n };
|
|
37
|
+
}
|
package/package.json
CHANGED
|
@@ -1,177 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 2D debug-draw commands and their recorder.
|
|
3
|
-
*
|
|
4
|
-
* The 2D layer is the same data flow as the 3D one — `FrameRecorder -> FrameCommand[] -> GPU` —
|
|
5
|
-
* reached through `frame.d2` instead of a second entry point, so a frame has one lifecycle, one
|
|
6
|
-
* layer mask and one set of defaults. The commands are separate types rather than 3D commands
|
|
7
|
-
* with a `space` flag because a 2D point is a `Vec2` and 2D strokes carry no surface normal;
|
|
8
|
-
* keeping them apart stops a 3D-only field from silently meaning something different here.
|
|
9
|
-
*
|
|
10
|
-
* See `camera-2d.ts` for what `world2d` and `screen` mean.
|
|
11
|
-
*/
|
|
12
|
-
import type { Space2D, Vec2 } from "./camera-2d.js";
|
|
13
|
-
import type { Color, LayerMask, LineJoint, RenderedJoint, ResolvedLineDefaults, ResolvedPointDefaults } from "./frame-recorder.js";
|
|
14
|
-
export type Line2DOptions = {
|
|
15
|
-
color?: Color;
|
|
16
|
-
colorEnd?: Color;
|
|
17
|
-
/** Stroke width in canvas pixels (`widthMode: "screen"`) or world2d units. */
|
|
18
|
-
width?: number;
|
|
19
|
-
widthMode?: "screen" | "world";
|
|
20
|
-
dash?: readonly [number, number, number];
|
|
21
|
-
joint?: LineJoint;
|
|
22
|
-
/** Defaults to `world2d`; see `camera-2d.ts`. */
|
|
23
|
-
space?: Space2D;
|
|
24
|
-
layer?: LayerMask;
|
|
25
|
-
};
|
|
26
|
-
export type Point2DOptions = {
|
|
27
|
-
color?: Color;
|
|
28
|
-
size?: number;
|
|
29
|
-
sizeMode?: "screen" | "world";
|
|
30
|
-
round?: boolean;
|
|
31
|
-
space?: Space2D;
|
|
32
|
-
layer?: LayerMask;
|
|
33
|
-
};
|
|
34
|
-
/** Every filled/stroked 2D shape the layer can draw. */
|
|
35
|
-
export type Shape2DKind = "rect" | "rounded-rect" | "circle" | "ellipse" | "arc" | "annulus" | "capsule" | "polygon" | "triangle" | "rhombus" | "sector" | "segment";
|
|
36
|
-
export type Shape2DOptions = {
|
|
37
|
-
color?: Color;
|
|
38
|
-
/** Rotation in radians, anticlockwise in `world2d` and screen-clockwise in `screen`. */
|
|
39
|
-
rotation?: number;
|
|
40
|
-
/** Filled by default; `false` strokes the outline with `strokeWidth`. */
|
|
41
|
-
fill?: boolean;
|
|
42
|
-
/** Outline width in canvas pixels; only read when `fill` is false. */
|
|
43
|
-
strokeWidth?: number;
|
|
44
|
-
/** Start angle of `arc`/`sector`/`segment` in radians. Defaults to `0`, the +x axis. */
|
|
45
|
-
startAngle?: number;
|
|
46
|
-
/** Sweep of `arc`/`sector`/`segment` in radians. Defaults to a full turn; negative turns the other way. */
|
|
47
|
-
sweep?: number;
|
|
48
|
-
space?: Space2D;
|
|
49
|
-
layer?: LayerMask;
|
|
50
|
-
};
|
|
51
|
-
export type Shape2DCommand = {
|
|
52
|
-
readonly kind: "shape2d";
|
|
53
|
-
readonly shape: Shape2DKind;
|
|
54
|
-
readonly center: Vec2;
|
|
55
|
-
/**
|
|
56
|
-
* Primary half-size, meaning depends on `shape`: box half extents, ellipse radii, circumradius,
|
|
57
|
-
* capsule `[radius, halfLength]`, annulus `[outer, inner]`, rhombus half diagonals, or a
|
|
58
|
-
* triangle's first vertex offset from the centroid.
|
|
59
|
-
*/
|
|
60
|
-
readonly size: Vec2;
|
|
61
|
-
/** Second/third triangle vertices relative to the centroid; zero for every other shape. */
|
|
62
|
-
readonly vertexB: Vec2;
|
|
63
|
-
readonly vertexC: Vec2;
|
|
64
|
-
readonly cornerRadius: number;
|
|
65
|
-
/** Outline width in canvas pixels. */
|
|
66
|
-
readonly thickness: number;
|
|
67
|
-
readonly filled: boolean;
|
|
68
|
-
readonly rotation: number;
|
|
69
|
-
readonly startAngle: number;
|
|
70
|
-
readonly sweep: number;
|
|
71
|
-
/** Vertex count of `polygon`; zero for every other shape. */
|
|
72
|
-
readonly sides: number;
|
|
73
|
-
readonly color: Color;
|
|
74
|
-
readonly space: Space2D;
|
|
75
|
-
readonly layer: LayerMask;
|
|
76
|
-
};
|
|
77
|
-
export type Point2DCommand = {
|
|
78
|
-
readonly kind: "point2d";
|
|
79
|
-
readonly position: Vec2;
|
|
80
|
-
readonly color: Color;
|
|
81
|
-
readonly size: number;
|
|
82
|
-
readonly sizeMode: "screen" | "world";
|
|
83
|
-
readonly round?: boolean;
|
|
84
|
-
readonly space: Space2D;
|
|
85
|
-
readonly layer: LayerMask;
|
|
86
|
-
};
|
|
87
|
-
export type Line2DCommand = {
|
|
88
|
-
readonly kind: "line2d";
|
|
89
|
-
readonly start: Vec2;
|
|
90
|
-
readonly end: Vec2;
|
|
91
|
-
readonly color: Color;
|
|
92
|
-
readonly colorEnd: Color;
|
|
93
|
-
readonly width: number;
|
|
94
|
-
readonly widthMode: "screen" | "world";
|
|
95
|
-
readonly dash?: readonly [number, number, number];
|
|
96
|
-
readonly joint?: LineJoint;
|
|
97
|
-
readonly space: Space2D;
|
|
98
|
-
readonly layer: LayerMask;
|
|
99
|
-
};
|
|
100
|
-
/**
|
|
101
|
-
* One path vertex of a `lineStrip`/`polyline`, the 2D twin of `JointCommand`. The renderer
|
|
102
|
-
* rebuilds the round/bevel/miter wedge from `previous`, `center` and `next`, exactly as the 3D
|
|
103
|
-
* joint pipeline does.
|
|
104
|
-
*/
|
|
105
|
-
export type Joint2DCommand = {
|
|
106
|
-
readonly kind: "joint2d";
|
|
107
|
-
readonly previous: Vec2;
|
|
108
|
-
readonly center: Vec2;
|
|
109
|
-
readonly next: Vec2;
|
|
110
|
-
readonly color: Color;
|
|
111
|
-
readonly width: number;
|
|
112
|
-
readonly widthMode: "screen" | "world";
|
|
113
|
-
readonly dash?: readonly [number, number, number];
|
|
114
|
-
readonly joint: RenderedJoint;
|
|
115
|
-
readonly space: Space2D;
|
|
116
|
-
readonly layer: LayerMask;
|
|
117
|
-
};
|
|
118
|
-
/** Everything a `FrameRecorder2D` can emit. */
|
|
119
|
-
export type FrameCommand2D = Point2DCommand | Line2DCommand | Joint2DCommand | Shape2DCommand;
|
|
120
|
-
/** The 3D recorder's resolved point/line styles, reused so 2D and 3D defaults cannot drift. */
|
|
121
|
-
export type FrameRecorder2DStyleDefaults = {
|
|
122
|
-
readonly point: ResolvedPointDefaults;
|
|
123
|
-
readonly line: ResolvedLineDefaults;
|
|
124
|
-
};
|
|
125
|
-
/**
|
|
126
|
-
* Records 2D primitives for one frame. Obtained through `FrameRecorder.d2`; it seals with its
|
|
127
|
-
* parent, so a caller cannot keep drawing into a finished frame.
|
|
128
|
-
*/
|
|
129
|
-
export declare class FrameRecorder2D {
|
|
130
|
-
#private;
|
|
131
|
-
constructor(defaults: FrameRecorder2DStyleDefaults, assertOpen: () => void);
|
|
132
|
-
point(position: Vec2, options?: Point2DOptions): this;
|
|
133
|
-
line(start: Vec2, end: Vec2, options?: Line2DOptions): this;
|
|
134
|
-
ray(origin: Vec2, direction: Vec2, options?: Line2DOptions): this;
|
|
135
|
-
/**
|
|
136
|
-
* Path of segments, the 2D twin of `FrameRecorder.lineStrip`. Dash distance accumulates over
|
|
137
|
-
* the whole path and an open strip joins only its interior vertices, matching the 3D layer.
|
|
138
|
-
*/
|
|
139
|
-
lineStrip(points: readonly Vec2[], options?: Line2DOptions, closed?: boolean): this;
|
|
140
|
-
polyline(points: readonly Vec2[], options?: Line2DOptions, closed?: boolean): this;
|
|
141
|
-
/**
|
|
142
|
-
* Axis-aligned grid of lines, the 2D twin of `FrameRecorder.grid` and reference `grid_2d`: `cells`
|
|
143
|
-
* are the cells per axis, so the drawn line count is one more on each side.
|
|
144
|
-
*/
|
|
145
|
-
grid(center: Vec2, cells: readonly [number, number], spacing: Vec2, options?: Line2DOptions): this;
|
|
146
|
-
/** Rectangle with the given full width and height (reference `Rectangle::new`). */
|
|
147
|
-
rect(center: Vec2, size: Vec2, options?: Shape2DOptions): this;
|
|
148
|
-
/** Rectangle with rounded corners; the shader clamps the radius to half the shorter side. */
|
|
149
|
-
roundedRect(center: Vec2, size: Vec2, cornerRadius: number, options?: Shape2DOptions): this;
|
|
150
|
-
/** Filled disc with the given radius (reference `Circle::new`). */
|
|
151
|
-
circle(center: Vec2, radius: number, options?: Shape2DOptions): this;
|
|
152
|
-
/** Ellipse with independent x and y radii (reference `Ellipse::new(x_radius, y_radius)`). */
|
|
153
|
-
ellipse(center: Vec2, radii: Vec2, options?: Shape2DOptions): this;
|
|
154
|
-
/**
|
|
155
|
-
* Circular arc from `startAngle` through `sweep`. It is a curve without an interior, so an
|
|
156
|
-
* unfilled arc (the default) strokes it with `strokeWidth` while `fill: true` leaves a hairline.
|
|
157
|
-
*/
|
|
158
|
-
arc(center: Vec2, radius: number, options?: Shape2DOptions): this;
|
|
159
|
-
/** Ring band between two radii (reference `Annulus::new(inner_radius, outer_radius)`). */
|
|
160
|
-
annulus(center: Vec2, innerRadius: number, outerRadius: number, options?: Shape2DOptions): this;
|
|
161
|
-
/** Capsule whose straight section is `length` long along y (reference `Capsule2d::new(radius, length)`). */
|
|
162
|
-
capsule(center: Vec2, radius: number, length: number, options?: Shape2DOptions): this;
|
|
163
|
-
/** Regular polygon with a vertex on the +x axis (reference `RegularPolygon::new(circumradius, sides)`). */
|
|
164
|
-
polygon(center: Vec2, circumradius: number, sides: number, options?: Shape2DOptions): this;
|
|
165
|
-
/** Arbitrary triangle (reference `Triangle2d::new(a, b, c)`); `rotation` turns about its centroid. */
|
|
166
|
-
triangle(a: Vec2, b: Vec2, c: Vec2, options?: Shape2DOptions): this;
|
|
167
|
-
/** Diamond with the given full diagonals (reference `Rhombus::new(horizontal, vertical)`). */
|
|
168
|
-
rhombus(center: Vec2, diagonals: Vec2, options?: Shape2DOptions): this;
|
|
169
|
-
/** Disc sector from `startAngle` through `sweep` (reference `CircularSector`). */
|
|
170
|
-
sector(center: Vec2, radius: number, options?: Shape2DOptions): this;
|
|
171
|
-
/** Disc region between a chord and its arc (reference `CircularSegment`). */
|
|
172
|
-
segment(center: Vec2, radius: number, options?: Shape2DOptions): this;
|
|
173
|
-
get points(): readonly Point2DCommand[];
|
|
174
|
-
get lines(): readonly Line2DCommand[];
|
|
175
|
-
get joints(): readonly Joint2DCommand[];
|
|
176
|
-
get shapes(): readonly Shape2DCommand[];
|
|
177
|
-
}
|