Draw a Line With a Set Width
Draw a Line of Given Width
Four versions of Line2D are given below, followed by a suggestion for drawing a line in 3D. All Line2Ds can be textured, but only one of them (the second version) has the texture follow the line direction. In addition, the code is adapted as parallelLines to produce lines parallel to a central line.
Line in XoY Plane Formed from Central Path
var line = line2D("line", options, scene);| option | value | default value |
|---|---|---|
| path | (Vector3[]) array of Vector3 points forming the centre line of the line2D | REQUIRED |
| width | (number) line width | 1 |
| closed | (boolean) true if the first and last points are to be joined to form a polygon | false |
| standardUV | (Color4[]) false squeezes a texture image onto each line segment | true |
Just copy the code below if you want to use it.
Playground Examples
These examples use an orthographic camera to give a 2D view.
Open Line Closed Line Standard UV = True Standard UV = FalseLine2D Code
import { Vector3 } from "@babylonjs/core/Maths/math.vector";import { Mesh } from "@babylonjs/core/Meshes/mesh";import { VertexData } from "@babylonjs/core/Meshes/mesh.vertexData";var line2D = function (name, options, scene) { //Arrays for vertex positions and indices var positions = []; var indices = []; var normals = []; var width = options.width / 2 || 0.5; var path = options.path; var closed = options.closed || false; if (options.standardUV === undefined) { standardUV = true; } else { standardUV = options.standardUV; } var interiorIndex; //Arrays to hold wall corner data var innerBaseCorners = []; var outerBaseCorners = []; var outerData = []; var innerData = []; var angle = 0; var nbPoints = path.length; var line = Vector3.Zero(); var nextLine = Vector3.Zero(); path[1].subtractToRef(path[0], line); if (nbPoints > 2 && closed) { path[2].subtractToRef(path[1], nextLine); for (let p = 0; p < nbPoints; p++) { angle = Math.PI - Math.acos(Vector3.Dot(line, nextLine) / (line.length() * nextLine.length())); direction = Vector3.Cross(line, nextLine).normalize().z; lineNormal = new Vector3(line.y, -1 * line.x, 0).normalize(); line.normalize(); innerData[(p + 1) % nbPoints] = path[(p + 1) % nbPoints].subtract(lineNormal.scale(width)).subtract(line.scale(direction * width / Math.tan(angle / 2))); outerData[(p + 1) % nbPoints] = path[(p + 1) % nbPoints].add(lineNormal.scale(width)).add(line.scale(direction * width / Math.tan(angle / 2))); line = nextLine.clone(); path[(p + 3) % nbPoints].subtractToRef(path[(p + 2) % nbPoints], nextLine); } } else { lineNormal = new Vector3(line.y, -1 * line.x, 0).normalize(); line.normalize(); innerData[0] = path[0].subtract(lineNormal.scale(width)); outerData[0] = path[0].add(lineNormal.scale(width)); for (let p = 0; p < nbPoints - 2; p++) { path[p + 2].subtractToRef(path[p + 1], nextLine); angle = Math.PI - Math.acos(Vector3.Dot(line, nextLine) / (line.length() * nextLine.length())); direction = Vector3.Cross(line, nextLine).normalize().z; lineNormal = new Vector3(line.y, -1 * line.x, 0).normalize(); line.normalize(); innerData[p + 1] = path[p + 1].subtract(lineNormal.scale(width)).subtract(line.scale(direction * width / Math.tan(angle / 2))); outerData[p + 1] = path[p + 1].add(lineNormal.scale(width)).add(line.scale(direction * width / Math.tan(angle / 2))); line = nextLine.clone(); } if (nbPoints > 2) { path[nbPoints - 1].subtractToRef(path[nbPoints - 2], line); lineNormal = new Vector3(line.y, -1 * line.x, 0).normalize(); line.normalize(); innerData[nbPoints - 1] = path[nbPoints - 1].subtract(lineNormal.scale(width)); outerData[nbPoints - 1] = path[nbPoints - 1].add(lineNormal.scale(width)); } else { innerData[1] = path[1].subtract(lineNormal.scale(width)); outerData[1] = path[1].add(lineNormal.scale(width)); } } var maxX = Number.MIN_VALUE; var minX = Number.MAX_VALUE; var maxY = Number.MIN_VALUE; var minY = Number.MAX_VALUE; for (let p = 0; p < nbPoints; p++) { positions.push(innerData[p].x, innerData[p].y, innerData[p].z); maxX = Math.max(innerData[p].x, maxX); minX = Math.min(innerData[p].x, minX); maxY = Math.max(innerData[p].y, maxY); minY = Math.min(innerData[p].y, minY); } for (let p = 0; p < nbPoints; p++) { positions.push(outerData[p].x, outerData[p].y, outerData[p].z); maxX = Math.max(innerData[p].x, maxX); minX = Math.min(innerData[p].x, minX); maxY = Math.max(innerData[p].y, maxY); minY = Math.min(innerData[p].y, minY); } for (let i = 0; i < nbPoints - 1; i++) { indices.push(i, i + 1, nbPoints + i + 1); indices.push(i, nbPoints + i + 1, nbPoints + i); } if (nbPoints > 2 && closed) { indices.push(nbPoints - 1, 0, nbPoints); indices.push(nbPoints - 1, nbPoints, 2 * nbPoints - 1); } var normals = []; var uvs = []; if (standardUV) { for (let p = 0; p < positions.length; p += 3) { uvs.push((positions[p] - minX) / (maxX - minX), (positions[p + 1] - minY) / (maxY - minY)); } } else { var flip = 0; var p1 = 0; var p2 = 0; var p3 = 0; var v0 = innerData[0]; var v1 = innerData[1].subtract(v0); var v2 = outerData[0].subtract(v0); var v3 = outerData[1].subtract(v0); var axis = v1.clone(); axis.normalize(); p1 = Vector3.Dot(axis, v1); p2 = Vector3.Dot(axis, v2); p3 = Vector3.Dot(axis, v3); var minX = Math.min(0, p1, p2, p3); var maxX = Math.max(0, p1, p2, p3); uvs[2 * indices[0]] = -minX / (maxX - minX); uvs[2 * indices[0] + 1] = 1; uvs[2 * indices[5]] = (p2 - minX) / (maxX - minX); uvs[2 * indices[5] + 1] = 0; uvs[2 * indices[1]] = (p1 - minX) / (maxX - minX); uvs[2 * indices[1] + 1] = 1; uvs[2 * indices[4]] = (p3 - minX) / (maxX - minX); uvs[2 * indices[4] + 1] = 0; for (let i = 6; i < indices.length; i += 6) { flip = (flip + 1) % 2; v0 = innerData[0]; v1 = innerData[1].subtract(v0); v2 = outerData[0].subtract(v0); v3 = outerData[1].subtract(v0); axis = v1.clone(); axis.normalize(); p1 = Vector3.Dot(axis, v1); p2 = Vector3.Dot(axis, v2); p3 = Vector3.Dot(axis, v3); var minX = Math.min(0, p1, p2, p3); var maxX = Math.max(0, p1, p2, p3); uvs[2 * indices[i + 1]] = flip + Math.cos(flip * Math.PI) * (p1 - minX) / (maxX - minX); uvs[2 * indices[i + 1] + 1] = 1; uvs[2 * indices[i + 4]] = flip + Math.cos(flip * Math.PI) * (p3 - minX) / (maxX - minX); uvs[2 * indices[i + 4] + 1] = 0; } } VertexData.ComputeNormals(positions, indices, normals); VertexData._ComputeSides(Mesh.DOUBLESIDE, positions, indices, normals, uvs); console.log(uvs); //Create a custom mesh var customMesh = new Mesh(name, scene); //Create a vertexData object var vertexData = new VertexData(); //Assign positions and indices to vertexData vertexData.positions = positions; vertexData.indices = indices; vertexData.normals = normals; vertexData.uvs = uvs; //Apply vertexData to custom mesh vertexData.applyToMesh(customMesh); return customMesh;};Texture Follows Line in XoY Plane Formed from Central Path
var line = line2D("line", options, scene);| option | value | default value |
|---|---|---|
| path | (Vector3[]) array of Vector3 points forming the centre line of the line2D | REQUIRED |
| width | (number) line width | 1 |
| closed | (boolean) true if the first and last points are to be joined to form a polygon | false |
Just copy the code below if you want to use it.
Playground Examples
These examples use an orthographic camera to give a 2D view.
Open Texture Line Closed Texture LineTexture Follow Line2D Code
import { Camera } from "@babylonjs/core/Cameras/camera";import { FreeCamera } from "@babylonjs/core/Cameras/freeCamera";import { HemisphericLight } from "@babylonjs/core/Lights/hemisphericLight";import { Vector3 } from "@babylonjs/core/Maths/math.vector";import { Mesh } from "@babylonjs/core/Meshes/mesh";import { VertexData } from "@babylonjs/core/Meshes/mesh.vertexData";import { Scene } from "@babylonjs/core/scene";var createScene = function () { var scene = new Scene(engine); // Camera camera = new FreeCamera("camera1", new Vector3(0, 0, -10), this.scene); camera.mode = Camera.ORTHOGRAPHIC_CAMERA; var canvas = document.getElementById("renderCanvas"); let ratio = canvas.width / canvas.height; let zoom = 30; let width = zoom * ratio; camera.orthoTop = zoom; camera.orthoLeft = -width; camera.orthoRight = width; camera.orthoBottom = -zoom; var light = new HemisphericLight("hemiLight", new Vector3(5, 10, 0), scene); var line2D = function (name, options, scene) { //Arrays for vertex positions and indices var positions = []; var indices = []; var normals = []; var uvs = []; var width = options.width / 2 || 0.5; var path = options.path; var closed = options.closed || false; if (options.standardUV === undefined) { standardUV = true; } else { standardUV = options.standardUV; } var angle = 0; var nbPoints = path.length; var line = Vector3.Zero(); var nextLine = Vector3.Zero(); if (nbPoints > 2 && closed) { path[0].subtractToRef(path[nbPoints - 1], line); for (let p = 0; p < nbPoints; p++) { path[(p + 1) % nbPoints].subtractToRef(path[p], nextLine); angle = Math.PI - Math.acos(Vector3.Dot(line, nextLine) / (line.length() * nextLine.length())); direction = Vector3.Cross(line, nextLine).normalize().z; lineNormal = new Vector3(line.y, -1 * line.x, 0).normalize(); line.normalize(); const in0 = path[p].subtract(lineNormal.scale(width)).subtract(line.scale(direction * width / Math.tan(angle / 2))); const out0 = path[p].add(lineNormal.scale(width)).add(line.scale(direction * width / Math.tan(angle / 2))); line = nextLine.clone(); path[(p + 2) % nbPoints].subtractToRef(path[(p + 1) % nbPoints], nextLine); angle = Math.PI - Math.acos(Vector3.Dot(line, nextLine) / (line.length() * nextLine.length())); direction = Vector3.Cross(line, nextLine).normalize().z; lineNormal = new Vector3(line.y, -1 * line.x, 0).normalize(); line.normalize(); const in1 = path[(p + 1) % nbPoints].subtract(lineNormal.scale(width)).subtract(line.scale(direction * width / Math.tan(angle / 2))); const out1 = path[(p + 1) % nbPoints].add(lineNormal.scale(width)).add(line.scale(direction * width / Math.tan(angle / 2))); positions.push(in0.x, in0.y, in0.z, out0.x, out0.y, out0.z); positions.push(in1.x, in1.y, in1.z, out1.x, out1.y, out1.z); uvs.push(0, 0, 0, 1, 1, 0, 1, 1); indices.push(4 * p + 2, 4 * p + 3, 4 * p + 1, 4 * p + 2, 4 * p + 1, 4 * p); path[(p + 1) % nbPoints].subtractToRef(path[p], line); } } else { path[1].subtractToRef(path[0], line); lineNormal = new Vector3(line.y, -1 * line.x, 0).normalize(); line.normalize(); const in0 = path[0].subtract(lineNormal.scale(width)); const out0 = path[0].add(lineNormal.scale(width)); positions.push(in0.x, in0.y, in0.z, out0.x, out0.y, out0.z); uvs.push(0, 0, 0, 1); indices.push(2, 3, 1); for (let p = 1; p < nbPoints - 1; p++) { path[p + 1].subtractToRef(path[p], nextLine); angle = Math.PI - Math.acos(Vector3.Dot(line, nextLine) / (line.length() * nextLine.length())); direction = Vector3.Cross(line, nextLine).normalize().z; lineNormal = new Vector3(line.y, -1 * line.x, 0).normalize(); line.normalize(); const in1 = path[p].subtract(lineNormal.scale(width)).subtract(line.scale(direction * width / Math.tan(angle / 2))); const out1 = path[p].add(lineNormal.scale(width)).add(line.scale(direction * width / Math.tan(angle / 2))); positions.push(in1.x, in1.y, in1.z, out1.x, out1.y, out1.z); uvs.push(1, 0, 1, 1); indices.push(4 * (p - 1) + 2, 4 * (p - 1) + 1, 4 * (p - 1)); const in0 = in1; const out0 = out1; positions.push(in0.x, in0.y, in0.z, out0.x, out0.y, out0.z); uvs.push(0, 0, 0, 1); indices.push(4 * p + 2, 4 * p + 3, 4 * p + 1); line = nextLine.clone(); } lineNormal = new Vector3(line.y, -1 * line.x, 0).normalize(); line.normalize(); const in1 = path[nbPoints - 1].subtract(lineNormal.scale(width)); const out1 = path[nbPoints - 1].add(lineNormal.scale(width)); positions.push(in1.x, in1.y, in1.z, out1.x, out1.y, out1.z); uvs.push(1, 0, 1, 1); indices.push(4 * (nbPoints - 2) + 2, 4 * (nbPoints - 2) + 1, 4 * (nbPoints - 2)); } //Create a custom mesh VertexData.ComputeNormals(positions, indices, normals); VertexData._ComputeSides(Mesh.DOUBLESIDE, positions, indices, normals, uvs); var customMesh = new Mesh("custom", scene); //Create a vertexData object var vertexData = new VertexData(); //Assign positions and indices to vertexData vertexData.positions = positions; vertexData.indices = indices; vertexData.normals = normals; vertexData.uvs = uvs; //Apply vertexData to custom mesh vertexData.applyToMesh(customMesh); return customMesh; };};Line in XoZ Plane Formed from Central Path
var line = line2D("line", options, scene);| option | value | default value |
|---|---|---|
| path | (Vector3[]) array of Vector3 points forming the centre line of the line2D | REQUIRED |
| width | (number) line width | 1 |
| closed | (boolean) true if the first and last points are to be joined to form a polygon | false |
| standardUV | (Color4[]) false squeezes a texture image onto each line segment | true |
Just copy the code below if you want to use it.
Playground Examples
Open Line Closed Line Standard UV = True Standard UV = FalseLine2D Code
import { Vector3 } from "@babylonjs/core/Maths/math.vector";import { Mesh } from "@babylonjs/core/Meshes/mesh";import { VertexData } from "@babylonjs/core/Meshes/mesh.vertexData";var line2D = function (name, options, scene) { //Arrays for vertex positions and indices var positions = []; var indices = []; var normals = []; var width = options.width / 2 || 0.5; var path = options.path; var closed = options.closed || false; if (options.standardUV === undefined) { standardUV = true; } else { standardUV = options.standardUV; } var interiorIndex; //Arrays to hold wall corner data var innerBaseCorners = []; var outerBaseCorners = []; var outerData = []; var innerData = []; var angle = 0; var nbPoints = path.length; var line = Vector3.Zero(); var nextLine = Vector3.Zero(); path[1].subtractToRef(path[0], line); if (nbPoints > 2 && closed) { path[2].subtractToRef(path[1], nextLine); for (let p = 0; p < nbPoints; p++) { angle = Math.PI - Math.acos(Vector3.Dot(line, nextLine) / (line.length() * nextLine.length())); direction = Vector3.Cross(line, nextLine).normalize().y; lineNormal = new Vector3(-line.z, 0, 1 * line.x).normalize(); line.normalize(); innerData[(p + 1) % nbPoints] = path[(p + 1) % nbPoints].subtract(lineNormal.scale(width)).subtract(line.scale(direction * width / Math.tan(angle / 2))); outerData[(p + 1) % nbPoints] = path[(p + 1) % nbPoints].add(lineNormal.scale(width)).add(line.scale(direction * width / Math.tan(angle / 2))); line = nextLine.clone(); path[(p + 3) % nbPoints].subtractToRef(path[(p + 2) % nbPoints], nextLine); } } else { lineNormal = new Vector3(-line.z, 0, 1 * line.x).normalize(); line.normalize(); innerData[0] = path[0].subtract(lineNormal.scale(width)); outerData[0] = path[0].add(lineNormal.scale(width)); for (let p = 0; p < nbPoints - 2; p++) { path[p + 2].subtractToRef(path[p + 1], nextLine); angle = Math.PI - Math.acos(Vector3.Dot(line, nextLine) / (line.length() * nextLine.length())); direction = Vector3.Cross(line, nextLine).normalize().y; lineNormal = new Vector3(-line.z, 0, 1 * line.x).normalize(); line.normalize(); innerData[p + 1] = path[p + 1].subtract(lineNormal.scale(width)).subtract(line.scale(direction * width / Math.tan(angle / 2))); outerData[p + 1] = path[p + 1].add(lineNormal.scale(width)).add(line.scale(direction * width / Math.tan(angle / 2))); line = nextLine.clone(); } if (nbPoints > 2) { path[nbPoints - 1].subtractToRef(path[nbPoints - 2], line); lineNormal = new Vector3(-line.z, 0, 1 * line.x).normalize(); line.normalize(); innerData[nbPoints - 1] = path[nbPoints - 1].subtract(lineNormal.scale(width)); outerData[nbPoints - 1] = path[nbPoints - 1].add(lineNormal.scale(width)); } else { innerData[1] = path[1].subtract(lineNormal.scale(width)); outerData[1] = path[1].add(lineNormal.scale(width)); } } var maxX = Number.MIN_VALUE; var minX = Number.MAX_VALUE; var maxZ = Number.MIN_VALUE; var minZ = Number.MAX_VALUE; for (let p = 0; p < nbPoints; p++) { positions.push(innerData[p].x, innerData[p].y, innerData[p].z); maxX = Math.max(innerData[p].x, maxX); minX = Math.min(innerData[p].x, minX); maxZ = Math.max(innerData[p].z, maxZ); minZ = Math.min(innerData[p].z, minZ); } for (let p = 0; p < nbPoints; p++) { positions.push(outerData[p].x, outerData[p].y, outerData[p].z); maxX = Math.max(innerData[p].x, maxX); minX = Math.min(innerData[p].x, minX); maxZ = Math.max(innerData[p].z, maxZ); minZ = Math.min(innerData[p].z, minZ); } for (let i = 0; i < nbPoints - 1; i++) { indices.push(i, i + 1, nbPoints + i + 1); indices.push(i, nbPoints + i + 1, nbPoints + i); } if (nbPoints > 2 && closed) { indices.push(nbPoints - 1, 0, nbPoints); indices.push(nbPoints - 1, nbPoints, 2 * nbPoints - 1); } var normals = []; var uvs = []; if (standardUV) { for (let p = 0; p < positions.length; p += 3) { uvs.push((positions[p] - minX) / (maxX - minX), (positions[p + 2] - minZ) / (maxZ - minZ)); } } else { var flip = 0; var p1 = 0; var p2 = 0; var p3 = 0; var v0 = innerData[0]; var v1 = innerData[1].subtract(v0); var v2 = outerData[0].subtract(v0); var v3 = outerData[1].subtract(v0); var axis = v1.clone(); axis.normalize(); p1 = Vector3.Dot(axis, v1); p2 = Vector3.Dot(axis, v2); p3 = Vector3.Dot(axis, v3); var minX = Math.min(0, p1, p2, p3); var maxX = Math.max(0, p1, p2, p3); uvs[2 * indices[0]] = -minX / (maxX - minX); uvs[2 * indices[0] + 1] = 1; uvs[2 * indices[5]] = (p2 - minX) / (maxX - minX); uvs[2 * indices[5] + 1] = 0; uvs[2 * indices[1]] = (p1 - minX) / (maxX - minX); uvs[2 * indices[1] + 1] = 1; uvs[2 * indices[4]] = (p3 - minX) / (maxX - minX); uvs[2 * indices[4] + 1] = 0; for (let i = 6; i < indices.length; i += 6) { flip = (flip + 1) % 2; v0 = innerData[0]; v1 = innerData[1].subtract(v0); v2 = outerData[0].subtract(v0); v3 = outerData[1].subtract(v0); axis = v1.clone(); axis.normalize(); p1 = Vector3.Dot(axis, v1); p2 = Vector3.Dot(axis, v2); p3 = Vector3.Dot(axis, v3); var minX = Math.min(0, p1, p2, p3); var maxX = Math.max(0, p1, p2, p3); uvs[2 * indices[i + 1]] = flip + Math.cos(flip * Math.PI) * (p1 - minX) / (maxX - minX); uvs[2 * indices[i + 1] + 1] = 1; uvs[2 * indices[i + 4]] = flip + Math.cos(flip * Math.PI) * (p3 - minX) / (maxX - minX); uvs[2 * indices[i + 4] + 1] = 0; } } VertexData.ComputeNormals(positions, indices, normals); VertexData._ComputeSides(Mesh.DOUBLESIDE, positions, indices, normals, uvs); console.log(uvs); //Create a custom mesh var customMesh = new Mesh(name, scene); //Create a vertexData object var vertexData = new VertexData(); //Assign positions and indices to vertexData vertexData.positions = positions; vertexData.indices = indices; vertexData.normals = normals; vertexData.uvs = uvs; //Apply vertexData to custom mesh vertexData.applyToMesh(customMesh); return customMesh;};Line in XoZ Plane Formed from Inner Path
With this version, you can use it as an edge of a given width for a polygon.
var line = line2D("line", options, scene);| option | value | default value |
|---|---|---|
| path | (Vector3[]) array of Vector3 points forming the inner line of the line2D, REQUIRED | |
| width | (number) line width | 1 |
| closed | (boolean) true if the first and last points are to be joined to form a polygon | false |
| standardUV | (Color4[]) false squeezes a texture image onto each line segment | true |
Just copy the code below if you want to use it.
Playground Examples (Including Use as Edge to Polygon)
Open Line Closed Line Edge Around Polygon, Standard UV = True Edge Around Polygon, Standard UV = FalseLine2D Code
import { Vector3 } from "@babylonjs/core/Maths/math.vector";import { Mesh } from "@babylonjs/core/Meshes/mesh";import { VertexData } from "@babylonjs/core/Meshes/mesh.vertexData";var line2D = function (name, options, scene) { //Arrays for vertex positions and indices var positions = []; var indices = []; var normals = []; var width = options.width || 1; var path = options.path; var closed = options.closed || false; if (options.standardUV === undefined) { standardUV = true; } else { standardUV = options.standardUV; } var interiorIndex; //Arrays to hold wall corner data var innerBaseCorners = []; var outerBaseCorners = []; var outerData = []; var innerData = []; var angle = 0; var nbPoints = path.length; var line = Vector3.Zero(); var nextLine = Vector3.Zero(); path[1].subtractToRef(path[0], line); if (nbPoints > 2 && closed) { path[2].subtractToRef(path[1], nextLine); for (let p = 0; p < nbPoints; p++) { angle = Math.PI - Math.acos(Vector3.Dot(line, nextLine) / (line.length() * nextLine.length())); direction = Vector3.Cross(line, nextLine).normalize().y; lineNormal = new Vector3(-line.z, 0, 1 * line.x).normalize(); line.normalize(); innerData[(p + 1) % nbPoints] = path[(p + 1) % nbPoints]; outerData[(p + 1) % nbPoints] = path[(p + 1) % nbPoints].add(lineNormal.scale(width)).add(line.scale(direction * width / Math.tan(angle / 2))); line = nextLine.clone(); path[(p + 3) % nbPoints].subtractToRef(path[(p + 2) % nbPoints], nextLine); } } else { lineNormal = new Vector3(-line.z, 0, 1 * line.x).normalize(); line.normalize(); innerData[0] = path[0]; outerData[0] = path[0].add(lineNormal.scale(width)); for (let p = 0; p < nbPoints - 2; p++) { path[p + 2].subtractToRef(path[p + 1], nextLine); angle = Math.PI - Math.acos(Vector3.Dot(line, nextLine) / (line.length() * nextLine.length())); direction = Vector3.Cross(line, nextLine).normalize().y; lineNormal = new Vector3(-line.z, 0, 1 * line.x).normalize(); line.normalize(); innerData[p + 1] = path[p + 1]; outerData[p + 1] = path[p + 1].add(lineNormal.scale(width)).add(line.scale(direction * width / Math.tan(angle / 2))); line = nextLine.clone(); } if (nbPoints > 2) { path[nbPoints - 1].subtractToRef(path[nbPoints - 2], line); lineNormal = new Vector3(-line.z, 0, 1 * line.x).normalize(); line.normalize(); innerData[nbPoints - 1] = path[nbPoints - 1]; outerData[nbPoints - 1] = path[nbPoints - 1].add(lineNormal.scale(width)); } else { innerData[1] = path[1]; outerData[1] = path[1].add(lineNormal.scale(width)); } } var maxX = Number.MIN_VALUE; var minX = Number.MAX_VALUE; var maxZ = Number.MIN_VALUE; var minZ = Number.MAX_VALUE; for (let p = 0; p < nbPoints; p++) { positions.push(innerData[p].x, innerData[p].y, innerData[p].z); maxX = Math.max(innerData[p].x, maxX); minX = Math.min(innerData[p].x, minX); maxZ = Math.max(innerData[p].z, maxZ); minZ = Math.min(innerData[p].z, minZ); } for (let p = 0; p < nbPoints; p++) { positions.push(outerData[p].x, outerData[p].y, outerData[p].z); maxX = Math.max(innerData[p].x, maxX); minX = Math.min(innerData[p].x, minX); maxZ = Math.max(innerData[p].z, maxZ); minZ = Math.min(innerData[p].z, minZ); } for (let i = 0; i < nbPoints - 1; i++) { indices.push(i, i + 1, nbPoints + i + 1); indices.push(i, nbPoints + i + 1, nbPoints + i); } if (nbPoints > 2 && closed) { indices.push(nbPoints - 1, 0, nbPoints); indices.push(nbPoints - 1, nbPoints, 2 * nbPoints - 1); } var normals = []; var uvs = []; if (standardUV) { for (let p = 0; p < positions.length; p += 3) { uvs.push((positions[p] - minX) / (maxX - minX), (positions[p + 2] - minZ) / (maxZ - minZ)); } } else { var flip = 0; var p1 = 0; var p2 = 0; var p3 = 0; var v0 = innerData[0]; var v1 = innerData[1].subtract(v0); var v2 = outerData[0].subtract(v0); var v3 = outerData[1].subtract(v0); var axis = v1.clone(); axis.normalize(); p1 = Vector3.Dot(axis, v1); p2 = Vector3.Dot(axis, v2); p3 = Vector3.Dot(axis, v3); var minX = Math.min(0, p1, p2, p3); var maxX = Math.max(0, p1, p2, p3); uvs[2 * indices[0]] = -minX / (maxX - minX); uvs[2 * indices[0] + 1] = 1; uvs[2 * indices[5]] = (p2 - minX) / (maxX - minX); uvs[2 * indices[5] + 1] = 0; uvs[2 * indices[1]] = (p1 - minX) / (maxX - minX); uvs[2 * indices[1] + 1] = 1; uvs[2 * indices[4]] = (p3 - minX) / (maxX - minX); uvs[2 * indices[4] + 1] = 0; for (let i = 6; i < indices.length; i += 6) { flip = (flip + 1) % 2; v0 = innerData[0]; v1 = innerData[1].subtract(v0); v2 = outerData[0].subtract(v0); v3 = outerData[1].subtract(v0); axis = v1.clone(); axis.normalize(); p1 = Vector3.Dot(axis, v1); p2 = Vector3.Dot(axis, v2); p3 = Vector3.Dot(axis, v3); var minX = Math.min(0, p1, p2, p3); var maxX = Math.max(0, p1, p2, p3); uvs[2 * indices[i + 1]] = flip + Math.cos(flip * Math.PI) * (p1 - minX) / (maxX - minX); uvs[2 * indices[i + 1] + 1] = 1; uvs[2 * indices[i + 4]] = flip + Math.cos(flip * Math.PI) * (p3 - minX) / (maxX - minX); uvs[2 * indices[i + 4] + 1] = 0; } } VertexData.ComputeNormals(positions, indices, normals); VertexData._ComputeSides(Mesh.DOUBLESIDE, positions, indices, normals, uvs); console.log(uvs); //Create a custom mesh var customMesh = new Mesh(name, scene); //Create a vertexData object var vertexData = new VertexData(); //Assign positions and indices to vertexData vertexData.positions = positions; vertexData.indices = indices; vertexData.normals = normals; vertexData.uvs = uvs; //Apply vertexData to custom mesh vertexData.applyToMesh(customMesh); return customMesh;};Lines in 3D
The lines and line system methods only produce a thin line, and you cannot vary the width. The best way to simulate a line with a given width in 3D is to use a tube.
Tube As LineParallel Lines in XoY Plane
Given the path of a central line, the function parallelLines will return the path points of two lines on either side of it.
var line = line2D("line", options, scene);| option | value | default value |
|---|---|---|
| path | (Vector3[]) array of Vector3 points forming the centre line of the line2D, REQUIRED | |
| innerWidth | (number) distance from central line | 0.5 |
| outerWidth | (number) distance from central line | 0.5 |
Just copy the code below if you want to use it.
Playground Example
Dotted Lines Parallel To Central LineParallel Line Code
import { Vector3 } from "@babylonjs/core/Maths/math.vector";var parallelLines = function (options, scene) { //Arrays for vertex positions and indices var positions = []; var indices = []; var normals = []; var innerWidth = options.innerWidth || 0.5; var outerWidth = options.outerWidth || 0.5; var path = options.path; var interiorIndex; //Arrays to hold wall corner data var innerBaseCorners = []; var outerBaseCorners = []; var outerData = []; var innerData = []; var angle = 0; var nbPoints = path.length; var line = Vector3.Zero(); var nextLine = Vector3.Zero(); path[1].subtractToRef(path[0], line); lineNormal = new Vector3(line.y, -1 * line.x, 0).normalize(); line.normalize(); innerData[0] = path[0].subtract(lineNormal.scale(innerWidth)); outerData[0] = path[0].add(lineNormal.scale(outerWidth)); for (let p = 0; p < nbPoints - 2; p++) { path[p + 2].subtractToRef(path[p + 1], nextLine); angle = Math.PI - Math.acos(Vector3.Dot(line, nextLine) / (line.length() * nextLine.length())); direction = Vector3.Cross(line, nextLine).normalize().z; lineNormal = new Vector3(line.y, -1 * line.x, 0).normalize(); line.normalize(); innerData[p + 1] = path[p + 1].subtract(lineNormal.scale(innerWidth)).subtract(line.scale(direction * innerWidth / Math.tan(angle / 2))); outerData[p + 1] = path[p + 1].add(lineNormal.scale(outerWidth)).add(line.scale(direction * outerWidth / Math.tan(angle / 2))); line = nextLine.clone(); } if (nbPoints > 2) { path[nbPoints - 1].subtractToRef(path[nbPoints - 2], line); lineNormal = new Vector3(line.y, -1 * line.x, 0).normalize(); line.normalize(); innerData[nbPoints - 1] = path[nbPoints - 1].subtract(lineNormal.scale(innerWidth)); outerData[nbPoints - 1] = path[nbPoints - 1].add(lineNormal.scale(outerWidth)); } else { innerData[1] = path[1].subtract(lineNormal.scale(innerWidth)); outerData[1] = path[1].add(lineNormal.scale(outerWidth)); } return { outerPoints: outerData, innerPoints: innerData };};