ID:274452
 
As some of you may know, for a while now I've been in the market for a set of water icons for Incursion. I wanted 5 animated icons that would tile like this:
ABCDE
DEABC
BCDEA
EABCD
CDEAB

If you take a look at the pattern, you'll see that while it looks like it repeats every 5x5 tiles, it's actually made up of smaller tiles tilted at an angle. (Just take any four of the A's to see what I mean. They line up as a square, but tilted. Each side of the square is sqrt(5) tiles in length, and the area is 5 square tiles.) Each of the tiles fits the edges of another tile in a unique way:
 C   D   E   A   B
EAB ABC BCD CDE DEA
D E A B C

Now, I wanted something that looked like water waves bobbing up and down, maybe moving around a bit. But of course, I couldn't model simple ripples because ripples don't repeat in a square pattern; they're circular. It was just last week, however, that an idea struck me: straight waves (like corrugated ridges) might form a pleasing pattern when overlapping in different directions. A simple sine or cosine calculation would suffice for each wave.

A few thoughts occurred to me then. The first was that I couldn't just use any waves, in any direction or frequency. These would have to line up precisely with the tiles so they would fit. Which tiles? Well not the 5 icons I'd be making, and not the big 5x5 grid; they'd have to line up with the slanted squares.

And then I thought: What do I mean by "line up"? Well, in each tile, a wave would have to have the same exact height on one edge as on the opposite edge; by extension, all corners must share the same height. In the horizontal or vertical directions (in tile coordinates), the wave can repeat any number of times, as long as that number is an integer. When I thought about this some more, I realized this meant I could use waves whose crest lines slanted at unusual angles.

So now I was getting somewhere. A wave would have so many periods on the x'-axis (x',y' being the tile coordinates. for convenience written as (0,0) to (1,1)), and so many on the y'-axis. This I'd call RX and RY. Starting at one corner, you'd traverse RX periods along one edge and RY along another, making RX+RY periods from one corner to another. Either (but not both) of these numbers could be zero, for a horizontal or vertical wave. Negative numbers would work too; the wave would just go in the other direction.

So what is that direction, exactly? I needed to know the exact vector from one period to the next. Well, consider the case where RX=RY=1. Draw a line from (1,0) to (0,1); that's the first period out from (0,0). (1,1) is the second period (RX+RY=2). From the starting point (0,0), draw a line perpendicular to the first period. The length of that line is the length of a period. With a little calculation, the vector for this line turns out to be <RX,RY>/(RX2+RY2). Its total length is 1/sqrt(RX2+RY2).

The distance, in periods of the wave, from (x',y') to (0,0) is simply RX*x'+RY*y', which is convenient. So the wave can be expressed like this:
t = RX*x'+RY*y' + phi + clock*speed
z = A * cos( 2*pi*t )

A is the amplitude of the wave. The clock is a value that goes from 0 to 1; speed is an integer, usually 1 but sometimes 2 or 3 (for faster waves). The phi value is a random value from 0 to 1 that offsets the wave a little bit. And 2*pi is of course 360 degrees (in radians), which gives us a full period.

For amplitude, I wanted bigger waves (smaller RX,RY values) to be bigger in size. The direction vector for the wave is directly proportional to the wave's period. Thus, bigger waves, bigger direction, bigger value of 1/sqrt(RX2+RY2). So my wave amplitude is:
A = h * (1/sqrt(RX<SUP>2</SUP>+RY<SUP>2</SUP>)) / sum(1/sqrt(RX<SUP>2</SUP>+RY<SUP>2</SUP>))

What's h? That's the maximum or minimum height.

Now, this will be repeated for lots of waves. At each (x',y') point, the height of a wave is added to the total. As the clock goes from 0 to 1, the waves will move in an interference pattern; it looks more and more like natural water as the number of waves is increased. Once the clock reaches 1, or any integer value, the waves have each moved an integral number of periods; which is to say, they all appear to be in their original position again, which is how the animation can loop smoothly.

With me so far? Good, because now it gets complicated.

To draw semi-realistic looking water, I needed to simulate a ray tracer. That is, I needed a light source and some equations to calculate the color based on which direction the water surface is facing. For that, I'd need the normal vector for my water function. Fortunately, like with height, the normals can be added together for each wav (as long as you don't scale them until after you're done). To calculate a normal vector, I dipped into calculus and got a partial derivative of z:
t = RX*x'+RY*y' + phi + clock*speed
z = A * cos( 2*pi*t )

d[t] = RX*d[x'] + RY*d[y']
d[z] = -2*pi*A * sin( 2*pi*t) * d[t]
2*pi*A * sin( 2*pi*t) * (RX*d[x'] + RY*d[y']) + d[z] = 0

A 3-dimensional normal vector would be defined as the coefficients of d[x], d[y], and d[z] when they're on the same side of the equation. d[z]'s easy; its coefficient is 1. However, we don't have d[x] and d[y]; we have d[x'] and d[y']. You see, x' and y' are calculate from pixel locations.

Back to the slanted tiles a minute: Their horizontal edge go 2 tiles to the right, 1 down. I called these variables tiltx and tilty, with tiltsq=(tiltx2+tilty2). In this case, tiltx=2, tilty=1 (using standard pixel coordinates), so tiltsq=5. I also needed a tile size for the icon tiles, so tilesize=32 since BYOND uses 32x32 pixel tiles.
x' = (tiltx*x+tilty*y)/(tilesize*tiltsq)
y' = (tilty*x-tiltx*y)/(tilesize*tiltsq)

d[x'] = (tiltx*d[x]+tilty*d[y])/(tilesize*tiltsq)
d[y'] = (tilty*d[x]-tiltx*d[y])/(tilesize*tiltsq)

Now, substituting into the original tile-coordinate normal:
2*pi*A * sin( 2*pi*t) * (RX*d[x'] + RY*d[y']) + d[z] = 0

Coefficients:
d[x]: (tiltx*RX+tilty*RY)/(tilesize*tiltsq) * 2*pi*A * sin(2*pi*t)
d[y]: (tilty*RX-tiltx*RX)/(tilesize*tiltsq) * 2*pi*A * sin(2*pi*t)
d[z]: 1

Now, once all those normals are added up, you can scale the normal down to a length of 1. Now you need a light source (direction and color), so the direction vector to that needs to be scaled to a length of 1 also. Do a dot product on the light vector and the normal vector, and you have d. d is the cosine of the angle between the light source and the normal; if it's 1, the water surface is directly facing the light; -1, it's facing directly away; 0, the light just skims the surface.

I didn't go into details of refraction and shadows, which would have been way too complicated. Instead I just focused on diffuse reflection and specular highlights. For diffuse reflection I assumed a "mid-point" water color for light just glancing off:
water color = mid color + diffuse color * d

This is a little unrealistic, in that water surfaces facing away from the light are still not totally dark, though they'll still be darker than if they were facing the light; I rationalize it as a cheap substitute for light shining through a wave. For specular highlighting, I needed to raise the value of d (if greater than 0) to a power:
spec = d ** exponent, d>0

The exponent was high, but not too high. I think I used about 40 in the end, though these values all needed tweaking a lot. Finally, I multiplied spec (which was either 0-1 or just 0) by a specular highlight color--in the end I made it gray, not white--and added this to the water color, then cut off the final color at regular minimum and maximum boundaries.

In the end, I got an animated image of water as the clock value moved from 0 to 1 and then reset. I later rendered this as a static image, in 5x1-tile strips, so I could simply copy and paste icon-sized segments into DM.

And that's how you make water.

Lummox JR
You looked like you were having way too much fun doing that, and confusing alot of people in the process math was never my strong point I'll do things like you did when I HAVE to but no other times. (I think your new water adds another minute to getting into Incurrsion's already long login time)
In response to Nadrew
Nadrew wrote:
You looked like you were having way too much fun doing that, and confusing alot of people in the process math was never my strong point I'll do things like you did when I HAVE to but no other times. (I think your new water adds another minute to getting into Incurrsion's already long login time)

The water icons make a pretty small .dmi file. A previous water icon set I did (which was too light and too dark) was 80K, and I believe this new one should be about 50K though I didn't check. On a slow connection only getting 2 KBps, that should be only about half a minute at most.

Lummox JR
In response to Lummox JR
It takes me 5 minutes to login and I'm on 3kbps...
In response to Nadrew
Nadrew wrote:
It takes me 5 minutes to login and I'm on 3kbps...

That it takes that long in total doesn't surprise me. For every map, Incursion has to send a set of several hundred custom-built icons cobbled together from its own extensive set. This is, well, quite a bit. While these icons are less complex than the water icons (of which there are only 80 pixmaps), and therefore shouldn't be very big, the sheer number of them is quite large. Each territory icon also comes with a border highlight icon (of slightly more complexity), so that number is then doubled. Then there's rolldice.wav, another unknown (I can check on its size), plus during the game, all players' dice and army icons are sent to every client for caching so they'll appear in the text output.

Once Incursion is downloadable, some of this problem should go away. However there will always be a need to send the map icons.

Lummox JR
In response to Lummox JR
The sound doesn't matter I always have BYOND sound off, I never know when someone can slip a midi into the mix (midi freezes my computer).
And that's how you make water.

heh Seems like a lot of work to do just for water animation. My good effecient solution is just to make the tile blue which is small in size and effective with no calculus :).
In response to Theodis
Theodis wrote:
And that's how you make water.

heh Seems like a lot of work to do just for water animation. My good effecient solution is just to make the tile blue which is small in size and effective with no calculus :).

...not to mention goes better as a background for a flat, bold map. Now if Incursion had 3d geographic details on land to go with the 3d water, that would be nifty indeed, though I don't really see that happening...
Really? I just mix two parts H, one part O.

(I hope to God someone didn't beat me to this.)
In response to Lesbian Assassin
Lesbian Assassin wrote:
Really? I just mix two parts H, one part O.

(I hope to God someone didn't beat me to this.)

No one beat you to it. For instance, I, unfortunately, was too busy trying to squeeze water from a stone. Then I realized that's for blood.
In response to ACWraith
Really? I've been wondering who brought forth water from the stone... Moses says it was him, but I'm not sure I buy it.
In response to ACWraith
ACWraith wrote:
Lesbian Assassin wrote:
Really? I just mix two parts H, one part O.

(I hope to God someone didn't beat me to this.)

No one beat you to it. For instance, I, unfortunately, was too busy trying to squeeze water from a stone. Then I realized that's for blood.

No, no no!

It's soup from a stone, blood from a turnip!
In response to Skysaw
Well, if you hold a stone in your hand and squeeze tight enough, I guarantee you, you'll get blood.
In response to Lesbian Assassin
Lesbian Assassin wrote:
Well, if you hold a stone in your hand and squeeze tight enough, I guarantee you, you'll get blood.

You got me there. But may I recommend to you a pine cone? Or better still, a shot glass? It greatly reduces the value of "tight enough."

And if you must use a stone, banging it against your forehead might be more efficient as well.
In response to Skysaw
Skysaw wrote:
Lesbian Assassin wrote:
Well, if you hold a stone in your hand and squeeze tight enough, I guarantee you, you'll get blood.

You got me there. But may I recommend to you a pine cone? Or better still, a shot glass? It greatly reduces the value of "tight enough."

And if you must use a stone, banging it against your forehead might be more efficient as well.

I still think it's easiest to get blood from a vein.
In response to Leftley
Leftley wrote:
I still think it's easiest to get blood from a vein.

Arteries are easier. The heart does all the hard work for you.

Lummox JR
In response to Lummox JR
Im not sure whats weirder the seemingly endless complexity of LUMMOX JR's math concept for H2o or the fact that you pepole are trying to kill your selfs with rocks and or other objects.

Magus_XII
That had sooooooooo better be 8+ years away because where I am people seem to be having trouble on 'y = mx + b'.
In response to WizDragon
WizDragon wrote:
That had sooooooooo better be 8+ years away because where I am people seem to be having trouble on 'y = mx + b'.

Well, you could reach that point in less than that, though it's extremely doubtful you'd choose to do with the math what I did with it. Certainly I know of no math course that would go into that level of depth on a single problem.

Lummox JR
In response to Lummox JR
Only experience can bring you to that level (with the proper knowledge to learn it of course).
Page: 1 2