## Sample Program morph3d

Sample program morph3d displays a number of morphing pyramid objects in an interactive scene. Each pyramid slowly changes shape from a compact pyramid to a more elongated shape, then back again. You can interactively fly though the scene with the standard keyboard controls used in l3d library: j: Rotate left (rotates around the VUP axis)

l: Rotate right (rotates around the VUP axis)

i: Thrust forward (translates along the VFW axis)

k: Thrust backward (translates along the VFW axis)

J: Roll left (rotates around the VFW axis)

L: Roll right (rotates around the VFW axis)

I: Pitch forward (rotates around the VRI axis)

K: Pitch backward (rotates around the VRI axis)

s: Thrust left (translates along the VRI axis)

f: Thrust right (translates along the VRI axis)

e: Thrust up (translates along the VUP axis)

d: Thrust down (translates along the VUP axis)

v: Cycle through to the next thrust level

ICTjy NOTE The VFW axis is the forward axis along which the camera is looking. The VUP axis is ^^ the axis pointing straight up from the camera. The VRI axis is the axis pointing straight to the right of the camera. Taken as a set, the vectors VRI, VUP and VFW form a left-handed camera coordinate system.

Figure 2-3: Output from program morph3d. Each pyramid slowly changes shape.

 #include #include #include #include #include "../l b/geom/object/object3d.h #include "../l b/geom/polygon/p3 flat.h #include "../l b/tool 2d/screen.h" #include "../l b/tool os/dispatch.h" #include "../l b/raster/rast3.h" #include "../l ib/tool_2d/scrinfo.h" #include "../l b/geom/world/world.h"

#include "../lib/system/fact0_2.h" #include "../lib/pipeline/pi_wor.h" #include "../lib/dynamics/plugins/plugenv.h" #include "shapes.h"

#include <stdlib.h> #include <string.h> #include <stdio.h> #include <math.h> #include <stdarg.h>

class my_world:public l3d_world { public: my_world(void);

my_world::my_world(void) : l3d_world(640,400)

l3d_screen_info *si = screen->sinfo;

camera->VRP.set(0,0,-50,0); camera->near_z = float_to_l3d_real(5.5); camera->far_z = int_to_l3d_real(500);

//- create some pyramid objects for(i=1; i<100; i +=20) { objects[onum=objects.next_index()] = new pyramid(); l3d_screen_info_indexed *si_idx; l3d_screen_info_rgb *si_rgb;

for(int pnum=0; pnum<objects[onum]->polygons.num_items; pnum++) { p = dynamic_cast<l3d_polygon_3d_flatshaded *>(objects[onum]->polygons[pnum]); if(p) {

p->final_color = si->ext_to_native

(rand()%si->ext_max_red, rand()%si->ext_max_green, rand()%si->ext_max_blue);

if (objects[onum]==NULL) exit;

objects[onum]->modeling_xforms.set

( int_to_l3d_real(1), int_to_l3d_real(0), int_to_l3d_real(0), int_to_l3d_real(i), int_to_l3d_real(0), int_to_l3d_real(1), int_to_l3d_real(0), int_to_l3d_real(1), int_to_l3d_real(0), int_to_l3d_real(0), int_to_l3d_real(1), int_to_l3d_real(1), int_to_l3d_real(0), int_to_l3d_real(0), int_to_l3d_real(0), int_to_l3d_real(1) );

objects[onum]->modeling_xform = objects[onum]->modeling_xforms | objects[onum]->modeling_xforms ;

screen->refresh_palette();

l3d_dispatcher *d; l3d_pipeline_world *p; my_world *w;

factory_manager_v_0_2.choose_factories(); d = factory_manager_v_0_2.dispatcher_factory->create();

w = new my_world(); p = new l3d_pipeline_world(w); d->pipeline = p; d->event_source = w->screen;

delete d; delete p; delete w;

Listing 2-2: shapes.h, the header file for the 3D objects for program morph3d

#include "../lib/geom/object/object3d.hM #include "geom/vertex/verint.h"

class pyramid:public l3d_object {

static const int num_keyframes = 2; int keyframe_no;

l3d_two_part_list<l3d_coordinate> *keyframes; l3d_vertex_interpolator interp; bool currently_interpolating;

public: pyramid(void); virtual ~pyramid(void); int update(void);

Listing 2-3: shapes.cc, the implementation file for the 3D objects for program morph3d #include "shapes.h"

#include <stdlib.h> #include <string.h>

pyramid::pyramid(void) : l3d_object(4)

keyframes = new l3d_two_part_list<l3d_coordinate>(4); (*keyframes).original.set (float_to_l3d_real(0.), float_to_l3d_real(0.), float_to_l3d_real(o.), float_to_l3d_real(l.)); (*keyframes).original.set (float_to_l3d_real(10.0), float_to_l3d_real(o.), float_to_l3d_real(0.), float_to_l3d_real(1.)); (*keyframes).original.set (float_to_l3d_real(0.), float_to_l3d_real(l0.), float_to_l3d_real(0.), float_to_l3d_real(1.)); (*keyframes).original.set (float_to_l3d_real(0.), float_to_l3d_real(o.), float_to_l3d_real(l0.), float_to_l3d_real(l.));

keyframes = new l3d_two_part_list<l3d_coordinate>(4); (*keyframes).original.set (float_to_l3d_real(5.), float_to_l3d_real(5.), float_to_l3d_real(5.), float_to_l3d_real(l.)); (*keyframes).original.set (float_to_l3d_real(-10.), float_to_l3d_real(10.), float_to_l3d_real(0.), float_to_l3d_real(1.)); (*keyframes).original.set (float_to_l3d_real(10.), float_to_l3d_real(0.), float_to_l3d_real(0.), float_to_l3d_real(1.)); (*keyframes).original.set (float_to_l3d_real(10.0), float_to_l3d_real(15.0), float_to_l3d_real(0.), float_to_l3d_real(1.));

int pi;

pi = polygons.next_index(); polygons[pi] = new l3d_polygon_3d_flatshaded(3); printf("before: %p", polygons[pi]->vlist); polygons[pi]->vlist = &vertices; printf("after: %p", polygons[pi]->vlist);

(*polygons[pi]->ivertices)[polygons[pi]->ivertices->next_index()].ivertex=0 (*polygons[pi]->ivertices)[polygons[pi]->ivertices->next_index()].ivertex=1 (*polygons[pi]->ivertices)[polygons[pi]->ivertices->next_index()].ivertex=3 polygons[pi]->compute_center();polygons[pi]->compute_sfcnormal();

pi = polygons.next_index();

polygons[pi]->vlist = &vertices;

(*polygons[pi]->ivertices)[polygons[pi]->ivertices->next_index()].ivertex=2 (*polygons[pi]->ivertices)[polygons[pi]->ivertices->next_index()].ivertex=3 (*polygons[pi]->ivertices)[polygons[pi]->ivertices->next_index()].ivertex=1 polygons[pi]->compute_center();polygons[pi]->compute_sfcnormal();

pi = polygons.next_index();

polygons[pi]->vlist = &vertices;

(*polygons[pi]->ivertices)[polygons[pi]->ivertices->next_index()].ivertex=0 (*polygons[pi]->ivertices)[polygons[pi]->ivertices->next_index()].ivertex=2 (*polygons[pi]->ivertices)[polygons[pi]->ivertices->next_index()].ivertex=1 polygons[pi]->compute_center();polygons[pi]->compute_sfcnormal();

pi = polygons.next_index();

polygons[pi]->vlist = &vertices;

(*polygons[pi]->ivertices)[polygons[pi]->ivertices->next_index()].ivertex=3 (*polygons[pi]->ivertices)[polygons[pi]->ivertices->next_index()].ivertex=2 (*polygons[pi]->ivertices)[polygons[pi]->ivertices->next_index()].ivertex=0 polygons[pi]->compute_center();polygons[pi]->compute_sfcnormal();

modeling_xforms = l3d_mat_rotx(0); modeling_xforms.set

( float_to_l3d_real(1.), float_to_l3d_real(0.), float_to_l3d_real(0.), float_to_l3d_real(o.), float_to_l3d_real(l.), float_to_l3d_real(o.), float_to_l3d_real(0.), float_to_l3d_real(0.), float_to_l3d_real(1.), float_to_l3d_real(o.), float_to_l3d_real(o.), float_to_l3d_real(o.), modeling_xform= modeling_xforms | modeling_xforms;

currently_interpolating = false; keyframe_no=0;

vertices = keyframes[keyframe_no];

pyramid::~pyramid(void) { for(register int i=0; i<polygons.num_items; i++) {delete polygons[i]; } for(int i=0; i<num_keyframes; i++) { delete keyframes[i];

int pyramid::update(void) { if(currently_interpolating) { vertices = interp.list; if(! interp.step()) { currently_interpolating = false;

if(keyframe_no >= num_keyframes) {keyframe_no = 0; }

int next_keyframe = keyframe_no + 1;

if(next_keyframe >= num_keyframes) {next_keyframe = 0; }

vertices = keyframes[keyframe_no];

interp.start( *keyframes[keyframe_no], *keyframes[next_keyframe], rand()%100 + 50, 3);

currently_interpolating = true;

The program morph3d uses the l3d_world and l3d_object classes to store the virtual world. The file main.cc is very simple: it declares a world subclass which creates some pyra -mid objects.

The pyramid class is a morphing 3D pyramid. It changes its shape between a compact and an elongated pyramid. To accomplish this, we added the following lines to the pyramid class:

class pyramid:public l3d_object {

static const int num_keyframes = 2; int keyframe_no;

l3d_two_part_list<l3d_coordinate> *keyframes; l3d_vertex_interpolator interp; bool currently_interpolating;

float_to_l3d_real(0.), float_to_l3d_real(0.), float_to_l3d_real(0.), float_to_l3d_real(l.) );

The keyframes variable holds two separate, different vertex lists. Each list is one complete set of vertex positions defining a shape for the 3D object. We create and fill these lists within the pyramid constructor.

keyframes = new l3d_two_part_list<l3d_coordinate>(4); (*keyframes).original.set (float_to_l3d_real(0.), float_to_l3d_real(0.), float_to_l3d_real(o.), float_to_l3d_real(l.)); (*keyframes).original.set (float_to_l3d_real(10.0), float_to_l3d_real(o.), float_to_l3d_real(0.), float_to_l3d_real(1.)); (*keyframes).original.set (float_to_l3d_real(0.), float_to_l3d_real(l0.), float_to_l3d_real(0.), float_to_l3d_real(l.)); (*keyframes).original.set (float_to_l3d_real(0.), float_to_l3d_real(0.), float_to_l3d_real(l0.), float_to_l3d_real(l.));

keyframes = new l3d_two_part_list<l3d_coordinate>(4); (*keyframes[l]).original.set (float_to_l3d_real(5.), float_to_l3d_real(5.), float_to_l3d_real(5.), float_to_l3d_real(l.)); (*keyframes[l])[l].original.set (float_to_l3d_real(-l0.), float_to_l3d_real(l0.), float_to_l3d_real(0.), float_to_l3d_real(l.)); (*keyframes[l]).original.set (float_to_l3d_real(l0.), float_to_l3d_real(0.), float_to_l3d_real(0.), float_to_l3d_real(l.)); (*keyframes[l]).original.set (float_to_l3d_real(l0.0), float_to_l3d_real(l5.0), float_to_l3d_real(0.), float_to_l3d_real(l.));

Now, given two different shapes of the object stored in two separate vertex lists, we use the class l3d_vertex_interpolator, exactly as we did in the earlier program morph2d, to interpolate the positions of the vertices between the two vertex lists. This takes place in the method pyramid::update. The usage of the vertex interpolator is exactly the same as before: we specify a starting vertex list, an ending vertex list, a total count of interpolation steps, and a dimension (2D or 3D) determining which elements of the vertex are interpolated. The vertex interpolator has its own internal, separate vertex list, which contains the interpolated "in-between" positions between the specified starting vertex list and ending vertex list. We interpolate between vertex lists by calling interp.step, and assign the interpolated vertex list to the pyramid's vertex list.

if(currently_interpolating) { vertices = interp.list; if(! interp.step()) { currently_interpolating = false;

Once the interpolation has finished, after the specified number of steps have been taken and the positions of the vertices in the vertex list have reached the positions in the target vertex list, we then proceed to interpolate between the next two pairs of vertex lists in the array keyframes. In this program, since we only have two vertex lists, this simply has the effect of morphing back from the second to the first vertex list again. Notice that we pass to the vertex interpolator a random parameter for the number of interpolation steps, so that each pyramid always morphs at a different speed.

if(keyframe_no >= num_keyframes) {keyframe_no = 0; }

int next_keyframe = keyframe_no + 1;

if(next_keyframe >= num_keyframes) {next_keyframe = 0; }

vertices = keyframes[keyframe_no];

interp.start( *keyframes[keyframe_no], *keyframes[next_keyframe], rand()%100 + 50, 3);

currently_interpolating = true;

That's all there is to morphing in 3D. It's nothing more than a step-by-step interpolation of all vertex positions from a starting list to an ending list, just as with the 2D case.

NOTE When morphing more complicated shapes, you will want to use a 3D modeling pro-^^ gram (also called a 3D modeler) to create the 3D objects. Chapter 3 covers using Blender to do exactly this. To create models which morph into one another, it is vital that both models have the same number of vertices and faces in the same order. The best way to ensure this is simply to start with the first model and modify it within the 3D modeler to become the second, target model. If you create the target model from scratch, it is almost certain that the number or order of the vertices and faces will be different from those of the first model, which would make morphing impossible or visually disturbing. See Chapter 3 for more details.

NOTE It is actually not "impossible" to morph between two objects with different numbers ^^ of vertices or faces, but doing so is much more complicated than the case described above (where a 1:1 correspondence does exist between the vertices of the two objects). You would have to define some function to automatically map vertices to "reasonable" in-between positions, based on the shapes of the source and target objects, a topic which goes beyond the scope of this book.