src/functions.js
/**
* Default asset root
* @type {string}
*/
export const DEFAULT_ROOT = "https://assets.mcasset.cloud/1.13";
/**
* Texture cache
* @type {Object.<string,string>}
*/
const textureCache = {};
/**
* Texture callbacks
* @type {Object.<string,function[]>}
*/
const textureCallbacks = {};
/**
* Model cache
* @type {Object.<string,string>}
*/
const modelCache = {};
/**
* Model callbacks
* @type {Object.<string,function[]>}
*/
const modelCallbacks = {};
/**
* Loads a Mincraft texture an returns it as Base64
*
* @param {string} root Asset root, see {@link DEFAULT_ROOT}
* @param {string} namespace Namespace, usually 'minecraft'
* @param {string} dir Directory of the texture
* @param {string} name Name of the texture
* @returns {Promise<string>}
*/
export function loadTextureAsBase64(root, namespace, dir, name) {
return new Promise((resolve, reject) => {
loadTexture(root, namespace, dir, name, resolve, reject);
})
};
/**
* Load a texture as base64 - shouldn't be used directly
* @see loadTextureAsBase64
* @ignore
*/
function loadTexture(root, namespace, dir, name, resolve, reject, forceLoad) {
let path = "/assets/" + namespace + "/textures" + dir + name + ".png";
if (textureCache.hasOwnProperty(path)) {
if (textureCache[path] === "__invalid") {
reject();
return;
}
resolve(textureCache[path]);
return;
}
if (!textureCallbacks.hasOwnProperty(path) || textureCallbacks[path].length === 0 || forceLoad) {
// https://gist.github.com/oliyh/db3d1a582aefe6d8fee9 / https://stackoverflow.com/questions/20035615/using-raw-image-data-from-ajax-request-for-data-uri
let xhr = new XMLHttpRequest();
xhr.open('GET', root + path, true);
xhr.responseType = 'arraybuffer';
xhr.onloadend = function () {
if (xhr.status === 200) {
let arr = new Uint8Array(xhr.response || xhr.responseText);
let raw = String.fromCharCode.apply(null, arr);
let b64 = btoa(raw);
let dataURL = "data:image/png;base64," + b64;
textureCache[path] = dataURL;
if (textureCallbacks.hasOwnProperty(path)) {
while (textureCallbacks[path].length > 0) {
let cb = textureCallbacks[path].shift(0);
cb[0](dataURL);
}
}
} else {
if (DEFAULT_ROOT === root) {
textureCache[path] = "__invalid";
if (textureCallbacks.hasOwnProperty(path)) {
while (textureCallbacks[path].length > 0) {
let cb = textureCallbacks[path].shift(0);
cb[1]();
}
}
} else {
loadTexture(DEFAULT_ROOT, namespace, dir, name, resolve, reject, true)
}
}
};
xhr.send();
// init array
if (!textureCallbacks.hasOwnProperty(path))
textureCallbacks[path] = [];
}
// add the promise callback
textureCallbacks[path].push([resolve, reject]);
}
/**
* Loads a blockstate file and returns the contained JSON
* @param {string} state Name of the blockstate
* @param {string} assetRoot Asset root, see {@link DEFAULT_ROOT}
* @returns {Promise<object>}
*/
export function loadBlockState(state, assetRoot) {
return loadJsonFromPath(assetRoot, "/assets/minecraft/blockstates/" + state + ".json")
};
export function loadTextureMeta(texture, assetRoot) {
return loadJsonFromPath(assetRoot, "/assets/minecraft/textures/block/" + texture + ".png.mcmeta")
}
/**
* Loads a model file and returns the contained JSON
* @param {string} root Asset root, see {@link DEFAULT_ROOT}
* @param {string} path Path to the model file
* @returns {Promise<object>}
*/
export function loadJsonFromPath(root, path) {
return new Promise((resolve, reject) => {
loadJsonFromPath_(root, path, resolve, reject);
})
}
/**
* Load a model - shouldn't used directly
* @see loadJsonFromPath
* @ignore
*/
export function loadJsonFromPath_(root, path, resolve, reject, forceLoad) {
if (modelCache.hasOwnProperty(path)) {
if (modelCache[path] === "__invalid") {
reject();
return;
}
resolve(Object.assign({}, modelCache[path]));
return;
}
if (!modelCallbacks.hasOwnProperty(path) || modelCallbacks[path].length === 0 || forceLoad) {
console.log(root + path)
fetch(root + path, {
mode: "cors",
redirect: "follow"
})
.then(response => response.json())
.then(data => {
console.log("json data:", data);
modelCache[path] = data;
if (modelCallbacks.hasOwnProperty(path)) {
while (modelCallbacks[path].length > 0) {
let dataCopy = Object.assign({}, data);
let cb = modelCallbacks[path].shift(0);
cb[0](dataCopy);
}
}
})
.catch((err) => {
console.warn(err);
if (DEFAULT_ROOT === root) {
modelCache[path] = "__invalid";
if (modelCallbacks.hasOwnProperty(path)) {
while (modelCallbacks[path].length > 0) {
let cb = modelCallbacks[path].shift(0);
cb[1]();
}
}
} else {
// Try again with default root
loadJsonFromPath_(DEFAULT_ROOT, path, resolve, reject, true);
}
});
if (!modelCallbacks.hasOwnProperty(path))
modelCallbacks[path] = [];
}
modelCallbacks[path].push([resolve, reject]);
}
/**
* Scales UV values
* @param {number} uv UV value
* @param {number} size
* @param {number} [scale=16]
* @returns {number}
*/
export function scaleUv(uv, size, scale) {
if (uv === 0) return 0;
return size / (scale || 16) * uv;
}
// https://gist.github.com/remy/784508
export function trimCanvas(c) {
let ctx = c.getContext('2d'),
copy = document.createElement('canvas').getContext('2d'),
pixels = ctx.getImageData(0, 0, c.width, c.height),
l = pixels.data.length,
i,
bound = {
top: null,
left: null,
right: null,
bottom: null
},
x, y;
for (i = 0; i < l; i += 4) {
if (pixels.data[i + 3] !== 0) {
x = (i / 4) % c.width;
y = ~~((i / 4) / c.width);
if (bound.top === null) {
bound.top = y;
}
if (bound.left === null) {
bound.left = x;
} else if (x < bound.left) {
bound.left = x;
}
if (bound.right === null) {
bound.right = x;
} else if (bound.right < x) {
bound.right = x;
}
if (bound.bottom === null) {
bound.bottom = y;
} else if (bound.bottom < y) {
bound.bottom = y;
}
}
}
let trimHeight = bound.bottom - bound.top,
trimWidth = bound.right - bound.left,
trimmed = ctx.getImageData(bound.left, bound.top, trimWidth, trimHeight);
copy.canvas.width = trimWidth;
copy.canvas.height = trimHeight;
copy.putImageData(trimmed, 0, 0);
// open new window with trimmed image:
return copy.canvas;
}