Skip to content

feat: add Fireplace node with animated procedural fire#488

Open
toolucid wants to merge 1 commit into
pascalorg:mainfrom
toolucid:feat/fireplace-node
Open

feat: add Fireplace node with animated procedural fire#488
toolucid wants to merge 1 commit into
pascalorg:mainfrom
toolucid:feat/fireplace-node

Conversation

@toolucid

@toolucid toolucid commented Jul 11, 2026

Copy link
Copy Markdown

What does this PR do?

Adds a new architectural Fireplace node kind to the editor — the first node with an animated procedural effect (instanced fire particles + flickering point light via useFrame).

  • 4 fireplace styles: wall, freestanding, corner, double-sided
  • Parametric mantel, hearth, surround, and firebox with inspector controls
  • Animated instanced fire particle system with additive blending + a flickering point light
  • 5 fire intensity levels (noneroaring), 4 fire colors (orange, amber, blue, white)
  • Placement tool with grid snap, alignment guides, SFX, and translucent ghost preview
  • Resize / rotate / move handles in the 3D viewport

Registered through the builtin plugin via the same NodeDefinition contract as all other node kinds. Follows the three-checkbox model from wiki/architecture/node-definitions.md: geometry (pure builder) + renderer (custom React with useFrame) + tool (lazy placement component).

How to test

  1. git clone https://github.com/toolucid/editor.git && cd editor && bun install && bun dev
  2. Open http://localhost:3002 in a browser
  3. In the left sidebar palette, find the Fireplace under the Furnish section
  4. Click it, then click on the floor to place a fireplace
  5. Verify: mantel, hearth, surround, and firebox render correctly; fire particles animate upward with a flickering light
  6. Select the fireplace and use the inspector to change fire intensity (noneroaring), fire color, and fireplace style
  7. Verify resize / rotate / move handles work in the 3D viewport
  8. Run bun check-types and bun check — both pass with 0 errors

Screenshots / screen recording

Will add a screen recording before review.

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

Note

Medium Risk
Adds a new discriminated union member and event surface area, plus per-instance useFrame animation and extra lights that could affect viewer performance with many fireplaces; otherwise mirrors existing furnish nodes with no auth or persistence changes.

Overview
Introduces a new fireplace furnish node end-to-end: Zod schema in core, FireplaceEvent on the editor bus, and registration in the builtin plugin.

The node supports parametric dimensions (mantel, hearth, surround, firebox), four styles (wall, freestanding, corner, double-sided), and inspector fire settings (intensity and color). Implementation follows the standard NodeDefinition split: box-based geometry builder, a custom renderer with instanced particles and a flickering point light driven by useFrame, floor placement tool (grid snap, alignment guides, ghost preview), and viewport resize/rotate/move handles.

Also updates apps/ifc-converter/next-env.d.ts to import Next route types from ./.next/types/routes.d.ts instead of the dev path.

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

New architectural fireplace node for the Pascal 3D building editor:
- 4 styles: wall, freestanding, corner, double-sided
- Parametric mantel, hearth, surround, firebox with inspector controls
- Animated instanced fire particle system with flickering point light
- 5 fire intensity levels (none → roaring), 4 fire colors
- Placement tool with grid snap, alignment guides, SFX
- Translucent ghost preview during placement
- Resize/rotate/move handles in 3D viewport

Registered through the builtin plugin via the same NodeDefinition
contract as all other node kinds. Follows the three-checkbox model:
geometry (pure builder) + renderer (custom React with useFrame) +
tool (lazy placement component).

@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 11 potential issues.

Fix All in Cursor

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

Want reviews to match your repository better? Bugbot Learning can learn team-specific rules from PR activity. A team admin can enable Learning in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit a8d3b37. Configure here.

<group
ref={ref}
position={node.position}
rotation={[0, (node.rotation * Math.PI) / 180, 0]}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Rotation treated as degrees

High Severity

The FireplaceRenderer expects node.rotation to be in degrees and converts it to radians, but node.rotation is stored in radians elsewhere. This unit mismatch causes the fireplace to rotate by a much smaller amount than intended when adjusted via handles or other controls.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit a8d3b37. Configure here.

visible={node.visible}
{...handlers}
{...liveTransform}
>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Live transform breaks rotation prop

High Severity

Spreading liveTransform onto the <group> passes a scalar rotation value. However, the <group>'s rotation prop expects an Euler tuple, causing incorrect rotation behavior and visual jumps during interactive drags.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit a8d3b37. Configure here.

handles: fireplaceHandles,
parametrics: fireplaceParametrics,
geometry: buildFireplaceGeometry,
renderer: { kind: 'parametric', module: () => import('./renderer') },

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Duplicate fireplace geometry mounted

High Severity

The fireplace geometry is rendered twice. It's defined in NodeDefinition.geometry for GeometrySystem to handle, but the custom FireplaceRenderer also explicitly renders the same geometry. This results in doubled visuals and can cause transformation issues, like dropped corner yaw, especially since markDirty is called on every render.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit a8d3b37. Configure here.

mantel.name = 'fireplace-mantel'
const mantelY = height - mantelThickness / 2
mantel.position.set(0, mantelY, mantelOverhang / 2)
group.add(mantel)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Mantel height control ignored

Medium Severity

The mantelHeight property is exposed and triggers rebuilds, but its value only determines if a mantel is rendered. The mantel's actual dimensions and vertical placement are controlled by mantelThickness and the overall height, so adjusting mantelHeight has no visual impact.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit a8d3b37. Configure here.

intensity={fireConfig.scale * 2}
color={fireColor}
/>
</>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Double-sided fire only on front

Medium Severity

For double-sided fireplaces, the geometry includes a rear firebox opening, but fire particles and light are only rendered at the front. This leaves the back opening dark and static, preventing the fireplace from appearing genuinely two-sided.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit a8d3b37. Configure here.

// Corner style — rotate the whole structure by cornerAngle.
if (style === 'corner') {
group.rotation.y = (cornerAngle * Math.PI) / 180
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Corner style desyncs handles

Medium Severity

For style: 'corner', yaw is applied on the builder Group via cornerAngle, while resize/rotate/move handles are placed in the outer node frame. The visible mesh rotates under the handles, so gizmos no longer line up with the fireplace body. GeometrySystem also drops that group rotation when reparenting children.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit a8d3b37. Configure here.

node.fireboxMaterialPreset,
shading,
],
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Geometry resources never disposed

Low Severity

buildFireplaceGeometry results are swapped via useMemo with no dispose of child BoxGeometry/materials, and FireParticles creates a SphereGeometry plus MeshBasicMaterial without teardown. Parametric edits and fire toggles leak GPU resources over a session.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit a8d3b37. Configure here.

/// <reference types="next" />
/// <reference types="next/image-types/global" />
import "./.next/dev/types/routes.d.ts";
import "./.next/types/routes.d.ts";

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Unrelated Next env change

Low Severity

This commit changes apps/ifc-converter/next-env.d.ts (an auto-generated Next.js types stub marked “should not be edited”) with no connection to the Fireplace feature. It looks like an accidental local regen included in the PR.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit a8d3b37. Configure here.

}

emitter.on('grid:move', onGridMove)
const unsubscribePlacementClicks = subscribeFloorPlacementClicks(commitAtCursor)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Placement ignores fireplace clicks

Medium Severity

subscribeFloorPlacementClicks only listens for clicks on kinds listed in FLOOR_PLACEMENT_CLICK_TRIGGER_KINDS, and that list was never updated for fireplace. Clicks on an existing fireplace do not commit floor placement for the fireplace tool or other floor tools that share this helper.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit a8d3b37. Configure here.

selectable: { hitVolume: 'bbox' },
duplicable: true,
deletable: 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.

Missing slab elevation support

Medium Severity

Unlike shelf, column, spawn, and item, the fireplace definition never declares capabilities.floorPlaced. FloorElevationSystem therefore never lifts it onto overlapping slabs, so a fireplace on a raised slab stays at level Y and sinks through the slab. The placement tool still calls getFloorStackPreviewPosition, which cannot elevate correctly without a footprint.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit a8d3b37. 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