-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add rule to detect unused textures in model files
- Loading branch information
1 parent
55c2840
commit a2e67ed
Showing
1 changed file
with
89 additions
and
0 deletions.
There are no files selected for viewing
89 changes: 89 additions & 0 deletions
89
package-scripts/linting-rules/no-unused-textures-in-models.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
const { readFileSync } = require('fs'); | ||
const { differenceBy, uniq } = require('lodash'); | ||
|
||
const name = 'no-unused-textures-in-models'; | ||
const applicableExtensions = ['.ajmodel']; | ||
|
||
/** | ||
* Iterates through each face of each cube (the model's default variant) | ||
* and returns a (deduplicated) list of found textures | ||
*/ | ||
const getDefaultVariantTextures = (ajmodel) => { | ||
const defaultVariantTextureIdxs = []; | ||
const cubes = ajmodel.elements.filter(({ type }) => type === 'cube'); | ||
for (const element of cubes) { | ||
// side = 'north', 'south', etc. | ||
for (const side of Object.keys(element.faces)) { | ||
const textureIdx = element.faces[side].texture; | ||
if (typeof textureIdx !== 'undefined') { | ||
defaultVariantTextureIdxs.push(textureIdx); | ||
} | ||
} | ||
} | ||
// Deduplicate and convert from texture-index to texture object | ||
const defaultTextures = uniq(defaultVariantTextureIdxs).map( | ||
(idx) => ajmodel.textures[idx], | ||
); | ||
|
||
return defaultTextures; | ||
}; | ||
|
||
/** | ||
* Takes in an allowlist of texture UUIDs (ones that are used in the default variant), | ||
* iterates through a model's variants, and returns a list of (target) textures from | ||
* each variant's texture map whose source texture was found in the allowlist | ||
*/ | ||
const getAllowedVariantTextures = (ajmodel, allowlist) => { | ||
const allowedVariantTextures = []; | ||
for (const variant of ajmodel.animated_java.variants) { | ||
// Skip the default variant since that's what our allowlist consists of | ||
if (variant.default) { | ||
continue; | ||
} | ||
for (const [source, target] of Object.entries(variant.textureMap)) { | ||
if (allowlist.includes(source)) { | ||
allowedVariantTextures.push( | ||
ajmodel.textures.find(({ uuid }) => uuid === target), | ||
); | ||
} | ||
} | ||
} | ||
return uniq(allowedVariantTextures); | ||
}; | ||
|
||
/** Errors for textures defined in a model file that aren't actually used by any cube's face (in any variant) */ | ||
const noUnusedTexturesInModels = (file) => { | ||
// Return early if file does not match any applicable extension | ||
if (applicableExtensions.every((extension) => !file.endsWith(extension))) { | ||
return []; | ||
} | ||
|
||
const errors = []; | ||
|
||
const ajmodel = JSON.parse(readFileSync(file, 'utf8')); | ||
|
||
const defaultTextures = getDefaultVariantTextures(ajmodel); | ||
const defaultTextureUuids = defaultTextures.map(({ uuid }) => uuid); | ||
const variantTextures = getAllowedVariantTextures( | ||
ajmodel, | ||
defaultTextureUuids, | ||
); | ||
|
||
const allSeenTextures = uniq(defaultTextures.concat(variantTextures)); | ||
const unusedTextures = differenceBy( | ||
ajmodel.textures, | ||
allSeenTextures, | ||
'uuid', | ||
); | ||
if (unusedTextures.length > 0) { | ||
const unusedTextureNames = unusedTextures.map(({ name }) => name); | ||
errors.push(`delete unused textures: [ ${unusedTextureNames.join(', ')} ]`); | ||
} | ||
|
||
return errors; | ||
}; | ||
|
||
module.exports = { | ||
function: noUnusedTexturesInModels, | ||
name, | ||
}; |