From 497796e55042df945585e1392e63efea678983ee Mon Sep 17 00:00:00 2001 From: aap Date: Thu, 10 Aug 2017 00:42:33 +0200 Subject: [PATCH] wrote basic skeleton; clumpview --- premake5.lua | 45 ++++++++ rw.h | 3 - skeleton/glfw.cpp | 184 +++++++++++++++++++++++++++++++ skeleton/skeleton.cpp | 57 ++++++++++ skeleton/skeleton.h | 91 ++++++++++++++++ skeleton/win.cpp | 217 +++++++++++++++++++++++++++++++++++++ src/base.cpp | 20 ++++ src/engine.cpp | 10 ++ src/frame.cpp | 7 ++ src/rwbase.h | 1 + src/rwengine.h | 2 + src/rwobjects.h | 3 +- tools/clumpview/camera.cpp | 134 +++++++++++++++++++++++ tools/clumpview/camera.h | 26 +++++ tools/clumpview/main.cpp | 181 +++++++++++++++++++++++++++++++ tools/clumpview/teapot.dff | Bin 0 -> 42796 bytes 16 files changed, 977 insertions(+), 4 deletions(-) create mode 100644 skeleton/glfw.cpp create mode 100644 skeleton/skeleton.cpp create mode 100644 skeleton/skeleton.h create mode 100644 skeleton/win.cpp create mode 100644 tools/clumpview/camera.cpp create mode 100644 tools/clumpview/camera.h create mode 100644 tools/clumpview/main.cpp create mode 100644 tools/clumpview/teapot.dff diff --git a/premake5.lua b/premake5.lua index b1934e6..984fcab 100755 --- a/premake5.lua +++ b/premake5.lua @@ -71,3 +71,48 @@ project "dumprwtree" includedirs { "." } libdirs { Libdir } links { "librw" } + +function findlibs() + filter { "platforms:linux*gl3" } + links { "GL", "GLEW", "glfw" } + filter { "platforms:win*gl3" } + defines { "GLEW_STATIC" } + filter { "platforms:win-amd64-gl3" } + libdirs { path.join(GLEWdir, "lib/Release/x64") } + libdirs { path.join(GLFW64dir, "lib-vc2015") } + filter { "platforms:win-x86-gl3" } + libdirs { path.join(GLEWdir, "lib/Release/Win32") } + filter { "platforms:win*gl3" } + links { "glew32s", "glfw3", "opengl32" } + filter { "platforms:*d3d9" } + links { "d3d9", "Xinput9_1_0" } + filter {} +end + +function skeleton() + files { "skeleton/*.cpp", "skeleton/*.h" } + includedirs { "skeleton" } +end + +function skeltool(dir) + targetdir (Bindir) + files { path.join("tools", dir, "*.cpp"), + path.join("tools", dir, "*.h") } + vpaths { + {["src"] = { path.join("tools", dir, "*") }}, + {["skeleton"] = { "skeleton/*" }}, + } + skeleton() + debugdir ( path.join("tools", dir) ) + includedirs { "." } + libdirs { Libdir } + links { "librw" } + findlibs() +end + +project "clumpview" + kind "WindowedApp" + characterset ("MBCS") + skeltool("clumpview") + flags { "WinMain" } + removeplatforms { "*null" } diff --git a/rw.h b/rw.h index 8638793..af7d6eb 100644 --- a/rw.h +++ b/rw.h @@ -15,9 +15,6 @@ #include "src/d3d/rwd3d.h" #include "src/d3d/rwd3d8.h" #include "src/d3d/rwd3d9.h" -#ifdef RW_OPENGL -#include -#endif #include "src/gl/rwwdgl.h" #include "src/gl/rwgl3.h" #include "src/gl/rwgl3shader.h" diff --git a/skeleton/glfw.cpp b/skeleton/glfw.cpp new file mode 100644 index 0000000..5690a04 --- /dev/null +++ b/skeleton/glfw.cpp @@ -0,0 +1,184 @@ +#include +#include +#include "skeleton.h" + +using namespace sk; +using namespace rw; + +#ifdef RW_OPENGL + +GLFWwindow *window; +int keymap[GLFW_KEY_LAST+1]; + +static void +initkeymap(void) +{ + int i; + for(i = 0; i < GLFW_KEY_LAST+1; i++) + keymap[i] = KEY_NULL; + keymap[GLFW_KEY_SPACE] = ' '; + keymap[GLFW_KEY_APOSTROPHE] = '\''; + keymap[GLFW_KEY_COMMA] = ','; + keymap[GLFW_KEY_MINUS] = '-'; + keymap[GLFW_KEY_PERIOD] = '.'; + keymap[GLFW_KEY_SLASH] = '/'; + keymap[GLFW_KEY_0] = '0'; + keymap[GLFW_KEY_1] = '1'; + keymap[GLFW_KEY_2] = '2'; + keymap[GLFW_KEY_3] = '3'; + keymap[GLFW_KEY_4] = '4'; + keymap[GLFW_KEY_5] = '5'; + keymap[GLFW_KEY_6] = '6'; + keymap[GLFW_KEY_7] = '7'; + keymap[GLFW_KEY_8] = '8'; + keymap[GLFW_KEY_9] = '9'; + keymap[GLFW_KEY_SEMICOLON] = ';'; + keymap[GLFW_KEY_EQUAL] = '='; + keymap[GLFW_KEY_A] = 'A'; + keymap[GLFW_KEY_B] = 'B'; + keymap[GLFW_KEY_C] = 'C'; + keymap[GLFW_KEY_D] = 'D'; + keymap[GLFW_KEY_E] = 'E'; + keymap[GLFW_KEY_F] = 'F'; + keymap[GLFW_KEY_G] = 'G'; + keymap[GLFW_KEY_H] = 'H'; + keymap[GLFW_KEY_I] = 'I'; + keymap[GLFW_KEY_J] = 'J'; + keymap[GLFW_KEY_K] = 'K'; + keymap[GLFW_KEY_L] = 'L'; + keymap[GLFW_KEY_M] = 'M'; + keymap[GLFW_KEY_N] = 'N'; + keymap[GLFW_KEY_O] = 'O'; + keymap[GLFW_KEY_P] = 'P'; + keymap[GLFW_KEY_Q] = 'Q'; + keymap[GLFW_KEY_R] = 'R'; + keymap[GLFW_KEY_S] = 'S'; + keymap[GLFW_KEY_T] = 'T'; + keymap[GLFW_KEY_U] = 'U'; + keymap[GLFW_KEY_V] = 'V'; + keymap[GLFW_KEY_W] = 'W'; + keymap[GLFW_KEY_X] = 'X'; + keymap[GLFW_KEY_Y] = 'Y'; + keymap[GLFW_KEY_Z] = 'Z'; + keymap[GLFW_KEY_LEFT_BRACKET] = '['; + keymap[GLFW_KEY_BACKSLASH] = '\\'; + keymap[GLFW_KEY_RIGHT_BRACKET] = ']'; + keymap[GLFW_KEY_GRAVE_ACCENT] = '`'; + keymap[GLFW_KEY_ESCAPE] = KEY_ESC; + keymap[GLFW_KEY_ENTER] = KEY_ENTER; + keymap[GLFW_KEY_TAB] = KEY_TAB; + keymap[GLFW_KEY_BACKSPACE] = KEY_BACKSP; + keymap[GLFW_KEY_INSERT] = KEY_INS; + keymap[GLFW_KEY_DELETE] = KEY_DEL; + keymap[GLFW_KEY_RIGHT] = KEY_RIGHT; + keymap[GLFW_KEY_LEFT] = KEY_LEFT; + keymap[GLFW_KEY_DOWN] = KEY_DOWN; + keymap[GLFW_KEY_UP] = KEY_UP; + keymap[GLFW_KEY_PAGE_UP] = KEY_PGUP; + keymap[GLFW_KEY_PAGE_DOWN] = KEY_PGDN; + keymap[GLFW_KEY_HOME] = KEY_HOME; + keymap[GLFW_KEY_END] = KEY_END; + keymap[GLFW_KEY_CAPS_LOCK] = KEY_CAPSLK; + keymap[GLFW_KEY_SCROLL_LOCK] = KEY_NULL; + keymap[GLFW_KEY_NUM_LOCK] = KEY_NULL; + keymap[GLFW_KEY_PRINT_SCREEN] = KEY_NULL; + keymap[GLFW_KEY_PAUSE] = KEY_NULL; + + keymap[GLFW_KEY_F1] = KEY_F1; + keymap[GLFW_KEY_F2] = KEY_F2; + keymap[GLFW_KEY_F3] = KEY_F3; + keymap[GLFW_KEY_F4] = KEY_F4; + keymap[GLFW_KEY_F5] = KEY_F5; + keymap[GLFW_KEY_F6] = KEY_F6; + keymap[GLFW_KEY_F7] = KEY_F7; + keymap[GLFW_KEY_F8] = KEY_F8; + keymap[GLFW_KEY_F9] = KEY_F9; + keymap[GLFW_KEY_F10] = KEY_F10; + keymap[GLFW_KEY_F11] = KEY_F11; + keymap[GLFW_KEY_F12] = KEY_F12; + keymap[GLFW_KEY_F13] = KEY_NULL; + keymap[GLFW_KEY_F14] = KEY_NULL; + keymap[GLFW_KEY_F15] = KEY_NULL; + keymap[GLFW_KEY_F16] = KEY_NULL; + keymap[GLFW_KEY_F17] = KEY_NULL; + keymap[GLFW_KEY_F18] = KEY_NULL; + keymap[GLFW_KEY_F19] = KEY_NULL; + keymap[GLFW_KEY_F20] = KEY_NULL; + keymap[GLFW_KEY_F21] = KEY_NULL; + keymap[GLFW_KEY_F22] = KEY_NULL; + keymap[GLFW_KEY_F23] = KEY_NULL; + keymap[GLFW_KEY_F24] = KEY_NULL; + keymap[GLFW_KEY_F25] = KEY_NULL; + keymap[GLFW_KEY_KP_0] = KEY_NULL; + keymap[GLFW_KEY_KP_1] = KEY_NULL; + keymap[GLFW_KEY_KP_2] = KEY_NULL; + keymap[GLFW_KEY_KP_3] = KEY_NULL; + keymap[GLFW_KEY_KP_4] = KEY_NULL; + keymap[GLFW_KEY_KP_5] = KEY_NULL; + keymap[GLFW_KEY_KP_6] = KEY_NULL; + keymap[GLFW_KEY_KP_7] = KEY_NULL; + keymap[GLFW_KEY_KP_8] = KEY_NULL; + keymap[GLFW_KEY_KP_9] = KEY_NULL; + keymap[GLFW_KEY_KP_DECIMAL] = KEY_NULL; + keymap[GLFW_KEY_KP_DIVIDE] = KEY_NULL; + keymap[GLFW_KEY_KP_MULTIPLY] = KEY_NULL; + keymap[GLFW_KEY_KP_SUBTRACT] = KEY_NULL; + keymap[GLFW_KEY_KP_ADD] = KEY_NULL; + keymap[GLFW_KEY_KP_ENTER] = KEY_NULL; + keymap[GLFW_KEY_KP_EQUAL] = KEY_NULL; + keymap[GLFW_KEY_LEFT_SHIFT] = KEY_LSHIFT; + keymap[GLFW_KEY_LEFT_CONTROL] = KEY_LCTRL; + keymap[GLFW_KEY_LEFT_ALT] = KEY_LALT; + keymap[GLFW_KEY_LEFT_SUPER] = KEY_NULL; + keymap[GLFW_KEY_RIGHT_SHIFT] = KEY_RSHIFT; + keymap[GLFW_KEY_RIGHT_CONTROL] = KEY_RCTRL; + keymap[GLFW_KEY_RIGHT_ALT] = KEY_RALT; + keymap[GLFW_KEY_RIGHT_SUPER] = KEY_NULL; + keymap[GLFW_KEY_MENU] = KEY_NULL; +} + +static void KeyUp(int key) { EventHandler(KEYUP, &key); } +static void KeyDown(int key) { EventHandler(KEYDOWN, &key); } + +static void +keypress(GLFWwindow *window, int key, int scancode, int action, int mods) +{ + if(key >= 0 && key <= GLFW_KEY_LAST){ + if(action == GLFW_RELEASE) KeyUp(keymap[key]); + else if(action == GLFW_PRESS) KeyDown(keymap[key]); + else if(action == GLFW_REPEAT) KeyDown(keymap[key]); + } +} + +int +main(int argc, char *argv[]) +{ + EventHandler(INITIALIZE, nil); + + engineStartParams.width = sk::globals.width; + engineStartParams.height = sk::globals.height; + engineStartParams.windowtitle = sk::globals.windowtitle; + engineStartParams.window = &window; + + if(EventHandler(RWINITIALIZE, nil) == EVENTERROR) + return 0; + + initkeymap(); + glfwSetKeyCallback(window, keypress); + + float lastTime = glfwGetTime()*1000; + while(!sk::globals.quit && !glfwWindowShouldClose(window)){ + float currTime = glfwGetTime()*1000; + float timeDelta = (currTime - lastTime)*0.001f; + glfwPollEvents(); + + EventHandler(IDLE, &timeDelta); + + lastTime = currTime; + } + + EventHandler(RWTERMINATE, nil); + + return 0; +} +#endif diff --git a/skeleton/skeleton.cpp b/skeleton/skeleton.cpp new file mode 100644 index 0000000..d0bdb55 --- /dev/null +++ b/skeleton/skeleton.cpp @@ -0,0 +1,57 @@ +#include +#include "skeleton.h" + +namespace sk { + +Globals globals; + +bool +InitRW(void) +{ + if(!rw::Engine::init()) + return false; + if(AppEventHandler(sk::PLUGINATTACH, nil) == EVENTERROR) + return false; + if(!rw::Engine::open()) + return false; + if(!rw::Engine::start(&engineStartParams)) + return false; + rw::engine->loadTextures = 1; + + rw::TexDictionary::setCurrent(rw::TexDictionary::create()); + rw::Image::setSearchPath("."); + return true; +} + +void +TerminateRW(void) +{ + // TODO: delete all tex dicts + rw::Engine::stop(); + rw::Engine::close(); + rw::Engine::term(); +} + +EventStatus +EventHandler(Event e, void *param) +{ + EventStatus s; + s = AppEventHandler(e, param); + if(e == QUIT){ + globals.quit = 1; + return EVENTPROCESSED; + } + if(s == EVENTNOTPROCESSED) + switch(e){ + case RWINITIALIZE: + return InitRW() ? EVENTPROCESSED : EVENTERROR; + case RWTERMINATE: + TerminateRW(); + return EVENTPROCESSED; + default: + break; + } + return s; +} + +} diff --git a/skeleton/skeleton.h b/skeleton/skeleton.h new file mode 100644 index 0000000..dc049d7 --- /dev/null +++ b/skeleton/skeleton.h @@ -0,0 +1,91 @@ +extern rw::EngineStartParams engineStartParams; + +namespace sk { + +using namespace rw; + +// same as RW skeleton +enum Key +{ + // ascii... + + KEY_ESC = 128, + + KEY_F1 = 129, + KEY_F2 = 130, + KEY_F3 = 131, + KEY_F4 = 132, + KEY_F5 = 133, + KEY_F6 = 134, + KEY_F7 = 135, + KEY_F8 = 136, + KEY_F9 = 137, + KEY_F10 = 138, + KEY_F11 = 139, + KEY_F12 = 140, + + KEY_INS = 141, + KEY_DEL = 142, + KEY_HOME = 143, + KEY_END = 144, + KEY_PGUP = 145, + KEY_PGDN = 146, + + KEY_UP = 147, + KEY_DOWN = 148, + KEY_LEFT = 149, + KEY_RIGHT = 150, + + // some stuff ommitted + + KEY_BACKSP = 168, + KEY_TAB = 169, + KEY_CAPSLK = 170, + KEY_ENTER = 171, + KEY_LSHIFT = 172, + KEY_RSHIFT = 173, + KEY_LCTRL = 174, + KEY_RCTRL = 175, + KEY_LALT = 176, + KEY_RALT = 177, + + KEY_NULL, // unused + KEY_NUMKEYS, +}; + +enum EventStatus +{ + EVENTERROR, + EVENTPROCESSED, + EVENTNOTPROCESSED +}; + +enum Event +{ + INITIALIZE, + RWINITIALIZE, + RWTERMINATE, + SELECTDEVICE, + PLUGINATTACH, + KEYDOWN, + KEYUP, + IDLE, + QUIT +}; + +struct Globals +{ + const char *windowtitle; + int32 width; + int32 height; + bool32 quit; +}; +extern Globals globals; + +bool InitRW(void); +void TerminateRW(void); +EventStatus EventHandler(Event e, void *param); + +} + +sk::EventStatus AppEventHandler(sk::Event e, void *param); diff --git a/skeleton/win.cpp b/skeleton/win.cpp new file mode 100644 index 0000000..b8cc034 --- /dev/null +++ b/skeleton/win.cpp @@ -0,0 +1,217 @@ +#include +#include +#include "skeleton.h" + +using namespace sk; +using namespace rw; + +#ifdef RW_D3D9 + +static int keymap[256]; +static void +initkeymap(void) +{ + int i; + for(i = 0; i < 256; i++) + keymap[i] = KEY_NULL; + keymap[VK_SPACE] = ' '; + keymap[VK_OEM_7] = '\''; + keymap[VK_OEM_COMMA] = ','; + keymap[VK_OEM_MINUS] = '-'; + keymap[VK_OEM_PERIOD] = '.'; + keymap[VK_OEM_2] = '/'; + for(i = '0'; i <= '9'; i++) + keymap[i] = i; + keymap[VK_OEM_1] = ';'; + keymap[VK_OEM_NEC_EQUAL] = '='; + for(i = 'A'; i <= 'Z'; i++) + keymap[i] = i; + keymap[VK_OEM_4] = '['; + keymap[VK_OEM_5] = '\\'; + keymap[VK_OEM_6] = ']'; + keymap[VK_OEM_3] = '`'; + keymap[VK_ESCAPE] = KEY_ESC; + keymap[VK_RETURN] = KEY_ENTER; + keymap[VK_TAB] = KEY_TAB; + keymap[VK_BACK] = KEY_BACKSP; + keymap[VK_INSERT] = KEY_INS; + keymap[VK_DELETE] = KEY_DEL; + keymap[VK_RIGHT] = KEY_RIGHT; + keymap[VK_LEFT] = KEY_LEFT; + keymap[VK_DOWN] = KEY_DOWN; + keymap[VK_UP] = KEY_UP; + keymap[VK_PRIOR] = KEY_PGUP; + keymap[VK_NEXT] = KEY_PGDN; + keymap[VK_HOME] = KEY_HOME; + keymap[VK_END] = KEY_END; + keymap[VK_MODECHANGE] = KEY_CAPSLK; + for(i = VK_F1; i <= VK_F24; i++) + keymap[i] = i-VK_F1+KEY_F1; + keymap[VK_LSHIFT] = KEY_LSHIFT; + keymap[VK_LCONTROL] = KEY_LCTRL; + keymap[VK_LMENU] = KEY_LALT; + keymap[VK_RSHIFT] = KEY_RSHIFT; + keymap[VK_RCONTROL] = KEY_RCTRL; + keymap[VK_RMENU] = KEY_RALT; +} +bool running; + +static void KeyUp(int key) { EventHandler(KEYUP, &key); } +static void KeyDown(int key) { EventHandler(KEYDOWN, &key); } + +LRESULT CALLBACK +WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + switch(msg){ + case WM_DESTROY: + PostQuitMessage(0); + break; + + case WM_SYSKEYDOWN: + case WM_KEYDOWN: + if(wParam == VK_MENU){ + if(GetKeyState(VK_LMENU) & 0x8000) KeyDown(keymap[VK_LMENU]); + if(GetKeyState(VK_RMENU) & 0x8000) KeyDown(keymap[VK_RMENU]); + }else if(wParam == VK_CONTROL){ + if(GetKeyState(VK_LCONTROL) & 0x8000) KeyDown(keymap[VK_LCONTROL]); + if(GetKeyState(VK_RCONTROL) & 0x8000) KeyDown(keymap[VK_RCONTROL]); + }else if(wParam == VK_SHIFT){ + if(GetKeyState(VK_LSHIFT) & 0x8000) KeyDown(keymap[VK_LSHIFT]); + if(GetKeyState(VK_RSHIFT) & 0x8000) KeyDown(keymap[VK_RSHIFT]); + }else + KeyDown(keymap[wParam]); + break; + + case WM_SYSKEYUP: + case WM_KEYUP: + if(wParam == VK_MENU){ + if((GetKeyState(VK_LMENU) & 0x8000) == 0) KeyUp(keymap[VK_LMENU]); + if((GetKeyState(VK_RMENU) & 0x8000) == 0) KeyUp(keymap[VK_RMENU]); + }else if(wParam == VK_CONTROL){ + if((GetKeyState(VK_LCONTROL) & 0x8000) == 0) KeyUp(keymap[VK_LCONTROL]); + if((GetKeyState(VK_RCONTROL) & 0x8000) == 0) KeyUp(keymap[VK_RCONTROL]); + }else if(wParam == VK_SHIFT){ + if((GetKeyState(VK_LSHIFT) & 0x8000) == 0) KeyUp(keymap[VK_LSHIFT]); + if((GetKeyState(VK_RSHIFT) & 0x8000) == 0) KeyUp(keymap[VK_RSHIFT]); + }else + KeyUp(keymap[wParam]); + break; + + case WM_CLOSE: + DestroyWindow(hwnd); + break; + + case WM_QUIT: + running = false; + break; + } + return DefWindowProc(hwnd, msg, wParam, lParam); +} + +HWND +MakeWindow(HINSTANCE instance, int width, int height, const char *title) +{ + WNDCLASS wc; + wc.style = CS_HREDRAW | CS_VREDRAW; + wc.lpfnWndProc = WndProc; + wc.cbClsExtra = 0; + wc.cbWndExtra = 0; + wc.hInstance = instance; + wc.hIcon = LoadIcon(0, IDI_APPLICATION); + wc.hCursor = LoadCursor(0, IDC_ARROW); + wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); + wc.lpszMenuName = 0; + wc.lpszClassName = "librwD3D9"; + if(!RegisterClass(&wc)){ + MessageBox(0, "RegisterClass() - FAILED", 0, 0); + return 0; + } + + HWND win; + win = CreateWindow("librwD3D9", title, + WS_BORDER | WS_CAPTION | WS_SYSMENU | + WS_MINIMIZEBOX | WS_MAXIMIZEBOX, + 0, 0, width, height, 0, 0, instance, 0); + if(!win){ + MessageBox(0, "CreateWindow() - FAILED", 0, 0); + return 0; + } + ShowWindow(win, SW_SHOW); + UpdateWindow(win); + return win; +} + +void +pollEvents(void) +{ + MSG msg; + while(PeekMessage(&msg, 0, 0, 0, PM_REMOVE)){ + if(msg.message == WM_QUIT){ + running = false; + break; + }else{ + TranslateMessage(&msg); + DispatchMessage(&msg); + } + } +} + +int WINAPI +WinMain(HINSTANCE instance, HINSTANCE, + PSTR cmdLine, int showCmd) +{ + AllocConsole(); + freopen("CONIN$", "r", stdin); + freopen("CONOUT$", "w", stdout); + freopen("CONOUT$", "w", stderr); + + EventHandler(INITIALIZE, nil); + + HWND win = MakeWindow(instance, + sk::globals.width, sk::globals.height, + sk::globals.windowtitle); + if(win == 0){ + MessageBox(0, "MakeWindow() - FAILED", 0, 0); + return 0; + } + engineStartParams.window = win; + initkeymap(); + + if(EventHandler(RWINITIALIZE, nil) == EVENTERROR) + return 0; + + float lastTime = (float)GetTickCount(); + running = true; + while(pollEvents(), !globals.quit){ + float currTime = (float)GetTickCount(); + float timeDelta = (currTime - lastTime)*0.001f; + + EventHandler(IDLE, &timeDelta); + + lastTime = currTime; + } + + EventHandler(RWTERMINATE, nil); + + return 0; +} +#endif + +#ifdef RW_OPENGL +int main(int argc, char *argv[]); + +int WINAPI +WinMain(HINSTANCE instance, HINSTANCE, + PSTR cmdLine, int showCmd) +{ + char *argv[1] = { + "clumpview", + }; + AllocConsole(); + freopen("CONIN$", "r", stdin); + freopen("CONOUT$", "w", stdout); + freopen("CONOUT$", "w", stderr); + + return main(1, argv); +} +#endif diff --git a/src/base.cpp b/src/base.cpp index 7ac5f11..98a1678 100644 --- a/src/base.cpp +++ b/src/base.cpp @@ -301,6 +301,26 @@ Matrix::scale(V3d *scale, CombineOp op) return this; } +Matrix* +Matrix::transform(Matrix *mat, CombineOp op) +{ + Matrix tmp; + switch(op){ + case COMBINEREPLACE: + *this = *mat; + break; + case COMBINEPRECONCAT: + mult(&tmp, mat, this); + *this = tmp; + break; + case COMBINEPOSTCONCAT: + mult(&tmp, this, mat); + *this = tmp; + break; + } + return this; +} + void Matrix::lookAt(const V3d &dir, const V3d &up) { diff --git a/src/engine.cpp b/src/engine.cpp index 5b78309..d9d5043 100644 --- a/src/engine.cpp +++ b/src/engine.cpp @@ -128,6 +128,16 @@ Engine::start(EngineStartParams *p) return 1; } +void +Engine::term(void) +{ +} + +void +Engine::close(void) +{ +} + void Engine::stop(void) { diff --git a/src/frame.cpp b/src/frame.cpp index d868039..80fbe41 100644 --- a/src/frame.cpp +++ b/src/frame.cpp @@ -269,6 +269,13 @@ Frame::scale(V3d *scl, CombineOp op) updateObjects(); } +void +Frame::transform(Matrix *mat, CombineOp op) +{ + this->matrix.transform(mat, op); + updateObjects(); +} + void Frame::updateObjects(void) { diff --git a/src/rwbase.h b/src/rwbase.h index 7955006..7497601 100644 --- a/src/rwbase.h +++ b/src/rwbase.h @@ -231,6 +231,7 @@ struct Matrix Matrix *rotate(const Quat &q, CombineOp op); Matrix *translate(V3d *translation, CombineOp op); Matrix *scale(V3d *scl, CombineOp op); + Matrix *transform(Matrix *mat, CombineOp op); void lookAt(const V3d &dir, const V3d &up); // helper functions. consider private diff --git a/src/rwengine.h b/src/rwengine.h index 1428c1e..a1c835c 100644 --- a/src/rwengine.h +++ b/src/rwengine.h @@ -123,6 +123,8 @@ struct Engine static bool32 init(void); static bool32 open(void); static bool32 start(EngineStartParams*); + static void term(void); + static void close(void); static void stop(void); }; diff --git a/src/rwobjects.h b/src/rwobjects.h index 5bbf75b..d38373a 100644 --- a/src/rwobjects.h +++ b/src/rwobjects.h @@ -125,7 +125,8 @@ struct Frame : PluginBase Matrix *getLTM(void); void rotate(V3d *axis, float32 angle, CombineOp op); void translate(V3d *trans, CombineOp op); - void scale(V3d *trans, CombineOp op); + void scale(V3d *scale, CombineOp op); + void transform(Matrix *mat, CombineOp op); void updateObjects(void); diff --git a/tools/clumpview/camera.cpp b/tools/clumpview/camera.cpp new file mode 100644 index 0000000..27185aa --- /dev/null +++ b/tools/clumpview/camera.cpp @@ -0,0 +1,134 @@ +#include +#include + +#include + +#define PI 3.14159265359f +#include "camera.h" + +using rw::Quat; +using rw::V3d; + +void +Camera::update(void) +{ + if(m_rwcam){ + m_rwcam->nearPlane = m_near; + m_rwcam->farPlane = m_far; + m_rwcam->setFOV(m_fov, m_aspectRatio); + + rw::Frame *f = m_rwcam->getFrame(); + if(f){ + V3d forward = normalize(sub(m_target, m_position)); + V3d left = normalize(cross(m_up, forward)); + V3d nup = cross(forward, left); + f->matrix.right = left; // lol + f->matrix.up = nup; + f->matrix.at = forward; + f->matrix.pos = m_position; + f->matrix.optimize(); + f->updateObjects(); + } + } +} + +void +Camera::setTarget(V3d target) +{ + m_position = sub(m_position, sub(m_target, target)); + m_target = target; +} + +float +Camera::getHeading(void) +{ + V3d dir = sub(m_target, m_position); + float a = atan2(dir.y, dir.x)-PI/2.0f; + return m_localup.z < 0.0f ? a-PI : a; +} + +void +Camera::turn(float yaw, float pitch) +{ + V3d dir = sub(m_target, m_position); + Quat r = Quat::rotation(yaw, rw::makeV3d(0.0f, 0.0f, 1.0f)); + dir = rotate(dir, r); + m_localup = rotate(m_localup, r); + + V3d right = normalize(cross(dir, m_localup)); + r = Quat::rotation(pitch, right); + dir = rotate(dir, r); + m_localup = normalize(cross(right, dir)); + if(m_localup.z >= 0.0) m_up.z = 1.0; + else m_up.z = -1.0f; + + m_target = add(m_position, dir); +} + +void +Camera::orbit(float yaw, float pitch) +{ + V3d dir = sub(m_target, m_position); + Quat r = Quat::rotation(yaw, rw::makeV3d(0.0f, 0.0f, 1.0f)); + dir = rotate(dir, r); + m_localup = rotate(m_localup, r); + + V3d right = normalize(cross(dir, m_localup)); + r = Quat::rotation(-pitch, right); + dir = rotate(dir, r); + m_localup = normalize(cross(right, dir)); + if(m_localup.z >= 0.0) m_up.z = 1.0; + else m_up.z = -1.0f; + + m_position = sub(m_target, dir); +} + +void +Camera::dolly(float dist) +{ + V3d dir = setlength(sub(m_target, m_position), dist); + m_position = add(m_position, dir); + m_target = add(m_target, dir); +} + +void +Camera::zoom(float dist) +{ + V3d dir = sub(m_target, m_position); + float curdist = length(dir); + if(dist >= curdist) + dist = curdist-0.01f; + dir = setlength(dir, dist); + m_position = add(m_position, dir); +} + +void +Camera::pan(float x, float y) +{ + V3d dir = normalize(sub(m_target, m_position)); + V3d right = normalize(cross(dir, m_up)); + V3d localup = normalize(cross(right, dir)); + dir = add(scale(right, x), scale(localup, y)); + m_position = add(m_position, dir); + m_target = add(m_target, dir); +} + +float +Camera::distanceTo(V3d v) +{ + return length(sub(m_position, v)); +} + +Camera::Camera() +{ + m_position.set(0.0f, 6.0f, 0.0f); + m_target.set(0.0f, 0.0f, 0.0f); + m_up.set(0.0f, 0.0f, 1.0f); + m_localup = m_up; + m_fov = 70.0f; + m_aspectRatio = 1.0f; + m_near = 0.1f; + m_far = 100.0f; + m_rwcam = NULL; +} + diff --git a/tools/clumpview/camera.h b/tools/clumpview/camera.h new file mode 100644 index 0000000..8a0315d --- /dev/null +++ b/tools/clumpview/camera.h @@ -0,0 +1,26 @@ +class Camera +{ +public: + rw::Camera *m_rwcam; + rw::V3d m_position; + rw::V3d m_target; + rw::V3d m_up; + rw::V3d m_localup; + + float m_fov, m_aspectRatio; + float m_near, m_far; + + + void setTarget(rw::V3d target); + float getHeading(void); + + void turn(float yaw, float pitch); + void orbit(float yaw, float pitch); + void dolly(float dist); + void zoom(float dist); + void pan(float x, float y); + + void update(void); + float distanceTo(rw::V3d v); + Camera(void); +}; diff --git a/tools/clumpview/main.cpp b/tools/clumpview/main.cpp new file mode 100644 index 0000000..124534d --- /dev/null +++ b/tools/clumpview/main.cpp @@ -0,0 +1,181 @@ +#include +#include +#include "camera.h" +#include + +rw::V3d zero = { 0.0f, 0.0f, 0.0f }; +Camera *camera; +rw::Clump *clump; +rw::World *world; +rw::EngineStartParams engineStartParams; + +void +Init(void) +{ + sk::globals.windowtitle = "Clump viewer"; + sk::globals.width = 640; + sk::globals.height = 480; + sk::globals.quit = 0; +} + +bool +attachPlugins(void) +{ + rw::ps2::registerPDSPlugin(40); + rw::ps2::registerPluginPDSPipes(); + + rw::registerMeshPlugin(); + rw::registerNativeDataPlugin(); + rw::registerAtomicRightsPlugin(); + rw::registerMaterialRightsPlugin(); + rw::xbox::registerVertexFormatPlugin(); + rw::registerSkinPlugin(); + rw::registerHAnimPlugin(); + rw::registerMatFXPlugin(); + rw::registerUVAnimPlugin(); + rw::ps2::registerADCPlugin(); + return true; +} + +bool +InitRW(void) +{ +// rw::platform = rw::PLATFORM_D3D8; + if(!sk::InitRW()) + return false; + + char *filename = "teapot.dff"; + rw::StreamFile in; + if(in.open(filename, "rb") == NULL){ + printf("couldn't open file\n"); + return false; + } + rw::findChunk(&in, rw::ID_CLUMP, NULL, NULL); + clump = rw::Clump::streamRead(&in); + assert(clump); + in.close(); + + clump->getFrame()->translate(&zero, rw::COMBINEREPLACE); + + FORLIST(lnk, clump->atomics){ + rw::Atomic *a = rw::Atomic::fromClump(lnk); + if(a->pipeline && a->pipeline->platform != rw::platform) + a->pipeline = NULL; + } + + + world = rw::World::create(); + + rw::Light *ambient = rw::Light::create(rw::Light::AMBIENT); + ambient->setColor(0.2f, 0.2f, 0.2f); + world->addLight(ambient); + + rw::V3d xaxis = { 1.0f, 0.0f, 0.0f }; + rw::Light *direct = rw::Light::create(rw::Light::DIRECTIONAL); + direct->setColor(0.8f, 0.8f, 0.8f); + direct->setFrame(rw::Frame::create()); + direct->getFrame()->rotate(&xaxis, 180.0f, rw::COMBINEREPLACE); + world->addLight(direct); + + camera = new Camera; + camera->m_rwcam = rw::Camera::create(); + camera->m_rwcam->setFrame(rw::Frame::create()); + camera->m_aspectRatio = 640.0f/480.0f; + camera->m_near = 0.1f; + camera->m_far = 450.0f; + camera->m_target.set(0.0f, 0.0f, 0.0f); + camera->m_position.set(0.0f, -10.0f, 0.0f); +// camera->setPosition(Vec3(0.0f, 5.0f, 0.0f)); +// camera->setPosition(Vec3(0.0f, -70.0f, 0.0f)); +// camera->setPosition(Vec3(0.0f, -1.0f, 3.0f)); + camera->update(); + + world->addCamera(camera->m_rwcam); + + return true; +} + +void +Draw(float timeDelta) +{ + static rw::RGBA clearcol = { 0x80, 0x80, 0x80, 0xFF }; + camera->m_rwcam->clear(&clearcol, rw::Camera::CLEARIMAGE|rw::Camera::CLEARZ); + camera->update(); + camera->m_rwcam->beginUpdate(); + + clump->render(); + + camera->m_rwcam->endUpdate(); + camera->m_rwcam->showRaster(); +} + + +void +KeyUp(int key) +{ +} + +void +KeyDown(int key) +{ + switch(key){ + case 'W': + camera->orbit(0.0f, 0.1f); + break; + case 'S': + camera->orbit(0.0f, -0.1f); + break; + case 'A': + camera->orbit(-0.1f, 0.0f); + break; + case 'D': + camera->orbit(0.1f, 0.0f); + break; + case sk::KEY_UP: + camera->turn(0.0f, 0.1f); + break; + case sk::KEY_DOWN: + camera->turn(0.0f, -0.1f); + break; + case sk::KEY_LEFT: + camera->turn(0.1f, 0.0f); + break; + case sk::KEY_RIGHT: + camera->turn(-0.1f, 0.0f); + break; + case 'R': + camera->zoom(0.1f); + break; + case 'F': + camera->zoom(-0.1f); + break; + case sk::KEY_ESC: + sk::globals.quit = 1; + break; + } +} + +sk::EventStatus +AppEventHandler(sk::Event e, void *param) +{ + using namespace sk; + switch(e){ + case INITIALIZE: + Init(); + return EVENTPROCESSED; + case RWINITIALIZE: + return ::InitRW() ? EVENTPROCESSED : EVENTERROR; + case PLUGINATTACH: + return attachPlugins() ? EVENTPROCESSED : EVENTERROR; + case KEYDOWN: + KeyDown(*(int*)param); + return EVENTPROCESSED; + case KEYUP: + KeyUp(*(int*)param); + return EVENTPROCESSED; + case IDLE: + Draw(*(float*)param); + return EVENTPROCESSED; + } + return sk::EVENTNOTPROCESSED; +} diff --git a/tools/clumpview/teapot.dff b/tools/clumpview/teapot.dff new file mode 100644 index 0000000000000000000000000000000000000000..7a65176c0659c246a447047757cf984498180b97 GIT binary patch literal 42796 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 literal 0 HcmV?d00001