Flatten 3d Coordinates to 2d Coordinates - javascript

I am trying to make some code that can take in 3d coordinates (x, y, z), and return 2d coordinates (x, y) by translating the x and y values towards the perspective point based on the z value.
My code is :
translate() {
this.distance = Math.sqrt((this.x**2)+(this.y**2));
this.bear = (180/Math.PI)*(Math.asin(this.y/this.distance));
this.transX = (this.x + Math.cos(this.bear)*this.z);
this.transY = (this.y + Math.sin(this.bear)*this.z);
}
and gets me this:
To me, the code looks like it should just move the non-square points inwards but it doesn't.
Does anyone have any ideas to make this work? or are there other ways to do this?

To get a perspective projection at it's simplest form, you don't have to do much. We simply determine a scale and apply the following calculation to all 3d coordinates:
projectedPoint = point * scale / (pointZ + scale)
Here's an example:
class Point {
constructor(x, y, z) {
this.x = x;
this.y = y;
this.z = z;
}
}
let cube = [
new Point(-50, -50, -50), new Point(50, -50, -50), new Point(50, 50, -50), new Point(-50, 50, -50),
new Point(-50, -50, 50), new Point(50, -50, 50), new Point(50, 50, 50), new Point(-50, 50, 50)
];
function project(vertices) {
let scale = 200;
let projectedX, projectedY, x, y, z, point;
let centerX = canvas.width / 2;
let centerY = canvas.height / 2;
for (let a = 0; a < vertices.length; a++) {
point = vertices[a];
ctx.beginPath();
projectedX = centerX + point.x * scale / (point.z + scale);
projectedY = centerY + point.y * scale / (point.z + scale);
ctx.arc(projectedX, projectedY, 5, 0, 2 * Math.PI);
ctx.fill();
ctx.closePath();
}
}
let canvas = document.getElementById("canvas");
let ctx = canvas.getContext("2d");
ctx.fillStyle = "blue";
project(cube);
<canvas id="canvas"></canvas>

Related

How to test if a point is in a rectangle area which rotates an angle?

I am trying to test if a point is inside a rectangle area that rotates an angle around (x, y), like the image below. This is language agnostic problem but I am working with HTML5 canvas now.
Suppose the point we need to test is (x1, y1), the width of the rectangle is 100 and the height is 60. In normal cartesian coordinate system the rectangle ABCD top left point A is (canvas.width / 2, canvas.height / 2 -rect.height/2). I assume that (canvas.width / 2, canvas.height / 2) is at the middle of line AB where B is (canvas.width / 2, canvas.height / 2 + rect.height /2).
I have read some resources here and wrote a test project, but it doesn't test the correct area. In my test project I want the this effect:
if the mouse is on a point that is within the range of the testing rectangle area a dot will be displayed around the mouse. If it is outside the rectangle nothing will be displayed.
However my test project looks like this: (Note that although I used the vector based technique to test the point in a rotated rectangle area, the test area remains the rectangle before rotation)
// Detecting a point is in a rotated rectangle area
// using vector based method
const canvas = document.getElementById('canvas');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
const ctx = canvas.getContext('2d');
class Rectangle {
constructor(x, y, width, height) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
this.searchPoint = { x: 0, y: 0};
this.binding();
}
binding() {
let self = this;
window.addEventListener('mousemove', e => {
if (!e) return;
let rect = canvas.getBoundingClientRect();
let mx = e.clientX - rect.left - canvas.clientLeft;
let my = e.clientY - rect.top - canvas.clientTop;
self.searchPoint = { x: mx, y: my };
});
}
}
let rect = new Rectangle(canvas.width /2, canvas.height /2 - 30, 100, 60);
function vector(p1, p2) {
return {
x: (p2.x - p1.x),
y: (p2.y - p1.y)
};
}
function point(x, y) {
return { x, y };
}
// Vector dot operation
function dot(a, b) {
return a.x * b.x + a.y * b.y;
}
function pointInRect(p, rect, angle) {
let a = newPointTurningAngle(0, -rect.height / 2, angle);
let b = newPointTurningAngle(0, rect.height / 2, angle);
let c = newPointTurningAngle(rect.width, rect.height / 2, angle);
let AB = vector(a, b);
let AM = vector(a, p);
let BC = vector(b, c);
let BM = vector(b, p);
let dotABAM = dot(AB, AM);
let dotABAB = dot(AB, AB);
let dotBCBM = dot(BC, BM);
let dotBCBC = dot(BC, BC);
return 0 <= dotABAM && dotABAM <= dotABAB && 0 <= dotBCBM && dotBCBM <= dotBCBC;
}
function drawLine(x, y) {
ctx.strokeStyle = 'black';
ctx.lineTo(x, y);
ctx.stroke();
}
function text(text, x, y) {
ctx.font = "18px serif";
ctx.fillText(text, x, y);
}
function newPointTurningAngle(nx, ny, angle) {
return {
x: nx * Math.cos(angle) - ny * Math.sin(angle),
y: nx * Math.sin(angle) + ny * Math.cos(angle)
};
}
function animate() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.moveTo(canvas.width / 2, 0);
drawLine(canvas.width /2, canvas.height / 2);
ctx.moveTo(0, canvas.height / 2);
drawLine(canvas.width / 2, canvas.height /2);
let angle = -Math.PI / 4;
ctx.setTransform(Math.cos(angle), Math.sin(angle), -Math.sin(angle), Math.cos(angle), canvas.width / 2, canvas.height / 2);
//ctx.setTransform(1, 0, 0, 1, canvas.width/2, canvas.height / 2);
ctx.strokeStyle = 'red';
ctx.strokeRect(0, -rect.height / 2, rect.width, rect.height);
let p = newPointTurningAngle(rect.searchPoint.x - canvas.width / 2, rect.searchPoint.y - canvas.height / 2, angle);
let testResult = pointInRect(p, rect, angle);
if (testResult) {
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.beginPath();
ctx.fillStyle = 'black';
ctx.arc(rect.searchPoint.x, rect.searchPoint.y, 5, 0, Math.PI * 2);
ctx.fill();
}
ctx.setTransform(1, 0, 0, 1, 0, 0);
text('searchPoint x: ' + rect.searchPoint.x + ', y: ' + rect.searchPoint.y, 60, 430);
text('x: ' + canvas.width / 2 + ', y: ' + canvas.height / 2, 60, 480);
requestAnimationFrame(animate);
}
animate();
<canvas id='canvas'></canvas>
Updated Solution
I am still using the vector based method as followed:
0 <= dot(AB,AM) <= dot(AB,AB) &&
0 <= dot(BC,BM) <= dot(BC,BC)
Now I have changed the point's rotated angle and the corner point coordinates so the point can be detected in the rectangle. The corner points are already in the rotated coordinate system so they don't need to be translated, however the point of the mouse location needs to be translated before testing it in the rectangle area.
In setTransform method the angle rotated is positive when rotated clockwise, the form is :
ctx.setTransform(angle_cosine, angle_sine, -angle_sine, angle_cosine, x, y);
So when calculating the point's new coordinate after rotating an angle, the formula need to change to this so that the angle is also positive when rotated clockwise:
new_x = x * angle_cosine + y * angle_sine;
new_y = -x * angle_sine + y * angle_cos;
// Detecting a point is in a rotated rectangle area
// using vector based method
const canvas = document.getElementById('canvas');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
const ctx = canvas.getContext('2d');
class Rectangle {
constructor(x, y, width, height) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
this.searchPoint = { x: 0, y: 0};
this.binding();
}
binding() {
let self = this;
window.addEventListener('mousemove', e => {
if (!e) return;
let rect = canvas.getBoundingClientRect();
let mx = e.clientX - rect.left - canvas.clientLeft;
let my = e.clientY - rect.top - canvas.clientTop;
self.searchPoint = { x: mx, y: my };
});
}
}
let rect = new Rectangle(canvas.width /2, canvas.height /2 - 30, 100, 60);
function vector(p1, p2) {
return {
x: (p2.x - p1.x),
y: (p2.y - p1.y)
};
}
function point(x, y) {
return { x, y };
}
// Vector dot operation
function dot(a, b) {
return a.x * b.x + a.y * b.y;
}
function pointInRect(p, rect) {
let a = { x: 0, y: -rect.height / 2};
let b = { x: 0, y: rect.height / 2};
let c = { x: rect.width, y: rect.height / 2};
text('P x: ' + p.x.toFixed() + ', y: ' + p.y.toFixed(), 60, 430);
text('A x: ' + a.x.toFixed() + ', y: ' + a.y.toFixed(), 60, 455);
text('B x: ' + b.x.toFixed() + ', y: ' + b.y.toFixed(), 60, 480);
let AB = vector(a, b);
let AM = vector(a, p);
let BC = vector(b, c);
let BM = vector(b, p);
let dotABAM = dot(AB, AM);
let dotABAB = dot(AB, AB);
let dotBCBM = dot(BC, BM);
let dotBCBC = dot(BC, BC);
return 0 <= dotABAM && dotABAM <= dotABAB && 0 <= dotBCBM && dotBCBM <= dotBCBC;
}
function drawLine(x, y) {
ctx.strokeStyle = 'black';
ctx.lineTo(x, y);
ctx.stroke();
}
function text(text, x, y) {
ctx.font = "18px serif";
ctx.fillText(text, x, y);
}
function newPointTurningAngle(nx, ny, angle) {
let cos = Math.cos(angle);
let sin = Math.sin(angle);
return {
x: nx * cos + ny * sin,
y: -nx * sin + ny * cos
};
}
function animate() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.moveTo(canvas.width / 2, 0);
drawLine(canvas.width /2, canvas.height / 2);
ctx.moveTo(0, canvas.height / 2);
drawLine(canvas.width / 2, canvas.height /2);
let angle = - Math.PI / 4;
ctx.setTransform(Math.cos(angle), Math.sin(angle), -Math.sin(angle), Math.cos(angle), canvas.width / 2, canvas.height / 2);
ctx.strokeStyle = 'red';
ctx.strokeRect(0, -rect.height / 2, rect.width, rect.height);
let p = newPointTurningAngle(rect.searchPoint.x - canvas.width / 2, rect.searchPoint.y - canvas.height / 2, angle);
ctx.setTransform(1, 0, 0, 1, 0, 0);
let testResult = pointInRect(p, rect);
if (testResult) {
ctx.beginPath();
ctx.fillStyle = 'black';
ctx.arc(rect.searchPoint.x, rect.searchPoint.y, 5, 0, Math.PI * 2);
ctx.fill();
}
ctx.setTransform(1, 0, 0, 1, 0, 0);
text('searchPoint x: ' + rect.searchPoint.x + ', y: ' + rect.searchPoint.y, 60, 412);
text('x: ' + canvas.width / 2 + ', y: ' + canvas.height / 2, 60, 510);
requestAnimationFrame(animate);
}
animate();
<canvas id='canvas'></canvas>
Assuming that you know how to check whether a dot is in the rectangle the approach to solution is to rotate and translate everything (dot and rectangle) to "normalized" coordinating system (Cartesian coordinate system that is familiar to us) and then to check it trivially.
For more information you should check Affine transformations. The good link where you could start is
http://www.mathworks.com/discovery/affine-transformation.html?requestedDomain=www.mathworks.com
As you can see on this Codepen i did (to detect 2 rotate rect collide).
You have to check the 2 projections of your point (in my case, the 4 points of a rect) and look if the projections are on the other rect
You have to handle the same thing but only for a point and a rect instead of 2 rects
All projections are not colliding
All projections are colliding
required code for codepen link
The browser always report mouse position untransformed (==unrotated).
So to test if the mouse is inside a rotated rectangle, you can:
Get the unrotated mouse position from the mouse event (relative to the canvas).
Rotate the mouse x,y versus the rotation point by the same rotation as the rectangle.
Test if the mouse is inside the rectangle. Now that both the rect and the mouse position have been similarly rotated, you can just test as if the mouse and rect were unrotated.
Annotated code and a Demo:
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
function reOffset(){
var BB=canvas.getBoundingClientRect();
offsetX=BB.left;
offsetY=BB.top;
}
var offsetX,offsetY;
reOffset();
window.onscroll=function(e){ reOffset(); }
window.onresize=function(e){ reOffset(); }
var isDown=false;
var startX,startY;
var rect=makeRect(50,20,35,20,Math.PI/4,60,30);
function makeRect(x,y,w,h,angle,rotationPointX,rotationPointY){
return({
x:x,y:y,width:w,height:h,
rotation:angle,rotationPoint:{x:rotationPointX,y:rotationPointY},
});
}
drawRect(rect);
$("#canvas").mousedown(function(e){handleMouseDown(e);});
function drawRect(r){
var rx=r.rotationPoint.x;
var ry=r.rotationPoint.y;
// demo only, draw the rotation point
dot(rx,ry,'blue');
// draw the rotated rect
ctx.translate(rx,ry);
ctx.rotate(r.rotation);
ctx.strokeRect(rect.x-rx,rect.y-ry,r.width,r.height);
// always clean up, undo the transformations (in reverse order)
ctx.rotate(-r.rotation);
ctx.translate(-rx,-ry);
}
function dot(x,y,fill){
ctx.fillStyle=fill;
ctx.beginPath();
ctx.arc(x,y,3,0,Math.PI*2);
ctx.fill();
}
function handleMouseDown(e){
// tell the browser we're handling this event
e.preventDefault();
e.stopPropagation();
// get mouse position relative to canvas
mouseX=parseInt(e.clientX-offsetX);
mouseY=parseInt(e.clientY-offsetY);
// rotate the mouse position versus the rotationPoint
var dx=mouseX-rect.rotationPoint.x;
var dy=mouseY-rect.rotationPoint.y;
var mouseAngle=Math.atan2(dy,dx);
var mouseDistance=Math.sqrt(dx*dx+dy*dy);
var rotatedMouseX=rect.rotationPoint.x+mouseDistance*Math.cos(mouseAngle-rect.rotation);
var rotatedMouseY=rect.rotationPoint.y+mouseDistance*Math.sin(mouseAngle-rect.rotation);
// test if rotated mouse is inside rotated rect
var mouseIsInside=rotatedMouseX>rect.x &&
rotatedMouseX<rect.x+rect.width &&
rotatedMouseY>rect.y &&
rotatedMouseY<rect.y+rect.height;
// draw a dot at the unrotated mouse position
// green if inside rect, otherwise red
var hitColor=mouseIsInside?'green':'red';
dot(mouseX,mouseY,hitColor);
}
body{ background-color: ivory; }
#canvas{border:1px solid red; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<h4>Clicks inside rect are green, otherwise red.</h4>
<canvas id="canvas" width=512 height=512></canvas>

Simplest way to plot points randomly inside a circle

I have a basic JSFiddle whereby I want to have random points plotted inside a circle.
But I do not know how to limit the points to be inside the circle.
This is what I currently have:
var ctx = canvas.getContext('2d'),
count = 1000, // number of random points
cx = 150,
cy = 150,
radius = 148;
ctx.beginPath();
ctx.moveTo(cx, cy);
ctx.arc(canvas.width/2, canvas.height/2, radius, 0, 2*Math.PI);
ctx.closePath();
ctx.fillStyle = '#00000';
ctx.fill();
// create random points
ctx.fillStyle = '#ffffff';
while(count) {
// randomise x:y
ctx.fillRect(x + canvas.width/2, y + canvas.height/2, 2, 2);
count--;
}
How would i go about generating random (x,y) coordinates to plot random points inside the circle?
My current fiddle: http://jsfiddle.net/e8jqdxp3/
To plot points randomly in a circle, you can pick a random value from the radius squared, then square root it, pick a random angle, and convert the polar coordinate to rectangular. The square / square root step ensures that we get a uniform distribution (otherwise most points would be near the center of the circle).
So the formula to plot a random point in the circle is the following, where r' is a random value between 0 and r2, and θ is a random value between 0 and 2π:
Screenshot of result:
Live Demo:
var canvas = document.getElementById("thecanvas");
var ctx = canvas.getContext('2d'),
count = 1000, // number of random points
cx = 150,
cy = 150,
radius = 148;
ctx.fillStyle = '#CCCCCC';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = '#000000';
ctx.beginPath();
ctx.moveTo(cx, cy);
ctx.arc(canvas.width / 2, canvas.height / 2, radius, 0, 2 * Math.PI);
ctx.closePath();
ctx.fill();
// create random points
ctx.fillStyle = '#ffffff';
while (count) {
var pt_angle = Math.random() * 2 * Math.PI;
var pt_radius_sq = Math.random() * radius * radius;
var pt_x = Math.sqrt(pt_radius_sq) * Math.cos(pt_angle);
var pt_y = Math.sqrt(pt_radius_sq) * Math.sin(pt_angle);
ctx.fillRect(pt_x + canvas.width / 2, pt_y + canvas.width / 2, 2, 2);
count--;
}
<canvas id="thecanvas" width="400" height="400"></canvas>
JSFiddle Version: https://jsfiddle.net/qc735bqw/
Randomly pick dSquared (0..radius^2) and theta (0..2pi), then
x = sqrt(dSquared) cos(theta)
y = sqrt(dSquared) sin(theta)
JSFiddle
var ctx = canvas.getContext('2d'),
count = 1000, // number of random points
cx = canvas.width/2,
cy = canvas.height/2,
radius = 148;
ctx.beginPath();
ctx.moveTo(cx, cy);
ctx.arc(0+canvas.width/2, 0+canvas.height/2, radius, 0, 2*Math.PI);
ctx.closePath();
ctx.fillStyle = '#00000';
ctx.fill();
ctx.fillStyle = '#ffffff';
while(count) {
var x = Math.random() * canvas.width;
var y = Math.random() * canvas.height;
var xDiff = cx - x;
var yDiff = cy - y;
if(Math.sqrt(xDiff*xDiff+yDiff*yDiff)<radius)
{
ctx.fillRect(x, y, 2, 2);
count--;
}
}
This worked for me:
const getRandomCoordinateInCircle = radius => {
var angle = Math.random() * Math.PI * 2;
const x = Math.cos(angle) * radius * Math.random();
const y = Math.sin(angle) * radius * Math.random();
return { x, y };
};
console.log(getRandomCoordinateInCircle(1000);
// { x: 118.35662725763385, y: -52.60516556856313 }
Returns a random point with { x: 0, y: 0} as the centre of the circle.

Positioning and rotating a mesh in three.js

I am trying to build something like this site hero-unit: http://www.nodeplus.com.cn/
My approach is to make a temporary 2d canvas, print out my message to it, scan the pixels and then use them to position the meshes in 3d space:
// STEP 1: if a pixel is detected on every gridX / gridY step, put it in the pos array
for (y = 0; y <= height; y += gridY) {
for (x = 0; x <= width; x += gridX) {
if (buffer32[y * width + x]) {
positions.push({x: x, y: y});
}
}
}
then i put them in 3d space accordingly
for (i = 0; i < len; i++) {
pos = positions[i];
shape = new Shape({
x: pos.x / 10,
y: -pos.y / 10,
z: Math.random() * 4
});
shape.init(scene);
shapes.push(shape);
}
Thats all fine and good, but i want them to be positioned at the scene center.
Because of the way im scanning the 2d canvas and getting the positions from there (starting at 0, 0), they get some very weird positioning in 3d space with three.js (because i have pixels that are positioned like {x: 1600, y:500} on bigger screens)
I had to put a strange camera.lookAt position just to look at it, something like:
camera.position.set(100, 0, 80);
camera.lookAt({
x: 95,
y: -50,
z: scene.position.z
});
This kinda works for now, but its not a good option for me. I want to rotate the camera around the letters and dont want to rotate it around some weird position like this.
You can check out the code here:
var heroUnit = (function(self) {
function Shape(pos) {
this.pos = pos;
this.mesh = null;
this.radius = 2 + Math.random() * 2;
this.speed = 0.4 + Math.random() * 0.4;
}
Shape.prototype.init = function(scene) {
var geometry, material, mesh,
v1, v2, v3;
geometry = new THREE.Geometry();
v1 = new THREE.Vector3(this.pos.x - this.radius / 2, this.pos.y - this.radius / 2, this.pos.z - this.radius / 2);
v2 = new THREE.Vector3(this.pos.x + this.radius / 2, this.pos.y - this.radius / 2, this.pos.z - this.radius / 2);
v3 = new THREE.Vector3(this.pos.x + this.radius / 2, this.pos.y + this.radius / 2, this.pos.z + this.radius / 2);
geometry.vertices.push(v1);
geometry.vertices.push(v2);
geometry.vertices.push(v3);
geometry.faces.push(new THREE.Face3(0, 1, 2));
geometry.computeFaceNormals();
material = new THREE.MeshLambertMaterial({color: Math.random() * 0xFFFFFF});
mesh = new THREE.Mesh(geometry, material);
this.mesh = mesh;
this.mesh.receiveShadow = true;
//this.mesh.scale.set(Math.random() * 20, Math.random() * 20, Math.random() * 20);
this.mesh.rotation.set(0, 0, 0)
scene.add(this.mesh);
}
var width, height,
aspectRatio, pov,
near, far,
scene, camera,
renderer,
effectPositions,
shapes, light;
function init(string) {
// constants
width = window.innerWidth;
height = window.innerHeight;
pov = 45;
aspectRatio = width / height;
near = 0.1;
far = 1000;
// three.js specific stuff
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(pov, aspectRatio, near, far);
renderer = new THREE.WebGLRenderer();
light = new THREE.DirectionalLight(0x000000, 0.6);
// containers
effectPositions = getPixels(string);
shapes = [];
// set up light
light.position.set(100, 0, 200);
light.castShadow = true;
scene.add(light);
// set size to renderer, color it in dark blue & append it to dom
renderer.setSize(width, height);
renderer.setClearColor(0x17293a);
document.body.appendChild(renderer.domElement);
// set up camera
camera.position.set(100, 0, 80);
camera.lookAt({
x: 50,
y: -50,
z: scene.position.z
});
scene.add(camera);
generateShapes(effectPositions);
}
// generate 3d shapes according to our 2d positions array
function generateShapes(positions) {
var i, n, len,
pos, shape,
maxIterations
len = positions.length;
maxIterations = 2;
for (n = 0; n <= maxIterations; n++) {
for (i = 0; i < len; i++) {
pos = positions[i];
shape = new Shape({
x: pos.x / 10,
y: -pos.y / 10,
z: Math.random() * 4
});
shape.init(scene);
shapes.push(shape);
}
}
}
// get 2d positions
function getPixels(string) {
var canvas, ctx,
idata, buffer32,
x, y,
gridX, gridY,
positions;
//make temp canvas on which to draw and get pixel data
canvas = document.createElement('canvas');
ctx = canvas.getContext('2d');
positions = [];
string = string.toUpperCase().split("").join(String.fromCharCode(8202));
canvas.width = width;
canvas.height = height;
//document.body.appendChild(canvas);
ctx.fillStyle = '#000';
ctx.font = 'bold 260px Arial';
ctx.fillText(string, width / 2 - ctx.measureText(string).width / 2, height / 2);
idata = ctx.getImageData(0, 0, width, height);
buffer32 = new Uint32Array(idata.data.buffer);
gridX = gridY = 14;
// if a pixel is detected on every gridX / gridY step, put it in the pos array
for (y = 0; y <= height; y += gridY) {
for (x = 0; x <= width; x += gridX) {
if (buffer32[y * width + x]) {
positions.push({x: x, y: y});
}
}
}
// return pos array
return positions;
}
// render function
function render(ts) {
renderer.render(scene, camera);
shapes.forEach(function(shape) {
// shape.mesh.position.x = shape.dist + (Math.sin(ts / 500)) * shape.speed;
})
}
return {
init: init,
render: render
}
}(heroUnit || {}))
// init our program
heroUnit.init('100');
// redraw each frame
(function drawFrame(ts) {
window.requestAnimationFrame(drawFrame);
heroUnit.render(ts);
}());
<script src="//cdnjs.cloudflare.com/ajax/libs/three.js/r71/three.js"></script>
Any ideas and suggestions are more then welcome, cheers

Drawing Object Arrays with JS Canvas

I'm new to JS, and I have not gone into jQuery or such yet. All that I'm trying to do is draw 4 simple circles on the canvas. I put the objects into an array because I planned to possibly expand in the future, but that information is irrelevant.
What I need help with, is understanding the basics of how to create objects within an array and then display them on a canvas. Here is my dysfunctionate code:
var canvas = document.getElementById("canvas"),
ctx = canvas.getContext("2d");
var W = 400, H = 500
canvas.width = W, canvas.height = H;
var ball = [3];
for (i===0;i<=3; i++){
ball[i] = {
x: (Math.floor(Math.random() * 350) + 50),
y: (Math.floor(Math.random() * 450) + 50),
color: "red",
radius: (Math.floor(Math.random() * 35) + 15),
draw: balls(x, y, color, radius)
};
}
function balls(x, y, color, radius){
ctx.beginPath();
ctx.arc(x, y, radius, 0, 2*Math.PI, false);
ctx.fillStyle = color;
ctx.fill();
ctx.closePath();
}
You are making it very complex use this method:
function ball(){
this.x = Math.random() * 100;
this.y = Math.random() * 100;
this.radius = Math.random() * 10;
this.color = "#" + (Math.random() * 16777215).toString(16);
this.draw = function(){
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, 2*Math.PI, false);
ctx.fillStyle = color;
ctx.fill();
ctx.closePath();
}
}
var balls = [];
for(var i = 0; i < 4; i++) {
balls[i] = new ball();
balls[i].draw();
}
You can simply assign draw to the balls function like so:
ball[i] = {
....
draw: balls
};
Then to draw it, once you defined ball[i] call it's draw() function:
var b = ball[i];
b.draw(b.x, b.y, b.color ,b.radius);
Example Here
Edit: Using no parentheses like ball, is how to reference the function ball() itself. And one last thing, you need to change for (i===0;i<=3; i++) to for (i=0;i<=3; i++). The === is a strict comparison operator, = is assignment.

Get boundaries (position) of canvas drawn context lines

I'm making a little testing thing for physics. I have drawn a circle with lines using canvas and context:
function drawAngles () {
var d = 50; //start line at (10, 20), move 50px away at angle of 30 degrees
var angle = 80 * Math.PI/180;
ctx.beginPath();
ctx.moveTo(300,0);
ctx.lineTo(300,600); //x, y
ctx.moveTo(0,300);
ctx.lineTo(600,300);
ctx.arc(300,300,300,0,2*Math.PI);
ctx.stroke();
}
I want to somehow get the curved boundaries of the circle so I can tell if the ball element has collided.
If I'm testing boundaries on a typical div, I can just do this:
var divCoords= $(".boundingBoxDiv").position();
var top = divCoords.top;
etc...
How do I do this with context lines?
Here's an image... the ball should bounce off of the circle.
Live Demo
This is pretty easy to accomplish, in a radius based collision you just check the distance if the distance is closer than the radius the objects have collided. In this instance we need to do the opposite, if the objects distance is greater than the boundary radius we need to change the angle to keep the objects in.
So first we need to identify our boundary center points, and boundary radius,
var boundaryRadius = 300,
boundaryX = 300,
boundaryY = 300;
Later on in the ball.update method I check against these values.
var dx = boundaryX - this.x,
dy = boundaryY - this.y
We need to get the distance from our ball and the boundaries center point.
dist = Math.sqrt(dx * dx + dy * dy);
Now we need to get the velocity based on our current angle
this.radians = this.angle * Math.PI/ 180;
this.velX = Math.cos(this.radians) * this.speed;
this.velY = Math.sin(this.radians) * this.speed;
Next we check if we are still inside of the boundaries. In this instance if our distance is less than 300 - our own radius (which is 10) so 290, then keep moving.
if (dist < boundaryRadius-this.radius) {
this.x += this.velX;
this.y += this.velY;
Else if our distance is greater than 290, we need to first move back a bit so we aren't colliding, then we just change our heading angle. You can do this in much fancier ways to actual calculate where you should bounce to, in this example I just make it the opposite angle with a tad bit of randomness.
} else {
this.x -= this.velX;
this.y -= this.velY;
this.angle += 180+Math.random()*45;
}
Code in its entirety.
var canvas = document.getElementById("canvas"),
ctx = canvas.getContext("2d"),
width = 600,
height = 600;
canvas.width = width;
canvas.height = height;
var boundaryRadius = 300,
boundaryX = 300,
boundaryY = 300;
var Ball = function (x, y, speed) {
this.x = x || 0;
this.y = y || 0;
this.radius = 10;
this.speed = speed || 10;
this.color = "rgb(255,0,0)";
this.angle = Math.random() * 360;
this.radians = this.angle * Math.PI/ 180;
this.velX = 0;
this.velY = 0;
}
Ball.prototype.update = function () {
var dx = boundaryX - this.x,
dy = boundaryY - this.y,
dist = Math.sqrt(dx * dx + dy * dy);
this.radians = this.angle * Math.PI/ 180;
this.velX = Math.cos(this.radians) * this.speed;
this.velY = Math.sin(this.radians) * this.speed;
// check if we are still inside of our boundary.
if (dist < boundaryRadius-this.radius) {
this.x += this.velX;
this.y += this.velY;
} else {
// collision, step back and choose an opposite angle with a bit of randomness.
this.x -= this.velX;
this.y -= this.velY;
this.angle += 180+Math.random()*45;
}
};
Ball.prototype.render = function () {
ctx.fillStyle = this.color;
ctx.beginPath();
// draw our circle with x and y being the center
ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
ctx.closePath();
ctx.fill();
ctx.beginPath();
ctx.moveTo(this.x, this.y);
ctx.lineTo(this.px, this.py);
ctx.closePath();
};
var balls = [],
ballNum = 10;
for(var i = 0; i < ballNum; i++){
balls.push(new Ball(boundaryX + Math.random()*30, boundaryY + Math.random() * 30, 5 + Math.random()*15));
}
function drawAngles() {
var d = 50; //start line at (10, 20), move 50px away at angle of 30 degrees
var angle = 80 * Math.PI / 180;
ctx.beginPath();
ctx.moveTo(300, 0);
ctx.lineTo(300, 600); //x, y
ctx.moveTo(0, 300);
ctx.lineTo(600, 300);
ctx.arc(boundaryX, boundaryY, boundaryRadius, 0, 2 * Math.PI);
ctx.strokeStyle = "#000";
ctx.stroke();
}
function render() {
ctx.clearRect(0, 0, width, height);
drawAngles();
balls.forEach(function(e){
e.update();
e.render();
});
requestAnimationFrame(render);
}
render();
Have you looked into ray casting? Also a neat trick for collision detection is to create a new hidden canvas used for collision detection only. You can then draw the circle on to it using only black and white. If the canvas is filled black with a white circle on it you can test collisions by checking the color of a specific pixel at point x. If point x is black the object has collided, if not it hasn't.

Categories

Resources