I am trying to model a galaxy that displays some stars. The planets are children of the star mesh.
When you zoom, the star mesh gets bigger but I want at a certain zoom level, that the star remains the same size and only the planets increase in reference to the zoom scale.
This code is working just fine, but I am wondering if this is the best way and maybe there is another, better way to achieve this:
var scale = instance.controls.scale;
obj.scale.set(obj.scale.x*scale,obj.scale.y*scale,obj.scale.z*scale);
for (var c = 0; c < obj.children.length; c++) {
var child = obj.children[c];
child.scale.set(child.scale.x/scale,child.scale.y/scale,child.scale.z/scale);
}
The scale variable comes from the camera control that informs me how much zoom is applied. Zoom in will result in scale > 1 and scale out will result in scale < 1
In order to keep my star the same size if I zoom, I have to multiply it by the scale factor but because my planets are children of the star, I need to negate the scaling by doing the opposite.
Add an extra container between the parent object and the children, like so:
var container = new THREE.Group();
obj.add( container );
containter.add( child );
Then,
obj.scale.multiplyScalar( scale );
container.scale.divideScalar( scale );
three.js r.71
Related
after loading several gltf files, I am renaming these files and try to reposition the camera so that it is centered and looking at the centroid of the new objects and the whole scene fits within the camera.
But the centering does not always work, sometimes the centroid is calculated somewhere completely different. The following code is ran in render() only once after all objects have been loaded:
var all_centers = [];
scene.updateMatrixWorld();
scene.traverse(function(child){
if (child instanceof THREE.Mesh){
if (child.name.indexOf("_") !== -1){ // the newly imported objects
child.geometry.computeBoundingSphere();
var the_center = new THREE.Vector3();
child.getWorldPosition(the_center);
all_centers.push(the_center);
}
}
});
var the_centroid = getPointsCentroid(all_centers);
var cameraPosition = new THREE.Vector3(the_centroid.x,the_centroid.y,-55);
camera.position.copy(cameraPosition);
camera.lookAt(the_centroid);
and here is the function for the centroid:
function getPointsCentroid(points){
var centroid = [0.,0.,0.];
for(var i = 0; i < points.length; i++) {
var point = points[i];
centroid[0] += point.x;
centroid[1] += point.y;
centroid[2] += point.z;
}
centroid[0] /= points.length;
centroid[1] /= points.length;
centroid[2] /= points.length;
return new THREE.Vector3(centroid[0],centroid[1],centroid[2]);
}
For now ignoring the problem of getting the whole scene to fit within the camera (this is a common problem, I'm sure you can find useful information online (perhaps this?)).
Instead, let us focus on what seems to be your main question: You wish to find the center of a group of objects. What you are currently doing is you are computing an average of the object centers. This means that if you have one object to the far left, and 9 objects to the far right, your computed center point will also be far to the right. (This would be an approximation of the center of mass, assuming the objects are of similar mass.)
However, for the purpose of centering the camera so that every object is visible, you are not interested in the center of mass, but you wish to find a point such that the distance to the leftmost point is equal to the distance to the rightmost, and similarly the lowermost to the highermost, etc. Such a point can be found using the bounding box of all your objects. The center of this bounding box is the point you are looking for.
If your camera is to be aligned to the axes, you can easily compute such a bounding box for each object as follows:
new THREE.Box3().setFromObject(myMesh)
The bounding box of all the objects is simply the box represented by the lowest and highest coordinates of all the object bounding boxes you computed. The center of the complete bounding box will give you the point you are after.
Next, assuming the camera is aligned with the axes, the problem is simply finding a suitable distance from the camera to this point so that the entire bounding box fits inside the viewport.
For example, I have a unit cube. When there is no scaling, I want to get one coordinate unit represented by how many screen pixels.
When I zoom in, then this one coordinate unit is represented by how many screen pixels?
I'm not entirely sure if i understood your question, but to get any distance between any two THREE vectors you do this:
const distanceAB = new THREE.Vector3(1,1,1).sub( new THREE.Vector3(2,2,2) ).length()
Your unit cube would have vertices like (-1,-1,-1) and (1,1,1) among others, (or actually 0.5). Either way you need to obtain these values (easier when using THREE.Geometry than BufferGeometry).
Then you project these vertices
const vertexA = new THREE.Vector3() // set this from cube
const vertexB = new THREE.Vector3()
const screenSpaceVector = new THREE.Vector3().subVectors(vertexA.project(myCamera),vertexB.project(myCamera))
The result is now in something called NDC which is a cube going from -1 to 1. To normalize it:
screenSpaceVector.multiplyScalar(0.5).add(new THREE.Vector3(0.5,0.5,0.5))
Finally to figure out how many pixels this is
screenSpaceVector.x *= renderer.getSize().width
screenSpaceVector.y *= renderer.getSize().height
screenSpaceVector.z = 0
const pixelLength = screenSpaceVector.length()
I think should do the trick
I will resite a collada model to a user given size...
Maybe with patterns, maybe with scalling only.
My actual idea is to implement a bounding box with the given sizes. And then scale the collada until it hits one side of the bounding box.
But maybe there are better solutions?
You want to resize a model to a given size. The model may have child meshes.
One solution is to determine the model's bounding box.
var box = new THREE.Box3().setFromObject( model );
var boxCenter = box.center();
var boxSize = box.size();
Then, depending on your criteria, you can reset the model's scale and position:
model.position.set( x, y, z );
model.scale.set( s, s, s ); // same value for each component to prevent distortion
How you determine the new position and scale is up to you.
three.js r.71
Alpha invisibility.
I currently define circular regions on some images as "hot spots". For instance, I could have my photo on screen and overlay a circle on my head. To check for interaction with my head in realtime, I would returnOverlaps and do some manipulation on all objects overlapping the circle. For debugging, I make the circle yellow with alpha 0.5, and for release I decrease alpha to 0, making the circle invisible (as it should be).
Does this slow down the program? Is there another way to make the circle itself invisible while still remaining capable of interaction? Is there some way to color it "invisible" without using a (potentially) costly alpha of 0? Cache as bitmap matrix? Or some other efficient way to solve the "hot spot" detection without using masks?
Having just a few invisible display objects should not slow it down that much, but having many could. I think a more cleaner option may be to just handle it all in code, rather then have actual invisible display objects on the stage.
For a circle, you would define the center point and radius. Then to get if anyone clicked on it, you could go:
var xDist:Number = circle.x - mousePoint.x;
var yDist:Number = circle.y - mousePoint.y;
if((xDist * xDist) + (yDist * yDist) <= (circle.radius * circle.radius)){
// mousePoint is within circle
} else {
// mousePoint is outside of circle
}
If you insist on using display objects to set these circular hit areas (sometimes it can be easier visually, then by numbers), you could also write some code to read those display objects (and remove them from being rendered) in to get their positions and radius size.
added method:
// inputX and inputY are the hotspot's x and y positions, and inputRadius is the radius of the hotspot
function hitTestObj(inputA:DisplayObject, inputX:int, inputY:int, inputRadius:int):Boolean {
var xDist:Number = inputX - inputA.x;
var yDist:Number = inputY - inputA.y;
var minDist:Number = inputRadius + (inputA.width / 2);
return (((xDist * xDist) + (yDist * yDist)) =< (minDist * minDist))
}
An alpha=0 isn't all that costly in terms of rendering as Flash player will optimize for that (check here for actual figures). Bitmap caching wouldn't be of any help as the sprite is invisible. There's other ways to perform collision detection by doing the math yourself (more relevant in games with tens or even hundreds of sprites) but that would be an overkill in your case.
I have one outer canvas inside which I am loading another canvas (large size). I have set the clip geometry so only a part of inner (large) canvas is visible on screen.
Since the inner canvas is large in size so I have kept the initial scale of inner canvas as 0.4 i.e. I have applied composite transform on inner canvas and made scaleX and scaleY as 0.4.
Now I have implemented gesture listener methods OnPinchStart and OnPinchDelta. In OnPinchDelta I am zooming the canvas.
The problem is that since the initial scale is 0.4 so until the scale reaches (or scale crosses 1), the canvas is not zooming from center means it's position gets changed. However as soon as scale factor reaches (or crosses) 1, the zooming of canvas starts properly.
In short when the scale factor is less than 1 the zooming is not happening from center or other way, canvas position does not remain proper.
I have tried many different approaches but scale factor less than 1 is not working properly for me. Any help.
var factor = // calculate this based on amount of data and the speed
var width = // element rendered width
var height = // element rendered height
var changeInWidth = (1-factor) * width; // can be 0, neg or pos
var changeInHeight = (1-factor) * height;
var changeInX = changeInWidth / 2;
var changeInY = changeInHeight / 2;
Canvas.SetLeft(element, Canvas.GetLeft(element) + changeInX);
Canvas.SetTop(element, Canvas.GetTop(element) + changeInY);
Just make sure your element is wrapped by a Canvas, otherwise the Left and Top would be ignored.
In order to calculate factor, you can use e.DistanceRatio;
It can be used itself as factor, but you can multiply it by a constant if you want to increase(e.g. 1.2) or decrease (0.8) the speed of zooming.