Summary
DirectionalLight's constructor calls calculateLightDirection(), which reads sin_table[]. That table is only populated by initializeTrigTables(), which lives inside Scene::Scene(). Any DirectionalLight constructed before a Scene exists silently caches worldLightDir = (0, 0, 0) from the still-zeroed table. The cached value never refreshes on its own — subsequent updateViewSpaceDirection() calls also produce zero vectors, all N·L clamp to zero, and the light has no effect.
Upstream Sample.cpp doesn't trip this because it constructs the Scene first and lights second. Code that organizes lighting state into a host struct (lights as value members alongside a Scene*) hits it.
Repro
struct DemoScene {
DirectionalLight sun{Vector3{300, 35, 0}, Color{255, 245, 220}, 240};
Scene *scene = nullptr;
};
int main() {
DemoScene demo; // sun's ctor runs against sin_table[]==0
demo.scene = new Scene(fb, ...); // initializeTrigTables() runs here — too late
demo.scene->setDirectionalLight(&demo.sun);
// worldLightDir is cached as (0,0,0); no light reaches any object.
}
External workaround:
demo.sun.updateDirection(demo.sun.direction); // force recompute after Scene exists
Suggested fixes
A few options, ordered cleanest → easiest:
-
Move initializeTrigTables() out of Scene::Scene() and into a static / namespace-level constructor, or expose a Renderer::init() function the host must call before any other Jet API. Cleanest; mildly breaking for users who relied on Scene's ctor running it.
-
Have DirectionalLight defer the world-direction calc to first read rather than caching in its constructor (lazy getWorldLightDir()).
-
At minimum: document the ordering requirement prominently on DirectionalLight's class comment. "Construct AFTER a Scene exists" is currently invisible.
Summary
DirectionalLight's constructor callscalculateLightDirection(), which readssin_table[]. That table is only populated byinitializeTrigTables(), which lives insideScene::Scene(). AnyDirectionalLightconstructed before aSceneexists silently cachesworldLightDir = (0, 0, 0)from the still-zeroed table. The cached value never refreshes on its own — subsequentupdateViewSpaceDirection()calls also produce zero vectors, allN·Lclamp to zero, and the light has no effect.Upstream
Sample.cppdoesn't trip this because it constructs the Scene first and lights second. Code that organizes lighting state into a host struct (lights as value members alongside a Scene*) hits it.Repro
External workaround:
demo.sun.updateDirection(demo.sun.direction); // force recompute after Scene existsSuggested fixes
A few options, ordered cleanest → easiest:
Move
initializeTrigTables()out ofScene::Scene()and into a static / namespace-level constructor, or expose aRenderer::init()function the host must call before any other Jet API. Cleanest; mildly breaking for users who relied on Scene's ctor running it.Have
DirectionalLightdefer the world-direction calc to first read rather than caching in its constructor (lazygetWorldLightDir()).At minimum: document the ordering requirement prominently on
DirectionalLight's class comment. "Construct AFTER a Scene exists" is currently invisible.