Code covered by the BSD License  

Highlights from
Operation Eigenfaust 3D (Tech Demo)

  • Operation Eigenfaust 3D DemoA playable first-person shooting game demo featuring a real-time discrete ray casting engine. Possibly the coolest MATLAB game demo ever.
  • opeig opeig - The main file for Operation Eigenfaust 3D for MATLAB
  • View all files
image thumbnail

Operation Eigenfaust 3D (Tech Demo)

by

 

17 Jun 2013 (Updated )

A playable first-person shooter demo featuring interative objects and real-time ray casting.

opeig
function opeig
% opeig - The main file for Operation Eigenfaust 3D for MATLAB
% Usage:
% ------
%  opeig;

% History
% -------
% Date              Updater             Modification
% ----              -------             ------------
% Jun 14, 2013      M. Zhang        v0.35: Correct texture projection
% Jun 15, 2013      M. Zhang        v0.40: Correct rendering implemented
% Jun 15, 2013      M. Zhang        v0.45: Attempted Collision Detection
%                                          Added 'Enter' - mouse on/off
%                                           'Escape' - quit
% Jun 20, 2013      M. Zhang        v0.50: Now supports arbitrary resolution
%                                          Added mouse wheel support
% Jun 21, 2013      M. Zhang        v0.55: Added rendering for doors
%                                          (facing only left and right)
%                                          Collision detection completed
% Jun 27, 2013      M. Zhang        v0.60: Improve the variable monitor to 
%                                          allow multiple variables
%                                          Corrected a bug where only the
%                                          most recently pressed dir key is
%                                          effective
%                                          Added 'Tab' - var monitor on/off
% Jul 11, 2013      M. Zhang        v0.65: The door can now be half opened
%                                          (only applies to doors on the 
%                                           right side of the player)
%                                          However, the rendering of scene
%                                          behind an opened door is
%                                          completely wrong: All the
%                                          'horizontal' walls are rendered
%                                          'vertically'.
% Jul 11, 2013      M. Zhang        v0.68: Corrected the rendering of walls
%                                          behind the opened door
% Jul 11, 2013      M. Zhang        v0.75: Now the door to the 'left' of
%                                           the player can be properly
%                                           rendered (with texture flipped)
% Jul 13, 2013      M. Zhang        v0.76: Extend 'processPlayer' to find
%                                          out the block type right in
%                                          front of the player character
% Jul 15, 2013      M. Zhang        v0.80: Now the doors can be opened and
%                                          closed. Although they wont close
%                                          by themselves.
% Jul 18, 2013      M. Zhang        v0.85: Change the name of the demo to
%                                           'Operation Eigenfaust 3D';  
%                                          and all the variable/resource 
%                                          file names accordingly;
%                                          
% ----              -------             ------------
% Copyright (C) Stellari Studio, 2013
% Mingjing Zhang @ Vision & Media Lab, Simon Fraser University, Canada

%% Variable Declaration
try
    Opeig = '0.85';     % Last updated Jul 18, 2013
    MainAxesSize = [];    % The size of the main axes, same as GAME_RESOLUTION
    FPS = [];             % Frames-per-second, ideally over 60
    
    FRAME_DURATION = [];  % The duration of one single frame, ideally less than 1/60
    MAX_FRAME_SKIP = [];  % The maximum of frame skips allowed if the game runs sluggishly
    
    MainFigureInitPos = [];  % The initial position of the main figure
    MainFigureSize = [];  % The size of the figure
    MainAxesInitPos = []; % The initial position of the axes IN the figure
    
    % Handles
    MainFigureHdl = [];
    MainAxesHdl = [];
    SceneHandle = [];
    
    % Keyboard-related variables
    KeyStatus = [];
    LastKeyStatus = [];
    KeyNames = [];
    
    % Variables for Debugging
    ShowFPS = false;
    SHOWFPS_FRAMES = 60;
    
    % Collision Flags
    CloseReq = false;
    
    key = [];
    
    % player character
    
    % Canvas
    EmptyCanvas = [];
    CurCanvas = [];
    
    %% Raycasting & Rendering related variables
    view = [];
    scr_res = [];
    wall_texture = [];   % A 64 x whatever strip that contains all possible wall textures
    cmap = [];          % The global palette
    
    texture_wid = [];
    texture_hgt = [];
    
    xx = [];
    yy = [];
    yy_p = [];
    yy_text = [];
    yy_standard = [];
    apparent_h = [];
    h_scale_factor = [];
    cam_angle_comp = [];
    ones200 = [];       % ones(1,200)
    DEG2RAD = pi/180;
    screenCenter = [];
    use_mouse = true;
    n_obs = [];
    obs_pos = [];
    niche = [];
    niche_pos = [];
    encroach_pos = [];
   fw_vec = [];   % The vector from the current position to the next position
   
   fobj_ind = []; % The index of the object player is facing in the world array
   world_door = []; % A world-sized array where each door block is labelled 1
   all_doors = [];
   myalldoors = [];
   
   all_door_timers = [];
   
    %% Initialization
    initVariable;
    initWindow;
    
    % Variable monitors
    % Help Texts:
    ESC = 'Quit';
    ENTER = 'Mouse On/Off';
    SCROLL_WHEEL = 'Zoom In/Out'
    varname = {'pos','KeyStatus', 'world(fobj_ind)', 'ESC','ENTER','SCROLL_WHEEL'};
    n_varname = numel(varname);
    
    StageList = {'floor1'};
    
    %% Main Game Cycle
    for i_stage = 1:length(StageList)
        %% Load the current stage
        StageTemp = load('OPEIG_WAD.mat', StageList{i_stage});
        CurrentStage = StageTemp.(StageList{i_stage});
        
        %% Setup the map and the walls
        world = CurrentStage.world;
        world_size =CurrentStage.world_size;
        wall = CurrentStage.wall;
        wallD = CurrentStage.wallD;
        wallU = CurrentStage.wallU;
        wallL = CurrentStage.wallL;
        wallR = CurrentStage.wallR;
        world_door = world>=2 & world <=5;
        world_door_status = zeros(size(world)); % 1 - opening, -1 - closing
                                          % 0 - hold
        world_timers = zeros(size(world));
        % An array of timers for all the sliding doors
%         all_door_locs = find(world_door); % 2-3, 4-5 are for sliding doors
        
        % The status of all doors
%         all_door_openness = mod(world(all_door_locs),2); % 2-3->0-1 4-5 ->0-1
        
%         all_door_status = ones(size(all_door_locs)); % 1 - opening, -1 - closing
                                                    % 0 - hold
        %
%         all_door_timers = zeros(size(all_door_locs));
        
        
        readTextures;
        colormap(cmap);
        pos = CurrentStage.init_pos;
        facing = CurrentStage.init_facing;
        
        %% Main Game Loop for each stage
        
        frame_updated = false;   % Whether the world is actually updated
        
        CurrentFrameNo = 0;      % the number of the current frame
        
%         if ShowFPS
        if ShowFPS
            vis = 'on';
        else
            vis = 'off';
        end
        fps_text_handle = text(10,10, '','Visible',vis, 'Interpreter','none');
        var_text_handle = zeros(1, n_varname);  % Pre-allocate text monitor handles
        for itext = 1:n_varname
            var_text_handle(itext) = text(10,10 + 10*itext, '',...
                'Visible',vis,...
                'Interpreter','none'); % Display a variable
        end
        total_frame_update = 0;
%         end
        
        stageStartTime = tic;
        c = stageStartTime;
        FPS_lastTime = toc(stageStartTime);
        terminateFlag = false;
        while 1
            loops = 0;
            curTime = toc(stageStartTime);
            while (curTime >= ((CurrentFrameNo) * FRAME_DURATION) && loops < MAX_FRAME_SKIP)
                %% Process Player
                if ~terminateFlag
                    % Process the movement of the players
                    [pos, facing, fobj_ind] = processPlayer(pos, facing);
                    world = world + world_door_status * 2 / FPS; % change doors
                    world(world >= 3 & world < 4) = 3; % clip 
                    world(world >= 4 & world < 5) = 5;
                    world(world > 1 & world <= 2) = 2;
                    world(world > 3 & world <= 4) = 4;
                    
                    [dist_all, wall_type, all_walls] = stl_RayCasting(pos, facing);
                end
                
                CurrentFrameNo = CurrentFrameNo + 1;
                loops = loops + 1;
                frame_updated = true;
            end
            
            %% Redraw the frame if the world has been processed
            if frame_updated
                renderScene(dist_all, wall_type, all_walls);
                drawnow;
                c = toc(stageStartTime);
                frame_updated = false;
                %
                total_frame_update = total_frame_update + 1;
                
                if mod(total_frame_update,SHOWFPS_FRAMES) == 0 % If time to update fps
                    set(fps_text_handle, 'String',sprintf('FPS: %.2f',SHOWFPS_FRAMES./(c-FPS_lastTime)));
                    FPS_lastTime = toc(stageStartTime);
                end
                if ShowFPS
                    for itext = 1:n_varname
                        set(var_text_handle(itext), 'String', sprintf('%s = %s', varname{itext}, num2str(eval(varname{itext}))));
                    end
                end
            end
            if CloseReq
                delete(MainFigureHdl);
%                 clear all;
                return;
            end
        end
    end
catch err
    delete(MainFigureHdl);
    rethrow(err);
end
%% ---------------- Regular Subfunctions ----------------------------------

%% Initializations

    function initVariable()
        % initVariable - initialize variables
        
        MainAxesSize = [320 200];
        
        maxWinSize = [800 600];
        
        FPS = 60;
        
        FRAME_DURATION = 1./FPS;
        MAX_FRAME_SKIP = 5;
        %         DEFAULT_FRAME_SKIP = 2;
        MainFigureSize = MainAxesSize .* 2;
        maxReal2max = max(MainFigureSize./maxWinSize);  % 
        if maxReal2max > 1  % at least one of them exceeds the maximum window
            MainFigureSize = MainFigureSize ./ maxReal2max;
        end
        MainFigureInitPos = [300 50];
        MainAxesInitPos = [0 0];
        
        KeyNames = {'w','s','a','d','leftarrow','rightarrow','space','return',...
            'escape','tab'};
        %         KeyFuncs = {'up','down','left','right','sprint','jump','start','select'};
        KeyStatus = false(1, length(KeyNames));
        LastKeyStatus = KeyStatus;
        ShowFPS = true;

        view.angle = 90;
        view.dist = 0.25;
        h_scale_factor =  tan(DEG2RAD * view.angle /2);
        view.plane_size = 2 * view.dist *h_scale_factor;
        
        % opeigview = viewdef; % Get the 3D viewing setting
        view.resx = MainAxesSize(1); % 320 pixels
        view.resy = MainAxesSize(2);
        
        scr_res = -view.resx/2:view.resx/2-1;  % number pixels from -160-159
        
        texture_wid = 64;
        texture_hgt = 64;
        % The angle of each ray relative to the facing vector
        cam_angle_comp = atan((scr_res+0.5) / view.resx * view.plane_size./view.dist);
        
        [xx, yy] = meshgrid(1:view.resx,1:view.resy);
        yy_p = (yy(:,:) - view.resy/2)./(view.resy/2);
        [~, yy_text] = meshgrid(1:view.resx, linspace(1,texture_hgt,view.resy));
        [~, yy_standard] = meshgrid(1:view.resx, linspace(0,1,view.resy));
        yy_text = round(yy_text);
        EmptyCanvas = uint8([ones(view.resy./2, view.resx);zeros(view.resy./2, view.resx)]);
        CurCanvas = uint8(zeros(view.resy, view.resx));
        
        ones200 = ones(1,view.resy);
    end

    function readTextures()
    % READTEXTURES: Load the wall textures
        wall_texture = load('OPEIG_WALLS.mat');
        
        % Discard the wall textures which definitely won't be used.
        wall_texture = wall_texture.wall_texture(:,1:max(wall(:))*texture_wid,:);
        floor_color = [0.7 0.7 0.7];
        ceiling_color = [0.5 0.5 0.5];
        
        % Convert the wall texture to index images. Use only 254 colors 
        % The other 2 colors are reserved for floor and ceiling.
        [wall_texture, cmap] = rgb2ind(wall_texture,254);
        wall_texture = wall_texture + 2;
        cmap = [floor_color; ceiling_color; cmap];
    end

    function initWindow()
        % initWindow - initialize the main window, axes and image objects
        MainFigureHdl = figure('Name', ['Operation Eigenfaust 3D MAT ' Opeig], ...
            'NumberTitle' ,'off', ...
            'Units', 'pixels', ...
            'Position', [MainFigureInitPos, MainFigureSize], ...
            'MenuBar', 'figure', ...
            'Renderer', 'Painter',...\
            'UserData', 'opeig',...
            'KeyPressFcn', @stl_KeyDown,...
            'KeyReleaseFcn', @stl_KeyUp, ...
            'WindowScrollWheelFcn', @stl_MouseWheel); %,...
        %             'CloseRequestFcn', @stl_CloseReqFcn);
        MainAxesHdl = axes('Parent', MainFigureHdl, ...
            'Units', 'normalized',...
            'Position', [MainAxesInitPos, 1-MainAxesInitPos.*2], ...
            'color', [0 0 0], ...
            'XLim', [0 MainAxesSize(1)]-0.5, ...
            'YLim', [0 MainAxesSize(2)]-0.5, ...
            'YDir', 'reverse', ...
            'NextPlot', 'add', ...
            'Visible', 'on', ...
            'XTick',[], ...
            'YTick',[]);
        SceneHandle = image(0, 0, [],...
            'Parent', MainAxesHdl,...
            'Visible', 'on');

        screenCenter = get(0,'ScreenSize');
        screenCenter = screenCenter(3:4)./2;
        set(0,'PointerLocation',screenCenter)
    end

%% Game logics
    function [pos, facing, facing_obj_ind] = processPlayer(pos, facing)
        %
        unit_facing_vec = [cos(facing * DEG2RAD), sin(facing * DEG2RAD)];
        fw_vec = unit_facing_vec * 2/FPS;
        left_vec = fw_vec * [0 -1;1 0]; % rotate -90 deg
        rot_angle = 60/FPS;
        new_pos = pos;
        if KeyStatus(1)
            new_pos = new_pos + fw_vec;
        end
        if KeyStatus(2)
            new_pos = new_pos - fw_vec;
        end
        if KeyStatus(3)
            new_pos = new_pos + left_vec;
        end
        if KeyStatus(4)
            new_pos = new_pos - left_vec;
        end
        if KeyStatus(5)
            facing = facing - rot_angle;
        end
        if KeyStatus(6)
            facing = facing + rot_angle;
        end
        
        if use_mouse
            curMousePos = get(0, 'PointerLocation');
            curMouseDispl = curMousePos - screenCenter;
            set(0,'PointerLocation',screenCenter)
            facing = facing + curMouseDispl(1);
        end
        facing = mod(facing, 360);
        
        %% Collision Detection
%         if false
        temp_new_pos = new_pos+0.5; %[floor(new_pos(1) + 0.5), floor(new_pos(2)+0.5)];
        niche_pos =  [floor(temp_new_pos); ceil(temp_new_pos)];
        niche = world(niche_pos(:,2), niche_pos(:,1));
        [obs_pos_r, obs_pos_c] = find(niche~=0&niche~=3&niche~=5);
        obs_pos = (obs_pos_c-1)*2+obs_pos_r;
        n_obs = numel(obs_pos);
        if n_obs == 3       % In a corner (3 blocks)
            new_pos = round(new_pos + 0.5) - 0.5;
        elseif n_obs == 2   % Stopped by a wall (2 blocks)
            thiswalltype = obs_pos(2) - obs_pos(1);
            if  thiswalltype == 1   % a Vertical wall
                new_pos(1) = round(new_pos(1) + 0.5) - 0.5;
            elseif thiswalltype == 2  % a horizontal wall
                new_pos(2) = round(new_pos(2) + 0.5) - 0.5;
            elseif thiswalltype == 3  % diagonal ... ?
                new_pos = round(new_pos + 0.5) - 0.5;
            end
        elseif n_obs == 1   % Only 1 block
            % The player will be pushed back to a most reasonable location
            x = obs_pos_c;
            y = obs_pos_r;
            block_pos = [ niche_pos(x,1), niche_pos(y,2)];

            [~, pushed_pos] = circle_square_intersect(block_pos, temp_new_pos);
            new_pos = pushed_pos-0.5;
        elseif n_obs == 4
%             error(' you end up in the wall..');
        end
        pos = new_pos;
        
        % The object the player is facing.
        obj_pos = pos + unit_facing_vec./sqrt(2); % must be within distance sqrt(2) 
        if all(floor(obj_pos) == floor(pos)) 
            % If the end of the 'facing' vector is in the same block as
            % player
            facing_obj_ind = [];
        else
            obj_pos = ceil(obj_pos);
            facing_obj_ind = (obj_pos(1)-1)*world_size(1) + obj_pos(2);
        end
    end
%% Perform raycasting for a frame

    function [dist_all, int_wall_type, all_walls] = stl_RayCasting(pos, facing)
        
        cur_view.angle = DEG2RAD * (facing) + cam_angle_comp;
        
        % Precalculate all the cos/sin/tans
        cur_view.angle_cos = cos(cur_view.angle);
        cur_view.angle_sin = sin(cur_view.angle);
        cur_view.angle_tan = tan(cur_view.angle);
        
        %% Find intersections on horizontal walls
        faceup = cur_view.angle_sin < 0;        % Those rays that point upward
        facedown = cur_view.angle_sin > 0;      % Those that go downward
        
        nup = sum(faceup);
        ndown = sum(facedown);
        
        %{ player pos
        %  -0---1-2----------------
        %  |  |  |  |
        %  0  1  2  3 ...
        %  |  |  |  |
        %  -1---1-2----------------
        %}
        
        %% UP and DOWN
        % Y coord of the first intersection in the UP and DOWN directions
        yup = ceil(pos(2)-1);    % Ensures correct calculation if
        ydown = floor(pos(2)+1); % player happens to be on a boundary
        
        %The Y coord of the first intersections for ALL rays
        yups = yup(1,ones(1,nup));
        % Calculate the corresponding x coords
        xups = (pos(1) + (yups - pos(2))./cur_view.angle_tan(faceup));
        
        % Do the same for downward rays
        ydowns = ydown(1, ones(1, ndown));
        xdowns = (pos(1) + (ydown - pos(2))./cur_view.angle_tan(facedown));
        
        % The final (x,y) for intersections:
        yhoris = zeros(1, view.resx)+Inf;
        xhoris = zeros(1, view.resx)+Inf;
        wall_horis = zeros(1, view.resx);
        
        % Init the intersection flags
        cur_intersect = false(1,nup);
        cur_non_intersect = ~cur_intersect;
        no_intersect = false(1, nup);
        wall_up = zeros(1,nup);   % The texture id of each intersection
        
        cur_ints_d = false(1, ndown);
        cur_non_ints_d = ~cur_ints_d;
        no_ints_d = ~false(1, ndown);
        wall_down = zeros(1,ndown);
        
        cur_inv_tan = 1./cur_view.angle_tan(faceup);
        cur_inv_tan_d = 1./cur_view.angle_tan(facedown);
        
        %% Process UPWARD first
        
        % Flags that are set to true when the rays intersect with a wall.
        for ii = 1:1
            
            cur_intersect(:) = false;
            cur_non_intersect(:) = ~cur_intersect(:);
            no_intersect(:) = false;
            k = floor(xups(cur_non_intersect))+1;
            no_intersect(cur_non_intersect) = (yup < 1) | ...          % if y is too small
                (floor(xups(cur_non_intersect))+1 > world_size(2)) | ...   % if x is too large
                (floor(xups(cur_non_intersect))+1 < 1);
                        
            while 1
                % If it is impossible for a ray to make an intersection with 
                % a horizontal wall within the range of the map
                % then its 'no_intersect' flag will be set to true, 
                % treating them as 'already intersected', and thus will not
                % be processed in the next iteration.
                
                no_intersect(cur_non_intersect) = (yup < 1) | ...          % if y is too small
                    (floor(xups(cur_non_intersect))+1 > world_size(2)) | ...   % if x is too large
                    (floor(xups(cur_non_intersect))+1 < 1);                % if x is too small
                
                cur_intersect = cur_intersect | no_intersect;
                cur_non_intersect = ~cur_intersect;
                
                if all(cur_intersect)
                    break;
                end
                
                % Check if the intersection is a wall
                % for all the non-intersected rays
                cur_intersect(cur_non_intersect) = ...
                    world(yup, floor(xups(cur_non_intersect))+1);
                
                % Find those that still aren't intersected
                cur_non_intersect = ~cur_intersect;
                
                %  scan the next possible point
                yup = yup - 1;
                yups(cur_non_intersect) = yup;
                
                % using sign always generates 1 or -1, since xups/xdowns do not include
                % 0 cases.
                xups(cur_non_intersect) = xups(cur_non_intersect) - ...
                    cur_inv_tan(cur_non_intersect);
            end
            
            xups(no_intersect) = Inf;
            yups(no_intersect) = Inf;
            % Get the texture
            xups_int = floor(xups(~no_intersect));
            ind = world_size(1) * xups_int +yups(~no_intersect); % No + 1
            wall_up(~no_intersect) = (wallD(ind)-1) * texture_wid + ... % The texture index
                floor((xups(~no_intersect) - xups_int) * texture_wid)+1; % The column index
            
            % The wall texture column the rays encounters on their way up
            
            % wallups(~no_intersect) = wall(sub2ind(world_size, yups(~no_intersect),...
            %     ceil(xups(~no_intersect))));
            % xups
            %toc;
            % disp('Up over');
            % visual_raycast(world, pos, xups, yups)
            %% Then process DOWNWARD
            % Flags that are set to true when the rays intersect with a wall.
            cur_ints_d(:) = false;
            cur_non_ints_d(:) = ~cur_ints_d(:);
            no_ints_d(:) = false;
            while 1
                % If it is impossible for a ray to make an intersection with 
                % a horizontal wall within the range of the map
                % then its 'no_ints_d' flag will be set to true, 
                % treating them as 'already intersected', and thus will not
                % be processed in the next iteration.
                no_ints_d(cur_non_ints_d) = (ydown > world_size(1)-1) | ...          % if y is too small
                    (floor(xdowns(cur_non_ints_d))+1 > world_size(2)) | ...   % if x is too large
                    (floor(xdowns(cur_non_ints_d))+1 < 1);                % if x is too small
                
                cur_ints_d = cur_ints_d | no_ints_d;
                cur_non_ints_d = ~cur_ints_d;
                if all(cur_ints_d)
                    break;
                end
                
                % Check if the intersection is a wall
                % for all the non-intersected rays
                % For downward rays, 'ydown+1' means takes the
                % block 'below' the intersected wall.
                cur_ints_d(cur_non_ints_d) = ...
                    world(ydown+1, floor(xdowns(cur_non_ints_d))+1);
                
                % Find those that still aren't intersected
                cur_non_ints_d = ~cur_ints_d;
                
                %  scan the next possible point
                ydown = ydown + 1;
                ydowns(cur_non_ints_d) = ydown;
                xdowns(cur_non_ints_d) = xdowns(cur_non_ints_d) + ...
                    cur_inv_tan_d(cur_non_ints_d);
            end
            xdowns(no_ints_d) = Inf;
            ydowns(no_ints_d) = Inf;
            
            % Get the texture
            % ind = world_size(1)*xdowns_int+ydowns(~no_ints_d)+1;
            % wall_down(~no_ints_d) = wallU(ind);
            % Get the texture
            xdowns_int = floor(xdowns(~no_ints_d));
            ind = world_size(1)*xdowns_int+ydowns(~no_ints_d)+1; % Have to +1 because the block is below the intersection
            wall_down(~no_ints_d) = ((wallU(ind)-1)*texture_wid) + floor((1- xdowns(~no_ints_d) + xdowns_int) * texture_wid)+1; % The wall texture index the rays encounters on their way up
            % if the ray goes down, it means the texture starts from the right. Hence '1- xdowns(~no_ints_d) + xdowns_int'
            
            %toc;
            % visual_raycast(world, pos, xdowns, ydowns)
            
            %% Combine UP and DOWN together
            
            %% ---------------------------------------
            %% ---------------------------------------
            %% ---------- Now find intersections on Verticle Walls
            %% -----------------------------------------
            %% ------------------------------------------
            %% Find intersections on horizontal walls
            faceleft = cur_view.angle_cos < 0;        % Those rays that point leftward
            faceright = cur_view.angle_cos > 0;      % Those that go rightward
            
            nleft = sum(faceleft);
            nright = sum(faceright);
            
            %% LEFT and RIGHT
            % x coord of the first intersection in the LEFT-RIGHT directions
            xleft = ceil(pos(1)-1);    % Ensures correct calculation if
            xright = floor(pos(1)+1); % player happens to be on a boundary
            
            %The Y coord of the first intersections for ALL leftward rays
            xlefts = xleft(1,ones(1,nleft));
            % Calculate the corresponding x coords
            ylefts = (pos(2) + (xlefts - pos(1)) .* cur_view.angle_tan(faceleft));
            
            % Do the same for rightward rays
            xrights = xright(1, ones(1, nright));
            yrights = (pos(2) + (xrights - pos(1)).*cur_view.angle_tan(faceright));
            
            % The final (x,y) for ALL intersections:
            yverts = zeros(1, view.resx) + Inf;
            xverts = zeros(1, view.resx) + Inf;
            wall_verts = zeros(1, view.resx);
            
            cur_ints_l = false(1,nleft);
            cur_non_ints_l = ~cur_ints_l;
            no_ints_l = false(1, nleft);
            cur_tan_l = cur_view.angle_tan(faceleft);
            wall_left = zeros(1, nleft);
            
            cur_ints_r = zeros(1, nright);
            cur_non_ints_r = ~cur_ints_r;
            no_ints_r = ~false(1, nright);
            cur_tan_r = cur_view.angle_tan(faceright);
            wall_right = zeros(1, nright);
            
            %% Process LEFTward first
            
            % Flags that are set to true when the rays intersect with a wall.
            cur_ints_l(:) = false;
            cur_non_ints_l(:) = ~cur_ints_l(:);
            no_ints_l(:) = false;
            %tic;
            while 1
                % If a ray is impossible to make an intersection with a vertical wall
                % then its 'no_ints_l' flag will be set to true, treating them as
                % 'already intersected'
                no_ints_l(cur_non_ints_l) = (xleft < 1) | ...          % if x is too small
                    (floor(ylefts(cur_non_ints_l))+1 > world_size(1)) | ...   % if y is too large
                    (floor(ylefts(cur_non_ints_l))+1 < 1);                % if y is too small
                
                cur_ints_l = double(cur_ints_l | no_ints_l);
                cur_non_ints_l = ~cur_ints_l;
                if all(cur_ints_l)
                    break;
                end
                
                % Check if the intersection is a wall
                % for all the non-intersected rays
                cur_ints_l(cur_non_ints_l) = ...
                    world(floor(ylefts(cur_non_ints_l))+1, xleft);

                % a value between 2-3 indicates a vertical door.                
                vert_doors = (cur_ints_l>=2 & cur_ints_l<=3); 
                
                % how much a door is opened
                vert_doors_open = zeros(size(cur_ints_l)); 
                vert_doors_open(vert_doors) = cur_ints_l(vert_doors) - 2;
%                 vert_doors_open = cur_ints_l - floor(cur_ints_l);
                
                if any(vert_doors)  % Indent 0.5 for doors
                    xlefts(vert_doors) = xleft - 0.5;
                    ylefts(vert_doors) = ylefts(vert_doors) - 0.5*cur_tan_l(vert_doors);
                    
                    % The decimal part of all y values,
                    % used to determine if a ray would pass a half opened 
                    % door or not.
                    y_decimal = mod(ylefts,1);

                    % If a ray happens to fall on the opened part of door
                    rays_possible_crack = (y_decimal > 0 & y_decimal < vert_doors_open);
                    rays_crack = vert_doors & rays_possible_crack;
                    
                    xlefts(rays_crack) = xleft;
                    ylefts(rays_crack) =  ylefts(rays_crack) + 0.5*cur_tan_l(rays_crack);
                    cur_ints_l(rays_crack) = 0; % Continue to propagate those rays
                end
                % Find those that still aren't intersected
                cur_non_ints_l = ~cur_ints_l;
                
                %  scan the next possible point (the one on the left)
                xleft = xleft - 1;
                xlefts(cur_non_ints_l) = xleft;
                
                % using sign always generates 1 or -1, since there are no
                % 0 cases.
                ylefts(cur_non_ints_l) = ylefts(cur_non_ints_l) -...
                    cur_tan_l(cur_non_ints_l);
                
                %     no_intersect = ylefts(floor(ylefts)+1 > world_size(2)) = 0;
                %     yup(yup < 1) = 1;
            end
            no_ints_l = no_ints_l | ((ylefts < 0) | (ylefts >  world_size(1)));
            
            
            ylefts(no_ints_l) = Inf;
            xlefts(no_ints_l) = Inf;
            %toc;
            % Get the texture
            ylefts_int = floor(ylefts(~no_ints_l));
            ind = world_size(1) * (ceil(xlefts(~no_ints_l)-1)) + ylefts_int+1; % -1 for the block on the 'left' of the wall
            ind(ind==0) = 1;
            wall_left(~no_ints_l) = (wallR(ind)-1) * texture_wid + ... % The texture index
                floor((1 - ylefts(~no_ints_l) + ylefts_int) * texture_wid)+1; % The column index
            
            worldshift = mod(world,1);
            
            % Those rays the intersects with an half-opened door
            all_doors = world>=2 & world<=3;

            vis_world = worldshift(ind); % Visual part of the world; 1 x n
            doors_in_view = all_doors(ind);
            
%             all_doors = vis_world~=0; % 1 x n logical
%             myalldoors = ind(all_doors);
%              all_shifts = vis_world(all_doors); % 1 x k
            % doors in the world
%             wall_right(~no_ints_r) = (wallL(ind)-1) * texture_wid + ... % The texture index
%                 mod(floor((yrights(~no_ints_r) - yrights_int - worldshift(ind)) * texture_wid), texture_wid)+1; % The column index
            vis_wall = wallR(ind);
%             texture_ind = (wallR(ind)-1) * texture_wid;
            offsets = ylefts(~no_ints_l) - ylefts_int;
            offsign = ones(size(offsets));
            offsign(doors_in_view) = -1; %(vis_world > 0) = -1;
            onesign = ~doors_in_view; %(vis_world == 0);
            wall_left(~no_ints_l) = (vis_wall-1) * texture_wid + ... % The texture index
                mod(floor((onesign - offsign .* offsets - vis_world) * texture_wid), texture_wid)+1; % The column index
%             wall_left(all_doors) = 1; %(vis_wall(all_doors)-1)*texture_wid + ...
                %mod(floor((ylefts(all_doors) - ylefts_int(all_doors) - all_shifts) * texture_wid), texture_wid) + 1;
            
            if any(all_doors)
                a = 3;
            end
%             wall_left(~no_ints_l) = (wallR(ind)-1) * texture_wid + ... % The texture index
%                 floor((1 - ylefts(~no_ints_l) + ylefts_int) * texture_wid)+1; % The column index
            % If it is a door, then we must reverse its texture
            
%             assert(all(wall_left>=0))
            
            % visual_raycast(world, pos, xlefts, ylefts);
            
            %% Then process RIGHTWARD
            
            % Flags that are set to true when the rays intersect with a wall.
            cur_ints_r(:) = false;
            cur_non_ints_r(:) = ~cur_ints_r(:);
            no_ints_r(:) = false;
            %tic;
            while 1
                % If a ray is impossible to make an intersection with a horizontal wall
                % then its 'no_intersect' flag will be set to true, treating them as
                % 'already intersected'
                no_ints_r(cur_non_ints_r) = (xright + 1 > world_size(2)) | ...          % if x is too large
                    (floor(yrights(cur_non_ints_r))+1 > world_size(1)) | ...   % if y is too large
                    (floor(yrights(cur_non_ints_r))+1 < 1);                % if y is too small
                
                cur_ints_r = double(cur_ints_r | no_ints_r);
                cur_non_ints_r = ~cur_ints_r;
                if all(cur_ints_r)
                    break;
                end
                
                % Check if the intersection is a wall
                % for all the non-intersected rays
                cur_ints_r(cur_non_ints_r) = ...
                    world(floor(yrights(cur_non_ints_r))+1, xright+1);
                
                % If there is any VERTICAL door
                
                vert_doors = (cur_ints_r>=2 & cur_ints_r <=3);

                % how much a door is opened
                vert_doors_open = zeros(size(cur_ints_r)); 
                vert_doors_open(vert_doors) = cur_ints_r(vert_doors) - 2;
%                 cur_ints_r(vert_doors);
%                 cur_ints_r - floor(cur_ints_r);
                
                if any(vert_doors)  % Indent 0.5
                    % y_vert_doors = floor(yrights(vert_doors)); 
                    % all the y values of intersection on doors
                    
                    xrights(vert_doors) = xright + 0.5;
                    yrights(vert_doors) = yrights(vert_doors) + 0.5*cur_tan_r(vert_doors);
                    
                    % The decimal part of all y values,
                    % used to determine if a ray would pass a half opened 
                    % door or not.
                    y_decimal = mod(yrights,1);

                    % If a ray happens to fall on the opened part of door
                    rays_possible_crack = (y_decimal > 0 & y_decimal < vert_doors_open);
                    rays_crack = vert_doors & rays_possible_crack;
                    
                    xrights(rays_crack) = xright;
                    yrights(rays_crack) =  yrights(rays_crack) - 0.5*cur_tan_r(rays_crack);
                    cur_ints_r(rays_crack) = 0; % Continue to propagate those rays
                end
                % If all rays are 'intersected', then quit.
                %     if all(cur_ints_r)
                %         break;
                %     end
                
                % Find those that still aren't intersected
                cur_non_ints_r = ~cur_ints_r;
                
                %  scan the next possible point (the one on the left)
                xright = xright + 1;
                xrights(cur_non_ints_r) = xright;
                
                % using sign always generates 1 or -1, since there are no
                % 0 cases.
                yrights(cur_non_ints_r) = yrights(cur_non_ints_r) +...
                    cur_tan_r(cur_non_ints_r); % + ...
%                 if any(yrights < -100 )
%                     a = 33
%                 end
                %     no_intersect = ylefts(floor(ylefts)+1 > world_size(2)) = 0;
                %     yup(yup < 1) = 1;
            end
            no_ints_r = no_ints_r | ((yrights < 0) | (yrights >  world_size(1)));
            yrights(no_ints_r) = Inf;
            xrights(no_ints_r) = Inf;
            %toc;
            yrights_int = floor(yrights(~no_ints_r));
            
            % The 1D index on the world map.
            ind = world_size(1) * (floor(xrights(~no_ints_r))) + yrights_int + 1 ;
            % has +1 and has no -1 for the block on the 'right' of the wall
            
%             all_vert_door = (world > 2  & world < 3) & ~no_ints_r; % all the vertical
            worldshift = mod(world,1);
            
            % doors in the world
            wall_right(~no_ints_r) = (wallL(ind)-1) * texture_wid + ... % The texture index
                mod(floor((yrights(~no_ints_r) - yrights_int - worldshift(ind)) * texture_wid), texture_wid)+1; % The column index
            
            % visual_raycast(world, pos, xrights, yrights);
            
            %% Combine UP and DOWN together, LEFT and RIGHT together
            xhoris(faceup) = xups;
            yhoris(faceup) = yups;
            
            xhoris(facedown) = xdowns;
            yhoris(facedown) = ydowns;
            
            % wall_up(~no_intersect) = wallD(ind); % The wall texture index the rays encounters on their way up
            % wall_down(~no_ints_d)
            wall_horis(faceup) = wall_up;
            wall_horis(facedown) = wall_down;
            
            xverts(faceleft) = xlefts;
            yverts(faceleft) = ylefts;
            
            xverts(faceright) = xrights;
            yverts(faceright) = yrights;
            
            wall_verts(faceleft) = wall_left;
            wall_verts(faceright) = wall_right;
            
            dist_hori = sqrt((xhoris-pos(1)).^2 + (yhoris-pos(2)).^2);
            dist_vert = sqrt((xverts-pos(1)).^2 + (yverts-pos(2)).^2);
            [dist_all, int_wall_type] = min([dist_hori; dist_vert]);
            all_walls = [wall_horis, wall_verts];
        end
    end

    function renderScene(dist_all, int_wall_type, all_walls)
        % Render the final image !!!!1
        % The apparent height of each wall column on screen (in pixels);
%         apparent_h = round(view.resx.*view.plane_size./(dist_all)./cos(cam_angle_comp));
        h_scale_factor = tan(DEG2RAD * view.angle ./2);
        apparent_h = round(view.resx./2./h_scale_factor./(dist_all)./cos(cam_angle_comp));

        % The y coords where the wall columns start
        start_y_wallcols = ceil((view.resy - apparent_h)./2);
        apparent_h_ary = apparent_h(ones200,:);
        
        % All the wall columns to be mapped on the actual walls
        texture_strip = wall_texture(:,all_walls([1:view.resx] +(int_wall_type-1)*view.resx));
        
        
        full_walls = start_y_wallcols >=0;
        all_yy = round(yy(:,full_walls) + (apparent_h_ary(:,full_walls)-view.resy)./2 .* yy_p(:,full_walls));
        all_yy(all_yy>view.resy) = view.resy;
        all_yy(all_yy<1) = 1;
        
        all_xx = xx(:,full_walls);
        all_yy_text = round(yy_text(:, full_walls));
        all_text_xx = xx(:,~full_walls);
        
        cropped_yy_standard = yy_standard(:, ~full_walls);
        text_strip_stary_y =  repmat(round((-start_y_wallcols)*texture_hgt./apparent_h),[view.resy 1]); % Where the texture y starts
        cropped_yy_text = round(cropped_yy_standard .* ((texture_hgt-1) - 2*text_strip_stary_y(:,~full_walls)) + text_strip_stary_y(:,~full_walls)+1);
        cropped_yy_text(cropped_yy_text<1) = 1;
        cropped_yy_text(cropped_yy_text > texture_hgt) = texture_hgt;
        
        CurCanvas = EmptyCanvas;
        CurCanvas((all_xx(:)-1)*view.resy + all_yy(:)) = texture_strip((all_xx(:)-1)*texture_hgt + all_yy_text(:));
        CurCanvas(:, ~full_walls) = texture_strip((all_text_xx-1)*texture_hgt + cropped_yy_text);
        
        set(SceneHandle, 'CData', CurCanvas);
    end

%% Callback functions
    function stl_KeyUp(hObject, eventdata, handles)
        LastKeyStatus = KeyStatus;
        
        key = get(hObject,'CurrentKey');
        KeyStatus = (~strcmp(key, KeyNames) & LastKeyStatus);
        
    end
    function stl_KeyDown(hObject, eventdata, handles)
        LastKeyStatus = KeyStatus;
        key = get(hObject,'CurrentKey');
        
        KeyStatus = (strcmp(key, KeyNames) | LastKeyStatus);
        
        % If 'space' key is pressed
        if KeyStatus(7)
              if world_door(fobj_ind) % If facing a door
                if world_door_status(fobj_ind) ~= 0 % If the door is sliding
                    % Reverse the process
                    world_door_status(fobj_ind) = - world_door_status(fobj_ind);                    
                else  % If the door is not sliding 
                    if mod(world(fobj_ind),2) == 0 % If the door is fully closed
                        % then start opening
                        world_door_status(fobj_ind) = 1;
                    elseif mod(world(fobj_ind),2) == 1 % If it is fully open
                        % then start closing
                        world_door_status(fobj_ind) = -1;
                    else % If it is halfway open
                        % then do not move
                        world_door_status(fobj_ind) = 1;
                    end
                end
            end
        end
        
        if KeyStatus(8)
            use_mouse = ~use_mouse;
        end
        if KeyStatus(9)
            CloseReq = true;
        end
        % Turn the test info on/off
        if KeyStatus(10) 
            ShowFPS = ~ShowFPS;
            if ShowFPS
                set(var_text_handle,'Visible','on');
                set(fps_text_handle,'Visible','on');
            else
                set(var_text_handle,'Visible','off');
                set(fps_text_handle,'Visible','off');
            end
        end
    end
    function stl_MouseWheel(hObject, eventdata, handles)
        view.angle =  view.angle + eventdata.VerticalScrollCount*1.5;
        view.angle = min(max(view.angle, 10),90);
       
        % Refresh some variables
        h_scale_factor =  tan(DEG2RAD * view.angle /2);
        view.plane_size = 2 * view.dist *h_scale_factor;
        cam_angle_comp = atan((scr_res+0.5) / view.resx * view.plane_size./view.dist);
    end

    function stl_CloseReqFcn(hObject, eventdata, handles)
        CloseReq = true;
    end
%% Utility functions:
    function [interflag, circle_new_pos] = circle_square_intersect(sq_pos, circle_pos)
    % Determines if a circle (player) intersects with a square (wall)
        vec = sq_pos - circle_pos;
        
        abs_vec = abs(vec);
        sign_vec = sign(vec);
%         dist =vec(:)'*vec(:); % Doesn't seem to be used?
        interflag = false;
        circle_new_pos = circle_pos;
        
        if all(abs_vec >= 1)    % No intersection
            return;
        end
        
        if abs_vec(1)<=0.5
            if abs_vec(2) < 1
                interflag = true;
                circle_new_pos(2) = sq_pos(2) - [sign_vec(2)];
                return
            else
                return
            end
        elseif abs_vec(2) <= 0.5
            if abs_vec(1) < 1
                interflag = true;
                circle_new_pos(1) = sq_pos(1) - [sign_vec(1)];                
                return
            else
                return
            end
        else
            corner_pos = sq_pos - sign_vec.*0.5; 
            vec2 = corner_pos - circle_pos;
%             sign_vec2 = sign(vec2);  % doesn't seem to be used
            dist2 = sqrt(vec2(:)'*vec2(:));
            if dist2 < 0.5
                interflag = true;
                circle_new_pos = corner_pos - vec2./dist2*0.5;
                return
            end
        end
            
    end

end


Contact us