three.js: Cylinder with negative thetaLength - three.js

I want to use an "inverted" cylinder with the thetaLength set to -6.3 so that the inside will be classed as the FrontSide, so then that cylinder can go inside another normal cylinder with rings on the top and bottom to form a tube.
The reason I want to do all this is so the whole object can be combined into one mesh with a single material. If you don't set the thetaLength to negative you have to have a duplicate material with the side set to BackSide, so you can't have it all as one mesh.
I've done what I'm talking about in the example below (you can zoom and move with the mouse). The negative theta cylinder is on the left and the normal one is on the right.
The problem that I'm having is, you can see that cylinder in question (the inside) of the left one is much darker than on the right one. The right one looks much more realistic.
I'm thinking maybe it's because it thinks the light is coming from a different direction to where it's actually coming from.
Is there any way to fix this and make the inverted cylinder appear like the BackSide one so that I can have a tube like this as a single mesh?
width = window.innerWidth
height = window.innerHeight
renderer = new THREE.WebGLRenderer({antialias: true})
renderer.setClearColor(0x8e8ed7)
renderer.setPixelRatio(window.devicePixelRatio)
renderer.setSize(width, height)
document.body.appendChild(renderer.domElement)
scene = new THREE.Scene()
camera = new THREE.PerspectiveCamera(35, width / height, 0.1, 3000)
camera.position.set(0, 50, 100)
controls = new THREE.OrbitControls(camera)
controls.minDistance = 40
controls.maxDistance = 1300
scene.add(camera, new THREE.AmbientLight(0xffffff, 0.48))
light = new THREE.PointLight(0xffffff, 0.55)
light.position.copy( camera.position );
light.position.y -= 80
light.position.x += 100
camera.add(light)
requestAnimationFrame(function animate(){
requestAnimationFrame(animate)
renderer.render(scene, camera)
})
material = new THREE.MeshPhongMaterial({color: 0xFF7E14, specular: 0x111111, shininess: 75})
material2= new THREE.MeshPhongMaterial({color: 0xFF7E14, specular: 0x111111, shininess: 75, side: THREE.BackSide})
tube_a = new THREE.Mesh(new THREE.CylinderGeometry(6,6,20,32,1,true,0,-6.3), material)
tube_aa = new THREE.Mesh(new THREE.CylinderGeometry(6,6,20,32,1,true,0,6.3), material2)
tube_b = new THREE.Mesh(new THREE.CylinderGeometry(8.1375,8.1375,20,32,1,true), material)
ring = new THREE.Mesh(new THREE.RingGeometry(6,8.1375,32), material)
group1 = new THREE.Group()
ta1 = tube_a.clone()
group1.add(ta1)
tb1 = tube_b.clone()
group1.add(tb1)
r = ring.clone()
r.position.y -= 10
r.rotateX((9*Math.PI)/18)
group1.add(r)
r = ring.clone()
r.position.y += 10
r.rotateX((27*Math.PI)/18)
group1.add(r)
group1.position.x -= 15
scene.add(group1)
group2 = new THREE.Group()
ta2 = tube_aa.clone()
group2.add(ta2)
tb2 = tube_b.clone()
group2.add(tb2)
r = ring.clone()
r.position.y -= 10
r.rotateX((9*Math.PI)/18)
group2.add(r)
r = ring.clone()
r.position.y += 10
r.rotateX((27*Math.PI)/18)
group2.add(r)
group2.position.x += 15
scene.add(group2)
<script src="http://threejs.org/build/three.min.js"></script>
<script src="http://threejs.org/examples/js/controls/OrbitControls.js"></script>

The problem that I'm having is, you can see that cylinder in question (the inside) of the left one is much darker than on the right one. The right one looks much more realistic.
You have to update the vertex normal vectors by Geometry.computeVertexNormals() after generating the mesh, to solve this issue:
tube_a = new THREE.Mesh(new THREE.CylinderGeometry(6,6,20,32,1,true,0,-6.3), material)
tube_a.geometry.computeVertexNormals();
width = window.innerWidth
height = window.innerHeight
renderer = new THREE.WebGLRenderer({antialias: true})
renderer.setClearColor(0x8e8ed7)
renderer.setPixelRatio(window.devicePixelRatio)
renderer.setSize(width, height)
document.body.appendChild(renderer.domElement)
scene = new THREE.Scene()
camera = new THREE.PerspectiveCamera(35, width / height, 0.1, 3000)
camera.position.set(0, 50, 100)
controls = new THREE.OrbitControls(camera)
controls.minDistance = 40
controls.maxDistance = 1300
scene.add(camera, new THREE.AmbientLight(0xffffff, 0.48))
light = new THREE.PointLight(0xffffff, 0.55)
light.position.copy( camera.position );
light.position.y -= 80
light.position.x += 100
camera.add(light)
requestAnimationFrame(function animate(){
requestAnimationFrame(animate)
renderer.render(scene, camera)
})
material = new THREE.MeshPhongMaterial({color: 0xFF7E14, specular: 0x111111, shininess: 75})
material2= new THREE.MeshPhongMaterial({color: 0xFF7E14, specular: 0x111111, shininess: 75, side: THREE.BackSide})
tube_a = new THREE.Mesh(new THREE.CylinderGeometry(6,6,20,32,1,true,0,-6.3), material)
tube_a.geometry.computeVertexNormals();
tube_aa = new THREE.Mesh(new THREE.CylinderGeometry(6,6,20,32,1,true,0,6.3), material2)
tube_b = new THREE.Mesh(new THREE.CylinderGeometry(8.1375,8.1375,20,32,1,true), material)
ring = new THREE.Mesh(new THREE.RingGeometry(6,8.1375,32), material)
group1 = new THREE.Group()
ta1 = tube_a.clone()
group1.add(ta1)
tb1 = tube_b.clone()
group1.add(tb1)
r = ring.clone()
r.position.y -= 10
r.rotateX((9*Math.PI)/18)
group1.add(r)
r = ring.clone()
r.position.y += 10
r.rotateX((27*Math.PI)/18)
group1.add(r)
group1.position.x -= 15
scene.add(group1)
group2 = new THREE.Group()
ta2 = tube_aa.clone()
group2.add(ta2)
tb2 = tube_b.clone()
group2.add(tb2)
r = ring.clone()
r.position.y -= 10
r.rotateX((9*Math.PI)/18)
group2.add(r)
r = ring.clone()
r.position.y += 10
r.rotateX((27*Math.PI)/18)
group2.add(r)
group2.position.x += 15
scene.add(group2)
function resize() {
var aspect = window.innerWidth / window.innerHeight;
renderer.setSize(window.innerWidth, window.innerHeight);
camera.aspect = aspect;
camera.updateProjectionMatrix();
//controls.handleResize();
}
window.onresize = resize;
<script src="http://threejs.org/build/three.min.js"></script>
<script src="http://threejs.org/examples/js/controls/OrbitControls.js"></script>

Related

three.js: Casting SpotLight Shadows

all.
I'm working with three.js 127, following the documentation to add a SpotLight. However, shadows won't get shown on the scene. Below, you can find my scene. I already set the plane to receive shadow as well as other elements enabled to cast shadows yet no shadows are rendered on the scene.
const canvas = document.getElementById('webgl-output')
const init = () => {
const scene = new THREE.Scene()
const camera = new THREE.PerspectiveCamera(
45,
window.innerWidth / window.innerHeight,
0.1,
1000,
)
const renderer = new THREE.WebGLRenderer({
canvas,
})
renderer.setClearColor(new THREE.Color(0x000000))
renderer.setSize(window.innerWidth, window.innerHeight)
const planeGeometry = new THREE.PlaneGeometry(60, 20)
const planeMaterial = new THREE.MeshLambertMaterial({
color: 0xaaaaaa,
})
const plane = new THREE.Mesh(planeGeometry, planeMaterial)
plane.receiveShadow = true
plane.rotation.x = -0.5 * Math.PI
plane.position.x = 15
plane.position.y = 0
plane.position.z = 0
scene.add(plane)
// Creating a cube
const cubeGeometry = new THREE.BoxGeometry(4, 4, 4)
const cubeMaterial = new THREE.MeshLambertMaterial({
color: 0xff0000,
})
const cube = new THREE.Mesh(cubeGeometry, cubeMaterial)
cube.castShadow = true
cube.position.x = -4
cube.position.y = 3
cube.position.z = 0
scene.add(cube)
// Creating a sphere
const sphereGeometry = new THREE.SphereGeometry(4, 20, 20)
const sphereMaterial = new THREE.MeshLambertMaterial({
color: 0x7777ff,
})
const sphere = new THREE.Mesh(sphereGeometry, sphereMaterial)
sphere.castShadow = true
sphere.position.x = 20
sphere.position.y = 4
sphere.position.z = 2
scene.add(sphere)
// Position and point the camera to the center of the scene
camera.position.x = -30
camera.position.y = 12
camera.position.z = 30
camera.lookAt(scene.position)
// Add spotlight for the shadows
const spotLight = new THREE.SpotLight(0xffffff)
spotLight.position.set(8, 40, 80)
spotLight.castShadow = true
spotLight.shadow.mapSize.width = 1024
spotLight.shadow.mapSize.height = 1024
spotLight.shadow.camera.near = 8
spotLight.shadow.camera.far = 80
spotLight.shadow.camera.fov = 16
scene.add(spotLight)
renderer.render(scene, camera)
}
init()
Am I missing something to get the shadow rendered?
I noticed a few things from your code that may have caused this issue:
The biggest thing is that you didn't enable the shadowMap on your renderer, you can do this like so :
renderer.shadowMap.enabled = true;
This part doesn't seem to be appearing on the documentation of SpotLight which you linked, more information about this can be found on the documentation of SpotLightShadow.
After I tried this on a JS sandbox, I still didn't see the shadow, but it seems like the spotLight was simply too far away, it was set to 80 at it's 'z' position. I made the value slightly smaller (40) and it seemed to do the trick. What helped me figure it out is by using a helper and OrbitControls which makes it a little easier to debug:
import { OrbitControls } from 'https://cdn.jsdelivr.net/npm/three#0.127/examples/jsm/controls/OrbitControls.js';
...
spotLight.position.set(8, 40, 40); // Third parameter (z) with value of 80 was too far away
controls = new OrbitControls(camera, renderer.domElement);
helper = new THREE.PointLightHelper(pointLight);
scene.add(helper);
Here is a JSFiddle of the fixed version: https://jsfiddle.net/tombugolya/hx61L5vp/17/

Connecting two components in Three.js

I would like to build something that allows the connection of two components, kind of like a guitar cable will plug into an amp from a guitar. So I want to connect to one point, and then drag the connection to a second point and have there be some natural hang in the rope. I made this example from a previous SO question (Three.js rope / cable effect - animating thick lines), but I can't seem to get the calculation right. So the question would be, how would I be able to make a function with this signature:
function drawSpline(startPoint, endPoint, ropeLength) {
// returns a new THREE.Line object with a natural curvature for "slack" in the rope between the two points
}
This is what I have so far:
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 );
scene.add(camera);
camera.position.z = 10;
const renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
function animate() {
requestAnimationFrame( animate );
renderer.render( scene, camera );
}
animate();
const RADIUS = 1;
const SEGMENTS = 16;
const RINGS = 16;
const sphereMaterial =
new THREE.MeshLambertMaterial(
{
color: 0xCC0000
});
const sphere = new THREE.Mesh(
new THREE.SphereGeometry(
RADIUS,
SEGMENTS,
RINGS),
sphereMaterial);
sphere.position.z = 0;
const pointLight =
new THREE.PointLight(0xFFFFFF);
// set its position
pointLight.position.x = 10;
pointLight.position.y = 50;
pointLight.position.z = 130;
// add to the scene
scene.add(pointLight);
const shiftRatio = 7.5;
scene.add(drawSpline({x: 0, y: -3, z: 0}, {x: 6, y: 1, z: 0}, 'blue'));
function drawSpline(beginning, end, clr){
let ySign = Math.sign((end.y + beginning.y) / 2)
let appliedRatio = shiftRatio;
let midVector = new THREE.Vector3( (end.x + beginning.x) / 8, (end.y+beginning.y)/2, (end.z+beginning.z)/ 2 )
let positionVector = new THREE.Vector3(0,end.y-beginning.y,end.z-beginning.z)
let orthogVector = new THREE.Vector3(0,positionVector.z,-positionVector.y).normalize()
var curve = new THREE.CatmullRomCurve3( [
new THREE.Vector3( beginning.x, beginning.y, beginning.z ),
midVector.clone().addScaledVector(orthogVector,ySign*appliedRatio),
new THREE.Vector3( end.x, end.y, end.z ),
]);
var points = curve.getPoints( 20 );
console.log(points);
var geometry = (new THREE.BufferGeometry()).setFromPoints( points );
var material = new THREE.LineBasicMaterial( { color : clr } );
// Create the final object to add to the scene
var curveObject = new THREE.Line( geometry, material );
return curveObject;
}

How to build a perpendicular in three.js?

I need to build a perpendicular (line at an angle of 90 degrees to the original line) across the mid point of a straight line.
line = new THREE.Line( geometry, material );
geometry.vertices.push(
new THREE.Vector3( 100, 200, 0 ),
new THREE.Vector3( 300, 500, 0 ) );
In the manual I was not found information about it.
https://threejs.org/docs/#api/en/objects/Line
Thank you!
You can find the normal of a line by subtracting its start point from its end point (thus you'll get its direction), then rotate the resulted vector at 90 degrees (Math.PI * 0.5), then normalize it, and this is it, you've got the normal.
In the code snippet, the line itself is blueish (aqua), its normal is red.
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 1000);
camera.position.set(0, 0, 10);
var renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
var grid = new THREE.GridHelper(10, 10);
grid.rotation.x = -Math.PI * 0.5;
scene.add(grid);
var lineVertices = [
new THREE.Vector3(1, 2),
new THREE.Vector3(3, 5)
];
var lineGeom = new THREE.BufferGeometry().setFromPoints(lineVertices);
var lineMat = new THREE.LineBasicMaterial({color: 0x00ffff});
var line = new THREE.Line(lineGeom, lineMat);
scene.add(line);
var midPoint = new THREE.Vector3()
.subVectors(lineVertices[1], lineVertices[0])
.multiplyScalar(0.5)
.add(lineVertices[0]);
var normal = new THREE.Vector3()
.subVectors(lineVertices[1], lineVertices[0])
.applyAxisAngle(new THREE.Vector3(0, 0, 1), Math.PI * 0.5)
.normalize();
var normalVertices = [
normal.clone().setLength(2).add(midPoint),
normal.clone().negate().setLength(2).add(midPoint)
];
var normalGeom = new THREE.BufferGeometry().setFromPoints(normalVertices);
var normalMat = new THREE.LineBasicMaterial({color: 0xff0000 });
var normal = new THREE.Line(normalGeom, normalMat);
scene.add(normal);
renderer.setAnimationLoop(()=>{renderer.render(scene, camera)});
body {
overflow:hidden;
margin: 0;
}
<script src="https://threejs.org/build/three.min.js"></script>

three.js: Bright edges in shadows

It seems that when an object casts and receives shadows you get this bright edge that looks like light bleeding through. Is there any way to overcome this? In the snippet I give here you can see that the shadow from the cylinder on the two cubes doesn't have the issue in the picture, but on the back is where you see the line happening.
width = window.innerWidth
height = window.innerHeight
renderer = new THREE.WebGLRenderer({antialias: true})
renderer.setClearColor(0xeeeeee)
renderer.setPixelRatio(window.devicePixelRatio)
renderer.setSize(width, height)
document.body.appendChild(renderer.domElement)
scene = new THREE.Scene()
camera = new THREE.PerspectiveCamera(35, width / height, 0.1, 3000)
camera.position.set(-45, 47, 75)
controls = new THREE.OrbitControls(camera, renderer.domElement)
controls.minDistance = 40
controls.maxDistance = 1300
material = new THREE.MeshPhongMaterial({color: 0xFF0000, specular: 0x111111, shininess: 75})
scene.add(camera, new THREE.AmbientLight(0xffffff, 0.4))
light = new THREE.PointLight(0xffffff, 0.8)
renderer.shadowMap.enabled = true
renderer.shadowMap.type = THREE.PCFShadowMap
light.castShadow = true
light.shadow.mapSize.width = 3072
light.shadow.mapSize.height = 3072
light.shadow.camera.left = 500
function shadow(w) {
w.castShadow = true
w.receiveShadow = true
}
camera.add(light)
light.position.y += 60
light.position.x += 70
requestAnimationFrame(function animate(){
requestAnimationFrame(animate)
renderer.render(scene, camera)
})
b = new THREE.Mesh(new THREE.BoxGeometry(20, 20, 20), material)
shadow(b)
scene.add(b)
c = new THREE.Mesh(new THREE.CylinderGeometry(5,5,10,32), material)
c.position.set(3,15,3)
shadow(c)
scene.add(c)
d = new THREE.Mesh(new THREE.BoxGeometry(20, 10, 1), material)
d.position.set(3,15,-5)
shadow(d)
scene.add(d)
<script src="https://threejs.org/build/three.min.js"></script>
<script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>

three.js: At a certain distance the entire scene goes dark in shadow

In this example you can zoom in and out with the mouse wheel. If you keep zooming out and keep making the scene smaller it eventually goes completely dark. Is there any way to stop it doing that?
I thought maybe the range of the light is the problem but the default is set to go on forever so I don't know what's causing it.
width = window.innerWidth
height = window.innerHeight
renderer = new THREE.WebGLRenderer({antialias: true})
renderer.setClearColor(0xeeeeee)
renderer.setPixelRatio(window.devicePixelRatio)
renderer.setSize(width, height)
document.body.appendChild(renderer.domElement)
scene = new THREE.Scene()
camera = new THREE.PerspectiveCamera(35, width / height, 0.1, 3000)
camera.position.set(-45, 47, 75)
controls = new THREE.OrbitControls(camera)
controls.minDistance = 40
controls.maxDistance = 1300
material = new THREE.MeshPhongMaterial({color: 0xFF0000, specular: 0x111111, shininess: 75})
scene.add(camera, new THREE.AmbientLight(0xffffff, 0.4))
light = new THREE.PointLight(0xffffff, 0.8)
renderer.shadowMap.enabled = true
renderer.shadowMap.type = THREE.PCFShadowMap
light.castShadow = true
light.shadow.mapSize.width = 3072
light.shadow.mapSize.height = 3072
light.shadow.camera.left = 500
function shadow(w) {
w.castShadow = true
w.receiveShadow = true
}
camera.add(light)
light.position.y += 60
light.position.x += 70
requestAnimationFrame(function animate(){
requestAnimationFrame(animate)
renderer.render(scene, camera)
})
b = new THREE.Mesh(new THREE.BoxGeometry(20, 20, 20), material)
shadow(b)
scene.add(b)
c = new THREE.Mesh(new THREE.CylinderGeometry(5,5,10,32), material)
c.position.set(3,15,3)
shadow(c)
scene.add(c)
d = new THREE.Mesh(new THREE.BoxGeometry(20, 10, 1), material)
d.position.set(3,15,-5)
shadow(d)
scene.add(d)
<script src="http://threejs.org/build/three.min.js"></script>
<script src="http://threejs.org/examples/js/controls/OrbitControls.js"></script>
You can solve the problem by increasing the far property of the light`s internal shadow camera. Try something like:
light.shadow.camera.far = 3000

Resources