ray tracer objects to reduce the complexity of the ray tracer we will try to avoid duplicating code...

36
Ray tracer objects To reduce the complexity of the ray tracer we will try to avoid duplicating code as much as possible. We will find many cases where we do essentially the same thing no matter what type of “object” we are working with. Underlying our code we need data structures that can accommodate the data that we need to retain, and that can be easily accessed.

Upload: basil-grant

Post on 04-Jan-2016

214 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: Ray tracer objects To reduce the complexity of the ray tracer we will try to avoid duplicating code as much as possible. We will find many cases where

Ray tracer objects

To reduce the complexity of the ray tracer we will try to avoid duplicating code as much as possible. We will find many cases where we do essentially the same thing no matter what type of “object” we are working with.

Underlying our code we need data structures that can accommodate the data that we need to retain, and that can be easily accessed.

Page 2: Ray tracer objects To reduce the complexity of the ray tracer we will try to avoid duplicating code as much as possible. We will find many cases where

The entity_t structure

• Whether we are working with a sphere, plane, light or “window” we will find that certain fields are common to all of these types. We will find that every entity has a “name”, a “type” and a “type code”. So we will define a base type called an entity_t. The typedef for an entity_t is:

typedef struct entity {int magic; /* magic number */char *name; /* left_wall, center_sphere */char *type; /* plane, sphere, ... */int code; /* Code for object type */void *entDerived; /* Pointer to object derived from an entity_t */

} entity_t;

Page 3: Ray tracer objects To reduce the complexity of the ray tracer we will try to avoid duplicating code as much as possible. We will find many cases where

Ray tracer objects

• The name pointer will point to the unique name of this object,

• the type field will point to a C-string containing the type of the object (e.g. “plane”, “sphere”, “window”, etc.),

• and the code field contains the unique integer code for an object type (e.g. a “plane” is code 1, a “sphere” code 2 and a “window” code 0. The code field is convenient to use with a switch statement.

• The use of the magic field will be described later – each object type will have a unique magic number that we will use for error checking (don’t get your hopes up – it’s really not magic).

Page 4: Ray tracer objects To reduce the complexity of the ray tracer we will try to avoid duplicating code as much as possible. We will find many cases where

The entity_t structure

• Obviously, objects as diverse as planes, spheres, lights and windows differ in the type of data we need to know about each.

• Defining the possible fields that would be needed for every possible object type in entity_t would be difficult to do, wasteful of space, and difficult to maintain when we try to add new types (like rectangles or triangles).

• Rather than adding fields to the entity_t for new types, we will instead use a pointer to point to another structure that contains data that is unique for that object type. That is where the void pointer, entDerived, comes in. This pointer points to data that is unique to this particular type.

Page 5: Ray tracer objects To reduce the complexity of the ray tracer we will try to avoid duplicating code as much as possible. We will find many cases where

Scene objects

• A scene object is an object that may be visible in the image, such as a sphere or a plane.

• Once again we find that spheres and planes share certain common data – so rather than immediately defining sphere and plane objects, we instead define an intermediate object from which plane and sphere objects can be derived.

• Every shape object has a color, a value that indicates how much light it reflects if illuminated by a light source (other than the ambient light) and how reflective its surface is.

Page 6: Ray tracer objects To reduce the complexity of the ray tracer we will try to avoid duplicating code as much as possible. We will find many cases where

Scene objects

• We will define a sobj_t as:

typedef struct sceneobj {

int magic; /* "Magic" number*/

/* Surface data */

pixel_t color; /* surface color */

intensity_t diffuse; /* light reflection */

intensity_t reflective; /* ray reflection */

void *sobjDerived; /* Pointer to derived scene object data */

} sobj_t;

Page 7: Ray tracer objects To reduce the complexity of the ray tracer we will try to avoid duplicating code as much as possible. We will find many cases where

Scene objects

• the color, diffuse (reflected light), and reflective fields describe the characteristics of the surface of the shape object, whether it is a plane, sphere, rectangle, etc. The interpretation of these fields will be given later.

• Also note the pointer field, sobjDerived

– This pointer will point to yet another structure that will contain data that is unique to the object type, such as the radius of a sphere or the orientation (normal) of a plane.

Page 8: Ray tracer objects To reduce the complexity of the ray tracer we will try to avoid duplicating code as much as possible. We will find many cases where

The plane object

• Next we will define an infinite plane (as opposed to a plane with dimensions like a rectangle or triangle).

• An infinite plane’s position and orientation in 3-D space can be described in several ways, e.g. 3 points on the plane, or one point and a vector that is perpendicular to the plane (this is called the normal).

• This semester we will use:

1. point – the coordinates of any point on the plane,

2. orient1 – a vector that is parallel to the plane,

3. orient2 – a second vector that is also parallel to the plane but not parallel to orient1.

• We will use orient1 and orient2 to compute the plane’s normal, which establishes the orientation of the plane. The point establishes the plane’s location in 3-D space.

Page 9: Ray tracer objects To reduce the complexity of the ray tracer we will try to avoid duplicating code as much as possible. We will find many cases where

The plane object

We will define a plane_t object that will be pointed to by the derived field of a shape_t as:

typedef struct plane_type {

int magic; /* magic number */

vector_t orient1; /* Orientation 1 of plane */

vector_t orient2; /* Orientation 2 of plane */

point_t point; /* Any point on surface */

vector_t normal; /* Computed normal to plane */

void *planeDerived; /* Pointer to derived object data */

} plane_t;

Page 10: Ray tracer objects To reduce the complexity of the ray tracer we will try to avoid duplicating code as much as possible. We will find many cases where

The plane object

• Note the fields to hold the plane’s orientation and point values. We also include a field to hold the normal vector, which will be computed.

• We don’t need to worry about a plane_t’s derivedPlane field right now, but later, if we implement rectangles and triangles, we will derive them from a plane_t.

Page 11: Ray tracer objects To reduce the complexity of the ray tracer we will try to avoid duplicating code as much as possible. We will find many cases where

The plane object

• So, if we create a plane object, we actually create a sobj_t and a plane_t. Consider the following example.

• Individual plane, sphere and other visible objects will be added to the sobjList list pointed to in the scene_t structure.

Page 12: Ray tracer objects To reduce the complexity of the ray tracer we will try to avoid duplicating code as much as possible. We will find many cases where

The sphere object

• In a like manner, we will also derive a sphere from the sobj_t object. For a sphere we need two pieces of information:

1. center – the coordinates of the center of the sphere,

2. radius – the radius of the sphere.• We will define a sphere_t that has the following format:

typedef struct sphere_type {

int magic; /* magic number */

point_t center; /* Location of the */

double radius; /* distance from center to surface */

void *sphereDerived; /* Pointer to derived object data */

} sphere_t;

Page 13: Ray tracer objects To reduce the complexity of the ray tracer we will try to avoid duplicating code as much as possible. We will find many cases where

The sphere object

• A sphere object could be illustrated by the following example

• Spheres and planes will be added to the sobjList in the scene_t structure.

Page 14: Ray tracer objects To reduce the complexity of the ray tracer we will try to avoid duplicating code as much as possible. We will find many cases where

The scene_t object

• We need to maintain a LOT of data about the virtual scene and the environment it lives in.

• This data needs to be readily accessible to code in various parts of the ray tracer. We will find that it is convenient for all data about the scene and all objects to be accessible from one central data structure, which we will call the scene. The scene is described in the scene_t structure:

typedef struct scene {

int magic; /* magic number */

entity_t *window; /* Window data */

image_t *picture; /* output image */

/** Lists of objects in the virtual world **/

list_t *sobjList; / * scene objects list */

list_t *lightList; /* "Lights" list */

} scene_t;

Page 15: Ray tracer objects To reduce the complexity of the ray tracer we will try to avoid duplicating code as much as possible. We will find many cases where

The scene_t object

• The window contains information about the window that we are rendering. This is further described in the next section.

• The picture field is a pointer to an image_t that will hold data about the output image we are creating, including the pixel data. Recall that image_t includes the pixel dimensions of the image (columns and rows), brightness and a pointer to the pixel data.

• The sobjList points to a linked list of scene objects (e.g. planes, spheres, etc.) and the lightList points to a linked list of light sources in the virtual world.

• An illustration of a scene object and the entities it point to is:

Page 16: Ray tracer objects To reduce the complexity of the ray tracer we will try to avoid duplicating code as much as possible. We will find many cases where

The scene_t object

• An illustration of a scene object and the entities it points to:

sobj_t * scene

struct scene { window_t *window; image_t *picture; list_t *sobjList; list_t *lightsList;

struct window { char *name; double windowWidth; double windowHeight; point_t viewpoint; int pixelColumns; …

struct image_type { int columns; int rows; int brightness; pixel_t *image;

"myimage"

list of virtual world visible scene objects

list of lights

Page 17: Ray tracer objects To reduce the complexity of the ray tracer we will try to avoid duplicating code as much as possible. We will find many cases where

The window_t object

• The window_t object keeps track of window dimensions, global lighting, viewpoint coordinates, etc. related to the particular image we are trying to generate. A window_t has the following format:

typedef struct windowType {

int magic; /* Magic number */

double windowWidth; /* Window width in world units */

double windowHeight; /* Window height in world units */

int pixelColumns; /* Window width in pixels */

intensity_t ambient; /* Ambient lighting on scene */

point_t viewPoint; /* Viewpoint coordinates */

} window_t;

Page 18: Ray tracer objects To reduce the complexity of the ray tracer we will try to avoid duplicating code as much as possible. We will find many cases where

The window_t object

• The fields windowWidth and windowHeight are the dimensions of the window (image) that we are creating in “world” coordinate units. More on this later. These two values give us the relationship between the width and height of the image, e.g. worldWidth = 6 and worldHeight = 6 would be a square image, and worldWidth = 8 and worldHeight=4 would give us a rectangular window that was twice as wide as high. We will relate this to pixels later.

• The viewpoint is the coordinates from which the virtual world is being viewed.

Page 19: Ray tracer objects To reduce the complexity of the ray tracer we will try to avoid duplicating code as much as possible. We will find many cases where

The window_t object

• The ambient field is the intensity of the general “wash” of light that is hitting all objects in the scene with the same intensity level.

– Ambient light is why we can see something that is totally blocked from a direct light source.

• The pixelColumns field holds the number of pixel columns in the image. We will find that this value is duplicated in the image_t – but for reasons that will become apparent later, we need a “holding” area for the value of the number of pixel columns until the image_t is created.

Page 20: Ray tracer objects To reduce the complexity of the ray tracer we will try to avoid duplicating code as much as possible. We will find many cases where

The image_t object

• We will use the same image_t data type that we used in homework 2, i.e.:

typedef struct imageType {

int magic; /* magic number */

pixel_t *image; /* pointer to pixel data */

int columns; /* number of pixel columns */

int rows; /* number of pixel rows */

int brightness; /* image brightness */

} image_t;

Page 21: Ray tracer objects To reduce the complexity of the ray tracer we will try to avoid duplicating code as much as possible. We will find many cases where

The image_t object

• As in homework 2, the image_t contains data about an image, including its dimensions, (columns and rows), brightness and a pointer to the pixel data of the image.

• We will use the getimage() procedure from homework 2 to create a new image_t.

Page 22: Ray tracer objects To reduce the complexity of the ray tracer we will try to avoid duplicating code as much as possible. We will find many cases where

Summary Overview of Data StructuresThe following illustrates the relationship between the various structures.

Page 23: Ray tracer objects To reduce the complexity of the ray tracer we will try to avoid duplicating code as much as possible. We will find many cases where

Examples of Accessing Object fields

• Here are several examples of how we might access fields from the structures pointed to by the scene_t.

• Assume scene is pointing to an instance of a scene_t. Suppose we want to set imageRows to the number of pixel rows in the image. We can get this value from the image_t and could say:

imageRows = scene -> picture -> rows;• That’s fairly straight-forward.

Page 24: Ray tracer objects To reduce the complexity of the ray tracer we will try to avoid duplicating code as much as possible. We will find many cases where

Examples of Accessing Object fields

• Now suppose we want to set the windowWidth to 5. The statement:

scene->window->entDerived->worldWidth = 5;

would generate compile errors because the compiler can’t figure out what type of data “entDerived” is pointing to. We could use type-casting to keep the compiler happy:((window_t *)(scene->window->entDerived))->worldWidth = 5;

• Or we could break this up into several statements:window_t *window;

window = scene->window->entDerived;

window->worldWidth = 5;

Page 25: Ray tracer objects To reduce the complexity of the ray tracer we will try to avoid duplicating code as much as possible. We will find many cases where

Examples of Accessing Object fields

• We didn’t need typecasting in this case because both “window” and “scene->derived->entDerived” are of type pointer – they just differ in the type of data they point to.

• You can always assign a pointer to a pointer.

Page 26: Ray tracer objects To reduce the complexity of the ray tracer we will try to avoid duplicating code as much as possible. We will find many cases where

Examples of Accessing Object fields

• Now let’s assume the first shape object in the sobjList list is a plane, and we want to set the x coordinate of the plane object’s point to 5.

• We want to make use of the list functions that we previously developed. So to get access to the first entry of the sobjList list we would first create an iterator and then use a l_next() call. So to fetch the first object from the list we could do:

interator_t *iter;

entity_t *ent;

iter = createIterator(scene->sobjList);

sobj = l_next(iter);• So ent should now be pointing to the first scene object. So

assuming that object is a plane object, we could set the x coordinate of the plane object’s point to 5 with:((plane_t *)((sobj_t *)(ent->entDerived))->sobjDerived)->point.x = 5;

Page 27: Ray tracer objects To reduce the complexity of the ray tracer we will try to avoid duplicating code as much as possible. We will find many cases where

Examples of Accessing Object fields

• First let me explain, and then offer an alternative. The l_next() function returned a pointer to the entity_t of the first object in the objects list, so ent is pointing to the entity_t. The entDerived field points to the associated sobj_t, but it is a void pointer, so we have to type cast the pointer telling the compiler that this is a pointer to something of type sobj_t. In the sobj_t the sobjDerived pointer is pointing to the plane specific information that contains the point data. But again this pointer is also declared as being of type void – we don’t specify what type of object it is pointing to.

• Without additional information the C compiler would have no way of knowing that sobjDerived was pointing to a plane_t. So we must use typecasting in this case, which is the reason for the (plane_t *). We also have to be careful with parentheses to make it clear what is being modified by the typecast. Finally x is a field in the vector_t structure, hence the point.x.

Page 28: Ray tracer objects To reduce the complexity of the ray tracer we will try to avoid duplicating code as much as possible. We will find many cases where

Examples of Accessing Object fields

• I personally find writing a line like this confusing and prone to errors, and something that makes me want to throw my laptop out of the window. An alternative is to break it up. Consider the following alternative:

sobj_t *sobj = ent->entDerived;

plane_t *planePtr = sobj->sobjDerived;

planePtr->point.x = 5;• So first we set the pointer sobj to point to the scene object’s

sobj_t. Then we set planePtr to the derived field. Note this time we did not need to use typecasting. In this case we are just assigning a pointer value to a pointer value, so there is no ambiguity.

• Now that we have planePtr pointing to the plane_t structure, we can access fields within the plane_t directly.

Page 29: Ray tracer objects To reduce the complexity of the ray tracer we will try to avoid duplicating code as much as possible. We will find many cases where

Examples of Accessing Object fields

• Another advantage of this approach is that if we are going to be writing additional code that also accesses fields in the plane_t (or for that matter the sobj_t), we already have pointers set.

• We could, for example, now write additional statements like:

planePtr->normal.z = 1;

sobj->color.y = 100;

fscanf(inFile, “%lf %lf %lf”, &planePtr->point.x,

&planePtr->point.y,

&planePtr->point.z);

Page 30: Ray tracer objects To reduce the complexity of the ray tracer we will try to avoid duplicating code as much as possible. We will find many cases where

"magic" Numbers• Notice that each object type (typedef) we have defined has

included a field called “magic”. • This is a common debugging technique. A very common

programming error is to think a pointer is pointing to one type of object, but in fact the pointer is pointing at a totally different type of object

• For example we might think that “ptr” is pointing to a plane_t, but in fact it is pointing to a sphere_t. This would clearly cause problems that, at best, will result in broken images, at worse, seg faults.

Page 31: Ray tracer objects To reduce the complexity of the ray tracer we will try to avoid duplicating code as much as possible. We will find many cases where

"magic" Numbers

• So the idea of an “magic” number is to assign this field a value that is unique for this type of object. The value of the number is not important as long as it isn’t a value that might happen by accident (e.g. 0 would be a terrible choice for a magic number, as would 1).

• But something like 5322158 is not a number that is likely to be in the magic field by accident. So let’s say we define magic numbers as follows:

#define ENTITY_T 5351298

#define IMAGE_T 8833547

#define PLANE_T 2337867

#define SCENE_T 1933482

#define SCENEOBJ_T 4658829

#define SPHERE_T 9133242

#define WINDOW_T 3311675

Page 32: Ray tracer objects To reduce the complexity of the ray tracer we will try to avoid duplicating code as much as possible. We will find many cases where

"magic" Numbers

• Once again, the choice of values is totally arbitrary – they just need to be unique and not a likely value to happen by accident.

• So how do we use these? We will see shortly that typedef’d data are allocated and initialized by create…() functions, e.g. createPlane(), createSphere(), etc. The create…() function will malloc space for the data type, and initialize its fields. So

• createPlane() would set the “magic” field of the plane_t to PLANE_T (i.e. 2337867), etc.

Page 33: Ray tracer objects To reduce the complexity of the ray tracer we will try to avoid duplicating code as much as possible. We will find many cases where

"magic" Numbers

• Now when we set a pointer to point to one of these data types, BEFORE we do anything else we should check the magic number to make sure the pointer is pointing to the right data type (i.e. what we think it is pointing to). An easy way to do this is to use the assert() macro, e.g. if “ptr” should be pointing to a plane_t, then:

assert(ptr->magic == PLANE_T);• The argument to assert() is a conditional expression that

has a TRUE or FALSE value.• The assert() is saying this condition MUST BE TRUE. If the

condition is not TRUE, the assert() statement will abort the program.

• The advantage of this is that the program immediately aborts on a bad pointer, rather than at a later point because memory has been overwritten.

Page 34: Ray tracer objects To reduce the complexity of the ray tracer we will try to avoid duplicating code as much as possible. We will find many cases where

"magic" Numbers

• Here is an example of a program aborting because the magic number didn’t match the “expected” value:

~/cs2100/ray1: ./ray1 world.txt output.ppm

ray1: shape.c:65: completeShape: Assertion `obj->magic ==

9912556' failed.

Aborted

Page 35: Ray tracer objects To reduce the complexity of the ray tracer we will try to avoid duplicating code as much as possible. We will find many cases where

"magic" Numbers

• Another example involving traversing multiple pointers to get to a sphere_t is:

entity_t *ent;

sobj_t *sobj;

sphere_t *sphereptr;

ent = l_next(itr);

assert(ent->magic == ENTITY_T);

sobj = ent->entDerived;

assert(sobj == SCENEOBJ_T);

if (ent->code == SPHERE) {

sphereptr = sobj->sobjDerived;

assert(sphereptr->magic == SPHERE_T);

}

Page 36: Ray tracer objects To reduce the complexity of the ray tracer we will try to avoid duplicating code as much as possible. We will find many cases where

"magic" Numbers

• Normally you would test the pointer variable right after it has been set in a procedure.

• Note: You might be concerned about the additional overhead in your program to run these tests. The assert() statements in your program can be disabled without removing them by including the definition:

#define NDEBUG

before the “#include <assert.h>” statement in the code (note the include is in ray.h in the provided code for the raytracer). If this flag is defined the assert() statement generates no code (i.e. no overhead – but no testing either). More on this later when we talk about # compiler directives.