Added support for .tga textures

This commit is contained in:
Lucas Dower 2022-11-21 18:40:45 +00:00
parent 17e7d0ab59
commit 08aafe046b
No known key found for this signature in database
GPG Key ID: B3EE6B8499593605
11 changed files with 144 additions and 21 deletions

1
.gitignore vendored
View File

@ -9,6 +9,7 @@ ObjToSchematic-darwin-x64
/res/palettes/empty.palette
/dist
/dev
/gen
/tools/blocks
/tools/models
/tests/out

62
package-lock.json generated
View File

@ -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
}
}
}
}

View File

@ -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"
}
}
}

View File

@ -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 = (<HTMLCanvasElement>document.getElementById('canvas')).getContext('webgl');
if (!gl) {
throw Error('Could not load WebGL context');

View File

@ -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);
}
}
}
}

View File

@ -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;
}
}

View File

@ -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]) {

View File

@ -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':

View File

@ -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`));
}
}

View File

@ -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/');
}
}

View File

@ -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/'));
};