@fonsecabarreto/genesis-gl-react 0.1.1 → 0.1.31

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,394 @@
1
+ import {
2
+ useCameraControls,
3
+ useCameraMouseDrag,
4
+ useGenesisGL,
5
+ useRenderer,
6
+ useWorldToScreen
7
+ } from "./chunk-MINHVZ2M.js";
8
+
9
+ // src/components/GenesisGLCanvas.tsx
10
+ import React, { useRef, useCallback } from "react";
11
+ import { jsx } from "react/jsx-runtime";
12
+ var GenesisGLCanvas = React.forwardRef(
13
+ ({
14
+ onReady,
15
+ onFrame,
16
+ width,
17
+ height,
18
+ initialYaw,
19
+ initialPitch,
20
+ initialZoom,
21
+ style,
22
+ className
23
+ }, ref) => {
24
+ const canvasRef = useRef(null);
25
+ const setRef = useCallback(
26
+ (el) => {
27
+ canvasRef.current = el;
28
+ if (typeof ref === "function") ref(el);
29
+ else if (ref) ref.current = el;
30
+ },
31
+ [ref]
32
+ );
33
+ const onFrameRef = useRef(onFrame);
34
+ onFrameRef.current = onFrame;
35
+ const genesisContext = useGenesisGL({
36
+ canvasRef,
37
+ width,
38
+ height,
39
+ initialYaw,
40
+ initialPitch,
41
+ initialZoom,
42
+ onReady
43
+ });
44
+ const genesisContextRef = useRef(genesisContext);
45
+ genesisContextRef.current = genesisContext;
46
+ const stableOnFrame = useCallback((time) => {
47
+ const { renderer, scene } = genesisContextRef.current;
48
+ if (renderer && scene) {
49
+ renderer.render(scene);
50
+ onFrameRef.current?.(time);
51
+ }
52
+ }, []);
53
+ useRenderer({ renderer: genesisContext.renderer, onFrame: stableOnFrame });
54
+ useCameraMouseDrag(genesisContext, canvasRef);
55
+ return /* @__PURE__ */ jsx(
56
+ "canvas",
57
+ {
58
+ ref: setRef,
59
+ style: { display: "block", width: "100%", height: "100%", ...style },
60
+ className
61
+ }
62
+ );
63
+ }
64
+ );
65
+ GenesisGLCanvas.displayName = "GenesisGLCanvas";
66
+
67
+ // src/components/RotationControls.tsx
68
+ import { useState } from "react";
69
+ import { jsx as jsx2, jsxs } from "react/jsx-runtime";
70
+ var STEP = 0.05;
71
+ function IconBtn({
72
+ children,
73
+ title,
74
+ onClick,
75
+ color
76
+ }) {
77
+ const [active, setActive] = useState(false);
78
+ const [hover, setHover] = useState(false);
79
+ return /* @__PURE__ */ jsx2(
80
+ "button",
81
+ {
82
+ title,
83
+ onClick,
84
+ onMouseEnter: () => setHover(true),
85
+ onMouseLeave: () => {
86
+ setHover(false);
87
+ setActive(false);
88
+ },
89
+ onMouseDown: () => setActive(true),
90
+ onMouseUp: () => setActive(false),
91
+ style: {
92
+ display: "flex",
93
+ alignItems: "center",
94
+ justifyContent: "center",
95
+ width: "22px",
96
+ height: "22px",
97
+ border: `1px solid ${hover ? color + "88" : color + "33"}`,
98
+ borderRadius: "5px",
99
+ background: active ? color + "30" : hover ? color + "18" : color + "0c",
100
+ color: hover ? color : color + "aa",
101
+ cursor: "pointer",
102
+ fontSize: "11px",
103
+ fontWeight: 700,
104
+ lineHeight: 1,
105
+ padding: 0,
106
+ transition: "all 0.12s",
107
+ transform: active ? "scale(0.88)" : "scale(1)",
108
+ flexShrink: 0
109
+ },
110
+ children
111
+ }
112
+ );
113
+ }
114
+ function AxisPill({
115
+ label,
116
+ color,
117
+ value,
118
+ onDec,
119
+ onInc,
120
+ onReset
121
+ }) {
122
+ const deg = (value * 180 / Math.PI).toFixed(1);
123
+ const normalized = (value % (Math.PI * 2) + Math.PI * 2) % (Math.PI * 2);
124
+ const pct = normalized / (Math.PI * 2) * 100;
125
+ return /* @__PURE__ */ jsxs(
126
+ "div",
127
+ {
128
+ style: {
129
+ display: "flex",
130
+ alignItems: "center",
131
+ gap: "5px",
132
+ background: color + "0a",
133
+ border: `1px solid ${color}22`,
134
+ borderRadius: "8px",
135
+ padding: "5px 7px",
136
+ flex: 1,
137
+ minWidth: 0
138
+ },
139
+ children: [
140
+ /* @__PURE__ */ jsxs("div", { style: { position: "relative", width: "18px", height: "18px", flexShrink: 0 }, children: [
141
+ /* @__PURE__ */ jsxs("svg", { width: "18", height: "18", viewBox: "0 0 18 18", children: [
142
+ /* @__PURE__ */ jsx2("circle", { cx: "9", cy: "9", r: "7", fill: "none", stroke: color + "22", strokeWidth: "2" }),
143
+ /* @__PURE__ */ jsx2(
144
+ "circle",
145
+ {
146
+ cx: "9",
147
+ cy: "9",
148
+ r: "7",
149
+ fill: "none",
150
+ stroke: color,
151
+ strokeWidth: "2",
152
+ strokeDasharray: `${pct / 100 * 44} 44`,
153
+ strokeLinecap: "round",
154
+ transform: "rotate(-90 9 9)",
155
+ style: { transition: "stroke-dasharray 0.1s ease" }
156
+ }
157
+ )
158
+ ] }),
159
+ /* @__PURE__ */ jsx2(
160
+ "span",
161
+ {
162
+ style: {
163
+ position: "absolute",
164
+ inset: 0,
165
+ display: "flex",
166
+ alignItems: "center",
167
+ justifyContent: "center",
168
+ fontSize: "7px",
169
+ fontWeight: 800,
170
+ color,
171
+ letterSpacing: "-0.02em"
172
+ },
173
+ children: label
174
+ }
175
+ )
176
+ ] }),
177
+ /* @__PURE__ */ jsxs(
178
+ "span",
179
+ {
180
+ style: {
181
+ fontSize: "10px",
182
+ color: color + "cc",
183
+ fontVariantNumeric: "tabular-nums",
184
+ width: "36px",
185
+ textAlign: "right",
186
+ flexShrink: 0
187
+ },
188
+ children: [
189
+ deg,
190
+ "\xB0"
191
+ ]
192
+ }
193
+ ),
194
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: "3px", marginLeft: "2px" }, children: [
195
+ /* @__PURE__ */ jsx2(IconBtn, { color, title: `\u2212${label}`, onClick: onDec, children: "\u2212" }),
196
+ /* @__PURE__ */ jsx2(IconBtn, { color, title: `+${label}`, onClick: onInc, children: "+" }),
197
+ /* @__PURE__ */ jsx2(IconBtn, { color: color + "88", title: "Reset", onClick: onReset, children: "\u21BA" })
198
+ ] })
199
+ ]
200
+ }
201
+ );
202
+ }
203
+ function RotationControls({ context, style }) {
204
+ const { camera, rotate, setYaw, setPitch, reset } = useCameraControls(context);
205
+ return /* @__PURE__ */ jsxs(
206
+ "div",
207
+ {
208
+ style: {
209
+ display: "flex",
210
+ alignItems: "center",
211
+ gap: "6px",
212
+ background: "rgba(8,8,12,0.78)",
213
+ border: "1px solid rgba(255,255,255,0.07)",
214
+ borderRadius: "12px",
215
+ padding: "6px 8px",
216
+ backdropFilter: "blur(16px)",
217
+ boxShadow: "0 4px 24px rgba(0,0,0,0.5)",
218
+ fontFamily: '"Inter", system-ui, monospace',
219
+ ...style
220
+ },
221
+ children: [
222
+ /* @__PURE__ */ jsx2(
223
+ "div",
224
+ {
225
+ title: context ? "Camera ready" : "Waiting for context\u2026",
226
+ style: {
227
+ width: "6px",
228
+ height: "6px",
229
+ borderRadius: "50%",
230
+ background: context ? "#c3e88d" : "#555",
231
+ boxShadow: context ? "0 0 6px #c3e88daa" : "none",
232
+ flexShrink: 0,
233
+ transition: "all 0.3s"
234
+ }
235
+ }
236
+ ),
237
+ /* @__PURE__ */ jsx2(
238
+ AxisPill,
239
+ {
240
+ label: "Yaw",
241
+ color: "#ff5370",
242
+ value: camera.yaw,
243
+ onDec: () => rotate(-STEP, 0),
244
+ onInc: () => rotate(STEP, 0),
245
+ onReset: () => setYaw(0)
246
+ }
247
+ ),
248
+ /* @__PURE__ */ jsx2(
249
+ AxisPill,
250
+ {
251
+ label: "Pitch",
252
+ color: "#82aaff",
253
+ value: camera.pitch,
254
+ onDec: () => rotate(0, -STEP),
255
+ onInc: () => rotate(0, STEP),
256
+ onReset: () => setPitch(0)
257
+ }
258
+ ),
259
+ /* @__PURE__ */ jsx2(IconBtn, { color: "#ffffff", title: "Reset all", onClick: reset, children: "\u2298" })
260
+ ]
261
+ }
262
+ );
263
+ }
264
+
265
+ // src/components/FaceLabel.tsx
266
+ import { jsx as jsx3 } from "react/jsx-runtime";
267
+ function transformLocalToWorld(m, x, y, z) {
268
+ return {
269
+ x: m[0] * x + m[4] * y + m[8] * z + m[12],
270
+ y: m[1] * x + m[5] * y + m[9] * z + m[13],
271
+ z: m[2] * x + m[6] * y + m[10] * z + m[14]
272
+ };
273
+ }
274
+ function cross(ax, ay, az, bx, by, bz) {
275
+ return {
276
+ x: ay * bz - az * by,
277
+ y: az * bx - ax * bz,
278
+ z: ax * by - ay * bx
279
+ };
280
+ }
281
+ function solveHomography(w, h, p) {
282
+ const [p0, p1, p2, p3] = p;
283
+ const sx = [0, w, w, 0];
284
+ const sy = [0, 0, h, h];
285
+ const dx = [p0.x, p1.x, p2.x, p3.x];
286
+ const dy = [p0.y, p1.y, p2.y, p3.y];
287
+ const M = [];
288
+ for (let i = 0; i < 4; i++) {
289
+ M.push([sx[i], sy[i], 1, 0, 0, 0, -dx[i] * sx[i], -dx[i] * sy[i], dx[i]]);
290
+ M.push([0, 0, 0, sx[i], sy[i], 1, -dy[i] * sx[i], -dy[i] * sy[i], dy[i]]);
291
+ }
292
+ const n = 8;
293
+ for (let col = 0; col < n; col++) {
294
+ let maxRow = col;
295
+ for (let row = col + 1; row < n; row++) {
296
+ if (Math.abs(M[row][col]) > Math.abs(M[maxRow][col])) maxRow = row;
297
+ }
298
+ [M[col], M[maxRow]] = [M[maxRow], M[col]];
299
+ if (Math.abs(M[col][col]) < 1e-10) return null;
300
+ const pivot = M[col][col];
301
+ for (let k = col; k <= n; k++) M[col][k] /= pivot;
302
+ for (let row = 0; row < n; row++) {
303
+ if (row === col) continue;
304
+ const f = M[row][col];
305
+ for (let k = col; k <= n; k++) M[row][k] -= f * M[col][k];
306
+ }
307
+ }
308
+ return M.map((row) => row[n]);
309
+ }
310
+ function buildMatrix3d(w, h, p) {
311
+ const hv = solveHomography(w, h, p);
312
+ if (!hv) return "none";
313
+ const [h00, h01, h02, h10, h11, h12, h20, h21] = hv;
314
+ return `matrix3d(${h00},${h10},0,${h20},${h01},${h11},0,${h21},0,0,1,0,${h02},${h12},0,1)`;
315
+ }
316
+ function FaceSkin({
317
+ context,
318
+ model,
319
+ corners,
320
+ children,
321
+ style,
322
+ className,
323
+ surfaceWidth = 300,
324
+ surfaceHeight = 200
325
+ }) {
326
+ const { project } = useWorldToScreen(context);
327
+ if (!model || !context) return null;
328
+ const mm = model.getModelMatrix();
329
+ const worldPts = corners.map(
330
+ ([lx, ly, lz]) => transformLocalToWorld(mm, lx, ly, lz)
331
+ );
332
+ const e1x = worldPts[1].x - worldPts[0].x;
333
+ const e1y = worldPts[1].y - worldPts[0].y;
334
+ const e1z = worldPts[1].z - worldPts[0].z;
335
+ const e2x = worldPts[3].x - worldPts[0].x;
336
+ const e2y = worldPts[3].y - worldPts[0].y;
337
+ const e2z = worldPts[3].z - worldPts[0].z;
338
+ const normal = cross(e1x, e1y, e1z, e2x, e2y, e2z);
339
+ const camera = context.viewport.camera;
340
+ const radius = Math.sqrt(
341
+ (camera.position[0] - camera.target[0]) ** 2 + (camera.position[1] - camera.target[1]) ** 2 + (camera.position[2] - camera.target[2]) ** 2
342
+ );
343
+ const eyeX = camera.target[0] + radius * Math.cos(camera.pitch) * Math.sin(camera.yaw);
344
+ const eyeY = camera.target[1] + radius * Math.sin(camera.pitch);
345
+ const eyeZ = camera.target[2] + radius * Math.cos(camera.pitch) * Math.cos(camera.yaw);
346
+ const toCamera = {
347
+ x: eyeX - worldPts[0].x,
348
+ y: eyeY - worldPts[0].y,
349
+ z: eyeZ - worldPts[0].z
350
+ };
351
+ const dot = normal.x * toCamera.x + normal.y * toCamera.y + normal.z * toCamera.z;
352
+ if (dot <= 0) return null;
353
+ const screenPts = worldPts.map((w) => project(w.x, w.y, w.z));
354
+ if (screenPts.some((pt) => !pt.visible)) return null;
355
+ const remapped = [screenPts[3], screenPts[2], screenPts[1], screenPts[0]];
356
+ const matrix = buildMatrix3d(surfaceWidth, surfaceHeight, remapped);
357
+ return /* @__PURE__ */ jsx3(
358
+ "div",
359
+ {
360
+ style: {
361
+ position: "absolute",
362
+ inset: 0,
363
+ pointerEvents: "none",
364
+ overflow: "hidden"
365
+ },
366
+ children: /* @__PURE__ */ jsx3(
367
+ "div",
368
+ {
369
+ className,
370
+ style: {
371
+ position: "absolute",
372
+ top: 0,
373
+ left: 0,
374
+ width: surfaceWidth,
375
+ height: surfaceHeight,
376
+ transformOrigin: "0 0",
377
+ transform: matrix,
378
+ overflow: "hidden",
379
+ pointerEvents: "auto",
380
+ ...style
381
+ },
382
+ children
383
+ }
384
+ )
385
+ }
386
+ );
387
+ }
388
+
389
+ export {
390
+ GenesisGLCanvas,
391
+ RotationControls,
392
+ FaceSkin
393
+ };
394
+ //# sourceMappingURL=chunk-UVQX6HJJ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/GenesisGLCanvas.tsx","../src/components/RotationControls.tsx","../src/components/FaceLabel.tsx"],"sourcesContent":["import React, { useRef, useCallback } from 'react';\nimport { useGenesisGL } from '../hooks/useGenesisGL';\nimport type { GenesisGLContext } from '../hooks/useGenesisGL';\nimport { useRenderer } from '../hooks/useRenderer';\nimport { useCameraMouseDrag } from '../hooks/useCameraMouseDrag';\n\nexport interface GenesisGLCanvasProps {\n onReady?: (context: GenesisGLContext) => void;\n onFrame?: (time: number) => void;\n width?: number;\n height?: number;\n initialYaw?: number;\n initialPitch?: number;\n initialZoom?: number;\n style?: React.CSSProperties;\n className?: string;\n}\n\nexport const GenesisGLCanvas = React.forwardRef<\n HTMLCanvasElement,\n GenesisGLCanvasProps\n>(\n (\n {\n onReady,\n onFrame,\n width,\n height,\n initialYaw,\n initialPitch,\n initialZoom,\n style,\n className,\n },\n ref,\n ) => {\n const canvasRef = useRef<HTMLCanvasElement>(null);\n\n // Always use the internal ref for GL init; expose to parent via callback ref\n const setRef = useCallback(\n (el: HTMLCanvasElement | null) => {\n (\n canvasRef as React.MutableRefObject<HTMLCanvasElement | null>\n ).current = el;\n if (typeof ref === 'function') ref(el);\n else if (ref) ref.current = el;\n },\n [ref],\n );\n\n const onFrameRef = useRef(onFrame);\n onFrameRef.current = onFrame;\n\n const genesisContext = useGenesisGL({\n canvasRef,\n width,\n height,\n initialYaw,\n initialPitch,\n initialZoom,\n onReady,\n });\n\n const genesisContextRef = useRef(genesisContext);\n genesisContextRef.current = genesisContext;\n\n const stableOnFrame = useCallback((time: number) => {\n const { renderer, scene } = genesisContextRef.current;\n if (renderer && scene) {\n renderer.render(scene);\n onFrameRef.current?.(time);\n }\n }, []);\n\n useRenderer({ renderer: genesisContext.renderer, onFrame: stableOnFrame });\n useCameraMouseDrag(genesisContext, canvasRef);\n\n return (\n <canvas\n ref={setRef}\n style={{ display: 'block', width: '100%', height: '100%', ...style }}\n className={className}\n />\n );\n },\n);\n\nGenesisGLCanvas.displayName = 'GenesisGLCanvas';\n","import React, { useState } from 'react';\nimport { useCameraControls } from '../hooks/useCameraControls';\nimport type { GenesisGLContext } from '../hooks/useGenesisGL';\n\nconst STEP = 0.05;\n\nfunction IconBtn({\n children,\n title,\n onClick,\n color,\n}: {\n children: React.ReactNode;\n title?: string;\n onClick: () => void;\n color: string;\n}) {\n const [active, setActive] = useState(false);\n const [hover, setHover] = useState(false);\n\n return (\n <button\n title={title}\n onClick={onClick}\n onMouseEnter={() => setHover(true)}\n onMouseLeave={() => { setHover(false); setActive(false); }}\n onMouseDown={() => setActive(true)}\n onMouseUp={() => setActive(false)}\n style={{\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n width: '22px',\n height: '22px',\n border: `1px solid ${hover ? color + '88' : color + '33'}`,\n borderRadius: '5px',\n background: active ? color + '30' : hover ? color + '18' : color + '0c',\n color: hover ? color : color + 'aa',\n cursor: 'pointer',\n fontSize: '11px',\n fontWeight: 700,\n lineHeight: 1,\n padding: 0,\n transition: 'all 0.12s',\n transform: active ? 'scale(0.88)' : 'scale(1)',\n flexShrink: 0,\n }}\n >\n {children}\n </button>\n );\n}\n\nfunction AxisPill({\n label,\n color,\n value,\n onDec,\n onInc,\n onReset,\n}: {\n label: string;\n color: string;\n value: number;\n onDec: () => void;\n onInc: () => void;\n onReset: () => void;\n}) {\n const deg = ((value * 180) / Math.PI).toFixed(1);\n const normalized = ((value % (Math.PI * 2)) + Math.PI * 2) % (Math.PI * 2);\n const pct = (normalized / (Math.PI * 2)) * 100;\n\n return (\n <div\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: '5px',\n background: color + '0a',\n border: `1px solid ${color}22`,\n borderRadius: '8px',\n padding: '5px 7px',\n flex: 1,\n minWidth: 0,\n }}\n >\n <div style={{ position: 'relative', width: '18px', height: '18px', flexShrink: 0 }}>\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 18 18\">\n <circle cx=\"9\" cy=\"9\" r=\"7\" fill=\"none\" stroke={color + '22'} strokeWidth=\"2\" />\n <circle\n cx=\"9\" cy=\"9\" r=\"7\"\n fill=\"none\"\n stroke={color}\n strokeWidth=\"2\"\n strokeDasharray={`${(pct / 100) * 44} 44`}\n strokeLinecap=\"round\"\n transform=\"rotate(-90 9 9)\"\n style={{ transition: 'stroke-dasharray 0.1s ease' }}\n />\n </svg>\n <span\n style={{\n position: 'absolute',\n inset: 0,\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n fontSize: '7px',\n fontWeight: 800,\n color,\n letterSpacing: '-0.02em',\n }}\n >\n {label}\n </span>\n </div>\n\n <span\n style={{\n fontSize: '10px',\n color: color + 'cc',\n fontVariantNumeric: 'tabular-nums',\n width: '36px',\n textAlign: 'right',\n flexShrink: 0,\n }}\n >\n {deg}°\n </span>\n\n <div style={{ display: 'flex', gap: '3px', marginLeft: '2px' }}>\n <IconBtn color={color} title={`−${label}`} onClick={onDec}>−</IconBtn>\n <IconBtn color={color} title={`+${label}`} onClick={onInc}>+</IconBtn>\n <IconBtn color={color + '88'} title=\"Reset\" onClick={onReset}>↺</IconBtn>\n </div>\n </div>\n );\n}\n\nexport interface RotationControlsProps {\n context: GenesisGLContext | null;\n style?: React.CSSProperties;\n}\n\nexport function RotationControls({ context, style }: RotationControlsProps) {\n const { camera, rotate, setYaw, setPitch, reset } = useCameraControls(context);\n\n return (\n <div\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: '6px',\n background: 'rgba(8,8,12,0.78)',\n border: '1px solid rgba(255,255,255,0.07)',\n borderRadius: '12px',\n padding: '6px 8px',\n backdropFilter: 'blur(16px)',\n boxShadow: '0 4px 24px rgba(0,0,0,0.5)',\n fontFamily: '\"Inter\", system-ui, monospace',\n ...style,\n }}\n >\n <div\n title={context ? 'Camera ready' : 'Waiting for context…'}\n style={{\n width: '6px',\n height: '6px',\n borderRadius: '50%',\n background: context ? '#c3e88d' : '#555',\n boxShadow: context ? '0 0 6px #c3e88daa' : 'none',\n flexShrink: 0,\n transition: 'all 0.3s',\n }}\n />\n\n <AxisPill\n label=\"Yaw\"\n color=\"#ff5370\"\n value={camera.yaw}\n onDec={() => rotate(-STEP, 0)}\n onInc={() => rotate(STEP, 0)}\n onReset={() => setYaw(0)}\n />\n\n <AxisPill\n label=\"Pitch\"\n color=\"#82aaff\"\n value={camera.pitch}\n onDec={() => rotate(0, -STEP)}\n onInc={() => rotate(0, STEP)}\n onReset={() => setPitch(0)}\n />\n\n <IconBtn color=\"#ffffff\" title=\"Reset all\" onClick={reset}>⊘</IconBtn>\n </div>\n );\n}\n","import React from 'react';\nimport type { Model } from '@fonsecabarreto/genesis-gl-core/Core';\nimport type { GenesisGLContext } from '../hooks/useGenesisGL';\nimport { useWorldToScreen } from '../hooks/useWorldToScreen';\n\nexport interface FaceSkinProps {\n context: GenesisGLContext | null;\n model: Model | null;\n corners: [\n [number, number, number],\n [number, number, number],\n [number, number, number],\n [number, number, number],\n ];\n /** Width of the virtual surface div warped onto the face (px) */\n surfaceWidth?: number;\n /** Height of the virtual surface div warped onto the face (px) */\n surfaceHeight?: number;\n children?: React.ReactNode;\n style?: React.CSSProperties;\n className?: string;\n}\n\nfunction transformLocalToWorld(\n m: Float32Array,\n x: number,\n y: number,\n z: number,\n) {\n return {\n x: m[0] * x + m[4] * y + m[8] * z + m[12],\n y: m[1] * x + m[5] * y + m[9] * z + m[13],\n z: m[2] * x + m[6] * y + m[10] * z + m[14],\n };\n}\n\nfunction cross(\n ax: number,\n ay: number,\n az: number,\n bx: number,\n by: number,\n bz: number,\n) {\n return {\n x: ay * bz - az * by,\n y: az * bx - ax * bz,\n z: ax * by - ay * bx,\n };\n}\n\n/**\n * Solve for the homography matrix H (8 unknowns, h22=1) that maps\n * a w×h rectangle to four destination points using Gaussian elimination.\n * Returns [h00,h01,h02,h10,h11,h12,h20,h21] or null on failure.\n */\nfunction solveHomography(\n w: number,\n h: number,\n p: { x: number; y: number }[],\n): number[] | null {\n const [p0, p1, p2, p3] = p;\n const sx = [0, w, w, 0];\n const sy = [0, 0, h, h];\n const dx = [p0.x, p1.x, p2.x, p3.x];\n const dy = [p0.y, p1.y, p2.y, p3.y];\n\n // Build augmented 8×9 matrix\n const M: number[][] = [];\n for (let i = 0; i < 4; i++) {\n M.push([sx[i], sy[i], 1, 0, 0, 0, -dx[i] * sx[i], -dx[i] * sy[i], dx[i]]);\n M.push([0, 0, 0, sx[i], sy[i], 1, -dy[i] * sx[i], -dy[i] * sy[i], dy[i]]);\n }\n\n const n = 8;\n for (let col = 0; col < n; col++) {\n let maxRow = col;\n for (let row = col + 1; row < n; row++) {\n if (Math.abs(M[row][col]) > Math.abs(M[maxRow][col])) maxRow = row;\n }\n [M[col], M[maxRow]] = [M[maxRow], M[col]];\n if (Math.abs(M[col][col]) < 1e-10) return null;\n const pivot = M[col][col];\n for (let k = col; k <= n; k++) M[col][k] /= pivot;\n for (let row = 0; row < n; row++) {\n if (row === col) continue;\n const f = M[row][col];\n for (let k = col; k <= n; k++) M[row][k] -= f * M[col][k];\n }\n }\n\n return M.map((row) => row[n]);\n}\n\n/**\n * Build the CSS matrix3d string that perspectively maps a w×h div\n * (origin top-left) onto four screen-space points [p0,p1,p2,p3]\n * corresponding to the div corners (0,0),(w,0),(w,h),(0,h).\n */\nfunction buildMatrix3d(\n w: number,\n h: number,\n p: { x: number; y: number }[],\n): string {\n const hv = solveHomography(w, h, p);\n if (!hv) return 'none';\n const [h00, h01, h02, h10, h11, h12, h20, h21] = hv;\n // CSS matrix3d is column-major 4×4\n return (\n `matrix3d(` +\n `${h00},${h10},0,${h20},` +\n `${h01},${h11},0,${h21},` +\n `0,0,1,0,` +\n `${h02},${h12},0,1)`\n );\n}\n\nexport function FaceSkin({\n context,\n model,\n corners,\n children,\n style,\n className,\n surfaceWidth = 300,\n surfaceHeight = 200,\n}: FaceSkinProps) {\n const { project } = useWorldToScreen(context);\n\n if (!model || !context) return null;\n\n const mm = model.getModelMatrix() as unknown as Float32Array;\n\n const worldPts = corners.map(([lx, ly, lz]) =>\n transformLocalToWorld(mm, lx, ly, lz),\n );\n\n const e1x = worldPts[1].x - worldPts[0].x;\n const e1y = worldPts[1].y - worldPts[0].y;\n const e1z = worldPts[1].z - worldPts[0].z;\n const e2x = worldPts[3].x - worldPts[0].x;\n const e2y = worldPts[3].y - worldPts[0].y;\n const e2z = worldPts[3].z - worldPts[0].z;\n const normal = cross(e1x, e1y, e1z, e2x, e2y, e2z);\n\n const camera = context.viewport!.camera;\n const radius = Math.sqrt(\n (camera.position[0] - camera.target[0]) ** 2 +\n (camera.position[1] - camera.target[1]) ** 2 +\n (camera.position[2] - camera.target[2]) ** 2,\n );\n const eyeX =\n camera.target[0] + radius * Math.cos(camera.pitch) * Math.sin(camera.yaw);\n const eyeY = camera.target[1] + radius * Math.sin(camera.pitch);\n const eyeZ =\n camera.target[2] + radius * Math.cos(camera.pitch) * Math.cos(camera.yaw);\n\n const toCamera = {\n x: eyeX - worldPts[0].x,\n y: eyeY - worldPts[0].y,\n z: eyeZ - worldPts[0].z,\n };\n const dot =\n normal.x * toCamera.x + normal.y * toCamera.y + normal.z * toCamera.z;\n\n if (dot <= 0) return null;\n\n const screenPts = worldPts.map((w) => project(w.x, w.y, w.z));\n if (screenPts.some((pt) => !pt.visible)) return null;\n\n // Remap corners so the div renders right-side-up from the isometric view:\n // div (0,0)→screenPts[3], (w,0)→[2], (w,h)→[1], (0,h)→[0]\n const remapped = [screenPts[3], screenPts[2], screenPts[1], screenPts[0]];\n const matrix = buildMatrix3d(surfaceWidth, surfaceHeight, remapped);\n\n return (\n <div\n style={{\n position: 'absolute',\n inset: 0,\n pointerEvents: 'none',\n overflow: 'hidden',\n }}\n >\n <div\n className={className}\n style={{\n position: 'absolute',\n top: 0,\n left: 0,\n width: surfaceWidth,\n height: surfaceHeight,\n transformOrigin: '0 0',\n transform: matrix,\n overflow: 'hidden',\n pointerEvents: 'auto',\n ...style,\n }}\n >\n {children}\n </div>\n </div>\n );\n}\n\nexport { FaceSkin as FaceLabel };\nexport type { FaceSkinProps as FaceLabelProps };\n"],"mappings":"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","names":["jsx","jsx"]}
@@ -0,0 +1,66 @@
1
+ // src/hooks/useModel.ts
2
+ import { useEffect, useRef, useState } from "react";
3
+ function useModel(context, options) {
4
+ const [result, setResult] = useState({
5
+ model: null,
6
+ loading: false,
7
+ error: null
8
+ });
9
+ const optionsRef = useRef(options);
10
+ optionsRef.current = options;
11
+ useEffect(() => {
12
+ if (!context?.scene || !context?.loadOBJ || !context?.webglCore) return;
13
+ const { scene, loadOBJ, webglCore } = context;
14
+ const { name, objUrl, postLoad, ...loadOpts } = optionsRef.current;
15
+ let cancelled = false;
16
+ setResult({ model: null, loading: true, error: null });
17
+ loadOBJ(objUrl, loadOpts).then(async (model) => {
18
+ if (cancelled) return;
19
+ if (postLoad) await postLoad(model, webglCore);
20
+ scene.add(name, model);
21
+ setResult({ model, loading: false, error: null });
22
+ }).catch((err) => {
23
+ if (cancelled) return;
24
+ const error = err instanceof Error ? err : new Error(String(err));
25
+ setResult({ model: null, loading: false, error });
26
+ });
27
+ return () => {
28
+ cancelled = true;
29
+ };
30
+ }, [context?.scene, context?.loadOBJ, context?.webglCore]);
31
+ return result;
32
+ }
33
+
34
+ // src/hooks/useModelRotation.ts
35
+ import { useCallback, useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
36
+ function useModelRotation(model) {
37
+ const [rotation, setRotationState] = useState2([0, 0, 0]);
38
+ const modelRef = useRef2(model);
39
+ modelRef.current = model;
40
+ useEffect2(() => {
41
+ if (model) setRotationState([...model.rotation]);
42
+ }, [model]);
43
+ const setRotation = useCallback((x, y, z) => {
44
+ const m = modelRef.current;
45
+ if (!m) return;
46
+ m.setRotation(x, y, z);
47
+ setRotationState([x, y, z]);
48
+ }, []);
49
+ const rotate = useCallback((dx, dy, dz) => {
50
+ const m = modelRef.current;
51
+ if (!m) return;
52
+ const [cx, cy, cz] = m.rotation;
53
+ const nx = cx + dx;
54
+ const ny = cy + dy;
55
+ const nz = cz + dz;
56
+ m.setRotation(nx, ny, nz);
57
+ setRotationState([nx, ny, nz]);
58
+ }, []);
59
+ return { rotation, setRotation, rotate };
60
+ }
61
+
62
+ export {
63
+ useModel,
64
+ useModelRotation
65
+ };
66
+ //# sourceMappingURL=chunk-VAMVRTC4.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/hooks/useModel.ts","../src/hooks/useModelRotation.ts"],"sourcesContent":["import { useEffect, useRef, useState } from 'react';\nimport type { Model, WebGLCore } from '../../../GenesisGL/src/Core';\nimport type { GenesisGLContext, LoadOBJOptions } from './useGenesisGL';\n\nexport interface UseModelOptions extends LoadOBJOptions {\n name: string;\n objUrl: string;\n postLoad?: (model: Model, webglCore: WebGLCore) => Promise<void> | void;\n}\n\nexport interface UseModelResult {\n model: Model | null;\n loading: boolean;\n error: Error | null;\n}\n\nexport function useModel(\n context: GenesisGLContext | null,\n options: UseModelOptions,\n): UseModelResult {\n const [result, setResult] = useState<UseModelResult>({\n model: null,\n loading: false,\n error: null,\n });\n\n const optionsRef = useRef(options);\n optionsRef.current = options;\n\n useEffect(() => {\n if (!context?.scene || !context?.loadOBJ || !context?.webglCore) return;\n\n const { scene, loadOBJ, webglCore } = context;\n const { name, objUrl, postLoad, ...loadOpts } = optionsRef.current;\n let cancelled = false;\n\n setResult({ model: null, loading: true, error: null });\n\n loadOBJ(objUrl, loadOpts)\n .then(async (model) => {\n if (cancelled) return;\n if (postLoad) await postLoad(model, webglCore);\n scene.add(name, model);\n setResult({ model, loading: false, error: null });\n })\n .catch((err: unknown) => {\n if (cancelled) return;\n const error = err instanceof Error ? err : new Error(String(err));\n setResult({ model: null, loading: false, error });\n });\n\n return () => {\n cancelled = true;\n };\n }, [context?.scene, context?.loadOBJ, context?.webglCore]);\n\n return result;\n}\n","import { useCallback, useEffect, useRef, useState } from 'react';\nimport type { Model } from '@fonsecabarreto/genesis-gl-core/Core';\n\ntype Rotation = [number, number, number];\n\nexport interface UseModelRotationResult {\n rotation: Rotation;\n setRotation: (x: number, y: number, z: number) => void;\n rotate: (dx: number, dy: number, dz: number) => void;\n}\n\nexport function useModelRotation(model: Model | null): UseModelRotationResult {\n const [rotation, setRotationState] = useState<Rotation>([0, 0, 0]);\n const modelRef = useRef(model);\n modelRef.current = model;\n\n useEffect(() => {\n if (model) setRotationState([...model.rotation]);\n }, [model]);\n\n const setRotation = useCallback((x: number, y: number, z: number) => {\n const m = modelRef.current;\n if (!m) return;\n m.setRotation(x, y, z);\n setRotationState([x, y, z]);\n }, []);\n\n const rotate = useCallback((dx: number, dy: number, dz: number) => {\n const m = modelRef.current;\n if (!m) return;\n const [cx, cy, cz] = m.rotation;\n const nx = cx + dx;\n const ny = cy + dy;\n const nz = cz + dz;\n m.setRotation(nx, ny, nz);\n setRotationState([nx, ny, nz]);\n }, []);\n\n return { rotation, setRotation, rotate };\n}\n"],"mappings":";AAAA,SAAS,WAAW,QAAQ,gBAAgB;AAgBrC,SAAS,SACd,SACA,SACgB;AAChB,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAyB;AAAA,IACnD,OAAO;AAAA,IACP,SAAS;AAAA,IACT,OAAO;AAAA,EACT,CAAC;AAED,QAAM,aAAa,OAAO,OAAO;AACjC,aAAW,UAAU;AAErB,YAAU,MAAM;AACd,QAAI,CAAC,SAAS,SAAS,CAAC,SAAS,WAAW,CAAC,SAAS,UAAW;AAEjE,UAAM,EAAE,OAAO,SAAS,UAAU,IAAI;AACtC,UAAM,EAAE,MAAM,QAAQ,UAAU,GAAG,SAAS,IAAI,WAAW;AAC3D,QAAI,YAAY;AAEhB,cAAU,EAAE,OAAO,MAAM,SAAS,MAAM,OAAO,KAAK,CAAC;AAErD,YAAQ,QAAQ,QAAQ,EACrB,KAAK,OAAO,UAAU;AACrB,UAAI,UAAW;AACf,UAAI,SAAU,OAAM,SAAS,OAAO,SAAS;AAC7C,YAAM,IAAI,MAAM,KAAK;AACrB,gBAAU,EAAE,OAAO,SAAS,OAAO,OAAO,KAAK,CAAC;AAAA,IAClD,CAAC,EACA,MAAM,CAAC,QAAiB;AACvB,UAAI,UAAW;AACf,YAAM,QAAQ,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,GAAG,CAAC;AAChE,gBAAU,EAAE,OAAO,MAAM,SAAS,OAAO,MAAM,CAAC;AAAA,IAClD,CAAC;AAEH,WAAO,MAAM;AACX,kBAAY;AAAA,IACd;AAAA,EACF,GAAG,CAAC,SAAS,OAAO,SAAS,SAAS,SAAS,SAAS,CAAC;AAEzD,SAAO;AACT;;;ACzDA,SAAS,aAAa,aAAAA,YAAW,UAAAC,SAAQ,YAAAC,iBAAgB;AAWlD,SAAS,iBAAiB,OAA6C;AAC5E,QAAM,CAAC,UAAU,gBAAgB,IAAIA,UAAmB,CAAC,GAAG,GAAG,CAAC,CAAC;AACjE,QAAM,WAAWD,QAAO,KAAK;AAC7B,WAAS,UAAU;AAEnB,EAAAD,WAAU,MAAM;AACd,QAAI,MAAO,kBAAiB,CAAC,GAAG,MAAM,QAAQ,CAAC;AAAA,EACjD,GAAG,CAAC,KAAK,CAAC;AAEV,QAAM,cAAc,YAAY,CAAC,GAAW,GAAW,MAAc;AACnE,UAAM,IAAI,SAAS;AACnB,QAAI,CAAC,EAAG;AACR,MAAE,YAAY,GAAG,GAAG,CAAC;AACrB,qBAAiB,CAAC,GAAG,GAAG,CAAC,CAAC;AAAA,EAC5B,GAAG,CAAC,CAAC;AAEL,QAAM,SAAS,YAAY,CAAC,IAAY,IAAY,OAAe;AACjE,UAAM,IAAI,SAAS;AACnB,QAAI,CAAC,EAAG;AACR,UAAM,CAAC,IAAI,IAAI,EAAE,IAAI,EAAE;AACvB,UAAM,KAAK,KAAK;AAChB,UAAM,KAAK,KAAK;AAChB,UAAM,KAAK,KAAK;AAChB,MAAE,YAAY,IAAI,IAAI,EAAE;AACxB,qBAAiB,CAAC,IAAI,IAAI,EAAE,CAAC;AAAA,EAC/B,GAAG,CAAC,CAAC;AAEL,SAAO,EAAE,UAAU,aAAa,OAAO;AACzC;","names":["useEffect","useRef","useState"]}