I need to position these 5 elements in circle. And my trigonometry knowledge is too poor to come up with a solution.
http://jsbin.com/acOSeTE/1/edit
Whoever helps please give some explanation on the math formula or forward me to a link with info about it.
I do not need a jQuery solution, since I want to learn what is behind the frameworks.
Thanks.
You'll almost certainly need CSS to position the divs with that kind of precision.
The general formula you're looking for is that for any given radian position on the circle (there are 2π radians in a circle), the x and y are:
x = originX + (Math.cos(radians) * radius);
y = originY + (Math.sin(radians) * radius);
Example: Live Copy | Live Source
CSS (you could use inline styles, I suppose):
#target {
position: absolute;
border: 1px solid black;
width: 1px;
height: 1px;
}
HTML:
<input type="button" id="theButton" value="Start/Stop">
<div id="target"></div>
JavaScript:
(function() {
var radians, maxRadians, target, radius, originX, originY, inc, timer;
radius = 50;
originX = 100;
originY = 100;
radians = 0;
maxRadians = 2 * Math.PI;
inc = 10 / 360;
target = document.getElementById("target");
positionTarget();
function positionTarget() {
var x, y;
x = originX + (Math.cos(radians) * radius);
y = originY + (Math.sin(radians) * radius);
console.log(x + "," + y);
target.style.left = x + "px";
target.style.top = y + "px";
radians += inc;
if (radians > maxRadians) {
radians -= maxRadians;
}
timer = setTimeout(positionTarget, 30);
}
document.getElementById("theButton").onclick = function() {
if (timer) {
clearTimeout(timer);
timer = 0;
}
else {
timer = setTimeout(positionTarget, 30);
}
};
})();
Related
I am attempting to create a 2d tile based game in Javascript, and I need to be able to calculate the angle between two points. I am using the atan2 function to find the angle between two points like so:
function getAngleDegrees(fromX, fromY, toX, toY, force360 = true) {
let deltaX = toX - fromX;
let deltaY = toY - fromY;
let radians = Math.atan2(deltaY, deltaX);
let degrees = (radians * 180) / Math.PI;
if (force360) {
while (degrees >= 360) degrees -= 360;
while (degrees < 0) degrees += 360;
}
return degrees;
}
However, this isn't providing me with the correct result. I have checked the code for logic or math errors and can't find any. No matter what points I input to this function the result will be off by many degrees.
I have created a JS fiddle to visualize the problem:
https://jsfiddle.net/fa6o7wdy/40/
If anyone knows how I can fix my angle function to provide the correct result please help!
Edit:
Here is a picture of the problem:
https://imgur.com/a/OXDCOux
Based on the photo sample you provide, for getting the desired angle you want with current Math.atan() function, you want to reverse first and then rotate the angle by 90 degrees couter clockwise
function getAngleDegrees(fromX,fromY,toX,toY,force360 = true) {
let deltaX = fromX-toX;
let deltaY = fromY-toY; // reverse
let radians = Math.atan2(deltaY, deltaX)
let degrees = (radians * 180) / Math.PI - 90; // rotate
if (force360) {
while (degrees >= 360) degrees -= 360;
while (degrees < 0) degrees += 360;
}
console.log('angle to degree:',{deltaX,deltaY,radians,degrees})
return degrees;
}
or simply + 90 degrees to this line without changing deltaX and deltaY
let degrees = (radians * 180) / Math.PI + 90; // rotate
Note: I haven't test out all possible edge cases
const inBlk = document.createElement('i')
, getXY = (p,xy) => Number(p.split('-')[xy==='x'?0:1])
;
for(let i=0;i<100;i++) // build Grid
{
let nI = inBlk.cloneNode()
, u1 = i%10
;
nI.textContent = u1+'-'+(i-u1)/10
grid.appendChild(nI)
}
let points = [ {x:0, y:0, old:null}, {x:0, y:0, old:null}]
, pN = 0
;
grid.onclick=e=>
{
if (!e.target.matches('i')) return
let elm = e.target.textContent
points[pN].x = getXY(elm, 'x')
points[pN].y = getXY(elm, 'y')
if (points[pN].old ) points[pN].old.classList.remove('color_0', 'color_1')
points[pN].old = e.target
points[pN].old.classList.add(`color_${pN}` )
pN = ++pN %2
if (pN==0) angle.textContent = ` angle: ${getAngleDegrees(points[0],points[1])}°`
}
function getAngleDegrees( from, to, force360 =true)
{
let deltaX = from.x - to.x
, deltaY = from.y - to.y // reverse
, radians = Math.atan2(deltaY, deltaX)
, degrees = (radians * 180) / Math.PI - 90 // rotate
;
if (force360)
{
while (degrees >= 360) degrees -= 360;
while (degrees < 0) degrees += 360;
}
return degrees.toFixed(2)
}
:root { --sz-hw: 26px; }
#grid {
font-family: 'Courier New', Courier, monospace;
font-size : 10px;
margin : calc( var(--sz-hw) /2);
}
#grid i {
display : block;
float : left;
width : var(--sz-hw);
height : var(--sz-hw);
border : 1px solid grey;
text-align : center;
margin : 2px;
line-height: var(--sz-hw);
cursor : pointer;
}
#grid i:nth-child(10n-9) {clear: both; }
.color_0 { background-color: lightcoral; }
.color_1 { background-color: lightgreen; }
#angle { padding: calc( var(--sz-hw) /2) 0 0 calc( var(--sz-hw) *13.4); }
<p id="grid"></p>
<h4 id="angle">angle: ?</h4>
DeltaX toX and fromX needed to be swapped around, same goes for DeltaY. Also, I've subtracted 90 to angle, in order to make 0 degree being North.
The % (mod) operator does same job as your 2 x while loop.
function getAngleDegrees(fromX,fromY,toX,toY,force360 = true) {
let deltaX = fromX - toX;
let deltaY = fromY - toY;
let radians = Math.atan2(deltaY, deltaX)
let degrees = ((radians * 180) / Math.PI) - 90;
if (force360) {
degrees = (degrees + 360) % 360;
}
console.log('angle to degree:',{deltaX,deltaY,radians,degrees})
return degrees;
}
Your code is working correctly, it's just that you have a bit of confusion in your coordinate system.
The angle between 2 points is relative to where you measure from. By subtracting P2 from P1, you're making the angle relative to your starting point. Thus, atan2 is giving you the clockwise angle relative to the X axis.
Traditionally, the X axis is the starting point for rotations, so a horizontal line has an angle of 0:
x = 1;
y = 0;
angle = atan2(y, x) // Equals 0
You've got your grid with Y+ going down, so as Y becomes positive, you'll get clockwise angles from the x-axis.
x = 0;
y = 1;
angle = atan2(y, x) // Equals PI/2, or 90deg
If this is confusing with Y+ going down, you may want to rethink your grid so that Y+ goes up instead.
PS: Good luck on your game!
I am looking for the algorithm that takes 3D coordinates and change them to 2D coordinates.
I tried the steps form this Wikipedia Page : https://en.wikipedia.org/wiki/3D_projection#Perspective_projection
and my code so far is this :
var WIDTH = 1280/2;
var HEIGHT = 720/2;
// Distance from center of Canvas (Camera) with a Field of View of 90 digress, to the Canvas
var disToCanvas = Math.tan(45) * WIDTH/2;
var canvas = document.createElement('canvas');
canvas.width = WIDTH;
canvas.height = HEIGHT;
document.body.appendChild(canvas);
var ctx = canvas.getContext('2d');
var Player = function (){ // Camera
// Camera Coordinates
this.x = 0;
this.y = 0;
this.z = 0;
// Camera Rotation (Angle)
this.rx = 0;
this.ry = 90;
this.rz = 0;
};
var player = new Player();
var Point = function (x, y ,z){
// Point 3D Coordinates
this.x = x;
this.y = y;
this.z = z;
// Point 2D Coordinates
this.X2d = 0;
this.Y2d = 0;
// The function that changes 3D coordinates to 2D
this.update = function (){
var X = (player.x - this.x);
var Y = (player.y - this.y);
var Z = (player.z - this.z);
var Cx = Math.cos(player.rx); // cos(θx)
var Cy = Math.cos(player.ry); // cos(θy)
var Cz = Math.cos(player.rz); // cos(θz)
var Sx = Math.sin(player.rx); // sin(θx)
var Sy = Math.sin(player.ry); // sin(θy)
var Sz = Math.sin(player.rz); // sin(θz)
var Dx = Cy * (Sy*Y + Cz*X) - Sy*Z;
var Dy = Sx * (Cy*Z + Sy * (Sz*Y + Cz*X)) + Cx * (Cy*Y + Sz*X);
var Dz = Cx * (Cy*Z + Sy * (Sz*Y + Cz*X)) - Sx * (Cy*Y + Sz*X);
var Ex = this.x / this.z * disToCanvas; // This isn't 100% correct
var Ey = this.y / this.z * disToCanvas; // This isn't 100% correct
var Ez = disToCanvas; // This isn't 100% correct
this.X2d = Ez/Dz * Dx - Ex + WIDTH/2; // Adding WIDTH/2 to center the camera
this.Y2d = Ez/Dz * Dy - Ez + HEIGHT/2; // Adding HEIGHT/2 to center the camera
}
}
// CREATING, UPDATING AND RENDERING A SQUARE
var point = [];
point[0] = new Point(10, 10, 10);
point[1] = new Point(20, 10, 10);
point[2] = new Point(20, 20, 10);
point[3] = new Point(10, 20, 10);
var run = setInterval(function (){
for (key in point){
point[key].update();
}
ctx.beginPath();
ctx.moveTo(point[0].X2d, point[0].Y2d);
ctx.lineTo(point[1].X2d, point[1].Y2d);
ctx.lineTo(point[2].X2d, point[2].Y2d);
ctx.lineTo(point[3].X2d, point[3].Y2d);
ctx.lineTo(point[0].X2d, point[0].Y2d);
}, 1000/30);
html, body {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
background: rgba(73,72,62,.99);
}
canvas {
position: absolute;
margin: auto;
top: 0;
bottom: 0;
left: 0;
right: 0;
outline: 1px solid white;
}
<html>
<head>
</head>
<body>
</body>
</html>
I want a function that can translate 3D to 2D depending on both position and rotation of the camera.
Taking a look at the Wikipedia page you linked it appears that you have errors in your equations for D. It should be:
var Dx = Cy * (Sz*Y + Cz*X) - Sy*Z;
var Dy = Sx * (Cy*Z + Sy * (Sz*Y + Cz*X)) + Cx * (Cz*Y + Sz*X);
var Dz = Cx * (Cy*Z + Sy * (Sz*Y + Cz*X)) - Sx * (Cz*Y + Sz*X);
Also I think you are using the wrong coordinates for E, which is "the viewer's position relative to the display surface" and should not depend on the coordinates of the point.
The y coordinate of your 2D position appears to contain an error too; you use Ez instead of Ey.
Additionally i can recommend this site. It is written for C++ and OpenGL, but it contains a lot of good explanations and diagrams to get a better understanding of what it is you are trying to do.
The code snippet below calculates the speed of the mouse cursor on the screen. It appears to work correctly, however, I have some questions about how it works.
var div = document.body.children[0],
isOverElem, cX, cY, pX, pY, cTime, pTime;
div.addEventListener('mouseover', function(event) {
if (isOverElem) return;
isOverElem = true;
pX = event.pageX;
pY = event.pageY;
pTime = Date.now();
this.addEventListener('mousemove', move);
});
div.addEventListener('mouseout', function(event) {
if ( event.relatedTarget && !this.contains(event.relatedTarget) ) {
isOverElem = false;
this.removeEventListener('mousemove', move);
this.innerHTML = '';
}
});
function move(event) {
var speed;
cX = event.pageX;
cY = event.pageY;
cTime = Date.now();
if (pTime == cTime) return; // mouseover with mousemove
speed = Math.sqrt( Math.pow(pX - cX, 2) + Math.pow(pY - cY, 2) ) / (cTime - pTime);
this.innerHTML = 'Mouse speed: ' + speed;
setTimeout(function() {
pX = cX;
pY = cY;
pTime = cTime;
}, 10);
}
div {
width: 80%;
height: 200px;
padding: 10px;
line-height: 200px;
font-size: 1.5em;
border: 2px solid red;
}
<div></div>
I don't understand the following line:
speed = Math.sqrt( Math.pow(pX - cX, 2) + Math.pow(pY - cY, 2) ) / (cTime - pTime);
Why can't this just use (pX - cX)/(cTime - pTime)? Why does it require a more complicated equation that includes Math.sqrt and Math.pow? I am interested in the algorithm and whether the script is correct.
(pX - cX) / (cTime - pTime) would be the horizontal speed.
(pY - cY) / (cTime - pTime) would be the vertical speed.
If you go horizontally and then vertically, you would travel more distance then if you would have gone straight from X to Y.
So, to get the minimal distance between X and Y, you have to use the distance formula.
I am using this color wheel picker, and I'm trying to add a div as the dragger instead of having it embedded in the canvas.
I created an outer div (a wrapper), and inserted a div (dragger), then the canvas. I made the dragger div's position to absolute. Then in the redraw(e) function, I set the left and top to the following:
dragger.style.left = currentX + 'px';
dragger.style.top = currentY + 'px';
This works i.e. the dragger moves when it should, but the it's at the wrong position.
How can I get the dragger to be at the same position as the cursor?
JSFiddle
var b = document.body;
var c = document.getElementsByTagName('canvas')[0];
var a = c.getContext('2d');
var wrapper = document.getElementById('wrapper');
var dragger = document.createElement('div');
dragger.id = 'dragger';
wrapper.appendChild(dragger);
wrapper.insertBefore(dragger, c);
document.body.clientWidth; // fix bug in webkit: http://qfox.nl/weblog/218
(function() {
// Declare constants and variables to help with minification
// Some of these are inlined (with comments to the side with the actual equation)
var doc = document;
doc.c = doc.createElement;
b.a = b.appendChild;
var width = c.width = c.height = 400,
label = b.a(doc.c("p")),
input = b.a(doc.c("input")),
imageData = a.createImageData(width, width),
pixels = imageData.data,
oneHundred = input.value = input.max = 100,
circleOffset = 10,
diameter = 380, //width-circleOffset*2,
radius = 190, //diameter / 2,
radiusPlusOffset = 200, //radius + circleOffset
radiusSquared = radius * radius,
two55 = 255,
currentY = oneHundred,
currentX = -currentY,
wheelPixel = 16040; // circleOffset*4*width+circleOffset*4;
// Math helpers
var math = Math,
PI = math.PI,
PI2 = PI * 2,
sqrt = math.sqrt,
atan2 = math.atan2;
// Setup DOM properties
b.style.textAlign = "center";
label.style.font = "2em courier";
input.type = "range";
// Load color wheel data into memory.
for (y = input.min = 0; y < width; y++) {
for (x = 0; x < width; x++) {
var rx = x - radius,
ry = y - radius,
d = rx * rx + ry * ry,
rgb = hsvToRgb(
(atan2(ry, rx) + PI) / PI2, // Hue
sqrt(d) / radius, // Saturation
1 // Value
);
// Print current color, but hide if outside the area of the circle
pixels[wheelPixel++] = rgb[0];
pixels[wheelPixel++] = rgb[1];
pixels[wheelPixel++] = rgb[2];
pixels[wheelPixel++] = d > radiusSquared ? 0 : two55;
}
}
// Bind Event Handlers
input.onchange = redraw;
c.onmousedown = doc.onmouseup = function(e) {
// Unbind mousemove if this is a mouseup event, or bind mousemove if this a mousedown event
doc.onmousemove = /p/.test(e.type) ? 0 : (redraw(e), redraw);
}
// Handle manual calls + mousemove event handler + input change event handler all in one place.
function redraw(e) {
// Only process an actual change if it is triggered by the mousemove or mousedown event.
// Otherwise e.pageX will be undefined, which will cause the result to be NaN, so it will fallback to the current value
currentX = e.pageX - c.offsetLeft - radiusPlusOffset || currentX;
currentY = e.pageY - c.offsetTop - radiusPlusOffset || currentY;
// Scope these locally so the compiler will minify the names. Will manually remove the 'var' keyword in the minified version.
var theta = atan2(currentY, currentX),
d = currentX * currentX + currentY * currentY;
// If the x/y is not in the circle, find angle between center and mouse point:
// Draw a line at that angle from center with the distance of radius
// Use that point on the circumference as the draggable location
if (d > radiusSquared) {
currentX = radius * math.cos(theta);
currentY = radius * math.sin(theta);
theta = atan2(currentY, currentX);
d = currentX * currentX + currentY * currentY;
}
label.textContent = b.style.background = hsvToRgb(
(theta + PI) / PI2, // Current hue (how many degrees along the circle)
sqrt(d) / radius, // Current saturation (how close to the middle)
input.value / oneHundred // Current value (input type="range" slider value)
)[3];
dragger.style.left = currentX + 'px';
dragger.style.top = currentY + 'px';
// Reset to color wheel and draw a spot on the current location.
a.putImageData(imageData, 0, 0);
// Draw the current spot.
// I have tried a rectangle, circle, and heart shape.
/*
// Rectangle:
a.fillStyle = '#000';
a.fillRect(currentX+radiusPlusOffset,currentY+radiusPlusOffset, 6, 6);
*/
/*
// Circle:
a.beginPath();
a.strokeStyle = '#000';
a.arc(~~currentX+radiusPlusOffset,~~currentY+radiusPlusOffset, 4, 0, PI2);
a.stroke();
*/
// Heart:
a.font = "1em arial";
a.fillText("♥", currentX + radiusPlusOffset - 4, currentY + radiusPlusOffset + 4);
}
// Created a shorter version of the HSV to RGB conversion function in TinyColor
// https://github.com/bgrins/TinyColor/blob/master/tinycolor.js
function hsvToRgb(h, s, v) {
h *= 6;
var i = ~~h,
f = h - i,
p = v * (1 - s),
q = v * (1 - f * s),
t = v * (1 - (1 - f) * s),
mod = i % 6,
r = [v, q, p, p, t, v][mod] * two55,
g = [t, v, v, q, p, p][mod] * two55,
b = [p, p, t, v, v, q][mod] * two55;
return [r, g, b, "rgb(" + ~~r + "," + ~~g + "," + ~~b + ")"];
}
// Kick everything off
redraw(0);
/*
// Just an idea I had to kick everything off with some changing colors…
// Probably no way to squeeze this into 1k, but it could probably be a lot smaller than this:
currentX = currentY = 1;
var interval = setInterval(function() {
currentX--;
currentY*=1.05;
redraw(0)
}, 7);
setTimeout(function() {
clearInterval(interval)
}, 700)
*/
})();
#wrapper {
width: 400px;
height: 400px;
position: relative;
}
#dragger {
background-color: orange;
width: 15px;
height: 15px;
border-radius: 50%;
position: relative;
}
<div id='wrapper'>
<canvas id="c"></canvas>
</div>
I got it pretty close by manipulating the currentX and currentY coordinates. The commenter above is close to the solution; ~50% of the height and width must be added to the relative position. I also recommend using the z-index property on the dragger, if you want the heart to be on top and the dragger between it and the color wheel.
Final and correct version with proper offset: Fiddle
If the dragger is reduced in size from 15px to 5px, add 7 and 4px respectively:
https://jsfiddle.net/6n9zwahL/ (fixed amounts) or https://jsfiddle.net/mak3Lace/ (non-fixed, programmatic solution).
dragger.style.left = (currentX + radiusPlusOffset + (radiusPlusOffset/30)) + 'px';
dragger.style.top = (currentY + radiusPlusOffset+(radiusPlusOffset/40)) + 'px';
Preserved historical responses until I have time to edit:
Forked Fiddle
code - here it is being done manually in pixels but I'd create a variable and set it to a more accurate value by querying the browser for width:
dragger.style.left = currentX + 210 + 'px';
dragger.style.top = currentY +195 + 'px';
New Fiddle, with dragger exactly aligned to heart
New Positions:
dragger.style.left = currentX + 204 + 'px';
dragger.style.top = currentY +199 + 'px';
Added a pointer for improved ux: https://jsfiddle.net/4cLpvu2m/
To elaborate further, use the code you have for heart position:
currentX + radiusPlusOffset - 4, currentY + radiusPlusOffset + 4
and query browser for position of your dragger div. Then simply subtract x from x-dragger and y from y-dragger to get the difference. Add the difference to your dragger.style.left and so on (it happens that those numbers are ~204 and 199).
Another fork with alerts indicating x/y position values
Fork of Jessica's update, removing +/-4 to align elements:
https://jsfiddle.net/hf5k2ecg/
Just add radiusPlusOffset here:
dragger.style.left = currentX + radiusPlusOffset + 'px';
dragger.style.top = currentY + radiusPlusOffset + 'px';
https://jsfiddle.net/qsvmyh3z/1/
Also adjust/subtract the height & width of the the dragger to pin point at the right location.
Basically I've managed to layout my DIV elements into a circle shape but I've not managed to work out how to calculate the deg of rotation need to have them face OUTWARD from the center of the circle.
$(document).ready(function(){
var elems = document.getElementsByClassName('test_box');
var increase = Math.PI * 2 / elems.length;
var x = 0, y = 0, angle = 0;
for (var i = 0; i < elems.length; i++) {
var elem = elems[i];
// modify to change the radius and position of a circle
x = 400 * Math.cos(angle) + 700;
y = 400 * Math.sin(angle) + 700;
elem.style.position = 'absolute';
elem.style.left = x + 'px';
elem.style.top = y + 'px';
//need to work this part out
var rot = 45;
elem.style['-moz-transform'] = "rotate("+rot+"deg)";
elem.style.MozTransform = "rotate("+rot+"deg)";
elem.style['-webkit-transform'] = "rotate("+rot+"deg)";
elem.style['-o-transform'] = "rotate("+rot+"deg)";
elem.style['-ms-transform'] = "rotate("+rot+"deg)";
angle += increase;
console.log(angle);
}
});
does anyone have to knowledge on how I can do this.
Cheers -C
Note that rot depends on angle, except angle is in radians.
DRY, so either convert from angle to rot:
// The -90 (degrees) makes the text face outwards.
var rot = angle * 180 / Math.PI - 90;
Or just use angle when setting the style (but use radians as a unit), and drop rot's declaration:
// The -0.5*Math.PI (radians) makes the text face outwards.
elem.style.MozTransform = "rotate("+(angle-0.5*Math.PI)+"rad)";
Try this:
var rot = 90 + (i * (360 / elems.length));
Demo at http://jsfiddle.net/gWZdd/
I've added the 90 degrees at the start there to ensure the baseline of the divs face towards the centre, however you can adjust this to suit your needs.