Trying to add a simple object to a three.js scene. I'm getting the error Uncaught ReferenceError: Cube is not defined, but it seems to be defined to me. What am I doing wrong here?
Here's a plunker
Here's the relevant js:
// Cube
Cube = function(){
var geometry = new THREE.BoxGeometry( 50, 50, 50 );
for ( var i = 0; i < geometry.faces.length; i += 2 ) {
var hex = Math.random() * 0xffffff;
geometry.faces[ i ].color.setHex( hex );
geometry.faces[ i + 1 ].color.setHex( hex );
}
var material = new THREE.MeshBasicMaterial( { vertexColors: THREE.FaceColors, overdraw: 0.5 } );
box = new THREE.Mesh( geometry, material );
box.position.y = 25;
}
function createCube(){
cube = new Cube();
scene.add(cube);
}
Two things are wrong.
You should be adding the cube mesh to the scene. So instead of box = new THREE.Mesh( geometry, material ); put return box = new THREE.Mesh( geometry, material );
call createCube() after you have defined the function
Related
I have loaded a 3D terrain via Blender with the OBJLoader. I have also created a mesh (yellow pointer on the picture below) which i want to follow the mouse while it's on the terrain.
I tried to use the raycaster method but i don't know exactly how to apply it to my .obj as it seems that I can't access it outside the loader.
How can i make my pointer(yellow mesh) stick to my terrain (loaded .obj) while it's following the mouse ?
Please help a total three.js noob...
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 10000 );
var renderer = new THREE.WebGLRenderer();
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
camera.position.z = 150;
camera.position.y=300;
camera.position.x=350;
var light = new THREE.HemisphereLight( 0xffffbb, 0x080820, 1 );
scene.add( light );
var controls= new THREE.OrbitControls(camera,renderer.domElement);
controls.enableDamping=true;
controls.campingFactor=0.25;
controls.enableZoom=true;
controls.minDistance= 1;
controls.maxDistance=3000;
controls.minPolarAngle= -Math.PI/2;
controls.maxPolarAngle= Math.PI/2;
var terrain;
var mtlLoader = new THREE.MTLLoader();
mtlLoader.load('models/terrain.mtl',
(materials) => {
materials.preload();
var loader = new THREE.OBJLoader();
loader.setMaterials(materials);
loader.load(
'models/terrain.obj',
function ( object ) {
terrain = object;
scene.add( terrain );
});
}
);
var Cylindergeometry = new THREE.CylinderGeometry( 5, 0, 8, 32 );
var material = new THREE.MeshBasicMaterial( {color: 0xffff00} );
var cylinder = new THREE.Mesh( Cylindergeometry, material );
var Torusgeometry = new THREE.TorusGeometry( 7, 0.5, 8, 6);
var material = new THREE.MeshBasicMaterial( { color: 0xffff00 } );
var torus = new THREE.Mesh( Torusgeometry, material);
Torusgeometry.rotateX(1.5708);
Torusgeometry.translate(0,-4.5,0);
//Merge the two parts to make it one mesh (yellow pivot)
var PivotGeometry = new THREE.Geometry();
PivotGeometry.merge(Cylindergeometry);
PivotGeometry.merge(Torusgeometry);
var pivot = new THREE.Mesh(PivotGeometry, material);
scene.add(pivot);
// Trying to Raycast the terrain to make the pivot follow the mouse while it's on it
var raycaster = new THREE.Raycaster();
var mouse = new THREE.Vector2();
function onMouseMove( event ) {
mouse.x = ( event.clientX / renderer.domElement.clientWidth ) * 2 - 1;
mouse.y = - ( event.clientY / renderer.domElement.clientHeight ) * 2 + 1;
raycaster.setFromCamera( mouse, camera );
// See if the ray from the camera into the world hits one of our meshes
var intersects = raycaster.intersectObject( terrain,true );
// Toggle rotation bool for meshes that we clicked
if ( intersects.length > 0 ) {
pivot.position.set( 0, 0, 0 );
pivot.lookAt( intersects[ 0 ].face.normal );
pivot.position.copy( intersects[ 0 ].point );
}
}
//Met à jour l'affichage de la scène
var animate = function () {
requestAnimationFrame( animate );
renderer.render( scene, camera );
};
animate();
My loaded .obj terrain
You have the following line in your code:
var intersects = raycaster.intersectObjects( object );
I don't see where object as a variable is declared. I suggest you declare this variable in your module scope, assign the loaded OBJ file to it and then use this code instead:
var intersects = raycaster.intersectObject( object, true );
OBJLoader.load() returns an object of type THREE.Group which is in some sense a container holding an arbitrary number of meshes. Hence, you want to raycast recursively.
When I create multiple mesh's with the same name I can't select them all when I want to remove them from a scene.
I've tried traversing the function to no avail.
event.preventDefault();
scene.traverse(function(child) {
if (child.name === "blueTiles") {
var remove_object = scene.getObjectByName( "blueTiles", true );
scene.remove(remove_object);
}
});
var surroundMaterial = new THREE.MeshBasicMaterial({ color: 0x154995, side: THREE.DoubleSide, transparent: true, opacity: 0.8 });
surroundingCubes = new THREE.Mesh( geometry, surroundMaterial );
scene.add( surroundingCubes );
surroundingCubes.name = "blueTiles";
surroundingCubes.position.set(selectedObject.position.x - 1, 0.11, selectedObject.position.z);
var surroundMaterial = new THREE.MeshBasicMaterial({ color: 0x154995, side: THREE.DoubleSide, transparent: true, opacity: 0.8 });
surroundingCubes = new THREE.Mesh( geometry, surroundMaterial );
scene.add( surroundingCubes );
surroundingCubes.name = "blueTiles";
surroundingCubes.position.set(selectedObject.position.x + 1, 0.11, selectedObject.position.z);
surroundingCubes.rotation.x = Math.PI / 2;
I should be able to only delete all the objects with the name blueTiles
EDIT I switched from names to Groups, and that worked wonders
SOLUTION BELOW
function onDocumentMouseDown(event) {
for (var i = group.children.length - 1; i >= 0; i--) {
group.remove(group.children[i]);
}
var surroundingMaterial = new THREE.MeshBasicMaterial({ color: 0x154995, side: THREE.DoubleSide, transparent: true, opacity: 0.8 });
var geometry = new THREE.PlaneGeometry(1, 1, 1, 1);
if ( selectedObject.position.x - 1 >= 0) {
surroundingCube = new THREE.Mesh( geometry, surroundingMaterial );
surroundingCube.position.set(selectedObject.position.x - 1, 0.11, selectedObject.position.z);
surroundingCube.rotation.x = Math.PI / 2;
group.add(surroundingCube);
}
if ( selectedObject.position.x + 1 <= 9) {
surroundingCube = new THREE.Mesh( geometry, surroundingMaterial );
surroundingCube.position.set(selectedObject.position.x + 1, 0.11, selectedObject.position.z);
surroundingCube.rotation.x = Math.PI / 2;
group.add(surroundingCube);
}
scene.add( group );
}
You can try grouping the meshes using the THREE.group class.
A very basic usage would be something like:
var meshes = new THREE.Group();
var mesh1 = new THREE.Mesh( geometry, material );
var mesh2 = new THREE.Mesh( geometry, material );
var mesh3 = new THREE.Mesh( geometry, material );
meshes.add( mesh1 );
meshes.add( mesh2 );
meshes.add( mesh3 );
scene.add( meshes );
getObjectByName just calls getObjectByProperty (using the name property) which only returns the first object it finds.
You really just need to loop over children of the scene / object3d, check their name, and remove.
If you know all the ones you want to remove will be at the top level it is simple.
Something like....(untested)
for ( var i = 0, l = scene.children.length; i < l; i ++ ) {
if (scene.children[i].name === 'blueTiles') {
scene.remove(scene.children[i]);
}
}
If you also want to check at lower levels, you probably want some sort of recursion.
EDIT...
After having another look at your question..and remembering that there is a traverse function
You seem to already be traversing the scene and have access to the child.
I think you just need to change your function (and not call getObjectByName at all).
scene.traverse(function(child) {
if (child.name === "blueTiles") {
scene.remove(child);
}
});
in Three.js i'm trying to draw a cube with dashed line edges but the lines are still showing as solid. Here's my code:
var mat_line = new THREE.LineDashedMaterial( { color: "black", dashSize: 1, gapSize: 1 } );
var geometry = new THREE.BoxGeometry( 10, 10, 10 );
geometry.computeLineDistances();
var cube = new THREE.Mesh( geometry, mat_cube );
scene.add( cube )
var edges = new THREE.EdgesGeometry( geometry )
var line = new THREE.LineSegments( edges, mat_line )
scene.add( line )
can anyone see where I'm going wrong here? or is it just not possible with this workflow?
You want use LineDashedMaterial with EdgesGeometry.
To use LineDashedMaterial the line must have line distances specified.
Use a pattern like so:
var material = new THREE.LineDashedMaterial( { color: 0xff0000, dashSize: 1, gapSize: 1 } );
var geometry = new THREE.BoxGeometry( 10, 10, 10 );
geometry = new THREE.EdgesGeometry( geometry );
var line = new THREE.LineSegments( geometry, material );
line.computeLineDistances();
scene.add( line );
three.js r.92
I would to apply a transparent material on front-side faces of a geometry. It's quite easy:
var normal = new THREE.MeshNormalMaterial();
normal.side = THREE.BackSide;
var materials = [
normal,
new THREE.MeshBasicMaterial( { transparent: true, opacity: 0 } )
];
for( var i = 0; i < geometry.faces.length; i++ ) {
geometry.faces[ i ].materialIndex = 0;
}
//a 'hole' to look inside
geometry.faces[ 0 ].materialIndex = 1;
geometry.faces[ 1 ].materialIndex = 1;
mesh = new THREE.Mesh( geometry, new THREE.MeshFaceMaterial( materials ) );
Codepen to example above: http://codepen.io/pyort/pen/egqbLY
But there is a catch: when looking on a front-side of a face, I would like to show what's underneath this geometry, not back-side.
I'm not sure how to explain that in easy language, here is visual explanation
What is my goal is this kind of 'portal' thing, so when you look from one side, it appears to have depth, but from other angles it's look super thin. How to achieve such an effect? Use some kind of shader? Use mirror techniques?
Thanks.
Try something like this:
// the inside of the hole
let geometry1 = new THREE.CubeGeometry(2,2,2);
let material1 = new THREE.MeshLambertMaterial({
transparent : true,
color: 0xff0000,
side: THREE.BackSide
});
mesh1 = new THREE.Mesh( geometry1, material1 );
scene.add( mesh1 );
// the invisibility cloak (box with a hole)
let geometry0 = new THREE.BoxGeometry(2,2,2);
geometry0.faces.splice(4, 2); // make hole by removing top two triangles
let material0 = new THREE.MeshBasicMaterial({
colorWrite: false
});
let mesh0 = new THREE.Mesh( geometry0, material0 );
mesh0.scale.set(1,1,1).multiplyScalar(1.01); // slightly larger than inside cube
scene.add(mesh0);
Live examples are at https://stemkoski.github.io/AR-Examples/
I have a loop where I create a multiple Mesh with different geometry, because each mesh has one texture:
var geoCube = new THREE.CubeGeometry(voxelSize, voxelSize, voxelSize);
var geometry = new THREE.Geometry();
for( var i = 0; i < voxels.length; i++ ){
var voxel = voxels[i];
var object;
color = voxel.color;
texture = almacen.textPlaneTexture(voxel.texto,color,voxelSize);
//Return the texture with a color and a text for each face of the geometry
material = new THREE.MeshBasicMaterial({ map: texture });
object = new THREE.Mesh(geoCube, material);
THREE.GeometryUtils.merge( geometry, object );
}
//Add geometry merged at scene
mesh = new THREE.Mesh( geometry, new THREE.MeshFaceMaterial() );
mesh.geometry.computeFaceNormals();
mesh.geometry.computeVertexNormals();
mesh.geometry.computeTangents();
scene.add( mesh );
But now I have this error in the javascript code Three.js
Uncaught TypeError: Cannot read property 'map' of undefined
In the function:
function bufferGuessUVType ( material ) {
}
Update:
Finally I have removed the merged solution and I can use an unique geometry for the all voxels. Altough I think that If I use merge meshes the app would have a better performance.
For r53+ the code should be something like this:
var geoCube = new THREE.CubeGeometry(voxelSize, voxelSize, voxelSize);
var geometry = new THREE.Geometry();
var materials = [];
for( var i = 0; i < voxels.length; i++ ){
for ( var j = 0; j < geoCube.faces.length; j ++ ) {
geoCube.faces[ j ].materialIndex = i;
}
var object = new THREE.Mesh( geoCube );
// here I assume you'll me positioning the object.
THREE.GeometryUtils.merge( geometry, object );
var voxel = voxels[i];
var texture = almacen.textPlaneTexture(voxel.texto,voxel.color,voxelSize);
materials.push( new THREE.MeshBasicMaterial( { map: texture } ) );
}
// Add geometry to scene
var mesh = new THREE.Mesh( geometry, new THREE.MeshFaceMaterial( materials ) );
mesh.geometry.computeFaceNormals();
mesh.geometry.computeVertexNormals();
scene.add( mesh );
Some methods are expecting the materials array to be in the mesh face material. For example:
function getBufferMaterial( object, geometryGroup ) {
return object.material instanceof THREE.MeshFaceMaterial
? object.material.materials[ geometryGroup.materialIndex ]
: object.material;
};
Its possible to handle this by referencing the materials array both in the geometry and in the mesh face material. So the line where the mesh is created would look something like:
mesh = new THREE.Mesh( geometry, new THREE.MeshFaceMaterial(geometry.materials) );
and in the loop processing the voxels:
geometry.materials.push( new THREE.MeshBasicMaterial({ map: texture }));
Each face in the geometry also needs to have a materialIndex assigned or there will be an error. Looping through geometry.faces[] and assigning a materialIndex might be a good way if you know how many faces each of the voxels has.