diff --git a/.gitignore b/.gitignore index aeb0dec..1c3b809 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ ObjToSchematic-darwin-x64 /res/palettes/empty.palette /dist /dev +/gen /tools/blocks /tools/models /tests/out diff --git a/package-lock.json b/package-lock.json index c0d959e..7d7da04 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "jpeg-js": "^0.4.4", "pngjs": "^6.0.0", "prismarine-nbt": "^1.6.0", + "tga": "^1.0.7", "twgl.js": "^4.19.1", "varint-array": "^0.0.0" }, @@ -6629,6 +6630,11 @@ "lowercase-keys": "^1.0.0" } }, + "node_modules/restructure": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/restructure/-/restructure-2.0.1.tgz", + "integrity": "sha512-e0dOpjm5DseomnXx2M5lpdZ5zoHqF1+bqdMJUohoYVVQa7cBdnk7fdmeI6byNWP/kiME72EeTiSypTCVnpLiDg==" + }, "node_modules/reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", @@ -7257,6 +7263,31 @@ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "dev": true }, + "node_modules/tga": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/tga/-/tga-1.0.7.tgz", + "integrity": "sha512-GFVJwov5aJTMgh8U1QfaRheIELXo+dYc1qYIvQEIqZX4n+S6Fj/SDWsdbelHt7WP08xOR6W1z5aJQ+Ilh5gIeA==", + "dependencies": { + "debug": "^2.6.1", + "restructure": "^2.0.0" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/tga/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/tga/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, "node_modules/throat": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/throat/-/throat-6.0.1.tgz", @@ -13020,6 +13051,11 @@ "lowercase-keys": "^1.0.0" } }, + "restructure": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/restructure/-/restructure-2.0.1.tgz", + "integrity": "sha512-e0dOpjm5DseomnXx2M5lpdZ5zoHqF1+bqdMJUohoYVVQa7cBdnk7fdmeI6byNWP/kiME72EeTiSypTCVnpLiDg==" + }, "reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", @@ -13481,6 +13517,30 @@ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "dev": true }, + "tga": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/tga/-/tga-1.0.7.tgz", + "integrity": "sha512-GFVJwov5aJTMgh8U1QfaRheIELXo+dYc1qYIvQEIqZX4n+S6Fj/SDWsdbelHt7WP08xOR6W1z5aJQ+Ilh5gIeA==", + "requires": { + "debug": "^2.6.1", + "restructure": "^2.0.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + } + } + }, "throat": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/throat/-/throat-6.0.1.tgz", @@ -13978,4 +14038,4 @@ "dev": true } } -} \ No newline at end of file +} diff --git a/package.json b/package.json index 5635092..4c0526f 100644 --- a/package.json +++ b/package.json @@ -62,7 +62,8 @@ "jpeg-js": "^0.4.4", "pngjs": "^6.0.0", "prismarine-nbt": "^1.6.0", + "tga": "^1.0.7", "twgl.js": "^4.19.1", "varint-array": "^0.0.0" } -} \ No newline at end of file +} diff --git a/src/app_context.ts b/src/app_context.ts index 5048e27..8ec8cea 100644 --- a/src/app_context.ts +++ b/src/app_context.ts @@ -18,6 +18,7 @@ import { UI } from './ui/layout'; import { UIMessageBuilder, UITreeBuilder } from './ui/misc'; import { ColourSpace, EAction } from './util'; import { ASSERT } from './util/error_util'; +import { FileUtil } from './util/file_util'; import { LOG_ERROR, Logger } from './util/log_util'; import { AppPaths, PathUtil } from './util/path_util'; import { Vector3 } from './vector'; @@ -42,6 +43,8 @@ export class AppContext { AppConfig.Get.dumpConfig(); + FileUtil.rmdirIfExist(AppPaths.Get.gen); + const gl = (document.getElementById('canvas')).getContext('webgl'); if (!gl) { throw Error('Could not load WebGL context'); diff --git a/src/mesh.ts b/src/mesh.ts index 56c4542..c86f892 100644 --- a/src/mesh.ts +++ b/src/mesh.ts @@ -4,7 +4,7 @@ import path from 'path'; import { Bounds } from './bounds'; import { RGBA, RGBAColours, RGBAUtil } from './colour'; import { StatusHandler } from './status'; -import { Texture, TextureFiltering } from './texture'; +import { Texture, TextureConverter, TextureFiltering } from './texture'; import { Triangle, UVTriangle } from './triangle'; import { getRandomID, UV } from './util'; import { AppError, ASSERT } from './util/error_util'; @@ -215,10 +215,6 @@ export class Mesh { if (material.type === MaterialType.textured) { ASSERT(path.isAbsolute(material.path), 'Material texture path not absolute'); if (!fs.existsSync(material.path)) { - //StatusHandler.Get.add( - // 'warning', - // `Could not find ${material.path}`, - //); LOG_WARN(`Could not find ${material.path} for material ${materialName}, changing to solid-white material`); this._materials[materialName] = { type: MaterialType.solid, @@ -227,6 +223,11 @@ export class Mesh { canBeTextured: true, set: false, }; + } else { + const parsedPath = path.parse(material.path); + if (parsedPath.ext === '.tga') { + material.path = TextureConverter.createPNGfromTGA(material.path); + } } } } diff --git a/src/texture.ts b/src/texture.ts index 99701f6..bfee857 100644 --- a/src/texture.ts +++ b/src/texture.ts @@ -2,13 +2,16 @@ import * as fs from 'fs'; import * as jpeg from 'jpeg-js'; import path from 'path'; import { PNG } from 'pngjs'; +const TGA = require('tga'); import { RGBA, RGBAColours, RGBAUtil } from './colour'; import { AppConfig } from './config'; import { clamp, wayThrough } from './math'; import { UV } from './util'; import { AppError, ASSERT } from './util/error_util'; -import { LOG, LOG_ERROR } from './util/log_util'; +import { FileUtil } from './util/file_util'; +import { LOG, LOG_ERROR, LOGF } from './util/log_util'; +import { AppPaths } from './util/path_util'; /* eslint-disable */ export enum TextureFormat { @@ -49,15 +52,31 @@ export class Texture { const filePath = path.parse(filename); try { const data = fs.readFileSync(filename); - if (filePath.ext.toLowerCase() === '.png') { - return PNG.sync.read(data); - } else if (['.jpg', '.jpeg'].includes(filePath.ext.toLowerCase())) { - this._useAlphaChannelValue = false; - return jpeg.decode(data, { - maxMemoryUsageInMB: AppConfig.Get.MAXIMUM_IMAGE_MEM_ALLOC, - }); + + switch (filePath.ext.toLowerCase()) { + case '.png': { + return PNG.sync.read(data); + } + case '.jpg': + case '.jpeg': { + this._useAlphaChannelValue = false; + return jpeg.decode(data, { + maxMemoryUsageInMB: AppConfig.Get.MAXIMUM_IMAGE_MEM_ALLOC, + }); + } + /* + case '.tga': { + const tga = new TGA(data); + return { + width: tga.width, + height: tga.height, + data: tga.pixels, + }; + } + */ + default: + ASSERT(false, 'Unsupported image format'); } - ASSERT(false); } catch (err) { LOG_ERROR(err); throw new AppError(`Could not read ${filename}`); @@ -165,3 +184,24 @@ export class Texture { }; } } + +export class TextureConverter { + public static createPNGfromTGA(filepath: string): string { + ASSERT(fs.existsSync(filepath), '.tga does not exist'); + const parsed = path.parse(filepath); + ASSERT(parsed.ext === '.tga'); + const data = fs.readFileSync(filepath); + const tga = new TGA(data); + const png = new PNG({ + width: tga.width, + height: tga.height, + }); + png.data = tga.pixels; + FileUtil.mkdirIfNotExist(AppPaths.Get.gen); + const buffer = PNG.sync.write(png); + const newTexturePath = path.join(AppPaths.Get.gen, parsed.name + '.gen.png'); + LOGF(`Creating new generated texture of '${filepath}' at '${newTexturePath}'`); + fs.writeFileSync(newTexturePath, buffer); + return newTexturePath; + } +} diff --git a/src/ui/elements/material.ts b/src/ui/elements/material.ts index df2d0e4..67681d1 100644 --- a/src/ui/elements/material.ts +++ b/src/ui/elements/material.ts @@ -80,7 +80,7 @@ export class TextureMaterialUIElement extends MaterialUIElement { buttonLabel: 'Load', filters: [{ name: 'Images', - extensions: ['png', 'jpeg', 'jpg'], + extensions: ['png', 'jpeg', 'jpg', 'tga'], }], }); if (files && files[0]) { diff --git a/src/util/file_util.ts b/src/util/file_util.ts index d27b9a2..8b7ff3d 100644 --- a/src/util/file_util.ts +++ b/src/util/file_util.ts @@ -1,17 +1,25 @@ import child from 'child_process'; import fs from 'fs'; +import { LOGF } from './log_util'; export namespace FileUtil { export function fileExists(absolutePath: string) { return fs.existsSync(absolutePath); } - export function mkdirSyncIfNotExist(path: fs.PathLike) { + export function mkdirIfNotExist(path: fs.PathLike) { if (!fs.existsSync(path)) { fs.mkdirSync(path); } } + export function rmdirIfExist(path: fs.PathLike) { + if (fs.existsSync(path)) { + LOGF(`Deleting '${path.toString()}'`); + fs.rmSync(path, { recursive: true, force: true }); + } + } + export function openDir(absolutePath: string) { switch (process.platform) { case 'darwin': diff --git a/src/util/log_util.ts b/src/util/log_util.ts index 68f2a4c..4bfebd3 100644 --- a/src/util/log_util.ts +++ b/src/util/log_util.ts @@ -79,7 +79,7 @@ export const TIME_END = (label: string) => { } }; -/** +/** * Logs an error to the console and file, always. */ export const LOG_ERROR = (...data: any[]) => { @@ -122,7 +122,7 @@ export class Logger { */ public initLogFile(suffix: string) { if (this._logStream === undefined && this._enabledLogToFile === true) { - FileUtil.mkdirSyncIfNotExist(AppPaths.Get.logs); + FileUtil.mkdirIfNotExist(AppPaths.Get.logs); this._logStream = fs.createWriteStream(PathUtil.join(AppPaths.Get.logs, `./${Date.now()}-${suffix}.log`)); } } diff --git a/src/util/path_util.ts b/src/util/path_util.ts index 4918fa6..bc6c90b 100644 --- a/src/util/path_util.ts +++ b/src/util/path_util.ts @@ -64,4 +64,13 @@ export class AppPaths { public get logs() { return PathUtil.join(this._base, './logs/'); } + + /** + * The `gen` directory stores any data generated at runtime. + * This can safely be deleted when the program is not running and will + * be empted upon each startup. + */ + public get gen() { + return PathUtil.join(this._base, './gen/'); + } } diff --git a/tests/preamble.ts b/tests/preamble.ts index f6edd24..6a810ca 100644 --- a/tests/preamble.ts +++ b/tests/preamble.ts @@ -5,5 +5,5 @@ import { AppPaths, PathUtil } from '../src/util/path_util'; export const TEST_PREAMBLE = () => { Logger.Get.disableLogToFile(); AppPaths.Get.setBaseDir(PathUtil.join(__dirname, '..')); - FileUtil.mkdirSyncIfNotExist(PathUtil.join(AppPaths.Get.tests, './out/')); + FileUtil.mkdirIfNotExist(PathUtil.join(AppPaths.Get.tests, './out/')); };