Skip to content

Commit 4b5cd52

Browse files
author
CubeCoders
committed
LensFlare: compile without picking; ParticleSystem: water splash emitter; Renderer: SSR sky fallback, packed span-fill optimisations
1 parent 77c4f4c commit 4b5cd52

3 files changed

Lines changed: 178 additions & 71 deletions

File tree

src/LensFlare.hpp

Lines changed: 30 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,9 @@ namespace Renderer {
1616
///
1717
/// Each frame, the helper:
1818
/// 1. Projects the light direction to a screen-space point.
19-
/// 2. Uses a single PickQuery (slot `pickSlot`) on that pixel to test
19+
/// 2. When MAX_PICK_QUERIES > 0, uses a PickQuery on that pixel to test
2020
/// whether any geometry occludes the "sun". No hit => sun visible.
21+
/// When picking is compiled out, the sun is treated as unobstructed.
2122
/// 3. Repositions a fixed set of Sprite2D elements along the line that
2223
/// passes through both the sun's screen position and the screen centre,
2324
/// and sets their alpha to reflect the current visibility (with optional
@@ -40,12 +41,11 @@ namespace Renderer {
4041
/// flare.update(dt);
4142
/// @endcode
4243
///
43-
/// @note Requires MAX_PICK_QUERIES > 0 in JetConfig.hpp.
44+
/// @note MAX_PICK_QUERIES > 0 enables occlusion testing; the flare still
45+
/// renders without occlusion when picking is compiled out.
4446
/// @note The LensFlare registers all sprites with the scene on construction;
4547
/// do not call scene->addSprite() for the same sprites separately.
4648

47-
#if MAX_PICK_QUERIES > 0
48-
4949
class LensFlare {
5050
public:
5151
/// @brief One sprite element in the flare chain.
@@ -106,7 +106,7 @@ class LensFlare {
106106
}
107107

108108
/// @brief Call once per frame BEFORE scene->render() to submit the pick
109-
/// query and compute sun screen position.
109+
/// query, when enabled, and compute sun screen position.
110110
///
111111
/// @param camera Active camera. Must match what scene will render with.
112112
/// @param screenW Render width in pixels (framebuffer coords).
@@ -125,9 +125,13 @@ class LensFlare {
125125
if (!sunInFrustum) {
126126
// Disable all sprites immediately.
127127
for (int i = 0; i < elementCount; ++i) sprites[i].enabled = false;
128+
#if MAX_PICK_QUERIES > 0
128129
// Still need to clear the pick slot so it doesn't report stale results.
129-
PickQuery q; q.x = -1; q.y = -1;
130-
scene->setPickQueries(&q, 1); // slot 0 only; see note below
130+
PickQuery queries[MAX_PICK_QUERIES] = {};
131+
const int slot = pickSlot < 0 ? 0 : (pickSlot >= MAX_PICK_QUERIES ? MAX_PICK_QUERIES - 1 : pickSlot);
132+
for (int i = 0; i <= slot; ++i) { queries[i].x = -1; queries[i].y = -1; }
133+
scene->setPickQueries(queries, slot + 1);
134+
#endif
131135
return;
132136
}
133137

@@ -146,16 +150,19 @@ class LensFlare {
146150
const int pqx = sunScreenX < 0 ? 0 : (sunScreenX >= screenW ? screenW - 1 : sunScreenX);
147151
const int pqy = sunScreenY < 0 ? 0 : (sunScreenY >= screenH ? screenH - 1 : sunScreenY);
148152

149-
// Submit pick query for the sun pixel.
150-
PickQuery q;
151-
q.x = (int16_t)pqx;
152-
q.y = (int16_t)pqy;
153-
// We only manage one slot; the caller specified which index to use.
154-
// Scene::setPickQueries replaces all queries so we must submit all
155-
// slots the caller wants active. For simplicity we take exclusive
156-
// ownership of the whole query array here — if the caller also needs
157-
// other pick queries they should subclass / extend this helper.
158-
scene->setPickQueries(&q, 1);
153+
#if MAX_PICK_QUERIES > 0
154+
// Submit pick query for the sun pixel. Scene::setPickQueries replaces
155+
// all queries, so fill disabled slots up through our selected slot.
156+
PickQuery queries[MAX_PICK_QUERIES] = {};
157+
const int slot = pickSlot < 0 ? 0 : (pickSlot >= MAX_PICK_QUERIES ? MAX_PICK_QUERIES - 1 : pickSlot);
158+
for (int i = 0; i <= slot; ++i) { queries[i].x = -1; queries[i].y = -1; }
159+
queries[slot].x = (int16_t)pqx;
160+
queries[slot].y = (int16_t)pqy;
161+
scene->setPickQueries(queries, slot + 1);
162+
#else
163+
(void)pqx;
164+
(void)pqy;
165+
#endif
159166
}
160167

161168
/// @brief Call once per frame AFTER scene->render() to read pick results,
@@ -199,10 +206,12 @@ class LensFlare {
199206
// Clamp to [0,1]: negative when sun is off-screen; sprites get alpha=0.
200207
if (edgeFade < 0.0f) edgeFade = 0.0f;
201208

202-
// Read pick result for our slot.
203-
// const PickResult* results = scene->getPickResults();
204-
// const bool occluded = results && results[0].hit;
205-
const bool occluded = false; // TEST: always visible — re-enable pick check when confirmed working
209+
bool occluded = false;
210+
#if MAX_PICK_QUERIES > 0
211+
const PickResult* results = scene->getPickResults();
212+
const int slot = pickSlot < 0 ? 0 : (pickSlot >= MAX_PICK_QUERIES ? MAX_PICK_QUERIES - 1 : pickSlot);
213+
occluded = results && scene->getPickQueryCount() > slot && results[slot].hit;
214+
#endif
206215

207216
// Fade visibility toward target.
208217
const float target = occluded ? 0.0f : 1.0f;
@@ -303,6 +312,4 @@ class LensFlare {
303312
Sprite2D sprites[MAX_FLARE_ELEMENTS];
304313
};
305314

306-
#endif // MAX_PICK_QUERIES > 0
307-
308315
} // namespace Renderer

src/ParticleSystem.hpp

Lines changed: 62 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,13 +40,21 @@ static constexpr int PARTICLE_POOL_SIZE = 200;
4040
// RGB565 colour keyframes
4141
static constexpr uint16_t SPARK_COL_WHITE = 0xFFFF;
4242
static constexpr uint16_t SPARK_COL_BLUE = 0x055F; // electric blue #00A8F8
43+
static constexpr uint16_t SPLASH_COL_FOAM = 0xEFFF;
44+
static constexpr uint16_t SPLASH_COL_BLUE = 0x5DDF;
45+
46+
enum class ParticleKind : uint8_t {
47+
Spark,
48+
Splash,
49+
};
4350

4451
// ---- Single particle -------------------------------------------------------
4552
struct Particle {
4653
Vec3f pos;
4754
Vec3f vel;
4855
float life; ///< Seconds remaining.
4956
float maxLife; ///< Total lifespan in seconds.
57+
ParticleKind kind = ParticleKind::Spark;
5058
bool active;
5159
};
5260

@@ -100,6 +108,45 @@ class ParticleSystem {
100108
const float lifeVar = 0.10f + 0.25f * lifeFrac;
101109
p->maxLife = lifeMin + randF() * lifeVar;
102110
p->life = p->maxLife;
111+
p->kind = ParticleKind::Spark;
112+
p->active = true;
113+
}
114+
}
115+
116+
/// @brief Emit a short water spray from a ship fin skimming the surface.
117+
/// @param origin World-space fin contact point.
118+
/// @param travelDir Unit-ish direction of ship travel; splash streaks align to this.
119+
/// @param sideDir Unit-ish vector pointing out from the ship side.
120+
/// @param upDir Track/local up vector for upward spray bias.
121+
/// @param speed Characteristic ship speed.
122+
/// @param count Number of particles to spawn.
123+
/// @param baseVel Inherited ship velocity.
124+
void emitWaterSplash(const Vec3f& origin, const Vec3f& travelDir,
125+
const Vec3f& sideDir, const Vec3f& upDir,
126+
float speed, int count,
127+
const Vec3f& baseVel = Vec3f{0.0f, 0.0f, 0.0f}) {
128+
const float baseSpeed = 70.0f * worldScale + speed * 0.10f;
129+
for (int n = 0; n < count; ++n) {
130+
Particle* p = allocate();
131+
if (!p) break;
132+
133+
float rx = randF() - 0.5f;
134+
float rz = randF() - 0.5f;
135+
Vec3f dir {
136+
travelDir.x * 1.15f + sideDir.x * 0.28f + upDir.x * 0.35f + rx * 0.20f,
137+
travelDir.y * 1.15f + sideDir.y * 0.28f + upDir.y * 0.35f + fabsf(randF()) * 0.25f,
138+
travelDir.z * 1.15f + sideDir.z * 0.28f + upDir.z * 0.35f + rz * 0.20f
139+
};
140+
float dlen = dir.length();
141+
if (dlen < 1e-4f) dlen = 1.0f;
142+
dir = dir * (1.0f / dlen);
143+
144+
float spd = baseSpeed * (0.55f + randF() * 0.75f);
145+
p->vel = dir * spd + baseVel * 0.25f;
146+
p->pos = origin;
147+
p->maxLife = 0.12f + randF() * 0.3f;
148+
p->life = p->maxLife;
149+
p->kind = ParticleKind::Splash;
103150
p->active = true;
104151
}
105152
}
@@ -215,18 +262,27 @@ class ParticleSystem {
215262

216263
// Smooth white → blue crossfade over age 0.25–0.45.
217264
// White: R=31 G=63 B=31 (0xFFFF); Blue: R=0 G=42 B=31 (0x055F).
218-
uint16_t sparkCol;
219-
if (age < 0.25f) {
220-
sparkCol = SPARK_COL_WHITE;
265+
uint16_t particleCol;
266+
if (p.kind == ParticleKind::Splash) {
267+
if (age < 0.35f) {
268+
particleCol = SPLASH_COL_FOAM;
269+
} else {
270+
const float t = std::min(1.0f, (age - 0.35f) / 0.65f);
271+
const uint8_t rC = (uint8_t)(29.0f * (1.0f - t) + 11.0f * t + 0.5f);
272+
const uint8_t gC = (uint8_t)(63.0f * (1.0f - t) + 46.0f * t + 0.5f);
273+
particleCol = ((uint16_t)rC << 11) | ((uint16_t)gC << 5) | 31u;
274+
}
275+
} else if (age < 0.25f) {
276+
particleCol = SPARK_COL_WHITE;
221277
} else if (age < 0.45f) {
222278
const float t = (age - 0.25f) * 5.0f;
223279
const uint8_t rC = (uint8_t)(31.0f * (1.0f - t) + 0.5f);
224280
const uint8_t gC = (uint8_t)(63.0f * (1.0f - t) + 42.0f * t + 0.5f);
225-
sparkCol = ((uint16_t)rC << 11) | ((uint16_t)gC << 5) | 31u;
281+
particleCol = ((uint16_t)rC << 11) | ((uint16_t)gC << 5) | 31u;
226282
} else {
227-
sparkCol = SPARK_COL_BLUE;
283+
particleCol = SPARK_COL_BLUE;
228284
}
229-
mat.color = sparkCol;
285+
mat.color = particleCol;
230286

231287
// Transform world pos to camera space
232288
const int32_t wx = (int32_t)p.pos.x - cam->position.x;

0 commit comments

Comments
 (0)