Skip to content

Spotlights, per-light shadows and lamp glows - #1196

Merged
wass08 merged 3 commits into
mainfrom
feat/spot-shadow-glow-lights
Oct 29, 2026
Merged

wass08 merged 3 commits into
mainfrom
feat/spot-shadow-glow-lights

Conversation

@wass08

@wass08 wass08 commented Oct 29, 2026 •

Copy link
Copy Markdown
Collaborator

What does this PR do?

Lights held by items can now be spotlights, cast shadows, and draw a glow at the lamp.

  • Scene graph: the item light effect (and the authored-object manifest light) gains three optional fields. Old scenes load unchanged.
    • spot { direction, angle, softness }: direction is the aim in the item's frame, so it turns with the item; angle is the full cone in degrees; softness runs from 0 (hard edge) to 1 (fully feathered).
    • shadows: casts shadows.
    • glow { size, strength = 1 }: a soft additive halo at the lamp in the light's colour, nearly invisible in daylight and strongest at night (follows the sun's elevation, or the theme).
  • Modules:
    • three.js: a SpotLight keeps its target, angle and penumbra. castShadow (or userData.shadows) and userData.glow carry over.
    • Blender: a spot keeps its cone and blend. The shadows and glow custom properties carry over.
  • author_scene: lights[] takes direction or target, angle, softness, shadows and glow. A target is stored as a direction in the fixture's frame.
  • Viewer: the editor and baked viewers share one pooled runtime (PooledLights): 12 point lights, 6 spots and 2 shadow slots.
    • Spots get the shadow slots before point lights, since a point shadow is six renders.
    • The spot and shadow pools mount the first time a scene needs them, so existing scenes keep today's light count and shaders.
    • Glows are one instanced sprite for every lit lamp, drawn their radius toward the camera so the fixture around the bulb doesn't hide them.
  • Fix: the baked viewer and the in-scene controls now read an authored item's own asset.interactive, as the editor already did. Before, lights author_scene hung on a module fixture went dark once published. A rebuild of the object now keeps those lights after its new module's own.

How to test

  1. Build an object with add_object whose module adds a THREE.SpotLight named light:wash with castShadow = true and userData.glow = { size: 0.2 }. Its manifest.lights[0] has spot, shadows: true and glow { size: 0.2, strength: 1 }.
  2. Switch the theme to Night. The cone lights the wall or floor with a soft edge, it casts shadows, and a soft halo shows at the lamp. In the Studio theme the halo almost disappears.
  3. Run author_scene with a fixture light given target, angle: 40 and glow. Rotate the fixture: the beam turns with it. Bake or publish the scene: the light is still there.
  4. Ran locally: bun test src in core, viewer, geometry-script, mcp and nodes; bun run check (no errors).

Screenshots / screen recording

Not added yet.

Checklist

  • I've tested this locally with bun dev
  • My code follows the existing code style (run bun check to verify)
  • I've updated relevant documentation (if applicable)
  • This PR targets the main branch

🤖 Generated with Claude Code


Note

Medium Risk
Substantial viewer lighting and pooling changes affect baked and live scenes, though new scene-graph fields are optional and backward compatible.

Overview
Item and author_scene lights gain optional spot aim (in the host’s frame), shadows, and glow halos, wired from geometry scripts through the manifest into scene-graph light effects. The compiler now reads SpotLight aim, cone, penumbra, castShadow, and userData.glow from three.js (and documents the Blender equivalents).

author_scene accepts direction/target, angle, softness, shadows, and glow on payload lights and stores spotlight aim relative to the fixture so beams rotate with their host.

On rebuild (add_object with nodeId), rebuiltInteractive keeps lights author_scene appended after the module’s own; scriptedInteractive makes the baked GLB viewer and in-scene controls use the item’s stored asset.interactive (fixing hung fixture lights going dark after publish).

The viewer replaces duplicated point-light pooling with shared PooledLights: fixed point, spot, and shadow-casting pools plus instanced glow sprites (day/night dimming), used by both the live editor and baked GLB path.

Reviewed by Cursor Bugbot for commit 9c93b97. Bugbot is set up for automated code reviews on this repo. Configure here.

wass08 and others added 3 commits October 29, 2026 12:54
A light effect can now be a spot (aim in the item's frame, full cone in
degrees, edge softness), cast shadows, and draw a glow: a soft additive
halo at the lamp in its colour that shows more the darker the scene.

- Modules: a THREE.SpotLight (or a Blender spot) keeps its aim, angle and
  penumbra; castShadow / userData.shadows and userData.glow carry over.
- author_scene lights take direction or target, angle, softness, shadows
  and glow; the aim is stored in the host's frame so it turns with it.
- The editor and baked viewers share one pooled runtime: 12 point lights,
  6 spots and 2 shadow slots (spots ranked before point shadows), the spot
  and shadow pools mounting the first time a scene needs them; glows are
  one instanced sprite for every lit lamp.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…bjects

An authored item's controls and effects are its asset.interactive: its
module's own, then any light author_scene hung on it. The editor read
that, but the baked viewer and the in-scene controls rebuilt them from
the module, so hung lights (now spots, shadows and glows too) went dark
once published; and rebuilding the object replaced them with the new
module's. Both viewers now read the item's own (scriptedInteractive), and
a rebuild keeps the hung lights after the new module's (rebuiltInteractive).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Saved scenes are not parsed on load, so a glow stored as { size } reached the
viewer without its schema default and turned its colour NaN: a black square.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@wass08
wass08 merged commit 29c992b into main Oct 29, 2026
5 checks passed

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 9c93b97. Configure here.

return {
controls: controls.some((control) => control.kind === 'toggle')
? controls
: [...controls, { kind: 'toggle', label: 'Lights', default: true }],

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rebuild binds hung lights to wrong toggle

Medium Severity

rebuiltInteractive keeps hung author_scene lights when the new module has no lights, but it only adds a Lights toggle if that module has no toggle at all. A rebuild that introduces an open or named clip leaves those hung lights bound to that first toggle, so they switch with Open or the clip instead of a Lights control.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 9c93b97. Configure here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant