abstract-3d 4.1.1 → 4.1.3
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/lib/renderers/svg/svg-encoding.d.ts +0 -2
- package/lib/renderers/svg/svg-encoding.d.ts.map +1 -1
- package/lib/renderers/svg/svg-encoding.js +0 -12
- package/lib/renderers/svg/svg-encoding.js.map +1 -1
- package/lib/renderers/svg/svg-geometries/shared.d.ts +2 -19
- package/lib/renderers/svg/svg-geometries/shared.d.ts.map +1 -1
- package/lib/renderers/svg/svg-geometries/shared.js +1 -16
- package/lib/renderers/svg/svg-geometries/shared.js.map +1 -1
- package/lib/renderers/svg/svg-geometries/svg-box.d.ts.map +1 -1
- package/lib/renderers/svg/svg-geometries/svg-box.js +30 -44
- package/lib/renderers/svg/svg-geometries/svg-box.js.map +1 -1
- package/lib/renderers/svg/svg-geometries/svg-cone.d.ts.map +1 -1
- package/lib/renderers/svg/svg-geometries/svg-cone.js +3 -3
- package/lib/renderers/svg/svg-geometries/svg-cone.js.map +1 -1
- package/lib/renderers/svg/svg-geometries/svg-cylinder.d.ts.map +1 -1
- package/lib/renderers/svg/svg-geometries/svg-cylinder.js +6 -19
- package/lib/renderers/svg/svg-geometries/svg-cylinder.js.map +1 -1
- package/lib/renderers/svg/svg-geometries/svg-image.d.ts.map +1 -1
- package/lib/renderers/svg/svg-geometries/svg-image.js +2 -3
- package/lib/renderers/svg/svg-geometries/svg-image.js.map +1 -1
- package/lib/renderers/svg/svg-geometries/svg-line.d.ts.map +1 -1
- package/lib/renderers/svg/svg-geometries/svg-line.js +1 -3
- package/lib/renderers/svg/svg-geometries/svg-line.js.map +1 -1
- package/lib/renderers/svg/svg-geometries/svg-plane.d.ts.map +1 -1
- package/lib/renderers/svg/svg-geometries/svg-plane.js +2 -6
- package/lib/renderers/svg/svg-geometries/svg-plane.js.map +1 -1
- package/lib/renderers/svg/svg-geometries/svg-polygon.d.ts.map +1 -1
- package/lib/renderers/svg/svg-geometries/svg-polygon.js +2 -10
- package/lib/renderers/svg/svg-geometries/svg-polygon.js.map +1 -1
- package/lib/renderers/svg/svg-geometries/svg-shape.d.ts.map +1 -1
- package/lib/renderers/svg/svg-geometries/svg-shape.js +2 -5
- package/lib/renderers/svg/svg-geometries/svg-shape.js.map +1 -1
- package/lib/renderers/svg/svg-geometries/svg-text.js +1 -1
- package/lib/renderers/svg/svg-geometries/svg-text.js.map +1 -1
- package/lib/renderers/svg/svg-order.d.ts +5 -6
- package/lib/renderers/svg/svg-order.d.ts.map +1 -1
- package/lib/renderers/svg/svg-order.js +124 -233
- package/lib/renderers/svg/svg-order.js.map +1 -1
- package/lib/renderers/svg/svg.d.ts.map +1 -1
- package/lib/renderers/svg/svg.js +12 -11
- package/lib/renderers/svg/svg.js.map +1 -1
- package/lib/renderers/svg-bsp/class-names.d.ts +11 -0
- package/lib/renderers/svg-bsp/class-names.d.ts.map +1 -0
- package/lib/renderers/svg-bsp/class-names.js +11 -0
- package/lib/renderers/svg-bsp/class-names.js.map +1 -0
- package/lib/renderers/svg-bsp/index.d.ts +5 -0
- package/lib/renderers/svg-bsp/index.d.ts.map +1 -0
- package/lib/renderers/svg-bsp/index.js +4 -0
- package/lib/renderers/svg-bsp/index.js.map +1 -0
- package/lib/renderers/svg-bsp/svg-encoding.d.ts +19 -0
- package/lib/renderers/svg-bsp/svg-encoding.d.ts.map +1 -0
- package/lib/renderers/svg-bsp/svg-encoding.js +135 -0
- package/lib/renderers/svg-bsp/svg-encoding.js.map +1 -0
- package/lib/renderers/svg-bsp/svg-geometries/shared.d.ts +47 -0
- package/lib/renderers/svg-bsp/svg-geometries/shared.d.ts.map +1 -0
- package/lib/renderers/svg-bsp/svg-geometries/shared.js +45 -0
- package/lib/renderers/svg-bsp/svg-geometries/shared.js.map +1 -0
- package/lib/renderers/svg-bsp/svg-geometries/svg-box.d.ts +4 -0
- package/lib/renderers/svg-bsp/svg-geometries/svg-box.d.ts.map +1 -0
- package/lib/renderers/svg-bsp/svg-geometries/svg-box.js +58 -0
- package/lib/renderers/svg-bsp/svg-geometries/svg-box.js.map +1 -0
- package/lib/renderers/svg-bsp/svg-geometries/svg-cone.d.ts +4 -0
- package/lib/renderers/svg-bsp/svg-geometries/svg-cone.d.ts.map +1 -0
- package/lib/renderers/svg-bsp/svg-geometries/svg-cone.js +39 -0
- package/lib/renderers/svg-bsp/svg-geometries/svg-cone.js.map +1 -0
- package/lib/renderers/svg-bsp/svg-geometries/svg-cylinder.d.ts +4 -0
- package/lib/renderers/svg-bsp/svg-geometries/svg-cylinder.d.ts.map +1 -0
- package/lib/renderers/svg-bsp/svg-geometries/svg-cylinder.js +62 -0
- package/lib/renderers/svg-bsp/svg-geometries/svg-cylinder.js.map +1 -0
- package/lib/renderers/svg-bsp/svg-geometries/svg-image.d.ts +4 -0
- package/lib/renderers/svg-bsp/svg-geometries/svg-image.d.ts.map +1 -0
- package/lib/renderers/svg-bsp/svg-geometries/svg-image.js +44 -0
- package/lib/renderers/svg-bsp/svg-geometries/svg-image.js.map +1 -0
- package/lib/renderers/svg-bsp/svg-geometries/svg-line.d.ts +4 -0
- package/lib/renderers/svg-bsp/svg-geometries/svg-line.d.ts.map +1 -0
- package/lib/renderers/svg-bsp/svg-geometries/svg-line.js +12 -0
- package/lib/renderers/svg-bsp/svg-geometries/svg-line.js.map +1 -0
- package/lib/renderers/svg-bsp/svg-geometries/svg-plane.d.ts +4 -0
- package/lib/renderers/svg-bsp/svg-geometries/svg-plane.d.ts.map +1 -0
- package/lib/renderers/svg-bsp/svg-geometries/svg-plane.js +24 -0
- package/lib/renderers/svg-bsp/svg-geometries/svg-plane.js.map +1 -0
- package/lib/renderers/svg-bsp/svg-geometries/svg-polygon.d.ts +4 -0
- package/lib/renderers/svg-bsp/svg-geometries/svg-polygon.d.ts.map +1 -0
- package/lib/renderers/svg-bsp/svg-geometries/svg-polygon.js +24 -0
- package/lib/renderers/svg-bsp/svg-geometries/svg-polygon.js.map +1 -0
- package/lib/renderers/svg-bsp/svg-geometries/svg-shape.d.ts +4 -0
- package/lib/renderers/svg-bsp/svg-geometries/svg-shape.d.ts.map +1 -0
- package/lib/renderers/svg-bsp/svg-geometries/svg-shape.js +19 -0
- package/lib/renderers/svg-bsp/svg-geometries/svg-shape.js.map +1 -0
- package/lib/renderers/svg-bsp/svg-geometries/svg-text.d.ts +4 -0
- package/lib/renderers/svg-bsp/svg-geometries/svg-text.d.ts.map +1 -0
- package/lib/renderers/svg-bsp/svg-geometries/svg-text.js +26 -0
- package/lib/renderers/svg-bsp/svg-geometries/svg-text.js.map +1 -0
- package/lib/renderers/svg-bsp/svg-order.d.ts +10 -0
- package/lib/renderers/svg-bsp/svg-order.d.ts.map +1 -0
- package/lib/renderers/svg-bsp/svg-order.js +257 -0
- package/lib/renderers/svg-bsp/svg-order.js.map +1 -0
- package/lib/renderers/svg-bsp/svg.d.ts +16 -0
- package/lib/renderers/svg-bsp/svg.d.ts.map +1 -0
- package/lib/renderers/svg-bsp/svg.js +120 -0
- package/lib/renderers/svg-bsp/svg.js.map +1 -0
- package/package.json +6 -1
- package/src/renderers/svg/svg-encoding.ts +0 -13
- package/src/renderers/svg/svg-geometries/shared.ts +3 -31
- package/src/renderers/svg/svg-geometries/svg-box.ts +36 -51
- package/src/renderers/svg/svg-geometries/svg-cone.ts +3 -18
- package/src/renderers/svg/svg-geometries/svg-cylinder.ts +6 -26
- package/src/renderers/svg/svg-geometries/svg-image.ts +2 -3
- package/src/renderers/svg/svg-geometries/svg-line.ts +1 -3
- package/src/renderers/svg/svg-geometries/svg-plane.ts +2 -12
- package/src/renderers/svg/svg-geometries/svg-polygon.ts +2 -10
- package/src/renderers/svg/svg-geometries/svg-shape.ts +2 -11
- package/src/renderers/svg/svg-geometries/svg-text.ts +1 -1
- package/src/renderers/svg/svg.ts +12 -14
- package/src/renderers/svg-bsp/class-names.ts +10 -0
- package/src/renderers/svg-bsp/index.ts +4 -0
- package/src/renderers/svg-bsp/svg-encoding.ts +190 -0
- package/src/renderers/svg-bsp/svg-geometries/shared.ts +88 -0
- package/src/renderers/svg-bsp/svg-geometries/svg-box.ts +76 -0
- package/src/renderers/svg-bsp/svg-geometries/svg-cone.ts +68 -0
- package/src/renderers/svg-bsp/svg-geometries/svg-cylinder.ts +82 -0
- package/src/renderers/svg-bsp/svg-geometries/svg-image.ts +56 -0
- package/src/renderers/svg-bsp/svg-geometries/svg-line.ts +21 -0
- package/src/renderers/svg-bsp/svg-geometries/svg-plane.ts +43 -0
- package/src/renderers/svg-bsp/svg-geometries/svg-polygon.ts +33 -0
- package/src/renderers/svg-bsp/svg-geometries/svg-shape.ts +34 -0
- package/src/renderers/svg-bsp/svg-geometries/svg-text.ts +40 -0
- package/src/renderers/svg-bsp/svg.ts +183 -0
- /package/src/renderers/{svg → svg-bsp}/svg-order.test.ts +0 -0
- /package/src/renderers/{svg → svg-bsp}/svg-order.ts +0 -0
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import type { Hole, Vec2, Vec3 } from "../../abstract-3d.js";
|
|
2
|
+
import { bounds2FromVec2Array, vec2, vec2Add, vec2Scale, vec2Sub, vec3, vec3Rot, vec3Zero } from "../../abstract-3d.js";
|
|
3
|
+
import { classNames } from "./class-names.js";
|
|
4
|
+
import { svgTrsMatrix } from "./svg-geometries/shared.js";
|
|
5
|
+
|
|
6
|
+
export const svg = (min: Vec2, size: Vec2, children: string, className: string): string => {
|
|
7
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" class="${className ? `${classNames.root} ${className}` : classNames.root}" viewBox="${min.x.toFixed(0)} ${min.y.toFixed(0)} ${size.x.toFixed(
|
|
8
|
+
0
|
|
9
|
+
)} ${size.y.toFixed(0)}" width="${size.x.toFixed(0)}px" height="${size.y.toFixed(0)}px">${children}</svg>`;
|
|
10
|
+
};
|
|
11
|
+
export const svgLine = (p1: Vec2, p2: Vec2, stroke: string, strokeWidth: number): string =>
|
|
12
|
+
`<line class="${classNames.stroke}" x1="${p1.x.toFixed(0)}" y1="${p1.y.toFixed(0)}" x2="${p2.x.toFixed(0)}" y2="${p2.y.toFixed(
|
|
13
|
+
0
|
|
14
|
+
)}" stroke="${stroke}" stroke-width="${strokeWidth}" />`;
|
|
15
|
+
|
|
16
|
+
export function svgPolygon(
|
|
17
|
+
rot: Vec3,
|
|
18
|
+
points: ReadonlyArray<Vec2>,
|
|
19
|
+
fill: string,
|
|
20
|
+
opacity: number,
|
|
21
|
+
stroke: string,
|
|
22
|
+
strokeWidth: number,
|
|
23
|
+
holes?: ReadonlyArray<Hole>,
|
|
24
|
+
_parentPos?: Vec3
|
|
25
|
+
): string {
|
|
26
|
+
const bounds = bounds2FromVec2Array(points);
|
|
27
|
+
const size = vec2Sub(bounds.max, bounds.min);
|
|
28
|
+
const pos = vec2Scale(vec2Add(bounds.max, bounds.min), 0.5);
|
|
29
|
+
const [mask, maskAttribute] = svgHoleMask(rot, size, holes ?? []);
|
|
30
|
+
const pol = `<polygon class="${classNames.stroke}" points="${points
|
|
31
|
+
.reduce((a, c) => (a += `${c.x.toFixed(0)},${c.y.toFixed(0)} `), "")
|
|
32
|
+
.slice(0, -1)}" fill="${fill}" fill-opacity="${opacity.toFixed(1)}" stroke="${stroke}" stroke-width="${strokeWidth}" ${maskAttribute}/>`;
|
|
33
|
+
return mask + pol + svgStrokedHoles(pos, rot, holes ?? [], stroke, strokeWidth);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function svgCircle(
|
|
37
|
+
radius: number,
|
|
38
|
+
rot: Vec3,
|
|
39
|
+
pos: Vec2,
|
|
40
|
+
fill: string,
|
|
41
|
+
opacity: number,
|
|
42
|
+
stroke: string,
|
|
43
|
+
strokeWidth: number,
|
|
44
|
+
holes?: ReadonlyArray<Hole>
|
|
45
|
+
): string {
|
|
46
|
+
const size = vec2Scale(vec2(radius, radius), 2);
|
|
47
|
+
const [mask, maskAttribute] = svgHoleMask(rot, size, holes ?? []);
|
|
48
|
+
const cir = `<circle class="${classNames.stroke}" r="${radius.toFixed(0)}" cx="${pos.x.toFixed(0)}" cy="${pos.y.toFixed(0)}" fill="${fill}" fill-opacity="${opacity.toFixed(
|
|
49
|
+
1
|
|
50
|
+
)}" stroke="${stroke}" stroke-width="${strokeWidth}" ${maskAttribute}/>`;
|
|
51
|
+
return mask + cir + svgStrokedHoles(pos, rot, holes ?? [], stroke, strokeWidth);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function svgStrokedHoles(pos: Vec2, rot: Vec3, holes: ReadonlyArray<Hole>, strokeColor: string, strokeThickness: number): string {
|
|
55
|
+
if (strokeThickness <= Number.EPSILON) {
|
|
56
|
+
return "";
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
let svgHoles = "";
|
|
60
|
+
for (const hole of holes) {
|
|
61
|
+
const localSvg = vec3(hole.pos.x, -hole.pos.y, 0);
|
|
62
|
+
const rotated = vec3Rot(localSvg, vec3Zero, rot);
|
|
63
|
+
const holeCenter = vec2Add(vec2(pos.x, pos.y), vec2(rotated.x, rotated.y));
|
|
64
|
+
switch (hole.type) {
|
|
65
|
+
case "RoundHole": {
|
|
66
|
+
const matrix = svgTrsMatrix(holeCenter, vec3Zero);
|
|
67
|
+
svgHoles += `<circle
|
|
68
|
+
class="${classNames.stroke}"
|
|
69
|
+
r="${hole.radius.toFixed(0)}"
|
|
70
|
+
transform="${matrix}"
|
|
71
|
+
fill="none"
|
|
72
|
+
stroke="${strokeColor}"
|
|
73
|
+
stroke-width="${strokeThickness}"
|
|
74
|
+
/>`;
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
case "SquareHole": {
|
|
79
|
+
const half = vec2Scale(hole.size, 0.5);
|
|
80
|
+
const points = [vec2(-half.x, -half.y), vec2(half.x, -half.y), vec2(half.x, half.y), vec2(-half.x, half.y)]
|
|
81
|
+
.map((p) => `${p.x.toFixed(0)},${p.y.toFixed(0)}`)
|
|
82
|
+
.join(" ");
|
|
83
|
+
|
|
84
|
+
const matrix = svgTrsMatrix(holeCenter, rot);
|
|
85
|
+
|
|
86
|
+
svgHoles += `<polygon
|
|
87
|
+
class="${classNames.stroke}"
|
|
88
|
+
points="${points}"
|
|
89
|
+
transform="${matrix}"
|
|
90
|
+
fill="none"
|
|
91
|
+
stroke="${strokeColor}"
|
|
92
|
+
stroke-width="${strokeThickness}"
|
|
93
|
+
/>`;
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
default:
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return svgHoles;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** A polygon's edges that are flagged, the edge from each point to the next, as one path, without a fill */
|
|
104
|
+
export function svgEdges(points: ReadonlyArray<Vec2>, edges: ReadonlyArray<boolean>, stroke: string, strokeWidth: number): string {
|
|
105
|
+
// From after an unflagged edge, so a run of edges isn't broken where the points wrap around
|
|
106
|
+
const start = edges.indexOf(false) + 1;
|
|
107
|
+
const d = points.reduce((path, _, k) => {
|
|
108
|
+
const i = (start + k) % points.length;
|
|
109
|
+
const [p, q] = [points[i]!, points[(i + 1) % points.length]!];
|
|
110
|
+
const move = k > 0 && edges[(i + points.length - 1) % points.length] ? "" : `M${p.x.toFixed(0)},${p.y.toFixed(0)}`;
|
|
111
|
+
return edges[i] ? `${path}${move}L${q.x.toFixed(0)},${q.y.toFixed(0)}` : path;
|
|
112
|
+
}, "");
|
|
113
|
+
return `<path class="${classNames.stroke}" d="${d}" fill="none" stroke="${stroke}" stroke-width="${strokeWidth}" />`;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export const svgCircle2 = (radius: number, pos: Vec2, fill: string, stroke: string, strokeWidth: number, _holes?: ReadonlyArray<Hole>): string =>
|
|
117
|
+
`<circle class="${classNames.stroke}" r="${radius.toFixed(0)}" cx="${pos.x.toFixed(0)}" cy="${pos.y.toFixed(
|
|
118
|
+
0
|
|
119
|
+
)}" fill="${fill}" stroke="${stroke}" stroke-width="${strokeWidth}" />`;
|
|
120
|
+
|
|
121
|
+
function svgHoleMask(rot: Vec3, size: Vec2, holes: ReadonlyArray<Hole>): [string, string] {
|
|
122
|
+
const id = `mask_${counter()}`;
|
|
123
|
+
return holes.length > 0
|
|
124
|
+
? [
|
|
125
|
+
`<mask id="${id}" mask-type="luminance" maskContentUnits="objectBoundingBox">
|
|
126
|
+
<rect x="0" y="0" width="1" height="1" fill="white" />
|
|
127
|
+
${svgMaskHoles(rot, size, holes)}
|
|
128
|
+
</mask>\n`,
|
|
129
|
+
`mask="url(#${id}) "`,
|
|
130
|
+
]
|
|
131
|
+
: ["", ""];
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function svgMaskHoles(rot: Vec3, size: Vec2, holes: ReadonlyArray<Hole>): string {
|
|
135
|
+
const holeMasks: Array<string> = [];
|
|
136
|
+
for (const hole of holes) {
|
|
137
|
+
const holePosRotated = vec3Rot(vec3(hole.pos.x, hole.pos.y, 0), vec3Zero, rot);
|
|
138
|
+
const holePos = vec2Add(holePosRotated, vec2Scale(size, 0.5));
|
|
139
|
+
switch (hole.type) {
|
|
140
|
+
case "RoundHole": {
|
|
141
|
+
holeMasks.push(
|
|
142
|
+
`<ellipse cx="${holePos.x / size.x}" cy="${holePos.y / size.y}" rx="${hole.radius / size.x}" ry="${hole.radius / size.y}" fill="black" />`
|
|
143
|
+
);
|
|
144
|
+
break;
|
|
145
|
+
}
|
|
146
|
+
case "SquareHole": {
|
|
147
|
+
const holeSizeRotated = vec3Rot(vec3(hole.size.x, hole.size.y, 0), vec3Zero, rot);
|
|
148
|
+
const holeSize = vec2(Math.abs(holeSizeRotated.x), Math.abs(holeSizeRotated.y));
|
|
149
|
+
const halfSize = vec2Scale(holeSize, 0.5);
|
|
150
|
+
holeMasks.push(
|
|
151
|
+
`<rect x="${(holePos.x - halfSize.x) / size.x}" y="${(holePos.y - halfSize.y) / size.y}" width="${
|
|
152
|
+
holeSize.x / size.x
|
|
153
|
+
}" height="${holeSize.y / size.y}" fill="black" />`
|
|
154
|
+
);
|
|
155
|
+
break;
|
|
156
|
+
}
|
|
157
|
+
default:
|
|
158
|
+
break;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return holeMasks.join("\n");
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export const svgText = (text: string, matrix: string, color: string, font: string, fontSize: number): string =>
|
|
165
|
+
`<text font-family="${font}" font-size="${fontSize}px" text-anchor="middle" alignment-baseline="middle" fill="${color}" transform="${matrix}">${text}</text>`;
|
|
166
|
+
|
|
167
|
+
export type EmbededImage = { readonly type: "url"; readonly url: string } | { readonly type: "svg"; readonly svg: string };
|
|
168
|
+
|
|
169
|
+
export const rawSvgPrefix = "data:image/svg+xml,";
|
|
170
|
+
|
|
171
|
+
export const svgImage = (p: Vec2, size: Vec2, rot: Vec3, data: EmbededImage, background?: string, scale?: Vec2): string => {
|
|
172
|
+
const matrix = svgTrsMatrix(p, rot, scale);
|
|
173
|
+
|
|
174
|
+
if (data.type === "url") {
|
|
175
|
+
const bg = background ? `<rect width="${size.x.toFixed(0)}" height="${size.y.toFixed(0)}" fill="${background}"/>` : "";
|
|
176
|
+
return `<g transform="${matrix}">${bg}<image class="${classNames.image}" width="${size.x.toFixed(0)}" height="${size.y.toFixed(0)}" href="${data.url}"/></g>`;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// scale maps image-space coords → world coords, so the wrapper SVG and rect
|
|
180
|
+
// must use image-space dimensions: imgW * scale.x = size.x (the desired world size).
|
|
181
|
+
const imgW = scale ? size.x / scale.x : size.x;
|
|
182
|
+
const imgH = scale ? size.y / scale.y : size.y;
|
|
183
|
+
const bg = background ? `<rect width="${imgW.toFixed(0)}" height="${imgH.toFixed(0)}" fill="${background}"/>` : "";
|
|
184
|
+
return `<g transform="${matrix}"><svg class="${classNames.image}" width="${imgW.toFixed(0)}" height="${imgH.toFixed(0)}">${bg}${data.svg}</svg></g>`;
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
const counter = (() => {
|
|
188
|
+
let counter = 0;
|
|
189
|
+
return () => counter++;
|
|
190
|
+
})();
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import type { Vec2, Vec3, View } from "../../../abstract-3d.js";
|
|
2
|
+
import { vec3TransRot, vec3Zero } from "../../../abstract-3d.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* An element and its mean depth, and the points it covers in view space (x right, y up, z toward the viewer), by which it's ordered when given,
|
|
6
|
+
* whether it hides what's behind it, and the style of a plain polygon, without holes, by which its pieces are drawn when it's split
|
|
7
|
+
*/
|
|
8
|
+
export type zOrderElement = {
|
|
9
|
+
readonly element: string;
|
|
10
|
+
readonly zOrder: number;
|
|
11
|
+
readonly polygon?: ReadonlyArray<Vec3>;
|
|
12
|
+
readonly opaque?: boolean;
|
|
13
|
+
readonly style?: PolygonStyle;
|
|
14
|
+
};
|
|
15
|
+
export type PolygonStyle = { readonly fill: string; readonly opacity: number; readonly stroke: string; readonly strokeWidth: number };
|
|
16
|
+
export const zElem = (element: string, zOrder: number, polygon?: ReadonlyArray<Vec3>, opaque?: boolean, style?: PolygonStyle): zOrderElement => ({
|
|
17
|
+
element,
|
|
18
|
+
zOrder,
|
|
19
|
+
...(polygon && { polygon }),
|
|
20
|
+
...(opaque && { opaque }),
|
|
21
|
+
...(style && { style }),
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
/** Whether a fill hides what's behind it: fully opaque, and not transparent, e.g. a sketch's default background */
|
|
25
|
+
export const isOpaque = (fill: string, opacity: number): boolean => opacity >= 1 && !/^(transparent|none|rgba\(.*,\s*0(\.0*)?\s*\))$/.test(fill);
|
|
26
|
+
|
|
27
|
+
/** Twice the signed area of a polygon on screen */
|
|
28
|
+
export function getArea(points: ReadonlyArray<Vec2>): number {
|
|
29
|
+
return points.reduce((sum, p, i) => {
|
|
30
|
+
const q = points[(i + 1) % points.length]!;
|
|
31
|
+
return sum + p.x * q.y - q.x * p.y;
|
|
32
|
+
}, 0);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export const stBW = 0.7;
|
|
36
|
+
export const stN = 0.1;
|
|
37
|
+
|
|
38
|
+
export const gray = "rgb(85, 85, 85)";
|
|
39
|
+
export const white = "rgb(255, 255, 255)";
|
|
40
|
+
export const transparent = "rgba(255, 255, 255, 0)";
|
|
41
|
+
export const black = "rgb(15, 15, 15)";
|
|
42
|
+
|
|
43
|
+
export type ImageDataUri = `data:image/png;base64,${string}` | `data:image/jpeg;base64,${string}` | `data:image/svg+xml,${string}`;
|
|
44
|
+
|
|
45
|
+
/** A view, or a three-quarter one from the front's top left or top right, which shows the front foremost, the top and that side */
|
|
46
|
+
export type SvgView = View | "TopLeft" | "TopRight";
|
|
47
|
+
|
|
48
|
+
export type SvgOptions = {
|
|
49
|
+
readonly view: SvgView;
|
|
50
|
+
readonly stroke_thickness: number;
|
|
51
|
+
readonly only_stroke: boolean;
|
|
52
|
+
readonly gray_scale: boolean;
|
|
53
|
+
readonly background: string;
|
|
54
|
+
readonly rotation: number;
|
|
55
|
+
readonly imageBg: boolean;
|
|
56
|
+
|
|
57
|
+
readonly font: string;
|
|
58
|
+
readonly imageDataByUrl: Record<string, ImageDataUri>;
|
|
59
|
+
/** Its svg's class besides `classNames.root`, e.g. sized to fill its box */
|
|
60
|
+
readonly className: string;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export function svgTrsMatrix(translation: Vec2, rotation: Vec3, scale?: Vec2): string {
|
|
64
|
+
const xRot = vec3TransRot({ x: 1, y: 0, z: 0 }, vec3Zero, rotation);
|
|
65
|
+
const yRot = vec3TransRot({ x: 0, y: -1, z: 0 }, vec3Zero, rotation);
|
|
66
|
+
|
|
67
|
+
const x2D = { x: xRot.x, y: -xRot.y };
|
|
68
|
+
const y2D = { x: yRot.x, y: -yRot.y };
|
|
69
|
+
|
|
70
|
+
const sx = scale?.x ?? 1;
|
|
71
|
+
const sy = scale?.y ?? 1;
|
|
72
|
+
|
|
73
|
+
const a = x2D.x * sx;
|
|
74
|
+
const b = x2D.y * sx;
|
|
75
|
+
const c = y2D.x * sy;
|
|
76
|
+
const d = y2D.y * sy;
|
|
77
|
+
const e = translation.x;
|
|
78
|
+
const f = translation.y;
|
|
79
|
+
|
|
80
|
+
return `matrix(${svgNum(a)} ${svgNum(b)} ${svgNum(c)} ${svgNum(d)} ${svgNum(e)} ${svgNum(f)})`;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const svgNum = (num: number): string => {
|
|
84
|
+
if (Math.abs(num) < 1e-9) {
|
|
85
|
+
return "0";
|
|
86
|
+
}
|
|
87
|
+
return num.toFixed(6);
|
|
88
|
+
};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import type { Box, Vec2, Vec3, Material } from "../../../abstract-3d.js";
|
|
2
|
+
import { vec3Scale, vec3TransRot, vec3RotCombine, vec3Zero, vec3, vec3ZMean } from "../../../abstract-3d.js";
|
|
3
|
+
import { rgbGrayScale } from "../../../utils.js";
|
|
4
|
+
import { svgPolygon } from "../svg-encoding.js";
|
|
5
|
+
import type { zOrderElement, SvgOptions } from "./shared.js";
|
|
6
|
+
import { gray, black, zElem, getArea, isOpaque } from "./shared.js";
|
|
7
|
+
|
|
8
|
+
export function box(
|
|
9
|
+
b: Box,
|
|
10
|
+
point: (x: number, y: number) => Vec2,
|
|
11
|
+
material: Material,
|
|
12
|
+
opts: SvgOptions,
|
|
13
|
+
parentPos: Vec3,
|
|
14
|
+
parentRot: Vec3
|
|
15
|
+
): ReadonlyArray<zOrderElement> {
|
|
16
|
+
const half = vec3Scale(b.size, 0.5);
|
|
17
|
+
const pos = vec3TransRot(b.pos, parentPos, parentRot);
|
|
18
|
+
const rot = vec3RotCombine(parentRot, b.rot ?? vec3Zero);
|
|
19
|
+
const corners = unitCorners.map((c) => vec3TransRot(vec3(c.x * half.x, c.y * half.y, c.z * half.z), pos, rot));
|
|
20
|
+
|
|
21
|
+
const color = material.normal;
|
|
22
|
+
const opacity = material.opacity ?? 1.0;
|
|
23
|
+
const [strokeColor, fill, strokeUse] = opts.only_stroke
|
|
24
|
+
? [opts.gray_scale ? gray : color, opts.background, opts.stroke_thickness]
|
|
25
|
+
: [black, opts.gray_scale ? rgbGrayScale(color) : color, 0];
|
|
26
|
+
const opaque = isOpaque(fill, opacity);
|
|
27
|
+
|
|
28
|
+
return faces.flatMap(([one, other], i) => {
|
|
29
|
+
const polygon = (one.reduce((z, c) => z + corners[c]!.z, 0) > other.reduce((z, c) => z + corners[c]!.z, 0) ? one : other).map((c) => corners[c]!);
|
|
30
|
+
const points = polygon.map((c) => point(c.x, c.y));
|
|
31
|
+
// Seen edge on, a face is only a line along the edges of the box's other faces
|
|
32
|
+
if (Math.abs(getArea(points)) < minArea) {
|
|
33
|
+
return [];
|
|
34
|
+
}
|
|
35
|
+
// Only the front and back have holes, which show what's behind them
|
|
36
|
+
const holes = i === 0 ? b.holes : undefined;
|
|
37
|
+
const plain = !holes?.length;
|
|
38
|
+
return [
|
|
39
|
+
zElem(
|
|
40
|
+
svgPolygon(rot, points, fill, opacity, strokeColor, strokeUse, holes),
|
|
41
|
+
vec3ZMean(...polygon),
|
|
42
|
+
polygon,
|
|
43
|
+
opaque && plain,
|
|
44
|
+
plain ? { fill, opacity, stroke: strokeColor, strokeWidth: strokeUse } : undefined
|
|
45
|
+
),
|
|
46
|
+
];
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const unitCorners = [
|
|
51
|
+
vec3(-1, -1, 1),
|
|
52
|
+
vec3(1, -1, 1),
|
|
53
|
+
vec3(1, 1, 1),
|
|
54
|
+
vec3(-1, 1, 1),
|
|
55
|
+
vec3(-1, -1, -1),
|
|
56
|
+
vec3(1, -1, -1),
|
|
57
|
+
vec3(1, 1, -1),
|
|
58
|
+
vec3(-1, 1, -1),
|
|
59
|
+
];
|
|
60
|
+
/** Pairs of opposite faces by their corners, front and back, top and bottom, right and left, of which the one toward the viewer is drawn */
|
|
61
|
+
const faces: ReadonlyArray<readonly [ReadonlyArray<number>, ReadonlyArray<number>]> = [
|
|
62
|
+
[
|
|
63
|
+
[0, 1, 2, 3],
|
|
64
|
+
[4, 5, 6, 7],
|
|
65
|
+
],
|
|
66
|
+
[
|
|
67
|
+
[7, 6, 2, 3],
|
|
68
|
+
[4, 5, 1, 0],
|
|
69
|
+
],
|
|
70
|
+
[
|
|
71
|
+
[5, 1, 2, 6],
|
|
72
|
+
[4, 0, 3, 7],
|
|
73
|
+
],
|
|
74
|
+
];
|
|
75
|
+
/** Of a face on screen, in the scene's units squared, below which it's seen edge on */
|
|
76
|
+
const minArea = 1e-6;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { Cone, Vec2, Vec3, Material } from "../../../abstract-3d.js";
|
|
2
|
+
import { vec3, vec3TransRot, vec3RotCombine, vec3Zero, vec3ZMean, equals } from "../../../abstract-3d.js";
|
|
3
|
+
import { rgbGrayScale } from "../../../utils.js";
|
|
4
|
+
import { svgCircle, svgPolygon } from "../svg-encoding.js";
|
|
5
|
+
import type { zOrderElement, SvgOptions } from "./shared.js";
|
|
6
|
+
import { gray, isOpaque, stBW, zElem, transparent } from "./shared.js";
|
|
7
|
+
|
|
8
|
+
export function cone(
|
|
9
|
+
c: Cone,
|
|
10
|
+
point: (x: number, y: number) => Vec2,
|
|
11
|
+
material: Material,
|
|
12
|
+
opts: SvgOptions,
|
|
13
|
+
parentPos: Vec3,
|
|
14
|
+
parentRot: Vec3
|
|
15
|
+
): ReadonlyArray<zOrderElement> {
|
|
16
|
+
const half = vec3(c.radius, c.length / 2, c.radius);
|
|
17
|
+
const pos = vec3TransRot(c.pos, parentPos, parentRot);
|
|
18
|
+
const rot = vec3RotCombine(parentRot, c.rot ?? vec3Zero);
|
|
19
|
+
const vec3tr = (p: Vec3): Vec3 => vec3TransRot(p, pos, rot);
|
|
20
|
+
|
|
21
|
+
const color = material.normal;
|
|
22
|
+
const opacity = material.opacity ?? 1.0;
|
|
23
|
+
const [stroke, fill] = opts.only_stroke
|
|
24
|
+
? [opts.gray_scale ? gray : color, opts.background]
|
|
25
|
+
: [transparent, opts.gray_scale ? rgbGrayScale(color) : color];
|
|
26
|
+
const zOrderComponents = Array<zOrderElement>();
|
|
27
|
+
|
|
28
|
+
const sides = 8;
|
|
29
|
+
const angleStep = (2 * Math.PI) / sides;
|
|
30
|
+
let currentAngle = 0;
|
|
31
|
+
|
|
32
|
+
const topPos = vec3tr(vec3(0, half.y, 0));
|
|
33
|
+
const botVec3Array = Array<Vec3>();
|
|
34
|
+
for (let i = 0; i <= sides; i++) {
|
|
35
|
+
const currBot = vec3tr(vec3(0 + Math.sin(currentAngle) * c.radius, -half.y, 0 + Math.cos(currentAngle) * c.radius));
|
|
36
|
+
botVec3Array.push(currBot);
|
|
37
|
+
if (i !== 0) {
|
|
38
|
+
const prevBot = botVec3Array[i - 1]!;
|
|
39
|
+
const points = [point(currBot.x, currBot.y), point(prevBot.x, prevBot.y), point(topPos.x, topPos.y), point(topPos.x, topPos.y)];
|
|
40
|
+
zOrderComponents.push(
|
|
41
|
+
zElem(
|
|
42
|
+
svgPolygon(rot, points, fill, opacity, stroke, stBW),
|
|
43
|
+
vec3ZMean(currBot, prevBot, topPos),
|
|
44
|
+
[currBot, prevBot, topPos],
|
|
45
|
+
isOpaque(fill, opacity),
|
|
46
|
+
{ fill, opacity, stroke, strokeWidth: stBW }
|
|
47
|
+
)
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
currentAngle += angleStep;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Add circle if direcly facing camera
|
|
54
|
+
const cylTop = vec3tr(vec3(0, half.y, 0));
|
|
55
|
+
const cylBottom = vec3tr(vec3(0, -half.y, 0));
|
|
56
|
+
if (equals(cylTop.x, cylBottom.x, 0.1) && equals(cylTop.y, cylBottom.y, 0.1)) {
|
|
57
|
+
zOrderComponents.push(
|
|
58
|
+
zElem(
|
|
59
|
+
svgCircle(c.radius, rot, point(cylBottom.x, cylBottom.y), fill, opacity, stroke, stBW),
|
|
60
|
+
cylBottom.z,
|
|
61
|
+
botVec3Array,
|
|
62
|
+
isOpaque(fill, opacity)
|
|
63
|
+
)
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return zOrderComponents;
|
|
68
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import type { Cylinder, Vec2, Vec3, Material } from "../../../abstract-3d.js";
|
|
2
|
+
import { vec3, vec3TransRot, vec3RotCombine, vec3Zero, vec3ZMean, equals } from "../../../abstract-3d.js";
|
|
3
|
+
import { rgbGrayScale } from "../../../utils.js";
|
|
4
|
+
import { svgCircle, svgPolygon } from "../svg-encoding.js";
|
|
5
|
+
import type { SvgOptions, zOrderElement } from "./shared.js";
|
|
6
|
+
import { getArea, gray, isOpaque, stBW, transparent, zElem } from "./shared.js";
|
|
7
|
+
|
|
8
|
+
export function cylinder(
|
|
9
|
+
c: Cylinder,
|
|
10
|
+
point: (x: number, y: number) => Vec2,
|
|
11
|
+
material: Material,
|
|
12
|
+
opts: SvgOptions,
|
|
13
|
+
parentPos: Vec3,
|
|
14
|
+
parentRot: Vec3
|
|
15
|
+
): ReadonlyArray<zOrderElement> {
|
|
16
|
+
const half = c.length / 2;
|
|
17
|
+
const radiusEnd = c.radiusEnd ?? c.radius;
|
|
18
|
+
const pos = vec3TransRot(c.pos, parentPos, parentRot);
|
|
19
|
+
const rot = vec3RotCombine(parentRot, c.rot ?? vec3Zero);
|
|
20
|
+
const vec3tr = (p: Vec3): Vec3 => vec3TransRot(p, pos, rot);
|
|
21
|
+
|
|
22
|
+
const color = material.normal;
|
|
23
|
+
const opacity = material.opacity ?? 1.0;
|
|
24
|
+
const [stroke, fill] = opts.only_stroke
|
|
25
|
+
? [opts.gray_scale ? gray : color, opts.background]
|
|
26
|
+
: [transparent, opts.gray_scale ? rgbGrayScale(color) : color];
|
|
27
|
+
const opaque = isOpaque(fill, opacity);
|
|
28
|
+
const style = { fill, opacity, stroke, strokeWidth: stBW };
|
|
29
|
+
// A closed opaque cylinder's facets turned away are hidden by those toward the viewer and its near end
|
|
30
|
+
const cullBack = opaque && !c.open;
|
|
31
|
+
const zOrderComponents = Array<zOrderElement>();
|
|
32
|
+
|
|
33
|
+
const sides = 8;
|
|
34
|
+
const angleStep = (2 * Math.PI) / sides;
|
|
35
|
+
let currentAngle = 0;
|
|
36
|
+
|
|
37
|
+
const botVec3Array = Array<Vec3>();
|
|
38
|
+
const topVec3Array = Array<Vec3>();
|
|
39
|
+
for (let i = 0; i <= sides; i++) {
|
|
40
|
+
const sin = Math.sin(currentAngle);
|
|
41
|
+
const cos = Math.cos(currentAngle);
|
|
42
|
+
const currBot = vec3tr(vec3(sin * c.radius, -half, cos * c.radius));
|
|
43
|
+
const currTop = vec3tr(vec3(sin * radiusEnd, half, cos * radiusEnd));
|
|
44
|
+
botVec3Array.push(currBot);
|
|
45
|
+
topVec3Array.push(currTop);
|
|
46
|
+
const mid = currentAngle - angleStep / 2;
|
|
47
|
+
if (i !== 0 && !(cullBack && vec3TransRot(vec3(Math.sin(mid), 0, Math.cos(mid)), vec3Zero, rot).z < -1e-6)) {
|
|
48
|
+
const prevBot = botVec3Array[i - 1]!;
|
|
49
|
+
const prevTop = topVec3Array[i - 1]!;
|
|
50
|
+
|
|
51
|
+
const points = [point(currBot.x, currBot.y), point(prevBot.x, prevBot.y), point(prevTop.x, prevTop.y), point(currTop.x, currTop.y)];
|
|
52
|
+
const polygon = [currBot, prevBot, prevTop, currTop];
|
|
53
|
+
zOrderComponents.push(zElem(svgPolygon(rot, points, fill, opacity, stroke, stBW), vec3ZMean(...polygon), polygon, opaque, style));
|
|
54
|
+
}
|
|
55
|
+
currentAngle += angleStep;
|
|
56
|
+
}
|
|
57
|
+
if (!c.open) {
|
|
58
|
+
// The near end: a circle when directly facing the camera, else seen at an angle, its ring of facet corners
|
|
59
|
+
const circleTop = vec3tr(vec3(0, half, 0));
|
|
60
|
+
const circleBottom = vec3tr(vec3(0, -half, 0));
|
|
61
|
+
const [circlePos, circleRadius, ring] =
|
|
62
|
+
circleTop.z > circleBottom.z ? [circleTop, radiusEnd, topVec3Array] : [circleBottom, c.radius, botVec3Array];
|
|
63
|
+
if (equals(circleTop.x, circleBottom.x, 0.1) && equals(circleTop.y, circleBottom.y, 0.1)) {
|
|
64
|
+
const holes = radiusEnd === c.radius ? c.holes : undefined;
|
|
65
|
+
zOrderComponents.push(
|
|
66
|
+
zElem(
|
|
67
|
+
svgCircle(circleRadius, rot, point(circlePos.x, circlePos.y), fill, opacity, stroke, stBW, holes),
|
|
68
|
+
circlePos.z,
|
|
69
|
+
ring,
|
|
70
|
+
opaque && !holes?.length
|
|
71
|
+
)
|
|
72
|
+
);
|
|
73
|
+
} else {
|
|
74
|
+
const points = ring.map((p) => point(p.x, p.y));
|
|
75
|
+
// Not seen edge on, when it's only a line along the facets' edges
|
|
76
|
+
if (Math.abs(getArea(points)) >= 1e-6) {
|
|
77
|
+
zOrderComponents.push(zElem(svgPolygon(rot, points, fill, opacity, stroke, stBW), circlePos.z, ring, opaque && !c.holes?.length, style));
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return zOrderComponents;
|
|
82
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { createSVG } from "abstract-image";
|
|
2
|
+
import { exhaustiveCheck } from "ts-exhaustive-check";
|
|
3
|
+
import type { ImageMesh, Vec2, Vec3 } from "../../../abstract-3d.js";
|
|
4
|
+
import { vec2Scale, vec3TransRot, vec3RotCombine, vec3Zero, vec3 } from "../../../abstract-3d.js";
|
|
5
|
+
import type { EmbededImage } from "../svg-encoding.js";
|
|
6
|
+
import { rawSvgPrefix, svgImage } from "../svg-encoding.js";
|
|
7
|
+
import type { zOrderElement, SvgOptions } from "./shared.js";
|
|
8
|
+
import { zElem } from "./shared.js";
|
|
9
|
+
|
|
10
|
+
export function image(
|
|
11
|
+
i: ImageMesh,
|
|
12
|
+
point: (x: number, y: number) => Vec2,
|
|
13
|
+
opts: SvgOptions,
|
|
14
|
+
parentPos: Vec3,
|
|
15
|
+
parentRot: Vec3
|
|
16
|
+
): ReadonlyArray<zOrderElement> {
|
|
17
|
+
const half = vec2Scale(i.size, 0.5);
|
|
18
|
+
const pos = vec3TransRot(i.pos, parentPos, parentRot);
|
|
19
|
+
const rot = vec3RotCombine(parentRot, i.rot ?? vec3Zero);
|
|
20
|
+
const vec3tr = (x: number, y: number): Vec3 => vec3TransRot(vec3(x, y, 0), pos, rot);
|
|
21
|
+
|
|
22
|
+
const v2 = vec3tr(half.x, -half.y);
|
|
23
|
+
const v4 = vec3tr(-half.x, half.y);
|
|
24
|
+
const corners = [vec3tr(-half.x, -half.y), v2, vec3tr(half.x, half.y), v4];
|
|
25
|
+
|
|
26
|
+
switch (i.image.type) {
|
|
27
|
+
case "AbstractImage": {
|
|
28
|
+
const scale = {
|
|
29
|
+
x: i.size.x / i.image.image.size.width,
|
|
30
|
+
y: i.size.y / i.image.image.size.height,
|
|
31
|
+
};
|
|
32
|
+
const svg = createSVG(i.image.image, opts);
|
|
33
|
+
const img = svgImage(point(v4.x, v4.y), i.size, rot, { type: "svg", svg }, opts.imageBg ? opts.background : undefined, scale);
|
|
34
|
+
return [zElem(img, (v2.z + v4.z) / 2, corners)];
|
|
35
|
+
}
|
|
36
|
+
case "Url": {
|
|
37
|
+
const imageData = opts.imageDataByUrl[i.image.url];
|
|
38
|
+
const image: EmbededImage | undefined = imageData?.startsWith(rawSvgPrefix)
|
|
39
|
+
? {
|
|
40
|
+
type: "svg",
|
|
41
|
+
svg: decodeURIComponent(imageData.slice(rawSvgPrefix.length)).replace(/^\s*(<\?xml[^>]*\?>\s*)?(<!DOCTYPE[^>]*>\s*)?/i, ""),
|
|
42
|
+
}
|
|
43
|
+
: i.image.url
|
|
44
|
+
? { type: "url", url: imageData ?? i.image.url }
|
|
45
|
+
: undefined;
|
|
46
|
+
|
|
47
|
+
if (image) {
|
|
48
|
+
const img = svgImage(point(v4.x, v4.y), i.size, rot, image, opts.imageBg ? opts.background : undefined);
|
|
49
|
+
return [zElem(img, (v2.z + v4.z) / 2, corners)];
|
|
50
|
+
}
|
|
51
|
+
return [];
|
|
52
|
+
}
|
|
53
|
+
default:
|
|
54
|
+
return exhaustiveCheck(i.image);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { CulledLine, Line, Vec2, Vec3 } from "../../../abstract-3d.js";
|
|
2
|
+
import { vec3TransRot, vec3ZMean } from "../../../abstract-3d.js";
|
|
3
|
+
import { rgbGrayScale } from "../../../utils.js";
|
|
4
|
+
import { svgLine } from "../svg-encoding.js";
|
|
5
|
+
import type { SvgOptions, zOrderElement } from "./shared.js";
|
|
6
|
+
import { zElem } from "./shared.js";
|
|
7
|
+
|
|
8
|
+
export function line(
|
|
9
|
+
l: Line | CulledLine,
|
|
10
|
+
point: (x: number, y: number) => Vec2,
|
|
11
|
+
fill: string,
|
|
12
|
+
opts: SvgOptions,
|
|
13
|
+
parentPos: Vec3,
|
|
14
|
+
parentRot: Vec3
|
|
15
|
+
): ReadonlyArray<zOrderElement> {
|
|
16
|
+
const v1 = vec3TransRot(l.start, parentPos, parentRot);
|
|
17
|
+
const v2 = vec3TransRot(l.end, parentPos, parentRot);
|
|
18
|
+
return [
|
|
19
|
+
zElem(svgLine(point(v1.x, v1.y), point(v2.x, v2.y), opts.gray_scale ? rgbGrayScale(fill) : fill, l.thickness), vec3ZMean(v1, v2), [v1, v2]),
|
|
20
|
+
];
|
|
21
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { Plane, Vec2, Vec3, Material, Hole } from "../../../abstract-3d.js";
|
|
2
|
+
import { vec2Scale, vec3TransRot, vec3RotCombine, vec3Zero, vec3, vec3ZMean } from "../../../abstract-3d.js";
|
|
3
|
+
import { rgbGrayScale } from "../../../utils.js";
|
|
4
|
+
import { svgPolygon } from "../svg-encoding.js";
|
|
5
|
+
import type { zOrderElement, SvgOptions } from "./shared.js";
|
|
6
|
+
import { gray, black, isOpaque, zElem } from "./shared.js";
|
|
7
|
+
|
|
8
|
+
export function plane(
|
|
9
|
+
p: Plane,
|
|
10
|
+
point: (x: number, y: number) => Vec2,
|
|
11
|
+
material: Material,
|
|
12
|
+
opts: SvgOptions,
|
|
13
|
+
parentPos: Vec3,
|
|
14
|
+
parentRot: Vec3,
|
|
15
|
+
holes?: ReadonlyArray<Hole>
|
|
16
|
+
): ReadonlyArray<zOrderElement> {
|
|
17
|
+
const half = vec2Scale(p.size, 0.5);
|
|
18
|
+
const pos = vec3TransRot(p.pos, parentPos, parentRot);
|
|
19
|
+
const rot = vec3RotCombine(parentRot, p.rot ?? vec3Zero);
|
|
20
|
+
const vec3tr = (x: number, y: number): Vec3 => vec3TransRot(vec3(x, y, 0), pos, rot);
|
|
21
|
+
|
|
22
|
+
const v1 = vec3tr(-half.x, -half.y);
|
|
23
|
+
const v2 = vec3tr(half.x, -half.y);
|
|
24
|
+
const v3 = vec3tr(half.x, half.y);
|
|
25
|
+
const v4 = vec3tr(-half.x, half.y);
|
|
26
|
+
|
|
27
|
+
const points = [point(v1.x, v1.y), point(v2.x, v2.y), point(v3.x, v3.y), point(v4.x, v4.y)];
|
|
28
|
+
|
|
29
|
+
const [strokeColor, fill, strokeThickness] = opts.only_stroke
|
|
30
|
+
? [opts.gray_scale ? gray : material.normal, opts.background, opts.stroke_thickness]
|
|
31
|
+
: [black, opts.gray_scale ? rgbGrayScale(material.normal) : material.normal, 0];
|
|
32
|
+
const opacity = material.opacity ?? 1.0;
|
|
33
|
+
const plain = !holes?.length;
|
|
34
|
+
return [
|
|
35
|
+
zElem(
|
|
36
|
+
svgPolygon(rot, points, fill, opacity, strokeColor, strokeThickness, holes),
|
|
37
|
+
vec3ZMean(v1, v2, v3, v4),
|
|
38
|
+
[v1, v2, v3, v4],
|
|
39
|
+
isOpaque(fill, opacity) && plain,
|
|
40
|
+
plain ? { fill, opacity, stroke: strokeColor, strokeWidth: strokeThickness } : undefined
|
|
41
|
+
),
|
|
42
|
+
];
|
|
43
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { Polygon, Vec3, Vec2, Material } from "../../../abstract-3d.js";
|
|
2
|
+
import { vec3ZMean, vec3TransRot, vec3RotCombine, vec3Zero } from "../../../abstract-3d.js";
|
|
3
|
+
import { rgbGrayScale } from "../../../utils.js";
|
|
4
|
+
import { svgPolygon } from "../svg-encoding.js";
|
|
5
|
+
import type { zOrderElement, SvgOptions } from "./shared.js";
|
|
6
|
+
import { gray, isOpaque, zElem, transparent } from "./shared.js";
|
|
7
|
+
|
|
8
|
+
export function polygon(
|
|
9
|
+
p: Polygon,
|
|
10
|
+
point: (x: number, y: number) => Vec2,
|
|
11
|
+
material: Material,
|
|
12
|
+
opts: SvgOptions,
|
|
13
|
+
parentPos: Vec3,
|
|
14
|
+
parentRot: Vec3
|
|
15
|
+
): ReadonlyArray<zOrderElement> {
|
|
16
|
+
const pos = vec3TransRot(p.pos, parentPos, parentRot);
|
|
17
|
+
const rot = vec3RotCombine(parentRot, p.rot ?? vec3Zero);
|
|
18
|
+
const rotatedPoints = p.points.map((p) => vec3TransRot(p, pos, rot));
|
|
19
|
+
const points = rotatedPoints.map(({ x, y }) => point(x, y));
|
|
20
|
+
const color = material.normal;
|
|
21
|
+
const [strokeColor, fill, strokeThickness] = opts.only_stroke
|
|
22
|
+
? [opts.gray_scale ? gray : color, opts.background, opts.stroke_thickness]
|
|
23
|
+
: [transparent, opts.gray_scale ? rgbGrayScale(color) : color, 0];
|
|
24
|
+
const opacity = material.opacity ?? 1.0;
|
|
25
|
+
return [
|
|
26
|
+
zElem(svgPolygon(rot, points, fill, opacity, strokeColor, strokeThickness), vec3ZMean(...rotatedPoints), rotatedPoints, isOpaque(fill, opacity), {
|
|
27
|
+
fill,
|
|
28
|
+
opacity,
|
|
29
|
+
stroke: strokeColor,
|
|
30
|
+
strokeWidth: strokeThickness,
|
|
31
|
+
}),
|
|
32
|
+
];
|
|
33
|
+
}
|