diff --git a/premake5.lua b/premake5.lua index dd6fffc..2557039 100755 --- a/premake5.lua +++ b/premake5.lua @@ -272,6 +272,14 @@ project "demoreel" removeplatforms { "*null" } removeplatforms { "ps2" } -- for now +project "clumpview" + kind "WindowedApp" + characterset ("MBCS") + skeltool("clumpview") + entrypoint("WinMainCRTStartup") + removeplatforms { "*null" } + removeplatforms { "ps2" } -- has its own Makefile + project "ska2anm" kind "ConsoleApp" characterset ("MBCS") diff --git a/tools/clumpview/Makefile b/tools/clumpview/Makefile new file mode 100644 index 0000000..757ae00 --- /dev/null +++ b/tools/clumpview/Makefile @@ -0,0 +1,62 @@ +# PS2 build (SCE toolchain). PC builds are generated by premake. +# Build librw for ps2 first: premake5 gmake; make -C build librw config=debug_ps2 + +CC=ee-gcc +CXX=ee-g++ + +TARGET=clumpview + +LIBRW := ../.. +OBJDIR := obj/ps2 + +SRC := main.cpp +SKELSRC := skeleton.cpp camera.cpp ps2.cpp + +SCELIBDIR := /usr/local/sce/ee/lib + +CFLAGS := -Os -DRW_PS2 -fno-common -fno-exceptions + +LIBS = $(LIBRW)/lib/ps2/Debug/librw.a \ + $(SCELIBDIR)/libgraph.a \ + $(SCELIBDIR)/libdma.a \ + $(SCELIBDIR)/libmc.a \ + $(SCELIBDIR)/libpc.a \ + $(SCELIBDIR)/libpad.a \ + $(SCELIBDIR)/libcdvd.a \ + $(SCELIBDIR)/libscf.a + +GCCLIB := /usr/local/sce/ee/gcc/lib/gcc-lib/ee/3.2-ee-030926 +CRT_BEGIN := $(OBJDIR)/crt0.o $(GCCLIB)/crti.o $(GCCLIB)/crtbegin.o +CRT_END := $(GCCLIB)/crtend.o $(GCCLIB)/crtn.o + +OBJ := $(addprefix $(OBJDIR)/,$(SRC:.cpp=.o) $(SKELSRC:.cpp=.o)) +DEP := $(OBJ:.o=.d) + +INC := -I. \ + -I$(LIBRW) \ + -I$(LIBRW)/skeleton \ + -I/usr/local/sce/common/include \ + -I/usr/local/sce/ee/include + +$(TARGET).elf: $(OBJDIR)/crt0.o $(OBJ) + $(CXX) -o $@ $(CRT_BEGIN) $(OBJ) $(LIBS) $(CRT_END) -T $(SCELIBDIR)/app.cmd -L$(SCELIBDIR) -lm -nostartfiles -Wl,--gc-sections + +run: $(TARGET).elf + dsedb -r run $(TARGET).elf + +$(OBJDIR)/crt0.o: + @mkdir -p $(@D) + $(CC) -c -xassembler-with-cpp -o $@ $(SCELIBDIR)/crt0.s + +$(OBJDIR)/%.o: %.cpp + @mkdir -p $(@D) + $(CXX) $(CFLAGS) $(INC) -MMD -c $< -o $@ + +$(OBJDIR)/%.o: $(LIBRW)/skeleton/%.cpp + @mkdir -p $(@D) + $(CXX) $(CFLAGS) $(INC) -MMD -c $< -o $@ + +clean: + rm -rf $(OBJDIR) $(TARGET).elf + +-include $(DEP) diff --git a/tools/clumpview/main.cpp b/tools/clumpview/main.cpp new file mode 100644 index 0000000..3aae4b1 --- /dev/null +++ b/tools/clumpview/main.cpp @@ -0,0 +1,401 @@ +#include +#include +#include + +#include +#include +#include + +char *argv0; + +rw::EngineOpenParams engineOpenParams; + +rw::RGBA BackgroundColor = { 64, 64, 96, 255 }; + +// defaults, so the ps2 build has something to load without arguments +static const char *dffPath = "files/world.dff"; +static const char *txdPath = nil; +static const char *imagePath = "files/"; + +struct SceneGlobals +{ + rw::World *world; + rw::Camera *camera; + rw::Clump *clump; +}; +static SceneGlobals Scene; + +static sk::Camera Cam; +static int flyMode = 0; +static float TimeDelta; + +static void +usage(void) +{ + fprintf(stderr, "usage: %s [-i imagepath] file.dff [file.txd]\n", argv0); + exit(1); +} + +static void +ParseArgs(void) +{ + int argc = sk::args.argc; + char **argv = sk::args.argv; + + ARGBEGIN{ + case 'i': + imagePath = EARGF(usage()); + break; + default: + usage(); + }ARGEND; + + // an explicit dff drops the default txd, it wouldn't belong to it + if(argc > 0){ + dffPath = argv[0]; + txdPath = nil; + } + if(argc > 1) + txdPath = argv[1]; +} + +/* + * Loading. + * Read whole files and stream from memory: seeking on the ps2 host + * fs is slow and negative seeks over ps2link are broken. + */ + +static void* +ReadFile(const char *path, rw::uint32 *len) +{ + char buf[SK_MAXPATH]; + void *data; + + data = rw::getFileContents(sk::GetFilePath(buf, path), len); + if(data == nil) + printf("couldn't open %s\n", buf); + return data; +} + +static rw::TexDictionary* +LoadTexDictionary(const char *path) +{ + rw::StreamMemory in; + rw::TexDictionary *txd; + rw::uint8 *data; + rw::uint32 len; + + data = (rw::uint8*)ReadFile(path, &len); + if(data == nil) + return nil; + txd = nil; + in.open(data, len); + if(rw::findChunk(&in, rw::ID_TEXDICTIONARY, nil, nil)) + txd = rw::TexDictionary::streamRead(&in); + in.close(); + rwFree(data); + return txd; +} + +static rw::Clump* +LoadClump(const char *path) +{ + rw::StreamMemory in; + rw::Clump *clump; + rw::uint8 *data; + rw::uint32 len; + + data = (rw::uint8*)ReadFile(path, &len); + if(data == nil) + return nil; + clump = nil; + in.open(data, len); + if(rw::findChunk(&in, rw::ID_CLUMP, nil, nil)) + clump = rw::Clump::streamRead(&in); + in.close(); + rwFree(data); + return clump; +} + +/* + * Fit the camera to whatever we loaded, so any model shows up + * at a sensible size without fiddling. + */ +static void +FrameClump(rw::Clump *clump) +{ + rw::Sphere *sph; + rw::V3d center, d; + float radius, dist; + int n; + + center.set(0.0f, 0.0f, 0.0f); + n = 0; + + { + FORLIST(lnk, clump->atomics){ + sph = rw::Atomic::fromClump(lnk)->getWorldBoundingSphere(); + center = rw::add(center, sph->center); + n++; + } + } + if(n == 0) + return; + center = rw::scale(center, 1.0f/n); + + // radius that covers every atomic's sphere from that center + radius = 0.0f; + { + FORLIST(lnk, clump->atomics){ + sph = rw::Atomic::fromClump(lnk)->getWorldBoundingSphere(); + dist = rw::length(rw::sub(sph->center, center)) + sph->radius; + if(dist > radius) + radius = dist; + } + } + if(radius < 0.001f) + radius = 1.0f; + + d.set(0.0f, -radius*2.5f, radius*0.7f); + Cam.m_target = center; + Cam.m_position = rw::add(center, d); + Cam.m_near = radius*0.01f; + Cam.m_far = radius*20.0f; + if(Cam.m_near < 0.05f) Cam.m_near = 0.05f; + if(Cam.m_far < 100.0f) Cam.m_far = 100.0f; + + // so flying feels the same on a teapot and on a world + sk::cameraControls.moveSpeed = radius; +} + +void +Initialize(void) +{ + sk::globals.windowtitle = "librw clump view"; + sk::globals.width = 1280; + sk::globals.height = 800; + sk::globals.quit = 0; +} + +bool +Initialize3D(void) +{ + char buf[SK_MAXPATH]; + rw::TexDictionary *txd; + rw::Light *light; + rw::V3d xaxis = { 1.0f, 0.0f, 0.0f }; + + if(!sk::InitRW()) + return false; + + rw::Image::setSearchPath(sk::GetFilePath(buf, imagePath)); + + if(txdPath){ + txd = LoadTexDictionary(txdPath); + if(txd) + rw::TexDictionary::setCurrent(txd); + } + + Scene.clump = LoadClump(dffPath); + if(Scene.clump == nil) + return false; + + Scene.world = rw::World::create(); + + light = rw::Light::create(rw::Light::AMBIENT); + light->setColor(0.4f, 0.4f, 0.4f); + Scene.world->addLight(light); + + light = rw::Light::create(rw::Light::DIRECTIONAL); + light->setColor(0.7f, 0.7f, 0.7f); + light->setFrame(rw::Frame::create()); + light->getFrame()->rotate(&xaxis, 150.0f, rw::COMBINEREPLACE); + Scene.world->addLight(light); + + Scene.world->addClump(Scene.clump); + + Scene.camera = sk::CameraCreate(sk::globals.width, sk::globals.height, 1); + assert(Scene.camera); + Scene.world->addCamera(Scene.camera); + + Cam.init(); + Cam.attach(Scene.camera); + Cam.m_aspectRatio = (float)sk::globals.width/sk::globals.height; + FrameClump(Scene.clump); + Cam.update(); + +#ifndef RW_PS2 + ImGui_ImplRW_Init(); + ImGui::StyleColorsClassic(); +#endif + + return true; +} + +void +Terminate3D(void) +{ + if(Scene.clump){ + Scene.world->removeClump(Scene.clump); + Scene.clump->destroy(); + Scene.clump = nil; + } + if(Scene.camera){ + Scene.world->removeCamera(Scene.camera); + sk::CameraDestroy(Scene.camera); + Scene.camera = nil; + } + // TODO: lights? + if(Scene.world){ + Scene.world->destroy(); + Scene.world = nil; + } + sk::TerminateRW(); +} + +bool +attachPlugins(void) +{ + rw::ps2::registerPDSPlugin(40); + rw::ps2::registerPluginPDSPipes(); + + rw::registerMeshPlugin(); + rw::registerNativeDataPlugin(); + rw::registerAtomicRightsPlugin(); + rw::registerMaterialRightsPlugin(); + rw::xbox::registerVertexFormatPlugin(); + rw::registerSkinPlugin(); + rw::registerUserDataPlugin(); + rw::registerHAnimPlugin(); + rw::registerMatFXPlugin(); + rw::registerUVAnimPlugin(); + rw::ps2::registerADCPlugin(); + return true; +} + +#ifndef RW_PS2 +void +Gui(void) +{ + static bool showWindow = true; + + ImGui::Begin("clump view", &showWindow); + ImGui::Text("%s", dffPath); + ImGui::Text("%.1f fps", 1.0f/TimeDelta); + ImGui::NewLine(); + if(ImGui::RadioButton("fly", flyMode != 0)) + flyMode = 1; + if(ImGui::RadioButton("editor", flyMode == 0)) + flyMode = 0; + ImGui::NewLine(); + ImGui::DragFloat("move speed", &sk::cameraControls.moveSpeed, 0.1f, 0.01f, 1e6f); + ImGui::DragFloat("mouse sens", &sk::cameraControls.mouseSens, 0.1f, 0.01f, 100.0f); + ImGui::End(); +} +#endif + +void +Render(void) +{ + Scene.camera->clear(&BackgroundColor, rw::Camera::CLEARIMAGE|rw::Camera::CLEARZ); + Scene.camera->beginUpdate(); + +#ifndef RW_PS2 + ImGui_ImplRW_NewFrame(TimeDelta); +#endif + + Scene.world->render(); + +#ifndef RW_PS2 + Gui(); + ImGui::EndFrame(); + ImGui::Render(); + ImGui_ImplRW_RenderDrawLists(ImGui::GetDrawData()); +#endif + + Scene.camera->endUpdate(); + Scene.camera->showRaster(0); +} + +void +Idle(float timeDelta) +{ + int wantmouse; + + TimeDelta = timeDelta; + if(TimeDelta <= 0.0f) TimeDelta = 1.0f/60.0f; + if(TimeDelta > 0.1f) TimeDelta = 0.1f; + + wantmouse = 0; +#ifndef RW_PS2 + wantmouse = ImGui::GetIO().WantCaptureMouse; +#endif + if(!wantmouse){ + if(flyMode) + sk::CameraFlyControls(&Cam, TimeDelta); + else + sk::CameraEditorControls(&Cam, TimeDelta); + } + Cam.update(); + + Render(); +} + +void +KeyDown(int key) +{ + switch(key){ + case sk::KEY_ESC: + sk::globals.quit = 1; + break; + case sk::KEY_TAB: + flyMode = !flyMode; + break; + case 'F': + FrameClump(Scene.clump); + break; + } +} + +sk::EventStatus +AppEventHandler(sk::Event e, void *param) +{ + using namespace sk; + Rect *r; + +#ifndef RW_PS2 + ImGuiEventHandler(e, param); +#endif + + switch(e){ + case INITIALIZE: + ParseArgs(); + Initialize(); + return EVENTPROCESSED; + case RWINITIALIZE: + return Initialize3D() ? EVENTPROCESSED : EVENTERROR; + case RWTERMINATE: + Terminate3D(); + return EVENTPROCESSED; + case PLUGINATTACH: + return attachPlugins() ? EVENTPROCESSED : EVENTERROR; + case KEYDOWN: + KeyDown(*(int*)param); + return EVENTPROCESSED; + case RESIZE: + r = (Rect*)param; + if(r->w == 0) r->w = 1; + if(r->h == 0) r->h = 1; + sk::globals.width = r->w; + sk::globals.height = r->h; + if(Scene.camera){ + sk::CameraSize(Scene.camera, r, 0.5f, 4.0f/3.0f); + Cam.m_aspectRatio = (float)r->w/r->h; + } + break; + case IDLE: + Idle(*(float*)param); + return EVENTPROCESSED; + } + return sk::EVENTNOTPROCESSED; +}