Model viewer

This tool allows you to see the models in their full splendor. Just copy the models inside the same folder with the viewer, type their name, choose some start / end frames, set the ambient and / or the animation speed and enjoy the view!

Let's see some string definitions first:

string modelname_str = "                    "; // 20 chars, stores the model name
string start_str = "     "; // holds the start frame
string end_str = "     "; // holds the end frame
string empty_str = ""; // an empty string

The main panel includes 2 sliders that control the animation speed and the ambient light:

panel main_pan
{
     bmap = main_pcx;
     pos_x = 824;
     pos_y = 0;
     layer = 10;
 
     vslider = 35, 340, 200, slider_pcx, 1, 50, frame_speed;
     vslider = 133, 340, 200, slider_pcx, 0, 100, model_ambient;

     digits = 95, 155, 3, arial_font, 1, frame_speed;
     digits = 85, 210, 4, arial_font, 1, player.frame;

     on_click input_data;
 
     flags = refresh, d3d, visible;
}

If we click this panel, function input_data() will run. We will use 3 texts to store and display the model name, the starting animation frame and the end animation frame:

text modelname_txt
{
     layer = 30;
     pos_x = 857;
     pos_y = 628;
     font = arial_font;
     string = modelname_str;
}

text start_txt
{
     layer = 30;
     pos_x = 900;
     pos_y = 45;
     font = arial_font;
     string = start_str;
}

text end_txt
{
     layer = 30;
     pos_x = 900;
     pos_y = 100;
     font = arial_font;
     string = end_str;
}

Let's see the code inside function main:

function main()
{
     level_load (mview_wmb);
     wait (2);
     camera.x = -150;
     camera.y = 0;
     camera.z = 30;
     clip_size = 0; // show all the triangles for all the models
     on_d = null; // disable the debug panel (key "D")
     fps_max = 40; // lock the frame rate to 40 fps
     mouse_init();
     while (1)
     {
          if ((mouse_pos.x < 824) || (mouse_mode == 0)) // if the pointer isn't placed over the panel
          {
               camera.x += 10 * cos(camera.pan) * (mouse_right - mouse_left) * time;
               camera.y += 10 * sin(camera.pan) * (mouse_right - mouse_left) * time;
               camera.z += 30 * sin(camera.tilt) * (mouse_right - mouse_left) * time;
               camera.pan -= 5 * mouse_force.x * time;
               camera.tilt += 3 * mouse_force.y * time;
          }
          wait (1);
     }
}

We load the level (a simple hollowed cube), we wait for it to be loaded and then we set the initial camera position behind the model. We make sure that we will see all the triangles for all the models, we disable the debug panel and then we lock the frame rate to 40 fps.

If the mouse pointer isn't placed over the main panel, it is used to move the camera, with help from the 2 mouse buttons. If you took a good look at function main you might have noticed that I "forgot" to talk about mouse_init(). In fact, I wanted to show you its code here:

function mouse_init()
{
     mouse_map = cursor_pcx;
     mouse_mode = 2;
     while (1)
     {
          if (key_space == 1) {mouse_mode = 0;}
          else {mouse_mode = 2;}
          mouse_pos.x = pointer.x;
          mouse_pos.y = pointer.y;
          wait (1);
     }
}

This function sets the mouse pointer bitmap, makes it visible and moves it on the screen. If we press and hold the "space" key, the mouse pointer will disappear and we will be able to move inside the cube, in order to get a better view of our model.

Do you remember the main panel? Please take a look at its bitmap: main.pcx. If we click it this function will run:

function input_data()
{
     if ((mouse_pos.x > 855) && (mouse_pos.x < 985) && (mouse_pos.y > 630) && (mouse_pos.y < 650))
     {
          modelname_txt.visible = on;
          str_cpy (modelname_str, empty_str);
          while (str_cmpi (modelname_str, empty_str) == 1)
          {
               inkey modelname_str;
               wait (1);
          }
          str_cat(modelname_str, ".mdl");
          if (file_open_read(modelname_str) == 0)
          {
               beep; beep;
          }
          else // the model exists
          {
               if (player != null)
               {
                    ent_remove (player);
               }
               ent_create(modelname_str, nullvector, model_driver);
          }
     }

If we have clicked somewhere near the "Model name" text box, modelname_txt is made visible and its string is cleared. The while loop makes sure that we have typed a name for the model; the name of the model is stored in modelname_str. You don't have to type guard.mdl because the code adds .mdl to your input string, so use "guard", "warlock", etc (without quotes) for your model names.

If the model doesn't exist (you have typed a wrong name or you have forgotten to copy it in model viewer's folder), the engine will beep twice as an error message. If the model can be found in the current folder and its name was typed correctly, we remove the previously loaded model (if any) and we create the new model in the origin, attaching it the model_driver() function - more about this function a little later.

     if ((mouse_pos.x > 895) && (mouse_pos.x < 955) && (mouse_pos.y > 45) && (mouse_pos.y < 65))
     {
          start_txt.visible = on; // show the text
          str_cpy (start_str, empty_str);
          while (str_cmpi (start_str, empty_str) == 1) // make sure that the player has typed something
          {
               inkey start_str; // show the cursor -> store the input in start_str
               wait (1);
          }
          start_frame = str_to_num(start_str);
     }
     if ((mouse_pos.x > 895) && (mouse_pos.x < 955) && (mouse_pos.y > 100) && (mouse_pos.y < 120))
     {
          end_txt.visible = on; // show the text
          str_cpy (end_str, empty_str);
          while (str_cmpi (end_str, empty_str) == 1) // make sure that the player has typed something
          {
               inkey end_str; // show the cursor -> store the input in start_str
               wait (1);
          }
          end_frame = str_to_num(end_str);
     }
}

If we click somewhere near the "Start frame" or "End frame" boxes, we show the corresponding text and our input will be stored in start_str and end_str. Let's get back to model_driver():

function model_driver()
{
     my.albedo = 0;
     while (1)
     {
          camera.ambient = model_ambient;
          my.frame += 0.03 * frame_speed * time;
          if (my.frame > end_frame)
          {
               my.next_frame = start_frame;
          }
          else
          {
               my.next_frame = 0;
          }
          if (my.frame >= end_frame + 1)
          {
               my.frame -= (end_frame - start_frame + 1);
          }
          wait (1);
     }
}

This is the function associated to our model. I have set albedo to zero because I want the model to look as crisp as possible; camera.ambient gets model's ambient. The ambient and the animation speed are set using the 2 sliders on main_pan, remember?

The model loops between start_frame and end_frame set by us, using smooth frame interpolation. Here's an example that uses figures; it animates a model using start_frame = 8 and end_frame = 11.

if (my.frame > 11)
{
     my.next_frame = 8;
}
else
{
     my.next_frame = 0;
}
if (my.frame >= 12)
{
     my.frame -= 4;
}

I have included two models with this project: guard and warlock.
 
 

Calculator

This calculator allows you to add, substract, multiply and divide. I have added code that takes care of overflow as an example but you can improve the calculator by adding code that deals with divisions by zero, multiple operations (6 * 3 - 4 = 14 instead of 6 * 3 = 18, 18 - 4 = 14) and so on.

Let's start with some string definitions:

string result_string = "     "; // for 5 digits
string temp_string = "  "; // for 2 digits, number of the button (1..15)

Function main sets the background color to rgb = 000 (black), sets the mouse pointer bitmap, copies "0" to the display string because the calculator has run for the first time and then it allows us to move the mouse.

function main()
{
     screen_color.red = 0;
     screen_color.green = 0;
     screen_color.blue = 0;
     mouse_map = mouse_pcx;
     mouse_mode = 2;
     str_cpy(result_string, "0"); // start with a zero displayed on the screen
     while (1)
     {
          mouse_pos.x = pointer.x;
          mouse_pos.y = pointer.y;
          wait (1);
     }
}

We will use a panel for all the buttons and a text for the display:

panel main_pan
{
     bmap = main_pcx;
     pos_x = 0;
     pos_y = 0;
     layer = 10;

     button = 292, 180, button1_pcx, button1_pcx, button1_pcx, null, key_pressed, null;
     button = 332, 180, button2_pcx, button2_pcx, button2_pcx, null, key_pressed, null;
     button = 372, 180, button3_pcx, button3_pcx, button3_pcx, null, key_pressed, null;
     button = 292, 215, button4_pcx, button4_pcx, button4_pcx, null, key_pressed, null;
     button = 332, 215, button5_pcx, button5_pcx, button5_pcx, null, key_pressed, null;
     button = 372, 215, button6_pcx, button6_pcx, button6_pcx, null, key_pressed, null;
     button = 292, 250, button7_pcx, button7_pcx, button7_pcx, null, key_pressed, null;
     button = 332, 250, button8_pcx, button8_pcx, button8_pcx, null, key_pressed, null;
     button = 372, 250, button9_pcx, button9_pcx, button9_pcx, null, key_pressed, null;
     button = 292, 285, button0_pcx, button0_pcx, button0_pcx, null, key_pressed, null;
     button = 332, 285, buttonplus_pcx, buttonplus_pcx, buttonplus_pcx, null, key_pressed, null;
     button = 372, 285, buttonminus_pcx, buttonminus_pcx, buttonminus_pcx, null, key_pressed, null;
     button = 292, 320, buttonmultiply_pcx, buttonmultiply_pcx, buttonmultiply_pcx, null, key_pressed, null;
     button = 332, 320, buttondivide_pcx, buttondivide_pcx, buttondivide_pcx, null, key_pressed, null;
     button = 372, 320, buttonequal_pcx, buttonequal_pcx, buttonequal_pcx, null, key_pressed, null;

     flags = refresh, d3d, visible;
}
 
text display_text
{
     layer = 20;
     pos_x = 380;
     pos_y = 145;
     font = lcd_font;
     string = result_string;
     flags = visible;
}

The panel definition includes 15 buttons that will appear on the screen. When one of the buttons is clicked, function key_pressed will run and key_number will be set to a value between 1 (for button1_pcx) and 15 (for buttonequal_pcx), depending on what button was clicked. Let's see what this key_pressed function does:

function key_pressed(key_number)
{
     snd_play(key_wav, 70, 0);
     if (key_number >= 1 && key_number <= 10)
     {
          str_for_num(temp_string, key_number);
          if (str_len(temp_string) == 2)
          {
               str_clip(temp_string, 1);
          }
          if (str_cmp(result_string, "0"))
          {
               str_cpy(result_string, temp_string);
          }
          else // we add a digit to the existing number
          {
               if (str_len(result_string) < 5)
               {
                    str_cat(result_string, temp_string);
                    align_result();
               }
          }
     }

Every time we click a button on the panel, a sound is played. If we have pressed 1, 2, 3, ..., 9 or 0, we convert the corresponding key_number to a string. If we press the 10th button (zero) we have to change 10 (it is the 10th button!) to its correct value (0). That's an easy task because 10 is the only figure that has 2 digits so we simply str_clip the first char from 10 and we get 0.

If the display shows "0" and we have pressed a button, "0" will be replaced with our button. If the display shows something different our button will add another digit to the existing number, got it? You can run the project to see what I mean. I have limited the numbers that can be input to 5 digits. Every time we add a digit, function align_result() ... aligns the result. We'll talk about it later.

     if (key_number >= 11 && key_number <= 14)
     {
          display_text.pos_x = 380; // restore the initial pos_x
          str_cpy(result_string, "0");
          number_one = str_to_num(result_string);
          add = (key_number == 11);
          substract = (key_number == 12);
          multiply = (key_number == 13);
          divide = (key_number == 14);
     }

If we press "+", "-", "x" or ":" display_text needs to move at its initial position because the display will show "0". The first number appears on the display before pressing "+", "-", "x" or ":" and it is stored in number_one; the second number appears on the display before pressing "=" and it is stored in number_two. We have learned that when we click one of the buttons, we keep adding digits on the display. In fact, we keep adding elements to result_string, and now it is the time to convert this string to a number -> number_one.

If we have clicked "+", add will be set to 1 and substract, multiply and divide will be set to 0.

     if (key_number == 15) // we have pressed "="
     {
          number_two = str_to_num(result_string);
          result = (number_one + number_two) * add + (number_one - number_two) * substract + (number_one * number_two) * multiply + (number_one / number_two) * divide;
          if (result < 99999)
          {
               str_for_num(result_string, result);
          }
          else // overflow
          {
               str_cpy(result_string, "ERROR");
          }
          align_result();
          number_one = 0;
          number_two = 0;
          add = 0;
          substract = 0;
          multiply = 0;
          divide = 0;
          while (key_any == 0) {wait (1);} // don't erase the result - wait for the next mouse click
          str_cpy(result_string, "0"); // reset the display
          display_text.pos_x = 380; // restore the initial pos_x
     }
}

When we press "=", result_string (the string displayed on the screen before pressing "=") will be converted to a number and stored in number_two. Now we have to do the real math using a simple formula:

result = (number_one + number_two) * add + (number_one - number_two) * substract + (number_one * number_two) * multiply + (number_one / number_two) * divide;

Well, it is a simple formula because if we want to add 2 numbers add = 1 and substract, multiply and divide = 0 so our formula changes to

result = number_one + number_two

and this looks so much better, isn't it? The same thing happens if we choose "-", "x" or ":".

If the result of the operation is below 99,999 (we have decided to use 5 digits, remember?) the number will be converted to result_string so it will appear on the display. If the result exceeds 99,999 the display will show "ERROR". The result will be aligned using that mysterious align_result() function and then we will reset number_one, number_two, etc. As long as we haven't pressed any key / mouse button, the result will be displayed on the screen; when we press a key or we click a mouse button, the display will be reset and result_string will be moved to its initial position.

I can't stand the pressure! I have to show you the following function:

function align_result()
{
     display_text.pos_x = 380 - (str_len(result_string) - 1) * 18; // move / align the text to the left as more digits appear / disappear
}

This function is simple but it does its job: it aligns the text to the left as more digits appear or disappear. I know that a character has a width of 18 pixels (take a look at the lcd_font definition if you don't believe me) so I substract 18 * (number_of_digits - 1) from 380 pixels in order to move the text that displays result_string to the left or right.

Please remember that this calculator needs more error protection code for division by zero and so on. At this point the operations must be executed like this:

32 x 2 = (it will display 64) and then you will have to type 64 + 7 = (it will display 71)

You can't use 32 x 2 + 7 = ... but if you want to improve the code you can replace number_one and number_two with a big array and store the temporary numbers inside it.