Cubby Multiscreen
Volume Number: 16
Issue Number: 12
Column Tag: QuickDraw 3D Tricks
Cubby: Multiscreen Desktop VR Part III
By Tom Djajadiningrat and Maarten Gribnau
Reading an input sprocket device and calibrating
In this month's final episode of our 'Cubby: Multiscreen Desktop VR' trilogy we explain
how you read the InputSprocket driver from part II, how you use it as input for the
cameras from part I and how you calibrate the input device so that it leads to the
correct head position.
Relating the Virtual Cubby to the Real World
Before we can talk calibration we need to establish how the virtual Cubby relates to
the real world. We have made life easy for ourselves by choosing the same orientation
for the coordinate system of the real Cubby as the virtual Cubby. We also made the
dimensions of the virtual Cubby in QuickDraw3D units equal to the dimensions of the
real world Cubby in millimetres. This is determined by the constants kHalfEdgeLength
and kEdgeLength which are half an edge length and a whole edge length of Cubby
respectively. You can find these constants in MyDefines.h (Listing 1). The Cubby we
built has an edge length of 195mm and so we've given our virtual Cubby an edge length
of 195 QuickDraw3D units. In a sense the scale of the virtual Cubby is arbitrary. As
long as everything is scaled equally (the background planes, the model, the lights and
the camera) you end up with the same perspectives. If you like you can create a virtual
Cubby with an edge length of 1. However, we think that the way we do it here has one
major advantage: the coordinates in QuickDraw 3D units that you see during debugging
are meaningful because you can directly relate them to sizes in millimetres in the real
world. For example, if you end up with a camera position that is 20000 QuickDraw 3D
units from the origin, you know that something has gone haywire because 20 metres is
well out of range of the tracker.
Listing 1: MyDefines.h
// the width and half the width of a Cubby edge in QD3D units
#define kEdgeLength 195
#define kHalfEdgeLength 195/2.0
We use these constants for three things:
1. setting the size of the background planes
2. scaling the model read from disk to Cubby's display space
3. setting the area cut out of the view plane
Setting the size of the background planes
Listing 2 shows the creation of the background planes. Each background plane is a
polygon. The constant kEdgeLength is used to indicate the vertices of a polygon.
Listing 2: DisplaySpace.c
DisplaySpace
TQ3GeometryObject thePolygonZ0, thePolygonX0, thePolygonY0;
TQ3PolygonData theData;
long i ;
TQ3Vertex3D theVerticesZ0[4] = {
0, 0,
0, nil,
kEdgeLength, 0, 0,
nil,
kEdgeLength, kEdgeLength, 0, nil,
0, kEdgeLength, 0,
TQ3Vertex3D theVerticesX0[4] = {
0, 0, kEdgeLength,
nil,
0, 0, 0,
nil,
0, kEdgeLength, 0,
nil,
0, kEdgeLength, kEdgeLength, nil};
TQ3Vertex3D theVerticesY0[4] = {
0, 0, kEdgeLength,
nil,
kEdgeLength, 0, kEdgeLength, nil,
kEdgeLength, 0, 0,
nil,
0, 0, 0,
// create new polygon objects.
// four corners per polygon.
theData.numVertices = 4;
// point to our array of vertices
// for the polygon in the Z=0 plane.
theData.vertices = theVerticesZ0;
// polygon itself has no attributes.
theData.polygonAttributeSet = nil;
// create the polygon.
thePolygonZ0 = Q3Polygon_New(&theData);
// point to our array of vertices
// for the polygon in the X=0 plane.
theData.vertices = theVerticesX0;
// create the polygon.
thePolygonX0 = Q3Polygon_New(&theData);
// point to our array of vertices
// for the polygon in the Y=0 plane.
theData.vertices = theVerticesY0;
// create the polygon.
thePolygonY0 = Q3Polygon_New(&theData);
Scaling the model
We want to scale and translate the model that was read from disk so that it fits within
and is centred within Cubby's display space. This is accomplished through the
procedure ScaleModelToDisplaySpace within the source file ReadModelAndScaleIt.c
(Listing 3). It should look pretty familiar to you as similar procedures are found in
most examples from the QuickDraw 3D SDK. Basically, what we do here is calculate
the bounding box of the model and use the dimensions of the bounding box to scale and
translate the model so that it fits and is centred within Cubby's display space. Let's
look at the code in detail.
The first thing we do is call GetModelBoundingBox after which theViewBBox holds the
bounding box. From the bounding box we can work out its dimensions along the three
world axes. The next thing is a check to see whether all these dimensions are smaller
or equal than kQ3RealZero. This could happen if the file read from disk was a single
point. We want to avoid a bounding box of which all three dimensions are smaller than
zero as that would give scaling problems later. So if it happens we set the dimensions
of the bounding box to a very small number (0,0001). We work out the bounding box'
centre theBBoxCenter by calling Q3Point3D_AffineComb with the box' minimum and
maximum corners. We also calculate theBBoxDiagonal, the length of the diagonal of the
bounding box. We then calculate theDisplaySpaceDiagonal, the length of the diagonal of
Cubby's display space. The ratio of these two lengths gives us the scale factor for the
model so that its bounding box fits within Cubby's display space. Note how we have a
fiddle factor kScaleFineTune. This factor makes sure that the model ends up slightly
smaller than Cubby's display space as it does not look very good when the model
touches the background planes. We chose kScaleFineTune = 0.75 though you can of
course change it should you prefer a tighter or looser fit. We can now work out the
required transformation matrices. The first one, theTransMatrix1, translates the
centre of the model to the origin. The second one, theScaleMatrix, scales the model