mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-25 18:05:12 -04:00
Add debug synthetic head translation and pitch
Co-authored-by: Yves <calibratedbeats@gmail.com>
This commit is contained in:
@@ -493,9 +493,9 @@ between Play and TCP availability.
|
||||
|
||||
## Complete command index (generated)
|
||||
|
||||
**384 commands registered** — 371 on the native server (`runtime/src/debug_server.c`), 61 on the Beetle server (`runtime/src/beetle_debug_server.c`).
|
||||
**385 commands registered** — 372 on the native server (`runtime/src/debug_server.c`), 61 on the Beetle server (`runtime/src/beetle_debug_server.c`).
|
||||
|
||||
85 of 384 have prose above; **299 are index-only**. An index-only command still works — it just has no description here yet. Send it `{"cmd":"<name>"}` and read the reply, or find its `handle_*` function in the server source.
|
||||
85 of 385 have prose above; **300 are index-only**. An index-only command still works — it just has no description here yet. Send it `{"cmd":"<name>"}` and read the reply, or find its `handle_*` function in the server source.
|
||||
|
||||
Regenerate with `python tools/gen_tcp_commands.py`; `--check` fails if this block has drifted from the code.
|
||||
|
||||
@@ -705,6 +705,7 @@ Regenerate with `python tools/gen_tcp_commands.py`; `--check` fails if this bloc
|
||||
| `openxr_hands_override` | ✓ | | |
|
||||
| `openxr_input` | ✓ | | |
|
||||
| `openxr_input_override` | ✓ | | |
|
||||
| `openxr_pose_override` | ✓ | | |
|
||||
| `openxr_stats` | ✓ | | |
|
||||
| `openxr_views` | ✓ | | |
|
||||
| `overlay_candidates` | ✓ | | |
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
# Add debug synthetic head translation and pitch
|
||||
|
||||
Source: [FractalEngineer/psxrecomp](https://github.com/FractalEngineer/psxrecomp).
|
||||
- [2ed12640](https://github.com/FractalEngineer/psxrecomp/commit/2ed12640adab3602acbf3307d42e9c2739b7d0eb)
|
||||
- [a3714fad](https://github.com/FractalEngineer/psxrecomp/commit/a3714fada4d0582500001b0c22e4dfa00840403e)
|
||||
|
||||
Original author: Yves <calibratedbeats@gmail.com>; retained as commit author
|
||||
and Co-authored-by on this reconstructed port.
|
||||
|
||||
Adapted: this feature and its tests. Excluded: other VR features, game policy,
|
||||
assets and chronological investigation notes.
|
||||
Base: `e4f1f985428101eb6c8f83fce834e281860b1fd2` (current upstream master or the preceding feature).
|
||||
|
||||
Validation: Windows/GCC 13.2 Release: XR-off input fixture passes, including finite/range rejection, binocular translation/pitch, disabled state and clearing; command index is current.
|
||||
|
||||
Consumer: [Medal of Honor v0.1.6](https://github.com/FractalEngineer/Medal-Of-Honor-PSX-Recomp/tree/1957e28a5dc7f5daa79e2b9b6ed867eb06b41ace).
|
||||
Requires a framework pin bump and rebuild; no game/BIOS regeneration.
|
||||
@@ -9218,6 +9218,25 @@ static void handle_openxr_hands_override(int id,const char *json) {
|
||||
/* Debug-only synthetic action sample; axes are signed thousandths [-1000,1000].
|
||||
* Explicitly tagged so desktop controls cannot be mistaken for device evidence. */
|
||||
#ifndef PSX_NO_DEBUG_TOOLS
|
||||
/* openxr_pose_override: test-only synthetic head motion in millimetres, with an optional pitch_deg
|
||||
* look angle, for headless head-motion checks. clear=1 disarms. Each call is absolute. The pitch is
|
||||
* what reaches the defects that only appear when looking down, which no translation can show. */
|
||||
static void handle_openxr_pose_override(int id,const char *json) {
|
||||
extern int psx_openxr_pose_override(double,double,double);
|
||||
extern int psx_openxr_pose_pitch_override(double);
|
||||
extern void psx_openxr_pose_clear(void);
|
||||
if(json_get_int(json,"clear",0)){ psx_openxr_pose_clear(); send_fmt("{\"id\":%d,\"ok\":true,\"cleared\":1}",id); return; }
|
||||
int x=json_get_int(json,"x_mm",0),y=json_get_int(json,"y_mm",0),z=json_get_int(json,"z_mm",0);
|
||||
int pitch_deg=json_get_int(json,"pitch_deg",0);
|
||||
if(!psx_openxr_pose_override((double)x/1000.0,(double)y/1000.0,(double)z/1000.0)){
|
||||
send_err(id,"pose offset must be finite and within +/-10 m");return;
|
||||
}
|
||||
if(!psx_openxr_pose_pitch_override((double)pitch_deg)){
|
||||
send_err(id,"pitch_deg must be finite and within +/-90");return;
|
||||
}
|
||||
send_fmt("{\"id\":%d,\"ok\":true,\"x_mm\":%d,\"y_mm\":%d,\"z_mm\":%d,\"pitch_deg\":%d}",id,x,y,z,pitch_deg);
|
||||
}
|
||||
|
||||
static void handle_openxr_input_override(int id,const char *json) {
|
||||
if(json_get_int(json,"clear",0)) psx_openxr_input_override(NULL);
|
||||
else {
|
||||
@@ -16294,7 +16313,8 @@ static const CmdEntry s_commands[] = {
|
||||
#endif
|
||||
{ "openxr_hands", handle_openxr_hands },
|
||||
#ifndef PSX_NO_DEBUG_TOOLS
|
||||
{ "openxr_hands_override", handle_openxr_hands_override },
|
||||
{ "openxr_hands_override", handle_openxr_hands_override },
|
||||
{ "openxr_pose_override", handle_openxr_pose_override },
|
||||
#endif
|
||||
{ "stereo_stats", handle_stereo_stats },
|
||||
{ "frame_trace", handle_frame_trace },
|
||||
|
||||
@@ -43,6 +43,41 @@ static int valid_pose_numbers(const double q[4],const double p[3]) {
|
||||
for(int i=0;i<3;i++)if(!isfinite(p[i]) || fabs(p[i])>100)return 0;
|
||||
return n>=.5 && n<=1.5;
|
||||
}
|
||||
/* Test-only synthetic head motion: translation in room-space metres and an optional pitch, added to
|
||||
* both eyes after the origin is established so it reads as the head moving in the world. The
|
||||
* translation checks that the world shifts rigidly with the head. The pitch exists because the
|
||||
* defects that only appear when looking down cannot be reached by translation, and because the only
|
||||
* other look path is unwired. Armed by the first override call; default off, so shipped behaviour
|
||||
* is unchanged. */
|
||||
static int s_pose_bob_armed;
|
||||
static double s_pose_bob_m[3];
|
||||
static double s_pose_bob_pitch_deg;
|
||||
void psx_openxr_pose_bob(double q[2][4],double p[2][3]) {
|
||||
if(!s_pose_bob_armed)return;
|
||||
for(int i=0;i<2;i++)for(int j=0;j<3;j++)p[i][j]+=s_pose_bob_m[j];
|
||||
if(s_pose_bob_pitch_deg!=0.0) {
|
||||
double h=s_pose_bob_pitch_deg*(3.141592653589793/180.0)*0.5,sq=sin(h),cq=cos(h);
|
||||
for(int i=0;i<2;i++) {
|
||||
double x=q[i][0],y=q[i][1],z=q[i][2],w=q[i][3];
|
||||
/* Left multiply by a rotation about the XR X axis: a look up or down. */
|
||||
q[i][0]=sq*w+cq*x;q[i][1]=cq*y-sq*z;q[i][2]=sq*y+cq*z;q[i][3]=cq*w-sq*x;
|
||||
}
|
||||
}
|
||||
}
|
||||
int psx_openxr_pose_override(double x_m,double y_m,double z_m) {
|
||||
if(!isfinite(x_m)||!isfinite(y_m)||!isfinite(z_m))return 0;
|
||||
if(fabs(x_m)>10.0||fabs(y_m)>10.0||fabs(z_m)>10.0)return 0;
|
||||
s_pose_bob_m[0]=x_m;s_pose_bob_m[1]=y_m;s_pose_bob_m[2]=z_m;s_pose_bob_armed=1;
|
||||
return 1;
|
||||
}
|
||||
int psx_openxr_pose_pitch_override(double pitch_deg) {
|
||||
if(!isfinite(pitch_deg) || fabs(pitch_deg)>90.0)return 0;
|
||||
s_pose_bob_pitch_deg=pitch_deg;s_pose_bob_armed=1;
|
||||
return 1;
|
||||
}
|
||||
void psx_openxr_pose_clear(void) {
|
||||
s_pose_bob_m[0]=s_pose_bob_m[1]=s_pose_bob_m[2]=0.0;s_pose_bob_pitch_deg=0.0;s_pose_bob_armed=0;
|
||||
}
|
||||
int psx_openxr_hands_override(const PSXModOpenXRHands *hands) {
|
||||
if(!hands){s_hands_override=0;hands_clear();return 1;}
|
||||
if(hands->struct_size!=sizeof *hands || hands->focused>1 || hands->origin_valid>1)return 0;
|
||||
@@ -643,6 +678,9 @@ int psx_openxr_begin(int width,int height,double units) {
|
||||
double separation=0;
|
||||
for(int j=0;j<3;j++)separation+=(p[1][j]-p[0][j])*(p[1][j]-p[0][j]);
|
||||
s_stats.ipd_m=(float)sqrt(separation);
|
||||
#ifndef PSX_NO_DEBUG_TOOLS
|
||||
psx_openxr_pose_bob(q, p); /* test-only synthetic head motion; no-op unless armed */
|
||||
#endif
|
||||
for(int i=0;i<2;i++) {
|
||||
XrFovf *v=&s_views[i].fov;
|
||||
double f[4]={v->angleLeft,v->angleRight,v->angleUp,v->angleDown};
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
#include <stdio.h>
|
||||
#include <math.h>
|
||||
#include "xr_surface_math.h"
|
||||
extern void psx_openxr_pose_bob(double q[2][4], double p[2][3]);
|
||||
extern int psx_openxr_pose_override(double, double, double);
|
||||
extern int psx_openxr_pose_pitch_override(double);
|
||||
extern void psx_openxr_pose_clear(void);
|
||||
/* Evaluate side effects and failures even when NDEBUG is defined. */
|
||||
#define CHECK(expr) do { \
|
||||
if (!(expr)) { \
|
||||
@@ -10,6 +14,26 @@
|
||||
} \
|
||||
} while (0)
|
||||
int main(void) {
|
||||
/* Synthetic controls are absolute, bounded and inert after clearing. */
|
||||
double bob_q[2][4]={{0,0,0,1},{0,0,0,1}};
|
||||
double bob_p[2][3]={{-.03,0,0},{.03,0,0}};
|
||||
psx_openxr_pose_clear();
|
||||
CHECK(!psx_openxr_pose_override(NAN,0,0));
|
||||
CHECK(!psx_openxr_pose_override(11,0,0));
|
||||
CHECK(!psx_openxr_pose_pitch_override(INFINITY));
|
||||
CHECK(!psx_openxr_pose_pitch_override(91));
|
||||
psx_openxr_pose_bob(bob_q,bob_p);
|
||||
CHECK(bob_p[0][0]==-.03 && bob_q[0][3]==1);
|
||||
CHECK(psx_openxr_pose_override(.1,.2,-.3));
|
||||
CHECK(psx_openxr_pose_pitch_override(30));
|
||||
psx_openxr_pose_bob(bob_q,bob_p);
|
||||
CHECK(fabs(bob_p[0][0]-.07)<1e-12 && fabs(bob_p[1][0]-.13)<1e-12);
|
||||
CHECK(bob_p[0][1]==.2 && bob_p[1][2]==-.3);
|
||||
CHECK(fabs(bob_q[0][0]-sin(3.141592653589793/12))<1e-12);
|
||||
CHECK(bob_q[0][0]==bob_q[1][0] && bob_q[0][3]==bob_q[1][3]);
|
||||
psx_openxr_pose_clear();
|
||||
psx_openxr_pose_bob(bob_q,bob_p);
|
||||
CHECK(fabs(bob_p[0][0]-.07)<1e-12);
|
||||
/* Shared head center gives coherent binocular geometry. Billboarding must
|
||||
* ignore attachment roll, respond to head motion, and reject near-plane
|
||||
* crossings. Common translation of eye/head/surface preserves projection. */
|
||||
|
||||
Reference in New Issue
Block a user