From 0a7eb48ce3ffee7c189780b09b8a6e0e998d399f Mon Sep 17 00:00:00 2001 From: aap Date: Sun, 9 Aug 2026 01:39:54 +0200 Subject: [PATCH] added simple demoreel and ps2 skeleton coded by claude fable 5 --- premake5.lua | 15 +- skeleton/ps2.cpp | 260 +++++++++++++++++++++++++ skeleton/skeleton.cpp | 2 + skeleton/skeleton.h | 2 + src/base.cpp | 33 ++++ src/gl/rwgl3.h | 4 +- src/png.cpp | 2 + src/ps2/ps2device.cpp | 44 ++++- src/rwbase.h | 32 ++- tools/CMakeLists.txt | 1 + tools/demoreel/CMakeLists.txt | 16 ++ tools/demoreel/Makefile | 62 ++++++ tools/demoreel/demoreel.h | 39 ++++ tools/demoreel/main.cpp | 301 +++++++++++++++++++++++++++++ tools/demoreel/proctex.cpp | 137 +++++++++++++ tools/demoreel/scene_knot.cpp | 221 +++++++++++++++++++++ tools/demoreel/scene_particles.cpp | 165 ++++++++++++++++ tools/demoreel/scene_tunnel.cpp | 155 +++++++++++++++ 18 files changed, 1482 insertions(+), 9 deletions(-) create mode 100644 skeleton/ps2.cpp create mode 100644 tools/demoreel/CMakeLists.txt create mode 100644 tools/demoreel/Makefile create mode 100644 tools/demoreel/demoreel.h create mode 100644 tools/demoreel/main.cpp create mode 100644 tools/demoreel/proctex.cpp create mode 100644 tools/demoreel/scene_knot.cpp create mode 100644 tools/demoreel/scene_particles.cpp create mode 100644 tools/demoreel/scene_tunnel.cpp diff --git a/premake5.lua b/premake5.lua index 105478e..dd6fffc 100755 --- a/premake5.lua +++ b/premake5.lua @@ -138,9 +138,10 @@ project "librw" files { "src/*/*.*" } filter { "platforms:*gl3" } files { "src/gl/glad/*.*" } - vucode() - filter { "platforms:ps2" } - files { "src/ps2/vu1/*.dsm" } + vucode() + filter { "platforms:ps2" } + files { "src/ps2/vu1/*.dsm" } + project "dumprwtree" kind "ConsoleApp" @@ -263,6 +264,14 @@ project "im3d" removeplatforms { "*null" } removeplatforms { "ps2" } +project "demoreel" + kind "WindowedApp" + characterset ("MBCS") + skeltool("demoreel") + entrypoint("WinMainCRTStartup") + removeplatforms { "*null" } + removeplatforms { "ps2" } -- for now + project "ska2anm" kind "ConsoleApp" characterset ("MBCS") diff --git a/skeleton/ps2.cpp b/skeleton/ps2.cpp new file mode 100644 index 0000000..3aa9541 --- /dev/null +++ b/skeleton/ps2.cpp @@ -0,0 +1,260 @@ +#ifdef RW_PS2 + +//#define SKEL_CDROM + +#include +#include +#include + +#include +#include +#include +#include +#ifdef SKEL_CDROM +#include +#endif + +#include +#include "skeleton.h" + +using namespace sk; +using namespace rw; + +// timer functions from the ps2 driver +void StartTime(void); +int GetTime(void); +float GetTimeF(void); + +/* + * Pad + */ + +struct PadData +{ + union { + unsigned short bits; + struct { + unsigned short l2 : 1; + unsigned short r2 : 1; + unsigned short l1 : 1; + unsigned short r1 : 1; + unsigned short triangle : 1; + unsigned short circle : 1; + unsigned short cross : 1; + unsigned short square : 1; + unsigned short select : 1; + unsigned short l3 : 1; + unsigned short r3 : 1; + unsigned short start : 1; + unsigned short Dup : 1; + unsigned short Dright : 1; + unsigned short Ddown : 1; + unsigned short Dleft : 1; + }; + }; + /* down/right is positive */ + float rX, rY; + float lX, lY; +}; + +struct Pad +{ + PadData prev, now; + PadData rising, falling; +}; + +static bool gotPadirx; +static Pad pad; +static u_long128 pad_dma_buf[scePadDmaBufferMax] __attribute__((aligned(64))); + +#define EPSILON 0.2f + +static float +PadAnalog(int x) +{ + float f = (x/255.0f - 0.5f)*2.0f; + if(f > EPSILON) return (f-EPSILON)/(1.0f-EPSILON); + if(f < -EPSILON) return (f+EPSILON)/(1.0f-EPSILON); + return 0.0f; +} + +static void +ReadPad(PadData *pd) +{ + u_char rdata[32]; + + if(gotPadirx && scePadRead(0, 0, rdata) > 0){ + pd->bits = 0xFFFF ^ ((rdata[2] << 8) | rdata[3]); + if(rdata[1] == 0x73){ + pd->rX = PadAnalog(rdata[4]); + pd->rY = PadAnalog(rdata[5]); + pd->lX = PadAnalog(rdata[6]); + pd->lY = PadAnalog(rdata[7]); + }else{ + pd->rX = pd->rY = 0.0f; + pd->lX = pd->lY = 0.0f; + } + }else + pd->bits = 0; +} + +static void +UpdatePad(Pad *p) +{ + p->prev = p->now; + ReadPad(&p->now); + p->rising = p->now; + p->rising.bits &= ~p->prev.bits; + p->falling = p->prev; + p->falling.bits &= ~p->now.bits; +} + +static void +InitPad(void) +{ + if(gotPadirx){ + scePadInit(0); + scePadPortOpen(0, 0, pad_dma_buf); + } +} + +// translate pad buttons to key events so apps +// written against the keyboard interface just work +static const struct { + unsigned short mask; + int key; +} padkeymap[] = { + { 0x8000, KEY_LEFT }, // Dleft + { 0x4000, KEY_DOWN }, // Ddown + { 0x2000, KEY_RIGHT }, // Dright + { 0x1000, KEY_UP }, // Dup + { 0x0040, ' ' }, // cross + { 0x0020, KEY_ENTER }, // circle + { 0x0080, KEY_TAB }, // square + { 0x0010, KEY_ESC }, // triangle + { 0x0004, KEY_PGUP }, // l1 + { 0x0008, KEY_PGDN }, // r1 + { 0x0800, KEY_ENTER }, // start +}; + +static void +PadEvents(void) +{ + int i, key; + + UpdatePad(&pad); + for(i = 0; i < (int)(sizeof(padkeymap)/sizeof(padkeymap[0])); i++){ + key = padkeymap[i].key; + if(pad.rising.bits & padkeymap[i].mask) + EventHandler(KEYDOWN, &key); + if(pad.falling.bits & padkeymap[i].mask) + EventHandler(KEYUP, &key); + } +} + +/* + * IOP modules + */ + +static char* +GetModulePath(char *dst, const char *module) +{ +#ifdef SKEL_CDROM + char *p; + sprintf(dst, "cdrom0:\\MODULES\\%s;1", module); + for(p = dst+7; *p; p++) + if(islower(*p)) + *p = toupper(*p); +#else + sprintf(dst, "host0:/usr/local/sce/iop/modules/%s", module); +#endif + return dst; +} + +static void +LoadModules(void) +{ + char buf[128]; + int i; + + gotPadirx = true; + for(i = 0; i < 10; i++) + if(sceSifLoadModule(GetModulePath(buf, "sio2man.irx"), 0, NULL) >= 0) + goto sio2ok; + printf("can't load module sio2man\n"); + gotPadirx = false; + return; +sio2ok: + for(i = 0; i < 10; i++) + if(sceSifLoadModule(GetModulePath(buf, "padman.irx"), 0, NULL) >= 0) + return; + printf("can't load module padman\n"); + gotPadirx = false; +} + +/* + * Main loop + */ + +int +main(int argc, char *argv[]) +{ + float timeDelta, drawTime, synchTime; + int frame; + + args.argc = argc; + args.argv = argv; + + sceSifInitRpc(0); +#ifdef SKEL_CDROM + while(!sceSifRebootIop("cdrom0:\\MODULES\\" IOP_IMAGE_FILE ";1")); + while(!sceSifSyncIop()); + sceSifInitRpc(0); + sceSifLoadFileReset(); + sceCdInit(SCECdINIT); + sceCdMmode(SCECdCD); + sceFsReset(); + sceSifInitIopHeap(); +#endif + LoadModules(); + InitPad(); + + if(EventHandler(INITIALIZE, nil) == EVENTERROR) + return 0; + + if(EventHandler(RWINITIALIZE, nil) == EVENTERROR) + return 0; + + timeDelta = 1.0f/60.0f; + frame = 0; + while(!sk::globals.quit){ + StartTime(); + + rw::ps2::beginFrame(frame); + + PadEvents(); + + EventHandler(IDLE, &timeDelta); + + rw::ps2::endFrame(&drawTime, &synchTime); + + timeDelta = GetTimeF()*0.001f; + frame++; + } + + EventHandler(RWTERMINATE, nil); + + return 0; +} + +namespace sk { + +// no window to move the pointer in +void +SetMousePosition(int x, int y) +{ +} + +} + +#endif diff --git a/skeleton/skeleton.cpp b/skeleton/skeleton.cpp index a3faea6..f69d21a 100644 --- a/skeleton/skeleton.cpp +++ b/skeleton/skeleton.cpp @@ -167,9 +167,11 @@ EventStatus EventHandler(Event e, void *param) { EventStatus s; +#ifndef RW_PS2 if (e == INITIALIZE) { ImGui::CreateContext(); } +#endif s = AppEventHandler(e, param); if(e == QUIT){ diff --git a/skeleton/skeleton.h b/skeleton/skeleton.h index a4d14f7..53c5211 100644 --- a/skeleton/skeleton.h +++ b/skeleton/skeleton.h @@ -118,5 +118,7 @@ EventStatus EventHandler(Event e, void *param); sk::EventStatus AppEventHandler(sk::Event e, void *param); +#ifndef RW_PS2 #include "imgui/imgui.h" #include "imgui/imgui_impl_rw.h" +#endif diff --git a/src/base.cpp b/src/base.cpp index 3c3ca08..4d493e7 100644 --- a/src/base.cpp +++ b/src/base.cpp @@ -89,6 +89,15 @@ mult(const Quat &q, const Quat &p) q.w*p.z + q.z*p.w + q.x*p.y - q.y*p.x); } +Quat +cross(const Quat &q, const Quat &p) +{ + return makeQuat(0.0f, + q.x*p.w + q.y*p.z - q.z*p.y, + q.y*p.w + q.z*p.x - q.x*p.z, + q.z*p.w + q.x*p.y - q.y*p.x); +} + Quat* Quat::rotate(const V3d *axis, float32 angle, CombineOp op) @@ -108,6 +117,30 @@ Quat::rotate(const V3d *axis, float32 angle, CombineOp op) return this; } +inline Quat quat(float32 w=1.0f, float32 x=0.0f, float32 y=0.0f, float32 z=0.0f) { return makeQuat(w, x, y, z); } + +Quat +exp(const Quat &q) +{ + Quat qv = qvec(q); + float32 l = norm(qv); + if(l == 0.0f) + return quat(expf(q.w)); + return scale(add(quat(cosf(l)), scale(qv, sinf(l)/l)), expf(q.w)); +} + +Quat +log(const Quat &q) +{ + float32 l = norm(q); + Quat p = scale(q, 1.0f/l); + float32 c = p.w; p.w = 0.0f; + float32 s = norm(p); + if(s == 0) + return quat(logf(l)); + return add(quat(logf(l)), scale(p, atan2f(s,c)/s)); +} + Quat lerp(const Quat &q, const Quat &p, float32 r) { diff --git a/src/gl/rwgl3.h b/src/gl/rwgl3.h index f7d963d..65eb777 100644 --- a/src/gl/rwgl3.h +++ b/src/gl/rwgl3.h @@ -7,7 +7,9 @@ #elif defined(LIBRW_GLFW) #include #else -not implemented +// sane fallback +#define LIBRW_GLFW +#include #endif #endif diff --git a/src/png.cpp b/src/png.cpp index 45aa102..3eb16e6 100644 --- a/src/png.cpp +++ b/src/png.cpp @@ -96,6 +96,7 @@ writePNG(Image *image, const char *filename) lodepng_state_init(&state); pixels = image->pixels; +int depth = image->depth; switch(image->depth){ case 4: state.info_raw.bitdepth = 4; @@ -125,6 +126,7 @@ writePNG(Image *image, const char *filename) // Don't think we can have 16 bits with PNG // TODO: don't change original image image->convertTo32(); + pixels = image->pixels; break; case 24: state.info_raw.colortype = LCT_RGB; diff --git a/src/ps2/ps2device.cpp b/src/ps2/ps2device.cpp index 2186b3a..490df95 100644 --- a/src/ps2/ps2device.cpp +++ b/src/ps2/ps2device.cpp @@ -513,7 +513,7 @@ InitGSregs(void) { uint128 tmp; - int nregs = 3; + int nregs = 4; MAKEQ(tmp, VIFdirect + nregs+1, VIFnop, 0, DMAcnt + nregs+1); vifPacket[vifPacksz++].q_u128 = tmp; MAKE128(tmp, 0xe, SCE_GIF_SET_TAG(nregs, 1, 0,0, SCE_GIF_PACKED, 1)); @@ -524,6 +524,9 @@ InitGSregs(void) vifPacket[vifPacksz++].q_u128 = tmp; MAKE128(tmp, SCE_GS_PRMODE, gsRegs.prmode); vifPacket[vifPacksz++].q_u128 = tmp; + // clamp additive blending instead of wrapping around + MAKE128(tmp, SCE_GS_COLCLAMP, 1); + vifPacket[vifPacksz++].q_u128 = tmp; } // cannot blend with color at all @@ -1155,6 +1158,34 @@ deviceSystem(DeviceReq req, void *arg, int32 n) // after plugins are constructed break; + // there's only one of everything + case DEVICEGETNUMSUBSYSTEMS: + return 1; + case DEVICEGETCURRENTSUBSYSTEM: + return 0; + case DEVICESETSUBSYSTEM: + return 1; + case DEVICEGETSUBSSYSTEMINFO: { + SubSystemInfo *info = (SubSystemInfo*)arg; + strncpy(info->name, "PlayStation 2", sizeof(info->name)); + return 1; + } + + case DEVICEGETNUMVIDEOMODES: + return 1; + case DEVICEGETCURRENTVIDEOMODE: + return 0; + case DEVICESETVIDEOMODE: + return n == 0; + case DEVICEGETVIDEOMODEINFO: { + VideoMode *mode = (VideoMode*)arg; + mode->width = SCREEN_WIDTH; + mode->height = SCREEN_HEIGHT; + mode->depth = 32; + mode->flags = VIDEOMODEEXCLUSIVE; + return 1; + } + default: printf("system request %d not implemented\n", req); return 0; @@ -1174,6 +1205,17 @@ beginFrame(int frame) void endFrame(float *t1, float *t2) { + uint128 tmp; + + // send a FINISH at the end of the frame so the + // interrupt handler can record the finish time + MAKEQ(tmp, VIFdirect + 2, VIFnop, 0, DMAcnt + 2); + vifPacket[vifPacksz++].q_u128 = tmp; + MAKE128(tmp, 0xe, SCE_GIF_SET_TAG(1, 1, 0,0, SCE_GIF_PACKED, 1)); + vifPacket[vifPacksz++].q_u128 = tmp; + MAKE128(tmp, SCE_GS_FINISH, 0); + vifPacket[vifPacksz++].q_u128 = tmp; + sceGsSyncPath(0, 0); *t1 = GetTimeF(); // we could start uploading textures here now so they'll be ready after vsynch diff --git a/src/rwbase.h b/src/rwbase.h index 6bc8a54..0f1eff0 100644 --- a/src/rwbase.h +++ b/src/rwbase.h @@ -290,15 +290,39 @@ inline Quat makeQuat(float32 w, const V3d &vec) { return makeQuat(w, vec.x, vec. inline Quat add(const Quat &q, const Quat &p) { return makeQuat(q.w+p.w, q.x+p.x, q.y+p.y, q.z+p.z); } inline Quat sub(const Quat &q, const Quat &p) { return makeQuat(q.w-p.w, q.x-p.x, q.y-p.y, q.z-p.z); } inline Quat negate(const Quat &q) { return makeQuat(-q.w, -q.x, -q.y, -q.z); } -inline float32 dot(const Quat &q, const Quat &p) { return q.w*p.w + q.x*p.x + q.y*p.y + q.z*p.z; } +inline Quat qvec(const Quat &q) { return makeQuat(0.0f, q.x, q.y, q.z); } inline Quat scale(const Quat &q, float32 r) { return makeQuat(q.w*r, q.x*r, q.y*r, q.z*r); } -inline float32 length(const Quat &q) { return sqrtf(q.w*q.w + q.x*q.x + q.y*q.y + q.z*q.z); } -inline Quat normalize(const Quat &q) { return scale(q, 1.0f/length(q)); } +inline float32 normsq(const Quat &q) { return q.w*q.w + q.x*q.x + q.y*q.y + q.z*q.z; } +inline float32 norm(const Quat &q) { return sqrtf(normsq(q)); } +inline Quat normalize(const Quat &q) { return scale(q, 1.0f/norm(q)); } +inline float32 length(const Quat &q) { return norm(q); } inline Quat conj(const Quat &q) { return makeQuat(q.w, -q.x, -q.y, -q.z); } +inline Quat inv(const Quat &q) { return scale(conj(q), 1.0f/normsq(q)); } Quat mult(const Quat &q, const Quat &p); -inline V3d rotate(const V3d &v, const Quat &q) { return mult(mult(q, makeQuat(0.0f, v)), conj(q)).vec(); } +Quat cross(const Quat &q, const Quat &p); +inline float32 inner(const Quat &q, const Quat &p) { return q.w*p.w - q.x*p.x - q.y*p.y - q.z*p.z; } +inline float32 dot(const Quat &q, const Quat &p) { return q.w*p.w + q.x*p.x + q.y*p.y + q.z*p.z; } +inline Quat sandwich(const Quat &q, const Quat &v) { return mult(mult(q, v), conj(q)); } +inline Quat transform(const Quat &q, const Quat &v) { return scale(sandwich(q, v), 1.0f/normsq(q)); } +Quat exp(const Quat &q); +Quat log(const Quat &q); Quat lerp(const Quat &q, const Quat &p, float32 r); Quat slerp(const Quat &q, const Quat &p, float32 a); +inline V3d rotate(const V3d &v, const Quat &q) { return transform(q, makeQuat(0.0f, v)).vec(); } + +// Dual Quaternion +// not a RW type +struct DQuat +{ + Quat r, d; +}; +inline DQuat makeDQuat(const Quat &r, const Quat &d) { DQuat q; q.r = r; q.d = d; return q; } +inline DQuat makeDQuat(const Quat &r) { DQuat q; q.r = r; q.d = makeQuat(0.0f, 0.0f, 0.0f, 0.0f); return q; } +inline DQuat add(const DQuat &q, const DQuat &p) { return makeDQuat(add(q.r,p.r), add(q.d,p.d)); } +inline DQuat sub(const DQuat &q, const DQuat &p) { return makeDQuat(sub(q.r,p.r), sub(q.d,p.d)); } +inline DQuat negate(const DQuat &q) { return makeDQuat(negate(q.r), negate(q.d)); } +inline DQuat scale(const DQuat &q, float32 r) { return makeDQuat(scale(q.r,r), scale(q.d,r)); } + struct RawMatrix { diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index be7ea66..e913f47 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -12,6 +12,7 @@ if(LIBRW_EXAMPLES) add_subdirectory(camera) add_subdirectory(im2d) add_subdirectory(im3d) + add_subdirectory(demoreel) endif() if(LIBRW_PLATFORM_PS2) diff --git a/tools/demoreel/CMakeLists.txt b/tools/demoreel/CMakeLists.txt new file mode 100644 index 0000000..a59e0e4 --- /dev/null +++ b/tools/demoreel/CMakeLists.txt @@ -0,0 +1,16 @@ +add_executable(demoreel WIN32 + demoreel.h + main.cpp + proctex.cpp + scene_knot.cpp + scene_particles.cpp + scene_tunnel.cpp +) + +target_link_libraries(demoreel + PRIVATE + librw::skeleton + librw::librw +) + +librw_platform_target(demoreel) diff --git a/tools/demoreel/Makefile b/tools/demoreel/Makefile new file mode 100644 index 0000000..9274c91 --- /dev/null +++ b/tools/demoreel/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=demoreel + +LIBRW := ../.. +OBJDIR := obj/ps2 + +SRC := main.cpp proctex.cpp scene_tunnel.cpp scene_particles.cpp scene_knot.cpp +SKELSRC := skeleton.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/demoreel/demoreel.h b/tools/demoreel/demoreel.h new file mode 100644 index 0000000..b7f2701 --- /dev/null +++ b/tools/demoreel/demoreel.h @@ -0,0 +1,39 @@ +#ifndef DEMOREEL_H +#define DEMOREEL_H + +#include +#include + +// A demo scene. Plain function pointers and static data only; +// scene code is meant to stay compilable for PS2 (old gcc, no STL). +struct DemoScene +{ + const char *name; + void (*init)(void); + void (*term)(void); + void (*update)(float dt); + void (*render)(void); +}; + +struct SceneGlobals +{ + rw::World *world; + rw::Camera *camera; +}; +extern SceneGlobals Scene; +extern float DemoTime; // seconds since scene start + +// main.cpp +void LookAt(rw::Frame *frame, rw::V3d pos, rw::V3d target); +rw::RGBA HSV(float h, float s, float v, rw::uint8 alpha); + +// proctex.cpp +rw::Texture *MakeGlowTexture(void); +rw::Texture *MakeGridTexture(void); +rw::Texture *MakeEnvTexture(void); + +extern DemoScene TunnelScene; +extern DemoScene ParticleScene; +extern DemoScene KnotScene; + +#endif diff --git a/tools/demoreel/main.cpp b/tools/demoreel/main.cpp new file mode 100644 index 0000000..242e2c3 --- /dev/null +++ b/tools/demoreel/main.cpp @@ -0,0 +1,301 @@ +#include +#include + +#include "demoreel.h" + +rw::EngineOpenParams engineOpenParams; + +rw::RGBA ForegroundColor = { 200, 200, 200, 255 }; +rw::RGBA BackgroundColor = { 0, 0, 0, 0 }; + +SceneGlobals Scene; +float DemoTime; + +float TimeDelta; + +static DemoScene *scenes[] = { + &TunnelScene, + &ParticleScene, + &KnotScene, +}; +static int numScenes = sizeof(scenes)/sizeof(scenes[0]); +static int curScene = -1; +static int nextScene = 0; + +void +LookAt(rw::Frame *frame, rw::V3d pos, rw::V3d target) +{ + static rw::V3d worldup = { 0.0f, 0.0f, 1.0f }; + rw::Matrix m; + rw::V3d at, right, up; + + at = rw::normalize(rw::sub(target, pos)); + right = rw::cross(worldup, at); + if(rw::length(right) < 0.001f) + right.x = 1.0f, right.y = right.z = 0.0f; + right = rw::normalize(right); + up = rw::cross(at, right); + + m.setIdentity(); + m.right = right; + m.up = up; + m.at = at; + m.pos = pos; + m.optimize(); + frame->transform(&m, rw::COMBINEREPLACE); +} + +rw::RGBA +HSV(float h, float s, float v, rw::uint8 alpha) +{ + rw::RGBA col; + float r, g, b; + float f, p, q, t; + int i; + + h = h - floorf(h); + h *= 6.0f; + i = (int)h; + f = h - i; + p = v*(1.0f - s); + q = v*(1.0f - s*f); + t = v*(1.0f - s*(1.0f - f)); + switch(i){ + default: + case 0: r = v; g = t; b = p; break; + case 1: r = q; g = v; b = p; break; + case 2: r = p; g = v; b = t; break; + case 3: r = p; g = q; b = v; break; + case 4: r = t; g = p; b = v; break; + case 5: r = v; g = p; b = q; break; + } + col.red = (rw::uint8)(r*255.0f); + col.green = (rw::uint8)(g*255.0f); + col.blue = (rw::uint8)(b*255.0f); + col.alpha = alpha; + return col; +} + +static void +SwitchScene(int n) +{ + while(n < 0) n += numScenes; + n %= numScenes; + if(n == curScene) + return; + if(curScene >= 0) + scenes[curScene]->term(); + curScene = n; + DemoTime = 0.0f; + scenes[curScene]->init(); +} + +rw::World* +CreateWorld(void) +{ + rw::BBox bb; + bb.inf.x = bb.inf.y = bb.inf.z = -1000.0f; + bb.sup.x = bb.sup.y = bb.sup.z = 1000.0f; + return rw::World::create(&bb); +} + +rw::Camera* +CreateCamera(rw::World *world) +{ + rw::Camera *camera; + camera = sk::CameraCreate(sk::globals.width, sk::globals.height, 1); + assert(camera); + camera->setNearPlane(0.1f); + camera->setFarPlane(300.0f); + camera->setFOV(70.0f, (float)sk::globals.width/sk::globals.height); + world->addCamera(camera); + return camera; +} + +void +Initialize(void) +{ + sk::globals.windowtitle = "librw demo reel"; + sk::globals.width = 1280; + sk::globals.height = 800; + sk::globals.quit = 0; +} + +bool +Initialize3D(void) +{ + if(!sk::InitRW()) + return false; + + Scene.world = CreateWorld(); + Scene.camera = CreateCamera(Scene.world); + +#ifndef RW_PS2 + ImGui_ImplRW_Init(); + ImGui::StyleColorsClassic(); +#endif + + SwitchScene(0); + + return true; +} + +void +Terminate3D(void) +{ + if(curScene >= 0){ + scenes[curScene]->term(); + curScene = -1; + } + + if(Scene.camera){ + Scene.world->removeCamera(Scene.camera); + Scene.camera->destroy(); + Scene.camera = nil; + } + 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; + int i; + + ImGui::Begin("Demo reel", &showWindow); + for(i = 0; i < numScenes; i++) + if(ImGui::RadioButton(scenes[i]->name, curScene == i)) + nextScene = i; + ImGui::NewLine(); + ImGui::Text("%.1f fps", 1.0f/TimeDelta); + 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 + + scenes[curScene]->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) +{ + TimeDelta = timeDelta; + if(TimeDelta <= 0.0f) TimeDelta = 1.0f/60.0f; + if(TimeDelta > 0.1f) TimeDelta = 0.1f; + + SwitchScene(nextScene); + nextScene = curScene; + + DemoTime += TimeDelta; + scenes[curScene]->update(TimeDelta); + + Render(); +} + +void +KeyDown(int key) +{ + switch(key){ + case sk::KEY_ESC: + sk::globals.quit = 1; + break; + case sk::KEY_LEFT: + nextScene = curScene-1; + break; + case sk::KEY_RIGHT: + case ' ': + nextScene = curScene+1; + break; + default: + if(key >= '1' && key < '1'+numScenes) + nextScene = key-'1'; + 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: + 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); + break; + case IDLE: + Idle(*(float*)param); + return EVENTPROCESSED; + } + return sk::EVENTNOTPROCESSED; +} diff --git a/tools/demoreel/proctex.cpp b/tools/demoreel/proctex.cpp new file mode 100644 index 0000000..9899779 --- /dev/null +++ b/tools/demoreel/proctex.cpp @@ -0,0 +1,137 @@ +#include + +#include "demoreel.h" + +// Procedural textures so the demos need no asset files at all. + +using namespace rw; + +static Texture* +imageToTexture(Image *img) +{ + Raster *ras = Raster::createFromImage(img); + img->destroy(); + if(ras == nil) + return nil; + Texture *tex = Texture::create(ras); + tex->setFilter(Texture::LINEAR); + tex->setAddressU(Texture::WRAP); + tex->setAddressV(Texture::WRAP); + return tex; +} + +// soft radial blob for additive particles +Texture* +MakeGlowTexture(void) +{ + const int SZ = 64; + Image *img = Image::create(SZ, SZ, 32); + img->allocate(); + + int x, y; + for(y = 0; y < SZ; y++){ + uint8 *line = img->pixels + y*img->stride; + for(x = 0; x < SZ; x++){ + float dx = (x + 0.5f - SZ/2) / (SZ/2); + float dy = (y + 0.5f - SZ/2) / (SZ/2); + float r = sqrtf(dx*dx + dy*dy); + float v = 1.0f - r; + if(v < 0.0f) v = 0.0f; + v = v*v*(3.0f - 2.0f*v); // smoothstep + v = v*v; // sharpen the core + uint8 c = (uint8)(v*255.0f); + line[x*4+0] = c; + line[x*4+1] = c; + line[x*4+2] = c; + line[x*4+3] = c; + } + } + return imageToTexture(img); +} + +// glowing neon grid on dark ground +Texture* +MakeGridTexture(void) +{ + const int SZ = 128; + const int CELL = 32; + Image *img = Image::create(SZ, SZ, 32); + img->allocate(); + + int x, y; + for(y = 0; y < SZ; y++){ + uint8 *line = img->pixels + y*img->stride; + for(x = 0; x < SZ; x++){ + int mx = x % CELL; if(mx > CELL/2) mx = CELL-mx; + int my = y % CELL; if(my > CELL/2) my = CELL-my; + int d = mx < my ? mx : my; + float v = 1.0f - d/6.0f; + if(v < 0.0f) v = 0.0f; + v = v*v; + // dark blue ground, cyan-white lines + float r = 0.02f + v*0.75f; + float g = 0.03f + v*0.95f; + float b = 0.10f + v*0.90f; + line[x*4+0] = (uint8)(r*255.0f); + line[x*4+1] = (uint8)(g*255.0f); + line[x*4+2] = (uint8)(b*255.0f); + line[x*4+3] = 255; + } + } + return imageToTexture(img); +} + +// fake sky-ground gradient with highlight streaks, for env mapping +Texture* +MakeEnvTexture(void) +{ + const int SZ = 128; + Image *img = Image::create(SZ, SZ, 32); + img->allocate(); + + int x, y; + for(y = 0; y < SZ; y++){ + uint8 *line = img->pixels + y*img->stride; + float fy = (float)y/SZ; + for(x = 0; x < SZ; x++){ + float fx = (float)x/SZ; + float r, g, b; + if(fy < 0.5f){ + // sky: bright at horizon + float t = fy*2.0f; + r = 0.05f + t*0.55f; + g = 0.15f + t*0.65f; + b = 0.35f + t*0.65f; + }else{ + // ground: dark, fading down + float t = (fy-0.5f)*2.0f; + r = 0.45f - t*0.40f; + g = 0.35f - t*0.32f; + b = 0.30f - t*0.28f; + } + // horizon band + float h = fabsf(fy - 0.5f); + float band = 1.0f - h*8.0f; + if(band > 0.0f){ + band = band*band; + r += band*0.5f; + g += band*0.5f; + b += band*0.4f; + } + // vertical highlight streaks in the sky + float streak = sinf(fx*3.14159f*6.0f); + streak = streak*streak*streak*streak; + if(fy < 0.5f) + r += streak*0.15f, g += streak*0.15f, b += streak*0.1f; + + if(r > 1.0f) r = 1.0f; + if(g > 1.0f) g = 1.0f; + if(b > 1.0f) b = 1.0f; + line[x*4+0] = (uint8)(r*255.0f); + line[x*4+1] = (uint8)(g*255.0f); + line[x*4+2] = (uint8)(b*255.0f); + line[x*4+3] = 255; + } + } + return imageToTexture(img); +} diff --git a/tools/demoreel/scene_knot.cpp b/tools/demoreel/scene_knot.cpp new file mode 100644 index 0000000..ff8b21b --- /dev/null +++ b/tools/demoreel/scene_knot.cpp @@ -0,0 +1,221 @@ +#include +#include + +#include "demoreel.h" + +// Retained mode: a procedural torus knot with env-mapped chrome, +// lit by two colored directional lights. Exercises Geometry +// creation, materials, MatFX and the world/light path. + +using namespace rw; + +#define KNOT_P 2 +#define KNOT_Q 3 + +#define SEGS 160 +#define SIDES 12 +#define NUMVERTS ((SEGS+1)*(SIDES+1)) +#define NUMTRIS (SEGS*SIDES*2) + +static Atomic *knotAtomic; +static Frame *knotFrame; +static Light *ambient; +static Light *keyLight; +static Light *fillLight; +static Texture *envTex; +static Texture *gridTex; + +static V3d +knotCenter(float t) +{ + V3d p; + float r = 6.0f + 2.5f*cosf(KNOT_Q*t); + p.x = r*cosf(KNOT_P*t); + p.y = r*sinf(KNOT_P*t); + p.z = 2.5f*sinf(KNOT_Q*t); + return p; +} + +static Geometry* +CreateKnotGeometry(void) +{ + Geometry *geo = Geometry::create(NUMVERTS, NUMTRIS, + Geometry::POSITIONS | Geometry::NORMALS | + Geometry::LIGHT | Geometry::TEXTURED); + assert(geo); + + MorphTarget *mt = &geo->morphTargets[0]; + V3d *verts = mt->vertices; + V3d *norms = mt->normals; + TexCoords *uv = geo->texCoords[0]; + + float tuberad = 1.1f; + int i, j, v; + + v = 0; + for(i = 0; i <= SEGS; i++){ + float t = (float)i/SEGS*2.0f*3.14159265f; + V3d c = knotCenter(t); + V3d tan = normalize(sub(knotCenter(t+0.01f), knotCenter(t-0.01f))); + static V3d worldup = { 0.0f, 0.0f, 1.0f }; + V3d n = normalize(cross(worldup, tan)); + V3d b = cross(tan, n); + + for(j = 0; j <= SIDES; j++){ + float a = (float)j/SIDES*2.0f*3.14159265f; + V3d ring = add(scale(n, cosf(a)), scale(b, sinf(a))); + verts[v] = add(c, scale(ring, tuberad)); + norms[v] = ring; + uv[v].u = (float)i/SEGS*24.0f; + uv[v].v = (float)j/SIDES; + v++; + } + } + + Triangle *tri = geo->triangles; + for(i = 0; i < SEGS; i++) + for(j = 0; j < SIDES; j++){ + int r0 = i*(SIDES+1) + j; + int r1 = (i+1)*(SIDES+1) + j; + tri->v[0] = r0; tri->v[1] = r0+1; tri->v[2] = r1; + tri->matId = 0; + tri++; + tri->v[0] = r0+1; tri->v[1] = r1+1; tri->v[2] = r1; + tri->matId = 0; + tri++; + } + + Material *mat = Material::create(); + gridTex = MakeGridTexture(); + if(gridTex) + mat->setTexture(gridTex); + envTex = MakeEnvTexture(); + MatFX::setEffects(mat, MatFX::ENVMAP); + MatFX *mfx = MatFX::get(mat); + if(envTex) + mfx->setEnvTexture(envTex); + mfx->setEnvFrame(Scene.camera->getFrame()); + mfx->setEnvCoefficient(0.6f); + geo->matList.appendMaterial(mat); + mat->destroy(); // list holds a ref now + + geo->calculateBoundingSphere(); + // strips are the well-trodden path on PS2 (and a test for the tristripper) + geo->flags |= Geometry::TRISTRIP; + geo->buildMeshes(); + return geo; +} + +static Light* +MakeDirLight(float r, float g, float b, float yaw, float pitch) +{ + static V3d Xaxis = { 1.0f, 0.0f, 0.0f }; + static V3d Zaxis = { 0.0f, 0.0f, 1.0f }; + Light *light = Light::create(Light::DIRECTIONAL); + light->setColor(r, g, b); + Frame *f = Frame::create(); + f->rotate(&Xaxis, pitch, COMBINEREPLACE); + f->rotate(&Zaxis, yaw, COMBINEPOSTCONCAT); + light->setFrame(f); + Scene.world->addLight(light); + return light; +} + +static void +KnotInit(void) +{ + Geometry *geo = CreateKnotGeometry(); + + knotFrame = Frame::create(); + knotAtomic = Atomic::create(); + knotAtomic->setGeometry(geo, 0); + geo->destroy(); // atomic holds a ref now + knotAtomic->setFrame(knotFrame); + MatFX::enableEffects(knotAtomic); + Scene.world->addAtomic(knotAtomic); + + ambient = Light::create(Light::AMBIENT); + ambient->setColor(0.15f, 0.15f, 0.2f); + Scene.world->addLight(ambient); + keyLight = MakeDirLight(1.0f, 0.85f, 0.6f, 30.0f, 120.0f); + fillLight = MakeDirLight(0.3f, 0.4f, 0.9f, 200.0f, 60.0f); +} + +static void +KnotTerm(void) +{ + if(knotAtomic){ + Scene.world->removeAtomic(knotAtomic); + knotAtomic->destroy(); + knotAtomic = nil; + } + if(knotFrame){ + knotFrame->destroy(); + knotFrame = nil; + } + if(ambient){ + Scene.world->removeLight(ambient); + ambient->destroy(); + ambient = nil; + } + if(keyLight){ + Scene.world->removeLight(keyLight); + Frame *f = keyLight->getFrame(); + keyLight->setFrame(nil); + f->destroy(); + keyLight->destroy(); + keyLight = nil; + } + if(fillLight){ + Scene.world->removeLight(fillLight); + Frame *f = fillLight->getFrame(); + fillLight->setFrame(nil); + f->destroy(); + fillLight->destroy(); + fillLight = nil; + } + if(envTex){ + envTex->destroy(); + envTex = nil; + } + if(gridTex){ + gridTex->destroy(); + gridTex = nil; + } +} + +static void +KnotUpdate(float dt) +{ + static V3d Xaxis = { 1.0f, 0.0f, 0.0f }; + static V3d Zaxis = { 0.0f, 0.0f, 1.0f }; + + knotFrame->rotate(&Zaxis, dt*20.0f, COMBINEPOSTCONCAT); + knotFrame->rotate(&Xaxis, dt*8.0f, COMBINEPRECONCAT); + + float ca = DemoTime*0.1f; + V3d campos; + campos.x = 24.0f*cosf(ca); + campos.y = 24.0f*sinf(ca); + campos.z = 8.0f*sinf(DemoTime*0.23f); + V3d origin = { 0.0f, 0.0f, 0.0f }; + LookAt(Scene.camera->getFrame(), campos, origin); +} + +static void +KnotRender(void) +{ + SetRenderState(ZTESTENABLE, 1); + SetRenderState(ZWRITEENABLE, 1); + SetRenderState(CULLMODE, CULLBACK); + + knotAtomic->render(); +} + +DemoScene KnotScene = { + "Torus knot", + KnotInit, + KnotTerm, + KnotUpdate, + KnotRender +}; diff --git a/tools/demoreel/scene_particles.cpp b/tools/demoreel/scene_particles.cpp new file mode 100644 index 0000000..e181d29 --- /dev/null +++ b/tools/demoreel/scene_particles.cpp @@ -0,0 +1,165 @@ +#include + +#include "demoreel.h" + +// Additive-blended particle vortex, camera-facing quads via im3d. +// The kind of thing the GS fillrate was made for. + +using namespace rw; + +#ifdef RW_PS2 +#define NUMPARTS 600 +#else +#define NUMPARTS 1200 +#endif + +struct Particle +{ + float angle; + float radius; + float height; + float size; + float phase; +}; + +static Particle parts[NUMPARTS]; +static RWDEVICE::Im3DVertex partVerts[NUMPARTS*4]; +static uint16 partIndices[NUMPARTS*6]; +static Texture *glowTex; +static Matrix identMat; + +// cheap deterministic pseudo-random, no libc rand() +static float +frand(int n) +{ + n = (n<<13) ^ n; + n = n*(n*n*15731 + 789221) + 1376312589; + return ((n & 0x7fffffff) / (float)0x7fffffff); +} + +static void +ParticleInit(void) +{ + int i; + + glowTex = MakeGlowTexture(); + identMat.setIdentity(); + + for(i = 0; i < NUMPARTS; i++){ + Particle *p = &parts[i]; + float u = (float)i/NUMPARTS; + p->radius = 4.0f + 30.0f*powf(u, 0.7f); + p->angle = i*2.3999632f + frand(i)*0.6f; // golden angle spiral + p->height = (frand(i*3+1) - 0.5f)*10.0f*expf(-p->radius/18.0f); + p->size = 0.5f + frand(i*7+2)*1.1f; + p->phase = frand(i*13+3)*6.28f; + } + + for(i = 0; i < NUMPARTS; i++){ + partIndices[i*6+0] = i*4+0; + partIndices[i*6+1] = i*4+1; + partIndices[i*6+2] = i*4+2; + partIndices[i*6+3] = i*4+0; + partIndices[i*6+4] = i*4+2; + partIndices[i*6+5] = i*4+3; + } +} + +static void +ParticleTerm(void) +{ + if(glowTex){ + glowTex->destroy(); + glowTex = nil; + } +} + +static void +ParticleUpdate(float dt) +{ + int i; + + // differential rotation: inner particles spin faster + for(i = 0; i < NUMPARTS; i++){ + Particle *p = &parts[i]; + p->angle += dt*16.0f/(p->radius + 4.0f); + } + + // slow orbiting camera + float ca = DemoTime*0.12f; + V3d campos; + campos.x = 55.0f*cosf(ca); + campos.y = 55.0f*sinf(ca); + campos.z = 14.0f + 10.0f*sinf(DemoTime*0.2f); + V3d origin = { 0.0f, 0.0f, 0.0f }; + LookAt(Scene.camera->getFrame(), campos, origin); + + // billboard the quads using the camera axes + Matrix *cm = Scene.camera->getFrame()->getLTM(); + V3d right = cm->right; + V3d up = cm->up; + + for(i = 0; i < NUMPARTS; i++){ + Particle *p = &parts[i]; + V3d pos; + pos.x = p->radius*cosf(p->angle); + pos.y = p->radius*sinf(p->angle); + pos.z = p->height + 0.8f*sinf(DemoTime*2.0f + p->phase); + + float tw = 0.7f + 0.3f*sinf(DemoTime*5.0f + p->phase); + float sz = p->size*tw; + RGBA col = HSV(DemoTime*0.02f + p->radius*0.012f, 0.8f, tw, 255); + + RWDEVICE::Im3DVertex *v = &partVerts[i*4]; + V3d r = scale(right, sz); + V3d u = scale(up, sz); + V3d pu = add(pos, u); + V3d pd = sub(pos, u); + V3d c; + + c = sub(pu, r); + v[0].setX(c.x); v[0].setY(c.y); v[0].setZ(c.z); + v[0].setU(0.0f); v[0].setV(0.0f); + c = add(pu, r); + v[1].setX(c.x); v[1].setY(c.y); v[1].setZ(c.z); + v[1].setU(1.0f); v[1].setV(0.0f); + c = add(pd, r); + v[2].setX(c.x); v[2].setY(c.y); v[2].setZ(c.z); + v[2].setU(1.0f); v[2].setV(1.0f); + c = sub(pd, r); + v[3].setX(c.x); v[3].setY(c.y); v[3].setZ(c.z); + v[3].setU(0.0f); v[3].setV(1.0f); + + int j; + for(j = 0; j < 4; j++) + v[j].setColor(col.red, col.green, col.blue, col.alpha); + } +} + +static void +ParticleRender(void) +{ + SetRenderState(ZTESTENABLE, 1); + SetRenderState(ZWRITEENABLE, 0); + SetRenderState(SRCBLEND, BLENDONE); + SetRenderState(DESTBLEND, BLENDONE); + SetRenderState(CULLMODE, CULLNONE); + SetRenderState(TEXTUREFILTER, Texture::LINEAR); + SetRenderStatePtr(TEXTURERASTER, glowTex ? glowTex->raster : nil); + + im3d::Transform(partVerts, NUMPARTS*4, &identMat, im3d::VERTEXUV); + im3d::RenderIndexedPrimitive(PRIMTYPETRILIST, partIndices, NUMPARTS*6); + im3d::End(); + + SetRenderState(ZWRITEENABLE, 1); + SetRenderState(SRCBLEND, BLENDSRCALPHA); + SetRenderState(DESTBLEND, BLENDINVSRCALPHA); +} + +DemoScene ParticleScene = { + "Particle vortex", + ParticleInit, + ParticleTerm, + ParticleUpdate, + ParticleRender +}; diff --git a/tools/demoreel/scene_tunnel.cpp b/tools/demoreel/scene_tunnel.cpp new file mode 100644 index 0000000..36432bc --- /dev/null +++ b/tools/demoreel/scene_tunnel.cpp @@ -0,0 +1,155 @@ +#include + +#include "demoreel.h" + +// Classic endless tunnel: fly along a wobbling closed loop, +// vertices regenerated on the CPU every frame, im3d rendering. + +using namespace rw; + +#define SIDES 16 +#define RINGS 48 +#define NUMVERTS ((SIDES+1)*(RINGS+1)) +#define NUMINDICES (SIDES*RINGS*6) + +#define LOOPRADIUS 60.0f + +static RWDEVICE::Im3DVertex tunnelVerts[NUMVERTS]; +static uint16 tunnelIndices[NUMINDICES]; +static Texture *gridTex; +static Matrix identMat; +static float pathPos; + +// closed loop in space with some vertical wobble +static V3d +tunnelPath(float s) +{ + V3d p; + p.x = LOOPRADIUS*cosf(s) + 8.0f*cosf(3.0f*s); + p.y = LOOPRADIUS*sinf(s) + 8.0f*sinf(2.0f*s); + p.z = 14.0f*sinf(2.0f*s) + 5.0f*sinf(5.0f*s); + return p; +} + +static float +tunnelRadius(float s) +{ + return 6.0f + 1.5f*sinf(5.0f*s + DemoTime*2.0f); +} + +static void +pathFrame(float s, V3d *pos, V3d *t, V3d *n, V3d *b) +{ + static V3d worldup = { 0.0f, 0.0f, 1.0f }; + *pos = tunnelPath(s); + *t = normalize(sub(tunnelPath(s+0.01f), *pos)); + *n = normalize(cross(worldup, *t)); + *b = cross(*t, *n); +} + +static void +TunnelInit(void) +{ + int i, j, v; + + gridTex = MakeGridTexture(); + identMat.setIdentity(); + pathPos = 0.0f; + + v = 0; + for(i = 0; i < RINGS; i++) + for(j = 0; j < SIDES; j++){ + int r0 = i*(SIDES+1) + j; + int r1 = (i+1)*(SIDES+1) + j; + tunnelIndices[v++] = r0; + tunnelIndices[v++] = r1; + tunnelIndices[v++] = r0+1; + tunnelIndices[v++] = r0+1; + tunnelIndices[v++] = r1; + tunnelIndices[v++] = r1+1; + } +} + +static void +TunnelTerm(void) +{ + if(gridTex){ + gridTex->destroy(); + gridTex = nil; + } +} + +static void +TunnelUpdate(float dt) +{ + pathPos += dt*0.30f; + + // camera rides the tunnel, slightly off center + V3d pos, t, n, b; + pathFrame(pathPos, &pos, &t, &n, &b); + V3d target = tunnelPath(pathPos + 0.15f); + pos = add(pos, scale(b, -1.5f*sinf(DemoTime*0.7f))); + pos = add(pos, scale(n, 1.5f*cosf(DemoTime*0.5f))); + LookAt(Scene.camera->getFrame(), pos, target); + + // rebuild the tube + float ds = 0.028f; + float hue = DemoTime*0.03f; + // V texcoords must stay small: the GS wraps texel + // coordinates beyond ~1024, so shift out the integer part + float vscale = 4.0f; + float vbase = floorf(pathPos*vscale); + int i, j, v; + + v = 0; + for(i = 0; i <= RINGS; i++){ + float s = pathPos - ds + i*ds; + V3d c, tt, nn, bb; + pathFrame(s, &c, &tt, &nn, &bb); + float rad = tunnelRadius(s); + + // brightness: fade out towards the far end, pulses running along + float fade = 1.0f - (float)i/RINGS; + fade = fade*fade; + float pulse = 0.55f + 0.45f*sinf(s*18.0f - DemoTime*6.0f); + float bright = fade*(0.35f + 0.65f*pulse); + RGBA col = HSV(hue + s*0.02f, 0.55f, bright, 255); + + float twist = DemoTime*0.4f; + for(j = 0; j <= SIDES; j++){ + float a = (float)j/SIDES*2.0f*3.14159265f + twist; + V3d p = add(c, add(scale(nn, rad*cosf(a)), scale(bb, rad*sinf(a)))); + RWDEVICE::Im3DVertex *vert = &tunnelVerts[v++]; + vert->setX(p.x); + vert->setY(p.y); + vert->setZ(p.z); + vert->setU((float)j/SIDES*4.0f); + vert->setV(s*vscale - vbase); + vert->setColor(col.red, col.green, col.blue, col.alpha); + } + } +} + +static void +TunnelRender(void) +{ + SetRenderState(ZTESTENABLE, 1); + SetRenderState(ZWRITEENABLE, 1); + SetRenderState(SRCBLEND, BLENDSRCALPHA); + SetRenderState(DESTBLEND, BLENDINVSRCALPHA); + SetRenderState(CULLMODE, CULLNONE); + SetRenderState(TEXTUREFILTER, Texture::LINEAR); + SetRenderStatePtr(TEXTURERASTER, gridTex ? gridTex->raster : nil); + + im3d::Transform(tunnelVerts, NUMVERTS, &identMat, im3d::VERTEXUV|im3d::ALLOPAQUE); + im3d::RenderIndexedPrimitive(PRIMTYPETRILIST, tunnelIndices, NUMINDICES); + im3d::End(); +} + +DemoScene TunnelScene = { + "Tunnel", + TunnelInit, + TunnelTerm, + TunnelUpdate, + TunnelRender +};