Three.js - Edit plane geometry - javascript

So, I want to make a simple terrain editor. So, on mouseDown, I want the selected face to move up.
The intersection works great, and I try to modify the geometry like so:
var intersects2 = ray.intersectObjects([plane]);
if (intersects2.length > 0) {
var face = intersects2[0].face;
var obj1 = intersects2[0].object;
var geo = obj1.geometry;
geo.vertices[face.a].z += 50;
geo.vertices[100].z += 50;
geo.vertices[0].z += 50;
geo.computeVertexNormals();
geo.computeFaceNormals();
geo.__dirtyVertices = true;
geo.__dirtyNormals = true;
console.log(face.a);
}
The console log shows the correct vertex index, but nothing on the plane moves. Any ideas why?
The plane is created like this:
var planegeo = new THREE.PlaneGeometry( 500, 500, 10, 10 );
planegeo.dynamic = true;
plane = new THREE.Mesh( planegeo, new THREE.MeshPhongMaterial( { color: 0x99ff66 } ) );
plane.receiveShadow = true;
scene.add( plane );

Looking at your code, it looks like you are using syntax pre R49. It may just be that you need to update your dirty flag code to (assuming you are now using a newer library!):
geo.verticesNeedUpdate = true;
geo.normalsNeedUpdate = true;

Related

ThreeJS extension for Thingworx

I am trying to create a 3 JS extension for Thingworx, but the renderHtml keeps bugging in combination with a 3 JS canvas in it (See code).
//runtime.ts file
renderHtml(): string {
let htmlString = '<div class="widget-content"><canvas></canvas></div>';
return htmlString;
}
afterRender(): void {
const OrbitControls = require('three-orbit-controls')(CourseView);
const OBJLoader = require('three-obj-loader')(CourseView);
var scene = new CourseView.Scene();
var width = this.getProperty('SceneWidth', 0);
var height = this.getProperty('SceneHeight', 0);
var color = this.getProperty('SceneColor', '#000000');
if(width <= 0) { width = window.innerWidth }
if(height <= 0) { height = window.innerHeight }
if(color == undefined){ color = "#000000" }
var ratio = width / height;
var camera = new CourseView.PerspectiveCamera(75, ratio, 0.1, 1000);
camera.position.z = 30;
var cv = this.jqElement.find("canvas").get(0);
console.log(cv);
this.renderer = new CourseView.WebGLRenderer({canvas: cv});
this.renderer.setSize(width, height);
this.renderer.setClearColor("#0000ff");
var control = new OrbitControls(camera, this.renderer.domElement);
const geometry = new CourseView.SphereGeometry( 15, 32, 16 );
const material = new CourseView.MeshBasicMaterial( { color: 0xff00ff, wireframe: true } );
const sphere = new CourseView.Mesh( geometry, material );
scene.add( sphere );
control.addEventListener('change', () => this.myRender(scene, camera));
this.myRender(scene, camera);
}
myRender(scene, camera) {
this.renderer.render(scene, camera);
}
As shown, the WebGLRenderer gets the canvas inside the div with the class widget-content. I need this div, to realize bindings of Thingworks. When I leave out the div, everything works fine. If the div exists to implement bindings, the sphere is not rendered. Moreover, the renderer seems stuck and also has no blue background, despite the clear-color call.
When I click on it (maybe its not updated) the color changes to blue, but still there is no sphere. Does anyone has realized ThreeJS in Thingworx and can show me how they did it? I think maybe the div widget-content does apply some changes to all childern (also my ThreeJS canvas), but I cant tell which changes... Maybe someone knows?
Full code: https://www.toptal.com/developers/hastebin/olelowawih.js
For those of you might having this problem in the future, check your setter, you might want to render there as well and keep track of NaN values...

How to animate the growth of a line in Three.js?

What I have:
var pointA = new THREE.Vector3(camera_RC_Holder.position.x, camera_RC_Holder.position.y, camera_RC_Holder.position.z);
var direction = camera_RC.position.clone();
direction.applyMatrix4( camera_RC.matrixWorld );
direction.normalize();
var distance = 700;
var pointB = new THREE.Vector3();
pointB.addVectors ( pointA, direction.multiplyScalar( -distance ) );
var geometry = new THREE.Geometry();
geometry.vertices.push( pointA );
geometry.vertices.push( pointB );
var material = new THREE.LineBasicMaterial( { color : 0xff0000 } );
var line = new THREE.Line( geometry, material );
scene_Main.add( line );
What I want:
What I'm trying to do is to show that a ray has began from the camera and explores through the view volume. So, instead of instantly create a line (point_A, point_B) I want to grow the line from point_A pixel by pixel until it meets it's destination (point_B).
Question:
How to draw the lines pixel by pixel as shown in the code snippet below??
var w = 200;
var h = 150;
var x;
function setup(){
createCanvas(w,h);
x=0;
y=0;
}
function draw(){
if(x>w){
x = 0;
}
background(250);
line(0,50,x,50); //x1,y1,x2,y2
x++;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.7.3/p5.js"></script>
Title question and explanation does not fit to me very well, maybe I did not understand your question perfectly. I will try to answer just the title question and edit this answer as soon as some clarifications are done from your side.
( Also I would always try to avoid multi-questions as much as possible. )
About the title question:
Adding something to the scene is basically drawing it... or do you mean something else?
Bind this code snippet to your click mouse event. This will create your raycast, in order to see it you need to add it to your scene. Afterwards you can move your camera and check how it looks like:
var material = new THREE.LineBasicMaterial({
color: 0x0000ff
});
var geometry = new THREE.Geometry();
geometry.vertices.push(new THREE.Vector3(raycaster.ray.origin.x, raycaster.ray.origin.y, raycaster.ray.origin.z));
geometry.vertices.push(new THREE.Vector3(raycaster.ray.origin.x + (raycaster.ray.direction.x * 100000), raycaster.ray.origin.y + (raycaster.ray.direction.y * 100000), raycaster.ray.origin.z + (raycaster.ray.direction.z * 100000)));
var line = new THREE.Line(geometry, material);

Bounding Box of a stl object in three.js

Good day everyone,
I have come across several posts about this topic but no solution would work. I have an object that I load with the help of the STLLoader in three.js for which I would like to get a bounding box.
// Add stl objects and a name
function addSTLObject(url, name) {
var loader = new THREE.STLLoader();
loader.load(url, function (geometry) {
var material = new THREE.MeshPhongMaterial({ color: 0xff5533 });
var mesh = new THREE.Mesh(geometry, material);
// To scale element, use:
// mesh.scale.set(0.01, 0.01, 0.01);
// Add a name to the object to find it if it needs to be removed
mesh.name = name;
mesh.position.x = 0;
mesh.position.y = 0;
mesh.position.z = 0;
scene.add(mesh);
});
}
and I am loading this object as follows:
addSTLObject('model/cases/iphone5.stl', 'phone-model');
var phoneModelAdded = scene.getObjectByName('phone-model', true);
Now I have tried the solutions provided here: https://github.com/mrdoob/three.js/issues/3471 and here Any way to get a bounding box from a three.js Object3D?
var bbox = new THREE.Box3().setFromObject(phoneModelAdded);
var geometry = phoneModel.children[0].children[0].geometry;
geometry.computeBoundingBox();
While the first solution gives me an error saying "Cannot read property 'updateMatrixWorld' of undefined", the second gives me no error at all but does nothing and if I try accessing the "geometry" property it says that it doesn't exist.
Does someone have a working solution?
Any help is appreciated.
Have a good day!
EDIT:
// Add stl objects and a name
function addSTLObject(url, name) {
var bbox;
var loader = new THREE.STLLoader();
loader.load(url, function (geometry) {
var material = new THREE.MeshPhongMaterial({ color: 0xff5533 });
var mesh = new THREE.Mesh(geometry, material);
// To scale element, use:
// mesh.scale.set(0.01, 0.01, 0.01);
// Add a name to the object to find it if it needs to be removed
mesh.name = name;
mesh.position.x = 0;
mesh.position.y = 0;
mesh.position.z = 0;
bbox = new THREE.Box3().setFromObject(mesh);
scene.add(mesh);
return bbox;
});
}
and aftwards
var bbox = addSTLObject('model/cases/iphone5.stl', 'phone-model');
scene.add(bbox);
Error: "THREE.Object3D.add: object not an instance of THREE.Object3D"
EDIT 2:
var bbox, bboxComputed = false;
function addSTLObject(url, name) {
var bbox;
var loader = new THREE.STLLoader();
loader.load(url, function (geometry) {
var material = new THREE.MeshPhongMaterial({ color: 0xff5533 });
var mesh = new THREE.Mesh(geometry, material);
// To scale element, use:
// mesh.scale.set(0.01, 0.01, 0.01);
// Add a name to the object to find it if it needs to be removed
mesh.name = name;
mesh.position.x = 0;
mesh.position.y = 0;
mesh.position.z = 0;
bbox = new THREE.BoundingBoxHelper(mesh);
bbox.update();
bboxComputed = true;
scene.add(mesh);
});
}
addSTLObject('model/cases/iphone5.stl', 'phone-model');
var myInterval = setInterval( function(){
if( bboxComputed ) {
alert( bbox.box.min, bbox.box.max, bbox.box.size() );
scene.add(bbox);
clearInterval( myInterval );
bboxComputed = false;
}
}, 100 );
This won't work.
EDIT 3:
I was trying to set up a function that would have everything I need and give back an object with all of the calculated information:
function calculateSTLProperties(url, name) {
var STLObject, STLbbox, STLComputed = false, STLGeometry;
var loader = new THREE.STLLoader();
loader.load(url, function (geometry) {
var material = new THREE.MeshPhongMaterial({ color: 0xff5533 });
var mesh = new THREE.Mesh(geometry, material);
// To scale element, use:
// mesh.scale.set(0.01, 0.01, 0.01);
// Add a name to the object to find it if it needs to be removed
mesh.name = name;
mesh.position.x = 0;
mesh.position.y = 0;
mesh.position.z = 0;
// Compute a bounding box for the element
STLbbox = new THREE.BoundingBoxHelper(mesh);
STLbbox.update();
// Get the geometry of the case
STLGeometry = geometry;
STLComputed = true;
});
// Set an interval to wait for the corresponding bounding box and geometry to be computed
var myInterval = setInterval( function(){
if( STLComputed ) {
STLObject = {
"geometry" : STLGeometry,
"bbox" : STLbbox,
"x" : STLbbox.box.size().x,
"y" : STLbbox.box.size().y,
"z" : STLbbox.box.size().z
};
clearInterval( myInterval );
bboxComputed = false;
}
}, 100 );
return STLObject;
}
Unfortunately, somehow the object doesn't get passed through and I end up with an "undefined" in the end when trying to save it:
var STLObjectLoaded = calculateSTLProperties('model/cases/iphone5.stl', 'phone-model');
console.log(STLObjectLoaded);
What am I missing?
Model loading is asynchronous so all you computations should happen after the model has loaded. Add a callback, which is called when the model has loaded and add there the call to the bbox. The way you have it the scene.getObjectByName() call is being made with an empty object which you also verify ("if I try accessing the "geometry" property it says that it doesn't exist")
Update:
Use:
var bbox = new THREE.BoundingBoxHelper( mesh ); bbox.update();
scene.add( bbox );
from inside the loader.load() function.
The setFromObject() call only create the bounding box but not the geometry for the bbox.
Update II
If you want the bbox to be available outside your loader.load() function you would need to use a global variable
var bboxComputed = false;
which you set to true right after you compute the bbox inside your loader.load() function:
scene.add( bbox );
bboxComputed = true;
Then in your main instead of using:
console.log( bbox.box.min, bbox.box.max, bbox.box.size() );
you would use something like:
var myInterval = setInterval( function(){
if( bboxComputed ) {
console.log( bbox.box.min, bbox.box.max, bbox.box.size() );
clearInterval( myInterval );
}
}, 100 );
I am setting a delay of 0.1 second and when the bbox has finally been computed I clear the interval so that it does not run forever.
As of three.js R125, the recommended way to do this is with the loadAsync method, which is now native to three.js:
https://threejs.org/docs/#api/en/loaders/Loader.loadAsync
That method returns a promise. You could then use a 'then' to get the geometry of the STL and create the mesh. You could also use a traditional callback as in earlier answers, or an async/await structure, but I think the example below using the native three.js method is the simplest way. The example shows how you can get geometry to a global variable once the promise is resolved and the STL file is loaded:
// Global variables for bounding boxes
let bbox;
const loader = new STLLoader();
const promise = loader.loadAsync('model1.stl');
promise.then(function ( geometry ) {
const material = new THREE.MeshPhongMaterial();
const mesh = new THREE.Mesh( geometry, material );
mesh.geometry.computeBoundingBox();
bbox = mesh.geometry.boundingBox;
scene.add( mesh );
buildScene();
console.log('STL file loaded!');
}).catch(failureCallback);
function failureCallback(){
console.log('Could not load STL file!');
}
function buildScene() {
console.log('STL file is loaded, so now build the scene');
// !VA bounding box of the STL mesh accessible now
console.log(bbox);
// Build the rest of your scene...
}

Issue accessing object on mouseover in THREE.js

I am working with the threex domevents by Jeromeetienne: https://github.com/jeromeetienne/threex.domevents to handle my hover over of rendered in objects in my canvas
I am having some issue when updating the perspective view when the user changes the type of object, my dom sets the event listener but it seems to never get called. Here is the overview:
When user first loads the page, we set our camera:
// setup local vars
var camera = new THREE.PerspectiveCamera(40,window.innerWidth / window.innerHeight,1,10000);
camera.position.set(500, 800, 1300);// 1st = swing (left or right) 2nd = up or down 3rd = zoom
camera.lookAt( new THREE.Vector3());
camera.positionID = 1;// default
camera.positionType = 'sideView';// default
this.cam = camera;
Than we set our renderer:
renderer = new THREE.CanvasRenderer();
renderer.setClearColor( 0xf0f0f0 );
renderer.setSize( window.innerWidth, window.innerHeight );
Now when a user clicks the canvas, an object is added. We first get our positioning and call a intersectofobjects to see if we are actually on the canvas (mousedown):
event.preventDefault();
this.mouse.x = ( event.clientX / this.renderer.domElement.width ) * 2 - 1;
this.mouse.y = - ( event.clientY / this.renderer.domElement.height ) * 2 + 1;
this.raycaster.setFromCamera( this.mouse, this.camera.cam );
var intersects = this.raycaster.intersectObjects( this.blocks );
After we actually figure out that we are on the plane, we setup our object:
var voxel = new THREE.Mesh( this.room.cubeGeometry.clone(), this.room.cubeMaterial.clone() );
voxel.geometry.computeBoundingBox();
voxel.position.copy( intersect.point ).add( intersect.face.normal );
voxel.position.divideScalar( this.room.divideScalar ).floor().multiplyScalar( this.room.multiplyScalar ).addScalar( this.room.addScalar );
After we setup our object we setup the event listeners on the object
// get our dom event to attach to object
var domEvents = new THREEx.DomEvents(this.camera.cam, this.renderer.domElement);
// DOM events for inside 3d rendering
domEvents.addEventListener(voxel, 'mouseover', this.onDocumentMouseOverCube.bind(this),false);
domEvents.addEventListener(voxel, 'mouseout', this.onDocumentMouseOutCube.bind(this),false);
this.scene.add( voxel );
this.blocks.push( voxel );
this.rooms.push(voxel);
this.render();
This works perfect on first use. It only seems to stop working when the user changes the object that they want to place on the screen, here is how.
We have a gui object that handles our connection with the UI and the core, when someone chooses to add a different object, we call:
jQuery(".changeRoomType.active").removeClass('active');
jQuery(event.srcElement).toggleClass('active');
this.canvas.room.updateRoomType(event.srcElement.id);
Now when room.updateRoomType...() is called it initiates an object like this:
var cubeGeometry = new THREE.BoxGeometry( 100, 50, 100,2,1,2 );// moving to roomObj
var cubeMaterial = new THREE.MeshLambertMaterial( { color: 0x00ff80,overdraw: 1 } ); // moving to roomObj
var cubeTmpHoverMaterial = new THREE.MeshLambertMaterial({ color: 0x00ff80, transparent: true, overdraw: 0, opacity:0.5});// used to give a transparency to the cube tmp view // moving to roomObj
var divideScalar = 50;
var multiplyScalar = 50;
var addScalar = 25;
var additionalZ = 25;
var additionalX = 25;
this.divideScalar = divideScalar;
this.multiplyScalar = multiplyScalar;
this.addScalar = addScalar;
var colorNormal = 0x00ff80;
var colorHover = 0xE4E4E4;
var colorTmpHover = 0xEAEAEA;
var colorRemove = 0xE23434;
this.colorNormal = colorNormal;
this.colorHover = colorHover;
this.colorTmpHover = colorTmpHover;
this.colorRemove = colorRemove;
this.additionalZ = additionalZ;
this.additionalX = additionalX;
this.cubeGeometry = cubeGeometry;
this.cubeMaterial = cubeMaterial;
this.cubeTmpHoverMaterial = cubeTmpHoverMaterial;
Now after this point the mouseover and mouseout on the object seem to never work anymore. My question is why is this? Am I not updating my camera perspective after changing the object type to be displayed?
EDIT:
Please note that our initiated object that is first selected on page load is the following:
var cubeGeometry = new THREE.BoxGeometry( 50, 50, 50,1,1,1 );// moving to roomObj
var cubeMaterial = new THREE.MeshLambertMaterial( { color: 0x00ff80, overdraw: 1 } ); // moving to roomObj
var cubeTmpHoverMaterial = new THREE.MeshLambertMaterial({ color: 0x00ff80, transparent: true, overdraw: 0,opacity:0.5});// used to give a transparency to the cube tmp view // moving to roomObj
var divideScalar = 50;
var multiplyScalar = 50;
var addScalar = 25;
var additionalZ = 0;
var additionalX = 0;
this.additionalZ = additionalZ;
this.additionalX = additionalX;
var colorNormal = 0x00ff80;
var colorHover = 0xE4E4E4;
var colorTmpHover = 0xEAEAEA;
var colorRemove = 0xE23434;
this.colorNormal = colorNormal;
this.colorHover = colorHover;
this.colorTmpHover = colorTmpHover;
this.colorRemove = colorRemove;
this.cubeGeometry = cubeGeometry;
this.cubeMaterial = cubeMaterial;
this.cubeTmpHoverMaterial = cubeTmpHoverMaterial;
The other object that was called above shown in the original question demonstrates our other object size. After this is called it seems to stop recognizing the location of our mouseover and mouseout on the object and or its being placed on someplace else it should not be.

Dynamic height of cube with fixed floor

I am creating a dynamic cube that can be dynamically changed by scaling its mesh. The issue is, I would like to keep it fixed to the floor when modifying its height. This is a snippet of my code:
function init() {
// Floor position
floor = new THREE.Mesh( shadowGeo, shadowMaterial );
floor.position.y = 0;
floor.rotation.x = - Math.PI / 2;
scene.add( floor );
// Defines the cube and its original position
var BoxGeometry = new THREE.BoxGeometry(50, 50, 50);
var boxMaterial = new THREE.MeshLambertMaterial({color: 0x000088});
cube = new THREE.Mesh(BoxGeometry, boxMaterial);
cube.position.set(0,30,0);
scene.add(cube);
// GUI PANEL INTERACTION
// Now the GUI panel for the interaction is defined
gui = new dat.GUI();
parameters = {
height: 1,
reset: function() {resetCube()}
}
// Define the second folder which takes care of the scaling of the cube
var folder1 = gui.addFolder("Dimensions");
var cubeHeight = folder2.add(parameters, "height").min(0).max(200).step(1);
folder1.open();
// Function taking care of the cube changes
cubeHeight.onChange(function(value){cube.scale.y = value;});
gui.open();
}
// Update cube characteristics
function updateCube() {
cube.scale.y = parameters.y;
}
// Reset cube settings
function resetCube() {
parameters.height = 1;
updateCube();
}
// Rest of the code
I have searched around and I saw this similar topic, but still it does not properly explain how to modify dimensions when the object with a reference floor. Do you know how can I solve this issue?
Changed your .onChange() function to have the cube stay on the ground:
// Function taking care of the cube changes
cubeHeightScale.onChange(
function(value)
{
cube.scale.y = value;
cube.position.y = (cubeHeight * value) / 2;
} );
Here is a fiddle to check the changes live: http://jsfiddle.net/Lsjh965o/
three.js r71

Categories

Resources