0% found this document useful (0 votes)
437 views55 pages

Virtual Reality Matlab Engine: Virmen - The Manual

Uploaded by

Mozi
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
437 views55 pages

Virtual Reality Matlab Engine: Virmen - The Manual

Uploaded by

Mozi
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 55

VIRTUAL REALITY MATLAB ENGINE

VIRMEN – THE MANUAL

by Dmitriy Aronov

Laboratory of David W. Tank

Princeton University

Last updated: February 12, 2016


**********************************************************************
Copyright 2013, Princeton University. All rights reserved.

By using this software the USER indicates that he or she has read,
understood and will comply with the following:

-- Princeton University hereby grants USER nonexclusive permission to


use, copy and/or modify this software for internal, noncommercial,
research purposes only. Any distribution, including publication or
commercial sale or license, of this software, copies of the software,
its associated documentation and/or modifications of either is
strictly prohibited without the prior consent of Princeton University.
Title to copyright to this software and its associated documentation
shall at all times remain with Princeton University. Appropriate
copyright notice shall be placed on all software copies, and a
complete copy of this notice shall be included in all copies of the
associated documentation. No right is granted to use in advertising,
publicity or otherwise any trademark, service mark, or the name of
Princeton University.

-- This software and any associated documentation is provided "as is"

PRINCETON UNIVERSITY MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS


OR IMPLIED, INCLUDING THOSE OF MERCHANTABILITY OR FITNESS FOR A
PARTICULAR PURPOSE, OR THAT USE OF THE SOFTWARE, MODIFICATIONS, OR
ASSOCIATED DOCUMENTATION WILL NOT INFRINGE ANY PATENTS, COPYRIGHTS,
TRADEMARKS OR OTHER INTELLECTUAL PROPERTY RIGHTS OF A THIRD PARTY.

Princeton University shall not be liable under any circumstances for


any direct, indirect, special, incidental, or consequential damages
with respect to any claim by USER or any third party on account of or
arising from the use, or inability to use, this software or its
associated documentation, even if Princeton University has been
advised of the possibility of those damages.
*********************************************************************

2
TABLE OF CONTENTS

TABLE OF CONTENTS .......................................................................................................................................... 3


INTRODUCTION.................................................................................................................................................. 4
ACKNOWLEDGEMENTS .....................................................................................................................................................4
SETUP ................................................................................................................................................................ 5
OBTAINING VIRMEN ......................................................................................................................................................5
SYSTEM REQUIREMENTS ...................................................................................................................................................5
INSTALLATION STEPS ........................................................................................................................................................5
VIRMEN FOLDERS ..........................................................................................................................................................5
UPDATING VIRMEN .......................................................................................................................................................6
TROUBLESHOOTING.........................................................................................................................................................6
GRAPHICAL USER INTERFACE (GUI)..................................................................................................................... 7
VIRMEN GUI BASICS ......................................................................................................................................................7
ENVIRONMENT (WORLD) MANIPULATIONS ...........................................................................................................................7
OBJECT MANIPULATIONS ..................................................................................................................................................8
TEXTURE MANIPULATIONS ..............................................................................................................................................11
VIEWING AND EXPORTING ..............................................................................................................................................16
CUSTOM VARIABLES ......................................................................................................................................................17
RUNNING THE WORLD ...................................................................................................................................................19
RUNTIME PROGRAMMING ................................................................................................................................22
VIRMEN ENGINE PIPELINE..............................................................................................................................................22
INTRODUCTION TO CODING VIRMEN EXPERIMENTS ............................................................................................................23
PROGRAMMING THE RUNTIME “VR” STRUCTURE .................................................................................................................25
TRANSFORMATION FUNCTIONS ........................................................................................................................................29
MOVEMENT FUNCTIONS ................................................................................................................................................30
REAL-TIME WORLD MANIPULATIONS .................................................................................................................................31
INPUT-OUTPUT METHODS...............................................................................................................................................34
FRAME GRABBING .........................................................................................................................................................35
OBJECT-ORIENTED PROGRAMMING ..................................................................................................................36
VIRMEN CLASSES .........................................................................................................................................................36
VIRMEN CLASS PROPERTIES ...........................................................................................................................................37
VIRMEN CLASS METHODS ..............................................................................................................................................42
CUSTOMIZING VIRMEN .....................................................................................................................................45
EDITING GUI LAYOUTS...................................................................................................................................................45
EDITING THE DEFAULT TEMPLATE CODE .............................................................................................................................46
CHANGING DEFAULT MOVEMENT AND TRANSFORMATION FUNCTIONS .....................................................................................46
CHANGING DEFAULT PROPERTY VALUES .............................................................................................................................46
CREATING NEW OBJECT TYPES .........................................................................................................................................47
CREATING NEW SHAPE TYPES...........................................................................................................................................48
VIRMEN MACROS ........................................................................................................................................................49
APPENDIX .........................................................................................................................................................53
DERIVATION OF A TRANSFORMATION FUNCTION .................................................................................................................53

3
INTRODUCTION

Welcome to ViRMEn – the Virtual Reality MATLAB Engine. ViRMEn is a MATLAB-based software package that
contains 3 components:

1. A graphical user interface (GUI) for interactively designing virtual environments (worlds).
2. An engine that performs 3D rendering of worlds on a computer monitor or projector.
3. An object-oriented toolbox for manipulating worlds programmatically outside of the GUI.

ViRMEn achieves fast graphics by directly accessing low-level OpenGL commands instead of using built-in MATLAB
functions. The engine also recognizes physical boundaries in virtual worlds and performs continuous-time collision
detection to manage the animal’s contact with these boundaries.

ViRMEn is designed to be highly customizable. The engine can apply arbitrary spatial transformations to virtual
worlds to display them on practically any type of screen (flat, conical, toroidal, etc.). The engine can also access any
type of an input device (such as an optical mouse or a rotary encoder) to obtain the animal’s movement
information. Finally, the engine runs custom user-written MATLAB code that can implement experiment logic,
reward delivery, data logging, etc.

ACKNOWLEDGEMENTS

ViRMEn was written by Dmitriy Aronov during his postdoctoral fellowship in David Tank’s lab at Princeton
University. We thank all members of the Tank lab for the initial inspiration, troubleshooting and suggestions.
Special thanks go to:

“ViRMEn” acronym: Andrew Miri


Beta testing and troubleshooting: Amina Kinkhabwala
GLFW routines and compilation: Eric Miller
Improved collision detection code: Sue Ann Koay
Funding to D.A.: Helen Hay Whitney Fellowship, NINDS K99 Award

4
SETUP

OBTAINING VIRMEN

ViRMEn is freely available for download from the Princeton University website virmen.princeton.edu. Please email
Dmitriy Aronov at [email protected] with any questions, reports of problems/bugs, or suggestions; these
are greatly appreciated. ViRMEn has been described in a publication and should be cited as follows

Aronov, D. and Tank, D. W. (2014) Engagement of Neural Circuits Underlying 2D Spatial Navigation in a
Rodent Virtual Reality System. Neuron 84(2): 442-56.

SYSTEM REQUIREMENTS

- Operating system: ViRMEn runs on either Windows or Mac operating systems.


- MATLAB: ViRMEn has been successfully tested on MATLAB 2010 through 2015, in both 32 and 64-bit
versions. If you plan to interface with a NI-DAQ card, consider the differences in the Data Acquisition
Toolbox between 32 and 64-bit versions (see Matlab help). 32-bit MATLAB can be installed on a 64-
bit machine.
- Graphics card: Although ViRMEn will run on any computer, achieving a fast refresh rate requires a
good graphics card, especially for very large environments. One graphics card we successfully used is
NVIDIA Quadro 4000, but fast graphics are also produced by many newer computers (even laptops).
Worse graphics cards might produce “jerky” movement graphics.

INSTALLATION STEPS

- Place the virmen folder anywhere on your computer.


- Add the virmen folder, with subfolders, to the MATLAB path.
- Run >> virmen at the command line in MATLAB.

VIRMEN FOLDERS

The ViRMEn folder contains the following subfolders.

- bin: contains all files related to the engine, the GUI and the documentation. Nothing in this folder
should ever be changed by the user.
- defaults: contains files that store various user preferences for the GUI. See customizing ViRMEn.
- experiments: contains experiments created by the user. Each experiment is defined by a .mat file and
a .m file. The .mat file contains a variable exper, which stores the experiment itself. The .m file
contains all custom MATLAB code used by the experiment.
- macros: contains custom user-written routines that can be run within the GUI. See ViRMEn macros.
- movements: contains all functions that provide information about the animal’s movement in the
virtual world. See movement functions.

5
- objects: contains all definitions of 3D objects recognized by ViRMEn. See object types.
- shapes: contains all definitions of 2D shapes recognized by ViRMEn that are used to create textures
for objects. See shape types.
- transformations: contains all transformation functions that can be used to transform a 3D
representation of a world into a 2D projection onto a virtual reality screen. See transformation
functions.

UPDATING VIRMEN

New versions of ViRMEn are placed on the website virmen.princeton.edu. The versions are dated, and the date of
your current version can be checked by pressing on the About ViRMEn button within the GUI. To update ViRMEn to
a newer version, replace only the “bin” subfolder. Other subfolders may contain files that you created for your
experiments and which you might not want to be overwritten.

TROUBLESHOOTING

Problem Try solution


Any errors that appear after updating Most likely, MATLAB needs to update class definitions. Restart
ViRMEn to a more recent version. MATLAB and run ViRMEn again. Alternatively, you can run this at
command line without restarting:

>> clear classes

Then run ViRMEn again.

The ViRMEn engine freezes First, make sure the freezing is not caused by a problem in the user-
defined code, such as an infinite while loop. If you’re sure, try deleting
any antivirus program from your computer.

6
GRAPHICAL USER INTERFACE (GUI)

The following sections will take you through a series of steps to acquaint you with the ViRMEn graphical user
interface (GUI). At this point, we will only be creating worlds, with no coding. The next section of runtime
programming will begin to add custom code to your experiments.

Unless otherwise indicated, all buttons and dropdown menus you will be asked to press are located on the toolbar
of the GUI. Each button’s command is also available in the GUI menus. Not all buttons are available at all times –
make sure the correct window in the GUI is activated first by clicking on it.

VIRMEN GUI BASICS

STARTING VIRMEN

- Run >> virmen at the command line in MATLAB. The ViRMEn GUI will start.

CHANGING LAYOUTS

- The GUI will start in the Experiment layout, which is used to edit general properties of your
experiment. The layout contains three windows: Custom variables, Experiment properties and Worlds
menu. These will all be explained later. One of the windows is active and is indicated by a red border.
Click inside different windows to make them active instead.
- Press the World layout button. The GUI will switch to the World layout, which contains three
different windows. This layout is used to place objects into your world and arrange them in virtual
space.
- Press the Texture layout button. The GUI will switch to the Texture layout and display three more
windows. This layout is used to edit the textures of the objects in your worlds.
- Press the Experiment layout button. You are now back in the Experiment layout.
- Experiment, World and Texture are the three built-in layouts of the ViRMEn GUI. You can, however,
change the arrangement of windows in each of these layouts. You can even create additional layouts
that combine windows in the way you like. See customizing layouts.

UNDO AND REDO

- You can undo or redo any changes you will make to your experiment from now on. The GUI stores in
history the last 100 changes you’ve made. Press the Undo or Redo buttons.

HELP

- Press the ViRMEn manual button to open this manual.

ENVIRONMENT (WORLD) MANIPULATIONS

7
ADDING AND DELETING WORLDS

- Go the Experiment layout of the GUI and select the Worlds menu window by clicking on it. The
Worlds menu shows thumbnails of all environments (called “worlds”) used by your experiment. If you
just started, your experiment contains a single world, which is blank except for a square-and-arrow
symbol. This symbol shows the location where the animal is initially placed when the world is loaded.
- To add more worlds, press the Add world button. More blank worlds will appear in the worlds menu.
- To delete a world, press on the thumbnail to select it. Then press the Delete world button or the
Delete keyboard key.

NAMING A WORLD

- Whenever you add a new world to your experiment, it is automatically given the name virmenWorld.
When multiple worlds get the same name, they are indexed virmenWorld(1), virmenWorld(2), etc.
You may wish to give some worlds custom names. To do so, click on the world thumbnail to select it
and then press the Rename world button. You can also click on the world name above the thumbnail.
Enter the new world name, which must be a valid MATLAB variable name.

IMPORTING OR DUPLICATING A WORLD

- You can import an entire world that you’ve previously designed into the current experiment. Press
the Import world button. A window will open, giving you the list of all previously created
experiments.
- Click on an experiment name. The window will display thumbnails of all worlds in that experiment.
Click on a thumbnail to import that world into the current experiment.
- Note that the list includes the current experiment. Importing a world from the current experiment
will duplicate it. This is useful for designing multiple worlds that are largely the same without having
to repeat the entire design process.

OBJECT MANIPULATIONS

DRAWING AN OBJECT

- You are now ready to design a world. To do so, select the world you want to change in the Worlds
menu and press the World layout button. Alternatively, you can press Edit under the thumbnail of the
world, double-click the world icon, or select a world and press Enter.
- Click on the World sketch window, which contains a 2D sketch of your world. If your world is blank, it
will only contain a square-and-arrow symbol indicating the starting location of the animal. The
window will become active, as indicated by a red border.
- In the Add object dropdown menu, select the object type you wish to add. Let’s start with a
VerticalCylinder. On the world sketch, click locations where you wish to add cylinders. Let’s start with
2 locations. When you’re done adding locations, double-click or press Enter.
- The World sketch window shows a top view of your world. If you’ve added two cylinders, the world
sketch shows two circles.

8
- Note that the cylinders do not appear in the World drawing menu, which is supposed to show the 3D
view of your world. This is because your object does not yet have a texture and is therefore invisible.
- Note that the two cylinders you’ve added are treated as a single object that happens to have multiple
locations. This means that any properties you change, such as the radius or texture, will apply to both
cylinders. If you wish to have two cylinders that are different, you must add two separate objects
containing one location each.
- In addition to using object types available in the Add object dropdown menu, you can create your
own object types. See custom object types.

ZOOMING IN AND OUT

- You can zoom in on a part of the world by left-clicking in the World sketch window and dragging a
rectangle over the region of interest.
- To zoom out, right-click within the World sketch window.
- To return axes to the automatically determined x and y limits, hold the Shift key and left-click in the
World sketch window.
- You can change the default x and y limits of the world sketch by editing the default properties file.

SELECTING AN OBJECT

- You can select an object in one of three ways: 1) By clicking on it in the World sketch window, 2) By
clicking on it in the 3D wireframe drawing or 3) by choosing it from the dropdown menu in the
Object/world properties window. The selected object is colored red on the world sketch.
- You can select the initial location of the animal by clicking on the square-and-arrow symbol or by
choosing “Initial conditions” from the dropdown menu. This will allow you to change the animal’s
starting location and view angle.

NAMING AN OBJECT AND ITS TEXTURE

- New objects are given default names according to their type, such objectVerticalCylinder. Objects
that have the same name are indexed, such as objectVerticalCylinder(1), objectVerticalCylinder(2),
etc. The objects’ textures are similarly named virmenTexture(1), virmenTexture(2), etc. For
programming purposes, it is good to give meaningful names to your objects. To rename an object
and/or its texture, select it and press the Rename object button (or the Rename button next to the
dropdown list or the name of the texture above its image). The name must be a valid MATLAB
variable name.

DELETING AN OBJECT

- To delete an object, select it and press the Delete object button or the Delete keyboard key.

SORTING OBJECTS

9
- ViRMEn lists objects in the order in which they were created. To re-organize object names into any
order you wish, press the Sort objects button. A window will pop up, allowing you to move individual
object names up or down, or to sort object names alphabetically.

CHANGING OBJECT PROPERTIES

- Each object type has properties associated with it. The properties are listed in a table in the
Object/world properties window and can be modified after the object has been created. For example,
try changing the radius of the cylinders you’ve created.

CHANGING THE ANIMAL’S STARTING LOCATION

- The animal’s starting location (indicated by the square-and-arrow symbol) is treated as an object has
four properties associated with it: X, Y, Z, and rotation. These are the starting location and view angle
of the animal in the world (i.e., where the animal is placed when the world is loaded during an
experiment). You can edit these properties by selecting the animal’s location and changing values in
the table in the Object/world properties window. Note that different worlds in the same experiment
can have different starting locations.
- By default, the location is set to (0, 0, 0) and the rotation is set to 0. View angle is offset by π/2
radians from the rotation value, such that the rotation of 0 corresponds to facing “north.” You can
change the default location and rotation by editing the default properties file.

CHANGING OBJECT LOCATIONS

- In addition to other properties, each object has locations associated with it. These locations can have
different meanings for different object types. For instance, for cylinders each location indicates an
instance of a cylinder (e.g., two locations = two cylinders). For a wall, locations indicate endpoints and
corners (e.g., four locations = single wall with two corners).
- Locations are listed in a table in the Object/world properties window. You can manually modify
individual locations by changing values in this table.
- You can also move an object by drag-and-dropping it with the left mouse button. To move only one of
the object’s locations rather than the entire object, use Shift+Left mouse button.

ADDING AND DELETING OBJECT LOCATIONS

- To add a location, make sure that the Object/world properties window is active by clicking on it. Then
press the Add object location(s) button. A new row will be added to the table of locations.
- You can also add an object location by holding the Ctrl key and drag-and-dropping an existing
location.
- To delete a location, select the row(s) of the table you wish to delete. Then press the Delete object
location(s) button.

ENTERING A SYMBOLIC LOCATION FOR AN OBJECT

10
- Suppose you want to create a row of 9 cylinders, spaced at 25 units from -100 to 100 along the x-axis.
Rather than entering each location manually, you can enter a MATLAB expression to create cylinders
in bulk. To do so, press the Symbolic object location(s) button (or the Symbolic button next to the
table of locations). Enter -100:25:100 in the first box; this will generate x values from -100 to 100. You
can enter an array of the same size into the second box, but since all of the y values for the 9
cylinders are the same, simply enter 0.

IMPORTING OR DUPLICATING AN OBJECT

- You can import an object that you’ve previously created. To do so, press the Import object button. A
window will appear, giving you the list of all previously created experiment names.
- Select the experiment from which you wish to import an object. Images of all available objects will
appear as thumbnails. Click on the thumbnail of the object you wish to import.
- Click on the location(s) where you wish to place the imported object. When you’re done, double-click
or press Enter.
- Note that the list includes the current experiment. Importing an object from the current experiment
will duplicate it.

TEXTURE MANIPULATIONS

DRAWING A SHAPE

- Now that you’ve created at least one object, you can create a texture for it. Select the object and
press the Texture layout button (you can also double-click the object, click the texture image, or
select an object and press Enter).
- If you’re working with a newly created object, you will see a blank square in the Texture sketch
window. This is the boundary of the texture. The Texture drawing window shows the triangulation of
your texture. A blank texture is divided into two triangles, neither of which is visible.
- Activate the Texture sketch window by clicking on it. In the Add shape dropdown menu, select the
shape type you wish to add. Let’s start with a Rectangle. On the texture sketch, draw the rectangle
somewhere in the middle of the texture.
- Note that the rectangle shape you’ve drawn does not change the triangulation of your texture. This is
because you haven’t yet computed the texture.
- In addition to using shape types available in the Add shape dropdown menu, you can create your own
shape types. See custom shape types.

ADDING A COLOR TO A TEXTURE

- By drawing a rectangle, you’ve divided the texture into two regions: the inside and the outside of the
rectangle.
- To add a color to one of the regions, make sure the Texture sketch window is activated. Then click the
Add color button. In the Texture sketch window, click inside all regions where you wish to add a new
color. When you’re done, double-click or press Enter.

11
- A menu will ask you to choose a color for the regions you selected. Note that if you clicked inside
several regions, all of those regions must now have the same color. To assign different colors to
different regions, you must add colors separately.
- You don’t have to color each region of the texture. If you leave a region uncolored, ViRMEn will still
triangulate it, but the triangles will be invisible and not included in the final rendering of the world.
This is a good way to reduce the number of triangles in your world: for instance, if you have a black
wall with white circles on it, and the background of your world is black, you can save on triangles by
only coloring the circles and leaving the wall itself invisible.

CHANGING COLOR OR TRANSPARENCY

- Once a color has been added to a texture, you can change it by selecting it and modifying the RGB
values in the Shape properties table. You can also press the Set color button, double-click on the
color, or select a color and press Enter to choose a color interactively.
- To change the transparency value of a color, modify the Alpha property in the Shape properties table.
By default, all Alpha values are set to NaN, meaning that transparency is disabled, and all the colors
are completely opaque. You can set the Alpha value to anything from 0 (completely transparent) to 1
(completely opaque). Values of NaN and 1 are equivalent.
- Transparent colors in a world will only be displayed if the transparency property for the world turned
on. If the transparency property is turned off, all colors will be displayed as opaque, even if their
Alpha values are <1.

ZOOMING IN AND OUT

- Zooming in and out on parts of the texture work the same way as on the World sketch. You can zoom
either on the Texture sketch window or on the Texture drawing window. Both images of the texture
will automatically change axis limits together.

SELECTING A SHAPE OR COLOR

- Selecting shapes and colors works the same way as selecting objects.

NAMING A SHAPE OR COLOR

- Naming shapes works the same way as naming objects using the Rename shape button.

DELETING A SHAPE OR COLOR

- Deleting shapes and colors works the same way as deleting objects using the Delete shape button or
the Delete keyboard key.

SORTING SHAPES AND COLORS

- Sorting the names of shapes and colors works the same as sorting objects using the Sort shapes
button.

12
CHANGING SHAPE OR COLOR PROPERTIES

- Changing shape and color properties works the same way as changing object properties.

CHANGING SHAPE OR COLOR LOCATIONS

- Changing shape or color locations works the same as changing object locations using the table in the
Shape properties window or drag-and-dropping.

ADDING AND DELETING SHAPE OR COLOR LOCATIONS

- Adding and deleting locations of shapes and colors works the same way as for objects, using the Add
shape location(s) and Delete shape location(s) buttons by drag-and-dropping.

ENTERING A SYMBOLIC LOCATION FOR A SHAPE OR COLOR

- You can enter a symbolic expression for the location of shapes the same way as you would for
objects, using the Symbolic shape location(s) button.

COMPUTING A TEXTURE

- Once you sketch a texture and assign colors to all regions, the next step is to triangulate it. This will
convert the sketch of the texture into a set of triangles that will be displayed by the ViRMEn engine.
To triangulate, press the Compute texture button.
- The computed triangulation will be displayed in the Texture drawing window. Temporarily switch to
the World layout to see that the newly computed texture has been applied to your object.

DISPLAYING AND COLORING THE TRIANGULATION MESH

- By default, boundaries of the triangles (mesh) in the computed texture are shown. You can turn
on/off the visibility of the mesh by pressing the Show/hide triangulation button.
- You can also change the color of the displayed mesh by pressing the Triangulation mesh color button
and choosing a color.
- Note that the triangulation mesh is only displayed in the GUI for design purposes. The ViRMEn engine
does not display the mesh during the experiment, and it is therefore invisible to the animal when the
experiment is running.
- By default, mesh is visible and colored red. You can change these defaults by editing the default
properties file.

REFINING TEXTURE TRIANGULATION

- The ViRMEn triangulation algorithm attempts to split the texture into the smallest possible number of
triangles. This often creates triangles that are too large and cause problems with the way objects look
(the exact potential problems are discussed below). To solve these problems, you can refine

13
triangulation – i.e., split a texture into a larger number of smaller triangles. Press the Refine
triangulation button. This will open a GUI that allows you to change several triangulation parameters.
- Refining increases the number of triangles used by your world. Unfortunately, this is the most
important determinant of how quickly the ViRMEn engine will refresh graphics. When refining, you
will need to find a compromise between a high-quality look of your objects and smooth running of
the engine. The number of triangles used by a texture is displayed by the refining GUI; the total
number of triangles used by a world is displayed above the rendering of the world in the World view
window. (Note that, in the wireframe mode, the world is not rendered by the GUI and the number of
triangles is not computed).
- The refining parameters are listed below. By default, edge refining parameters are both set to 1, grid
size is set to 1x1, and tilability along both dimensions are turned on. You can change these defaults by
editing the default properties file.

Refining parameters

o Edge refining
An “edge” is a line segment that is part of a boundary between two differently-colored
regions of a texture. (For example, if the texture consists of a white square on gray
background, each side of the square is an edge). If you are using a flat screen for virtual
reality, and the edge is on a flat object (like a wall or floor) it is sufficient to use only two
points to represent each edge. However, on a curved object (like a cylinder) and/or curved
screen (such as a toroidal one), a line segment must actually be projected as a curve. In
order for curvature to be represented smoothly, each edge needs to be broken into a
sufficiently large number of small line segments. (An edge that is too large will appear
“scalloped”). Vertical and horizontal edge refining are parameters specifying the maximum
allowed size of a line segment (as a fraction of the entire texture, between 0 and 1). Any
edge exceeding this size will be broken up by the ViRMEn triangulation algorithm.

o Grid size
Even if all edges are well-refined, the triangulation of a large single-color region can contain
large triangles. This is fine if the object that carries with texture is flat, such as wall or a floor.
However, if the object is curved, large triangles will cause the shape of the surface to be
distorted; a cylinder, for example, will appear “boxy”. To solve this problem, ViRMEn can
break up the texture into a rectangular grid and triangulate each rectangle separately; this is
controlled by the vertical and horizontal grid parameters. The grid prevents any single
triangle from being too large. Increase the size of the grid along the dimensions that matter
for object curvature. For example, for a vertical cylinder, only the horizontal grid size is
relevant.

o “Tilable” option
By default, ViRMEn assumes that you will be tiling the texture both vertically and
horizontally on the surface of an object. It therefore ensures that every triangle vertex
located on the top edge of the texture matches a vertex in the corresponding location on the
bottom edge. Similarly, it matches the locations of vertices on the left and right edges of the
texture. If you don’t plan to tile the texture along one (or either) of the directions, you can
turn tilability off using the refining GUI. This sometimes reduces the number of triangles

14
used by the texture. Note that if you tile a texture on the surface of a curved object but turn
tilability off, the object may contain visible gaps at locations where tiles meet.

IMPORTING A TEXTURE

- You can import a texture that you’ve previously created. To do so, press the Import texture button. A
window will appear, giving you the list of all previously created experiment names.
- Select the experiment from which you wish to import a texture. Images of all available textures will
appear. Click on the texture you wish to import.

LOADING A TEXTURE IMAGE FROM A FILE

- To load a texture from an image file, press the Load image button and select a file. ViRMEn will load
an image and convert it into a ViRMEn texture. Press the Compute texture button to triangulate the
texture, as usual.
- ViRMEn represents each pixel of the image using two triangles. This can substantially increase the
number of triangles in the world and slow down the engine. For simple patterns, it is more efficient to
draw textures from scratch using the GUI.
- Images are not refined the same way as other ViRMEn textures. Rather, triangulation of an image can
be changed by increasing or decreasing the number of pixels in the image using the width and height
properties in the Shape properties table. Modify the value of the “interpolate” property (0 or 1) to
determine whether image colors get interpolated when the number of pixels is changed. After
changing these properties, press the Compute texture button to update the triangulation.
- You can modify the brightness, contrast, or saturation of an image using the corresponding properties
in the Shape properties table. These values are percentages above or below zero (e.g. -20 or +30).
After changing these properties, re-compute the texture to update the triangulation.
- You can modify the transparency of an image using the Alpha property in the Shape properties table.
The value is from 0 to 1, or NaN if the shape is not opaque (1 is equivalent to NaN). Make sure that
the transparency property of the world is turned on in order to see transparent objects.

USING THE SAME TEXTURE ON MULTIPLE OBJECTS

- When you import a texture, the list of experiment names includes the current experiment. Importing
a texture from the current experiment will duplicate it. Use this to apply a texture of one object to
another object in the same experiment.
- Whenever you change an object’s texture, ViRMEn identifies other objects that had identical textures
prior to the change. If such objects exist, a menu will pop up, allowing you to select any subset of
these objects whose textures should also change. Use this to simultaneously change the textures of
several objects.

TILING TEXTURE ON AN OBJECT

- At this point, switch to the World layout of the GUI to visualize the entire object you’ve applied the
texture to. In the Object/world properties window, there are two textboxes for the amount of

15
vertical tiling and horizontal tiling. These numbers indicate how many times the texture is repeated
on the surface of the object. Change these numbers and observe the effect on your object.
- If you change the size of the object (e.g., by increasing the height or radius of the cylinder), the
texture will stretch with it. For example, if vertical tiling is set at 5, each tile will take up 1/5 of the
height of the cylinder, independently of the height. It is, however, possible to have the texture not
scale with the object by using custom variables.
- By default, both vertical and horizontal tiling values are set to 5. You can change these defaults by
editing the default properties file.

VIEWING AND EXPORTING

CHANGING THE WORLD BACKGROUND COLOR

- To change the background color of a world, activate the World drawing window and press the World
background color button. The color you select will be used as background both in the GUI and during
the actual experiment.
- Alternatively, you can select the “Initial conditions” from the dropdown menu in the Object/world
properties window and enter expressions for the R, G, and B components of the background color
using the backgroundR, backgroundG, and backgroundB properties listed in the table.
- By default, world background is set to black. You can change the default by editing the default
properties file.

CHANGING THE WORLD TRANSPARENCY

- Each world has a transparency property that can be turned on or off. This property determines
whether the ViRMEn engine will activate graphics options for displaying transparent colors. If the
transparency property for a world is turned off, all colors will be displayed as opaque, even if the
Alpha property for some colors is <1. Transparency graphics are time consuming for the ViRMEn
engine. Therefore, if all colors within a world are opaque (and will remain opaque throughout the
experiment) the transparency property should be turned off.
- To change the transparency of a world, activate the World drawing window and use the World
transparency on/off toggle button. The transparency option you choose will be used both in the GUI
and during the actual experiment.
- Alternatively, you can select the “Initial conditions” from the dropdown menu in the Object/world
properties window and enter an expression (evaluating to 0 or 1) for the enableTransparency
property listed in the table.
- By default, world transparency is turned off. You can change the default by editing the default
properties file.

VIEWING THE WORLD WIREFRAME

- Instead of plotting full 3D rendering in the World drawing window, you can plot a wireframe
rendering. To switch between full rendering and wireframe, using the Wireframe on/off toggle
button.

16
- In the wireframe mode, you can select individual objects by clicking on them in the 3D view. This is
useful for selecting objects that are on top on one another and therefore cannot be easily selected in
the 2D view.
- Another advantage of the wireframe is that it plots faster, which is useful for working with large
worlds in the GUI.
- By default, the wireframe mode is turned off and the full 3D rendering is used. You can change the
default by editing the default properties file.

VIEWING AND ROTATING THE WORLD IN 3D

- You can interactively rotate the image of the world by pressing the 3D world view button. An image
of your world will take up the entire figure and the MATLAB rotation tool will be activated.
- You can also change the 3D view of your world by selecting the World drawing window and using one
of several buttons on the toolbar: Isometric view, Top view, Front view, and Side view buttons switch
the view to a corresponding angle; Rotate counterclockwise, Rotate clockwise, Rotate down, and
Rotate up buttons rotate the view by 15°. These rotations can also be achieved using the keyboard
arrow keys.

EXPORTING IMAGES OF WORLDS AND TEXTURES

- You can export images to external MATLAB figures. This is useful for creating figures and
presentations. Press on the Export dropdown menu. You have the choice of exporting a 2D sketch of
the world, the full 3D rendering of the world, the wireframe of the world, or an image of the current
object’s texture.

CUSTOM VARIABLES

The properties and locations of objects and shapes in ViRMEn don’t have to be specified as numbers, but can be
defined in terms of custom variables. Furthermore, ViRMEn will recognize any MATLAB expression in these
definitions. This is a powerful tool that you can use to allow fast and seamless changes to your worlds. The
following examples show you how to use variables and suggest possible applications.

EXAMPLE: FIXING THE ASPECT RATIO OF AN OBJECT

- Add a Floor object to your world. Change the location to (0, 0). In the table in the Object/world
properties window, change the width to 100 and the height to 200.
- Now, suppose you want to change the size of the object you’ve created, but you want to keep its
height:width ratio at 2:1. Rather than changing both the width and the height each time, we will
define both dimensions in terms of a variable we will call floorWidth.
- In the table in the Object/world properties window, for the value of the width, enter floorWidth.
ViRMEn will recognize it as a new variable and prompt you for its value. Enter 100.
- In the table in the Object/world properties window, for the value of the height, enter 2*floorWidth.

17
- Press the Variables table button. A table will pop up, containing the variable floorWidth and its value.
Change the value and observe the change to the object. The object should scale without a change in
aspect ratio.

EXAMPLE: LOCKING OBJECTS TO EACH ANOTHER

- Custom variables can be used to link multiple objects. After completing the previous example, add
vertical cylinder to the world.
- Suppose you want the cylinder to always be located at the corner of the floor. In the Locations table,
change the x location to floorWidth/2 and the y location to –floorWidth. The cylinder will be placed in
the bottom-right corner of the floor.
- Open the variables table by pressing the Variables table button. Change the value of floorWidth and
confirm that the cylinder is locked to the floor.

EXAMPLE: SCALE-FREE TILING OF A TEXTURE

- Add a floor object to your world and define both its width and height as 100. Now assign any texture
to this object and make sure that both the vertical and the horizontal tiling are set to 5. The texture
should be tiled 25 times in a square.
- Now change the floor width to 200. The texture remains tiled 25 times, but stretches horizontally.
This may not be the behavior you want. You might want the individual texture tiles to remain square,
but the total number of these squares to change.
- If order to achieve scale-free tiling, enter floorWidth for the width of the floor. ViRMEn will recognize
floorWidth as a new variable and prompt you for its value. Enter 100.
- For horizontal tiling, enter floorWidth/20. The texture will be tiled 5 times horizontally.
- Press the Variables table button. A table will pop up, containing the variable floorWidth and its value.
Change the value to 200. As the floor expands, ViRMEn should add additional texture tiles to it
instead of stretching tiles.

INSERTING AND DELETING VARIABLES

- Press the Experiment layout button to switch to the Experiment layout. The Custom variables table
contains the list of all variables you added to your world. Note that if you imported anything into your
current experiment from other experiments, you might have imported some associated variables as
well.
- To delete variables you no longer need, select the corresponding row(s) in the table and press the
Delete variable(s) button. If you delete a variable that’s still in use by the experiment, ViRMEn will
prompt you for its value and restore it to the experiment.
- To add a new variable, press the Add variable button. You can enter variables that are not used by
any object in your world, but are rather parameters you will later use in your code. This way, when
you run an experiment, you can change relevant parameters using the table, without having to edit
the MATLAB code each time.

RENAMING VARIABLES

18
- To rename variable, select the corresponding row(s) in the variables table and press the Rename
variable(s) button. Enter new names for the variables. ViRMEn will replace the name of the variable
in every expression within the experiment that uses it.
- Note that ViRMEn will not replace variable names within your code. You must do this manually if you
are accessing the value of a variable in your MATLAB code.

SORTING VARIABLES

- ViRMEn lists custom variables in the order in which they were created. To re-organize variable names
into any order you wish, press the Sort variables button. A window will pop up, allowing you to move
individual variable names up or down, or to sort variable names alphabetically.

RUNNING THE WORLD

At this point, you should have enough knowledge of the interface to design and draw a world. We will now learn
how to run this world using the ViRMEn engine.

TRANSFORMATION AND MOVEMENT FUNCTIONS

- Go to the Experiment layout of the GUI. The Experiment properties window in this layout contains
dropdown lists that allow you to select external functions to be used by your experiment.
- Select a movement function from one of the dropdown lists. A movement function is used by the
ViRMEn engine to obtain the animal’s movement information. For initial testing, chose a movement
function that allows you to control movement using the computer mouse or keyboard. See
movement functions for details.
- Select a transformation function from another dropdown list. The transformation function is specific
to the geometry of the projection you’re using for the virtual reality. See transformation functions for
details.
- You can open the movement or transformation function in the MATLAB editor by choosing
appropriate options from the Edit code dropdown menu, or by pressing the Edit buttons next to the
dropdown lists in the Experiment properties window.
- When you create a new experiment using the ViRMEn GUI, dropdown menus will be set to the
default transformation and movement functions. You can change these defaults to determine which
transformation and movement functions are automatically selected on your experimental setup.

WINDOWS MANAGER

- ViRMEn can display graphics in multiple windows. This is useful for virtual reality systems that use
multiple monitors. Alternatively, this feature can be used for convenience – for example to display
graphics to the animal on one monitor, but different graphics or indicators to the experimenter on
the other monitor.
- To organize windows used by ViRMEn, press the Windows manager button on the toolbar or in the
Experiment properties window. A table listing all windows will pop-up. Buttons below the table allow
adding, deleting, and reordering the windows.

19
- To determine what is being displayed in a given window, change the “Function #” table column for
that window. This parameter sets the index of the transformation function that is used for the
window. If the experiment’s transformation function performs only one transformation, the value in
the table should be set to 1. For transformation functions that return multiple outputs, the value
indicates which output to use. If the window is not used to display 3D world rendering, uncheck the
“3D graphics” checkbox; the “Function #” column in this case is ignored. A window without 3D
graphics can still be used to display textboxes or line plots.
- To set the monitor used to display a given window, change the “Monitor #” table column. To use the
default primary monitor, check the “Main monitor” checkbox; the “Monitor #” column in this case is
ignored. If the specified monitor number exceeds the total number of available monitors, the window
will be displayed in the main monitor.
- To set the position of the window (in pixels relative to the bottom-left corner of the monitor), change
the “Left” and “Bottom” table columns. To set the size of the window (in pixels), change the “Width”
and “Height” columns. To make the window full-screen, check the “Full screen” checkbox; the
position and size set with the other columns in this case are ignored.
- To set the level of antialiasing for a given window, change the “Antialiasing” table column.
Antialiasing is implemented by the graphics card using “multisampling” – rendering of the world
multiple times, each time jittering all coordinates by a sub-pixel amount. The number in the table
indicates the number of samples to use, with 0 indicating no antialiasing. Try different numbers, (for
example, between 2 and 16). Antialiasing will improve graphics, but may slow down the refresh rate
for large environments.
- By default, the experiment is displayed in a single window that occupies the full screen of the primary
monitor. You can change the defaults by editing the default properties file.

SAVING AN EXPERIMENT

- To save an experiment, press the Save experiment button. You will be prompted for the name of a
.mat file to save the experiment to. Place this .mat file in the “experiments” subfolder of ViRMEn. The
.mat file contains a single variable exper, containing all information about the worlds, objects,
textures, etc. in your experiment.
- When you save an experiment, ViRMEn also creates a blank template .m file that will contain all of
the custom MATLAB code that you write for your experiment. You can edit this file by choosing
“Experiment code” from the Edit code dropdown menu.

OPENING OR CREATING A NEW EXPERIMENT

- To open a previously created experiment, press the Open experiment button.


- To erase the current experiment and start over with a blank experiment, press the New experiment
button.
- When you open a .mat experiment file, ViRMEn attempts to find the .m file that contains all
associated MATLAB code. If this file cannot be found, ViRMEn creates it and populates it with code
recovered from the codeText property. This provides a convenient way to share experiments with
other people – you only need to share a single .mat file.

RUNNING AN EXPERIMENT

20
- To run your experiment, press the Run button. ViRMEn will start the engine.
- To stop the engine, press Esc or click the cancel button in the upper-right corner of the screen (the
button auto-hides when you move the mouse cursor away). You can also stop the engine at a desired
time using the vr.experimentEnded field in MATLAB code you write.

ADDING PHYSICAL EDGES TO OBJECTS

- Create a world with at least one object and run the engine. Move around the world to get a feel for
how the display updates. Now try running into one of the objects. You should be able to run through
the object. This is because ViRMEn by default does not treat objects as physical entities. To create a
physical boundary around the object, you will need to use edges.
- To add an edge to an object, go to the World layout and select one of the objects in the world. In the
Object/world properties window, find a textbox labeled “Edge radius.” By default, the edge radius
value is NaN, indicating that the object does not have an edge. Change this value to 5. You will see
that a dotted line appears around the object. This line indicates an edge that cannot be crossed by
the animal. The ViRMEn engine performs continuous-time collision detection (CTCD). If an animal’s
displacement vector attempts to cross one of the edges, the vector is projected onto the edge,
causing the animal to “skid” along the edge. (You can, however, control the skidding behavior with
code).
- Play around with the edge radii. Then run the engine again and attempt to run through one of the
objects.
- By default, object edges are turned off (i.e., radii are set to NaN). You can instead set a default radius
value by editing the default properties file.

21
RUNTIME PROGRAMMING

Runtime programming refers to all custom code you write that will be executed while the experiment is running.
This section walks you through code organization and some basic examples.

VIRMEN ENGINE PIPELINE

Read movement
inputs

Execute Switch worlds, Calculate velocity Detect


initialization code if necessary and displacement collisions

Execute Render Update Update


termination code environment position displacement

Transform Execute
world runtime code

Diagram of the steps executed by the ViRMEn engine. Routines in the red
dotted box are part of the ViRMEn engine itself. The five routines outside of this
box are not part of the engine and can be customized by the user.

To begin writing custom code for ViRMEn, it is important to understand what exactly ViRMEn does and in which
order. The diagram above illustrates the operation of the ViRMEn engine. Here is a brief explanation of each of the
steps.

1. Execute initialization code. The initialization code is a custom function written by the user. It can
contain routines like initialization of a DAQ, initialization of data log files, initialization of custom
variables that will be used during the experiment, etc. It is run once before the engine starts.
2. Switch world, if necessary. If the world has been switched during the last engine iteration (by
changing the value of vr.currentWorld), or if no world has been loaded yet, ViRMEn loads the world
indicated by vr.currentWorld into memory.
3. Read movement inputs. ViRMEn runs an external movement function, which is custom-written by
the user and provides ViRMEn with information about the animal’s movement. Depending on how
the user writes this function, movement information can be either velocity, displacement, or a new
position value. This information can obtain those values from any input device, such as a DAQ, a
mouse, or a keyboard.
4. Calculate velocity and displacement. If the movement function (see 3 above) outputs velocity, then
ViRMEn, calculates displacement by multiplying velocity values by the amount of time elapsed since
the last iteration. If the movement function specifies displacement, then ViRMEn calculates velocity
by dividing displacement by elapsed time. If the movement function outputs a new position value,

22
then displacement is calculated by subtracting the current position value from the new position, and
velocity is calculated by dividing by elapsed time. In any of these cases, the fields vr.velocity and vr.dp
are updated.
5. Detect collisions. ViRMEn runs continuous-time collision detection (CTCD) to determine if the animal
is currently colliding with any of the physical edges in the world. If the user-written movement
function (see 3 above) outputs a position value (rather than displacement or velocity), then this step
is skipped. The field vr.collision is updated.
6. Update displacement. If a collision between an animal and a physical edge is detected, ViRMEn
corrects the displacement vector vr.dp by projecting it onto the physical edge. This causes the animal
to “skid” along edges.
7. Execute runtime code. The runtime code is a custom function written by the user. This function
should contain most of the routines related to the experiment, such as all the experiment logic,
triggering of rewards, logging data to a file, writing outputs to DAQ, teleporting, real-time world
manipulations, etc.
8. Update position. ViRMEn updates the animal’s position by adding the displacement to it. The field
vr.position is updated.
9. Transform world. ViRMEn transforms the 3D virtual world coordinates into 2D coordinates that will
be displayed on the computer monitor or projector. The transformation is a function written by the
user and is customized to the particular screen shape and projection geometry used.
10. Render world. ViRMEn renders the world using OpenGL graphics commands.
11. Execute termination code. If the value of vr.experimentEnded is set to true, or if the user has pressed
the Esc button or clicked the cancel button, the engine run is terminated. Termination code is a
custom function written by the user. If can include routines like termination of a DAQ, closing of files,
displaying summary information about the experiment, etc.

INTRODUCTION TO CODING VIRMEN EXPERIMENTS

For each experiment, you need to write three MATLAB functions: initialization function, runtime function, and
termination function. Initialization and termination functions are run once each – before the ViRMEn engine starts
running and after the ViRMEn engine stops running. The runtime function runs once on each iteration of the
engine. See pipeline for more details.

For convenience, all three functions are stored in a single .m file. This file is automatically generated whenever you
save a new experiment with the ViRMEn GUI. A new blank template file has the following structure.

function code = sampleCode


% sampleCode Code for the ViRMEn experiment sampleCode.
% code = sampleCode Returns handles to the functions that ViRMEn
% executes during engine initialization, runtime and termination.

% Begin header code - DO NOT EDIT


code.initialization = @initializationCodeFun;
code.runtime = @runtimeCodeFun;
code.termination = @terminationCodeFun;
% End header code - DO NOT EDIT

% --- INITIALIZATION code: executes before the ViRMEn engine starts.


function vr = initializationCodeFun(vr)

% --- RUNTIME code: executes on every iteration of the ViRMEn engine.

23
function vr = runtimeCodeFun(vr)

% --- TERMINATION code: executes after the ViRMEn engine stops.


function vr = terminationCodeFun(vr)

All three functions use a MATLAB structure variable called vr as both input and output. The values in this structure
persist throughout an entire engine run. Therefore, vr is a convenient place to store any custom variables that
need to be accessed and/or changed during an experiment (see example below).

You can change the default template file so that it differs from the one shown above. See customizing the
template file for details.

EXAMPLE: KEEPING TRACK OF THE NUMBER OF REWARDS

A good way to keep track of the number of rewards is to define a variable numRewards as a field of the structure
vr. The ViRMEn engine will keep the value stored in this variable from one iteration to the next, and will also make
it available after the engine stops running. The following example defines the variable in the initialization code,
updates it using the runtime code, and displays the value after the engine stops using the termination code

In the initialization code


vr.numRewards = 0; % define and initialize the variable numRewards

In the runtime code


if reward_condition % test if the reward condition has been satisfied
vr.numRewards = vr.numRewards + 1;
end

In the termination code


% display the number of rewards received while the engine was running
disp(['The animal received ' num2str(vr.numRewards) ' rewards.']);

EXAMPLE: SHARING CODE BETWEEN EXPERIMENTS

Many routines, such as initializing a DAQ or defining certain variables, will be common to multiple experiments (or
even multiple users on a given experimental setup). I recommend placing such common routines into external
functions, and running these functions from within the initialization, runtime, or termination code. Typically, an
external function you write will use the structure vr as both an input and an output. The following example
assumes that multiple experiments use a variable numRewards and write data to a file called log.dat. The
statements that initialize numRewards and open the file are therefore placed into a separate function. The
statements that display the number of rewards and close the file are also placed into a separate function.

In the initialization code


vr = commonInitialization(vr); % call an external function

In the termination code


vr = commonTermination(vr); % call another external function

In a separate file
function vr = commonInitialization(vr)

% define and initialize a variable numRewards


vr.numRewards = 0;

% open a log file and store the file identifier in vr

24
vr.fid = fopen('log.dat');

In another separate file


function vr = commonTermination(vr)

% display the number of rewards received while the engine was running
disp(['The animal received ' num2str(vr.numRewards) ' rewards.']);

% close the log file


vr.fid = fclose(vr.fid);

PROGRAMMING THE RUNTIME “VR” STRUCTURE

In addition to custom variables, vr contains built-in fields that can be accessed by the user. These fields store real-
time information about the engine, such as the animal’s current position, velocity, etc. These fields can be changed
by the user to trigger real-time changes – for example, changing vr.position will instantaneously teleport the
animal through the world. The following is a list of all built-in fields or vr.

- vr.exper: a variable of the class virmenExperiment that contains all information about the experiment,
worlds, etc. I recommend saving this variable for future reference each time you run an experiment – that
way you will have a record of all day-to-day changes you might have made to your experiment. Changing
this variable has no effect on the ongoing experiment; to perform real-time manipulations, use vr.worlds
instead.
- vr.code: a structure containing function handles of the initialization, runtime, and termination functions.
Changing these handles during an experiment will change the code that is run.
- vr.worlds: a cell array of structures containing all information about the worlds used by the current
experiment. Changing these structures will cause instantaneous changes to worlds. See the section on
manipulating worlds for details.
- vr.experimentEnded: a Boolean indicating whether the experiment should be forced to end. Setting this
to true will stop the engine and run the termination function.
- vr.currentWorld: the index of the current world. This must have an integer value from 1 to the length of
vr.worlds. Changing this value will instantaneously switch worlds.
- vr.position: an array of length 4 containing the current position in the form [x, y, z, viewAngle]. The units
are ViRMEn space units for x, y, and z and radians for the viewAngle. Changing these values will
instantaneously teleport or rotate the animal in the world.
- vr.movement: an array of length 4 containing the latest movement values returned by the user-defined
movement function. This can be either a velocity vector, a displacement vector, or a position vector,
depending on the format of the function (and indicated by vr.movementType). Changing these values
does not do anything; to effect the animal’s movement, change vr.dp or vr.position instead.
- vr.movementType: a string indicating the type of movement indicated by the user-defined movement
function. This can be ‘velocity’ (or ‘v’), ‘displacement’ (or ‘d’), or ‘position’ (or ‘p’).
- vr.velocity: an array of length 4 containing the current velocity in the form (d/dt)[x, y, z, viewAngle]. The
units are ViRMEn space units/sec for the first three values and radians/sec for the fourth value. Changing
these values does not do anything; to effect the animal’s movement, change vr.dp or vr.position instead.
- vr.dt: the amount of time in seconds elapsed since the previous iteration. Changing this value does not do
anything.
- vr.dp: the change in position and view angle being implemented on the current iteration. Typically,
vr.dp=vr.velocity*vr.dt. However, if the animal is currently in collision with an edge of an object, vr.dp is

25
projected onto the edge, so that the animal “skids” along it. Changing the value of vr.dp will change the
amount by which the animal moves and/or rotates on the current iteration.
- vr.dpResolution: the resolution with which vr.dp is resolved during collisions. The value indicates how far
from a wall the animal is allowed to remain during “skidding” if a collision is detected. By default, the
value is Inf. Finite values cause the ViRMEn engine to move the animal closer to the wall, and only then to
skid; this behavior is necessary for proper navigation in tight spaces, such as narrow corridors.
- vr.collision: Boolean indicating whether the animal is currently in collision with an edge of an object.
- vr.text: a structure containing all textboxes displayed on the screen. Changing this structure can be used
to introduce new textboxes or modify or delete existing ones.
- vr.plot: a structure containing all line plots displayed this structure. Changing this structure can be used to
introduce new line plots or modify or delete existing ones.
- vr.textClicked: the index of the textbox that was clicked by the user during the previous iteration. If no
textbox was clicked, the value is NaN.
- vr.keyPressed: the keyboard key that was pressed by the user during the last iteration. If no key was
pressed, the value is NaN.
- vr.keyReleased: the keyboard key that released by the user during the last iteration. If no key was
released, the value is NaN.
- vr.buttonPressed: the mouse button that was pressed by the user during the last iteration. If no mouse
button was pressed, the value is NaN.
- vr.buttonReleased: the mouse button that was pressed by the user during the last iteration. If no mouse
button was pressed, the value is NaN.
- vr.modifiers: modifier keys (such as Shift, Control, or Alt) that were pressed by the user simultaneously
with a keyboard key or mouse button press/release. If neither a keyboard nor a mouse event occurred,
the value is NaN.
- vr.activeWindow: index the ViRMEn window in which a keyboard or mouse event (press or release) was
triggered during the last iteration. If neither a keyboard nor a mouse event occurred, the value is NaN.
- vr.cursorPosition: mouse cursor position. The position is measured in pixels (Δx and Δy) relative to the
corners of all N windows and represented in an Nx2 matrix.
- vr.iterations: the number of iterations performed by the engine during the current run.
- vr.timeStarted: MATLAB time stamp indicating the time point at which the engine started running.
- vr.timeElapsed: total amount of time elapsed since the engine started running.

EXAMPLE: TELEPORTING

The animal can be teleported by changing vr.position within the runtime code. The following code will teleport the
animal to the beginning of a linear track once he reaches the end of the track. We assume that the track is oriented
along the y dimension of the environment and has length 200. While teleporting the animal, we also set the
displacement vector vr.dp to 0; this is necessary to prevent any additional movement during teleportation.
(Because vr.dp was calculated before teleportation, it may not be valid at the new location.)

In the runtime code


if vr.position(2) > 200 % test if the animal is at the end of the track (y > 200)
vr.position(2) = 0; % set the animal’s y position to 0
vr.dp(:) = 0; % prevent any additional movement during teleportation
end

EXAMPLE: SWITCHING WORLDS

26
Worlds can be switched by changing the value of vr.currentWorld. The following code will switch the animal
between two linear tracks. On each trial, the animal runs on one of two linear tracks (worlds 1 and 2). As soon as
he reaches the end of the track (length 200 along the y direction), he is teleported to the beginning of a randomly
chosen track.

In the runtime code


if vr.position(2) > 200 % test if the animal is at the end of the track (y > 200)
newWorldIndx = ceil(rand*2); % choose a random world index from 1 to 2
vr.currentWorld = newWorldIndx; % set the current world
vr.position(2) = 0; % set the animal’s y position to 0
end

EXAMPLE: CONTROLING SKIDDING ALONG WALLS

When a collision with an edge of an object is detected, the animal’s displacement vector is projected onto the
edge. In other words, the animal “skids” along the edge with no friction. It is possible to control the skidding
behavior by manipulating the value of vr.dp.

In the initialization code


vr.friction = 0.3; % define friction that will reduce velocity by 70% during collisions

In the runtime code


if vr.collision % test if the animal is currently in collision
% reduce the x and y components of displacement
vr.dp(1:2) = vr.dp(1:2) * vr.friction;
end

In the above code, vr.friction = 1 will produce the default frictionless behavior. A value of vr.friction = 0 will
completely block movement whenever collision happens.

EXAMPLE: USING CLICKABLE TEXTBOXES

Some parts of the computer monitor are typically invisible to the animal because they do not project onto the
virtual reality screen. These regions can be used to display useful information to the experimenter, such as the
amount of time elapsed or the number of rewards received. The structure vr.text can be used to create and update
textboxes. This structure contains fields string, position, size and color. The string can contain capital letters A-Z,
digits 0-9, spaces, as well as the following characters: - + * / = % . , ( ) [ ]. Position and size are both in units of
screen height/2, such as the top of the screen is at 1 and the bottom is at -1. The center of the monitor is at (0, 0).

The following example displays the time elapsed since the beginning of the engine run.

In the initialization code


% Define a textbox and set its position, size and color
vr.text(1).position = [-1.2 1]; % upper-left corner of the screen
vr.text(1).size = 0.03; % letter size as fraction of the screen
vr.text(1).color = [1 1 0]; % yellow
vr.text(1).window = 1; % display text in the first window

In the runtime code


% On every iteration, update the string to display the time elapsed
vr.text(1).string = ['TIME ' datestr(now-vr.startTime,'MM.SS')];

Textboxes can also be programmed to respond to mouse clicks. That way, they act like buttons that can allow the
experimenter to trigger manual rewards, teleport the animal, flash stimuli, etc. The value of vr.textClicked

27
indicates which textbox has been clicked during the last iteration of the engine. If no textbox has been clicked, the
value is NaN. For example, adding these lines to the previous example will generate a sound each time the textbox
is clicked.

In the runtime code


if vr.textClicked == 1 % check if textbox #1 has been clicked
beep
end

Note that you can also make ViRMEn respond to user input via keyboard using the vr.keyPressed field.

EXAMPLE: USING LINE PLOTS

Parts of the computer monitor that are not visible to the animal can also be used to display line plots. This is useful
for providing the experimenter with a real-time graphical indicator – for instance, a moving symbol that shows the
animal’s position in a virtual world. The structure vr.plot can be used to create and update line plots. This structure
contains three fields: x, y, and color. The following example assumes that the animal is navigating on a linear track
(oriented in the y-direction) and displays a symbol indicating the animal’s position on the track.

In the initialization code


% X-position of the symbol on the screen (0 is center)
symbolXPosition = 0;

% Symbol size as fraction of the screen


vr.symbolSize = 0.02;

% Track minimum and maximum positions


vr.trackMinY = -10;
vr.trackMaxY = 310;

% Create a square symbol and assign color to it


vr.plot(1).x = [-1 1 1 -1 -1]*vr.symbolSize + symbolXPosition;
vr.plot(1).y = [-1 -1 1 1 -1]*vr.symbolSize;
vr.plot(1).color = [1 0 0];
vr.plot(1).window = 1; % display plot in the first window

In the runtime code


% Normalize animal’s y-position to range from -1 to 1 (the monitor range)
symbolYPosition = 2*(vr.position(2)-vr.trackMinY)/(vr.trackMaxY-vr.trackMinY) - 1;

% Update the y-position of the symbol


vr.plot(1).y = [-1 -1 1 1 -1]*vr.symbolSize + symbolYPosition;

EXAMPLE: ACCESSING VALUES OF CUSTOM VARIABLES

The ViRMEn GUI allows the user to define custom variables that describe various features of the virtual world.
Sometimes, it may be necessary to access the values of these variables during the experiment itself. This can be
done by accessing vr.exper.variables. The following example assumes that a variable trackLength has been used to
define the length of a linear track. Whenever the animal reaches the end of the linear track, he is teleported to the
beginning. Using the custom variable guarantees that the code will work properly whenever trackLength is
changed using the GUI in order to shorten or lengthen the track.

In the initialization code


% evaluate the expression for trackLength

28
vr.trackLength = eval(vr.exper.variables.trackLength);

In the runtime code


if vr.position(2) > vr.trackLength % check if the animal’s y position exceeds trackLength
vr.position(2) = 0; % teleport the animal to the beginning of the track
end

TRANSFORMATION FUNCTIONS

TRANSFORMATION STEPS

A transformation function determines how 3D coordinates within the virtual world are transformed into 2D
coordinates that the projector displays on the screen. A transformation function is specific to each experimental
setup and is determined by the shape of the screen (flat, toroidal, conical, etc.) and the geometry of the projection
(projector angle, mirror shape, etc.). Transformations functions recognized by ViRMEn must be .m or mex files
placed in the “transformations” subfolder of ViRMEn. The following is the sequence of steps used by the ViRMEn
engine to render a world on the screen. This should help you understand how to derive a transformation function
for your experimental setup.

1. Whenever a world is loaded by the ViRMEn engine, it is triangulated – converted in to a set of triangles
defined by their vertices and colors. Consider one of the vertices, . Our goal is to understand
where this vertex should appear on the computer monitor, and consequently in the projected image.
2. On every iteration of the engine, the animal’s location in the virtual world is denoted by .
Here is the view angle. The ViRMEn engine first subtracts the animal’s location from the vertex location
to produce the translated (subscript T) version of the coordinates , where ,
and . These are the coordinates of the vertex in the reference frame in which
the animal is at the origin.
3. The vertex location is then rotated around the z axis in the opposite direction of the animal’s view angle
to produce the translated and rotated (subscript T&R) version of the coordinates ,
where [ ] [ ] , where is the rotation matrix of angle . The
resulting coordinates are the coordinates of the vertex in the reference frame in which the animal is at
the origin and facing in the direction of 0 degrees.
4. Steps 2 and 3 illustrate how ViRMEn handles animal’s movements: instead of modifying the viewpoint on
every iteration, it moves and rotates the entire world, while keeping the viewpoint stationary. This
simplifies the 3D->2D transformation, because it allows us to assume that the animal is always at the
origin facing in the 0-degree direction. The transformation function then converts the 3D coordinate
into a 2D monitor/projector coordinate ). (See function format).
5. In the final step, ViRMEn sorts all triangles according to their distance from the animal. Triangles that are
farther from the animal are rendered first, while triangles close to the animal are rendered last. This way,
if two or more triangles overlap in the projected image, the triangle closest to the animal occludes those
located farther away.

FUNCTION FORMAT

In MATLAB, the transformation function takes a 3xN matrix as input, with rows containing , and
. As output, it returns a 3xN matrix where the first two rows contain and . The third row of the

29
output matrix contains 0’s or 1’s indicating whether the location is visible. This is necessary for making
invisible those locations that are outside the projected image. The monitor/projector coordinate system is
defined to be (0, 0) at the center of the monitor; the monitor is defined to be 2 units high, such that the top of
the monitor is at and the bottom of the monitor is at . The limits of depend on the aspect
ratio of the monitor.

It is possible to create a function that performs multiple transformations on the coordinates (for example, for
displaying different transformations in different windows). For this, the output of the function must be a
3xNxT matrix, where the outputs of transformations 1, …, T are stacked in the 3rd dimension of the matrix. In
the windows manager, the “Function #” value can be used to indicate which of the T transformations should
be used in each window.

It is also possible to pass additional parameters to the transformation function. To do so, write a function that
accepts a second input. In this case, ViRMEn will pass the 3xN matrix of coordinates as the first input and the
vr structure as the second input. Necessary parameters can be stored as fields of the vr structure.

Transformation functions are often computationally intensive functions because they operate on large
matrices of coordinates. Furthermore, the transformation function in use is called by the ViRMEn engine on
every iteration and can therefore substantially slow down the engine refresh rate. If possible, I recommend
coding transformation functions as mex files rather than MATLAB routines.

As an example, see the Appendix for the derivation of transformation functions for radially symmetric screens.

MOVEMENT FUNCTIONS

On each iteration, the ViRMEn engine calls a movement function, which returns values indicating the current
movement of the animal in the world. Typically, a movement function will read data from analog input channels
and appropriately filter and/or scale them. Alternatively, a movement function can calculate velocity using inputs
from a mouse or keyboard.

Movement functions recognized by ViRMEn must be .m of mex files placed in the “movements” subfolder of
ViRMEn. The format of the function is [movement, movementType] = movementFunction(vr); Here, the input is
the structure vr. The first output (movement) is a vector of 4 values, which are interpreted in different ways
depending on the value of the second output (movementType).

- If movementType is ‘velocity’, ‘v’, or omitted, then movement is interpreted to be in the form (d/dt)*x, y,
z, viewAngle] and used to update the value of vr.velocity. The units of the first three values are ViRMEn
space units per second; the units for the fourth value are radians per second. These velocity values are
multiplied by vr.dt to compute the displacement vector vr.dp.
- If movementType is ‘displacement’, ‘dp’, or ‘d’, then movement is interpreted to be in the form [x, y, z,
viewAngle] and used to update the value of vr.dp. The units of the first three values are ViRMEn space
units; the units of the fourth value are radians. The displacement values are divided by vr.dt to compute
the velocity vector vr.velocity.
- If movementType is ‘position’ or ‘p’, then movement is interpreted to be in the form *x, y, z, viewAngle+.
The current position (vr.position) is subtracted from this value to compute the value of vr.dp, and the
value of vr.dp is then divided by vr.dt to compute the velocity vector vr.velocity.

30
Outputs of the movement function are thus used to update the values of vr.movement, vr.movementType,
vr.velocity, and vr.dp. After these manipulations, the value of vr.dp can be further modified by the collision
detection algorithm (the other three variables remain the same). After collision detection, the new value of vr.dp is
used to update the value of vr.position. (Note that if movementType is ‘position’ or ‘p’, then collision detection is
not implemented.)

Devices like optical mice output velocity-related signals, whereas devices like rotary encoders output position-
related signals. If your device outputs a position-related signal, it is better to use the movement function with
either the ‘displacement’ or ‘position’ option, because these options will not introduce division-related noise
during the calculation of velocity.

EXAMPLE: MOVING FORWARD WITH CONSTANT VELOCITY

The following example is a movement function that moves the animal forward in the world (along the y direction)
with a constant velocity. The value of velocity can be defined in the initialization code without having to change
the movement function each time.

In the initialization code


vr.forwardVelocity = 20;

In the movement function


function velocity = moveForward(vr)

velocity = [0 vr.forwardVelocity 0 0];

REAL-TIME WORLD MANIPULATIONS

You can program your runtime code to manipulate worlds in in real time by modifying values in the vr.worlds field
on the vr structure. This section will show you how to work with vr.worlds by demonstrating several simple
examples of world manipulations.

The field vr.worlds is a cell array containing information about all worlds in the current experiment. To make real-
time changes to the current world, we will make changes to vr.worlds{vr.currentWorld}.

EXAMPLE: MAKING THE WORLD INVISIBLE

Suppose we want to “turn off” the world and place the animal in complete darkness. The ViRMEn engine
represents the entire worlds as a set of triangles, which are stored in the “surface” structure:

>> vr.worlds{vr.currentWorld}.surface

ans =

vertices: [3x10793 double]


triangulation: [3x19862 int32]
visible: [1x19862 logical]
colors: [4x10793 double]

This example world contains 10793 triangle vertices, whose coordinates are store in the “vertices” field (the 3 rows
are x, y, and z coordinates). These vertices form 19862 triangles (many of vertices are shared by multiple triangles.

31
The matrix in the “triangulation” field indicates indices of the triplets of vertices that are linked into triangles. Each
triangle has a visibility Boolean assigned to it, indicated by the “visible” field. To make the entire world invisible,
set all of the visibility Booleans to false:

In the runtime code


vr.worlds{vr.currentWorld}.surface.visible(:) = false; % make all triangles invisible

EXAMPLE: MOVING AN OBJECT

As mentioned above, the field vr.worlds{vr.currentWorld}.surface.vertices contains the coordinates of all triangles
in the world. By changing these coordinates, we can instantaneously move objects in the world.

Suppose we have an object in the current world called targetCylinder, and we want to move this object 5 units
along the y axis. To do so, we should change the 2nd row of vr.worlds{vr.currentWorld}.surface.vertices. However,
the world contains 10793 vertices, and only some of them belong to targetCylinder. Thus, we first need to find out
which of the vertices we should change. To obtain this information, we will use the “objects.vertices” subfield:

>> vr.worlds{vr.currentWorld}.objects.vertices

ans =

1 7357
7358 10169
10170 10467
10468 10793

These values indicate that there are 4 objects in the current world. The first object occupies vertices 1 through
7357, the second object occupies vertices 7358 through 10169, and so on. One of these four objects is our
targetCylinder. To find out which one, we will use the “objects.indices” subfield:

>> vr.worlds{vr.currentWorld}.objects.indices

ans =

roomWall: 1
objectCircularFloor: 2
targetCylinder: 3
cueCard: 4

The object named targetCylinder is the 3rd one, meaning that it occupies vertices 10170 through 10467. (This is
why it’s important to assign meaningful names to all of your objects). If we change columns 10170 through 10467
of vr.worlds{vr.currentWorld}.surface.vertices, we will instantaneously move targetCylinder without effecting any
other objects. Putting all this together (note that the code which doesn’t need to get repeated on each iteration is
placed in the initialization code):

In the initialization code


% determine the index of targetCylinder
indx = vr.worlds{vr.currentWorld}.objects.indices.targetCylinder;

% determine the indices of the first and last vertex of targetCylinder


vertexFirstLast = vr.worlds{vr.currentWorld}.objects.vertices(indx,:);

% create an array of all vertex indices belonging to targetCylinder and store it in vr


vr.cylinderIndx = vertexFirstLast(1):vertexFirstLast(2);

In the runtime code


% determine the current y coordinates of targetCylinder vertices

32
y = vr.worlds{vr.currentWorld}.surface.vertices(2, vr.cylinderIndx);

% increase the y coordinates by 5 units


vr.worlds{vr.currentWorld}.surface.vertices(2, vr.cylinderIndx) = y + 5;

EXAMPLE: CHANGING OBJECT TRANSPARENCY

(Note that in order to perform this operation, the transparency property of the world must be turned on).

To change the transparency or color of an object, we will modify the “color” subfield of
vr.worlds{vr.currentWorld}.surface:

>> vr.worlds{vr.currentWorld}.surface

ans =

vertices: [3x10793 double]


triangulation: [3x19862 int32]
visible: [1x19862 logical]
colors: [4x10793 double]

The 4 rows of the “colors” matrix contain the RGB values and the Alpha (transparency) value. Each column of the
“colors” matrix indicates the color and transparency of the corresponding vertex. (If the transparency property of
the world was turned off, the “colors” matrix would contain only 3 rows, corresponding to the RGB values.) Note
that triangles in OpenGL are colored according to their last vertex; for instance, a triangle composed of vertices [6
20 83] will be colored with the color assigned to vertex 83.

The following code sets the transparency of targetCylinder to 70%.

In the initialization code


% determine the index of targetCylinder
indx = vr.worlds{vr.currentWorld}.objects.indices.targetCylinder;

% determine the indices of the first and last vertex of targetCylinder


vertexFirstLast = vr.worlds{vr.currentWorld}.objects.vertices(indx,:);

% create an array of all vertex indices belonging to targetCylinder and store it in vr


vr.cylinderIndx = vertexFirstLast(1):vertexFirstLast(2);

In the runtime code


% set the transparency of targetCylinder to 0.7
vr.worlds{vr.currentWorld}.surface.colors(4,vr.cylinderIndx) = 0.7;

EXAMPLE: ADDING AN OBJECT

A new object can be introduced using the same methods as described above – by changing the values within
vr.worlds{vr.currentWorld}.surface. However, adding an object this way is very involved because new columns
indicating new triangles need to be appended to the matrices of vertices, colors, etc. Instead, changes to the
ongoing experiment can be made using simpler, high-level object-oriented programming commands by modifying
vr.exper. After modifying vr.exper, we will re-triangulate the world to change its low-level representation (within
vr.worlds) that the ViRMEn engine uses.

Note that this method is slower than the low-level programming, and a change to a world can take tens of
milliseconds. This is suitable for occasional changes – such as those implemented at the ends of behavioral trials –
but not for changes that occur often during the experiment.

33
Suppose that the current world contains an object called targetCylinder. We will create a new instance of this
cylinder at location (50, 50).

In the runtime code


% Create a new instance of a cylinder at (50, 50)
vr.exper.worlds{vr.currentWorld}.items.targetCylinder.x(end+1) = 50;
vr.exper.worlds{vr.currentWorld}.items.targetCylinder.y(end+1) = 50;

% Re-triangulate the world


vr.worlds{vr.currentWorld} = vr.exper.worlds{vr.currentWorld}.triangulate;

INPUT-OUTPUT METHODS

The ViRMEn engine does not include any built-in features for reading and outputting data. These features must be
written in MATLAB and included in your code. This section provides examples for how to integrate simple
input/output methods into custom ViRMEn code.

EXAMPLE: INTERFACING WITH A DAQ

To interface with a DAQ, you need the MATLAB Data Acquisition Toolbox installed. Refer to MATLAB
documentation for how to install and register your DAQ hardware in MATLAB.

The easiest way to deal with a DAQ is to define all input and output channels in the initialization code of your
custom ViRMEn code. Handles of all input and output objects should be stored in the vr structure. That way, all
functions run by ViRMEn during the experiment (e.g., the runtime function and the movement function) will have
access to these objects. The following is sample code illustrating how to define 2 analog input objects for use in
ViRMEn.

In the initialization code


% reset DAQ in case it's still in use by a previous MATLAB program
daqreset;

% connect to the DAQ card dev1; store the input object handle in vr for use by ViRMEn
vr.ai = analoginput('nidaq','dev1');

% start analog input channels 0 and 1


addchannel(vr.ai,0:1);

% define the sampling rate to 1kHz and set the duration to be unlimited
set(vr.ai,'samplerate',1000,'samplespertrigger',inf)

% set the buffering window to be 8 ms long - shorter than a single ViRMEn refresh cycle
set(vr.ai,'bufferingconfig',[8 100]);

% define a temporary log file to be deleted at the end of the experiment


set(vr.ai,'loggingmode','Disk');
vr.tempfile = [tempname '.log'];
set(vr.ai,'logfilename',vr.tempfile);

% start acquisition from the analog input object


start(vr.ai);

In the termination function


% stop the analog input object
stop(vr.ai);

% delete the temporary log file

34
delete(vr.tempfile);

I recommend using the MATLAB commands peekdata for reading data from an analog input object and putsample
for writing data to an analog output object. These are both non-blocking functions that will not slow down the
execution of the ViRMEn engine. Refer to the MATLAB help on these functions. Data can be read or written in the
runtime function or your custom code. In addition, you can read data from a DAQ within your movement function
in order to obtain velocity information from an input device (such as an optical mouse).

EXAMPLE: LOGGING TO A FILE

Writing to a binary file in MATLAB is fast an can be implemented within the runtime function or your MATLAB
code. Use this to log behavioral data – or other relevant information – to a file. The following example shows how
to log the animal’s time-stamped position and velocity and how to reconstruct these data after you’re done with
the experiment.

In the initialization code


% open or create binary file for writing and store its file ID in vr
vr.fid = fopen('virmenLog.dat','w');

In the runtime code


% obtain the current timestamp
timestamp = now;

% write timestamp and the x & y components of position and velocity to a file
% using floating-point precision
fwrite(vr.fid, [timestamp vr.position(1:2) vr.velocity(1:2)],'double');

In the termination code


% close the file
fclose(vr.fid);

External code used to log and plot behavioral data


% open the binary file
fid = fopen('virmenLog.data');

% read all data from the file into a 5-row matrix


data = fread(fid,[5 inf],'double');

% close the file


fclose(fid);

% plot the 2D position information


plot(data(2,:),data(3,:));

FRAME GRABBING

You can grab the image currently displayed in a ViRMEn window using the virmenGetFrame function. The format is
M = virmenGetFrame(w) where w is the index of the window, and M is a Height x Width x 3 matrix of RGB values
for all of the pixels.

35
OBJECT-ORIENTED PROGRAMMING

Object-oriented programming is a method for manipulating and displaying virtual worlds programmatically with
MATLAB scripts without using the ViRMEn GUI. This is a powerful tool that allows you to code experiments that
would be too time-consuming to create with the GUI and gives you more flexibility for making complex changes.
For this section, I suggest some knowledge of object-oriented programming and the MATLAB class structure.

Object-oriented commands allow you to create experiments and worlds entirely from scratch. Alternatively, they
let you make changes to an experiment you created with the ViRMEn GUI. To do so, load the .mat file stored in the
“experiments” subfolder of ViRMEn. This file contains a single variable exper, which is a variable of class
virmenExperiment (described below).

VIRMEN CLASSES

ViRMEn object-oriented programming includes 5 variable classes:

1. virmenExperiment – ViRMEn experiment


2. virmenWorld – environment (world)
3. virmenObject – 3D object (such as a vertical cylinder)
4. virmenTexture – texture of an object
5. virmenShape – 2D shape (such as rectangle) used in textures

Classes virmenObject and virmenShape are superclasses containing individual object and shape types. For
example, the virmenVerticalCylinder class is a subclass of virmenObject, and shapeRectangle is a subclass of
virmenShape. All recognized objects classes are defined in the “objects” subfolder of ViRMEn. All recognized shape
classes are defined in the “shapes” subfolder of ViRMEn.

CREATING VIRMEN CLASS VARIABLES

You can create experiments, worlds, and textures by simply calling the corresponding ViRMEn class name. For
example, the following code creates a ViRMEn experiment:

>> ex = virmenExperiment

ex =

virmenExperiment handle

Properties:
windows: {[1x1 virmenWindow]}
movementFunction: @undefined
transformationFunction: @undefined
experimentCode: @undefined
codeText: {}
worlds: {[1x1 virmenWorld]}
name: 'virmenExperiment'
parent: {}
items: [1x1 struct]
symbolic: [1x1 struct]
variables: [1x1 struct]
userdata: []

Methods, Events, Superclasses

36
To create objects or shapes, call the particular object or shape class type. For example, the following code creates
a vertical cylinder object:

>> c = objectVerticalCylinder

c =

objectVerticalCylinder handle

Properties:
radius: 5
bottom: 0
startAngle: 0
stopAngle: 360
top: 40
x: [0x1 double]
y: [0x1 double]
tiling: [5 5]
edgeRadius: NaN
texture: [1x1 virmenTexture]
iconLocations: [2x2 double]
helpString: 'Click cylinder centers, then press Enter'
name: 'objectVerticalCylinder'
parent: {}
items: [1x1 struct]
symbolic: [1x1 struct]
variables: [1x1 struct]
userdata: []

Methods, Events, Superclasses

To create a color to add to a texture, use the built-in shapeColor class:

>> c = shapeColor

c =

shapeColor handle

Properties:
R: []
G: []
B: []
Alpha: NaN
x: [0x1 double]
y: [0x1 double]
iconLocations: [2x2 double]
helpString: 'Click color locations, then press Enter'
name: 'shapeColor'
parent: {}
symbolic: [1x1 struct]
variables: [1x1 struct]
userdata: []

Methods, Events, Superclasses

VIRMEN CLASS PROPERTIES

All ViRMEn classes have properties assigned to them. These properties are easy to set. For example, the following
code creates a world and changes its background color to white:

>> w = virmenWorld;
>> w.backgroundColor = [1 1 1]

37
w =

virmenWorld handle

Properties:
backgroundColor: [1 1 1]
startLocation: [0 0 0 0]
objects: {}
name: 'virmenWorld'
parent: {}
items: [1x1 struct]
symbolic: [1x1 struct]
variables: [1x1 struct]
userdata: []

Methods, Events, Superclasses

Object and shape classes have type-dependent properties. For example, a variable of type objectVerticalCylinder
has a property named radius. To obtain a list of all properties for a given class, use the MATLAB properties
command:

>> properties(objectVerticalCylinder)

Properties for class objectVerticalCylinder:

radius
bottom
startAngle
stopAngle
top
x
y
tiling
edgeRadius
texture
iconLocations
helpString
name
parent
items
symbolic
variables
userdata

The properties can also be specified using custom variables. Just set the property value to a string containing a
mathematical expression. For example, to create a vertical cylinder and set its radius to a variable ‘r’ (see symbolic
property for more details):

>> c = objectVerticalCylinder;
>> c.radius = 'r';

The following sections are a reference of the properties of all ViRMEn.

VIRMENEXPERIMENT PROPERTIES

- windows: cell array listing all windows used by the ViRMEn display. Each element of the cell array is an
item of class virmenWindow, having the following properties:

rendering3D: Boolean indicating whether any 3D world rendering is displayed in the window. If the
value is false, the window can still be used to display textboxes or line plots.

38
transformation: The index of the transformation function used in the window. For functions that
perform only one transformation, this value should be one. For functions that perform multiple
transformations, this value can be used to determine which of the functions is displayed. If the
rendering 3D is set to false, this property value is ignored.
primaryMonitor: Boolean indicating whether the window is displayed on the primary monitor.
monitor: Index of the monitor that displays the window. If primaryMonitor is set to true, this property
value is ignored.
fullScreen: Boolean indicating whether the window occupies the full screen of the monitor.
left: window position in pixels. If fullScreen is set to true, this property value is ignored.
bottom: window position in pixels. If fullScreen is set to true, this property value is ignored.
width: window size in pixels. If fullScreen is set to true, this property value is ignored.
height: window size in pixels. If fullScreen is set to true, this property value is ignored.
antialiasing: the level of antialiasing (number of samples) in the window.

- movementFunction: MATLAB function handle of the experiment’s movement function


- transformationFunction: MATLAB function handle of the experiment’s transformation function
- experimentCode: MATLAB function handle of the file containing the experiment’s initialization, runtime,
and transformation functions
- codeText: cell array of strings containing all the code from the experiment’s .m file. Note that if you
change the .m file, the codeText property does not automatically update. To update the codeText
property, use the updateCodeText method.
- worlds: cell array of virmenWorld variables containing all of the experiment’s worlds

VIRMENWORLD PROPERTIES

- backgroundColor: background color of the world (1x3 array)


- startLocation: the animal’s starting location in the world (1x4 array in the form [x, y, z, viewAngle])
- transparency: transparency of the world, set either to 0 (off) or 1 (on)
- objects: cell array of virmenObject variables containing all of the world’s objects

VIRMENOBJECT PROPERTIES

Properties of ViRMEn objects depend of the object class type. For instance, a variable of class
objectVerticalCylinder has a property called radius. For reference on these properties, refer to the particular object
type. In addition, there are properties of all ViRMEn objects, independent of the object type, listed below.

- x: x component of the object’s locations (Nx1 array for N locations or a scalar if all x coordinates are the
same)
- y: y component of the object’s locations (Nx1 array for N locations or a scalar if all y coordinates are the
same)
For example, to create 2 vertical cylinders and placed at (0, 0) and (100, 200):
>> c = objectVerticalCylinder;
>> c.x = [0; 100];
>> c.y = [0; 200];

- tiling: amount of texture tiling on the surface of the object (1x2 array in the form [vertical horizontal])
- edgeRadius: radius of the physical edge around the object (NaN for no edge)

39
- texture: virmenTexture variable containing the object’s texture
- iconLocations: matrix containing xy locations of the object for display in the icon representing the object
in the Add object dropdown menu of the ViRMEn GUI (Nx2 matrix)
- helpString: help string displayed to the user when object locations are being entered in the GUI

VIRMENTEXTURE PROPERTIES

- width: the width of the texture. Typically set to 1 and not changed.
- height: the height of the texture. Typically set to 1 and not changed.
- tilable: Booleans indicating whether the texture is tilable (1x2 array in the form [vertical horizontal])
- refining: the edge refining used for triangulating the texture (1x2 array in the form [vertical horizontal])
- grid: the grid size used for triangulating the texture (1x2 array in the form [vertical horizontal]
- shapes: cell array of virmenShape variables containing all of the texture’s shapes
- triangles: structure containing information about all of the triangles making up the texture’s triangulation.
Note that the triangles property does not automatically update if you make changes to the texture. To
update the triangles property, use the compute method. The triangles structure contains the following
fields
vertices: coordinates of all triangle vertices (Nx2 matrix of x and y values)
triangulation: indices of vertex triplets that are linked together to create triangles (Mx3 array matrix
of triplet). A single vertex can be shared by multiple triangles.
cdata: color and transparency values of all triangle vertices (Nx4 matrix of RGB and Alpha values). A
triangle is assigned the color and transparency of its last (3rd) listed vertex.

VIRMENSHAPE PROPERTIES

Properties of ViRMEn shapes depend of the shape class type. For instance, a variable of class shapeRegularPolygon
has a property called radius. For reference on these properties, refer to the particular shape type. In addition,
there are properties of all ViRMEn shapes, independent of the shape type, listed below.

- x: x component of the shape’s locations (Nx1 array for N locations)


- y: y component of the shape’s locations (Nx1 array for N locations)
- iconLocations: matrix containing xy locations of the shape for display in the icon representing the shape in
the Add shape dropdown menu of the ViRMEn GUI (Nx2 matrix)
- helpString: help string displayed to the user when shape locations are being entered in the GUI

PROPERTIES COMMON TO ALL VIRMEN CLASSES

The following are properties carried by variables of any ViRMEn class type. For instance, any ViRMEn item – be it a
virmenExperiment, virmenWorld, etc – has a property called “name”.

- name: the item’s name. When a ViRMEn variable is created, it is automatically given a name according to
the variable class (e.g., “virmenExperiment”, “virmenWorld”, etc.)
- parent: handle of the item one step above the variable in the hierarchy (virmenExperiment 
virmenWorld  virmenObject  virmenTexture  virmenShape). For example, the parent of a
virmenWorld is a virmenExperiment variable; the parent of a virmenTexture is a virmenObject variable.
The parent of a virmenExperiment variable is an empty array.

40
- symbolic: structure containing all of the item’s properties in symbolic form. This is useful for looking up
expressions of the objects properties that were specified using custom variables.
For example, suppose we want to create a vertical cylinder and assign it a radius indicated by a
custom variable r. To do so, you can simply set the object’s radius property to the string ‘r’. Once
you do that, you will be prompted for the value of the new variable r. Enter 10 for the value.
>> c = objectVerticalCylinder;
>> c.radius = 'r';

Now, when you prompt for the radius property of the cylinder, the value 10 is returned
>> c.radius

ans =

10

However, when you prompt for the symbolic property, the string ‘r’ is returned
>> c.symbolic.radius

ans =

To change the radius to a different symbolic expression, change the radius property (not the
symbolic property):
>> c.radius = '2*r';
>> c.radius

ans =

20

- userdata: user-specified data. This property can be set to any user-defined variable type and value.
ViRMEn does not use this data; it is solely for the convenience of the user’s code.
- variables: a structure containing all custom variable names and values.
For instance, after running the above example, you will find the variable r in this structure:
>> c.variables

ans =

r: '10'

You can set the variable to a new value, which must be a string containing an expression. This will
automatically update all properties that depend on the variable. For instance, in the above
example, changing the variable r will automatically change the radius of the cylinder (which has
been set to 2*r):
>> c.variables.r = '100';
>> c.radius

ans =

200

Note that all variable values are shared by members of the same hierarchy. For instance, each of
the objects inside a world will have the same variable values as the world itself. However, the
values are only accessible through the top member of the hierarchy. The variables structure for
all other members is empty.

41
VIRMEN CLASS METHODS

VIRMENEXPERIMENT METHODS

- addWorld
addWorld(e, w) adds the virmenWorld w to the virmenExperiment e. To delete a world from the
experiment, simply remove it from the e.worlds array.
- updateCodeText
updateCodeText(e) updates the codeText property of the virmenExperiment e by loading the
code from the .m file
- run
err = run(e) runs the the virmenExperiment e in the ViRMEn engine. Variable err contains any
errors that were produced during the engine log.

VIRMENWORLD METHODS

- draw3D
h = draw3D(w) creates a 3D rendering of the virmenWorld w in a MATLAB figure. Output h is the
handle of the 3D surface.
- draw2D
[ho, he, ha] = draw2D(w) creates a wireframe sketch of the virmenWorld w in a MATLAB figure.
The outputs ho contains handles of all objects sketched, he contains handles of all edges around
objects, and ha contains the handle of the square-and-arrow symbol representing the animal’s
starting location in the world.
- addObject
addObject(w, obj) adds the virmenObject obj to the virmenWorld w. To delete an object from
the world, simply remove it from the o.objects array.
- triangulate
tri = triangulate(w) creates a structure tri containing the triangulation of the world. This structure
is the same as those stored in vr.worlds.

VIRMENOBJECT METHODS

- locations
loc = locations(obj) returns an Nx2 matrix of the locations of the virmenObject obj.
- draw3D
h = draw2D(obj) creates a 3D rendering of the virmenObject obj in a MATLAB figure. Output h is
the handle of the 3D surface.
- draw2D
[ho he] = draw2D(obj) creates a wireframe sketch of the virmenObject obj in a MATLAB figure.
The output ho contains the handle of the object sketch, and he contains the handle of the edge
around the object.
- setTexture

42
setTexture(obj, t) sets virmenTexture t as the texture of virmenObject obj.

VIRMENTEXTURE METHODS

- draw
h = draw(t) draws a triangulated image of virmenTexture t. The output h is a handle of the
created surface object.
- sketch
h = sketch(t) creates a un-triangulated sketch of virmenTexture t. The output h contains the
handles of all shapes contained in the texture.
- compute
compute(t) computes virmenTexture t. Running this method updates the triangles property of
the texture.
- addShape
addShape(t, s) adds the virmenShape s to the virmenTexture t. To delete a shape from the
texture, simply remove it from the t.shape array. Note that adding or deleting a shape will not
automatically update the triangulation of the texture. To update triangulation, run the compute
method (see above).
- loadImage
loadImage(t, img, col) converts an indexed image img (2D matrix) with colormap col (Nx3 matrix)
to a ViRMEn texture t. The texture is represented as a set of colored regions, but is initially not
triangulated. To triangulate, run the compute method (see above).

VIRMENSHAPE METHODS

- locations
loc = locations(s) returns an Nx2 matrix of the locations of the virmenShape s.

METHODS COMMON TO ALL VIRMEN CLASSES

The following are methods allowed for any ViRMEn class type. For instance, any ViRMEn item – be it a
virmenExperiment, virmenWorld, etc – has a method called “fullName”.

- ancestor
Returns the handle of the top-level item in the ViRMEn class hierarchy (virmenExperiment 
virmenWorld  virmenObject  virmenTexture  virmenShape). Usually, the ancestor of any
ViRMEn item will be the virmenExperiment that this item is contained in.
- children
Returns the handles of all items one level lower in the ViRMEn class hierarchy (virmenExperiment
 virmenWorld  virmenObject  virmenTexture  virmenShape). For example, virmenWorld
items are the children of a virmenExperiment, whereas a virmenTexture item is the child of a
virmenObject.
- copyItem
All ViRMEn classes are MATLAB handles. This means that copying them is somewhat different
from copying other variable types, such as arrays. For example, suppose you create a vertical
cylinder object as a variable named c1 and set its radius to 10:

43
>> c1 = objectVerticalCylinder;
>> c1.radius = 10;

Now suppose you create a copy of variable c1 and name it c2. Because c1 and c2 are handles,
they point to the same object, and any change you make to one of them will now affect the
other:

>> c2 = c1;
>> c1.radius = 50;
>> c2.radius

ans =

50

If instead of having two handles that point to the same object, you want to duplicate the object
itself, use the copyItem method:

>> c1 = objectVerticalCylinder;
>> c1.radius = 10;
>> c2 = c1.copyItem;

Now, c1 and c2 refer to different objects, and changing one will have no effect on the other:

>> c1.radius = 50;


>> c1.radius

ans =

50

>> c2.radius

ans =

10

- descendants
Returns the handles of all items lower in the ViRMEn class hierarchy (virmenExperiment 
virmenWorld  virmenObject  virmenTexture  virmenShape). For example, the descendants
of a virmenWorld will include all virmenObject handles in that world, the virmenTexture handles
assigned to those objects, and the virmenShape handles that make up those textures.
- enableCallbacks
enableCallbacks(e) enables all of the automatic behavior that ViRMEn classes exhibit. In order for
a ViRMEn class (such as a virmenExperiment) to function properly, you must run enableCallbacks
whenever you load a saved ViRMEn class variable from a file.
- fullName
Returns a string containing the full name of an item. This is normally the same as the name
property of the item, except if there exist multiple items with the same name. For example, if
there are two objects with the name “myCylinder”, the method fullName will return strings
'myCylinder(1)' and 'myCylinder(2)' for those objects.
- indices
Returns a struct containing the indices of all children of an item. This is useful for making code
access children by name and continue working properly even if the order of children is modified.
- renameVariable

44
renameVariable(itm, oldVar, newVar) renames a variable named oldVar into newVar for an
ViRMEn-class item itm. The variable name is replaced within every expression that uses it (but
not in the custom code).

CUSTOMIZING VIRMEN

EDITING GUI LAYOUTS

The ViRMEn GUI has three built-in layouts: Experiment, World and Texture. Each of these layouts displays a set of
windows at particular positions within the GUI figure. You can customize the positions where these windows are
displayed to suit your preferences. In addition, you can create your own custom layouts that display any subset of
windows you wish. Information about all layouts recognized by the ViRMEn GUI is stored in the file
defaultLayouts.txt in the “defaults” subfolder of ViRMEn. This file contains a tab-delimited table and is easiest to
edit with a program like Microsoft Excel. To return this file to the “factory” default state, delete it and restart
ViRMEn. ViRMEn will automatically create a new defaultLayouts.m file matching the “factory” default file.

MODIFYING THE EXPERIMENT, WORLD AND TEXTURE LAYOUTS

To edit built-in layouts (Experiment, World and Texture), we will change values in the row entitled “Default” of the
table in defaultLayouts.txt.

The Experiment layout includes three windows: Custom variables, Experiment properties, and Worlds menu. These
are hard-wired into the Experiment layout – you cannot remove any of these three windows or add any window
other than these three. (If you want more flexibility, see creating a custom layout). To change the position of one
of these windows within the Experiment layout, find the name of the window among column headers of the table.
Underneath, you will find sub-headers titled x, y, w, and h. These specify the bottom-left corner of the window (x,
y), the width w, and the height h in normalized units.

Similarly, the World layout contains three windows: Object/world properties, World sketch, and World drawing.
Changing the coordinates of any of these windows will affect the World layout. The Texture layout also contains
three windows: Shape properties, Texture sketch, and Texture drawing.

CREATING A CUSTOM LAYOUT

You can create your own layout other than the three built-in ones (Experiment, World and Texture). To do so, add
a new row to the table in defaultLayouts.txt. In the first column of the new row, type in the title of your layout. To
include a window in your layout, fill in the four values under the header of the desired window name: x, y, w, and
h, specifying the bottom-left corner of the window (x, y), the width w, and the height h of the window in
normalized units. For windows that you don’t want included in your layout, fill all cells with dashes (-).

For example, let’s create a layout “myLayout” that includes only the Experiment properties window filling up the
entire figure. The table would look like this (the values you would add to the existing table are emphasized in red):

- variablesTable - - - experimentProperties - - - worldsMenu - - - objectProperties - - -



Name x y w h x y w h X y w h x y w H

Default 0 0.5 1 0.5 0 0 0.25 0.5 0.25 0 0.75 0.5 0 0 0.25 1

myLayout - - - - 0 0 1 1 - - - - - - - -

45
OPENING A CUSTOM LAYOUT

After creating a custom layout, restart the ViRMEn GUI. Your new layout will be available in the Layout dropdown
menu.

EDITING THE DEFAULT TEMPLATE CODE

When you save a newly created experiment, ViRMEn automatically creates a “template” .m file for your MATLAB
code containing the initialization, runtime, and termination functions. You can change the default template file –
for instance, to include code that you always want to have in all of your experiments. The default template file is
called defaultVirmenCode.m and located in the “defaults” subfolder of ViRMEn. Changing this file will affect every
new experiment you create. To return this file to the “factory” default state, delete it and restart ViRMEn. ViRMEn
will automatically create a new defaultVirmenCode.m file matching the “factory” default file.

CHANGING DEFAULT MOVEMENT AND TRANSFORMATION FUNCTIONS

The movement function and the transformation function are typically specific to a particular experimental setup
and never change. You may therefore want to set the default movement and transformation functions so that the
ViRMEn GUI assigns them automatically to new experiments you create. Default functions are listed in the
defaultFunctions.txt file located in the “defaults” subfolder of ViRMEn. This is a tab-delimited text file. Simply
change names of functions to those you want to use. To return functions to “factory” defaults, delete the
defaultFunctions.txt file and restart ViRMEn. ViRMEn will automatically create a new defaultFunctions.txt file with
“factory” defaults.

CHANGING DEFAULT PROPERTY VALUES

You can customize certain default properties to change the behavior of the ViRMEn GUI. The following table lists
these properties. Default property values are listed in the defaultProperties.txt file located in the “defaults”
subfolder of ViRMEn. This file is a tab-delimited text file best edited with a program like Microsoft Excel. To return
all default values to “factory” defaults, delete the defaultProperties.txt file and restart ViRMEn. ViRMEn will
automatically create a new defaultProperties.txt file with all “factory” defaults.

Property Explanation “Factory” default value

Windows-related properties. Each of these properties is a 1xN array indicating the value for each of the N windows
used by default. If a single scalar value is used instead of an array, that value is applied to all windows.
windowRendering3D Boolean indicating whether the window contains 3D true
rendering of the world
windowTransformation Index of the transformation function displayed in the 1 (ignored when
window windowRendering3D = true)

46
windowPrimaryMonitor Boolean indicating whether the window is displayed in true
the primary monitor
windowMonitor Index of the monitor that displays the window 1 (ignored when
windowPrimaryMonitor =
true)
windowFullScreen Boolean indicating whether the window is full screen true
windowLeft window position in pixels 0 (ignored when
windowFullScreen = true)
windowBottom window position in pixels 0 (ignored when
windowFullScreen = true)
windowWidth window size in pixels 0 (ignored when
windowFullScreen = true)
windowHeight window size in pixels 0 (ignored when
windowFullScreen = true)
windowAntialiasing level of antialiasing (number of samples) 0 (no antialiasing)

Other properties
worldXLim limits of the x-axis on the world sketch [-100 100]
worldYLim limits of the y-axis on the world sketch [-100 100]
worldBackgroundColor world background color [0 0 0] (black)
worldTransparency world transparency 0 (off)
startLocation starting location and view angle of the animal, in the [0 0 0 0]
form [x, y, z, viewAngle]
tiling amount of texture tiling on the surface of an object, in [5 5]
the form [vertical horizontal]
edgeRadius radius of a physical boundary (edge) around an object NaN (no edge)
triangulationRefining triangulation refining parameter, in the form [vertical [1 1]
horizontal]
triangulationGrid triangulation grid size parameter, in the form [vertical
horizontal]
showTriangulation Boolean indicating whether the triangulation mesh of 1
a texture should be visible
triangulationColor color of the triangulation mesh [1 0 0] (red)
textureTilable Booleans indicating whether a texture is tilable, in the [1 1]
form [vertical horizontal] (tilable in both dimensions)
showWireframe Boolean indicating whether the wireframe mode 0
should be used instead of full 3D rendering.

CREATING NEW OBJECT TYPES

To create a new object type, you need to write a MATLAB class definition describing that object. Refer to MATLAB
help for all class programming information. The class must be a subclass of the virmenObject class. In order to be
recognized by ViRMEn, the class definition file should be placed in the “objects” subfolder of ViRMEn.

The class can have any custom properties you wish your object to have, as well as the default values of these
properties. Properties that have the SetObservable meta-property set to true will be displayed by the ViRMEn GUI,
and the user will be able to edit them.

In addition, the class definition must contain 5 required methods, described below.

47
1. creation method. This method must have the same name as the class itself. It will be executed each
time the object of your class is created. You might want to set iconLocations and the helpString in this
method.
2. getPoints. This method must query the user for object locations and set the x and y properties of the
class.
3. coords2D. This method must return the wireframe coordinates of the object that will be drawn when
the object is sketched in the top 2D view or the wireframe 3D view. The form is [x y z] =
coords2D(obj); x, y, and z should be Nx1 arrays containing the x and y coordinates of the line
segments.
4. coords3D. This method should return a structure containing the triangulation of your object. The
form is st = coords3D(obj). The structure st must have the following fields:
vertices: coordinates of all triangle vertices (Nx2 matrix of x and y values)
triangulation: indices of vertex triplets that are linked together to create triangles (Mx3 array
matrix of triplet). A single vertex can be shared by multiple triangles.
cdata: color and transparency values of all triangle vertices (Nx4 matrix of RGB and Alpha
values). A triangle is assigned the color and transparency of its last (3rd) listed vertex.
5. edges. This method must return the coordinates of all physical edges associated with the object. The
format edge = edges(obj) should return an Nx4 matrix, where every row is in the form [x1 y1 x2 y2]. If
x1=x2 and y1=y2, ViRMEn will draw a circular edge of specified radius around (x1, y1). If these value
are not equal, ViRMEn will draw an edge at the specified radius around a line segment connecting
(x1, y1) and (x2, y2).

To see some examples, looks at some shape classes already available in the “objects” subfolder of ViRMEn. Once
you create a new object class, restart ViRMEn. Your class should appear in the Add object dropdown menu.

CREATING NEW SHAPE TYPES

To create a new shape type, you need to write a MATLAB class definition describing that shape. Refer to MATLAB
help for all class programming information. The class must be a subclass of the virmenShape class. In order to be
recognized by ViRMEn, the class definition file should be placed in the “shapes” subfolder of ViRMEn.

The class can have any custom properties you wish your shape to have, as well as the default values these
properties. These properties will be recognized by the ViRMEn GUI, and the user will be allowed to edit them. All
properties should have the SetObservable meta-property set to true.

In addition, the class definition must contain 3 required methods, described below

1. creation method. This method must have the same name as the class itself. It will be executed each
time the shape of your class is created. You might want to set iconLocations and helpString in this
method.
2. getPoints. This method must query the user for shape locations and set the x and y properties of the
class.
3. coords2D. This method must return the actual 2D coordinates of the line segments that make up the
shape. The form for calling this method on shape s is [x y] = coords2D(s); x and y should be Nx1 arrays
containing the x and y coordinates of the line segments.

48
To see some examples, looks at some shape classes already available in the “shapes” subfolder of ViRMEn. Once
you create a new shape class, restart ViRMEn. Your shape should appear in the Add object dropdown menu.

VIRMEN MACROS

A macro is a custom user-written routine that can be executed within the ViRMEn GUI. By writing a macro, you
can automate certain tasks that are otherwise menial or time consuming in the GUI. To run an existing macro,
select it from the Run macro dropdown list. To change any settings related to an existing macro, select that macro
from the Macro settings dropdown list.

CREATING A NEW MACRO

Each macro is defined by a user-written MATLAB class. This class must be a subclass of the virmenMacro class and
placed in the “macros” subfolder of ViRMEn in order to be recognized. Whenever the ViRMEn GUI starts, an object
of each custom macro subclass is created. This object persists through the entire GUI session and can be either
modified (using the settings method, described below) or executed (using the run method). Please refer to
MATLAB help for all information about class programming, or simply mimic the examples shown below.

The following are the basic properties of the virmenMacro class. Additional properties can be defined by the user-
written macro subclass and used to store any information specific to that macro.

- exper: handle of the experiment currently open in the GUI. This property can be modified to make
arbitrary changes to the worlds, objects, textures, etc. The GUI is updated immediately after execution of
the macro in order to implement these changes.
- guiState: an object of the class virmenGUIState that contains various GUI-related variables. Changing
these variables will implement the corresponding changes in the GUI immediately after execution of the
macro. The following are the properties of guiState

selectedWorld: index of the world that is currently selected in the GUI


selectedObject: index of the object that is currently selected in the GUI, or 0 if no object is selected.
selectedShape: index of the shape in the texture that is currently selected in the GUI, or 0 if no shape
is selected.
showTriangulation: Boolean indicating whether the triangulation mesh is displayed in the GUI.
triangulationColor: 1x3 array indicating the color of the triangulation mesh.
worldXLim: 1x2 array indicating the x limits of the world sketch axes.
worldYLim: 1x2 array indicating the y limits of the world sketch axes
textureXLim: 1x2 array indicating the x limits of the texture sketch and drawing axes.
textureYLim: 1x2 array indicating the y limits of the texture sketch and drawing axes.
filename: name of the file currently open in the GUI. This also serves as the default file name for
saving the experiment to a file.
showWireframe: Boolean indicating whether the world is rendered in the wireframe mode in the GUI.

- string: text displayed in the ViRMEn GUI to describe the macro. This string can be left empty (the default);
in this case, the name of the .m file that defines the macro is used.

49
- icon: a 16x16x3 uint8 matrix that defines an icon used for the macro by the ViRMEn GUI. If this matrix is
empty (the default), a standard macro icon (red gear) is used.
- shortcut: a character that defines a keyboard shortcut that can be used to execute the macro. For
example, if shortcut = ‘d’, then the macro can be executed by pressing Ctrl+D (Windows) or Command+D
(Mac). If empty (the default), no shortcut is defined.

The following are the 3 methods of the virmenMacro class. All three methods are optional and can be
omitted.

- creation method: This method must have the same name as the macro itself and gets executed once
when the ViRMEn GUI starts. Use this method to define properties “string,” “icon,” and “shortcut” (if
necessary) or the values or any custom properties specific to the macro.
- run: This method is run whenever the macro is executed via selection from the Run macro dropdown list
in the GUI. Use this method to modify the “exper” property (in order to make changes to the experiment)
or the “guiState” property (in order to make changes in the GUI).
- settings: This method is run whenever the macro is selected from the Macro settings dropdown list in the
GUI. Use this method to modify any custom properties that are specific to the macro. This is a good place
to allow the user to input values for these properties (see examples below).

EXAMPLE: AUTOMATICALLY COMPUTING ALL TEXTURES

The following is a simple example that cycles through all textures defined in the current experiment and computes
them. This is useful, for example, if multiple textures use a custom variable (e.g. contrast) and all need to be
recomputed when that variable changes. This example macro does not have any custom properties that can be set
by the user and only defines a “run” method (no “settings” or creation methods).

This code must be saved as macroComputeAllTextures.m and placed in the “macros” subfolder
% Define macro as a subclass of the virmenMacroClass
classdef macroComputeAllTextures < virmenMacro
methods
% This function is executed whenever the user runs the macro
function run(macro)
% Obtain all components of the current experiment
items = macro.exper.descendants;
% Find and compute all textures
for indx = 1:length(items)
if isa(items{indx},'virmenTexture')
items{indx}.compute;
end
end
end
end
end

EXAMPLE: SNAPPING OBJECTS TO A GRID

The following example cycles through all objects in the currently selected world and snaps their coordinates to a
grid. The example is a bit more complex than the previous one: it defines properties related to the size of the grid
and allows the user to modify them using the “settings” method. It also defines a custom string and a custom icon
for the macro instead of using the defaults, as well as a shortcut (Ctrl+D or Command+D) that can be used to
execute the macro.

50
This code must be saved as macroSnapToGrid.m and placed in the “macros” subfolder
% Define macro as a subclass of the virmenMacroClass
classdef macroSnapToGrid < virmenMacro
% Define custom properties that define the grid size
properties
xGridSize = 1;
yGridSize = 1;
end

methods
% This function is run when the macro is created (at ViRMEn startup)
function macro = macroSnapToGrid
% Define a string to be displayed in the GUI menus
macro.string = 'Snap objects to grid';
% Define a shortcut (Ctrl+D or Command+D)
macro.shortcut = 'd';
% Define an icon (evenly spaced black dots on white background)
icon = uint8(255*ones(16,16,3));
icon(1:3:16,1:3:16,:) = 0;
macro.icon = icon;
end

% This function is run whenever the user selects macro settings


function settings(macro)
% Use a dialog box to obtain the grid size from the user
defaults = {num2str(macro.xGridSize),num2str(macro.yGridSize)};
answer = inputdlg({'x grid','y grid'},'Grid size',1,defaults);
if ~isempty(answer)
macro.xGridSize = str2double(answer{1});
macro.yGridSize = str2double(answer{2});
end
end

% This function is executed whenever the user runs the macro


function run(macro)
% Obtain a list of all objects in the current world
worldNum = macro.guiState.selectedWorld;
objects = macro.exper.worlds{worldNum}.objects;
for indx = 1:length(objects)
% Round off object coordinates to the nearest grid point
objects{indx}.x = round(objects{indx}.x / macro.xGridSize) * macro.xGridSize;
objects{indx}.y = round(objects{indx}.y / macro.yGridSize) * macro.yGridSize;
end
end
end
end

SPEEDING UP MACROS

By default, the ViRMEn GUI is refreshed after a macro is executed. This means that every window in the GUI is
recomputed and redrawn. In addition, any modifications made by the macro are archived in history and can be
reverted using the Undo function. These operations can be time consuming and, in certain cases, unnecessary. For
example, imagine a macro that changes the names of objects in a world. Because this operation has no effect on
the appearance of the world, there is no reason to re-render the world and to refresh the world drawing window
in the GUI. Another example might be a macro that computes certain statistics of a world and displays them to the
user. Such a macro has no effect on the GUI and should neither refresh the GUI nor be archived in history.

The virmenMacro class has the following properties in addition to the basic properties described above. To speed
up a macro, some or all of these properties can be changed to false (they are all true by default). Set these
properties in the creation method of the macro.

51
Properties indicating that a particular window in the GUI (e.g. the variables table) should be updated and refreshed
after a macro is executed:
updateExperimentProperties
updateVariablesTable
updateWorldSketch
updateObjectProperties
updateWorldsMenu
updateWorldDrawing
updateTextureSketch
updateShapeProperties
updateTextureDrawing
Property indicating that changes made by the macro should be archived in history and be revertible using the Undo
command:
trackHistory

52
APPENDIX

DERIVATION OF A TRANSFORMATION FUNCTION

We will derive a transformation function for the screen in the shape of an inverted cone. The procedure we will
use is applicable for any other radially symmetric screen, such as a cylinder or a torus.

Note that ViRMEn is compatible with screens that are not radially symmetric, but you will have to derive a
transformation function for each screen. For some particularly complex screens, it may be easiest to approximate a
transformation function with empirical measurements (i.e., create a list of monitor locations and corresponding
virtual reality screen locations, then fit relationship between them with some reasonably simple function).

At first, we define some constants that describe the shape of the screen and the projection geometry:

vertical distance from the animal to the apex of the cone. (Note that the bottom of the cone can be truncated,
but we will nonetheless derive the equation as if the entire cone were intact.)
height of the screen
radius of the screen at the top
distance along the optical axis from the animal to the focal point of the projector
maximum angle from the optical axis that the projector can project in every direction

projector
focal point 𝜑

animal
point S
𝜃
H
screen
B

1. Suppose the projector projects a point at some angle from the optical axis. This point will be
projected at a location S on the screen. To the animal, point S appears to be at some elevation angle
above the horizon. Our first goal is to determine the relationship between angle and angle .
Suppose point S is located at some radial distance from the animal. This means that its vertical
distance from the animal is

The shape of the screen can be described by the linear equation

53

Substituting the values of and ,

Solving this equation for ,


From the projector geometry,

Substituting in the values of from the previous equation,

( )
2. Now, we will determine how the angle is related to the location of a point on the monitor screen.
ViRMEn normalizes monitor coordinates such that the shorter (vertical) monitor dimension extends
from -1 to 1. This means that points at a radial distance of 1 from the center of the monitor display at
a maximum projection angle of . Suppose that the projection angle of corresponds to a radial
distance of on the monitor.
From trigonometry,

Substituting these values into the previous equation

where


Values of and are constants that can be calculated from the dimensions of a setup and the
specifications of a projector – for instance, for my rat virtual reality setup, the values are
and .
3. We’ve now derived at an expression for the relationship between and . This relationship can
easily be transformed into a relationship between 3D world coordinates and 2D monitor coordinates.
Suppose there is a point is the 3D world. The tangent of the elevation angle can be expressed
as

where √ (remember that the animal is defined to be always at the origin). Thus

This indicates that a radius in the 3D world is scaled by ⁄ to produce radius on the
computer monitor. Because the projection is radially symmetric, the same scaling factor must apply
to and components of the coordinates
⁄( √ )
⁄( √ )
Where are the coordinates of the point on the monitor screen. These equations describe
the relationship between a point in the world and a point on the monitor; this is
exactly what a transformation function requires.
Putting this into MATLAB code,

54
function coords2D = transformConical(coords3D)

% define constants that describe setup geometry


alpha = 2.0349;
beta = -0.98988;

% create an output matrix of the same size as the input


% first two rows are x and y
% the third row indicates whether the location should be visible
coords2D = zeros(size(coords3D));

% by default, make all points visible


coords2D(3,:) = true;

% calculate radius
r = sqrt(coords3D(1,:).^2 + coords3D(2,:).^2);

% calculate a scaling factor


s = 1./(alpha*r + beta*coords3D(3,:));

% if a point is outside of the screen, set the scaling factor such that the
% point is plotted at the edge of the screen, and make the point invisible
% (if all 3 vertices of a triangle are invisible, it is not plotted)
f = find(s < 0 | r.*s > 1);
s(f) = 1./r(f);
coords2D(3,f) = false;

% calculate x and y components using the scaling factor


coords2D(1,:) = s.*coords3D(1,:);
coords2D(2,:) = s.*coords3D(2,:);

EXTENSION TO ALL RADIALLY SYMMETRIC SCREENS: TOROIDAL EXAMPLE

For any other radially symmetric screen (e.g., cylinder or torus), it is necessary to calculate a different relationship
between and (see steps 1 and 2 in the above derivation). This does not have to be derived, but can be
calculated empirically by plotting circles of varying radii on the monitor and measuring at what elevation angles
they appear on the virtual reality screen.

For example, for a mouse toroidal screen, this relationship was measured to be approximately linear

(See Forrest Colman’s Ph.D. dissertation).

Multiplying the right side of this equation by ⁄ , we again obtain version of the equation where the radius is
scaled to produce

[ ⁄ ]

Here, √ and ⁄ . The derivation of then follows the same procedure as for the conical
screen:

[ ⁄ ]

[ ⁄ ]

which are expressions of and in terms of , , and required by transformation function.

The same process can be followed for any other radially symmetric screen.

55

You might also like