Three.js rotating a cube around a sphere - javascript

i'm trying to rotate a cube around a sphere, when i press the spacebar, the cube starts rotating around the sphere just fine, but it goes a lot quicker than i wanted, i wrote a function which would calculate the rotation using "angle" as a parameter. a full rotation would require the angle to go from 0 to 359 (or 1 to 360), but somehow the cube rotates around the sphere completely when the angle increases by just 7 degrees.
code: (im excluding the initialization of the cube and sphere meshes, just the functions)
var rotationAngle = 0;
function rotate(angle)
{
if(angle == 0)
{
keu.position.x = whiteBall.position.x + 1;
keu.position.z = whiteBall.position.z;
} else if(angle > 0 && angle < 90)
{
keu.position.x = whiteBall.position.x + Math.cos(angle);
keu.position.z = whiteBall.position.z - Math.sin(angle);
} else if(angle == 90)
{
keu.position.x = whiteBall.position.x;
keu.position.z = whiteBall.position.z - 1;
} else if(angle > 90 && angle < 180)
{
angle -= 90;
keu.position.x = whiteBall.position.x - Math.sin(angle);
keu.position.z = whiteBall.position.z - Math.cos(angle);
} else if(angle == 180)
{
keu.position.x = whiteBall.position.x - 1;
keu.position.z = whiteBall.position.z;
} else if(angle > 180 && angle < 270)
{
angle -= 180;
keu.position.x = whiteBall.position.x - Math.cos(angle);
keu.position.z = whiteBall.position.z + Math.sin(angle);
} else if(angle == 270)
{
keu.position.x = whiteBall.position.x;
keu.position.z = whiteBall.position.z + 1;
}else if(angle > 270 && angle < 360)
{
angle -= 270;
keu.position.x = whiteBall.position.x + Math.sin(angle);
keu.position.z = whiteBall.position.z + Math.cos(angle);
}
console.log(angle);
}
in the code above "whiteball is the sphere, and "keu" is the cube.
in my render function i have to following code to increase the angle and apply the rotation:
if(isKeyPressed)
{
if(rotationAngle < 360)
{
rotationAngle += 1;
}
if(rotationAngle == 360)
rotationAngle = 0;
}
rotate(rotationAngle);
i have no idea why an increase of just 7 degrees would cause the cube to make a full rotation around the sphere, any code snippets / advice would be appreciated.

Treat x position of the cube as Math.sin(counter) and y position as Math.cos(counter) where counter is some number being incremented in some requestAnimationFrame loop and if spacebar is down then increment the counter and if up then stop incrementing it. You can also modify the distance from your central point around which you move the cube by multiplying Math.sin(counter) by that distance (in pixels). You surely know range of sin is from -1 to 1.
So the code would look something like:
let isMoving = false;
document.body.addEventListener('keydown', event => {
if (event.key === 'Space') {
isMoving = true;
}
});
document.body.addEventListener('keyup', event => {
if (event.key === 'Space') {
isMoving = false;
}
});
const X = ...; //your central point, X coordinate of the sphere
const Y = ...// your central point, Y coordinate of the sphere
const distance = 100;
const speed = ...; // how fast do you want your cube to move around the sphere
let counter = 0;
let wholeCircle = false; // will be set to true after first round and stop further movement of the cube
function render() {
if (isMoving) {
counter += speed;
}
cube.position.x = X + distance * Math.sin(counter);
cube.position.y = Y + distance * Math.cos(counter);
}
render();
This is not a code to copy and paste, you needto adjust it to your situation and variable names. It is just to give you an idea on how to do this kind of movement. I didn't use the wholeCircle, you can surely figure it out.

Related

Why won't the balls bounce fully in my pong JavaScript canvas game?

I have multiple elipses on the canvas in javascript and I want all of them to bounce off each other. I tried using the distance formula and then changing the x and y direction of the ball when the distance is less than the ball radius *2.
This worked well for one ball, but it doesn't work so well for many balls as it often leads to the dreaded 'bounce loop' depicted Here
To remedy this issue, I resolved to change the way the balls bounce depending on where they collide with each other to avoid the bounce loop and to make the game closer to real life physics.
If there's a side to side collision, I want to reverse the x direction of both balls and if there's a top to bottom collision, I want to reverse the y direction of both balls.
So, I calculated all the points, for example, between 45 degrees and 135 degrees that correlate with a degree (that's 90 points) and compared them to all 90 point between 225 degrees and 315 degrees and vice versa.
If the distance between any of the points on the edge of the circle and all the other balls center point is less than the radius, I want the Y direction of both balls to be reversed.
I repeated the same process for 135 degrees and 225 degress to 315 degrees and 405 degrees (equivalent to 45) and reversed the X direction of both balls.
As of right now, I think the balls should bounce off each other how I want them to, but they just don't. They bounce off each other's sides and tops, bottoms, and occasionally at angles, but they tend to dip inside of each other and then change direction. Here is a video of the output.
Below is the code comparing top to bottom:
// radius is the same for all the balls and is at 25.
let ballToBallDistance = (x1, y1, x2, y2) => {
return Math.sqrt((Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2)));
}
const ballCollisionY = (start, end) => {
for (let i = start; i <= end; i++) {
return ballObjects[0].ballRadius * Math.sin((i * Math.PI / 180));
}
}
const ballCollisionX = (start, end) => {
for (let i = start; i <= end; i++) {
return ballObjects[0].ballRadius * Math.cos((i * Math.PI / 180));
}
}
const upperYBall = {
bounceTopBottom() {
let n = 0;
for (let i = 0; i < ballObjects.length; i++) {
if (ballObjects.length == 1) {
return;
}
if (n == i) {
continue;
}
let yUpXPoint = ballObjects[n].ballXPos - ballCollisionX(45, 135);
let yUpYPoint = ballObjects[n].ballYPos - ballCollisionY(45, 135);
let centerBallX = ballObjects[i].ballXPos;
let centerBallY = ballObjects[i].ballYPos;
let pointDistance = ballToBallDistance(yUpXPoint, yUpYPoint, centerBallX, centerBallY);
if (pointDistance <= 25) {
ballObjects[n].ballMotionY = ballObjects[n].ballMotionY * -1;
}
if (i == ballObjects.length - 1) {
++n;
i = -1;
continue;
}
}
}
}
const lowerYBall = {
bounceBottomTop() {
let n = 0;
for (let i = 0; i < ballObjects.length; i++) {
if (ballObjects.length == 1) {
return;
}
if (n == i) {
continue;
}
let yDownXPoint = ballObjects[n].ballXPos - ballCollisionX(225, 315);
let yDownYPoint = ballObjects[n].ballYPos - ballCollisionY(225, 315);
let centerBallX = ballObjects[i].ballXPos;
let centerBallY = ballObjects[i].ballYPos;
let pointDistance = ballToBallDistance(yDownXPoint, yDownYPoint, centerBallX, centerBallY);
if (pointDistance <= 25) {
ballObjects[n].ballMotionY = ballObjects[n].ballMotionY * -1;
}
if (i == ballObjects.length - 1) {
++n;
i = -1;
continue;
}
}
}
}
I've been stuck on this feature for two weeks. If anyone has any insight as to what I am doing wrong and perhaps a solution to achieve the desired result, that would be very much appreciated.
I propose you switch from the special-case coding to a more general approach.
When two balls collide:
Calculate the collision normal (angle)
Calculate the new velocity based on the previous velocity and the normal
Reposition the balls so that they do not overlap anymore, preventing the 'bounce loop'.
You will need:
A method to calculate the angle between two balls:
function ballToBallAngle(ball1,ball2) {
return Math.atan2(ball2.y-ball1.y,ball2.x-ball1.x)
}
A method to derive the normal vector from a angle:
function calcNormalFromAngle(angle){
return [
Math.cos(angle),
Math.sin(angle)
]
}
A method to calculate the dot product of two vectors:
function dotproduct (a, b){
return a.map((x, i) => a[i] * b[i]).reduce((m, n) => m + n)
}
Finally a way to calculate the bounce angle. Read this, it describes it perfectly.
So to put it together, see the snippet below:
let canvas = document.querySelector('canvas')
let ctx = canvas.getContext('2d')
let balls = [
{x:40,y:40,radius:25,vx:4,vy:3},
{x:300,y:300,radius:50,vx:-2,vy:-3},
{x:100,y:220,radius:25,vx:4,vy:-3},
{x:400,y:400,radius:50,vx:-1,vy:-3},
{x:200,y:400,radius:32,vx:2,vy:-3}
]
function tick() {
balls.forEach((ball, index) => {
ball.x += ball.vx
ball.y += ball.vy
//check for x bounds collision
if (ball.x - ball.radius < 0) {
bounceBall(ball, Math.PI)
ball.x = ball.radius
} else if (ball.x + ball.radius > 500) {
bounceBall(ball, 0)
ball.x = 500 - ball.radius
}
//check for y bounds collision
if (ball.y - ball.radius < 0) {
bounceBall(ball, Math.PI / 2)
ball.y = ball.radius
} else if (ball.y + ball.radius > 500) {
bounceBall(ball, -Math.PI / 2)
ball.y = 500 - ball.radius
}
balls.forEach((other_ball, other_index) => {
if (index == other_index)
return
// how many px the balls intersect
let intersection = ball.radius + other_ball.radius - ballToBallDistance(ball, other_ball)
// if its greater than 0, they must be colliding
if (intersection > 0) {
let angle = ballToBallAngle(ball, other_ball)
let normal = calcNormalFromAngle(angle)
bounceBall(ball, angle)
bounceBall(other_ball, angle + Math.PI)
// set positions so that they are not overlapping anymore
ball.x -= normal[0] * intersection / 2
ball.y -= normal[1] * intersection / 2
other_ball.x += normal[0] * intersection / 2
other_ball.y += normal[1] * intersection / 2
}
})
})
render()
requestAnimationFrame(tick)
}
function render() {
ctx.clearRect(0, 0, canvas.width, canvas.height)
balls.forEach(ball => {
ctx.beginPath();
ctx.arc(ball.x, ball.y, ball.radius, 0, 2 * Math.PI);
ctx.stroke();
})
}
function bounceBall(ball, angle) {
let normal = calcNormalFromAngle(angle)
let velocity = [ball.vx, ball.vy]
let ul = dotproduct(velocity, normal) / dotproduct(normal, normal)
let u = [
normal[0] * ul,
normal[1] * ul
]
let w = [
velocity[0] - u[0],
velocity[1] - u[1]
]
let new_velocity = [
w[0] - u[0],
w[1] - u[1]
]
ball.vx = new_velocity[0]
ball.vy = new_velocity[1]
}
function dotproduct(a, b) {
return a.map((x, i) => a[i] * b[i]).reduce((m, n) => m + n)
}
function ballToBallDistance(ball1, ball2) {
return Math.sqrt((Math.pow(ball2.x - ball1.x, 2) + Math.pow(ball2.y - ball1.y, 2)));
}
function ballToBallAngle(ball1, ball2) {
return Math.atan2(ball2.y - ball1.y, ball2.x - ball1.x)
}
function calcNormalFromAngle(angle) {
return [
Math.cos(angle),
Math.sin(angle)
]
}
tick();
body{
background-color: #eee;
}
canvas{
background-color: white;
}
<canvas width="500" height="500"></canvas>

Mouse click angle

So I'm creating a brick breaker game, and I need some help finding an angle.
Pretty much the game consists of blocks that, when hit, will cause you to lose 1 health. The point of the game is to hit the blocks with the balls to break them before they reach the bottom. If the ball hits a wall or a block, its trajectory is reversed.
I want the user to be able to click someone within the html canvas. Then the balls, which start in the center of the screen at the bottom of the canvas, will follow that angle. In other words, the user will click and the balls will move to that spot and then continue until it hits something.
I have some code here, But it probably won't help on how to achieve the angle thing.
function animate(callback) {
window.requestAnimationFrame(function() {
window.setTimeout(callback, 1000/60);
});
}
// canvas
var canvas = document.getElementById('canvas');
var context = canvas.getContext('2d');
// variables
var ballList = [];
var maxBalls = 1;
var checkAmount = 0;
var interval;
// onload/refresh/update/render
window.onload = function() {
refresh();
}
function refresh() {
update();
render();
animate(refresh);
}
function update() {
document.addEventListener("click", spawn);
for(var i = 0; i < ballList.length; i++) {
ballList[i].move();
}
}
function render() {
context.fillStyle = '#000';
context.fillRect(0, 0, canvas.width, canvas.height);
for(var i = 0; i < ballList.length; i++) {
ballList[i].show();
}
}
// ball
function Ball() {
this.x = canvas.width / 2;
this.y = canvas.height - 50;
this.width = 10;
this.height = 10;
this.xVel = 5;
this.yVel = -10;
this.show = function() {
context.fillStyle = '#fff';
context.fillRect(this.x, this.y, this.width, this.height);
}
this.move = function() {
this.x += this.xVel;
this.y += this.yVel;
if(this.x >= canvas.width || this.x <= 0) {
this.xVel *= -1;
}
if(this.y >= canvas.height || this.y <= 0) {
this.yVel *= -1;
}
}
}
function spawn(event) {
var xVel = (event.clientX - canvas.width / 2) / 90;
if(ballList.length < maxBalls) {
if(checkAmount < maxBalls) {
interval = setInterval(function() {
ballList.push(new Ball((event.clientX)));
checkAmount++;
if(checkAmount > maxBalls) {
clearInterval(interval);
checkAmount = 0;
}
}, 10);
}
}
}
Thanks in advance.
Unit Vectors
To move an object from one point towards another you use a vector. A vector is just two numbers that represent a direction and a speed. It can be polar in that one number is an angle and the other is a distance, or cartesian that represent the vector as the amount of change in x and y.
Cartesian unit vector
For this you can use either but I prefer the cartesian vector and a particular type called a unit vector. The unit vector is 1 unit long. In computer graphics the unit is normally the pixel.
So we have a point to start at
var startX = ?
var startY = ?
And a point the we want to head towards
var targetX = ?
var targetY = ?
We want the unit vector from start to target,
var vectorX = targetX - startX;
var vectorY = targetY - startY;
The vector's length is the distance between the two points. This is not so handy so we will turn it into a unit vector by dividing both the x and y by the length
var length = Math.sqrt(vectorX * vectorX + vectorY * vectorY);
var unitVectX = vectorX / length;
var unitVectY = vectorY / length;
Now we have a one pixel long unit vector.
The Ball will start at start
var ballX = startX
var ballY = startY
And will move at a speed of 200 pixels per second (assuming 60fps)
var ballSpeed = 200 / 60;
Now to move the ball just add the unit vector times the speed and you are done. Well till the next frame that is.
ballX += unitVectX * ballSpeed;
ballY += unitVectY * ballSpeed;
Using the cartesian makes it very easy to bounce off of walls that are aligned to the x or y axis.
if(ballX + ballRadius > canvas.width){
ballX = canvas.width - ballRadius;
unitVectX = - unitVectX;
}
Polar vector
You can also use polar coordinates. As we use a unit vector the polar unit vector just needs the direction. You use the trig function atan2
// get the direction in radians
var polarDirection = Math.atan2(targetY - startY, targetX - startX);
The direction is in radians, many poeple don't like radians and convert to degrees, but there is no need to know which way it is going just as long as it goes in the correct direction. To remember radians is easy. 360 degrees is 2 radian 180 is 1 randian 90 is 0.5. The actual units used are PI (not many people know many of the digits of pi but you don't need to). So 270 degree is 1.5 radians or as a number 1.5 * Math.PI.
The angles start at the 3 o'clock point (pointing to the right of screen) as 0 radians or 0 deg then clockwise 90deg is at 6 o'clock 0.5 radian, and 180deg 1 radian at 6 o'clock and so on.
To move the ball with the polarDirection you need to use some more trig.
// do this once a frame
ballX += Math.cos(polarDirection) * ballSpeed;
ballY += Math.sin(polarDirection) * ballSpeed;
// note that the cos and sin actually generate the cartesian unit vector
/**
* #param {number} x1 - x coordinate of the first point
* #param {number} y1 - y coordinate of the first point
* #param {number} x2 - x coordinate of the second point
* #param {number} y2 - y coordinate of the second point
* #return {number} - the angle (between 0 and 360)
*/
function getDirection(x1, y1, x2, y2) {
// might be negative:
var angle = Math.atan2(y2 - y1, x2 - x1) * 180 / Math.PI;
// correct, positive angle:
return (angle + 360) % 360;
}
I wrote this function for a similar purpose. Don't forget that you might have to negate x.

smooth animation using deviceorientation

I'm creating a game using the device deviceorientation with the createjs library,
I have a graphic that moves on it's x axis depending on the tilt of the phone.
The problem I'm having is the graphic animation is jerky.
I'm wondering if it's down to the logic I'm using?
I have two functions "handleOrientation" and "render"
window.addEventListener('deviceorientation', handleOrientation);
function handleOrientation(event) {
nextX = car.x;
x = Math.round(event.beta); // In degree in the range [-180,180]
y = Math.round(event.gamma); // In degree in the range [-90,90]
// Because we don't want to have the device upside down
// We constrain the x value to the range [-90,90]
if (x > 90) { x = 90};
if (x < -90) { x = -90};
hit = "nothing"
// To make computation easier we shift the range of
// x and y to [0,180]
x += 90;
//y += 90;
if(y >0){
nextX = car.x +carSpeed;
}
if(y < 0){
nextX = car.x -carSpeed;
};
car.nextX= nextX;
}
function render(){
if(gameState=="game started"){
if (car.nextX < 0 + car.width / 2){
car.x = 0 + car.width / 2 ;
}else if(car.nextX > stage.canvas.width - car.width / 2){
car.x = stage.canvas.width - car.width / 2;
} else{
car.x = car.nextX;
}
}
}
function tick(){
render();
stage.update();
}
I know that deviceorientation is quite sensitive, maybe calling the function in a timer, so it's not updating so often?
Any pointers?
maybe calling the function in a timer, so it's not updating so often
Instead of updating the location on every tick, you could read multiple readings before updating the position, take an average, and update position using that average.
Alternatively, you could update the position on every tick but use last N readings to get the average in a similar way.
Here's a simple example of the second suggestion:
window.addEventListener('deviceorientation', handleOrientation);
xAvg = 0;
yAvg = 0;
//how much stored "average" affects current speed
coef = 0.9;
function handleOrientation(event) {
nextX = car.x;
x = Math.round(event.beta); // In degree in the range [-180,180]
y = Math.round(event.gamma); // In degree in the range [-90,90]
// Because we don't want to have the device upside down
// We constrain the x value to the range [-90,90]
if (x > 90) { x = 90};
if (x < -90) { x = -90};
hit = "nothing"
// To make computation easier we shift the range of
// x and y to [0,180]
x += 90;
y += 90;
//include current readings to the "average"
xAvg = xAvg*coef + x*(1.0 - coef);
yAvg = yAvg*coef + y*(1.0 - coef);
x = xAvg;
y = yAvg;
if(y >0){
nextX = car.x +carSpeed;
}
if(y < 0){
nextX = car.x -carSpeed;
};
car.nextX= nextX;
}
function render(){
if(gameState=="game started"){
if (car.nextX < 0 + car.width / 2){
car.x = 0 + car.width / 2 ;
}else if(car.nextX > stage.canvas.width - car.width / 2){
car.x = stage.canvas.width - car.width / 2;
} else{
car.x = car.nextX;
}
}
}
function tick(){
render();
stage.update();
}
I have not tested this, and you should play around with coef value.

Find distance the mouse has moved along a vector in Three.js

I'm trying to find the distance that the mouse has traveled along a normal vector.
The idea is to move a set of vertices within an object along the intersecting face's normal vector.
Currently, I have an onmousedown event handler that finds the intersecting face, adjacent faces with the same normal, and the vertices associated to those faces. I also have an onmousemove event handler that moves the vertices along the normal.
Right now, the onmousemove just moves the vertices 1 unit along the face normal every time the event is fired. I'd like them to move with the mouse.
The code that I am working off of came largely from the three.js editor. Any help is very much appreciated, thanks!
var object; // Set outside this code
var camera; // Set outside this code
var viewport; // Set outside this code
var raycaster = new THREE.Raycaster();
var point = new THREE.Vector2();
var mouse = new THREE.Vector2();
var _dragging = false;
var faces = [];
var vertices = [];
function onMouseDown(event) {
if (object === undefined || _dragging === true) {
return;
}
event.preventDefault();
event.stopPropagation();
var intersect = getIntersects(event, object)[0];
if (intersect && intersect.face) {
faces = getAdjacentNormalFaces(intersect.object.geometry, intersect.face);
vertices = getFaceVertices(intersect.object.geometry, self.faces);
}
_dragging = true;
}
function onMouseMove(event) {
if (object === undefined || vertices.length === 0 || _dragging === false) {
return;
}
event.preventDefault();
event.stopPropagation();
var normal = faces[0].normal;
/*
* Get the distance to move the vertices
*/
var distance = 1;
var i;
for (i = 0; i < self.vertices.length; i++) {
self.vertices[i].x += (normal.x * distance);
self.vertices[i].y += (normal.y * distance);
self.vertices[i].z += (normal.z * distance);
}
object.geometry.verticesNeedUpdate = true;
object.geometry.computeBoundingBox();
object.geometry.computeBoundingSphere();
}
var getIntersects = function (event, object) {
var rect = viewport.getBoundingClientRect();
point.fromArray([
( event.clientX - rect.left ) / rect.width,
( event.clientY - rect.top ) / rect.height
]);
mouse.set(( point.x * 2 ) - 1, -( point.y * 2 ) + 1);
raycaster.setFromCamera(mouse, camera);
if (object instanceof Array) {
return raycaster.intersectObjects(object);
}
return raycaster.intersectObject(object);
};
var getAdjacentNormalFaces = function (geometry, face) {
// Returns an array of all faces that are adjacent and share the same normal vector
};
var getFaceVertices = function (geometry, faces) {
// Returns an array of vertices that belong to the array of faces
};
Update:
As a summary... I have the event handlers, the set of vertices that need to be moved and the normal vector that the vertices should be moved on. What I need is the offset distance that the vertices should be moved based on where the mouse is.
My first thought is to create a plane perpendicular to the normal vector and track the mouse position on that plane. However, I am not sure 1. how to create the perpendicular plane where the largest side is visible to the camera and 2. how to translate the x/y coordinates of the mouse on the plane to the distance the vertices should be moved.
The way I solved this is to map the zero and normal points on the 2D plane and then use the inverse slope to find the perpendicular line that intersects the normal line. I can then use the starting point and the point of intersection to find the distance the mouse moved. I also had to scale the final distance using the camera.
For a quick reference:
// linear slope/intercept: y = mx + b
// solve for b: b = y - mx
// solve for m: (y2 - y1) / (x2 - x1)
// get inverse slope: -1 / m
// get intersect point: (b2 - b1) / (m1 - m2)
There may be an easier way but this is what I did and hopefully it helps others:
On Mousedown
Project the center (0,0,0) vector, the face normal vector and an arbitrary 1 unit vector (1,0,0) onto the camera and get the screen position of the three points
var zero2D = toScreenPosition(0, 0, 0);
var one2D = toScreenPosition(1, 0, 0);
var normal2D = toScreenPosition(intersect.face.normal.x, intersect.face.normal.y, intersect.face.normal.z);
/ ***** /
var toScreenPosition = function (x, y, z) {
var rect = viewport.getBoundingClientRect();
var point = new THREE.Vector2();
screenPositionVector.set(x || 0, y || 0, z || 0);
screenPositionVector.project(camera);
point.set((screenPositionVector.x + 1) / 2 * rect.width, -(screenPositionVector.y - 1) / 2 * rect.height);
return point;
};
Store the mouse starting point and the x direction of the normal (1 or -1).
start2D.set(event.clientX, event.clientY);
normalDir = zero2D.x < normal2D.x ? 1 : -1;
Store the slope and inverse slope of the zero/normal line.
slope = (normal2D.y - zero2D.y) / (normal2D.x - zero2D.x); // TODO: Handle zero slope
inverseSlope = -1 / slope; // TODO: If slope is 0, inverse is infinity
Store the y intercept of the normal line based on the mouse coordinates.
startingYIntercept = event.clientY - (slope * event.clientX);
Use the zero2D and one2D point to find the camera scale. The camera scale is the distance between the two 2D points divided by the distance between the two 3D points (1).
cameraScale = one2D.distanceTo(zero2D);
For better accuracy, we are going to move the vertices based on total movement, not the delta between event handler calls. Because of this, we need to track the starting position of all the vertices.
startingVertices = [];
var i;
for (i = 0; i < vertices.length; i++) {
startingVertices.push({x: vertices[i].x, y: vertices[i].y, z: vertices[i].z});
}
On Mousemove
Using the mouse position and the inverse slope, find the perpendicular line's y intercept.
var endingYIntercept = event.clientY - (inverseSlope * event.clientX);
Use the intercept equation to find the x location where the normal line and perpendicular line intercept.
var endingX = (endingYIntercept - startingYIntercept) / (slope / inverseSlope);
Plug x back in to find the y point. Since the lines intercept at x, you can use either the normal line or perpendicular line. Set the end point based on this.
var endingY = (slope * endingX) + startingYIntercept;
end2D.set(endingX, endingY);
Find the distance between the points and divide by the camera scale.
var distance = end2D.distanceTo(start2D) / cameraScale;
If the normal is in the opposite direction of the mouse movement, multiply distance by -1.
if ((normalDir > 0 && endingX < start2D.x) || (normalDir < 0 && endingX > start2D.x)) {
distance = distance * -1;
}
Since we are moving the vertices by a total distance and not the delta between event handlers, the vertex update code is a little different.
var i;
for (i = 0; i < self.vertices.length; i++) {
vertices[i].x = startingVertices[i].x + (normal.x * distance);
vertices[i].y = startingVertices[i].y + (normal.y * distance);
vertices[i].z = startingVertices[i].z + (normal.z * distance);
}
Extra Credit On Mouseup
When the vertices are moved, the geometry's center is not changed and needs to be updated. To update the center, I can call geometry.center(), however, in Three.js, the geometry's position is based off of its center so this will effectively move the center and the position of the geometry in the opposite direction at half the distance of the vertex move. I don't want this, I want the geometry to stay in the same position when I move the vertices. To do this, I take the first vertex's ending position minus its start position divided by 2 and add that vector to the geometry's position. I then recenter the geometry.
if (_dragging && self.vertices.length > 0) {
offset.set(self.vertices[0].x - startingVertices[0].x, self.vertices[0].y - startingVertices[0].y, self.vertices[0].z - startingVertices[0].z);
offset.divideScalar(2);
object.position.add(offset);
object.geometry.center();
}
All Together
var object; // Set outside this code
var camera; // Set outside this code
var viewport; // Set outside this code
var raycaster = new THREE.Raycaster();
var point = new THREE.Vector2();
var mouse = new THREE.Vector2();
var _dragging = false;
var faces = [];
var vertices = [];
var startingVertices = [];
var slope = 0;
var inverseSlope;
var startingYIntercept = 0;
var normalDir = 1;
var cameraScale = 1;
var start2D = new THREE.Vector2();
var end2D = new THREE.Vector2();
var offset = new THREE.Vector3();
var onMouseDown = function (event) {
if (object === undefined || _dragging === true) {
return;
}
event.preventDefault();
event.stopPropagation();
var intersect = getIntersects(event, object)[0];
if (intersect && intersect.face) {
var zero2D = toScreenPosition(0, 0, 0);
var one2D = toScreenPosition(1, 0, 0);
var normal2D = toScreenPosition(intersect.face.normal.x, intersect.face.normal.y, intersect.face.normal.z);
start2D.set(event.clientX, event.clientY);
normalDir = zero2D.x < normal2D.x ? 1 : -1;
slope = (normal2D.y - zero2D.y) / (normal2D.x - zero2D.x); // TODO: Handle zero slope
inverseSlope = -1 / slope; // TODO: If slope is 0, inverse is infinity
startingYIntercept = event.clientY - (slope * event.clientX);
cameraScale = one2D.distanceTo(zero2D);
faces = getAdjacentNormalFaces(intersect.object.geometry, intersect.face);
vertices = getFaceVertices(intersect.object.geometry, self.faces);
startingVertices = [];
var i;
for (i = 0; i < vertices.length; i++) {
startingVertices.push({x: vertices[i].x, y: vertices[i].y, z: vertices[i].z});
}
}
_dragging = true;
}
var onMouseMove = function (event) {
if (object === undefined || vertices.length === 0 || _dragging === false) {
return;
}
event.preventDefault();
event.stopPropagation();
var normal = faces[0].normal;
var endingYIntercept = event.clientY - (inverseSlope * event.clientX);
var endingX = (endingYIntercept - startingYIntercept) / (slope / inverseSlope);
var endingY = (slope * endingX) + startingYIntercept;
end2D.set(endingX, endingY);
var distance = end2D.distanceTo(start2D) / cameraScale;
if ((normalDir > 0 && endingX < start2D.x) || (normalDir < 0 && endingX > start2D.x)) {
distance = distance * -1;
}
var i;
for (i = 0; i < self.vertices.length; i++) {
vertices[i].x = startingVertices[i].x + (normal.x * distance);
vertices[i].y = startingVertices[i].y + (normal.y * distance);
vertices[i].z = startingVertices[i].z + (normal.z * distance);
}
object.geometry.verticesNeedUpdate = true;
object.geometry.computeBoundingBox();
object.geometry.computeBoundingSphere();
}
var onMouseUp = function (event) {
if (_dragging && vertices.length > 0) {
offset.set(vertices[0].x - startingVertices[0].x, vertices[0].y - startingVertices[0].y, vertices[0].z - startingVertices[0].z);
offset.divideScalar(2);
object.position.add(offset);
object.geometry.center();
}
}
var getIntersects = function (event, object) {
var rect = viewport.getBoundingClientRect();
point.fromArray([
( event.clientX - rect.left ) / rect.width,
( event.clientY - rect.top ) / rect.height
]);
mouse.set(( point.x * 2 ) - 1, -( point.y * 2 ) + 1);
raycaster.setFromCamera(mouse, camera);
if (object instanceof Array) {
return raycaster.intersectObjects(object);
}
return raycaster.intersectObject(object);
};
var toScreenPosition = function (x, y, z) {
var rect = viewport.getBoundingClientRect();
var point = new THREE.Vector2();
screenPositionVector.set(x || 0, y || 0, z || 0);
screenPositionVector.project(camera);
point.set((screenPositionVector.x + 1) / 2 * rect.width, -(screenPositionVector.y - 1) / 2 * rect.height);
return point;
};
var getAdjacentNormalFaces = function (geometry, face) {
// Returns an array of all faces that are adjacent and share the same normal vector
};
var getFaceVertices = function (geometry, faces) {
// Returns an array of vertices that belong to the array of faces
};
You could achieve two ways, on mouse move or in animationframe.
onmouseMove(){
mouseX = ( event.clientX - windowHalfX ) / resistanceh;
mouseY = ( event.clientY - windowHalfY ) / resistancew;
var raycaster = new THREE.Raycaster();
raycaster.setFromCamera(mouse, camera);
var intersects = raycaster.intersectObjects(objects);
if ( intersects.length > 0 ) {
if(mousedown){
//do your thing
}
or in your animation updating these values is more accurate I found.
AnimationFrame(){
mouseX = ( event.clientX - windowHalfX ) / resistanceh;
mouseY = ( event.clientY - windowHalfY ) / resistancew;

calculate the x, y position of a canvas point

I'm trying to learn some canvas in html5 and javascript and I want to create those typical Illustrator sun rays:
But my problem is that I want to automate it and make it full screen.
To calculate the coordinates of the points in the middle isn't hard, it's the outer points that I cant seem to get a grip on.
K, so this is what I got.
The problem lies in the for-loop for creating an array for the outer coordinates.
So it starts calculating from the center of the screen.
If it's the first point (we ignore the inner points for now) it takes the x_coordinate variable (which is the horizontal center of the screen) and adds the width_between_rays divided by two (because I want to mimic the picture above with some space between the two upper rays).
The rest of the points are checked if they are divided by two to see if I should add the width_between_rays (should probably be offset or something) or the width_of_rays to the last points cordinates.
Well this seems pretty straight forward but since the window size isn't a fixed size I need some way of calculating where the point should be if, for example; the position of a point is outside the width/height of the screen.
So my way of calculating this doesn't work (I think).
Anyways, can someone (who's obviously smarter than me) point me in the right direction?
function sun_rays(z_index, element, color, number_of_rays, width_of_rays, width_between_rays) {
// Start the canvas stuff
var canvas = document.getElementById(element);
var ctx = canvas.getContext("2d");
console.log();
ctx.canvas.width = $(window).width();
ctx.canvas.height = $(window).width();
ctx.fillStyle = color;
// calculate the window size and center position
var window_width = $(window).width();
var window_hight = $(window).height();
var x_coordinate = window_width / 2;
var y_coordinate = window_hight / 2;
// create an array for the center coordinates
var center_coordinate_array = new Array();
for(i=0; i < number_of_rays; i++){
center_coordinate_array[i] = new Array(x_coordinate, y_coordinate);
}
// create an array for the outer coordinates
var outer_coordinate_array = new Array();
for(i=1; i == number_of_rays*2; i++){
if(i == 1) {
// X
var last_outer_x_coordinate = x_coordinate + (width_between_rays/2);
// Y
if(last_outer_x_coordinate < window_width) {
last_outer_y_coordinate = last_outer_y_coordinate;
} else {
$x_coordinate_difference = last_outer_x_coordinate - window_width;
last_outer_y_coordinate = x_coordinate_difference;
}
center_coordinate_array[i] = new Array(last_outer_x_coordinate, last_outer_y_coordinate);
} else {
if(i % 2 == 0) {
// X
last_outer_x_coordinate = last_outer_x_coordinate + width_of_rays;
// Y
//calculate the y position
center_coordinate_array[i] = new Array(last_outer_x_coordinate);
} else {
// X
last_outer_x_coordinate = last_outer_x_coordinate + width_between_rays;
// Y
//calculate the y position
center_coordinate_array[i] = new Array(last_outer_x_coordinate);
}
}
}
}
It seems like you should use the trig functions to do something like this.
var coordinate_array = [];
var xCoord = 0;
var yCoord = 0;
var angleIncrement = 15;
var i = 0;
//iterate over angles (in degrees) from 0 to 360
for (var theta = 0; theta < 360; theta += angleIncrement) {
//angle is in sector from bottom right to top right corner
if (theta >= 315 || theta <= 45)
{
xCoord = $(window).width();//point on right side of canvas
yCoord = abs($(window).width()/2 * tan(theta));
yCoord = tranformY(theta,yCoord);
}
//angle is in sector from top right to top left corner
else if (theta > 45 && theta <= 135)
{
yCoord = 0; //top is zero
xCoord = abs($(window).height()/2 * tan(theta));
xCoord = transformX(theta, xCoord);
}
//angle is in sector from top left to bottom left corner
else if (theta > 135 && theta <= 225)
{
xCoord = 0; //left edge on a canvas is zero
yCoord = abs($(window).width()/2 * tan(theta);
yCoord = transformY(theta, yCoord);
}
//angle is in sector from bottom left to bottom right corner
else // theta > 225 && theta < 315
{
yCoord = $(window).height();
xCoord = abs($(window).height()/2 * tan(theta));
xCoord = transformX(theta, xCoord);
}
coordinate_array[i++] = new Array(xCoord, yCoord);
}
//Transform from cartesian coordinates to top left is 0,0
function tranformY(theta, y)
{
var centerYCoord = $(window).height()/2;
//if angle falls in top half (Quadrant 1 or 2)
if(theta > 0 && theta < 180)
{
return centerYCoord - y;
}
elseif(theta > 180 && theta < 360)
{
return centerYCoord + y;
}
//coord falls on 0/360 or 180 (vert. median)
return centerYCoord;
}
//Transform from cartesian coordinates to top left is 0,0
function transformX(theta, x)
{
var centerXCoord = $(window).width()/2;
//if angle falls in right half (Quadrant 1 or 4)
if(theta > 270 || theta < 90)
{
return centerXCoord + x;
}
elseif(theta > 90 && theta < 270)
{
return centerXCoord - x;
}
//coordinate falls on 270 or 90 (center)
return centerXCoord;
}
//now draw your rays from the center coordinates to the points in coordinate_array
//NOTE: This code will need to be cleaned up - I just wrote it in the textbox.
The previous code puts the coordinates for the red points into an array.
This problem is by its very nature related to the incremental change of an angle. Your solution is going to need to deal with the angles using trig functions.

Categories