function DispObj(ObjFile, stereomode)
% function DispObj(ObjFile, stereomode)
% Simple display of face OBJ: give it the filename without .obj extension
%
%  Distributed 'as is' from the ESRC face database page at http://pics.stir.ac.uk/ESRC/software.html
%  It may well be very inefficient - it uses the moglmorpher code within
%  Psychtoolbox, because I was adapting a morphing example.  I have no idea
%  what it might do with other obj files, but it seems to work with ours.
%
% Control keys and their meaning:
% 'a' == Zoom out by moving object away from viewer.
% 'z' == Zoom in by moving object close to viewer.
% Left and Right arrow == Rotate object around axis.
% up and Down arrow == nodding
% 'q' == Quit demo.
%
% Options:
%
% stereomode = n. For n>0 this activates stereoscopic rendering - The shape is
% rendered from two slightly different viewpoints and one of Psychtoolbox's
% built-in stereo display algorithms is used to present the 3D stimulus. This
% is very preliminary so it doesn't work that well yet.
%
%
% This demo is adapted from Mario Kleiner's adaptation of one from
% Dr. Quoc C. Vuong, MPI for Biological Cybernetics, Tuebingen, Germany.

global win;

% Is the script running in OpenGL Psychtoolbox?
AssertOpenGL;

if nargin < 2 | isempty(stereomode)
    stereomode = 0;
end;

KbName('UnifyKeyNames');
% Response keys: Mapping of keycodes to keynames.
closer = KbName('a');
farther = KbName('z');
quitkey = KbName('q');
rotateleft = KbName('LeftArrow');
rotateright = KbName('RightArrow');
rotateup = KbName('UpArrow');
rotatedown = KbName('DownArrow');

% Load OBJs. This will define topology and use of texcoords and normals:
% One can call LoadOBJFile() multiple times for loading multiple objects.
objs = LoadOBJFile([ObjFile '.obj']);


% Find the screen to use for display:
screenid=max(Screen('Screens'));

% Disable Synctests for this simple demo:
Screen('Preference','SkipSyncTests',1);

% Setup Psychtoolbox for OpenGL 3D rendering support and initialize the
% mogl OpenGL for Matlab wrapper. We need to do this before the first call
% to any OpenGL function:
InitializeMatlabOpenGL(0,1);

% Open a double-buffered window: Everything is left at default
% settings, except stereomode:
   rect = [0 0 800 800];
%   rect = [];

[win , winRect] = Screen('OpenWindow', screenid, 0, rect, [], [], stereomode, [], kPsychNeedFastBackingStore);
SetStereoBlueLineSyncParameters(win); 

% Load and create face textures in Psychtoolbox:

% First we load & create texture as 'texid1':
texture = imread([ObjFile '.jpg']);
texid = Screen('MakeTexture', win, texture);


% Now we create an Offscreen window - a texture one can draw into - of
% the same size and format as the face texture. 
disptex = Screen('OpenOffscreenWindow', win, [0 0 0 0], Screen('Rect', texid));
Screen('DrawTexture', disptex, texid);

% Retrieve a standard OpenGL texture handle and target from Psychtoolbox for use with MOGL:
%[gltexid gltextarget] = Screen('GetOpenGLTexture', win, texid);

% We retrieve the texture handle of our buffer - this one will be
% applied to the 3D mesh:
[gltexid, gltextarget] = Screen('GetOpenGLTexture', win, disptex);

texcoords(2,:) = objs{1}.texcoords(2,:);
texcoords(1,:) = objs{1}.texcoords(1,:);

% Which texture type is provided to us by Psychtoolbox?
if gltextarget == GL.TEXTURE_2D
    % Nothing to do for GL_TEXTURE_2D textures...
else
    % Rectangle texture: We need to rescale our texcoords as they are made for
    % power-of-two textures, not rectangle textures:
    texcoords(1,:) = texcoords(1,:) * size(texture,2);
    texcoords(2,:) = texcoords(2,:) * size(texture,1);
end;

% Setup the OpenGL rendering context of the onscreen window for use by
% OpenGL wrapper. After this command, all following OpenGL commands will
% draw into the onscreen window 'win':
Screen('BeginOpenGL', win);


% Setup texture mapping for our face texture:
glBindTexture(gltextarget, gltexid);
glEnable(gltextarget);

% Choose texture application function: It shall modulate the light
% reflection properties of the the objects surface:
glTexEnvfv(GL.TEXTURE_ENV,GL.TEXTURE_ENV_MODE,GL.MODULATE);

% Setup texture wrapping behaviour to clamp, as other behaviours are
% unsupported on many gfx-cards for rectangle textures:
glTexParameterfv(gltextarget,GL.TEXTURE_WRAP_S,GL.CLAMP);
glTexParameterfv(gltextarget,GL.TEXTURE_WRAP_T,GL.CLAMP);

% Setup filtering for the textures - Use bilinear filtering:
glTexParameterfv(gltextarget,GL.TEXTURE_MAG_FILTER,GL.LINEAR);
glTexParameterfv(gltextarget,GL.TEXTURE_MIN_FILTER,GL.LINEAR);

% Reset moglmorpher:
moglmorpher('reset');

objs{1}.texcoords = texcoords; % Add modified texture coords.
meshid = moglmorpher('addMesh', objs{1});


% Get the aspect ratio of the screen, we need to correct for non-square
% pixels if we want undistorted displays of 3D objects:
ar=winRect(4)/winRect(3);

% Enable proper occlusion handling via depth tests:
glEnable(GL.DEPTH_TEST);

% Set projection matrix: This defines a perspective projection,
% corresponding to the model of a pin-hole camera - which is a good
% approximation of the human eye and of standard real world cameras --
% well, the best aproximation one can do with 3 lines of code ;-)
glMatrixMode(GL.PROJECTION);
glLoadIdentity;

% Field of view is +/- 25 degrees from line of sight. Objects close than
% 0.1 distance units or farther away than 200 distance units get clipped
% away, aspect ratio is adapted to the monitors aspect ratio:
gluPerspective(25.0,1/ar,0.1,200.0);

% Setup modelview matrix: This defines the position, orientation and
% looking direction of the virtual camera:
glMatrixMode(GL.MODELVIEW);
glLoadIdentity;


% Initialize amount and direction of rotation for our slowly spinning,
% morphing objects:
theta=0; %y axis - nodding
ang = 0.0; % Initial rotation angle, left/right

% Setup initial z-distance of objects:
zz = 70.0;

% Compute initial morphed shape for next frame, based on initial weights:
moglmorpher('computeMorph', 1);

% Half eye separation in length units for quick & dirty stereoscopic
% rendering. Our way of stereo is not correct, but it makes for a
% nice demo. Figuring out proper values is not too difficult, but
% left as an exercise to the reader.
eye_halfdist=5;

% Finish OpenGL setup and check for OpenGL errors:
Screen('EndOpenGL', win);

% Retrieve duration of a single monitor flip interval: Needed for smooth
% animation.
ifi = Screen('GetFlipInterval', win);

% Initially sync us to the VBL:
vbl=Screen('Flip', win);

% Some stats...
tstart=vbl;
framecount = 0;
waitframes = 1;

% Animation loop: Run until key press or one minute has elapsed...
while (true)
    % Switch to OpenGL rendering for drawing of next frame:
    Screen('BeginOpenGL', win);

    % Left-eye cam is located at 3D position (-eye_halfdist,0,zz), points upright (0,1,0) and fixates
    % at the origin (0,0,0) of the worlds coordinate system:
    glLoadIdentity;
    gluLookAt(-eye_halfdist, 0, zz, 0, 0, 0, 0, 1, 0);


    % Draw into image buffer for left eye:
    Screen('EndOpenGL', win);
    Screen('SelectStereoDrawBuffer', win, 0);
    Screen('BeginOpenGL', win);

    % Clear out the depth-buffer for proper occlusion handling:
    glClear(GL.DEPTH_BUFFER_BIT);

    % do the actual drawing of the shape :
    % Backup modelview matrix:
    glPushMatrix;

    % Setup rotation around axis:
    glRotated(theta,1,0,0);
    glRotated(ang,0,1,0);

    % Scale object by a factor of a:
    a=0.1;
    glScalef(a,a,a);

    % Render current morphed shape via moglmorpher:
    moglmorpher('render');

    % Restore modelview matrix:
    glPopMatrix;
    

    % Stereo rendering requested?
    if (stereomode > 0)
        % Yes! We need to render the same object again, just with a different
        % camera position, this time for the right eye:
                
        % Right-eye cam is located at 3D position (+eye_halfdist,0,zz), points upright (0,1,0) and fixates
        % at the origin (0,0,0) of the worlds coordinate system:
        glLoadIdentity;
        gluLookAt(+eye_halfdist, 0, zz, 0, 0, 0, 0, 1, 0);
        % Draw into image buffer for right eye:
        Screen('EndOpenGL', win);
        Screen('SelectStereoDrawBuffer', win, 1);
        Screen('BeginOpenGL', win);
    
        % Clear out the depth-buffer for proper occlusion handling:
        glClear(GL.DEPTH_BUFFER_BIT);
        
        % do the actual drawing of the shape :
        % Backup modelview matrix:
        glPushMatrix;
        
        % Setup rotation around axis:
        glRotated(theta,1,0,0);
        glRotated(ang,0,1,0);
        
        % Scale object by a factor of a:
        glScalef(a,a,a);
        
        % Render current morphed shape via moglmorpher:
        moglmorpher('render');
        
        % Restore modelview matrix:
        glPopMatrix;
    end;

    
    % Finish OpenGL rendering into Psychtoolbox - window and check for OpenGL errors.
    Screen('EndOpenGL', win);

    
    % Tell Psychtoolbox that drawing of this stim is finished, so it can optimize
    % drawing:
    Screen('DrawingFinished', win);

    % Now that all drawing commands are submitted, we can do the other stuff before
    % the Flip: 
        
    % Check for keyboard press:
    [KeyIsDown, endrt, KeyCode] = KbCheck;
    
    if ( KeyIsDown==1 & KeyCode(closer)==1 )
        zz=zz-0.3;
        KeyIsDown=0;
    end
    
    if ( KeyIsDown==1 & KeyCode(farther)==1 )
        zz=zz+0.3;
        KeyIsDown=0;
    end
    
    if ( KeyIsDown==1 & KeyCode(rotateright)==1 )
        ang=ang+2.0;
        KeyIsDown=0;
    end
    
    if ( KeyIsDown==1 & KeyCode(rotateleft)==1 )
        ang=ang-2.0;
        KeyIsDown=0;
    end
    
    if ( KeyIsDown==1 & KeyCode(rotateup)==1 )
        theta=theta-1;
        KeyIsDown=0;
    end
    
    if ( KeyIsDown==1 & KeyCode(rotatedown)==1 )
        theta=theta+1;
        KeyIsDown=0;
    end
    if ( KeyIsDown==1 & KeyCode(quitkey)==1 )
        break;
    end

    % Update frame animation counter:
    framecount = framecount + 1;

    % We're done for this frame:

    % Show rendered image 'waitframes' refreshes after the last time
    % the display was updated and in sync with vertical retrace:
    vbl = Screen('Flip', win, vbl + (waitframes - 0.5) * ifi);
end

% Calculate and display average framerate:
fps = framecount / (vbl - tstart)

Screen('Flip', win);

moglmorpher('reset');
% Close onscreen window and release all other ressources:
Screen('CloseAll');

% Reenable Synctests after this simple demo:
Screen('Preference','SkipSyncTests',1);

% Well done!
return

