rotate mesh on page scroll in three.js - three.js

I'd like to rotate an object when the page scrolls, but I cant make it work properly.
What I have so far and updates the rotation of the object on page load is:
mesh.rotation.y = window.pageYOffset;
this however only updates the object on page load and not updating it further on scroll.
I know that for the camera you have to update the projection matrix to be able to have an interactive camera, on objects/meshes however this seems to be on by default (mesh.updateMatrix), I still cant make it work. What am I doing wrong?

I know that for the camera you have to update the projection matrix to be able to have an interactive camera,
I'm afraid this is not right. In many applications the projection matrix is actually static. A camera in three.js is a 3D object like a mesh. You can modify the position or rotation properties in order to transform it.
Check out the following live example that shows how to animate a cube on scroll via GSAP:
let camera, scene, renderer;
init();
animate();
function init() {
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(40, window.innerWidth / ( window.innerHeight * 2 ), 0.1, 10);
camera.position.set(0, 0, 7);
const geometry = new THREE.BoxBufferGeometry();
const material = new THREE.MeshNormalMaterial();
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
renderer = new THREE.WebGLRenderer({antialias: true});
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize(window.innerWidth, window.innerHeight * 2);
document.body.appendChild(renderer.domElement);
// gsap
gsap.registerPlugin(ScrollTrigger);
gsap.to(mesh.rotation, {
scrollTrigger: {
trigger: "#trigger",
start: "top top",
end: "bottom top",
scrub: true,
toggleActions: "restart pause resume pause"
},
y: Math.PI
});
}
function animate() {
requestAnimationFrame(animate);
renderer.render(scene, camera);
}
body {
margin: 0px;
}
canvas {
display: block;
}
#trigger {
position: absolute;
height: 100%;
}
<script src="https://cdn.jsdelivr.net/npm/three#0.122/build/three.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.5.1/gsap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.5.1/ScrollTrigger.min.js"></script>
<div id="trigger">
</div>

Related

ThreeJS - Create cube where the surfaces are transparent instead of the cube volume

I am using the following code to create this 3D transparent cube.
// Create the cube itself
const cubeGeom = new THREE.BoxGeometry( 1, 1, 1 );
const material = new THREE.MeshBasicMaterial( {color: 0x00ff00, opacity:0.4, transparent:true});
const cube = new THREE.Mesh( cubeGeom, material );
// Also add a wireframe to the cube to better see the depth
const _wireframe = new THREE.EdgesGeometry( cubeGeom ); // or WireframeGeometry( geometry )
const wireframe = new THREE.LineSegments( _wireframe);
// Rotate it a little for a better vantage point
cube.rotation.set(0.2, -0.2, -0.1)
wireframe.rotation.set(0.2, -0.2, -0.1)
// add to scene
scene.add( cube )
scene.add( wireframe );
As can been seen, the cube appears as a single volume that is transparent. Instead, I would want to create a hollow cube with 6 transparent faces. Think of a cube made out of 6 transparent and colored window-panes. See this example: my desired result would be example 1 for each of the 6 faces, but now it is like example 2.
Update
I tried to create individual 'window panes'. However the behavior is not as I would expect.
I create individual panes like so:
geometry = new THREE.PlaneGeometry( 1, 1 );
material = new THREE.MeshBasicMaterial( {color: 0x00ff00, side: THREE.DoubleSide, transparent:true, opacity:0.2});
planeX = new THREE.Mesh( geometry, material);
planeY = new THREE.Mesh( geometry, material);
planeZ = new THREE.Mesh( geometry, material);
And then I add all three planes to wireframe.
Then I rotate them a little, so they intersect at different orientations.
const RAD_TO_DEG = Math.PI * 2 / 360;
planeX.rotation.y = RAD_TO_DEG * 90
planeY.rotation.x = RAD_TO_DEG * 90
Now I can see the effect of 'stacking' the panes on top of each other, however it is not as it should be.
I would instead expect something like this based on real physics (made with terrible paint-skills). That is, the color depends on the number of overlapping panes.
EDIT
When transparent panes overlap from the viewing direciton, transparancy appears to work perfectly. However, when the panes intersect it breaks.
Here I have copied the snipped provided by #Anye and added one.rotation.y = Math.PI * 0.5 and commented out two.position.set(0.5, 0.5, 0.5); so that the panes intersect.
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera( 75, window.innerWidth/window.innerHeight, 0.1, 1000 );
var renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
var cube = new THREE.Group();
one = new Pane();
two = new Pane();
one.rotation.y = Math.PI * 0.5
one.position.z = 0.2;
// two.position.set(0.5, 0.5, 0.5);
cube.add(one);
cube.add(two);
cube.rotation.set(Math.PI / 4, Math.PI / 4, Math.PI / 4);
scene.add(cube);
function Pane() {
let geometry = new THREE.PlaneGeometry(1, 1);
let material = new THREE.MeshBasicMaterial({color:0x00ff00, transparent: true, opacity: 0.4});
let mesh = new THREE.Mesh(geometry, material);
return mesh;
}
camera.position.z = 2;
var animate = function () {
requestAnimationFrame( animate );
renderer.render(scene, camera);
};
animate();
body {
margin: 0;
overflow: hidden;
}
canvas {
width: 640px;
height: 360px;
}
<html>
<head>
<title>Demo</title>
</head>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/87/three.min.js"></script>
</body>
</html>
EDIT
The snipped looks pretty good; it clearly shows a different color where the panes overlap. However, it does not show this everywhere. See this image. The left is what the snippet generates, the right is what it should look like. Only the portion of overlap that is in front of the intersection shows the discoloration, while the section behind the intersection should, but does not show discoloration.
You might want to take a look at CSG, Constructive Solid Geometry. With CSG, you can create a hole in your original cube using a boolean. To start, you could take a look at this quick tutorial. Below are some examples of what you can do with CSG.
var cube = new CSG.cube();
var sphere = CSG.sphere({radius: 1.3, stacks: 16});
var geometry = cube.subtract(sphere);
=>
CSG, though, has some limitations, since it isn't made specifically for three.js. A cheap alternative would be to create six individual translucent panes, and format them to create a cube. Then you could group them:
var group = new THREE.Group();
group.add(pane1);
group.add(pane2);
group.add(pane3);
group.add(pane4);
group.add(pane5);
group.add(pane6);
Update
Something may be wrong with your code, which is why it isn't shading accordingly for you. See this minimal example, which shows how the panes shade appropriately based on overlaps.
Update 2
I updated the snippet so the 2 panes aren't touching at all... I am still able to see the shading. Maybe if you were to try to reproduce this example?
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera( 75, window.innerWidth/window.innerHeight, 0.1, 1000 );
var renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
var cube = new THREE.Group();
one = new Pane();
two = new Pane();
one.rotation.y = Math.PI * 0.5;
one.position.z = 0.2;
cube.add(one);
cube.add(two);
cube.rotation.set(Math.PI / 4, Math.PI / 4, Math.PI / 4);
scene.add(cube);
function Pane() {
let geometry = new THREE.PlaneGeometry(1, 1);
let material = new THREE.MeshBasicMaterial({color:0x00ff00, transparent: true, opacity: 0.4});
material.depthWrite = false
let mesh = new THREE.Mesh(geometry, material);
return mesh;
}
camera.position.z = 2;
var animate = function () {
requestAnimationFrame( animate );
renderer.render(scene, camera);
};
animate();
body {
margin: 0;
overflow: hidden;
}
canvas {
width: 640px;
height: 360px;
}
<html>
<head>
<title>Demo</title>
</head>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/87/three.min.js"></script>
</body>
</html>
Update 3
Below is a screenshot of what I see in your snippet... Seems to be working fine...
You're experiencing one of my first head-scratchers:
ShaderMaterial transparency
As the answer to that question states, the three.js transparency system performs order-dependent transparency. Normally, it will take whichever object is closest to the camera (by mesh position), but because all of your planes are centered at the same point, there is no winner, so you get some strange transparency effects.
If you move the plane meshes out to form the actual sides of the box, then you should see the effect you're looking for. But that won't be the end of strange transparency effects, And you would need to implement your own Order-Independent Transparency (or find an extension library that does it for you) to achieve more physically-accurate transparency effects.

How to set opacity in drawing lines use three.js, use LineBasicMaterial?

I want to change lines' width and opacity,and my code as this:
var geometry_link = new THREE.Geometry();
geometry_link.vertices.push(
new THREE.Vector3(item[0].x *1,item[0].y *1,item[0].z),
new THREE.Vector3(item[1].x *1,item[1].y *1,item[1].z)
);
geometry_link.colors.push(
new THREE.Color(0x000000),
new THREE.Color(0xffffff)
);
var line = new THREE.Line(geometry_link, new THREE.LineBasicMaterial({
vertexColors: true,
linewidth: 8,
transparent: true,
opacity: 0.1
})
but it dose not work, both linewidth and opacity. I have found the similar question,said linewidth does not work on windows, that's 5 years ago, how about now?
Thanks for your idea
LineBasicMaterial does support transparent lines as demonstrated by the following live example:
let camera, scene, renderer;
init();
animate();
function init() {
camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.01, 10);
camera.position.z = 1;
scene = new THREE.Scene();
const geometry = new THREE.BufferGeometry().setFromPoints([new THREE.Vector3(), new THREE.Vector3(1, 0, 0)]);
const material = new THREE.LineBasicMaterial({
transparent: true,
opacity: 0.5
});
const mesh = new THREE.Line(geometry, material);
scene.add(mesh);
renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
}
function animate() {
requestAnimationFrame(animate);
renderer.render(scene, camera);
}
body {
margin: 0;
}
canvas {
display: block;
}
<script src="https://cdn.jsdelivr.net/npm/three#0.119.1/build/three.js"></script>
Wide lines however are not supported. So setting linewidth to a different value like 1 has no effect in most browsers. This is a WebGL limitation in context of line primitives.
However, three.js provides a wide line implementation based on triangles (sometimes called mesh lines or ribbons). Check out the following example for more information:
https://threejs.org/examples/webgl_lines_fat

3D rendering a 2D cylinder in blender or three.js?

I'm curious if it's possible to render this object, for the purposes of being exported and used in three.js
pic of cylinder
Seemed simple at the time, but I've been having a lot of trouble getting it to work. I was able to get close to what I wanted with Blender's "freestyle lines", but they aren't exportable.
cylinder in blender
It looks like I will have to make a custom material or something like that, but I was wondering if there was a more straightforward way I haven't seen. I tried Three's "wireframe" and "EdgeGeometry" tools, but I'm trying to avoid rendering the interior edges of the model.
And it seems really difficult to make a mathematically perfect cylinder outline from a UV, unless there's something I'm not seeing!
Anyways, thank you for your time
I'm not sure how correct this approach is, but its result is almost similar
to what is shown on the picture you provided.
Reference
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 1000);
camera.position.set(2, 3, 5).setLength(400);
camera.lookAt(scene.position);
var renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setClearColor(0x999999);
renderer.setSize(window.innerWidth, window.innerHeight);
effect = new THREE.OutlineEffect(renderer);
document.body.appendChild(renderer.domElement);
var controls = new THREE.OrbitControls(camera, renderer.domElement);
var cylinder = new THREE.Mesh(new THREE.CylinderBufferGeometry(100, 100, 200, 32), new THREE.MeshBasicMaterial({
color: "white"
}));
scene.add(cylinder);
var lid = new THREE.Mesh(new THREE.CylinderBufferGeometry(99, 99, 0.001, 32), new THREE.MeshBasicMaterial({
color: "white"
}));
lid.geometry.translate(0, 101.0, 0);
scene.add(lid);
var lid2 = new THREE.Mesh(new THREE.CylinderBufferGeometry(99, 99, 0.001, 32), new THREE.MeshBasicMaterial({
color: "white"
}));
lid2.geometry.translate(0, -101.0, 0);
scene.add(lid2);
render();
function render() {
requestAnimationFrame(render);
effect.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>
<script src="https://threejs.org/examples/js/effects/OutlineEffect.js"></script>

THREE.js Orbit Controls not working

I am attempting to learn THREE and so far things are going well. I'm working to create a simple project building a golf ball on a golf tee. Eventually I'll put it inside a skybox and use some shaders to create the appearance of dimples in the ball.
For now I've been able to create the ball and the tee and get the camera and lights set up. I'm now attempting to add some orbit controls so I can rotate, pan and zoom around the scene. I added the orbitControls script and set it up how I see in the examples online. However when I attempt to orbit my camera snaps to the either 1,0,0 0,1,0 or 0,0,1 and eventually the scene completely fails and I end up looking along 0,0 with no object visible.
Here's my code in it's entirety
<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8>
<title>3D Sandbox</title>
<style>
html, body {
margin: 0px;
}
canvas {
background-color: #999;
position: absolute;
height: 100vh;
width: 100vw;
overflow: hidden;
}
</style>
</head>
<body>
<script src='/three.js'></script>
<script src="/orbitControls.js"></script>
<script type='text/javascript'>
let scene,
camera,
controls,
renderer,
height = window.innerHeight,
width = window.innerWidth;
window.addEventListener('load', init, false);
function init () {
createScene();
createCamera();
createLights();
createBall();
createTee();
render();
animate();
}
function createScene () {
let grid;
scene = new THREE.Scene();
renderer = new THREE.WebGLRenderer({
antialias: true,
alpha: true
});
renderer.setSize(width, height);
document.body.appendChild(renderer.domElement);
grid = new THREE.GridHelper(100, 5);
scene.add(grid);
}
function createCamera () {
camera = new THREE.PerspectiveCamera(45, width / height, 0.1, 1000);
camera.position.z = 50;
camera.position.y = 13;
camera.up = new THREE.Vector3(0,1,0);
//camera.lookAt(new THREE.Vector3(0,13,0));
controls = new THREE.OrbitControls(camera);
controls.target = new THREE.Vector3(0, 13, 0);
controls.addEventListener('change', render);
}
function createLights () {
let directionalLight, ambientLight;
directionalLight = new THREE.DirectionalLight(0x404040, 4);
directionalLight.position.set(0, 2000, 2000);
directionalLight.target.position.set(0, 2, 0);
scene.add(directionalLight);
ambientLight = new THREE.AmbientLight(0x404040, 2);
scene.add(ambientLight);
}
function createBall () {
let ballGeom, ballMaterial, ball;
ballGeom = new THREE.SphereGeometry(5, 32, 32);
ballMaterial = new THREE.MeshPhongMaterial({
color: 0xFFFFFF
});
ball = new THREE.Mesh(ballGeom, ballMaterial);
ball.position.y = 13.3;
scene.add(ball);
}
function createTee () {
let tee, stemGeom, stem, bevelGeom, bevel, topGeom, top, teeMat;
tee = new THREE.Object3D();
teeMat = new THREE.MeshPhongMaterial({
color: 0x0000FF
});
stemGeom = new THREE.CylinderGeometry(.9, 0.75, 7);
stem = new THREE.Mesh(stemGeom, teeMat);
tee.add(stem);
bevelGeom = new THREE.CylinderGeometry(1.5, .9, 2);
bevel = new THREE.Mesh(bevelGeom, teeMat);
bevel.position.y = 3.75;
tee.add(bevel);
topGeom = new THREE.CylinderGeometry(1.5, 1.5, .25);
top = new THREE.Mesh(topGeom, teeMat);
top.position.y = 4.875;
tee.add(top);
tee.position.y = 3.5;
scene.add(tee);
}
function render () {
renderer.render(scene, camera);
}
function animate() {
requestAnimationFrame( animate );
controls.update();
render();
}
</script>
</body>
</html>
In my case, the camera position was the same as OrbitControl target, and this made my OrbitControl seem "broken" (different symptoms than OP -- my OrbitControl just wouldn't work at all.)
As mentioned in this github comment, camera position and OrbitControl target cannot be the same; I repositioned my camera to fix this (camera.position.z = 10).
Remember, the camera (like any Object3D) is positioned at (0,0,0) by default, and the OrbitControl target property (a Vector3D) seems to also default that same point (0,0,0)
controls = new THREE.OrbitControls(camera);
needs to be
controls = new THREE.OrbitControls(camera, renderer.domElement);
The result is on this block.

Combining kinetic.js and three.js af kinetic.js v 5?

Read the article:
http://architects.dzone.com/articles/combining-threejs-and
It seems however thay after kinetic.js v. 5-> this solution doesn't work anymore.
Has anyone been able to find a workaround?
Best regards and thank you
Jens
Three.js used canvas element
so used jquery to get the canvas used by kineti.js stage as shown in following code.
<script>
var camera, scene, renderer, geometry, material, mesh;
var stage, layer3D, layerGUI;
var animating = true;
function init() {
stage = new Kinetic.Stage({container: "container", width: 700, height: 700});
layer3D = new Kinetic.Layer();
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(75, 600 / 600, 1, 10000);
camera.position.z = 1000;
scene.add(camera);
geometry = new THREE.CubeGeometry(200, 200, 200);
material = new THREE.MeshBasicMaterial({ color: 0xff0000, wireframe: true });
mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
stage.add(layer3D);
var canvas= layer3D.getCanvas();
canvas = $("#container canvas").get(0);
console.log( canvas );
console.log( layer3D );
renderer = new THREE.CanvasRenderer({ canvas: canvas });
renderer.setSize(700, 700);
var group = new Kinetic.Group({ draggable: true });
layerGUI = new Kinetic.Layer();
layerGUI.add(group);
stage.add(layerGUI);
render();
}
function animate() {
// note: three.js includes requestAnimationFrame shim
requestAnimationFrame(animate);
render();
}
function render() {
if (animating) {
mesh.rotation.x += 0.01;
mesh.rotation.y += 0.02;
}
renderer.render(scene, camera);
layerGUI.draw();
}
$(function () {
init();
animate();
});
</script>
Here canvas = $("#container canvas").get(0); is used to fetch the canvas of kinetic.js container.

Resources