// iso: o motor 3D do kit. Projeção de câmera real (elevação de 32°, giro livre em torno do eixo vertical), cubóides com 3 faces // sombreadas, sombra no chão, ordenação por profundidade. Unidades do mundo: 1 = um bloco. `S` = pixels por unidade. import React from 'react'; export const ELEV = (32 * Math.PI) / 180; export type Cubo = {x: number; y: number; z?: number; w: number; d: number; h: number; cor: string; /** quadro em que começa a cair */ cai?: number}; export const proj = (x: number, y: number, z: number, ang: number, S: number) => { const c = Math.cos(ang), s = Math.sin(ang), rx = x * c - y * s, ry = x * s + y * c; return {x: rx * S, y: (ry * Math.sin(ELEV) - z * Math.cos(ELEV)) * S, d: ry}; }; const hex = (h: string) => {const n = parseInt(h.slice(1), 16); return [(n >> 16) & 255, (n >> 8) & 255, n & 255];}; /** multiplica o brilho de uma cor hex */ export const tom = (h: string, f: number) => `rgb(${hex(h).map((v) => Math.max(0, Math.min(255, Math.round(v * f)))).join(',')})`; /** as faces visíveis de um cubóide, já projetadas e sombreadas (luz vindo de cima à esquerda) */ export const faces = (b: Cubo, ang: number, S: number, dz = 0) => { const {x, y, w, d, h, cor} = b, z = (b.z ?? 0) + dz, hw = w / 2, hd = d / 2; const P = (px: number, py: number, pz: number) => proj(px, py, pz, ang, S); const lista: {pts: {x: number; y: number}[]; n: [number, number]; topo?: boolean}[] = [ {topo: true, n: [0, 0], pts: [P(x - hw, y - hd, z + h), P(x + hw, y - hd, z + h), P(x + hw, y + hd, z + h), P(x - hw, y + hd, z + h)]}, {n: [1, 0], pts: [P(x + hw, y - hd, z), P(x + hw, y + hd, z), P(x + hw, y + hd, z + h), P(x + hw, y - hd, z + h)]}, {n: [-1, 0], pts: [P(x - hw, y + hd, z), P(x - hw, y - hd, z), P(x - hw, y - hd, z + h), P(x - hw, y + hd, z + h)]}, {n: [0, 1], pts: [P(x + hw, y + hd, z), P(x - hw, y + hd, z), P(x - hw, y + hd, z + h), P(x + hw, y + hd, z + h)]}, {n: [0, -1], pts: [P(x - hw, y - hd, z), P(x + hw, y - hd, z), P(x + hw, y - hd, z + h), P(x - hw, y - hd, z + h)]}, ]; const c = Math.cos(ang), s = Math.sin(ang); return lista.map((f) => { const nx = f.n[0] * c - f.n[1] * s, ny = f.n[0] * s + f.n[1] * c; if (!f.topo && ny <= 0.02) return null; const luz = f.topo ? 1.12 : 0.78 + (nx * -0.6 + ny * 0.3) * 0.38; return {d: f.pts.map((p) => `${p.x.toFixed(1)},${p.y.toFixed(1)}`).join(' '), cor: tom(cor, luz)}; }).filter(Boolean) as {d: string; cor: string}[]; }; /** um cubóide como grupo SVG */ export const CuboSVG: React.FC<{b: Cubo; ang: number; S: number; dz?: number; opacidade?: number}> = ({b, ang, S, dz = 0, opacidade = 1}) => {faces(b, ang, S, dz).map((f, i) => )}; /** sombra achatada no chão (um pouco deslocada pela luz) */ export const SombraSVG: React.FC<{b: Cubo; ang: number; S: number; escala?: number}> = ({b, ang, S, escala = 1}) => { const hw = (b.w / 2) * escala, hd = (b.d / 2) * escala, o = b.h * 0.35 + (b.z ?? 0) * 0.25; const pts = [[-hw, -hd], [hw, -hd], [hw, hd], [-hw, hd]].map(([px, py]) => proj(b.x + px + o, b.y + py + o * 0.6, 0, ang, S)); return `${p.x.toFixed(1)},${p.y.toFixed(1)}`).join(' ')} fill="rgba(27,43,75,0.16)" />; }; /** profundidade do centro de um cubóide (menor = mais longe) */ export const profundidade = (b: Cubo, ang: number) => proj(b.x, b.y, 0, ang, 1).d + (b.z ?? 0) * 0.01;