THREE.JS Level of Detail Texture Loading - three.js

Here is the small Three.JS sketch with THREE.LOD() objects.
As you can see there are 4 levels with their unique textures.
By now, all these textures are preloaded on start.
Is there any way to load 1, 2, 3 levels textures on the fly while zooming in?
Yes, I could do same thing without THREE.LOD() just by coding my own custom algorithm, which would generate/remove planes on zooming, but I'm very interesting in built-in THREE.LOD().
var folder = "http://vault.vkuchinov.co.uk/test/assets";
var levels = [0xF25E6B, 0x4EA6A6, 0x8FD9D1, 0xF2B29B, 0xF28E85];
var renderer, scene, camera, controls, loader, lod, glsl, uniforms;
renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearColor(0x000000);
document.body.appendChild(renderer.domElement);
scene = new THREE.Scene();
loader = new THREE.TextureLoader();
loader.crossOrigin = "";
camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 1, 51200);
camera.position.set(-2048, 2048, -2048);
controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.05;
controls.screenSpacePanning = false;
controls.minDistance = 8;
controls.maxDistance = 5120;
controls.maxPolarAngle = Math.PI / 2;
lod = new THREE.LOD();
lod.name = "0,0";
generateTiles(lod, 2048, 2048, 2048, 0, 0x00FFFF);
scene.add(lod);
animate();
function animate(){
controls.update();
renderer.render(scene, camera);
requestAnimationFrame(animate);
}
function generateTiles(parent_, width_, height_, zoom_, level_, hex_){
var id = parent_.name.split(",");
var colors = [0xFFFF00, 0xFF000, 0x00FF00, 0x0000FF, 0xFF00FF, 0xF0F0F0];
var group = new THREE.Group(), geometry, plane;
var dx = 0, dy = 0;
dy *= Math.pow(2, level_); dx *= Math.pow(2, level_);
var url = folder + "/textures/level" + level_ + "/" + id[0] + "_" + id[1] + ".jpg";
if(level_ < 3){
var uniforms = {
satellite: {
type: "t",
value: loader.load(url)
}
};
var glsl = new THREE.ShaderMaterial({
uniforms: uniforms,
vertexShader: document.getElementById("vertexTerrain").textContent,
fragmentShader: document.getElementById("fragmentTerrain").textContent,
lights: false,
fog: false,
transparent: true
});
glsl.extensions.derivatives = true;
geometry = new THREE.PlaneGeometry(width_, height_, 256, 256);
plane = new THREE.Mesh(geometry, glsl);
plane.rotation.set(-Math.PI / 2, 0, 0);
parent_.addLevel(plane, zoom_);
geometry = new THREE.PlaneGeometry(width_ / 2, height_ / 2, 128, 128);
var ix = (Number(id[0]) * 2);
var iy = (Number(id[1]) * 2);
var lod1 = new THREE.LOD();
var url1 = getURL(ix + "," + iy, width_ / 2, height_ / 2, zoom_ / 2, level_ + 1);
var uniforms1 = {
satellite: {
type: "t",
value: loader.load(url1)
}
};
var glsl1 = new THREE.ShaderMaterial({
uniforms: uniforms1,
vertexShader: document.getElementById("vertexTerrain").textContent,
fragmentShader: document.getElementById("fragmentTerrain").textContent,
lights: false,
fog: false,
transparent: true
});
glsl1.extensions.derivatives = true;
plane = new THREE.Mesh(geometry, glsl1);
plane.rotation.set(-Math.PI / 2, 0, 0);
lod1.addLevel(plane, zoom_ / 2);
lod1.position.set(-width_ / 4, 0, -height_ / 4);
lod1.name = ix + "," + iy;
group.add(lod1);
var lod2 = new THREE.LOD();
var url2 = getURL(ix + "," + (iy + 1), width_ / 2, height_ / 2, zoom_ / 2, level_ + 1);
var uniforms2 = {
satellite: {
type: "t",
value: loader.load(url2)
}
};
var glsl2 = new THREE.ShaderMaterial({
uniforms: uniforms2,
vertexShader: document.getElementById("vertexTerrain").textContent,
fragmentShader: document.getElementById("fragmentTerrain").textContent,
lights: false,
fog: false,
transparent: true
});
glsl2.extensions.derivatives = true;
plane = new THREE.Mesh(geometry, glsl2);
plane.rotation.set(-Math.PI / 2, 0, 0);
lod2.addLevel(plane, zoom_ / 2);
lod2.position.set(width_ / 4, 0, -height_ / 4);
lod2.name = ix + "," + (iy + 1);
group.add(lod2);
var lod3 = new THREE.LOD();
var url3 = getURL((ix + 1) + "," + iy, width_ / 2, height_ / 2, zoom_ / 2, level_ + 1);
var uniforms3 = {
satellite: {
type: "t",
value: loader.load(url3)
}
};
var glsl3 = new THREE.ShaderMaterial({
uniforms: uniforms3,
vertexShader: document.getElementById("vertexTerrain").textContent,
fragmentShader: document.getElementById("fragmentTerrain").textContent,
lights: false,
fog: false,
transparent: true
});
glsl3.extensions.derivatives = true;
plane = new THREE.Mesh(geometry, glsl3);
plane.rotation.set(-Math.PI / 2, 0, 0);
lod3.addLevel(plane, zoom_ / 2);
lod3.position.set(-width_ / 4, 0, height_ / 4);
lod3.name = (ix + 1) + "," + iy;
group.add(lod3);
var lod4 = new THREE.LOD();
var url4 = getURL((ix + 1) + "," + (iy + 1), width_ / 2, height_ / 2, zoom_ / 2, level_ + 1);
var uniforms4 = {
satellite: {
type: "t",
value: loader.load(url4)
}
};
var glsl4 = new THREE.ShaderMaterial({
uniforms: uniforms4,
vertexShader: document.getElementById("vertexTerrain").textContent,
fragmentShader: document.getElementById("fragmentTerrain").textContent,
lights: false,
fog: false,
transparent: true
});
glsl4.extensions.derivatives = true;
plane = new THREE.Mesh(geometry, glsl4);
plane.rotation.set(-Math.PI / 2, 0, 0);
lod4.addLevel(plane, zoom_ / 2);
lod4.position.set(width_ / 4, 0, height_ / 4);
lod4.name = (ix + 1) + "," + (iy + 1);
group.add(lod4);
parent_.addLevel(group, zoom_ / 2);
generateTiles(lod1, width_ / 2, height_ / 2, zoom_ / 2, level_ + 1, colors[level_]);
generateTiles(lod2, width_ / 2, height_ / 2, zoom_ / 2, level_ + 1, colors[level_]);
generateTiles(lod3, width_ / 2, height_ / 2, zoom_ / 2, level_ + 1, colors[level_]);
generateTiles(lod4, width_ / 2, height_ / 2, zoom_ / 2, level_ + 1, colors[level_]);
}
}
function getURL(name_, width_, height_, zoom_, level_){
var id = name_.split(",");
return folder + "/textures/level" + level_ + "/" + id[0] + "_" + id[1] + ".jpg";
}
body { margin: 0; }
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>GLSL Intersection</title>
<meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no" />
<script src="https://unpkg.com/three#0.116.0/build/three.min.js"></script>
<script src="https://unpkg.com/three#0.116.0/examples/js/controls/OrbitControls.js"></script>
</head>
<body>
<script id="vertexTerrain" type="x-shader/x-vertex">
uniform sampler2D satellite;
varying vec2 vUv;
void main() {
vUv = uv;
vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );
gl_Position = projectionMatrix * mvPosition;
}
</script>
<script id="fragmentTerrain" type="x-shader/x-fragment">
precision highp float;
precision highp int;
uniform sampler2D satellite;
varying vec2 vUv;
void main() {
gl_FragColor = texture2D(satellite, vUv);
}
</script>
</body>
</html>

Looking at the code you could scan your lods, see what their current level is, and check if it's loaded or not?
body {
margin: 0;
}
#c {
width: 100vw;
height: 100vh;
display: block;
}
<canvas id="c"></canvas>
<script type="module">
import * as THREE from 'https://threejsfundamentals.org/threejs/resources/threejs/r115/build/three.module.js';
import {OrbitControls} from 'https://threejsfundamentals.org/threejs/resources/threejs/r115/examples/jsm/controls/OrbitControls.js';
function main() {
const canvas = document.querySelector('#c');
const renderer = new THREE.WebGLRenderer({canvas});
const fov = 75;
const aspect = 2; // the canvas default
const near = 0.1;
const far = 500;
const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
camera.position.z = 2;
const controls = new OrbitControls(camera, canvas);
controls.update();
const scene = new THREE.Scene();
{
const color = 0xFFFFFF;
const intensity = 1;
const light = new THREE.DirectionalLight(color, intensity);
light.position.set(-1, 2, 4);
scene.add(light);
}
const numLevels = 4;
const lodInfos = [];
function createLod(pos) {
const lod = new THREE.LOD();
lod.position.set(...pos);
scene.add(lod);
for (let level = 0; level < numLevels; ++level) {
const obj = new THREE.Object3D();
lod.addLevel(obj, 3 + Math.pow(2, level));
}
lodInfos.push({
lod,
levels: [],
});
}
createLod([0, 0, 0]);
function scanLods() {
for (const {lod, levels} of lodInfos) {
const level = lod.getCurrentLevel();
if (!levels[level]) {
// this level is not loaded
levels[level] = true; // mark it as loaded
// load it
loadLodLevel(level, lod.levels[level].object);
// optimization: if all levels are loaded
// remove this from the lodInfos
}
}
}
function loadLodLevel(level, obj) {
// obviously I'd use some kind of data structure but just to
// get something working
let geometry;
let material;
switch(level) {
case 0:
geometry = new THREE.BoxBufferGeometry(1, 1, 1);
material = new THREE.MeshPhongMaterial({color: 'red'});
break;
case 1:
geometry = new THREE.SphereBufferGeometry(0.5, 12, 6);
material = new THREE.MeshPhongMaterial({color: 'yellow'});
break;
case 2:
geometry = new THREE.ConeBufferGeometry(0.5, 1, 12);
material = new THREE.MeshPhongMaterial({color: 'green'});
break;
case 3:
geometry = new THREE.CylinderBufferGeometry(0.5, 0.5, 1, 12);
material = new THREE.MeshPhongMaterial({color: 'purple'});
break;
}
const lodMesh = new THREE.Mesh(geometry, material);
obj.add(lodMesh);
}
function resizeRendererToDisplaySize(renderer) {
const canvas = renderer.domElement;
const width = canvas.clientWidth;
const height = canvas.clientHeight;
const needResize = canvas.width !== width || canvas.height !== height;
if (needResize) {
renderer.setSize(width, height, false);
}
return needResize;
}
function render(time) {
time *= 0.001;
if (resizeRendererToDisplaySize(renderer)) {
const canvas = renderer.domElement;
camera.aspect = canvas.clientWidth / canvas.clientHeight;
camera.updateProjectionMatrix();
}
scanLods();
renderer.render(scene, camera);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
}
main();
</script>
The solution above adds a THREE.Object3D for each lod and then childs a mesh to it when it's made visible.
You could also replace the THREE.Object3D so instead of
obj.add(lodMesh);
it would be something like
obj.levels[level].object = lodMesh;
obj.parent.add(lodMesh);
obj.parent.remove(obj);

Related

custom bufferGeometry with texture

I did tests on particle systems some time ago. because I'm working on it again at the moment. i want to use a texture but i only get black particles. i think the problem lies in the uv coordinates but i don't know how to use it in this case.
I have described in the code where I suspect the problem.
how do i access the texture coordinates in the shader in this case
var camera, controls, scene, renderer, container;
var PI = Math.PI;
var clock = new THREE.Clock();
var plane;
var MAX_Planes = 2000;
var velocity = [];
var geometry;
function init() {
renderer = new THREE.WebGLRenderer( { antialias: true, alpha: true} );
renderer.setPixelRatio( window.devicePixelRatio );
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
container = document.getElementById('container');
renderer.setSize(container.clientWidth, container.clientHeight);
container.appendChild( renderer.domElement );
var aspect = container.clientWidth / container.clientHeight;
scene = new THREE.Scene();
scene.background = new THREE.Color( 0x000000 );
camera = new THREE.PerspectiveCamera( 45, container.clientWidth / container.clientHeight, 1, 100000 );
camera.position.set(0, 0, 4000);
controls = new THREE.OrbitControls( camera, renderer.domElement );
controls.enableZoom = true;
controls.enabled = true;
controls.target.set(0, 0, 0);
//---------------shader---------------
var VertexShader = `
varying vec3 vUv;
uniform vec3 pos;
void main() {
vUv = uv;
vec4 finalPosition;
finalPosition = modelViewMatrix * vec4( pos, 1.0 );
finalPosition.xyz += vec3(position.x, position.y, 0.0);
finalPosition = projectionMatrix * finalPosition;
gl_Position = finalPosition;
}`;
var FragmentShader = `
varying vec3 vUv;
uniform sampler2D tDiffuse;
void main() {
gl_FragColor = vec4(texture2D(tDiffuse, vUv).rgb, 1.);
//gl_FragColor = vec4(1.0, 1.0, 0.8, 1.0); //just for testing
}`;
var loader = new THREE.TextureLoader();
var texture = loader.load( 'textures/test.jpg' ); //the texture is loaded correctly. I tested that with a box
var uniform = {
tDiffuse: {value: texture},
pos: { value: new THREE.Vector3(0,0,0) },
}
var material = new THREE.ShaderMaterial( {
uniforms: uniform,
vertexShader: VertexShader,
fragmentShader: FragmentShader,
transparent: true,
depthTest: false,
depthWrite: false
});
//-------------------------------------------------
//create a plane: points, normals, uv
const vertices = [
{ pos: [-20, -20, 0], norm: [ 0, 0, 1], uv: [0, 1], },
{ pos: [ 20, -20, 0], norm: [ 0, 0, 1], uv: [1, 1], },
{ pos: [-20, 20, 0], norm: [ 0, 0, 1], uv: [0, 0], },
{ pos: [ 20, 20, 0], norm: [ 0, 0, 1], uv: [1, 0], },
];
const numVertices = vertices.length;
const positionNumComponents = 3;
const normalNumComponents = 3;
const uvNumComponents = 2;
//arrays for buffergeometry
const positions = new Float32Array(numVertices * positionNumComponents * MAX_Planes);
const normals = new Float32Array(numVertices * normalNumComponents * MAX_Planes);
const uvs = new Float32Array(numVertices * uvNumComponents * MAX_Planes);
//fill arrays with vertices
var posPointer = 0;
var nrmPointer = 0;
var uvPointer = 0;
for(var i = 0; i <= MAX_Planes; i++) {
var posNdx = 0;
var nrmNdx = 0;
var uvNdx = 0;
for (const vertex of vertices) {
positions.set(vertex.pos, posNdx + posPointer);
normals.set(vertex.norm, nrmNdx + nrmPointer);
uvs.set(vertex.uv, uvNdx + uvPointer);
posNdx += positionNumComponents;
nrmNdx += normalNumComponents;
uvNdx += uvNumComponents;
}
posPointer = i * posNdx;
nrmPointer = i * nrmNdx;
uvPointer = i * uvNdx;
}
//create buffergeometry and assign the attribut arrays
geometry = new THREE.BufferGeometry();
geometry.setAttribute('position', new THREE.BufferAttribute(positions, positionNumComponents));
geometry.setAttribute('normal', new THREE.BufferAttribute(normals, normalNumComponents));
geometry.setAttribute('uv', new THREE.BufferAttribute(uvs, uvNumComponents));
var ndx = 0;
var indices = [];
//instead 6 vertices for the both triangles of a plane i used 4, so reindication is neccessary
for(var i = 0; i < MAX_Planes; i++){
indices.push(ndx, ndx + 1, ndx + 2, ndx + 2, ndx + 1, ndx + 3);
ndx += 4;
}
geometry.setIndex(indices);
var materials = [];
geometry.clearGroups();
for(var i = 0; i < MAX_Planes; i++){
geometry.addGroup( 6*i, 6*(i+1), i );
materials.push(material.clone());
}
plane = new THREE.Mesh(geometry, materials);
scene.add(plane);
//----------------------velocity---------------------------
for(var i = 0; i < MAX_Planes; i++){
velocity[i] = new THREE.Vector3(
Math.random()*2-1,
Math.random()*2-1,
Math.random()*2-1);
}
}//-------End init----------
function animate() {
requestAnimationFrame( animate );
render();
}//-------End animate----------
var loop = 0;
function render() {
loop = loop + 0.5;
for(var i = 0; i < MAX_Planes; i++){
var pos = new THREE.Vector3(0, 0, 0);
pos.x += velocity[i].x*loop;
pos.y += velocity[i].y*loop;
pos.z += velocity[i].z*loop;
plane.material[i].uniforms.pos.value = pos;
}
plane.geometry.attributes.position.needsUpdate = true;
plane.geometry.attributes.uv.needsUpdate = true;
camera.updateMatrixWorld();
camera.updateProjectionMatrix();
renderer.render(scene, camera);
}//-------End render----------
my initial assumption was wrong. I suspect the problem now in the regrouping. with the following line i see the texture
plane = new THREE.Mesh(geometry, material);
but that's no use to me. each group "one plane inside the bufferarray" should have its own material and for that i need the grouping but obviously i'm doing something wrong because i don't see anything with the "materials" array
ah, the problem is that the texture is not cloned when the material is cloned. cloning the material only works if I work purely with shader code without texture. if i want to have the texture i have to instantiate the material every time in the for loop

buffergeometry groups with individual shaders

I think showing a code example is the best. Therefore i have created a small example that shows the problem.
I want to create a buffer geometry in which each group has its own shader. although i always create a new instance in the material array, i cannot use the uniforms of the individual shaders independently. what i adjust in the uniform of one shader in the array always has the same effect on all other shaders in the material array.
Before i ask, i try to advance through research, but here i have reached a point where i can not get any further. Does anyone know why the individual shaders in the material array are dependent on each other and how to avoid this?
var camera, controls, scene, renderer, container;
var PI = Math.PI;
var clock = new THREE.Clock();
var plane;
var MAX_Planes = 100;
var velocity = [];
var geometry;
var test1, test2, test3;
function init() {
renderer = new THREE.WebGLRenderer( { antialias: true, alpha: true} );
renderer.setPixelRatio( window.devicePixelRatio );
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
//renderer.sortObjects = true;
container = document.getElementById('container');
renderer.setSize(container.clientWidth, container.clientHeight);
container.appendChild( renderer.domElement );
var aspect = container.clientWidth / container.clientHeight;
scene = new THREE.Scene();
scene.background = new THREE.Color( 0x000000 );
camera = new THREE.PerspectiveCamera( 45, container.clientWidth / container.clientHeight, 1, 100000 );
camera.position.set(0, 0, 4000);
controls = new THREE.OrbitControls( camera, renderer.domElement );
controls.enableZoom = true;
controls.enabled = true;
controls.target.set(0, 0, 0);
//---------------shaders---------------
var BasicVertexShader = `
void main() {
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}`;
var BasicFragmentShader = `
void main() {
gl_FragColor = vec4(vec3(1.0, 1.0, 0.8), 1.0);
}`;
var VertexShader = `
varying vec3 sPos;
uniform vec3 pos;
uniform float stretch;
void main() {
float rotation = 0.0;
sPos = position;
vec3 scale;
scale.x = 1.0*stretch;
scale.y = 1.0*stretch;
scale.z = 1.0*stretch;
vec3 alignedPosition = vec3(position.x * scale.x, position.y * scale.y, 0.0);
vec3 rotatedPosition;
rotatedPosition.x = cos(rotation) * alignedPosition.x - sin(rotation) * alignedPosition.y;
rotatedPosition.y = sin(rotation) * alignedPosition.x + cos(rotation) * alignedPosition.y;
rotatedPosition.z = alignedPosition.z;
vec4 finalPosition;
finalPosition = modelViewMatrix * vec4( 0, 0, 0, 1.0 );
finalPosition.xyz += rotatedPosition;
finalPosition = projectionMatrix * finalPosition;
gl_Position = finalPosition;
// gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}`;
var FragmentShader = `
varying vec3 sPos;
void main() {
vec3 nDistVec = normalize(sPos);
float dist = pow(sPos.x, 2.0) + pow(sPos.y, 2.0);
float magnitude = 1.0/dist * pow(4.0, 2.0);
// gl_FragColor = vec4(vec3(1.0, 1.0, 0.8), 1.0) * magnitude;
gl_FragColor = vec4(vec3(1.0, 1.0, 0.8), 1.0);
}`;
var uniform = {
stretch: {type: 'f', value: 1.0},
pos: { value: new THREE.Vector3(0,0,0) },
}
Shader = new THREE.ShaderMaterial( {
uniforms: uniform,
vertexShader: VertexShader,
fragmentShader: FragmentShader,
transparent: true,
depthTest: false,
depthWrite: false
});
//just for tests
var Shade = new THREE.ShaderMaterial( {
vertexShader: BasicVertexShader,
fragmentShader: BasicFragmentShader,
side:THREE.DoubleSide
});
//-------------------------------------------------
//create a plane: points, normals, uv
const vertices = [
{ pos: [-10, -10, 0], norm: [ 0, 0, 1], uv: [0, 1], },
{ pos: [ 10, -10, 0], norm: [ 0, 0, 1], uv: [1, 1], },
{ pos: [-10, 10, 0], norm: [ 0, 0, 1], uv: [0, 0], },
{ pos: [ 10, 10, 0], norm: [ 0, 0, 1], uv: [1, 0], },
];
const numVertices = vertices.length;
const positionNumComponents = 3;
const normalNumComponents = 3;
const uvNumComponents = 2;
//arrays for buffergeometry
const positions = new Float32Array(numVertices * positionNumComponents * MAX_Planes);
const normals = new Float32Array(numVertices * normalNumComponents * MAX_Planes);
const uvs = new Float32Array(numVertices * uvNumComponents * MAX_Planes);
//fill arrays with vertices
var posPointer = 0;
var nrmPointer = 0;
var uvPointer = 0;
for(var i = 0; i <= MAX_Planes; i++) {
var posNdx = 0;
var nrmNdx = 0;
var uvNdx = 0;
for (const vertex of vertices) {
positions.set(vertex.pos, posNdx + posPointer);
normals.set(vertex.norm, nrmNdx + nrmPointer);
uvs.set(vertex.uv, uvNdx + uvPointer);
posNdx += positionNumComponents;
nrmNdx += normalNumComponents;
uvNdx += uvNumComponents;
}
posPointer = i * posNdx;
nrmPointer = i * nrmNdx;
uvPointer = i * uvNdx;
}
//create buffergeometry and assign the attribut arrays
geometry = new THREE.BufferGeometry();
geometry.setAttribute('position', new THREE.BufferAttribute(positions, positionNumComponents));
geometry.setAttribute('normal', new THREE.BufferAttribute(normals, normalNumComponents));
geometry.setAttribute('uv', new THREE.BufferAttribute(uvs, uvNumComponents));
var ndx = 0;
var indices = [];
//instead 6 vertices for the both triangles of a plane i used 4, so reindication is neccessary
for(var i = 0; i < MAX_Planes; i++){
indices.push(ndx, ndx + 1, ndx + 2, ndx + 2, ndx + 1, ndx + 3);
ndx += 4;
}
geometry.setIndex(indices);
var materials = [];
geometry.clearGroups();
for(var i = 0; i < MAX_Planes; i++){
geometry.addGroup( 6*i, 6, i );
materials.push(Shader);
}
plane = new THREE.Mesh(geometry, materials);
scene.add(plane);
plane.material[0].uniforms.stretch.value = 2;
plane.material[1].uniforms.stretch.value = 3;
test1 = Object.keys(plane.material);
test2 = plane.material[0].uniforms.stretch.value; //why this returns 3 and not 2?
test3 = plane.material[1].uniforms.stretch.value;
//the goal is that each group has its own Shader without effecting the other ones
//----------------------velocity---------------------------
for(var i = 0; i < MAX_Planes; i++){
velocity[i] = new THREE.Vector3(
Math.random()*2-1,
Math.random()*2-1,
Math.random()*2-1);
}
}//-------End init----------
function animate() {
requestAnimationFrame( animate );
render();
}//-------End animate----------
function render() {
document.getElementById("demo1").innerHTML = test1;
document.getElementById("demo2").innerHTML = test2;
document.getElementById("demo3").innerHTML = test3;
for(var i = 0; i < MAX_Planes; i++){
for(var j = 0; j < 4; j++){
plane.geometry.attributes.position.array[i*12+3*j] = plane.geometry.attributes.position.array[i*12+3*j] + velocity[i].x;
plane.geometry.attributes.position.array[i*12+3*j+1] = plane.geometry.attributes.position.array[i*12+3*j+1] + velocity[i].y;
plane.geometry.attributes.position.array[i*12+3*j+2] = plane.geometry.attributes.position.array[i*12+3*j+2] + velocity[i].z;
}
}
plane.geometry.attributes.position.needsUpdate = true;
camera.updateMatrixWorld();
camera.updateProjectionMatrix();
renderer.render(scene, camera);
}//-------End render----------
You are pushing references to a single ShaderMaterial into every index of your materials array.
materials.push(Shader);
Because each index is a reference, changing the properties on the object in one index will naturally change the object in all other indices.
If you want each group to have its own properties, then you will need to provide a unique material to each index. You can still do this by only creating one original definition, then using Material.clone to create copies.
for(var i = 0; i < MAX_Planes; i++){
geometry.addGroup( 6*i, 6, i );
materials.push( Shader.clone() ); // creates a unique copy for each index
}
this did not leave me in peace and it occurred to me that if i prealocate the buffer geometry, i would have to do that with the shaders for each group. I would now have made this much more complicated than with ".clone ()". good that i checked again. Your advice works wonderfully. I have corrected the positioning update and that is exactly the result that i had in mind. Here is the customized code for anyone interested. I will now combine this with my particle emitter.
var camera, controls, scene, renderer, container;
var PI = Math.PI;
var clock = new THREE.Clock();
var plane;
var MAX_Planes = 2000;
var velocity = [];
var geometry;
function init() {
renderer = new THREE.WebGLRenderer( { antialias: true, alpha: true} );
renderer.setPixelRatio( window.devicePixelRatio );
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
//renderer.sortObjects = true;
container = document.getElementById('container');
renderer.setSize(container.clientWidth, container.clientHeight);
container.appendChild( renderer.domElement );
var aspect = container.clientWidth / container.clientHeight;
scene = new THREE.Scene();
scene.background = new THREE.Color( 0x000000 );
camera = new THREE.PerspectiveCamera( 45, container.clientWidth / container.clientHeight, 1, 100000 );
camera.position.set(0, 0, 4000);
controls = new THREE.OrbitControls( camera, renderer.domElement );
controls.enableZoom = true;
controls.enabled = true;
controls.target.set(0, 0, 0);
//---------------shader---------------
var VertexShader = `
varying vec3 sPos;
uniform vec3 pos;
uniform float stretch;
void main() {
float rotation = 0.0;
sPos = position;
vec3 scale;
scale.x = 1.0*stretch;
scale.y = 1.0*stretch;
scale.z = 1.0*stretch;
vec3 alignedPosition = vec3(position.x * scale.x, position.y * scale.y, 0.0);
vec3 rotatedPosition;
rotatedPosition.x = cos(rotation) * alignedPosition.x - sin(rotation) * alignedPosition.y;
rotatedPosition.y = sin(rotation) * alignedPosition.x + cos(rotation) * alignedPosition.y;
rotatedPosition.z = alignedPosition.z;
vec4 finalPosition;
finalPosition = modelViewMatrix * vec4( pos, 1.0 );
finalPosition.xyz += rotatedPosition;
finalPosition = projectionMatrix * finalPosition;
gl_Position = finalPosition;
}`;
var FragmentShader = `
varying vec3 sPos;
void main() {
vec3 nDistVec = normalize(sPos);
float dist = pow(sPos.x, 2.0) + pow(sPos.y, 2.0);
float magnitude = 1.0/dist * pow(3.0, 2.0);
float alpha = 1.0;
if(magnitude < 0.01){
alpha = 0.0;
}
gl_FragColor = vec4(vec3(1.0, 1.0, 0.8), alpha) * magnitude;
// gl_FragColor = vec4(vec3(1.0, 1.0, 0.8), 1.0);
}`;
var uniform = {
stretch: {type: 'f', value: 1.0},
pos: { value: new THREE.Vector3(0,0,0) },
}
var Shader = new THREE.ShaderMaterial( {
uniforms: uniform,
vertexShader: VertexShader,
fragmentShader: FragmentShader,
transparent: true,
depthTest: false,
depthWrite: false
});
//-------------------------------------------------
//create a plane: points, normals, uv
const vertices = [
{ pos: [-20, -20, 0], norm: [ 0, 0, 1], uv: [0, 1], },
{ pos: [ 20, -20, 0], norm: [ 0, 0, 1], uv: [1, 1], },
{ pos: [-20, 20, 0], norm: [ 0, 0, 1], uv: [0, 0], },
{ pos: [ 20, 20, 0], norm: [ 0, 0, 1], uv: [1, 0], },
];
const numVertices = vertices.length;
const positionNumComponents = 3;
const normalNumComponents = 3;
const uvNumComponents = 2;
//arrays for buffergeometry
const positions = new Float32Array(numVertices * positionNumComponents * MAX_Planes);
const normals = new Float32Array(numVertices * normalNumComponents * MAX_Planes);
const uvs = new Float32Array(numVertices * uvNumComponents * MAX_Planes);
//fill arrays with vertices
var posPointer = 0;
var nrmPointer = 0;
var uvPointer = 0;
for(var i = 0; i <= MAX_Planes; i++) {
var posNdx = 0;
var nrmNdx = 0;
var uvNdx = 0;
for (const vertex of vertices) {
positions.set(vertex.pos, posNdx + posPointer);
normals.set(vertex.norm, nrmNdx + nrmPointer);
uvs.set(vertex.uv, uvNdx + uvPointer);
posNdx += positionNumComponents;
nrmNdx += normalNumComponents;
uvNdx += uvNumComponents;
}
posPointer = i * posNdx;
nrmPointer = i * nrmNdx;
uvPointer = i * uvNdx;
}
//create buffergeometry and assign the attribut arrays
geometry = new THREE.BufferGeometry();
geometry.setAttribute('position', new THREE.BufferAttribute(positions, positionNumComponents));
geometry.setAttribute('normal', new THREE.BufferAttribute(normals, normalNumComponents));
geometry.setAttribute('uv', new THREE.BufferAttribute(uvs, uvNumComponents));
var ndx = 0;
var indices = [];
//instead 6 vertices for the both triangles of a plane i used 4, so reindication is neccessary
for(var i = 0; i < MAX_Planes; i++){
indices.push(ndx, ndx + 1, ndx + 2, ndx + 2, ndx + 1, ndx + 3);
ndx += 4;
}
geometry.setIndex(indices);
var materials = [];
geometry.clearGroups();
for(var i = 0; i < MAX_Planes; i++){
geometry.addGroup( 6*i, 6, i );
materials.push(Shader.clone());
}
plane = new THREE.Mesh(geometry, materials);
scene.add(plane);
//----------------------velocity---------------------------
for(var i = 0; i < MAX_Planes; i++){
velocity[i] = new THREE.Vector3(
Math.random()*2-1,
Math.random()*2-1,
Math.random()*2-1);
}
}//-------End init----------
function animate() {
requestAnimationFrame( animate );
render();
}//-------End animate----------
var loop = 0;
function render() {
loop = loop + 0.5;
for(var i = 0; i < MAX_Planes; i++){
var pos = new THREE.Vector3(0, 0, 0);
pos.x += velocity[i].x*loop;
pos.y += velocity[i].y*loop;
pos.z += velocity[i].z*loop;
plane.material[i].uniforms.pos.value = pos;
}
plane.geometry.attributes.position.needsUpdate = true;
camera.updateMatrixWorld();
camera.updateProjectionMatrix();
renderer.render(scene, camera);
}//-------End render----------
I set 2000 rectangles here and am pleasantly surprised at how smoothly the code runs. Even with 5000 rectangles everything went smoothly, even though each rectangle has its own shader. buffer geometries are really cool.
Thank you TheJim01
without your advice i would have preinitialized all shaders in a for loop. Such as vertexshader[i], fragmentshader[i], uniforms[i], Shader[i]. Using
".clone()" is of course much better 👍

THREE.JS UV map over entire THREE.BufferGeometry

Here is a very simple Three.JS sketch, resulting following render:
As you may see this THREE.BufferGeometry is a 8x8 matrix and by setting UVs in this way:
for(var i = 0; i < points.length; i += 4){ quad_uvs.push(...[0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0]); }
uvs = new Float32Array(quad_uvs);
geometry.setAttribute( 'uv', new THREE.BufferAttribute( uvs, 2 ) );
It applies texture to each 8x8 segment, however I need to place texture over the entire geometry.
How I could update uv mapping for achieving it?
var img = "data:image/jpeg;base64,/9j/4AAQSkZJRgABAgAAAQABAAD/2wBDAAMCAgMCAgMDAwMEAwMEBQgFBQQEBQoHBwYIDAoMDAsKCwsNDhIQDQ4RDgsLEBYQERMUFRUVDA8XGBYUGBIUFRT/2wBDAQMEBAUEBQkFBQkUDQsNFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBT/wAARCAIAAgADASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwDwSiiivxo/0tCiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiuq8I/Crxj49Xd4d8MarrEWcGa0tHeIH3fG0fiaqMZTdoq7Ma1elhoOpWmoxXVtJfezlaK9gf9kT4wRweafA1+VxnCywlv8AvkPn9K8/8V/D3xP4EmWPxF4e1PRGY7UN/avErn/ZLDDfhWs6Famrzg16pnBhs2y/GT9nhsRCcu0ZRb+5M5+iiisD1QooooAKKKKACiiigAooooAKKKKACitrwx4J8Q+Nbprbw/oeo63OuNyafavMVz0ztBx+Nei2/wCyL8YLmDzk8C34TGcSSRI3/fLOD+lbQoVaivCDfomeZic1y/BS5MViIQfaUop/i0eQUV2Pi34O+OPAcTTeIPCer6Vbr1uZ7RxCP+2gG39a46s5QlB2krM66GIo4qHtKE1OPdNNfegoooqToCiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACii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AUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQB//Z";
var renderer, scene, camera, controls, glsl;
renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearColor(0x000000);
document.body.appendChild(renderer.domElement);
scene = new THREE.Scene();
loader = new THREE.TextureLoader();
loader.crossOrigin = "";
camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 1, 51200);
camera.position.set(-2048, 2048, -2048);
controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.05;
controls.screenSpacePanning = false;
controls.minDistance = 8;
controls.maxDistance = 5120;
camera.position.set(208.48355078304965, 45.28894677815297, 310.34089790619583);
controls.target.set(0, 0, 0);
var points = [], indices = [], quad_uvs = [];
for(var x = -512; x < 512; x += 128){
for(var z = -512; z < 512; z += 128){
points.push(new THREE.Vector3(x, 0, z));
points.push(new THREE.Vector3(x + 128 , 0, z));
points.push(new THREE.Vector3(x + 128, 0, z + 128));
points.push(new THREE.Vector3(x, 0, z + 128));
}
}
for(var i = 0; i < points.length; i += 4){
indices.push(i, i + 1, i + 2);
indices.push(i + 2, i + 3, i);
}
var geometry = new THREE.BufferGeometry().setFromPoints(points);
for(var i = 0; i < points.length; i += 4){ quad_uvs.push(...[0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0]); }
uvs = new Float32Array(quad_uvs);
geometry.setAttribute( 'uv', new THREE.BufferAttribute( uvs, 2 ) );
geometry.setIndex( indices );
geometry.computeVertexNormals();
glsl = new THREE.ShaderMaterial( {
uniforms: {
textureA: { type: 't', value: new THREE.TextureLoader().load(img) }
},
vertexShader: document.getElementById( "vertexShader" ).textContent,
fragmentShader: document.getElementById( "fragmentShader" ).textContent
} );
glsl.side = THREE.DoubleSide;
var plane = new THREE.Mesh(geometry, glsl);
scene.add(plane);
animate();
function animate(){
controls.update();
renderer.render(scene, camera);
requestAnimationFrame(animate);
}
body { margin: 0; }
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no" />
<script src="https://unpkg.com/three#0.116.0/build/three.min.js"></script>
<script src="https://unpkg.com/three#0.116.0/examples/js/controls/OrbitControls.js"></script>
</head>
<body>
<script id="vertexShader" type="x-shader/x-vertex">
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
</script>
<script id="fragmentShader" type="x-shader/x-fragment">
varying vec2 vUv;
uniform sampler2D textureA;
void main() {
gl_FragColor = texture2D(textureA, vUv);
}
</script>
</body>
</html>
I've updated your code with a possible solution. You will see that the geometry is now generated slightly differently. The most important part is that you have to generate your texture coordinates in the range [0,1] across the entire geometry and not just for each quad.
var img = "data:image/jpeg;base64,/9j/4AAQSkZJRgABAgAAAQABAAD/2wBDAAMCAgMCAgMDAwMEAwMEBQgFBQQEBQoHBwYIDAoMDAsKCwsNDhIQDQ4RDgsLEBYQERMUFRUVDA8XGBYUGBIUFRT/2wBDAQMEBAUEBQkFBQkUDQsNFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBT/wAARCAIAAgADASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwDwSiiivxo/0tCiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiuq8I/Crxj49Xd4d8MarrEWcGa0tHeIH3fG0fiaqMZTdoq7Ma1elhoOpWmoxXVtJfezlaK9gf9kT4wRweafA1+VxnCywlv8AvkPn9K8/8V/D3xP4EmWPxF4e1PRGY7UN/avErn/ZLDDfhWs6Famrzg16pnBhs2y/GT9nhsRCcu0ZRb+5M5+iiisD1QooooAKKKKACiiigAooooAKKKKACitrwx4J8Q+Nbprbw/oeo63OuNyafavMVz0ztBx+Nei2/wCyL8YLmDzk8C34TGcSSRI3/fLOD+lbQoVaivCDfomeZic1y/BS5MViIQfaUop/i0eQUV2Pi34O+OPAcTTeIPCer6Vbr1uZ7RxCP+2gG39a46s5QlB2krM66GIo4qHtKE1OPdNNfegoooqToCiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACii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AUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQB//Z";
var renderer, scene, camera, controls, glsl;
renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearColor(0x000000);
document.body.appendChild(renderer.domElement);
scene = new THREE.Scene();
loader = new THREE.TextureLoader();
loader.crossOrigin = "";
camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 1, 51200);
camera.position.set(-2048, 2048, -2048);
controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.05;
controls.screenSpacePanning = false;
controls.minDistance = 8;
controls.maxDistance = 5120;
camera.position.set(208.48355078304965, 45.28894677815297, 310.34089790619583);
controls.target.set(0, 0, 0);
var points = [], indices = [], uvs = [];
var step = 128;
for(var x = -512; x < 512; x += step){
for(var z = -512; z < 512; z += step){
points.push(x, 0, z);
points.push(x + step , 0, z);
points.push(x + step, 0, z + step);
points.push(x, 0, z + step);
var u = x + 512;
var v = z + 512;
uvs.push(u / 1024, v / 1024);
uvs.push((u + step) / 1024 , v / 1024);
uvs.push((u + step) / 1024 ,(v + step) / 1024);
uvs.push(u / 1024, (v + step) / 1024);
}
}
var geometry = new THREE.BufferGeometry();
geometry.setAttribute( 'position', new THREE.Float32BufferAttribute( points, 3 ) );
geometry.setAttribute( 'uv', new THREE.Float32BufferAttribute( uvs, 2 ) );
var positionAttribute = geometry.getAttribute( 'position' );
for(var i = 0; i < positionAttribute.count; i += 4){
indices.push(i, i + 1, i + 2);
indices.push(i + 2, i + 3, i);
}
geometry.setIndex( indices );
geometry.computeVertexNormals();
glsl = new THREE.ShaderMaterial( {
uniforms: {
textureA: { type: 't', value: new THREE.TextureLoader().load(img) }
},
vertexShader: document.getElementById( "vertexShader" ).textContent,
fragmentShader: document.getElementById( "fragmentShader" ).textContent
} );
glsl.side = THREE.DoubleSide;
var plane = new THREE.Mesh(geometry, glsl);
scene.add(plane);
animate();
function animate(){
controls.update();
renderer.render(scene, camera);
requestAnimationFrame(animate);
}
body { margin: 0; }
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no" />
<script src="https://unpkg.com/three#0.116.0/build/three.min.js"></script>
<script src="https://unpkg.com/three#0.116.0/examples/js/controls/OrbitControls.js"></script>
</head>
<body>
<script id="vertexShader" type="x-shader/x-vertex">
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
</script>
<script id="fragmentShader" type="x-shader/x-fragment">
varying vec2 vUv;
uniform sampler2D textureA;
void main() {
gl_FragColor = texture2D(textureA, vUv);
}
</script>
</body>
</html>

How to show only edge lines in three.js?

I want to show lines only on the edges. Here I have included my output model which I tried using edgeGeometry and LinebasicMaterial. I want to remove the inner edge lines and show only outline edges
You can use EdgesGeometry
You pass it some other geometry and a threshold angle
// only show edges with 15 degrees or more angle between faces
const thresholdAngle = 15;
const lineGeometry = new THREE.EdgesGeometry(geometry, thresholdAngle));
'use strict';
/* global THREE */
function main() {
const canvas = document.querySelector('#c');
const renderer = new THREE.WebGLRenderer({canvas});
const fov = 40;
const aspect = 2; // the canvas default
const near = 0.1;
const far = 1000;
const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
camera.position.z = 20;
const scene = new THREE.Scene();
scene.background = new THREE.Color(0xAAAAAA);
let solidMesh;
{
const color = 0xFFFFFF;
const intensity = 1;
const light = new THREE.DirectionalLight(color, intensity);
light.position.set(-1, 2, 4);
scene.add(light);
}
{
const color = 0xFFFFFF;
const intensity = 1;
const light = new THREE.DirectionalLight(color, intensity);
light.position.set(1, -2, -4);
scene.add(light);
}
const objects = [];
const spread = 15;
function addObject(x, y, obj) {
obj.position.x = x * spread;
obj.position.y = y * spread;
scene.add(obj);
objects.push(obj);
}
function createMaterial() {
const material = new THREE.MeshPhongMaterial({
side: THREE.DoubleSide,
});
const hue = Math.random();
const saturation = 1;
const luminance = .5;
material.color.setHSL(hue, saturation, luminance);
return material;
}
function addSolidGeometry(x, y, geometry) {
const mesh = new THREE.Mesh(geometry, createMaterial());
addObject(x, y, mesh);
return mesh;
}
function addLineGeometry(x, y, geometry) {
const material = new THREE.LineBasicMaterial({color: 0x000000});
const mesh = new THREE.LineSegments(geometry, material);
addObject(x, y, mesh);
return mesh;
}
{
const shape = new THREE.Shape();
const x = -2.5;
const y = -5;
shape.moveTo(x + 2.5, y + 2.5);
shape.bezierCurveTo(x + 2.5, y + 2.5, x + 2, y, x, y);
shape.bezierCurveTo(x - 3, y, x - 3, y + 3.5, x - 3, y + 3.5);
shape.bezierCurveTo(x - 3, y + 5.5, x - 1.5, y + 7.7, x + 2.5, y + 9.5);
shape.bezierCurveTo(x + 6, y + 7.7, x + 8, y + 4.5, x + 8, y + 3.5);
shape.bezierCurveTo(x + 8, y + 3.5, x + 8, y, x + 5, y);
shape.bezierCurveTo(x + 3.5, y, x + 2.5, y + 2.5, x + 2.5, y + 2.5);
const extrudeSettings = {
steps: 2,
depth: 2,
bevelEnabled: true,
bevelThickness: 1,
bevelSize: 1,
bevelSegments: 2,
};
const geometry = new THREE.ExtrudeBufferGeometry(shape, extrudeSettings);
solidMesh = addSolidGeometry(0, 0, geometry);
const thresholdAngle = 15;
addLineGeometry(0, 0, new THREE.EdgesGeometry(geometry, thresholdAngle));
}
function resizeRendererToDisplaySize(renderer) {
const canvas = renderer.domElement;
const width = canvas.clientWidth;
const height = canvas.clientHeight;
const needResize = canvas.width !== width || canvas.height !== height;
if (needResize) {
renderer.setSize(width, height, false);
}
return needResize;
}
function render(time) {
time *= 0.001;
if (resizeRendererToDisplaySize(renderer)) {
const canvas = renderer.domElement;
camera.aspect = canvas.clientWidth / canvas.clientHeight;
camera.updateProjectionMatrix();
}
solidMesh.visible = (time | 0) % 2 !== 0;
objects.forEach((obj, ndx) => {
const speed = .1 + ndx * .0;
const rot = time * speed;
obj.rotation.x = rot;
obj.rotation.y = rot;
});
renderer.render(scene, camera);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
}
main();
body {
margin: 0;
}
#c {
width: 100vw;
height: 100vh;
display: block;
}
<canvas id="c"></canvas>
<script src="https://threejsfundamentals.org/threejs/resources/threejs/r108/build/three.min.js"></script>

Draw a curve using multi plane in A-Frame and Three.js

I'm creating a A-Frame Application (with Three.js) draw a road using THREE.CatmullRomCurve3.
The application's concept is draw a road which connects multi points, I want to draw the road with exact width (eg: a road with 3 meters width), I'm using THREE.PlaneGeometry to connect 2 points.
My result, and my code snippet
var scene, camera, renderer;
var cube;
var controls;
function initScene() {
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(80, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.z = 30;
renderer = new THREE.WebGLRenderer({
alpha: true
});
renderer.setSize(window.innerWidth, window.innerHeight);
controls = new THREE.OrbitControls(camera);
controls.update();
document.body.appendChild(renderer.domElement);
}
function render() {
requestAnimationFrame(render);
// required if controls.enableDamping or controls.autoRotate are set to true
controls.update();
renderer.render(scene, camera);
}
function drawRoadByLine() {
//Create a closed wavey loop
var curve = new THREE.CatmullRomCurve3([
new THREE.Vector3(-10, 0, 10),
new THREE.Vector3(-5, 5, 5),
new THREE.Vector3(0, 0, 0),
new THREE.Vector3(5, -5, 5),
new THREE.Vector3(10, 0, 10)
]);
var points = curve.getPoints(50);
var geometry = new THREE.BufferGeometry().setFromPoints(points);
var material = new THREE.LineBasicMaterial({
color: 0xff0000
});
// Create the final object to add to the scene
var curveObject = new THREE.Line(geometry, material);
scene.add(curveObject);
}
function drawRoadByPlane() {
//Create a closed wavey loop
var curve = new THREE.CatmullRomCurve3([
new THREE.Vector3(-10, 0, 10),
new THREE.Vector3(-5, 5, 5),
new THREE.Vector3(0, 0, 0),
new THREE.Vector3(5, -5, 5),
new THREE.Vector3(10, 0, 10)
]);
var points = curve.getPoints(50);
var group = new THREE.Group();
var currentPos;
var nextPos;
var distance;
var plane;
var rotationMatrix;
for (var i = 0; i < points.length - 1; i++) {
currentPos = new THREE.Vector3(points[i].x, points[i].y, points[i].z);
nextPos = new THREE.Vector3(points[i + 1].x, points[i + 1].y, points[i + 1].z);
distance = currentPos.distanceTo(nextPos);
plane = createPlane(distance);
plane.position.set(currentPos.x, currentPos.y, currentPos.z);
// rotationMatrix = getRotationMatrix(currentPos, nextPos);
// plane.applyMatrix(rotationMatrix);
group.add(plane);
}
scene.add(group);
}
function createPlane(distance, position) {
var geometry = new THREE.PlaneGeometry(1, distance);
// Dummy random color each plane, true color is red (0xff0000)
var color = Math.floor((Math.random() * 0xffffff) + 1);
var material = new THREE.MeshBasicMaterial({
color: color,
side: THREE.DoubleSide
});
var plane = new THREE.Mesh(geometry, material);
return plane;
}
function getRotationMatrix(v1, v2) {
var quaternion = new THREE.Quaternion();
quaternion.setFromUnitVectors(v1, v2);
var matrix = new THREE.Matrix4();
matrix.makeRotationFromQuaternion(quaternion);
return matrix;
}
initScene();
drawRoadByPlane();
render();
body {
margin: 0;
width: 100%;
height: 100%;
overflow: hidden;
background-color: #000000;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/94/three.min.js"></script>
<script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>
My now issue is how to make the planes display continously (without space between 2 plane). I think I need rotate each plane, but I don't know to to calculate right rotation between 2 points.
Update:
I've just changed my solution to use THREE.Face3 instead of THREE.PlaneGeometry.
Here is my code snippet
var scene, camera, renderer;
var cube;
var controls;
function initScene() {
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(80, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.z = 30;
renderer = new THREE.WebGLRenderer({ alpha: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.update();
}
function render() {
requestAnimationFrame(render);
renderer.render(scene, camera);
}
function draw() {
var dummyPoints = [
new THREE.Vector3(-10, 0, 10),
new THREE.Vector3(-5, 5, 5),
new THREE.Vector3(0, 0, 0),
new THREE.Vector3(5, -5, 5),
new THREE.Vector3(10, 0, 10)
];
//Create a closed wavey loop
var curve = new THREE.CatmullRomCurve3(dummyPoints);
var material = new THREE.MeshBasicMaterial({ vertexColors: THREE.FaceColors, side: THREE.DoubleSide });
//create a triangular geometry
var points = curve.getPoints(100);
var roadPoints = [];
var length = points.length;
for (var i = 0; i < length - 1; i++) {
roadPoints = roadPoints.concat(extractRoadPoint(points[i], points[i + 1]));
}
roadPoints = roadPoints.concat(extractRoadPoint(points[length - 1], points[length - 2]));
var geometry = new THREE.Geometry().setFromPoints(roadPoints);
// var face = new THREE.Face3(0, 1, 2);
//add the face to the geometry's faces array
// geometry.faces.push(face);
for (var i = 0; i < roadPoints.length - 2; i++) {
var face = new THREE.Face3(i, i + 1, i + 2);
geometry.faces.push(face);
face.color.set(new THREE.Color(Math.random() * 0xffffff - 1));
}
//the face normals and vertex normals can be calculated automatically if not supplied above
geometry.computeFaceNormals();
geometry.computeVertexNormals();
scene.add(new THREE.Mesh(geometry, material));
}
function extractRoadPoint(point1, point2) {
var result = [];
var vector = {
x: point2.x - point1.x,
y: point2.y - point1.y,
z: point2.z - point1.z,
}
var uOxz = {
x: 0,
y: 1,
z: 0
};
var vectorVertices = {
x: vector.y * uOxz.z - vector.z * uOxz.y,
y: vector.z * uOxz.x - vector.x * uOxz.z,
z: vector.x * uOxz.y - vector.y * uOxz.x,
};
var t = Math.sqrt(1 * 1 / (vectorVertices.x * vectorVertices.x + vectorVertices.y * vectorVertices.y + vectorVertices.z * vectorVertices.z));
var sidePoint11 = {
x: point1.x + vectorVertices.x * t,
y: point1.y + vectorVertices.y * t,
z: point1.z + vectorVertices.z * t,
}
var sidePoint12 = {
x: point1.x - vectorVertices.x * t,
y: point1.y - vectorVertices.y * t,
z: point1.z - vectorVertices.z * t,
}
var sidePoint21 = {
x: point2.x + vectorVertices.x * t,
y: point2.y + vectorVertices.y * t,
z: point2.z + vectorVertices.z * t,
}
var sidePoint22 = {
x: point2.x - vectorVertices.x * t,
y: point2.y - vectorVertices.y * t,
z: point2.z - vectorVertices.z * t,
}
return [sidePoint11, sidePoint12, sidePoint21, sidePoint22];
}
initScene();
draw();
render();
body {
margin: 0;
width: 100%;
height: 100%;
overflow: hidden;
background-color: #000000;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/110/three.min.js"></script>
<script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>

Resources