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.
viewNew renders a second camera to a texture every frame (viewSetCamera
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
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,
`viewSetCamera` gives it a camera node, and `materialSetView` puts its
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
(see <<scenesprites,Sprites and Text in the Scene>>) faces the window's
camera, so a view sees it from the side.
each one renders the scene again, so keep them few and small. Each view
turns billboards (see <<scenesprites,Sprites and Text in the Scene>>) to
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]
----
@ -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
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
sprites and text, and follows the window's camera: a view sees the
billboard from the side.
sprites and text, and turns to whichever camera is drawing: a view sees
the billboard face on too, while its shadow follows the window's camera.
*Since:* 3.00.
*See also:* <<nodesetsprite,nodeSetSprite>>, <<nodesettext,nodeSetText>>, <<cameraset,cameraSet>>
@ -12541,7 +12542,7 @@ end
[#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

View file

@ -41,9 +41,9 @@
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 *_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 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
} Ktx2ImageT;
void ktx2Free(Ktx2ImageT *image);
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
void ktx2Free(Ktx2ImageT *image);
#endif

View file

@ -185,12 +185,12 @@ static void _crashHandler(int signalNumber);
#endif
static void _launcher(const char *exeName, ConfigT *conf);
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 ConfigT *_parseArguments(const char *exeName, int32_t argc, char *argv[]);
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 _resolveFiles(const char *exeName, ConfigT *conf);
static bool _runTool(const ConfigT *conf);
static void _showHeader(void);
static void _showUsage(const char *name, const char *message) __attribute__((noreturn));
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.
static bool _extractFile(const char *filename, const uint8_t *data, size_t length) {
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) {
int32_t x = 0;
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.
static bool _runTool(const ConfigT *conf) {
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) {
const int32_t helpColumn = 6 + USAGE_OPTION_WIDTH; // " -x, " plus the padded long form
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
void navInit(void);
int32_t navPollArrived(int32_t *agents, int32_t max); // Agents that reached their targets since last asked
void navQuit(void);
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 navBuild(int32_t nav); // Bakes what was added; may take a moment
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 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 navAgentMoveTo(int32_t agent, Vec3T target);
bool navAgentStop(int32_t agent);
int32_t navAgentGetNode(int32_t agent);
bool navAgentGetVelocity(int32_t agent, Vec3T *velocity);
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 navAgentStop(int32_t agent);
bool navAgentValid(int32_t agent);
int32_t navAgentGetNode(int32_t agent);
#ifdef __cplusplus
}

View file

@ -77,46 +77,46 @@ typedef struct EmitterViewT {
Vec3T trailOffset; // Added to trail points (a local emitter's origin)
} EmitterViewT;
void particlesClearQueue2D(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 particlesUpdate(bool advance);
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);
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);
bool emitterValid(int32_t emitter);
void emitterBurst(int32_t emitter, int32_t count);
void emitterClear(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
void emitterSetTrail(int32_t emitter, int32_t length, float width);
void emitterSetFrames(int32_t emitter, int32_t first, int32_t last);
int32_t emitterGetCount(int32_t emitter);
bool emitterIs3D(int32_t emitter);
bool emitterIsActive(int32_t emitter);
int32_t emitterNew(int32_t node);
void emitterSetBlend(int32_t emitter, ParticleBlendE blend);
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 emitterSetRate(int32_t emitter, float perSecond);
void emitterSetLife(int32_t emitter, float minSeconds, float maxSeconds);
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 emitterSetMax(int32_t emitter, int32_t count);
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 emitterStop(int32_t emitter);
void emitterBurst(int32_t emitter, int32_t count);
void emitterClear(int32_t emitter);
int32_t emitterGetCount(int32_t emitter);
bool emitterIsActive(int32_t emitter);
bool emitterIs3D(int32_t emitter);
bool emitterValid(int32_t emitter);
#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 jointValid(int32_t joint);
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 playerExists(int32_t node);
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 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 vehicleSetSteering(int32_t node, float maxDegrees);
bool vehicleSetRudder(int32_t node, float maxTorque);
bool vehicleSetSteering(int32_t node, float maxDegrees);
bool vehicleSetSuspension(int32_t node, float frequency, float damping);
bool vehicleSetThrust(int32_t node, float maxForce, Vec3T point);
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

View file

@ -2251,8 +2251,6 @@ namespace {
}
// ===== Bodies =====
// A force for this step, at the centre of mass or a world point.
bool bodyApplyForce(int32_t node, Vec3T force, const Vec3T *at) {
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.
bool bodySetEnabled(int32_t node, bool enabled) {
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) {
JointRecordT *record;
@ -2621,8 +2658,6 @@ bool jointValid(int32_t joint) {
}
// ===== World =====
bool physicsAvailable(void) {
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.
bool playerJump(int32_t node, float speed) {
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) {
PlayerRecordT *record = _findPlayer(node);
@ -3449,25 +3175,14 @@ bool playerSetSwim(int32_t node, float sinkSpeed, float drag) {
}
bool vehicleSetRudder(int32_t node, float maxTorque) {
VehicleRecordT *record = _findVehicle(node);
bool playerSetVelocity(int32_t node, Vec3T velocity) {
PlayerRecordT *record = _findPlayer(node);
if (record == nullptr) {
return false;
}
record->rudder = SDL_max(0.0f, maxTorque);
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;
record->character->SetLinearVelocity(JPH::Vec3(velocity.x, velocity.y, velocity.z));
record->intent = vec3(0.0f, 0.0f, 0.0f);
return true;
}
@ -4132,3 +3847,281 @@ bool softUnpin(int32_t node, Vec3T point) {
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
} 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 {
BILLBOARD_NONE = 0,
BILLBOARD_ALL = 1, // Faces the camera squarely
BILLBOARD_Y = 2 // Turns about its own up axis only (trees, health bars)
BILLBOARD_NONE = BILLBOARD_MODE_NONE,
BILLBOARD_ALL = BILLBOARD_MODE_ALL, // Faces the camera squarely
BILLBOARD_Y = BILLBOARD_MODE_Y // Turns about world up only (trees, health bars)
} BillboardE;
// 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 DEGREES_TO_RADIANS 0.017453292
#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 -----
@ -49,6 +50,9 @@ cbuffer DrawUniforms : register(b0, space1) {
float4 morphWeights[2]; // Up to MAX_MORPHS active targets ...
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
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);
// 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);
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 output;
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 normalMatrix = instanceMatrices[(morphInfo.z + instance) * 2 + 1];
billboard(model, normalMatrix);
morph(vertex, position, normal);
output.worldPosition = mul(model, float4(position, 1.0)).xyz;
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 skinnedTangent;
billboard(model, normalMatrix);
morph(vertex, morphed, normal);
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 _assetExists(DatabaseT *db, const char *key);
static bool _assetSize(DatabaseT *db, const char *key, int64_t *size);
static char *_bindListRange(DatabaseT *db, const char *key);
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 _hasDatabaseExtension(const char *path);
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 int _listCompare(const void *a, const void *b); // qsort callback. Not changing int.
static void _listDirectory(const char *path, ListT *list);
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 *_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);
@ -137,13 +137,6 @@ static char *_dataDir = NULL;
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.
static bool _assetDirectory(DatabaseT *db, const char *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) {
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
// 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) {
@ -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) {
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) {
VideoPlayerT *v = _getPlayer(playerHandle, "videoUnload");
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.
int64_t videoUpdate(int32_t playerHandle, SDL_Texture **texture) {
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);
void videoInit(MIX_Mixer *mixer);
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);
void videoLockAudio(void);
void videoPause(int32_t playerHandle);
void videoPlay(int32_t playerHandle);
void videoQuit(void);
@ -63,8 +63,8 @@ void videoSetAudioDelay(int32_t milliseconds);
void videoSetAudioTrack(int32_t playerHandle, int32_t track);
void videoSetHardwareDecoding(bool enabled);
void videoSetVolume(int32_t playerHandle, int32_t leftPercent, int32_t rightPercent);
void videoUnlockAudio(void);
void videoUnload(int32_t playerHandle);
void videoUnlockAudio(void);
int64_t videoUpdate(int32_t playerHandle, SDL_Texture **texture);