Spaces:
Sleeping
Sleeping
| /** | |
| * AxisGizmo.js | |
| * Indicador de orientación de ejes (estilo Blender) fijo en una esquina del | |
| * viewport. Renderiza una mini-escena con 3 flechas — X (rojo), Y (verde), | |
| * Z (azul) — y etiquetas de texto, que rotan junto con la cámara principal | |
| * para que el usuario nunca pierda la orientación mientras navega. | |
| * | |
| * Semántica en el Relieve: X = dimensión (1→1024), Y = valor, Z = chunk. | |
| * | |
| * API: | |
| * initAxisGizmo() → construye la mini-escena | |
| * renderAxisGizmo(renderer, camera) → dibujar en la esquina (post main render) | |
| */ | |
| import * as THREE from 'three'; | |
| import { PALETTE_3D } from './palette.js'; | |
| const AXIS_COLORS = { | |
| x: PALETTE_3D.AXIS_X, // rojo | |
| y: PALETTE_3D.AXIS_Y, // verde | |
| z: PALETTE_3D.AXIS_Z, // azul | |
| }; | |
| const GIZMO_SIZE = 120; // px del recuadro en la esquina | |
| const GIZMO_MARGIN = 16; // px de separación al borde | |
| let _gizmoScene = null; | |
| let _gizmoCamera = null; | |
| let _gizmoGroup = null; | |
| const _size = new THREE.Vector2(); | |
| // --------------------------------------------------------------------------- | |
| // Etiqueta de texto como sprite (canvas → textura) | |
| // --------------------------------------------------------------------------- | |
| function _makeLabel(text, colorHex) { | |
| const s = 64; | |
| const canvas = document.createElement('canvas'); | |
| canvas.width = s; | |
| canvas.height = s; | |
| const ctx = canvas.getContext('2d'); | |
| ctx.fillStyle = '#' + colorHex.toString(16).padStart(6, '0'); | |
| ctx.font = 'bold 44px Arial, sans-serif'; | |
| ctx.textAlign = 'center'; | |
| ctx.textBaseline = 'middle'; | |
| ctx.fillText(text, s / 2, s / 2); | |
| const texture = new THREE.CanvasTexture(canvas); | |
| texture.anisotropy = 4; | |
| const material = new THREE.SpriteMaterial({ map: texture, transparent: true, depthTest: false }); | |
| const sprite = new THREE.Sprite(material); | |
| sprite.scale.set(0.55, 0.55, 0.55); | |
| return sprite; | |
| } | |
| // --------------------------------------------------------------------------- | |
| // Una flecha de eje + su etiqueta, alineada al vector dir | |
| // --------------------------------------------------------------------------- | |
| function _makeAxis(dir, colorHex, label) { | |
| const group = new THREE.Group(); | |
| const arrow = new THREE.ArrowHelper( | |
| dir.clone().normalize(), | |
| new THREE.Vector3(0, 0, 0), | |
| 1.0, // longitud | |
| colorHex, | |
| 0.32, // headLength | |
| 0.22 // headWidth | |
| ); | |
| // Engrosar la línea del eje (en plataformas que lo soporten) | |
| if (arrow.line && arrow.line.material) arrow.line.material.linewidth = 2; | |
| group.add(arrow); | |
| const sprite = _makeLabel(label, colorHex); | |
| sprite.position.copy(dir.clone().normalize().multiplyScalar(1.35)); | |
| group.add(sprite); | |
| return group; | |
| } | |
| // --------------------------------------------------------------------------- | |
| // Inicialización de la mini-escena del gizmo | |
| // --------------------------------------------------------------------------- | |
| export function initAxisGizmo() { | |
| _gizmoScene = new THREE.Scene(); | |
| _gizmoCamera = new THREE.PerspectiveCamera(50, 1, 0.1, 20); | |
| _gizmoCamera.position.set(0, 0, 3.4); | |
| _gizmoCamera.lookAt(0, 0, 0); | |
| _gizmoGroup = new THREE.Group(); | |
| _gizmoGroup.add(_makeAxis(new THREE.Vector3(1, 0, 0), AXIS_COLORS.x, 'X')); | |
| _gizmoGroup.add(_makeAxis(new THREE.Vector3(0, 1, 0), AXIS_COLORS.y, 'Y')); | |
| _gizmoGroup.add(_makeAxis(new THREE.Vector3(0, 0, 1), AXIS_COLORS.z, 'Z')); | |
| _gizmoScene.add(_gizmoGroup); | |
| return { scene: _gizmoScene, camera: _gizmoCamera, group: _gizmoGroup }; | |
| } | |
| // --------------------------------------------------------------------------- | |
| // Render del gizmo en la esquina inferior derecha (llamar tras el render principal) | |
| // --------------------------------------------------------------------------- | |
| export function renderAxisGizmo(renderer, mainCamera) { | |
| if (!_gizmoScene) return; | |
| // El gizmo muestra los ejes del MUNDO tal como los ve la cámara principal: | |
| // rotamos el grupo por el inverso de la orientación de la cámara. | |
| _gizmoGroup.quaternion.copy(mainCamera.quaternion).invert(); | |
| renderer.getSize(_size); | |
| const left = _size.x - GIZMO_SIZE - GIZMO_MARGIN; | |
| const bottom = GIZMO_MARGIN; | |
| const prevAutoClear = renderer.autoClear; | |
| renderer.autoClear = false; | |
| renderer.setViewport(left, bottom, GIZMO_SIZE, GIZMO_SIZE); | |
| renderer.setScissor(left, bottom, GIZMO_SIZE, GIZMO_SIZE); | |
| renderer.setScissorTest(true); | |
| renderer.clearDepth(); // dibujar el gizmo por encima de la escena | |
| renderer.render(_gizmoScene, _gizmoCamera); | |
| // Restaurar estado del renderer para el siguiente frame | |
| renderer.setScissorTest(false); | |
| renderer.setViewport(0, 0, _size.x, _size.y); | |
| renderer.autoClear = prevAutoClear; | |
| } | |