Skip to content
Open
Show file tree
Hide file tree
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
3 changes: 2 additions & 1 deletion bin/minecraft-render.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ program
.option('-p, --plane', 'debugging plane and axis', 0)
.option('-A, --no-animation', 'disables apng generation')
.option('-f, --filter <regex>', 'regex pattern to filter blocks by name')
.option('-n, --namespace <regex>', 'namespace of the models', 'minecraft')
.version(package.version)
.parse(process.argv);

Expand All @@ -28,7 +29,7 @@ if (!program.args.length) {
async function Main() {
Logger.level = options.verbose;

const minecraft = Minecraft.open(path.resolve(program.args[0]));
const minecraft = Minecraft.open(path.resolve(program.args[0]), options.namespace);
const blocks = filterByRegex(options.filter, await minecraft.getBlockList());

let i = 0;
Expand Down
230 changes: 122 additions & 108 deletions src/minecraft.ts
Original file line number Diff line number Diff line change
@@ -1,133 +1,147 @@
import { destroyRenderer, prepareRenderer, render } from "./render";
import { Jar } from "./utils/jar";
import type { AnimationMeta, BlockModel, Renderer, RendererOptions } from "./utils/types";
//@ts-ignore
import * as deepAssign from 'assign-deep';
import * as deepAssign from 'assign-deep'
import { destroyRenderer, prepareRenderer, render } from './render'
import { Jar } from './utils/jar'
import type { AnimationMeta, BlockModel, Renderer, RendererOptions } from './utils/types'

export class Minecraft {
protected jar: Jar
protected renderer!: Renderer | null;
protected _cache: { [key: string]: any } = {};

protected constructor(public file: string | Jar) {
if (file instanceof Jar) {
this.jar = file;
} else {
this.jar = Jar.open(file);
}
}

static open(file: string | Jar) {
return new Minecraft(file);
}

async getBlockNameList(): Promise<string[]> {
return (await this.jar.entries('assets/minecraft/models/block'))
.filter(entry => entry.name.endsWith(".json"))
.map(entry => entry.name.slice('assets/minecraft/models/block/'.length, -('.json'.length)));
}

async getBlockList(): Promise<BlockModel[]> {
return await Promise.all((await this.getBlockNameList()).map(block => this.getModel(block)));
}

async getModelFile<T = BlockModel>(name = 'block/block'): Promise<T> {
if (name.startsWith('minecraft:')) {
name = name.substring('minecraft:'.length);
}

if (name.indexOf('/') == -1) {
name = `block/${name}`;
}

const path = `assets/minecraft/models/${name}.json`;

try {
if (this._cache[path]) {
return JSON.parse(JSON.stringify(this._cache[path]));
protected jar: Jar
protected renderer!: Renderer | null
protected _cache: { [key: string]: any } = {}

protected constructor(
public file: string | Jar,
protected readonly defaultNamespace = 'minecraft'
) {
if (file instanceof Jar) {
this.jar = file
} else {
this.jar = Jar.open(file)
}
}

protected id(name: string) {
if (name.includes(':')) {
const [namespace, id] = name.split(':')
return { namespace, id }
} else {
return { namespace: 'minecraft', id: name }
}
}

static open(file: string | Jar, namespace?: string) {
return new Minecraft(file, namespace)
}

async getBlockNameList(namespace = this.defaultNamespace): Promise<string[]> {
return (await this.jar.entries(`assets/${namespace}/models/block`))
.filter(entry => entry.name.endsWith('.json'))
.map(
entry =>
namespace +
':' +
entry.name.slice(`assets/${namespace}/models/block/`.length, -'.json'.length)
)
}

async getBlockList(namespace = this.defaultNamespace): Promise<BlockModel[]> {
return await Promise.all(
(await this.getBlockNameList(namespace)).map(block => this.getModel(block))
)
}

async getModelFile<T = BlockModel>(name = 'block/block'): Promise<T> {
let { namespace, id } = this.id(name)

if (id.indexOf('/') == -1) {
id = `block/${id}`
}

this._cache[path] = await this.jar.readJson(path);
const path = `assets/${namespace}/models/${id}.json`

return this._cache[path];
} catch (e) {
throw new Error(`Unable to find model file: ${path}`);
}
}
try {
if (this._cache[path]) {
return JSON.parse(JSON.stringify(this._cache[path]))
}

async getTextureFile(name: string = '') {
name = name ?? '';
if (name.startsWith('minecraft:')) {
name = name.substring('minecraft:'.length);
}
this._cache[path] = await this.jar.readJson(path)

const path = `assets/minecraft/textures/${name}.png`;
return this._cache[path]
} catch (e) {
throw new Error(`Unable to find model file: ${path}`)
}
}

try {
return await this.jar.read(path);
} catch (e) {
throw new Error(`Unable to find texture file: ${path}`);
}
}
async getTextureFile(name: string = '') {
const { namespace, id } = this.id(name)

const path = `assets/${namespace}/textures/${id}.png`

async getTextureMetadata(name: string = ''): Promise<AnimationMeta | null> {
name = name ?? '';
if (name.startsWith('minecraft:')) {
name = name.substring('minecraft:'.length);
}
try {
return await this.jar.read(path)
} catch (e) {
throw new Error(`Unable to find texture file: ${path}`)
}
}

const path = `assets/minecraft/textures/${name}.png.mcmeta`;
async getTextureMetadata(name: string = ''): Promise<AnimationMeta | null> {
const { namespace, id } = this.id(name)

try {
return await this.jar.readJson(path);
} catch (e) {
return null;
}
}
const path = `assets/${namespace}/textures/${name}.png.mcmeta`

async *render(blocks: BlockModel[], options?: RendererOptions) {
try {
await this.prepareRenderEnvironment(options);
try {
return await this.jar.readJson(path)
} catch (e) {
return null
}
}

for (const block of blocks) {
yield await render(this, block);
async *render(blocks: BlockModel[], options?: RendererOptions) {
try {
await this.prepareRenderEnvironment(options)

for (const block of blocks) {
yield await render(this, block)
}
} finally {
await this.cleanupRenderEnvironment()
}
} finally {
await this.cleanupRenderEnvironment();
}
}
}

async getModel(blockName: string): Promise<BlockModel> {
let { parent, ...model } = await this.getModelFile(blockName);
async renderSingle(block: BlockModel) {
return await render(this, block)
}

if (parent) {
model = deepAssign({}, await this.getModel(parent), model);
async getModel(blockName: string): Promise<BlockModel> {
let { parent, ...model } = await this.getModelFile(blockName)

if (!model.parents) {
model.parents = [];
}
if (parent) {
model = deepAssign({}, await this.getModel(parent), model)

model.parents.push(parent);
}
if (!model.parents) {
model.parents = []
}

model.parents.push(parent)
}

return deepAssign(model, { blockName });
}
return deepAssign(model, { blockName })
}

async close() {
await this.jar.close();
}
async close() {
await this.jar.close()
}

async prepareRenderEnvironment(options: RendererOptions = {}) {
this.renderer = await prepareRenderer(options)
}
async prepareRenderEnvironment(options: RendererOptions = {}) {
this.renderer = await prepareRenderer(options)
}

async cleanupRenderEnvironment() {
await destroyRenderer(this.renderer!);
this.renderer = null;
}
async cleanupRenderEnvironment() {
await destroyRenderer(this.renderer!)
this.renderer = null
}

getRenderer() {
return this.renderer!;
}
}
getRenderer() {
return this.renderer!
}
}
Loading