hbauzan's picture
Release v4.17.7 — sync from GitHub main
819a47f
Raw
History Blame Contribute Delete
4.86 kB
/**
* 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;
}