@felbrasil/dreamy 0.1.0 → 0.2.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 +21 -0
- package/dist/bake.d.ts +1 -0
- package/dist/bake.js +25 -0
- package/dist/bake.js.map +7 -0
- package/dist/chunks/{chunk-VZMBE5MV.js → chunk-5KZP6X65.js} +412 -119
- package/dist/chunks/chunk-5KZP6X65.js.map +7 -0
- package/dist/chunks/chunk-6TE546ZS.js +107 -0
- package/dist/chunks/chunk-6TE546ZS.js.map +7 -0
- package/dist/index.js +7 -37
- package/dist/index.js.map +3 -3
- package/dist/types/api/src/index.d.ts +1 -1
- package/dist/types/api/src/ports/viewport.d.ts +48 -0
- package/dist/types/dreamy/src/bake.d.ts +5 -0
- package/dist/types/studio/src/index.d.ts +2 -0
- package/dist/types/studio/src/sprite-baker.d.ts +26 -0
- package/dist/types/viewport/src/index.d.ts +1 -0
- package/dist/types/viewport/src/math/sprite-roles.d.ts +32 -0
- package/dist/types/viewport/src/scene/frame-camera.d.ts +6 -1
- package/dist/types/viewport/src/scene/render-pipeline.d.ts +11 -0
- package/dist/types/viewport/src/scene/resolve-frag.d.ts +3 -1
- package/dist/viewport.js +17 -3
- package/package.json +5 -1
- package/dist/chunks/chunk-VZMBE5MV.js.map +0 -7
|
@@ -1,18 +1,160 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
+
// ../viewport/src/math/colors.ts
|
|
4
|
+
import { Color, LinearSRGBColorSpace, Vector3 } from "three";
|
|
5
|
+
var FIXED_COLORS = { tongue: 16021135, teeth: 16514042, black: 1118481 };
|
|
6
|
+
var PALETTE_ORDER = ["primary", "secondary", "detail", "highlight", "eye", "pupil", "custom"];
|
|
7
|
+
function hexOf(ref, palette) {
|
|
8
|
+
if (ref === "tongue" || ref === "teeth" || ref === "black") return FIXED_COLORS[ref];
|
|
9
|
+
return palette[ref] ?? 16711935;
|
|
10
|
+
}
|
|
11
|
+
function cssOf(ref, palette) {
|
|
12
|
+
return cssHex(hexOf(ref, palette));
|
|
13
|
+
}
|
|
14
|
+
var cssHex = (hex) => "#" + (hex & 16777215).toString(16).padStart(6, "0");
|
|
15
|
+
function rawColor(hex, out = new Color()) {
|
|
16
|
+
return out.setRGB((hex >> 16 & 255) / 255, (hex >> 8 & 255) / 255, (hex & 255) / 255, LinearSRGBColorSpace);
|
|
17
|
+
}
|
|
18
|
+
var srgbToLinear = (c) => c <= 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
|
|
19
|
+
function linearVec(hex, out = new Vector3()) {
|
|
20
|
+
return out.set(srgbToLinear((hex >> 16 & 255) / 255), srgbToLinear((hex >> 8 & 255) / 255), srgbToLinear((hex & 255) / 255));
|
|
21
|
+
}
|
|
22
|
+
function linearColor(hex, out = new Color()) {
|
|
23
|
+
const v = linearVec(hex);
|
|
24
|
+
return out.setRGB(v.x, v.y, v.z, LinearSRGBColorSpace);
|
|
25
|
+
}
|
|
26
|
+
function rawVec(hex, out = new Vector3()) {
|
|
27
|
+
return out.set((hex >> 16 & 255) / 255, (hex >> 8 & 255) / 255, (hex & 255) / 255);
|
|
28
|
+
}
|
|
29
|
+
var paletteKey = (p) => PALETTE_ORDER.map((s) => p[s]).join(",");
|
|
30
|
+
|
|
31
|
+
// ../viewport/src/math/pixel-palette.ts
|
|
32
|
+
import { Color as Color2 } from "three";
|
|
33
|
+
function paletteVariants(hex, black) {
|
|
34
|
+
const base = rawColor(hex);
|
|
35
|
+
const hsl = { h: 0, s: 0, l: 0 };
|
|
36
|
+
rgbToHsl(base.r, base.g, base.b, hsl);
|
|
37
|
+
const { h, s, l } = hsl;
|
|
38
|
+
if (l < 0.08 || hex === black) return { base, light: base.clone(), shade: base.clone() };
|
|
39
|
+
const toward = (from, to, k) => {
|
|
40
|
+
let d = to - from;
|
|
41
|
+
d -= Math.round(d);
|
|
42
|
+
return (from + d * k + 1) % 1;
|
|
43
|
+
};
|
|
44
|
+
const light = hslToRaw(toward(h, 0.12, 0.1), s * 0.92, Math.min(0.97, l + 0.16 + (1 - l) * 0.18));
|
|
45
|
+
const shade = hslToRaw(toward(h, 0.7, 0.12), Math.min(1, s * 1.05 + 0.04), l * 0.58);
|
|
46
|
+
const lum = (c) => 0.299 * c.r + 0.587 * c.g + 0.114 * c.b;
|
|
47
|
+
if (lum(light) < lum(base)) light.copy(base);
|
|
48
|
+
return { base, light, shade };
|
|
49
|
+
}
|
|
50
|
+
function rgbToHsl(r, g, b, out) {
|
|
51
|
+
const max = Math.max(r, g, b), min = Math.min(r, g, b);
|
|
52
|
+
const l = (min + max) / 2;
|
|
53
|
+
let h = 0, s = 0;
|
|
54
|
+
if (min !== max) {
|
|
55
|
+
const d = max - min;
|
|
56
|
+
s = l <= 0.5 ? d / (max + min) : d / (2 - max - min);
|
|
57
|
+
if (max === r) h = (g - b) / d + (g < b ? 6 : 0);
|
|
58
|
+
else if (max === g) h = (b - r) / d + 2;
|
|
59
|
+
else h = (r - g) / d + 4;
|
|
60
|
+
h /= 6;
|
|
61
|
+
}
|
|
62
|
+
out.h = h;
|
|
63
|
+
out.s = s;
|
|
64
|
+
out.l = l;
|
|
65
|
+
}
|
|
66
|
+
function hslToRaw(h, s, l) {
|
|
67
|
+
const hue = (p2, q2, t) => {
|
|
68
|
+
if (t < 0) t += 1;
|
|
69
|
+
if (t > 1) t -= 1;
|
|
70
|
+
if (t < 1 / 6) return p2 + (q2 - p2) * 6 * t;
|
|
71
|
+
if (t < 1 / 2) return q2;
|
|
72
|
+
if (t < 2 / 3) return p2 + (q2 - p2) * 6 * (2 / 3 - t);
|
|
73
|
+
return p2;
|
|
74
|
+
};
|
|
75
|
+
const c = new Color2();
|
|
76
|
+
if (s === 0) {
|
|
77
|
+
c.r = c.g = c.b = l;
|
|
78
|
+
return c;
|
|
79
|
+
}
|
|
80
|
+
const q = l <= 0.5 ? l * (1 + s) : l + s - l * s, p = 2 * l - q;
|
|
81
|
+
c.r = hue(p, q, h + 1 / 3);
|
|
82
|
+
c.g = hue(p, q, h);
|
|
83
|
+
c.b = hue(p, q, h - 1 / 3);
|
|
84
|
+
return c;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// ../viewport/src/math/sprite-roles.ts
|
|
88
|
+
var SPRITE_SLOTS = [...PALETTE_ORDER, "tongue", "teeth"];
|
|
89
|
+
var ROLE_TRANSPARENT = 0;
|
|
90
|
+
var ROLE_INK = 1;
|
|
91
|
+
var SPRITE_ROLE_COUNT = 2 + SPRITE_SLOTS.length * 3;
|
|
92
|
+
var spriteRole = (slot, tone) => 2 + SPRITE_SLOTS.indexOf(slot) * 3 + tone;
|
|
93
|
+
function roleSlot(role) {
|
|
94
|
+
if (role < 2 || role >= SPRITE_ROLE_COUNT) return void 0;
|
|
95
|
+
const k = role - 2;
|
|
96
|
+
return { slot: SPRITE_SLOTS[Math.floor(k / 3)], tone: k % 3 };
|
|
97
|
+
}
|
|
98
|
+
var toHex = (c) => Math.round(c.r * 255) << 16 | Math.round(c.g * 255) << 8 | Math.round(c.b * 255);
|
|
99
|
+
function spriteColors(palette) {
|
|
100
|
+
const out = [0, FIXED_COLORS.black];
|
|
101
|
+
for (const slot of SPRITE_SLOTS) {
|
|
102
|
+
const hex = slot === "tongue" || slot === "teeth" ? FIXED_COLORS[slot] : palette[slot];
|
|
103
|
+
const v = paletteVariants(hex, FIXED_COLORS.black);
|
|
104
|
+
out.push(toHex(v.base), toHex(v.light), toHex(v.shade));
|
|
105
|
+
}
|
|
106
|
+
return out;
|
|
107
|
+
}
|
|
108
|
+
function decodeRoles(rgba, width, height) {
|
|
109
|
+
const roles = new Uint8Array(width * height);
|
|
110
|
+
let approximated = 0;
|
|
111
|
+
for (let y = 0; y < height; y++) {
|
|
112
|
+
const src = (height - 1 - y) * width * 4, dst = y * width;
|
|
113
|
+
for (let x = 0; x < width; x++) {
|
|
114
|
+
const role = rgba[src + x * 4];
|
|
115
|
+
roles[dst + x] = role < SPRITE_ROLE_COUNT ? role : ROLE_INK;
|
|
116
|
+
if (role && rgba[src + x * 4 + 1] > 127) approximated++;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return { roles, approximated };
|
|
120
|
+
}
|
|
121
|
+
function spriteAnchor(roles, width, height) {
|
|
122
|
+
let minX = width, maxX = -1, minY = height, maxY = -1;
|
|
123
|
+
for (let y = 0; y < height; y++) {
|
|
124
|
+
for (let x = 0; x < width; x++) {
|
|
125
|
+
if (!roles[y * width + x]) continue;
|
|
126
|
+
if (x < minX) minX = x;
|
|
127
|
+
if (x > maxX) maxX = x;
|
|
128
|
+
if (y < minY) minY = y;
|
|
129
|
+
if (y > maxY) maxY = y;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
if (maxX < 0) return { footY: 0, centerX: 0, centerY: 0 };
|
|
133
|
+
return { footY: maxY, centerX: (minX + maxX) / 2, centerY: (minY + maxY) / 2 };
|
|
134
|
+
}
|
|
135
|
+
|
|
3
136
|
// ../viewport/src/scene/frame-camera.ts
|
|
4
|
-
import { Vector3 } from "three";
|
|
137
|
+
import { Quaternion as Q, Vector3 as Vector32 } from "three";
|
|
5
138
|
var DEFAULT_PADDING = 0.03;
|
|
139
|
+
function viewDirection(view, rotation) {
|
|
140
|
+
const front = new Vector32(0, 0, 1).applyQuaternion(new Q(rotation.x, rotation.y, rotation.z, rotation.w));
|
|
141
|
+
front.y = 0;
|
|
142
|
+
if (front.lengthSq() < 1e-8) front.set(0, 0, 1);
|
|
143
|
+
front.normalize();
|
|
144
|
+
const left = new Vector32(front.z, 0, -front.x);
|
|
145
|
+
const yaw = view.yaw * Math.PI / 180, pitch = Math.max(-89.9, Math.min(89.9, view.pitch)) * Math.PI / 180;
|
|
146
|
+
return front.multiplyScalar(Math.cos(yaw)).addScaledVector(left, Math.sin(yaw)).multiplyScalar(Math.cos(pitch)).add(new Vector32(0, Math.sin(pitch), 0)).normalize();
|
|
147
|
+
}
|
|
6
148
|
function basis(dir) {
|
|
7
149
|
const forward = dir.clone().negate();
|
|
8
|
-
const right = new
|
|
150
|
+
const right = new Vector32().crossVectors(forward, new Vector32(0, 1, 0));
|
|
9
151
|
if (right.lengthSq() < 1e-8) right.set(1, 0, 0);
|
|
10
152
|
right.normalize();
|
|
11
|
-
return { right, up: new
|
|
153
|
+
return { right, up: new Vector32().crossVectors(right, forward).normalize() };
|
|
12
154
|
}
|
|
13
155
|
function frameContain(stage, box, points, options = {}) {
|
|
14
156
|
const { camera, orbit } = stage;
|
|
15
|
-
const dir = options.direction ? new
|
|
157
|
+
const dir = options.direction ? new Vector32(options.direction.x, options.direction.y, options.direction.z) : camera.position.clone().sub(orbit.target);
|
|
16
158
|
if (dir.lengthSq() < 1e-10) dir.set(0, 0.35, 1);
|
|
17
159
|
dir.normalize();
|
|
18
160
|
const { right, up } = basis(dir);
|
|
@@ -20,10 +162,10 @@ function frameContain(stage, box, points, options = {}) {
|
|
|
20
162
|
const aspect = options.aspect ?? 1;
|
|
21
163
|
const keep = 1 - Math.min(0.5, Math.max(0, options.padding ?? DEFAULT_PADDING));
|
|
22
164
|
const [areaH, areaV] = (tanH / tanV >= aspect ? [tanV * aspect, tanV] : [tanH, tanH / aspect]).map((t) => t * keep);
|
|
23
|
-
const center = box.getCenter(new
|
|
165
|
+
const center = box.getCenter(new Vector32());
|
|
24
166
|
let distance = 0;
|
|
25
167
|
for (let i = 0; i < 8; i++) {
|
|
26
|
-
const c = new
|
|
168
|
+
const c = new Vector32(i & 1 ? box.max.x : box.min.x, i & 2 ? box.max.y : box.min.y, i & 4 ? box.max.z : box.min.z).sub(center);
|
|
27
169
|
const toward = c.dot(dir);
|
|
28
170
|
distance = Math.max(distance, Math.abs(c.dot(right)) / areaH + toward, Math.abs(c.dot(up)) / areaV + toward);
|
|
29
171
|
}
|
|
@@ -59,34 +201,6 @@ import { MOUSE, Raycaster as Raycaster2, TOUCH, Vector3 as Vector327 } from "thr
|
|
|
59
201
|
// ../viewport/src/decals/decal-atlas.ts
|
|
60
202
|
import { CanvasTexture, LinearFilter, SRGBColorSpace } from "three";
|
|
61
203
|
|
|
62
|
-
// ../viewport/src/math/colors.ts
|
|
63
|
-
import { Color, LinearSRGBColorSpace, Vector3 as Vector32 } from "three";
|
|
64
|
-
var FIXED_COLORS = { tongue: 16021135, teeth: 16514042, black: 1118481 };
|
|
65
|
-
var PALETTE_ORDER = ["primary", "secondary", "detail", "highlight", "eye", "pupil", "custom"];
|
|
66
|
-
function hexOf(ref, palette) {
|
|
67
|
-
if (ref === "tongue" || ref === "teeth" || ref === "black") return FIXED_COLORS[ref];
|
|
68
|
-
return palette[ref] ?? 16711935;
|
|
69
|
-
}
|
|
70
|
-
function cssOf(ref, palette) {
|
|
71
|
-
return cssHex(hexOf(ref, palette));
|
|
72
|
-
}
|
|
73
|
-
var cssHex = (hex) => "#" + (hex & 16777215).toString(16).padStart(6, "0");
|
|
74
|
-
function rawColor(hex, out = new Color()) {
|
|
75
|
-
return out.setRGB((hex >> 16 & 255) / 255, (hex >> 8 & 255) / 255, (hex & 255) / 255, LinearSRGBColorSpace);
|
|
76
|
-
}
|
|
77
|
-
var srgbToLinear = (c) => c <= 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
|
|
78
|
-
function linearVec(hex, out = new Vector32()) {
|
|
79
|
-
return out.set(srgbToLinear((hex >> 16 & 255) / 255), srgbToLinear((hex >> 8 & 255) / 255), srgbToLinear((hex & 255) / 255));
|
|
80
|
-
}
|
|
81
|
-
function linearColor(hex, out = new Color()) {
|
|
82
|
-
const v = linearVec(hex);
|
|
83
|
-
return out.setRGB(v.x, v.y, v.z, LinearSRGBColorSpace);
|
|
84
|
-
}
|
|
85
|
-
function rawVec(hex, out = new Vector32()) {
|
|
86
|
-
return out.set((hex >> 16 & 255) / 255, (hex >> 8 & 255) / 255, (hex & 255) / 255);
|
|
87
|
-
}
|
|
88
|
-
var paletteKey = (p) => PALETTE_ORDER.map((s) => p[s]).join(",");
|
|
89
|
-
|
|
90
204
|
// ../viewport/src/decals/builtin-docs.ts
|
|
91
205
|
var BUILTIN_DOCS = {
|
|
92
206
|
u6: {
|
|
@@ -801,7 +915,7 @@ var DecalAtlas = class {
|
|
|
801
915
|
};
|
|
802
916
|
|
|
803
917
|
// ../viewport/src/decals/interior-thumbs.ts
|
|
804
|
-
import { Color as
|
|
918
|
+
import { Color as Color3, DoubleSide, Mesh, OrthographicCamera, PlaneGeometry, Scene, ShaderMaterial, WebGLRenderer } from "three";
|
|
805
919
|
|
|
806
920
|
// ../viewport/src/decals/decal-glsl.ts
|
|
807
921
|
var INTERIOR_GLSL = (
|
|
@@ -1111,7 +1225,7 @@ function renderInteriorThumbs(rooms) {
|
|
|
1111
1225
|
r.setSize(S, S, false);
|
|
1112
1226
|
const mat = new ShaderMaterial({
|
|
1113
1227
|
side: DoubleSide,
|
|
1114
|
-
uniforms: { uRoom: { value: 0 }, uDepth: { value: 1 }, uWall: { value: new
|
|
1228
|
+
uniforms: { uRoom: { value: 0 }, uDepth: { value: 1 }, uWall: { value: new Color3() }, uSeed: { value: 3 } },
|
|
1115
1229
|
vertexShader: "varying vec2 vP; void main() { vP = position.xy; gl_Position = vec4(position.xy * 2.0, 0.0, 1.0); }",
|
|
1116
1230
|
fragmentShader: `${INTERIOR_GLSL}
|
|
1117
1231
|
varying vec2 vP; uniform float uRoom; uniform float uDepth; uniform vec3 uWall; uniform float uSeed;
|
|
@@ -1433,14 +1547,14 @@ function writeSegmentMatrices(bones, m, out) {
|
|
|
1433
1547
|
const size = bones[i]?.size;
|
|
1434
1548
|
const th = Math.max(0.02, fs.thick / Math.max(bindFs.thick, 1e-6) * (size?.thickness ?? 1));
|
|
1435
1549
|
let sL;
|
|
1436
|
-
let
|
|
1550
|
+
let Q2 = rel;
|
|
1437
1551
|
if (segChild >= 0) {
|
|
1438
1552
|
const seg = new Vector35().setFromMatrixPosition(m.shownGlobal[segChild]).sub(sp);
|
|
1439
1553
|
const L = seg.length();
|
|
1440
1554
|
sL = bindLen > 1e-6 ? L / bindLen : 1;
|
|
1441
1555
|
if (L > 1e-6) {
|
|
1442
1556
|
const from = axis.clone().applyQuaternion(rel);
|
|
1443
|
-
|
|
1557
|
+
Q2 = new Quaternion2().setFromUnitVectors(from, seg.divideScalar(L)).multiply(rel);
|
|
1444
1558
|
}
|
|
1445
1559
|
} else {
|
|
1446
1560
|
sL = fs.along / Math.max(bindFs.along, 1e-6);
|
|
@@ -1465,7 +1579,7 @@ function writeSegmentMatrices(bones, m, out) {
|
|
|
1465
1579
|
0,
|
|
1466
1580
|
1
|
|
1467
1581
|
);
|
|
1468
|
-
mR.makeRotationFromQuaternion(
|
|
1582
|
+
mR.makeRotationFromQuaternion(Q2).multiply(mS);
|
|
1469
1583
|
mR.multiply(mT.makeTranslation(-H0.x, -H0.y, -H0.z));
|
|
1470
1584
|
mR.premultiply(mT.makeTranslation(sp.x, sp.y, sp.z));
|
|
1471
1585
|
mR.toArray(out, i * 16);
|
|
@@ -3859,7 +3973,7 @@ var SelectTool = class {
|
|
|
3859
3973
|
};
|
|
3860
3974
|
|
|
3861
3975
|
// ../viewport/src/interaction/selection-boxes.ts
|
|
3862
|
-
import { Box3Helper, Box3 as Box34, Color as
|
|
3976
|
+
import { Box3Helper, Box3 as Box34, Color as Color4 } from "three";
|
|
3863
3977
|
var SelectionBoxes = class {
|
|
3864
3978
|
constructor(ctx, ui) {
|
|
3865
3979
|
this.ctx = ctx;
|
|
@@ -3873,7 +3987,7 @@ var SelectionBoxes = class {
|
|
|
3873
3987
|
const ids = s.mode === "assembly" && !this.ctx.locked() ? s.selection.objectIds : [];
|
|
3874
3988
|
const groups = ids.map((id) => this.ctx.views.group(id)).filter((g) => !!g);
|
|
3875
3989
|
while (this.helpers.length < groups.length) {
|
|
3876
|
-
const h = new Box3Helper(new Box34(), new
|
|
3990
|
+
const h = new Box3Helper(new Box34(), new Color4(3718648));
|
|
3877
3991
|
this.ui.add(h);
|
|
3878
3992
|
this.helpers.push(h);
|
|
3879
3993
|
}
|
|
@@ -4531,10 +4645,13 @@ var MeshDriver = class {
|
|
|
4531
4645
|
const key = req.key;
|
|
4532
4646
|
const seq = ++this.seq;
|
|
4533
4647
|
this.last.set(key, { seq, content: "", resolution: 0 });
|
|
4648
|
+
this.inFlight++;
|
|
4534
4649
|
this.evaluator.skinWeights(req).then((r) => {
|
|
4535
4650
|
if (!this.disposed && this.last.get(key)?.seq === seq) done(r);
|
|
4536
4651
|
}, (e) => {
|
|
4537
4652
|
if (!this.disposed && !isAbort(e)) console.warn("skin weights failed", key, e);
|
|
4653
|
+
}).finally(() => {
|
|
4654
|
+
this.inFlight--;
|
|
4538
4655
|
});
|
|
4539
4656
|
}
|
|
4540
4657
|
/** Forget a key (the object is gone): pending work is dropped. */
|
|
@@ -4750,10 +4867,10 @@ var SceneViews = class {
|
|
|
4750
4867
|
};
|
|
4751
4868
|
|
|
4752
4869
|
// ../viewport/src/rig/bone-viz.ts
|
|
4753
|
-
import { Box3 as Box37, BufferAttribute as BufferAttribute4, BufferGeometry as BufferGeometry7, Color as
|
|
4754
|
-
var COL_RIG = new
|
|
4755
|
-
var COL_POSE = new
|
|
4756
|
-
var COL_SEL = new
|
|
4870
|
+
import { Box3 as Box37, BufferAttribute as BufferAttribute4, BufferGeometry as BufferGeometry7, Color as Color5, Group as Group6, LineBasicMaterial as LineBasicMaterial3, LineSegments as LineSegments2, Points, PointsMaterial, Sphere, Vector3 as Vector321 } from "three";
|
|
4871
|
+
var COL_RIG = new Color5("#34d399");
|
|
4872
|
+
var COL_POSE = new Color5("#60a5fa");
|
|
4873
|
+
var COL_SEL = new Color5("#fde047");
|
|
4757
4874
|
var BoneViz = class {
|
|
4758
4875
|
root = new Group6();
|
|
4759
4876
|
line;
|
|
@@ -4856,7 +4973,7 @@ import { Vector3 as Vector324 } from "three";
|
|
|
4856
4973
|
import {
|
|
4857
4974
|
ACESFilmicToneMapping,
|
|
4858
4975
|
AmbientLight,
|
|
4859
|
-
Color as
|
|
4976
|
+
Color as Color6,
|
|
4860
4977
|
DirectionalLight,
|
|
4861
4978
|
FogExp2,
|
|
4862
4979
|
Group as Group7,
|
|
@@ -4914,7 +5031,7 @@ function createStage(host, globals2) {
|
|
|
4914
5031
|
renderer.domElement.style.touchAction = "none";
|
|
4915
5032
|
host.appendChild(renderer.domElement);
|
|
4916
5033
|
const scene = new Scene3();
|
|
4917
|
-
scene.background = new
|
|
5034
|
+
scene.background = new Color6(SKY);
|
|
4918
5035
|
scene.fog = new FogExp2(SKY, 0.012);
|
|
4919
5036
|
const camera = new PerspectiveCamera(50, (host.clientWidth || 1) / (host.clientHeight || 1), 0.1, 1e3);
|
|
4920
5037
|
camera.position.copy(DEFAULT_CAMERA);
|
|
@@ -5062,63 +5179,9 @@ import {
|
|
|
5062
5179
|
WebGLRenderTarget
|
|
5063
5180
|
} from "three";
|
|
5064
5181
|
|
|
5065
|
-
// ../viewport/src/math/pixel-palette.ts
|
|
5066
|
-
import { Color as Color6 } from "three";
|
|
5067
|
-
function paletteVariants(hex, black) {
|
|
5068
|
-
const base = rawColor(hex);
|
|
5069
|
-
const hsl = { h: 0, s: 0, l: 0 };
|
|
5070
|
-
rgbToHsl(base.r, base.g, base.b, hsl);
|
|
5071
|
-
const { h, s, l } = hsl;
|
|
5072
|
-
if (l < 0.08 || hex === black) return { base, light: base.clone(), shade: base.clone() };
|
|
5073
|
-
const toward = (from, to, k) => {
|
|
5074
|
-
let d = to - from;
|
|
5075
|
-
d -= Math.round(d);
|
|
5076
|
-
return (from + d * k + 1) % 1;
|
|
5077
|
-
};
|
|
5078
|
-
const light = hslToRaw(toward(h, 0.12, 0.1), s * 0.92, Math.min(0.97, l + 0.16 + (1 - l) * 0.18));
|
|
5079
|
-
const shade = hslToRaw(toward(h, 0.7, 0.12), Math.min(1, s * 1.05 + 0.04), l * 0.58);
|
|
5080
|
-
const lum = (c) => 0.299 * c.r + 0.587 * c.g + 0.114 * c.b;
|
|
5081
|
-
if (lum(light) < lum(base)) light.copy(base);
|
|
5082
|
-
return { base, light, shade };
|
|
5083
|
-
}
|
|
5084
|
-
function rgbToHsl(r, g, b, out) {
|
|
5085
|
-
const max = Math.max(r, g, b), min = Math.min(r, g, b);
|
|
5086
|
-
const l = (min + max) / 2;
|
|
5087
|
-
let h = 0, s = 0;
|
|
5088
|
-
if (min !== max) {
|
|
5089
|
-
const d = max - min;
|
|
5090
|
-
s = l <= 0.5 ? d / (max + min) : d / (2 - max - min);
|
|
5091
|
-
if (max === r) h = (g - b) / d + (g < b ? 6 : 0);
|
|
5092
|
-
else if (max === g) h = (b - r) / d + 2;
|
|
5093
|
-
else h = (r - g) / d + 4;
|
|
5094
|
-
h /= 6;
|
|
5095
|
-
}
|
|
5096
|
-
out.h = h;
|
|
5097
|
-
out.s = s;
|
|
5098
|
-
out.l = l;
|
|
5099
|
-
}
|
|
5100
|
-
function hslToRaw(h, s, l) {
|
|
5101
|
-
const hue = (p2, q2, t) => {
|
|
5102
|
-
if (t < 0) t += 1;
|
|
5103
|
-
if (t > 1) t -= 1;
|
|
5104
|
-
if (t < 1 / 6) return p2 + (q2 - p2) * 6 * t;
|
|
5105
|
-
if (t < 1 / 2) return q2;
|
|
5106
|
-
if (t < 2 / 3) return p2 + (q2 - p2) * 6 * (2 / 3 - t);
|
|
5107
|
-
return p2;
|
|
5108
|
-
};
|
|
5109
|
-
const c = new Color6();
|
|
5110
|
-
if (s === 0) {
|
|
5111
|
-
c.r = c.g = c.b = l;
|
|
5112
|
-
return c;
|
|
5113
|
-
}
|
|
5114
|
-
const q = l <= 0.5 ? l * (1 + s) : l + s - l * s, p = 2 * l - q;
|
|
5115
|
-
c.r = hue(p, q, h + 1 / 3);
|
|
5116
|
-
c.g = hue(p, q, h);
|
|
5117
|
-
c.b = hue(p, q, h - 1 / 3);
|
|
5118
|
-
return c;
|
|
5119
|
-
}
|
|
5120
|
-
|
|
5121
5182
|
// ../viewport/src/scene/resolve-frag.ts
|
|
5183
|
+
var TEETH_INDEX = 8;
|
|
5184
|
+
var APPROX_DISTANCE = "0.08";
|
|
5122
5185
|
var RESOLVE_FRAG = (
|
|
5123
5186
|
/* glsl */
|
|
5124
5187
|
`
|
|
@@ -5144,6 +5207,7 @@ uniform float uTone1;
|
|
|
5144
5207
|
uniform float uTone2;
|
|
5145
5208
|
uniform float uDither;
|
|
5146
5209
|
uniform float uGlossWhite;
|
|
5210
|
+
uniform float uIndexOut;
|
|
5147
5211
|
varying vec2 vUv;
|
|
5148
5212
|
|
|
5149
5213
|
// same curves three.js applies to a material's output on screen
|
|
@@ -5195,13 +5259,16 @@ bool outlineAt(vec2 p) {
|
|
|
5195
5259
|
return false;
|
|
5196
5260
|
}
|
|
5197
5261
|
|
|
5198
|
-
|
|
5262
|
+
// The pixel-art choice for a lit pixel, as a sprite role: 1 ink, or 2 + palette index \xD7 3 + tone (0 base, 1 light,
|
|
5263
|
+
// 2 shade); a shiny surface's light tone is teeth-white (the teeth base role).
|
|
5264
|
+
float celRole(vec3 c, float gloss, vec2 p) {
|
|
5199
5265
|
// Pixels from black decals or unlit black surfaces render directly as solid ink
|
|
5200
|
-
if (max(c.r, max(c.g, c.b)) < 0.05) return
|
|
5266
|
+
if (max(c.r, max(c.g, c.b)) < 0.05) return 1.0;
|
|
5201
5267
|
|
|
5202
|
-
vec3 base = vec3(0.0)
|
|
5268
|
+
vec3 base = vec3(0.0);
|
|
5203
5269
|
float bestScore = 1e9;
|
|
5204
5270
|
float bestS = 1.0;
|
|
5271
|
+
float bestI = 0.0;
|
|
5205
5272
|
float cl = length(c) + 0.02;
|
|
5206
5273
|
for (int i = 0; i < ${PIXEL_MAX_PALETTE}; i++) {
|
|
5207
5274
|
if (i >= uPalCount) break;
|
|
@@ -5209,24 +5276,63 @@ vec3 celShade(vec3 c, float gloss, vec2 p) {
|
|
|
5209
5276
|
float s = clamp(dot(c, P) / max(dot(P, P), 0.0001), 0.2, 4.0);
|
|
5210
5277
|
float score = length(c - s * P) / cl + 0.12 * abs(log2(s / 1.25));
|
|
5211
5278
|
if (score < bestScore) {
|
|
5212
|
-
bestScore = score; bestS = s;
|
|
5213
|
-
base = uPalBase[i];
|
|
5279
|
+
bestScore = score; bestS = s; bestI = float(i);
|
|
5280
|
+
base = uPalBase[i];
|
|
5214
5281
|
}
|
|
5215
5282
|
}
|
|
5216
5283
|
|
|
5217
5284
|
// Black from the palette stays flat: no teeth-white specular, no dithering, no stepping
|
|
5218
|
-
if (length(base - uInk) < 0.05 || length(base) < 0.10) return
|
|
5285
|
+
if (length(base - uInk) < 0.05 || length(base) < 0.10) return 1.0;
|
|
5219
5286
|
|
|
5220
|
-
if (gloss > uGlossWhite) light = uTeeth; // only shiny materials catch teeth-white light
|
|
5221
5287
|
float tau = log2(bestS) + (bayer4(p) - 0.47) * uDither;
|
|
5222
|
-
if (tau > uTone2) return light
|
|
5223
|
-
if (tau > uTone1) return
|
|
5224
|
-
return
|
|
5288
|
+
if (tau > uTone2) return (gloss > uGlossWhite) ? ${2 + TEETH_INDEX * 3}.0 : 2.0 + bestI * 3.0 + 1.0; // only shiny materials catch teeth-white light
|
|
5289
|
+
if (tau > uTone1) return 2.0 + bestI * 3.0;
|
|
5290
|
+
return 2.0 + bestI * 3.0 + 2.0;
|
|
5291
|
+
}
|
|
5292
|
+
|
|
5293
|
+
vec3 roleColor(float role) {
|
|
5294
|
+
if (role < 1.5) return uInk;
|
|
5295
|
+
float k = role - 2.0;
|
|
5296
|
+
float slot = floor(k / 3.0 + 0.001);
|
|
5297
|
+
float tone = k - slot * 3.0;
|
|
5298
|
+
vec3 col = uInk;
|
|
5299
|
+
for (int i = 0; i < ${PIXEL_MAX_PALETTE}; i++) {
|
|
5300
|
+
if (float(i) == slot) col = (tone < 0.5) ? uPalBase[i] : ((tone < 1.5) ? uPalLight[i] : uPalShade[i]);
|
|
5301
|
+
}
|
|
5302
|
+
return col;
|
|
5303
|
+
}
|
|
5304
|
+
|
|
5305
|
+
vec3 celShade(vec3 c, float gloss, vec2 p) { return roleColor(celRole(c, gloss, p)); }
|
|
5306
|
+
|
|
5307
|
+
// A flat (decal) color as the nearest role: x the role, y 1 when the match is not close (art outside the palette)
|
|
5308
|
+
vec2 nearestRole(vec3 c) {
|
|
5309
|
+
if (max(c.r, max(c.g, c.b)) < 0.05) return vec2(1.0, 0.0);
|
|
5310
|
+
float best = length(c - uInk), role = 1.0;
|
|
5311
|
+
for (int i = 0; i < ${PIXEL_MAX_PALETTE}; i++) {
|
|
5312
|
+
if (i >= uPalCount) break;
|
|
5313
|
+
if (length(uPalBase[i] - uInk) < 0.05) continue;
|
|
5314
|
+
float d0 = length(c - uPalBase[i]), d1 = length(c - uPalLight[i]), d2 = length(c - uPalShade[i]);
|
|
5315
|
+
if (d0 < best) { best = d0; role = 2.0 + float(i) * 3.0; }
|
|
5316
|
+
if (d1 < best) { best = d1; role = 2.0 + float(i) * 3.0 + 1.0; }
|
|
5317
|
+
if (d2 < best) { best = d2; role = 2.0 + float(i) * 3.0 + 2.0; }
|
|
5318
|
+
}
|
|
5319
|
+
return vec2(role, best > ${APPROX_DISTANCE} ? 1.0 : 0.0);
|
|
5225
5320
|
}
|
|
5226
5321
|
|
|
5227
5322
|
void main() {
|
|
5228
5323
|
vec2 p = floor(vUv * uSize);
|
|
5229
5324
|
vec4 s = texture2D(tColor, (uPixel > 0.5) ? (p + 0.5) / uSize : vUv);
|
|
5325
|
+
// Sprite bake: the role of each pixel in red (/ 255), "approximated" in green
|
|
5326
|
+
if (uIndexOut > 0.5) {
|
|
5327
|
+
vec2 r = vec2(0.0);
|
|
5328
|
+
if (s.a > 0.05) {
|
|
5329
|
+
if (s.a >= 0.45 && s.a <= 0.50) r = nearestRole(s.rgb);
|
|
5330
|
+
else r = vec2(celRole(s.rgb, (s.a < 0.4) ? 0.0 : clamp((1.0 - s.a) * 2.0, 0.0, 1.0), p), 0.0);
|
|
5331
|
+
}
|
|
5332
|
+
if (outlineAt(p)) r = vec2(1.0, 0.0);
|
|
5333
|
+
gl_FragColor = vec4(r.x / 255.0, r.y, 0.0, 1.0);
|
|
5334
|
+
return;
|
|
5335
|
+
}
|
|
5230
5336
|
vec3 bg = uBg;
|
|
5231
5337
|
if (uChecker > 0.5) {
|
|
5232
5338
|
vec2 g = floor(gl_FragCoord.xy / (9.0 * uDpr));
|
|
@@ -5284,7 +5390,8 @@ var RenderPipeline = class {
|
|
|
5284
5390
|
uTone1: { value: Math.log2(PIXEL_SHADE_BELOW) },
|
|
5285
5391
|
uTone2: { value: Math.log2(PIXEL_LIGHT_ABOVE) },
|
|
5286
5392
|
uDither: { value: PIXEL_DITHER },
|
|
5287
|
-
uGlossWhite: { value: PIXEL_GLOSS_WHITE }
|
|
5393
|
+
uGlossWhite: { value: PIXEL_GLOSS_WHITE },
|
|
5394
|
+
uIndexOut: { value: 0 }
|
|
5288
5395
|
},
|
|
5289
5396
|
vertexShader: "varying vec2 vUv; void main() { vUv = uv; gl_Position = vec4(position.xy, 0.0, 1.0); }",
|
|
5290
5397
|
fragmentShader: RESOLVE_FRAG,
|
|
@@ -5304,6 +5411,8 @@ var RenderPipeline = class {
|
|
|
5304
5411
|
quadCam = new OrthographicCamera2(-1, 1, 1, -1, 0, 1);
|
|
5305
5412
|
paletteKey = "";
|
|
5306
5413
|
size = new Vector24();
|
|
5414
|
+
/** Sprite bakes: the world pass and the role target, kept apart from the view's target. */
|
|
5415
|
+
bake = null;
|
|
5307
5416
|
render(prefs, palette, keep, isolated) {
|
|
5308
5417
|
const { renderer, scene, camera } = this.stage;
|
|
5309
5418
|
const selOnly = prefs.renderSelectedOnly && !!keep && keep.length > 0;
|
|
@@ -5414,6 +5523,12 @@ var RenderPipeline = class {
|
|
|
5414
5523
|
this.target.depthTexture?.dispose();
|
|
5415
5524
|
this.target.dispose();
|
|
5416
5525
|
}
|
|
5526
|
+
this.target = this.worldTarget(w, h, pixel, smooth, half);
|
|
5527
|
+
this.targetKey = key;
|
|
5528
|
+
return this.target;
|
|
5529
|
+
}
|
|
5530
|
+
/** The offscreen world pass: linear, with "kind" in alpha, and a depth texture for pixel-art outlines. */
|
|
5531
|
+
worldTarget(w, h, pixel, smooth, half) {
|
|
5417
5532
|
const filter = smooth ? LinearFilter2 : NearestFilter2;
|
|
5418
5533
|
const rt = new WebGLRenderTarget(w, h, {
|
|
5419
5534
|
minFilter: filter,
|
|
@@ -5429,10 +5544,93 @@ var RenderPipeline = class {
|
|
|
5429
5544
|
} else if (smooth) {
|
|
5430
5545
|
rt.samples = 4;
|
|
5431
5546
|
}
|
|
5432
|
-
this.target = rt;
|
|
5433
|
-
this.targetKey = key;
|
|
5434
5547
|
return rt;
|
|
5435
5548
|
}
|
|
5549
|
+
/**
|
|
5550
|
+
* A sprite bake: `keep` alone (lights stay, ground and helpers go) in pixel art into a `size`-square target, resolved
|
|
5551
|
+
* to sprite roles instead of colors, read back as RGBA bytes (rows from the bottom). The camera must already be
|
|
5552
|
+
* square. Nothing reaches the canvas.
|
|
5553
|
+
*/
|
|
5554
|
+
renderSprite(size, palette, keep) {
|
|
5555
|
+
const { renderer, scene, camera } = this.stage;
|
|
5556
|
+
if (this.bake?.size !== size) {
|
|
5557
|
+
this.disposeBake();
|
|
5558
|
+
const r = this.stage.renderer;
|
|
5559
|
+
const half = r.extensions.has("EXT_color_buffer_float") || r.extensions.has("EXT_color_buffer_half_float");
|
|
5560
|
+
const roles2 = new WebGLRenderTarget(size, size, {
|
|
5561
|
+
minFilter: NearestFilter2,
|
|
5562
|
+
magFilter: NearestFilter2,
|
|
5563
|
+
format: RGBAFormat2,
|
|
5564
|
+
type: UnsignedByteType,
|
|
5565
|
+
depthBuffer: false,
|
|
5566
|
+
stencilBuffer: false
|
|
5567
|
+
});
|
|
5568
|
+
this.bake = { size, world: this.worldTarget(size, size, true, false, half), roles: roles2 };
|
|
5569
|
+
}
|
|
5570
|
+
const { world, roles } = this.bake;
|
|
5571
|
+
const prev = {
|
|
5572
|
+
tm: renderer.toneMapping,
|
|
5573
|
+
bg: scene.background,
|
|
5574
|
+
fog: scene.fog,
|
|
5575
|
+
autoClear: renderer.autoClear,
|
|
5576
|
+
clearColor: renderer.getClearColor(new Color7()),
|
|
5577
|
+
clearAlpha: renderer.getClearAlpha()
|
|
5578
|
+
};
|
|
5579
|
+
const hidden = [];
|
|
5580
|
+
const hide = (n) => {
|
|
5581
|
+
if (n.visible) {
|
|
5582
|
+
n.visible = false;
|
|
5583
|
+
hidden.push(n);
|
|
5584
|
+
}
|
|
5585
|
+
};
|
|
5586
|
+
const u = this.quadMat.uniforms;
|
|
5587
|
+
try {
|
|
5588
|
+
hide(this.stage.ui);
|
|
5589
|
+
this.hideUnselected(keep, hide);
|
|
5590
|
+
renderer.toneMapping = NoToneMapping;
|
|
5591
|
+
scene.background = null;
|
|
5592
|
+
scene.fog = null;
|
|
5593
|
+
this.globals.uPixelOut.value = 1;
|
|
5594
|
+
renderer.setRenderTarget(world);
|
|
5595
|
+
renderer.setClearColor(0, 0);
|
|
5596
|
+
renderer.autoClear = true;
|
|
5597
|
+
renderer.render(scene, camera);
|
|
5598
|
+
this.globals.uPixelOut.value = 0;
|
|
5599
|
+
u.tColor.value = world.texture;
|
|
5600
|
+
u.tDepth.value = world.depthTexture;
|
|
5601
|
+
u.uSize.value.set(size, size);
|
|
5602
|
+
u.uNear.value = camera.near;
|
|
5603
|
+
u.uFar.value = camera.far;
|
|
5604
|
+
u.uPixel.value = 1;
|
|
5605
|
+
u.uChecker.value = 0;
|
|
5606
|
+
u.uIndexOut.value = 1;
|
|
5607
|
+
this.setPalette(palette, u);
|
|
5608
|
+
renderer.setRenderTarget(roles);
|
|
5609
|
+
renderer.render(this.quadScene, this.quadCam);
|
|
5610
|
+
const out = new Uint8Array(size * size * 4);
|
|
5611
|
+
renderer.readRenderTargetPixels(roles, 0, 0, size, size, out);
|
|
5612
|
+
return out;
|
|
5613
|
+
} finally {
|
|
5614
|
+
u.uIndexOut.value = 0;
|
|
5615
|
+
this.globals.uPixelOut.value = 0;
|
|
5616
|
+
hidden.forEach((n) => {
|
|
5617
|
+
n.visible = true;
|
|
5618
|
+
});
|
|
5619
|
+
renderer.setRenderTarget(null);
|
|
5620
|
+
renderer.toneMapping = prev.tm;
|
|
5621
|
+
renderer.autoClear = prev.autoClear;
|
|
5622
|
+
renderer.setClearColor(prev.clearColor, prev.clearAlpha);
|
|
5623
|
+
scene.background = prev.bg;
|
|
5624
|
+
scene.fog = prev.fog;
|
|
5625
|
+
}
|
|
5626
|
+
}
|
|
5627
|
+
disposeBake() {
|
|
5628
|
+
if (!this.bake) return;
|
|
5629
|
+
this.bake.world.depthTexture?.dispose();
|
|
5630
|
+
this.bake.world.dispose();
|
|
5631
|
+
this.bake.roles.dispose();
|
|
5632
|
+
this.bake = null;
|
|
5633
|
+
}
|
|
5436
5634
|
setPalette(palette, u) {
|
|
5437
5635
|
const hexes = [...PALETTE_ORDER.map((s) => palette[s]), FIXED_COLORS.tongue, FIXED_COLORS.teeth, FIXED_COLORS.black].slice(0, PIXEL_MAX_PALETTE);
|
|
5438
5636
|
const key = paletteKey(palette);
|
|
@@ -5451,6 +5649,7 @@ var RenderPipeline = class {
|
|
|
5451
5649
|
rawVec(FIXED_COLORS.black, u.uInk.value);
|
|
5452
5650
|
}
|
|
5453
5651
|
dispose() {
|
|
5652
|
+
this.disposeBake();
|
|
5454
5653
|
this.target?.depthTexture?.dispose();
|
|
5455
5654
|
this.target?.dispose();
|
|
5456
5655
|
this.quadMat.dispose();
|
|
@@ -5513,6 +5712,11 @@ function stressTestObjects(rnd = Math.random) {
|
|
|
5513
5712
|
// ../viewport/src/create-viewport.ts
|
|
5514
5713
|
var LOD_EVERY = 4;
|
|
5515
5714
|
var LOD_CHANGES_PER_TICK = 8;
|
|
5715
|
+
var BAKE_SETTLED_CHECKS = 3;
|
|
5716
|
+
var BAKE_POLL_MS = 16;
|
|
5717
|
+
var BAKE_TIMEOUT_MS = 3e4;
|
|
5718
|
+
var BAKE_FOV = 25;
|
|
5719
|
+
var IDENTITY_Q = { x: 0, y: 0, z: 0, w: 1 };
|
|
5516
5720
|
function createViewport({ evaluator }) {
|
|
5517
5721
|
let creation = null;
|
|
5518
5722
|
let session = null;
|
|
@@ -5525,6 +5729,7 @@ function createViewport({ evaluator }) {
|
|
|
5525
5729
|
let lastDesigns = null;
|
|
5526
5730
|
let levelsBase = 0;
|
|
5527
5731
|
let stress = [];
|
|
5732
|
+
const baking = /* @__PURE__ */ new Set();
|
|
5528
5733
|
const driver = new MeshDriver(evaluator);
|
|
5529
5734
|
const getAtlas = (palette) => atlas ??= new DecalAtlas(palette);
|
|
5530
5735
|
const toast = (msg) => callbacks?.toast(msg);
|
|
@@ -5658,7 +5863,7 @@ function createViewport({ evaluator }) {
|
|
|
5658
5863
|
const c = v.bounds.getCenter(new Vector327()).applyMatrix4(v.group.matrixWorld);
|
|
5659
5864
|
const size = v.bounds.getSize(new Vector327());
|
|
5660
5865
|
const px = projectedPx(Math.max(size.x, size.y, size.z) * v.group.matrixWorld.getMaxScaleOnAxis(), cam.position.distanceTo(c), cam.fov, h);
|
|
5661
|
-
const want = lodWanted(mm.vctx.levels, px, v.resolution, v === scoped || sel.has(id));
|
|
5866
|
+
const want = lodWanted(mm.vctx.levels, px, v.resolution, v === scoped || sel.has(id) || baking.has(id));
|
|
5662
5867
|
if (want !== v.resolution) {
|
|
5663
5868
|
v.setResolution(want);
|
|
5664
5869
|
changes++;
|
|
@@ -5683,7 +5888,7 @@ function createViewport({ evaluator }) {
|
|
|
5683
5888
|
const s = session, c = creation;
|
|
5684
5889
|
const { stage } = mm;
|
|
5685
5890
|
if (pendingFrame && pendingFrame.ids.every((id) => !mm.views.worldBounds([id]).isEmpty())) {
|
|
5686
|
-
frameContain(stage, mm.views.worldBounds(pendingFrame.ids), mm.views.worldPoints(pendingFrame.ids), pendingFrame.options);
|
|
5891
|
+
frameContain(stage, mm.views.worldBounds(pendingFrame.ids), mm.views.worldPoints(pendingFrame.ids), withView(pendingFrame.ids, pendingFrame.options));
|
|
5687
5892
|
pendingFrame = null;
|
|
5688
5893
|
}
|
|
5689
5894
|
mm.vctx.globals.uTime.value = time * 1e-3;
|
|
@@ -5883,6 +6088,86 @@ function createViewport({ evaluator }) {
|
|
|
5883
6088
|
c.getContext("2d").drawImage(src, 0, 0, c.width, c.height);
|
|
5884
6089
|
return c.toDataURL("image/png");
|
|
5885
6090
|
}
|
|
6091
|
+
function withView(ids, options) {
|
|
6092
|
+
if (!options.view) return options;
|
|
6093
|
+
const facing = creation?.objects.find((o) => o.id === ids[0])?.transform.rotation ?? IDENTITY_Q;
|
|
6094
|
+
const d = viewDirection(options.view, facing);
|
|
6095
|
+
return { ...options, direction: { x: d.x, y: d.y, z: d.z } };
|
|
6096
|
+
}
|
|
6097
|
+
function sculptViewsOf(mm, ids) {
|
|
6098
|
+
return ids.flatMap((id) => {
|
|
6099
|
+
const v = mm.views.views.get(id)?.view;
|
|
6100
|
+
return v instanceof RigView ? v.sculptViews() : v instanceof SculptView ? [v] : [];
|
|
6101
|
+
});
|
|
6102
|
+
}
|
|
6103
|
+
function settled(mm, ids) {
|
|
6104
|
+
const finest = mm.vctx.levels[0] ?? 36;
|
|
6105
|
+
const started = performance.now();
|
|
6106
|
+
return new Promise((resolve, reject) => {
|
|
6107
|
+
let calm = 0;
|
|
6108
|
+
const check = () => {
|
|
6109
|
+
if (m !== mm) {
|
|
6110
|
+
reject(new Error("The viewport was disposed during a bake."));
|
|
6111
|
+
return;
|
|
6112
|
+
}
|
|
6113
|
+
const views = sculptViewsOf(mm, ids);
|
|
6114
|
+
const ready = ids.every((id) => !mm.views.worldBounds([id]).isEmpty()) && driver.busy === 0 && views.every((v) => v.resolution === finest);
|
|
6115
|
+
calm = ready ? calm + 1 : 0;
|
|
6116
|
+
if (calm >= BAKE_SETTLED_CHECKS) {
|
|
6117
|
+
resolve();
|
|
6118
|
+
return;
|
|
6119
|
+
}
|
|
6120
|
+
if (performance.now() - started > BAKE_TIMEOUT_MS) {
|
|
6121
|
+
const fine = views.filter((v) => v.resolution === finest).length;
|
|
6122
|
+
reject(new Error(`The objects did not finish meshing in time for the bake (${driver.busy} requests in flight, ${fine} of ${views.length} parts at the finest level).`));
|
|
6123
|
+
return;
|
|
6124
|
+
}
|
|
6125
|
+
setTimeout(check, BAKE_POLL_MS);
|
|
6126
|
+
};
|
|
6127
|
+
setTimeout(check, BAKE_POLL_MS);
|
|
6128
|
+
});
|
|
6129
|
+
}
|
|
6130
|
+
async function bakeSprite(options) {
|
|
6131
|
+
const mm = m;
|
|
6132
|
+
if (!mm || !creation || !session) throw new Error("The viewport is not mounted.");
|
|
6133
|
+
const ids = options.objectIds.filter((id) => mm.views.views.has(id));
|
|
6134
|
+
if (!ids.length) throw new Error("There is nothing to bake.");
|
|
6135
|
+
const size = Math.round(options.size);
|
|
6136
|
+
if (!(size >= 16 && size <= 512)) throw new Error("A sprite is 16 to 512 pixels wide.");
|
|
6137
|
+
ids.forEach((id) => baking.add(id));
|
|
6138
|
+
try {
|
|
6139
|
+
const finest = mm.vctx.levels[0] ?? 36;
|
|
6140
|
+
for (const v of sculptViewsOf(mm, ids)) if (v.resolution !== finest) v.setResolution(finest);
|
|
6141
|
+
await settled(mm, ids);
|
|
6142
|
+
const c = creation;
|
|
6143
|
+
if (!c) throw new Error("The viewport is not mounted.");
|
|
6144
|
+
const { camera, orbit } = mm.stage;
|
|
6145
|
+
const saved = { position: camera.position.clone(), target: orbit.target.clone(), aspect: camera.aspect, fov: camera.fov };
|
|
6146
|
+
try {
|
|
6147
|
+
camera.aspect = 1;
|
|
6148
|
+
camera.fov = Math.min(60, Math.max(5, options.fov ?? BAKE_FOV));
|
|
6149
|
+
camera.updateProjectionMatrix();
|
|
6150
|
+
frameContain(mm.stage, mm.views.worldBounds(ids), mm.views.worldPoints(ids), withView(ids, { aspect: 1, padding: options.padding ?? 0.03, view: options.view }));
|
|
6151
|
+
camera.updateMatrixWorld();
|
|
6152
|
+
updateCameraSun(mm.stage, true);
|
|
6153
|
+
for (const v of mm.views.allSculptViews()) v.updateLocalCamera(camera.position);
|
|
6154
|
+
mm.vctx.atlas.flush();
|
|
6155
|
+
const keep = ids.map((id) => mm.views.group(id)).filter((g) => !!g);
|
|
6156
|
+
const { roles, approximated } = decodeRoles(mm.pipeline.renderSprite(size, c.palette, keep), size, size);
|
|
6157
|
+
return { width: size, height: size, roles, colors: spriteColors(c.palette), anchor: spriteAnchor(roles, size, size), approximated };
|
|
6158
|
+
} finally {
|
|
6159
|
+
camera.aspect = saved.aspect;
|
|
6160
|
+
camera.fov = saved.fov;
|
|
6161
|
+
camera.updateProjectionMatrix();
|
|
6162
|
+
camera.position.copy(saved.position);
|
|
6163
|
+
orbit.target.copy(saved.target);
|
|
6164
|
+
camera.lookAt(saved.target);
|
|
6165
|
+
orbit.update();
|
|
6166
|
+
}
|
|
6167
|
+
} finally {
|
|
6168
|
+
ids.forEach((id) => baking.delete(id));
|
|
6169
|
+
}
|
|
6170
|
+
}
|
|
5886
6171
|
function dispose() {
|
|
5887
6172
|
const mm = m;
|
|
5888
6173
|
m = null;
|
|
@@ -5918,6 +6203,7 @@ function createViewport({ evaluator }) {
|
|
|
5918
6203
|
return m?.views.contentBounds(objectId);
|
|
5919
6204
|
},
|
|
5920
6205
|
thumbnail,
|
|
6206
|
+
bakeSprite,
|
|
5921
6207
|
// Releases the canvas, GPU resources and pending meshes, but stays reusable: React StrictMode (and a
|
|
5922
6208
|
// remounted page) mounts again after dispose. The evaluator's lifetime belongs to the app.
|
|
5923
6209
|
dispose() {
|
|
@@ -5931,6 +6217,13 @@ function createViewport({ evaluator }) {
|
|
|
5931
6217
|
}
|
|
5932
6218
|
|
|
5933
6219
|
export {
|
|
6220
|
+
SPRITE_SLOTS,
|
|
6221
|
+
ROLE_TRANSPARENT,
|
|
6222
|
+
ROLE_INK,
|
|
6223
|
+
SPRITE_ROLE_COUNT,
|
|
6224
|
+
spriteRole,
|
|
6225
|
+
roleSlot,
|
|
6226
|
+
spriteColors,
|
|
5934
6227
|
createViewport
|
|
5935
6228
|
};
|
|
5936
|
-
//# sourceMappingURL=chunk-
|
|
6229
|
+
//# sourceMappingURL=chunk-5KZP6X65.js.map
|