Three.js - how to draw a discontinuous line using BufferGeometry? - javascript

I have the following situation: I need to draw a Line with holes (a discontinuous line). That means that my Line consists of several segments which are not combined visually but they belong together in some other context. These segments consists of more than just two points, so not the way like THREE.LinePieces works.
At this time, I am using a BufferGeometry to store my vertices. A colleague told me, that in WebGL it is possible to create two arrays additional to the vertices: one which contains the indices of the vertices and one which contains the order of how the vertices should be combined.
Here an example of what I mean:
indices = [0,1,2,3,4,5]
vertices = [x0, y0, z0, x1, y1, z1, x2, y2, z2, x3, y3, z3, x4, y4, z4, x5, y5, z5]
order = [0,1,1,2,3,4,4,5]
With this I would get two lines: the first one from Point 0 over 1 to 2, then a hole, then a second line from 3 over 4 to 5.
So something like this:
.___.___. .___.___.
0 1 2 3 4 5
I am not familiar with WebGL, so I'm trusting my colleague that it is possible to create such a construct. But is this also possible with Three.js? If yes, how do you do it?
EDIT:
I talked once more to my colleague and I got this code snippet
indexBufferData = [0,1,1,2,3,4,4,5];
gl.glBindBuffer(GL.GL_ELEMENT_ARRAY_BUFFER, indexBufferID);
gl.glBufferData(GL.GL_ELEMENT_ARRAY_BUFFER,
indexBufferData.limit() * Buffers.SIZEOF_INT,
indexBufferData, GL.GL_STATIC_DRAW);
He said I only have to duplicate the indices and NOT the vertices (would also be possible but not recommended) to get line segments.
So I searched in the WebGLRenderer and saw on line 2380 that if there is an attribute index in my BufferGeometry, the necessary buffer will be created. But setting this attribute has no effect. When using THREE.LinePieces it is still connecting only two points.

A code example and a fiddle to play around with.
// .___.___.___. .___.
// 0 1 2 3 4 5
// line material
var material = new THREE.LineBasicMaterial({
color: 0xffffff
});
vertices = [
new THREE.Vector3(0, 0, 0),
new THREE.Vector3(10, 0, 0),
new THREE.Vector3(20, 0, 0),
new THREE.Vector3(30, 0, 0),
new THREE.Vector3(40, 0, 0),
new THREE.Vector3(50, 0, 0)
];
var positions = new Float32Array(vertices.length * 3);
for (var i = 0; i < vertices.length; i++) {
positions[i * 3] = vertices[i].x;
positions[i * 3 + 1] = vertices[i].y;
positions[i * 3 + 2] = vertices[i].z;
}
indices = [0, 1, 1, 2, 2, 3, 4, 5];
var geometry = new THREE.BufferGeometry();
geometry.addAttribute('position', new THREE.BufferAttribute(positions, 3));
geometry.setIndex(new THREE.BufferAttribute(new Uint16Array(indices), 1));
var line = new THREE.LineSegments(geometry, material);
scene.add(line);

If you are trying to draw a series of connected line segments, followed by a gap, and then another series of connected line segments, you use THREE.LineSegments for that.
For example, this is the pattern for a line with three segments, followed with a line with one segment:
v0, v1, v1, v2, v2, v3, v4, v5
Looks like this:
.___.___.___. .___.
0 1 2 3 4 5
three.js r.91

Related

Distorted mesh when rotating group in Three.JS

Bit of context, I have two skinned meshes with the same bone structure that over lap each other. The difference between these two meshes is what they have attached to them and where. For the purpose of this question I will be referring to the body and hand skinned meshes.
I am trying to rotate both the body forearm and hand forearm at the same time and in the same direction. I have added both to the group leftforearmgroup which is a child group. The group structure present is bodygroup > leftclaviclegroup > leftarmgroup > leftforearmgroup.
The issue I am running into is when I attempt to rotate the leftforearmgroup it distorts as shown in the image below. The other image shows how it should behave, this is achieved by attaching the control directly to the body forearm which won't control the hand forearm sadly. Every other group when rotated via TransformControl behaves as expected.
distorted mesh when rotating
correct mesh behavior
So my question is how do I fix the mesh from being distorted when rotating?
Code:
const headaccgroup = new THREE.Group();
headaccgroup.position.set(0, -15, 0);
headaccgroup.rotateY(THREE.Math.degToRad(180));
const headgroup = new THREE.Group();
headgroup.scale.set(1, 1, 1);
headgroup.position.set(0, 0.38, 0.028);
headgroup.rotateY(THREE.Math.degToRad(0));
const bodygroup = new THREE.Group();
bodygroup.rotateY(THREE.Math.degToRad(180));
const leftclavgroup = new THREE.Group();
leftclavgroup.scale.set(1, 1, 1);
leftclavgroup.position.set(-0.055, -0.055, 40);
leftclavgroup.rotateY(THREE.Math.degToRad(0));
const leftarmgroup = new THREE.Group();
leftarmgroup.scale.set(1, 1, 1);
leftarmgroup.position.set(-0.055, -0.055, 0);
leftarmgroup.rotateY(THREE.Math.degToRad(180));
const leftforearmgroup = new THREE.Group();
leftforearmgroup.scale.set(1, 1, 1);
leftforearmgroup.position.set(0,0,0);
function loadBody(modelPath, x = 0.1, y = 0.1, z = 0.1) {
gltf_loader.load(modelPath, function (gltf) {
scene.remove(body);
body = gltf.scene;
gltf.scene.traverse(function(object) {
if(object.isMesh) object.frustumCulled = false;
});
gltf.scene.scale.set(x, y, z);
body.position.set( 0, -2, 20);
leftforearmgroup.add( body.getObjectByName('bip02_l_forearm'));
controlleftforearm.attach(leftforearmgroup);
controlleftforearm.setMode('rotate');
scene.add(controlleftforearm);
leftarmgroup.add(body.getObjectByName('bip02_l_upperarm'));
leftclavgroup.add(body.getObjectByName('bip02_l_clavicle'));
leftarmgroup.add(leftforearmgroup);
leftclavgroup.add(leftarmgroup);
bodygroup.add(leftclavgroup);
headgroup.add(body.getObjectByName('bip02_head'));
bodygroup.scale.set( 10, 10, 10);
bodygroup.add(body.getObjectByName('bip02_spine1'));
bodygroup.add(headgroup);
scene.add(body);
controlspine1.attach(bodygroup);
controlspine1.setMode('rotate');
scene.add(controlspine1);
});
}

convert bezier into a plane road in three.js

I am trying to draw a curved road in three.js from some beziers I get with previous calculations, the problem is that I can't find the way to convert the sequence of curved lines (one starting at the end of the previous one) to a curved plane.
I have a 3D scene where there are some cars, a road created with a plane and the path of the coming road is painted. I use that Bezier curves I said to represent the path as a Line with
function createAdasisBezier(initx, inity, cp1x, cp1y, cp2x, cp2y, finalx, finaly) {
bezier = new THREE.CubicBezierCurve3(
new THREE.Vector3(initx, inity, 0),
new THREE.Vector3(cp1x, cp1y, 0),
new THREE.Vector3( cp2x, cp2y, 0),
new THREE.Vector3(finalx, finaly, 0)
);
curvePath = new THREE.CurvePath();
curvePath.add(bezier);
var geoPath = curvePath.createPointsGeometry( 5 );
var lineMat = new THREE.LineBasicMaterial({color: 0xff0000});
curveLine = new THREE.Line(geoPath, lineMat);
curveLine.rotation.set(-Math.PI/2,0,0);
curveLine.position.y = 0.1;
scene.add(curveLine);
}
First, I tried extruding the line, but then I realized that it might not be the solution because I wanted to do a road, and although I could move top vertices on X and Y to place them near the bezier in order to be the external part of the curve, the result was not only unfavourable, it also made impossible to preserve a relation between a left and a right curve.
To move vertices (once identified) I did a loop and move them manually:
for (var i = 0; i < geoPath.vertices.length; ++i) {
geoPath.vertices[i].y += 10;
}
Bevel is not enabled in the extude.
Then I tried to draw a plane over each bezier (as a child of them) and rotate it to face the path, but the result was not as I expected, and it if it were, it would spoil the arcs of the curves.
To do it, I created a copy of every bezier, and place it aside the original ones, then I created the plane.
var plane = new THREE.PlaneBufferGeometry(10,25,1,1);
var planemesh = new THREE.Mesh(plane, material);
planemesh.position.set(copy.geometry.vertices[0].x, copy.geometry.vertices[0].y, 0);
Last thing I was trying to do is creating a clone of the line, separate it some meters and "connect" the first vertex from one, to the first of the other, so I get a closed geometry, and I can create a Face, but I don't find how to "connect" vertices from 2 different geometries. I tried adding the vertex from one to the other, but it did not work.
Does anybody have an idea how could I convert the line into a curved road? Thanks in adcance.
You should try looking at the Geometry > Extrude > Shapes example. As you can see, all extruded shapes maintain their width and direction, despite turning left/right or looping completely.
Instead of using bezier curves, they're using a CatmullRomCurve3 to define the extrusion. If you look at the source code, the essential code to make the red extruded shape begins in line 69:
// Define the curve
var closedSpline = new THREE.CatmullRomCurve3( [
new THREE.Vector3( -60, -100, 60 ),
new THREE.Vector3( -60, 20, 60 ),
new THREE.Vector3( -60, 120, 60 ),
new THREE.Vector3( 60, 20, -60 ),
new THREE.Vector3( 60, -100, -60 )
] );
closedSpline.type = 'catmullrom';
closedSpline.closed = true;
// Set up settings for later extrusion
var extrudeSettings = {
steps : 100,
bevelEnabled : false,
extrudePath : closedSpline
};
// Define a triangle
var pts = [], count = 3;
for ( var i = 0; i < count; i ++ ) {
var l = 20;
var a = 2 * i / count * Math.PI;
pts.push( new THREE.Vector2 ( Math.cos( a ) * l, Math.sin( a ) * l ) );
}
var shape = new THREE.Shape( pts );
// Extrude the triangle along the CatmullRom curve
var geometry = new THREE.ExtrudeGeometry( shape, extrudeSettings );
var material = new THREE.MeshLambertMaterial( { color: 0xb00000, wireframe: false } );
// Create mesh with the resulting geometry
var mesh = new THREE.Mesh( geometry, material );
From here, it should only be a matter of small tweaks to these parameters to get the specific road shape you want.

Draw .obj file with Three.js without native OBJLoader

I need to draw .obj file, without its loading. For example I have .obj file with follow content
v 0.1 0.2 0.3
v 0.2 0.1 0.5
vt 0.5 -1.3
vn 0.7 0.0 0.7
f 1 2 3
I read this file, parse content and have its data in a JavaScript object.
{
v: [
{x:0.1, 0.2, 0.3}
{x:0.2, 0.1, 0.5}
],
vt: [
{u: 0.5, v: -1.3}
],
vn: [
{x: 0.7, 0.0, 0.7}
],
f: [
// ...
]
}
Next I need to draw this data with three.js. I read documentation, but can't find any example or description how to do it. Who knows?
Is there any method for that purpose?
First question is, why wont you use THREE.ObjLoader ? The reason is not clear to me. There could be so much different test cases for loading obj file. Its better to use THREE.ObjLoader.
If you cant use that then
My preferred way would be to create a THREE.BufferGeometry. We are going to create some THREE.BufferAttribute from the arrays of your javascript object. One THREE.BufferAttribute for each vertex attribute. Also, we gonna set the index buffer. Here is a function to do it -
function make_3D_object(js_object) {
let vertices = new Float32Array(js_object.v);
let uvs = new Float32Array(js_object.vt);
let normals = new Float32Array(js_object.vn);
let indices = new Uint8Array(js_object.f);
// this is to make it 0 indexed
for(let i = 0; i < indices.length; i++)
indices[i]--;
let geom = new THREE.BufferGeometry();
geom.addAttribute('position', new THREE.BufferAttribute(vertices, 3));
geom.addAttribute('normal', new THREE.BufferAttribute(normals, 3));
geom.addAttribute('uv', new THREE.BufferAttribute(uvs, 2));
geom.setIndex(new THREE.BufferAttribute(indices, 1));
let material = new THREE.MeshPhongMaterial( {
map: js_object.texture, // assuming you have texture
color: new THREE.Color().setRGB(1, 1, 1),
specular: new THREE.Color().setRGB(0, 0,0 )
} );
let obj_mesh = new THREE.Mesh(geom, material);
return obj_mesh;
}
In this code i have assumed you have only a single body, a single material with only a texture. Also this code is not tested.

spline curve with steady transition in three js

I am drawing a CubicBezierCurve3 curve in three js. However, I would like it to be drawn part-by-part with a steady transition, instead of the entire curve being drawn at once. You may visualize it as a moving rocket leaving behind a gas trail.
My idea was the following
Find all the points that form the CubicBezierCurve3, and save in a variable called 'allpoints'. Assume we have found exactly 50 points in the CubicBezierCurve3 as shown below
var curve = new THREE.CubicBezierCurve3(
new THREE.Vector3( -5, 0, 20 ),
new THREE.Vector3(0, 15, 0 ),
new THREE.Vector3(0, 15, 0 ),
new THREE.Vector3( 2, 0, -10 )
);
geometry = new THREE.Geometry();
geometry.vertices = curve.getPoints( 50 );
allpoints = geometry.vertices;
At each iteration, plot 10 points, which means
Iteration 0 : plots points 0 to 9 Iteration 1 : plots points 10 to 19
Iteration 2 : plots points 20 to 29 Iteration 3 : plots points 30 to
39 Iteration 4 : plots points 40 to 49
This function takes care of plotting 10 points at every iteration, Since we have 50 points, we can call cancelAnimationFrame at the end of the 5th iteration. The function contains some work-arounds, to take care of the timing.(Only after every 10 iterations, I plot the 10-points sequence, otherwise, the transition would be too fast, and we wouldn't be able to make out the difference)
function drawPointCloud() {
//scene.remove(dot);
//scene.remove(dotsequence);
//renderer.setClearColor(0x000000, 1.0);
console.log("inside drawPointCloud,count== "+count)
if(count == 50)
{
console.log("stopped.")
cancelAnimationFrame(animationTracker);
return;
}
if(count%10 == 0)
{
var tempcount = count;
count /= 10;
var first = 10*count;
var last = 10*(count + 1);
console.log("inside drawPointCloud, count=="+count+"first=="+first+"last=="+last)
console.log(allpoints[first])
console.log(allpoints[last])
var dotGeometry = new THREE.Geometry();
var dotMaterial = new THREE.PointCloudMaterial( { size: 5, sizeAttenuation: false } );
for(var i = first;i<last;i++)
{
dotGeometry.vertices.push(new THREE.Vector3( allpoints[i].x, allpoints[i].y, allpoints[i].z));
}
scene.remove(dotsequence);
dotsequence = new THREE.PointCloud( dotGeometry, dotMaterial );
scene.add( dotsequence );
count = tempcount;
renderer.render(scene, camera);
camera.position.z -= 0.1;
camera.position.y -= 0.1;
}
count++;
animationTracker = requestAnimationFrame(drawPointCloud);
}
JS Fiddles
Here is a JS Fiddle of the smooth transition that I have tried
Here is a JS Fiddle of all points in the CubicBezierCurve3
My question is, is there a better way/API to achieve this? Also, I would like to give the points a bit of a delay before turning off, like how an LED gets off instead of becoming invisible abruptly. Actually, it doesn't even have to be done by making use of individual points, as I have tried, I'm looking for anything that incrementally draws parts of a given cubic bezier curve.
Maybe you should consider using particle system with the THREE.GPUParticleSystem plugin like shown in this example.
It already reminded me of a "gas trail" of a rocket...

How can I bind two shapes together as one in Three.js?

Can I bind two different shapes together as one shape?
For example, binding sphere and cylinder together as one?
Kind of, yes. There are multiple options:
via hierarchy you can simply add one mesh to another using the add() function
via the GeometryUtil's merge() function to merge vertices and meshes of two Geometry objects into one
using a basic 3D editor that supports Boolean operations between meshes and exporting.
Method 1 is pretty straightforward:
var sphere = new THREE.Mesh(new THREE.SphereGeometry(100, 16, 12), new THREE.MeshLambertMaterial({ color: 0x2D303D, wireframe: true, shading: THREE.FlatShading }));
var cylinder = new THREE.Mesh(new THREE.CylinderGeometry(100, 100, 200, 16, 4, false), new THREE.MeshLambertMaterial({ color: 0x2D303D, wireframe: true, shading: THREE.FlatShading } ));
cylinder.position.y = -100;
scene.add(sphere);
scene.add(cylinder);
Notice that 16 is repeated, so the subdivisions level in one mesh matches the other (for a decent look).
Method 2.1 - via GeometryUtils
// Make a sphere
var sg = new THREE.SphereGeometry(100, 16, 12);
// Make a cylinder - ideally the segmentation would be similar to predictable results
var cg = new THREE.CylinderGeometry(100, 100, 200, 16, 4, false);
// Move vertices down for cylinder, so it maches half the sphere - offset pivot
for(var i = 0 ; i < cg.vertices.length; i++)
cg.vertices[i].position.y -= 100;
// Merge meshes
THREE.GeometryUtils.merge(sg, cg);
var mesh = new THREE.Mesh(sg, new THREE.MeshLambertMaterial({ color: 0x2D303D, wireframe: true, shading: THREE.FlatShading }));
scene.add(mesh);
Method 2.2 merging a Lathe half-sphere and a cylinder:
var pts = []; // Points array
var detail = .1; // Half-circle detail - how many angle increments will be used to generate points
var radius = 100; // Radius for half_sphere
var total = Math.PI * .51;
for(var angle = 0.0; angle < total ; angle+= detail) // Loop from 0.0 radians to PI (0 - 180 degrees)
pts.push(new THREE.Vector3(0,Math.cos(angle) * radius,Math.sin(angle) * radius)); // Angle/radius to x,z
var lathe = new THREE.LatheGeometry(pts, 16); // Create the lathe with 12 radial repetitions of the profile
// Rotate vertices in lathe geometry by 90 degrees
var rx90 = new THREE.Matrix4();
rx90.setRotationFromEuler(new THREE.Vector3(-Math.PI * .5, 0, 0));
lathe.applyMatrix(rx90);
// Make cylinder - ideally the segmentation would be similar for predictable results
var cg = new THREE.CylinderGeometry(100, 100, 200, 16, 4, false);
// Move vertices down for cylinder, so it maches half the sphere
for(var i = 0 ; i < cg.vertices.length; i++)
cg.vertices[i].position.y -= 100;
// Merge meshes
THREE.GeometryUtils.merge(lathe, cg);
var mesh = new THREE.Mesh(lathe, new THREE.MeshLambertMaterial({ color: 0x2D303D, wireframe: true, shading: THREE.FlatShading}));
mesh.position.y = 150;
scene.add(mesh);
The one problem I can't address at the moment comes from the faces that are inside the mesh. Ideally, those would have normals flipped, so they wouldn't render, but I haven't found a quick solution for that.
The third is fairly straightforward. Most 3D packages allow Boolean operation on meshes (e.g., merging two meshes together with the ADD operation (meshA + meshB)). Try creating a cylinder and a sphere in Blender (free and opensource), which already has a Three.js exporter. Alternatively you can export an .obj file of the merged meshes from your 3D editor or choice and use the convert_obj_three script.
I've found yet another method, which might be easier/more intuitive. Remember the Boolean operations I've mentioned above?
It turns out there is an awesome JavaScript library just for that: Constructive Solid Geometry:
Chandler Prall wrote some handy functions to connect CSG with three.js. So with the CSG library and the Three.js wrapper for it, you can simply do this:
var cylinder = THREE.CSG.toCSG(new THREE.CylinderGeometry(100, 100, 200, 16, 4, false), new THREE.Vector3(0, -100, 0));
var sphere = THREE.CSG.toCSG(new THREE.SphereGeometry(100, 16, 12));
var geometry = cylinder.union(sphere);
var mesh = new THREE.Mesh(THREE.CSG.fromCSG(geometry), new THREE.MeshNormalMaterial());
Which gives you a nice result (no problems with extra faces/flipping normals, etc.):

Categories