How to draw 3d object javascript - javascript

I am making a simple JavaScript program that draws a rotated prism. I have vectors that contain x, y and z coordinates of every point and to which point they are connected. The problem I am having is I cannot find the perfect way to compare the dots in 2d. When the figure rotates, it has little, but still visible, defects and it looks like the lines become longer and shorter. I will paste the important parts of the code.
var canvas = document.getElementById("canvas-id");
canvas.width = 1200;
canvas.height = 500;
var ctx = canvas.getContext("2d");
var dx=[],dy=[],dz=[],dh=[],dgo=[],h=10,ang=10*Math.PI/360;
var pause=false;
for(var i=0;i<h/2;i++){
dx[i]=Math.cos(i/h*4*Math.PI+Math.PI/4)*70.71;
dy[i]=Math.sin(i/h*4*Math.PI+Math.PI/4)*70.71;
console.log(i/h*2*360);
dz[i]=150;
dh[i]=3;
dgo[i]=[];
dgo[i][0]=i+h/2;
dgo[i][1]=i+1;
dgo[i][2]=i-1;
dx[i+h/2]=Math.cos(i/h*4*Math.PI+Math.PI/4)*70.71;
dy[i+h/2]=Math.sin(i/h*4*Math.PI+Math.PI/4)*70.71;
dz[i+h/2]=250;
dh[i+h/2]=3;
dgo[i+h/2]=[];
dgo[i+h/2][0]=i;
dgo[i+h/2][1]=i+h/2+1;
dgo[i+h/2][2]=i+h/2-1;
if(i==h/2-1){
dgo[i][1]-=h/2;
dgo[i+h/2][1]-=h/2;
}
if(i==0){
dgo[i][2]+=h/2;
dgo[i+h/2][2]+=h/2;
}
}
window.addEventListener("mouseup", function (args) {
pause=!pause;
}, false);
function abs(a){
if(a>=0) return a;
return -a;
}
function pit(x1,y1,x2,y2){
return Math.sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2));
}
function rotate(x,y,z){
for(var i=0;i<h;i++){
var m=Math.atan2(dx[i]-x,dz[i]-z)/2/Math.PI*360-90,dis=pit(dx[i],dz[i],x,z);
if(m>0) m-=360
dx[i]=x+Math.cos(abs(m)/360*2*Math.PI+ang)*dis;
dz[i]=z+Math.sin(abs(m)/360*2*Math.PI+ang)*dis;
}
}
function update() {
if(!pause)rotate(0,0,200)
setTimeout(update, 10);
}
function line(x1,y1,x2,y2){
if((x1>0 && y1>0 && x1<canvas.width && y1<canvas.height) || (x2>0 && y2>0 && x2<canvas.width && y2<canvas.height))
ctx.beginPath();
ctx.moveTo(x1,y1);
ctx.lineTo(x2,y2);
ctx.stroke();
ctx.closePath();
}
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.globalAlpha = 1;
for(var i=0;i<h;i++){
for(var j=0;j<dh[i];j++){
if(dz[i]>0 && dz[dgo[i][j]]>0)
line(dx[i]/Math.sqrt(dz[i])*10+canvas.width/2,dy[i]/Math.sqrt(dz[i])*10+canvas.height/2,
dx[dgo[i][j]]/Math.sqrt(dz[dgo[i][j]])*10+canvas.width/2,dy[dgo[i][j]]/Math.sqrt(dz[dgo[i][j]])*10+canvas.height/2);
}
}
console.log(ctx.translate(0,0,10));
requestAnimationFrame(draw);
}
draw();
update();

Related

Why does my triangle on canvas have artifacts? [duplicate]

I'm doing a Pong game in javascript in order to learn making games, and I want to make it object oriented.
I can't get clearRect to work. All it does is draw a line that grows longer.
Here is the relevant code:
function Ball(){
this.radius = 5;
this.Y = 20;
this.X = 25;
this.draw = function() {
ctx.arc(this.X, this.Y, this.radius, 0, Math.PI*2, true);
ctx.fillStyle = '#00ff00';
ctx.fill();
};
}
var ball = new Ball();
function draw(){
player.draw();
ball.draw();
}
function update(){
ctx.clearRect(0, 0, 800, 400);
draw();
ball.X++;
}
I've tried to put the ctx.clearRect part in the draw() and ball.draw() functions and it doesn't work.
I also tried fillRect with white but it gives the same results.
How can I fix this?
Your real problem is you are not closing your circle's path.
Add ctx.beginPath() before you draw the circle.
jsFiddle.
Also, some tips...
Your assets should not be responsible for drawing themselves (their draw() method). Instead, perhaps define their visual properties (is it a circle? radius?) and let your main render function handle canvas specific drawing (this also has the advantage that you can switch your renderer to regular DOM elements or WebGL further down the track easily).
Instead of setInterval(), use requestAnimationFrame(). Support is not that great at the moment so you may want to shim its functionality with setInterval() or the recursive setTimeout() pattern.
Your clearRect() should be passed the dimensions from the canvas element (or have them defined somewhere). Including them in your rendering functions is akin to magic numbers and could lead to a maintenance issue further down the track.
window.onload = function() {
var cvs = document.getElementById('canvas');
var ctx = cvs.getContext('2d');
var cvsW = cvs.Width;
var cvsH = cvs.Height;
var snakeW = 10;
var snakeH = 10;
function drawSnake(x, y) {
ctx.fillStyle = '#FFF';
ctx.fillRect(x*snakeW, y * snakeH, snakeW, snakeH);
ctx.fillStyle = '#000';
ctx.strokeRect(x*snakeW, y * snakeH, snakeW, snakeH);
}
// drawSnake(4, 5)
//create our snake object, it will contain 4 cells in default
var len = 4;
var snake = [];
for(var i = len -1; i >=0; i--) {
snake.push(
{
x: i,
y: 0
}
)
};
function draw() {
ctx.clearRect(0, 0, cvsW, cvsH)
for(var i = 0; i < snake.length; i++) {
var x = snake[i].x;
var y = snake[i].y;
drawSnake(x, y)
}
//snake head
var snakeX = snake[0].x;
var snakeY = snake[0].y;
//remove to last entry (the snake Tail);
snake.pop();
// //create a new head, based on the previous head and the direction;
snakeX++;
let newHead = {
x: snakeX,
y: snakeY
}
snake.unshift(newHead)
}
setInterval(draw, 60);
}
my clearRect is not working, what's the problem and solution?

Canvas Javascript Looping

I'm trying to loop my animation, but no matter what I do, it won't loop. I'm pretty new to canvas, javascript and code in general.
var canvas = document.getElementById("fabrication");
var ctx = canvas.getContext("2d");
var background = new Image();
background.src =
"C:/Users/dylan/Desktop/ProjectTwo/Images/fabricationbackground.jpg";
background.onload = function(){
}
//Loading all of my canvas
var posi =[];
posi[1] = 20;
posi[2] = 20;
var dx=10;
var dy=10;
var ballRadius = 4;
//Variables for drawing a ball and it's movement
function drawballleft(){
posi =xy(posi[1],posi[2])
}
function xy(x,y){
ctx.drawImage(background,0,0);
ctx.beginPath();
ctx.arc(x, y, ballRadius, 0, Math.PI*2);
ctx.fillStyle = "#FFFFFFF";
ctx.fill();
ctx.closePath();
var newpos=[];
newpos[1]= x +dx;
newpos[2]= y +dy;
return newpos;
//Drawing the ball, making it move off canvas.
if (newpos[1] > canvas.width) {
newpos[1] = 20;
}
if (newpos[2] > canvas.height) {
newpos[2] = 20;
}
//If statement to detect if the ball moves off the canvas, to make it return to original spot
}
setInterval(drawballleft, 20);
//Looping the function
Please let me know if I've done something wrong, I really want to learn what I'm doing here. The ball is supposed to go off the canvas, and loop back onto itself, but it goes off the canvas and ends.
Thanks in advance!
I have made a few changes to your code.
First I am using requestAnimationFrame instead of setInterval. http://www.javascriptkit.com/javatutors/requestanimationframe.shtml
Second I am not using an image because I didn't want to run into a CORS issue. But you can put your background image back.
I simplified your posi array to use indexes 0 and 1 instead of 1 and 2 to clean up how you create your array.
I moved your return from before the two ifs to after so the ball will move back to the left or top when it goes off the side. I think that was the real problem you were seeing
var canvas = document.getElementById("fabrication");
var ctx = canvas.getContext("2d");
//Loading all of my canvas
var posi =[20,20];
var dx=10;
var dy=10;
var ballRadius = 4;
//Variables for drawing a ball and it's movement
function drawballleft(){
posi = xy(posi[0],posi[1])
requestAnimationFrame(drawballleft);
}
function xy(x,y){
ctx.fillStyle = '#FFF';
ctx.fillRect(0,0,400,300);
ctx.beginPath();
ctx.arc(x, y, ballRadius, 0, Math.PI*2);
ctx.fillStyle = "#000";
ctx.fill();
ctx.closePath();
var newpos=[x+dx,y+dy];
//Drawing the ball, making it move off canvas.
if (newpos[0] > canvas.width) {
newpos[0] = 20;
}
if (newpos[1] > canvas.height) {
newpos[1] = 20;
}
//If statement to detect if the ball moves off the canvas, to make it return to original spot
return newpos;
}
requestAnimationFrame(drawballleft);
canvas {
outline: 1px solid red;
}
<canvas width="400" height="300" id="fabrication"></canvas>
To make it all even simpler...
Use an external script for handling the canvas.
A really good one ;) :
https://github.com/GustavGenberg/handy-front-end#canvasjs
Include it with
<script type="text/javascript" src="https://gustavgenberg.github.io/handy-front-end/Canvas.js"></script>
Then it's this simple:
// Setup canvas
const canvas = new Canvas('my-canvas', 400, 300).start(function (ctx, handyObject, now) {
// init
handyObject.Ball = {};
handyObject.Ball.position = { x: 20, y: 20 };
handyObject.Ball.dx = 10;
handyObject.Ball.dy = 10;
handyObject.Ball.ballRadius = 4;
});
// Update loop, runs before draw loop
canvas.on('update', function (handyObject, delta, now) {
handyObject.Ball.position.x += handyObject.Ball.dx;
handyObject.Ball.position.y += handyObject.Ball.dy;
if(handyObject.Ball.position.x > canvas.width)
handyObject.Ball.position.x = 20;
if(handyObject.Ball.position.y > canvas.height)
handyObject.Ball.position.y = 20;
});
// Draw loop
canvas.on('draw', function (ctx, handyObject, delta, now) {
ctx.clear();
ctx.beginPath();
ctx.arc(handyObject.Ball.position.x, handyObject.Ball.position.y, handyObject.Ball.ballRadius, 0, Math.PI * 2);
ctx.fillStyle = '#000';
ctx.fill();
ctx.closePath();
});
I restructured your code and used the external script, and now it looks much cleaner and easier to read and toubleshoot!
JSFiddle: https://jsfiddle.net/n7osvt7y/

Canvas: X value changing in console but not in canvas

I am pulling coordinates from an API and setting them to a class of Rectangle. When looping through the data array, I am able to create each rectangle, but when it comes to moving them, that is where I am having trouble.
The x coordinates are changing in the console at the setInterval of move(), but the squares themselves are not moving across the screen.
When I have c.clearRect(0,0,innerWidth, innerHeight); in move(), all of the rectangles disappear. Without it, they do not move at all.
if(this % 6 === 0){
c.fillStyle = "#000";
} else {
c.fillStyle = "" + this.color + "";
is referring to the data array and if the index is divisible by 6, make that square black. Although, in this context, it is not working.
Here is my code:
<script>
const canvas = document.getElementById("canvas");
const c = canvas.getContext('2d');
let xhReq = new XMLHttpRequest();
xhReq.open("GET", "(api here)", false);
xhReq.send(null);
const data = JSON.parse(xhReq.responseText);
class Rectangle {
constructor(x, y, w, h, vx, color) {
this.x = x;
this.y = y;
this.w = w;
this.h = h;
this.vx = vx;
this.color = color;
}
draw() {
c.fillRect(this.x, this.y, this.w, this.h);
if(this % 6 === 0){
c.fillStyle = "#000";
} else {
c.fillStyle = "" + this.color + "";
}
}
move(){
c.clearRect(0,0,innerWidth, innerHeight);
if (this.x > 800 || this.x < 0){
this.vx = -this.vx;
}
this.x+= this.vx;
c.beginPath();
console.log("here is the x value:" + this.x);
}
}
for(let i=0; i<data.length; i++){
let info = data[i]
let rec = new Rectangle(info.x, info.y, info.w, info.h, info.vx, info.color);
rec.draw();
setInterval(function() {
rec.move();
}, 50);
}
</script>
There are a lot of issues with your code.
You are only updating the position and not making the draw call again.
Move the rec.draw() call inside your setInterval. Remember, ordering is important. Ideally, you should update() first and draw() later.
setInterval(function() {
rec.move();
rec.draw();
}, 50);
Also, remove these lines from the move() function as you don't want to clear the canvas every time you update the variables. Clear the canvas in the beginning of the draw() function. Plus, since you're dealing with fillRect(), you dont need to include beginPath()
c.clearRect(0,0,innerWidth, innerHeight);
...
c.beginPath();
You first need to specify the fillStyle then proceed with drawing the rectangle using fillRect(), not the other way round. The draw() function should look like this:
draw() {
c.clearRect(0, 0, innerWidth, innerHeight);
if(this % 6 === 0){ // <-- Not really sure what is going on here
c.fillStyle = "#000";
} else {
c.fillStyle = "" + this.color + "";
}
c.fillRect(this.x, this.y, this.w, this.h);
}
In the code block in point number #3 you are using the Modulus Operator % on the the instance of the class itself i.e. this and not a number. I'm assuming you want something like this.x in there.
Apart from the points above there are still a handful of issues with your code. But it's outside the context of this question.
Edited for a final solution below:
const canvas = document.getElementById("canvas");
const context = canvas.getContext('2d');
const xhr = new XMLHttpRequest();
// Usng var as we are making an async request and need to update this variable
var data = null;
// An empty array to store our rectangles
var collection = [];
// Using an asyn request as we don't necessarily need to wait for this step
xhr.open('GET', '<api>', true)
xhr.onreadystatechange = function(){
if(xhr.readyState === 4){
if(xhr.status >= 200 && xhr.status < 300){
data = JSON.parse(xhr.responseText);
dataLoaded();
} else {
// The request gave an error response
}
}
};
xhr.send();
function dataLoaded(){
for(let i = 0; i < data.length; i++){
let info = data[i];
collection.push(new Rectangle(info.x, info.y, info.w, info.h, info.vx, info.color));
}
setInterval(animate, 50);
}
function animate(){
context.clearRect(0, 0, canvas.width, canvas.height);
for(let i = 0; i < collection.length; i++){
let rect = collection[i];
rect.update();
rect.draw();
}
}
class Rectangle {
constructor(x, y, w, h, vx, color){
this.x = x;
this.y = y;
this.w = w;
this.h = h;
this.vx = vx;
this.color = color;
}
update(){
if (this.x > 800 || this.x < 0){
this.vx = -this.vx;
}
this.x += this.vx;
}
draw(){
context.fillStyle = this.color;
context.fillRect(this.x, this.y, this.w, this.h);
}
}

How do I redraw the background, so it will look like my "player" moving in html5-canvas

This is my JavaScript. I have an animate function, I think this is where the redrawing background code goes. I have tried to use document.getElementById to get the id of the canvas, and give the style rule of background: white. But it didn't work. All help is appreciated!:
function initCanvas(){
var ctx = document.getElementById('my_canvas').getContext('2d');
var cW = ctx.canvas.width, cH = ctx.canvas.height;
var dist = 10;
function Player(){
this.x = 0, this.y = 0, this.w = 50, this.h = 50;
ctx.fillStyle = "orange";
this.render = function(){
ctx.fillRect(this.x, this.y, this.w, this.h);
}
}
var player = new Player();
player.x = 100;
player.y = 225;
function animate(){
//This is where I think the background redrawing should go
player.render();
}
var animateInterval = setInterval(animate, 30);
document.addEventListener('keydown', function(event) {
var key_press = String.fromCharCode(event.keyCode);
if(key_press == "W"){
player.y-=dist;
} else if(key_press == "S"){
player.y+=dist;
} else if(key_press == "A"){
player.x-=dist;
} else if(key_press == "D"){
player.x+=dist;
}
});
}
window.addEventListener('load', function(event) {
initCanvas();
});
Here are a couple things I've noticed:
You didn't declare var canvas = document.getElementById('my_canvas');
Your initCanvas() wasn't firing(It wasn't for me)
Your render() function should reset the fillStyle before it draws your character. Having the fillStyle set when initializing the Player constructor is useless(It can be overridden).
Here is what your animate() function should look like:
function animate(){
ctx.fillStyle = '#000';
ctx.fillRect(0,0,canvas.width, canvas.height);
player.render();
}
Notice how fillRect() clears the canvas before drawing the player.
I have created a working JSFiddle with the fixes here.

JavaScript animation clingy circles

On my canvas animation, I have a problem where my circles are getting drawn "without the metaphorical pen" lifting from the canvas.
I need a way to stop the function and just draw one circle than another one.
Here is my JSFiddle (Warning: uses 100% of one logical processor core/thread).
JavaScript:
window.requestAnimFrame = (function(callback) {
return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame ||
function(callback) {
window.setTimeout(callback, 1000 / 60);
};
})();
var canvas = document.getElementById("myCanvas");
var context = canvas.getContext("2d");
var size = 19;
var size_two = 19;
function start(){
requestAnimationFrame(start);
size++;
context.arc(95, 85, size, 0, 2*Math.PI);
context.stroke();
}
function othercircle(){
requestAnimationFrame(othercircle);
size_two++;
context.arc(500, 300, size_two, 0, 3*Math.PI);
}
start();
othercircle();
The other answers are good, but I wanted to highlight why your unwanted line is appearing.
Your unwanted connecting line
The unwanted connecting line is created because you must call context.beginPath() before drawing each arc. If you don't call begainPath the browser assumes you want to connect the 2 circle paths.
context.beginPath()
context.arc(95, 85, size, 0, 2*Math.PI);
context.stroke();
context.beginPath();
context.arc(200, 200, size_two, 0, 3*Math.PI);
context.stroke();
Just a couple more notes
Your othercircle is drawing an arc that's 3 * PI. 2*PI is a complete circle and any value above 2*PI will not add to the circle.
If it was your intent to draw an expanding stroke-circle, then you should clear the canvas at the start of each animation loop (before drawing the expanded circles).
One requestAnimationFrame is enough. You can put both your circle and your othercircle code in one requestAnimationFrame.
Example code and a Demo: http://jsfiddle.net/m1erickson/62mFF/
var sizeCounter=19;
var maxSizeCounter=60;
var size = sizeCounter;
var maxSize=40;
var size_two = sizeCounter;
var maxSizeTwo=60;
function start(){
if(sizeCounter<maxSizeCounter){ requestAnimationFrame(start); }
// clear the canvas if you want strokes instead of filled circles
context.clearRect(0,0,canvas.width,canvas.height);
context.beginPath()
context.arc(95, 85, size, 0, 2*Math.PI);
context.stroke();
if(size<maxSize){ size++; }
context.beginPath();
context.arc(200, 200, size_two, 0, 3*Math.PI);
context.stroke();
if(size_two<maxSizeTwo){ size_two++; }
sizeCounter++;
}
start();
Then use a condition to stop the recursive call, or just omit size++ and size_two++ from your code.
Your functions don't have all the information for a complete circle, thus the line between the two circles. Below will make two circles without the line between them.
function start(){
requestAnimationFrame(start);
size++;
context.beginPath();
context.arc(95, 85, size, 0, 2*Math.PI);
context.closePath();
context.fill();
}
function othercircle(){
requestAnimationFrame(othercircle);
size_two++;
context.beginPath();
context.arc(500, 300, size_two, 0, 3*Math.PI);
context.closePath();
context.fill();
}
Here is an updated jsFiddle
http://jsfiddle.net/gamealchemist/4nQCa/5
I think it's simpler to go object in your case : define a Circle class that will update with time and draw itself :
/// Circle class.
// has update(dt) and draw as public method.
function Circle(x, y, initSize, endSize, duration, color) {
this.x = x;
this.y = y;
this.size = initSize;
this.endSize = endSize;
this.color = color;
this.speed = (endSize - initSize) / duration;
this.update = function (dt) {
if (this.speed == 0) return;
this.size += dt * this.speed;
if (this.size > this.endSize) {
this.size = this.endSize;
this.speed = 0;
}
}
this.draw = function () {
context.beginPath();
context.strokeStyle = this.color;
context.arc(this.x, this.y, this.size, 0, 2 * Math.PI);
context.stroke();
}
}
then hold all your animated objects within an array
var scene=[];
And have the objects animated/drawn :
function animate() {
// keep alive
requestAnimationFrame(animate);
// handle time
var callTime = Date.now();
var dt = callTime - lastTime;
lastTime = callTime;
// clear screen
context.clearRect(0, 0, canvasWidth, canvasHeight);
context.fillText('Click anywhere !',40, 80);
// draw
for (var i = 0; i < scene.length; i++) {
var thisObject = scene[i];
thisObject.draw();
}
// update
for (var i = 0; i < scene.length; i++) {
var thisObject = scene[i];
thisObject.update(dt);
}
}
var lastTime = Date.now();
animate();
With this scheme it's easy to add/remove objects. For instance to add circles on mouse click :
addEventListener('mousedown', function (e) {
var x = e.clientX,
y = e.clientY;
var color = 'hsl(' + Math.floor(Math.random() * 360) + ',80%, 85%)';
var newCircle = new Circle(x, y, 20, 100, 1000, color);
scene.push(newCircle);
});

Categories

Resources