Skip to content

Refactor material parameter extraction and add fog support - #502

Merged
tracygardner merged 4 commits into
mainfrom
claude/review-material-handling-SodPl
Mar 31, 2026
Merged

Refactor material parameter extraction and add fog support#502
tracygardner merged 4 commits into
mainfrom
claude/review-material-handling-SodPl

Conversation

@tracygardner

@tracygardnertracygardner commented Mar 31, 2026

Copy link
Copy Markdown
Contributor

Summary

This PR refactors material parameter extraction into a reusable utility function and enhances the multi-color gradient shader with fog support. It also improves material caching by including glow state in cache keys.

Key Changes

  • New getMaterialParamsFromMesh() utility function: Extracts material parameters (color, materialName, alpha, glow) from a mesh's material, supporting both cached materials (with metadata.cacheKey) and raw GLTF materials. This eliminates duplicate parameter extraction logic across multiple methods.

  • Refactored material effect methods: Updated glowMesh(), setAlpha(), and clearEffects() to use the new utility function, reducing code duplication and improving maintainability.

  • Enhanced multi-color gradient shader with fog support:

    • Renamed shader keys from multiColorGradient to multiColorGradientFog to avoid collisions
    • Added fog uniforms (fogColor, fogDensity, fogStart, fogEnd, fogMode) and varyings (vFogPosition)
    • Implemented three fog modes: exponential, exponential squared, and linear
    • Integrated registerFogAwareShaderMaterial() and updateFogUniformsForShaderMaterial() calls
  • Improved material cache key generation: Modified generateMaterialCacheKey() to include glow state in the cache key (mat_${colorKey}_${alphaKey}_${texName}_${glowKey}), ensuring materials with different glow states are cached separately.

  • Added comprehensive gradient material tests: New test suite verifying that gradient materials (2-color and 3+ color) are properly preserved after applying glow, clearing effects, and setting alpha values. Also tests cache hit behavior for repeated material application.

  • Added gradient material block generator: New Blockly code generator for gradient materials that outputs material descriptor objects with color, materialName, and alpha properties.

Notable Implementation Details

  • The getMaterialParamsFromMesh() function handles both cached materials (parsing metadata.cacheKey) and raw GLTF materials (extracting from diffuseColor/albedoColor)
  • Fog calculations in the fragment shader use view-space distance for consistent fog application
  • Material cache keys now use fixed-point alpha values (.toFixed(2)) for better cache consistency
  • Gradient material preservation is tested across multiple operations to ensure shader material types aren't inadvertently replaced

https://claude.ai/code/session_01Jckn16VqMzdP7SYXerpW9y

Summary by CodeRabbit

  • New Features

    • Fog-aware gradient materials for correct fog blending
    • Block generator for creating gradient material descriptors
  • Improvements

    • Unified handling of color, alpha, and glow to preserve material parameters during effects
    • Glow now respects per-object emissive color; clearing effects removes glow color metadata
    • Material cache keys include glow discriminator and quantized alpha
  • Tests

    • New tests for gradient material preservation, alpha application, glow/clear behavior, and cache reuse

- Add missing gradient_material block generator
- Fix makeTargetCacheKey to match getOrCreateMaterial format (alpha
toFixed(2) and glow key), so the cache-hit early-exit in
applyMaterialToHierarchy now fires correctly
- Extract getMaterialParamsFromMesh helper that reads colour from
metadata.cacheKey (preserving gradient arrays) with fallback to
diffuseColor/albedoColor for raw GLTF materials; use it in glowMesh,
clearEffects and setAlpha, replacing duplicated logic that was losing
gradient colours by falling back to white
- Add fog uniforms and fog-aware registration to
createMultiColorGradientMaterial so multicolour gradients respond to
scene fog consistently with other shader materials
- Add tests covering gradient preservation through glow, clearEffects,
setAlpha and cache-hit behaviour
https://claude.ai/code/session_01Jckn16VqMzdP7SYXerpW9y
@coderabbitai

coderabbitaiBot commented Mar 31, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

Centralized material parameter extraction from meshes; made multi-color gradient shaders fog-aware; included glow and quantized alpha in material cache keys; added a Blockly generator for gradient materials; and added tests validating gradient-material preservation, alpha handling, and cache reuse.

Changes

Cohort / File(s)Summary
Material core & shaders
api/material.js
Added flockMaterial.getMaterialParamsFromMesh(mesh); refactored glowMesh, setAlpha, clearEffects to use it; introduced fog-aware multi-color gradient shader variants and fog uniform wiring; material cache-key now includes quantized alpha and glow discriminator; clearEffects clears glowColor.
Blockly generator
generators/generators-material.js
Added javascriptGenerator.forBlock["gradient_material"] to emit { color, materialName: "none.png", alpha } with defaults for COLOR/ALPHA.
Tests
tests/materials.test.js
Added tests asserting gradient-material type persistence across effects (2-color → GradientMaterial; 3+ → ShaderMaterial), alpha application via setAlpha, glowColor removal on clearEffects, and material instance caching when reapplying descriptors.

Sequence Diagram(s)

sequenceDiagram
participant Mesh
participant FlockAPI as Flock API
participant MaterialCache as Material Cache
participant ShaderFactory as Shader/Material Factory
participant Scene
Mesh->>FlockAPI: request material params / apply material
FlockAPI->>FlockAPI: getMaterialParamsFromMesh(mesh)
FlockAPI->>MaterialCache: lookup key (mat_<color>_<alphaKey>_<tex>_<glowKey>)
alt cache hit
MaterialCache-->>FlockAPI: existing material
else cache miss
FlockAPI->>ShaderFactory: create/register fog-aware shader/material
ShaderFactory-->>MaterialCache: store material
MaterialCache-->>FlockAPI: new material
end
FlockAPI->>Scene: apply material to mesh (setMaterialWithCleanup)
Scene-->>Mesh: material applied
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

codex

Poem

🐰
I hop through vertices and keys so bright,
I tuck in glow and trim alpha light,
Gradients learn fog's gentle song,
Blockly hops colors all day long,
Tests clap paws — the shaders sing tonight.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 0.00% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title accurately reflects the main changes: refactoring material parameter extraction into a reusable utility and adding fog support to the gradient shader.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/review-material-handling-SodPl

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
tests/materials.test.js (1)

1079-1110: One of the changed gradient branches is still untested.

setAlpha is only exercised against the 3+ color ShaderMaterial path, and the cache-hit assertion uses a scalar descriptor. That leaves the 2-color/array-descriptor branch out of the new alpha/caching coverage. Switching the cache case to a 2-color descriptor, or adding a dedicated 2-color setAlpha test, would protect the other half of this refactor.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@tests/materials.test.js` around lines 1079 - 1110, The test suite misses
coverage for the 2-color/array-descriptor gradient code path; update tests so
the caching/assertion or alpha path exercises that branch by either (A) changing
the "should hit cache when applying the same material twice via
applyMaterialToHierarchy" test to use a 2-color gradient descriptor (e.g.,
descriptor.color as an array like ["#ff0000","#00ff00"]) when calling
flock.applyMaterialToHierarchy on the mesh, or (B) add a new spec that calls
createGradientBox (or createBox + apply a 2-color descriptor) then calls
flock.setAlpha(id, {value: ...}) and asserts the material class and alpha just
like the 3+ color test; reference flock.setAlpha and
flock.applyMaterialToHierarchy to locate where to change/add the test.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@api/material.js`:
- Around line 187-191: The code is overwriting a possible gradient color
descriptor by replacing params.color with a scalar glow tint; instead preserve
params.color (so createMaterial keeps gradients) and carry the glow tint
separately (e.g., add glowTint or glowColor property on materialParams) and use
that tint only for the glow call (see getMaterialParamsFromMesh, materialParams,
createMaterial and glow(mesh, { color })). Modify the assignment so color:
params.color (or leave out color) and add glowTint: glowColor ?
flock.getColorFromString(glowColor) : undefined, then update the code that calls
glow(mesh, { color }) to read the tint from glowTint instead of params.color.
- Around line 155-179: getMaterialParamsFromMesh is mis-parsing cacheKey and
dropping valid state: split("_") breaks when materialName contains underscores,
parseFloat(parts[2]) || 1 turns legitimate 0.00 alphas into 1, and the
non-cacheKey fallback always returns "none.png" causing textured materials to
lose their texture. Fix getMaterialParamsFromMesh by: parsing cacheKey more
robustly (e.g., split with a limit or extract materialName as the remainder
after the known prefix parts so underscores are preserved), treating alpha using
a strict numeric check rather than `||` so 0 or 0.00 stays 0 (use
Number.isFinite or explicit NaN check after Number()/parseFloat), and in the
fallback branch preserve any existing texture name from mat (e.g., read
mat.diffuseTexture/name or equivalent) instead of hardcoding "none.png"; update
references in callers that use getMaterialParamsFromMesh to rely on the
corrected fields.
---
Nitpick comments:
In `@tests/materials.test.js`:
- Around line 1079-1110: The test suite misses coverage for the
2-color/array-descriptor gradient code path; update tests so the
caching/assertion or alpha path exercises that branch by either (A) changing the
"should hit cache when applying the same material twice via
applyMaterialToHierarchy" test to use a 2-color gradient descriptor (e.g.,
descriptor.color as an array like ["#ff0000","#00ff00"]) when calling
flock.applyMaterialToHierarchy on the mesh, or (B) add a new spec that calls
createGradientBox (or createBox + apply a 2-color descriptor) then calls
flock.setAlpha(id, {value: ...}) and asserts the material class and alpha just
like the 3+ color test; reference flock.setAlpha and
flock.applyMaterialToHierarchy to locate where to change/add the test.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 775df81d-9a7b-4a02-9715-4aa4527f7ac5

📥 Commits

Reviewing files that changed from the base of the PR and between 8c63d0a and c85f68f.

📒 Files selected for processing (3)
  • api/material.js
  • generators/generators-material.js
  • tests/materials.test.js

Comment threadapi/material.js
Comment threadapi/material.js Outdated
- Parse materialName by taking all parts between index 3 and the glow
suffix, joined with "_", so texture names containing underscores are
preserved
- Use Number.isFinite check instead of || 1 so a cached alpha of 0.00
is not coerced to 1
- Detect glow/noglow suffix by checking the last part, with fallback to
mesh.metadata.glow for older unmanaged materials
- In fallback branch, read texture name from mat.diffuseTexture/
albedoTexture instead of hardcoding "none.png" so unmanaged textured
GLTF materials preserve their texture on glow/setAlpha/clearEffects
- In glowMesh, only replace params.color with the explicit glowColor
when params.color is a scalar; gradient arrays are kept intact so
createMaterial continues to produce a GradientMaterial/ShaderMaterial
Addresses CodeRabbit review comments on PR #502.
https://claude.ai/code/session_01Jckn16VqMzdP7SYXerpW9y
@cloudflare-workers-and-pages

cloudflare-workers-and-pagesBot commented Mar 31, 2026

Copy link
Copy Markdown

Deploying flockdev with Cloudflare Pages Cloudflare Pages

Latest commit:c610685
Status: ✅ Deploy successful!
Preview URL:https://aa762a52.flockdev.pages.dev
Branch Preview URL:https://claude-review-material-handl.flockdev.pages.dev

View logs

GradientMaterial and ShaderMaterial have no emissiveColor property so
Babylon's GlowLayer never produced a visible effect on gradient meshes.
Install customEmissiveColorSelector on the GlowLayer that reads
metadata.glowColor per-mesh, bypassing the material entirely. Store the
resolved glow colour on metadata.glowColor in glowMesh (using the first
array colour for gradients, or the explicit glowColor arg for scalar
materials). Clear metadata.glowColor in clearEffects.
This makes glow work consistently for all material types: Standard,
GradientMaterial (2-colour), and ShaderMaterial (3+ colour).
Update tests to assert glowColor metadata is set after glow and cleared
after clearEffects.
https://claude.ai/code/session_01Jckn16VqMzdP7SYXerpW9y

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
api/material.js (1)

219-225: ⚠️ Potential issue | 🟠 Major

Don't feed the glow tint back into the base material.

The gradient case is fixed, but Lines 223-225 still overwrite scalar params.color even though the GlowLayer now reads metadata.glowColor. That makes glow(mesh, { color }) change the rendered material as well as the glow, and it also creates a separate cache entry for what should be a per-mesh glow-only override.

Suggested change
 const materialParams = {
...params,
- color:- glowColor && !Array.isArray(baseColor)- ? flock.getColorFromString(glowColor)- : baseColor,+ color: baseColor,
glow: true,
};
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@api/material.js` around lines 219 - 225, The materialParams object should not
inject glowColor into the base material; remove any use of glowColor when
building materialParams so the base material's color remains
params.color/baseColor and glow tint stays only in metadata.glowColor. Update
the code that constructs materialParams (the materialParams spread in
api/material.js) to stop setting color from glowColor — either remove the color
override entirely or only set color from baseColor when baseColor is a scalar —
ensuring glowColor is only used for metadata and does not produce a separate
material cache entry.
🧹 Nitpick comments (1)
api/material.js (1)

1109-1110: Gradient fog uniforms are refreshed after fog changes via setFog().

The main concern is addressed: api/effects.js line 224 calls flock.updateFogAwareShaderMaterials?.() immediately after all fog properties are set in setFog(), ensuring registered materials get updated. However, createMultiGradientShaderMaterial() lacks the defensive onBindObservable refresh pattern used by createColorReplaceShaderMaterial(). Consider adding the same pattern for consistency—this guards against potential direct scene.fog* mutations that bypass setFog() and provides redundant safety on every material bind.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@api/material.js` around lines 1109 - 1110, createMultiGradientShaderMaterial
lacks the defensive onBindObservable refresh used by
createColorReplaceShaderMaterial; add an onBindObservable observer on the
material created in createMultiGradientShaderMaterial that calls
flock.updateFogUniformsForShaderMaterial(shaderMaterial) (the same pattern used
for createColorReplaceShaderMaterial) so fog uniforms are refreshed on every
bind in case scene.fog* was mutated outside setFog(); keep the existing
flock.registerFogAwareShaderMaterial call but add the onBindObservable hook to
ensure redundant safety.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@api/material.js`:
- Around line 173-188: The code currently reconstructs materialName by parsing
mat.metadata.cacheKey (using parts.slice(...)) which uses a
canonicalized/lowercased key and can mangle original texture case; instead, read
the original texture name from a lossless metadata field (e.g.
mat.metadata.originalTextureName or mat.metadata.materialName) or, if present,
the live material texture property (e.g. mat.textureName / mat.name) and use
that for materialName; only fall back to parsing cacheKey if no original name
exists, and preserve casing (do not .toLowerCase) — update the return to use
that metadata/original property for materialName and keep existing alpha/glow
logic (mat.alpha, mesh.metadata?.glow) unchanged.
---
Duplicate comments:
In `@api/material.js`:
- Around line 219-225: The materialParams object should not inject glowColor
into the base material; remove any use of glowColor when building materialParams
so the base material's color remains params.color/baseColor and glow tint stays
only in metadata.glowColor. Update the code that constructs materialParams (the
materialParams spread in api/material.js) to stop setting color from glowColor —
either remove the color override entirely or only set color from baseColor when
baseColor is a scalar — ensuring glowColor is only used for metadata and does
not produce a separate material cache entry.
---
Nitpick comments:
In `@api/material.js`:
- Around line 1109-1110: createMultiGradientShaderMaterial lacks the defensive
onBindObservable refresh used by createColorReplaceShaderMaterial; add an
onBindObservable observer on the material created in
createMultiGradientShaderMaterial that calls
flock.updateFogUniformsForShaderMaterial(shaderMaterial) (the same pattern used
for createColorReplaceShaderMaterial) so fog uniforms are refreshed on every
bind in case scene.fog* was mutated outside setFog(); keep the existing
flock.registerFogAwareShaderMaterial call but add the onBindObservable hook to
ensure redundant safety.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 52d9d4d8-832b-4f74-8331-48879d9c3791

📥 Commits

Reviewing files that changed from the base of the PR and between a6a5dce and aac9bea.

📒 Files selected for processing (2)
  • api/material.js
  • tests/materials.test.js
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/materials.test.js

Comment threadapi/material.js
The cacheKey is lowercased before storage, so deserialising materialName
from it produces a lowercased path. On case-sensitive hosts (Linux, PWA
service worker cache) this breaks asset lookup.
Store texName with its original case in metadata.texName when the
material is created in getOrCreateMaterial, and prefer that in
getMaterialParamsFromMesh over the cacheKey-derived string. Keeps the
cacheKey-split as a fallback for any pre-existing materials that lack
the field.
Addresses CodeRabbit review comment (offline PWA behaviour).
https://claude.ai/code/session_01Jckn16VqMzdP7SYXerpW9y

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

♻️ Duplicate comments (2)
api/material.js (2)

1611-1618: ⚠️ Potential issue | 🟠 Major

Don't fold texName into a case-insensitive cache key.

This helper still lowercases the texture portion of the material identity, so Foo.PNG and foo.png collapse to the same cached material. On case-sensitive hosts and the service-worker cache, that can reuse/skip the wrong asset even though metadata.texName now preserves the original case.

💡 Suggested direction
 const texName = String(getTexName(v));
const alphaKey = parseFloat(getAlpha(v)).toFixed(2);
const glow =
typeof v === "object" && v !== null && !Array.isArray(v)
? (v.glow ?? false)
: false;
const glowKey = glow ? "glow" : "noglow";
- return `mat_${colorKey}_${alphaKey}_${texName}_${glowKey}`.toLowerCase();+ const normalizedColorKey = String(colorKey).toLowerCase();+ return `mat_${normalizedColorKey}_${alphaKey}_${texName}_${glowKey}`;

Please mirror the same change where cacheKey is built in getOrCreateMaterial(). As per coding guidelines, "Don't introduce changes that would break offline PWA behaviour".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@api/material.js` around lines 1611 - 1618, The material cache key lowercases
the texture name causing case-sensitive hosts/service-worker caches to mix
assets; update the key builder to avoid lowercasing texName while keeping the
rest lowercased: in the key assembly that uses colorKey, alphaKey, texName, and
glowKey (the block returning
`mat_${colorKey}_${alphaKey}_${texName}_${glowKey}`), remove the .toLowerCase()
call and instead apply .toLowerCase() only to the other components (e.g.,
colorKey and glowKey) so texName preserves its original case; apply the same
change where cacheKey is constructed in getOrCreateMaterial() so both places
treat texName case-sensitively.

220-226: ⚠️ Potential issue | 🟠 Major

Keep the base material color when a custom glow tint is provided.

m.metadata.glowColor already carries the glow-only tint, but this branch still rewrites materialParams.color for scalar colors. That permanently changes the managed material, so clearEffects()/later round-trips come back in the glow tint instead of the original base color.

💡 Suggested fix
 if (params) {
const materialParams = {
...params,
- color:- glowColor && !Array.isArray(baseColor)- ? flock.getColorFromString(glowColor)- : baseColor,+ color: baseColor,
glow: true,
};
flock.setMaterialWithCleanup(m, materialParams);
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@api/material.js` around lines 220 - 226, The branch that sets
materialParams.color uses glowColor to rewrite the base color for scalar
baseColor values, which overwrites the managed material; change the logic so
materialParams.color always preserves baseColor (never replace it with
flock.getColorFromString(glowColor)), and if a glow tint must be stored on the
material, set a separate property (e.g., materialParams.glowColor or
metadata.glowColor) to flock.getColorFromString(glowColor) instead; update the
assignment around materialParams, color, glowColor, baseColor and
flock.getColorFromString so the base color remains unchanged while the glow tint
is stored independently.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@api/material.js`:
- Around line 1611-1618: The material cache key lowercases the texture name
causing case-sensitive hosts/service-worker caches to mix assets; update the key
builder to avoid lowercasing texName while keeping the rest lowercased: in the
key assembly that uses colorKey, alphaKey, texName, and glowKey (the block
returning `mat_${colorKey}_${alphaKey}_${texName}_${glowKey}`), remove the
.toLowerCase() call and instead apply .toLowerCase() only to the other
components (e.g., colorKey and glowKey) so texName preserves its original case;
apply the same change where cacheKey is constructed in getOrCreateMaterial() so
both places treat texName case-sensitively.
- Around line 220-226: The branch that sets materialParams.color uses glowColor
to rewrite the base color for scalar baseColor values, which overwrites the
managed material; change the logic so materialParams.color always preserves
baseColor (never replace it with flock.getColorFromString(glowColor)), and if a
glow tint must be stored on the material, set a separate property (e.g.,
materialParams.glowColor or metadata.glowColor) to
flock.getColorFromString(glowColor) instead; update the assignment around
materialParams, color, glowColor, baseColor and flock.getColorFromString so the
base color remains unchanged while the glow tint is stored independently.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b6ecf58b-e419-4a6c-93fc-3836475acdca

📥 Commits

Reviewing files that changed from the base of the PR and between aac9bea and c610685.

📒 Files selected for processing (1)
  • api/material.js

@tracygardner
tracygardner merged commit a2873e3 into mainMar 31, 2026
9 checks passed
@tracygardner
tracygardner deleted the claude/review-material-handling-SodPl branch March 31, 2026 06:51
@coderabbitaicoderabbitaiBot mentioned this pull request Aug 13, 2026
Sign up for freeto 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.

2 participants

@tracygardner@claude