The only way I know how to resize an extruded path is but reconstructing it. I want to know whether there is a way I can control the length/width/height in the threejs controls.
Here is my code:
'use strict';
/* global THREE, dat */
function main() {
const canvas = document.querySelector('#canvas');
const renderer = new THREE.WebGLRenderer({canvas});
const fov = 45;
const aspect = 2; // the canvas default
const near = 0.1;
const far = 100;
const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
camera.position.set(0, 10, 20);
const controls = new THREE.OrbitControls(camera, canvas);
controls.target.set(0, 5, 0);
controls.update();
const scene = new THREE.Scene();
scene.background = new THREE.Color('black');
{
const planeSize = 40;
const loader = new THREE.TextureLoader();
const texture = loader.load('https://r105.threejsfundamentals.org/threejs/resources/images/checker.png');
texture.wrapS = THREE.RepeatWrapping;
texture.wrapT = THREE.RepeatWrapping;
texture.magFilter = THREE.NearestFilter;
const repeats = planeSize / 2;
texture.repeat.set(repeats, repeats);
const planeGeo = new THREE.PlaneBufferGeometry(planeSize, planeSize);
const planeMat = new THREE.MeshPhongMaterial({
map: texture,
side: THREE.DoubleSide,
});
const mesh = new THREE.Mesh(planeGeo, planeMat);
mesh.rotation.x = Math.PI * -.5;
scene.add(mesh);
}
{
// Create a Shape Mesh with basic material
const shape = new THREE.Shape();
const x_len = 12;
const y_len = 8;
const z_len = 1;
shape.moveTo( 0, 0 );
shape.lineTo( 0, y_len );
shape.lineTo( x_len, y_len );
shape.lineTo( x_len, 0 );
shape.lineTo( 0, 0 );
const thickness = 0.5;
shape.lineTo( thickness, thickness );
shape.lineTo( x_len-thickness, thickness );
shape.lineTo( x_len-thickness, y_len-thickness );
shape.lineTo( thickness, y_len-thickness );
shape.lineTo( thickness, thickness );
const extrudeSettings = {
steps: 2,
depth: z_len,
bevelEnabled: false,
bevelThickness: 1,
bevelSize: 1,
bevelOffset: 0,
bevelSegments: 1
};
const geometry = new THREE.ExtrudeGeometry( shape, extrudeSettings );
const material = new THREE.MeshPhongMaterial({color: '#CA8'});
const mesh = new THREE.Mesh( geometry, material ) ;
scene.add( mesh );
}
class ColorGUIHelper {
constructor(object, prop) {
this.object = object;
this.prop = prop;
}
get value() {
return `#${this.object[this.prop].getHexString()}`;
}
set value(hexString) {
this.object[this.prop].set(hexString);
}
}
function makeXYZGUI(gui, vector3, name, onChangeFn) {
const folder = gui.addFolder(name);
folder.add(vector3, 'x', -10, 10).onChange(onChangeFn);
folder.add(vector3, 'y', 0, 10).onChange(onChangeFn);
folder.add(vector3, 'z', -10, 10).onChange(onChangeFn);
folder.open();
}
{
const color = 0xFFFFFF;
const intensity = 1;
const light = new THREE.PointLight(color, intensity);
light.position.set(0, 10, 4);
scene.add(light);
const helper = new THREE.PointLightHelper(light);
scene.add(helper);
function updateLight() {
helper.update();
}
const gui = new dat.GUI();
gui.addColor(new ColorGUIHelper(light, 'color'), 'value').name('color');
gui.add(light, 'intensity', 0, 2, 0.01);
gui.add(light, 'distance', 0, 40).onChange(updateLight);
makeXYZGUI(gui, light.position, 'position');
}
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();
<html lang="en">
<head>
<meta charset="UTF-8" />
<!-- Simple reset to delete the margins -->
<style>
body { margin: 0; }
canvas { width: 100%; height: 100% }
</style>
</head>
<body>
<!-- Our code -->
<canvas id="canvas"></canvas>
<script src="https://r105.threejsfundamentals.org/threejs/resources/threejs/r105/three.min.js"></script>
<script src="https://r105.threejsfundamentals.org/threejs/resources/threejs/r105/js/controls/OrbitControls.js"></script>
<script src="https://r105.threejsfundamentals.org/threejs/../3rdparty/dat.gui.min.js"></script>
</html>
In this code, I want to change x_len, y_len, z_len
I'm using a library called dat.gui.
The problem is that each of these values map to multiple properties. For example, x_len maps to
shape.curves[2].x
shape.curves[3].x
shape.curves[6].x
shape.curves[7].x
so I probably need something like:
gui.add(some object to point to all relevant curves, 'x', 0, 40).onChange(someCallback);
any help will be appreciated.
Related
Screen capture
I tried to make a 3/4 cylinder surface, and I made it from extruting by a ellipe path,
but when I tried to load a texture to the surface, It does not as I exprected: uniformly painted to the surface, it's stretched
I know it's about texture projection, But I dont know how to set options.
class EllipseCurve3 extends THREE.Curve {
ellipse = null
constructor (ellipse) {
super()
this.ellipse = ellipse
}
getPoint(t, optionalTarget = new THREE.Vector3()) {
const point = this.ellipse.getPoint(t, optionalTarget)
return new THREE.Vector3(
point.x,
point.y,
0
)
}
}
// Scene
const scene = new THREE.Scene();
var shape = new THREE.Shape();
shape.moveTo(0, 0);
shape.moveTo(0, 1);
shape.lineTo(50, 1);
shape.moveTo(50, 0);
shape.lineTo(0, 0);
// var curve = new THREE.CatmullRomCurve3([
// new THREE.Vector3(0, 0, 50),
// new THREE.Vector3(-50, 0, 0),
// new THREE.Vector3(0, 0, -50)
// ]);
const arc = new THREE.EllipseCurve(
0,
0, // ax, aY
100,
100, // xRadius, yRadius
0,
1.5 * Math.PI, // aStartAngle, aEndAngle
false, // aClockwise
0 // aRotation
),
path = new EllipseCurve3(arc)
geometry = new THREE.ExtrudeGeometry(shape, {
bevelEnabled: false,
extrudePath: path,
steps: 50,
depth: 5,
amount: 20,
material: 0,
extrudeMaterial: 1
});
// Set up lights
const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
scene.add(ambientLight);
const axesHelper = new THREE.AxesHelper(500);
scene.add(axesHelper);
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.6);
directionalLight.position.set(100, 200, 100); // x, y, z
scene.add(directionalLight);
// Camera
const width = 200;
const height = width * (window.innerHeight / window.innerWidth);
const camera = new THREE.OrthographicCamera(
width / -2, // left
width / 2, // right
height / 2, // top
height / -2, // bottom
0.1, // near
1000 // far
);
camera.position.set(400, 400, 400);
camera.lookAt(0, 0, 0);
// Renderer
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
const controls = new THREE.OrbitControls(camera, renderer.domElement);
renderer.render(scene, camera);
// Add it to HTML
document.body.appendChild(renderer.domElement);
var textureLoader = new THREE.TextureLoader();
textureLoader.crossOrigin = true;
const picture = 'https://threejs.org/examples/textures/uv_grid_opengl.jpg'
textureLoader.load(picture, function(texture) {
// repeat pattern
texture.wrapS = texture.wrapT = THREE.MirroredRepeatWrapping;
// zoom in on pattern
texture.repeat.set(.01, .01);
// assign texture via MeshBasicMaterial
var material = new THREE.MeshBasicMaterial({
map: texture,
needsUpdate: true,
// transparent: true,
// premultipliedAlpha: true,
// side: THREE.DoubleSide,
// blending: THREE.AdditiveBlending
});
// var material = new THREE.MeshPhongMaterial({ color: 0x0048ff });
var mesh = new THREE.Mesh(geometry, material)
mesh.rotation.x = Math.PI / 2
// mesh.rotation.z = Math.PI / 2
scene.add(mesh)
scene.add(cube)
})
function render() {
renderer.render(scene, camera);
// Rotate out group
// svgGroup.rotation.y -= 0.005
controls.update();
requestAnimationFrame(render);
}
render();
Code here
https://codepen.io/mike-xu/pen/RwQeEXJ
"I know it's about texture projection" - It's about computing UV, based on vertices coordinates.
CylinderGeometry also may help to achieve the result you described. With less code, and more convenient and predictable way.
body{
overflow: hidden;
margin: 0;
}
<script type="module">
import * as THREE from "https://cdn.skypack.dev/three#0.136.0";
import {OrbitControls} from "https://cdn.skypack.dev/three#0.136.0/examples/jsm/controls/OrbitControls.js";
console.clear();
let scene = new THREE.Scene();
let camera = new THREE.PerspectiveCamera(60, innerWidth / innerHeight, 1, 1000);
camera.position.set(0, 10, 10);
let renderer = new THREE.WebGLRenderer();
renderer.setSize(innerWidth, innerHeight);
document.body.appendChild(renderer.domElement);
window.addEventListener("resize", event => {
camera.aspect = innerWidth / innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(innerWidth, innerHeight);
});
let controls = new OrbitControls(camera, renderer.domElement);
scene.add(new THREE.AxesHelper(10));
let g = new THREE.CylinderGeometry(5, 5, 5, 100, 20, true, 0, Math.PI * 1.5);
let m = new THREE.MeshBasicMaterial({
side: THREE.DoubleSide,
map: new THREE.TextureLoader().load(
"https://threejs.org/examples/textures/uv_grid_opengl.jpg",
tex => {
tex.wrapS = tex.wrapT = THREE.MirroredRepeatWrapping;
tex.repeat.set(3, 1);
}
)
});
let c = new THREE.Mesh(g, m);
scene.add(c);
renderer.setAnimationLoop(() => {
renderer.render(scene, camera);
});
</script>
I want to turn an image into a Point Cloud and offset the z-position based on the color value. So far I have:
Loaded an image, and upon load
Built a geometry and stored image colour within the geometry
Create a PointsMaterial and build a THREE.Points
Add to scene
However, the result looks nothing like the input image. I am missing something but unsure of what.
What am I missing with regards to displaying a point cloud version of the image?
Example: https://img2pointcloud.glitch.me/
<html>
<head>
<script src="https://threejs.org/build/three.js"></script>
</head>
<body style="margin:0">
<script>
var container, renderer, points;
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(
45,
window.innerWidth / window.innerHeight,
1,
10000
);
camera.position.set(0, 0, 1000);
var renderer = new THREE.WebGLRenderer();
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
var imgSrc =
"https://cdn.glitch.com/c3afecd9-365c-424f-a08e-90fce02a151a%2Fimg.jpeg?v=1588102020636";
var img = new Image();
var width = 1920 / 4;
var height = 1080 / 4;
img.crossOrigin = "anonymous";
img.src = imgSrc;
img.onload = function() {
var canvas = document.createElement("canvas");
canvas.width = width;
canvas.height = height;
var ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0);
var imageData = ctx.getImageData(0, 0, width, height);
var data = imageData.data;
document.body.appendChild(canvas);
createPointCloud(imageData);
};
function createPointCloud(imageData) {
var geometry = new THREE.BufferGeometry();
var positions = [];
for (var x = 0; x < height; x++) {
for (var z = 0; z < width; z++) {
positions.push(x, z, x);
}
}
var color = new THREE.Color();
var colors = [];
for (let i = 0; i < imageData.data.length; i += 4) {
const r = imageData.data[i + 0];
const g = imageData.data[i + 1];
const b = imageData.data[i + 2];
const a = imageData.data[i + 3];
color.setRGB(r, g, b);
colors.push(color.r, color.b, color.c);
}
geometry.setAttribute(
"position",
new THREE.Float32BufferAttribute(positions, 3)
);
geometry.setAttribute(
"color",
new THREE.Float32BufferAttribute(colors, 3)
);
geometry.computeBoundingSphere();
var material = new THREE.PointsMaterial({
size: 0.1,
vertexColors: true
});
points = new THREE.Points(geometry, material);
scene.add(points);
animate();
}
function animate() {
requestAnimationFrame(animate);
render();
}
function render() {
renderer.render(scene, camera);
}
</script>
</body>
</html>
[1]: https://i.stack.imgur.com/9etFB.png
[2]: https://i.stack.imgur.com/HKM7m.png
Another approach is to modify shaders of THREE.PointsMaterial():
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(60, innerWidth / innerHeight, 1, 1000);
camera.position.set(-75, 0, 1);
var renderer = new THREE.WebGLRenderer();
renderer.setSize(innerWidth, innerHeight);
document.body.appendChild(renderer.domElement);
var controls = new THREE.OrbitControls(camera, renderer.domElement);
new THREE.TextureLoader().load("https://cdn.glitch.com/c3afecd9-365c-424f-a08e-90fce02a151a%2Fimg.jpeg?v=1588102020636", tex => {
let img = tex.image;
console.log(img.width, img.height);
let g = new THREE.PlaneBufferGeometry(Math.floor(img.width / 4), Math.floor(img.height / 4), img.width, img.height);
let m = new THREE.PointsMaterial({
map: tex,
size: 0.1
});
m.onBeforeCompile = shader => {
shader.vertexShader = `
varying vec2 vUv;
${shader.vertexShader}
`;
shader.vertexShader = shader.vertexShader.replace(
`#include <color_vertex>`,
`
vUv = uv;
#include <color_vertex>`
);
shader.fragmentShader = `
varying vec2 vUv;
${shader.fragmentShader}
`;
shader.fragmentShader = shader.fragmentShader.replace(
`#include <map_particle_fragment>`,
`vec4 mapTexel = texture2D( map, vUv );
diffuseColor = mapTexel;
`
);
console.log(shader.vertexShader);
};
let p = new THREE.Points(g, m);
scene.add(p);
});
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>
There's a big typo in my code where it for some reason says colors.push(color.r, color.b, color.c); and not colors.push(color.r, color.g, color.b); (notice the rbc vs rgb).
Sorted now, thanks for the comment, made me see it.
I'm trying to add a THREE.js scene into a Mapbox GL visualization following this example. I've added a sphere and a ground plane and a DirectionalLight. Now I'm trying to get the light to cast a shadow on the ground plane. Adding a DirectionalLightHelper and a CameraHelper for the light's shadow camera, everything looks pretty reasonable to me:
I'd expect to see a shadow for the sphere on the plane.
Full code here, but here are the highlights:
class SpriteCustomLayer {
type = 'custom';
renderingMode = '3d';
constructor(id) {
this.id = id;
this.gui = new dat.GUI();
THREE.Object3D.DefaultUp.set(0, 0, 1);
}
async onAdd(map, gl) {
this.camera = new THREE.Camera();
const centerLngLat = map.getCenter();
this.center = MercatorCoordinate.fromLngLat(centerLngLat, 0);
const {x, y, z} = this.center;
this.cameraTransform = new THREE.Matrix4()
.makeTranslation(x, y, z)
.scale(new THREE.Vector3(1, -1, 1));
this.map = map;
this.scene = this.makeScene();
this.renderer = new THREE.WebGLRenderer({
canvas: map.getCanvas(),
context: gl,
antialias: true,
});
this.renderer.shadowMap.enabled = true;
this.renderer.autoClear = false;
}
makeScene() {
const scene = new THREE.Scene();
scene.add(new THREE.AmbientLight(0xffffff, 0.25));
const s = this.center.meterInMercatorCoordinateUnits();
const light = new THREE.DirectionalLight(0xffffff, 1);
light.position.set(0.000002360847837325531, 0.000004566603480958114, 0.00000725142167844218);
light.target.position.set(0, 0, 0);
light.castShadow = true;
light.shadow.mapSize.width = 1024;
light.shadow.mapSize.height = 1024;
light.shadow.camera.left = -0.000002383416166278454 * 2;
light.shadow.camera.right = 0.000002383416166278454 * 2;
light.shadow.camera.bottom = -0.000002383416166278454 * 2;
light.shadow.camera.top = 0.000002383416166278454 * 2;
light.shadow.camera.near = 0.0000012388642793465356;
light.shadow.camera.far *= s;
scene.add(light);
this.light = light;
{
const planeSize = 500;
const loader = new THREE.TextureLoader();
const texture = loader.load('/checker.png');
texture.wrapS = THREE.RepeatWrapping;
texture.wrapT = THREE.RepeatWrapping;
texture.magFilter = THREE.NearestFilter;
const repeats = 10;
texture.repeat.set(repeats, repeats);
const planeGeo = new THREE.PlaneBufferGeometry(planeSize, planeSize);
const planeMat = new THREE.MeshPhongMaterial({
map: texture,
side: THREE.DoubleSide,
});
const plane = new THREE.Mesh(planeGeo, planeMat);
plane.scale.setScalar(s);
plane.receiveShadow = true;
scene.add(plane);
}
{
const sphereRadius = 5e-7;
const sphereGeo = new THREE.SphereBufferGeometry(sphereRadius, 32, 32);
const sphereMat = new THREE.MeshPhongMaterial({color: '#CA8'});
const mesh = new THREE.Mesh(sphereGeo, sphereMat);
mesh.position.set(0, 0, 5e-6);
mesh.castShadow = true;
mesh.receiveShadow = false;
sphereMat.side = THREE.DoubleSide;
scene.add(mesh);
}
return scene;
}
render(gl, matrix) {
this.camera.projectionMatrix = new THREE.Matrix4()
.fromArray(matrix)
.multiply(this.cameraTransform);
this.renderer.state.reset();
this.renderer.render(this.scene, this.camera);
this.map.triggerRepaint();
}
}
Mapbox GL JS uses a coordinate system where the entire world is in [0, 1] so the coordinates are pretty tiny. It also uses x/y for lat/lng and z for up, which is different than usual Three.js coordinates.
How can I get the shadow to appear? I'm using Three.js r109 and Mapbox GL JS 1.4.0. I've tried replacing the PlaneBufferGeometry with a thin BoxGeometry to no avail.
EDIT
Forget everything I said in my old answer.
The example below scales things WAY down and the shadow remains.
The kicker was here:
shadowLight.shadow.camera.near *= (scaleDown) ? 0.1 : 10;
shadowLight.shadow.camera.far *= (scaleDown) ? 0.1 : 10;
shadowLight.shadow.camera.updateProjectionMatrix(); // <========= !!!!!
I was updating the scale, but wasn't updating the near/far of the shadow camera. Then, once I was, I was forgetting to update that camera's projection matrix. With all the pieces back together, it seems to be working well.
Try adding a call to update the shadow-casting light's camera's projection matrix after you configure the values.
If it still doesn't work, maybe you can use my example to figure out what's going on in your code.
If MY example doesn't work for you, then it might be your hardware doesn't support the level of precision you need.
// just some random colors to show it's actually rendering
const colors = [
0xff0000, // 1e+1
0x00ff00, // 1e+0
0x0000ff, // 1e-1
0xffff00, // 1e-2
0xff00ff, // 1e-3
0x00ffff, // 1e-4
0xabcdef, // 1e-5
0xfedcba, // 1e-6
0x883300, // 1e-7
0x008833, // 1e-8
0x330088, // 1e-9
0x338800 // 1e-10
];
const renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.shadowMap.enabled = true; // turn on shadow mapping
renderer.setClearColor(0xcccccc);
document.body.appendChild(renderer.domElement);
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(28, 1, 1, 1000)
camera.position.set(25, 10, 15);
camera.lookAt(0, 0, 0);
const camLight = new THREE.PointLight(0xffffff, 1);
camera.add(camLight);
const floor = new THREE.Mesh(
new THREE.PlaneBufferGeometry(50, 50),
new THREE.MeshPhongMaterial({
color: "gray"
})
);
floor.receiveShadow = true;
floor.rotation.set(Math.PI / -2, 0, 0);
floor.position.set(0, -1, 0);
const sphere = new THREE.Mesh(
new THREE.SphereBufferGeometry(2, 16, 32),
new THREE.MeshPhongMaterial({
color: colors[0]
})
);
sphere.castShadow = true;
sphere.position.set(0, 1, 0);
const shadowLight = new THREE.PointLight(0xffffff, 1);
shadowLight.castShadow = true;
shadowLight.position.set(-10, 10, 5);
const group = new THREE.Group();
group.add(floor);
group.add(sphere);
group.add(shadowLight);
group.add(camera);
scene.add(group);
function render() {
renderer.render(scene, camera);
}
function resize() {
const W = window.innerWidth;
const H = window.innerHeight;
renderer.setSize(W, H);
camera.aspect = W / H;
camera.updateProjectionMatrix();
}
window.onresize = resize;
resize();
render();
let scaler = 10;
let scaleLevel = 10;
let scaleLevelOutput = document.getElementById("scaleLevel");
let scaleDown = true;
let colorIndex = 0;
setInterval(() => {
colorIndex += (scaleDown) ? 1 : -1;
scaleLevel *= (scaleDown) ? 0.1 : 10;
shadowLight.shadow.camera.near *= (scaleDown) ? 0.1 : 10;
shadowLight.shadow.camera.far *= (scaleDown) ? 0.1 : 10;
shadowLight.shadow.camera.updateProjectionMatrix();
if (scaleLevel < 1e-9 && scaleDown) {
scaleDown = false;
}
if (scaleLevel >= 10 && !scaleDown) {
scaleDown = true;
}
scaleLevelOutput.innerText = `SCALE LEVEL: ${scaleLevel.toExponential()}`;
group.scale.set(scaleLevel, scaleLevel, scaleLevel);
sphere.material.color.setHex(colors[colorIndex]);
sphere.material.needsUpdate = true;
render();
}, 1000);
body {
margin: 0;
overflow: hidden;
}
#scaleLevel {
font-family: monospace;
font-size: 2em;
position: absolute;
top: 0;
left: 0;
font-weight: bold;
margin: 5px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/109/three.min.js"></script>
<div id="scaleLevel">SCALE LEVEL: 1e+1</div>
In three.js I create a material that uses a texture to control transparency. The texture is created from a canvas. The canvas is drawn with fillStyle of rgba. Alpha varies across the canvas. The effect I am after is to vary transparency across the object the material is attached to. That is not happening. The object remains opaque.
Code:
tubeTexture = new THREE.Texture(canvas);
tubeTexture.center.set(0.5, 0.5);
tubeTexture.rotation = Math.PI/2.0;
// turn off any filtering to create sharp edges when highlighting
// tube section based on colorRamp highlighting.
tubeTexture.minFilter = tubeTexture.magFilter = THREE.NearestFilter;
// let tubeMaterial = new THREE.MeshBasicMaterial({ map: tubeTexture });
let tubeMaterial = new THREE.MeshPhongMaterial({ map: tubeTexture });
tubeMaterial.side = THREE.DoubleSide;
tubeMaterial.transparent = true;
// let tubeMaterial = sceneManager.stickMaterial.clone();
const tubeMesh = new THREE.Mesh(tubeGeometry, tubeMaterial);
What am I missing?
It seems to work for me
'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 = 5;
const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
camera.position.z = 2;
const scene = new THREE.Scene();
scene.background = new THREE.Color('white');
{
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 = 1;
const boxDepth = 1;
const geometry = new THREE.BoxGeometry(boxWidth, boxHeight, boxDepth);
const ctx = document.createElement('canvas').getContext('2d');
ctx.canvas.width = 256;
ctx.canvas.height = 256;
ctx.fillStyle = 'rgba(255, 255, 255, 0.25)';
ctx.beginPath();
ctx.arc(128, 128, 120, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = 'rgba(255, 255, 255, 0.5)';
ctx.beginPath();
ctx.arc(128, 128, 64, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = 'rgba(255, 255, 255, 1.0)';
ctx.beginPath();
ctx.arc(128, 128, 32, 0, Math.PI * 2);
ctx.fill();
const texture = new THREE.CanvasTexture(ctx.canvas);
const root = new THREE.Object3D();
scene.add(root);
function makeInstance(geometry, color, x) {
const material = new THREE.MeshPhongMaterial({
color,
map: texture,
transparent: true,
side: THREE.DoubleSide,
alphaTest: 0.1,
});
const cube = new THREE.Mesh(geometry, material);
root.add(cube);
cube.position.x = x;
return cube;
}
const cubes = [
makeInstance(geometry, 0x44aa88, 0),
makeInstance(geometry, 0x8844aa, -2),
makeInstance(geometry, 0xaa8844, 2),
];
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();
}
root.rotation.y = time * .2;
cubes.forEach((cube, ndx) => {
const speed = 1 + ndx * .1;
const rot = time * speed;
cube.rotation.x = rot;
cube.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/r103/three.min.js"></script>
Of course there are the normal issues related to transparency and sorting. An object will not be consistantly transparent to itself, only to other objects.
Hi i am having a problem maybe you can help me.
I have a camera that is going down a tube following a path. and a camera that rotates around that tube always pointing toward the next point in the tube. However, the camera sometimes can be below or beside the tube like a roller coaster. Like this
I have the position of point a and the position of the camera which is point b. I am always looking at point a+1
var bpoints = this.cameraPathpoints;
var apoints = this.pathPoints;
this.camera.position.copy(bpoints[i]);
this.camera.lookAt(apoints[i+1]);
The camera is always looking at the point correctly however i want that the camera rotates in its z axis so that it is always normal to the tube. I tried making some calculations so that the camera rotates in its z axis so that the camera always faces normal to the tube, however my calculations work only on certain positions. Maybe there is a simpler way to do this. Thank you very much for any help.
var angleRadians = Math.atan2(cpv[this.cameraPos].pos.y - centePoints[this.cameraPos].pos.y, cpv[this.cameraPos].pos.x - centePoints[this.cameraPos].pos.x);
if(angleRadians > 0 && angleRadians > Math.PI/2){
console.log("+90",(Math.PI/2) - angleRadians);
angleRadians = (Math.PI/2) - angleRadians;
this.camera.rotateZ(angleRadians);
console.log("rotated ", angleRadians * 180/Math.PI);
}
else if(angleRadians > 0 && angleRadians < Math.PI/2 && anglesum >
Math.PI/2){
console.log("-90",(Math.PI/2) - angleRadians);
angleRadians = (Math.PI/2) - angleRadians;
this.camera.rotateZ(-angleRadians);
console.log("rotated ", -angleRadians * 180/Math.PI);
}
else if(angleRadians > 0 && angleRadians < Math.PI/2){
console.log("-90",(Math.PI/2) + angleRadians);
angleRadians = -(Math.PI/2) - (angleRadians/Math.PI/2);
this.camera.rotateZ(angleRadians);
console.log("rotated ", angleRadians * 180/Math.PI);
}
else if(angleRadians < 0 && angleRadians < -Math.PI/2){
console.log("--90");
angleRadians = (Math.PI/2) + angleRadians;
this.camera.rotateZ(-angleRadians);
console.log("rotated ",-angleRadians * 180/Math.PI);
}else if(angleRadians < 0 && angleRadians > -Math.PI/2){
console.log("+-90");
angleRadians = (Math.PI/2) - angleRadians;
this.camera.rotateZ(-angleRadians);
console.log("rotated ", -angleRadians * 180/Math.PI);
}
Rather than doing math, make the camera a child of some other THREE.Object3D and use lookAt with that object. Set the camera's position and rotation relative to that object.
Below the object is called the mount. It goes down the path (center of the tube). The camera is a child of mount. The tube has a 1 unit radius so setting the camera.position.y to 1.5 makes it outside the tube. lookAt makes non-camera objects look down positive Z but the camera looks down negative Z so we rotate the camera 180 degrees.
Example:
'use strict';
/* global THREE */
function main() {
const canvas = document.querySelector('#c');
const renderer = new THREE.WebGLRenderer({canvas: canvas});
const scene = new THREE.Scene();
scene.background = new THREE.Color(0xAAAAAA);
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.y = 1.5; // 2 units above the mount
camera.rotation.y = Math.PI; // the mount will lootAt positiveZ
const mount = new THREE.Object3D();
mount.add(camera);
scene.add(mount);
{
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 curve = new THREE.Curves.GrannyKnot();
const tubularSegments = 200;
const radius = 1;
const radialSegments = 6;
const closed = true;
const tube = new THREE.TubeBufferGeometry(
curve, tubularSegments, radius, radialSegments, closed);
const texture = new THREE.DataTexture(new Uint8Array([128, 255, 255, 128]),
2, 2, THREE.LuminanceFormat);
texture.needsUpdate = true;
texture.magFilter = THREE.NearestFilter;
texture.wrapS = THREE.RepeatWrapping;
texture.wrapT = THREE.RepeatWrapping;
texture.repeat.set( 100, 4 );
const material = new THREE.MeshPhongMaterial({
map: texture,
color: '#8CF',
flatShading: true,
});
const mesh = new THREE.Mesh(tube, material);
scene.add(mesh);
const target = new THREE.Vector3();
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();
}
const t = time * 0.1 % 1;
curve.getPointAt(t, mount.position);
curve.getPointAt((t + 0.01) % 1, target);
mount.lookAt(target);
renderer.render(scene, camera);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
}
main();
body { margin: 0; }
canvas { 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/CurveExtras.js"></script>
You can easily orient the camera relative to the mount to say look more toward the path or way by setting camera.rotation.x. If you want to rotate around the mount either change the mount's up property or add another object between the mount and the camera and set its Z rotation.
'use strict';
/* global THREE */
function main() {
const canvas = document.querySelector('#c');
const renderer = new THREE.WebGLRenderer({canvas: canvas});
const scene = new THREE.Scene();
scene.background = new THREE.Color(0xAAAAAA);
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.y = 1.5; // 2 units above the mount
camera.rotation.y = Math.PI; // the mount will lootAt positiveZ
const mount = new THREE.Object3D();
const subMount = new THREE.Object3D();
subMount.rotation.z = Math.PI * .5;
subMount.add(camera);
mount.add(subMount);
scene.add(mount);
{
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 curve = new THREE.Curves.GrannyKnot();
const tubularSegments = 200;
const radius = 1;
const radialSegments = 6;
const closed = true;
const tube = new THREE.TubeBufferGeometry(
curve, tubularSegments, radius, radialSegments, closed);
const texture = new THREE.DataTexture(new Uint8Array([128, 255, 255, 128]),
2, 2, THREE.LuminanceFormat);
texture.needsUpdate = true;
texture.magFilter = THREE.NearestFilter;
texture.wrapS = THREE.RepeatWrapping;
texture.wrapT = THREE.RepeatWrapping;
texture.repeat.set( 100, 4 );
const material = new THREE.MeshPhongMaterial({
map: texture,
color: '#8CF',
flatShading: true,
});
const mesh = new THREE.Mesh(tube, material);
scene.add(mesh);
const target = new THREE.Vector3();
const target2 = new THREE.Vector3();
const mountToTarget = new THREE.Vector3();
const targetToTarget2 = new THREE.Vector3();
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();
}
const t = time * 0.1 % 1;
curve.getPointAt(t, mount.position);
curve.getPointAt((t + 0.01) % 1, target);
// set mount up to be perpenticular to the
// curve
curve.getPointAt((t + 0.02) % 1, target2);
mountToTarget.subVectors(mount.position, target).normalize();
targetToTarget2.subVectors(target2, target).normalize();
mount.up.crossVectors(mountToTarget, targetToTarget2);
mount.lookAt(target);
renderer.render(scene, camera);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
}
main();
body { margin: 0; }
canvas { 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/CurveExtras.js"></script>