Skip to content
Closed
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
125 changes: 113 additions & 12 deletions api/material.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,25 @@
let flock;

const materialCache = new Map();
const materialCacheStats = { hits: 0, misses: 0 };

const buildMaterialCacheKey = ({ materialName, color, alpha, shaderType }) => {
const normalizedAlpha = alpha ?? 1;
const normalizeColour = (value) =>
value == null ? "" : flock.getColorFromString(value);

const normalizedColour = Array.isArray(color)
? color.map(normalizeColour).join("|")
: normalizeColour(color);

return [shaderType || "standard", materialName || "", normalizedColour, normalizedAlpha].join(
"::",
);
};

export function setFlockReference(ref) {
flock = ref;
flock.materialCacheStats = materialCacheStats;
}

export const flockMaterial = {
Expand All @@ -14,11 +32,14 @@ export const flockMaterial = {
const bakedShapes = new Set(["Box", "Sphere", "Cylinder", "Capsule", "Plane"]);
if (shapeType && bakedShapes.has(shapeType)) return;

const tex =
material.diffuseTexture ||
material.albedoTexture ||
material.baseTexture ||
null;
const textureProp = material.diffuseTexture
? "diffuseTexture"
: material.albedoTexture
? "albedoTexture"
: material.baseTexture
? "baseTexture"
: null;
let tex = textureProp ? material[textureProp] : null;

if (!tex || typeof tex.uScale !== "number" || typeof tex.vScale !== "number") {
return;
Expand Down Expand Up @@ -49,8 +70,43 @@ export const flockMaterial = {
const newUScale = worldWidth / tile;
const newVScale = Math.max(worldHeight, worldDepth) / tile;

const needsTiling =
(Number.isFinite(newUScale) && newUScale > 0 && tex.uScale !== newUScale) ||
(Number.isFinite(newVScale) && newVScale > 0 && tex.vScale !== newVScale);

if (needsTiling && material.metadata?.sharedMaterial) {
const clonedMaterial =
typeof material.clone === "function"
? material.clone(
`${material.name || "material"}_autoTile_${mesh?.id ?? mesh?.uniqueId ?? Date.now()}`,
)
: null;

if (clonedMaterial) {
clonedMaterial.metadata = {
...(material.metadata || {}),
sharedMaterial: false,
internal: true,
};

const clonedTexture = tex?.clone?.() || null;
if (textureProp && clonedTexture) {
clonedMaterial[textureProp] = clonedTexture;
tex = clonedTexture;
}

mesh.material = clonedMaterial;
material = clonedMaterial;
} else {
// Skip tiling when we cannot safely detach a shared material.
return material;
}
}

if (Number.isFinite(newUScale) && newUScale > 0) tex.uScale = newUScale;
if (Number.isFinite(newVScale) && newVScale > 0) tex.vScale = newVScale;

return material;
},
adjustMaterialTilingForHierarchy(mesh, unitsPerTile) {
if (!mesh) return;
Expand Down Expand Up @@ -846,11 +902,42 @@ export const flockMaterial = {
},
createMaterial({ color, materialName, alpha } = {}) {
if (flock?.materialsDebug) console.log(`Create material: ${materialName}`);
const isTwoColor = Array.isArray(color) && color.length === 2;
const shaderType = isTwoColor
? materialName === "none.png"
? "gradient"
: "colorReplace"
: "standard";
const resolvedAlpha = alpha ?? 1;

const cacheKey = buildMaterialCacheKey({
materialName,
color,
alpha: resolvedAlpha,
shaderType,
});

if (materialCache.has(cacheKey)) {
materialCacheStats.hits += 1;
const cachedMaterial = materialCache.get(cacheKey);
cachedMaterial.metadata = {
...(cachedMaterial.metadata || {}),
sharedMaterial: true,
internal: true,
};
if (flock?.materialsDebug)
console.log(
`Cache hit for material ${materialName}: ${materialCacheStats.hits} hits, ${materialCacheStats.misses} misses`,
);
return cachedMaterial;
}

materialCacheStats.misses += 1;
let material;
const texturePath = flock.texturePath + materialName;
const texturePath = materialName ? flock.texturePath + materialName : null;

// Handle two-color case
if (Array.isArray(color) && color.length === 2) {
if (isTwoColor) {
// Use gradient for Flat material
if (materialName === "none.png") {
material = new flock.GradientMaterial(materialName, flock.scene);
Expand Down Expand Up @@ -896,15 +983,26 @@ export const flockMaterial = {
material.backFaceCulling = false;
}

material.alpha = alpha;
material.alpha = resolvedAlpha;

// Update alpha for shader materials
if (material.setFloat && alpha !== undefined) {
material.setFloat("alpha", alpha);
if (material.setFloat && resolvedAlpha !== undefined) {
material.setFloat("alpha", resolvedAlpha);
}

material.metadata = {
...(material.metadata || {}),
sharedMaterial: true,
internal: true,
cacheKey,
};

materialCache.set(cacheKey, material);

if (flock.materialsDebug)
console.log(`Created the material: ${material.name}`);
console.log(
`Created the material: ${material.name} (cache miss ${materialCacheStats.misses})`,
);
return material;
},
createMultiColorGradientMaterial(name, colors) {
Expand Down Expand Up @@ -1321,7 +1419,10 @@ export const flockMaterial = {

const applyMaterialWithTilingIfAny = (m) => {
mesh.material = m;
flock.adjustMaterialTilingToMesh(mesh, m);
const adjustedMaterial = flock.adjustMaterialTilingToMesh(mesh, m);
if (adjustedMaterial) {
mesh.material = adjustedMaterial;
}
};

const clearVertexColors = () => {
Expand Down