skeleton: tracked input state, analog pad api, camera manipulator, file paths

EventHandler now keeps keys/mouse/pad state so apps don't have to;
sk::Camera is an orbit/dolly/pan manipulator over an rw::Camera;
GetFilePath mangles paths for cdrom0:/host:/host0: on ps2.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
aap
2026-08-26 17:52:52 +02:00
parent c1a826b13b
commit da98dab4a5
5 changed files with 532 additions and 36 deletions
+268
View File
@@ -0,0 +1,268 @@
#include <math.h>
#include <rw.h>
#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);
}
}
}
+75
View File
@@ -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
+54 -9
View File
@@ -29,11 +29,8 @@ float GetTimeF(void);
* Pad * Pad
*/ */
struct PadData struct PadBtns
{ {
union {
unsigned short bits;
struct {
unsigned short l2 : 1; unsigned short l2 : 1;
unsigned short r2 : 1; unsigned short r2 : 1;
unsigned short l1 : 1; unsigned short l1 : 1;
@@ -50,7 +47,13 @@ struct PadData
unsigned short Dright : 1; unsigned short Dright : 1;
unsigned short Ddown : 1; unsigned short Ddown : 1;
unsigned short Dleft : 1; unsigned short Dleft : 1;
}; };
struct PadData
{
union {
unsigned short bits;
PadBtns btns;
}; };
/* down/right is positive */ /* down/right is positive */
float rX, rY; float rX, rY;
@@ -64,7 +67,7 @@ struct Pad
}; };
static bool gotPadirx; static bool gotPadirx;
static Pad pad; static Pad syspad;
static u_long128 pad_dma_buf[scePadDmaBufferMax] __attribute__((aligned(64))); static u_long128 pad_dma_buf[scePadDmaBufferMax] __attribute__((aligned(64)));
#define EPSILON 0.2f #define EPSILON 0.2f
@@ -142,12 +145,21 @@ PadEvents(void)
{ {
int i, key; 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++){ for(i = 0; i < (int)(sizeof(padkeymap)/sizeof(padkeymap[0])); i++){
key = padkeymap[i].key; key = padkeymap[i].key;
if(pad.rising.bits & padkeymap[i].mask) if(syspad.rising.bits & padkeymap[i].mask)
EventHandler(KEYDOWN, &key); EventHandler(KEYDOWN, &key);
if(pad.falling.bits & padkeymap[i].mask) if(syspad.falling.bits & padkeymap[i].mask)
EventHandler(KEYUP, &key); 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 #endif
+67 -8
View File
@@ -1,3 +1,5 @@
#include <string.h>
#include <rw.h> #include <rw.h>
#include "skeleton.h" #include "skeleton.h"
@@ -7,6 +9,21 @@ namespace sk {
Globals globals; Globals globals;
Args args; 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 bool
InitRW(void) InitRW(void)
@@ -64,11 +81,11 @@ TerminateRW(void)
rw::Engine::term(); rw::Engine::term();
} }
Camera* rw::Camera*
CameraCreate(int32 width, int32 height, bool32 z) CameraCreate(int32 width, int32 height, bool32 z)
{ {
Camera *cam; rw::Camera *cam;
cam = Camera::create(); cam = rw::Camera::create();
cam->setFrame(Frame::create()); cam->setFrame(Frame::create());
cam->frameBuffer = Raster::create(width, height, 0, Raster::CAMERA); cam->frameBuffer = Raster::create(width, height, 0, Raster::CAMERA);
cam->zBuffer = Raster::create(width, height, 0, Raster::ZBUFFER); cam->zBuffer = Raster::create(width, height, 0, Raster::ZBUFFER);
@@ -95,7 +112,7 @@ CameraDestroy(rw::Camera *cam)
} }
void void
CameraSize(Camera *cam, Rect *r, float viewWindow, float aspectRatio) CameraSize(rw::Camera *cam, Rect *r, float viewWindow, float aspectRatio)
{ {
if(cam->frameBuffer){ if(cam->frameBuffer){
cam->frameBuffer->destroy(); cam->frameBuffer->destroy();
@@ -123,7 +140,7 @@ CameraSize(Camera *cam, Rect *r, float viewWindow, float aspectRatio)
} }
void void
CameraMove(Camera *cam, V3d *delta) CameraMove(rw::Camera *cam, V3d *delta)
{ {
rw::V3d offset; rw::V3d offset;
rw::V3d::transformVectors(&offset, delta, 1, &cam->getFrame()->matrix); rw::V3d::transformVectors(&offset, delta, 1, &cam->getFrame()->matrix);
@@ -131,7 +148,7 @@ CameraMove(Camera *cam, V3d *delta)
} }
void void
CameraPan(Camera *cam, V3d *pos, float angle) CameraPan(rw::Camera *cam, V3d *pos, float angle)
{ {
rw::Frame *frame = cam->getFrame(); rw::Frame *frame = cam->getFrame();
rw::V3d trans = pos ? *pos : frame->matrix.pos; rw::V3d trans = pos ? *pos : frame->matrix.pos;
@@ -142,7 +159,7 @@ CameraPan(Camera *cam, V3d *pos, float angle)
} }
void void
CameraTilt(Camera *cam, V3d *pos, float angle) CameraTilt(rw::Camera *cam, V3d *pos, float angle)
{ {
rw::Frame *frame = cam->getFrame(); rw::Frame *frame = cam->getFrame();
rw::V3d trans = pos ? *pos : frame->matrix.pos; rw::V3d trans = pos ? *pos : frame->matrix.pos;
@@ -153,7 +170,7 @@ CameraTilt(Camera *cam, V3d *pos, float angle)
} }
void void
CameraRotate(Camera *cam, V3d *pos, float angle) CameraRotate(rw::Camera *cam, V3d *pos, float angle)
{ {
rw::Frame *frame = cam->getFrame(); rw::Frame *frame = cam->getFrame();
rw::V3d trans = pos ? *pos : frame->matrix.pos; rw::V3d trans = pos ? *pos : frame->matrix.pos;
@@ -167,13 +184,55 @@ EventStatus
EventHandler(Event e, void *param) EventHandler(Event e, void *param)
{ {
EventStatus s; EventStatus s;
int key;
MouseState *ms;
#ifndef RW_PS2 #ifndef RW_PS2
if (e == INITIALIZE) { if (e == INITIALIZE) {
ImGui::CreateContext(); ImGui::CreateContext();
} }
#endif #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); 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){ if(e == QUIT){
globals.quit = 1; globals.quit = 1;
return EVENTPROCESSED; return EVENTPROCESSED;
+55 -6
View File
@@ -102,20 +102,69 @@ struct Args
}; };
extern Args 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); bool InitRW(void);
void TerminateRW(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 CameraDestroy(rw::Camera *cam);
void CameraSize(Camera *cam, Rect *r, float viewWindow = 0.0f, float aspectRatio = 0.0f); void CameraSize(rw::Camera *cam, Rect *r, float viewWindow = 0.0f, float aspectRatio = 0.0f);
void CameraMove(Camera *cam, V3d *delta); void CameraMove(rw::Camera *cam, V3d *delta);
void CameraPan(Camera *cam, V3d *pos, float angle); void CameraPan(rw::Camera *cam, V3d *pos, float angle);
void CameraTilt(Camera *cam, V3d *pos, float angle); void CameraTilt(rw::Camera *cam, V3d *pos, float angle);
void CameraRotate(Camera *cam, V3d *pos, float angle); void CameraRotate(rw::Camera *cam, V3d *pos, float angle);
void SetMousePosition(int x, int y); void SetMousePosition(int x, int y);
EventStatus EventHandler(Event e, void *param); EventStatus EventHandler(Event e, void *param);
} }
#include "camera.h"
sk::EventStatus AppEventHandler(sk::Event e, void *param); sk::EventStatus AppEventHandler(sk::Event e, void *param);
#ifndef RW_PS2 #ifndef RW_PS2