I for now work on a 3D engine for an amateur video game, and i want to add to him tons of features to get the best performances & gameplay. But i've got serious problems to get an "on click event" with lod meshes.
For the "lod" part, no problem for now, he's integrated, but i found no solution to apply that exemple with him :
https://stackoverflow.com/a/12808987/3379444
Did i must push the "lod" object, or every mesh individually ?
a part of my code :
var i, j, mesh, lod;
for ( j = 0; j <= 42; j++) {
lod = new THREE.LOD();
//Here, it's just var for place my mesh into a circle
rayonSysteme = Math.floor(Math.random() * 10000) + 2500;
angleSysteme = Math.random() * 360;
for ( i = 0; i < geometry.length; i ++ ) {
mesh = new THREE.Mesh( geometry[ i ][ 0 ], material );
mesh.scale.set( 1.5, 1.5, 1.5 );
mesh.updateMatrix();
mesh.matrixAutoUpdate = false;
//Callback here ?
mesh.callback = function() { console.log( this.name ); }
lod.addLevel( mesh, geometry[ i ][ 1 ] );
}
lod.position.x = rayonSysteme * Math.cos(angleSysteme);
lod.position.y = Math.floor(Math.random() * 1000)-500;
lod.position.z = rayonSysteme * Math.sin(angleSysteme);
//Or here ?
lod.updateMatrix();
lod.matrixAutoUpdate = false;
gravity = drawCircle("XZ", 64, 500, 360);
gravity.position.x = lod.position.x;
gravity.position.y = lod.position.y;
gravity.position.z = lod.position.z;
scene.add( lod );
//objects.push( lod );
scene.add( gravity );
};
Raycaster will test the mesh that should be used based on the distance to the ray origin.
This is the relevant code inside Raycaster:
} else if ( object instanceof THREE.LOD ) {
matrixPosition.setFromMatrixPosition( object.matrixWorld );
var distance = raycaster.ray.origin.distanceTo( matrixPosition );
intersectObject( object.getObjectForDistance( distance ), raycaster, intersects );
}
Related
I'm trying to get a mesh's position to change constantly so that it moves at a constant velocity. The console shows that the position Vector3 is changing, but the actual scene shows all of the meshes remaining static. This is the code I tried:
let ball = new THREE.Mesh();
let equator = new THREE.Mesh()
for (let i = 1; i <= 12; i++) {
let scale = Math.floor(Math.random() * 3) + 1
let pos_x = Math.floor(Math.random() * 10) * 5 - 25
let pos_y = Math.floor(Math.random() * 10) * 5 - 25
let pos_z = Math.floor(Math.random() * 10) * 5 - 25
pos = new THREE.Vector3(pos_x, pos_y, pos_z)
loader.load( './ball.gltf', function ( gltf ) { //load sphere
gltf.scene.traverse(function(model) {
if (model.isMesh) {
model.castShadow = true;
model.material = sphereMaterial;
pos_arr.push(model)
}
});
ball = gltf.scene
ball.position.set(pos_x, pos_y, pos_z)
ball.scale.set(scale, scale, scale)
scene.add( ball );
}, undefined, function ( error ) {
console.error( error );
} );
loader.load( './equator.gltf', function ( gltf2 ) {
gltf2.scene.traverse(function(model) { //for gltf shadows!
if (model.isMesh) {
model.castShadow = true
model.material = equatorMaterial
}
});
equator = gltf2.scene
equator.position.set(pos_x, pos_y, pos_z)
equator.scale.set(scale, scale, scale)
scene.add( equator )
}, undefined, function ( error ) {
console.error( error )
} );
pos_arr.push([ball, equator, pos])
}
//light
const light = new THREE.AmbientLight( 0xffffff );
scene.add( light );
let y_axis_dist = []
for (let j = 0; j < pos_arr.length; j++) {
y_axis_dist.push(pos_arr[j][2].distanceTo(new THREE.Vector3(0, pos_arr[j][2].y , 0)))
}
//render
console.log(pos_arr)
function animate() {
requestAnimationFrame( animate );
pos_arr[0][1].position.x += 0.1
console.log(pos_arr[0][1].position)
renderer.render( scene, camera );
}
animate()
If anyone knows what I'm doing wrong, I will appreciate it a lot.
I’m trying to get .stl files to appear smooth, but the edges result in these weird dark areas.
With flatShading set to true
With flatShading set to false
Is there any way to make the edges perfectly smooth without these weird artifacts?
var renderer = new THREE.WebGLRenderer({ alpha: true });
var camera = new THREE.PerspectiveCamera(30, window.innerWidth / window.innerHeight, 1, 500);
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
window.addEventListener('resize', function () {
renderer.setSize(window.innerWidth, window.innerHeight);
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
}, false);
var controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.2;
var scene = new THREE.Scene();
// Hemisphere light
var hemiLight = new THREE.HemisphereLight(0xffffff, 0x444444);
hemiLight.position.set(0, 100, 0);
scene.add(hemiLight);
// Directional light
var dirLight = new THREE.DirectionalLight(0x323232);
dirLight.position.set(- 0, 40, 50);
dirLight.castShadow = true;
dirLight.shadow.camera.top = 50;
dirLight.shadow.camera.bottom = - 25;
dirLight.shadow.camera.left = - 25;
dirLight.shadow.camera.right = 25;
dirLight.shadow.camera.near = 0.1;
dirLight.shadow.camera.far = 200;
dirLight.shadow.mapSize.set(1024, 1024);
scene.add(dirLight);
var loader = new THREE.STLLoader();
loader.load( 'https://bymu.eu/test.stl', function ( geometry ) {
var material = new THREE.MeshPhongMaterial({ specular: 0x111111, shininess: 200, color: 0xff5533, flatShading: false });
var tempGeometry = new THREE.Geometry().fromBufferGeometry(geometry);
tempGeometry.mergeVertices();
tempGeometry.computeVertexNormals();
tempGeometry.computeFaceNormals();
geometry.fromGeometry(tempGeometry);
var mesh = new THREE.Mesh(tempGeometry, material);
scene.add(mesh);
// Compute the middle
var middle = new THREE.Vector3();
geometry.computeBoundingBox();
geometry.boundingBox.getCenter(middle);
// Center it
mesh.position.x = -1 * middle.x;
mesh.position.y = -1 * middle.y;
mesh.position.z = -1 * middle.z;
// Pull the camera away as needed
var largestDimension = Math.max(geometry.boundingBox.max.x,
geometry.boundingBox.max.y, geometry.boundingBox.max.z)
camera.position.z = largestDimension * 1.5;
render();
});
function render() {
renderer.render( scene, camera );
}
var animate = function () {
requestAnimationFrame(animate);
controls.update();
renderer.render(scene, camera);
}; animate();
body {
background: #b2b2b2;
margin:0;
padding:0;
overflow: hidden;
}
<script src="https://raw.githack.com/mrdoob/three.js/dev/build/three.min.js"></script>
<script src="https://raw.githack.com/mrdoob/three.js/dev/examples/js/controls/OrbitControls.js"></script>
<script src="https://raw.githack.com/mrdoob/three.js/dev/examples/js/loaders/STLLoader.js"></script>
The problem isn't with Three.js, but with your geometry. Three.js uses "vertex normals" to know which direction the vertex is facing. This is used to smooth out faces. See the illustration below, your edge has a "smooth edge" (left diagram), where the direction of the faces is blended along that 90-degree angle. If you want a "sharp edge" (on the right), you'll have to tell your your geometry to create a second normal, each one pointing perpendicular to the face, so the angles don't blend.
.
Here's what your normals look like in Blender, in a before/after animation. Notice that a single normal down the middle gives the undesired smooth shading:
The way to achieve this varies from one editor to another, but I'm sure you can find the exact step-by-step instructions by looking up "mark sharp edge" for your editor of choice.
Solved it, found this great function https://codepen.io/Ni55aN/pen/zROmoe
THREE.Geometry.prototype.computeAngleVertexNormals = function(angle){
function weightedNormal( normals, vector ) {
var normal = new THREE.Vector3();
for ( var i = 0, l = normals.length; i < l; i ++ ) {
if ( normals[ i ].angleTo( vector ) < angle ) {
normal.add( normals[ i ] );
}
}
return normal.normalize();
}
this.computeFaceNormals();
var vertexNormals = [];
for ( var i = 0, l = this.vertices.length; i < l; i ++ ) {
vertexNormals[ i ] = [];
}
for ( var i = 0, fl = this.faces.length; i < fl; i ++ ) {
var face = this.faces[ i ];
vertexNormals[ face.a ].push( face.normal );
vertexNormals[ face.b ].push( face.normal );
vertexNormals[ face.c ].push( face.normal );
}
for ( var i = 0, fl = this.faces.length; i < fl; i ++ ) {
var face = this.faces[ i ];
face.vertexNormals[ 0 ] = weightedNormal( vertexNormals[ face.a ], face.normal );
face.vertexNormals[ 1 ] = weightedNormal( vertexNormals[ face.b ], face.normal );
face.vertexNormals[ 2 ] = weightedNormal( vertexNormals[ face.c ], face.normal );
}
if ( this.faces.length > 0 ) {
this.normalsNeedUpdate = true;
}
}
Results in exactly what I want after playing around with the angle, however increases loading time and has a bit of a performance penalty when a bunch of meshes are loaded, something I can live with as the meshes look amazing. I tried exporting the meshes so the browser wouldn’t have to recalculate every time, but some of them inflated 5-10 times due to this process. So it’s a sacrifice of loading time either way.
I try to make a shape with holes and to extrude it, but I got a strange result :
here is my code :
var shape = new THREE.Shape();
shape.moveTo(0,0);
var radius = 1;
shape.absarc(0,radius,radius,3/2*(Math.PI),1/2*(Math.PI), false);
shape.lineTo(6.34,ToolSize);
shape.absarc(6.34,radius,radius,1/2*(Math.PI),3/2*(Math.PI), false);
for (var i=0; i<3; i++) {
var hole = new THREE.Shape();
var centerX = 1.9+i*1.27;
hole.absarc(centerX,0.6,0.3,0,2*(Math.PI), false);
shape.holes.push(hole);
}
var extrudeSettings={amount: 2, bevelEnabled: false, material: 0, extrudeMaterial: 1, steps: 10};
var geometry = new THREE.ExtrudeGeometry( shape, extrudeSettings );
var material1 = new THREE.MeshStandardMaterial({color: 0x111111, roughness: 0.1, metalness: 0.4, side: THREE.DoubleSide});
var material2 = new THREE.MeshStandardMaterial({color: 0x8dbe8d, roughness: 0.7, metalness: 0, side: THREE.DoubleSide});
var materials = [
material1,
material2];
var mesh = THREE.SceneUtils.createMultiMaterialObject(geometry,materials);
Thank you for your help
The triangulation will work if you use earcut as the triangulation algorithm.
This change should be made permanent in future releases of three.js, but for now, follow the changes implemented in this three.js example.
The change involves adding the following code:
<!-- replace built-in triangulation with Earcut -->
<script src="js/libs/earcut.js"></script>
<script>
THREE.ShapeUtils.triangulateShape = function ( contour, holes ) {
function removeDupEndPts( points ) {
var l = points.length;
if ( l > 2 && points[ l - 1 ].equals( points[ 0 ] ) ) {
points.pop();
}
}
function addContour( vertices, contour ) {
for ( var i = 0; i < contour.length; i ++ ) {
vertices.push( contour[ i ].x );
vertices.push( contour[ i ].y );
}
}
removeDupEndPts( contour );
holes.forEach( removeDupEndPts );
var vertices = [];
addContour( vertices, contour );
var holeIndices = [];
var holeIndex = contour.length;
for ( i = 0; i < holes.length; i ++ ) {
holeIndices.push( holeIndex );
holeIndex += holes[ i ].length;
addContour( vertices, holes[ i ] );
}
var result = earcut( vertices, holeIndices, 2 );
var grouped = [];
for ( var i = 0; i < result.length; i += 3 ) {
grouped.push( result.slice( i, i + 3 ) );
}
return grouped;
};
</script>
three.js r.88
I have a particle system where all the particles are positioned at the same coordinates and one after another, in random directions, they (should) start orbiting the center of the scene forming a sphere.
What I managed to achieve until now is a group of Vector3 objects (the particles) that one after another start orbiting the center along the Z axis simply calculating their sine and cosine based on the current angle.
I'm not that good at math and I don't even know what to look for precisely.
Here's what I wrote:
var scene = new THREE.Scene();
let container = document.getElementById('container'),
loader = new THREE.TextureLoader(),
renderer,
camera,
maxParticles = 5000,
particlesDelay = 50,
radius = 50,
sphereGeometry,
sphere;
loader.crossOrigin = true;
function init() {
let vw = window.innerWidth,
vh = window.innerHeight;
renderer = new THREE.WebGLRenderer();
renderer.setSize(vw, vh);
renderer.setPixelRatio(window.devicePixelRatio);
camera = new THREE.PerspectiveCamera(45, vw / vh, 1, 1000);
camera.position.z = 200;
camera.position.x = 30;
camera.position.y = 30;
camera.lookAt(scene.position);
scene.add(camera);
let controls = new THREE.OrbitControls(camera, renderer.domElement);
let axisHelper = new THREE.AxisHelper(50);
scene.add(axisHelper);
container.appendChild(renderer.domElement);
window.addEventListener('resize', onResize, false);
}
function onResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
function draw() {
sphereGeometry = new THREE.Geometry();
sphereGeometry.dynamic = true;
let particleTexture = loader.load('https://threejs.org/examples/textures/particle2.png'),
material = new THREE.PointsMaterial({
color: 0xffffff,
size: 3,
transparent: true,
blending: THREE.AdditiveBlending,
map: particleTexture,
depthWrite: false
});
for ( let i = 0; i < maxParticles; i++ ) {
let vertex = new THREE.Vector3(radius, 0, 0);
vertex.delay = Date.now() + (particlesDelay * i);
vertex.angle = 0;
sphereGeometry.vertices.push(vertex);
}
sphere = new THREE.Points(sphereGeometry, material);
scene.add(sphere);
}
function update() {
for ( let i = 0; i < maxParticles; i++ ) {
let particle = sphereGeometry.vertices[i];
if ( Date.now() > particle.delay ) {
let angle = particle.angle += 0.01;
particle.x = radius * Math.cos(angle);
if ( i % 2 === 0 ) {
particle.y = radius * Math.sin(angle);
} else {
particle.y = -radius * Math.sin(angle);
}
}
}
sphere.geometry.verticesNeedUpdate = true;
}
function render() {
update();
renderer.render(scene, camera);
requestAnimationFrame(render);
}
init();
draw();
render();
And here's the JSFiddle if you want to see it live:
https://jsfiddle.net/kekkorider/qs6s0wv2/
EDIT: Working example
Can someone please give me a hand?
Thanks in advance!
You want each particle to rotate around a specific random axis. You can either let them follow a parametric equation of a circle in 3D space, or you can make use of THREE.js rotation matrices.
Right now all your particles are rotating round the vector (0, 0, 1). Since your particles start off on the x-axis, you want them all to rotate around a random vector in the y-z plane (0, y, z). This can be defined during the creation of the vertices:
vertex.rotationAxis = new THREE.Vector3(0, Math.random() * 2 - 1, Math.random() * 2 - 1);
vertex.rotationAxis.normalize();
now you can just call the THREE.Vector3.applyAxisAngle(axis, angle) method on each of your particles with the random rotation axis you created each update:
particle.applyAxisAngle(particle.rotationAxis, 0.01);
To sum up, this is how it should look like:
draw():
...
for ( let i = 0; i < maxParticles; i++ ) {
let vertex = new THREE.Vector3(radius, 0, 0);
vertex.delay = Date.now() + (particlesDelay * i);
vertex.rotationAxis = new THREE.Vector3(0, Math.random() * 2 - 1, Math.random() * 2 - 1);
vertex.rotationAxis.normalize();
sphereGeometry.vertices.push(vertex);
}
...
update():
...
for ( let i = 0; i < maxParticles; i++ ) {
let particle = sphereGeometry.vertices[i];
if ( Date.now() > particle.delay ) {
particle.applyAxisAngle(particle.rotationAxis, 0.01);
}
}
...
I'm using Three.js and I wonder how to get all objects in a given area?
For example, get all objects that found in the green-square:
Solution:
getEntitiesInSelection: function(x, z, width, height, inGroup) {
var self = this,
entitiesMap = [],
color = 0,
colors = [],
ids = [],
pickingGeometry = new THREE.Geometry(),
pickingMaterial = new THREE.MeshBasicMaterial( { vertexColors: THREE.VertexColors } ),
pickingScene = new THREE.Scene(),
pickingTexture = new THREE.WebGLRenderTarget( this._renderer.domElement.width, this._renderer.domElement.height),
cloneMesh,
entities = inGroup ?
engine.getObjectsByGroup(inGroup) : engine.getRegisteredEntities();
pickingTexture.generateMipmaps = false;
//Go over each entity, change its color into its ID
_.forEach(entities, function(entity) {
if(undefined == entity.threeRenderable) {
return ;
}
//Clone entity
cloneMesh = entity.threeRenderable.mesh().clone();
cloneMesh.material = entity.threeRenderable.mesh().material.clone();
cloneMesh.material.map = null;
cloneMesh.material.vertexColors = THREE.VertexColors;
cloneMesh.geometry = entity.threeRenderable.mesh().geometry.clone();
cloneMesh.position.copy( entity.threeRenderable.mesh().position );
cloneMesh.rotation.copy( entity.threeRenderable.mesh().rotation );
cloneMesh.scale.copy( entity.threeRenderable.mesh().scale );
//Cancel shadow
cloneMesh.castShadow = false;
cloneMesh.receiveShadow = false;
//Set color as entity ID
entitiesMap[color] = entity.id();
self._applyVertexColors(cloneMesh.geometry, new THREE.Color( color ) );
color++;
THREE.GeometryUtils.merge( pickingGeometry, cloneMesh);
});
pickingScene.add( new THREE.Mesh( pickingGeometry, pickingMaterial ) );
//render the picking scene off-screen
this._renderer.render(pickingScene, this._objs[this._mainCamera], pickingTexture );
var gl = this._renderer.getContext();
//read the pixel under the mouse from the texture
var pixelBuffer = new Uint8Array( 4 * width * height );
gl.readPixels( x, this._renderer.domElement.height - z, width, height, gl.RGBA, gl.UNSIGNED_BYTE, pixelBuffer );
//Convert RGB in the selected area back to color
for(var i=0; i<pixelBuffer.length; i+=4) {
if( 0 == pixelBuffer[i] && 0 == pixelBuffer[i+1] && 0 == pixelBuffer[i+2] && 0 == pixelBuffer[i+3] ) {
continue;
}
color = ( pixelBuffer[i] << 16 ) | ( pixelBuffer[i+1] << 8 ) | ( pixelBuffer[i+2] );
colors.push(color);
}
colors = _.unique(colors);
//Convert colors to ids
_.forEach(colors, function(color) {
ids.push(entitiesMap[color]);
});
return ids;
}
The line engine.getObjectsByGroup(inGroup) : engine.getRegisteredEntities();
just return an array of entities, which in turn, I iterate over the entities:
_.forEach(entities, function(entity) { ...
Only entities that have the 'threeRenderable' property (object) are visible, therefore, I ignore those that doesn't have it:
if(undefined == entity.threeRenderable) {
return ;
}
then I merge the entity's cloned mesh with with the pickingGeometry:
THREE.GeometryUtils.merge( pickingGeometry, cloneMesh);
eventually, I add the pickingGeometry to the pickingScene:
pickingScene.add( new THREE.Mesh( pickingGeometry, pickingMaterial ) );
Then I read the colors of the selected area, and return an array of IDs.
You can checkout the Node.js game engine I wrote back then.
I've wanted to implement something like this and I choose a very different method - maybe much worse, I don't really know - but much easier to do IMO, so I put it here in case someone wants it.
Basically, I used only 2 raycasts to know the first and last points of the selection rectangle, projected on my ground plane, and iterate over my objects to know which ones are in.
Some very basic code:
function onDocumentMouseDown(event) {
// usual Raycaster stuff ...
// get the ground intersection
var intersects = raycaster.intersectObject(ground);
GlobalGroundSelection = {
screen: { x: event.clientX, y: event.clientY },
ground: intersects[0].point
};
}
function onDocumentMouseUp(event) {
// ends a ground selection
if (GlobalGroundSelection) {
// usual Raycaster stuff ...
// get the ground intersection
var intersects = raycaster.intersectObjects(ground);
var selection = {
begins: GlobalGroundSelection.ground,
ends: intersects[0].point
};
GlobalGroundSelection = null;
selectCharactersInZone(selection.begins, selection.ends);
}
}
function onDocumentMouseMove(event) {
if (GlobalGroundSelection) {
// in a selection, draw a rectangle
var p1 = GlobalGroundSelection.screen,
p2 = { x: event.clientX, y: event.clientY };
/* with these coordinates
left: p1.x > p2.x ? p2.x : p1.x,
top: p1.y > p2.y ? p2.y : p1.y,
width: Math.abs(p1.x - p2.x),
height: Math.abs(p1.y - p2.y)
*/
}
}
Here is my select function:
function selectCharactersInZone (start, end) {
var selected = _.filter( SELECTABLE_OBJECTS , function(object) {
// warning: this ignore the Y elevation value
var itsin = object.position.x > start.x
&& object.position.z > start.z
&& object.position.x < end.x
&& object.position.z < end.z;
return itsin;
});
return selected;
}
Some warnings: as far as I know, this technique is only usable when you don't care about Y positions AND your selection is a basic rectangle.
My 2c