# $Id$ # $Revision$ # $HeadURL$ # $Date$ # $Source$ # Lifevis # # The base intention of this program is to provide an OpenGL engine capable of # rendering ascii-based map files in such a way as to semi-accurately display # the interior layout of a dwarven fortress and surroundings. # # It is Public Domain software. # # This program based on this: # ### ---------------------- ### OpenGL cube demo. ### ### Written by Chris Halsall (chalsall@chalsall.com) for the ### O'Reilly Network on Linux.com (oreilly.linux.com). ### May 2000. ### ### Released into the Public Domain; do with it as you wish. ### We would like to hear about interesting uses. ### ### Coded to the groovy tunes of Yello: Pocket Universe. ### ### Translated from C to Perl by J-L Morel ### ( http://www.bribes.org/perl/wopengl.html ) package Lifevis::Viewer; use 5.010; use strict; use warnings; our ($VERSION) = '$Revision$' =~ m{ \$Revision: \s+ (\S+) }x; # define minor version $VERSION = 0 + $VERSION / 1000; # define major version #use warnings::unused; #use diagnostics; =cut use criticism ( -exclude => [ 'ProhibitCallsToUndeclaredSubs', 'ProhibitConstantPragma', 'RequireExtendedFormatting', 'ProhibitComplexRegexes', 'ProhibitLongLines', 'ProhibitMagicNumbers', # TODO : Reconsider this. 'ProhibitCommentedOutCode', # TODO : Comment out in clean-up 'ProhibitFlagComments', # TODO : Comment out in clean-up 'ProhibitPostfixControls', 'RequireLineBoundaryMatching', 'ProhibitAccessOfPrivateData', 'RequireDotMatchAnything', 'RequireUseOfExceptions', 'RequireEmacsFileVariables', 'RequirePodSections', # TODO : Reconsider this. 'ProhibitCallsToUnexportedSubs', 'ProhibitTies' ], -severity => 1 ); =cut use Carp; use utf8; use English qw(-no_match_vars); $OUTPUT_AUTOFLUSH = 1; my $detached; use threads; use threads::shared; use Config::Simple; use LWP::Simple; use Win32; use Benchmark qw/:hireswallclock timesum timestr timediff/; use Time::HiRes qw ( time sleep ); use Coro qw[ cede schedule ]; use OpenGL::Image; use Math::Trig; use Win32; use Win32::Process::List; use Win32::Process; use Win32::Process::Memory; use Image::Magick; use Win32::GUI::Constants qw ( :window :accelerator ); use Win32::GuiTest qw( :FUNC ); use Math::Vec qw(:terse); =cut use Devel::AutoProfiler ( -pkg_exception => [ 'OpenGL', 'constants', 'df_internals' ], -sub_exception => [ 'GL_', '[a-z]st$', 'GLUT_' ], ); =cut use lib '.'; use lib '..'; use Lifevis::constants; use Lifevis::df_internals; use Lifevis::ProcessConnection; use Lifevis::Vtables; my $memory_use; $memory_use = 0; my %building_visuals = get_df_building_visuals(); my @TILE_TYPES = get_df_tile_type_data(); my %DRAW_MODEL; my %model_display_lists; my @ramps = get_ramp_bitmasks(); my %vtables = get_vtables(); my $config_loaded; my %c; tie %c, 'Config::Simple', 'lifevis.cfg'; $c{redraw_delay} = 0.5 / $c{fps_limit}; my $memory_limit; $memory_limit = $c{memory_limit}; $config_loaded = 1; my $time_slice = $c{time_slice}; my ( $xcount, $ycount, $zcount ); # dimensions of the map data we're dealing with, counts in cells my ( $x_max, $y_max ); # dimensions of the map data we're dealing with, counts in tiles my @cells; my @cell_strings; my %creatures_present; my $current_creat_proc_task = 0; my $max_creat_proc_tasks = 0; my %creatures; my %building_present; my %buildings; my $current_buil_proc_task = 0; my $max_buil_proc_tasks = 0; my @item_present; my $current_item_proc_task = 0; my $max_item_proc_tasks = 0; my %items; my $gap = 65535; my %bucket_strings; my ( $mouse_cursor_x, $mouse_cursor_y ); # cursor coordinates at last refresh, in tiles my ( $xmouse_old, $ymouse_old, $zmouse_old ) = ( 0, 0, 15 ); # current cursor coordinates, in tiles my ( $xmouse, $ymouse, $zmouse ) = ( 0, 0, 15 ); # Camera position and rotation variables. my ( $x_pos, $y_pos, $z_pos, $x_off, $y_off, $z_off, $x_rot, $y_rot ); my $min_x_range = 0; my $max_x_range = 0; my $min_y_range = 3; my $max_y_range = 3; my $current_data_proc_task = 0; my $max_data_proc_tasks = 0; my @cache; my @cache_bucket; my @texture_ID; my @tiles; # Settings for our light. Try playing with these (or add more lights). my @light_ambient = ( 0.7, 0.7, 0.7, 1.0 ); my @light_diffuse = ( 0.9, 0.9, 0.9, 1.0 ); #my @light_specular = ( 0.9, 0, 0, 1.0 ); my @light_position = ( -0.8, 1.5, 1.0, 0.0 ); my ( $submenid, $menid ); my $window_ID; my $DF_window; my $next_render_time = 0; my $middle_mouse = 0; my $last_mouse_x; my $last_mouse_y; my $mouse_dist = 100; my $cam_angle = 45; my $rotating = 0; my $changing_ceiling; my $ceiling_slice; my $ceiling_locked = 0; my $view_range_changed; my $memory_needs_clears = 0; my $memory_full_checks = 0; my $memory_clears = 0; my $memory_loop; my $all_protected; my $render_loop; my $redraw_needed; my $next_creature_time = 0; my $next_landscape_time = 0; my $next_building_time = 0; my $next_item_time = 0; my $next_cursor_time = 0; my $creature_delay_counter; my $creature_loop; my $location_delay_counter; my $loc_loop; my $landscape_delay_counter; my $land_loop; my $building_delay_counter; my $buil_loop; my $item_delay_counter; my $item_loop; my $force_rt = 0; my $pixels = ''; my %time = ( landscape => 0, creature => 0, building => 0, item => 0, cursor => 0 ); my @offsets; my $df_proc_handle; my $proc; my $occlusion_supported = 1; __PACKAGE__->run(@ARGV) unless caller(); BEGIN { share(%c); share($config_loaded); share($memory_use); share($memory_needs_clears); share($memory_limit); share($all_protected); my $thr = threads->create( { 'stack_size' => 64 }, \&update_memory_use ); $thr->detach(); sub update_memory_use { require Win32::OLE; import Win32::OLE qw(in); my @state_array; my $pid = $PROCESS_ID; my $sleep_time = 2; my $small_sleep_time = 0.1; sleep $small_sleep_time while ( !$config_loaded ); while (1) { @state_array = in( Win32::OLE->GetObject("winmgmts:\\\\.\\root\\CIMV2")->ExecQuery( 'SELECT PrivatePageCount FROM Win32_Process' . " WHERE ProcessId = $pid", 'WQL', 0x10 | 0x20 ) ); $memory_use = $state_array[0]->{PrivatePageCount}; if ( $memory_use > $memory_limit && !$all_protected ) { $memory_needs_clears = 1; sleep $small_sleep_time; } else { $all_protected = 0; sleep $sleep_time; } } Win32::OLE->Uninitialize(); return 1; } } sub check_for_new_version { my $source = 'http://code.google.com/p/dwarvis/wiki/LifevisVersionInfo'; my $new_version = get($source); return if !defined $new_version; if ( $new_version =~ m/-----(\d+?.\d+?)-----/ ) { $new_version = $1; } else { notify_user( "Could not identify version info during online check. Please check internet connection or contact Mithaldu." ); } notify_user( "New version $new_version available, please check the download section on [ http://dwarvis.googlecode.com ].") if ( $new_version + 0 ) > $VERSION; return; } sub run { check_for_new_version() if $c{update_checks}; use OpenGL qw/ :all /; use Lifevis::models; %DRAW_MODEL = get_model_subs(); # Some global variables. # Window and texture IDs, window width and height. # Object and scene global variables. $x_pos = 0; $y_pos = 0; $z_pos = 0; $x_rot = -45.0; $y_rot = 157.5; reposition_camera(); # sets up initial camera position offsets # hashes containing the functions called on certain key presses #my ( %special_inputs, %normal_inputs ); # disabled until we actually pipe stuff to lifevis again # TODO: Split these and ramp-tops into seperate models. Fix texturing on ramp models where i fucked up diagonals. # ------ # The main() function. Inits OpenGL. Calls our own init function, # then passes control onto OpenGL. Lifevis::ProcessConnection::initialize( $VERSION, $detached ); my $offsets_ref; ( $proc, $df_proc_handle, $offsets_ref ) = connect_to_DF(); @offsets = @{$offsets_ref}; extract_base_memory_data(); glutInit(); print 'setting up OpenGL environment... '; glutInitDisplayMode( GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH ); glutInitWindowSize( $c{window_width}, $c{window_height} ); # clamp viewer window to bottom of df window, right side ($DF_window) = FindWindowLike( 0, '^Dwarf Fortress$' ); my ( undef, undef, $right, $bottom ) = GetWindowRect($DF_window); ( $right, $bottom ) = ClientToScreen( $DF_window, $right, $bottom ); # reset to 0,0 if outside of screen my ( $screen_width, $screen_height ) = GetScreenRes(); ( $right, $bottom ) = ( $c{window_width}, 0 ) if ( $right > $screen_width or $bottom + $c{window_height} > $screen_height ); glutInitWindowPosition( $right - $c{window_width}, $bottom ); $window_ID = glutCreateWindow( PROGRAM_TITLE . " v$VERSION" ); # Open a window glutSetOption( GLUT_ACTION_ON_WINDOW_CLOSE, GLUT_ACTION_CONTINUE_EXECUTION ); glutIgnoreKeyRepeat(1); create_menu(); # Set up Callback functions #################################################### # Register the callback function to do the drawing. glutDisplayFunc( sub { $redraw_needed = 1; } ); glutIdleFunc( \&idle_tasks ); # If there's nothing to do, draw. # It's a good idea to know when our window's resized. glutReshapeFunc( \&resize_scene ); glutKeyboardFunc( \&process_key_press ); # And let's get some keyboard input. glutSpecialFunc( \&process_special_key_press ); glutMotionFunc( \&process_active_mouse_motion ); glutMouseFunc( \&process_mouse_click ); print "OpenGL environment ready.\n"; print "initializing OpenGL...\n"; # OK, OpenGL's ready to go. Let's call our own init function. initialize_opengl( $c{window_width}, $c{window_height} ); print "OpenGL initialized.\n"; # Print out a bit of help dialog. print PROGRAM_TITLE, "\n"; $loc_loop = new Coro \&location_update_loop; $loc_loop->ready; cede(); generate_model_display_lists(); $land_loop = new Coro \&landscape_update_loop; $land_loop->ready; $creature_loop = new Coro \&creature_update_loop; $buil_loop = new Coro \&building_update_loop; $item_loop = new Coro \&item_update_loop; $memory_loop = new Coro \&memory_control_loop; $render_loop = new Coro \&render_scene; $render_loop->prio(1); # Pass off control to OpenGL. # Above functions are called as appropriate. print "switching to main loop...\n"; $ceiling_slice = $zcount; glutMainLoop(); return; } ################################################################################ ## Rendering Functions ######################################################### ################################################################################ sub extract_base_memory_data { my $buf = ''; my $map_base;# offset of the address where the map blocks start $xcount = $proc->get_u32( $offsets[x_count] ); # map size in cells $ycount = $proc->get_u32( $offsets[y_count] ); $zcount = $proc->get_u32( $offsets[z_count] ); set_zcount_for_models($zcount); $x_max = ( $xcount * 16 ) - 1; $y_max = ( $ycount * 16 ) - 1; # checking whether the game has a map already $map_base = $proc->get_u32( $offsets[map_loc] ); croak 'Map data is not yet available, make sure you have a game loaded.' unless ($map_base); # get the offsets of the address storages for each x-slice and cycle through _ReadMemory( $df_proc_handle, $map_base, $xcount * 4, $buf ); my @xoffsets = unpack( 'L' x $xcount, $buf ); for my $bx ( 0 .. $xcount - 1 ) { # get the offsets of the address storages # for each y-column in this x-slice and cycle through _ReadMemory( $df_proc_handle, $xoffsets[$bx], $ycount * 4, $buf ); my @yoffsets = unpack( 'L' x $ycount, $buf ); for my $by ( 0 .. $ycount - 1 ) { $cells[$bx][$by][offset] = $yoffsets[$by]; } } return; } sub creature_update_loop { my $entry_time; my $full_crea_run; while (1) { $next_creature_time = time + $c{creature_update_delay} - $time{creature}; schedule(); $entry_time = time; my $buf = ""; my $t0 = time; _ReadMemory( $df_proc_handle, $offsets[creature_vector] + 4, 4 * 2, $buf ); my @creature_vector_offsets = unpack( 'L' x 2, $buf ); my $creature_list_length = ( $creature_vector_offsets[1] - $creature_vector_offsets[0] ) / 4; _ReadMemory( $df_proc_handle, $creature_vector_offsets[0], 4 * $creature_list_length, $buf ); my @creature_offsets = unpack( 'L' x $creature_list_length, $buf ); for my $creature ( values %creatures_present ) { $creature = 0; } for my $creature (@creature_offsets) { $creatures_present{$creature} = 1; } $current_creat_proc_task = 0; $max_creat_proc_tasks = $#creature_offsets; for my $creature (@creature_offsets) { #say $creature unless $full_crea_run; my $buf = ""; $current_creat_proc_task++; if ( ( time - $entry_time ) > $time_slice ) { cede(); $entry_time = time; } #say $proc->hexdump( $creature, 0x688 ); _ReadMemory( $df_proc_handle, $creature, 232, $buf ); next if ( defined $creatures{$creature}[string] and $creatures{$creature}[string] eq $buf ); $creatures{$creature}[string] = $buf; my $flags = unpack( "L", substr( $buf, 228, 4 ) ); $creatures{$creature}[flags] = $flags; next if $flags & 2; # extract coordinates of current creature and skip if out of bounds my $rx = unpack( "S", substr( $buf, 148, 2 ) ); next if ( $rx > $xcount * 16 ); my $rz = unpack( "S", substr( $buf, 152, 2 ) ); next if ( $rz > $zcount + 1 ); my $ry = unpack( "S", substr( $buf, 150, 2 ) ); # get name and race, but only if we don't have them yet, since they're unlikely to change if ( !defined $creatures{$creature}[name] ) { my $name_length = unpack( "L", substr( $buf, 120, 4 ) ); $creatures{$creature}[name] = substr( $buf, 4, $name_length ); } if ( !defined $creatures{$creature}[race] ) { my $race = unpack( "L", substr( $buf, 140, 4 ) ); $creatures{$creature}[race] = $race; } # update record of current creature $creatures{$creature}[c_x] = $rx; $creatures{$creature}[c_y] = $ry; # get old and new cell location and compare my $old_x = $creatures{$creature}[cell_x]; my $old_y = $creatures{$creature}[cell_y]; my $old_z = $creatures{$creature}[c_z]; my $bx = int $rx / 16; my $by = int $ry / 16; if ( !defined $old_x or $bx != $old_x or $by != $old_y or $rz != $old_z ) { # creature moved to other cell or is new # get creature list of old cell then cycle through it and remove the old entry if ( defined $old_x ) { $redraw_needed = 1; my $creature_list = $cells[$old_x][$old_y][creature_list][$old_z]; for my $entry ( @{$creature_list} ) { if ( $entry == $creature ) { $entry = $creature_list->[$#$creature_list]; pop @{$creature_list}; last; } } } # add entry to new cell and update cell coordinates push @{ $cells[$bx][$by][creature_list][$rz] }, $creature; $creatures{$creature}[cell_x] = $bx; $creatures{$creature}[cell_y] = $by; $creatures{$creature}[c_z] = $rz; } } my $t1 = time; $time{creature} = sprintf( "%.3f", $t1 - $t0 ); $redraw_needed = 1; $full_crea_run = 1; } } sub building_update_loop { my $entry_time; my $full_buil_run; while (1) { $next_building_time = time + $c{building_update_delay} - $time{building}; schedule(); $entry_time = time; my $buf = ""; my $t0 = time; _ReadMemory( $df_proc_handle, $offsets[building_vector] + 4, 4 * 2, $buf ); my @building_vector_offsets = unpack( 'L' x 2, $buf ); my $building_list_length = ( $building_vector_offsets[1] - $building_vector_offsets[0] ) / 4; _ReadMemory( $df_proc_handle, $building_vector_offsets[0], 4 * $building_list_length, $buf ); my @building_offsets = unpack( 'L' x $building_list_length, $buf ); for my $building ( values %building_present ) { $building = 0; } for my $building (@building_offsets) { $building_present{$building} = 1; } $current_buil_proc_task = 0; $max_buil_proc_tasks = $#building_offsets; for my $building (@building_offsets) { #say $building unless $full_buil_run; my $buf = ""; $current_buil_proc_task++; if ( ( time - $entry_time ) > $time_slice ) { cede(); $entry_time = time; } #say $proc->hexdump( $building, 0xD8 ); _ReadMemory( $df_proc_handle, $building, 30, $buf ); next if ( defined $buildings{$building}[string] and $buildings{$building}[string] eq $buf ); $buildings{$building}[string] = $buf; # extract coordinates of current creature and skip if out of bounds my $rx = unpack( "S", substr( $buf, 4, 2 ) ); next if ( $rx > $xcount * 16 ); my $rz = unpack( "S", substr( $buf, 28, 2 ) ); next if ( $rz > $zcount + 1 ); my $ry = unpack( "S", substr( $buf, 8, 2 ) ); my $vtable = unpack( "L", substr( $buf, 0, 4 ) ); # update record of current creature $buildings{$building}[b_x] = $rx; $buildings{$building}[b_y] = $ry; $buildings{$building}[b_vtable_const] = $vtable; $buildings{$building}[b_vtable_id] = $vtables{$vtable}; warn sprintf "UNKNOWN BUILDING VTABLE: %x\n", $vtable unless $vtables{$vtable}; # get old and new cell location and compare my $old_x = $buildings{$building}[b_cell_x]; my $old_y = $buildings{$building}[b_cell_y]; my $old_z = $buildings{$building}[b_z]; my $bx = int $rx / 16; my $by = int $ry / 16; if ( !defined $old_x || $bx != $old_x || $by != $old_y or $rz != $old_z ) { # creature moved to other cell or is new # get creature list of old cell then cycle through it and remove the old entry if ( defined $old_x ) { $redraw_needed = 1; my $building_list = $cells[$old_x][$old_y][building_list][$old_z]; for my $entry ( @{$building_list} ) { if ( $entry == $building ) { $entry = $building_list->[$#$building_list]; pop @{$building_list}; last; } } } # add entry to new cell and update cell coordinates push @{ $cells[$bx][$by][building_list][$rz] }, $building; $buildings{$building}[b_cell_x] = $bx; $buildings{$building}[b_cell_y] = $by; $buildings{$building}[b_z] = $rz; } } my $t1 = time; $time{building} = sprintf( "%.3f", $t1 - $t0 ); $redraw_needed = 1; $full_buil_run = 1; } } sub item_update_loop { my $full_loop_completed; my $largest_id = 0; my $entry_time; while (1) { $next_item_time = time + $c{item_update_delay} - $time{item}; schedule(); $entry_time = time; my $buf = ""; my $t0 = time; _ReadMemory( $df_proc_handle, $offsets[item_vector] + 4, 4 * 2, $buf ); my @item_vector_offsets = unpack( 'L' x 2, $buf ); my $item_list_length = ( $item_vector_offsets[1] - $item_vector_offsets[0] ) / 4; _ReadMemory( $df_proc_handle, $item_vector_offsets[0], 4 * $item_list_length, $buf ); my @item_offsets = unpack( 'L' x $item_list_length, $buf ); $current_item_proc_task = 0; $max_item_proc_tasks = $#item_offsets; my @sort = sort { $a <=> $b } @item_offsets; my @buckets; my $start = shift @sort; push @buckets, [ $start, $start ]; push @{ $buckets[$#buckets][2] }, $start; for my $item (@sort) { if ( $item < $buckets[$#buckets][1] + $gap ) { $buckets[$#buckets][1] = $item; push @{ $buckets[$#buckets][2] }, $item; } else { push @buckets, [ $item, $item ]; push @{ $buckets[$#buckets][2] }, $item; } } for my $bucket (@buckets) { if ( ( time - $entry_time ) > $time_slice ) { cede(); $entry_time = time; } my $string = ""; _ReadMemory( $df_proc_handle, $bucket->[0], $bucket->[1] - $bucket->[0] + 24, $string ); next if ( $string eq "" ); # cycle through items in bucket for my $item_address ( @{ $bucket->[2] } ) { $current_item_proc_task++; if ( ( time - $entry_time ) > $time_slice ) { cede(); $entry_time = time; } my $new_offset = $item_address - $bucket->[0]; my $buf = substr( $string, $new_offset, 24 ); # extract DF item id my $id = unpack( "L", substr( $buf, 20, 4 ) ); if ( $id > $largest_id ) { #say $largest_id; $largest_id = $id; } next if ( defined $items{$id}[string] and $items{$id}[string] eq $buf ); $items{$id}[string] = $buf; # extract state of current item and skip applicable items my $state = unpack( "L", substr( $buf, 12, 4 ) ); if ( !( $state & 0x1 ) # is not lying on the ground || ( $state & 0x1000000 ) # is hidden ) { $items{$id}[invisible] = 1; next; } # extract coordinates of current creature and skip if out of bounds my $rx = unpack( "S", substr( $buf, 4, 2 ) ); if ( $rx > $xcount * 16 ) { $items{$id}[invisible] = 1; next; } my $rz = unpack( "S", substr( $buf, 8, 2 ) ); next if ( $rz > $zcount + 1 ); my $ry = unpack( "S", substr( $buf, 6, 2 ) ); my $type = unpack( "L", substr( $buf, 0, 4 ) ); my $vtable = $type; #say $proc->hexdump( $item, 0x88 ),"\n "; # update record of current creature $items{$id}[i_x] = $rx; $items{$id}[i_y] = $ry; $items{$id}[i_type] = $type; $items{$id}[i_state] = $state; $items{$id}[i_address] = $item_address; $items{$id}[i_vtable_const] = $vtable; $items{$id}[i_vtable_id] = $vtables{$vtable}; warn sprintf "UNKNOWN ITEM VTABLE: %x\n", $vtable unless $items{$id}[i_vtable_id]; #say "X: $rx Y: $ry Z: $rz ST: $state T: $type" if ( !$full_loop_completed && $state & (1 << 0) && ( 0 || $state & (1 << 2) )); # get old and new cell location and compare my $old_x = $items{$id}[i_cell_x]; my $old_y = $items{$id}[i_cell_y]; my $old_z = $items{$id}[i_z]; my $bx = int $rx / 16; my $by = int $ry / 16; # if creature moved to other cell or is newly added if ( !defined $old_x || $bx != $old_x || $by != $old_y || $rz != $old_z ) { # if creature moved: get creature list of old cell then cycle through it and remove the old entry if ( defined $old_x ) { $redraw_needed = 1; my $item_list = $cells[$old_x][$old_y][item_list][$old_z]; for my $entry ( 0 .. @{$item_list} - 1 ) { if ( $item_list->[$entry] == $id ) { splice @{$item_list}, $entry, 1; last; } } } # add entry to new cell and update cell coordinates push @{ $cells[$bx][$by][item_list][$rz] }, $id; $items{$id}[i_cell_x] = $bx; $items{$id}[i_cell_y] = $by; $items{$id}[i_z] = $rz; } } } $full_loop_completed = 1; my $t1 = time; $time{item} = sprintf( "%.3f", $t1 - $t0 ); $redraw_needed = 1; } } sub location_update_loop { my $entry_time; while (1) { $next_cursor_time = time + $c{cursor_update_delay} - $time{cursor}; schedule(); $entry_time = time; my $t0 = time; my $old_ceiling_slice = $ceiling_slice; my $buf = ""; $xmouse_old = $xmouse; $ymouse_old = $ymouse; $zmouse_old = $zmouse; # get mouse data _ReadMemory( $df_proc_handle, $offsets[mouse_x], 4, $buf ); $xmouse = unpack( "L", $buf ); _ReadMemory( $df_proc_handle, $offsets[mouse_y], 4, $buf ); $ymouse = unpack( "L", $buf ); _ReadMemory( $df_proc_handle, $offsets[mouse_z], 4, $buf ); $zmouse = unpack( "L", $buf ); #say $proc->get_u32( $OFFSETS[$ver]{menu_state} ); #say $proc->get_u32( $OFFSETS[$ver]{view_state} ); # use viewport coords if out of bounds, i.e. cursor not in use if ( $xmouse > $xcount * 16 || $ymouse > $ycount * 16 || $zmouse > $zcount ) { _ReadMemory( $df_proc_handle, $offsets[viewport_x], 4, $buf ); my $viewport_x = unpack( "L", $buf ); _ReadMemory( $df_proc_handle, $offsets[window_grid_x], 4, $buf ); my $window_grid_x = unpack( "L", $buf ); _ReadMemory( $df_proc_handle, $offsets[viewport_y], 4, $buf ); my $viewport_y = unpack( "L", $buf ); _ReadMemory( $df_proc_handle, $offsets[window_grid_y], 4, $buf ); my $window_grid_y = unpack( "L", $buf ); _ReadMemory( $df_proc_handle, $offsets[viewport_z], 4, $buf ); my $viewport_z = unpack( "L", $buf ); $xmouse = $viewport_x + int( $window_grid_x / 6 ); $ymouse = $viewport_y + int( $window_grid_y / 3 ); $zmouse = $viewport_z; } $ceiling_slice = $zmouse if $ceiling_locked; if ( $xmouse != $xmouse_old || $ymouse != $ymouse_old || $zmouse != $zmouse_old || $ceiling_slice != $old_ceiling_slice || $view_range_changed ) { $view_range_changed = 0; $redraw_needed = 1; # update camera system with mouse data ( $x_pos, $z_pos, $y_pos ) = ( $xmouse, $ymouse, $zmouse ); reposition_camera(); # sets up initial camera position offsets # calculate cell coords from mouse coords my $xcell = int $xmouse / 16; my $ycell = int $ymouse / 16; $xcell = $c{view_range} if $xcell <= $c{view_range} - 1; $ycell = $c{view_range} if $ycell <= $c{view_range} - 1; $xcell = $xcount - $c{view_range} - 1 if $xcell >= $xcount - $c{view_range}; $ycell = $ycount - $c{view_range} - 1 if $ycell >= $ycount - $c{view_range}; $min_x_range = $xcell - $c{view_range}; $min_x_range = 0 if $min_x_range < 0; $max_x_range = $xcell + $c{view_range}; $max_x_range = $xcount - 1 if $max_x_range > $xcount - 1; $min_y_range = $ycell - $c{view_range}; $min_y_range = 0 if $min_y_range < 0; $max_y_range = $ycell + $c{view_range}; $max_y_range = $ycount - 1 if $max_y_range > $ycount - 1; } my $t1 = time; $time{cursor} = sprintf( "%.3f", $t1 - $t0 ); } } sub landscape_update_loop { my $full_land_run; my $entry_time; while (1) { $next_landscape_time = time + $c{landscape_update_delay} - $time{landscape}; schedule(); $entry_time = time; my $buf = ""; my $t0 = time; #TODO: When at the edge, only grab at inner edge. # cycle through cells in range around cursor to grab data for my $bx ( $min_x_range - 1 .. $max_x_range + 1 ) { next if ( $bx < 0 || $bx > $xcount - 1 ); # skip if block is outside the map next if ( $bx < $min_x_range - 1 || $bx > $max_x_range + 1 ); # skip if block is outside the current view range for my $by ( $min_y_range - 1 .. $max_y_range + 1 ) { next if ( $by < 0 || $by > $ycount - 1 ); # skip if block is outside the map next if ( $by < $min_y_range - 1 || $by > $max_y_range + 1 ) ; # skip if block is outside the current view range # cycle through slices in cell _ReadMemory( $df_proc_handle, $cells[$bx][$by][offset], 4 * $zcount, $buf ); my @zoffsets = unpack( 'L' x $zcount, $buf ); $cells[$bx][$by][changed] = 0 if !defined $cells[$bx][$by][changed]; for my $bz ( 0 .. $#zoffsets ) { # go to the next block if this one is not allocated next if ( $zoffsets[$bz] == 0 ); #say $zoffsets[$bz] unless $full_land_run; # process slice in cell and set slice to changed my $slice_changed = new_process_block( $zoffsets[$bz], # offset of the current slice $bx, # x location of the current slice $by, # y location of the current slice $bz ); # update changed status of cell if necessary if ($slice_changed) { $cells[$bx][$by][z][$bz] = 1; # slice was changed $cells[$bx][$by][changed] = 1; # cell was changed } if ( ( time - $entry_time ) > $time_slice ) { cede(); $entry_time = time; } $current_data_proc_task++; } } } # cycle through cells in range around cursor to generate display lists for my $bx ( $min_x_range .. $max_x_range ) { for my $by ( $min_y_range .. $max_y_range ) { my $cache_id; if ( defined $cells[$bx][$by][cache_ptr] ) { $cache_id = $cells[$bx][$by][cache_ptr]; # cell is in cache if ( $cells[$bx][$by][changed] ) { # cycle through slices and # create displaylists as necessary, # storing the ids in the cache entry my $slices = $cells[$bx][$by][z]; for my $slice ( 0 .. ( @{$slices} - 1 ) ) { if ( @{$slices}[$slice] ) { generate_display_list( $cache_id, $slice, $by, $bx ); @{$slices}[$slice] = 0; } $redraw_needed = 1; if ( ( time - $entry_time ) > $time_slice ) { cede(); $entry_time = time; } $current_data_proc_task++; } $cells[$bx][$by][changed] = 0; } $cache[$cache_id][use_counter]++; } else { my $slices = $cells[$bx][$by][z]; next if !defined $slices; # cell is not in cache # get fresh cache id either from end of cache or out of bucket $cache_id = $#cache + 1; $cache_id = pop @cache_bucket if ( $#cache_bucket > -1 ); # set up link to cell and back-link to cache id $cache[$cache_id][cell_ptr] = \$cells[$bx][$by][cache_ptr]; $cells[$bx][$by][cache_ptr] = $cache_id; $cache[$cache_id][use_counter] = 0; # cycle through slices and # create displaylists as necessary, # storing the ids in the cache entry for my $slice ( 0 .. ( @{$slices} - 1 ) ) { if ( defined @{$slices}[$slice] ) { generate_display_list( $cache_id, $slice, $by, $bx ); @{$slices}[$slice] = 0; } $redraw_needed = 1; if ( ( time - $entry_time ) > $time_slice ) { cede(); $entry_time = time; } $current_data_proc_task++; } $cells[$bx][$by][changed] = 0; } } } $max_data_proc_tasks = $current_data_proc_task; $current_data_proc_task = 0; my $t1 = time; $time{landscape} = sprintf( "%.3f", $t1 - $t0 ); $redraw_needed = 1; $full_land_run = 1; } return; } sub memory_control_loop { while (1) { schedule(); $memory_full_checks++; my @protected_caches; # cycle through cells in range around cursor to generate display lists for my $bx ( $min_x_range .. $max_x_range ) { for my $by ( $min_y_range .. $max_y_range ) { if ( defined $cells[$bx][$by] && defined $cells[$bx][$by][cache_ptr] ) { $protected_caches[ $cells[$bx][$by][cache_ptr] ] = 1; } } } # TODO: Limit cache deletions so $c{view_range} is never undercut # check that we're not using too much memory and destroy cache entries if necessary my $delete; my $use; for my $id ( 0 .. $#cache ) { # skip empty caches next if !defined $cache[$id][use_counter]; # skip caches we're currently looking at next if $protected_caches[$id]; if ( !defined $use || $cache[$id][use_counter] < $use ) { $delete = $id; $use = $cache[$id][use_counter]; } } if ( defined $delete ) { $memory_clears++; # delete landscape display lists my $slices = $cache[$delete][display_lists]; for my $slice ( 0 .. @{$slices} - 1 ) { glDeleteLists( $cache[$delete][display_lists][$slice], 1 ) if ( $cache[$delete][display_lists][$slice] ); } # delete landscape masks $slices = $cache[$delete][mask_lists]; for my $slice ( 0 .. @{$slices} - 1 ) { glDeleteLists( $cache[$delete][mask_lists][$slice], 1 ) if ( $cache[$delete][mask_lists][$slice] ); } undef ${ $cache[$delete][cell_ptr] }; undef $cache[$delete]; push @cache_bucket, $delete; } else { $all_protected = 1; } $memory_needs_clears = 0; } } # TODO : comment this better sub generate_model_display_lists { for my $model ( keys %DRAW_MODEL ) { for my $part ( 0 .. $#{ $DRAW_MODEL{$model} } ) { next if !defined $DRAW_MODEL{$model}[$part]; my $dl = $DRAW_MODEL{$model}[$part]->( 0, 0, 0, 1, 0, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY ); $model_display_lists{$model}[$part] = $dl; } } } sub generate_display_list { my ( $id, $z, $y, $x ) = @_; my $dl; my $type; my $type_below; my $type_above; my $brightness_mod; if ( $cache[$id][display_lists][$z] ) { $dl = $cache[$id][display_lists][$z]; } else { $dl = glGenLists(1); $cache[$id][display_lists][$z] = $dl; } glNewList( $dl, GL_COMPILE ); for my $texture ( 0 .. $#texture_ID ) { # cycle through textures glBindTexture( GL_TEXTURE_2D, $texture_ID[$texture] ); # set texture my $tile = $tiles[$z][type]; # get pointers to current layer my $tile_below = $tiles[ $z - 1 ][type]; # as well as to layer above and below my $tile_above = $tiles[ $z + 1 ][type]; #my $occup = $tiles[$z][occup]; # get pointers to current layer for my $rx ( ( $x * 16 ) .. ( $x * 16 ) + 15 ) { # cycle through tiles in current slice on layer for my $ry ( ( $y * 16 ) .. ( $y * 16 ) + 15 ) { next if !defined $tile->[$rx][$ry]; # skip tile if undefined $type = $tile->[$rx][$ry]; # store type of current tile next if $type == 32; # skip if tile is air next if !defined $TILE_TYPES[$type][base_texture]; # skip if tile type doesn't have associated texture next if $TILE_TYPES[$type][base_texture] != $texture; # skip if tile type texture doesn't match current texture $type_below = $tile_below->[$rx][$ry]; $type_above = $tile_above->[$rx][$ry]; $brightness_mod = $TILE_TYPES[$type][brightness_mod]; my ( $above, $below, $north, $south, $west, $east ) = ( EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY ); my ( $northeast, $southeast, $southwest, $northwest ) = ( EMPTY, EMPTY, EMPTY, EMPTY ); my $x_mod = $rx % 16; my $y_mod = $ry % 16; $below = $TILE_TYPES[$type_below][base_visual] if $type_below; $above = $TILE_TYPES[$type_above][base_visual] if $type_above; $north = $TILE_TYPES[ $tile->[$rx][ $ry - 1 ] ][base_visual] if $tile->[$rx][ $ry - 1 ] && $y_mod != 0; $west = $TILE_TYPES[ $tile->[ $rx - 1 ][$ry] ][base_visual] if $tile->[ $rx - 1 ][$ry] && $x_mod != 0; $south = $TILE_TYPES[ $tile->[$rx][ $ry + 1 ] ][base_visual] if $tile->[$rx][ $ry + 1 ] && $y_mod != 15; $east = $TILE_TYPES[ $tile->[ $rx + 1 ][$ry] ][base_visual] if $tile->[ $rx + 1 ][$ry] && $x_mod != 15; given ( $TILE_TYPES[$type][base_visual] ) { when ( [ FLOOR, TREE, SHRUB, BOULDER, SAPLING, STAIR, STAIR_UP, STAIR_DOWN, PILLAR, FORTIF ] ) { $brightness_mod *= 0.75 if ( defined $type_above && $above != EMPTY ); } } my @const_model_map; $const_model_map[WALL] = "Wall"; $const_model_map[PILLAR] = "Pillar"; $const_model_map[FORTIF] = "Fortif"; $const_model_map[TREE] = "Tree"; $const_model_map[SHRUB] = "Shrub"; $const_model_map[BOULDER] = "Boulder"; $const_model_map[SAPLING] = "Sapling"; $const_model_map[STAIR] = "Stairs"; $const_model_map[STAIR_UP] = "Stair_Up"; $const_model_map[STAIR_DOWN] = "Stair_Down"; $const_model_map[FLOOR] = "Floor"; my $base_visual = $TILE_TYPES[$type][base_visual]; my $brightness = ( ( ( $z / ( $zcount - 1 ) ) * 0.6 ) + 0.3 + $brightness_mod ); given ($base_visual) { when ( [ WALL, PILLAR, FORTIF, TREE, SHRUB, BOULDER, SAPLING, STAIR, STAIR_UP, STAIR_DOWN, FLOOR ] ) { glColor3f( $brightness, $brightness, $brightness ); glTranslatef( $rx, $z, $ry ); for my $part ( 0 .. $#{ $DRAW_MODEL{ $const_model_map[$base_visual] } } ) { next if ( ( $base_visual == WALL ) && ( $part == east ) && ( $east == WALL ) ); next if ( ( $base_visual == WALL ) && ( $part == west ) && ( $west == WALL ) ); next if ( ( $base_visual == WALL ) && ( $part == north ) && ( $north == WALL ) ); next if ( ( $base_visual == WALL ) && ( $part == south ) && ( $south == WALL ) ); next if ( ( $base_visual != STAIR ) && ( $base_visual != STAIR_DOWN ) && ( $part == bottom ) && ( $below == WALL ) ); next if ( ( $base_visual != WALL ) && ( $part == east ) && ( $east != EMPTY ) && ( $east != RAMP_TOP ) ); next if ( ( $base_visual != WALL ) && ( $part == west ) && ( $west != EMPTY ) && ( $west != RAMP_TOP ) ); next if ( ( $base_visual != WALL ) && ( $part == north ) && ( $north != EMPTY ) && ( $north != RAMP_TOP ) ); next if ( ( $base_visual != WALL ) && ( $part == south ) && ( $south != EMPTY ) && ( $south != RAMP_TOP ) ); next if !defined $model_display_lists{ $const_model_map[$base_visual] }[$part]; glCallList( $model_display_lists{ $const_model_map[$base_visual] }[$part] ); } glTranslatef( -$rx, -$z, -$ry ); } when (RAMP) { next if ( defined $type_below && $TILE_TYPES[$type_below][base_visual] == RAMP ); $northeast = $TILE_TYPES[ $tile->[ $rx + 1 ][ $ry - 1 ] ][base_visual] if $tile->[ $rx + 1 ][ $ry - 1 ] && ( $ry != 0 || $rx != $x_max ); $southeast = $TILE_TYPES[ $tile->[ $rx + 1 ][ $ry + 1 ] ][base_visual] if $tile->[ $rx + 1 ][ $ry + 1 ] && ( $ry != $y_max || $rx != $x_max ); $southwest = $TILE_TYPES[ $tile->[ $rx - 1 ][ $ry + 1 ] ][base_visual] if $tile->[ $rx - 1 ][ $ry + 1 ] && ( $ry != $y_max || $ry != 0 ); $northwest = $TILE_TYPES[ $tile->[ $rx - 1 ][ $ry - 1 ] ][base_visual] if $tile->[ $rx - 1 ][ $ry - 1 ] && ( $ry != 0 || $ry != 0 ); my $surroundings = 0; $surroundings += ( $north == WALL ) ? 0b1000_0000 : 0; $surroundings += ( $west == WALL ) ? 0b0100_0000 : 0; $surroundings += ( $south == WALL ) ? 0b0010_0000 : 0; $surroundings += ( $east == WALL ) ? 0b0001_0000 : 0; $surroundings += ( $northwest == WALL ) ? 0b0000_1000 : 0; $surroundings += ( $southwest == WALL ) ? 0b0000_0100 : 0; $surroundings += ( $southeast == WALL ) ? 0b0000_0010 : 0; $surroundings += ( $northeast == WALL ) ? 0b0000_0001 : 0; $surroundings = 0b1_0000_0000 if ( $surroundings == 0 ); for my $ramp_type ( 0 .. $#ramps ) { my $mask = $ramps[$ramp_type]{mask}; my $bit_comparison = $mask & $surroundings; if ( $bit_comparison == $mask ) { my $func = $ramps[$ramp_type]{func}; croak "Need following ramp model: $func" if !defined $DRAW_MODEL{$func}[main]; glColor3f( $brightness, $brightness, $brightness ); glTranslatef( $rx, $z, $ry ); # TODO : remove unneeded checks for definedness for my $part ( 0 .. $#{ $DRAW_MODEL{$func} } ) { next if !defined $model_display_lists{$func}[$part]; glCallList( $model_display_lists{$func}[$part] ); } glTranslatef( -$rx, -$z, -$ry ); last; } } } } } } } glEndList(); if ( $cache[$id][mask_lists][$z] ) { $dl = $cache[$id][mask_lists][$z]; } else { $dl = glGenLists(1); $cache[$id][mask_lists][$z] = $dl; } draw_quadrangle( ( $x * 16 ), $z, ( $y * 16 ), 16, 16, $dl ); return; } sub draw_quadrangle { my ( $x, $y, $z, $width, $length, $dl ) = @_; my $x_west = $x + -0.4; my $x_east = $x + $width - 0.4; my $y_top = $y + 0.4; my $y_bottom = $y + -0.4; my $z_north = $z + -0.4; my $z_south = $z + $length - 0.4; my @verts = ( $x_west, $y_bottom, $z_south, $x_east, $y_bottom, $z_south, $x_west, $y_top, $z_south, $x_west, $y_top, $z_south, $x_east, $y_bottom, $z_south, $x_east, $y_top, $z_south, $x_west, $y_bottom, $z_north, $x_east, $y_top, $z_north, $x_east, $y_bottom, $z_north, $x_west, $y_top, $z_north, $x_east, $y_top, $z_north, $x_west, $y_bottom, $z_north, $x_west, $y_top, $z_south, $x_east, $y_top, $z_north, $x_west, $y_top, $z_north, $x_east, $y_top, $z_south, $x_east, $y_top, $z_north, $x_west, $y_top, $z_south, $x_west, $y_top, $z_south, $x_west, $y_bottom, $z_north, $x_west, $y_bottom, $z_south, $x_west, $y_top, $z_north, $x_west, $y_bottom, $z_north, $x_west, $y_top, $z_south, $x_east, $y_bottom, $z_south, $x_east, $y_top, $z_north, $x_east, $y_top, $z_south, $x_east, $y_bottom, $z_north, $x_east, $y_top, $z_north, $x_east, $y_bottom, $z_south, $x_west, $y_bottom, $z_south, $x_west, $y_bottom, $z_north, $x_east, $y_bottom, $z_south, $x_east, $y_bottom, $z_south, $x_west, $y_bottom, $z_north, $x_east, $y_bottom, $z_north, ); my $verts = OpenGL::Array->new_list( GL_FLOAT, @verts ); my @texcoords = ( 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, ); my $texcoords = OpenGL::Array->new_list( GL_FLOAT, @texcoords ); my @norms = ( 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, ); my $norms = OpenGL::Array->new_list( GL_FLOAT, @norms ); my @indices = ( 0 .. 36 ); my $indices = OpenGL::Array->new_list( GL_UNSIGNED_INT, @indices ); glVertexPointer_p( 3, $verts ); glNormalPointer_p($norms); glTexCoordPointer_p( 2, $texcoords ); glNewList( $dl, GL_COMPILE ); glDrawArrays( GL_TRIANGLES, 0, 36 ); glEndList(); } sub new_process_block { my ( $block_offset, $bx, $by, $bz ) = @_; my $changed = 0; my $buf = ""; my ( $type_changed, $desig_changed, $occup_changed ) = ( 0, 0, 0 ); my $cell_string = $cell_strings[$bx][$by][$bz] ||= [ '', '', '' ]; _ReadMemory( $df_proc_handle, $block_offset + $offsets[type_off], 2 * 256, $buf ); if ( $cell_string->[type] ne $buf ) { $cell_string->[type] = $buf; $type_changed = 1; $changed = 1; } _ReadMemory( $df_proc_handle, $block_offset + $offsets[designation_off], 4 * 256, $buf ); if ( $cell_string->[desig] ne $buf ) { $cell_string->[desig] = $buf; $desig_changed = 1; $changed = 1; } _ReadMemory( $df_proc_handle, $block_offset + $offsets[occupancy_off], 4 * 256, $buf ); if ( $cell_string->[occup] ne $buf ) { $cell_string->[occup] = $buf; $occup_changed = 1; $changed = 1; } if ($changed) { my @type_data = unpack( 'S' x 256, $cell_string->[type] ) if $type_changed; my @designation_data = unpack( 'L' x 256, $cell_string->[desig] ) if $desig_changed; my @occupation_data = unpack( 'L' x 256, $cell_string->[occup] ) if $occup_changed; my ( $rx, $ry, $tile, $desig, $desig_below, $occup ); my $bx_scaled = $bx * 16; my $by_scaled = $by * 16; my $tile_index = 0; for my $rx ( $bx_scaled .. $bx_scaled + 15 ) { # this calculates the real x and y values # of this tile on the overall map_base $tile = $tiles[$bz][type][$rx] ||= []; $desig = $tiles[$bz][desig][$rx] ||= []; $desig_below = $tiles[ $bz - 1 ][desig][$rx] ||= []; $occup = $tiles[$bz][occup][$rx] ||= []; # cycle through 16 x and 16 y values, # which generate a total of 256 tile indexes for my $ry ( $by_scaled .. $by_scaled + 15 ) { my $hideflag = 0; # skip tile if it is hidden, but only if the tile directly below it is not loaded yet or also hidden $hideflag = $designation_data[$tile_index] & 512 if $desig_changed; $hideflag = $desig->[$ry] & 512 if !$desig_changed; if ( $hideflag == 512 and ( !defined $desig_below->[$ry] or ( $desig_below->[$ry] & 512 ) == 512 ) ) { $desig->[$ry] = 512; ++$tile_index; next; } $tile->[$ry] = $type_data[$tile_index] if $type_changed; $desig->[$ry] = $designation_data[$tile_index] if $desig_changed; $occup->[$ry] = $occupation_data[$tile_index] if $occup_changed; ++$tile_index; } } } return $changed; } sub ask { print "$_[0]"; return; } ################################################################################ ################################################################################ ################################################################################ ## Rendering Functions ######################################################### ################################################################################ # ------ # Does everything needed before losing control to the main # OpenGL event loop. sub initialize_opengl { my ( $width, $height ) = @_; eval 'glDeleteQueries([0])'; $occlusion_supported = 0 if $@ =~ /is not supported by this renderer/; build_textures(); glClearColor( 0.7, 0.7, 0.7, 0.0 ); # Color to clear color buffer to. glClearDepth(1.0); # Depth to clear depth buffer to; type of test. glDepthFunc(GL_LESS); glHint( GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST ); glCullFace(GL_BACK); glEnable(GL_CULL_FACE); # Enables Smooth Color Shading; try GL_FLAT for (lack of) fun. glShadeModel(GL_SMOOTH); # Load up the correct perspective matrix; using a callback directly. resize_scene( $width, $height ); glLightfv_p( GL_LIGHT1, GL_AMBIENT, @light_ambient ); glLightfv_p( GL_LIGHT1, GL_DIFFUSE, @light_diffuse ); # glLightfv_p( GL_LIGHT1, GL_SPECULAR, @light_specular ); glEnable(GL_LIGHT1); # A handy trick -- have surface material mirror the color. glColorMaterial( GL_FRONT, GL_AMBIENT_AND_DIFFUSE ); #glMaterialfv_p( GL_FRONT_AND_BACK, GL_SPECULAR, 1, 1, 1, 1); #glMaterialfv_p(GL_FRONT_AND_BACK, GL_SHININESS, 127); glEnable(GL_COLOR_MATERIAL); glEnableClientState(GL_VERTEX_ARRAY); glEnableClientState(GL_NORMAL_ARRAY); glEnableClientState(GL_TEXTURE_COORD_ARRAY); return; } # ------ # Routine which draws all cubes in the map # ------ # Barebone menu creation functions sub create_menu { $submenid = glutCreateMenu( \&menu ); glutAddMenuEntry( 'Teapot', 2 ); glutAddMenuEntry( 'Cube', 3 ); glutAddMenuEntry( 'Torus', 4 ); $menid = glutCreateMenu( \&menu ); glutAddMenuEntry( 'Clear', 1 ); glutAddSubMenu( 'Draw', $submenid ); glutAddMenuEntry( 'Quit', 0 ); glutAttachMenu(GLUT_RIGHT_BUTTON); return; } sub menu { my ($in) = @_; if ( $in == 0 && defined $in ) { glutDestroyWindow($window_ID); exit 0; } $redraw_needed = 1; return; } # ------ # Routine which handles background stuff when the app is idle sub idle_tasks { my $time = time; if ( $time > $next_cursor_time ) { $loc_loop->ready; } if ( $time > $next_creature_time ) { $creature_loop->ready; } if ( $time > $next_landscape_time ) { $land_loop->ready; } if ( $time > $next_item_time ) { $item_loop->ready; } if ( $time > $next_building_time ) { $buil_loop->ready; } $render_loop->ready if $force_rt or ( $redraw_needed and $time > $next_render_time ); $memory_loop->ready if $memory_needs_clears; cede(); return; } # ------ # Routine which actually does the drawing sub render_scene { while (1) { my $t0 = time; my $buf; # For our strings. # Enables, disables or otherwise adjusts # as appropriate for our current settings. glEnable(GL_TEXTURE_2D); glEnable(GL_LIGHTING); glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); glEnable(GL_DEPTH_TEST); # Need to manipulate the ModelView matrix to move our model around. glMatrixMode(GL_MODELVIEW); gluLookAt( $x_pos + $x_off, $y_pos + $y_off, $z_pos + $z_off, $x_pos, $y_pos, $z_pos, 0, 1, 0 ); # Set up a light, turn it on. glLightfv_p( GL_LIGHT1, GL_POSITION, ( $light_position[0], $light_position[1], $light_position[2], $light_position[3] ) ); # Clear the color and depth buffers. glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); glColor3f( 0.75, 0.75, 0.75 ); # Basic polygon color render_models(); ## get pixels of cursor #my @array = glGenQueries_p(1); # #glDepthMask(GL_FALSE); #glColorMask(GL_FALSE,GL_FALSE,GL_FALSE,GL_FALSE); # #glBeginQuery(GL_SAMPLES_PASSED, $array[0]); # #glLineWidth(2); #glPolygonMode( GL_FRONT, GL_LINE ); #glColor3f( 1, 1, 1 ); #glTranslatef( $x_pos, $y_pos, $z_pos ); #for my $part ( 0 .. $#{ $DRAW_MODEL{Cursor} } ) { # next if !defined $model_display_lists{Cursor}[$part]; # glCallList( $model_display_lists{Cursor}[$part] ); #} #glTranslatef( -$x_pos, -$y_pos, -$z_pos ); #glPolygonMode( GL_FRONT, GL_FILL ); #glEndQuery(GL_SAMPLES_PASSED); # #glDepthMask(GL_TRUE); #glColorMask(GL_TRUE,GL_TRUE,GL_TRUE,GL_TRUE); # #while (!glGetQueryObjectiv($array[0], GL_QUERY_RESULT_AVAILABLE)) {} # #$pixels = glGetQueryObjectuiv($array[0], GL_QUERY_RESULT); # draw visible cursor glDisable(GL_LIGHTING); glLineWidth(2); glBindTexture( GL_TEXTURE_2D, $texture_ID[cursor] ); glPolygonMode( GL_FRONT, GL_LINE ); glColor3f( 1, 1, 1 ); glTranslatef( $x_pos, $y_pos, $z_pos ); # TODO : remove unneeded checks for definedness for my $part ( 0 .. $#{ $DRAW_MODEL{Cursor} } ) { next if !defined $model_display_lists{Cursor}[$part]; glCallList( $model_display_lists{Cursor}[$part] ); } glTranslatef( -$x_pos, -$y_pos, -$z_pos ); glPolygonMode( GL_FRONT, GL_FILL ); glBindTexture( GL_TEXTURE_2D, $texture_ID[grass] ); =cut glBindTexture( GL_TEXTURE_2D, $texture_ID[test] ); glEnable(GL_POINT_SPRITE); glTexEnvi(GL_POINT_SPRITE, GL_COORD_REPLACE, GL_TRUE); glPointSize(50.0); glBegin(GL_POINTS); glVertex3f($x_pos, $y_pos, $z_pos); =cut glEnd(); glLoadIdentity(); # Move back to the origin (for the text, below). # We need to change the projection matrix for the text rendering. glMatrixMode(GL_PROJECTION); glPushMatrix(); # But we like our current view too; so we save it here. render_ui(); glPopMatrix(); # Done with this special projection matrix. Throw it away. glutSwapBuffers(); # All done drawing. Let's show it. my $t1 = time; $time{render} = $t1 - $t0; $next_render_time = time + $c{redraw_delay} - $time{render}; $redraw_needed = 0; schedule(); } return; } sub render_models { my @query_cells; for my $z ( 0 .. $ceiling_slice ) { # cycle through cells in range around cursor to render for my $bx ( $min_x_range .. $max_x_range ) { for my $by ( $min_y_range .. $max_y_range ) { my $cache_ptr = $cells[$bx][$by][cache_ptr]; next if !defined $cache_ptr; next if !defined $cache[$cache_ptr]; # draw landscape my $slices = $cache[$cache_ptr][display_lists]; if ($slices->[$z]) { glCallList( $slices->[$z] ); push @query_cells, [ $bx, $by, $z ]; } } } } my @cells_to_draw; if ($occlusion_supported) { my @queries; glDepthMask(GL_FALSE); glColorMask( GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE ); glDisable(GL_CULL_FACE); @queries = glGenQueries_p( scalar @query_cells ); my $id = 0; for my $cell ( @query_cells ) { my $bx = $cell->[0]; my $by = $cell->[1]; my $z = $cell->[2]; my $cache_ptr = $cells[$bx][$by][cache_ptr]; my $slices = $cache[$cache_ptr][mask_lists]; $cell->[3] = $queries[$id]; glBeginQuery( GL_SAMPLES_PASSED, $cell->[3] ); glCallList( $slices->[$z] ); glEndQuery(GL_SAMPLES_PASSED); $id++; } glEnable(GL_CULL_FACE); glDepthMask(GL_TRUE); glColorMask( GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE ); for my $cell ( @query_cells ) { while ( !glGetQueryObjectiv( $cell->[3], GL_QUERY_RESULT_AVAILABLE ) ) { } my $pixel = glGetQueryObjectuiv( $cell->[3], GL_QUERY_RESULT ); push @cells_to_draw, $cell if $pixel > 0; } glDeleteQueries(@queries); } else { @cells_to_draw = @query_cells; } $pixels = $#cells_to_draw . "/" . $#query_cells; for my $cell (@cells_to_draw) { my $bx = $cell->[0]; my $by = $cell->[1]; my $z = $cell->[2]; my $brightness = ( ( ( $z / ( $zcount - 1 ) ) * 0.6 ) + 0.3 ); my $cache_ptr = $cells[$bx][$by][cache_ptr]; next if !defined $cache_ptr; next if !defined $cache[$cache_ptr]; # draw creatures if ( defined $cells[$bx][$by][creature_list][$z] ) { my $creature_list_size = @{ $cells[$bx][$by][creature_list][$z] }; for my $entry ( 0 .. $creature_list_size ) { my $creature_id = $cells[$bx][$by][creature_list][$z][$entry]; next if !defined $creature_id; next unless $creatures_present{$creature_id}; next if $creatures{$creature_id}[flags] & 2; # dead, i think my $x = $creatures{$creature_id}[c_x]; my $y = $creatures{$creature_id}[c_y]; glColor3f( $brightness, $brightness, $brightness ); glTranslatef( $x, $z, $y ); my $model_name; given ( $creatures{$creature_id}[race] ) { when (166) { $model_name = "Creature"; } default { $model_name = "Creature2"; } } glBindTexture( GL_TEXTURE_2D, $texture_ID[creature] ); # TODO : remove unneeded checks for definedness for my $part ( 0 .. $#{ $DRAW_MODEL{$model_name} } ) { next if !defined $model_display_lists{$model_name}[$part]; glCallList( $model_display_lists{$model_name}[$part] ); } glTranslatef( -$x, -$z, -$y ); } } # draw buildings if ( defined $cells[$bx][$by][building_list][$z] ) { my $building_list_size = @{ $cells[$bx][$by][building_list][$z] }; for my $entry ( 0 .. $building_list_size ) { my $building_id = $cells[$bx][$by][building_list][$z][$entry]; next if !defined $building_id; next unless $building_present{$building_id}; my $x = $buildings{$building_id}[b_x]; my $y = $buildings{$building_id}[b_y]; glColor3f( $brightness, $brightness, $brightness ); glTranslatef( $x, $z, $y ); my $vtable_id = $buildings{$building_id}[b_vtable_id]; $vtable_id = 'default' if !defined $building_visuals{$vtable_id}[0]; my $model_name = $building_visuals{$vtable_id}[0]; glBindTexture( GL_TEXTURE_2D, $texture_ID[ $building_visuals{$vtable_id}[1] ] ); # TODO : remove unneeded checks for definedness for my $part ( 0 .. $#{ $DRAW_MODEL{$model_name} } ) { next if !defined $model_display_lists{$model_name}[$part]; glCallList( $model_display_lists{$model_name}[$part] ); } glTranslatef( -$x, -$z, -$y ); } } # draw items if ( defined $cells[$bx][$by][item_list][$z] ) { my $item_list_size = @{ $cells[$bx][$by][item_list][$z] }; for my $entry ( 0 .. $item_list_size ) { my $item_id = $cells[$bx][$by][item_list][$z][$entry]; next if !defined $item_id; next if $items{$item_id}[invisible]; my $x = $items{$item_id}[i_x]; my $y = $items{$item_id}[i_y]; glColor3f( $brightness, $brightness, $brightness ); glTranslatef( $x, $z, $y ); my $model_name; given ($item_id) { default { $model_name = "Items"; } } glBindTexture( GL_TEXTURE_2D, $texture_ID[items] ); # TODO : remove unneeded checks for definedness for my $part ( 0 .. $#{ $DRAW_MODEL{$model_name} } ) { next if !defined $model_display_lists{$model_name}[$part]; glCallList( $model_display_lists{$model_name}[$part] ); } glTranslatef( -$x, -$z, -$y ); } } } return; } sub render_ui { my $buf; glLoadIdentity(); # Now we set up a new projection for the text. gluOrtho2D( 0, $c{window_width}, $c{window_height}, 0 ); glDisable(GL_TEXTURE_2D); # Lit or textured text looks awful. glDisable(GL_LIGHTING); glDisable(GL_DEPTH_TEST); # We don't want depth-testing either. # But, for fun, let's make the text partially transparent too. glColor4f( 0.6, 1.0, 0.6, .75 ); $buf = sprintf 'X: %d', $x_pos; glRasterPos2i( 2, 14 ); glutBitmapString( GLUT_BITMAP_HELVETICA_12, $buf ); $buf = sprintf 'Y: %d', $z_pos; glRasterPos2i( 2, 26 ); glutBitmapString( GLUT_BITMAP_HELVETICA_12, $buf ); $buf = sprintf 'Z: %d', $y_pos; glRasterPos2i( 2, 38 ); glutBitmapString( GLUT_BITMAP_HELVETICA_12, $buf ); $buf = sprintf 'H-Angle: %.2f', $y_rot; glRasterPos2i( 2, 50 ); glutBitmapString( GLUT_BITMAP_HELVETICA_12, $buf ); $buf = sprintf 'V-Angle: %.2f', $x_rot; glRasterPos2i( 2, 62 ); glutBitmapString( GLUT_BITMAP_HELVETICA_12, $buf ); $buf = sprintf 'Cam-X: %.2f', $x_off; glRasterPos2i( 2, 74 ); glutBitmapString( GLUT_BITMAP_HELVETICA_12, $buf ); $buf = sprintf 'Cam-Y: %.2f', $z_off; glRasterPos2i( 2, 86 ); glutBitmapString( GLUT_BITMAP_HELVETICA_12, $buf ); $buf = sprintf 'Cam-Z: %.2f', $y_off; glRasterPos2i( 2, 98 ); glutBitmapString( GLUT_BITMAP_HELVETICA_12, $buf ); $buf = sprintf 'Mem: %.2f %%', ( ( $memory_use / $c{memory_limit} ) * 100 ); glRasterPos2i( 2, 110 ); glutBitmapString( GLUT_BITMAP_HELVETICA_12, $buf ); $buf = sprintf 'Caches: %d', ( ( $#cache + 1 ) - ( $#cache_bucket + 1 ) ); glRasterPos2i( 2, 122 ); glutBitmapString( GLUT_BITMAP_HELVETICA_12, $buf ); if ( $tiles[$zmouse][type][$xmouse][$ymouse] ) { $buf = sprintf 'Type: %d', $tiles[$zmouse][type][$xmouse][$ymouse]; glRasterPos2i( 2, 134 ); glutBitmapString( GLUT_BITMAP_HELVETICA_12, $buf ); } if ( $tiles[$zmouse][desig][$xmouse][$ymouse] ) { $buf = sprintf 'Desigs: 0b%059b', $tiles[$zmouse][desig][$xmouse][$ymouse]; glRasterPos2i( 2, $c{window_height} - 14 ); glutBitmapString( GLUT_BITMAP_HELVETICA_12, $buf ); } if ( $tiles[$zmouse][occup][$xmouse][$ymouse] ) { $buf = sprintf 'Occup: 0b%059b', ( $tiles[$zmouse][occup][$xmouse][$ymouse] & 7 ); glRasterPos2i( 2, $c{window_height} - 26 ); glutBitmapString( GLUT_BITMAP_HELVETICA_12, $buf ); } if ( $tiles[$zmouse][occup][$xmouse][$ymouse] ) { $buf = sprintf 'Occup: 0b%059b', $tiles[$zmouse][occup][$xmouse][$ymouse]; glRasterPos2i( 2, $c{window_height} - 2 ); glutBitmapString( GLUT_BITMAP_HELVETICA_12, $buf ); } $buf = sprintf 'Mouse: %d %d', $xmouse, $ymouse; glRasterPos2i( 2, 158 ); glutBitmapString( GLUT_BITMAP_HELVETICA_12, $buf ); $buf = sprintf 'Working threads: %d', Coro::nready; glRasterPos2i( 2, 146 ); glutBitmapString( GLUT_BITMAP_HELVETICA_12, $buf ); $buf = "Tasks: $current_data_proc_task / $max_data_proc_tasks : $time{landscape} secs"; glRasterPos2i( 2, 172 ); glutBitmapString( GLUT_BITMAP_HELVETICA_12, $buf ); $buf = "Creature-Tasks: $current_creat_proc_task / $max_creat_proc_tasks : $time{creature} secs"; glRasterPos2i( 2, 186 ); glutBitmapString( GLUT_BITMAP_HELVETICA_12, $buf ); $buf = "Ceiling: $ceiling_slice"; glRasterPos2i( 2, 198 ); glutBitmapString( GLUT_BITMAP_HELVETICA_12, $buf ); $buf = "Mem-Act: $memory_clears / $memory_full_checks"; glRasterPos2i( 2, 210 ); glutBitmapString( GLUT_BITMAP_HELVETICA_12, $buf ); $buf = "Building-Tasks: $current_buil_proc_task / $max_buil_proc_tasks : $time{building} secs"; glRasterPos2i( 2, 224 ); glutBitmapString( GLUT_BITMAP_HELVETICA_12, $buf ); $buf = "Item-Tasks: $current_item_proc_task / $max_item_proc_tasks : $time{item} secs"; glRasterPos2i( 2, 236 ); glutBitmapString( GLUT_BITMAP_HELVETICA_12, $buf ); $buf = "Pixels: $pixels"; glRasterPos2i( 2, 250 ); glutBitmapString( GLUT_BITMAP_HELVETICA_12, $buf ); my $timesum = $time{landscape} + $time{creature} + $time{building} + $time{item}; $buf = "Time: $timesum secs"; glRasterPos2i( 2, 262 ); glutBitmapString( GLUT_BITMAP_HELVETICA_12, $buf ); #$buf = "Crea: $creature_length"; #glRasterPos2i( 2, 222 ); #glutBitmapString( GLUT_BITMAP_HELVETICA_12, $buf ); glColor4f( 0.2, 0.2, 0.2, 0.75 ); glBegin(GL_QUADS); glVertex3f( $c{window_width} - 42, $c{window_height} - 20, 0.0 ); glVertex3f( $c{window_width} - 42, $c{window_height}, 0.0 ); glVertex3f( $c{window_width} - 22, $c{window_height}, 0.0 ); glVertex3f( $c{window_width} - 22, $c{window_height} - 20.0, 0.0 ); glEnd(); glBegin(GL_QUADS); glVertex3f( $c{window_width} - 20, $c{window_height} - 20, 0.0 ); glVertex3f( $c{window_width} - 20, $c{window_height}, 0.0 ); glVertex3f( $c{window_width}, $c{window_height}, 0.0 ); glVertex3f( $c{window_width}, $c{window_height} - 20.0, 0.0 ); glEnd(); glColor4f( 0.2, 0.2, 0.2, 0.75 ); glColor4f( 1, 1, 0.2, 0.75 ) if ( $c{view_range} > 0 ); glRasterPos2i( $c{window_width} - 36, $c{window_height} - 6 ); glutBitmapString( GLUT_BITMAP_HELVETICA_12, "-" ); glColor4f( 0.2, 0.2, 0.2, 0.75 ); my $size = ( $xcount > $ycount ) ? $xcount : $ycount; glColor4f( 1, 1, 0.2, 0.75 ) if ( $c{view_range} < $size / 2 ); glRasterPos2i( $c{window_width} - 14, $c{window_height} - 6 ); glutBitmapString( GLUT_BITMAP_HELVETICA_12, "+" ); my $height_mod = ( $c{window_height} - 22 ) / ( $zcount + 2 ); for my $slice ( 0 .. $zcount ) { my $part = ( 0.6 / $zcount ); my $bright = ( $part * $slice ) + 0.2; glColor4f( $bright, $bright, $bright, 1 ); glBegin(GL_QUADS); glVertex3f( $c{window_width} - 20, $c{window_height} - 22 - $height_mod * ( $slice + 1 ), 0.0 ); glVertex3f( $c{window_width} - 20, $c{window_height} - 22 - $height_mod * $slice, 0.0 ); glVertex3f( $c{window_width} - 0, $c{window_height} - 22 - $height_mod * $slice, 0.0 ); glVertex3f( $c{window_width} - 0, $c{window_height} - 22 - $height_mod * ( $slice + 1 ), 0.0 ); glEnd(); } for my $slice ( 0 .. $zcount ) { if ( $slice == $ceiling_slice ) { glColor4f( 1, 1, 0, 1 ); my $height = ( $height_mod * $slice ) - ( $height_mod * ( $slice + 1 ) ); glRasterPos2i( $c{window_width} - 17, $c{window_height} - 22 - ( $height / 2 ) - $height_mod * ( $slice + 1 ) ); glutBitmapString( GLUT_BITMAP_HELVETICA_12, $slice ); } } glEnable(GL_TEXTURE_2D); glBindTexture( GL_TEXTURE_2D, $texture_ID[ui] ); glColor4f( 1, 1, 1, 1 ); if ($ceiling_locked) { glBegin(GL_QUADS); OpenGL::glTexCoord2f( 0, 1 ); glVertex3f( $c{window_width} - 42, 0, 0.0 ); OpenGL::glTexCoord2f( 0, 0.84375 ); glVertex3f( $c{window_width} - 42, 20, 0.0 ); OpenGL::glTexCoord2f( 0.15625, 0.84375 ); glVertex3f( $c{window_width} - 22, 20, 0.0 ); OpenGL::glTexCoord2f( 0.15625, 1 ); glVertex3f( $c{window_width} - 22, 0, 0.0 ); glEnd(); } else { glBegin(GL_QUADS); OpenGL::glTexCoord2f( 0.15625, 1 ); glVertex3f( $c{window_width} - 42, 0, 0.0 ); OpenGL::glTexCoord2f( 0.15625, 0.84375 ); glVertex3f( $c{window_width} - 42, 20, 0.0 ); OpenGL::glTexCoord2f( 0.3125, 0.84375 ); glVertex3f( $c{window_width} - 22, 20, 0.0 ); OpenGL::glTexCoord2f( 0.3125, 1 ); glVertex3f( $c{window_width} - 22, 0, 0.0 ); glEnd(); } return; } ################################################################################ ## 3D Maintenance ############################################################## ################################################################################ # ------ # Callback routine executed whenever our window is resized. Lets us # request the newly appropriate perspective projection matrix for # our needs. Try removing the gluPerspective() call to see what happens. sub resize_scene { my ( $width, $height ) = @_; $height = 1 if ( $height == 0 ); # Let's not core dump, no matter what. glViewport( 0, 0, $width, $height ); glMatrixMode(GL_PROJECTION); glLoadIdentity(); my $cam_target = V( $x_pos, $y_pos, $z_pos ); my $cam_off = V( $x_off, $y_off, $z_off ); my $cam_pos = $cam_target + $cam_off; my $cam_normal = $cam_off * -1; my @x = ( $min_x_range * 16, ( $max_x_range * 16 ) + 15 ); my @y = ( $min_y_range * 16, ( $max_y_range * 16 ) + 15 ); my @z = ( 0, $zcount ); my $dist_min = 999999; my $dist_max = -999999; for my $x (@x) { for my $y (@y) { for my $z (@z) { my $target = V( $x, $z, $y ); my $dist = ( $cam_normal * ( $target - $cam_pos ) ) / abs($cam_normal); $dist_min = $dist if $dist < $dist_min; $dist_max = $dist if $dist > $dist_max; } } } $dist_min -= .75; $dist_min = 1 if $dist_min < 1; gluPerspective( $cam_angle, $width / $height, $dist_min, $dist_max ); #say "$dist_min, $dist_max"; glMatrixMode(GL_MODELVIEW); $c{window_width} = $width; $c{window_height} = $height; return; } ################################################################################ ## Texture Stuff ############################################################### ################################################################################ # ------ # Function to build a simple full-color texture with alpha channel, # and then create mipmaps. This could instead load textures from # graphics files from disk, or render textures based on external # input. sub build_textures { print 'loading textures..'; # Generate a texture index, then bind it for future operations. @texture_ID = glGenTextures_p(30); create_texture( 'grass', grass ); create_texture( 'stone', stone ); create_texture( 'cursor', cursor ); create_texture( 'obsidian', obsidian ); create_texture( 'unknown', unknown ); create_texture( 'minstone', minstone ); create_texture( 'pool', pool ); create_texture( 'water', water ); create_texture( 'soil', soil ); create_texture( 'tree', tree ); create_texture( 'shrub', shrub ); create_texture( 'sapling', sapling ); create_texture( 'creature', creature ); create_texture( 'grassb', grassb ); create_texture( 'boulder', boulder ); create_texture( 'shrub_dead', shrub_dead ); create_texture( 'tree_dead', tree_dead ); create_texture( 'sapling_dead', sapling_dead ); create_texture( 'constructed_floor_detailed', constructed_floor_detailed ); create_texture( 'constructed_wall', constructed_wall ); create_texture( 'grass_dry', grass_dry ); create_texture( 'lava', lava ); create_texture( 'curses3_960x300', test ); create_texture( 'metal', metal ); create_texture( 'stone_detailed', stone_detailed ); create_texture( 'minstone_detailed', minstone_detailed ); create_texture( 'ui', ui ); create_texture( 'items', items ); create_texture( 'wood', wood ); create_texture( 'red', red ); #glBindTexture(GL_TEXTURE_2D, $texture_ID[grass]); # select mipmapped texture #glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_REPEAT); # Some pretty standard settings for wrapping and filtering. #glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_REPEAT); say " textures loaded.\n"; return; } sub create_texture { my ( $name, $id ) = @_; glBindTexture( GL_TEXTURE_2D, $texture_ID[$id] ); my $tex = new OpenGL::Image( engine => 'Magick', source => "textures/$name.png" ); my ( $ifmt, $fmt, $type ) = $tex->Get( 'gl_internalformat', 'gl_format', 'gl_type' ); my ( $w, $h ) = $tex->Get( 'width', 'height' ); glTexParameteri( GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE ); glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST ); glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST ); glTexImage2D_c( GL_TEXTURE_2D, 0, $ifmt, $w, $h, 0, $fmt, $type, $tex->Ptr() ); return; } ################################################################################ ## Input Stuff ################################################################# ################################################################################ # Callback function called when a normal $key is pressed. ###################### sub process_key_press { my $key = shift; my $scan = VkKeyScan($key); $scan &= 0xff; #if ( $scan == VK_F ) { # $proc->set_u32( $OFFSETS[$ver]{mouse_z}, $zmouse + 1 ); # BE CAREFUL, MAY DAMAGE YOUR SYSTEM # print "moo"; #} PostMessage( $DF_window, WM_KEYDOWN, $scan, 0 ); $redraw_needed = 1; return; } # Callback Function called when a special $key is pressed. ##################### sub process_special_key_press { my $key = shift; if ( $key == GLUT_KEY_F12 ) { $force_rt = 1; open my $OUT, ">>", 'export.txt' or die( "horribly: " . $! ); print $OUT "\n\n--------------------------------\n"; print $OUT "X: $xmouse Y: $ymouse Z: $zmouse\n\n"; say "\n\n--------------------------------"; say "X: $xmouse Y: $ymouse Z: $zmouse\n"; for my $item ( values %items ) { next if !defined $item->[i_x]; next if !defined $item->[i_y]; next if !defined $item->[i_z]; if ( $item->[i_x] == $xmouse && $item->[i_y] == $ymouse && $item->[i_z] == $zmouse ) { my $hex_dump = $proc->hexdump( $item->[i_address], 0x88 ); print $OUT "Item:\n$hex_dump\n\n"; say "Item:\n$hex_dump\n"; } } for my $building ( keys %buildings ) { next if !defined $buildings{$building}[b_x]; next if !defined $buildings{$building}[b_y]; next if !defined $buildings{$building}[b_z]; if ( $buildings{$building}[b_x] == $xmouse && $buildings{$building}[b_y] == $ymouse && $buildings{$building}[b_z] == $zmouse ) { my $hex_dump = $proc->hexdump( $building, 0xD8 ); print $OUT "Building:\n$hex_dump\n\n"; say "Building:\n$hex_dump\n"; } } for my $creature ( keys %creatures ) { next if !defined $creatures{$creature}[c_x]; next if !defined $creatures{$creature}[c_y]; next if !defined $creatures{$creature}[c_z]; if ( $creatures{$creature}[c_x] == $xmouse && $creatures{$creature}[c_y] == $ymouse && $creatures{$creature}[c_z] == $zmouse ) { my $hex_dump = $proc->hexdump( $creature, 0x688 ); print $OUT "Creature:\n$hex_dump\n\n"; say "Creature:\n$hex_dump\n"; } } close $OUT; } if ( $key >= GLUT_KEY_F1 && $key <= GLUT_KEY_F12 ) { PostMessage( $DF_window, WM_KEYDOWN, $key + 111, 0 ); } elsif ( $key >= GLUT_KEY_LEFT && $key <= GLUT_KEY_DOWN ) { PostMessage( $DF_window, WM_KEYDOWN, $key - 63, 0 ); } elsif ( $key >= GLUT_KEY_PAGE_UP && $key <= GLUT_KEY_PAGE_DOWN ) { PostMessage( $DF_window, WM_KEYDOWN, $key - 71, 0 ); } elsif ( $key == GLUT_KEY_HOME ) { PostMessage( $DF_window, WM_KEYDOWN, VK_HOME, 0 ); } elsif ( $key == GLUT_KEY_END ) { PostMessage( $DF_window, WM_KEYDOWN, VK_END, 0 ); } elsif ( $key == GLUT_KEY_INSERT ) { PostMessage( $DF_window, WM_KEYDOWN, VK_INSERT, 0 ); } else { printf "SKP: No action for %d.\n", $key; } $redraw_needed = 1; return; } sub process_mouse_click { my ( $button, $state, $x, $y ) = @_; if ( $button == GLUT_MIDDLE_BUTTON && $state == GLUT_DOWN ) { glutSetCursor(GLUT_CURSOR_NONE); $middle_mouse = 1; $last_mouse_x = $x; $last_mouse_y = $y; $mouse_cursor_x = $x; $mouse_cursor_y = $y; } if ( $button == GLUT_LEFT_BUTTON && $state == GLUT_DOWN ) { $last_mouse_x = $x; $last_mouse_y = $y; if ( $x > $c{window_width} - 42 && $x < $c{window_width} - 22 && $y > $c{window_height} - 20 && $y < $c{window_height} ) { if ( $c{view_range} > 0 ) { --$c{view_range}; resize_scene( $c{window_width}, $c{window_height} ); $redraw_needed = 1; $view_range_changed = 1; } } elsif ($x > $c{window_width} - 42 && $x < $c{window_width} - 20 && $y > 0 && $y < 20 ) { if ( $ceiling_locked == 1 ) { $ceiling_locked = 0; $ceiling_slice = $zcount; $redraw_needed = 1; } else { $ceiling_locked = 1; $redraw_needed = 1; } } elsif ($x > $c{window_width} - 20 && $x < $c{window_width} && $y > $c{window_height} - 20 && $y < $c{window_height} ) { my $size = ( $xcount > $ycount ) ? $xcount : $ycount; if ( $c{view_range} < $size / 2 ) { ++$c{view_range}; resize_scene( $c{window_width}, $c{window_height} ); $redraw_needed = 1; $view_range_changed = 1; } } elsif ($x > $c{window_width} - 20 && $x < $c{window_width} && $y > 0 && $y < $c{window_height} - 22 ) { $ceiling_slice = int( ( ( $y / ( ( $c{window_height} - 22 ) / ( $zcount + 2 ) ) ) - ($zcount) ) * -1 ) + 2; $ceiling_slice = 0 if $ceiling_slice < 0; $ceiling_slice = $zcount if $ceiling_slice > $zcount; $changing_ceiling = 1; $redraw_needed = 1; } else { $mouse_cursor_x = $x; $mouse_cursor_y = $y; glutSetCursor(GLUT_CURSOR_NONE); $rotating = 1; } } glutWarpPointer( $mouse_cursor_x, $mouse_cursor_y ) if $button == GLUT_MIDDLE_BUTTON && $state == GLUT_UP && $middle_mouse == 1; glutWarpPointer( $mouse_cursor_x, $mouse_cursor_y ) if $button == GLUT_LEFT_BUTTON && $state == GLUT_UP && $rotating == 1; glutSetCursor(GLUT_CURSOR_INHERIT) if $button == GLUT_LEFT_BUTTON && $state == GLUT_UP; glutSetCursor(GLUT_CURSOR_INHERIT) if $button == GLUT_MIDDLE_BUTTON && $state == GLUT_UP; $changing_ceiling = 0 if $button == GLUT_LEFT_BUTTON && $state == GLUT_UP; $rotating = 0 if $button == GLUT_LEFT_BUTTON && $state == GLUT_UP; $middle_mouse = 0 if $button == GLUT_MIDDLE_BUTTON && $state == GLUT_UP; return; } sub process_active_mouse_motion { my ( $x, $y ) = @_; if ($changing_ceiling) { $ceiling_slice = int( ( ( $y / ( ( $c{window_height} - 22 ) / ( $zcount + 2 ) ) ) - ($zcount) ) * -1 ) + 2; $ceiling_slice = 0 if $ceiling_slice < 0; $ceiling_slice = $zcount if $ceiling_slice > $zcount; $redraw_needed = 1; return; } if ( $x > $c{window_width} - 42 && $x < $c{window_width} - 22 && $y > $c{window_height} - 20 && $y < $c{window_height} ) { return; } if ( $x > $c{window_width} - 20 && $x < $c{window_width} && $y > $c{window_height} - 20 && $y < $c{window_height} ) { return; } my ( $new_x, $new_y ) = ( 0, 0 ); $new_x = $x - $last_mouse_x if ($last_mouse_x); $new_y = $y - $last_mouse_y if ($last_mouse_y); if ( $middle_mouse == 0 ) { $y_rot -= ( 180 * $new_x / 300 ) * -1 * $c{sensitivity}; $y_rot -= 360 if ( $y_rot > 360 ); $y_rot += 360 if ( $y_rot < 0 ); my $diff = ( 180 * $new_y / 300 ) * -0.75 * $c{sensitivity}; $x_rot += $diff if ( ( $x_rot + $diff ) > -89 and ( $x_rot + $diff ) < 89 ); } else { my $zoom_by_fov = 0; if ($zoom_by_fov) { $cam_angle += $new_y * 0.2; $cam_angle = 1 if $cam_angle < 1; $cam_angle = 179 if $cam_angle > 179; } else { $mouse_dist += $new_y * 0.2; $mouse_dist = 0.2 if $mouse_dist < 0.2; } } resize_scene( $c{window_width}, $c{window_height} ); $last_mouse_x = $x; $last_mouse_y = $y; reposition_camera(); if ( ( $rotating == 1 || $middle_mouse == 1 ) && ( $new_x != 0 || $new_y != 0 ) ) { $last_mouse_x = $mouse_cursor_x; $last_mouse_y = $mouse_cursor_y; glutWarpPointer( $last_mouse_x, $last_mouse_y ); } $redraw_needed = 1; return; } sub reposition_camera { my $radial_x_rotation = $x_rot * PIOVER180; my $radial_y_rotation = $y_rot * PIOVER180; my $cos_y = cos $radial_y_rotation; my $sin_y = sin $radial_y_rotation; my $sin_x = sin $radial_x_rotation; my $cos_x = cos $radial_x_rotation; $x_off = ( $sin_y * $cos_x ) * $mouse_dist; $y_off = ( -$sin_x ) * $mouse_dist; $z_off = ( -$cos_y * $cos_x ) * $mouse_dist; return; } ################################################################################ ## Map Stuff ################################################################### ################################################################################ ################################################################################ ## Helper Stuff ################################################################ ################################################################################ # ------ # String rendering routine; leverages on GLUT routine. sub set_detached { ($detached) = $_[1]; } sub fatal_error { my ($error) = @_; if ($detached) { Win32::MsgBox( $error, MB_ICONSTOP, "Lifevis - $VERSION" ); exit; } else { croak $error; } } sub notify_user { my ($message) = @_; if ($detached) { Win32::MsgBox( $message, MB_ICONINFORMATION, "Lifevis - $VERSION" ); } else { say $message; } } 1; __END__