#include "ag.h" #include "anaglym.h" #include "Engine.h" #include "Video.h" #include "sdl_keymap.h" #include #include /* exit() */ #include #include #include #include #include #include #include #include /* fabs() */ #include #include #include "SDL.h" using namespace std; #define AG_EVENT_PREFIX "__ag_event_" #define EVENT_PRESENT_FLAG(event) m_event_ ## event ## _present #define EVENT_HANDLER_AG_NAME(event) AG_EVENT_PREFIX #event #define checkForFunction(lua_name, event) \ checkForFunctionFull(lua_name, #event, EVENT_PRESENT_FLAG(event)) #define doRegisterHandler(index, event) \ doRegisterHandlerFull(index, EVENT_HANDLER_AG_NAME(event), \ EVENT_PRESENT_FLAG(event)) #define doClearHandler(event) \ do { EVENT_PRESENT_FLAG(event) = false; } while(0) #define WHITESPACE " \t\r\n\f" #define HOOK_TIMEOUT 10000u #define HOOK_STEPS 250000000 #define FONT_NAME "FreeSans.ttf" #define FP_EQ(a,b) (fabsf(a - b) < 0.0001) /* Global data */ Engine * g_engine; /* static helper functions */ static string trim(const string & orig) { string result = orig; size_t pos = result.find_first_not_of(WHITESPACE); if (pos == string::npos) { result = ""; } else { if (pos > 0) result = result.substr(pos, result.length() - pos); pos = result.find_last_not_of(WHITESPACE); if (pos < result.length() - 1) result = result.substr(0, pos + 1); } return result; } static void cross_product(dVector3 result, dVector3 first, dVector3 second) { result[0] = first[1] * second[2] - first[2] * second[1]; result[1] = first[2] * second[0] - first[0] * second[2]; result[2] = first[0] * second[1] - first[1] * second[0]; } static void debug_hook(lua_State * L, lua_Debug * debug) { g_engine->debug_hook(debug); } #define DEBUG_GL_ERROR #ifdef DEBUG_GL_ERROR #define checkGLError() checkGLErrorLine(__FUNCTION__, __LINE__) static void checkGLErrorLine(const char * function, int line) { GLenum err = glGetError(); if (err != 0) { cerr << "gl error in " << function << ": " << err << " (0x" << hex << err << ") at line " << dec << line << endl; } } #else #define checkGLError() #endif /******** Engine functions ********/ Engine::Engine(const string & path, Video & video) : m_video(video) { m_next_object_index = 1; m_next_text_index = 1; m_eye[0] = 0; m_eye[1] = -1; m_eye[2] = 0; m_center[0] = 0; m_center[1] = 0; m_center[2] = 0; m_up[0] = 0; m_up[1] = 0; m_up[2] = 1; m_drawing = false; m_autoPhysics = true; m_autoStartFrame = true; m_autoEndFrame = true; m_autoDrawObjects = true; m_fileLoader = new EngineFileLoader(this); m_event_time = 0; m_font = NULL; size_t pos = path.find_last_of("\\/"); m_engine_path = (pos != string::npos) ? string(path, 0, pos) : "."; /* setup SDL user event structures */ m_updateEvent.type = SDL_USEREVENT; m_updateEvent.user.code = 0; m_exitEvent.type = SDL_USEREVENT; m_exitEvent.user.code = 1; m_event_init_present = false; m_event_update_present = false; m_event_update_overlay_present = false; m_event_key_down_present = false; m_event_key_up_present = false; m_event_mousebutton_down_present = false; m_event_mousebutton_up_present = false; m_event_mouse_motion_present = false; m_luaState = lua_open(); registerLibraries(); } Engine::~Engine() { lua_close(m_luaState); for (std::map::iterator it = m_objects.begin(); it != m_objects.end(); it++) { delete it->second; } delete m_fileLoader; if (m_font != NULL) delete m_font; } bool Engine::load(const char * program) { m_program_path = program; m_program_directory = program; size_t pos = m_program_directory.find_last_of("/\\"); m_program_directory = (pos != string::npos) ? m_program_directory.substr(0, pos) : "."; string path = locateResource(FONT_NAME); if (path == "") { cerr << "Couldn't locate " FONT_NAME << endl; return false; } else { m_font = new FTBufferFont(path.c_str()); //m_font = new FTTextureFont(path.c_str()); if (m_font->Error() != 0) { cerr << "Error loading font '" << path << "'" << endl; } } int s = luaL_loadfile(m_luaState, program); if (s == 0) { // execute Lua program lua_sethook(m_luaState, ::debug_hook, LUA_MASKCOUNT, HOOK_STEPS); s = lua_pcall(m_luaState, 0, 0, 0); } else { reportErrors(s); cerr << "Error loading '" << program << "'" << endl; cerr << "Exiting." << endl; return false; } if (s != 0) { reportErrors(s); return false; } checkForAllHandlerFunctions(); if (m_event_init_present) { lua_getfield(m_luaState, LUA_GLOBALSINDEX, EVENT_HANDLER_AG_NAME(init)); /* call the init function - pops the function ref from the stack */ int s = lua_pcall(m_luaState, 0, 0, 0); reportErrors(s); } return true; } void Engine::checkForAllHandlerFunctions() { checkForFunction("init_event", init); checkForFunction("update_event", update); checkForFunction("update_overlay_event", update_overlay); checkForFunction("key_down_event", key_down); checkForFunction("key_up_event", key_up); checkForFunction("mousebutton_down_event", mousebutton_down); checkForFunction("mousebutton_up_event", mousebutton_up); checkForFunction("mouse_motion_event", mouse_motion); } void Engine::reportErrors(int status) { if (status != 0) { cerr << "Engine: Error: " << lua_tostring(m_luaState, -1) << endl; lua_pop(m_luaState, 1); // remove error message } } void Engine::registerLibraries() { /* Load the Lua string library */ lua_pushcfunction(m_luaState, luaopen_string); lua_pcall(m_luaState, 0, 0, 0); /* Load the Lua math library */ lua_pushcfunction(m_luaState, luaopen_math); lua_pcall(m_luaState, 0, 0, 0); /* Load the Lua table library */ lua_pushcfunction(m_luaState, luaopen_table); lua_pcall(m_luaState, 0, 0, 0); ag::register_functions(m_luaState); } string Engine::locateResource(const string & shortname) { string try_path; /* look for the resource relative to the loaded script's directory */ try_path = m_program_directory + "/" + shortname; if (fileExists(try_path)) return try_path; /* next look for the resource in the engine's library directory */ try_path = m_engine_path + "/lib/" + shortname; if (fileExists(try_path)) return try_path; return ""; } bool Engine::fileExists(const string & path) { struct stat st; if (stat(path.c_str(), &st) == 0) { return S_ISREG(st.st_mode) && (st.st_size > 0); } return false; } int Engine::addObject(WFObj * obj, bool is_static, float scale) { int id = m_next_object_index++; Object * o = new Object(is_static, m_world, obj->render(), scale); m_objects[id] = o; return id; } int Engine::addObject(bool is_static, OdeWorld::GeomType geom_type, refptr< vector > args) { int id = m_next_object_index++; Object * o = new Object(is_static, m_world, geom_type, args); m_objects[id] = o; return id; } void Engine::removeObject(int id) { Object * obj = getObject(id); if (obj != NULL) { m_objects.erase(id); delete obj; } } int Engine::cloneObject(const Engine::Object * obj) { int id = m_next_object_index++; Object * o = new Object(*obj); m_objects[id] = o; return id; } Engine::Object * Engine::getObject(int id) { return m_objects.find(id) != m_objects.end() ? m_objects[id] : NULL; } void Engine::startFrame() { checkGLError(); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); gluLookAt(m_eye[0], m_eye[1], m_eye[2], m_center[0], m_center[1], m_center[2], m_up[0], m_up[1], m_up[2]); checkGLError(); } void Engine::endFrame() { SDL_GL_SwapBuffers(); } int Engine::setCamera(lua_State * L) { int argc = lua_gettop(L); vector args; for (int i = 1; i <= argc; i++) { int type = lua_type(L, i); if (type == LUA_TNUMBER || type == LUA_TSTRING) args.push_back(lua_tonumber(L, i)); else args.push_back(0); } if (argc >= 3) { m_eye[0] = args[0]; m_eye[1] = args[1]; m_eye[2] = args[2]; } if (argc >= 6) { m_center[0] = args[3]; m_center[1] = args[4]; m_center[2] = args[5]; } if (argc >= 9) { m_up[0] = args[6]; m_up[1] = args[7]; m_up[2] = args[8]; } return 0; } int Engine::getCamera(lua_State * L) { lua_pushnumber(L, m_eye[0]); lua_pushnumber(L, m_eye[1]); lua_pushnumber(L, m_eye[2]); lua_pushnumber(L, m_center[0]); lua_pushnumber(L, m_center[1]); lua_pushnumber(L, m_center[2]); lua_pushnumber(L, m_up[0]); lua_pushnumber(L, m_up[1]); lua_pushnumber(L, m_up[2]); return 9; } int Engine::registerEventHandler(lua_State * L) { int argc = lua_gettop(L); if (argc == 2 && lua_isstring(L, 1) && lua_isfunction(L, 2)) { string event = lua_tostring(L, 1); if (event == "update") doRegisterHandler(2, update); else if (event == "update_overlay") doRegisterHandler(2, update_overlay); else if (event == "key_down") doRegisterHandler(2, key_down); else if (event == "key_up") doRegisterHandler(2, key_up); else if (event == "mousebutton_down") doRegisterHandler(2, mousebutton_down); else if (event == "mousebutton_up") doRegisterHandler(2, mousebutton_up); else if (event == "mouse_motion") doRegisterHandler(2, mouse_motion); } return 0; } int Engine::clearEventHandler(lua_State * L) { int argc = lua_gettop(L); if (argc == 1 && lua_isstring(L, 1)) { string event = lua_tostring(L, 1); if (event == "update") doClearHandler(update); else if (event == "update_overlay") doClearHandler(update_overlay); else if (event == "key_down") doClearHandler(key_down); else if (event == "key_up") doClearHandler(key_up); else if (event == "mousebutton_down") doClearHandler(mousebutton_down); else if (event == "mousebutton_up") doClearHandler(mousebutton_up); else if (event == "mouse_motion") doClearHandler(mouse_motion); } return 0; } int Engine::loadModel(const string & name, bool static_data, float scale) { size_t pos = name.find_first_not_of(FILENAME_SAFE_CHARS); if (pos == string::npos) { FileLoader::Path model_path("", name + ".obj"); FileLoader::Path phys_path("", name + ".phy"); WFObj * obj = new WFObj(*m_fileLoader, m_textureCache); if (obj->load(model_path)) { int id = addObject(obj, static_data, scale); Engine::Object * engine_obj = getObject(id); if (engine_obj != NULL) { engine_obj->loadPhy(phys_path); } return id; } else { cerr << "error loading object '" << name << "'" << endl; } delete obj; } return 0; } bool Engine::isKeyDown(const std::string & key) { return m_keysDown.find(key) != m_keysDown.end(); } void Engine::exit() { SDL_PushEvent(&m_exitEvent); } bool Engine::import(const char * name) { string nm(name); size_t pos = nm.find_first_not_of(FILENAME_SAFE_CHARS); if (pos == string::npos) { string file_path = locateResource(nm + ".lua"); if (file_path != "") { return importFullPath(file_path.c_str()); } } return false; } bool Engine::importFullPath(const char * path) { int s = luaL_loadfile(m_luaState, path); if (s == 0) { s = lua_pcall(m_luaState, 0, 0, 0); if (s == 0) return true; reportErrors(s); } else { cerr << "Error " << s << " loading '" << path << "'" << endl; } return false; } GLuint Engine::loadTexture(const char * name) { FileLoader::Path path("", name); return m_textureCache.load(path, *m_fileLoader); } void Engine::debug_hook(lua_Debug * debug) { Uint32 ticks = SDL_GetTicks(); if (ticks - m_event_time > HOOK_TIMEOUT) { ::exit(3); } } void Engine::getScreenSize(int * width, int * height) { *width = m_video.getWidth(); *height = m_video.getHeight(); } void Engine::drawText(const char * text, GLfloat r, GLfloat g, GLfloat b, int ptsize, float x, float y) { checkGLError(); m_font->FaceSize(ptsize); glPushAttrib(GL_ENABLE_BIT | GL_TEXTURE_BIT); glEnable(GL_TEXTURE_2D); glDisable(GL_LIGHTING); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_BLEND); glPushMatrix(); glTranslatef(x, y, 0); glColor3f(r, g, b); m_font->Render(text); glPopMatrix(); glPopAttrib(); checkGLError(); } void Engine::getTextSize(const char * text, int ptsize, float * width, float * height) { m_font->FaceSize(ptsize); FTBBox box = m_font->BBox(text); *width = box.Upper().Xf() - box.Lower().Xf(); *height = box.Upper().Yf() - box.Lower().Yf(); } void Engine::drawLine(float r, float g, float b, float x1, float y1, float x2, float y2, float width) { checkGLError(); glPushAttrib(GL_LINE_BIT | GL_ENABLE_BIT); glEnable(GL_LINE_SMOOTH); glLineWidth(width); glBegin(GL_LINES); glColor3f(r, g, b); glVertex2f(x1, y1); glVertex2f(x2, y2); glEnd(); glPopAttrib(); checkGLError(); } void Engine::drawRect(float r, float g, float b, float width, float height, float x, float y, float rot) { checkGLError(); glPushAttrib(GL_ENABLE_BIT); glPushMatrix(); glTranslatef(x, y, 0); glRotatef(rot, 0, 0, 1); glEnable(GL_LINE_SMOOTH); glBegin(GL_LINE_LOOP); glColor3f(r, g, b); glVertex2f(-width / 2, -height / 2); glVertex2f(width / 2, -height / 2); glVertex2f(width / 2, height / 2); glVertex2f(-width / 2, height / 2); glEnd(); glPopMatrix(); glPopAttrib(); checkGLError(); } void Engine::fillRect(float r, float g, float b, float width, float height, float x, float y, float rot) { checkGLError(); glPushAttrib(GL_ENABLE_BIT); glPushMatrix(); glTranslatef(x, y, 0); glRotatef(rot, 0, 0, 1); glEnable(GL_POLYGON_SMOOTH); glBegin(GL_QUADS); glColor3f(r, g, b); glVertex2f(-width / 2, -height / 2); glVertex2f(width / 2, -height / 2); glVertex2f(width / 2, height / 2); glVertex2f(-width / 2, height / 2); glEnd(); glPopMatrix(); glPopAttrib(); checkGLError(); } void Engine::drawImage(float width, float height, float x, float y, int tex, float rot) { checkGLError(); glPushAttrib(GL_ENABLE_BIT /* for enablings */ | GL_TEXTURE_BIT /* texture bindings and env. mode */ | GL_COLOR_BUFFER_BIT); /* blend src. and dst. equations */ glPushMatrix(); glTranslatef(x, y, 0); glRotatef(rot, 0, 0, 1); glEnable(GL_POLYGON_SMOOTH); glEnable(GL_TEXTURE_2D); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); glBindTexture(GL_TEXTURE_2D, tex); glBegin(GL_QUADS); glTexCoord2f(0, 0); glVertex2f(-width / 2, -height / 2); glTexCoord2f(1, 0); glVertex2f(width / 2, -height / 2); glTexCoord2f(1, 1); glVertex2f(width / 2, height / 2); glTexCoord2f(0, 1); glVertex2f(-width / 2, height / 2); glEnd(); glPopMatrix(); glPopAttrib(); checkGLError(); } void Engine::drawArc(float r, float g, float b, float x, float y, float radius, float a1, float a2) { a1 *= M_PI / 180.0f; a2 *= M_PI / 180.0f; int segments = 1 + (int) (fabsf(a2 - a1) / (M_2_PI / 16.0f)); float step = (a2 - a1) / segments; glPushAttrib(GL_ENABLE_BIT); glPushMatrix(); glTranslatef(x, y, 0); glColor3f(r, g, b); glBegin(GL_LINE_STRIP); float angle = a1; for (int i = 0; i <= segments; i++) { glVertex2f(radius * cos(angle), radius * sin(angle)); angle += step; } glEnd(); glPopMatrix(); glPopAttrib(); } /* called by SDL when the update timer expires */ Uint32 Engine::updateCallback(Uint32 interval, void * param) { Engine * engine = (Engine *) param; return engine->updateCallback(interval); } /* member update function to be called by our registered * SDL callback non-member function */ Uint32 Engine::updateCallback(Uint32 interval) { if (!m_drawing) { SDL_PushEvent(&m_updateEvent); } return interval; } void Engine::run() { /* register a screen redrawing SDL event */ SDL_AddTimer(25, &updateCallback, this); SDL_Event event; while (SDL_WaitEvent(&event)) { m_event_time = SDL_GetTicks(); switch (event.type) { case SDL_KEYDOWN: switch (event.key.keysym.sym) { case SDLK_ESCAPE: goto RET; case SDLK_F1: m_video.toggleFullScreen(); break; case SDLK_F2: SDL_ShowCursor( SDL_ShowCursor(SDL_QUERY) == SDL_ENABLE ? SDL_DISABLE : SDL_ENABLE); SDL_WM_GrabInput( SDL_WM_GrabInput(SDL_GRAB_QUERY) == SDL_GRAB_ON ? SDL_GRAB_OFF : SDL_GRAB_ON); break; case SDLK_F5: reloadProgram(); break; default: key_down_event(event.key.keysym.sym); } break; case SDL_KEYUP: key_up_event(event.key.keysym.sym); break; case SDL_MOUSEBUTTONDOWN: mousebutton_down_event(event.button.button, event.button.x, event.button.y); break; case SDL_MOUSEBUTTONUP: mousebutton_up_event(event.button.button, event.button.x, event.button.y); break; case SDL_MOUSEMOTION: mouse_motion_event(event.motion.x, event.motion.y, event.motion.xrel, event.motion.yrel); break; case SDL_QUIT: goto RET; break; case SDL_USEREVENT: switch (event.user.code) { case 0: update_event(); break; case 1: goto RET; break; } break; } } RET: ; } void Engine::update_event() { m_drawing = true; if (m_autoPhysics) doPhysics(); if (m_autoStartFrame) startFrame(); if (m_event_update_present) { lua_getfield(m_luaState, LUA_GLOBALSINDEX, EVENT_HANDLER_AG_NAME(update)); /* call the update function - pops the function ref from the stack */ int s = lua_pcall(m_luaState, 0, 0, 0); reportErrors(s); } if (m_autoDrawObjects) drawObjects(); update_overlay_event(); if (m_autoEndFrame) endFrame(); m_drawing = false; } void Engine::update_overlay_event() { if (m_event_update_overlay_present) { checkGLError(); glPushAttrib(GL_ENABLE_BIT); glDisable(GL_DEPTH_TEST); glDisable(GL_LIGHTING); int width = m_video.getWidth(); int height = m_video.getHeight(); glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity(); glOrtho(0, width, 0, height, -1.01, 1.01); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); lua_getfield(m_luaState, LUA_GLOBALSINDEX, EVENT_HANDLER_AG_NAME(update_overlay)); lua_pushnumber(m_luaState, width); lua_pushnumber(m_luaState, height); /* call the update_overlay function * - pops the function ref from the stack, then the arguments */ int s = lua_pcall(m_luaState, 2, 0, 0); reportErrors(s); glMatrixMode(GL_PROJECTION); glPopMatrix(); glMatrixMode(GL_MODELVIEW); glPopAttrib(); checkGLError(); } } void Engine::key_down_event(int keysym) { m_keysDown[sdl_keymap[keysym]] = true; if (m_event_key_down_present) { lua_getfield(m_luaState, LUA_GLOBALSINDEX, EVENT_HANDLER_AG_NAME(key_down)); lua_pushstring(m_luaState, sdl_keymap[keysym]); /* call the key down event function * This pops the function ref and arguments from the stack */ int s = lua_pcall(m_luaState, 1, 0, 0); reportErrors(s); } } void Engine::key_up_event(int keysym) { m_keysDown.erase(sdl_keymap[keysym]); if (m_event_key_up_present) { lua_getfield(m_luaState, LUA_GLOBALSINDEX, EVENT_HANDLER_AG_NAME(key_up)); lua_pushstring(m_luaState, sdl_keymap[keysym]); /* call the key up event function * This pops the function ref and arguments from the stack */ int s = lua_pcall(m_luaState, 1, 0, 0); reportErrors(s); } } void Engine::mousebutton_down_event(int button, int x, int y) { if (m_event_mousebutton_down_present) { lua_getfield(m_luaState, LUA_GLOBALSINDEX, EVENT_HANDLER_AG_NAME(mousebutton_down)); lua_pushinteger(m_luaState, button); lua_pushnumber(m_luaState, x); lua_pushnumber(m_luaState, y); /* call the mouse button down event function * This pops the function ref and arguments from the stack */ int s = lua_pcall(m_luaState, 3, 0, 0); reportErrors(s); } } void Engine::mousebutton_up_event(int button, int x, int y) { if (m_event_mousebutton_up_present) { lua_getfield(m_luaState, LUA_GLOBALSINDEX, EVENT_HANDLER_AG_NAME(mousebutton_up)); lua_pushinteger(m_luaState, button); lua_pushnumber(m_luaState, x); lua_pushnumber(m_luaState, y); /* call the mouse button up event function * This pops the function ref and arguments from the stack */ int s = lua_pcall(m_luaState, 3, 0, 0); reportErrors(s); } } void Engine::mouse_motion_event(int x, int y, int xrel, int yrel) { if (m_event_mouse_motion_present) { lua_getfield(m_luaState, LUA_GLOBALSINDEX, EVENT_HANDLER_AG_NAME(mouse_motion)); lua_pushnumber(m_luaState, x); lua_pushnumber(m_luaState, y); lua_pushnumber(m_luaState, xrel); lua_pushnumber(m_luaState, yrel); /* call the mouse motion event function * This pops the function ref and arguments from the stack */ int s = lua_pcall(m_luaState, 4, 0, 0); reportErrors(s); } } void Engine::checkForFunctionFull(const std::string & lua_fn_name, const std::string & event_name, bool & presentFlag) { if (!presentFlag) /* only look for events we do not have a handler for */ { lua_getfield(m_luaState, LUA_GLOBALSINDEX, lua_fn_name.c_str()); if (lua_isfunction(m_luaState, -1)) { lua_setfield(m_luaState, LUA_GLOBALSINDEX, (AG_EVENT_PREFIX + event_name).c_str()); presentFlag = true; } else { lua_pop(m_luaState, 1); presentFlag = false; } } } void Engine::doRegisterHandlerFull(int index, const std::string & event_name, bool & presentFlag) { lua_pushvalue(m_luaState, index); lua_setfield(m_luaState, LUA_GLOBALSINDEX, event_name.c_str()); presentFlag = true; } void Engine::reloadProgram() { importFullPath(m_program_path.c_str()); checkForAllHandlerFunctions(); } void Engine::doPhysics() { static Uint32 last_updated = 0; Uint32 current_ticks = SDL_GetTicks(); if (last_updated > 0) { Uint32 msec_steps = current_ticks - last_updated; for (Uint32 i = 0; i < msec_steps; i++) m_world.step(); } last_updated = current_ticks; } void Engine::drawObjects() { std::map::iterator it; for (it = m_objects.begin(); it != m_objects.end(); it++) { it->second->draw(); } } /******** Engine::Object functions ********/ Engine::Object::Object(bool is_static, OdeWorld & world, GLuint dl, float scale) { m_ode_object = world.createObject(is_static, scale); m_display_list = dl; 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; } Engine::Object::Object(bool is_static, OdeWorld & world, OdeWorld::GeomType geom_type, refptr< std::vector > args) { m_is_visible = true; m_scale = 1.0f; m_is_scaled = false; m_display_list = 0; m_display_list_refcnt = NULL; m_ode_object = world.createObject( geom_type == OdeWorld::PLANE ? false : is_static, m_scale); 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; createManagedObject(); } Engine::Object::Object(const Engine::Object & orig) { m_is_visible = orig.m_is_visible; m_ode_object = new OdeWorld::Object(*orig.m_ode_object); 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_scale = orig.m_scale; m_is_scaled = orig.m_is_scaled; 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; if (m_is_managed) render(); } Engine::Object::~Object() { 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); m_ode_object->addBox(m_args); break; case OdeWorld::SPHERE: while (m_args->size() < 4) m_args->push_back(0); m_ode_object->addSphere(m_args); break; case OdeWorld::PLANE: while (m_args->size() < 4 || m_args->size() == 5) m_args->push_back(0); m_ode_object->addPlane(m_args); break; case OdeWorld::CYLINDER: while (m_args->size() < 8) m_args->push_back(0); m_ode_object->addCylinder(m_args); break; case OdeWorld::CAPSULE: while (m_args->size() < 8) m_args->push_back(0); m_ode_object->addCapsule(m_args); break; } m_ode_object->finalize(); render(); } /* render a managed object */ /* prerequisite: m_args->size() is big enough */ void Engine::Object::render() { if (!m_is_managed) return; GLUquadric * quad = gluNewQuadric(); if (m_display_list <= 0) { m_display_list = glGenLists(1); } checkGLError(); glNewList(m_display_list, GL_COMPILE); glPushAttrib(GL_ENABLE_BIT); 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) { glTexCoord2fv(tex_coords[v]); } 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, default_plane_direction, r, 1, 3, 3); dMultiply0(rotated_x, default_x, r, 1, 3, 3); dMultiply0(rotated_y, default_y, r, 1, 3, 3); 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; cross_product(rotated_x, normal, cross_vec); cross_product(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); 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); 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::loadPhy(const FileLoader::Path & path) { FileLoader::Buffer buff = g_engine->m_fileLoader->load(path); if (buff.size <= 0) return; string str(buff.data, buff.size); stringstream istr(str, ios_base::in); while (!istr.eof()) { string line; getline(istr, line); line = trim(line); if (line == "" || line[0] == '#') continue; size_t pos = line.find_first_of(WHITESPACE); if (pos == string::npos) continue; string type = line.substr(0, pos); pos = line.find("\"", pos); if (pos == string::npos) continue; size_t pos2 = line.find("\"", pos + 1); if (pos2 == string::npos) continue; string name = line.substr(pos + 1, pos2 - pos - 1); pos = pos2 + 1; refptr< vector > args = new vector(); for (;;) { pos = line.find_first_not_of(WHITESPACE, pos); if (pos == string::npos) break; pos2 = line.find_first_of(WHITESPACE, pos); string n = line.substr(pos, pos2 - pos); float f = atof(n.c_str()); args->push_back(f); if (pos2 == string::npos) break; pos = pos2 + 1; } if (type == "cube") { m_ode_object->addBox(args); } else if (type == "sphere") { m_ode_object->addSphere(args); } else if (type == "capsule") { m_ode_object->addCapsule(args); } else if (type == "plane") { m_ode_object->addPlane(args); } } 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_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); glScalef(m_scale, m_scale, m_scale); } checkGLError(); glCallList(m_display_list); checkGLError(); if (m_is_scaled) glPopAttrib(); if (transform) glPopMatrix(); checkGLError(); } } /******** Engine::EngineFileLoader functions ********/ Engine::EngineFileLoader::EngineFileLoader(Engine * engine) { m_engine = engine; } int Engine::EngineFileLoader::getSize(const Path & path) { struct stat st; string file_path = resolvePath(path); if (file_path == "") return -1; if (stat(file_path.c_str(), &st)) return -2; return st.st_size; } FileLoader::Buffer Engine::EngineFileLoader::load(const Path & path) { string file_path = resolvePath(path); int size = getSize(path); if (size > 0) { FILE * fp = fopen(file_path.c_str(), "rb"); if (fp != NULL) { Buffer buf(size); int num_read = fread(buf.data, size, 1, fp); fclose(fp); if (num_read > 0) { return buf; } } } return Buffer(0); } string Engine::EngineFileLoader::resolvePath(const Path & path) { string file_path = m_engine->locateResource(path.shortPath); if (file_path == "") file_path = path.fullPath; return file_path; }