Line, LineSegments y LineLoop: tres formas de unir vértices
Qué primitiva de WebGL usa cada una, cómo cambia la interpretación del mismo buffer de posiciones, y qué hace falta para que una línea discontinua funcione.
La misma geometría, tres lecturas
Toma un atributo position con seis vértices, A, B, C, D, E y F, en ese orden.
Line los interpreta como una polilínea abierta: dibuja A-B, B-C, C-D, D-E, E-F. Cinco segmentos con seis vértices. Es el modo LINE_STRIP de WebGL y es lo más eficiente en memoria para un trazo continuo, porque cada vértice intermedio sirve para dos segmentos.
LineSegments los interpreta por parejas: dibuja A-B, C-D, E-F. Tres segmentos independientes con seis vértices. Es el modo LINES. Cada segmento cuesta dos vértices aunque comparta extremo con otro, y a cambio puedes dibujar un conjunto de aristas inconexas en una sola llamada.
LineLoop es una polilínea que además cierra: A-B, B-C, C-D, D-E, E-F, F-A. Es el modo LINE_LOOP y ese último segmento de vuelta lo pone la GPU, no tu buffer.
Las tres aceptan índice, igual que una malla. Con índice, el buffer de posiciones se comparte y el de índices decide el recorrido, que es exactamente lo que necesitas para dibujar el esqueleto de una malla sin duplicar ningún vértice.
El contorno de una malla, gratis
Escribir a mano los doce segmentos de las aristas de un cubo es un ejercicio aburrido y propenso a erratas. Three.js trae dos generadores que producen geometrías pensadas para LineSegments.
WireframeGeometry devuelve todas las aristas de todos los triángulos. Es lo mismo que material.wireframe = true pero como objeto separado, así que puedes darle su propio color y su propio renderOrder.
EdgesGeometry devuelve solo las aristas donde el ángulo entre las dos caras adyacentes supera un umbral en grados. Con el valor por defecto de uno, en un cubo te da las doce aristas reales y ninguna diagonal interna. Es la herramienta correcta para el aspecto de plano técnico.
import * as THREE from 'three';const scene = new THREE.Scene();const caja = new THREE.BoxGeometry(2, 2, 2);// Solo las aristas donde hay un cambio de orientacion mayor de 15 grados.const aristas = new THREE.EdgesGeometry(caja, 15);const contorno = new THREE.LineSegments( aristas, new THREE.LineBasicMaterial({ color: 0x89b4fa }));const solido = new THREE.Mesh( caja, new THREE.MeshBasicMaterial({ color: 0x1e1e2e }));// El solido rellena el z-buffer y evita que se vean las aristas traseras.solido.material.polygonOffset = true;solido.material.polygonOffsetFactor = 1;solido.material.polygonOffsetUnits = 1;scene.add(solido, contorno);El polygonOffset de ahí resuelve un problema muy concreto: las aristas están exactamente en la superficie del sólido, así que sus fragmentos compiten con los del sólido por el mismo valor de profundidad y aparecen puntos de la línea que desaparecen y vuelven según el redondeo del interpolador. Empujando el sólido un pelo hacia atrás en profundidad, la línea gana siempre.
Líneas discontinuas y la llamada que falta
LineDashedMaterial no calcula la distancia recorrida por su cuenta: la lee de un atributo llamado lineDistance que tiene que existir en la geometría. Ese atributo lo genera el método computeLineDistances() del objeto, y hay que llamarlo a mano después de crear la línea.
const puntos = new THREE.CatmullRomCurve3([ new THREE.Vector3(-4, 0, 0), new THREE.Vector3(-1, 2, 1), new THREE.Vector3(2, -1, -1), new THREE.Vector3(4, 1, 0)]).getPoints(200);const geometria = new THREE.BufferGeometry().setFromPoints(puntos);const material = new THREE.LineDashedMaterial({ color: 0xf9e2af, dashSize: 0.3, gapSize: 0.15, scale: 1});const trazo = new THREE.Line(geometria, material);trazo.computeLineDistances(); // sin esto no hay guiones, y no hay errorscene.add(trazo);dashSize y gapSize están en las mismas unidades que las posiciones, y scale divide a las dos: subirlo a 2 hace los guiones y los huecos la mitad de largos sin tocar los otros dos valores. Si mueves los vértices del trazo, hay que volver a llamar a computeLineDistances(), porque el atributo guarda longitudes absolutas que ya no corresponden.
Cuándo cada una
LineLoop parece la opción natural para un polígono cerrado, y lo es, pero tiene una limitación que conviene saber antes de elegirla: hereda el computeLineDistances() de Line, que recorre el buffer de principio a fin sin contemplar el segmento de cierre. Una LineLoop con material discontinuo tendrá el último tramo sin patrón definido. Para un polígono cerrado con guiones, repite el primer punto al final del array y usa Line.
LineSegments es la elección por defecto para todo lo que sea un conjunto de aristas: contornos, rejillas, ejes, cajas de depuración, esqueletos. De hecho, los ayudantes que trae Three.js, como GridHelper o Box3Helper, son LineSegments por dentro.
Line es para trazos: curvas, caminos, gráficas, trayectorias. Y en cuanto necesites que ese trazo tenga más de un píxel de grosor, ninguna de las tres sirve y hay que cambiar de enfoque, que es el tema de la lección siguiente.