As documentation of Three.js said, after changing the position of the camera, we must call the updateProjectionMatrix() method.
I'm doing the same. But it gives me an error like this:
TypeError: object.updateWorldMatrix is not a function
at Box3.expandByObject (three.module.js:6329)
at Box3.setFromObject (three.module.js:6233)
at index.js:66
at GLTFLoader.js:147
at GLTFLoader.js:1639
My goal is to put my loaded GLTF object, in the center of the screen.
And this is the code I'm using for it:
this.gltfLoader.load("/corolla.gltf", (object) => {
const box = new THREE.Box3().setFromObject(object);
const size = box.getSize(new THREE.Vector3()).length();
const center = box.getCenter(new THREE.Vector3());
// reset OrbitControl
this.controls.reset();
object.position.x += (object.position.x - center.x);
object.position.y += (object.position.y - center.y);
object.position.z += (object.position.z - center.z);
this.controls.maxDistance = size * 10;
this.camera.near = size / 100;
this.camera.far = size * 100;
this.camera.updateProjectionMatrix();
this.camera.position.copy(center);
this.camera.position.x += size / 2.0;
this.camera.position.y += size / 5.0;
this.camera.position.z += size / 2.0;
this.camera.lookAt(center);
this.gltf = object.scene;
this.scene.add(this.gltf);
},
this.manageLoading,
this.gltfLoadErr);
For the record, I'm using the last version of Three.js, it's 0.110.0;
It looks like the errors is occurring here:
const box = new THREE.Box3().setFromObject(object);
Where object is the object coming back from the call to GLTFLoader.load.
According to the docs, object is not an Object3D (which is what Box3.setFromObject expects).
Instead, object is a JSON structure containing information about the GLTF data. The three.js example suggests object.scene would be a renderable entity (if it exists).
Take a look at the code for the GLTFLoader example, here. Compare it against your implementation. You can also debug your code to see exactly what is object contains. Once you have a handle on that, if you're still having problems, come back and ask more questions!
Short solution:
var hollowCylinderGeom = new THREE.LatheBufferGeometry([
new THREE.Vector2(1, 0),
new THREE.Vector2(2, 0),
new THREE.Vector2(2, 2),
new THREE.Vector2(1, 2),
new THREE.Vector2(1, 0)
], 90).toNonIndexed();
var mesh = new THREE.Mesh(hollowCylinderGeom);
console.log(new THREE.Box3().setFromObject(mesh));
Related
Im trying to synchronize Potree camera with Cesium camera, like in this briliant example, but in oposite way (moves of Cesium camera control Potree camera).
function syncPotreeWithCesium() {
//lets get camera position and direction in Cesium
var newCamCoordinatesWC = cesiumViewer.scene.camera.positionWC;
var newDirectionCoordinatesWC = cesiumViewer.scene.camera.directionWC;
//as potree works in declaredEPSG, lets transform Cesium WC to declaredEPSG
var positionCartographicWC = Cesium.Ellipsoid.WGS84.cartesianToCartographic(newCamCoordinatesWC);
var directionCartographicWC = Cesium.Ellipsoid.WGS84.cartesianToCartographic(newDirectionCoordinatesWC);
var camPosXY = proj4(declaredEPSG,[(positionCartographicWC.longitude * (180/Math.PI)),(positionCartographicWC.latitude * (180/Math.PI))]);
var camDirXY = proj4(declaredEPSG,[(directionCartographicWC.longitude * (180/Math.PI)),(directionCartographicWC.latitude * (180/Math.PI))]);
//set Potree camera coordinates and direciton
potreeViewer.scene.view.position.set(camPosXY[0], camPosXY[1], positionCartographicWC.height);
potreeViewer.scene.view.lookAt(camDirXY[0], camDirXY[1], directionCartographicWC.height);
//"aspect" part from mentioned example seems to be applicable almost 1:1
var cesiumAspect = cesiumViewer.scene.camera.aspectRatio;
if(cesiumAspect < 1){
let fovy = Math.PI * (cesiumViewer.camera.frustum.fov / 180);
potreeViewer.scene.getActiveCamera().fov = fovy;
}else{
let fovy = Math.PI * (cesiumViewer.camera.frustum.fov / 180);
let fovx = Math.atan(Math.tan(0.5 * fovy) * cesiumAspect) * 2
potreeViewer.scene.getActiveCamera().fov = fovx;
}
//not sure if this part is needed in this simplified example
let camera = potreeViewer.scene.getActiveCamera();
camera.updateProjectionMatrix();
}
cesiumViewer.camera.changed.addEventListener(syncPotreeWithCesium);
`
As a result point cloud "dances" around desirable place in closeup, and gets even worse when zoomed out.
Except position and direction Cesium also needs „up” vector to set camera with this method, like here:
cesiumViewer.camera.setView({
destination : cameraPositon,
orientation : {
direction : dirVector,
up : upVector
}
And I do not see such setting in Potree camera/view settings. Is that causing a problem ?
Or am I missing more pieces to do it properly ??? Any help would be appreciated.
I have a basic three.js game working and I'd like to add particles. I've been searching online, including multiple questions here, and the closest I've come to getting a 'particle system' working is using a THREE.BufferGeometry, a THREE.BufferAttribute and a THREE.Points mesh. I set it up like this:
const particleMaterial = new THREE.PointsMaterial( { size: 10, map: particleTexture, blending: THREE.AdditiveBlending, transparent: true } );
const particlesGeometry = new THREE.BufferGeometry;
const particlesCount = 300;
const posArray = new Float32Array(particlesCount * 3);
for (let i = 0; i < particlesCount; i++) {
posArray[i] = Math.random() * 10;
}
const particleBufferAttribute = new THREE.BufferAttribute(posArray, 3);
particlesGeometry.setAttribute( 'position', particleBufferAttribute );
const particlesMesh = new THREE.Points(particlesGeometry, particleMaterial);
particlesMesh.counter = 0;
scene.add(particlesMesh);
This part works and displays the particles fine, at their initial positions, but of course I'd like to move them.
I have tried all manner of things, in my 'animate' function, but I am not happening upon the right combination. I'd like to move particles, ideally one vertex per frame.
The current thing I'm doing in the animate function - which does not work! - is this:
particleBufferAttribute.setXYZ( particlesMesh.counter, objects[0].position.x, objects[0].position.y, objects[0].position.z );
particlesGeometry.setAttribute( 'position', particleBufferAttribute );
//posArray[particlesMesh.counter] = objects[0].position;
particlesMesh.counter ++;
if (particlesMesh.counter > particlesCount) {
particlesMesh.counter = 0;
}
If anyone has any pointers about how to move Points mesh vertices, that would be great.
Alternatively, if this is not at all the right approach, please let me know.
I did find Stemkoski's ShaderParticleEngine, but I could not find any information about how to make it work (the docs are very minimal and do not seem to include examples).
You don't need to re-set the attribute, but you do need to tell the renderer that the attribute has changed.
particleBufferAttribute.setXYZ( particlesMesh.counter, objects[0].position.x, objects[0].position.y, objects[0].position.z );
particleBufferAttribute.needsUpdate = true; // This is the kicker!
By setting needsUpdate to true, the renderer knows to re-upload that attribute to the GPU.
This might not be concern for you, but just know that moving particles in this way is expensive, because you re-upload the position attribute every single frame, which includes all the position data for every particle you aren't moving.
I'm wondering how I would go about calling the individual children of cube (mesh0, mesh1) so I'm able to set different styles / animations to them both.
AFRAME.registerComponent('multi_box', {
schema: {},
update: function() {
for (var i = 0; i < 2; i++) {
var material = new THREE.MeshBasicMaterial({color: "blue"});
var geometry = new THREE.BoxGeometry(1, 1, 1);
var cube = new THREE.Mesh(geometry, material);
cube.position.x = i == 0 ? -1 : 1;
cube.position.y = 0.5;
cube.position.z = -5;
this.el.setObject3D("mesh"+i, cube); //unique name for each object
}
console.log(this.el.object3DMap)
}
});
Codepen link: https://codepen.io/ubermario/pen/wrwjVG
I can console.log them both and see that they are unique objects to each other but I'm having trouble calling them:
var meshtest = this.el.getObject3D('mesh0')
console.log(meshtest.position)
I'v tried this method but with no luck: aframe get object3d children
Any help is appreciated :)
Instancing
In your for cycle, you create
a new geometry instance
a new material instance
a new mesh that connect the previous two
Each new keyword creates a unique instance that is independent of the others. In your case mesh0 and mesh1 are fully independent of each other. If you change any property of an instance, like for example material color or position, the other(s) will not be affected by that. In fact you do that by assigning a different x position to each cube.
Storage
Your component holds a map of 3D objects. Each is identified by a unique name. You generate this name by concatenating the prefix mesh with the iteration number (value of i).
You can later access the cubes just the same way you created them. Either by name
this.el.getObject3D('mesh0')
this.el.getObject3D('mesh1')
//etc.
or by index
this.el.object3D.children[0]
this.el.object3D.children[1]
and you can manipulate them further. For example you can put his on Ln19 in your Codepen:
this.el.getObject3D('mesh0').position.y = 2;
this.el.object3D.children[1].position.z = -3;
Just for completeness: If you would omit the +i at the end, you would overwrite the same key again and again, so mesh would reference just the last cube and others would get lost.
Changing properties
Three.js has a nice API, but in javascript you always need to think what happens behind the scenes. You will see that while learning new stuff. For example:
this.el.object3D.children[1].position.z = -3;
this.el.object3D.children[1].material.color.set('#ffff00');
this.el.object3D.children[1].material.color = [1, 1, 0];
As you can see the position can be changed directly, but color needs a setter sometimes. Things get more complicated with vectors where you need to watch which methods change the current instance and which produce a new one. You could easily forget to clone it. Say you had:
var pos = new THREE.Vector3(-1, 0.5, -5)
for (var i = 0; i < 2; i++) {
var material = new THREE.MeshBasicMaterial({color: "blue"});
var geometry = new THREE.BoxGeometry(1, 1, 1);
var cube = new THREE.Mesh(geometry, material);
//wrong, since it will assign the object reference,
//i.e. always the same object.
//Thus, the cubes will be at one position in the end
cube.position = pos;
//right
cube.pos = pos.clone();
pos.x += 1;
this.el.setObject3D("mesh"+i, cube); //unique name for each object
}
The difference is a bit more moderate topic on reference and value types. You will find many tutorials for that, for example this small gist I just found.
I hope this was helpful and wish you good progress in learning!
I'm working on a scene with three.js and imported .gtlf models via THREE.GLTFLoader(). I want to attach attributes to the objects and later work with those whenever an event occurs regarding those objects. But my problem is that the returned intersections by the raycaster do not carry the attributes added to the objects. Furthermore, I cannot identify which object was returned by the raycaster (for example by a obj.name attribute I attached beforehand, since the returned intersections are not 3DObjects anymore as they are when added to the scene).
How can I precisely identify which (gltf) object was returned by the raycaster?
For gemoetry objects it works perfectly fine and attributes are attached when returned from the raycaster, but for the loaded models (gltf) it's not working, even after using the "userData" attribute that aims for the purpose of carrying information, the attribute is empty afterwards.
gltfLoader.load('./assets/simple_shelf/scene.gltf', (gltf) => {
shelf = gltf.scene.children[0];
shelf.userData.direction = "test string";
scene.add(shelf);
});
//console.log(shelf.userData.direction) shows it correctly, but returned
//by raycaster on events (e.g. mousemove), userData is empty
function onMouseMove(event) {
event.preventDefault();
var rect = renderer.domElement.getBoundingClientRect();
mouse.x = ( ( event.clientX - rect.left ) / ( rect.width - rect.left ) ) * 2 - 1;
mouse.y = - ( ( event.clientY - rect.top ) / ( rect.bottom - rect.top) ) * 2 + 1;
raycaster.setFromCamera(mouse,camera);
var intersects = raycaster.intersectObjects(scene.children, true);
for (let i = 0; i < intersects.length; i++){
console.log(intersects[i]);
}
}
window.addEventListener('mousemove', onMouseMove);
I want to somehow exactly identify the object the raycaster returns, i.e. trigger the events. Hence I want to add attributes and retrieve them on event occurance.
What am I doing wrong here?
Considering a scene and 3 BoxBufferGeometries, I don't want to re-render the scene (drawObjects is called once, the 'first' time), but rather update it (updateScene is called on user action for example), the following code sucessfully updates the color and texture material attributes for all the objects of the scene, but only seems to affect one of the 3 objects when it comes to mesh.geometry transformation (one becomes as sphere, the 2 other remains squares), why? (live example)
drawObjects = (options) ->
map = (new (THREE.TextureLoader)).load(options.texture)
map.wrapS = map.wrapT = THREE.RepeatWrapping
map.anisotropy = 16
material = new (THREE.MeshToonMaterial)(
color: options.color
map: map
transparent: true
side: THREE.DoubleSide)
geometry = new THREE.BoxBufferGeometry( 20, 20, 20 )
i = 0
while i < 3
object = new (THREE.Mesh)(geometry, material, (color: Math.random() * 0xffffff))
object.position.x = Math.random() * 800 - 400
object.position.y = Math.random() * 800 - 400
object.position.z = Math.random() * 800 - 400
object.setGeometry = ->
geometry = new THREE.SphereGeometry(20)
object.geometry = geometry
return
object.setTexture = (texture) ->
object.material.map = new THREE.TextureLoader().load( texture )
return
object.setColor = (color) ->
object.material.color = new THREE.Color(color)
return
scene.add object
i++
updateScene = (options) ->
#scene.traverse (object) ->
if object.isMesh == true
object.setColor(options.color)
object.setTexture(options.texture)
object.setGeometry()
return
I tried few other approches aswell, the first was about updating vertices (like indicated here) but it doesn't work since BoxGeometry do no have vertices. Then I tied to add a few options such as, but without success.
# object.geometry.dynamic = true
# object.geometry.buffersNeedUpdate = true
# object.updateMatrix()
The second one was using object dispose basically making a clone of the original, like advised here dispose the original mesh and replace it with its clone such as but it didn't work in my case since I have functions attached to this object I'll need to re-use. Maybe I did it wrong:
object.geometry.dispose();
object.geometry = geometry.clone();
I bet there is a way to achieve this kind mesh.geometry transformation and apply it to every object while traversing the scene, but can't find how.
Kind regards
Ended up doing it with a function out of the object itself:
setGeometries = (geometry) ->
#scene.traverse (object) ->
if object
object.geometry = geometry
object.geometry.buffersNeedUpdate = true
return
return
return
Where geometry is something like new THREE.[Primitive]BuffferGeometry. Works for me.