I am developing a small planetary system, in which I have added some rotating and revolving planets around a pivot star.
This is the code which does the above:
var mercury = createMesh(new THREE.SphereGeometry(5.0, 10, 10, 0, Math.PI * 2, 0, Math.PI * 2), "mercury.png");
mercury.position.x = 4;
scene.add(mercury);
var speed = 100;
var distance = 2;
mercury.position.x = Math.sin(step * speed) * distance*5;
mercury.position.y = Math.cos(step * speed) * distance;
mercury.position.z = Math.cos(step * speed) * distance * 5;
Now I want to add a circle for each moving planet that will show the trajectory of the satellite around the pivot planet and add a label on the top of the planet, like a "mercury" label on the aforesaid planet, in my case. How would I do it?
Orbit Path
You can render a circle a couple of different ways. You can use Three.js lines, like in this example.
Or you could render a torus to mark the path.
Text
You can create more geometry as a text, as in this example.
Or you can use sprites with text, talked about in this SO Question.
Pattern
Note that you're hard-coding your planet again, like in your previous question. I recommend that you DRY out your code by creating a function that builds the three.js objects for you. Something with a signature such as:
function createPlanetParent(planetName,planetDiameter,planetRadius,...) {...}
That way you can separate out your data (planet information) from view code (three.js geometry and pivots).
Related
I’ve created a some basic model in Blender. It’s 4 times subdivided cube (I need faces to look like squares), then faces was split by edges (in Blender too). Then I need to separate final mesh by loose parts in threejs (if I do that in Blender the exported file is too big, like a few MB big). So each face become separate one.
How should I do that?
Step 1 (blender)
Step 2 (blender)
After step 2 each face is a separate mesh. I need to replicate step 2 in ThreeJS.
As a result I need to explode faces of a sphere
Here's what I have so far
I'll need much more faces to achieve the desired result. One possible solution would be to place 2 spheres one inside another and then "explode" them simultaneosly. But I need faces to be much smaller too.
My "explosion" code is heavily based on this: https://github.com/akella/ExplodingObjects/blob/0ed8d2668e3fe9913133382bb139c73b9d554494/src/egg.js#L178
And here's demo:
https://tympanus.net/Development/ExplodingObjects/index-heart.html
In your case I would use bufferGeometry.
According to this showcase: https://threejs.org/examples/#webgl_buffergeometry
16000 triangles are generated with normal orientations.
I think you should use BufferGeometry.
Build on top of your codePen,
Here you'll find a solution to have quad faces (instead of your triangles) oriented along a sphere surface.
The core to get the quad faces laying along the surface of a sphere:
for (let down = 0; down < segmentsDown; ++down) {
const v0 = down / segmentsDown;
const v1 = (down + 1) / segmentsDown;
const lat0 = (v0 - 0.5) * Math.PI;
const lat1 = (v1 - 0.5) * Math.PI;
for (let across = 0; across < segmentsAround; ++across) {
//for each quad we randomize the radius
const radius = radiusOfSphere + Math.random()*1.5*radiusOfSphere;
const u0 = across / segmentsAround;
const u1 = (across + 1) / segmentsAround;
const long0 = u0 * Math.PI * 2;
const long1 = u1 * Math.PI * 2;
//for each quad you have 2 triangles
//first triangle of the quad
//getPoint() returns xyz coord in vector3 array with (latitude longitude radius) input
positions.push(...getPoint(lat0, long0, radius));
positions.push(...getPoint(lat1, long0, radius));
positions.push(...getPoint(lat0, long1, radius));
//second triangle of the quad. Order matter for UV mapping,
positions.push(...getPoint(lat1, long0, radius));
positions.push(...getPoint(lat1, long1, radius));
positions.push(...getPoint(lat0, long1, radius));
}
}
https://codepen.io/mquantin/pen/mdqmwMa
I hope this will do the job for you.
I know a method from Unity whichs is very useful to convert a screen position to a world position : https://docs.unity3d.com/ScriptReference/Camera.ScreenToWorldPoint.html
I've been looking for something similar in A-Frame/THREE.js, but I didn't find anything.
Is there an easy way to convert a screen position to a world position in a plane which is positioned a given distance from the camera ?
This is typically done using Raycaster. An equivalent function using three.js would be written like this:
function screenToWorldPoint(screenSpaceCoord, target = new THREE.Vector3()) {
// convert the screen-space coordinates to normalized device coordinates
// (x and y ranging from -1 to 1):
const ndc = new THREE.Vector2()
ndc.x = 2 * screenSpaceCoord.x / screenWidth - 1;
ndc.y = 2 * screenSpaceCoord.y / screenHeight - 1;
// `Raycaster` can be used to convert this into a ray:
const raycaster = new THREE.Raycaster();
raycaster.setFromCamera(ndc, camera);
// finally, apply the distance:
return raycaster.ray.at(screenSpaceCoord.z, target);
}
Note that coordinates in browsers are usually measured from the top/left corner with y pointing downwards. In that case, the NDC calculation should be:
ndc.y = 1 - 2 * screenSpaceCoord.y / screenHeight;
Another note: instead of using a set distance in screenSpaceCoord.z you could also let three.js compute an intersection with any Object in your scene. For that you can use raycaster.intersectObject() and get a precise depth for the point of intersection with that object. See the documentation and various examples linked here: https://threejs.org/docs/#api/core/Raycaster
I want to update hud positon form 3d position to 2d when mouse moving. Since it may have a large number of 3d objects to project to the screen position, I meet a performance problem.
Are there any way to accelerate calculations? The following is how I calculate 3d object position on 2d screen.
function toScreenPosition(obj) {
var vector = new THREE.Vector3();
//calculate screen half size
var widthHalf = 0.5 * renderer.context.canvas.width;
var heightHalf = 0.5 * renderer.context.canvas.height;
//get 3d object position
obj.updateMatrixWorld();
vector.setFromMatrixPosition(obj.matrixWorld);
vector.project(this.camera);
//get 2d position on screen
vector.x = (vector.x * widthHalf) + widthHalf;
vector.y = -(vector.y * heightHalf) + heightHalf;
return {
x: vector.x,
y: vector.y
};
}
Rather than repositioning your HUD in world space every time your camera moves, add your HUD object(s) to your camera object, and position them only once. Then, when your camera moves, your HUD moves along with it, because the camera's transformation is cascaded to it's children.
yourCamera.add(yourHUD);
yourHUD.position.z = 10;
Note that doing it this way (or even positioning it the way you were) may allow scene objects to clip through your HUD geometry, or even appear between your HUD and the camera, obscuring the HUD. If that's what you want, great! If not, you could move your HUD to a second render pass, allowing it to remain "on top."
First, here is an example of your function rewritten for (almost) optimal performance as written in the comments above, the renderloop is obviously just an example to illustrate where to do which calls:
var width = renderer.context.canvas.width;
var height = renderer.context.canvas.height;
// has to be called whenever the canvas-size changes
function onCanvasResize() {
width = renderer.context.canvas.width;
height = renderer.context.canvas.height;
});
var projMatrix = new THREE.Matrix4();
// renderloop-function, called per animation-frame
function render() {
// just needed once per frame (even better would be
// once per camera-movement)
projMatrix.multiplyMatrices(
camera.projectionMatrix,
projMatrix.getInverse(camera.matrixWorld)
);
hudObjects.forEach(function(obj) {
toScreenPosition(obj, projMatrix);
});
}
// wrapped in IIFE to store the local vector-variable (this pattern
// is used everywhere in three.js)
var toScreenPosition = (function() {
var vector = new THREE.Vector3();
return function __toScreenPosition(obj, projectionMatrix) {
// this could potentially be left away, but isn't too
// expensive as there are 'needsUpdate'-checks in place
obj.updateMatrixWorld();
vector.setFromMatrixPosition(obj.matrixWorld);
vector.applyMatrix4(projectionMatrix);
vector.x = (vector.x + 1) * width / 2;
vector.y = (1 - vector.y) * height / 2;
// might want to consider returning a Vector3-instance
// instead, depends on how the result is used
return {x: vector.x, y: vector.y};
}
}) ();
But, considering you want to render a HUD, it would be better to do that independently of the main-scene, making all of the above computations obsolete and also allowing you to choose a different coordinate-system for sizing and positioning of HUD-elements.
I have an example for this here: https://codepen.io/usefulthink/pen/ZKPvPB. There I used an orthographic camera and a seperate scene to render HUD-Elements on top of the 3d-scene. No extra computations required. Plus I can specify the size and position of HUD-elements conveniently in pixel-units (The same would work using a perspective camera, only requires a bit more trigonometry to get it right).
Coming from this question I'm trying to generate UV Mappings programmatically with Three.js for some models, I need this because my models are being generated programmatically too and I need to apply a simple texture to them. I have read here and successfully generated UV mapping for some simple 3D text but when applying the same mapping to more complex models it just doesn't work.
The texture I'm trying to apply is something like this:
The black background it's just transparent in the PNG image. I need to apply this to my models, it's just a glitter effect so I don't care about the exact position in the model, is any way to create a simple UV Map programatically for this cases?
I'm using this code from the linked question which works great for planar models but doesn't work for non-planar models:
assignUVs = function( geometry ){
geometry.computeBoundingBox();
var max = geometry.boundingBox.max;
var min = geometry.boundingBox.min;
var offset = new THREE.Vector2(0 - min.x, 0 - min.y);
var range = new THREE.Vector2(max.x - min.x, max.y - min.y);
geometry.faceVertexUvs[0] = [];
var faces = geometry.faces;
for (i = 0; i < geometry.faces.length ; i++) {
var v1 = geometry.vertices[faces[i].a];
var v2 = geometry.vertices[faces[i].b];
var v3 = geometry.vertices[faces[i].c];
geometry.faceVertexUvs[0].push([
new THREE.Vector2( ( v1.x + offset.x ) / range.x , ( v1.y + offset.y ) / range.y ),
new THREE.Vector2( ( v2.x + offset.x ) / range.x , ( v2.y + offset.y ) / range.y ),
new THREE.Vector2( ( v3.x + offset.x ) / range.x , ( v3.y + offset.y ) / range.y )
]);
}
geometry.uvsNeedUpdate = true;
}
You need to be more specific. Here, I'll apply UV mapping programmatically
for (i = 0; i < geometry.faces.length ; i++) {
geometry.faceVertexUvs[0].push([
new THREE.Vector2( 0, 0 ),
new THREE.Vector2( 0, 0 ),
new THREE.Vector2( 0, 0 ),
]);
}
Happy?
There are an infinite ways of applying UV coordinates. How about this
for (i = 0; i < geometry.faces.length ; i++) {
geometry.faceVertexUvs[0].push([
new THREE.Vector2( Math.random(), Math.random() ),
new THREE.Vector2( Math.random(), Math.random() ),
new THREE.Vector2( Math.random(), Math.random() ),
]);
}
There's no RIGHT answer. There's just whatever you want to do is up to you. It's kind of like asking how do I apply pencil to paper.
Sorry to be so snarky, just pointing out the question is in one sense nonsensical.
Anyway, there are a few common methods for applying a texture.
Spherical mapping
Imagine your model is translucent, there's a sphere inside made of film and inside the sphere is a point light so that it projects (like a movie projector) from the sphere in all directions. So you do the math to computer the correct UVs for that situation
To get a point on there sphere multiply your points by the inverse of the world matrix for the sphere then normalize the result. After that though there's still the problem of how the texture itself is mapped to the imaginary sphere for which again there are an infinite number of ways.
The simplest way is I guess called mercator projection which is how most 2d maps of the world work. they have the problem that lots of space is wasted at the north and south poles. Assuming x,y,z are the normalized coordinates mentioned in the previous paragraph then
U = Math.atan2(z, x) / Math.PI * 0.5 - 0.5;
V = 0.5 - Math.asin(y) / Math.PI;
Projection Mapping
This is just like a movie. You have a 2d image being projected from a point. Imagine you pointed a movie projector (or a projection TV) at a chair. Compute those points
Computing these points is exactly like computing the 2D image from 3D data that nearly all WebGL apps do. Usually they have a line in their vertex shader like this
gl_Position = matrix * position;
Where matrix = worldViewProjection. You can then do
clipSpace = gl_Position.xy / gl_Position.w
You now have x,y values that go from -1 to +1. You then convert them
to 0 to 1 for UV coords
uv = clipSpace * 0.5 + 0.5;
Of course normally you'd compute UV coordinates at init time in JavaScript but the concept is the same.
Planar Mapping
This is the almost the same as projection mapping except imagine the projector, instead of being a point, is the same size as you want to project it. In other words, with projection mapping as you move your model closer to the projector the picture being projected will get smaller but with planar it won't.
Following the projection mapping example the only difference here is using an orthographic projection instead of a perspective projection.
Cube Mapping?
This is effectively planar mapping from 6 directions. It's up to you
to decide which UV coordinates get which of the 6 planes. I'd guess
most of the time you'd take the normal of the triangle to see which
plane it most faces, then do planar mapping from that plane.
Actually I might be getting my terms mixed up. You can also do
real cube mapping where you have a cube texture but that requires
U,V,W instead of just U,V. For that it's the same as the sphere
example except you just use the normalized coordinates directly as
U,V,W.
Cylindrical mapping
This is like sphere mapping except assume there's tiny cylinder projecting on to your model. Unlike a sphere a cylinder has orientation but basically you can move the points of the model into the orientation of the cylinder then assuming x,y,z are now relative to the cylinder (in other words you multiplied them by the inverse matrix of the matrix that represents the orientation of the cylinder), then .
U = Math.atan2(x, z) / Math.PI * 0.5 + 0.5
V = y
2 more solutions
Maybe you want Environment Mapping?
Here's 1 example and Here's another.
Maybe you should consider using a modeling package like Maya or Blender that have UV editors and UV projectors built in.
I have a problem about getting the mouse coordinates, it behaves irrelevant after zooming.
I have a JS fiddle link of my code, it will show what the problem I face, is it bug in three.js or the way I approach to draw a line is wrong, please give your feedback.
http://jsfiddle.net/ebeit303/ceej4jxq/1/
var elem = self.renderer.domElement,
boundingRect = elem.getBoundingClientRect(),
x = (e.clientX - boundingRect.left) * (elem.width / boundingRect.width),
y = (e.clientY - boundingRect.top) * (elem.height / boundingRect.height);
var vector = new THREE.Vector3((x / $("container").width()) * 2 - 1, -(y / $("container").height()) * 2 + 1, 0.5);
var pos = projector.unprojectVector(vector, camera);
var dir = pos.clone().sub(camera.position).normalize().multiplyScalar(-1);
var distance = camera.position.z / dir.z;
var pos1 = camera.position.clone().sub(dir.multiplyScalar(distance));
Thanks in advance..
Your camera near plane in your fiddle is 0.0001, and your camera far plane is 10,000,000,000.
Consequently, you are having numerical problems in your code when you call unprojectVector().
The issue is closely related to the depth buffer precision problems described here: http://www.opengl.org/wiki/Depth_Buffer_Precision.
Set your near plane to 1, or greater, and your far plane to the smallest value you can get away with, say 10000.
three.js r.68