@fonsecabarreto/genesis-gl-react 0.1.31 → 0.1.33

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-MAGVIIUT.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-R4MI3LWV.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,218 @@
1
+ // src/hooks/useGenesisGL.ts
2
+ import { useRef, useEffect, useState } from "react";
3
+ import {
4
+ WebGLCore as WebGLCoreImpl,
5
+ Scene as SceneImpl,
6
+ Renderer as RendererImpl,
7
+ Viewport as ViewportImpl
8
+ } from "@fonsecabarreto/genesis-gl-core/Core";
9
+ import { loadOBJWithMTL } from "@fonsecabarreto/genesis-gl-core/Core/utils/parse-obj";
10
+ var nullLoadOBJ = () => Promise.reject(new Error("GenesisGL not initialized"));
11
+ function useGenesisGL(options) {
12
+ const [context, setContext] = useState({
13
+ renderer: null,
14
+ scene: null,
15
+ viewport: null,
16
+ webglCore: null,
17
+ loadOBJ: nullLoadOBJ
18
+ });
19
+ const contextRef = useRef(context);
20
+ useEffect(() => {
21
+ const {
22
+ canvasRef,
23
+ onReady,
24
+ onError,
25
+ width,
26
+ height,
27
+ initialYaw,
28
+ initialPitch,
29
+ initialZoom
30
+ } = options;
31
+ if (!canvasRef.current) return;
32
+ try {
33
+ const canvas = canvasRef.current;
34
+ const vpWidth = width ?? window.innerWidth;
35
+ const vpHeight = height ?? window.innerHeight;
36
+ const webglCore = new WebGLCoreImpl(canvas);
37
+ const viewport = new ViewportImpl(canvas, vpWidth, vpHeight, webglCore, {
38
+ pointerLock: false
39
+ });
40
+ const renderer = new RendererImpl(webglCore, viewport);
41
+ const scene = SceneImpl.withDefaultLights(webglCore);
42
+ viewport.camera.target = [0, 0, 0];
43
+ viewport.camera.position = [4, 2, 8];
44
+ if (initialYaw !== void 0) viewport.camera.yaw = initialYaw;
45
+ if (initialPitch !== void 0) viewport.camera.pitch = initialPitch;
46
+ if (initialZoom !== void 0) viewport.camera.setZoom(initialZoom);
47
+ viewport.camera.invalidate();
48
+ const loadOBJ = (objUrl, opts = {}) => loadOBJWithMTL(
49
+ webglCore,
50
+ objUrl,
51
+ opts.mtlUrl ?? null,
52
+ opts.translation,
53
+ opts.scale
54
+ );
55
+ const ctx = {
56
+ renderer,
57
+ scene,
58
+ viewport,
59
+ webglCore,
60
+ loadOBJ
61
+ };
62
+ contextRef.current = ctx;
63
+ setContext(ctx);
64
+ if (onReady) onReady(ctx);
65
+ } catch (error) {
66
+ const err = error instanceof Error ? error : new Error(String(error));
67
+ console.error("Failed to initialize GenesisGL:", err);
68
+ if (onError) onError(err);
69
+ }
70
+ return () => {
71
+ const { scene, viewport, webglCore } = contextRef.current;
72
+ if (scene && webglCore) scene.dispose(webglCore);
73
+ if (viewport) viewport.dispose();
74
+ if (webglCore) webglCore.dispose();
75
+ };
76
+ }, []);
77
+ return context;
78
+ }
79
+
80
+ // src/hooks/useRenderer.ts
81
+ import { useRef as useRef2, useEffect as useEffect2 } from "react";
82
+ function useRenderer(options) {
83
+ const { renderer, onFrame, enabled = true } = options;
84
+ const animationIdRef = useRef2(null);
85
+ useEffect2(() => {
86
+ if (!renderer || !enabled) return;
87
+ const animate = (time) => {
88
+ if (onFrame) onFrame(time);
89
+ animationIdRef.current = requestAnimationFrame(animate);
90
+ };
91
+ animationIdRef.current = requestAnimationFrame(animate);
92
+ return () => {
93
+ if (animationIdRef.current !== null) {
94
+ cancelAnimationFrame(animationIdRef.current);
95
+ }
96
+ };
97
+ }, [renderer, onFrame, enabled]);
98
+ }
99
+
100
+ // src/hooks/useWorldToScreen.ts
101
+ import { useState as useState2, useCallback } from "react";
102
+ function mat4TransformVec4(m, x, y, z, w) {
103
+ return {
104
+ x: m[0] * x + m[4] * y + m[8] * z + m[12] * w,
105
+ y: m[1] * x + m[5] * y + m[9] * z + m[13] * w,
106
+ z: m[2] * x + m[6] * y + m[10] * z + m[14] * w,
107
+ w: m[3] * x + m[7] * y + m[11] * z + m[15] * w
108
+ };
109
+ }
110
+ function useWorldToScreen(context) {
111
+ const [, forceUpdate] = useState2(0);
112
+ const project = useCallback(
113
+ (worldX, worldY, worldZ) => {
114
+ if (!context?.viewport) return { x: 0, y: 0, visible: false };
115
+ const { viewport } = context;
116
+ const camera = viewport.camera;
117
+ const canvas = viewport.getCanvas();
118
+ const view = camera.getViewMatrix();
119
+ const proj = camera.getProjectionMatrix();
120
+ const v = mat4TransformVec4(view, worldX, worldY, worldZ, 1);
121
+ const c = mat4TransformVec4(proj, v.x, v.y, v.z, v.w);
122
+ if (Math.abs(c.w) < 1e-6) return { x: 0, y: 0, visible: false };
123
+ const ndcX = c.x / c.w;
124
+ const ndcY = c.y / c.w;
125
+ const ndcZ = c.z / c.w;
126
+ const visible = ndcZ > -1 && ndcZ < 1 && Math.abs(ndcX) < 1.2 && Math.abs(ndcY) < 1.2;
127
+ return {
128
+ x: (ndcX + 1) * 0.5 * canvas.clientWidth,
129
+ y: (1 - ndcY) * 0.5 * canvas.clientHeight,
130
+ visible
131
+ };
132
+ },
133
+ [context]
134
+ );
135
+ const tick = useCallback(() => forceUpdate((n) => n + 1), []);
136
+ return { project, tick };
137
+ }
138
+
139
+ // src/hooks/useCameraControls.ts
140
+ import { useCallback as useCallback2, useState as useState3 } from "react";
141
+ var DEFAULT_YAW = 0;
142
+ var DEFAULT_PITCH = 0;
143
+ var DEFAULT_ZOOM = 1;
144
+ function useCameraControls(ctx) {
145
+ const [camera, setCameraState] = useState3({
146
+ yaw: DEFAULT_YAW,
147
+ pitch: DEFAULT_PITCH,
148
+ zoom: DEFAULT_ZOOM
149
+ });
150
+ const sync = useCallback2((c) => {
151
+ setCameraState({ yaw: c.yaw, pitch: c.pitch, zoom: c.zoom });
152
+ }, []);
153
+ const rotate = useCallback2(
154
+ (deltaYaw, deltaPitch) => {
155
+ const c = ctx?.viewport?.camera;
156
+ if (!c) return;
157
+ c.rotate(deltaYaw, deltaPitch);
158
+ sync(c);
159
+ },
160
+ [ctx, sync]
161
+ );
162
+ const zoomBy = useCallback2(
163
+ (delta) => {
164
+ const c = ctx?.viewport?.camera;
165
+ if (!c) return;
166
+ c.zoomBy(delta);
167
+ sync(c);
168
+ },
169
+ [ctx, sync]
170
+ );
171
+ const setZoom = useCallback2(
172
+ (zoom) => {
173
+ const c = ctx?.viewport?.camera;
174
+ if (!c) return;
175
+ c.setZoom(zoom);
176
+ sync(c);
177
+ },
178
+ [ctx, sync]
179
+ );
180
+ const setYaw = useCallback2(
181
+ (yaw) => {
182
+ const c = ctx?.viewport?.camera;
183
+ if (!c) return;
184
+ c.yaw = yaw;
185
+ c.invalidate();
186
+ sync(c);
187
+ },
188
+ [ctx, sync]
189
+ );
190
+ const setPitch = useCallback2(
191
+ (pitch) => {
192
+ const c = ctx?.viewport?.camera;
193
+ if (!c) return;
194
+ c.pitch = pitch;
195
+ c.invalidate();
196
+ sync(c);
197
+ },
198
+ [ctx, sync]
199
+ );
200
+ const reset = useCallback2(() => {
201
+ const c = ctx?.viewport?.camera;
202
+ if (!c) return;
203
+ c.yaw = DEFAULT_YAW;
204
+ c.pitch = DEFAULT_PITCH;
205
+ c.invalidate();
206
+ c.setZoom(DEFAULT_ZOOM);
207
+ sync(c);
208
+ }, [ctx, sync]);
209
+ return { camera, rotate, setYaw, setPitch, zoomBy, setZoom, reset };
210
+ }
211
+
212
+ export {
213
+ useGenesisGL,
214
+ useRenderer,
215
+ useWorldToScreen,
216
+ useCameraControls
217
+ };
218
+ //# sourceMappingURL=chunk-TJHSBQII.js.map