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>
Related
How can I use the object1.lookat(object2) function but freeze the z rotation? in Three.js
I have some objects in the field i and I want them to always face a new object2. This object2 can move around freely. However, the z rotation of object1 must always remain the same. The problem with the lookat() is that rotates every axis. Is there another way to do it?
Did you mean don’t rotate in X? lookAt already doesn’t rotate in Z
// Three.js - Responsive
// from https://threejsfundamentals.org/threejs/threejs-responsive.html
'use strict';
/* global THREE */
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 = 250;
const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
camera.position.set(20, 10, 20);
const controls = new THREE.OrbitControls(camera, canvas);
controls.target.set(0, 0, 0);
controls.update();
const scene = new THREE.Scene();
scene.background = new THREE.Color('white');
function addLight(...pos) {
const color = 0xFFFFFF;
const intensity = 1;
const light = new THREE.DirectionalLight(color, intensity);
light.position.set(...pos);
scene.add(light);
}
addLight(-1, 2, 4);
addLight( 1, 2, 2);
const shape = new THREE.Shape();
shape.moveTo(-1, -1);
shape.lineTo( 0, -1);
shape.lineTo( 0, -2);
shape.lineTo( 2, 0);
shape.lineTo( 0, 2);
shape.lineTo( 0, 1);
shape.lineTo(-1, 1);
const extrudeSettings = {
depth: 1,
bevelEnabled: false,
};
const geometry = new THREE.ExtrudeBufferGeometry(shape, extrudeSettings);
geometry.applyMatrix(new THREE.Matrix4().makeRotationY(Math.PI * -0.5));
const arrows = [];
const spread = 5;
for (let z = -3; z <= 3 ; ++z) {
for (let x = -3; x <= 3; ++x) {
const material = new THREE.MeshPhongMaterial({
color: new THREE.Color().setHSL(Math.abs(Math.atan2(x, z)) / Math.PI, 1, 0.5),
});
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
arrows.push(mesh);
mesh.position.set(x * spread, 0, z * spread);
}
}
const geometry2 = new THREE.SphereBufferGeometry();
const sphere = new THREE.Mesh(geometry2, new THREE.MeshPhongMaterial({color:'red'}));
const base = new THREE.Object3D();
scene.add(base)
base.position.y = 10;
const base2 = new THREE.Object3D();
base.add(base2);
base2.position.z = 15;
const base3 = new THREE.Object3D();
base2.add(base3);
base3.position.z = 5;
base3.add(sphere);
sphere.position.y = 5;
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();
}
base.rotation.y = time;
base2.rotation.y = time * 0.77;
base3.rotation.z = time * 2.33;
const temp = new THREE.Vector3();
for (const arrow of arrows) {
sphere.getWorldPosition(temp);
arrow.lookAt(temp);
}
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/r105/three.min.js"></script>
<script src="https://threejsfundamentals.org/threejs/resources/threejs/r105/js/controls/OrbitControls.js"></script>
maybe you mean not rotate in X? In that case get the target's position in a temp Vector3, the set the Y so it matches the y of the thing you want to aim, then call lookAt.
// Three.js - Responsive
// from https://threejsfundamentals.org/threejs/threejs-responsive.html
'use strict';
/* global THREE */
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 = 250;
const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
camera.position.set(20, 10, 20);
const controls = new THREE.OrbitControls(camera, canvas);
controls.target.set(0, 0, 0);
controls.update();
const scene = new THREE.Scene();
scene.background = new THREE.Color('white');
function addLight(...pos) {
const color = 0xFFFFFF;
const intensity = 1;
const light = new THREE.DirectionalLight(color, intensity);
light.position.set(...pos);
scene.add(light);
}
addLight(-1, 2, 4);
addLight( 1, 2, 2);
const shape = new THREE.Shape();
shape.moveTo(-1, -1);
shape.lineTo( 0, -1);
shape.lineTo( 0, -2);
shape.lineTo( 2, 0);
shape.lineTo( 0, 2);
shape.lineTo( 0, 1);
shape.lineTo(-1, 1);
const extrudeSettings = {
depth: 1,
bevelEnabled: false,
};
const geometry = new THREE.ExtrudeBufferGeometry(shape, extrudeSettings);
geometry.applyMatrix(new THREE.Matrix4().makeRotationY(Math.PI * -0.5));
const arrows = [];
const spread = 5;
for (let z = -3; z <= 3 ; ++z) {
for (let x = -3; x <= 3; ++x) {
const material = new THREE.MeshPhongMaterial({
color: new THREE.Color().setHSL(Math.abs(Math.atan2(x, z)) / Math.PI, 1, 0.5),
});
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
arrows.push(mesh);
mesh.position.set(x * spread, 0, z * spread);
}
}
const geometry2 = new THREE.SphereBufferGeometry();
const sphere = new THREE.Mesh(geometry2, new THREE.MeshPhongMaterial({color:'red'}));
const base = new THREE.Object3D();
scene.add(base)
base.position.y = 10;
const base2 = new THREE.Object3D();
base.add(base2);
base2.position.z = 15;
const base3 = new THREE.Object3D();
base2.add(base3);
base3.position.z = 5;
base3.add(sphere);
sphere.position.y = 5;
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();
}
base.rotation.y = time;
base2.rotation.y = time * 0.77;
base3.rotation.z = time * 2.33;
const temp = new THREE.Vector3();
for (const arrow of arrows) {
sphere.getWorldPosition(temp);
temp.y = arrow.position.y
arrow.lookAt(temp);
}
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/r105/three.min.js"></script>
<script src="https://threejsfundamentals.org/threejs/resources/threejs/r105/js/controls/OrbitControls.js"></script>
I'm looking for a way to wrap text around sphere in babylon or threejs. And i'm open-minded for changing javascript technology
I would look at an example of generating text. I’d then generate each letter separately recording their individual widths and use those to compute the total width across the string I want to display
I could then parent each mesh to an Object3D and set that Object3D’s rotation y to
widthSoFar = 0;
for each letter
obj3d.rotation.y = widthSoFar / totalWidth * Math.PI * 2;
widthSoFar += widthOfCurrentLetter;
And set the letter’s position.z to some radius which would put the letters around a circle.
What radius?
circumference = 2 * PI * radius
so
radius = circumference / (2 * PI)
We know the circumference we need, it’s the totalWidth of the string.
You might find this tutorial helpful in understanding how to use scene graph nodes (like the Object3D node) to organize a scene to meet your needs.
'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 = 70;
const scene = new THREE.Scene();
scene.background = new THREE.Color('black');
function addLight(...pos) {
const color = 0xFFFFFF;
const intensity = 1;
const light = new THREE.DirectionalLight(color, intensity);
light.position.set(...pos);
scene.add(light);
}
addLight(-4, 4, 4);
addLight(5, -4, 4);
const lettersTilt = new THREE.Object3D();
scene.add(lettersTilt);
lettersTilt.rotation.set(
THREE.Math.degToRad(-15),
0,
THREE.Math.degToRad(-15));
const lettersBase = new THREE.Object3D();
lettersTilt.add(lettersBase);
{
const letterMaterial = new THREE.MeshPhongMaterial({
color: 'red',
});
const loader = new THREE.FontLoader();
loader.load('https://threejsfundamentals.org/threejs/resources/threejs/fonts/helvetiker_regular.typeface.json', (font) => {
const spaceSize = 1.0;
let totalWidth = 0;
let maxHeight = 0;
const letterGeometries = {
' ': { width: spaceSize, height: 0 }, // prepopulate space ' '
};
const size = new THREE.Vector3();
const str = 'threejs fundamentals ';
const letterInfos = str.split('').map((letter, ndx) => {
if (!letterGeometries[letter]) {
const geometry = new THREE.TextBufferGeometry(letter, {
font: font,
size: 3.0,
height: .2,
curveSegments: 12,
bevelEnabled: true,
bevelThickness: 0.5,
bevelSize: .3,
bevelSegments: 5,
});
geometry.computeBoundingBox();
geometry.boundingBox.getSize(size);
letterGeometries[letter] = {
geometry,
width: size.x / 2, // no idea why size.x is double size
height: size.y,
};
}
const {geometry, width, height} = letterGeometries[letter];
const mesh = geometry
? new THREE.Mesh(geometry, letterMaterial)
: null;
totalWidth += width;
maxHeight = Math.max(maxHeight, height);
return {
mesh,
width,
};
});
let t = 0;
const radius = totalWidth / Math.PI;
for (const {mesh, width} of letterInfos) {
if (mesh) {
const offset = new THREE.Object3D();
lettersBase.add(offset);
offset.add(mesh);
offset.rotation.y = t / totalWidth * Math.PI * 2;
mesh.position.z = radius;
mesh.position.y = -maxHeight / 2;
}
t += width;
}
{
const geo = new THREE.SphereBufferGeometry(radius - 1, 32, 24);
const mat = new THREE.MeshPhongMaterial({
color: 'cyan',
});
const mesh = new THREE.Mesh(geo, mat);
scene.add(mesh);
}
camera.position.z = radius * 3;
});
}
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();
}
lettersBase.rotation.y = time * -0.5;
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/r105/three.min.js"></script>
This question is more about Math, than about threejs but maybe there are usable Alternatives for my issue.
So what I want to do, is to go through every vertice in a Box Geometry and check weither it has to be moved down/up and move it then by a specific value. (it is only about the y-values of each vertice)
var width = 200,
height = 100,
depth = 50;
var roundCornerWidth = var roundCornerHeight = 10;
var helpWidth = width - 2*roundCornerWidth,
helpHeight = height - 2*roundCornerHeight;
var boxGeometry = new THREE.BoxGeometry(width, height, depth, 100, 50, 10);
boxGeometry.vertices.forEach(v => {
if(Math.abs(v.x)>helpWidth/2){
if(Math.abs(v.y)>helpHeight/2){
let helper = Math.abs(v.x)-helperWidth/2;
v.y = Math.sign(v.y)*(helperHeight + Math.cos(helper/roundWidth * Math.PI/2)*roundHeight);
}
}
});
The code above creates corners like you can see on the example image. Those aren't kind of beautiful! :( Another "function" than cos() is needed.
I've used a method without trigonometrical functions, as we can manipulate with vectors:
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 1000);
camera.position.set(50, 50, 150);
var renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
var controls = new THREE.OrbitControls(camera, renderer.domElement);
var radius = 10;
var width = 200,
height = 100;
var geometry = new THREE.BoxGeometry(width, height, 50, 100, 50, 10);
var v1 = new THREE.Vector3();
var w1 = (width - (radius * 2)) * 0.5,
h1 = (height - (radius * 2)) * 0.5;
var vTemp = new THREE.Vector3(),
vSign = new THREE.Vector3(),
vRad = new THREE.Vector3();
geometry.vertices.forEach(v => {
v1.set(w1, h1, v.z);
vTemp.multiplyVectors(v1, vSign.set(Math.sign(v.x), Math.sign(v.y), 1));
vRad.subVectors(v, vTemp);
if (Math.abs(v.x) > v1.x && Math.abs(v.y) > v1.y && vRad.length() > radius) {
vRad.setLength(radius).add(vTemp);
v.copy(vRad);
}
});
var mesh = new THREE.Mesh(geometry, new THREE.MeshBasicMaterial({
color: "aqua",
wireframe: true
}));
scene.add(mesh);
render();
function render() {
requestAnimationFrame(render);
renderer.render(scene, camera);
}
body {
overflow: hidden;
margin: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/90/three.min.js"></script>
<script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>
Disadvantage: you can't control the smoothness of the roundness without increasing the amount of width or height or depth segments.
EDIT: copied surrounding Code Blocks from prisoner849 to make result visbile for everyone.
I wanted to stay with the box Geometry, because I also deform the Geometry on the z-axis, so this is my solution:
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 1000);
camera.position.set(50, 50, 150);
var renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
var controls = new THREE.OrbitControls(camera, renderer.domElement);
var width = 200,
height = 100,
depth = 50;
var roundCornerWidth = roundCornerHeight = 10;
var helpWidth = width - 2*roundCornerWidth,
helpHeight = height - 2*roundCornerHeight;
var boxGeometry = new THREE.BoxGeometry(width, height, depth, 100, 50, 10);
boxGeometry.vertices.forEach(v => {
if(Math.abs(v.x)>helpWidth/2){
if(Math.abs(v.y)>helpHeight/2){
let helperX = Math.abs(v.x)-helpWidth/2;
let helperY2 = (Math.abs(v.y)-helpHeight/2)/roundCornerHeight;
let helperY = (1-helperX/roundCornerWidth) * roundCornerHeight * helperY2;
v.y = Math.sign(v.y)*((helpHeight/2 + helperY)+(Math.sin(helperX/roundCornerWidth * Math.PI)*(roundCornerHeight/4))*helperY2);
v.x = Math.sign(v.x)*(Math.abs(v.x)+(Math.sin(helperX/roundCornerWidth * Math.PI)*(roundCornerWidth/4))*helperY2);
}
}
});
var mesh = new THREE.Mesh(boxGeometry, new THREE.MeshBasicMaterial({
color: 0xffce00,
wireframe: true
}));
scene.add(mesh);
render();
function render() {
requestAnimationFrame(render);
renderer.render(scene, camera);
}
body {
overflow: hidden;
margin: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/90/three.min.js"></script>
<script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>
This worked for me perfectly and seems to be the simplest solution for my issue.
I'm trying to make the direction flow and air flow like this example: https://animagraffs.com/supercharger-vs-turbo/
As I can see there's only the texture is moving
I don't understand how to make the air flowing to correct direction on the pipe and surface like that.
Any idea is much appreciated!
(I'm thinking they might use some uv unwrap technique combine with animate the UV offset of texture)
It looks like you can just adjust a texture's UV offset
texture.offset.x = someAnimatedValue;
texture.offset.y = someAnimatedValue;
As for making the pipe itself, pretty much any 3d modeling package (blender, maya, 3d studio max, etc...) will let you extrude a line along a path. So to make the pipe you make a circle and then extrude it along the path. Similarly you can put a wall/fence down the center of a pipe by extruding a line down the same curve. The default UV coordinates will be correct for scrolling.
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, 1, 0.1, 1000);
const renderer = new THREE.WebGLRenderer();
document.body.appendChild(renderer.domElement);
// make a texture with an arrow
const ctx = document.createElement("canvas").getContext("2d");
ctx.canvas.width = 64;
ctx.canvas.height = 64;
ctx.fillStyle = "rgba(0,0,255,0.5)";
ctx.fillRect(0, 0, 64, 64);
ctx.translate(32, 32);
ctx.rotate(Math.PI * .5);
ctx.fillStyle = "rgb(0,255,255)";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.font = "48px sans-serif";
ctx.fillText("➡︎", 0, 0);
const texture = new THREE.CanvasTexture(ctx.canvas);
texture.wrapS = THREE.RepeatWrapping;
texture.wrapT = THREE.RepeatWrapping;
texture.repeat.x = 4;
texture.repeat.y = 9;
const radiusTop = 1;
const radiusBottom = 1;
const height = 5;
const radiusSegments = 20;
const heightSegments = 2;
const openEnded = true;
const geometry = new THREE.CylinderBufferGeometry(
radiusTop, radiusBottom, height, radiusSegments, heightSegments, openEnded);
const material = new THREE.MeshBasicMaterial({
map: texture,
side: THREE.DoubleSide,
depthWrite: false,
depthTest: false,
transparent: true,
});
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
mesh.rotation.z = Math.PI * .5;
function render(time) {
time *= 0.001;
resize();
const cameraSpeed = time * 0.3;
const cameraRadius = 5;
camera.position.x = Math.cos(cameraSpeed) * cameraRadius;
camera.position.y = 1;
camera.position.z = Math.sin(cameraSpeed) * cameraRadius;
camera.lookAt(mesh.position);
texture.offset.y = (time * 3 % 1);
renderer.render(scene, camera);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
function resize() {
const canvas = renderer.domElement;
const width = canvas.clientWidth;
const height = canvas.clientHeight;
if (canvas.width !== width || canvas.height !== height) {
renderer.setSize(width, height, false);
camera.aspect = width / height;
camera.updateProjectionMatrix();
}
}
body { margin: 0; }
canvas { width: 100vw; height: 100vh; display: block; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/84/three.min.js"></script>
Note that looking at the original diagram I'd stay they aren't animating a pipe, they are animating a strip that's inside the pipe at has an arrow texture being scrolled on it. So, in you modeling package, after creating the pipes extrude a line down the path.
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, 1, 0.1, 1000);
const renderer = new THREE.WebGLRenderer();
document.body.appendChild(renderer.domElement);
// make a texture with an arrow
const ctx = document.createElement("canvas").getContext("2d");
ctx.canvas.width = 64;
ctx.canvas.height = 64;
ctx.translate(32, 32);
ctx.rotate(Math.PI * .5);
ctx.fillStyle = "rgb(0,255,255)";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.font = "48px sans-serif";
ctx.fillText("➡︎", 0, 0);
const texture = new THREE.CanvasTexture(ctx.canvas);
texture.wrapS = THREE.RepeatWrapping;
texture.wrapT = THREE.RepeatWrapping;
texture.repeat.x = 1;
texture.repeat.y = 5;
const radiusTop = 1;
const radiusBottom = 1;
const height = 5;
const radiusSegments = 20;
const heightSegments = 2;
const openEnded = true;
const geometry = new THREE.CylinderBufferGeometry(
radiusTop, radiusBottom, height, radiusSegments, heightSegments, openEnded);
const material = new THREE.MeshBasicMaterial({
color: 0x4040FF,
opacity: 0.5,
side: THREE.DoubleSide,
depthWrite: false,
depthTest: false,
transparent: true,
});
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
mesh.rotation.z = Math.PI * .5;
const stripGeo = new THREE.PlaneBufferGeometry(radiusTop * 1.7, height);
const stripMat = new THREE.MeshBasicMaterial({
map: texture,
opacity: 0.5,
side: THREE.DoubleSide,
depthWrite: false,
depthTest: false,
transparent: true,
});
const stripMesh = new THREE.Mesh(stripGeo, stripMat);
scene.add(stripMesh);
stripMesh.rotation.z = Math.PI * .5;
function render(time) {
time *= 0.001;
resize();
const cameraSpeed = time * 0.3;
const cameraRadius = 5;
camera.position.x = Math.cos(cameraSpeed) * cameraRadius;
camera.position.y = 1;
camera.position.z = Math.sin(cameraSpeed) * cameraRadius;
camera.lookAt(mesh.position);
texture.offset.y = (time * 3 % 1);
renderer.render(scene, camera);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
function resize() {
const canvas = renderer.domElement;
const width = canvas.clientWidth;
const height = canvas.clientHeight;
if (canvas.width !== width || canvas.height !== height) {
renderer.setSize(width, height, false);
camera.aspect = width / height;
camera.updateProjectionMatrix();
}
}
body { margin: 0; }
canvas { width: 100vw; height: 100vh; display: block; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/84/three.min.js"></script>
PS: I'm too lazy to deal with the sorting issues but dealing with that should be a separate quesiton
I have a working animation, just not the way I would like.
I would like the object to rotate 90 degrees with a delay (works) then continue to rotate 90 degrees, ultimately looping forever. No matter what I do, it always resets. Even if I set up 4 tweens taking me to 360, the last tween that resets back zero makes the whole object spin in the opposite direction.
Thanks
var width = 1000;
var height = 600;
var scene = new THREE.Scene();
var group = new THREE.Object3D(); //create an empty container
var camera = new THREE.OrthographicCamera(width / -2, width / 2, height / 2, height / -2, -500, 1000);
camera.position.x = 180;
camera.position.y = 180;
camera.position.z = 200;
var renderer = new THREE.WebGLRenderer();
renderer.setSize(width, height);
renderer.setClearColor(0xf0f0f0);
document.body.appendChild(renderer.domElement);
var geometry = new THREE.BoxGeometry(300, 300, 300);
var material = new THREE.MeshLambertMaterial({
color: 0xffffff,
shading: THREE.SmoothShading,
overdraw: 0.5
});
var cube = new THREE.Mesh(geometry, material);
group.add(cube);
var canvas1 = document.createElement('canvas');
canvas1.width = 1000;
canvas1.height = 1000;
var context1 = canvas1.getContext('2d');
context1.font = "Bold 20px Helvetica";
context1.fillStyle = "rgba(0,0,0,0.95)";
context1.fillText('Text bit', 500, 500);
// canvas contents will be used for a texture
var texture1 = new THREE.Texture(canvas1)
texture1.needsUpdate = true;
var material1 = new THREE.MeshBasicMaterial({
map: texture1,
side: THREE.DoubleSide
});
material1.transparent = true;
var mesh1 = new THREE.Mesh(
new THREE.PlaneBufferGeometry(2000, 2000),
material1
);
mesh1.position.set(-150, 150, 151);
group.add(mesh1);
directionalLight = new THREE.DirectionalLight(0xffffff);
directionalLight.position.set(1, 0, 0)
scene.add(directionalLight);
directionalLight = new THREE.DirectionalLight(0x888888);
directionalLight.position.set(0, 1, 0)
scene.add(directionalLight);
directionalLight = new THREE.DirectionalLight(0xcccccc);
directionalLight.position.set(0, 0, 1)
scene.add(directionalLight);
scene.add(group)
// with help from https://github.com/tweenjs/tween.js/issues/14
var tween = new TWEEN.Tween(group.rotation).to({ y: -(90 * Math.PI / 180)}, 1000).delay(1000);
tween.onComplete(function() {
group.rotation.y = 0;
});
tween.chain(tween);
tween.start();
camera.lookAt(scene.position);
var render = function() {
requestAnimationFrame(render);
TWEEN.update();
renderer.render(scene, camera);
};
render();
=====EDIT=====
I got it working, not sure if this is the most efficient approach but I'm satisfied:
var start = {}
start.y = 0;
var targ = {};
targ.y = 90*Math.PI/180
function rot(s,t) {
start["y"] = s;
targ["y"] = t;
}
var cnt1 = 1;
var cnt2 = 2;
rot(0,90*Math.PI/180);
var tween = new TWEEN.Tween(start).to(targ, 1000).delay(1000);
tween.onUpdate(function() {
group.rotation.y = start.y;
})
tween.onComplete(function() {
_c = cnt1++;
_d = cnt2++;
rot((_c*90)*Math.PI/180,(_d*90)*Math.PI/180)
});
tween.chain(tween);
tween.start();
Simple call setTimeout when tween is end
( http://jsfiddle.net/bhpf4zvy/ ):
function tRotate( obj, angles, delay, pause ) {
new TWEEN.Tween(group.rotation)
.delay(pause)
.to( {
x: obj.rotation._x + angles.x,
y: obj.rotation._y + angles.y,
z: obj.rotation._z + angles.z
}, delay )
.onComplete(function() {
setTimeout( tRotate, pause, obj, angles, delay, pause );
})
.start();
}
tRotate(group, {x:0,y:-Math.PI/2,z:0}, 1000, 500 );
Upd: pfff, what nonsense am I??? Simple use relative animation (http://jsfiddle.net/vv06u6rs/7/):
var tween = new TWEEN.Tween(group.rotation)
.to({ y: "-" + Math.PI/2}, 1000) // relative animation
.delay(1000)
.onComplete(function() {
// Check that the full 360 degrees of rotation,
// and calculate the remainder of the division to avoid overflow.
if (Math.abs(group.rotation.y)>=2*Math.PI) {
group.rotation.y = group.rotation.y % (2*Math.PI);
}
})
.start();
tween.repeat(Infinity)