Reference Source

src/entity/index.js

import * as THREE from "three";
import * as $ from 'jquery';
import merge from 'deepmerge'
import Render, { defaultOptions } from "../renderBase";
import { loadTextureAsBase64, scaleUv, DEFAULT_ROOT, loadJsonFromPath, loadBlockState, loadTextureMeta } from "../functions";
import GuiRender from "../gui";


const FACE_ORDER = ["left", "right", "top", "bottom", "front", "back"];


/**
 * @see defaultOptions
 * @property {string} [assetRoot=DEFAULT_ROOT] root to get asset files from
 */
let defOptions = {
    camera: {
        type: "perspective",
        x: 35,
        y: 25,
        z: 20,
        target: [0, 16, 0]
    },
    assetRoot: DEFAULT_ROOT
};

/**
 * A renderer for Minecraft entities
 */
class EntityRender extends Render {


    /**
     * @param {Object} [options] The options for this renderer, see {@link defaultOptions}
     * @param {string} [options.assetRoot=DEFAULT_ROOT] root to get asset files from
     *
     * @param {HTMLElement} [element=document.body] DOM Element to attach the renderer to - defaults to document.body
     * @constructor
     */
    constructor(options, element) {
        super(options, defOptions, element);

        this.renderType = "EntityRender";

        this.entities = [];
        this.attached = false;
    }


    render(entities, cb) {
        let entityRender = this;

        if (!entityRender.attached && !entityRender._scene) {// Don't init scene if attached, since we already have an available scene
            super.initScene(function () {
                entityRender.element.dispatchEvent(new CustomEvent("entityRender", {detail: {entities: entityRender.entities}}));
            });
        } else {
            console.log("[EntityRender] is attached - skipping scene init");
        }

        let promises = [];
        for (let i = 0; i < entities.length; i++) {
            promises.push(new Promise((resolve) => {
                let entity = entities[i];
                console.log(entity)

                if (typeof entity !== "object") {
                    entity = {
                        model: entity,
                        texture: entity,
                        textureScale: 1
                    }
                }

                if (!entity.textureScale) entity.textureScale = 1;

                getEntityModel(entity.model)
                    .then(modelData => mergeParents(modelData))
                    .then((mergedModel) => {
                        console.log("Merged:")
                        console.log(mergedModel)
                        loadTextureAsBase64(entityRender.options.assetRoot, "minecraft", "/entity/", entity.texture).then((texture) => {
                            new THREE.TextureLoader().load(texture, function (textureData) {
                                textureData.magFilter = THREE.NearestFilter;
                                textureData.minFilter = THREE.NearestFilter;
                                textureData.anisotropy = 0;
                                textureData.needsUpdate = true;

                                renderEntity(entityRender, mergedModel, textureData, entity.textureScale).then((renderedEntity) => {
                                    entityRender.addToScene(renderedEntity);
                                    entityRender.entities.push(renderedEntity);
                                    resolve();
                                })
                            });
                        }).catch(() => {
                            console.warn("Missing texture for entity " + entity.texture);
                        })
                    }).catch(() => {
                    console.warn("No model file found for entity " + entity.model);
                })
            }))
        }

        Promise.all(promises).then(() => {
            if (typeof cb === "function") cb();
        })
    }


}

function renderEntity(entityRender, modelData, texture, textureScale) {
    console.log(modelData)
    return new Promise((resolve) => {
        let entityGroup = new THREE.Object3D();
        for (let g in modelData.groups) {
            if (modelData.groups.hasOwnProperty(g)) {
                let group = modelData.groups[g];

                let cubeGroup = new THREE.Object3D();
                cubeGroup.name = group.name;

                if (group.pivot) {
                    cubeGroup.applyMatrix(new THREE.Matrix4().makeTranslation(group.pivot[0], group.pivot[1], group.pivot[2]));
                }
                if (group.pos) {// There's a pos tag and I have absolutely no idea why the f* it's even there, since it just messes up everything
                    // cubeGroup.applyMatrix(new THREE.Matrix4().makeTranslation(group.pos[0], group.pos[1], group.pos[2]))
                }
                if (group.rotation && group.rotation.length === 3) {
                    cubeGroup.rotation.x = group.rotation[0];
                    cubeGroup.rotation.y = group.rotation[1];
                    cubeGroup.rotation.z = group.rotation[2];
                }


                for (let i = 0; i < group.cubes.length; i++) {
                    let cube = group.cubes[i];

                    let cubeContainer = new THREE.Object3D();
                    cubeContainer.applyMatrix(new THREE.Matrix4().makeTranslation(cube.origin[0], cube.origin[1], -cube.origin[2]));
                    if (group.pivot)
                        cubeContainer.applyMatrix(new THREE.Matrix4().makeTranslation(-group.pivot[0], -group.pivot[1], -group.pivot[2]));


                    let cubeMesh = createCube(cube.size[0], cube.size[1], cube.size[2], group.name + "_" + i, cube.uv, 0x000000, texture, cube.mirror || group.mirror, textureScale);


                    cubeMesh.translateOnAxis(new THREE.Vector3(0, 0, 1), cube.size[2]);
                    // Center the cube
                    cubeMesh.applyMatrix(new THREE.Matrix4().makeTranslation(cube.size[0] / 2, cube.size[1] / 2, -cube.size[2] / 2));

                    cubeContainer.add(cubeMesh);

                    if (entityRender.options.showOutlines) {
                        let geo = new THREE.WireframeGeometry(cubeMesh.geometry);
                        let mat = new THREE.LineBasicMaterial({color: 0x000000, linewidth: 2});
                        let line = new THREE.LineSegments(geo, mat);
                        line.name = cubeMesh.name + "_outline";

                        line.position.x = cubeMesh.position.x;
                        line.position.y = cubeMesh.position.y;
                        line.position.z = cubeMesh.position.z;

                        line.rotation.x = cubeMesh.rotation.x;
                        line.rotation.y = cubeMesh.rotation.y;
                        line.rotation.z = cubeMesh.rotation.z;

                        line.scale.set(1.01, 1.01, 1.01);

                        cubeContainer.add(line);

                        let box = new THREE.BoxHelper(cubeMesh, 0xff0000);
                        cubeContainer.add(box);
                    }


                    cubeGroup.add(cubeContainer);
                }

                entityGroup.add(cubeGroup);
            }
        }

        resolve(entityGroup);
    })
}


let createCube = function (width, height, depth, name, uv, color, texture, mirror, textureScale) {
    let geometry = new THREE.BoxGeometry(width, height, depth);
    let material;
    if (texture) {
        material = new THREE.MeshBasicMaterial({
            map: texture,
            side: THREE.DoubleSide,
            transparent: true,
            alphaTest: 0.5
        });
    } else {
        material = new THREE.MeshBasicMaterial({
            color: color,
            wireframe: true
        });
    }

    if (texture) {
        applyCubeTextureToGeometry(geometry, texture, uv, mirror, textureScale);
    }


    let cube = new THREE.Mesh(geometry, material);
    cube.name = name;
    cube.receiveShadow = true;

    return cube;
};

let applyCubeTextureToGeometry = function (geometry, texture, uv, mirror, textureScale) {
    let w = texture.image.width;
    let h = texture.image.height;

    geometry.computeBoundingBox();
    geometry.faceVertexUvs[0] = [];
    let faceUvs = [];
    for (let i = 0; i < 6; i++) {
        let tx1 = uv[FACE_ORDER[i]][0] * textureScale;
        let ty1 = h - uv[FACE_ORDER[i]][1] * textureScale;
        let tx2 = uv[FACE_ORDER[i]][2] * textureScale;
        let ty2 = h - uv[FACE_ORDER[i]][3] * textureScale;


        let flipY = false;
        let flipX = false;

        if (mirror) {
            flipX = true;
        }
        if (FACE_ORDER[i] === "front" || FACE_ORDER[i] === "left" || FACE_ORDER[i] === "right") flipY = true;

        tx1 /= w;
        ty1 /= h;
        tx2 /= w;
        ty2 /= h;

        faceUvs[i] = [
            new THREE.Vector2(tx1, ty2),
            new THREE.Vector2(tx1, ty1),
            new THREE.Vector2(tx2, ty1),
            new THREE.Vector2(tx2, ty2)
        ];

        let temp;
        if (flipY) {
            temp = faceUvs[i].slice(0);
            faceUvs[i][0] = temp[2];
            faceUvs[i][1] = temp[3];
            faceUvs[i][2] = temp[0];
            faceUvs[i][3] = temp[1]
        }
        if (flipX) {//flip x
            temp = faceUvs[i].slice(0);
            faceUvs[i][0] = temp[3];
            faceUvs[i][1] = temp[2];
            faceUvs[i][2] = temp[1];
            faceUvs[i][3] = temp[0]
        }

    }

    let j = 0;
    for (let i = 0; i < faceUvs.length; i++) {
        geometry.faceVertexUvs[0][j] = [faceUvs[i][0], faceUvs[i][1], faceUvs[i][3]];
        geometry.faceVertexUvs[0][j + 1] = [faceUvs[i][1], faceUvs[i][2], faceUvs[i][3]];
        j += 2;
    }
    geometry.uvsNeedUpdate = true;
};


function getEntityModel(entity) {
    return new Promise((resolve, reject) => {
        $.ajax("https://minerender.org/res/models/entities/" + entity + ".json")
            .done((data) => {
                resolve(data);
            })
            .fail(() => {
                reject();
            })
    })
}

const overwriteMerge = (destinationArray, sourceArray, options) => {
    return sourceArray;
};

let mergeParents = function (model) {
    return new Promise((resolve, reject) => {
        mergeParents_(model, [], resolve, reject);
    });
};
let mergeParents_ = function (model, stack, resolve, reject) {
    console.log(stack)

    stack.push(model);

    if (!model.hasOwnProperty("parent")) {// already at the highest parent
        stack.reverse();
        let merged = {};
        for (let i = 0; i < stack.length; i++) {
            console.log(i)
            merged = merge(merged, stack[i], {arrayMerge: overwriteMerge});
            console.log(merged)
        }

        resolve(merged);
        return;
    }

    let parent = model["parent"];
    delete model["parent"];// remove the child's parent so it will be replaced by the parent's parent

    getEntityModel(parent).then((parentData) => {
        // let mergedModel = Object.assign({}, model, parentData);
        mergeParents_(parentData, stack, resolve, reject);
    })


};

if (typeof window !== "undefined")
    window.EntityRender = EntityRender;
if (typeof global !== "undefined")
    global.EntityRender = EntityRender;

export default EntityRender;