% Clear the workspace
close all
clear
sca;

% Shuffle the random number generator to ensure randomness
rng('shuffle');

% 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;

% Open the main window with multi-sampling for anti-aliasing. Multisampling
% is a brute force but effective way in which to avoid aliasing of computer
% generated objects. PTB clamps the requested number of multisamples to the
% maximum allowed by the computer if more are requested. See help AntiAliasing
numMultiSamples = 6;
[window, windowRect] = PsychImaging('OpenWindow', screenid, 0, [],...
    32, 2, [], numMultiSamples,  []);

% Set the priority of PTB to max
topPriorityLevel = MaxPriority(window);
Priority(topPriorityLevel);

% Start the OpenGL context (you have to do this before you issue OpenGL
% commands such as we are using here)
Screen('BeginOpenGL', window);

% For this demo we will assume our screen is 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 = 30;
screenWidth = screenHeight * ar;

% Enable lighting
glEnable(GL.LIGHTING);

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

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

% 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 in the y direction assuming a distance to the
% objects of 100cm
dist = 100;
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 0.1cm and 200cm. If we draw outside of
% these regions then the stimuli won't be rendered
gluPerspective(angle, ar, 0.1, 200);

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

% Our point lightsource is at position (x,y,z) == (1,2,3)
glLightfv(GL.LIGHT0, GL.POSITION, [1 2 3 0]);

% Location of the camera is at the origin
cam = [0 0 0];

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

% Define "up": here we say that up is positive Y
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;

% Setup the positions of the spheres using the mexhgrid command
[cubeX, cubeY] = meshgrid(linspace(-25, 25, 10), linspace(-20, 20, 8));
[s1, s2] = size(cubeX);
cubeX = reshape(cubeX, 1, s1 * s2);
cubeY = reshape(cubeY, 1, s1 * s2);

% Draw all the cubes in a loop. We apply a translation and rotation to each
% of the cubes. The Puch and Pop commands ensure that our transforms are
% only applied to the current cube that we are drawing.
for i = 1:1:length(cubeX)

    % Push the matrix stack
    glPushMatrix;

    % Translate the cube in xyz
    glTranslatef(cubeX(i), cubeY(i), -dist);

    % Rotate the cube randomly in xyz
    glRotatef(rand * 360, 1, 0, 0);
    glRotatef(rand * 360, 0, 1, 0);
    glRotatef(rand * 360, 0, 0, 1);

    % Scale the cubes in size randomly. They will get a minimum of half the
    % specified size
    cubeScale = 0.5 + rand * 0.5;
    glScalef(cubeScale, cubeScale, cubeScale);

    % Change the light reflection properties of the material the cube is
    % made of. Here we do this randomly.
    theCubeColour = rand(1, 3);
    glMaterialfv(GL.FRONT_AND_BACK,GL.AMBIENT, [theCubeColour 1]);
    glMaterialfv(GL.FRONT_AND_BACK,GL.DIFFUSE, [theCubeColour 1]);

    % Draw the solid cube (3x3x3 in size)
    glutSolidCube(3);

    % Pop the matrix stack for the next cube
    glPopMatrix;

end

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

% Show rendered image at next vertical retrace. We do not specify a time
% here as for this demo we are only presenting the stimuli flipped to
% screen once.
Screen('Flip', window);

% Wait for keyboard press
KbStrokeWait;

% Shut the screen down
sca;