@rslsp1/fa-app-tools 10.1.3 → 10.3.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.
@@ -0,0 +1,311 @@
1
+ // src/lib/szenen/cameraContract.ts
2
+ var CAMERA_ASPECT_RATIOS = ["1:1", "4:3", "3:4", "16:9", "9:16"];
3
+ var object = (value) => value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
4
+ function cameraAspectRatioValue(aspectRatio) {
5
+ if (!/^[1-9][0-9]*(\.[0-9]+)?:[1-9][0-9]*(\.[0-9]+)?$/.test(aspectRatio)) {
6
+ throw new Error(`Ung\xFCltiges Kamera-Seitenverh\xE4ltnis \u201E${aspectRatio}\u201C.`);
7
+ }
8
+ const [width, height] = aspectRatio.split(":").map(Number);
9
+ const ratio = width / height;
10
+ if (!Number.isFinite(width) || !Number.isFinite(height) || !Number.isFinite(ratio) || ratio <= 0) {
11
+ throw new Error(`Kamera-Seitenverh\xE4ltnis \u201E${aspectRatio}\u201C ist nicht darstellbar.`);
12
+ }
13
+ return ratio;
14
+ }
15
+ function canonical(value) {
16
+ if (Array.isArray(value)) return `[${value.map(canonical).join(",")}]`;
17
+ const record = object(value);
18
+ if (record) return `{${Object.keys(record).sort().map((key) => `${JSON.stringify(key)}:${canonical(record[key])}`).join(",")}}`;
19
+ return JSON.stringify(value) ?? "undefined";
20
+ }
21
+ function camerasOf(payload) {
22
+ const cameras = object(object(payload)?.camera_rig)?.cameras;
23
+ return Array.isArray(cameras) ? cameras : [];
24
+ }
25
+ function checkCameraAspectRatioChanges(type, payload, previousPayloads = []) {
26
+ if (type !== "scene_setup") return [];
27
+ const previous = previousPayloads.flatMap(camerasOf);
28
+ return camerasOf(payload).flatMap((value, index) => {
29
+ const camera = object(value);
30
+ if (!camera) return [];
31
+ if (typeof camera.aspect_ratio === "string") {
32
+ try {
33
+ cameraAspectRatioValue(camera.aspect_ratio);
34
+ return [];
35
+ } catch (error) {
36
+ return [{ instancePath: `/camera_rig/cameras/${index}/aspect_ratio`, keyword: "aspect_ratio", message: error.message }];
37
+ }
38
+ }
39
+ if (camera.aspect_ratio === void 0 && previous.some((old) => canonical(old) === canonical(camera))) return [];
40
+ return [{
41
+ instancePath: `/camera_rig/cameras/${index}/aspect_ratio`,
42
+ keyword: "required",
43
+ message: `Kamera \u201E${String(camera.camera_id ?? index)}\u201C wurde neu angelegt oder ge\xE4ndert. Bitte ihr Seitenverh\xE4ltnis ausdr\xFCcklich speichern.`
44
+ }];
45
+ });
46
+ }
47
+ function cameraPerspective(camera) {
48
+ if (!camera.aspect_ratio) throw new Error(`Kamera \u201E${camera.camera_id}\u201C hat kein gespeichertes Seitenverh\xE4ltnis.`);
49
+ const lens = object(object(camera.preset)?.lens);
50
+ const focal = lens?.focal_length_mm;
51
+ const sensor = lens?.sensor_width_mm;
52
+ if (typeof focal !== "number" || !Number.isFinite(focal) || focal <= 0 || typeof sensor !== "number" || !Number.isFinite(sensor) || sensor <= 0) {
53
+ throw new Error(`Kamera \u201E${camera.camera_id}\u201C hat kein aufgel\xF6stes Objektiv mit Brennweite und Sensorbreite.`);
54
+ }
55
+ if (object(camera.preset)?.projection !== "perspective") {
56
+ throw new Error(`Kamera \u201E${camera.camera_id}\u201C verwendet keine perspektivische Projektion.`);
57
+ }
58
+ const aspect = cameraAspectRatioValue(camera.aspect_ratio);
59
+ return {
60
+ aspect_ratio: camera.aspect_ratio,
61
+ aspect,
62
+ focal_length_mm: focal,
63
+ sensor_width_mm: sensor,
64
+ vertical_fov_deg: 2 * Math.atan(sensor / (2 * focal * aspect)) * 180 / Math.PI
65
+ };
66
+ }
67
+ function frozenCameraAspectRatio(input) {
68
+ const aspect = object(object(input)?.camera)?.aspect_ratio;
69
+ if (aspect === void 0) return void 0;
70
+ if (typeof aspect !== "string") throw new Error("Das eingefrorene Kamera-Seitenverh\xE4ltnis ist kein Text.");
71
+ cameraAspectRatioValue(aspect);
72
+ return aspect;
73
+ }
74
+
75
+ // src/lib/szenen/stage3d.ts
76
+ var STAGE_CATALOGUE = [
77
+ { label: "Mensch", type: "subject", dimensions: [0.55, 0.35, 1.75], primitive: "humanoid_proxy", pose_ref: "standing" },
78
+ { label: "Stuhl", type: "object", dimensions: [0.5, 0.5, 0.9], primitive: "furniture_proxy", variant: "chair" },
79
+ { label: "Tisch", type: "surface", dimensions: [1.2, 0.7, 0.75], primitive: "furniture_proxy", variant: "table" },
80
+ { label: "Lampe", type: "light", dimensions: [0.35, 0.35, 1.5], primitive: "furniture_proxy", variant: "floor_lamp" },
81
+ { label: "Raum", type: "object", dimensions: [5, 4, 2.8], primitive: "room_shell" }
82
+ ];
83
+ var stageColor = (type) => ({ subject: "#df995f", light: "#ffe084", surface: "#65aabc", object: "#b998d9" })[type] ?? "#9bafbc";
84
+ var PREVIEW_COLOR_PATTERN = /^#[0-9a-f]{6}$/;
85
+ var PREVIEW_COLOR_MIN_DISTANCE = 75;
86
+ function normalizePreviewColor(value) {
87
+ if (typeof value !== "string") return null;
88
+ const hex = value.trim().toLowerCase();
89
+ return PREVIEW_COLOR_PATTERN.test(hex) ? hex : null;
90
+ }
91
+ function rgb(hex) {
92
+ return [1, 3, 5].map((i) => parseInt(hex.slice(i, i + 2), 16));
93
+ }
94
+ function previewColorDistance(a, b) {
95
+ const [r1, g1, b1] = rgb(a), [r2, g2, b2] = rgb(b);
96
+ const r = (r1 + r2) / 2, dr = r1 - r2, dg = g1 - g2, db = b1 - b2;
97
+ return Math.sqrt((2 + r / 256) * dr * dr + 4 * dg * dg + (2 + (255 - r) / 256) * db * db);
98
+ }
99
+ var HUE_NAMES = [
100
+ [15, "red"],
101
+ [40, "orange"],
102
+ [70, "yellow"],
103
+ [160, "green"],
104
+ [200, "cyan"],
105
+ [255, "blue"],
106
+ [290, "purple"],
107
+ [345, "pink"],
108
+ [360, "red"]
109
+ ];
110
+ function previewColorName(hex) {
111
+ const [r, g, b] = rgb(hex).map((c) => c / 255);
112
+ const max = Math.max(r, g, b), min = Math.min(r, g, b), d = max - min;
113
+ if (d < 0.08) return `${max > 0.8 ? "white" : max < 0.25 ? "black" : "grey"} (${hex})`;
114
+ const hue = max === r ? ((g - b) / d + 6) % 6 : max === g ? (b - r) / d + 2 : (r - g) / d + 4;
115
+ const grad = hue * 60;
116
+ const l = (max + min) / 2;
117
+ const stufe = l < 0.3 ? "dark " : l > 0.82 ? "light " : "";
118
+ return `${stufe}${HUE_NAMES.find(([grenze]) => grad < grenze)[1]} (${hex})`;
119
+ }
120
+ function fallbackCandidate(n) {
121
+ const hue = (n * 137.508 + 25) % 360;
122
+ const channel = (k0) => {
123
+ const k = (k0 + hue / 30) % 12;
124
+ return Math.round(255 * (0.62 - 0.29 * Math.max(-1, Math.min(k - 3, 9 - k, 1)))).toString(16).padStart(2, "0");
125
+ };
126
+ return `#${channel(0)}${channel(8)}${channel(4)}`;
127
+ }
128
+ function stageColors(nodes) {
129
+ const result = /* @__PURE__ */ Object.create(null);
130
+ const used = [];
131
+ const names = { subject: "orange", light: "yellow", surface: "cyan", object: "purple" };
132
+ for (const node of nodes) {
133
+ const hex = normalizePreviewColor(node.proxy_3d?.preview_color);
134
+ if (hex) {
135
+ used.push(hex);
136
+ result[node.node_id] = { hex, name: previewColorName(hex), explicit: true };
137
+ }
138
+ }
139
+ const nah = (hex) => used.some((u) => previewColorDistance(u, hex) < PREVIEW_COLOR_MIN_DISTANCE);
140
+ let next = 0;
141
+ for (const node of [...nodes].filter((n) => !result[n.node_id]).sort((a, b) => a.node_id.localeCompare(b.node_id))) {
142
+ let hex = stageColor(node.type), name = names[node.type] ?? "blue grey";
143
+ for (let tries = 0; nah(hex) && tries < 64; tries++) {
144
+ hex = fallbackCandidate(next++);
145
+ name = previewColorName(hex);
146
+ }
147
+ used.push(hex);
148
+ result[node.node_id] = { hex, name, explicit: false };
149
+ }
150
+ return result;
151
+ }
152
+ function previewColorLegibility(hex) {
153
+ const [r, g, b] = rgb(hex).map((c) => c / 255);
154
+ const l = (Math.max(r, g, b) + Math.min(r, g, b)) / 2;
155
+ return l < 0.3 ? "too_dark" : l > 0.85 ? "too_light" : null;
156
+ }
157
+ function previewColorConflicts(nodes) {
158
+ const colors = stageColors(nodes);
159
+ const ids = Object.keys(colors).sort();
160
+ const out = [];
161
+ for (let i = 0; i < ids.length; i++) for (let j = i + 1; j < ids.length; j++) {
162
+ const a = colors[ids[i]].hex, b = colors[ids[j]].hex, distance = previewColorDistance(a, b);
163
+ if (distance < PREVIEW_COLOR_MIN_DISTANCE) out.push({ node_ids: [ids[i], ids[j]], hexes: [a, b], distance: Math.round(distance), identical: a === b });
164
+ }
165
+ return out;
166
+ }
167
+ function distinctPreviewColor(type, taken) {
168
+ const nah = (hex2) => taken.some((u) => previewColorDistance(u, hex2) < PREVIEW_COLOR_MIN_DISTANCE);
169
+ let hex = stageColor(type);
170
+ for (let n = 0; nah(hex) && n < 64; n++) hex = fallbackCandidate(n);
171
+ return hex;
172
+ }
173
+ var stageTypeLabel = (type) => ({ subject: "Person", light: "Licht", surface: "Fl\xE4che", object: "Objekt" })[type] ?? type;
174
+ function vector3(value, label) {
175
+ if (!Array.isArray(value) || value.length !== 3 || value.some((n) => typeof n !== "number" || !Number.isFinite(n))) {
176
+ throw new Error(`${label}: drei endliche Koordinaten erforderlich.`);
177
+ }
178
+ return [...value];
179
+ }
180
+ function dragPosition(position, point, plane) {
181
+ const p = vector3(position, "Position");
182
+ const q = vector3(point, "Drag");
183
+ return plane === "top" ? [q[0], q[1], p[2]] : plane === "side" ? [q[0], p[1], q[2]] : p;
184
+ }
185
+ function stageNode(node) {
186
+ if (!node.placement) throw new Error(`\u201E${node.node_id}\u201C hat keine Weltplatzierung.`);
187
+ if (!node.proxy_3d) throw new Error(`\u201E${node.node_id}\u201C hat keine Proxy-Geometrie. Bitte Ma\xDFe festlegen.`);
188
+ const dimensions = vector3(node.proxy_3d.dimensions, `Ma\xDFe von ${node.node_id}`);
189
+ if (dimensions.some((d) => d <= 0)) throw new Error(`\u201E${node.node_id}\u201C braucht positive Proxy-Ma\xDFe.`);
190
+ return {
191
+ node_id: node.node_id,
192
+ type: node.type,
193
+ primitive: node.proxy_3d.primitive,
194
+ dimensions,
195
+ variant: node.proxy_3d.variant,
196
+ pose_ref: node.proxy_3d.pose_ref,
197
+ morph: node.proxy_3d.morph,
198
+ position: vector3(node.placement.position, `Position von ${node.node_id}`),
199
+ rotation: vector3(node.placement.rotation_euler_deg ?? [0, 0, 0], "Rotation"),
200
+ scale: vector3(node.placement.scale ?? [1, 1, 1], "Skalierung"),
201
+ anchors: node.anchors
202
+ };
203
+ }
204
+ function stageFrame(input) {
205
+ if (input.capabilities.can_place !== "yes") throw new Error("Die Szene ben\xF6tigt Meterma\xDFe und ein Scene Setup in Z-up-Koordinaten.");
206
+ const visible = input.scene.nodes.filter((n) => !n.hidden);
207
+ const colors = stageColors(visible);
208
+ const nodes = visible.map((n) => ({ ...stageNode(n), preview_color: { hex: colors[n.node_id].hex, name: colors[n.node_id].name } }));
209
+ if (!input.camera) throw new Error("Bitte eine Kamera ausw\xE4hlen.");
210
+ const optical = cameraPerspective(input.camera);
211
+ const placement = input.camera.placement;
212
+ if (placement?.mode !== "absolute") throw new Error("Diese Kamera verwendet eine Pattern-Platzierung. F\xFCr den MVP bitte eine explizite Weltposition speichern.");
213
+ const position = vector3(placement.position, "Kameraposition");
214
+ const orientation = input.camera.orientation;
215
+ if (orientation?.mode !== "explicit" && orientation?.mode !== "look_at") throw new Error("Bitte eine explizite Kameraausrichtung oder ein Blickziel festlegen.");
216
+ if (orientation.mode === "explicit") vector3(orientation.rotation_euler_deg, "Kameraausrichtung");
217
+ return { nodes, camera: { ...input.camera, ...optical, position, orientation } };
218
+ }
219
+ function planeAxes(plane) {
220
+ return plane === "top" ? [0, 1] : [0, 2];
221
+ }
222
+ function snapPosition(position, plane, step) {
223
+ const p = vector3(position, "Position");
224
+ for (const axis of planeAxes(plane)) p[axis] = Math.round(p[axis] / step) * step;
225
+ return p.map((v) => Number(v.toFixed(6)));
226
+ }
227
+ function nudgePosition(position, plane, right, up) {
228
+ const p = vector3(position, "Position");
229
+ const [u, v] = planeAxes(plane);
230
+ p[u] = Number((p[u] + right).toFixed(6));
231
+ p[v] = Number((p[v] + up).toFixed(6));
232
+ return p;
233
+ }
234
+ function fitPlaneView(points, plane, width, height) {
235
+ if (!points.length || width <= 0 || height <= 0) return { center: [0, 0], zoom: 60 };
236
+ const [u, v] = planeAxes(plane);
237
+ const us = points.map((p) => p[u]), vs = points.map((p) => p[v]);
238
+ const [minU, maxU, minV, maxV] = [Math.min(...us), Math.max(...us), Math.min(...vs), Math.max(...vs)];
239
+ const spanU = Math.max(maxU - minU, 2), spanV = Math.max(maxV - minV, 2);
240
+ const zoom = Math.min(width / (spanU * 1.25), height / (spanV * 1.25));
241
+ return { center: [(minU + maxU) / 2, (minV + maxV) / 2], zoom: clampZoom(zoom) };
242
+ }
243
+ var clampZoom = (zoom) => Math.min(800, Math.max(8, zoom));
244
+ function screenToPlane(view, dx, dy) {
245
+ return [view.center[0] + dx / view.zoom, view.center[1] - dy / view.zoom];
246
+ }
247
+ function zoomPlaneView(view, factor, dx, dy) {
248
+ const zoom = clampZoom(view.zoom * factor);
249
+ const [u, v] = screenToPlane(view, dx, dy);
250
+ return { zoom, center: [u - dx / zoom, v + dy / zoom] };
251
+ }
252
+ function gridStep(zoom, minPx = 14) {
253
+ return [0.05, 0.1, 0.25, 0.5, 1, 2, 5, 10, 25, 50].find((step) => step * zoom >= minPx) ?? 100;
254
+ }
255
+ function withStagePositions(input, nodes, camera) {
256
+ if (!nodes.size && !camera) return input;
257
+ const next = {
258
+ ...input,
259
+ scene: { ...input.scene, nodes: input.scene.nodes.map((n) => nodes.has(n.node_id) && n.placement ? { ...n, placement: { ...n.placement, position: nodes.get(n.node_id) } } : n) }
260
+ };
261
+ if (camera && input.camera) next.camera = { ...input.camera, placement: { ...input.camera.placement, position: camera } };
262
+ return next;
263
+ }
264
+ var roundMeters = (value) => Math.round(value * 1e3) / 1e3;
265
+ function facingRotationZ(position, target) {
266
+ const dx = target[0] - position[0], dy = target[1] - position[1];
267
+ if (!Number.isFinite(dx) || !Number.isFinite(dy) || Math.hypot(dx, dy) < 1e-8) throw new Error("F\xFCr die Ausrichtung m\xFCssen Quelle und Ziel unterschiedliche Bodenpositionen haben.");
268
+ return Math.round(Math.atan2(dx, -dy) * 180 / Math.PI * 1e3) / 1e3;
269
+ }
270
+ var snapRotationZ = (degrees) => Math.round(degrees / 15) * 15;
271
+ function stagePreviewResolution(aspectRatio) {
272
+ const [rw, rh] = aspectRatio.split(":").map(Number), scale = 768 / Math.max(rw, rh);
273
+ const width = Math.round(rw * scale), height = Math.round(rh * scale);
274
+ if (!Number.isFinite(width) || !Number.isFinite(height) || width < 1 || height < 1 || Math.abs(width / height - rw / rh) > 1e-9) throw new Error("Dieses Kameraformat l\xE4sst sich mit der Preview-Aufl\xF6sung nicht exakt abbilden. Bitte ein Format-Preset w\xE4hlen.");
275
+ return { width, height };
276
+ }
277
+
278
+ export {
279
+ CAMERA_ASPECT_RATIOS,
280
+ cameraAspectRatioValue,
281
+ checkCameraAspectRatioChanges,
282
+ cameraPerspective,
283
+ frozenCameraAspectRatio,
284
+ STAGE_CATALOGUE,
285
+ stageColor,
286
+ PREVIEW_COLOR_MIN_DISTANCE,
287
+ normalizePreviewColor,
288
+ previewColorDistance,
289
+ stageColors,
290
+ previewColorLegibility,
291
+ previewColorConflicts,
292
+ distinctPreviewColor,
293
+ stageTypeLabel,
294
+ vector3,
295
+ dragPosition,
296
+ stageNode,
297
+ stageFrame,
298
+ planeAxes,
299
+ snapPosition,
300
+ nudgePosition,
301
+ fitPlaneView,
302
+ clampZoom,
303
+ screenToPlane,
304
+ zoomPlaneView,
305
+ gridStep,
306
+ withStagePositions,
307
+ roundMeters,
308
+ facingRotationZ,
309
+ snapRotationZ,
310
+ stagePreviewResolution
311
+ };