#include #include #include "SDL.h" #include "Engine.h" using namespace std; #ifdef DEBUG_GL_ERROR #define checkGLError() checkGLErrorLine(__FUNCTION__, __LINE__) static void checkGLErrorLine(const char * function, int line) { GLenum err = glGetError(); if (err != 0) { std::cerr << "gl error in " << function << ": " << err << " (0x" << std::hex << err << ") at line " << std::dec << line << std::endl; } } #else #define checkGLError() #endif #define FP_EQ(a,b) (fabsf(a - b) < 0.0001) /* used for objects loaded directly from model files */ Engine::Object::Object(bool is_static, bool is_reference, bool enable_blending, OdeWorld & world, WFObj * obj, float scale) : m_world(world) { m_is_reference = is_reference; if (m_is_reference) { m_ode_object = NULL; } else { m_ode_object = world.createObject(is_static, scale); m_ode_object->setUserData(this); } m_is_static = is_static; m_enable_blending = enable_blending; m_display_list = obj->render(true, enable_blending); const float * obj_aabb = obj->getAABB(); for (int i = 0; i < 6; i++) m_aabb[i] = scale * obj_aabb[i]; m_display_list_refcnt = new int; *m_display_list_refcnt = 1; m_is_visible = true; m_scale = scale; m_is_scaled = ! (fabs(scale - 1.0) < 0.0001); m_is_managed = false; m_mass = 0.0; m_mass_is_set = false; m_gravity_mode = true; } /* used for "managed" objects with one geom */ Engine::Object::Object(bool is_static, bool is_reference, bool enable_blending, OdeWorld & world, OdeWorld::GeomType geom_type, refptr< std::vector > args) : m_world(world) { m_is_reference = is_reference; m_enable_blending = enable_blending; m_is_static = geom_type == OdeWorld::PLANE ? false : is_static; m_is_visible = true; m_scale = 1.0f; m_is_scaled = false; m_display_list = 0; m_display_list_refcnt = NULL; if (m_is_reference) { m_ode_object = NULL; } else { m_ode_object = world.createObject(m_is_static, m_scale); m_ode_object->setUserData(this); } m_is_managed = true; m_geom_type = geom_type; m_args = args; for (int i = 0; i < 4; i++) m_color[i] = 1.0f; m_texture = 0; m_texture_scale = 1.0f; createManagedObject(); render(); m_mass = 0.0; m_mass_is_set = false; m_gravity_mode = true; } /* used to clone objects */ Engine::Object::Object(const Engine::Object & orig) : m_world(orig.m_world) { m_is_reference = false; m_is_visible = orig.m_is_visible; m_is_static = orig.m_is_static; m_enable_blending = orig.m_enable_blending; m_scale = orig.m_scale; m_is_scaled = orig.m_is_scaled; m_phy = orig.m_phy; m_is_managed = orig.m_is_managed; if (m_is_managed) m_display_list = 0; else m_display_list = orig.m_display_list; m_display_list_refcnt = orig.m_display_list_refcnt; if (m_display_list_refcnt != NULL) (*m_display_list_refcnt)++; m_geom_type = orig.m_geom_type; m_args = orig.m_args; for (int i = 0; i < 4; i++) m_color[i] = orig.m_color[i]; m_texture = orig.m_texture; m_texture_scale = orig.m_texture_scale; if (orig.m_is_reference) { m_ode_object = m_world.createObject(m_is_static, m_scale); instantiatePhy(); } else { m_ode_object = new OdeWorld::Object(*orig.m_ode_object); } m_ode_object->setUserData(this); if (m_is_managed) { createManagedObject(); render(); } m_mass = orig.m_mass; m_mass_is_set = orig.m_mass_is_set; if (m_mass_is_set) { setMass(orig.getMass()); } m_gravity_mode = orig.m_gravity_mode; setGravityMode(m_gravity_mode); } Engine::Object::~Object() { if (m_ode_object != NULL) delete m_ode_object; if (m_display_list_refcnt != NULL) { (*m_display_list_refcnt)--; if (*m_display_list_refcnt < 1) { /* we hold the last reference to the OpenGL display list */ delete m_display_list_refcnt; glDeleteLists(m_display_list, 1); } } else { glDeleteLists(m_display_list, 1); } } void Engine::Object::createManagedObject() { switch (m_geom_type) { case OdeWorld::BOX: { while (m_args->size() < 9) m_args->push_back(0); if (m_ode_object != NULL) m_ode_object->addBox(m_args); float aabb[6] = {-(*m_args)[0], -(*m_args)[1], -(*m_args)[2], (*m_args)[0], (*m_args)[1], (*m_args)[2]}; for (int i = 0; i < 6; i++) aabb[i] /= 2.0f; memcpy(m_aabb, aabb, sizeof(m_aabb)); break; } case OdeWorld::SPHERE: { while (m_args->size() < 4) m_args->push_back(0); if (m_ode_object != NULL) m_ode_object->addSphere(m_args); float aabb[6] = {-(*m_args)[0], -(*m_args)[0], -(*m_args)[0], (*m_args)[0], (*m_args)[0], (*m_args)[0]}; memcpy(m_aabb, aabb, sizeof(m_aabb)); break; } case OdeWorld::PLANE: { while (m_args->size() < 4 || m_args->size() == 5) m_args->push_back(0); if (m_ode_object != NULL) m_ode_object->addPlane(m_args); for (int i = 0; i < 6; i++) m_aabb[i] = 0.0f; break; } case OdeWorld::CYLINDER: { while (m_args->size() < 8) m_args->push_back(0); if (m_ode_object != NULL) m_ode_object->addCylinder(m_args); float aabb[6] = {-(*m_args)[0], -(*m_args)[0], -(*m_args)[1], (*m_args)[0], (*m_args)[0], (*m_args)[1]}; memcpy(m_aabb, aabb, sizeof(m_aabb)); break; } case OdeWorld::CAPSULE: { while (m_args->size() < 8) m_args->push_back(0); if (m_ode_object != NULL) m_ode_object->addCapsule(m_args); float aabb[6] = {-(*m_args)[0], -(*m_args)[0], -(*m_args)[1], (*m_args)[0], (*m_args)[0], (*m_args)[1]}; memcpy(m_aabb, aabb, sizeof(m_aabb)); break; } } if (m_ode_object != NULL) { m_ode_object->finalize(); } if (!m_is_reference) { render(); } } /* render a managed object */ /* prerequisite: m_args->size() is big enough */ void Engine::Object::render() { if (!m_is_managed || m_is_reference) return; GLUquadric * quad = gluNewQuadric(); if (m_display_list <= 0) { m_display_list = glGenLists(1); } checkGLError(); glNewList(m_display_list, GL_COMPILE); int attrib_flags = GL_ENABLE_BIT; if (m_enable_blending) { attrib_flags |= GL_COLOR_BUFFER_BIT | GL_TEXTURE_BIT; } glPushAttrib(attrib_flags); if (m_enable_blending) { glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); } if (m_texture != 0) { gluQuadricTexture(quad, 1); glEnable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, m_texture); } else { glDisable(GL_TEXTURE_2D); glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, m_color); } switch (m_geom_type) { case OdeWorld::BOX: { const static struct { float norm[3]; float verts[4][3]; } sides[] = { { {1, 0, 0}, /* right */ {{1, -1, -1}, {1, 1, -1}, {1, 1, 1}, {1, -1, 1}} }, { {-1, 0, 0}, /* left */ {{-1, 1, -1}, {-1, -1, -1}, {-1, -1, 1}, {-1, 1, 1}} }, { {0, 1, 0}, /* back */ {{1, 1, -1}, {-1, 1, -1}, {-1, 1, 1}, {1, 1, 1}} }, { {0, -1, 0}, /* front */ {{-1, -1, -1}, {1, -1, -1}, {1, -1, 1}, {-1, -1, 1}} }, { {0, 0, 1}, /* top */ {{-1, -1, 1}, {1, -1, 1}, {1, 1, 1}, {-1, 1, 1}} }, { {0, 0, -1}, /* bottom */ {{1, -1, -1}, {-1, -1, -1}, {-1, 1, -1}, {1, 1, -1}} } }; const float tex_coords[4][2] = { {0, 0}, {1, 0}, {1, 1}, {0, 1} }; glBegin(GL_QUADS); float width = (*m_args)[0]; float depth = (*m_args)[1]; float height = (*m_args)[2]; for (unsigned int s = 0; s < (sizeof(sides)/sizeof(sides[0])); s++) { glNormal3fv(sides[s].norm); for (int v = 0; v < 4; v++) { if (m_texture != 0) { glTexCoord2f(tex_coords[v][0] * m_texture_scale, tex_coords[v][1] * m_texture_scale); } glVertex3f(sides[s].verts[v][0] * width / 2.0, sides[s].verts[v][1] * depth / 2.0, sides[s].verts[v][2] * height / 2.0); } } glEnd(); } break; case OdeWorld::SPHERE: gluSphere(quad, (*m_args)[0], 16, 8); break; case OdeWorld::PLANE: { dVector3 normal; dVector3 rotated_x; dVector3 rotated_y; float x_o, y_o, z_o; if (m_args->size() == 6) { dMatrix3 r; dRFromEulerAngles(r, (*m_args)[3], (*m_args)[4], (*m_args)[5]); dVector3 default_plane_direction = {0, 0, 1, 0}; dVector3 default_x = {1, 0, 0, 0}; dVector3 default_y = {0, 1, 0, 0}; dMultiply0(normal, r, default_plane_direction, 3, 3, 1); dMultiply0(rotated_x, r, default_x, 3, 3, 1); dMultiply0(rotated_y, r, default_y, 3, 3, 1); x_o = (*m_args)[0]; y_o = (*m_args)[1]; z_o = (*m_args)[2]; } else { normal[0] = (*m_args)[0]; normal[1] = (*m_args)[1]; normal[2] = (*m_args)[2]; float d = (*m_args)[3]; float len = normal[0] * normal[0] + normal[1] * normal[1] + normal[2] * normal[2]; if (! FP_EQ(len, 1.0)) /* normalize if necessary */ { len = sqrtf(len); normal[0] /= len; normal[1] /= len; normal[2] /= len; d /= len; } int min_component = 0; float comp = 10000.0f; for (int i = 0; i < 3; i++) { if (normal[i] < comp) { min_component = i; comp = normal[i]; } } dVector3 cross_vec; for (int i = 0; i < 3; i++) cross_vec[i] = (i == min_component) ? 1 : 0; dCROSS(rotated_x, =, normal, cross_vec); dCROSS(rotated_y, =, normal, rotated_x); x_o = normal[0] * d; y_o = normal[1] * d; z_o = normal[2] * d; } /* plane equation: ax + by + cz = d, plane normal: (a, b, c) */ const int num_sections = 10; const float section_size = 100.0f; #ifdef dSINGLE glNormal3fv(normal); #else glNormal3dv(normal); #endif for (int x = 0; x < num_sections; x++) { glBegin(GL_QUAD_STRIP); for (int y = 0; y <= num_sections; y++) { float half_span = num_sections * section_size / 2.0; float x_c = x * section_size - half_span; float y_c = y * section_size - half_span; glTexCoord2f(0.0, y * m_texture_scale); glVertex3f(x_o + rotated_x[0] * x_c + rotated_y[0] * y_c, y_o + rotated_x[1] * x_c + rotated_y[1] * y_c, z_o + rotated_x[2] * x_c + rotated_y[2] * y_c); x_c += section_size; glTexCoord2f(1.0, y * m_texture_scale); glVertex3f(x_o + rotated_x[0] * x_c + rotated_y[0] * y_c, y_o + rotated_x[1] * x_c + rotated_y[1] * y_c, z_o + rotated_x[2] * x_c + rotated_y[2] * y_c); } glEnd(); } } break; case OdeWorld::CYLINDER: { glPushMatrix(); float half_span = (*m_args)[1] / 2.0; glTranslatef(0, 0, -half_span); gluCylinder(quad, (*m_args)[0], (*m_args)[0], (*m_args)[1], 12, 1); gluDisk(quad, 0.0, (*m_args)[0], 12, 4); glTranslatef(0, 0, 2 * half_span); gluDisk(quad, 0.0, (*m_args)[0], 12, 4); glPopMatrix(); } break; case OdeWorld::CAPSULE: { glPushAttrib(GL_TRANSFORM_BIT); /* save current clip planes */ double halfheight = (*m_args)[1] / 2.0; glPushMatrix(); glTranslatef(0, 0, -halfheight); gluCylinder(quad, (*m_args)[0], (*m_args)[0], (*m_args)[1], 12, 1); double clip_plane[] = {0, 0, -1, halfheight}; glClipPlane(GL_CLIP_PLANE0, clip_plane); glEnable(GL_CLIP_PLANE0); gluSphere(quad, (*m_args)[0], 12, 8); glTranslatef(0, 0, 2*halfheight); clip_plane[2] = 1.0; glClipPlane(GL_CLIP_PLANE0, clip_plane); gluSphere(quad, (*m_args)[0], 12, 8); glPopMatrix(); glPopAttrib(); } break; } gluDeleteQuadric(quad); glPopAttrib(); glEndList(); checkGLError(); } void Engine::Object::setPosition(double x, double y, double z) { if (m_ode_object != NULL) m_ode_object->setPosition(x, y, z); } void Engine::Object::getPosition(double * x, double * y, double * z) { if (m_ode_object != NULL) m_ode_object->getPosition(x, y, z); else *x = *y = *z = 0.0; } void Engine::Object::loadPhy(FileLoader * fl, const FileLoader::Path & path) { m_phy = new PhyObj(); m_phy->load(fl, path); if (!m_is_managed) instantiatePhy(); } void Engine::Object::instantiatePhy() { if (m_ode_object != NULL && !m_is_managed && !m_phy.isNull()) { for (int i = 0, sz = m_phy->getNumGeoms(); i < sz; i++) { refptr geom = m_phy->getGeom(i); refptr< vector > args = geom->getArgs(); switch (geom->getType()) { case PhyObj::BOX: m_ode_object->addBox(args); break; case PhyObj::SPHERE: m_ode_object->addSphere(args); break; case PhyObj::CAPSULE: m_ode_object->addCapsule(args); break; case PhyObj::PLANE: m_ode_object->addPlane(args); break; default: break; } } m_ode_object->finalize(); } } void Engine::Object::setTexture(GLuint tex) { if (m_is_managed) { m_texture = tex; render(); } } void Engine::Object::draw() { if (!m_is_reference && m_is_visible) { checkGLError(); const dReal * pos = m_ode_object->getPosition(); const dReal * rot = m_ode_object->getRotation(); bool transform = (pos != NULL && rot != NULL); if (transform) OdeWorld::pushTransform(pos, rot); if (m_is_scaled) { glPushAttrib(GL_TRANSFORM_BIT); glEnable(GL_NORMALIZE); glPushMatrix(); glScalef(m_scale, m_scale, m_scale); } checkGLError(); glCallList(m_display_list); checkGLError(); if (m_is_scaled) { glPopMatrix(); glPopAttrib(); } if (transform) glPopMatrix(); checkGLError(); } } dReal Engine::Object::getMass() const { return (m_ode_object != NULL) ? m_ode_object->getMass() : m_mass; } void Engine::Object::setMass(dReal mass) { m_mass = mass; m_mass_is_set = true; if (m_ode_object != NULL) { m_ode_object->setMass(mass); } }