% Clear the workspace
close all;
clear;

% Here we call some default settings for setting up Psychtoolbox
PsychDefaultSetup(2);

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

% Initialise OpenGL
InitializeMatlabOpenGL;

% We will use multisampling to get nice smooth edges (reduce this number to
% zero if you computer can't handle four samples)
numSamples = 6;

% Open the main window with multi-sampling for anti-aliasing
[window, windowRect] = PsychImaging('OpenWindow', screenid, 0, [],...
    32, 2, [], numSamples,  []);

% Query the frame duration
ifi = Screen('GetFlipInterval', window);

% Maximum priority level
topPriorityLevel = MaxPriority(window);
Priority(topPriorityLevel);

% Start the OpenGL context
Screen('BeginOpenGL', window);

% For this demo we will assume our screen is 0.3m (30cm) in height. The units are
% essentially arbitary with OpenGL as it is all about ratios. But it is
% nice to define things in normal scale numbers
ar = windowRect(3) / windowRect(4);
screenHeight = 0.3;
screenWidth = screenHeight * ar;

% Enable lighting See Chapter 5 of the OpenGL "red book"
glEnable(GL.LIGHTING);

% Force there to be no ambient light (OpenGL default is for there to be
% some, however for this demo we want to fully demonstrate directional
% lighting)
glLightModelfv(GL.LIGHT_MODEL_AMBIENT, [0 0 0 1]);

% Define a local light source
glEnable(GL.LIGHT0);

% Defuse light only
glLightfv(GL.LIGHT0, GL.DIFFUSE, [1 1 1 1]);

% Point the light at the origin (this is where we will place our sphere)
glLightfv(GL.LIGHT0, GL.SPOT_DIRECTION, [0 0 0]);

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

% Allow normalisation
glEnable(GL.NORMALIZE);

% Our light source is going to circle around the origin [0 0 0], where our
% ball will be positioned. Here we set the radius of the circle and use
% trig to calculate its X and Z position for an angle of zero.]
% Note: the lights Y position
% will be fixed, so that the light is always in the plane of the sphere.
% all angles will be specified in degrees.
lightRadius = 1;
lightAngle = 0;
lightZpos = lightRadius * cosd(lightAngle);
lightXpos = lightRadius * sind(lightAngle);

% Lets set up a projection matrix, the projection matrix defines how images
% in our 3D simulated scene are projected to the images on our 2D monitor
glMatrixMode(GL.PROJECTION);
glLoadIdentity;

% Calculate the field of view assming that our object is at at "dist" away
% from our camera
dist = 0.4;
angle = 2 * atand(screenHeight / dist);

% Set up our perspective projection. This is defined by our field of view
% (here given by the variable "angle") and the aspect ratio of our frustum
% (our screen) and two clipping planes. These define the minimum and
% maximum distances allowable here 1cm and 10m.
gluPerspective(angle, ar, 0.01, 10);

% Setup modelview matrix: This defines the position, orientation and
%  direction of the virtual camera that will  look at our scene with
glMatrixMode(GL.MODELVIEW);
glLoadIdentity;

% Location of the camera
cam = [0 0 dist];

% Set our camera to be looking directly down the -Z axis (depth) of our
% coordinate system
fix = [0 0 -1];

% Define "up"
up = [0 1 0];

% Here we set up the attributes of our camera using the variables we have
% defined in the last three lines of code
gluLookAt(cam(1), cam(2), cam(3), fix(1), fix(2), fix(3), up(1), up(2), up(3));

% Set background color to 'black' (the 'clear' color)
glClearColor(0, 0, 0, 0);

% Clear out the backbuffer
glClear;

% Size of our sphere (12cm)
dim = 0.12;

% Make a display list with our red ball. Our red ball is not changing at
% all, so compiling it in a display list can help with rendering speed. In
% reality it probably does not make a huge difference to such a simple
% stimulus, but can help greatly when rendering complex stimuli.
%
% Chapter 7 of the OpenGL "red book"
sphereList = glGenLists(1);
glNewList(sphereList, GL.COMPILE);

% Our ball will reflect only diffuse light as that is all that is coiming from
% our light source.
glMaterialfv(GL.FRONT_AND_BACK, GL.DIFFUSE, [0.8 0.0 0 1]);

% Here we use the GLUT library to define a simple sphere
glutSolidSphere(dim, 100, 100);

glEndList;

% End the OpenGL context now that we have finished setting things up
Screen('EndOpenGL', window);

% Set the frames to wait to one
waitframes = 1;

% Get a time stamp with a flip
vbl = Screen('Flip', window);


% The simulation will loop until you press any key on the keyboard
while ~KbCheck

    % Begin the OpenGL context now we want to issue OpenGL commands again
    Screen('BeginOpenGL', window);

    % Position the light-source for this run of the rendering loop
    glLightfv(GL.LIGHT0, GL.POSITION, [lightXpos 0 lightZpos 1]);

    % To start with we clear everything
    glClear;

    % Next we draw our sphere by calling our sphere display list
    glPushMatrix;
    glCallList(sphereList);
    glPopMatrix;

    % End the OpenGL context now that we have finished doing OpenGL stuff.
    % This hands back control to PTB
    Screen('EndOpenGL', window);

    % Show rendered image at next vertical retrace
    vbl = Screen('Flip', window, vbl + (waitframes - 0.5) * ifi);

    % Increment the light position by a degree for the next rendering loop
    lightAngle = lightAngle + 1;
    lightZpos = lightRadius * cosd(lightAngle);
    lightXpos = lightRadius * sind(lightAngle);

end

% Clear up and leave the building
sca