I'm creating a web-application that's going to display 3D objects in a canvas. Now I came across this problem:
I am slowly rotating the camera around the scene so the 3D object can be looked at from all sides. For this I use this code (JavaScript):
var step = 0.1*Math.PI/180;
scene.camera.position.x = Math.cos(step) * (scene.camera.position.x - 0) - Math.sin(step) * (scene.camera.position.z - 0) + 0;
scene.camera.position.z = Math.sin(step) * (scene.camera.position.x - 0) + Math.cos(step) * (scene.camera.position.z - 0) + 0;
Those zeroes are the center of the scene, I leave them there in case we decide to use another base-origin.
This code will make the camera rotate around point 0,0, but it slowly gets closer and closer to it. Here are some screenshots to show you what it does:
There are no other parameters that have impact on the camera's position. I don't understand why it's doing this and what the problem could be.
I found what was causing this issue: I change the camera's X position, then I change the camera's Z position with the new value of it's X position. Because this will be different the origin no longer is relatively at the same position for both calculations.
This was easy to fix, just by storing them into two new variables and then assigning them
var posx = Math.cos(step) * (scene.camera.position.x - 0) - Math.sin(step) * (scene.camera.position.z - 0) + 0;
var posz = Math.sin(step) * (scene.camera.position.x - 0) + Math.cos(step) * (scene.camera.position.z - 0) + 0;
scene.camera.position.x = posx;
scene.camera.position.z = posz;
Related
My Three.js project uses and OrthographicCamera and OrthographicTrackBallControls for zoom/pan. I'm trying to add functionality to zoom to the cursor position with no luck. First things first, here's how I'm getting mouse position:
var mX = ((event.clientX - offset.left) / renderer.domElement.clientWidth) * 2 - 1;
var mY = -((event.clientY - offset.top) / renderer.domElement.clientHeight) * 2 + 1;
var vector = new THREE.Vector3(mX, mY, 0.5);
vector.unproject(camera);
vector.sub(camera.position);
Through looking on StackOverflow, there seems to be a lot of information on how to do this with PerspectiveCamera, but these methods don't work with OrthographicCamera. I was able to find this example:
https://htmlpreview.github.io/?https://github.com/w3dot0/three.js/blob/973bf1d40ef552dbf19c19654a79f70e2882563d/examples/misc_controls_zoom_to_mouse.html
Which does precisely what I am trying to accomplish, but the code that achieves this is hidden, though I am able to discern that the camera position is being changed.
Another SO question which is similar suggests changing camera.left, camera.right, camera.top and camera.bottom, but I have had no luck with this approach. This approach seems like a possibility, but I dont understand the calculations necessary to get the correct left, right, top and bottom values.
So the way I see it I have two possibilities:
Change camera's left/right/top/bottom to get the correct view rectangle.
Change camera position.
But I don't know how to get the values I need to accomplish either, let alone which is the better approach.
UPDATE 11/16/2018:
I've updated my function to this ( based on https://github.com/w3dot0/three.js/blob/973bf1d40ef552dbf19c19654a79f70e2882563d/examples/misc_controls_zoom_to_mouse.html):
zoomDirection = new THREE.Vector3();
function mousewheel(event) {
event.preventDefault();
var amount = event.deltaY / 100;
var zoom = camera.zoom - amount;
var offset = el.offset();
;
var mX = amount > 0 ? 0 : ((event.clientX - offset.left) / renderer.domElement.clientWidth) * 2 - 1;
var mY = amount > 0 ? 0 : -((event.clientY - offset.top) / renderer.domElement.clientHeight) * 2 + 1;
zoomDirection.set(mX, mY, 0.001)
.unproject(camera)
.sub(camera.position)
.multiplyScalar(amount / zoom);
camera.position.subVectors(camera.position, zoomDirection);
orthographictrackBallControls.target.subVectors(orthographictrackBallControls.target, webGl.zoomDirection);
camera.zoom = zoom;
camera.updateProjectionMatrix();
}
This seems to work at first: the camera zooms into the mouse point, but then the camera starts to "jump" around after a bit of zooming, with the mesh no longer visible on screen.
Something that might help: I have an axis helper in the screen as well that "flips" when it stops working as expected. When the scene is loaded, the X-axis helper point due left, but when I get to the point where the camera jumps and I no longer see the mesh, the X-axis helper flips to point due right.
Also, if I zoom OUT first, I can zoom in further before the mesh disappears. I'm not sure what this all adds up to but I would appreciate any help.
First week back after New Year and it's taken too long to fix this. Six sides of A4 covered with linear algebra results in
if ( factor !== 1.0 && factor > 0.0 ) {
const mX = (event.offsetX / event.target.width ) * 2 - 1;
const mY = -(event.offsetY / event.target.height) * 2 + 1;
const vector1 = new THREE.Vector3(mX, mY, 0);
const vector2 = new THREE.Vector3(0, 0, 0);
vector1.unproject(this.camera);
vector2.unproject(this.camera);
vector1.subVectors(vector1, vector2);
this.camera.zoom /= factor;
vector1.multiplyScalar(factor - 1.0);
this.camera.position.subVectors(this.camera.position, vector1);
this.controls.target.subVectors(this.controls.target, vector1);
this.camera.updateProjectionMatrix();
this.camera.updateMatrix();
}
Note the different calculation of mX, mY so that it is valid for a viewport.
Implementing the D3-library with its zoom function may seem like a good idea for this case. But giving up the three-controls is in a lot of cases not a deal.
If you want a zoom-behavior like in Google Maps, the following code could be helpful:
const cameraPosition = camera.position.clone();
// my camera.zoom starts with 0.2
if (zoomOld !== 0.2) {
const xNew = this.curserVector.x + (((cameraPosition.x - this.curserVector.x) * camera.zoom) /zoomOld);
const yNew = this.curserVector.y + (((cameraPosition.y - this.curserVector.y) * camera.zoom) /zoomOld);
const diffX = cameraPosition.x - xNew;
const diffY = cameraPosition.y - yNew;
camera.position.x += diffX;
camera.position.y += diffY;
controls.target.x += diffX;
controls.target.y += diffY;
}
zoomOld = camera.zoom;
Your other problem could be caused by the frustum. But I don't know, I'm still a newbie with Three xD
I'm trying to animate a given element to go around a pre-defined radius and I'm having trouble getting the position of the element at a Y point given.
I'm trying to find each point with the circle equation, but I can only get one point out of the two possible ones.
In Javascript, I use Math.sqrt( Math.pow(radius, 2) - Math.pow(y, 2) , 2) to get the point. assuming the center of the of the circle is 0,0.
but then I need to translate it to pixels on the screen since there are no negative pixels in positions on the browser.
All the sizing is relative to the window. so the radius, for example, is 80% of the height of the window in my tests.
Also, I'm trying to calculate what the distance of the element between each frame should be for the duration, but I'm not using it yet because I try to fix the issue above first.
This is what I have(a cleaned up version):
let height = window.innerHeight * 0.8,
radius = height / 2,
circumferance = (radius * 2) * Math.PI,
container = document.getElementById('container'),
rotating = document.querySelector('.rotating'),
centerX = radius - (rotating.offsetWidth / 2),
centerY = radius - (rotating.offsetHeight / 2),
duration = 10,
stepDistance = circumferance / 16;
// Setting the dimensions of the container element.
container.style.height = height + 'px';
container.style.width = height + 'px';
// return positive X of any given Y.
function getXOffset(y) {
return Math.sqrt( Math.pow(radius, 2) - Math.pow(y, 2) , 2);
}
// Setting the position of the rotating element to the start.
rotating.style.top = 0 + 'px';
rotating.style.left = centerX + 'px';
setInterval(() => {
let top = parseInt(rotating.style.top),
y = radius - top;
rotating.style.top = (top + 1) + 'px';
rotating.style.left = (centerX + getXOffset(y)) + 'px';
}, 16);
Here is a fiddle with a bit more code for trying to get the right amount of distance between points for a smoother animation(currently needs fixing, but it doesn't bother me yet.)
https://jsfiddle.net/shock/1qcfvr4y/
Last note: I know that there might be other ways to do this with CSS, but I chose to use javascript for learning purposes.
Math.sqrt would only return the positive root. You'll have to account for the negative value based on the application. In this case, you need the positive x value during the 1st half of the cycle and negative during the 2nd half.
To do that, you should implement a method to track the progress and reverse the sign accordingly.
Here is a sample. I modified upon yours.
edit:
Instead of Math.sqrt( Math.pow(radius, 2) - Math.pow(y, 2) , 2) You can use the full formula to get x if you do not want to assume origin as center, which in this case is Math.sqrt( Math.pow(radius, 2) - Math.pow((actualY - centerY), 2) , 2)
explanation:
The original equation (x-a)² + (y'-b)² = r²
becomes x = √(r² - (y'-b)²) + a
Assuming .rotating box have 0 width and height.
The variable equivalents in your code are centerX = a, centerY = b.
By assuming origin as center you're basically doing a pre-calculation so that your y value becomes the equivalent of (y'-b). Hence x = √(r² - y²) + a is valid.
At initial state top = 0
i.e (y'-b) => height - centerY.
In your code y = radius => height/2.
Now (height - centerY) being equal to (height/2) is a side effect of your circle being bound by a square container whose height determines the y value.
In other words, when you use origin as center, you are taking the center offsets outside of circle equation and handling it separately. You could do the same thing by using the whole formula, that is, x = √(r² - (y'-b)²) + a
I'm working on an orthographic camera for our THREE.js app. Essentially, this camera will present the scene to the user in 2D (users have the option of switching between the 2D and 3D camera). This camera will allow for panning and zooming to mouse point. I have the panning working, and I have zooming working, but not zooming to mouse point. Here's my code:
import React from 'react';
import T from 'three';
let panDamper = 0.15;
let OrthoCamera = React.createClass({
getInitialState: function () {
return {
distance: 150,
position: { x: 8 * 12, y: 2 * 12, z: 20 * 12 },
};
},
getThreeCameraObject: function () {
return this.camera;
},
applyPan: function (x, y) { // Apply pan by changing the position of the camera
let newPosition = {
x: this.state.position.x + x * -1 * panDamper,
y: this.state.position.y + y * panDamper,
z: this.state.position.z
};
this.setState({position: newPosition});
},
applyDirectedZoom: function(x, y, z) {
let zoomChange = 10;
if(z < 0) zoomChange *= -1;
let newDistance = this.state.distance + zoomChange;
let mouse3D = {
x: ( x / window.innerWidth ) * 2 - 1,
y: -( y / window.innerHeight ) * 2 + 1
};
let newPositionVector = new T.Vector3(mouse3D.x, mouse3D.y, 0.5);
newPositionVector.unproject(this.camera);
newPositionVector.sub(this.camera.position);
let newPosition = {
x: newPositionVector.x,
y: newPositionVector.y,
z: this.state.position.z
};
this.setState({
distance: newDistance,
position: newPosition
});
},
render: function () {
let position = new T.Vector3(this.state.position.x, this.state.position.y, this.state.position.z);
let left = (this.state.distance / -2) * this.props.aspect + this.state.position.x;
let right = (this.state.distance / 2) * this.props.aspect + this.state.position.x;
let top = (this.state.distance / 2) + this.state.position.y;
let bottom = (this.state.distance / -2) + this.state.position.y;
// Using react-three-renderer
// https://github.com/toxicFork/react-three-renderer
return <orthographicCamera
{...(_.pick(this.props, ['near', 'far', 'name']))}
position={position}
left={left}
right={right}
top={top}
bottom={bottom}
ref={(camera) => this.camera = camera}/>
}
});
module.exports = OrthoCamera;
Some zooming towards the mouse point happens but it seems erratic. I want to keep a 2D view, so as I zoom, I also move the camera (rather than having a non-perpendicular target, which kills the 2D effect).
I took cues from this question. As far as I can tell, I am successfully converting to THREE.js coordinates in mouse3D (see the answer to this question).
So, given this setup, how can I smoothly zoom to the mouse point (mouse3D) using the orthographic camera and maintaining a two dimensional view? Thanks in advance.
Assuming you have a camera that is described by a position and a look-at (or pivot) point in world coordinates, zooming at (or away from) a specific point is quite simple at its core.
Your representation seems to be even simpler: just a position/distance pair. I didn't see a rotation component, so I'll assume your camera is meant to be a top-down orthographic one.
In that case, your look-at point (which you won't need) is simply (position.x, position.y - distance, position.z).
In the general case, all you need to do is move both the camera position and the look-at point towards the zoom-at point while preserving the camera normal (i.e. direction). Note that this will work regardless of projection type or camera rotation. EDIT (2020/05/01): When using an orthographic projection, this is not all you need to do (see update at the bottom).
If you think about it, this is exactly what happens when you're zooming at a point in 3D. You keep looking at the same direction, but you move ever closer (without ever reaching) your target.
If you want to zoom by a factor of 1.1 for example, you can imagine scaling the vector connecting your camera position to your zoom-at point by 1/1.1.
You can do that by simply interpolating:
var newPosition = new THREE.Vector3();
newPosition.x = (orgPosition.x - zoomAt.x) / zoomFactor + zoomAt.x;
newPosition.y = (orgPosition.y - zoomAt.y) / zoomFactor + zoomAt.y;
newPosition.z = (orgPosition.z - zoomAt.z) / zoomFactor + zoomAt.z;
As I said above, in your case you won't really need to update a look-at point and then calculate the new distance. Your new distance will simply be:
var newDistance = newPosition.y
That should do it.
It only gets a little bit more sophisticated (mainly in the general case) if you want to set minimum and maximum distance limits both between the position/look-at and position/zoom-at point pairs.
UPDATE (2020/05/01):
I just realized that the above, although correct (except for missing one minor but very important step) is not a complete answer to OP's question. Changing the camera's position in orthographic mode won't of course change the scale of graphics being rendered. For that, the camera's projection matrix will have to be updated (i.e. the left, right, top and bottom parameters of the orthographic projection will have to be changed).
For this reason, many graphics libraries include a scaling factor in their orthographic camera class, which does exactly that. I don't have experience with ThreeJS, but I think that property is called 'zoom'.
So, summing everything up:
var newPosition = new THREE.Vector3();
newPosition.x = (orgPosition.x - zoomAt.x) / zoomFactor + zoomAt.x;
newPosition.y = (orgPosition.y - zoomAt.y) / zoomFactor + zoomAt.y;
newPosition.z = (orgPosition.z - zoomAt.z) / zoomFactor + zoomAt.z;
myCamera.zoom = myCamera.zoom * zoomFactor
myCamera.updateProjectionMatrix()
If you want to use your orthographic camera class code above instead, you will probably have to change the section that computes left, right, top and bottom and add a scaling factor in the calculation. Here's an example:
var aspect = this.viewportWidth / this.viewportHeight
var dX = (this.right - this.left)
var dY = (this.top - this.bottom) / aspect
var left = -dX / (2 * this.scale)
var right = dX / (2 * this.scale)
var bottom = -dY / (2 * this.scale)
var top = dY / (2 * this.scale)
mat4.ortho(this.mProjection, left, right, bottom, top, this.near, this.far)
this is my first question after having relied on this site for years!
Anyway, I'd like to accomplish something similar to this effect:
http://www.flashmonkey.co.uk/html5/wave-physics/
But on a circular path, instead of a horizon. Essentially, a floating circle/blob in the center of the screen that would react to mouse interaction. What I'm not looking for is gravity, or for the circle to bounce around the screen - only surface ripples.
If at all possible I'd like to apply a static texture to the shape, is this a possibility? I'm completely new to Canvas!
I've already tried replacing some code from the above example with circular code from the following link, to very limited success:
http://www.html5canvastutorials.com/tutorials/html5-canvas-circles/
If only it were that easy :)
Any ideas?
Thanks in advance!
I tried to figure out how wave simulation works using View Source and JavaScript console. It's working fine but threw some JS errors. Also, it seems physics update is entangled with rendering in the render() method.
Here is what I found about the code:
The mouseMove() method creates disturbances on the wave based on mouse position, creating a peak around the mouse. The target variable is the index of the particle that needs to be updated, it's calculated from mouse pos.
if (particle && mouseY > particle.y) {
var speed = mouseY - storeY;
particles[target - 2].vy = speed / 6;
particles[target - 1].vy = speed / 5;
particles[target].vy = speed / 3;
particles[target + 1].vy = speed / 5;
particles[target + 2].vy = speed / 6;
storeY = mouseY;
}
Then, the particles around target are updated. The problem I found is that it does no bounds checking, i.e. it can potentially particles[-1] when target == 0. If that happens, an exception is thrown, the method call ends, but the code does not stop.
The render() method first updates the particle positions, then renders the wave.
Here is its physics code:
for (var u = particles.length - 1; u >= 0; --u) {
var fExtensionY = 0;
var fForceY = 0;
if (u > 0) {
fExtensionY = particles[u - 1].y - particles[u].y - springs[u - 1].iLengthY;
fForceY += -fK * fExtensionY;
}
if (u < particles.length - 1) {
fExtensionY = particles[u].y - particles[u + 1].y - springs[u].iLengthY;
fForceY += fK * fExtensionY;
}
fExtensionY = particles[u].y - particles[u].origY;
fForceY += fK / 15 * fExtensionY;
particles[u].ay = -fForceY / particles[u].mass;
particles[u].vy += particles[u].ay;
particles[u].ypos += particles[u].vy;
particles[u].vy /= 1.04;
}
Basically, it's Hooke's Law for a chain of particles linked by springs between them. For each particle u, it adds the attraction to the previous and next particles (the if statements check if they are available), to the variable fForceY. I don't fully understand the purpose of the springs array.
In the last four lines, it calculates the acceleration (force / mass), updates the velocity (add acceleration), then position (add velocity), and finally, reduce velocity by 1.04 (friction).
After the physics update, the code renders the wave:
context.clearRect(0, 0, stageWidth, stageHeight);
context.fillStyle = color;
context.beginPath();
for (u = 0; u < particles.length; u++) {
...
}
...
context.closePath();
context.fill();
I'm not explaining that, you need to read a canvas tutorial to understand it.
Here are some ideas to get started, note that I didn't test these code.
To modify the code to draw a circular wave, we need introduce a polar coordinate system, where the particle's x-position is the angle in the circle and y-position the distance from center. We should use theta and r here but it requires a large amount of refactoring. We will talk about transforming later.
mouseMove(): Compute particle index from mouse position on screen to polar coordinates, and make sure the disturbance wrap around:
Define the function (outside mouseMove(), we need this again later)
function wrapAround(i, a) { return (i + a.length) % a.length; }
Then change
particles[target - 2] --> particles[wrapAround(target - 2, particles)]
particles[target - 1] --> particles[wrapAround(target - 1, particles)]
...
The modulo operator does the job but I added particles.length so I don't modulo a negative number.
render(): Make sure the force calculation wrap around, so we need to wrapAround function again. We can strip away the two if statements:
fExtensionY = particles[wrapAround(u - 1, particles)].y - particles[u].y - springs[wrapAround(u - 1, springs)].iLengthY;
fForceY += -fK * fExtensionY;
fExtensionY = particles[u].y - particles[wrapAround(u + 1, particles)].y - springs[warpAround(u, springs)].iLengthY;
fForceY += fK * fExtensionY;
Here is the result so far in jsfiddle: Notice the wave propagate from the other side. http://jsfiddle.net/DM68M/
After that's done, the hardest part is rendering them on a circle. To do that, we need coordinate transform functions that treat particle's (x, y) as (angle in the circle, distance from center), and we also need inverse transforms for mouse interaction in mouseMove().
function particleCoordsToScreenCoords(particleX, particleY) {
return [ radiusFactor * particleY * Math.cos(particleX / angleFactor),
radiusFactor * particleY * Math.sin(particleX / angleFactor) ];
}
function screenCoordsToParticleCoords(screenX, screenY) {
// something involving Math.atan2 and Math.sqrt
}
Where the ...Factor variables needed to be determined separately. The angleFactor is two pi over the highest x-position found among particles array
Then, in the coordinates supplied to the context.lineTo, context.arc, use the particleCoordsToScreenCoords to transform the coordinates.
I am new in Three.js.
I need to implement one 3D screen in which there are many objects in sky and there is one scale in screen.
I can move the scale in up or down direction. When I move scale up then all objects come closer in endless manner and same when I move scale down then all objects goes far.
So ultimately I want the effect like I am moving in space and I am bypassing the starts.
So for getting this effect I have used Three.js.
The problem I am facing is the when the objects come closer to me their size is increased and when the come very close than their size become very large in size. I need to increase size for some fixed parameters. After that it should not increase the size of object when it come close to screen. How I can implement that?
This is the code of object rendering:
function renderobjects() {
if(speed != 0) {
if(textArray.length > 0 && textArray[0].material.opacity == 1) {
for(var i = 0; i < textArray.length; i++) {
textArray[i].material.opacity = 0;
}
}
camera.position.y += - mouseY * 0.01;
if (camera.position.y > 60) {
camera.position.y = 60;
}
if (camera.position.y < 35) {
camera.position.y = 35;
}
camera.position.z = (camera.position.z + 8*speed);
}
Please provide me the solution by which I can restrict the size of objects.
The OrthographicCamera may be of help to you, it uses parallel projection. You can then do your own selective scaling of objects based on your needs.