Smooth model shading

You know the problem: when your models moves from light to darkness, its ambient changes suddenly. The same thing happens when the model moves from dark places to brighter parts of the level. Wouldn't it be nive to have models that smoothly change their ambient light depending on the amount of light in the level? The idea behind this snippet is simple: we set unlit = on for our model in order to make it insensitive to any light source, we get tex_light on the floor below its feet and then we set the ambient of the model depending on tex_light's value.

I wanted to show you the difference between normal shading and smooth shading, so I have placed 2 guards in a test level. One of the guards uses the standard patrol_path action and the other guard uses the new patrol_path_ok action, a slightly modified version of patrol_path:

action patrol_path_ok
{
actor_init();
smooth_shadow(); // the only change
// attach next path
temp.pan = 360;
temp.tilt = 180;
temp.z = 1000;
result = scan_path(my.x,temp);
if (result == 0) { my._MOVEMODE = 0; } // no path found

// find first waypoint
ent_waypoint(my._TARGET_X,1);

while (my._MOVEMODE > 0)
{
// find direction
temp.x = MY._TARGET_X - MY.X;
temp.y = MY._TARGET_Y - MY.Y;
temp.z = 0;
result = vec_to_angle(my_angle,temp);

force = MY._FORCE;

// near target? Find next waypoint
// compare radius must exceed the turning cycle!
if (result < 25) { ent_nextpoint(my._TARGET_X); }

// turn and walk towards target
actor_turnto(my_angle.PAN);
actor_move();

// Wait one tick, then repeat
wait(1);
}
}

You can see that the extra line calls our small function named smooth_shadow. Let's take a look at it:

function smooth_shadow()
{
my.unlit = on;
while (1)
{
vec_set(temp, my.x);
temp.z -= 1000;
trace_mode = ignore_me + ignore_passable + ignore_models + ignore_sprites + scan_texture;
trace (my.x, temp); // get tex_light
my.ambient = tex_light / 2.5;
waitt(4); // trace 4 times a second
}
}

This function traces 1000 quants below actor's origin in order to get the value of tex_light - the ambient value of the shadow map brightness. Tex_light can range from 0 to 255, so we divide its value by 2.5 in order to set the ambient for the model in the 0..100 range. If you think that your model is too dark you can use any value below 2.5

To see my test level, copy the \smooth folder in your Gamestudio folder, open smooth.wmp, build it and run. You will see 2 guards walking on the same path - I'll ley you guess who's the guy that uses smooth shading.

Flickgen

Have you ever wondered if A5 can be used as a wdl - generator? Some of the users have created great particle generators a while ago; you set some values / drag some sliders around and after you click a button a new wdl file is generated. All you have to do is to include it in your project and everything will run great. How is this possible?

First of all you must create a room and set a decent camera position. You create a few sliders / buttons / whatever - they will change what you see. When you are happy with the result, you press a button and a wdl file is created. This file can include comments, etc but you can be sure that it will include the action(s) / function(s) needed for your effect. Sounds easy, right? Let's see how it is done.

Flickgen comes from flickering light generator - this project generates code for flickering lights. You can set the color (RGB), light range, time on and time off. Before we start, take a look at the test.wdl file in the \flickgen folder - its content was generated by Flickgen.

Every standalone project has its own main function:

function main()
{
d3d_lightres = 1; // better dynamic lights
level_load (flickgen_wmb);
wait (2);
mouse_init();
}

The first line in main improves the quality of the dynamic lights; the test level is loaded and then we show the mouse pointer:

function mouse_init()
{
mouse_map = arrow_pcx;
mouse_mode = 2;
while (1)
{
mouse_pos.x = pointer.x;
mouse_pos.y = pointer.y;
wait (1);
}
}

My test level includes a model that has this action attached to it:

action camera_init
{
my.invisible = on;
my.passable = on;
vec_set (camera.pos, my.pos);
camera.pan = my.pan;
camera.tilt = my.tilt;
}

I have used an arrow model; change its position and angles to get a better view, because the camera is tied up to this arrow model. The object that generates the flickering lights is a cube model that runs the following action:

action flicker
{
my.passable = on;
while (1)
{
my.z = height;
my.lightred = red;
my.lightgreen = green;
my.lightblue = blue;
my.lightrange = distance;
waitt (interval1);
my.lightrange = 0;
waitt (interval2);
}
}

This action is quite simple, but all the variables in it are controlled by sliders; height is the height of the flickering light source, red green and blue are the values for lightred, lightgreen and lightblue, distance is the value for lightrange, interval1 and interval2 are the on / off values for the light. Let's see how these sliders work:
panel adjust_pan // main panel
{
bmap = panel_pcx;
layer = 20;
pos_x = 0;
pos_y = 0;
vslider = 2, 10, 100, slider_pcx, 0, 255, red;
vslider = 22, 10, 100, slider_pcx, 0, 255, green;
vslider = 42, 10, 100, slider_pcx, 0, 255, blue;
vslider = 62, 10, 100, slider_pcx, 1, 100, interval1;
vslider = 82, 10, 100, slider_pcx, 1, 100, interval2;
vslider = 102, 10, 100, slider_pcx, 0, 500, distance;
vslider = 122, 10, 100, slider_pcx, 10, 500, height;
flags = d3d, overlay, refresh, visible;
}

The first definition places the slider at (2,10) pixels, sets its vertical size to 100 pixels, uses the slider_pcx bitmap for the slider and changes the var named red between 0 and 255. The rest of the sliders work the same way.

If you have opened and run Flickgen you have noticed that the proper RGB, etc values are diaplayed on the screen. I'm using a simple panel definition for that:
panel digits_pan // shows the rgb... values
{
layer = 21; // appears over adjust_pan
pos_x = 0;
pos_y = 0;
digits = 0, 130, 3, univers_font, 1, red;
digits = 20, 140, 3, univers_font, 1, green;
digits = 40, 130, 3, univers_font, 1, blue;
digits = 60, 140, 3, univers_font, 1, interval1;
digits = 80, 130, 3, univers_font, 1, interval2;
digits = 100, 140, 3, univers_font, 1, distance;
digits = 120, 130, 3, univers_font, 1, height;
flags = d3d, overlay, refresh, visible;
}

The value for "red" appears at (0,130) pixels, using 3 digits, with univers_font, its value being multiplied by 1. Flickgen has 2 more buttons: save and quit. In fact, these buttons are 2 small panels - and their definitions are as simple as possible:

panel quit_pan // shows the quit button
{
bmap = quit_pcx;
layer = 21; // appears over adjust_pan
pos_x = 10;
pos_y = 190;
flags = d3d, overlay, refresh, visible;
on_click quit;
}

panel save_pan // shows the save button
{
bmap = save_pcx;
layer = 21; // appears over adjust_pan
pos_x = 10;
pos_y = 160;
flags = d3d, overlay, refresh, visible;
on_click save_file;
}

function quit()
{
exit; // exit the engine
}

When we press the "Quit" button, its associated quit function will run. You can see that the function simply shuts down the engine - but that's what we want, right? Let me throw in the definitions for 2 texts and a few more strings; we will use them right away:

text action_txt
{
layer = 21;
pos_x = 55;
pos_y = 176;
font = albertus_font;
string = action_str;
}

text filename_txt
{
layer = 21;
pos_x = 55;
pos_y = 204;
font = albertus_font;
string = filename_str;
}

string action_str = " "; // holds the action name
string filename_str = " "; // holds the file name
string empty_str = ""; // an empty string
string content_str; // holds the content of the file
string indent_str = " "; // 4 spaces here but you can use any other indent value
string temp_str = " "; // just a temporary string used to convert numbers to strings

If we press the "Save" button, the ugly save_file function will run:

function save_file()
{
action_txt.visible = on; // show the text
str_cpy (action_str, empty_str);
while (str_cmpi (action_str, empty_str) == 1) // make sure that the player has typed something
{
inkey action_str; // show the cursor -> store the input in filename_str
wait (1);
}
filename_txt.visible = on; // show the text
str_cpy (filename_str, empty_str); // clear previous inputs
while (str_cmpi (filename_str, empty_str) == 1) // make sure that the player has typed something
{
inkey filename_str; // show the cursor -> store the input in filename_str
wait (1);
}
waitt (16); // wait a second
action_txt.visible = off; // hide the text
filename_txt.visible = off; // hide the text

The action_txt text is made visible and then the action_str is cleared. We want to store the name of our new action in action_str; if we call inkey at this point, the player could press the enter key, without typing anything - and this would be bad. We can prevent this from happening by including the inkey instruction in a while loop; if action_str = "" (nothing), inkey keeps running until something is entered in the "Action name" field. The same things happens with the rest of the code that stores the file name in filename_str. The texts are visible for one more second and then they disappear.

str_cpy (content_str, empty_str); // clear the string
str_cat (content_str, "// include this file in your main c-script file\n// place any model in your level and attach it the ");
str_cat (content_str, action_str);
str_cat (content_str, " action");
str_cat (content_str, "\n\naction ");
str_cat (content_str, action_str);
str_cat (content_str, "\n{\n");
str_cat (content_str, indent_str);
str_cat (content_str, "while (1)\n");

The string content_str will be used to store all the text that will be written to our wdl file. Please open the test.wdl file that came with Flickgen or another file created by you with Flickgen, otherwise I might loose you on the way :) The string is cleared and then the commented lines are appended to the string. Our comments should include the name of our action, so we append the named stored in action_str to it. Time to add the word "action" to the 2nd comment line. I hope that you remember: \n will jump to the next line the same way you type Enter in a word processor. After 2 line feeds we begin to write "action", followed by a space and the name stored in action_str. Line feed, a winged bracket, another line feed. Now it is the time to add our indent_str, a string consisting of 4 spaces, and then the text "while (1)" and another line feed. Did you see all this stuff in the generated wdl file? Let's move on:

str_cat (content_str, indent_str);
str_cat (content_str, "{\n");
str_cat (content_str, indent_str);
str_cat (content_str, indent_str);
str_cat (content_str, "my.lightred = ");
red = int(red);
str_for_num (temp_str, red);
str_cat (content_str, temp_str);

We append another indent_str to content_str, a winged bracket and a line feed, 2 indent_str strings and then we append the text: "my.lightred = ". The value set for red by its associated slider is converted to an integer, converted to string and then appended to content_str. The same things happen for green, blue and distance - trust me on that.

str_cat (content_str, ";\n");
str_cat (content_str, indent_str);
str_cat (content_str, indent_str);
str_cat (content_str, "my.lightgreen = ");
green = int(green);
str_for_num (temp_str, green);
str_cat (content_str, temp_str);
str_cat (content_str, ";\n");
str_cat (content_str, indent_str);
str_cat (content_str, indent_str);
str_cat (content_str, "my.lightblue = ");
blue = int(blue);
str_for_num (temp_str, blue);
str_cat (content_str, temp_str);
str_cat (content_str, ";\n");
str_cat (content_str, indent_str);
str_cat (content_str, indent_str);
str_cat (content_str, "my.lightrange = ");
distance = int(distance);
str_for_num (temp_str, distance);
str_cat (content_str, temp_str);

Now it is the time to add the waitt(x) lines; we have a line feed, 2 * indent_str, the "waitt (" string, the integer value of interval1 is converted to string and appended to content_str, we add ")" and move to the next line and so on.

str_cat (content_str, ";\n");
str_cat (content_str, indent_str);
str_cat (content_str, indent_str);
str_cat (content_str, "waitt (");
interval1 = int(interval1);
str_for_num (temp_str, interval1);
str_cat (content_str, temp_str);
str_cat (content_str, ");\n");
str_cat (content_str, indent_str);
str_cat (content_str, indent_str);
str_cat (content_str, "my.lightrange = 0;\n");
str_cat (content_str, indent_str);
str_cat (content_str, indent_str);
str_cat (content_str, "waitt (");
interval2 = int(interval2);
str_for_num (temp_str, interval2);
str_cat (content_str, temp_str);
str_cat (content_str, ");\n");
str_cat (content_str, indent_str);
str_cat (content_str, "}\n");
str_cat (content_str, "}");

If you are still awake you will notice that the lines below add .wdl to our file name stored in filename_str, open the file for writing, write content_str in the file and close it. The nightmare is over, can you believe it?

str_cat (filename_str, ".wdl");
filehandle = file_open_write (filename_str);
file_str_write (filehandle, content_str);
file_close (filehandle);
}

The slider named P (position) moves the light up or down. If you plan to have a flickering light on the ceiling of your room, 300 quants above the floor, you can set P to 300 and see how it will look.
Using this code snippet as a starting point you can create any c-script wizard: particle generators, script editors, etc.