Tuesday, September 3, 2013

HTML5 canvas - part 2 - Spiraling Square

In the previous post I've talked about some introductory techniques and good habits. Let's expand and add to that. In this tutorial I will focus on making an animation on canvas that is a little bit more complex than just spawning random circles here and there. This kind of tasks may seem silly and useless but they are a good piece of learning curve. They help you get more comfortable with the language, get used to syntax, and certain techniques. You can think of a different exercise and do that rather than doing exactly this one - this is just what I came up with. Besides, these type of exercises are fun to do and you get a nice visual output :) So don't shy away from doing this type of stuff!

Task 1


I want to make a square board which I will fill with black squares going in a spiral. I want the leading square to be red. I want the animation to stop as soon as the whole board is filled. I want it to work regardless of the board size and the unit square size. If my board has an even number of squares I want the central one to be twice as big. If it's an odd number I want it to be normal size. (To imagine that better you probably should copy the code in order and run it and see for yourself.) Let's get started!

In general, it's good to keep all your variables and data in the var model={};  object. (If you are new to JavaScript, you should probably read up on that first. A couple simple things though - there are no classes but there are objects; arrays are a sub case of objects; functions are values.) There might be a couple top level variables like c and ctx and maybe a couple others for your own convenience. For instance, maybe I want to make a square board and manually set the size of the board and the unit square at the very beginning. Like this:

<html>
    <head>
        <title>Spiraling Square</title>
        <script type="text/javascript">
            var c, ctx;
            var unitSquare = 20;
            var boardSize = 15;

In my code things will depend on those variables but I will never alter them - they are kind of static and I arbitrarily decide how big or small I want the board and the unit square to be. So I set them at the very beginning and if I want to change it my calculations in the code will not suffer from it. You can set it to any natural number.

Now let's make our model object.

var model = {
                count : 0 ,
                x : -1 * unitSquare ,
                y : 0,
                size : unitSquare,
                N : boardSize * unitSquare
            };

model.count variable is for me to know which loop of the spiral I'm on.
model.x and model.y are the coordinates for the squares to be drawn at.
model.size by default is the size of the unitSquare (if boardSize is odd i will double the model.size later).
model.N is the actual size of the board (in pixels) based on what you set your first 2 vars to.

Now notice that model.x is set to -20 in this instance. This is because I call update() first and then draw things out. I will come back to this later.

Now let's make our window.onload function.

window.onload = function() {
                c = document.getElementById("myCanvas");
                c.width = model.N;
                c.height = model.N;
                ctx = c.getContext("2d");
                setTimeout(draw, 20);
            };

As you can see I'm actually setting the canvas width and height here rather than later. This way your canvas will be as big as your board.

Now the draw() function:

var draw = function(){
                update();
                ctx.fillStyle = "#000000";
                ctx.fill();
                ctx.beginPath();
                ctx.fillStyle = "#ff0000";
                ctx.rect(model.x , model.y , model.size , model.size);
                ctx.fill();
                setTimeout(draw, 20);
            };

Call update() at the beginning, set fill color to black and use it. This will fill all your previous shapes with black. So everything that's drawn on the board prior to whatever you're drawing right now will be of the same color. Then I set color to red and draw the leading square - at model.x , model.y coordinates and with the model.size of unitSquare (at the moment..) ctx.beginPath(); is important there - it separates your leading square from everything else that was drawn before.

And the update() function:

var update = function(){
    if ((model.x==model.y)&&(boardSize % 2 == 0)&&(model.x==model.N/2-unitSquare))
        model.size=2*unitSquare;
    else if((model.x <= model.N-2*unitSquare-model.count)&&(model.y==model.count))
        model.x+=unitSquare;
    else if ((model.x == model.N-unitSquare-model.count)&&(model.y<=model.N-2*unitSquare-model.count))
        model.y+=unitSquare;
    else if((model.y == model.N-unitSquare-model.count)&&(model.x>=model.count+unitSquare))
        model.x-=unitSquare;
    else if((model.x==model.count)&&(model.y>=unitSquare+model.count))
    {
        model.y-=unitSquare;
        if((model.y==model.count+unitSquare)&&(model.count<model.N/2-unitSquare))
            model.count+=unitSquare;
    }
};

That might looks confusing at first. But notice that there are no loops anywhere in the code and draw() only draws things and update() only alters the values.

First if is checking if our model.x and model.y are the same and if they are equal to the central coordinates of the board and if our board has an even number of squares. If that condition is met we double the model.size of our leading square. So this happens only when we reach the center and the board is even.

The 4 "else if"s look at where we are on the board, what cycle of the spiral we are on, and what direction we're going in.
going Right: model.x+=unitSquare, leave model.y alone. We go right until model.x gets to the maximum it can be. As soon as model.x is at max we change the direction to down.
going Down: model.y+=unitSquare, leave model.x alone. We go down until model.y gets to the maximum it can be. As soon as model.y is at max we change the direction to left.
going Left: model.x-=unitSquare, leave model.y alone. We go left until model.x gets to the minimum it can be. As soon as model.x is at min we change the direction to up.
going Up: model.y-=unitSquare, leave model.x alone. We go up until model.y gets to the minimum it can be. As soon as model.y is at min we change the direction to right thus completing the current cycle of the spiral or current square. The if condition nested here is for taking care of model.count  As soon as we complete the current spiral cycle we increment the model.count by unitSquare.

Now remember how initially I set model.y to 0 and model.x to -1*unitSquare? That is because the very first thing that happens is we get into go right conditional in update() which will increase model.x by a unitSquare, thus, bringing it to 0. And after that draw() will draw this out - literally it will draw the first square at 0,0 then go into update() again, draw the next square at 0,20 and so on. (20 being our unitSquare).

Finally, last bit of code:

</script>
    </head>
    <body style="padding: 0 ; margin: 0">
        <canvas id="myCanvas" style="border: 5px solid red" tabindex="1">
        </canvas>
    </body>
</html>

The math behind it:


It is important to keep in mind that the coordinates you're dealing with are for the top left of your unitSquare. So, for example, during going right:
   0 <= model.x <= 300-20-0   for the first line,
20 <= model.x <= 300-20-20  for the second line
...
model.count <= model.x <= model.N - unitSquare - model.count

so for going right my condition is
model.x <= model.N - 2*unitSquare - model.count
and model.y == model.count
If these conditions are met model.x += unitSquare.

so for CONDITION model.x <= model.N - 2*unitSquare - model.count OR you can write it as model.x < model.N - unitSquare - model.count because if that's met model.x += unitSquare thus bringing it to model.N - unitSquare - model.count  on the other hand i don't need to worry about where model.x starts because model.y==model.count means we just changed the direction from up to right and model.x min was set in the previous run of update().

Task 2


So let's complicate things a little bit. Now let's say I want the spiral to leave empty corners on each cycle - basically I want the 2 diagonals to be seen and for them to meet in the center with a red square.

For that, leave everything as is except the update() function which should look like this:

var update = function(){
    if((model.x <= model.N-2*unitSquare-model.count)&&(model.y==model.count))
        model.x+=unitSquare;
    if ((model.x == model.N-unitSquare-model.count)&&(model.y<=model.N-2*unitSquare-model.count))
        model.y+=unitSquare;
    if((model.y == model.N-unitSquare-model.count)&&(model.x>=model.count+unitSquare))
        model.x-=unitSquare;
    if((model.x==model.count)&&(model.y>=unitSquare+model.count))
    {
        model.y-=unitSquare;
        if((model.y==model.count+unitSquare)&&(model.count<model.N/2-unitSquare))
            model.count+=unitSquare;
    }
    if(model.x==model.y)
        if(boardSize % 2 == 0)
            if(model.x==model.N/2-unitSquare)
                model.size=2*unitSquare;
            else
                model.x+=unitSquare;
        else if(model.x<=model.N/2-unitSquare)
            model.x+=unitSquare;
};

So, pretty similar. Just no "else"s and slightly different rules for what happens when model.x==model.y. In there we look if boardSize is even or odd. If it is even and if model.x is in the center we double the size of the leading square otherwise we increase model.x by unitSquare. And if the boardSize  is odd then we just increase model.x by unitSquare. Without this bit model.x==model.y half of diagonal would be filled with black squares. So this way we tell it to skip over. The lack of "else"s takes care of the other 3 parts of the diagonals.

The end.

In part 3 of these tutorial series I will show you how to make the snake game. That will be a lot more work but a lot more rewarding, too.

Friday, August 30, 2013

HTML5 canvas - part 1 - draw(), update() functions, confetti animation

I've been playing around with HTML5 canvas. It's really neat. Canvas is great for making games or animations. If somebody doesn't know flash and wants to make some small simple animation it might be worth looking into. The beauty of it all is that there isn't that much to learn, especially if you know some JavaScript.

This is a pretty good tutorial set http://www.html5canvastutorials.com/tutorials/html5-canvas-tutorials-introduction/ also the w3schools is a pretty good starting point.

All tutorials seem to be putting the <script>...</script> into the body tag after the canvas tag. If instead you out that into window.onload it will make sure that everything is rendered before the script runs. So I changed the order a little bit:

<html>
    <head>
        <title>My canvas</title>
        <script type="text/javascript">
            var c;
            var ctx;

            window.onload = function() {
                c = document.getElementById("myCanvas");
                ctx = c.getContext("2d");
                // your code here
            }
        </script>
    </head>
    <body style="padding: 0 ; margin: 0">
        <canvas id="myCanvas" width="500" height="500" style="border: 5px solid red" tabindex="1">
        </canvas>
    </body>
</html>

One thing that seems to be worth mentioning that I didn't see in many tutorials is that it's a good idea and habit to keep your draw code separate from the code where you update variables. This is not really important when you start and do very simple things. However, as soon as the project is big enough it's just asking for trouble and headaches.

So, I make 2 functions - draw() and update().

window.onload = function() {
    c = document.getElementById("myCanvas");
    ctx = c.getContext("2d");
    // your code here
    setTimeout(draw, 20);
}
var draw = function(){
    update();
    // your code here
    setTimeout(draw, 20);
};
var update = function(){
    
    // your code here
    
};

draw() ends up being very plain and boring - just sets colors and draws out shapes, text, or images.

update() is where all the fun stuff takes place - where all the variables get altered.

One thing to keep in mind though, is that draw() keeps calling update() every time before it draws anything. It might get weird a little bit at first but you can get used to it.

Sometimes it feels like you absolutely have to update variables in draw. When that happens just rethink the solution - there are many ways you can get somewhere - you can take a bus... or walk. And although walking might seem like unnecessary and extra work you will be better of with it in the long run. While shortcuts are nice they can get you into trouble later on if your project is big enough.

Now, a small and very simple example.
Let's have a 500x500 canvas, where circles of random color will spawn in random places within the canvas without even partly exceeding the canvas border. Also let the circle radius vary from 2px to 10px.

<html>
    <head>
        <title>Confetti</title>
        <script type="text/javascript">
            var c;
            var ctx;
            var R, G, B;
            var circle = { x: 0 , y: 0 , radius: 0 };

            var randInt = function (min, max) {
                return Math.floor(Math.random() * (max + 1 - min)) + min;
            };

            window.onload = function() {
                c = document.getElementById("myCanvas");
                ctx = c.getContext("2d");
                setTimeout(draw, 100);
            };

            var draw = function(){
                update();
                ctx.fillStyle = "rgb("+ R +" , "+ G +" , "+ B +")";
                ctx.beginPath();
                ctx.arc(circle.x , circle.y , circle.radius , 0 , 2*Math.PI);
                ctx.fill();
                setTimeout(draw, 100);
            };

            var update = function(){
                R = randInt(0,255);
                G = randInt(0,255);
                B = randInt(0,255);
                circle.x = randInt(9,489);
                circle.y = randInt(9,489);
                circle.radius = randInt(2,10);
            };

        </script>
    </head>
    <body style="padding: 0 ; margin: 0">
        <canvas id="myCanvas" width="500" height="500" style="border: 5px solid red" tabindex="1">
        </canvas>
    </body>
</html>

That's it. You can copy the code into your text editor (I hope you're not using Notepad :) but it will do too) and open the file in your favorite browser.

var circle = { x: 0 , y: 0 , radius: 0 };
You can replace that with 3 normal variables rather than doing it that way, although I personally prefer it. So instead you could have:
var x=0;
var y=0;
var radius=0;
and remove all circle. from the code.

However later on it makes more sense to not have too many loose variables and group things up. Makes things more readable and manageable. Same way var R, G, B; could be replaced with 
var color = { R: 0 , G: 0 , B: 0 };

You can go further with this problem and for instance make circles not overlap. Or have squares instead of circles (that will actually make things easier). Or add random gradients... Just play around with it, change things, break things, fix things :) you know... have fun with it.

Next example will be more complex but still just a simple animation. But I will probably have to comment it out a little bit for it to make sense. So stay tuned!

Tuesday, August 20, 2013

Watercolor and collage

I took an "Introduction to art" class that I ended up not liking and quitting. But I did the first two assignments.

This one I started a year or so ago just never finished.


It's primarily done in watercolor, although I also used some gouache and inking. Originally, I was inspired by the movie "Color of Pomegranates" by S. Parajanov. I feel like I could have refined it a little bit more instead of settling down for how it turned out to be. But I feel like that about all the work I do - I am never happy with the result. Anyway.

Here's the second piece. This was my first collage I ever made.


This was for the assignment "Fantasy and Me". Two years ago I was designing a dragon girl costume for the Halloween party and I drew this girl. It's in watercolor. The dragon I copied from some book and watercolored it. The rest was cut out from various magazines and calendars... and most of it was put together in Photoshop. I have mixed feelings about it. Collage is not my thing and I would've never tried if it wasn't for the class. And I could never find enough of the "correct" components - some I needed flipped, some resized. Thank god for Photoshop - I would have been very miserable to put everything together on paper.

Thursday, May 16, 2013

T-shirt


This is available as a print on a t-shirt or a hoodie here:
http://www.redbubble.com/people/irina121d/works/10342966-the-scoobies

Just figured it was worth a shot to submit this somewhere and see what happens. I'm very tempted to put everything else aside and make a couple other designs from my favorite shows. Which are many.