sketchmark 2.0.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 +188 -0
- package/bin/sketchmark.cjs +2008 -0
- package/dist/src/builders/index.d.ts +74 -0
- package/dist/src/builders/index.js +230 -0
- package/dist/src/compounds.d.ts +13 -0
- package/dist/src/compounds.js +118 -0
- package/dist/src/deck.d.ts +4 -0
- package/dist/src/deck.js +91 -0
- package/dist/src/diagnostics.d.ts +5 -0
- package/dist/src/diagnostics.js +113 -0
- package/dist/src/export/index.d.ts +8 -0
- package/dist/src/export/index.js +15 -0
- package/dist/src/index.d.ts +19 -0
- package/dist/src/index.js +35 -0
- package/dist/src/kernel.d.ts +8 -0
- package/dist/src/kernel.js +68 -0
- package/dist/src/normalize.d.ts +6 -0
- package/dist/src/normalize.js +191 -0
- package/dist/src/patch.d.ts +5 -0
- package/dist/src/patch.js +72 -0
- package/dist/src/path-sampling.d.ts +3 -0
- package/dist/src/path-sampling.js +275 -0
- package/dist/src/player/index.d.ts +68 -0
- package/dist/src/player/index.js +600 -0
- package/dist/src/project.d.ts +11 -0
- package/dist/src/project.js +107 -0
- package/dist/src/render/html.d.ts +2 -0
- package/dist/src/render/html.js +13 -0
- package/dist/src/render/raw-three.d.ts +7 -0
- package/dist/src/render/raw-three.js +17 -0
- package/dist/src/render/svg.d.ts +3 -0
- package/dist/src/render/svg.js +277 -0
- package/dist/src/render/three-html.d.ts +2 -0
- package/dist/src/render/three-html.js +303 -0
- package/dist/src/render/three-preview-svg.d.ts +3 -0
- package/dist/src/render/three-preview-svg.js +102 -0
- package/dist/src/scenes.d.ts +4 -0
- package/dist/src/scenes.js +25 -0
- package/dist/src/schema.d.ts +2 -0
- package/dist/src/schema.js +403 -0
- package/dist/src/sequences.d.ts +43 -0
- package/dist/src/sequences.js +109 -0
- package/dist/src/shapes/builtins.d.ts +2 -0
- package/dist/src/shapes/builtins.js +429 -0
- package/dist/src/shapes/common.d.ts +9 -0
- package/dist/src/shapes/common.js +75 -0
- package/dist/src/shapes/geometry.d.ts +22 -0
- package/dist/src/shapes/geometry.js +166 -0
- package/dist/src/shapes/index.d.ts +2 -0
- package/dist/src/shapes/index.js +18 -0
- package/dist/src/shapes/registry.d.ts +9 -0
- package/dist/src/shapes/registry.js +35 -0
- package/dist/src/shapes/types.d.ts +34 -0
- package/dist/src/shapes/types.js +2 -0
- package/dist/src/types.d.ts +439 -0
- package/dist/src/types.js +2 -0
- package/dist/src/utils.d.ts +25 -0
- package/dist/src/utils.js +157 -0
- package/dist/src/validate.d.ts +2 -0
- package/dist/src/validate.js +434 -0
- package/dist/tests/run.d.ts +1 -0
- package/dist/tests/run.js +651 -0
- package/package.json +52 -0
- package/schema/visual.schema.json +930 -0
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.renderToThreeHtml = renderToThreeHtml;
|
|
4
|
+
const kernel_1 = require("../kernel");
|
|
5
|
+
const DEFAULT_THREE_RUNTIME = "https://cdn.jsdelivr.net/npm/three@0.184.0/build/three.module.js";
|
|
6
|
+
function renderToThreeHtml(document, options = {}) {
|
|
7
|
+
const width = document.canvas.width;
|
|
8
|
+
const height = document.canvas.height;
|
|
9
|
+
const background = options.transparent ? "transparent" : (document.canvas.background ?? "#ffffff");
|
|
10
|
+
const kernel = (0, kernel_1.lowerVisualDocument)(document);
|
|
11
|
+
const elements = JSON.stringify(kernel.elements ?? []);
|
|
12
|
+
const initialTime = Number(options.time ?? 0);
|
|
13
|
+
const threeRuntime = options.threeRuntime ?? DEFAULT_THREE_RUNTIME;
|
|
14
|
+
return `<!doctype html>
|
|
15
|
+
<html>
|
|
16
|
+
<head>
|
|
17
|
+
<meta charset="utf-8">
|
|
18
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
19
|
+
<title>Sketchmark Three</title>
|
|
20
|
+
<style>
|
|
21
|
+
html, body {
|
|
22
|
+
margin: 0;
|
|
23
|
+
width: 100%;
|
|
24
|
+
height: 100%;
|
|
25
|
+
overflow: hidden;
|
|
26
|
+
background: ${background};
|
|
27
|
+
display: grid;
|
|
28
|
+
place-items: center;
|
|
29
|
+
}
|
|
30
|
+
main { width: min(100vw, ${width}px); }
|
|
31
|
+
canvas {
|
|
32
|
+
display: block;
|
|
33
|
+
width: min(100vw, ${width}px);
|
|
34
|
+
height: auto;
|
|
35
|
+
aspect-ratio: ${width} / ${height};
|
|
36
|
+
background: transparent;
|
|
37
|
+
}
|
|
38
|
+
</style>
|
|
39
|
+
</head>
|
|
40
|
+
<body>
|
|
41
|
+
<main><canvas id="stage"></canvas></main>
|
|
42
|
+
<script>
|
|
43
|
+
window.__SKETCHMARK_ERROR__ = "";
|
|
44
|
+
window.addEventListener("error", (event) => {
|
|
45
|
+
window.__SKETCHMARK_ERROR__ = event.message || String(event.error || "Unknown script error");
|
|
46
|
+
});
|
|
47
|
+
window.addEventListener("unhandledrejection", (event) => {
|
|
48
|
+
window.__SKETCHMARK_ERROR__ = event.reason && event.reason.message ? event.reason.message : String(event.reason || "Unhandled promise rejection");
|
|
49
|
+
});
|
|
50
|
+
</script>
|
|
51
|
+
<script type="module">
|
|
52
|
+
import * as THREE from ${JSON.stringify(threeRuntime)};
|
|
53
|
+
|
|
54
|
+
const width = ${JSON.stringify(width)};
|
|
55
|
+
const height = ${JSON.stringify(height)};
|
|
56
|
+
const background = ${JSON.stringify(background)};
|
|
57
|
+
const elements = ${elements};
|
|
58
|
+
const canvas = document.getElementById("stage");
|
|
59
|
+
const renderer = new THREE.WebGLRenderer({ canvas, antialias: true, alpha: true, preserveDrawingBuffer: true });
|
|
60
|
+
renderer.setSize(width, height, false);
|
|
61
|
+
renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 2));
|
|
62
|
+
|
|
63
|
+
const scene = new THREE.Scene();
|
|
64
|
+
if (background !== "transparent") scene.background = new THREE.Color(background);
|
|
65
|
+
|
|
66
|
+
const camera = new THREE.PerspectiveCamera(50, width / height, 0.1, 1000);
|
|
67
|
+
camera.position.set(6, 4, 8);
|
|
68
|
+
camera.lookAt(0, 0, 0);
|
|
69
|
+
|
|
70
|
+
const objects = [];
|
|
71
|
+
let hasLight = false;
|
|
72
|
+
for (const element of elements) {
|
|
73
|
+
const object = createObject(element);
|
|
74
|
+
if (!object) continue;
|
|
75
|
+
bindObject(object, element);
|
|
76
|
+
scene.add(object);
|
|
77
|
+
objects.push(object);
|
|
78
|
+
if (element.type === "light") hasLight = true;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (!hasLight) {
|
|
82
|
+
const ambient = new THREE.AmbientLight(0xffffff, 0.6);
|
|
83
|
+
scene.add(ambient);
|
|
84
|
+
const light = new THREE.DirectionalLight(0xffffff, 1);
|
|
85
|
+
light.position.set(5, 8, 5);
|
|
86
|
+
scene.add(light);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function createObject(element) {
|
|
90
|
+
if (element.type === "mesh3d") {
|
|
91
|
+
const vertices = Array.isArray(element.vertices) ? element.vertices.flatMap((point) => vector(point, [0, 0, 0])) : [];
|
|
92
|
+
const indices = Array.isArray(element.indices) ? element.indices.map((value) => Number(value) || 0) : [];
|
|
93
|
+
const geometry = new THREE.BufferGeometry();
|
|
94
|
+
geometry.setAttribute("position", new THREE.Float32BufferAttribute(vertices, 3));
|
|
95
|
+
if (indices.length) geometry.setIndex(indices);
|
|
96
|
+
geometry.computeVertexNormals();
|
|
97
|
+
const material = materialFor(element);
|
|
98
|
+
const mesh = new THREE.Mesh(geometry, material);
|
|
99
|
+
applyPosition(mesh, element.position);
|
|
100
|
+
return mesh;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (element.type === "line3d") {
|
|
104
|
+
const points = [new THREE.Vector3(...vector(element.from, [0, 0, 0])), new THREE.Vector3(...vector(element.to, [0, 0, 0]))];
|
|
105
|
+
return new THREE.Line(new THREE.BufferGeometry().setFromPoints(points), new THREE.LineBasicMaterial({ color: element.stroke || "#111827" }));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (element.type === "point3d") {
|
|
109
|
+
const object = new THREE.Object3D();
|
|
110
|
+
applyPosition(object, element.position);
|
|
111
|
+
return object;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (element.type === "group3d") {
|
|
115
|
+
const group = new THREE.Group();
|
|
116
|
+
for (const child of element.children || []) {
|
|
117
|
+
const childObject = createObject(child);
|
|
118
|
+
if (childObject) group.add(childObject);
|
|
119
|
+
}
|
|
120
|
+
applyPosition(group, element.position);
|
|
121
|
+
return group;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (element.type === "text3d") {
|
|
125
|
+
const textCanvas = document.createElement("canvas");
|
|
126
|
+
textCanvas.width = 512;
|
|
127
|
+
textCanvas.height = 128;
|
|
128
|
+
const ctx = textCanvas.getContext("2d");
|
|
129
|
+
ctx.clearRect(0, 0, textCanvas.width, textCanvas.height);
|
|
130
|
+
ctx.fillStyle = element.fill || "#111827";
|
|
131
|
+
ctx.font = "700 48px Inter, Arial, sans-serif";
|
|
132
|
+
ctx.textAlign = "center";
|
|
133
|
+
ctx.textBaseline = "middle";
|
|
134
|
+
ctx.fillText(element.text || "", 256, 64);
|
|
135
|
+
const texture = new THREE.CanvasTexture(textCanvas);
|
|
136
|
+
const sprite = new THREE.Sprite(new THREE.SpriteMaterial({ map: texture, transparent: true, opacity: number(element.opacity, 1) }));
|
|
137
|
+
const scale = number(element.fontSize, 1);
|
|
138
|
+
sprite.scale.set(scale * 4, scale, 1);
|
|
139
|
+
applyPosition(sprite, element.position);
|
|
140
|
+
return sprite;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (element.type === "light") {
|
|
144
|
+
const kind = element.kind || "ambient";
|
|
145
|
+
const intensity = number(element.intensity, kind === "ambient" ? 0.6 : 1);
|
|
146
|
+
const light = kind === "directional"
|
|
147
|
+
? new THREE.DirectionalLight(0xffffff, intensity)
|
|
148
|
+
: kind === "point"
|
|
149
|
+
? new THREE.PointLight(0xffffff, intensity)
|
|
150
|
+
: new THREE.AmbientLight(0xffffff, intensity);
|
|
151
|
+
applyPosition(light, element.position);
|
|
152
|
+
return light;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return undefined;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function materialFor(element) {
|
|
159
|
+
const opacity = number(element.opacity, 1);
|
|
160
|
+
return new THREE.MeshStandardMaterial({
|
|
161
|
+
color: element.fill || element.stroke || "#2563eb",
|
|
162
|
+
roughness: 0.45,
|
|
163
|
+
transparent: opacity < 1,
|
|
164
|
+
opacity
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function bindObject(object, element) {
|
|
169
|
+
object.userData.sketchmark = {
|
|
170
|
+
element,
|
|
171
|
+
basePosition: object.position.clone(),
|
|
172
|
+
baseRotation: object.rotation.clone(),
|
|
173
|
+
baseScale: object.scale.clone(),
|
|
174
|
+
baseOpacity: materialOpacity(object),
|
|
175
|
+
baseIntensity: typeof object.intensity === "number" ? object.intensity : undefined
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function showTime(rawTime = 0) {
|
|
180
|
+
const time = Number.isFinite(Number(rawTime)) ? Number(rawTime) : 0;
|
|
181
|
+
for (const object of objects) applyAnimatedObject(object, time);
|
|
182
|
+
renderer.render(scene, camera);
|
|
183
|
+
return true;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function applyAnimatedObject(object, time) {
|
|
187
|
+
const data = object.userData.sketchmark;
|
|
188
|
+
if (!data) return;
|
|
189
|
+
const element = data.element;
|
|
190
|
+
const animation = element.animate || {};
|
|
191
|
+
|
|
192
|
+
const basePosition = vector(element.position, [data.basePosition.x, data.basePosition.y, data.basePosition.z]);
|
|
193
|
+
const px = valueAt(animation.positionX, time, basePosition[0]);
|
|
194
|
+
const py = valueAt(animation.positionY, time, basePosition[1]);
|
|
195
|
+
const pz = valueAt(animation.positionZ, time, basePosition[2]);
|
|
196
|
+
object.position.set(px, py, pz);
|
|
197
|
+
|
|
198
|
+
const rx = data.baseRotation.x + degrees(valueAt(animation.rotationX, time, number(element.rotationX, 0)));
|
|
199
|
+
const ry = data.baseRotation.y + degrees(valueAt(animation.rotationY, time, number(element.rotationY, 0)));
|
|
200
|
+
const rz = data.baseRotation.z + degrees(valueAt(animation.rotationZ, time, number(element.rotationZ, 0)));
|
|
201
|
+
object.rotation.set(rx, ry, rz);
|
|
202
|
+
|
|
203
|
+
const baseScale = data.baseScale;
|
|
204
|
+
const allScale = valueAt(animation.scale, time, number(element.scale, 1));
|
|
205
|
+
const sx = baseScale.x * valueAt(animation.scaleX, time, number(element.scaleX, allScale));
|
|
206
|
+
const sy = baseScale.y * valueAt(animation.scaleY, time, number(element.scaleY, allScale));
|
|
207
|
+
const sz = baseScale.z * valueAt(animation.scaleZ, time, number(element.scaleZ, allScale));
|
|
208
|
+
object.scale.set(sx, sy, sz);
|
|
209
|
+
|
|
210
|
+
const opacity = valueAt(animation.opacity, time, number(element.opacity, data.baseOpacity ?? 1));
|
|
211
|
+
setMaterialOpacity(object, opacity);
|
|
212
|
+
|
|
213
|
+
if (typeof object.intensity === "number") {
|
|
214
|
+
object.intensity = valueAt(animation.intensity, time, number(element.intensity, data.baseIntensity ?? 1));
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function valueAt(animation, time, fallback) {
|
|
219
|
+
if (!animation || typeof animation !== "object") return fallback;
|
|
220
|
+
if (Array.isArray(animation.keyframes) && animation.keyframes.length) {
|
|
221
|
+
const frames = animation.keyframes
|
|
222
|
+
.filter((frame) => Array.isArray(frame) && Number.isFinite(Number(frame[0])) && Number.isFinite(Number(frame[1])))
|
|
223
|
+
.map((frame) => [Number(frame[0]), Number(frame[1])])
|
|
224
|
+
.sort((a, b) => a[0] - b[0]);
|
|
225
|
+
if (!frames.length) return fallback;
|
|
226
|
+
if (time <= frames[0][0]) return frames[0][1];
|
|
227
|
+
for (let index = 0; index < frames.length - 1; index += 1) {
|
|
228
|
+
const from = frames[index];
|
|
229
|
+
const to = frames[index + 1];
|
|
230
|
+
if (time > to[0]) continue;
|
|
231
|
+
const span = Math.max(0.000001, to[0] - from[0]);
|
|
232
|
+
const t = easeValue((time - from[0]) / span, animation.ease);
|
|
233
|
+
return from[1] + (to[1] - from[1]) * t;
|
|
234
|
+
}
|
|
235
|
+
return frames[frames.length - 1][1];
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const from = number(animation.from, fallback);
|
|
239
|
+
const to = number(animation.to, fallback);
|
|
240
|
+
const delay = number(animation.delay, 0);
|
|
241
|
+
const duration = Math.max(0.000001, number(animation.duration, 1));
|
|
242
|
+
const t = easeValue((time - delay) / duration, animation.ease);
|
|
243
|
+
if (time <= delay) return from;
|
|
244
|
+
if (time >= delay + duration) return to;
|
|
245
|
+
return from + (to - from) * t;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function easeValue(value, kind) {
|
|
249
|
+
const t = clamp(value, 0, 1);
|
|
250
|
+
if (kind === "ease-in") return t * t;
|
|
251
|
+
if (kind === "ease-out") return 1 - (1 - t) * (1 - t);
|
|
252
|
+
if (kind === "ease-in-out") return t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2;
|
|
253
|
+
return t;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function materialOpacity(object) {
|
|
257
|
+
const material = Array.isArray(object.material) ? object.material[0] : object.material;
|
|
258
|
+
return material && typeof material.opacity === "number" ? material.opacity : 1;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function setMaterialOpacity(object, opacity) {
|
|
262
|
+
if (!object.material) return;
|
|
263
|
+
const materials = Array.isArray(object.material) ? object.material : [object.material];
|
|
264
|
+
for (const material of materials) {
|
|
265
|
+
material.opacity = opacity;
|
|
266
|
+
material.transparent = opacity < 1;
|
|
267
|
+
material.needsUpdate = true;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function applyPosition(object, position) {
|
|
272
|
+
const p = vector(position, [0, 0, 0]);
|
|
273
|
+
object.position.set(p[0], p[1], p[2]);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function vector(value, fallback) {
|
|
277
|
+
return Array.isArray(value) ? fallback.map((item, index) => number(value[index], item)) : fallback;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function number(value, fallback) {
|
|
281
|
+
const numeric = Number(value);
|
|
282
|
+
return Number.isFinite(numeric) ? numeric : fallback;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function degrees(value) {
|
|
286
|
+
return number(value, 0) * Math.PI / 180;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function clamp(value, min, max) {
|
|
290
|
+
return Math.max(min, Math.min(max, Number(value) || 0));
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
window.__SKETCHMARK_SHOW_TIME__ = showTime;
|
|
294
|
+
window.__SKETCHMARK_READY__ = true;
|
|
295
|
+
window.addEventListener("message", (event) => {
|
|
296
|
+
const data = event.data || {};
|
|
297
|
+
if (data.type === "sketchmark-show") showTime(data.time || 0);
|
|
298
|
+
});
|
|
299
|
+
showTime(${JSON.stringify(initialTime)});
|
|
300
|
+
</script>
|
|
301
|
+
</body>
|
|
302
|
+
</html>`;
|
|
303
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { KernelVisualDocument, RenderOptions, ResolvedVisualDocument, VisualDocument } from "../types";
|
|
2
|
+
export declare function renderThreePreviewSvg(document: VisualDocument, time?: number, options?: RenderOptions): string;
|
|
3
|
+
export declare function renderResolvedThreePreviewSvg(document: ResolvedVisualDocument | KernelVisualDocument, options?: RenderOptions): string;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.renderThreePreviewSvg = renderThreePreviewSvg;
|
|
4
|
+
exports.renderResolvedThreePreviewSvg = renderResolvedThreePreviewSvg;
|
|
5
|
+
const kernel_1 = require("../kernel");
|
|
6
|
+
function renderThreePreviewSvg(document, time = 0, options = {}) {
|
|
7
|
+
const frame = (0, kernel_1.resolveKernelFrame)(document, time);
|
|
8
|
+
return renderResolvedThreePreviewSvg(frame, options);
|
|
9
|
+
}
|
|
10
|
+
function renderResolvedThreePreviewSvg(document, options = {}) {
|
|
11
|
+
const kernel = isKernelVisualDocument(document) ? document : (0, kernel_1.lowerResolvedVisualDocument)(document);
|
|
12
|
+
const width = document.canvas.width;
|
|
13
|
+
const height = document.canvas.height;
|
|
14
|
+
const background = document.canvas.background ?? "#ffffff";
|
|
15
|
+
const elements = [...(kernel.elements ?? [])].sort((a, b) => depthOf(a) - depthOf(b));
|
|
16
|
+
const backdrop = options.transparent ? "" : `<rect x="0" y="0" width="${width}" height="${height}" fill="${escapeAttr(background)}"/>`;
|
|
17
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}" role="img">${backdrop}${elements.map((element) => renderElement(element, width, height)).join("")}</svg>`;
|
|
18
|
+
}
|
|
19
|
+
function renderElement(element, width, height) {
|
|
20
|
+
const opacity = element.opacity === undefined ? "" : ` opacity="${Number(element.opacity)}"`;
|
|
21
|
+
const id = element.id ? ` id="${escapeAttr(element.id)}"` : "";
|
|
22
|
+
const fill = escapeAttr(String(element.fill ?? "#2563eb"));
|
|
23
|
+
const stroke = escapeAttr(String(element.stroke ?? "#111827"));
|
|
24
|
+
const strokeWidth = Number(element.strokeWidth ?? 1);
|
|
25
|
+
if (element.type === "mesh3d") {
|
|
26
|
+
const faces = meshFaces(element, width, height);
|
|
27
|
+
return faces
|
|
28
|
+
.sort((a, b) => a.depth - b.depth)
|
|
29
|
+
.map((face, index) => `<polygon${index === 0 ? `${id}${opacity}` : opacity} points="${face.points.map((point) => point.join(",")).join(" ")}" fill="${shade(fill, face.shade)}" stroke="${stroke}" stroke-width="${strokeWidth}"/>`)
|
|
30
|
+
.join("");
|
|
31
|
+
}
|
|
32
|
+
if (element.type === "line3d" && Array.isArray(element.from) && Array.isArray(element.to)) {
|
|
33
|
+
const from = project(element.from, width, height);
|
|
34
|
+
const to = project(element.to, width, height);
|
|
35
|
+
return `<line${id}${opacity} x1="${from[0]}" y1="${from[1]}" x2="${to[0]}" y2="${to[1]}" stroke="${stroke}" stroke-width="${strokeWidth}" fill="none"/>`;
|
|
36
|
+
}
|
|
37
|
+
if (element.type === "text3d") {
|
|
38
|
+
const point = project(element.position, width, height);
|
|
39
|
+
return `<text${id}${opacity} x="${point[0]}" y="${point[1]}" text-anchor="middle" dominant-baseline="middle" font-family="Inter, Arial, sans-serif" font-size="${Number(element.fontSize ?? 18)}" font-weight="700" fill="${fill}">${escapeText(element.text)}</text>`;
|
|
40
|
+
}
|
|
41
|
+
return "";
|
|
42
|
+
}
|
|
43
|
+
function isKernelVisualDocument(document) {
|
|
44
|
+
return (document.elements ?? []).every((element) => element.type === "group" || element.type === "path" || element.type === "text" || element.type === "image" || element.type === "point" || element.type === "group3d" || element.type === "mesh3d" || element.type === "line3d" || element.type === "text3d" || element.type === "point3d" || element.type === "light");
|
|
45
|
+
}
|
|
46
|
+
function meshFaces(element, width, height) {
|
|
47
|
+
const faces = element.faces && element.faces.length ? element.faces : indicesToFaces(element.indices);
|
|
48
|
+
return faces
|
|
49
|
+
.filter((face) => face.length >= 3)
|
|
50
|
+
.map((face, index) => {
|
|
51
|
+
const world = face.map((vertexIndex) => worldVertex(element, vertexIndex));
|
|
52
|
+
const depth = world.reduce((total, point) => total + point[2] - point[1] * 0.1, 0) / world.length;
|
|
53
|
+
return {
|
|
54
|
+
points: world.map((point) => project(point, width, height)),
|
|
55
|
+
shade: 0.74 + (index % 4) * 0.08,
|
|
56
|
+
depth
|
|
57
|
+
};
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
function indicesToFaces(indices) {
|
|
61
|
+
const faces = [];
|
|
62
|
+
for (let index = 0; index < indices.length; index += 3)
|
|
63
|
+
faces.push(indices.slice(index, index + 3));
|
|
64
|
+
return faces;
|
|
65
|
+
}
|
|
66
|
+
function worldVertex(element, index) {
|
|
67
|
+
const vertex = element.vertices[index] ?? [0, 0, 0];
|
|
68
|
+
const position = element.position ?? [0, 0, 0];
|
|
69
|
+
return [vertex[0] + position[0], vertex[1] + position[1], vertex[2] + position[2]];
|
|
70
|
+
}
|
|
71
|
+
function project([x, y, z], width, height) {
|
|
72
|
+
const s = scale(width, height);
|
|
73
|
+
return [
|
|
74
|
+
width / 2 + (x - z) * s * 0.9,
|
|
75
|
+
height / 2 - y * s * 0.9 + (x + z) * s * 0.35
|
|
76
|
+
];
|
|
77
|
+
}
|
|
78
|
+
function scale(width, height) {
|
|
79
|
+
return Math.min(width, height) / 8;
|
|
80
|
+
}
|
|
81
|
+
function depthOf(element) {
|
|
82
|
+
if ("position" in element && Array.isArray(element.position))
|
|
83
|
+
return element.position[2] - element.position[1] * 0.1;
|
|
84
|
+
return 0;
|
|
85
|
+
}
|
|
86
|
+
function shade(hex, factor) {
|
|
87
|
+
const color = parseHex(hex);
|
|
88
|
+
if (!color)
|
|
89
|
+
return hex;
|
|
90
|
+
return `#${color.map((channel) => Math.max(0, Math.min(255, Math.round(channel * factor))).toString(16).padStart(2, "0")).join("")}`;
|
|
91
|
+
}
|
|
92
|
+
function parseHex(value) {
|
|
93
|
+
if (!/^#[0-9a-fA-F]{6}$/.test(value))
|
|
94
|
+
return undefined;
|
|
95
|
+
return [parseInt(value.slice(1, 3), 16), parseInt(value.slice(3, 5), 16), parseInt(value.slice(5, 7), 16)];
|
|
96
|
+
}
|
|
97
|
+
function escapeAttr(value) {
|
|
98
|
+
return value.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<");
|
|
99
|
+
}
|
|
100
|
+
function escapeText(value) {
|
|
101
|
+
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
102
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { ResolvedVisualDocument, VisualDocument } from "./types";
|
|
2
|
+
export declare function listScenes(document: VisualDocument): string[];
|
|
3
|
+
export declare function documentForScene(document: VisualDocument, sceneId: string): VisualDocument;
|
|
4
|
+
export declare function resolvedFrameForScene(document: VisualDocument, sceneId: string, time?: number): ResolvedVisualDocument;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.listScenes = listScenes;
|
|
4
|
+
exports.documentForScene = documentForScene;
|
|
5
|
+
exports.resolvedFrameForScene = resolvedFrameForScene;
|
|
6
|
+
const normalize_1 = require("./normalize");
|
|
7
|
+
const utils_1 = require("./utils");
|
|
8
|
+
function listScenes(document) {
|
|
9
|
+
return Object.keys(document.scenes ?? {});
|
|
10
|
+
}
|
|
11
|
+
function documentForScene(document, sceneId) {
|
|
12
|
+
const scene = document.scenes?.[sceneId];
|
|
13
|
+
if (!scene)
|
|
14
|
+
throw new Error(`Unknown scene '${sceneId}'.`);
|
|
15
|
+
return {
|
|
16
|
+
...(0, utils_1.clone)(document),
|
|
17
|
+
canvas: { ...document.canvas, ...(scene.canvas ?? {}) },
|
|
18
|
+
elements: (0, utils_1.clone)(scene.elements),
|
|
19
|
+
scenes: undefined,
|
|
20
|
+
sequences: undefined
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
function resolvedFrameForScene(document, sceneId, time = 0) {
|
|
24
|
+
return (0, normalize_1.resolveVisualFrame)(documentForScene(document, sceneId), time);
|
|
25
|
+
}
|