diff --git a/skeleton/camera.cpp b/skeleton/camera.cpp new file mode 100644 index 0000000..816f10c --- /dev/null +++ b/skeleton/camera.cpp @@ -0,0 +1,268 @@ +#include + +#include +#include "skeleton.h" + +#define PI 3.14159265359f + +namespace sk { + +using rw::Quat; +using rw::V3d; + +void +Camera::init(void) +{ + 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 = nil; +} + +void +Camera::attach(rw::Camera *cam) +{ + m_rwcam = cam; +} + +void +Camera::update(void) +{ + rw::Frame *f; + V3d forward, left, nup; + + if(m_rwcam == nil) + return; + m_rwcam->setNearPlane(m_near); + m_rwcam->setFarPlane(m_far); + m_rwcam->setFOV(m_fov, m_aspectRatio); + + f = m_rwcam->getFrame(); + if(f == nil) + return; + forward = normalize(sub(m_target, m_position)); + left = normalize(cross(m_up, forward)); + 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, right; + Quat r; + + dir = sub(m_target, m_position); + r = Quat::rotation(yaw, rw::makeV3d(0.0f, 0.0f, 1.0f)); + dir = rotate(dir, r); + m_localup = rotate(m_localup, r); + + 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.0f) m_up.z = 1.0f; + else m_up.z = -1.0f; + + m_target = add(m_position, dir); +} + +void +Camera::orbit(float yaw, float pitch) +{ + V3d dir, right; + Quat r; + + dir = sub(m_target, m_position); + r = Quat::rotation(yaw, rw::makeV3d(0.0f, 0.0f, 1.0f)); + dir = rotate(dir, r); + m_localup = rotate(m_localup, r); + + 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.0f) m_up.z = 1.0f; + 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)); +} + +float +Camera::distanceToTarget(void) +{ + return length(sub(m_position, m_target)); +} + +rw::bool32 +Camera::isSphereVisible(rw::Sphere *sph, rw::Matrix *xform) +{ + rw::Sphere sphere = *sph; + if(xform) + rw::V3d::transformPoints(&sphere.center, &sphere.center, 1, xform); + return m_rwcam->frustumTestSphere(&sphere) != rw::Camera::SPHEREOUTSIDE; +} + +/* + * Controls + */ + +CameraControls cameraControls; + +void +CameraControlsInit(void) +{ + cameraControls.mouseSens = 4.5f; + cameraControls.wheelSens = 0.2f; + cameraControls.stickSens = 2.0f; + cameraControls.moveSpeed = 30.0f; + cameraControls.speedUpMult = 4.0f; +} + +// Mouse motion since the last frame, normalized to the viewport. +// This is a displacement, so it must NOT be scaled by dt. +static void +MouseDelta(float *dx, float *dy) +{ + *dx = (float)(mouse.posx - prevmouse.posx) / (float)globals.width; + *dy = (float)(mouse.posy - prevmouse.posy) / (float)globals.height; +} + +// Move along world up, keeping the view direction. +static void +MoveUp(Camera *cam, float dist) +{ + V3d d = scale(cam->m_up, dist); + cam->m_position = add(cam->m_position, d); + cam->m_target = add(cam->m_target, d); +} + +void +CameraEditorControls(Camera *cam, float dt) +{ + float dx, dy, s, d; + + MouseDelta(&dx, &dy); + s = cameraControls.mouseSens; + d = cam->distanceToTarget(); + + if(mouse.buttons & 1) + cam->orbit(-dx*s, dy*s); + else if(mouse.buttons & 2) + cam->pan(-dx*d, dy*d); + else if(mouse.buttons & 4) + cam->zoom(-dy*s*d); + // zoom proportional to distance so it approaches the target smoothly + if(mouse.wheelDelta != 0.0f) + cam->zoom(mouse.wheelDelta * cameraControls.wheelSens * d); + + // stick deflection is a rate + if(pad.connected){ + cam->orbit(-pad.leftx*cameraControls.stickSens*dt, + -pad.lefty*cameraControls.stickSens*dt); + cam->pan(-pad.rightx*d*dt, pad.righty*d*dt); + if(pad.buttons & PADL1) + cam->zoom(-d*dt); + if(pad.buttons & PADR1) + cam->zoom(d*dt); + } +} + +void +CameraFlyControls(Camera *cam, float dt) +{ + float dx, dy, s, speed; + + MouseDelta(&dx, &dy); + s = cameraControls.mouseSens; + + if(mouse.buttons & 1) + cam->turn(-dx*s, -dy*s); + + // stick look is a rate + if(pad.connected) + cam->turn(-pad.rightx*cameraControls.stickSens*dt, + -pad.righty*cameraControls.stickSens*dt); + + speed = cameraControls.moveSpeed; + if(keys[KEY_LSHIFT] || keys[KEY_RSHIFT] || (pad.buttons & PADR1)) + speed *= cameraControls.speedUpMult; + speed *= dt; + + if(keys['W']) cam->dolly(speed); + if(keys['S']) cam->dolly(-speed); + if(keys['A']) cam->pan(-speed, 0.0f); + if(keys['D']) cam->pan(speed, 0.0f); + if(keys['Q']) MoveUp(cam, -speed); + if(keys['E']) MoveUp(cam, speed); + + if(pad.connected){ + cam->dolly(-pad.lefty*speed); + cam->pan(pad.leftx*speed, 0.0f); + if(pad.buttons & PADL2) MoveUp(cam, -speed); + if(pad.buttons & PADR2) MoveUp(cam, speed); + } +} + +} diff --git a/skeleton/camera.h b/skeleton/camera.h new file mode 100644 index 0000000..7a4b3c3 --- /dev/null +++ b/skeleton/camera.h @@ -0,0 +1,75 @@ +#ifndef SKELCAMERA_H +#define SKELCAMERA_H + +namespace sk { + +// Manipulator around an rw::Camera. Keeps position/target/up and +// drives the rw camera's frame from them in update(). +struct Camera +{ + 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 init(void); + void attach(rw::Camera *cam); + + 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); + float distanceToTarget(void); + rw::bool32 isSphereVisible(rw::Sphere *sph, rw::Matrix *xform); +}; + +/* + * Controls. + * + * Two kinds of input, and they must not be treated alike: + * + * displacement - mouse motion, wheel. Already frame rate independent, + * a given hand movement gives the same delta at any fps. + * Scale by a constant gain, never by dt. + * rate - held keys, stick deflection. A speed, so scale by dt. + * + * Mouse deltas are normalized by the viewport, so sensitivity does not + * change with resolution. + */ +struct CameraControls +{ + float mouseSens; // gain for mouse displacement + float wheelSens; // gain for wheel displacement + float stickSens; // radians/sec for stick look + float moveSpeed; // units/sec + float speedUpMult; // while shift/R1 held +}; +extern CameraControls cameraControls; + +void CameraControlsInit(void); + +// Editor style: orbit/pan/zoom about the target. +// mouse: left orbit, middle pan, right zoom, wheel zoom +// pad: left stick orbit, right stick pan, L1/R1 zoom +void CameraEditorControls(Camera *cam, float dt); + +// First person style: look with the mouse/right stick, fly with the keys. +// mouse: left drag look +// keys: WASD move, Q/E down/up, shift faster +// pad: right stick look, left stick move, L2/R2 down/up, R1 faster +void CameraFlyControls(Camera *cam, float dt); + +} + +#endif diff --git a/skeleton/ps2.cpp b/skeleton/ps2.cpp index c170482..19d0a3c 100644 --- a/skeleton/ps2.cpp +++ b/skeleton/ps2.cpp @@ -29,28 +29,31 @@ float GetTimeF(void); * Pad */ +struct PadBtns +{ + 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; +}; + 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; - }; + PadBtns btns; }; /* down/right is positive */ float rX, rY; @@ -64,7 +67,7 @@ struct Pad }; static bool gotPadirx; -static Pad pad; +static Pad syspad; static u_long128 pad_dma_buf[scePadDmaBufferMax] __attribute__((aligned(64))); #define EPSILON 0.2f @@ -142,12 +145,21 @@ PadEvents(void) { int i, key; - UpdatePad(&pad); + UpdatePad(&syspad); + + // analog state for apps that want it (camera controls) + sk::pad.connected = gotPadirx; + sk::pad.buttons = syspad.now.bits; + sk::pad.leftx = syspad.now.lX; + sk::pad.lefty = syspad.now.lY; + sk::pad.rightx = syspad.now.rX; + sk::pad.righty = syspad.now.rY; + for(i = 0; i < (int)(sizeof(padkeymap)/sizeof(padkeymap[0])); i++){ key = padkeymap[i].key; - if(pad.rising.bits & padkeymap[i].mask) + if(syspad.rising.bits & padkeymap[i].mask) EventHandler(KEYDOWN, &key); - if(pad.falling.bits & padkeymap[i].mask) + if(syspad.falling.bits & padkeymap[i].mask) EventHandler(KEYUP, &key); } } @@ -256,6 +268,39 @@ SetMousePosition(int x, int y) { } +// pcsx2 wants host:, the SCE TOOL wants host0: +#ifndef SKEL_HOSTPREFIX +#define SKEL_HOSTPREFIX "host:" +#endif +const char *hostPrefix = SKEL_HOSTPREFIX; + +char* +GetFilePath(char *dst, const char *path) +{ +#ifdef SKEL_CDROM + char *p; + + // cdrom0:\SOME\PATH.EXT;1 + strcpy(dst, "cdrom0:\\"); + strcat(dst, path); + for(p = dst+8; *p; p++){ + if(islower(*p)) + *p = toupper(*p); + if(*p == '/') + *p = '\\'; + } + strcat(dst, ";1"); +#else + // absolute paths go through as they are, relative ones + // need the ./ or the host fs won't find them + strcpy(dst, hostPrefix); + if(path[0] != '/') + strcat(dst, "./"); + strcat(dst, path); +#endif + return dst; +} + } #endif diff --git a/skeleton/skeleton.cpp b/skeleton/skeleton.cpp index f69d21a..1628ec3 100644 --- a/skeleton/skeleton.cpp +++ b/skeleton/skeleton.cpp @@ -1,3 +1,5 @@ +#include + #include #include "skeleton.h" @@ -7,6 +9,21 @@ namespace sk { Globals globals; Args args; +bool32 keys[KEY_NUMKEYS]; +MouseState mouse; +MouseState prevmouse; +PadState pad; + +// the ps2 needs to mangle paths, see ps2.cpp +#ifndef RW_PS2 +char* +GetFilePath(char *dst, const char *path) +{ + strcpy(dst, path); + return dst; +} +#endif + bool InitRW(void) @@ -64,11 +81,11 @@ TerminateRW(void) rw::Engine::term(); } -Camera* +rw::Camera* CameraCreate(int32 width, int32 height, bool32 z) { - Camera *cam; - cam = Camera::create(); + rw::Camera *cam; + cam = rw::Camera::create(); cam->setFrame(Frame::create()); cam->frameBuffer = Raster::create(width, height, 0, Raster::CAMERA); cam->zBuffer = Raster::create(width, height, 0, Raster::ZBUFFER); @@ -95,7 +112,7 @@ CameraDestroy(rw::Camera *cam) } void -CameraSize(Camera *cam, Rect *r, float viewWindow, float aspectRatio) +CameraSize(rw::Camera *cam, Rect *r, float viewWindow, float aspectRatio) { if(cam->frameBuffer){ cam->frameBuffer->destroy(); @@ -123,7 +140,7 @@ CameraSize(Camera *cam, Rect *r, float viewWindow, float aspectRatio) } void -CameraMove(Camera *cam, V3d *delta) +CameraMove(rw::Camera *cam, V3d *delta) { rw::V3d offset; rw::V3d::transformVectors(&offset, delta, 1, &cam->getFrame()->matrix); @@ -131,7 +148,7 @@ CameraMove(Camera *cam, V3d *delta) } void -CameraPan(Camera *cam, V3d *pos, float angle) +CameraPan(rw::Camera *cam, V3d *pos, float angle) { rw::Frame *frame = cam->getFrame(); rw::V3d trans = pos ? *pos : frame->matrix.pos; @@ -142,7 +159,7 @@ CameraPan(Camera *cam, V3d *pos, float angle) } void -CameraTilt(Camera *cam, V3d *pos, float angle) +CameraTilt(rw::Camera *cam, V3d *pos, float angle) { rw::Frame *frame = cam->getFrame(); rw::V3d trans = pos ? *pos : frame->matrix.pos; @@ -153,7 +170,7 @@ CameraTilt(Camera *cam, V3d *pos, float angle) } void -CameraRotate(Camera *cam, V3d *pos, float angle) +CameraRotate(rw::Camera *cam, V3d *pos, float angle) { rw::Frame *frame = cam->getFrame(); rw::V3d trans = pos ? *pos : frame->matrix.pos; @@ -167,13 +184,55 @@ EventStatus EventHandler(Event e, void *param) { EventStatus s; + int key; + MouseState *ms; + #ifndef RW_PS2 if (e == INITIALIZE) { ImGui::CreateContext(); } #endif + // keep input state so apps don't all have to do it themselves. + // the backends only fill the field an event actually carries. + switch(e){ + case INITIALIZE: + CameraControlsInit(); + break; + case KEYDOWN: + key = *(int*)param; + if(key >= 0 && key < KEY_NUMKEYS) + keys[key] = 1; + break; + case KEYUP: + key = *(int*)param; + if(key >= 0 && key < KEY_NUMKEYS) + keys[key] = 0; + break; + case MOUSEMOVE: + ms = (MouseState*)param; + mouse.posx = ms->posx; + mouse.posy = ms->posy; + break; + case MOUSEBTN: + ms = (MouseState*)param; + mouse.buttons = ms->buttons; + break; + case MOUSEWHEEL: + ms = (MouseState*)param; + mouse.wheelDelta += ms->wheelDelta; + break; + default: + break; + } + s = AppEventHandler(e, param); + + if(e == IDLE){ + // latch after the app has seen this frame's deltas + prevmouse = mouse; + mouse.wheelDelta = 0.0f; + } if(e == QUIT){ globals.quit = 1; return EVENTPROCESSED; diff --git a/skeleton/skeleton.h b/skeleton/skeleton.h index 53c5211..b51eb9f 100644 --- a/skeleton/skeleton.h +++ b/skeleton/skeleton.h @@ -102,20 +102,69 @@ struct Args }; extern Args args; +// Pad buttons, same bit order as the PS2 pad +enum PadButton +{ + PADL2 = 0x0001, + PADR2 = 0x0002, + PADL1 = 0x0004, + PADR1 = 0x0008, + PADTRIANGLE = 0x0010, + PADCIRCLE = 0x0020, + PADCROSS = 0x0040, + PADSQUARE = 0x0080, + PADSELECT = 0x0100, + PADL3 = 0x0200, + PADR3 = 0x0400, + PADSTART = 0x0800, + PADUP = 0x1000, + PADRIGHT = 0x2000, + PADDOWN = 0x4000, + PADLEFT = 0x8000, +}; + +struct PadState +{ + // -1..1, down/right positive, deadzone already applied + float leftx, lefty; + float rightx, righty; + uint32 buttons; + bool32 connected; +}; + +// Input state, tracked by EventHandler so apps don't have to. +extern bool32 keys[KEY_NUMKEYS]; +extern MouseState mouse; // live +extern MouseState prevmouse; // as of the previous frame +extern PadState pad; + +// File paths. +// On PS2 a CDROM build goes through cdrom0:, otherwise paths are +// prefixed with hostPrefix ("host:" for pcsx2, "host0:" for the SCE +// TOOL) and relative paths get a "./" so the host fs resolves them. +// On PC the path is passed through unchanged. +#define SK_MAXPATH 256 +char *GetFilePath(char *dst, const char *path); +#ifdef RW_PS2 +extern const char *hostPrefix; +#endif + bool InitRW(void); void TerminateRW(void); -Camera *CameraCreate(int32 width, int32 height, bool32 z); +rw::Camera *CameraCreate(int32 width, int32 height, bool32 z); void CameraDestroy(rw::Camera *cam); -void CameraSize(Camera *cam, Rect *r, float viewWindow = 0.0f, float aspectRatio = 0.0f); -void CameraMove(Camera *cam, V3d *delta); -void CameraPan(Camera *cam, V3d *pos, float angle); -void CameraTilt(Camera *cam, V3d *pos, float angle); -void CameraRotate(Camera *cam, V3d *pos, float angle); +void CameraSize(rw::Camera *cam, Rect *r, float viewWindow = 0.0f, float aspectRatio = 0.0f); +void CameraMove(rw::Camera *cam, V3d *delta); +void CameraPan(rw::Camera *cam, V3d *pos, float angle); +void CameraTilt(rw::Camera *cam, V3d *pos, float angle); +void CameraRotate(rw::Camera *cam, V3d *pos, float angle); void SetMousePosition(int x, int y); EventStatus EventHandler(Event e, void *param); } +#include "camera.h" + sk::EventStatus AppEventHandler(sk::Event e, void *param); #ifndef RW_PS2