colors for lines in three.js - javascript

I have such code:
const materialLinearInterpolation = new THREE.LineBasicMaterial({ color: 0x0000c9, linewidth: 1 })
const pointsLinearInterpolation = []
for (var i = 0; i < this.pointsCoordinatesLinearInterpolation.length; i++) {
pointsLinearInterpolation.push(
new THREE.Vector3(
this.pointsCoordinatesLinearInterpolation[i].x,
this.pointsCoordinatesLinearInterpolation[i].y,
this.pointsCoordinatesLinearInterpolation[i].z
)
)
}
const geometryLinearInterpolation = new THREE.BufferGeometry().setFromPoints(pointsLinearInterpolation)
this.lineLinearInterpolation = new THREE.Line(geometryLinearInterpolation, materialLinearInterpolation)
this.scene.add(this.lineLinearInterpolation)
I need to use multiple colors for lines, is it possible in such configuration? If not possible, how can I draw several connected lines with different colors

If you want a distinct color per line segment, you have to use THREE.LineSegment and vertex colors like in the following live example:
let camera, scene, renderer;
init();
render();
function init() {
camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.01, 10);
camera.position.z = 3;
scene = new THREE.Scene();
const geometry = new THREE.BufferGeometry().setFromPoints([new THREE.Vector3(1, 0, 0), new THREE.Vector3(0, 0, 0), new THREE.Vector3(0, 0, 0), new THREE.Vector3(-1, 0, 0)]);
const colors = [
255, 255, 0, 255, 255, 0,
0, 255, 255, 0, 255, 255
];
geometry.setAttribute('color', new THREE.Uint8BufferAttribute(colors, 3, true));
const material = new THREE.LineBasicMaterial({
vertexColors: true
});
const lines = new THREE.LineSegments(geometry, material);
scene.add(lines);
renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
}
function render() {
renderer.render(scene, camera);
}
body {
margin: 0;
}
<script src="https://cdn.jsdelivr.net/npm/three#0.130.1/build/three.min.js"></script>
Using THREE.Line instead will produce a color gradient between line parts which is often not wanted.

WebGL2.0 only solution, using flat for vColor varying by modifying the shaders with onBeforeCompile:
let scene = new THREE.Scene();
let camera = new THREE.PerspectiveCamera(60, innerWidth/innerHeight, 1, 100);
camera.position.set(0, 0, 10);
let renderer = new THREE.WebGLRenderer();
renderer.setSize(innerWidth, innerHeight);
document.body.appendChild(renderer.domElement);
let grid = new THREE.GridHelper(10, 10, 0x7f7f7f, 0x444444);
grid.rotation.x = - Math.PI * 0.5;
scene.add(grid);
const vertCount = 11;
let c = [];
let g = new THREE.BufferGeometry().setFromPoints(new Array(vertCount).fill(0).map((p,idx) => {
c.push(idx / (vertCount - 1), idx % 2, idx % 3);
return new THREE.Vector3(((vertCount - 1) * -0.5) + idx, (Math.random() - 0.5) * 10, 0);
}))
g.setAttribute("color", new THREE.Float32BufferAttribute(c, 3));
let m = new THREE.LineBasicMaterial({
vertexColors: true,
onBeforeCompile: shader => {
shader.vertexShader = shader.vertexShader.replace(
`#include <color_pars_vertex>`,
`flat out vec3 vColor;`
);
console.log(shader.vertexShader);
shader.fragmentShader = shader.fragmentShader.replace(
`#include <color_pars_fragment>`,
`flat in vec3 vColor;`
);
console.log(shader.fragmentShader);
}
});
let l = new THREE.Line(g, m);
scene.add(l);
renderer.setAnimationLoop( _ => {
renderer.render(scene, camera);
})
body{
overflow: hidden;
margin: 0;
}
<script src="https://cdn.jsdelivr.net/npm/three#0.130.1/build/three.min.js"></script>

Related

Three.js shapes with more than one color

I'm quite new to Three.js and to webGL programming. I have a cone and a cylinder in my scene created according to the script below:
var scene;
var camera;
var renderer;
var createACone = function() {
const geometry = new THREE.ConeGeometry( 3.5, 0.5, 100, 100, true, 0, 2*Math.PI);
const material = new THREE.MeshBasicMaterial( {color: 'blue', opacity: 0.8, transparent: true} );
const cone = new THREE.Mesh( geometry, material );
cone.rotation.z +=3.1416;
cone.position.x = 0.5;
cone.position.y = 0.25;
cone.position.z = 0;
cone.castShadow = true;
scene.add( cone );
}
var createACylinder = function(radiusTop, radiusBottom, height, x, y, z, opacity=0.5, z_rotate = 0, transparent=true, thetaStart=0, thetaLength=2*Math.PI) {
const geometry = new THREE.CylinderGeometry(radiusTop, radiusBottom, height, 100, 100, true, thetaStart, thetaLength);
const material = new THREE.MeshBasicMaterial( {color: 'blue', opacity: opacity, transparent: transparent} );
const cylinder = new THREE.Mesh( geometry, material );
cylinder.position.x = x;
cylinder.position.y = y;
cylinder.position.z = z;
cylinder.rotation.z = z_rotate;
scene.add( cylinder );
}
var createLight = function () {
var spotLight1 = new THREE.DirectionalLight(0xffffff);
var spotLight2 = new THREE.DirectionalLight(0xffffff, 0.3);
var spotLight3 = new THREE.DirectionalLight(0xffffff, 0.3);
var spotLight4 = new THREE.DirectionalLight(0xffffff, 0.3);
spotLight1.position.set(20, 20, 50);
spotLight2.position.set(0, 1, 0);
spotLight3.position.set(-50, 20, 2);
spotLight4.position.set(50, 20, 2);
spotLight1.castShadow = true;
spotLight2.castShadow = false;
scene.add(spotLight1);
scene.add(spotLight2);
scene.add(spotLight3);
scene.add(spotLight4);
};
var init = function(){
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 2000 );
renderer = new THREE.WebGLRenderer({antialias: true});
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
camera.position.z = 6;
camera.position.y = 0.5;
camera.position.x= 0.5;
this.createLight();
this.createACone();
this.createACylinder(3.5, 3.5, 1, 0.5, 1, 0, 0.8, 0, true,0, 2*Math.PI);
this.render();
}
window.onload = this.init;
Now, I want the cone and the cylinder to be divided horizontally by two colors, according to their height (for instance, from lower base to half the cylinder's height I want it to be red, and the rest of it to be blue; for the cone vertex to half its height I want it red, the rest of the cone should be blue). I looked for a solution in the material's and the geometry's sources but couldn't find anything that would help me achieve this. I thought about dividing each geometry in two, but that is cumbersome since i want to dynamically change this color size ratio. Is there a better way to achieve this in Three.js?
Thanks in advance for your help.
Just an option of how you can have two colors, using UV coordinates in ShaderMaterial:
body {
overflow: hidden;
margin: 0;
}
<script type="module">
console.clear();
import * as THREE from "https://threejs.org/build/three.module.js";
import {OrbitControls} from "https://threejs.org/examples/jsm/controls/OrbitControls.js";
import {BufferGeometryUtils} from "https://threejs.org/examples/jsm/utils/BufferGeometryUtils.js";
import {GUI} from "https://threejs.org/examples/jsm/libs/dat.gui.module.js";
let scene = new THREE.Scene();
let camera = new THREE.PerspectiveCamera(60, innerWidth / innerHeight, 1, 100);
camera.position.set(0, -5, 8);
let renderer = new THREE.WebGLRenderer();
renderer.setSize(innerWidth, innerHeight);
document.body.appendChild(renderer.domElement);
let controls = new OrbitControls(camera, renderer.domElement);
let gCyl = new THREE.CylinderGeometry(5, 5, 1, 32, 1, true);
let gCon = new THREE.ConeGeometry(5, 1, 32, 1, true);
for(let i = 0; i < gCon.attributes.uv.count; i++){
gCon.attributes.uv.setY(i, 1 - gCon.attributes.uv.getY(i));
}
gCon.rotateX(Math.PI);
gCon.translate(0, -1, 0);
let g = BufferGeometryUtils.mergeBufferGeometries([gCyl, gCon]);
let m = new THREE.ShaderMaterial({
uniforms: {
color1: {value: new THREE.Color("red")},
color2: {value: new THREE.Color("blue")},
colorRatio: {value: 0.5}
},
vertexShader:`
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position,1.0);
}
`,
fragmentShader: `
uniform vec3 color1;
uniform vec3 color2;
uniform float colorRatio;
varying vec2 vUv;
void main() {
vec3 col = mix(color1, color2, step(colorRatio, vUv.y));
gl_FragColor = vec4( col, 1.0);
}
`
});
let o = new THREE.Mesh(g, m);
scene.add(o);
let gui = new GUI();
gui.add(m.uniforms.colorRatio, "value", 0., 1.).name("colorRatio");
renderer.setAnimationLoop( _ => {
renderer.render(scene, camera);
});
</script>

Creating same circles with PointsMaterial and CircleGeometry

I would like to create circles in two different ways:
With a circle sprite, then draw it with Points and PointsMaterial
With basic circle buffer geometries
However, I cannot make them match together because of PointsMaterial size.
const width = window.innerWidth;
const height = window.innerHeight;
const fov = 40;
const near = 10;
const far = 200;
const camera = new THREE.PerspectiveCamera(fov, width / height, near, far);
const renderer = new THREE.WebGLRenderer();
renderer.setSize(width, height);
document.body.appendChild(renderer.domElement);
const circle_sprite = new THREE.TextureLoader().load(
'https://fastforwardlabs.github.io/visualization_assets/circle-sprite.png'
);
const factor = 280;
const positions = [
{ x: 100, y: -5 },
{ x: 6, y: 50 }
];
const circleRadius = 20;
const scene = new THREE.Scene();
scene.background = new THREE.Color(0xefefef);
/* First method */
const pointsGeometry = new THREE.Geometry();
const colors = [];
for (const position of positions) {
// Set vector coordinates from data
const vertex = new THREE.Vector3(position.x, position.y, 0);
pointsGeometry.vertices.push(vertex);
const color = new THREE.Color(0xff0000);
colors.push(color);
}
pointsGeometry.colors = colors;
const pointsMaterial = new THREE.PointsMaterial({
size: (factor * circleRadius) / fov,
sizeAttenuation: false,
vertexColors: true,
map: circle_sprite,
transparent: true,
opacity: 0.5
});
const firstPoints = new THREE.Points(pointsGeometry, pointsMaterial);
scene.add(firstPoints);
/* Second method */
const circleGeometry = new THREE.CircleBufferGeometry(circleRadius, 32);
const circleMaterial = new THREE.MeshBasicMaterial({
color: 0xffff00,
transparent: true,
opacity: 0.5
});
positions.forEach((position) => {
const circleMesh = new THREE.Mesh(circleGeometry, circleMaterial);
circleMesh.position.set(position.x, position.y, 0);
scene.add(circleMesh);
});
/* Render loop */
function animate() {
requestAnimationFrame(animate);
renderer.render(scene, camera);
}
animate();
camera.position.set(0, 0, far);
I try to find the factor variable value but I also discovered that width or height are involved in this factor.
How can I draw same circles with PointsMaterial?
Having this: https://github.com/mrdoob/three.js/issues/12150#issuecomment-327874431, you can compute the size of points.
body {
overflow: hidden;
margin: 0;
}
<script type="module">
import * as THREE from "https://threejs.org/build/three.module.js";
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(50, innerWidth / innerHeight, 1, 100);
camera.position.set(0, 0, 10);
var renderer = new THREE.WebGLRenderer();
renderer.setSize(innerWidth, innerHeight);
document.body.appendChild(renderer.domElement);
var grid = new THREE.GridHelper(10, 10);
grid.rotation.x = Math.PI * 0.5;
scene.add(grid);
// geometry
var cGeom = new THREE.CircleBufferGeometry(1, 32);
var cMat = new THREE.MeshBasicMaterial({
color: "magenta"
});
var circle = new THREE.Mesh(cGeom, cMat);
circle.position.set(0, 3, 0);
scene.add(circle);
// points
var g = new THREE.BufferGeometry().setFromPoints([new THREE.Vector3()]);
var c = document.createElement("canvas");
c.width = 128;
c.height = 128;
var ctx = c.getContext("2d");
ctx.clearRect(0, 0, 128, 128);
ctx.fillStyle = "white";
ctx.beginPath();
ctx.arc(64, 64, 64, 0, 2 * Math.PI);
ctx.fill();
var tex = new THREE.CanvasTexture(c);
var desiredSize = 2;
var pSize = desiredSize / Math.tan( THREE.Math.degToRad( camera.fov / 2 ) );
var m = new THREE.PointsMaterial({
size: pSize,
color: "aqua",
map: tex,
alphaMap: tex,
alphaTest: 0.5
});
var p = new THREE.Points(g, m);
scene.add(p);
renderer.setAnimationLoop(() => {
renderer.render(scene, camera)
});
</script>

Change three.js Line material runtime

I have Line object with THREE.LineBasicMaterial, then i'am trying to change material:
material = new THREE.LineDashedMaterial({
dashSize: 5,
gapSize: 5,
})
line.material = material
line.material.needsUpdate = true
line.geometry.uvsNeedUpdate = true
line.geometry.verticesNeedUpdate = true
line.computeLineDistances()
But nothing happens, the line is still not dotted, any ideas?
Works as expected:
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(60, innerWidth / innerHeight, 1, 1000);
camera.position.set(3, 5, 8);
var renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setSize(innerWidth, innerHeight);
document.body.appendChild(renderer.domElement);
var controls = new THREE.OrbitControls(camera, renderer.domElement);
scene.add(new THREE.GridHelper(10, 10));
var pts = [];
var ptsCount = 10;
for (let i = 0; i < ptsCount; i++) {
pts.push(new THREE.Vector3(
Math.random() - 0.5,
Math.random() - 0.5,
Math.random() - 0.5
).multiplyScalar(10))
}
var geom = new THREE.BufferGeometry().setFromPoints(pts);
var mat = new THREE.LineBasicMaterial({
color: "yellow"
});
var line = new THREE.Line(geom, mat);
scene.add(line);
btnDashed.addEventListener("click", event => {
let mat = new THREE.LineDashedMaterial({
color: (Math.random() * 0x888888) + 0x888888,
dashSize: 0.5 + Math.random() * 0.5,
gapSize: Math.random() * 0.5
});
line.computeLineDistances();
line.material = mat;
})
renderer.setAnimationLoop(() => {
renderer.render(scene, camera)
})
body {
overflow: hidden;
margin: 0;
}
<script src="https://threejs.org/build/three.min.js"></script>
<script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>
<button id="btnDashed" style="position: absolute;">
Make dashed
</button>

Three.js Animated curve

I try to animate a 2D curve in Three.js over time.
I'll need more than 4 control points, so I'm not using Bezier curves.
I created a Three.Line based on a SplineCurve.
If I log the geometry.vertices position of my line they'll change over time, but geometry.attributes.position remains the same. Is it possible to animate the line based on the curve animation ? I managed to do this with Bezier Curves, but can't find a way with SplineCurve.
Thank you for your help, here's my code :
First I create the line :
var curve = new THREE.SplineCurve( [
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
curveObject = new THREE.Line( geometry, material );
scene.add( curveObject );
curveObject.curve = curve;
Then I try to update it :
curveObject.curve.points[0].x += 1;
curveObject.geometry.vertices = curveObject.curve.getPoints( 50 );
curveObject.geometry.verticesNeedUpdate = true;
curveObject.geometry.attributes.needsUpdate = true;
You can do it like that:
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(60, 1, 1, 1000);
camera.position.set(8, 13, 25);
var renderer = new THREE.WebGLRenderer({
antialias: true
});
var canvas = renderer.domElement;
document.body.appendChild(canvas);
var controls = new THREE.OrbitControls(camera, renderer.domElement);
scene.add(new THREE.GridHelper(20, 40));
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: 0x00ffff
});
var curveObject = new THREE.Line(geometry, material);
scene.add(curveObject);
var clock = new THREE.Clock();
var time = 0;
render();
function resize(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() {
if (resize(renderer)) {
camera.aspect = canvas.clientWidth / canvas.clientHeight;
camera.updateProjectionMatrix();
}
renderer.render(scene, camera);
time += clock.getDelta();
curve.points[1].y = Math.sin(time) * 2.5;
geometry = new THREE.BufferGeometry().setFromPoints(curve.getPoints(50));
curveObject.geometry.dispose();
curveObject.geometry = geometry;
requestAnimationFrame(render);
}
html,
body {
height: 100%;
margin: 0;
overflow: hidden;
}
canvas {
width: 100%;
height: 100%;
display;
block;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/97/three.min.js"></script>
<script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>

How to animate objects different with three.js

I just wanted to create a very simple scene with 2 different objects that move
differently.
But my sphere isnt moving at all.
When I change the line:
scene.add(obj2)
to:
obj.add(obj2)
the sphere is moving exactly as the box
what can I change to just let the sphere rotate?
//set up the scene
const scene = new THREE.Scene();
const renderer = new THREE.WebGLRenderer();
const camera = new THREE.PerspectiveCamera(30, window.innerWidth / window.innerHeight, 0.1, 1000);
const light = new THREE.DirectionalLight();
scene.background = new THREE.Color("white");
renderer.setSize((3 * window.innerWidth) / 4, (3 * window.innerHeight) / 4);
document.body.appendChild(renderer.domElement);
camera.position.z = 17;
light.position.set(-1, 1, 1);
//-----GEOMETRY VARIABLES------//
let box = new THREE.BoxGeometry(1, 1, 1);
let sphere = new THREE.SphereGeometry(0.5, 32, 32);
//-----MATERIAL VARIABLES------//
let phong = new THREE.MeshPhongMaterial({
color: "pink",
emissive: 0,
specular: 0x070707,
shininess: 100,
});
//-----FUNCTIONALITY------//
//make the objects and add them to the scene
let obj, currentShape, currentMesh, obj2;
currentShape = box;
currentMesh = phong;
obj = new THREE.Mesh(currentShape, currentMesh);
scene.add(light);
scene.add(obj);
obj2 = new THREE.Mesh(sphere, currentMesh);
obj2.position.set(3, 0, 0);
scene.add(obj2);
//methods for making the objects move
let up = true;
function animate() {
requestAnimationFrame(animate);
obj.rotation.y += 0.01;
obj2.rotation.x += 0.02;
if (up) {
obj.translateOnAxis(new THREE.Vector3(0, 1, 0).normalize(), 0.1);
if (obj.position.y > 3.4) {
up = false;
}
} else if (!up) {
obj.translateOnAxis(new THREE.Vector3(0, 1, 0).normalize(), -0.1);
if (obj.position.y < -3.4) {
up = true;
}
}
renderer.render(scene, camera);
}
window.onload = () => {
document.getElementById("shape-form").onchange = evt => {
switch (evt.target.value) {
case "box":
currentShape = box;
break;
}
obj.geometry = currentShape;
obj.geometry.buffersNeedUpdate = true;
};
document.getElementById("mesh-form").onchange = evt => {
switch (evt.target.value) {
case "phong":
currentMesh = phong;
break;
}
obj.material = currentMesh;
obj.material.needsUpdate = true;
};
animate();
};
It's just hard to see a sphere rotating :)
Here's a simplified version of your example - I made both the cube and the sphere rotate and float around in gentle waves.
const renderer = new THREE.WebGLRenderer();
renderer.setSize((3 * window.innerWidth) / 4, (3 * window.innerHeight) / 4);
document.body.appendChild(renderer.domElement);
const scene = new THREE.Scene();
scene.background = new THREE.Color("white");
const camera = new THREE.PerspectiveCamera(30, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.z = 17;
const light = new THREE.DirectionalLight();
light.position.set(-1, 1, 1);
scene.add(light);
const phong = new THREE.MeshPhongMaterial({
color: "pink",
emissive: 0,
specular: 0x070707,
shininess: 100,
});
const boxGeom = new THREE.BoxGeometry(1, 1, 1);
const boxObj = new THREE.Mesh(boxGeom, phong);
scene.add(boxObj);
const sphereGeom = new THREE.SphereGeometry(0.5, 32, 32);
const sphereObj = new THREE.Mesh(sphereGeom, phong);
scene.add(sphereObj);
function animate() {
const time = +new Date() / 1000;
requestAnimationFrame(animate);
boxObj.rotation.y += 0.01;
sphereObj.rotation.x += 0.02;
boxObj.position.x = Math.sin(time * .2) * 3.4;
sphereObj.position.x = Math.cos(time * .3) * 3.4;
boxObj.position.y = Math.sin(time) * 3.4;
sphereObj.position.y = Math.cos(time) * 3.4;
renderer.render(scene, camera);
}
animate();
<script src="https://threejs.org/build/three.min.js"></script>

Categories

Resources