How do I construct a hollow cylinder in Three.js - three.js

I'm having difficulties constructing a hollow cylinder in Three.js.
Should I go and construct it using CSG or by stitching the vertices together?

var extrudeSettings = {
amount : 2,
steps : 1,
bevelEnabled: false,
curveSegments: 8
};
var arcShape = new THREE.Shape();
arcShape.absarc(0, 0, 1, 0, Math.PI * 2, 0, false);
var holePath = new THREE.Path();
holePath.absarc(0, 0, 0.8, 0, Math.PI * 2, true);
arcShape.holes.push(holePath);
var geometry = new THREE.ExtrudeGeometry(arcShape, extrudeSettings);

This solution uses ChandlerPrall's ThreeCSG.js project: http://github.com/chandlerprall/ThreeCSG
(For now, I recommend using the experimental version that supports materials - the uv branch - http://github.com/chandlerprall/ThreeCSG/tree/uvs)
Here's the code you will need:
// Cylinder constructor parameters:
// radiusAtTop, radiusAtBottom, height, segmentsAroundRadius, segmentsAlongHeight
var smallCylinderGeom = new THREE.CylinderGeometry( 30, 30, 80, 20, 4 );
var largeCylinderGeom = new THREE.CylinderGeometry( 40, 40, 80, 20, 4 );
var smallCylinderBSP = new ThreeBSP(smallCylinderGeom);
var largeCylinderBSP = new ThreeBSP(largeCylinderGeom);
var intersectionBSP = largeCylinderBSP.subtract(smallCylinderBSP);
var redMaterial = new THREE.MeshLambertMaterial( { color: 0xff0000 } );
var hollowCylinder = intersectionBSP.toMesh( redMaterial );
scene.add( hollowCylinder );

It is unlikely that you would have to stitch vertices together. If your cylinder has no thickness, you can use THREE.CylinderGeometry(). If it does have thickness, you can use CSG.

Use SVGloader to load a circle of desired radius (as an SVG). Set the geometry to ExtrudeBufferGeometry and give it your desired height as depth in the extrude settings object.

Related

How to create a Points (Mesh) of TextGeometry?

I am creating a TextGeometry but not able to convert in Points like a sphere or like a detailed 3d object.
In the below image I have created a sphere using
var dotGeometry = new THREE.SphereBufferGeometry(2, 100, 100);
var dotMaterial = new THREE.PointsMaterial({ size: 1, sizeAttenuation: false });
var dot = new THREE.Points(dotGeometry, dotMaterial);//new THREE.MeshBasicMaterial()
scene.add(dot);
and the for text I use
const fontloader = new THREE.FontLoader();
fontloader.load('./models/font.json', function (font) {
const textgeometry = new THREE.TextGeometry('Hello', {
font: font,
size: 1,
height: 0.5,
curveSegments: 12,
bevelEnabled: false,
bevelThickness: 10,
bevelSize: 8,
bevelOffset: 0,
bevelSegments: 5
});
var dotMaterial = new THREE.PointsMaterial({ size: 1, sizeAttenuation: false });
let textmesh = new THREE.Points(textgeometry, dotMaterial) //new THREE.MeshBasicMaterial({
// color: 0xffffff,
// wireframe: true
// }));
textmesh.geometry.center();
scene.add(textmesh);
// textmesh.position.set(0, 2, 0)
});
So how to create a geometry having many points for text also, Why I am not able to use MeshBasicMaterial for Points in sphere?
What you see is actually the expected result. TextGeometry produces a geometry intended for meshes. If you use the same data for a point cloud, the result is probably not as expected since you just render each vertex as a point.
When doing this with TextGeometry, you will only see points at the front and back side of the text since points in between are not necessary for a mesh.
Consider to author the geometry in a tool like Blender instead and import it via GLTFLoader.

Three.js, Calculating the transformation matrix between two BufferGeometry position state

I'm working on a AR project, where an objects detection AI return the vertices of the detected object.
On the scene I have the detected object as the parent mesh with a BufferGeometry and the position attributes updated from the AI output, I need to calculate its transformation matrix when the vertices change and apply those transformation to its children.
How can I calculate the transformation matrix (Translation, Scaling, Rotation) from one "detection (position vertices)" to another.
Here is a simplified illustration of my problem, where the blue plane is the detected object and the red one its child, I need to calculate the blue plane transformation from its previous position apply them to red one so they can move together :
https://jsfiddle.net/uv76tj89/1/
Thanks.
If you know that the child geometry is going to be half as big as the parent geometry, just apply child.scale.set(0.5, 0.5, 0.5); and then assign the exact same vertex positions as the parent on update with:
parentGeometry.getAttribute('position').array = parentPositions[posIndex];
parentGeometry.getAttribute('position').needsUpdate = true;
childGeometry.getAttribute('position').array = parentPositions[posIndex];
childGeometry.getAttribute('position').needsUpdate = true;
the Three.js engine will apply the 1/2 scale and +5 to the z axis to the child vertices, so you don't have to worry about manually making these adjusments. See the demo below.
(Notice I created Float32Arrays within parentPositions[] as an optimization so you don't have to make a new array and a new BufferAttribute each time you update them. It's not a noticeable performance boost with just 4 vertices, but it does help when you have 1000's of vertices).
var scene = new THREE.Scene();
var camera = new THREE.OrthographicCamera( window.innerWidth / - 2, window.innerWidth / 2, window.innerHeight / 2, window.innerHeight / - 2, 1, 1000 );
var renderer = new THREE.WebGLRenderer({antialias : true});
renderer.setClearColor(0x444444);
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
camera.position.z = 100;
new THREE.OrbitControls( camera, renderer.domElement );
/* Helpers */
// Grid helper
var width = 100;
var height = 100;
var gridHelper = new THREE.GridHelper(width * 2, 10, 0x999999, 0x000000);
gridHelper.position.y = -height / 2;
scene.add(gridHelper)
// Axes helper
scene.add(new THREE.AxesHelper(50));
/* parent Mesh */
var parentGeometry = new THREE.PlaneBufferGeometry(width, height);
var parentMaterial = new THREE.MeshBasicMaterial( { color: 0x209ad6} );
var parent = new THREE.Mesh( parentGeometry, parentMaterial );
scene.add(parent);
/* Child mesh */
var childGeometry = new THREE.PlaneBufferGeometry(width, height);
var childMaterial = new THREE.MeshBasicMaterial( {color: 0xFF0000} );
var child = new THREE.Mesh( childGeometry, childMaterial );
parent.add(child);
// Apply desired transformations to the child Mesh
child.position.z = 5;
child.scale.set(0.5, 0.5, 0.5);
/* Parent positions */
// Make all position arrays Float32Array
// so we don't have to create a new one each frame.
var parentPositions = [
//Reference point
new Float32Array([
-50, 50, 0,
50, 50, 0,
-50, -50, 0,
50, -50, 0
]),
//Variations
new Float32Array([
-50, 50, 50,
50, 50, 50,
-50, -50, -50,
50, -50, -50
]),
new Float32Array([
-75, 75, -25,
75, 75, -25,
-75, -75, 25,
75, -75, 25
]),
new Float32Array([
0, 75, -25,
75, 0, -25,
-75, 0, 25,
0, -75, 25,
]),
//... random positions
];
var lastTime = 0;
var posIndex = 0;
function render(currentTime) {
requestAnimationFrame( render );
// Update position attributes
// Instead of making a new attribute on each update
if (currentTime >= lastTime + 1000) {
parentGeometry.getAttribute('position').array = parentPositions[posIndex];
parentGeometry.getAttribute('position').needsUpdate = true;
childGeometry.getAttribute('position').array = parentPositions[posIndex];
childGeometry.getAttribute('position').needsUpdate = true;
lastTime = currentTime;
posIndex = posIndex === 3 ? 0 : posIndex + 1;
}
renderer.render( scene, camera );
}
render()
html, body {margin: 0; padding: 0;overflow: hidden;}
<script src="https://cdn.jsdelivr.net/npm/three#0.117.1/build/three.min.js"></script>
<script src="https://threejsfundamentals.org/threejs/resources/threejs/r110/examples/js/controls/OrbitControls.js"></script>
I'm not sure why you originally made the child a PlaneGeometry, but I had to change it to match the parent PlaneBufferGeometry.
Edit:
I guess I'm just now starting to understand what your problem is, and you'd need to calculate the vector that's perpendicular to your plane face. You can pick any three out of the four vertices to do this:
You can use this answer to figure out which point the triangle is pointing towards, and then you can make the child point in that direction with child.lookAt(x, y, z);
You can read this article for a little more in-depth explanation on how to get that perpendicular.

how to draw a flat shape in 3-space in three.js?

I'm trying to construct a collection of flat shapes in three.js. Each one is defined as a series of coplanar Vector3 points, but the shapes are not all coplanar. Imagine two flat rectangles as the roof of a house, but with much more complex shapes.
I can make flat Shape objects and then rotate and position them, but since my shapes are conceived in 3d coordinates, it would be much simpler to keep it all in 3-space, which the Shape object doesn't like.
Is there some much more direct way to simply specify an array of coplanar Vector3's, and let three.js do the rest of the work?
I thought about this problem and came up with the idea, when you have a set of co-planar points and you know the normal of the plane (let's name it normal), which your points belong to.
We need to rotate our set of points to make it parallel to the xy-plane, thus the normal of that plane is [0, 0, 1] (let's name it normalZ). To do it, we find quaternions with .setFromUnitVectors() of THREE.Quaternion():
var quaternion = new THREE.Quaternion().setFromUnitVectors(normal, normalZ);
var quaternionBack = new THREE.Quaternion().setFromUnitVectors(normalZ, normal);
Apply quaternion to our set of points
As it's parallel to xy-plane now, z-coordinates of points don't matter, so we can now create a THREE.Shape() object of them. And then create THREE.ShapeGeometry() (name it shapeGeom) from given shape, which will triangulate our shape.
We need to put our points back to their original positions, so we'll apply quaternionBack to them.
After all, we'll assign our set of points to the .vertices property of the shapeGeom.
That's it. If it'll work for you, let me know ;)
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 1000);
camera.position.set(0, 20, 40);
camera.lookAt(scene.position);
var renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
var controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.target = new THREE.Vector3(10, 0, 10);
controls.update();
var grid = new THREE.GridHelper(50, 50, 0x808080, 0x202020); // xy-grid
grid.geometry.rotateX(Math.PI * 0.5);
scene.add(grid);
var points = [ // all of them are on the xz-plane
new THREE.Vector3(5, 0, 5),
new THREE.Vector3(25, 0, 5),
new THREE.Vector3(25, 0, 15),
new THREE.Vector3(15, 0, 15),
new THREE.Vector3(15, 0, 25),
new THREE.Vector3(5, 0, 25),
new THREE.Vector3(5, 0, 5)
]
var geom = new THREE.BufferGeometry().setFromPoints(points);
var pointsObj = new THREE.Points(geom, new THREE.PointsMaterial({
color: "red"
}));
scene.add(pointsObj);
var line = new THREE.LineLoop(geom, new THREE.LineBasicMaterial({
color: "aqua"
}));
scene.add(line);
// normals
var normal = new THREE.Vector3(0, 1, 0); // I already know the normal of xz-plane ;)
scene.add(new THREE.ArrowHelper(normal, new THREE.Vector3(10, 0, 10), 5, 0xffff00)); //yellow
var normalZ = new THREE.Vector3(0, 0, 1); // base normal of xy-plane
scene.add(new THREE.ArrowHelper(normalZ, scene.position, 5, 0x00ffff)); // aqua
// 1 quaternions
var quaternion = new THREE.Quaternion().setFromUnitVectors(normal, normalZ);
var quaternionBack = new THREE.Quaternion().setFromUnitVectors(normalZ, normal);
// 2 make it parallel to xy-plane
points.forEach(p => {
p.applyQuaternion(quaternion)
});
// 3 create shape and shapeGeometry
var shape = new THREE.Shape(points);
var shapeGeom = new THREE.ShapeGeometry(shape);
// 4 put our points back to their origins
points.forEach(p => {
p.applyQuaternion(quaternionBack)
});
// 5 assign points to .vertices
shapeGeom.vertices = points;
var shapeMesh = new THREE.Mesh(shapeGeom, new THREE.MeshBasicMaterial({
color: 0x404040
}));
scene.add(shapeMesh);
render();
function render() {
requestAnimationFrame(render);
renderer.render(scene, camera);
}
body {
overflow: hidden;
margin: 0;
}
<script src="https://cdn.jsdelivr.net/npm/three#0.90.0/build/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three#0.90.0/examples/js/controls/OrbitControls.js"></script>

Smooth Ring 3D in three.js

I want to draw a ring with the help of ExtrudeGeometry.
Ring3D = function(innerRadius, outerRadius, heigth, Segments) {
var extrudeSettings = {
amount: heigth,
bevelEnabled: false,
curveSegments: Segments
};
var arcShape = new THREE.Shape();
arcShape.moveTo(outerRadius, 0);
arcShape.absarc(0, 0, outerRadius, 0, Math.PI * 2, false);
var holePath = new THREE.Path();
holePath.moveTo(innerRadius, 0);
holePath.absarc(0, 0, innerRadius, 0, Math.PI * 2, true);
arcShape.holes.push(holePath);
var geometry = new THREE.ExtrudeGeometry(arcShape, extrudeSettings);
var material = new THREE.MeshPhongMaterial({
color: 0x00ffff
});
var mesh = new THREE.Mesh(geometry, material);
mesh.rotation.x = Math.PI / 2;
mesh.position.y = heigth / 2;
var object = new THREE.Object3D;
object.add(mesh);
return object;
}
The resulting figure has visible scars. And the cylinder and torus such scars not. How to get rid of them? Example here.
with geometry.computeVertexNormals();
var shape = new THREE.Shape();
shape.moveTo(0, 0);
shape.lineTo(0, 10);
shape.quadraticCurveTo(35, 30, 0, 50);
shape.lineTo(0, 60);
shape.quadraticCurveTo(48, 30, 0, 0);
var extrudeSettings = {
amount : 20,
steps : 10,
bevelEnabled: false,
curveSegments: 8
};
var geometry = new THREE.ExtrudeGeometry( shape, extrudeSettings );
var mesh = new THREE.Mesh( geometry, new THREE.MeshPhongMaterial( { color: '0x804000' ,transparent: true,opacity: 0.2} ) );
scene.add( mesh );
You need to .computeVertexNormals() from your geometry. But it seems there is some issue (with a solution) explained here: https://github.com/mrdoob/three.js/issues/323. I am not sure if it will work in your case.
I have found a comment in the code of ExtrudeGeometry:
this.computeFaceNormals();
// can't really use automatic vertex normals
// as then front and back sides get smoothed too
// should do separate smoothing just for sides
//this.computeVertexNormals();
So it seems it is not supported now :(

three js geometry merge matrix

var ShipGeometry = new THREE.Geometry();
var ShipModule1geo = new THREE.SphereGeometry( 150.0, 40, 30 );
ShipGeometry.merge(ShipModule1geo); // this works
var ShipModule2geo = new THREE.BoxGeometry( 5.0, 5.0, 600, 1, 1, 1 );
var matrix = new THREE.Matrix4().makeTranslation(0,0,450);
ShipGeometry.merge(ShipModule2geo,matrix); // this works too
var ShipModule3geo = new THREE.CylinderGeometry( 150, 150, 20, 32 );
var matrix = new THREE.Matrix4().makeTranslation(0, 0, 850).makeRotationZ(Math.PI/2);
ShipGeometry.merge(ShipModule3geo,matrix); // only rotation is applyed
shipMesh = new THREE.Mesh( ShipGeometry, ShipMaterial );
....
My question, why is applyed only makeTranslation(0, 0, 850), not makeRotationZ(Math.PI/2) in last case?
How to apply both before merging?
You can apply both matrices like this:
var matrix = new THREE.Matrix4();
matrix.multiply( new THREE.Matrix4().makeTranslation(0, 0, 850) );
matrix.multiply( new THREE.Matrix4().makeRotationZ(Math.PI/2) );

Resources