@rslsp1/fa-app-tools 10.3.2 → 10.3.4

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.
@@ -74,33 +74,102 @@ function frozenCameraAspectRatio(input) {
74
74
 
75
75
  // src/lib/szenen/stage3d.ts
76
76
  var STAGE_CATALOGUE = [
77
- { label: "Mensch", type: "subject", dimensions: [0.55, 0.35, 1.75] },
78
- { label: "Stuhl", type: "object", dimensions: [0.5, 0.5, 0.9] },
79
- { label: "Tisch", type: "surface", dimensions: [1.2, 0.7, 0.75] },
80
- { label: "Lampe", type: "light", dimensions: [0.35, 0.35, 1.5] }
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" }
81
82
  ];
82
83
  var stageColor = (type) => _nullishCoalesce(({ 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
+ }
83
128
  function stageColors(nodes) {
84
129
  const result = /* @__PURE__ */ Object.create(null);
85
- const used = /* @__PURE__ */ new Set();
130
+ const used = [];
86
131
  const names = { subject: "orange", light: "yellow", surface: "cyan", object: "purple" };
132
+ for (const node of nodes) {
133
+ const hex = normalizePreviewColor(_optionalChain([node, 'access', _15 => _15.proxy_3d, 'optionalAccess', _16 => _16.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);
87
140
  let next = 0;
88
- for (const node of [...nodes].sort((a, b) => a.node_id.localeCompare(b.node_id))) {
141
+ for (const node of [...nodes].filter((n) => !result[n.node_id]).sort((a, b) => a.node_id.localeCompare(b.node_id))) {
89
142
  let hex = stageColor(node.type), name = _nullishCoalesce(names[node.type], () => ( "blue grey"));
90
- while (used.has(hex)) {
91
- const hue = (next++ * 137.508 + 25) % 360;
92
- const channel = (n) => {
93
- const k = (n + hue / 30) % 12;
94
- return Math.round(255 * (0.62 - 0.29 * Math.max(-1, Math.min(k - 3, 9 - k, 1)))).toString(16).padStart(2, "0");
95
- };
96
- hex = `#${channel(0)}${channel(8)}${channel(4)}`;
97
- name = `${["red", "orange", "yellow", "green", "cyan", "blue", "purple", "pink"][Math.round(hue / 45) % 8]} (${hex})`;
143
+ for (let tries = 0; nah(hex) && tries < 64; tries++) {
144
+ hex = fallbackCandidate(next++);
145
+ name = previewColorName(hex);
98
146
  }
99
- used.add(hex);
100
- result[node.node_id] = { hex, name };
147
+ used.push(hex);
148
+ result[node.node_id] = { hex, name, explicit: false };
101
149
  }
102
150
  return result;
103
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
+ }
104
173
  var stageTypeLabel = (type) => _nullishCoalesce(({ subject: "Person", light: "Licht", surface: "Fl\xE4che", object: "Objekt" })[type], () => ( type));
105
174
  function vector3(value, label) {
106
175
  if (!Array.isArray(value) || value.length !== 3 || value.some((n) => typeof n !== "number" || !Number.isFinite(n))) {
@@ -123,6 +192,9 @@ function stageNode(node) {
123
192
  type: node.type,
124
193
  primitive: node.proxy_3d.primitive,
125
194
  dimensions,
195
+ variant: node.proxy_3d.variant,
196
+ pose_ref: node.proxy_3d.pose_ref,
197
+ morph: node.proxy_3d.morph,
126
198
  position: vector3(node.placement.position, `Position von ${node.node_id}`),
127
199
  rotation: vector3(_nullishCoalesce(node.placement.rotation_euler_deg, () => ( [0, 0, 0])), "Rotation"),
128
200
  scale: vector3(_nullishCoalesce(node.placement.scale, () => ( [1, 1, 1])), "Skalierung"),
@@ -133,14 +205,14 @@ function stageFrame(input) {
133
205
  if (input.capabilities.can_place !== "yes") throw new Error("Die Szene ben\xF6tigt Meterma\xDFe und ein Scene Setup in Z-up-Koordinaten.");
134
206
  const visible = input.scene.nodes.filter((n) => !n.hidden);
135
207
  const colors = stageColors(visible);
136
- const nodes = visible.map((n) => ({ ...stageNode(n), preview_color: colors[n.node_id] }));
208
+ const nodes = visible.map((n) => ({ ...stageNode(n), preview_color: { hex: colors[n.node_id].hex, name: colors[n.node_id].name } }));
137
209
  if (!input.camera) throw new Error("Bitte eine Kamera ausw\xE4hlen.");
138
210
  const optical = cameraPerspective(input.camera);
139
211
  const placement = input.camera.placement;
140
- if (_optionalChain([placement, 'optionalAccess', _15 => _15.mode]) !== "absolute") throw new Error("Diese Kamera verwendet eine Pattern-Platzierung. F\xFCr den MVP bitte eine explizite Weltposition speichern.");
212
+ if (_optionalChain([placement, 'optionalAccess', _17 => _17.mode]) !== "absolute") throw new Error("Diese Kamera verwendet eine Pattern-Platzierung. F\xFCr den MVP bitte eine explizite Weltposition speichern.");
141
213
  const position = vector3(placement.position, "Kameraposition");
142
214
  const orientation = input.camera.orientation;
143
- if (_optionalChain([orientation, 'optionalAccess', _16 => _16.mode]) !== "explicit" && _optionalChain([orientation, 'optionalAccess', _17 => _17.mode]) !== "look_at") throw new Error("Bitte eine explizite Kameraausrichtung oder ein Blickziel festlegen.");
215
+ if (_optionalChain([orientation, 'optionalAccess', _18 => _18.mode]) !== "explicit" && _optionalChain([orientation, 'optionalAccess', _19 => _19.mode]) !== "look_at") throw new Error("Bitte eine explizite Kameraausrichtung oder ein Blickziel festlegen.");
144
216
  if (orientation.mode === "explicit") vector3(orientation.rotation_euler_deg, "Kameraausrichtung");
145
217
  return { nodes, camera: { ...input.camera, ...optical, position, orientation } };
146
218
  }
@@ -190,6 +262,27 @@ function withStagePositions(input, nodes, camera) {
190
262
  return next;
191
263
  }
192
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
+
279
+
280
+
281
+
282
+
283
+
284
+
285
+
193
286
 
194
287
 
195
288
 
@@ -215,4 +308,4 @@ var roundMeters = (value) => Math.round(value * 1e3) / 1e3;
215
308
 
216
309
 
217
310
 
218
- exports.CAMERA_ASPECT_RATIOS = CAMERA_ASPECT_RATIOS; exports.cameraAspectRatioValue = cameraAspectRatioValue; exports.checkCameraAspectRatioChanges = checkCameraAspectRatioChanges; exports.cameraPerspective = cameraPerspective; exports.frozenCameraAspectRatio = frozenCameraAspectRatio; exports.STAGE_CATALOGUE = STAGE_CATALOGUE; exports.stageColor = stageColor; exports.stageColors = stageColors; exports.stageTypeLabel = stageTypeLabel; exports.vector3 = vector3; exports.dragPosition = dragPosition; exports.stageNode = stageNode; exports.stageFrame = stageFrame; exports.planeAxes = planeAxes; exports.snapPosition = snapPosition; exports.nudgePosition = nudgePosition; exports.fitPlaneView = fitPlaneView; exports.clampZoom = clampZoom; exports.screenToPlane = screenToPlane; exports.zoomPlaneView = zoomPlaneView; exports.gridStep = gridStep; exports.withStagePositions = withStagePositions; exports.roundMeters = roundMeters;
311
+ exports.CAMERA_ASPECT_RATIOS = CAMERA_ASPECT_RATIOS; exports.cameraAspectRatioValue = cameraAspectRatioValue; exports.checkCameraAspectRatioChanges = checkCameraAspectRatioChanges; exports.cameraPerspective = cameraPerspective; exports.frozenCameraAspectRatio = frozenCameraAspectRatio; exports.STAGE_CATALOGUE = STAGE_CATALOGUE; exports.stageColor = stageColor; exports.PREVIEW_COLOR_MIN_DISTANCE = PREVIEW_COLOR_MIN_DISTANCE; exports.normalizePreviewColor = normalizePreviewColor; exports.previewColorDistance = previewColorDistance; exports.stageColors = stageColors; exports.previewColorLegibility = previewColorLegibility; exports.previewColorConflicts = previewColorConflicts; exports.distinctPreviewColor = distinctPreviewColor; exports.stageTypeLabel = stageTypeLabel; exports.vector3 = vector3; exports.dragPosition = dragPosition; exports.stageNode = stageNode; exports.stageFrame = stageFrame; exports.planeAxes = planeAxes; exports.snapPosition = snapPosition; exports.nudgePosition = nudgePosition; exports.fitPlaneView = fitPlaneView; exports.clampZoom = clampZoom; exports.screenToPlane = screenToPlane; exports.zoomPlaneView = zoomPlaneView; exports.gridStep = gridStep; exports.withStagePositions = withStagePositions; exports.roundMeters = roundMeters; exports.facingRotationZ = facingRotationZ; exports.snapRotationZ = snapRotationZ; exports.stagePreviewResolution = stagePreviewResolution;
@@ -74,33 +74,102 @@ function frozenCameraAspectRatio(input) {
74
74
 
75
75
  // src/lib/szenen/stage3d.ts
76
76
  var STAGE_CATALOGUE = [
77
- { label: "Mensch", type: "subject", dimensions: [0.55, 0.35, 1.75] },
78
- { label: "Stuhl", type: "object", dimensions: [0.5, 0.5, 0.9] },
79
- { label: "Tisch", type: "surface", dimensions: [1.2, 0.7, 0.75] },
80
- { label: "Lampe", type: "light", dimensions: [0.35, 0.35, 1.5] }
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" }
81
82
  ];
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
+ }
83
128
  function stageColors(nodes) {
84
129
  const result = /* @__PURE__ */ Object.create(null);
85
- const used = /* @__PURE__ */ new Set();
130
+ const used = [];
86
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);
87
140
  let next = 0;
88
- for (const node of [...nodes].sort((a, b) => a.node_id.localeCompare(b.node_id))) {
141
+ for (const node of [...nodes].filter((n) => !result[n.node_id]).sort((a, b) => a.node_id.localeCompare(b.node_id))) {
89
142
  let hex = stageColor(node.type), name = names[node.type] ?? "blue grey";
90
- while (used.has(hex)) {
91
- const hue = (next++ * 137.508 + 25) % 360;
92
- const channel = (n) => {
93
- const k = (n + hue / 30) % 12;
94
- return Math.round(255 * (0.62 - 0.29 * Math.max(-1, Math.min(k - 3, 9 - k, 1)))).toString(16).padStart(2, "0");
95
- };
96
- hex = `#${channel(0)}${channel(8)}${channel(4)}`;
97
- name = `${["red", "orange", "yellow", "green", "cyan", "blue", "purple", "pink"][Math.round(hue / 45) % 8]} (${hex})`;
143
+ for (let tries = 0; nah(hex) && tries < 64; tries++) {
144
+ hex = fallbackCandidate(next++);
145
+ name = previewColorName(hex);
98
146
  }
99
- used.add(hex);
100
- result[node.node_id] = { hex, name };
147
+ used.push(hex);
148
+ result[node.node_id] = { hex, name, explicit: false };
101
149
  }
102
150
  return result;
103
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
+ }
104
173
  var stageTypeLabel = (type) => ({ subject: "Person", light: "Licht", surface: "Fl\xE4che", object: "Objekt" })[type] ?? type;
105
174
  function vector3(value, label) {
106
175
  if (!Array.isArray(value) || value.length !== 3 || value.some((n) => typeof n !== "number" || !Number.isFinite(n))) {
@@ -123,6 +192,9 @@ function stageNode(node) {
123
192
  type: node.type,
124
193
  primitive: node.proxy_3d.primitive,
125
194
  dimensions,
195
+ variant: node.proxy_3d.variant,
196
+ pose_ref: node.proxy_3d.pose_ref,
197
+ morph: node.proxy_3d.morph,
126
198
  position: vector3(node.placement.position, `Position von ${node.node_id}`),
127
199
  rotation: vector3(node.placement.rotation_euler_deg ?? [0, 0, 0], "Rotation"),
128
200
  scale: vector3(node.placement.scale ?? [1, 1, 1], "Skalierung"),
@@ -133,7 +205,7 @@ function stageFrame(input) {
133
205
  if (input.capabilities.can_place !== "yes") throw new Error("Die Szene ben\xF6tigt Meterma\xDFe und ein Scene Setup in Z-up-Koordinaten.");
134
206
  const visible = input.scene.nodes.filter((n) => !n.hidden);
135
207
  const colors = stageColors(visible);
136
- const nodes = visible.map((n) => ({ ...stageNode(n), preview_color: colors[n.node_id] }));
208
+ const nodes = visible.map((n) => ({ ...stageNode(n), preview_color: { hex: colors[n.node_id].hex, name: colors[n.node_id].name } }));
137
209
  if (!input.camera) throw new Error("Bitte eine Kamera ausw\xE4hlen.");
138
210
  const optical = cameraPerspective(input.camera);
139
211
  const placement = input.camera.placement;
@@ -190,6 +262,18 @@ function withStagePositions(input, nodes, camera) {
190
262
  return next;
191
263
  }
192
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
+ }
193
277
 
194
278
  export {
195
279
  CAMERA_ASPECT_RATIOS,
@@ -199,7 +283,13 @@ export {
199
283
  frozenCameraAspectRatio,
200
284
  STAGE_CATALOGUE,
201
285
  stageColor,
286
+ PREVIEW_COLOR_MIN_DISTANCE,
287
+ normalizePreviewColor,
288
+ previewColorDistance,
202
289
  stageColors,
290
+ previewColorLegibility,
291
+ previewColorConflicts,
292
+ distinctPreviewColor,
203
293
  stageTypeLabel,
204
294
  vector3,
205
295
  dragPosition,
@@ -214,5 +304,8 @@ export {
214
304
  zoomPlaneView,
215
305
  gridStep,
216
306
  withStagePositions,
217
- roundMeters
307
+ roundMeters,
308
+ facingRotationZ,
309
+ snapRotationZ,
310
+ stagePreviewResolution
218
311
  };