added simple demoreel and ps2 skeleton coded by claude fable 5

This commit is contained in:
aap
2026-08-09 01:39:54 +02:00
parent 92851aa1ed
commit 0a7eb48ce3
18 changed files with 1482 additions and 9 deletions
+12 -3
View File
@@ -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")
+260
View File
@@ -0,0 +1,260 @@
#ifdef RW_PS2
//#define SKEL_CDROM
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <eekernel.h>
#include <sifdev.h>
#include <sifrpc.h>
#include <libpad.h>
#ifdef SKEL_CDROM
#include <libcdvd.h>
#endif
#include <rw.h>
#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
+2
View File
@@ -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){
+2
View File
@@ -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
+33
View File
@@ -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)
{
+3 -1
View File
@@ -7,7 +7,9 @@
#elif defined(LIBRW_GLFW)
#include <GLFW/glfw3.h>
#else
not implemented
// sane fallback
#define LIBRW_GLFW
#include <GLFW/glfw3.h>
#endif
#endif
+2
View File
@@ -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;
+43 -1
View File
@@ -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
+28 -4
View File
@@ -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
{
+1
View File
@@ -12,6 +12,7 @@ if(LIBRW_EXAMPLES)
add_subdirectory(camera)
add_subdirectory(im2d)
add_subdirectory(im3d)
add_subdirectory(demoreel)
endif()
if(LIBRW_PLATFORM_PS2)
+16
View File
@@ -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)
+62
View File
@@ -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)
+39
View File
@@ -0,0 +1,39 @@
#ifndef DEMOREEL_H
#define DEMOREEL_H
#include <rw.h>
#include <skeleton.h>
// 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
+301
View File
@@ -0,0 +1,301 @@
#include <assert.h>
#include <math.h>
#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;
}
+137
View File
@@ -0,0 +1,137 @@
#include <math.h>
#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);
}
+221
View File
@@ -0,0 +1,221 @@
#include <math.h>
#include <assert.h>
#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
};
+165
View File
@@ -0,0 +1,165 @@
#include <math.h>
#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
};
+155
View File
@@ -0,0 +1,155 @@
#include <math.h>
#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
};