UNPKG

arx-level-generator

Version:
352 lines 14.3 kB
import path from 'node:path'; import { sharpToBmp } from 'sharp-bmp'; import { ClampToEdgeWrapping, Texture as ThreeJsTextue, UVMapping, MathUtils } from 'three'; import { fileExists } from './helpers.js'; import { createCacheFolderIfNotExists, loadHashOf, getHashOfFile, saveHashOf } from './services/cache.js'; import { getMetadata, getSharpInstance } from './services/image.js'; export const SIZE_UNKNOWN = -1; export const NO_TEXTURE_CONTAINER = 0; const supportedExtensions = ['.jpg', '.png', '.bmp']; export class Texture extends ThreeJsTextue { static targetPath = 'graph/obj3d/textures'; alreadyMadeTileable = false; filename; isNative; _width; _height; sourcePath; isInternalAsset; constructor(props) { super(undefined, UVMapping, ClampToEdgeWrapping, ClampToEdgeWrapping); this.filename = props.filename; this.isNative = props.isNative ?? true; this.sourcePath = props.sourcePath; this.isInternalAsset = props.isInternalAsset ?? false; this._width = props.size ?? props.width ?? SIZE_UNKNOWN; this._height = props.size ?? props.height ?? SIZE_UNKNOWN; } clone() { const copy = new Texture({ filename: this.filename, isNative: this.isNative, width: this._width, height: this._height, sourcePath: this.sourcePath, isInternalAsset: this.isInternalAsset, }); return copy; } static fromCustomFile(props) { return new Texture({ ...props, isNative: false, }); } static fromArxTextureContainer(texture) { return new Texture({ filename: texture.filename, }); } getFilename(settings) { return path.resolve(this.isInternalAsset ? settings.internalAssetsDir : settings.assetsDir, this.sourcePath ?? Texture.targetPath, this.filename); } async getWidth(settings) { if (this._width === SIZE_UNKNOWN) { const { width } = await getMetadata(this.getFilename(settings)); this._width = width ?? SIZE_UNKNOWN; } return this._width; } async getHeight(settings) { if (this._height === SIZE_UNKNOWN) { const { height } = await getMetadata(this.getFilename(settings)); this._height = height ?? SIZE_UNKNOWN; } return this._height; } /** * this also gives value to this._width and this._height * if any of them is SIZE_UNKNOWN */ async isTileable(settings) { const width = await this.getWidth(settings); const height = await this.getHeight(settings); return width === height && MathUtils.isPowerOfTwo(width); } /** * default value for needsToBeTileable is false */ async exportSourceAndTarget(settings, needsToBeTileable = false, _dontCatchTheError = false) { if (this.isNative) { throw new Error('trying to export a native Texture'); } try { const isTileable = await this.isTileable(settings); if (needsToBeTileable && !isTileable) { return this.makeTileable(settings); } else { return this.makeCopy(settings); } } catch (e) { if (_dontCatchTheError) { throw e; } console.error(`[error] Texture: file not found: "${this.filename}", using fallback texture`); const fallbackTexture = Texture.missingTexture; this.filename = fallbackTexture.filename; this.sourcePath = fallbackTexture.sourcePath; this._width = fallbackTexture._width; this._height = fallbackTexture._height; this.isInternalAsset = fallbackTexture.isInternalAsset; return this.makeCopy(settings); } } async makeCopy(settings) { const { ext, name } = path.parse(this.filename); const hasSupportedFormat = supportedExtensions.includes(ext); const newFilename = hasSupportedFormat ? this.filename : `${name}.jpg`; const originalSource = this.getFilename(settings); const convertedTarget = path.resolve(settings.outputDir, Texture.targetPath, newFilename); const convertedSourceFolder = await createCacheFolderIfNotExists(this.sourcePath ?? Texture.targetPath, settings); const convertedSource = path.join(convertedSourceFolder, newFilename); const currentHash = await getHashOfFile(originalSource); if (await fileExists(convertedSource)) { const storedHash = await loadHashOf(originalSource, settings); if (storedHash === currentHash) { return [convertedSource, convertedTarget]; } } await saveHashOf(originalSource, currentHash, settings); const image = await getSharpInstance(originalSource); switch (ext) { case '.bmp': await sharpToBmp(image, convertedSource); break; case '.png': await image.png({ quality: 100 }).toFile(convertedSource); break; default: case '.jpg': await image.jpeg({ quality: 100, progressive: false }).toFile(convertedSource); break; } return [convertedSource, convertedTarget]; } async makeTileable(settings) { const { ext, name } = path.parse(this.filename); const hasSupportedFormat = supportedExtensions.includes(ext); const newFilename = hasSupportedFormat ? this.filename : `${name}.jpg`; const originalSource = this.getFilename(settings); const convertedTarget = path.resolve(settings.outputDir, Texture.targetPath, newFilename); const convertedSourceFolder = await createCacheFolderIfNotExists(this.sourcePath ?? Texture.targetPath, settings); const convertedSource = path.join(convertedSourceFolder, newFilename); if (this.alreadyMadeTileable) { return [convertedSource, convertedTarget]; } const currentHash = await getHashOfFile(originalSource); if (await fileExists(convertedSource)) { const storedHash = await loadHashOf(originalSource, settings); if (storedHash === currentHash) { this.alreadyMadeTileable = true; return [convertedSource, convertedTarget]; } } await saveHashOf(originalSource, currentHash, settings); const image = await getSharpInstance(originalSource); const powerOfTwo = MathUtils.floorPowerOfTwo(this._width); image.resize(powerOfTwo, powerOfTwo, { fit: 'cover' }); switch (ext) { case '.bmp': await sharpToBmp(image, convertedSource); break; case '.png': await image.png({ quality: 100 }).toFile(convertedSource); break; default: case '.jpg': await image.jpeg({ quality: 100, progressive: false }).toFile(convertedSource); break; } this.alreadyMadeTileable = true; return [convertedSource, convertedTarget]; } /** * compares filenames of textures without the extensions * comparision is case insensitive * * For example: * - texture.jpg == TEXTURE.JPG * - texture.bmp == texture.jpg */ equals(texture) { const { name: aFilename } = path.parse(this.filename.toLowerCase()); const { name: bFilename } = path.parse(typeof texture === 'string' ? texture.toLowerCase() : texture.filename.toLowerCase()); return aFilename === bFilename; } equalsAny(textures) { if (textures.length === 0) { return false; } return textures.some(this.equals.bind(this)); } // ---------------- static get alpha() { return new Texture({ filename: 'alpha.bmp', size: 32 }); } static get stoneHumanPaving() { return new Texture({ filename: '[stone]_human_paving.bmp', size: 256 }); } static get stoneHumanPaving1() { return new Texture({ filename: '[stone]_human_paving1.bmp', size: 128 }); } static get aliciaRoomMur01() { return new Texture({ filename: 'aliciaroom_mur01.jpg', size: 128 }); } static get aliciaRoomMur02() { return new Texture({ filename: 'aliciaroom_mur02.jpg', size: 128 }); } static get waterCavewater() { return new Texture({ filename: '(water)cavewater.jpg', size: 128 }); } static get l1DragonStoneGround01() { return new Texture({ filename: 'l1_dragon_[stone]_ground01.jpg', size: 128 }); } static get l1DragonIceGround08() { return new Texture({ filename: 'l1_dragon_[ice]_ground08.jpg', size: 128 }); } static get l1DragonSpideLime1Nocol() { return new Texture({ filename: 'L1_Dragon_[spide]_Lime1nocol.bmp', size: 128 }); } static get l1PrisonStoneGridl02() { return new Texture({ filename: 'l1_prison_(stone)_gridl02.bmp', size: 64 }); } static get l1PrisonSandGround01() { return new Texture({ filename: 'l1_prison_[sand]_ground01.jpg', size: 64 }); } static get l1TempleStoneWall03() { return new Texture({ filename: 'l1_temple_[stone]_wall03.jpg', size: 128 }); } static get l2CavesRustyItem01() { return new Texture({ filename: 'l2_caves_[rusty]_item01.jpg', size: 128 }); } static get l2TrollStoneGround04() { return new Texture({ filename: 'l2_troll_[stone]_ground04.jpg', size: 128 }); } static get l2TrollWoodPillar08() { return new Texture({ filename: 'l2_troll_[wood]_pillar08.jpg', size: 256 }); } static get l2GobelStoneFloor02() { return new Texture({ filename: 'l2_gobel_[stone]_floor02.jpg', size: 128 }); } static get l2GobelStoneCenter() { return new Texture({ filename: 'l2_gobel_[stone]_center.jpg', size: 128 }); } static get l3DissidWetGround01() { return new Texture({ filename: 'l3_dissid_[wet]_ground01.jpg', size: 128 }); } static get l3DissidStoneGround09() { return new Texture({ filename: 'l3_dissid_[stone]_ground09.jpg', size: 128 }); } static get l3DissidStoneGround10() { return new Texture({ filename: 'l3_dissid_[stone]_ground10.jpg', size: 128 }); } static get l3DissidIronWall02() { return new Texture({ filename: 'l3_dissid_[iron]_wall02.bmp', size: 64 }); } static get l3DissidStoneWall03() { return new Texture({ filename: 'l3_dissid_[stone]_wall03.jpg', size: 128 }); } static get l3DissidStoneTrans01() { return new Texture({ filename: 'l3_dissid_[stone]_trans01.jpg', size: 128 }); } static get l3DissidWoodFabric03() { return new Texture({ filename: 'l3_dissid_[wood]_fabric03.jpg', size: 256 }); } static get l3DissidWoodFabric04() { return new Texture({ filename: 'l3_dissid_[wood]_fabric04.jpg', size: 128 }); } static get l4YlsideStoneGround01() { return new Texture({ filename: 'l4_ylside_[stone]_ground01.jpg', size: 64 }); } static get l4DwarfIronBoard02() { return new Texture({ filename: 'l4_dwarf_[iron]_board02.jpg', size: 256 }); } static get l4DwarfWoodBoard02() { return new Texture({ filename: 'l4_dwarf_[wood]_board02.jpg', size: 128 }); } static get l5CavesGravelGround05() { return new Texture({ filename: 'l5_caves_[gravel]_ground05.jpg', size: 128 }); } static get l6RatmenFleshWall03() { return new Texture({ filename: 'l6_ratmen_[flesh]_wall03.jpg', size: 256 }); } static get l7DwarfMetalPlate10() { return new Texture({ filename: 'l7_dwarf_[metal]_plate10.jpg', size: 128 }); } static get stoneHumanStoneWall() { return new Texture({ filename: '[stone]_human_stone_wall.jpg', size: 256 }); } static get stoneHumanStoneWall1() { return new Texture({ filename: '[stone]_human_stone_wall1.jpg', size: 256 }); } static get stoneHumanStoneWall2() { return new Texture({ filename: '[stone]_human_stone_wall2.jpg', size: 256 }); } static get stoneHumanAkbaa2F() { return new Texture({ filename: '[stone]_human_akbaa2_f.jpg', size: 256 }); } static get stoneHumanAkbaa4F() { return new Texture({ filename: '[stone]_human_akbaa4_f.jpg', size: 256 }); } static get stoneHumanPriest4() { return new Texture({ filename: '[stone]_human_priest4.jpg', size: 256 }); } static get itemFishingPole2() { return new Texture({ filename: 'item_fishing pole2.bmp', size: 128 }); } static get itemRope() { return new Texture({ filename: 'item_rope.bmp', size: 128 }); } static get fixinterHeavyCatacombDoor() { return new Texture({ filename: 'fixinter_heavy_catacomb_door.bmp', size: 256 }); } static get stoneGroundCavesWet05() { return new Texture({ filename: '[stone]_ground_caves_wet05', size: 128 }); } static get glassGlass01() { return new Texture({ filename: '[glass]_glass01.jpg', size: 128 }); } static get itemCheese() { return new Texture({ filename: 'item_cheese.jpg', size: 64 }); } static get itemRuneAam() { return new Texture({ filename: 'item_rune_aam', size: 64 }); } static get missingTexture() { return Texture.fromCustomFile({ filename: 'jorge-[stone].jpg', sourcePath: 'textures', size: 32, isInternalAsset: true, }); } static get uvDebugTexture() { return Texture.fromCustomFile({ filename: 'uv-reference-map-[stone].jpg', sourcePath: 'textures', size: 1024, isInternalAsset: true, }); } static get missingInventoryIcon() { return Texture.fromCustomFile({ filename: 'missing[icon].bmp', sourcePath: 'ui', size: 32, isInternalAsset: true, }); } } //# sourceMappingURL=Texture.js.map