Code cleanup

This commit is contained in:
Scott Duensing 2026-09-07 01:28:53 -05:00
parent 259c1d5d55
commit 3f417b1e24
17 changed files with 2156 additions and 2049 deletions

View file

@ -135,7 +135,8 @@ API Changes
threshold glow, blurred over a half-size chain before the tone curve. threshold glow, blurred over a half-size chain before the tone curve.
viewNew renders a second camera to a texture every frame (viewSetCamera viewNew renders a second camera to a texture every frame (viewSetCamera
points it) that materialSetView shows on any surface: monitors, points it) that materialSetView shows on any surface: monitors,
mirrors, portals. mirrors, portals. Each view turns billboards to its own camera and
draws transparency back to front from it.
- Particle extras. Trails (emitterSetTrail: a fading ribbon behind - Particle extras. Trails (emitterSetTrail: a fading ribbon behind
each particle, 2D and 3D), collisions with a floor or, by ray cast, each particle, 2D and 3D), collisions with a floor or, by ray cast,

View file

@ -1023,9 +1023,10 @@ a material: a security monitor, a rear-view mirror, a portal, a picture in
picture. `viewNew(width, height)` makes one at that resolution, picture. `viewNew(width, height)` makes one at that resolution,
`viewSetCamera` gives it a camera node, and `materialSetView` puts its `viewSetCamera` gives it a camera node, and `materialSetView` puts its
picture on any mesh. Views share the frame's shadows and skip bloom, and picture on any mesh. Views share the frame's shadows and skip bloom, and
each one renders the scene again, so keep them few and small. A billboard each one renders the scene again, so keep them few and small. Each view
(see <<scenesprites,Sprites and Text in the Scene>>) faces the window's turns billboards (see <<scenesprites,Sprites and Text in the Scene>>) to
camera, so a view sees it from the side. its own camera and draws transparent materials back to front from it, so
a monitor sees name tags face on and glass in the right order.
[source,lua] [source,lua]
---- ----
@ -7820,8 +7821,8 @@ world up only, so the node stays upright (trees, health bars);
`BILLBOARD_NONE`, the default, does not turn at all. The node's world `BILLBOARD_NONE`, the default, does not turn at all. The node's world
position and scale are kept and its own rotation is ignored while a mode is position and scale are kept and its own rotation is ignored while a mode is
set. Any other value aborts the script. It works on meshes as well as set. Any other value aborts the script. It works on meshes as well as
sprites and text, and follows the window's camera: a view sees the sprites and text, and turns to whichever camera is drawing: a view sees
billboard from the side. the billboard face on too, while its shadow follows the window's camera.
*Since:* 3.00. *Since:* 3.00.
*See also:* <<nodesetsprite,nodeSetSprite>>, <<nodesettext,nodeSetText>>, <<cameraset,cameraSet>> *See also:* <<nodesetsprite,nodeSetSprite>>, <<nodesettext,nodeSetText>>, <<cameraset,cameraSet>>
@ -12541,7 +12542,7 @@ end
[#view] [#view]
=== View === View
A view is a second camera rendered to a texture every frame, for a monitor, a mirror or a portal in the scene: `viewNew` makes one and returns an integer handle, `viewSetCamera` points it at a node, and `materialSetView` shows it on a material. Up to four exist at once; each renders the whole scene again at its own size, in overlay-independent pixels, with the main camera's projection, the frame's shadows and no bloom, so keep them few and small. Billboards (`nodeSetBillboard`) are turned to face the window's camera and blended draws are sorted back to front from it, once for every view, so a view whose camera looks from elsewhere sees billboards side on and may see transparent objects overlap in the wrong order. A bad handle raises an error. See <<scenes3d,3D Scenes>>. A view is a second camera rendered to a texture every frame, for a monitor, a mirror or a portal in the scene: `viewNew` makes one and returns an integer handle, `viewSetCamera` points it at a node, and `materialSetView` shows it on a material. Up to four exist at once; each renders the whole scene again at its own size, in overlay-independent pixels, with the main camera's projection, the frame's shadows and no bloom, so keep them few and small. Every view turns billboards (`nodeSetBillboard`) to its own camera and sorts blended draws back to front from it, so a view whose camera looks from elsewhere still sees billboards face on and transparent objects in the right order. A bad handle raises an error. See <<scenes3d,3D Scenes>>.
[#viewdelete] [#viewdelete]
==== viewDelete ==== viewDelete

View file

@ -41,9 +41,9 @@
static bool _isRadiance(const uint8_t *bytes, size_t size); static bool _isRadiance(const uint8_t *bytes, size_t size);
static float _linear(uint8_t value);
static float *_loadRadiance(const uint8_t *bytes, size_t size, int32_t *width, int32_t *height); static float *_loadRadiance(const uint8_t *bytes, size_t size, int32_t *width, int32_t *height);
static float *_loadSurface(SDL_Surface *surface, int32_t *width, int32_t *height); static float *_loadSurface(SDL_Surface *surface, int32_t *width, int32_t *height);
static float _linear(uint8_t value);
static bool _readScanline(const uint8_t *bytes, size_t size, size_t *offset, uint8_t *rgbe, int32_t width); static bool _readScanline(const uint8_t *bytes, size_t size, size_t *offset, uint8_t *rgbe, int32_t width);
static void _rgbeToFloat(const uint8_t *rgbe, float *out); static void _rgbeToFloat(const uint8_t *rgbe, float *out);

View file

@ -54,8 +54,8 @@ typedef struct Ktx2ImageS {
bool srgb; // The file says its colours are sRGB bool srgb; // The file says its colours are sRGB
} Ktx2ImageT; } Ktx2ImageT;
void ktx2Free(Ktx2ImageT *image);
bool ktx2Is(const void *bytes, size_t size); // Starts with the KTX2 identifier bool ktx2Is(const void *bytes, size_t size); // Starts with the KTX2 identifier
bool ktx2Transcode(const void *bytes, size_t size, Ktx2FormatE wanted, Ktx2ImageT *out); // Every level; free with ktx2Free bool ktx2Transcode(const void *bytes, size_t size, Ktx2FormatE wanted, Ktx2ImageT *out); // Every level; free with ktx2Free
void ktx2Free(Ktx2ImageT *image);
#endif #endif

View file

@ -185,12 +185,12 @@ static void _crashHandler(int signalNumber);
#endif #endif
static void _launcher(const char *exeName, ConfigT *conf); static void _launcher(const char *exeName, ConfigT *conf);
static void _mainTrace(const ConfigT *conf, const char *fmt, ...) __attribute__((format(printf, 2, 3))); static void _mainTrace(const ConfigT *conf, const char *fmt, ...) __attribute__((format(printf, 2, 3)));
static bool _runTool(const ConfigT *conf);
static bool _modeMatchesRatio(int32_t index, int32_t ratioIndex); static bool _modeMatchesRatio(int32_t index, int32_t ratioIndex);
static ConfigT *_parseArguments(const char *exeName, int32_t argc, char *argv[]); static ConfigT *_parseArguments(const char *exeName, int32_t argc, char *argv[]);
static bool _parseInteger(const char *text, int32_t *value); static bool _parseInteger(const char *text, int32_t *value);
static void _requireRange(const char *exeName, int32_t value, int32_t min, int32_t max, const char *what, const char *unit); static void _requireRange(const char *exeName, int32_t value, int32_t min, int32_t max, const char *what, const char *unit);
static void _resolveFiles(const char *exeName, ConfigT *conf); static void _resolveFiles(const char *exeName, ConfigT *conf);
static bool _runTool(const ConfigT *conf);
static void _showHeader(void); static void _showHeader(void);
static void _showUsage(const char *name, const char *message) __attribute__((noreturn)); static void _showUsage(const char *name, const char *message) __attribute__((noreturn));
static void _startSDL(void); static void _startSDL(void);
@ -207,6 +207,22 @@ static char *_cloneString(const char *string) {
} }
// Last words on a crash: where it happened, so a report can name the line. Async-signal-unsafe
// calls are acceptable here; the process is already lost.
#ifndef _WIN32
static void _crashHandler(int signalNumber) {
void *frames[CRASH_FRAMES_MAX];
int32_t count = backtrace(frames, CRASH_FRAMES_MAX);
fprintf(stderr, "\nSinge crashed (signal %d). Backtrace:\n", signalNumber);
backtrace_symbols_fd(frames, count, STDERR_FILENO);
fprintf(stderr, "Run with --program and send trace.txt with this.\n");
signal(signalNumber, SIG_DFL);
raise(signalNumber);
}
#endif
// Writes an embedded support file, or rewrites it when the installed copy differs from this build's. // Writes an embedded support file, or rewrites it when the installed copy differs from this build's.
static bool _extractFile(const char *filename, const uint8_t *data, size_t length) { static bool _extractFile(const char *filename, const uint8_t *data, size_t length) {
FILE *out = NULL; FILE *out = NULL;
@ -278,22 +294,6 @@ static char *_findVideoFile(const char *baseName) {
} }
// Last words on a crash: where it happened, so a report can name the line. Async-signal-unsafe
// calls are acceptable here; the process is already lost.
#ifndef _WIN32
static void _crashHandler(int signalNumber) {
void *frames[CRASH_FRAMES_MAX];
int32_t count = backtrace(frames, CRASH_FRAMES_MAX);
fprintf(stderr, "\nSinge crashed (signal %d). Backtrace:\n", signalNumber);
backtrace_symbols_fd(frames, count, STDERR_FILENO);
fprintf(stderr, "Run with --program and send trace.txt with this.\n");
signal(signalNumber, SIG_DFL);
raise(signalNumber);
}
#endif
static void _launcher(const char *exeName, ConfigT *conf) { static void _launcher(const char *exeName, ConfigT *conf) {
int32_t x = 0; int32_t x = 0;
int32_t bestResIndex = -1; int32_t bestResIndex = -1;
@ -887,23 +887,6 @@ static void _resolveFiles(const char *exeName, ConfigT *conf) {
} }
static void _showHeader(void) {
static bool shown = false;
if (!shown) {
utilRedirectConsole();
// 00000000011111111112222222222333333333344444444445555555555666666666677777777778
// 12345678901234567890123456789012345678901234567890123456789012345678901234567890
utilSay(" ___ ___ _ _ ___ ___");
utilSay("/ __|_ _| \\| |/ __| __| SINGE Is Not a Game Emulator %s", VERSION_STRING);
utilSay("\\__ \\| || .` | (_ | _| Copyright (c) 2006-%s Scott C. Duensing", COPYRIGHT_END_YEAR);
utilSay("|___/___|_|\\_|\\___|___| https://KangarooPunch.com https://SingeEngine.com");
utilNewline();
shown = true;
}
}
// --pack, --unpack, and --patch: the option carries the source, the script argument the destination. // --pack, --unpack, and --patch: the option carries the source, the script argument the destination.
static bool _runTool(const ConfigT *conf) { static bool _runTool(const ConfigT *conf) {
switch (conf->toolMode) { switch (conf->toolMode) {
@ -922,6 +905,23 @@ static bool _runTool(const ConfigT *conf) {
} }
static void _showHeader(void) {
static bool shown = false;
if (!shown) {
utilRedirectConsole();
// 00000000011111111112222222222333333333344444444445555555555666666666677777777778
// 12345678901234567890123456789012345678901234567890123456789012345678901234567890
utilSay(" ___ ___ _ _ ___ ___");
utilSay("/ __|_ _| \\| |/ __| __| SINGE Is Not a Game Emulator %s", VERSION_STRING);
utilSay("\\__ \\| || .` | (_ | _| Copyright (c) 2006-%s Scott C. Duensing", COPYRIGHT_END_YEAR);
utilSay("|___/___|_|\\_|\\___|___| https://KangarooPunch.com https://SingeEngine.com");
utilNewline();
shown = true;
}
}
static void _showUsage(const char *name, const char *message) { static void _showUsage(const char *name, const char *message) {
const int32_t helpColumn = 6 + USAGE_OPTION_WIDTH; // " -x, " plus the padded long form const int32_t helpColumn = 6 + USAGE_OPTION_WIDTH; // " -x, " plus the padded long form
const int32_t helpWidth = USAGE_LINE_WIDTH - helpColumn; const int32_t helpWidth = USAGE_LINE_WIDTH - helpColumn;

View file

@ -42,32 +42,32 @@ extern "C" {
#define NAV_MAX_CROWD_AGENTS 128 // Agents one mesh's crowd can steer #define NAV_MAX_CROWD_AGENTS 128 // Agents one mesh's crowd can steer
void navInit(void); void navInit(void);
int32_t navPollArrived(int32_t *agents, int32_t max); // Agents that reached their targets since last asked
void navQuit(void); void navQuit(void);
void navUpdate(bool advance); // Once per frame: moves the agents void navUpdate(bool advance); // Once per frame: moves the agents
int32_t navPollArrived(int32_t *agents, int32_t max); // Agents that reached their targets since last asked
int32_t navNew(float agentRadius, float agentHeight, float maxSlopeDegrees, float maxStep);
bool navAddNode(int32_t nav, int32_t node); // The node's mesh and its children's, where they stand now bool navAddNode(int32_t nav, int32_t node); // The node's mesh and its children's, where they stand now
bool navBuild(int32_t nav); // Bakes what was added; may take a moment bool navBuild(int32_t nav); // Bakes what was added; may take a moment
bool navDelete(int32_t nav); bool navDelete(int32_t nav);
bool navValid(int32_t nav);
int32_t navPath(int32_t nav, Vec3T from, Vec3T to, Vec3T *points, int32_t max); // Corners of the path, or -1 for none
bool navNearest(int32_t nav, Vec3T point, Vec3T *out); // The closest point on the mesh
bool navRaycast(int32_t nav, Vec3T from, Vec3T to, Vec3T *hit); // true when the walk from from to to is blocked, hit where
bool navRandomPoint(int32_t nav, Vec3T *out);
bool navSave(int32_t nav, const char *path); // The baked mesh, to reload with navLoad
int32_t navLoad(const void *data, size_t size, float agentRadius, float agentHeight);
int32_t navGetPolygons(int32_t nav, Vec3T *vertices, int32_t max); // Triangles of the baked mesh (3 per), for debug drawing int32_t navGetPolygons(int32_t nav, Vec3T *vertices, int32_t max); // Triangles of the baked mesh (3 per), for debug drawing
int32_t navLoad(const void *data, size_t size, float agentRadius, float agentHeight);
bool navNearest(int32_t nav, Vec3T point, Vec3T *out); // The closest point on the mesh
int32_t navNew(float agentRadius, float agentHeight, float maxSlopeDegrees, float maxStep);
int32_t navPath(int32_t nav, Vec3T from, Vec3T to, Vec3T *points, int32_t max); // Corners of the path, or -1 for none
bool navRandomPoint(int32_t nav, Vec3T *out);
bool navRaycast(int32_t nav, Vec3T from, Vec3T to, Vec3T *hit); // true when the walk from from to to is blocked, hit where
bool navSave(int32_t nav, const char *path); // The baked mesh, to reload with navLoad
bool navValid(int32_t nav);
int32_t navAgentNew(int32_t nav, int32_t node, float radius, float height, float speed);
bool navAgentDelete(int32_t agent); bool navAgentDelete(int32_t agent);
bool navAgentMoveTo(int32_t agent, Vec3T target); int32_t navAgentGetNode(int32_t agent);
bool navAgentStop(int32_t agent);
bool navAgentGetVelocity(int32_t agent, Vec3T *velocity); bool navAgentGetVelocity(int32_t agent, Vec3T *velocity);
bool navAgentIsArrived(int32_t agent); bool navAgentIsArrived(int32_t agent);
bool navAgentMoveTo(int32_t agent, Vec3T target);
int32_t navAgentNew(int32_t nav, int32_t node, float radius, float height, float speed);
bool navAgentSetPlayer(int32_t agent, bool player); // Steer a player controller on the node instead of placing it bool navAgentSetPlayer(int32_t agent, bool player); // Steer a player controller on the node instead of placing it
bool navAgentStop(int32_t agent);
bool navAgentValid(int32_t agent); bool navAgentValid(int32_t agent);
int32_t navAgentGetNode(int32_t agent);
#ifdef __cplusplus #ifdef __cplusplus
} }

View file

@ -77,46 +77,46 @@ typedef struct EmitterViewT {
Vec3T trailOffset; // Added to trail points (a local emitter's origin) Vec3T trailOffset; // Added to trail points (a local emitter's origin)
} EmitterViewT; } EmitterViewT;
void particlesClearQueue2D(void);
void particlesInit(void); void particlesInit(void);
void particlesQueue2D(int32_t emitter); // Draw this emitter this frame
int32_t particlesQueued2D(ParticleLayerE layer, int32_t *emitters, int32_t max);
void particlesQuit(void); void particlesQuit(void);
void particlesUpdate(bool advance); void particlesUpdate(bool advance);
int32_t particlesView3D(EmitterViewT *views, int32_t max); // Every 3D emitter with live particles, up to max; returns how many int32_t particlesView3D(EmitterViewT *views, int32_t max); // Every 3D emitter with live particles, up to max; returns how many
bool particlesViewEmitter(int32_t emitter, EmitterViewT *view); bool particlesViewEmitter(int32_t emitter, EmitterViewT *view);
void particlesQueue2D(int32_t emitter); // Draw this emitter this frame
int32_t particlesQueued2D(ParticleLayerE layer, int32_t *emitters, int32_t max);
void particlesClearQueue2D(void);
int32_t emitterNew(int32_t node); void emitterBurst(int32_t emitter, int32_t count);
bool emitterValid(int32_t emitter); void emitterClear(int32_t emitter);
void emitterDelete(int32_t emitter); void emitterDelete(int32_t emitter);
void emitterSetTexture(int32_t emitter, SDL_Surface **frames, int32_t frameCount); // Copies the surfaces; NULL restores the disc int32_t emitterGetCount(int32_t emitter);
void emitterSetTrail(int32_t emitter, int32_t length, float width); bool emitterIs3D(int32_t emitter);
void emitterSetFrames(int32_t emitter, int32_t first, int32_t last); bool emitterIsActive(int32_t emitter);
int32_t emitterNew(int32_t node);
void emitterSetBlend(int32_t emitter, ParticleBlendE blend); void emitterSetBlend(int32_t emitter, ParticleBlendE blend);
void emitterSetCollide(int32_t emitter, ParticleCollideE mode, float bounce, float friction, float floor); void emitterSetCollide(int32_t emitter, ParticleCollideE mode, float bounce, float friction, float floor);
void emitterSetColor(int32_t emitter, const float *start, const float *finish);
void emitterSetDirection(int32_t emitter, Vec3T direction);
void emitterSetDrag(int32_t emitter, float perSecond);
void emitterSetFrames(int32_t emitter, int32_t first, int32_t last);
void emitterSetGravity(int32_t emitter, Vec3T acceleration);
void emitterSetLayer(int32_t emitter, ParticleLayerE layer); void emitterSetLayer(int32_t emitter, ParticleLayerE layer);
void emitterSetRate(int32_t emitter, float perSecond);
void emitterSetLife(int32_t emitter, float minSeconds, float maxSeconds); void emitterSetLife(int32_t emitter, float minSeconds, float maxSeconds);
void emitterSetLit(int32_t emitter, bool lit); void emitterSetLit(int32_t emitter, bool lit);
void emitterSetSpeed(int32_t emitter, float min, float max);
void emitterSetDirection(int32_t emitter, Vec3T direction);
void emitterSetSpread(int32_t emitter, float degrees);
void emitterSetGravity(int32_t emitter, Vec3T acceleration);
void emitterSetDrag(int32_t emitter, float perSecond);
void emitterSetSize(int32_t emitter, float start, float finish, float variation);
void emitterSetSoftness(int32_t emitter, float distance);
void emitterSetColor(int32_t emitter, const float *start, const float *finish);
void emitterSetSpin(int32_t emitter, float min, float max);
void emitterSetRadius(int32_t emitter, float radius);
void emitterSetLocal(int32_t emitter, bool local); void emitterSetLocal(int32_t emitter, bool local);
void emitterSetMax(int32_t emitter, int32_t count); void emitterSetMax(int32_t emitter, int32_t count);
void emitterSetPosition(int32_t emitter, Vec3T position); void emitterSetPosition(int32_t emitter, Vec3T position);
void emitterSetRadius(int32_t emitter, float radius);
void emitterSetRate(int32_t emitter, float perSecond);
void emitterSetSize(int32_t emitter, float start, float finish, float variation);
void emitterSetSoftness(int32_t emitter, float distance);
void emitterSetSpeed(int32_t emitter, float min, float max);
void emitterSetSpin(int32_t emitter, float min, float max);
void emitterSetSpread(int32_t emitter, float degrees);
void emitterSetTexture(int32_t emitter, SDL_Surface **frames, int32_t frameCount); // Copies the surfaces; NULL restores the disc
void emitterSetTrail(int32_t emitter, int32_t length, float width);
void emitterStart(int32_t emitter); void emitterStart(int32_t emitter);
void emitterStop(int32_t emitter); void emitterStop(int32_t emitter);
void emitterBurst(int32_t emitter, int32_t count); bool emitterValid(int32_t emitter);
void emitterClear(int32_t emitter);
int32_t emitterGetCount(int32_t emitter);
bool emitterIsActive(int32_t emitter);
bool emitterIs3D(int32_t emitter);
#endif #endif

View file

@ -132,6 +132,15 @@ int32_t jointNew(JointTypeE type, int32_t nodeA, int32_t nodeB, Vec3T anchor, Ve
bool jointSetLimits(int32_t joint, float low, float high); bool jointSetLimits(int32_t joint, float low, float high);
bool jointValid(int32_t joint); bool jointValid(int32_t joint);
bool physicsAvailable(void); bool physicsAvailable(void);
int32_t physicsGetEvents(PhysicsEventT *events, int32_t maximum);
bool physicsInit(void);
void physicsQuit(void);
bool physicsRaycast(Vec3T origin, Vec3T direction, float maxDistance, int32_t *node, Vec3T *point, Vec3T *normal);
void physicsSet2D(bool planar);
void physicsSetDebug(uint32_t mask);
void physicsSetEnabled(bool enabled);
void physicsSetGravity(Vec3T gravity);
void physicsUpdate(bool advance);
bool playerDelete(int32_t node); bool playerDelete(int32_t node);
bool playerExists(int32_t node); bool playerExists(int32_t node);
int32_t playerGetGround(int32_t node, Vec3T *normal); int32_t playerGetGround(int32_t node, Vec3T *normal);
@ -184,20 +193,11 @@ bool vehicleSetAntiRoll(int32_t node, float stiffness);
bool vehicleSetBrakes(int32_t node, float brake, float handBrake); bool vehicleSetBrakes(int32_t node, float brake, float handBrake);
bool vehicleSetEngine(int32_t node, float maxTorque, float maxRpm, float minRpm); bool vehicleSetEngine(int32_t node, float maxTorque, float maxRpm, float minRpm);
bool vehicleSetGears(int32_t node, const float *ratios, int32_t count, float reverse, bool automatic); bool vehicleSetGears(int32_t node, const float *ratios, int32_t count, float reverse, bool automatic);
bool vehicleSetSteering(int32_t node, float maxDegrees);
bool vehicleSetRudder(int32_t node, float maxTorque); bool vehicleSetRudder(int32_t node, float maxTorque);
bool vehicleSetSteering(int32_t node, float maxDegrees);
bool vehicleSetSuspension(int32_t node, float frequency, float damping); bool vehicleSetSuspension(int32_t node, float frequency, float damping);
bool vehicleSetThrust(int32_t node, float maxForce, Vec3T point); bool vehicleSetThrust(int32_t node, float maxForce, Vec3T point);
bool vehicleSetWheel(int32_t node, int32_t index, bool steered, bool driven); bool vehicleSetWheel(int32_t node, int32_t index, bool steered, bool driven);
int32_t physicsGetEvents(PhysicsEventT *events, int32_t maximum);
bool physicsInit(void);
void physicsQuit(void);
bool physicsRaycast(Vec3T origin, Vec3T direction, float maxDistance, int32_t *node, Vec3T *point, Vec3T *normal);
void physicsSet2D(bool planar);
void physicsSetDebug(uint32_t mask);
void physicsSetEnabled(bool enabled);
void physicsSetGravity(Vec3T gravity);
void physicsUpdate(bool advance);
#ifdef __cplusplus #ifdef __cplusplus

View file

@ -2251,8 +2251,6 @@ namespace {
} }
// ===== Bodies =====
// A force for this step, at the centre of mass or a world point. // A force for this step, at the centre of mass or a world point.
bool bodyApplyForce(int32_t node, Vec3T force, const Vec3T *at) { bool bodyApplyForce(int32_t node, Vec3T force, const Vec3T *at) {
BodyRecordT *record = _find(node); BodyRecordT *record = _find(node);
@ -2419,6 +2417,28 @@ bool bodySetBounce(int32_t node, float bounce) {
} }
bool bodySetBuoyancy(int32_t node, float factor) {
BodyRecordT *record = _find(node);
if (record == nullptr) {
return false;
}
record->buoyancy = SDL_max(0.0f, factor);
return true;
}
bool bodySetCurrent(int32_t node, Vec3T flow) {
BodyRecordT *record = _find(node);
if (record == nullptr) {
return false;
}
record->current = flow;
return true;
}
// Takes the body out of the world (it stops colliding and moving) and puts it back. // Takes the body out of the world (it stops colliding and moving) and puts it back.
bool bodySetEnabled(int32_t node, bool enabled) { bool bodySetEnabled(int32_t node, bool enabled) {
BodyRecordT *record = _find(node); BodyRecordT *record = _find(node);
@ -2500,7 +2520,24 @@ bool bodySetVelocity(int32_t node, Vec3T velocity) {
} }
// ===== Joints ===== // Fills a static trigger with water: bodies inside float, sink and drift; players swim.
bool bodySetWater(int32_t node, float density, float linearDrag, float angularDrag) {
BodyRecordT *record = _find(node);
if ((record == nullptr) || (record->type != BODY_STATIC)) {
utilTrace("Physics: water needs a static body on node %d.", node);
return false;
}
if (!record->trigger) {
bodySetTrigger(node, true);
}
record->water = true;
record->waterDensity = SDL_max(0.0f, density);
record->waterLinearDrag = SDL_max(0.0f, linearDrag);
record->waterAngularDrag = SDL_max(0.0f, angularDrag);
return true;
}
bool jointDelete(int32_t joint) { bool jointDelete(int32_t joint) {
JointRecordT *record; JointRecordT *record;
@ -2621,8 +2658,6 @@ bool jointValid(int32_t joint) {
} }
// ===== World =====
bool physicsAvailable(void) { bool physicsAvailable(void) {
return _world != nullptr; return _world != nullptr;
} }
@ -2914,6 +2949,13 @@ bool playerIsOnGround(int32_t node) {
} }
bool playerIsSwimming(int32_t node) {
PlayerRecordT *record = _findPlayer(node);
return (record != nullptr) && record->swimming;
}
// Asks for a jump at the next step; only granted with ground underfoot. // Asks for a jump at the next step; only granted with ground underfoot.
bool playerJump(int32_t node, float speed) { bool playerJump(int32_t node, float speed) {
PlayerRecordT *record = _findPlayer(node); PlayerRecordT *record = _findPlayer(node);
@ -3121,322 +3163,6 @@ bool playerSetStep(int32_t node, float height) {
} }
bool playerSetVelocity(int32_t node, Vec3T velocity) {
PlayerRecordT *record = _findPlayer(node);
if (record == nullptr) {
return false;
}
record->character->SetLinearVelocity(JPH::Vec3(velocity.x, velocity.y, velocity.z));
record->intent = vec3(0.0f, 0.0f, 0.0f);
return true;
}
// A wheel at the wheel node's position relative to the chassis; returns its index.
int32_t vehicleAddWheel(int32_t node, int32_t wheelNode, float radius, float width, float suspension) {
VehicleRecordT *record = _findVehicle(node);
WheelRecordT *wheel;
Vec3T chassisPosition;
QuatT chassisRotation;
Vec3T chassisScale;
if ((record == nullptr) || !nodeValid(wheelNode) || (record->wheelCount >= MAX_WHEELS)) {
return -1;
}
// The attachment point is the wheel node's place in the chassis' frame right now (the body's
// frame is unscaled: the chassis' scale is baked into its shape); the engine poses the node
// with suspension travel from here on, so a later rebuild must not read it back.
sceneUpdateTransforms();
nodeGetWorldTransform(node, &chassisPosition, &chassisRotation, &chassisScale);
wheel = &record->wheels[record->wheelCount];
wheel->node = wheelNode;
wheel->generation = nodeGetGeneration(wheelNode);
wheel->rest = quatRotate(quatInverse(chassisRotation), vec3Subtract(nodeGetWorldPosition(wheelNode), chassisPosition));
wheel->radius = SDL_max(radius, MIN_DIMENSION);
wheel->width = SDL_max(width, MIN_DIMENSION);
wheel->suspension = SDL_max(suspension, MIN_DIMENSION);
wheel->steered = false;
wheel->driven = true;
wheel->steeredSet = false;
record->dirty = true;
return record->wheelCount++;
}
bool vehicleDelete(int32_t node) {
VehicleRecordT *record = _findVehicle(node);
if (record == nullptr) {
return false;
}
_releaseVehicle(record);
return true;
}
bool vehicleDrive(int32_t node, float forward, float right, float brake, float handBrake) {
VehicleRecordT *record = _findVehicle(node);
if (record == nullptr) {
return false;
}
record->inputForward = SDL_clamp(forward, -1.0f, 1.0f);
record->inputRight = SDL_clamp(right, -1.0f, 1.0f);
record->inputBrake = SDL_clamp(brake, 0.0f, 1.0f);
record->inputHandBrake = SDL_clamp(handBrake, 0.0f, 1.0f);
return true;
}
bool vehicleExists(int32_t node) {
return _findVehicle(node) != nullptr;
}
int32_t vehicleGetGear(int32_t node) {
VehicleRecordT *record = _findVehicle(node);
if ((record == nullptr) || (record->constraint == nullptr)) {
return 0;
}
if (record->kind == VEHICLE_TANK) {
return static_cast<JPH::TrackedVehicleController *>(record->constraint->GetController())->GetTransmission().GetCurrentGear();
}
return static_cast<JPH::WheeledVehicleController *>(record->constraint->GetController())->GetTransmission().GetCurrentGear();
}
float vehicleGetRpm(int32_t node) {
VehicleRecordT *record = _findVehicle(node);
if ((record == nullptr) || (record->constraint == nullptr)) {
return 0.0f;
}
if (record->kind == VEHICLE_TANK) {
return static_cast<JPH::TrackedVehicleController *>(record->constraint->GetController())->GetEngine().GetCurrentRPM();
}
return static_cast<JPH::WheeledVehicleController *>(record->constraint->GetController())->GetEngine().GetCurrentRPM();
}
// Metres a second along the chassis' nose, negative in reverse.
float vehicleGetSpeed(int32_t node) {
VehicleRecordT *record = _findVehicle(node);
BodyRecordT *body;
JPH::Vec3 velocity;
JPH::Quat rotation;
if ((record == nullptr) || ((body = _find(record->node)) == nullptr)) {
return 0.0f;
}
velocity = _world->system->GetBodyInterface().GetLinearVelocity(body->id);
rotation = _world->system->GetBodyInterface().GetRotation(body->id);
return velocity.Dot(rotation * JPH::Vec3(0.0f, 0.0f, -1.0f));
}
// Longitudinal slip of a wheel, 0 gripping to about 1 spinning or locked.
float vehicleGetWheelSlip(int32_t node, int32_t index) {
VehicleRecordT *record = _findVehicle(node);
if ((record == nullptr) || (record->constraint == nullptr) || (index < 0) || (index >= record->wheelCount) || (record->kind == VEHICLE_TANK)) {
return 0.0f;
}
return SDL_min(fabsf(static_cast<const JPH::WheelWV *>(record->constraint->GetWheel((JPH::uint)index))->mLongitudinalSlip), 1.0f);
}
bool vehicleIsWheelOnGround(int32_t node, int32_t index) {
VehicleRecordT *record = _findVehicle(node);
if ((record == nullptr) || (record->constraint == nullptr) || (index < 0) || (index >= record->wheelCount)) {
return false;
}
return record->constraint->GetWheel((JPH::uint)index)->HasContact();
}
// A vehicle on the node, whose dynamic body is the chassis; add wheels before driving it.
bool vehicleNew(int32_t node, VehicleKindE kind) {
VehicleRecordT *record = nullptr;
BodyRecordT *body = _find(node);
int32_t x;
if ((_world == nullptr) || (body == nullptr) || (body->type != BODY_DYNAMIC)) {
utilTrace("Physics: a vehicle needs a dynamic body on node %d first.", node);
return false;
}
if (_world->planar && (kind != VEHICLE_BOAT)) {
utilTrace("Physics: vehicles need a 3D world; a 2D car is a body with hinged wheels.");
return false;
}
vehicleDelete(node);
for (x = 0; x < _world->vehicleCount; x++) {
if (!_world->vehicles[x].used) {
record = &_world->vehicles[x];
break;
}
}
if (record == nullptr) {
utilTrace("Physics: no room for another vehicle (%d already).", MAX_VEHICLES);
return false;
}
_resetVehicle(record);
record->node = node;
record->generation = nodeGetGeneration(node);
record->kind = kind;
record->dirty = (kind != VEHICLE_BOAT);
record->used = true;
_indexSet(_world->vehicleOfNode, node, x);
return true;
}
bool vehicleSetAntiRoll(int32_t node, float stiffness) {
VehicleRecordT *record = _findVehicle(node);
if (record == nullptr) {
return false;
}
record->antiRoll = SDL_max(0.0f, stiffness);
record->dirty = true;
return true;
}
bool vehicleSetBrakes(int32_t node, float brake, float handBrake) {
VehicleRecordT *record = _findVehicle(node);
if (record == nullptr) {
return false;
}
record->brakeTorque = SDL_max(0.0f, brake);
record->handBrakeTorque = SDL_max(0.0f, handBrake);
record->dirty = true;
return true;
}
bool vehicleSetEngine(int32_t node, float maxTorque, float maxRpm, float minRpm) {
VehicleRecordT *record = _findVehicle(node);
if (record == nullptr) {
return false;
}
record->maxTorque = SDL_max(MIN_ENGINE_TORQUE, maxTorque);
record->maxRpm = SDL_max(MIN_ENGINE_MAX_RPM, maxRpm);
record->minRpm = SDL_clamp(minRpm, MIN_ENGINE_RPM, record->maxRpm);
record->dirty = true;
return true;
}
bool vehicleSetGears(int32_t node, const float *ratios, int32_t count, float reverse, bool automatic) {
VehicleRecordT *record = _findVehicle(node);
int32_t x;
if ((record == nullptr) || (count < 1) || (count > VEHICLE_MAX_GEARS)) {
return false;
}
for (x = 0; x < count; x++) {
record->gears[x] = ratios[x];
}
record->gearCount = count;
record->reverseGear = (reverse > 0.0f) ? -reverse : reverse;
record->automatic = automatic;
record->dirty = true;
return true;
}
bool vehicleSetSteering(int32_t node, float maxDegrees) {
VehicleRecordT *record = _findVehicle(node);
if (record == nullptr) {
return false;
}
record->maxSteer = SDL_clamp(maxDegrees, 0.0f, MAX_STEER_DEGREES);
record->dirty = true;
return true;
}
bool vehicleSetSuspension(int32_t node, float frequency, float damping) {
VehicleRecordT *record = _findVehicle(node);
if (record == nullptr) {
return false;
}
record->suspensionHz = SDL_max(MIN_SUSPENSION_HZ, frequency);
record->suspensionDamping = SDL_max(0.0f, damping);
record->dirty = true;
return true;
}
bool vehicleSetWheel(int32_t node, int32_t index, bool steered, bool driven) {
VehicleRecordT *record = _findVehicle(node);
if ((record == nullptr) || (index < 0) || (index >= record->wheelCount)) {
return false;
}
record->wheels[index].steered = steered;
record->wheels[index].driven = driven;
record->wheels[index].steeredSet = true;
record->dirty = true;
return true;
}
bool bodySetBuoyancy(int32_t node, float factor) {
BodyRecordT *record = _find(node);
if (record == nullptr) {
return false;
}
record->buoyancy = SDL_max(0.0f, factor);
return true;
}
bool bodySetCurrent(int32_t node, Vec3T flow) {
BodyRecordT *record = _find(node);
if (record == nullptr) {
return false;
}
record->current = flow;
return true;
}
// Fills a static trigger with water: bodies inside float, sink and drift; players swim.
bool bodySetWater(int32_t node, float density, float linearDrag, float angularDrag) {
BodyRecordT *record = _find(node);
if ((record == nullptr) || (record->type != BODY_STATIC)) {
utilTrace("Physics: water needs a static body on node %d.", node);
return false;
}
if (!record->trigger) {
bodySetTrigger(node, true);
}
record->water = true;
record->waterDensity = SDL_max(0.0f, density);
record->waterLinearDrag = SDL_max(0.0f, linearDrag);
record->waterAngularDrag = SDL_max(0.0f, angularDrag);
return true;
}
bool playerIsSwimming(int32_t node) {
PlayerRecordT *record = _findPlayer(node);
return (record != nullptr) && record->swimming;
}
bool playerSetSwim(int32_t node, float sinkSpeed, float drag) { bool playerSetSwim(int32_t node, float sinkSpeed, float drag) {
PlayerRecordT *record = _findPlayer(node); PlayerRecordT *record = _findPlayer(node);
@ -3449,25 +3175,14 @@ bool playerSetSwim(int32_t node, float sinkSpeed, float drag) {
} }
bool vehicleSetRudder(int32_t node, float maxTorque) { bool playerSetVelocity(int32_t node, Vec3T velocity) {
VehicleRecordT *record = _findVehicle(node); PlayerRecordT *record = _findPlayer(node);
if (record == nullptr) { if (record == nullptr) {
return false; return false;
} }
record->rudder = SDL_max(0.0f, maxTorque); record->character->SetLinearVelocity(JPH::Vec3(velocity.x, velocity.y, velocity.z));
return true; record->intent = vec3(0.0f, 0.0f, 0.0f);
}
bool vehicleSetThrust(int32_t node, float maxForce, Vec3T point) {
VehicleRecordT *record = _findVehicle(node);
if (record == nullptr) {
return false;
}
record->thrust = SDL_max(0.0f, maxForce);
record->thrustPoint = point;
return true; return true;
} }
@ -4132,3 +3847,281 @@ bool softUnpin(int32_t node, Vec3T point) {
return false; return false;
} }
// A wheel at the wheel node's position relative to the chassis; returns its index.
int32_t vehicleAddWheel(int32_t node, int32_t wheelNode, float radius, float width, float suspension) {
VehicleRecordT *record = _findVehicle(node);
WheelRecordT *wheel;
Vec3T chassisPosition;
QuatT chassisRotation;
Vec3T chassisScale;
if ((record == nullptr) || !nodeValid(wheelNode) || (record->wheelCount >= MAX_WHEELS)) {
return -1;
}
// The attachment point is the wheel node's place in the chassis' frame right now (the body's
// frame is unscaled: the chassis' scale is baked into its shape); the engine poses the node
// with suspension travel from here on, so a later rebuild must not read it back.
sceneUpdateTransforms();
nodeGetWorldTransform(node, &chassisPosition, &chassisRotation, &chassisScale);
wheel = &record->wheels[record->wheelCount];
wheel->node = wheelNode;
wheel->generation = nodeGetGeneration(wheelNode);
wheel->rest = quatRotate(quatInverse(chassisRotation), vec3Subtract(nodeGetWorldPosition(wheelNode), chassisPosition));
wheel->radius = SDL_max(radius, MIN_DIMENSION);
wheel->width = SDL_max(width, MIN_DIMENSION);
wheel->suspension = SDL_max(suspension, MIN_DIMENSION);
wheel->steered = false;
wheel->driven = true;
wheel->steeredSet = false;
record->dirty = true;
return record->wheelCount++;
}
bool vehicleDelete(int32_t node) {
VehicleRecordT *record = _findVehicle(node);
if (record == nullptr) {
return false;
}
_releaseVehicle(record);
return true;
}
bool vehicleDrive(int32_t node, float forward, float right, float brake, float handBrake) {
VehicleRecordT *record = _findVehicle(node);
if (record == nullptr) {
return false;
}
record->inputForward = SDL_clamp(forward, -1.0f, 1.0f);
record->inputRight = SDL_clamp(right, -1.0f, 1.0f);
record->inputBrake = SDL_clamp(brake, 0.0f, 1.0f);
record->inputHandBrake = SDL_clamp(handBrake, 0.0f, 1.0f);
return true;
}
bool vehicleExists(int32_t node) {
return _findVehicle(node) != nullptr;
}
int32_t vehicleGetGear(int32_t node) {
VehicleRecordT *record = _findVehicle(node);
if ((record == nullptr) || (record->constraint == nullptr)) {
return 0;
}
if (record->kind == VEHICLE_TANK) {
return static_cast<JPH::TrackedVehicleController *>(record->constraint->GetController())->GetTransmission().GetCurrentGear();
}
return static_cast<JPH::WheeledVehicleController *>(record->constraint->GetController())->GetTransmission().GetCurrentGear();
}
float vehicleGetRpm(int32_t node) {
VehicleRecordT *record = _findVehicle(node);
if ((record == nullptr) || (record->constraint == nullptr)) {
return 0.0f;
}
if (record->kind == VEHICLE_TANK) {
return static_cast<JPH::TrackedVehicleController *>(record->constraint->GetController())->GetEngine().GetCurrentRPM();
}
return static_cast<JPH::WheeledVehicleController *>(record->constraint->GetController())->GetEngine().GetCurrentRPM();
}
// Metres a second along the chassis' nose, negative in reverse.
float vehicleGetSpeed(int32_t node) {
VehicleRecordT *record = _findVehicle(node);
BodyRecordT *body;
JPH::Vec3 velocity;
JPH::Quat rotation;
if ((record == nullptr) || ((body = _find(record->node)) == nullptr)) {
return 0.0f;
}
velocity = _world->system->GetBodyInterface().GetLinearVelocity(body->id);
rotation = _world->system->GetBodyInterface().GetRotation(body->id);
return velocity.Dot(rotation * JPH::Vec3(0.0f, 0.0f, -1.0f));
}
// Longitudinal slip of a wheel, 0 gripping to about 1 spinning or locked.
float vehicleGetWheelSlip(int32_t node, int32_t index) {
VehicleRecordT *record = _findVehicle(node);
if ((record == nullptr) || (record->constraint == nullptr) || (index < 0) || (index >= record->wheelCount) || (record->kind == VEHICLE_TANK)) {
return 0.0f;
}
return SDL_min(fabsf(static_cast<const JPH::WheelWV *>(record->constraint->GetWheel((JPH::uint)index))->mLongitudinalSlip), 1.0f);
}
bool vehicleIsWheelOnGround(int32_t node, int32_t index) {
VehicleRecordT *record = _findVehicle(node);
if ((record == nullptr) || (record->constraint == nullptr) || (index < 0) || (index >= record->wheelCount)) {
return false;
}
return record->constraint->GetWheel((JPH::uint)index)->HasContact();
}
// A vehicle on the node, whose dynamic body is the chassis; add wheels before driving it.
bool vehicleNew(int32_t node, VehicleKindE kind) {
VehicleRecordT *record = nullptr;
BodyRecordT *body = _find(node);
int32_t x;
if ((_world == nullptr) || (body == nullptr) || (body->type != BODY_DYNAMIC)) {
utilTrace("Physics: a vehicle needs a dynamic body on node %d first.", node);
return false;
}
if (_world->planar && (kind != VEHICLE_BOAT)) {
utilTrace("Physics: vehicles need a 3D world; a 2D car is a body with hinged wheels.");
return false;
}
vehicleDelete(node);
for (x = 0; x < _world->vehicleCount; x++) {
if (!_world->vehicles[x].used) {
record = &_world->vehicles[x];
break;
}
}
if (record == nullptr) {
utilTrace("Physics: no room for another vehicle (%d already).", MAX_VEHICLES);
return false;
}
_resetVehicle(record);
record->node = node;
record->generation = nodeGetGeneration(node);
record->kind = kind;
record->dirty = (kind != VEHICLE_BOAT);
record->used = true;
_indexSet(_world->vehicleOfNode, node, x);
return true;
}
bool vehicleSetAntiRoll(int32_t node, float stiffness) {
VehicleRecordT *record = _findVehicle(node);
if (record == nullptr) {
return false;
}
record->antiRoll = SDL_max(0.0f, stiffness);
record->dirty = true;
return true;
}
bool vehicleSetBrakes(int32_t node, float brake, float handBrake) {
VehicleRecordT *record = _findVehicle(node);
if (record == nullptr) {
return false;
}
record->brakeTorque = SDL_max(0.0f, brake);
record->handBrakeTorque = SDL_max(0.0f, handBrake);
record->dirty = true;
return true;
}
bool vehicleSetEngine(int32_t node, float maxTorque, float maxRpm, float minRpm) {
VehicleRecordT *record = _findVehicle(node);
if (record == nullptr) {
return false;
}
record->maxTorque = SDL_max(MIN_ENGINE_TORQUE, maxTorque);
record->maxRpm = SDL_max(MIN_ENGINE_MAX_RPM, maxRpm);
record->minRpm = SDL_clamp(minRpm, MIN_ENGINE_RPM, record->maxRpm);
record->dirty = true;
return true;
}
bool vehicleSetGears(int32_t node, const float *ratios, int32_t count, float reverse, bool automatic) {
VehicleRecordT *record = _findVehicle(node);
int32_t x;
if ((record == nullptr) || (count < 1) || (count > VEHICLE_MAX_GEARS)) {
return false;
}
for (x = 0; x < count; x++) {
record->gears[x] = ratios[x];
}
record->gearCount = count;
record->reverseGear = (reverse > 0.0f) ? -reverse : reverse;
record->automatic = automatic;
record->dirty = true;
return true;
}
bool vehicleSetRudder(int32_t node, float maxTorque) {
VehicleRecordT *record = _findVehicle(node);
if (record == nullptr) {
return false;
}
record->rudder = SDL_max(0.0f, maxTorque);
return true;
}
bool vehicleSetSteering(int32_t node, float maxDegrees) {
VehicleRecordT *record = _findVehicle(node);
if (record == nullptr) {
return false;
}
record->maxSteer = SDL_clamp(maxDegrees, 0.0f, MAX_STEER_DEGREES);
record->dirty = true;
return true;
}
bool vehicleSetSuspension(int32_t node, float frequency, float damping) {
VehicleRecordT *record = _findVehicle(node);
if (record == nullptr) {
return false;
}
record->suspensionHz = SDL_max(MIN_SUSPENSION_HZ, frequency);
record->suspensionDamping = SDL_max(0.0f, damping);
record->dirty = true;
return true;
}
bool vehicleSetThrust(int32_t node, float maxForce, Vec3T point) {
VehicleRecordT *record = _findVehicle(node);
if (record == nullptr) {
return false;
}
record->thrust = SDL_max(0.0f, maxForce);
record->thrustPoint = point;
return true;
}
bool vehicleSetWheel(int32_t node, int32_t index, bool steered, bool driven) {
VehicleRecordT *record = _findVehicle(node);
if ((record == nullptr) || (index < 0) || (index >= record->wheelCount)) {
return false;
}
record->wheels[index].steered = steered;
record->wheels[index].driven = driven;
record->wheels[index].steeredSet = true;
record->dirty = true;
return true;
}

File diff suppressed because it is too large Load diff

View file

@ -60,11 +60,11 @@ typedef enum MaterialFilterE {
FILTER_NEAREST = 1 FILTER_NEAREST = 1
} MaterialFilterE; } MaterialFilterE;
// How a node turns to face the camera. // How a node turns to face the camera (the codes are the shader's, sceneShared.h).
typedef enum BillboardE { typedef enum BillboardE {
BILLBOARD_NONE = 0, BILLBOARD_NONE = BILLBOARD_MODE_NONE,
BILLBOARD_ALL = 1, // Faces the camera squarely BILLBOARD_ALL = BILLBOARD_MODE_ALL, // Faces the camera squarely
BILLBOARD_Y = 2 // Turns about its own up axis only (trees, health bars) BILLBOARD_Y = BILLBOARD_MODE_Y // Turns about world up only (trees, health bars)
} BillboardE; } BillboardE;
// A material's textures, for the compressed path. // A material's textures, for the compressed path.

65
src/sceneShared.h Normal file
View file

@ -0,0 +1,65 @@
/*
*
* Singe 3
* Copyright (C) 2006-2026 Scott Duensing <scott@kangaroopunch.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 3
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*
*/
#ifndef SCENE_SHARED_H
#define SCENE_SHARED_H
// The limits and codes the scene's uniform blocks are laid out with, shared by the C side
// (scene.h, scene.c) and the shaders (shaders/scene.hlsl), so the two cannot drift apart.
// Preprocessor definitions only: this file is read by DXC as well as the C compiler.
// Sizes of the uniform arrays.
#define MAX_LIGHTS 8 // Lights the fragment shader sees per frame
#define MAX_JOINTS 128 // Joint matrices per skinned draw
#define MAX_SHADOWS MAX_LIGHTS // Every light may cast; the arrays are sized to the lights in use
#define MAX_MORPHS 8 // Active morph targets per draw
#define MAX_CASCADES 4 // Cascades of a directional light's shadow
#define SH_COEFFICIENTS 9 // Spherical harmonics to second order
// A shadow slot's kind (shadowInfo[slot].x).
#define SHADOW_NONE 0
#define SHADOW_MAP 1 // One map: a spot light, or a directional light without cascades
#define SHADOW_CUBE 2 // Six faces: a point light
#define SHADOW_CASCADE 3 // A directional light's shadow split along the camera's view
// A light's kind (positionType.w).
#define LIGHT_TYPE_DIRECTIONAL 0
#define LIGHT_TYPE_POINT 1
#define LIGHT_TYPE_SPOT 2
// The post pass's tone curve (postParams.y).
#define TONEMAP_CURVE_NONE 0
#define TONEMAP_CURVE_NEUTRAL 1
#define TONEMAP_CURVE_ACES 2
// What material.w says about the base texture.
#define TEXTURE_NONE 0
#define TEXTURE_SRGB 1 // A texture the sampler decodes to linear
#define TEXTURE_FEED 2 // A video frame or rendered view, sRGB, decoded in the shader
// How a draw turns to face the camera (the spare lane of its normal matrix, instanceMatrices).
#define BILLBOARD_MODE_NONE 0
#define BILLBOARD_MODE_ALL 1 // Its axes are the camera's
#define BILLBOARD_MODE_Y 2 // Turned about world +Y toward the eye only
#endif

View file

@ -40,6 +40,7 @@
#define PI 3.14159265 #define PI 3.14159265
#define DEGREES_TO_RADIANS 0.017453292 #define DEGREES_TO_RADIANS 0.017453292
#define MIN_ROUGHNESS 0.045 // Below this the GGX lobe is narrower than a pixel and sparkles #define MIN_ROUGHNESS 0.045 // Below this the GGX lobe is narrower than a pixel and sparkles
#define BILLBOARD_EPSILON 0.000001 // Below this a BILLBOARD_MODE_Y node is under the eye and faces +Z
// ----- Vertex ----- // ----- Vertex -----
@ -49,6 +50,9 @@ cbuffer DrawUniforms : register(b0, space1) {
float4 morphWeights[2]; // Up to MAX_MORPHS active targets ... float4 morphWeights[2]; // Up to MAX_MORPHS active targets ...
int4 morphTargets[2]; // ... and which targets they are int4 morphTargets[2]; // ... and which targets they are
int4 morphInfo; // x = active count, y = vertices per target, z = the draw's first pair in instanceMatrices int4 morphInfo; // x = active count, y = vertices per target, z = the draw's first pair in instanceMatrices
float4 billboardRight; // The axes and eye billboards turn to: the pass's camera (a shadow pass gives the window's)
float4 billboardUp;
float4 billboardEye;
}; };
@ -56,7 +60,8 @@ cbuffer DrawUniforms : register(b0, space1) {
StructuredBuffer<float4> morphDeltas : register(t0, space0); StructuredBuffer<float4> morphDeltas : register(t0, space0);
// Per draw, in draw order: the model matrix then its inverse transpose (for normals under // Per draw, in draw order: the model matrix then its inverse transpose (for normals under
// non-uniform scale); an instanced batch reads consecutive pairs. // non-uniform scale); an instanced batch reads consecutive pairs. The inverse transpose's spare
// lane (_m33) holds the draw's BILLBOARD_MODE_*; a billboard's pair is rebuilt here per camera.
StructuredBuffer<float4x4> instanceMatrices : register(t1, space0); StructuredBuffer<float4x4> instanceMatrices : register(t1, space0);
cbuffer SkinUniforms : register(b1, space1) { cbuffer SkinUniforms : register(b1, space1) {
@ -102,6 +107,44 @@ void morph(uint vertex, inout float3 position, inout float3 normal) {
} }
// Turns a billboard's model matrix (and its normal matrix) to this pass's camera: squarely, its
// axes the camera's, or about world +Y only toward the eye. The node's own position and scale
// stay; the normal matrix of an orthonormal frame under scale is the frame over the scale.
void billboard(inout float4x4 model, inout float4x4 normalMatrix) {
int mode = (int)normalMatrix._m33;
float3 position = model._m03_m13_m23;
float3 scale = float3(length(model._m00_m10_m20), length(model._m01_m11_m21), length(model._m02_m12_m22));
float3 right;
float3 up;
float3 toward;
if (mode == BILLBOARD_MODE_NONE) {
return;
}
if (mode == BILLBOARD_MODE_ALL) {
right = billboardRight.xyz;
up = billboardUp.xyz;
toward = cross(right, up);
} else {
toward = billboardEye.xyz - position;
toward.y = 0.0;
if (length(toward) < BILLBOARD_EPSILON) {
toward = float3(0.0, 0.0, 1.0);
}
toward = normalize(toward);
up = float3(0.0, 1.0, 0.0);
right = cross(up, toward);
}
scale = max(scale, BILLBOARD_EPSILON);
model._m00_m10_m20 = right * scale.x;
model._m01_m11_m21 = up * scale.y;
model._m02_m12_m22 = toward * scale.z;
normalMatrix._m00_m10_m20 = right / scale.x;
normalMatrix._m01_m11_m21 = up / scale.y;
normalMatrix._m02_m12_m22 = toward / scale.z;
}
VertexOutput vertexStatic(VertexInput input, uint vertex : SV_VertexID, uint instance : SV_InstanceID) { VertexOutput vertexStatic(VertexInput input, uint vertex : SV_VertexID, uint instance : SV_InstanceID) {
VertexOutput output; VertexOutput output;
float3 position = input.position; float3 position = input.position;
@ -109,6 +152,7 @@ VertexOutput vertexStatic(VertexInput input, uint vertex : SV_VertexID, uint ins
float4x4 model = instanceMatrices[(morphInfo.z + instance) * 2]; float4x4 model = instanceMatrices[(morphInfo.z + instance) * 2];
float4x4 normalMatrix = instanceMatrices[(morphInfo.z + instance) * 2 + 1]; float4x4 normalMatrix = instanceMatrices[(morphInfo.z + instance) * 2 + 1];
billboard(model, normalMatrix);
morph(vertex, position, normal); morph(vertex, position, normal);
output.worldPosition = mul(model, float4(position, 1.0)).xyz; output.worldPosition = mul(model, float4(position, 1.0)).xyz;
output.position = mul(viewProjection, float4(output.worldPosition, 1.0)); output.position = mul(viewProjection, float4(output.worldPosition, 1.0));
@ -130,6 +174,7 @@ VertexOutput vertexSkinned(VertexInput input, uint vertex : SV_VertexID, uint in
float3 skinnedNormal; float3 skinnedNormal;
float3 skinnedTangent; float3 skinnedTangent;
billboard(model, normalMatrix);
morph(vertex, morphed, normal); morph(vertex, morphed, normal);
position = float4(morphed, 1.0); position = float4(morphed, 1.0);

File diff suppressed because it is too large Load diff

View file

@ -104,8 +104,8 @@ struct VfsStreamS {
}; };
static bool _assetExists(DatabaseT *db, const char *key);
static bool _assetDirectory(DatabaseT *db, const char *key); static bool _assetDirectory(DatabaseT *db, const char *key);
static bool _assetExists(DatabaseT *db, const char *key);
static bool _assetSize(DatabaseT *db, const char *key, int64_t *size); static bool _assetSize(DatabaseT *db, const char *key, int64_t *size);
static char *_bindListRange(DatabaseT *db, const char *key); static char *_bindListRange(DatabaseT *db, const char *key);
static bool _cacheCurrent(const TargetT *target); static bool _cacheCurrent(const TargetT *target);
@ -115,12 +115,12 @@ static void _databasesClose(void);
static bool _fileModified(const char *path, int64_t *size, int64_t *modified); static bool _fileModified(const char *path, int64_t *size, int64_t *modified);
static bool _hasDatabaseExtension(const char *path); static bool _hasDatabaseExtension(const char *path);
static bool _hasParentComponent(const char *norm); static bool _hasParentComponent(const char *norm);
static bool _isAbsolute(const char *name);
static bool _isEngineName(const char *name);
static void _listAdd(ListT *list, const char *name); static void _listAdd(ListT *list, const char *name);
static int _listCompare(const void *a, const void *b); // qsort callback. Not changing int. static int _listCompare(const void *a, const void *b); // qsort callback. Not changing int.
static void _listDirectory(const char *path, ListT *list); static void _listDirectory(const char *path, ListT *list);
static char **_listFinish(ListT *list, int32_t *count); static char **_listFinish(ListT *list, int32_t *count);
static bool _isAbsolute(const char *name);
static bool _isEngineName(const char *name);
static char *_normalise(const char *name); static char *_normalise(const char *name);
static char *_overlayFor(const char *dataDirBase, const char *dataDir, const char *databasePath, bool isContainer, const char *directory); static char *_overlayFor(const char *dataDirBase, const char *dataDir, const char *databasePath, bool isContainer, const char *directory);
static uint8_t *_readAsset(DatabaseT *db, const char *key, size_t *bytes, bool sdlMemory); static uint8_t *_readAsset(DatabaseT *db, const char *key, size_t *bytes, bool sdlMemory);
@ -137,13 +137,6 @@ static char *_dataDir = NULL;
static char *_dataDirKey = NULL; // _dataDirBase normalised without trailing slashes, for _isEngineName static char *_dataDirKey = NULL; // _dataDirBase normalised without trailing slashes, for _isEngineName
static bool _assetExists(DatabaseT *db, const char *key) {
int64_t size = 0;
return _assetSize(db, key, &size);
}
// True when any asset lives below the key, which is what a directory is in a database. // True when any asset lives below the key, which is what a directory is in a database.
static bool _assetDirectory(DatabaseT *db, const char *key) { static bool _assetDirectory(DatabaseT *db, const char *key) {
char *from = _bindListRange(db, key); char *from = _bindListRange(db, key);
@ -156,6 +149,13 @@ static bool _assetDirectory(DatabaseT *db, const char *key) {
} }
static bool _assetExists(DatabaseT *db, const char *key) {
int64_t size = 0;
return _assetSize(db, key, &size);
}
static bool _assetSize(DatabaseT *db, const char *key, int64_t *size) { static bool _assetSize(DatabaseT *db, const char *key, int64_t *size) {
bool found = false; bool found = false;

View file

@ -1484,12 +1484,6 @@ bool videoIsPlaying(int32_t playerHandle) {
} }
// The mixer's audio thread runs the sound and video callbacks; hold this to read what they write.
void videoLockAudio(void) {
MIX_LockMixer(_mixer);
}
// audioFilename may be NULL when the audio lives in the video file. rgb players decode to BGRA so // audioFilename may be NULL when the audio lives in the video file. rgb players decode to BGRA so
// scripts can read the pixels; everything else stays YUV and is converted by the GPU. // scripts can read the pixels; everything else stays YUV and is converted by the GPU.
int32_t videoLoad(const char *videoFilename, const char *audioFilename, const char *indexPath, SDL_Renderer *renderer, bool rgb) { int32_t videoLoad(const char *videoFilename, const char *audioFilename, const char *indexPath, SDL_Renderer *renderer, bool rgb) {
@ -1572,6 +1566,12 @@ int32_t videoLoad(const char *videoFilename, const char *audioFilename, const ch
} }
// The mixer's audio thread runs the sound and video callbacks; hold this to read what they write.
void videoLockAudio(void) {
MIX_LockMixer(_mixer);
}
void videoPause(int32_t playerHandle) { void videoPause(int32_t playerHandle) {
VideoPlayerT *v = _getPlayer(playerHandle, "videoPause"); VideoPlayerT *v = _getPlayer(playerHandle, "videoPause");
@ -1664,11 +1664,6 @@ void videoSetVolume(int32_t playerHandle, int32_t leftPercent, int32_t rightPerc
} }
void videoUnlockAudio(void) {
MIX_UnlockMixer(_mixer);
}
void videoUnload(int32_t playerHandle) { void videoUnload(int32_t playerHandle) {
VideoPlayerT *v = _getPlayer(playerHandle, "videoUnload"); VideoPlayerT *v = _getPlayer(playerHandle, "videoUnload");
int32_t x = 0; int32_t x = 0;
@ -1717,6 +1712,11 @@ void videoUnload(int32_t playerHandle) {
} }
void videoUnlockAudio(void) {
MIX_UnlockMixer(_mixer);
}
// Advances playback to match the audio clock (or the wall clock for silent videos). Returns the frame now on the texture. // Advances playback to match the audio clock (or the wall clock for silent videos). Returns the frame now on the texture.
int64_t videoUpdate(int32_t playerHandle, SDL_Texture **texture) { int64_t videoUpdate(int32_t playerHandle, SDL_Texture **texture) {
VideoPlayerT *v = _getPlayer(playerHandle, "videoUpdate"); VideoPlayerT *v = _getPlayer(playerHandle, "videoUpdate");

View file

@ -52,8 +52,8 @@ void videoGetVolume(int32_t playerHandle, int32_t *leftPercent, int32_t *
int32_t videoGetWidth(int32_t playerHandle); int32_t videoGetWidth(int32_t playerHandle);
void videoInit(MIX_Mixer *mixer); void videoInit(MIX_Mixer *mixer);
bool videoIsPlaying(int32_t playerHandle); bool videoIsPlaying(int32_t playerHandle);
void videoLockAudio(void);
int32_t videoLoad(const char *videoFilename, const char *audioFilename, const char *indexPath, SDL_Renderer *renderer, bool rgb); int32_t videoLoad(const char *videoFilename, const char *audioFilename, const char *indexPath, SDL_Renderer *renderer, bool rgb);
void videoLockAudio(void);
void videoPause(int32_t playerHandle); void videoPause(int32_t playerHandle);
void videoPlay(int32_t playerHandle); void videoPlay(int32_t playerHandle);
void videoQuit(void); void videoQuit(void);
@ -63,8 +63,8 @@ void videoSetAudioDelay(int32_t milliseconds);
void videoSetAudioTrack(int32_t playerHandle, int32_t track); void videoSetAudioTrack(int32_t playerHandle, int32_t track);
void videoSetHardwareDecoding(bool enabled); void videoSetHardwareDecoding(bool enabled);
void videoSetVolume(int32_t playerHandle, int32_t leftPercent, int32_t rightPercent); void videoSetVolume(int32_t playerHandle, int32_t leftPercent, int32_t rightPercent);
void videoUnlockAudio(void);
void videoUnload(int32_t playerHandle); void videoUnload(int32_t playerHandle);
void videoUnlockAudio(void);
int64_t videoUpdate(int32_t playerHandle, SDL_Texture **texture); int64_t videoUpdate(int32_t playerHandle, SDL_Texture **texture);