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
Related
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>
I have a Sprite, created as follows, and added as a child of an Object with a transparent material:
let mySprite = new THREE.Sprite(new SpriteMaterial({
map: myTexture
}));
mySprite.scale.set(2, 2, 1.0);
mySprite.position.set(0, 0, 0);
myObject.add(mySprite);
The Object has depthWrite: false so that I can see the Sprite through it, however, since the Object is a sphere and the Sprite is square, the corners of the Sprite overflow the Object.
Is there a way that I can clip the corners of the Sprite so that only what is within the bounds of the spherical Object shows?
One way would be to make the parents draw a unique ID to the stencil buffer
const parentMaterial = new THREE.MeshPhongMaterial({
...
stencilWrite: true, // turn on stenci writing
stencilRef: stencilId, // write this value
stencilZPass: THREE.ReplaceStencilOp, // write if the depth buffer test passes
});
And set the sprite to only draw where that id appears in the stencil buffer
const spriteMaterial = new THREE.SpriteMaterial({
stencilWrite: true, // turn on writing
stencilRef: stencilId, //
stencilFunc: THREE.EqualStencilFunc, // draw only if stencil = stencilRef;
depthTest: false,
});
Note: writing to the stencil has 3 case. What to do if the stencil test fails, what to do if the depth test fails, what to do if both pass. The default for all 3 is to do nothing. So even though we are turning on stencilWrite true for the sprite because the 3 defaults are do nothing (THREE.KeepStencilOp) nothing will get written when the sprites are drawn.
You need to make sure the parents are drawn before the children. If they have the same position then I think that is already true. If they have different positions then you might need to set renderOrder. In the example above the sprites are transparent the bodies are opaque and transparent things get drawn after opaque by default.
Note, this will only work for 255 sprites since the stencil generally only has 256 possible values. Past that you'd need to render things in groups of 255 objects, clearing the stencil buffer between groups.
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 = 50;
const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
camera.position.set(0, 2, 5);
const controls = new THREE.OrbitControls(camera, canvas);
controls.target.set(0, 2, 0);
controls.update();
const scene = new THREE.Scene();
scene.background = new THREE.Color('white');
function addLight(position) {
const color = 0xFFFFFF;
const intensity = 1;
const light = new THREE.DirectionalLight(color, intensity);
light.position.set(...position);
scene.add(light);
scene.add(light.target);
}
addLight([-3, 1, 1]);
addLight([ 2, 1, .5]);
const bodyRadiusTop = .4;
const bodyRadiusBottom = .2;
const bodyHeight = 2;
const bodyRadialSegments = 6;
const bodyGeometry = new THREE.CylinderBufferGeometry(
bodyRadiusTop, bodyRadiusBottom, bodyHeight, bodyRadialSegments);
const headRadius = bodyRadiusTop * 0.8;
const headLonSegments = 12;
const headLatSegments = 5;
const headGeometry = new THREE.SphereBufferGeometry(
headRadius, headLonSegments, headLatSegments);
function makeLabelCanvas(baseWidth, size, name) {
const borderSize = 2;
const ctx = document.createElement('canvas').getContext('2d');
const font = `${size}px bold sans-serif`;
ctx.font = font;
// measure how long the name will be
const textWidth = ctx.measureText(name).width;
const doubleBorderSize = borderSize * 2;
const width = baseWidth + doubleBorderSize;
const height = size + doubleBorderSize;
ctx.canvas.width = width;
ctx.canvas.height = height;
// need to set font again after resizing canvas
ctx.font = font;
ctx.textBaseline = 'middle';
ctx.textAlign = 'center';
// scale to fit but don't stretch
const scaleFactor = Math.min(1, baseWidth / textWidth);
ctx.translate(width / 2, height / 2);
ctx.scale(scaleFactor, 1);
ctx.fillStyle = 'white';
ctx.fillText(name, 0, 0);
return ctx.canvas;
}
function makePerson(stencilId, x, labelWidth, size, name, color) {
const canvas = makeLabelCanvas(labelWidth, size, name);
const texture = new THREE.CanvasTexture(canvas);
// because our canvas is likely not a power of 2
// in both dimensions set the filtering appropriately.
texture.minFilter = THREE.LinearFilter;
texture.wrapS = THREE.ClampToEdgeWrapping;
texture.wrapT = THREE.ClampToEdgeWrapping;
const labelMaterial = new THREE.SpriteMaterial({
map: texture,
transparent: true,
stencilWrite: true,
stencilRef: stencilId,
stencilFunc: THREE.EqualStencilFunc,
depthTest: false,
});
const bodyMaterial = new THREE.MeshPhongMaterial({
color,
flatShading: true,
stencilWrite: true,
stencilRef: stencilId,
stencilZPass: THREE.ReplaceStencilOp,
});
const root = new THREE.Object3D();
root.position.x = x;
const body = new THREE.Mesh(bodyGeometry, bodyMaterial);
root.add(body);
body.position.y = bodyHeight / 2;
const head = new THREE.Mesh(headGeometry, bodyMaterial);
root.add(head);
head.position.y = bodyHeight + headRadius * 1.1;
const label = new THREE.Sprite(labelMaterial);
root.add(label);
label.position.y = bodyHeight * 4 / 5;
// if units are meters then 0.01 here makes size
// of the label into centimeters.
const labelBaseScale = 0.01;
label.scale.x = canvas.width * labelBaseScale;
label.scale.y = canvas.height * labelBaseScale;
scene.add(root);
return root;
}
makePerson(1, -3, 128, 128, '😜', 'purple');
makePerson(2, -0, 128, 128, '🚙', 'green');
makePerson(3, +3, 128, 128, '🔥', 'red');
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() {
if (resizeRendererToDisplaySize(renderer)) {
const canvas = renderer.domElement;
camera.aspect = canvas.clientWidth / canvas.clientHeight;
camera.updateProjectionMatrix();
}
renderer.render(scene, camera);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
}
main();
body {
margin: 0;
}
#c {
width: 100vw;
height: 100vh;
display: block;
}
<script src="https://threejsfundamentals.org/threejs/resources/threejs/r115/build/three.min.js"></script>
<script src= "https://threejsfundamentals.org/threejs/resources/threejs/r115/examples/js/controls/OrbitControls.js"></script>
<canvas id="c"></canvas>
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>
I create a scene, and it is all fine without using OrbitControls.
When I use OrbitControls, I found that my camera's position and rotation has changed, and I can't modify it.
Can somebody tell me how to set a default position and rotation of the camera with OrbitControls.
Thanks!
OrbitControls require a target. Set the target such that you get the same view.
camera.position.set(1, 8, 7);
const controls = new THREE.OrbitControls(camera, canvas);
controls.target.set(0, 3, 0);
controls.update();
'use strict';
/* global THREE */
function main() {
const canvas = document.querySelector('#c');
const renderer = new THREE.WebGLRenderer({canvas: 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.set(1, 8, 7);
const controls = new THREE.OrbitControls(camera, canvas);
controls.target.set(0, 3, 0);
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 boxWidth = 1;
const boxHeight = 10;
const boxDepth = 1;
const geometry = new THREE.BoxBufferGeometry(boxWidth, boxHeight, boxDepth);
const material = new THREE.MeshPhongMaterial({color: 'red'});
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);
cube.position.y = .5;
}
{
const geometry = new THREE.PlaneBufferGeometry(10, 10);
const material = new THREE.MeshPhongMaterial({color: 'gray'});
const plane = new THREE.Mesh(geometry, material);
scene.add(plane);
plane.rotation.x = Math.PI * -0.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) {
if (resizeRendererToDisplaySize(renderer)) {
const canvas = renderer.domElement;
camera.aspect = canvas.clientWidth / canvas.clientHeight;
camera.updateProjectionMatrix();
}
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/r102/three.min.js"></script>
<script src="https://threejsfundamentals.org/threejs/resources/threejs/r102/js/controls/OrbitControls.js"></script>
Or you could compute a target based on the current camera view. The OrbitControls orbit around the target so you'd need to choose a distance from the camera as your target
const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
camera.position.set(1, 2, 5);
camera.rotation.set(.1, .2, 0);
// get the direction of the camera
const direction = new THREE.Vector3();
camera.getWorldDirection(direction);
const controls = new THREE.OrbitControls(camera, canvas);
// point the target from the camera in the
// target direction
camera.getWorldPosition(controls.target);
controls.target.addScaledVector(direction, 5);
controls.update();
Note the 5 in addScaledVector means the target will be 5 units in front of the camera in the direction the camera was facing. Whether 5 is the right distance is up to you. In my sample scene the camera started at z = 5 so 5 units in front of the camera seemed like a reasonable place to put the target
'use strict';
/* global THREE */
function main() {
const canvas = document.querySelector('#c');
const renderer = new THREE.WebGLRenderer({canvas: 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.set(1, 2, 5);
camera.rotation.set(.1, .2, 0);
// compute a target direction
const direction = new THREE.Vector3();
camera.getWorldDirection(direction);
const controls = new THREE.OrbitControls(camera, canvas);
// point the target from the camera in the
// target direction
camera.getWorldPosition(controls.target);
controls.target.addScaledVector(direction, 5);
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 boxWidth = 1;
const boxHeight = 10;
const boxDepth = 1;
const geometry = new THREE.BoxBufferGeometry(boxWidth, boxHeight, boxDepth);
const material = new THREE.MeshPhongMaterial({color: 'red'});
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);
cube.position.y = .5;
}
{
const geometry = new THREE.PlaneBufferGeometry(10, 10);
const material = new THREE.MeshPhongMaterial({color: 'gray'});
const plane = new THREE.Mesh(geometry, material);
scene.add(plane);
plane.rotation.x = Math.PI * -0.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) {
if (resizeRendererToDisplaySize(renderer)) {
const canvas = renderer.domElement;
camera.aspect = canvas.clientWidth / canvas.clientHeight;
camera.updateProjectionMatrix();
}
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/r102/three.min.js"></script>
<script src="https://threejsfundamentals.org/threejs/resources/threejs/r102/js/controls/OrbitControls.js"></script>