Repository

js/Mobilizing/renderer/3D/three/shape/Mesh.js

import * as THREE from 'three';
// import Line3 from '../types/Line3';
import Vector2 from '../types/Vector2';
import Vector3 from '../types/Vector3';
import Matrix4 from '../types/Matrix4';
import Material from '../scene/Material';
import Transform from '../scene/Transform';
import * as _Math from '../../../../core/util/Math';
import EventEmitter from "../../../../core/util/EventEmitter";

/**
A Mesh is the aggregation of a geomtery, made of vertices (Vector3 Array), texture coordinates (or uv, Vector2 Array), vertexColor (Color Array) and various methods to create and modify 3D objects that have a material (see {{#crossLink "Material"}}{{/crossLink}}) and a transform (see {{#crossLink "Transform"}}{{/crossLink}}).
*/
export default class Mesh {
    /**
    * Construct an empty and unfinished Mesh for which a geometry must be constructed. Once filled with vertices, constructMesh method must be called, or a method generating a complete Mesh must be used (i.e generateFillMesh)
    *
    * @param {Object} params Parameters object, given by the constructor.
    * @param {String} params.name The name of the primitive to create, can be one of the listed names in this class description.
    * @param {String} [params.material="phong"] The name of the material to attach to the created primitive, can be one of : lambert, basic, phong.
    * @param {Object} [params.geometry=undefined] An already existing geometry
    * @param {Number} [params.Number=2000] maxVertices for dynamically defined geometry, we must use a bufferArray already filled to add vertices
    */
    constructor({
        name = undefined,
        material = "phong",
        geometry = undefined,
        type = undefined,
        maxVertices = 500
    } = {}) {
        this.name = name;
        this.material = material;
        this.geometry = geometry;
        this.maxVertices = maxVertices;

        if (!this.geometry) {
            //used for custom geom drawing
            this.vertexCount = 0;
            //used for custom normals counting
            this.normalsCount = 0;
            //used for custom UV counting
            this.uvCount = 0;

            //faces indices, defines how faces are drawn
            this.indices = [];
            //produce bufferAttributes for each array
            this.makeGeometryBuffers();
            this.attachMaterial(this.material);
            //generate the mesh from the empty geometry and material
            this.genereateMesh();
        }
        else {
            //we have a geometry, so attach the material and generate the mesh
            this.attachMaterial(this.material);
            this.genereateMesh();
        }

        this.type = type;

        this.events = new EventEmitter({ "scope": window });
    }

    setEventEmitterTrigger(scope, eventType) {

        if (scope.events) {
            scope.events.trigger(eventType, this);
            console.log(scope, eventType);
        }
    }

    /**
    * @returns the Three.js native objects used in this class
    */
    getNativeObject() {
        return this._mesh;
    }

    /**
    * @returns the Three.js native geometry of this mesh
    */
    getNativeGeometry() {
        return this.geometry;
    }

    /**
     * 
     * @param {String} name 
     */
    setRootName(name) {
        this.rootName = name;
    }

    /**
     * 
     * @param {*} val 
     */
    setMaxVertices(val) {
        this.maxVertices = val;
        this.makeGeometryBuffers();
    }

    /**
     * Generate Float32Array and BufferAttribute for this geometry
     */
    makeGeometryBuffers() {
        //fill the 3 Arrays with enough memory to draw geometry
        const vertices = new Float32Array(this.maxVertices * 3);
        const normals = new Float32Array(this.maxVertices * 3);
        const uv = new Float32Array(this.maxVertices * 2);
        const colors = new Float32Array(this.maxVertices * 3);

        //empty geometry
        if (!this.geometry) {
            this.geometry = new THREE.BufferGeometry();
        }
        //this is what we need to do to build a geometry
        //make a vertices buffer for geometries
        this.geometryVerticesAttributes = new THREE.Float32BufferAttribute(vertices, 3);
        this.geometry.setAttribute('position', this.geometryVerticesAttributes);

        //make a normal buffer
        this.geometryNormalAttributes = new THREE.Float32BufferAttribute(normals, 3);
        this.geometry.setAttribute('normal', this.geometryNormalAttributes);

        //make a UV buffer for texture mapping
        this.geometryUVAttributes = new THREE.Float32BufferAttribute(uv, 2);
        this.geometry.setAttribute('uv', this.geometryUVAttributes);

        //make a vertexColor buffer
        this.geometryColorAttributes = new THREE.Float32BufferAttribute(colors, 3);
        this.geometry.setAttribute('color', this.geometryColorAttributes);
    }

    /**
    * genereate a mesh from already made geometry and material
    */
    genereateMesh() {
        this._mesh = new THREE.Mesh(this.geometry, this.material._material);

        this.geometryVerticesAttributes = this.geometry.attributes.position;
        this.geometryNormalAttributes = this.geometry.attributes.normal;
        this.geometryUVAttributes = this.geometry.attributes.uv;

        this.transform = new Transform(this);
    }

    /**
    * Generate the mesh for the given vertices and generates flat uvs for it. Usefull for 2D shapes
    */
    generateFillMesh(vertices) {
        //main shape to hold paths
        const shape = new THREE.Shape();

        //draw shape
        shape.moveTo(vertices[0].x, vertices[0].y);
        for (let i = 1; i < vertices.length; i++) {
            shape.lineTo(vertices[i].x, vertices[i].y);
        }
        shape.closePath();

        //make a geometry from the shape
        const geometry = new THREE.ShapeGeometry(shape);

        //update buffer attributes for further manipulations
        this.geometryVerticesAttributes = geometry.attributes.position;
        this.geometryNormalAttributes = geometry.attributes.normal;
        this.geometryUVAttributes = geometry.attributes.uv;

        //construct the Mob Mesh
        this.geometry = geometry;
        this.constructMesh();

        //generate flat uvs
        this.generateFlatUVs();
    }

    /**
    * Generate the flat uvs for this mesh.
    */
    generateFlatUVs() {

        const bounds = this.getBoundingBox();

        for (let i = 0; i < this.geometryUVAttributes.count; i++) {
            const vertex = new Vector3(this.geometryVerticesAttributes.getX(i),
                this.geometryVerticesAttributes.getY(i),
                this.geometryVerticesAttributes.getZ(i)
            );
            const uv = new Vector2(
                _Math.map(vertex.x, bounds.min.x, bounds.max.x, 0, 1),
                _Math.map(vertex.y, bounds.min.y, bounds.max.y, 0, 1)
            );
            this.geometryUVAttributes.setXY(i, uv.x, uv.y);
        }
        this.geometryUVAttributes.needsUpdate = true;
    }


    /**
    * Static Utils to generate the stroke for the given vertices with the given stroke width
    * @static
    * @param {Mesh} mesh to use as a base to get vertices
    * @param {Number} inflateValue stroke width scale factor
    * @return (Shape) a Three.js Shape to make a geometry from
    */
    static generateStrokeShape(mesh, inflateValue) {

        const verticesArrayBuffer = mesh.geometry.attributes.position;
        //convert buffer back to a Vector3 array
        const vertices = [];
        for (let i = 0; i < verticesArrayBuffer.array.length; i += verticesArrayBuffer.itemSize) {
            const vec = new Vector3(
                verticesArrayBuffer.array[i],
                verticesArrayBuffer.array[i + 1],
                verticesArrayBuffer.array[i + 2]
            );
            vertices.push(vec);
        }

        //console.log(mesh, inflateValue);
        //console.log(Mesh.geometryIsCW(mesh));

        const outterVertices = [];

        const rot = Mesh.geometryIsCW(mesh) ? "ccw" : "cw";

        for (let i = 0; i < vertices.length; ++i) {

            // get this point (pt1), the point before it
            // (pt0) and the point that follows it (pt2)
            const pt0 = vertices[(i > 0) ? i - 1 : vertices.length - 1];
            const pt1 = vertices[i];
            const pt2 = vertices[(i < vertices.length - 1) ? i + 1 : 0];
            //console.log(pt0, pt1, pt2);

            // find the line vectors of the lines going
            // into the current point
            //const v01 = { x: pt1.x - pt0.x, y: pt1.y - pt0.y };
            const v01 = new Vector2(pt1.x - pt0.x, pt1.y - pt0.y);
            //const v12 = { x: pt2.x - pt1.x, y: pt2.y - pt1.y };
            const v12 = new Vector2(pt2.x - pt1.x, pt2.y - pt1.y);

            if (rot === 'ccw') {
                v01.rotate90CCW();
                v12.rotate90CCW();
            }
            else {
                v01.rotate90CW();
                v12.rotate90CW();
            }

            // find the normals of the two lines, multiplied
            // to the distance that polygon should inflate
            //const d01 = vecMul(vecUnit(rot(v01)), distance);
            //const d12 = vecMul(vecUnit(rot(v12)), distance);
            const d01 = v01.normalize().multiplyScalar(inflateValue).clone();
            const d12 = v12.normalize().multiplyScalar(inflateValue).clone();
            //console.log("d01", d01, "d12", d12);

            // use the normals to find two points on the
            // lines parallel to the polygon lines
            const ptx0 = new Vector2(pt0.x + d01.x, pt0.y + d01.y);
            const ptx10 = new Vector2(pt1.x + d01.x, pt1.y + d01.y);
            const ptx12 = new Vector2(pt1.x + d12.x, pt1.y + d12.y);
            const ptx2 = new Vector2(pt2.x + d12.x, pt2.y + d12.y);
            //console.log("ptx0", ptx0, "ptx10", ptx10, "ptx12", ptx12, "ptx2", ptx2);

            // find the intersection of the two lines, and
            // add it to the expanded polygon
            /* const l1 = new Line3(ptx0, ptx10);
            const l2 = new Line3(ptx2, ptx12);
            const intersect = l1.intersectionPoint(l2); */
            const intersect = _Math.intersectionPoint([ptx0, ptx10], [ptx12, ptx2]);
            outterVertices.push(intersect);
        }
        //console.log("outterVertices", outterVertices);

        //Shape and subPath
        const outterPath = new THREE.Shape();
        const innerPath = new THREE.Path();

        outterPath.moveTo(outterVertices[0].x, outterVertices[0].y);

        for (let i = 1; i < outterVertices.length; i++) {
            outterPath.lineTo(outterVertices[i].x, outterVertices[i].y);
        }

        innerPath.moveTo(vertices[0].x, vertices[0].y);
        for (let i = 1; i < outterVertices.length; i++) {
            innerPath.lineTo(vertices[i].x, vertices[i].y);
        }

        outterPath.closePath();
        innerPath.closePath();

        outterPath.holes.push(innerPath);
        //console.log(outterPath);

        return outterPath;
    }

    /**
    * Static Utils to generate the stroke Mesh from a given shape
    * @static
    * @param {Mesh} mesh to use as a base to get vertices
    * @param {Number} inflateValue stroke width
    * @return {Mesh} the resulting Mesh
    */
    static generateStrokeMesh(mesh, inflateValue) {

        const shape = Mesh.generateStrokeShape(mesh, inflateValue);
        const geometry = new THREE.ShapeGeometry(shape);
        /**  @TODO refactor */
        const strokeMesh = new Mesh();
        strokeMesh.geometry = geometry;
        strokeMesh.constructMesh();
        //strokeMesh.material.setWireframe(true);

        return strokeMesh;
    }

    /**
    * Utils to regenerate the stroke Mesh when already existing
    * @param {Number} inflateValue stroke width
    */
    /*   updateStroke(mesh, inflateValue) {
          const shape = Mesh.generateStrokeShape(mesh, inflateValue);
  
          const geometry = new THREE.ShapeGeometry(shape);
          this.setVertices(geometry.vertices);
          this.updateMesh();
      } */

    /**
    * Static Utils to generate the stroke Mesh from a given shape
    * @static
    * @param {Shape} shape
    * @return {Mesh} the resulting Mesh
    */
    /* static generateStrokeMeshFromShape(shape) {
        const geometry = new THREE.ShapeGeometry(shape);

        const strokeMesh = new Mesh({ geometry });
        strokeMesh.constructMesh();
        strokeMesh.material.setColor(this.strokeColor);

        return strokeMesh;
    } */

    /**
   * Static Utils to check the direction of a geometry (ccw o cw)
   * @static
   * @param {Geometry} geometry Three.js geometry
   * @return {Boolean} is CW or not
   */
    static geometryIsCW(mesh) {

        const verticesArrayBuffer = mesh.geometry.toNonIndexed().getAttribute("position");

        //convert buffer back to a Vector3 array
        const vertices = [];
        for (let i = 0; i < verticesArrayBuffer.array.length; i += verticesArrayBuffer.itemSize) {
            const vec = new Vector3(
                verticesArrayBuffer.array[i],
                verticesArrayBuffer.array[i + 1],
                verticesArrayBuffer.array[i + 2]
            );
            vertices.push(vec);
        }
        //console.log("vertices", vertices);
        for (let i = 0; i < vertices.length; i++) {
            const v1 = new Vector2(vertices[i + 1].x - vertices[i].x, vertices[i + 1].y - vertices[i].y);
            const v2 = new Vector2(vertices[i + 2].x - vertices[i + 1].x, vertices[i + 2].y - vertices[i + 1].y);

            //v1.rotate90CW();

            const dot = v1.dot(v2);
            return (dot >= 0);
        }

        return null;
    }

    /*
    * =====
    * Boundings management
    * =====
    */

    /**
    * Compute and returns the bounding box of the current geometry or Node, which is an object made of 2 Vector3.
    * @return {Object} boundingBox object {min: new Vector3, max: new Vector3}
    */
    getBoundingBox() {
        if (this.geometry) {
            if (!this.geometry.boundingBox) {
                this.geometry.computeBoundingBox();
            }
            return this.geometry.boundingBox;
        }
        //no geometry, let three do the math
        return new THREE.Box3().setFromObject(this._mesh);
    }

    /**
    * Compute and returns the bounding sphere of the current geometry or Node, which is an object with a radius :
    *
    * @return {Object} boundingSphere object {radius: number}
    */
    getBoundingSphere() {
        if (this.geometry) {
            if (!this.geometry.boundingSphere) {
                this.geometry.computeBoundingSphere();
            }
            return this.geometry.boundingSphere;
        }
        //no geometry, let three do the math
        const bbox = new THREE.Box3().setFromObject(this._mesh);
        return bbox.getBoundingSphere();
    }

    /**
    * Call this method to construct a custom mesh
    */
    constructMesh() {
        //this.material = new Material({ type: "basic" }); //basic
        this.attachMaterial(this.material);
        this._mesh = new THREE.Mesh(this.geometry, this.material._material);
        this.transform = new Transform(this);
        this.geometry.computeBoundingBox();
    }

    //================
    // VERTEX MANAGEMENT
    //================

    setVertex(index, vertex) {
        this.geometryVerticesAttributes.setXYZ(index, vertex.x, vertex.y, vertex.z);
        this.geometryVerticesAttributes.needsUpdate = true;
    }
    /**
    * Set the vertices of this mesh geometry
    * @param {Object} vertices And Array of Vector3 (points)
    */
    setVertices(vertices) {
        this.geometry.setFromPoints(vertices);
        //update inner ref to buffer attributes
        this.geometryVerticesAttributes = this.geometry.attributes.position;
        this.geometryVerticesAttributes.needsUpdate = true;
    }

    /**
    * Get the vertices of this mesh geometry
    * @return {Array} vertices an array of Vector3 representing vertices
    */
    getVertices() {
        const vertices = [];
        let count = 0;
        if (this.vertexCount !== 0 && this.vertexCount < this.geometry.attributes.position.count) {
            count = this.vertexCount;
        }
        else {
            count = this.geometry.attributes.position.count;
        }
        
        for (let i = 0; i < count; i++) {
            const vertex = new Vector3(
                this.geometry.attributes.position.getX(i),
                this.geometry.attributes.position.getY(i),
                this.geometry.attributes.position.getZ(i)
            );
            vertices.push(vertex);
        }
        return vertices;
    }

    /**
    * Adds a vertex to the current geometry
    * @param {Vector3} v vertex to add
    */
    pushVertex(vector, addIndex) {

        const { x, y, z } = vector;
        this.geometryVerticesAttributes.setXYZ(this.vertexCount, x, y, z);
        this.vertexCount++;
        if (addIndex) {
            this.indices.push(this.indices.length);
            this.geometry.setDrawRange(0, this.indices.length);
        }
        this.geometryVerticesAttributes.needsUpdate = true;
    }

    /**
    * Adds a triangle to the current geometry
    * @param {Vector3} v1 vertex coordinates of the triangle
    * @param {Vector3} v2 vertex coordinates of the triangle
    * @param {Vector3} v3 vertex coordinates of the triangle
    */
    pushTriangle(v1, v2, v3) {

        const maxIndex = this.indices.length;

        this.pushVertex(v1);
        this.pushVertex(v2);
        this.pushVertex(v3);

        this.indices.push(maxIndex + 0, maxIndex + 1, maxIndex + 2);
        this.geometry.setDrawRange(0, this.indices.length);
        this.geometryVerticesAttributes.needsUpdate = true;
        //apply to the geometry
        this.geometry.setIndex(this.indices);
    }

    /**
    * Adds a Quad to the current geometry
    * @param {Vector3} v1 vertex coordinates of the Quad
    * @param {Vector3} v2 vertex coordinates of the Quad
    * @param {Vector3} v3 vertex coordinates of the Quad
    * @param {Vector3} v4 vertex coordinates of the Quad
    */
    pushQuad(v0, v1, v2, v3) {
        //add vertices to current geom
        this.pushTriangle(v0, v1, v2);
        this.pushTriangle(v2, v3, v0);
    }


    //===========
    //UV
    //===========
    /**
    * Adds a UV to the current geometry
    * 
    * @param {Vector2} uv1 uv coordinates to add
    * @param {Vector2} uv2 uv coordinates to add
    * @param {Vector2} uv3 uv coordinates to add
    */
    pushUV(vector) {

        const { x, y } = vector;
        this.geometryUVAttributes.setXY(this.uvCount, x, y);
        this.uvCount++;
        this.geometryUVAttributes.needsUpdate = true;
    }

    //=================
    //Normals
    //=================
    /**
    Adds a Normal to the current geometry

    @param {Vector2} n1 n coordinates to add
    @param {Vector2} n2 n coordinates to add
    @param {Vector2} n3 n coordinates to add
    */
    pushNormal(vector) {
        const { x, y, z } = vector;
        this.geometryVerticesAttributes.setXYZ(this.normalsCount, x, y, z);
        this.normalsCount++;
        this.geometryNormalAttributes.needsUpdate = true;
    }

    //=================

    /**
    * compute Face and Vertex Normals for the current geometry
    */
    computeNormals() {
        //compute normals automatically
        this.geometry.computeVertexNormals();
        this.geometryNormalAttributes = this.geometry.attributes.normal;
    }

    /**
     * convinient method to reverse this Mesh geometry's normals
     */
    reverseNormals() {
        //generates normals if necessary
        this.computeNormals();
        const normalsCount = this.geometryNormalAttributes.count;
        for (let i = 0; i < normalsCount; i++) {
            const normal = new Vector3(
                this.geometryNormalAttributes.getX(i),
                this.geometryNormalAttributes.getY(i),
                this.geometryNormalAttributes.getZ(i)
            );
            normal.negate();
            this.geometryNormalAttributes.setXYZ(i, normal.x, normal.y, normal.z);
            this.geometryNormalAttributes.needsUpdate = true;
        }
    }

    /**
    * Clears this mesh's geometry, that is vertices, faces and faceVertexUvs
    */
    clear() {
        if (this.geometry) {
            this.geometry.dispose();
        }
    }

    /**
    * Erase this mesh's geometry and material from memory
    */
    erase() {
        if (this.geometry) {
            this.geometry.dispose();
        }
        if (this.material) {
            this.material.erase();
        }
    }

    /**
    * Set the UV of this mesh geometry
    * @param {integer} index
    * @param {Array} uvs
    */
    setUVs(index, uvs) {
        this.geometry.faceVertexUvs[index] = uvs;
        this.geometry.uvsNeedUpdate = true;
        this.geometry.elementsNeedUpdate = true;
    }

    /**
    * Get the UV of this mesh geometry
    * @param {integer} index of the UV to get
    * @return {Array} uvs
    */
    getUVs(index) {
        return this.geometry.faceVertexUvs[index];
    }

    //Vertex Color
    /**
    * Set the vertexColors of this mesh geometry's vertices
    * @param {Array<Color>} colors
    */
    setVertexColors(colors) {
        this.material._material.vertexColors = THREE.VertexColors;
        this.geometry.colors = colors;
        this.geometry.colorsNeedUpdate = true;
    }

    /**
    * Get the vertexColors of this mesh geometry's vertices
    * @return {Array<Color>} colors
    */
    getVertexColors() {
        return this.geometry.colors;
    }

    /**
    * Defines the position of the geometry's center (NOT the transform!).
    *
    * @param {number} x x coordinate
    * @param {number} y y coordinate
    * @param {number} z z coordinate
    */
    setCenter(x, y, z) {
        this.geometry.applyMatrix4(new Matrix4().makeTranslation(x, y, z));
    }

    /**
    * Returns the center of the geometry (vertices) based on boundingbox
    * @return {Vector3} vector3 of the current geometry center
    */
    getCenter() {
        //force bounding box computation
        if (this.geometry.boundingBox === null) {
            this.geometry.computeBoundingBox();
        }
        const resultVec = new Vector3();
        this.geometry.boundingBox.getCenter(resultVec);
        return resultVec;
    }

    /**
    * Returns the size of the geometry (vertices) based on boundingbox
    * @return {Vector3} vector3 sizes of the current geometry
    */
    getSize() {
        //force bounding box computation
        if (this.geometry.boundingBox === null) {
            this.geometry.computeBoundingBox();
        }
        const resultVec = new Vector3();
        this.geometry.boundingBox.getSize(resultVec);
        return resultVec;
    }

    /**
     * Shortcut for getSize().x
     * @returns {Number} this Mesh width
     */
    getWidth(){
        return this.getSize().x;
    }

    /**
     * Shortcut for getSize().y
     * @returns {Number} this Mesh height
     */
     getHeight(){
        return this.getSize().y;
    }

    /**
     * Shortcut for getSize().z
     * @returns {Number} this Mesh length (z-axis)
     */
     getLength(){
        return this.getSize().z;
    }

    /**
    * Set the scale of the geometry (vertices) - this is not like scaling the transform. Shouldn't be done in loop. Polymorphic : can take various agruments of various types. Possible arguments number is 1, 2 or 3.
    * @param {float|Vector3|Vector2} number|Vector3|Vector2 Value for the new scale of the geometry. If a Vector3 is given, its x, y, z will be used for the scale x, y, z. If a Vector2 is given, its x, y will be used for the scale x and y, but z will be 1. If a number is given, it will be the scale x.
    * @param {float} Number Value for the new y scale of the geometry.
    * @param {float} Number Value for the new z scale of the geometry.
    */
    setScale(arg1, arg2, arg3) {
        if (arguments.length === 3) {
            this.geometry.applyMatrix4(new Matrix4().makeScale(arg1, arg2, arg3));
        }
        else if (arguments.length === 2) {
            if (typeof arg1 === "number" && typeof arg2 === "number") {
                this.geometry.applyMatrix4(new Matrix4().makeScale(arg1, arg2, 1));
            }
        }
        else if (arguments.length === 1) {
            if (arg1 instanceof Vector3) {
                this.geometry.applyMatrix4(new Matrix4().makeScale(arg1.x, arg1.y, arg1.z));
            }
            else if (arg1 instanceof Vector2) {
                this.geometry.applyMatrix4(new Matrix4().makeScale(arg1.x, arg1.y, 1));
            }
            else if (typeof arg1 === "number") {
                this.geometry.applyMatrix4(new Matrix4().makeScale(arg1, arg1, arg1));
            }
        }
    }

    /**
    * Set the rotation around x axis of the vertices (not the transform!)
    * @param {float} value Rotation value in degree
    */
    setRotationX(arg1) {
        this.geometry.applyMatrix4(new Matrix4().makeRotationX(_Math.degToRad(arg1)));
    }

    /**
    * Set the rotation around y axis of the vertices (not the transform!)
    * @param {float} value Rotation value in degree
    */
    setRotationY(arg1) {
        this.geometry.applyMatrix4(new Matrix4().makeRotationY(_Math.degToRad(arg1)));
    }

    /**
    * Set the rotation around z axis of the vertices (not the transform!)
    * @param {float} value Rotation value in degree
    */
    setRotationZ(arg1) {
        this.geometry.applyMatrix4(new Matrix4().makeRotationZ(_Math.degToRad(arg1)));
    }

    /**
    * Set the translation along all axis of the vertices (not the transform!)
    * @param {float} value x value
    * @param {float} value y value
    * @param {float} value z value
    */
    setTranslation(arg1, arg2, arg3) {
        //this.geometry.applyMatrix4( new Matrix4().makeTranslation( _Math.degToRad(arg1)));
        if (arguments.length === 3) {
            this.geometry.applyMatrix4(new Matrix4().makeTranslation(arg1, arg2, arg3));
        }
        else if (arguments.length === 2) {
            if (typeof arg1 === "number" && typeof arg2 === "number") {
                this.geometry.applyMatrix4(new Matrix4().makeTranslation(arg1, arg2, 0));
            }
        }
        else if (arguments.length === 1) {
            if (arg1 instanceof Vector3) {
                this.geometry.applyMatrix4(new Matrix4().makeTranslation(arg1.x, arg1.y, arg1.z));
            }
            else if (arg1 instanceof Vector2) {
                this.geometry.applyMatrix4(new Matrix4().makeTranslation(arg1.x, arg1.y, 0));
            }
        }
    }


    /**
    * Set the visibility of this mesh
    * @param {Boolean} value visible or not
    */
    setVisible(val) {
        this._mesh.visible = val;
    }

    /**
    * Get the visibility of this mesh
    * @return {Boolean} value visible or not
    */
    getVisible() {
        return this._mesh.visible;
    }

    /**
    * Updates this mesh material
    * @private
    */
    updateMaterial() {
        if (this._mesh.material) {
            this._mesh.material = this.material._material;
        }
    }

    /**
    * Sets this Mesh material. Be warned that this method requiers to build a new mesh, so it can be slow on update. Better use it in setup.
    * @param {Material|String} material the new material to set (if String used, one of "phong", "lambert", "basic")
    */
    setMaterial(material) {

        this.material = null;

        if (material === undefined) {
            this.material = new Material({ type: "default" });
        }
        else if (material instanceof Material) {
            this.material = material;
        }
        else if (typeof material === "string") {
            this.material = new Material({ type: material });
        }

        const tempMesh = new THREE.Mesh(this.geometry, this.material._material);
        const tempTransform = this.transform;

        this._mesh = tempMesh;
        this.transform = new Transform(this);
        this.transform.setLocalScale(tempTransform.getLocalScale().x,
            tempTransform.getLocalScale().y,
            tempTransform.getLocalScale().z);

        this.transform.setLocalPosition(tempTransform.getLocalPosition().x,
            tempTransform.getLocalPosition().y,
            tempTransform.getLocalPosition().z);

        this.transform.setLocalRotation(tempTransform.getLocalRotation().x,
            tempTransform.getLocalRotation().y,
            tempTransform.getLocalRotation().z);
    }

    /**
    * Attach the material to this mesh. The material must have been created before calling this method
    * @param {Material} material
    */
    attachMaterial(material) {

        //console.log("material instanceof THREE.Material", material instanceof THREE.Material);

        if (material === undefined) {
            this.material = new Material({ type: "default" });
        }
        else if (material instanceof Material) {
            this.material = material;
        }
        else if (typeof material === "string") {
            this.material = new Material({ type: material });
        }
        else if (material instanceof THREE.Material) {
            this.material = new Material({ type: material });
        }
        if (material !== undefined && this._mesh) {
            this.updateMaterial();
        }
    }

    /**
    * setCastShadow
    * @param {Boolean} enabled
    */
    setCastShadow(enabled) {
        this._mesh.castShadow = enabled;
    }

    /**
    * setReceiveShadow
    * @param {Boolean} enabled
    */
    setReceiveShadow(enabled) {
        this._mesh.receiveShadow = enabled;
    }

    /**
    * Compute the intersection points between a plane and a mesh !Works ONLY if this mesh is "plane" primitive made
    * @param {Mesh} mesh the mesh to use for the intersections computation
    * @return {Array} an array of object as {vertex: Vector3, uv: Vector2};
    */
    /* getIntersectionsPoints(mesh) {
        if (this.primitive === "plane") {
            const plane = this._mesh;
            const obj = mesh.mesh;

            const intersectionPoints = [];

            const a = new THREE.Vector3(),
                b = new THREE.Vector3(),
                c = new THREE.Vector3();

            const planePointA = new THREE.Vector3(),
                planePointB = new THREE.Vector3(),
                planePointC = new THREE.Vector3();

            const mathPlane = new THREE.Plane();

            plane.localToWorld(planePointA.copy(plane.geometry.vertices[plane.geometry.faces[0].a]));
            plane.localToWorld(planePointB.copy(plane.geometry.vertices[plane.geometry.faces[0].b]));
            plane.localToWorld(planePointC.copy(plane.geometry.vertices[plane.geometry.faces[0].c]));

            mathPlane.setFromCoplanarPoints(planePointA, planePointB, planePointC)

            const uvs = mesh.getUVs(0);

            obj.geometry.faces.forEach((face, index) => {

                //faces vertices
                const fvA = obj.geometry.vertices[face.a];
                const fvB = obj.geometry.vertices[face.b];
                const fvC = obj.geometry.vertices[face.c];

                //intersection management
                obj.localToWorld(a.copy(fvA));
                obj.localToWorld(b.copy(fvB));
                obj.localToWorld(c.copy(fvC));

                const lineAB = new THREE.Line3(a, b);
                const lineBC = new THREE.Line3(b, c);
                const lineCA = new THREE.Line3(c, a);

                const lines = [lineAB, lineBC, lineCA];

                lines.forEach((line) => {
                    //new object to fill
                    const intersectionPoint = {};

                    //search an intersection
                    this.setPointOfIntersection(line, mathPlane, intersectionPoint);

                    //if we have one, search for uvs
                    if (intersectionPoint.vertex) {
                        //uv management
                        const uvA = new Vector2();
                        const uvB = new Vector2();
                        const uvC = new Vector2();

                        const uvs_f = uvs[index];
                        uvA.copy(uvs_f[0]);
                        uvB.copy(uvs_f[1]);
                        uvC.copy(uvs_f[2]);

                        const wIntersectionPoint = new Vector3();

                        obj.worldToLocal(wIntersectionPoint.copy(intersectionPoint.vertex));

                        const uv = this.getUVFromIntersectionPoint(wIntersectionPoint, fvA, fvB, fvC, uvA, uvB, uvC);
                        intersectionPoint.uv = uv;

                        intersectionPoints.push(intersectionPoint);
                    }
                });
            });

            return intersectionPoints;
        }
        return undefined;
    } */

    /**
    * setPointOfIntersection
    * @private
    * @param {Object} line three.js line
    * @param {Object} plane three.js plane
    * @param {Object} intersectionPoint object to be used to store the resulting Vector3
    * @return {Object} description
    */
    /* setPointOfIntersection(line, plane, intersectionPoint) {
        const pointOfIntersection = plane.intersectLine(line);

        if (pointOfIntersection) {
            const v = new Vector3(pointOfIntersection.x, pointOfIntersection.y, pointOfIntersection.z);
            intersectionPoint.vertex = v;
        }
    } */

    /**
    * getUVFromIntersectionPoint
    * @private
    * @param {Vector3} point the intersection point as a Vector3
    * @param {Vector3} p1 vertices of face1
    * @param {Vector3} p2 vertices of face2
    * @param {Vector3} p3 vertices of face3
    * @param {Vector2} uv1 uvs of face1
    * @param {Vector2} uv2 uvs of face2
    * @param {Vector2} uv3 uvs of face3
    * @return {Vector2} Uvs
    */
    /* getUVFromIntersectionPoint(point, p1, p2, p3, uv1, uv2, uv3) {
        const barycoord = new Vector3();

        THREE.Triangle.barycoordFromPoint(point, p1, p2, p3, barycoord);

        uv1.multiplyScalar(barycoord.x);
        uv2.multiplyScalar(barycoord.y);
        uv3.multiplyScalar(barycoord.z);

        uv1.add(uv2).add(uv3);

        const tempUvs = uv1.clone();
        const uvs = new Vector2(tempUvs.x, tempUvs.y);

        return uvs;
    } */

    /**
    * deep clone this Mesh
    * return {Mesh} a new clone (copy) of this Mesh in a new Mesh object
    */
    clone() {
        const tempTransform = this.transform;

        const clone = new Mesh({ primitive: "custom" });
        clone.geometry = this.geometry.clone();
        clone.material = new Material({ type: this.material.type });
        clone.material._material = this.material._material.clone();

        clone.genereateMesh();

        clone.transform.setLocalScale(tempTransform.getLocalScale());
        clone.transform.setLocalPosition(tempTransform.getLocalPosition());
        clone.transform.setLocalRotation(tempTransform.getLocalRotation());

        return clone;
    }
}