Three.js - Extrude certain vertex/face from BufferGeometry - javascript

I made a new THREE.PlaneBufferGeometry(100, 100, 100, 100); and have been able to update position of vertices to change the mesh's shape like following:
I achieved this by following this discussion: Threejs drag points
What am I looking for?
I want to be able to extrude a face (grab 4 vertices), so I achieve something like this:
I want to keep it all part of the same mesh, to keep it clean, because I will be exporting it as a single mesh with the ColladaExporter.
Edit
In order to achieve this, I would need to clone vertex and extrude them upwards. This means, adding 4 new vertex and connecting them together.
I tried this:
var geo = new THREE.PlaneBufferGeometry(1, 1, 1, 1);
geo.rotateX(-Math.PI * 0.5);
geo.translate(0,0.5,0);
//And the merge them together
var newplane = BufferGeometryUtils.mergeBufferGeometries([plane, geo]);
newplane = BufferGeometryUtils.mergeVertices(newplane,1);
And I got this:
I was hoping all vertices merged with the plane, leaving a flat plane. I did this for testing purposes, but it only merged one corner.
I started building a "cube" with multiple and placing them in the right spot, to then apply again BufferGeometryUtils.mergeVertices, but the vertices don't seem to merge correctly:
Edit 2 / Progress
I managed to create a PlaneBufferGeometry and extrude it by manually modifying the vertexes and normals, as told in: https://threejs.org/docs/#api/en/core/BufferGeometry
Extruded plane has all vertices connected, so whenever I drag one vertex it drags a whole piece, the problem now is that I need to connect these new vertices to the original grid to avoid this:
Goal is to merge all vertices, now I need to find a way to merge the base plane with the new extruded piece.
Edit 3 / Done
I made it, I will post answer when I have some time. I spent all day long on these today, and already very tired.

Not sure if that's what you need, but here's the modified example from the answer you referred to (please notice the difference in mouseMove implementation). I've extended that for two points only, but I believe you should get the idea:
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 1000);
camera.position.set(1.25, 7, 7);
camera.lookAt(scene.position);
var renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
var geometry = new THREE.PlaneBufferGeometry(10, 10, 10, 10);
geometry.rotateX(-Math.PI * 0.5);
var plane = new THREE.Mesh(geometry, new THREE.MeshBasicMaterial({
wireframe: true,
color: "red"
}));
scene.add(plane);
var points = new THREE.Points(geometry, new THREE.PointsMaterial({
size: 0.25,
color: "yellow"
}));
scene.add(points);
var raycaster = new THREE.Raycaster();
raycaster.params.Points.threshold = 0.25;
var mouse = new THREE.Vector2();
var intersects = null;
var plane = new THREE.Plane();
var planeNormal = new THREE.Vector3();
var currentIndex = null;
var planePoint = new THREE.Vector3();
var dragging = false;
window.addEventListener("mousedown", mouseDown, false);
window.addEventListener("mousemove", mouseMove, false);
window.addEventListener("mouseup", mouseUp, false);
function mouseDown(event) {
setRaycaster(event);
getIndex();
dragging = true;
}
function mouseMove(event) {
if (dragging && currentIndex !== null) {
setRaycaster(event);
raycaster.ray.intersectPlane(plane, planePoint);
var indicesToMoveUp = [currentIndex-1, currentIndex];
var delta_x = geometry.attributes.position.getX(currentIndex) - planePoint.x;
geometry.attributes.position.setXYZ(currentIndex, planePoint.x, planePoint.y, planePoint.z);
geometry.attributes.position.needsUpdate = true;
var old_x_neighbour = geometry.attributes.position.getX(currentIndex - 1);
geometry.attributes.position.setY(currentIndex-1, planePoint.y);
geometry.attributes.position.setZ(currentIndex-1, planePoint.z);
geometry.attributes.position.setX(currentIndex-1, old_x_neighbour - delta_x);
geometry.attributes.position.needsUpdate = true;
}
}
function mouseUp(event) {
dragging = false;
currentIndex = null;
}
function getIndex() {
intersects = raycaster.intersectObject(points);
if (intersects.length === 0) {
currentIndex = null;
return;
}
currentIndex = intersects[0].index;
setPlane(intersects[0].point);
}
function setPlane(point) {
planeNormal.subVectors(camera.position, point).normalize();
plane.setFromNormalAndCoplanarPoint(planeNormal, point);
}
function setRaycaster(event) {
getMouse(event);
raycaster.setFromCamera(mouse, camera);
}
function getMouse(event) {
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
}
render();
function render() {
requestAnimationFrame(render);
renderer.render(scene, camera);
}
body {
overflow: hidden;
margin: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/91/three.min.js"></script>

Related

create mesh before render in babylon.js

I want to create new mesh before each render, here's my code(a minimal demo):
let canvas,
engine,
camera,
scene;
function initEngine(){
canvas = document.getElementById("renderCanvas");
engine = new BABYLON.Engine(canvas, true);
}
function createScene(){
initEngine();
let scene = new BABYLON.Scene(engine);
camera = new BABYLON.ArcRotateCamera("camera", Math.PI / 2, Math.PI / 2, 4, BABYLON.Vector3.Zero(), scene);
camera.attachControl(canvas, true);
let light1 = new BABYLON.HemisphericLight("light1", new BABYLON.Vector3(0,1,0), scene);
let ground = BABYLON.MeshBuilder.CreateGround(
"ground",
{
width:30,
height:30
},
scene
);
ground.position.y -= 0.5;
let sphere1 = BABYLON.MeshBuilder.CreateSphere(
"sphere1",
{
},
scene
);
let sphere2 = sphere1.clone("sphere2");
let sphere3 = sphere1.clone("sphere3");
sphere1.position.z = -18;
sphere2.position.z = -19;
sphere3.position.z = -20;
let snake = [
sphere1,
sphere2,
sphere3
];
(function(){
let counter = 4;
scene.registerBeforeRender(function(){
let newOne = BABYLON.MeshBuilder.CreateSphere(
"sphere" + counter,
{
},
scene
);
let head = snake[0];
newOne.position = head.position;
newOne.position.x += 0.02;
snake.unshift(newOne);
++counter;
});
})();
window.addEventListener("resize", function(){
engine.resize();
});
return scene;
}
scene = createScene();
engine.runRenderLoop(function(){
// box.position.z += 0.01;
scene.render();
});
My expecting behavior is to create a series of spheres, each position.x is slightly higher than the previous one. However, there are only three meshes in the scene after rendering, like this:
result
I want to know what is wrong with my code, and how to implement it properly?
By the way, what is the difference between scene.removeMesh(mesh) and mesh.dispose()?
It's because of this statement.
newOne.position = head.position;
It just copy a reference. so now the new sphere and snake[0] share a same position instance, so all the newly created spheres share a same position instance(by holding a position reference), and located in the same position.

Three.js - Rotate camera separately from it's parent

I have a scene in which an Object moves around a CatmullRomCurve3 path, going left when you press the left or down arrow keys and right when you press the right or up arrow keys.
I initially had a problem in which I wanted the Object to face 90 degrees from the path instead of facing along the path. You can find my code for this scene there. This problem was solved with this answer.
Basically, all I had to do was implement the following code to rotate the object:
object.lookAt((p2).sub(p1).applyAxisAngle(axis, -Math.PI * 0.5).add(p1));
Although the object now rotated correctly, I now had the problem that the camera wasn't facing the same direction as the object or moving with the object (I wanted an over the shoulder type perspective of the object) but someone suggested I fix this by just adding the camera as a child of the object, which worked!
The New Problem
I now want the object's default position to be facing 90 degrees away from the path (as it is), but when you press the left or down arrows, I want the object to face left (back along the path as it was originally), and face the other way when the right and up arrows are pressed.
I managed to get this to work by setting up some if statements in the render loop that change the value of -Math.PI * 0.5 to either -Math.PI * 2 or -Math.PI * 0.5 for left and right depending on which key is pressed.
However, this also causes the camera to take that new lookAt value as it is a child of the object, but I want the camera to always stay in the same position (i.e. facing 90 degrees from the path, as the object does when it is still).
So, how can I stop the camera from rotating/moving/changing it's lookAt (I'm not sure which one it is) when the Object changes?
It seems like nothing I do to the camera affects it, it is always locked to the same orientation of the object.
Thanks in advance.
I hope I've understood your question correctly:
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 1000);
camera.position.set(0, 20, -20);
camera.lookAt(0, 0, 0);
var renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
var grid = new THREE.GridHelper(300, 30);
grid.position.setZ(100);
scene.add(grid);
var points = [
new THREE.Vector3(-80, 5, 35),
new THREE.Vector3(-80, 5, 192.5),
new THREE.Vector3(80, 5, 192.5),
new THREE.Vector3(80, 5, 35)
];
var curve = new THREE.CatmullRomCurve3(points);
curve.closed = true;
var curvePoints = curve.getPoints(200);
var geomCurvePath = new THREE.BufferGeometry().setFromPoints(curvePoints);
var matCurvePath = new THREE.LineBasicMaterial({
color: 0xff0000
});
var CurvePath = new THREE.Line(geomCurvePath, matCurvePath);
scene.add(CurvePath);
var group = new THREE.Group();
scene.add(group);
group.add(camera);
var pointerGeom = new THREE.ConeGeometry(4, 20, 4);
pointerGeom.translate(0, 10, 0);
pointerGeom.rotateX(Math.PI * 0.5);
var pointer = new THREE.Mesh(pointerGeom, new THREE.MeshNormalMaterial());
group.add(pointer);
btnLeft.addEventListener("click", function() {
turn(Math.PI * 0.5)
});
btnRight.addEventListener("click", function() {
turn(-Math.PI * 0.5)
});
function turn(angle) {
pointer.rotation.y += angle;
}
var p1 = new THREE.Vector3();
var p2 = new THREE.Vector3();
var lookAt = new THREE.Vector3();
var axis = new THREE.Vector3(0, 1, 0);
var percentage = 0;
render();
function render() {
requestAnimationFrame(render);
percentage += 0.0005;
curve.getPointAt(percentage % 1, p1);
curve.getPointAt((percentage + 0.001) % 1, p2);
lookAt.copy(p2).sub(p1).applyAxisAngle(axis, -Math.PI * 0.5).add(p1); // look at the point 90 deg from the path
group.position.copy(p1);
group.lookAt(lookAt);
renderer.render(scene, camera);
}
body {
overflow: hidden;
margin: 0;
}
#buttons {
position: absolute;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/92/three.min.js"></script>
<script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>
<div id="buttons">
<button id="btnLeft">left</button>
<button id="btnRight">right</button>
</div>

Three.js animated bezier curves

EDITED. SOLUTION FOUND
I need to know how to implement animation of the points in a curve to simulate string movement in 3D with performance in mind.
Multiple strings between two points for example.
Fiddle provided. (code updated)
So I have curveObject and I'm trying to change position of a point1. (code updated)
var camera, scene, renderer;
var angle1 = angle2 = 0;
var curve1, point1, curveObject, geometryCurve, materialCurve;
var params1 = {P0x: 0, P0y: 0,
P1x: 2, P1y: 2,
P2x: -2, P2y: 1,
P3x: 0, P3y: 3,
steps: 30};
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 1, 10000);
camera.position.z = 10;
scene.add(camera);
renderer = new THREE.WebGLRenderer( { antialias: true } );
renderer.setClearColor( 0x16112b, 1 );
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
createBezierCurveNEW = function (cpList, steps) {
var N = Math.round(steps)+1 || 20;
var geometry = new THREE.Geometry();
var curve = new THREE.CubicBezierCurve3();
var cp = cpList[0];
curve.v0 = new THREE.Vector3(cp[0], cp[1], cp[2]);
cp = cpList[1];
curve.v1 = new THREE.Vector3(cp[0], cp[1], cp[2]);
cp = cpList[2];
curve.v2 = new THREE.Vector3(cp[0], cp[1], cp[2]);
cp = cpList[3];
curve.v3 = new THREE.Vector3(cp[0], cp[1], cp[2]);
var j, stepSize = 1/(N-1);
for (j = 0; j < N; j++) {
geometry.vertices.push( curve.getPoint(j * stepSize) );
}
return geometry;
};
function CreateCurve(){
scene.remove(curve1);
var controlPoints1 = [
[params1.P0x, params1.P0y, 0],
[params1.P1x, params1.P1y, 0],
[params1.P2x, params1.P2y, 0],
[params1.P3x, params1.P3y, 0] ];
var curveGeom1 = createBezierCurveNEW(controlPoints1, params1.steps);
var mat = new THREE.LineBasicMaterial( { color: 0x000000, linewidth: 5 } );
curve1 = new THREE.Line( curveGeom1, mat );
scene.add(curve1);
};
CreateCurve();
function animate() {
CreateCurve();
render();
angle1 -= .007;
angle2 += .003;
params1.P1x = Math.cos(angle1)+2;
params1.P1y = Math.sin(angle1)+3;
params1.P2x = -Math.cos(angle2)-2;
params1.P2y = Math.cos(angle2)+1;
requestAnimationFrame(animate);
};
animate();
function render() {
renderer.render(scene, camera);
};
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r83/three.min.js"></script>
I see value increment in console,
but no actual visual feedback. My guess - I need to update curve somehow.
Final goal is to smoothly animate slow sine-like movement of the curve.
like control points of bezier curve are being moved in Photoshop.
(The goal was reached. Sadly not by my own. I've stumbled upon some helper code lib at http://cs.wellesley.edu/~cs307/lectures/15.shtml so BIG thanks to these guys.)
There is little info regarding curve animation in threejs.
Maybe someone already got going something similar.
(The goal was reached. Sadly not by my own. I've stumbled upon some helper code lib at http://cs.wellesley.edu/~cs307/lectures/15.shtml so BIG thanks to these guys.)

Three JS Keep Label Size On Zoom

I'm working on a solar system in three.js and am curious if there is an easy way to make the labels for the planets I have below all show up the same size regardless of how far they are from the camera? I can't seem to find a solution to this. I figure you could calculate the distance from each label to the camera then come up with some sort of scaling factor based on that. Seems like there would be an easier way to accomplish this?
Thanks!
Updated with answer from prisoner849. Works excellent!
I figure you could calculate the distance from each label to the camera then come up with some sort of scaling factor based on that.
And it's very simple. Let's say, a THREE.Sprite() object (label) is a child of a THREE.Mesh() object (planet), then in your animation loop you need to do
var scaleVector = new THREE.Vector3();
var scaleFactor = 4;
var sprite = planet.children[0];
var scale = scaleVector.subVectors(planet.position, camera.position).length() / scaleFactor;
sprite.scale.set(scale, scale, 1);
I've made a very simple example of the Solar System, using this technique.
For the benefit of future visitors, the transform controls example does exactly this:
https://threejs.org/examples/misc_controls_transform.html
Here's how its done in the example code:
var factor;
if ( this.camera.isOrthographicCamera ) {
factor = ( this.camera.top - this.camera.bottom ) / this.camera.zoom;
} else {
factor = this.worldPosition.distanceTo( this.cameraPosition ) * Math.min( 1.9 * Math.tan( Math.PI * this.camera.fov / 360 ) / this.camera.zoom, 7 );
}
handle.scale.set( 1, 1, 1 ).multiplyScalar( factor * this.size / 7 );
Finally I found the answer to your question:
First, create a DOM Element:
<div class="element">Not Earth</div>
Then set CSS styles for it:
.element {position: absolute; top:0; left:0; color: white}
// |-------------------------------| |-----------|
// make the element on top of canvas is
// the canvas black, so text
// must be white
After that, create moveDom() function and run it every time you render the scene requestAnimationFrame()
geometry is the geometry of the mesh
cube is the mesh you want to create label
var moveDom = function(){
vector = geometry.vertices[0].clone();
vector.applyMatrix4(cube.matrix);
vector.project(camera);
vector.x = (vector.x * innerWidth/2) + innerWidth/2;
vector.y = -(vector.y * innerHeight/2) + innerHeight/2;
//Get the DOM element and apply transforms on it
document.querySelectorAll(".element")[0].style.webkitTransform = "translate("+vector.x+"px,"+vector.y+"px)";
document.querySelectorAll(".element")[0].style.transform = "translate("+vector.x+"px,"+vector.y+"px)";
};
You can create a for loop to set label for all the mesh in your scene.
Because this trick only set 2D position of DOM Element, the size of label is the same even if you zoom (the label is not part of three.js scene).
Full test case: https://jsfiddle.net/0L1rpayz/1/
var renderer, scene, camera, cube, vector, geometry;
var ww = window.innerWidth,
wh = window.innerHeight;
function init(){
renderer = new THREE.WebGLRenderer({canvas : document.getElementById('scene')});
renderer.setSize(ww,wh);
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(50,ww/wh, 0.1, 10000 );
camera.position.set(0,0,500);
scene.add(camera);
light = new THREE.DirectionalLight(0xffffff, 1);
light.position.set( 0, 0, 500 );
scene.add(light);
//Vector use to get position of vertice
vector = new THREE.Vector3();
//Generate Not Earth
geometry = new THREE.BoxGeometry(50,50,50);
var material = new THREE.MeshLambertMaterial({color: 0x00ff00});
cube = new THREE.Mesh(geometry, material);
scene.add(cube);
//Render my scene
render();
}
var moveDom = function(){
vector = geometry.vertices[0].clone();
vector.applyMatrix4(cube.matrix);
vector.project(camera);
vector.x = (vector.x * ww/2) + ww/2;
vector.y = -(vector.y * wh/2) + wh/2;
//Get the DOM element and apply transforms on it
document.querySelectorAll(".element")[0].style.webkitTransform = "translate("+vector.x+"px,"+vector.y+"px)";
document.querySelectorAll(".element")[0].style.transform = "translate("+vector.x+"px,"+vector.y+"px)";
};
var counter = 0;
var render = function (a) {
requestAnimationFrame(render);
counter++;
//Move my cubes
cube.position.x = Math.cos((counter+1*150)/200)*(ww/6+1*80);
cube.position.y = Math.sin((counter+1*150)/200)*(70+1*80);
cube.rotation.x += .001*1+.002;
cube.rotation.y += .001*1+.02;
//Move my dom elements
moveDom();
renderer.render(scene, camera);
};
init();
body,html, canvas{width:100%;height:100%;padding:0;margin:0;overflow: hidden;}
.element{color:white;position:absolute;top:0;left:0}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r79/three.min.js"></script>
<!-- My scene -->
<canvas id="scene"></canvas>
<div class="element">
<h1>Not Earth</h1>
</div>
If you downvote this, please tell me why. I will try my best to improve my posts.
If you are using spriteMaterial to present your text, you could try to set the sizeAttenuation attribute to false.
var spriteMaterial = new THREE.SpriteMaterial( { map: spriteMap, color: 0xffffff, sizeAttenuation:false } );
See more information from here:
https://threejs.org/docs/index.html#api/en/materials/SpriteMaterial.sizeAttenuation

errors extruding shapes with three.js

I'm just getting started with three.js, and having some issues extruding some 2d shapes.
I have a GeoJSON file containing all the counties in the US. I'm using d3.js and a d3.geo.albersUSa() projection to convert each latitude/longitude into a list of X/Y coordinates to make a THREE.Shape that I'm then extruding and drawing. This seems to work OK for most counties.
The issue I'm seeing is that some subset of counties either fail to extrude or extrude incorrectly with the following sequences of warnings:
Warning, unable to triangulate polygon!
Duplicate point 653.4789181355854:204.0166729191409
Either infinite or no solutions!
Its finite solutions.
Either infinite or no solutions!
Too bad, no solutions.
I'm not sure I understand exactly what the issue is -- as far as I can tell, there's nothing special about these particular shapes. Am I doing something wrong, or is this an issue with the extrusion code in three.js?
For example here are some missing counties:
Also notice the triangular 'hourglass' missing pieces in Texas: these look like some counties which were only half rendered (they ended up as triangles instead of rectangles or squares?)
Larger
Apologies for the huge code dump, I tried to pare it down as much as possible.
setup:
/* initialize the scene, camera, light, and background plane */
var Map = function(params) {
this.width = params.width;
this.height = params.height;
this.container = params.target || document.body;
this.renderer = new THREE.WebGLRenderer({antialias: true});
this.renderer.setSize(this.width, this.height);
this.renderer.setClearColorHex(0x303030, 1.0);
this.container.appendChild(this.renderer.domElement);
this.camera = new THREE.PerspectiveCamera(45, this.width / this.height,
1, 10000);
this.scene = new THREE.Scene();
this.scene.add(this.camera);
this.camera.position.z = 550;
this.camera.position.x = 0;
this.camera.position.y = 550;
this.camera.lookAt(this.scene.position);
this.projection = d3.geo.albersUsa()
.scale(1000)
.translate([250, 0]);
var pointLight = new THREE.PointLight(0xFFFFFF);
pointLight.position.x = 800;
pointLight.position.y = 800;
pointLight.position.z = 800;
var plane = new THREE.Mesh(
new THREE.PlaneGeometry(10000, 10000, 10, 10),
new THREE.MeshLambertMaterial({color: 0xffffff})
);
plane.rotation.x = -Math.PI/2;
this.scene.add(pointLight);
this.scene.add(plane);
};
rendering:
/* given a GeoJSON Feature, return a list of Vector2s
* describing where to draw the feature, using the provided projection. */
function path(proj, feature) {
if (feature.geometry.type == 'Polygon') {
return polygonPath(proj, feature.geometry.coordinates);
} else if (feature.geometry.type == 'MultiPolygon') {
return multiPolygonPath(proj, feature.geometry.coordinates);
}
}
/* a GeoJSON Polygon is a set of 'rings'. The first ring is the shape of the polygon.
* each subsequent ring is a hole inside that polygon. */
function polygonPath(proj, rings) {
var list = [];
var cur = [];
$.each(rings, function(i, ring) {
cur = [];
$.each(ring, function(i, coord) {
var pts = proj(coord);
cur.push(new THREE.Vector2(pts[0], pts[1]));
});
list.push(cur);
});
return list;
}
/* a GeoJSON MultiPolgyon is just a series of Polygons. */
function multiPolygonPath(proj, polys) {
var list = [];
$.each(polys, function(i, poly) {
list.push(polygonPath(proj, poly));
});
return list;
}
/* for each feature, find it's X/Y Path, create shape(s) with the required holes,
* and extrude the shape */
function renderFeatures(proj, features, scene, isState) {
var color = 0x33ccff;
$.each(features, function(i, feature) {
var polygons = path(proj, feature);
if (feature.geometry.type != 'MultiPolygon') {
polygons = [polygons];
}
$.each(polygons, function(i, poly) {
var shape = new THREE.Shape(poly[0]);
if (poly.length > 1) {
shape.holes = poly.slice(1).map(function(item) { return new THREE.Shape(item); });
}
var geom = new THREE.ExtrudeGeometry(shape, { amount: 20, bevelEnabled: false });
var c = new THREE.Mesh(geom, new THREE.MeshLambertMaterial({color: color}) );
c.rotation.x = Math.PI/2;
c.translateX(-290);
c.translateZ(50);
c.translateY(5);
scene.add(c);
});
});
}
Map.prototype.renderCounties = function() {
$.getJSON('/data/us-counties.json', function(json) {
renderFeatures(this.projection, json.features, this.scene, false);
this.renderer.render(this.scene, this.camera);
}.bind(this));
}
It looks like the points of the polygon are in the wrong order.

Categories