Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 32 additions & 7 deletions api/material.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -900,8 +900,8 @@ export const flockMaterial = {
// Normalize single-element array to plain value
if (Array.isArray(color) && color.length === 1) color = color[0];

// Handle two-color case
if (Array.isArray(color) && color.length === 2) {
// Handle two-color case (extra colors beyond 2 are ignored)
if (Array.isArray(color) && color.length >= 2) {
// Use gradient for Flat material
if (materialName === "none.png") {
material = new flock.GradientMaterial(materialName, flock.scene);
Expand DownExpand Up@@ -989,8 +989,9 @@ export const flockMaterial = {
},
);

// Convert colors to Color3 array
// Convert colors to Color3 array (max 16, matching shader array size)
const color3Array = colors
.slice(0, 16)
.map((c) => {
const hex = flock.getColorFromString(c);
const color3 = flock.BABYLON.Color3.FromHexString(hex);
Expand All@@ -1003,7 +1004,7 @@ export const flockMaterial = {
console.log("Color array:", color3Array);
}

shaderMaterial.setInt("colorCount", colors.length);
shaderMaterial.setInt("colorCount", Math.min(colors.length, 16));
shaderMaterial.setArray3("colors", color3Array);
shaderMaterial.setFloat("alpha", 1.0);
shaderMaterial.setVector2("minMax", new flock.BABYLON.Vector2(-1, 1)); // Will be updated when applied to mesh
Expand DownExpand Up@@ -1088,6 +1089,8 @@ export const flockMaterial = {
uniform sampler2D textureSampler;
uniform vec3 lightColor; // Replaces white
uniform vec3 greyTintColor; // Tints greys in proportion
uniform vec3 darkColor; // Replaces black (when colorCount >= 3)
uniform int colorCount;
uniform float alpha;
uniform float uScale; // Horizontal tiling
uniform float vScale; // Vertical tiling
Expand All@@ -1110,6 +1113,9 @@ export const flockMaterial = {
if (brightness > 0.95 && colorDiff < 0.05) {
// Replace near-white
finalColor = lightColor;
} else if (colorCount >= 3 && brightness < 0.05 && colorDiff < 0.05) {
// Replace near-black (third color)
finalColor = darkColor;
} else if (colorDiff < 0.05) {
// Tint greys
finalColor = brightness * greyTintColor;
Expand DownExpand Up@@ -1137,6 +1143,8 @@ export const flockMaterial = {
"textureSampler",
"lightColor",
"greyTintColor",
"darkColor",
"colorCount",
"alpha",
"uScale",
"vScale",
Expand DownExpand Up@@ -1185,6 +1193,19 @@ export const flockMaterial = {
),
);

const colorDark = colors.length >= 3
? flock.hexToRgb(flock.getColorFromString(colors[2]))
: { r: 0, g: 0, b: 0 };
shaderMaterial.setVector3(
"darkColor",
new flock.BABYLON.Vector3(
colorDark.r / 255.0,
colorDark.g / 255.0,
colorDark.b / 255.0,
),
);
shaderMaterial.setInt("colorCount", colors.length);

shaderMaterial.setFloat("alpha", 1.0);

return shaderMaterial;
Expand DownExpand Up@@ -1335,7 +1356,7 @@ export const flockMaterial = {
if (cacheKey && flock.materialCache[cacheKey]) {
delete flock.materialCache[cacheKey];
}
oldMat.dispose(true, true);
oldMat.dispose(false, true);
}
}
},
Expand DownExpand Up@@ -1442,8 +1463,12 @@ export const flockMaterial = {
const isMaterialDescriptor = (v) =>
typeof v === "object" && v !== null && !Array.isArray(v);

const getRawColor = (v) =>
isMaterialDescriptor(v) ? v.color || v.baseColor : v;
const getRawColor = (v) => {
const raw = isMaterialDescriptor(v) ? v.color || v.baseColor : v;
// A single-element colour list produces ["#rrggbb"]; normalise to a string
// so that downstream callers like getColorFromString receive a plain string.
return Array.isArray(raw) && raw.length === 1 ? raw[0] : raw;
};

const getTexName = (v) =>
isMaterialDescriptor(v)
Expand Down
40 changes: 34 additions & 6 deletions api/scene.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -188,10 +188,6 @@ export const flockScene = {
}
dt.update(false);

const tex = new flock.BABYLON.Texture(null, flock.scene);
tex._texture = dt.getInternalTexture();
tex.wrapU = flock.BABYLON.Texture.CLAMP_ADDRESSMODE;
tex.wrapV = flock.BABYLON.Texture.CLAMP_ADDRESSMODE;
return dt;
},
createMap(image, material) {
Expand All@@ -202,6 +198,7 @@ export const flockScene = {
const applyMaterialToGround = (mesh, mat) => {
if (Array.isArray(mat) && mat.length === 1) mat = mat[0];
if (Array.isArray(mat) && mat.length >= 2) {
const oldMat = mesh.material;
const standardMat = new flock.BABYLON.StandardMaterial(
"mapGradientMat",
flock.scene,
Expand All@@ -216,9 +213,40 @@ export const flockScene = {
flock.BABYLON.Texture.CLAMP_ADDRESSMODE;
standardMat.diffuseTexture.wrapV =
flock.BABYLON.Texture.CLAMP_ADDRESSMODE;
flock.setMaterialWithCleanup(mesh, standardMat);
mesh.material = standardMat;
if (oldMat && oldMat.name === "mapGradientMat") {
oldMat.dispose(false, true);
}
} else {
flock.setMaterialWithCleanup(mesh, material);
// Update an existing GradientMaterial in-place to avoid shader recompilation.
const colors =
mat && typeof mat === "object" && Array.isArray(mat.color)
? mat.color
: null;
if (
colors?.length >= 2 &&
(mat.materialName === "none.png" || !mat.materialName) &&
mesh.material instanceof flock.GradientMaterial
) {
const existingMat = mesh.material;
existingMat.bottomColor = flock.BABYLON.Color3.FromHexString(
flock.getColorFromString(colors[0]),
);
existingMat.topColor = flock.BABYLON.Color3.FromHexString(
flock.getColorFromString(colors[1]),
);
existingMat.alpha = parseFloat(mat.alpha ?? 1);
const oldKey = existingMat.metadata?.cacheKey;
if (oldKey) delete flock.materialCache[oldKey];
const alphaKey = parseFloat(mat.alpha ?? 1).toFixed(2);
const newKey =
`mat_${colors.join("-")}_${alphaKey}_${mat.materialName ?? "none.png"}_noglow`.toLowerCase();
existingMat.name = newKey;
if (existingMat.metadata) existingMat.metadata.cacheKey = newKey;
flock.materialCache[newKey] = existingMat;
Comment thread
coderabbitai[bot] marked this conversation as resolved.
} else {
flock.setMaterialWithCleanup(mesh, material);
}
}
};

Expand Down
46 changes: 30 additions & 16 deletions ui/blockmesh.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -777,25 +777,39 @@ function updateMapFromBlock(mesh, block, changeEvent) {

if (!materialBlock) return;

const { textureSet, alpha } = extractMaterialInfo(materialBlock);
let read = readColourFromInputOrShadow(materialBlock, "BASE_COLOR");

if (read.value == null && !block.__mapRetry) {
block.__mapRetry = true;
requestAnimationFrame(() => {
block.__mapRetry = false;
updateMapFromBlock(mesh, block, changeEvent);
});
// A raw colour/list block may be connected directly to MATERIAL (not via a
// material block), so dispatch on block type and pass the raw value straight
// to createMap to match the generated-JS code path.
const isMaterialBlock = materialBlock.type === "material";
let read, mapArg;
if (isMaterialBlock) {
const { textureSet, alpha } = extractMaterialInfo(materialBlock);
read = readColourFromInputOrShadow(materialBlock, "BASE_COLOR");
const materialName =
!textureSet || textureSet === "NONE" ? "none.png" : textureSet;
mapArg = { color: read.value, materialName, alpha };
} else {
read = readColourValue(materialBlock);
mapArg = read.value;
}

const colorIsEmpty =
read.value == null ||
(Array.isArray(read.value) && read.value.length === 0);
if (colorIsEmpty) {
// Retry once — mutator operations briefly leave the colour list empty.
// If still empty after the retry, bail silently to avoid an infinite loop.
const wasRetrying = block.__mapRetry;
block.__mapRetry = false;
if (!wasRetrying) {
block.__mapRetry = true;
requestAnimationFrame(() => updateMapFromBlock(mesh, block, changeEvent));
}
return;
}
block.__mapRetry = false;

const materialOptions = {
color: read.value,
materialName: textureSet,
alpha,
};

flock.createMap(mapName, materialOptions);
flock.createMap(mapName, mapArg);
}

function resolveColorAndMaterialForBlock(block) {
Expand Down
Loading