I have the following code for a complex function plotter. It creates a phase plot of the complex function f(z) = z*(z+5)(z-v) where v is where your mouse is pointing. As you can see, it is pretty slow. Is there any way to speed this up and get a smooth animation? Just pointing me in the right direction would be helpful.
<html>
<head>
<script type="text/javascript" src="jquery-1.8.1.js"></script>
<script type="application/javascript">
function draw() {
var canvas = document.getElementById("canvas");
var ctx;// = canvas.getContext("2d");
//The following functions convert pixel Xs and Ys to real and imaginary
//parts of a complex number, and back again
var pixToReal = function(n){return n/15.0-10.0};
var pixToImag = function(n){return - n/15.0+10}
var realToPix = function(x){return Math.round((x+10.0)*15)}
var imagToPix = function(y){return Math.round((-y+10.0)*15)}
//Storing the complex number a+bi as [a,b], the following functions add,
//multiply, and find the modulus of the complex number
var add = function(z1,z2){return [z1[0]+z2[0],z1[1] + z2[1]]}
var mult = function(z1,z2){return [z1[0]*z2[0]-z1[1]*z2[1],z1[0]*z2[1]+z1[1]*z2[0]]}
var modulus = function(z){
if (z[1]>0){return Math.atan2(z[1],z[0])}
else {return Math.atan2(z[1],z[0])+2*Math.PI}
};
//Takes a complex number and returns the RGB value of the corresponding
//point on the color wheel.
var complexToRGB = function(z){
var theta = modulus(z)%(2*Math.PI)
var Hp = (theta/(2*Math.PI))*6
var X = Math.abs(Math.round((1 - Math.abs(Hp%2 -1))*255))
var C = "rgb(0,0,0)"
if (Hp>=0 && Hp<1){
C = "rgb("+255+","+X+",0)"
};
if (1<=Hp && Hp<2){
C = "rgb("+X+","+255+",0)"}
if (2<=Hp && Hp<3){
C = "rgb("+0+","+255+","+X+")"}
if (3<=Hp && Hp<4){
C = "rgb("+0+","+X+","+255+")"}
if (4<=Hp && Hp<5){
C = "rgb("+X+","+0+","+255+")"}
if (5<=Hp && Hp<6){
C = "rgb("+255+","+0+","+X+")"}
return C
}
//a complex number
var v = [0,4]
//the function f(z) = z*(z+5)*(z+v)
var f = function(z){return mult(add(mult(z,z),mult([5,5],z)),add(z,v))}
//makes v the opposite complex number your mouse is pointing at,
//i.e. your mouse points at a root of f
function onMouseMove(evt) {
v = [-pixToReal(evt.pageX), -pixToImag(evt.pageY)];
}
$(document).mousemove(onMouseMove);
makeFrame = function(){
ctx.clearRect(0,0,300,300);
for (var n =0;n<300;n++){
for (var m=0;m<300;m++){
var x = pixToReal(n)
var y = pixToImag(m)
var z = [x,y]
var w = f(z)
ctx.fillStyle = complexToRGB(w)
ctx.fillRect(n,m,1,1)
}
}
}
function animate() {
ctx = canvas.getContext("2d");
return setInterval(makeFrame, 1);
}
animate();
}
</script>
</head>
<body onload="draw()">
<canvas id="canvas" width="300" height="300"></canvas>
</body>
I have made some quick optimizations that speeds it up about 500%. I think you could speed it up further but it would require a bit more work.
What I have done is:
Instead of setting the pixel values using fillStyle and fillRect, all pixel values are retrieved as an array (imageData), and then makeFrame() manipulates the imageData array and then set all pixels at once using putImageData().
The change above required that complexToRGB() retuns an array with the red, green and blue color values instead of a string.
in the complexToRGB() function the list of if-cases has been changed to a chain of if-else (which is faster since the conditions after a true condition will not be evaluted).
Changed the setInterval from 1000 fps to 25. There's no way the algorithm will be able to keep up with that framerate, so it's better to set it to a more realistic frame rate.
Here's the code as a jsFiddle.
Next steps: I would also try to remove as many function calls as possible, for instance inline the pixToReal() and pixToImag() formulas in the inner for loop:
for (var m = 0; m < 300; m++) {
var x = n / 15.0 - 10.0;
var y = -m / 15.0 + 10;
And then optimize the code in complexToRGB() and consider doing the same to that function to remove that function call.
I made a fiddle here, using requestAnimationFrame and drawing with ImageData. Works pretty well, maybe you can merge mine with strille's approach.
Related
Im creating an object that randomly moves in a natural way using noise like this (works as intended):
The objects encounter a collision and their trajectory is manipulated, the movement path now changes to straight line (words as intended)
thisRabbit.x = _world.width * (noise(thisRabbit.t));
thisRabbit.y = _world.height * (noise(thisRabbit.t+5));
thisRabbit.t += 0.001;
The problem is after this movement , i want the object to start moving in a random direction again as it was initially. If i use the same function, the object jumps to the last location before the trajectory was modified.
let vx = this.acquiredFood[0] - this.x;
let vy = this.acquiredFood[1] - this.y;
let f = (this.genes.speed + 10) / Math.sqrt(vx*vx+vy*vy);
vx = vx * f;
vy = vy * f;
let newX = this.x + vx;
let newY = this.y + vy;
So how do i get the object to move as before, given a starting position
edit: snippet here: https://editor.p5js.org/vince.chinner/sketches/HPFKR8eIw
Your problem is that you used a factor from 0 to 1 generated with noise and an incremented seed to generate the position by multiplying directly the world dimentions. When reaching food, you cannot increment the seed as to be in the exact position where the movement to get your food led you (I found no inverse function for noise to get the seed from the return value).
What you need to do instead is use the noise to increment or decrement the coordinates, so that no matter where the seed is, you don't loose your current position.
Here are the different corrections I applied to the code, as there were also syntax errors, I can't really paste the whole stuff here for copyright reasons (you didn't share the whole code here and the sketch belongs to you)
MAIN CORRECTION:
used a var found because returning from the forEach callback doesn't make you leave the findFood function, but the callback one. And the forEach loop doesn't stop. Using this var prevents the further forEach tests to be made and allows you to return from findFood so that no further move is made after seeing food.
noise is now applied to a value of 4 and I subtract 2, so that x and y now change with a range of -2 to 2 each. Of course, with this method, you need to check against world dimentions or else the rabbit could leave the world. The seed increment has been changed too or else it would vary too slowly (adapt values as you wish)
findFood(){
var thisRabbit = this, found = false;
_world.food.forEach(f => {
if(!found){
let d = int(dist(f[0], f[1], thisRabbit.x, thisRabbit.y));
if(d < (thisRabbit.genes.vision / 2)+3){
thisRabbit.state = "foundFood";
this.acquiredFood = f;
found = true;
}
}
});
if(found){ return; }
thisRabbit.x += (noise(thisRabbit.t) * 4) - 2;
if(thisRabbit.x < 0){ thisRabbit.x = 0; }
if(thisRabbit.x > _world.width){ thisRabbit.x = _world.width; }
thisRabbit.y += (noise(thisRabbit.t + 5) * 4) - 2;
if(thisRabbit.y < 0){ thisRabbit.y = 0; }
if(thisRabbit.y > _world.height){ thisRabbit.y = _world.height; }
thisRabbit.t += 0.01;
}
SYNTAX ERRORS:
lines 23 / 24: assignment should be with a value (null or false)
this.genes = null;
this.acquiredFood = null;
lines 129 to 133: end you instructions with a ; instead of a ,
this.width = w;
this.height = h;
this.foodDensity = foodDensity;
this.food = [];
this.rabits = [];
line 156 to 160: there should be no space between rabbit and .t. Additionnally, because the coordinates are not directly linked to t, I would prefer to use random for starting position:
let x = this.width * random();
let y = this.height * random();
let _rabbit = new rabbit(x, y);
_rabbit.genes = genes;
_rabbit.t = t;
I'm trying to create a little simulation with the help of HTML5 and Javascript using a canvas. My problem however is, I can't really think of a way to control the behavior of my pixels, without making every single pixel an object, which leads to an awful slowdown of my simulation.
Heres the code so far:
var pixels = [];
class Pixel{
constructor(color){
this.color=color;
}
}
window.onload=function(){
canv = document.getElementById("canv");
ctx = canv.getContext("2d");
createMap();
setInterval(game,1000/60);
};
function createMap(){
pixels=[];
for(i = 0; i <= 800; i++){
pixels.push(sub_pixels = []);
for(j = 0; j <= 800; j++){
pixels[i].push(new Pixel("green"));
}
}
pixels[400][400].color="red";
}
function game(){
ctx.fillStyle = "white";
ctx.fillRect(0,0,canv.width,canv.height);
for(i = 0; i <= 800; i++){
for(j = 0; j <= 800; j++){
ctx.fillStyle=pixels[i][j].color;
ctx.fillRect(i,j,1,1);
}
}
for(i = 0; i <= 800; i++){
for(j = 0; j <= 800; j++){
if(pixels[i][j].color == "red"){
direction = Math.floor((Math.random() * 4) + 1);
switch(direction){
case 1:
pixels[i][j-1].color= "red";
break;
case 2:
pixels[i+1][j].color= "red";
break;
case 3:
pixels[i][j+1].color= "red";
break;
case 4:
pixels[i-1][j].color= "red";
break;
}
}
}
}
}
function retPos(){
return Math.floor((Math.random() * 800) + 1);
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<script language="javascript" type="text/javascript" src="game.js"></script>
</head>
<body>
<canvas width="800px" height="800px" id="canv"></canvas>
</body>
</html>
So my two big questions are, what better way of controlling those pixels is there? And how can I speed up the pixel generation?
Hope you can help me.
Optimizing pixel manipulation
There are many options to speed up your code
Pixels as 32bit ints
The following will slug most machines with too much work.
// I removed fixed 800 and replaced with const size
for(i = 0; i <= size; i++){
for(j = 0; j <= size; j++){
ctx.fillStyle=pixels[i][j].color;
ctx.fillRect(i,j,1,1);
}
}
Don't write each pixel via a rect. Use the pixel data you can get from the canvas API via createImageData and associated functions. It uses typed arrays that are a little quicker than arrays and can have multiple view on the same content.
You can write all the pixels to the canvas in a single call. Not blindingly fast but a zillion times faster than what you are doing.
const size = 800;
const imageData = ctx.createImageData(size,size);
// get a 32 bit view
const data32 = new Uint32Array(imageData.data.buffer);
// To set a single pixel
data32[x+y*size] = 0xFF0000FF; // set pixel to red
// to set all pixels
data32.fill(0xFF00FF00); // set all to green
To get a pixel at a pixel coord
const pixel = data32[x + y * imageData.width];
See Accessing pixel data for more on using the image data.
The pixel data is not displayed until you put it onto the canvas
ctx.putImageData(imageData,0,0);
That will give you a major improvement.
Better data organization.
When performance is critical you sacrifice memory and simplicity to get more CPU cycles doing what you want and less doing a lot of nothing.
You have red pixels randomly expanding into the scene, you read every pixel and check (via a slow string comparison) if it is red. When you find one you add a random red pixel besides it.
Checking the green pixels is a waste and can be avoided. Expanding red pixels that are completely surrounded by other reds is also pointless. They do nothing.
The only pixels you are interested in are the red pixels that are next to green pixels.
Thus you can create a buffer that holds the location of all active red pixels, An active red has at least one green. Each frame you check all the active reds, spawning new ones if they can, and killing them if they are surrounded in red.
We don't need to store the x,y coordinate of each red, just the memory address so we can use a flat array.
const reds = new Uint32Array(size * size); // max size way over kill but you may need it some time.
You dont want to have to search for reds in your reds array so you need to keep track of how many active reds there are. You want all the active reds to be at the bottom of the array. You need to check each active red only once per frame. If a red is dead than all above it must move down one array index. But you only want to move each red only once per frame.
Bubble array
I dont know what this type of array is called its like a separation tank, dead stuff slowly moves up and live stuff moves down. Or unused items bubble up used items settle to the bottom.
I will show it as functional because it will be easier to understand. but is better implemented as one brute force function
// data32 is the pixel data
const size = 800; // width and height
const red = 0xFF0000FF; // value of a red pixel
const green = 0xFF00FF00; // value of a green pixel
const reds = new Uint32Array(size * size); // max size way over kill but you var count = 0; // total active reds
var head = 0; // index of current red we are processing
var tail = 0; // after a red has been process it is move to the tail
var arrayOfSpawnS = [] // for each neighbor that is green you want
// to select randomly to spawn to. You dont want
// to spend time processing so this is a lookup
// that has all the possible neighbor combinations
for(let i = 0; i < 16; i ++){
let j = 0;
const combo = [];
i & 1 && (combo[j++] = 1); // right
i & 2 && (combo[j++] = -1); // left
i & 4 && (combo[j++] = -size); // top
i & 5 && (combo[j++] = size); // bottom
arrayOfSpawnS.push(combo);
}
function addARed(x,y){ // add a new red
const pixelIndex = x + y * size;
if(data32[pixelIndex] === green) { // check if the red can go there
reds[count++] = pixelIndex; // add the red with the pixel index
data32[pixelIndex] = red; // and set the pixel
}
}
function safeAddRed(pixelIndex) { // you know that some reds are safe at the new pos so a little bit faster
reds[count++] = pixelIndex; // add the red with the pixel index
data32[pixelIndex] = red; // and set the pixel
}
// a frame in the life of a red. Returns false if red is dead
function processARed(indexOfRed) {
// get the pixel index
var pixelIndex = reds[indexOfRed];
// check reds neighbors right left top and bottom
// we fill a bit value with each bit on if there is a green
var n = data32[pixelIndex + 1] === green ? 1 : 0;
n += data32[pixelIndex - 1] === green ? 2 : 0;
n += data32[pixelIndex - size] === green ? 4 : 0;
n += data32[pixelIndex + size] === green ? 8 : 0;
if(n === 0){ // no room to spawn so die
return false;
}
// has room to spawn so pick a random
var nCount = arrayOfSpawnS[n].length;
// if only one spawn point then rather than spawn we move
// this red to the new pos.
if(nCount === 1){
reds[indexOfRed] += arrayOfSpawnS[n][0]; // move to next pos
}else{ // there are several spawn points
safeAddRed(pixelIndex + arrayOfSpawnS[n][(Math.random() * nCount)|0]);
}
// reds frame is done so return still alive to spawn another frame
return true;
}
Now to process all the reds.
This is the heart of the bubble array. head is used to index each active red. tail is the index of where to move the current head if no deaths have been encountered tail is equal to head. If however a dead item is encountered the head move up one while the tail remains pointing to the dead item. This moves all the active items to the bottom.
When head === count all active items have been checked. The value of tail now contains the new count which is set after the iteration.
If you were using an object rather than a Integer, instead of moving the active item down you swap the head and tail items. This effectively creates a pool of available objects that can be used when adding new items. This type of array management incurs not GC or Allocation overhead and is hence very quick when compared to stacks and object pools.
function doAllReds(){
head = tail = 0; // start at the bottom
while(head < count){
if(processARed(head)){ // is red not dead
reds[tail++] = reds[head++]; // move red down to the tail
}else{ // red is dead so this creates a gap in the array
// Move the head up but dont move the tail,
// The tail is only for alive reds
head++;
}
}
// All reads done. The tail is now the new count
count = tail;
}
The Demo.
The demo will show you the speed improvement. I used the functional version and there could be some other tweaks.
You can also consider webWorkers to get event more speed. Web worker run on a separate javascript context and provides true concurrent processing.
For the ultimate speed use WebGL. All the logic can be done via a fragment shader on the GPU. This type of task is very well suited to parallel processing for which the GPU is designed.
Will be back later to clean up this answer (got a little too long)
I have also added a boundary to the pixel array as the reds were spawning off the pixel array.
const size = canvas.width;
canvas.height = canvas.width;
const ctx = canvas.getContext("2d");
const red = 0xFF0000FF;
const green = 0xFF00FF00;
const reds = new Uint32Array(size * size);
const wall = 0xFF000000;
var count = 0;
var head = 0;
var tail = 0;
var arrayOfSpawnS = []
for(let i = 0; i < 16; i ++){
let j = 0;
const combo = [];
i & 1 && (combo[j++] = 1); // right
i & 2 && (combo[j++] = -1); // left
i & 4 && (combo[j++] = -size); // top
i & 5 && (combo[j++] = size); // bottom
arrayOfSpawnS.push(combo);
}
const imageData = ctx.createImageData(size,size);
const data32 = new Uint32Array(imageData.data.buffer);
function createWall(){//need to keep the reds walled up so they dont run free
for(let j = 0; j < size; j ++){
data32[j] = wall;
data32[j * size] = wall;
data32[j * size + size - 1] = wall;
data32[size * (size - 1) +j] = wall;
}
}
function addARed(x,y){
const pixelIndex = x + y * size;
if (data32[pixelIndex] === green) {
reds[count++] = pixelIndex;
data32[pixelIndex] = red;
}
}
function processARed(indexOfRed) {
var pixelIndex = reds[indexOfRed];
var n = data32[pixelIndex + 1] === green ? 1 : 0;
n += data32[pixelIndex - 1] === green ? 2 : 0;
n += data32[pixelIndex - size] === green ? 4 : 0;
n += data32[pixelIndex + size] === green ? 8 : 0;
if(n === 0) { return false }
var nCount = arrayOfSpawnS[n].length;
if (nCount === 1) { reds[indexOfRed] += arrayOfSpawnS[n][0] }
else {
pixelIndex += arrayOfSpawnS[n][(Math.random() * nCount)|0]
reds[count++] = pixelIndex;
data32[pixelIndex] = red;
}
return true;
}
function doAllReds(){
head = tail = 0;
while(head < count) {
if(processARed(head)) { reds[tail++] = reds[head++] }
else { head++ }
}
count = tail;
}
function start(){
data32.fill(green);
createWall();
var startRedCount = (Math.random() * 5 + 1) | 0;
for(let i = 0; i < startRedCount; i ++) { addARed((Math.random() * size-2+1) | 0, (Math.random() * size-2+1) | 0) }
ctx.putImageData(imageData,0,0);
setTimeout(doItTillAllDead,1000);
countSameCount = 0;
}
var countSameCount;
var lastCount;
function doItTillAllDead(){
doAllReds();
ctx.putImageData(imageData,0,0);
if(count === 0 || countSameCount === 100){ // all dead
setTimeout(start,1000);
}else{
countSameCount += count === lastCount ? 1 : 0;
lastCount = count; //
requestAnimationFrame(doItTillAllDead);
}
}
start();
<canvas width="800" height="800" id="canvas"></canvas>
The main cause of your slow down is your assumption that you need to loop over every pixel for every operation. You do not do this, as that would be 640,000 iterations for every operation you need to do.
You also shouldn't be doing any manipulation logic within the render loop. The only thing that should be there is drawing code. So this should be moved out to preferably a separate thread (Web Workers). If unable to use those a setTimeout/Interval call.
So first a couple of small changes:
Make Pixel class contain the pixel's coordinates along with the color:
class Pixel{
constructor(color,x,y){
this.color=color;
this.x = x;
this.y = y;
}
}
Keep an array of pixels that will end up creating new red pixels. And another one to keep track of what pixels have been updated so we know which ones need drawn.
var pixels = [];
var infectedPixesl = [];
var updatedPixels = [];
Now the easiest part of the code to change is the render loop. Since the only thing that it needs to do is draw the pixels it will be only a couple lines.
function render(){
var numUpdatedPixels = updatedPixels.length;
for(let i=0; i<numUpdatedPixels; i++){
let pixel = updatedPixels[i];
ctx.fillStyle = pixel.color;
ctx.fillRect(pixel.x,pixel.y,1,1);
}
//clear out the updatedPixels as they should no longer be considered updated.
updatedPixels = [];
//better method than setTimeout/Interval for drawing
requestAnimationFrame(render);
}
From there we can move on to the logic. We will loop over the infectedPixels array, and with each pixel we decide a random direction and get that pixel. If this selected pixel is red we do nothing and continue on. Otherwise we change it's color and add it to a temporary array affectedPixels. After which we test to see if all the pixels around the original pixel are all red, if so we can remove it from the infectedPixels as there is no need to check it again. Then add all the pixels from affectedPixels onto the infectedPixels as these are now new pixels that need to be checked. And the last step is to also add affectedPixels onto updatedPixels so that the render loop draws the changes.
function update(){
var affectedPixels = [];
//needed as we shouldn't change an array while looping over it
var stillInfectedPixels = [];
var numInfected = infectedPixels.length;
for(let i=0; i<numInfected; i++){
let pixel = infectedPixels[i];
let x = pixel.x;
let y = pixel.y;
//instead of using a switch statement, use the random number as the index
//into a surroundingPixels array
let surroundingPixels = [
(pixels[x] ? pixels[x][y - 1] : null),
(pixels[x + 1] ? pixels[x + 1][y] : null),
(pixels[x] ? pixels[x][y + 1] : null),
(pixels[x - 1] ? pixels[x - 1][y] : null)
].filter(p => p);
//filter used above to remove nulls, in the cases of edge pixels
var rand = Math.floor((Math.random() * surroundingPixels.length));
let selectedPixel = surroundingPixels[rand];
if(selectedPixel.color == "green"){
selectedPixel.color = "red";
affectedPixels.push(selectedPixel);
}
if(!surroundingPixels.every(p=>p.color=="red")){
stillInfectedPixels.push(pixel);
}
}
infectedPixels = stillInfectedPixel.concat( affectedPixels );
updatedPixels.push(...affectedPixels);
}
Demo
var pixels = [],
infectedPixels = [],
updatedPixels = [],
canv, ctx;
window.onload = function() {
canv = document.getElementById("canv");
ctx = canv.getContext("2d");
createMap();
render();
setInterval(() => {
update();
}, 16);
};
function createMap() {
for (let y = 0; y < 800; y++) {
pixels.push([]);
for (x = 0; x < 800; x++) {
pixels[y].push(new Pixel("green",x,y));
}
}
pixels[400][400].color = "red";
updatedPixels = [].concat(...pixels);
infectedPixels.push(pixels[400][400]);
}
class Pixel {
constructor(color, x, y) {
this.color = color;
this.x = x;
this.y = y;
}
}
function update() {
var affectedPixels = [];
var stillInfectedPixels = [];
var numInfected = infectedPixels.length;
for (let i = 0; i < numInfected; i++) {
let pixel = infectedPixels[i];
let x = pixel.x;
let y = pixel.y;
let surroundingPixels = [
(pixels[x] ? pixels[x][y - 1] : null),
(pixels[x + 1] ? pixels[x + 1][y] : null),
(pixels[x] ? pixels[x][y + 1] : null),
(pixels[x - 1] ? pixels[x - 1][y] : null)
].filter(p => p);
var rand = Math.floor((Math.random() * surroundingPixels.length));
let selectedPixel = surroundingPixels[rand];
if (selectedPixel.color == "green") {
selectedPixel.color = "red";
affectedPixels.push(selectedPixel);
}
if (!surroundingPixels.every(p => p.color == "red")) {
stillInfectedPixels.push(pixel);
}
}
infectedPixels = stillInfectedPixels.concat(affectedPixels);
updatedPixels.push(...affectedPixels);
}
function render() {
var numUpdatedPixels = updatedPixels.length;
for (let i = 0; i < numUpdatedPixels; i++) {
let pixel = updatedPixels[i];
ctx.fillStyle = pixel.color;
ctx.fillRect(pixel.x, pixel.y, 1, 1);
}
updatedPixels = [];
requestAnimationFrame(render);
}
<canvas id="canv" width="800" height="800"></canvas>
I am trying to make my own raytracer in Javascript. So far the spheres work very well. I now want to expand the capabilities to include triangles, and from there I can go to squares, cubes and beyond. The code I have for finding intersections with triangles is as follows
function triangleIntersection(t, r) {
var norm = triangleNormal(t);
var dist = triangleDistance(t);
var a = Vector.dotProduct(r.vector, norm);
if (a === 0) {
return -1;
} else {
var b = Vector.dotProduct(norm, Vector.add(r.point, Vector.negative(Vector.multiply(norm, dist))));
var d = -1 * b / a;
var qx = Vector.scale(r.vector, d).x + r.point.x;
var qy = Vector.scale(r.vector, d).y + r.point.y;
var qz = Vector.scale(r.vector, d).z + r.point.z;
var q = new Vector(qx, qy, qz);
var ca = Vector.subtract(t.points[2], t.points[0]);
var qa = Vector.subtract(q, t.points[0]);
var t1 = Vector.dotProduct(Vector.crossProduct(ca, qa), norm);
var bc = Vector.subtract(t.points[1], t.points[2]);
var qc = Vector.subtract(q, t.points[2]);
var t2 = Vector.dotProduct(Vector.crossProduct(bc, qc), norm);
var ab = Vector.subtract(t.points[0], t.points[1]);
var qb = Vector.subtract(q, t.points[1]);
var t3 = Vector.dotProduct(Vector.crossProduct(ab, qb), norm);
if ((t1 >= 0) && (t2 >= 0) && (t3 >= 0)) {
return 1 * b / a;
} else {
return -1;
}
}
}
Triangle objects have a point array (points[]) and item 0 is point A, item 1 is point B and item 2 is point C. The parameter t is one of these triangles. The parameter r is a ray object, with properties point which is the origin, and vector, which is the direction.
I also have these functions for finding normal and distance of a triangle.
function triangleNormal(s) {
var ca = Vector.subtract(s.points[2], s.points[0]);
var ba = Vector.subtract(s.points[1], s.points[0]);
var norm = Vector.unitVector(Vector.crossProduct(ca, ba));
return norm;
}
function triangleDistance(t) {
return Vector.dotProduct(triangleNormal(t, 0), t.points[0]);
}
When I render my scene the triangle I use in my scene is red colored. No matter how far I move my camera back the triangle fills the whole scene red. I do not know why this happens.
An important error in your plane intersection code is this comparison operation:
a === 0
There are two things wrong with it:
For ray tracing you want the ray to hit planes in front of its source, not behind it, so you need a < 0.
Even if you did want the ray to hit planes behind it, you must never do equality operations between floating point values, because floating point calculations are not exact. (You must do something like abs(a) < 1e-6f or some small value)
I have an animation object, AnimateJS, in the example below. It functions to draw lines along a spiral.
I would like to convert it to Snap's mina(). I'm have a bit of difficulty in applying the various mina properties to make this happen.
Any help would be appreciated.
<!DOCTYPE HTML>
<html>
<head>
<script type="text/javascript" src="http://svgDiscovery.com/_SNP/snap.svg-min.js"></script>
</head>
<body onload="setTimeout(runAnimLinear,1000)" >
<h4>Draw Spiraled Lines</h4>
<div style='width:90%;background-color:gainsboro;text-align:justify;padding:10px;border-radius:6px;'>
Use Snap to animate lines drawn along a spiral </div>
<table><tr>
<td>
<table>
<tr><td colspan=2><b>This Example's AnimateJS Settings:</b></td></tr>
<tr><td>1. Smoothness</td><td>80 frames per second</td></tr>
<tr><td>2. Duration</td><td>3000 - runtime in ms</td></tr>
<tr><td>3. Range</td><td> 720 degrees : ending/maximum value</td></tr>
<tr><td>4. Output Equation</td><td><span style=color:blue>function</span> linear(p){return p} </td></tr>
<tr><td>5. Application Output </td><td><span style=color:blue>function</span> addToSpiral(angle)</td></tr>
</table><p></p>
The above values to be used in <b>mina(a, A, b, B, get, set, [easing])</b> <br>
<textarea style=border-width:0px;width:400px;height:180px;>
Parameters:
a - start slave number
A - end slave number
b - start master number (start time in general case)
B - end master number (end time in general case)
get - getter of master number (see mina.time)
set - setter of slave number
easing - oneasing function, default is mina.linear
</textarea>
</td>
<td>
<div id="svgDiv" style='background-color:lightgreen;width:400px;height:400px;'>
<svg id="mySVG" width="400" height="400">
</svg>
</div>
<center><button disabled id=runAgainButton onClick=clearLines();runAnimLinear();this.disabled=true>run again</button></center>
</td>
</tr> </table>
</center>
<script>
var SNPsvg = Snap("#mySVG");
var SpiralG = SNPsvg.g().attr({id:'SpiralG',strokeWidth:1,stroke:'black' });
/*---generalized animate core function
Allows progress/output to follow a specific/customized equation(delta)
by: Ilya Kantor - http://javascript.info/tutorial/animation
*/
var AnimateJS=function(options){
this.options=options
var start = new Date
var iT = setInterval(
function(){
var timePassed = new Date - start
var progress = timePassed / options.duration
if (progress > 1) progress = 1
this.progress=progress
var delta = options.delta(progress)
options.output(delta)
if (progress == 1)clearInterval(iT);
},options.delay)
}
//--onload, and 'run again' button---
function runAnimLinear()
{
var range=720 //--degrees: 2 revs---
var FPS=80 //---Frames Per Second = smoothness--
var delay=1000/FPS //---delay---
var duration=3000 //---duration ms, 3 seconds---
var delta=function linear(p){return p}//---linear---
//---this animation---
new AnimateJS({delay:delay,duration:duration,delta:delta,output:
function(delta)//---output: delta=0.0 thru 1.0---
{
addToSpiral(range * delta )
if(progress==1) //---finished---
{
runAgainButton.disabled=false
}
}})
}
//---fired at each output request---
function addToSpiral(angle)
{
radius = Constant*angle;
offsetX = radius*Math.cos(angle*Math.PI/180);
offsetY = radius*Math.sin(angle*Math.PI/180);
currentX = basePointX+offsetX;
currentY = basePointY-offsetY;
// add perpendicular line segments...
lineX = lineHHLength*Math.cos(
branches*angle*Math.PI/180);
lineY = lineHHLength*Math.sin(
branches*angle*Math.PI/180);
fromX = currentX-lineX;
fromY = currentY+lineY;
destX = currentX+lineX;
destY = currentY-lineY;
lineNode = SNPsvg.line(fromX,fromY,destX,destY)
SpiralG.append(lineNode);
lineX = lineHHLength*Math.cos(
branches*(angle+90)*Math.PI/180);
lineY = lineHHLength*Math.sin(
branches*(angle+90)*Math.PI/180);
fromX = currentX-lineX;
fromY = currentY+lineY;
destX = currentX+lineX;
destY = currentY-lineY;
lineNode = SNPsvg.line(fromX,fromY,destX,destY)
SpiralG.append(lineNode);
}
//--fired on 'run again' ---
function clearLines()
{
SpiralG.clear()
}
//----spiral variables---
var basePointX = 180.;
var basePointY = 170.;
var currentX = 0.;
var currentY = 0.;
var offsetX = 0.;
var offsetY = 0.;
var radius = 0.;
var minorAxis = 12;
var majorAxis = 20.;
var Constant = 0.25;
var fromX = 0.;
var fromY = 0.;
var destX = 0.;
var destY = 0.;
var lineX = 0.;
var lineY = 0.;
var branches = 3.;
var lineHVLength = 2*minorAxis;
var lineHHLength = 2*majorAxis;
var lineNode = null;
</script>
</body>
</html>
Do you really need mina ? Snap has a generic animation method called Snap.animate() (docs here). This doesn't act on a specific element (unlike element.animate() ); So you rarely need to get down and dirty with mina specifically (don't think I have ever needed to), but appreciate this isn't answering your question specifically.
The first 2 arguments are the start and end values (note, it can take an array of values as well I think to interpolate between). And these will get interpolated between (like the delta I think in the original code).
3rd argument is the function to call each time. The val/delta gets passed into this function.
4th argument is the easing (so mina.linear wanted here).
5th argument is the callback (so we reset the button to run the animation again if wanted)
The main core is this converted function..
function runAnimLinear() {
var range=720 //--degrees: 2 revs---
var duration=3000 //---duration ms, 3 seconds---
Snap.animate(0, 1, function( delta ) {
addToSpiral( range * delta )
}, duration, mina.linear, function() { runAgainButton.disabled=false } );
}
The rest I've left as is.
However, there are some issues here now depending if things like smoothness are really needed, so it's not quite the same. If so, I 'think' it would need another solution, which would be a bit more complex and I'm not sure if there would then be enough of a reason to not use the original. If it's a specific reason you need to use mina, maybe add that to the question.
jsfiddle
Here is kind of the same thing using mina to give an idea of how that works.
var anim = mina( 0, 1, mina.time(), mina.time() + duration, mina.time,
function( delta ) { addToSpiral( range * delta )})
//callback using eve, referencing the id of the animation from above
eve.once("mina.finish." + anim.id, function () {
runAgainButton.disabled=false
});
jsfiddle
Here's the code, below is a link to the jsfiddle.
The first row of the block isn't drawn for some reason, all these 2 dimension for loops are hard for me to wrap my head around, and can't find the reason why the block is only being drawn from the second row and not complete.
The add_block function is supposed to read array data from any block and put it on the grid where my x and y coordinates are.
If there's anyone who knows how to rotate the block, that would be cool too, i know in order to turn something +90 degrees i need to transpose and then revese each row, but it hasn't really worked in earlier tries.
I know i'm not good at explaining but i'll be sure to answer any of your questions.
Thanks in advance! I really want to have a complete picture of how arrays and double for loops interact with eachother, in my head.
var canvas = document.getElementById('c');
var ctx = canvas.getContext('2d');
canvas.width = 300;
canvas.height = 500;
var grid_columns = 10;
var grid_rows = 15;
var grid_cell_size = 10;
var grid = [];
function create_empty_grid(){
for(var i=0;i<grid_columns;i++){
grid[i] = [];
for(var j=0;j<grid_rows;j++){
grid[i][j] = 0;
}
}
// END DOUBLE FOR LOOPS
}
function clear_grid(){
for(var i=0;i<grid_columns;i++){
for(var j=0;j<grid_rows;j++){
grid[i][j] = 0;
}
}
// END DOUBLE FOR LOOPS
}
var x = 0;
var y = 0;
var w = 2;
var h = 3;
var block = [];
block[0] = [
[1,0,0,0],
[1,1,0,0],
[0,1,0,0],
[0,0,0,0]
];
function add_block(num){
var b = block[num];
for(var i=0;i<w;i++){
for(var j=0;j<h;j++){
if(i >= x && j >= y && i <= w && j <= h){
grid[i][j] = b[i][j];
}
}
}
// END DOUBLE FOR LOOPS
}
function draw(){
for(var i=0;i<grid_columns;i++){
for(var j=0;j<grid_rows;j++){
ctx.fillStyle = "black";
if(grid[i][j] === 1){
ctx.fillStyle = "red";
}else if(grid[i][j] === 0){
ctx.fillStyle = "green";
}
ctx.fillRect(i*grid_cell_size,j*grid_cell_size,grid_cell_size-1,grid_cell_size-1);
}
}
// END DOUBLE FOR LOOP
}
function update(){
}
function tick(){
clearRect(0,0,canvas.width,canvas.height);
clear_grid();
draw();
update();
}
create_empty_grid();
add_block(0);
draw();
View in jsfiddle
It seems like you're using i and j to represent "block" coordinates, and
x and y to represent "grid" coordinates. So I think this condition is wrong:
if(i >= x && j >= y && i <= w && j <= h){
grid[i][j] = b[i][j];
}
I think all you really need here is to replace the if statement with something like:
grid[i+x][j+y] = b[i][j];
But as #Arnauld pointed out, there is also a bit of confusion about whether i,j denotes "row, column" or "column, row", and it looks like your usage is opposite from the way the arrays are initialized. In other words, you're using:
grid[row][column] and b[row][column]
but the way you laid out the arrays, it needs to be
grid[column][row] and b[column][row]
So you'll need to make a few adjustments here and there to make the code do what you want.
The "b" arrays were upside-down for some reason so i changed grid[j+x][i+y] = b[j][i] to grid[j+x][i+y] = b[i][j] this displays the full block, but i still can't visualise the arrays or be able to prevent these problems in the future.
Arrays are so confusing to me.
I'll figure it out eventually i guess, but a day has to go by.