diff --git a/js/interface/actions.js b/js/interface/actions.js index 2faa0ffd..ca1832cf 100644 --- a/js/interface/actions.js +++ b/js/interface/actions.js @@ -308,7 +308,7 @@ class Tool extends Action { this.modes = data.modes; this.selectFace = data.selectFace; this.cursor = data.cursor; - this.selectCubes = data.selectCubes !== false; + this.selectElements = data.selectElements !== false; this.paintTool = data.paintTool; this.brushTool = data.brushTool; this.transformerMode = data.transformerMode; @@ -1378,7 +1378,7 @@ const BARS = { transformerMode: 'hidden', toolbar: 'vertex_snap', category: 'tools', - selectCubes: true, + selectElements: true, cursor: 'copy', modes: ['edit'], keybind: new Keybind({key: 'x'}), diff --git a/js/modes.js b/js/modes.js index 26864725..e54e81ef 100644 --- a/js/modes.js +++ b/js/modes.js @@ -10,7 +10,7 @@ class Mode extends KeybindItem { this.selected = false this.default_tool = data.default_tool; - this.selectCubes = data.selectCubes !== false + this.selectElements = data.selectElements !== false this.center_windows = data.center_windows||[]; this.hide_toolbars = data.hide_toolbars @@ -202,7 +202,7 @@ BARS.defineActions(function() { }, }) new Mode('display', { - selectCubes: false, + selectElements: false, default_tool: 'move_tool', category: 'navigate', condition: () => Format.display_mode, diff --git a/js/outliner/cube.js b/js/outliner/cube.js index a3e56ab3..66d7c6f0 100644 --- a/js/outliner/cube.js +++ b/js/outliner/cube.js @@ -840,7 +840,7 @@ new NodePreviewController(Cube, { this.updateFaces(element); if (Prop.view_mode === 'textured') { - Canvas.updateUV(element); + this.updateUV(element); } mesh.visible = element.visibility; Canvas.buildOutline(element); diff --git a/js/outliner/mesh.js b/js/outliner/mesh.js index 194af4d2..1827a741 100644 --- a/js/outliner/mesh.js +++ b/js/outliner/mesh.js @@ -1,8 +1,89 @@ +class MeshFace { + constructor(mesh, data) { + this.mesh = mesh; + //this.vertices = []; + //this.normal = [0, 1, 0]; + this.texture = false; + this.uv = {}; + for (var key in MeshFace.properties) { + MeshFace.properties[key].reset(this); + } + this.extend(data); + } + extend(data) { + for (var key in MeshFace.properties) { + MeshFace.properties[key].merge(this, data) + } + if (data.texture === null) { + this.texture = null; + } else if (data.texture === false) { + this.texture = false; + } else if (Texture.all.includes(data.texture)) { + this.texture = data.texture.uuid; + } else if (typeof data.texture === 'string') { + Merge.string(this, data, 'texture') + } + return this; + } + getSaveCopy() { + var copy = { + uv: this.uv + }; + for (var key in MeshFace.properties) { + if (this[key] != MeshFace.properties[key].default) MeshFace.properties[key].copy(this, copy); + } + var tex = this.getTexture() + if (tex === null) { + copy.texture = null; + } else if (tex instanceof Texture) { + copy.texture = Texture.all.indexOf(tex) + } + return copy; + } + getUndoCopy() { + var copy = new MeshFace(this.mesh, this); + delete copy.mesh; + return copy; + } + reset() { + for (var key in Mesh.properties) { + Mesh.properties[key].reset(this); + } + this.texture = false; + return this; + } + getTexture() { + if (Format.single_texture) { + return Texture.getDefault(); + } + if (typeof this.texture === 'string') { + return Texture.all.findInArray('uuid', this.texture) + } else { + return this.texture; + } + } +} +new Property(MeshFace, 'array', 'vertices', {default: 0}); +new Property(MeshFace, 'vector', 'normal', {default: 0}); + class Mesh extends OutlinerElement { constructor(data, uuid) { super(data, uuid) + this.vertices = {}; + this.faces = []; + + if (!data.vertices) { + this.addVertices([1, 1, 1], [1, 1, 0], [1, 0, 1], [1, 0, 0], [0, 1, 1], [0, 1, 0], [0, 0, 1], [0, 0, 0]); + let vertex_keys = Object.keys(this.vertices); + this.faces.push(new MeshFace( this, {vertices: [vertex_keys[0], vertex_keys[1], vertex_keys[2], vertex_keys[3]]} )); // East + this.faces.push(new MeshFace( this, {vertices: [vertex_keys[4], vertex_keys[5], vertex_keys[6], vertex_keys[7]]} )); // West + this.faces.push(new MeshFace( this, {vertices: [vertex_keys[0], vertex_keys[1], vertex_keys[4], vertex_keys[5]]} )); // Up + this.faces.push(new MeshFace( this, {vertices: [vertex_keys[2], vertex_keys[3], vertex_keys[6], vertex_keys[7]]} )); // Down + this.faces.push(new MeshFace( this, {vertices: [vertex_keys[0], vertex_keys[2], vertex_keys[4], vertex_keys[6]]} )); // South + this.faces.push(new MeshFace( this, {vertices: [vertex_keys[1], vertex_keys[3], vertex_keys[5], vertex_keys[7]]} )); // North + } for (var key in Mesh.properties) { Mesh.properties[key].reset(this); } @@ -10,6 +91,42 @@ class Mesh extends OutlinerElement { this.extend(data) } } + get from() { + return this.origin; + } + get vertice_list() { + return Object.keys(this.vertices).map(key => this.vertices[key]); + } + getWorldCenter() { + var m = this.mesh; + var pos = new THREE.Vector3() + + let vertice_list = this.vertice_list; + vertice_list.forEach(vector => { + pos.x += vector[0]; + pos.y += vector[1]; + pos.z += vector[2]; + }) + pos.x /= vertice_list.length; + pos.y /= vertice_list.length; + pos.z /= vertice_list.length; + + if (m) { + var r = m.getWorldQuaternion(new THREE.Quaternion()) + pos.applyQuaternion(r) + pos.add(THREE.fastWorldPosition(m, new THREE.Vector3())) + } + return pos; + } + addVertices(...vectors) { + vectors.forEach(vector => { + let key; + while (!key || this.vertices[key]) { + key = bbuid(4); + } + this.vertices[key] = [...vector]; + }) + } extend(object) { for (var key in Mesh.properties) { Mesh.properties[key].merge(this, object) @@ -39,6 +156,7 @@ class Mesh extends OutlinerElement { Mesh.prototype.type = 'mesh'; Mesh.prototype.icon = 'fa far fa-gem'; Mesh.prototype.movable = true; + Mesh.prototype.resizable = false; Mesh.prototype.rotatable = true; Mesh.prototype.needsUniqueName = false; Mesh.prototype.menu = new Menu([ @@ -102,7 +220,157 @@ new Property(Mesh, 'boolean', 'visibility', {default: true}); OutlinerElement.registerType(Mesh, 'mesh'); -new NodePreviewController(Mesh) +new NodePreviewController(Mesh, { + setup(element) { + var mesh = new THREE.Mesh(new THREE.BufferGeometry(1, 1, 1), emptyMaterials[0]); + Project.nodes_3d[element.uuid] = mesh; + mesh.name = element.uuid; + mesh.type = element.type; + mesh.isElement = true; + + mesh.geometry.setAttribute('highlight', new THREE.BufferAttribute(new Uint8Array(24).fill(1), 1)); + + this.updateTransform(element); + this.updateGeometry(element); + this.updateFaces(element); + + if (Prop.view_mode === 'textured') { + this.updateUV(element); + } + mesh.visible = element.visibility; + + let material = new THREE.PointsMaterial({size: 5, sizeAttenuation: false}); + let points = new THREE.Points(mesh.geometry, material) + mesh.add(points); + //Canvas.buildOutline(element); + }, + updateGeometry(element) { + + let {mesh} = element; + let position_array = []; + let position_indices = []; + let indices = []; + + for (let key in element.vertices) { + let vector = element.vertices[key]; + position_indices.push(key); + position_array.push(...vector); + } + + element.faces.forEach(face => { + if (face.vertices.length == 3) { + // Tri + face.vertices.forEach(key => { + let index = position_indices.indexOf(key); + indices.push(index); + }) + } else if (face.vertices.length == 4) { + // Quad + indices.push(position_indices.indexOf(face.vertices[0])); + indices.push(position_indices.indexOf(face.vertices[1])); + indices.push(position_indices.indexOf(face.vertices[2])); + + indices.push(position_indices.indexOf(face.vertices[1])); + indices.push(position_indices.indexOf(face.vertices[2])); + indices.push(position_indices.indexOf(face.vertices[3])); + } + }) + + mesh.geometry.setAttribute('position', new THREE.BufferAttribute(new Float32Array(position_array), 3)); + mesh.geometry.setIndex( indices ); + + //Canvas.getOutlineMesh(mesh, mesh.outline) + mesh.geometry.computeBoundingBox() + mesh.geometry.computeBoundingSphere() + }, + updateFaces(element) { + let {mesh} = element; + let {geometry} = mesh; + + /* + if (!geometry.all_faces) geometry.all_faces = geometry.groups.slice(); + geometry.groups.empty(); + + geometry.all_faces.forEach(face => { + let bb_face = element.faces[Canvas.face_order[face.materialIndex]]; + + if (bb_face && bb_face.texture === null && geometry.groups.includes(face)) { + geometry.groups.remove(face); + } else + if (bb_face && bb_face.texture !== null && !geometry.groups.includes(face)) { + geometry.groups.push(face); + } + }) + if (geometry.groups.length == 0) { + // Keep down face if no faces enabled + geometry.groups.push(geometry.all_faces[6], geometry.all_faces[7]); + } + + + + if (Prop.view_mode === 'solid') { + mesh.material = Canvas.solidMaterial + + } else if (Prop.view_mode === 'wireframe') { + mesh.material = Canvas.wireframeMaterial + + } else if (Format.single_texture && Texture.all.length >= 2 && Texture.all.find(t => t.render_mode == 'layered')) { + mesh.material = Canvas.getLayeredMaterial(); + + } else if (Format.single_texture) { + let tex = Texture.getDefault(); + mesh.material = tex ? tex.getMaterial() : emptyMaterials[element.color]; + + } else { + var materials = [] + Canvas.face_order.forEach(function(face) { + + if (cube.faces[face].texture === null) { + materials.push(Canvas.transparentMaterial) + + } else { + var tex = cube.faces[face].getTexture() + if (tex && tex.uuid) { + materials.push(Project.materials[tex.uuid]) + } else { + materials.push(emptyMaterials[cube.color]) + } + } + }) + if (materials.allEqual(materials[0])) materials = materials[0]; + mesh.material = materials + }*/ + }, + updateUV(cube, animation = true) { + if (Prop.view_mode !== 'textured') return; + var mesh = cube.mesh + if (mesh === undefined || !mesh.geometry) return; + return; + + + var stretch = 1 + var frame = 0 + + Canvas.face_order.forEach((face, fIndex) => { + + if (cube.faces[face].texture == null) return; + + stretch = 1; + frame = 0; + let tex = cube.faces[face].getTexture(); + if (tex instanceof Texture && tex.frameCount !== 1) { + stretch = tex.frameCount + if (animation === true && tex.currentFrame) { + frame = tex.currentFrame + } + } + Canvas.updateUVFace(mesh.geometry.attributes.uv, fIndex, cube.faces[face], frame, stretch) + }) + + mesh.geometry.attributes.uv.needsUpdate = true; + return mesh.geometry; + } +}) BARS.defineActions(function() { new Action({ diff --git a/js/outliner/outliner.js b/js/outliner/outliner.js index b295281e..79135dd4 100644 --- a/js/outliner/outliner.js +++ b/js/outliner/outliner.js @@ -1400,6 +1400,7 @@ Interface.definePanels(function() { }, menu: new Menu([ 'add_cube', + 'add_mesh', 'add_group', '_', 'sort_outliner', diff --git a/js/preview/preview.js b/js/preview/preview.js index fd65baeb..b0b5953d 100644 --- a/js/preview/preview.js +++ b/js/preview/preview.js @@ -347,9 +347,9 @@ class Preview { this.raycaster.setFromCamera( this.mouse, this.camera ); var objects = [] - Cube.all.forEach(cube => { - if (cube.visibility && !cube.locked) { - objects.push(cube.mesh); + Outliner.elements.forEach(element => { + if (element.mesh.geometry && element.visibility && !element.locked) { + objects.push(element.mesh); } }) if (Vertexsnap.vertexes.children.length) { @@ -387,17 +387,17 @@ class Preview { return { event: event, - type: 'cube', + type: 'element', intersects: intersects, face: face, - cube: obj + element: obj } } else if (intersect.isVertex) { return { event: event, type: 'vertex', intersects: intersects, - cube: intersect.cube, + element: intersect.element, vertex: intersect } } else if (intersect.isKeyframe) { @@ -660,7 +660,7 @@ class Preview { var data = this.raycast(event); if (data) { //this.static_rclick = false - if (data.cube && data.cube.locked) { + if (data.element && data.element.locked) { $('#preview').css('cursor', 'not-allowed') function resetCursor() { $('#preview').css('cursor', (Toolbox.selected.cursor ? Toolbox.selected.cursor : 'default')) @@ -668,7 +668,7 @@ class Preview { } addEventListeners(document, 'mouseup touchend', resetCursor, false) - } else if (Toolbox.selected.selectCubes && Modes.selected.selectCubes && data.type === 'cube') { + } else if (Toolbox.selected.selectElements && Modes.selected.selectElements && data.type === 'element') { if (Toolbox.selected.selectFace) { main_uv.setFace(data.face, false) } @@ -676,15 +676,15 @@ class Preview { if (Modes.paint) { event = 0; } - if (data.cube.parent.type === 'group' && ( + if (data.element.parent.type === 'group' && ( Animator.open || event.shiftKey || (!Format.rotate_cubes && Format.bone_rig && ['rotate_tool', 'pivot_tool'].includes(Toolbox.selected.id)) )) { - data.cube.parent.select().showInOutliner(); + data.element.parent.select().showInOutliner(); } else if (!Animator.open) { - data.cube.select(event) + data.element.select(event) } } else if (Animator.open && data.type == 'keyframe') { if (data.keyframe instanceof Keyframe) { @@ -711,7 +711,7 @@ class Preview { mousemove(event) { if (Settings.get('highlight_cubes')) { var data = this.raycast(event); - if (settings.highlight_cubes.value) updateCubeHighlights(data && data.cube); + if (settings.highlight_cubes.value) updateCubeHighlights(data && data.element); } } mouseup(event) { @@ -748,8 +748,8 @@ class Preview { Prop.active_panel = 'preview'; if (this.static_rclick && (event.which === 3 || (event.type == 'touchend' && this.rclick_cooldown == true))) { var data = this.raycast(event) - if (Toolbox.selected.selectCubes && Modes.selected.selectCubes && data && data.cube && !Modes.animate) { - data.cube.showContextMenu(event); + if (Toolbox.selected.selectElements && Modes.selected.selectElements && data && data.element && !Modes.animate) { + data.element.showContextMenu(event); } else if (data.type == 'keyframe') { data.keyframe.showContextMenu(event); @@ -853,7 +853,7 @@ class Preview { ray[uv_axes.v] ) unselectAll() - elements.forEach(function(cube) { + Outliner.elements.forEach(function(cube) { if ((event.shiftKey || event.ctrlOrCmd) && scope.selection.old_selected.indexOf(cube) >= 0) { var isSelected = true diff --git a/js/property.js b/js/property.js index 380f25f1..0d414265 100644 --- a/js/property.js +++ b/js/property.js @@ -107,7 +107,7 @@ class Property { if (instance[this.name] instanceof Array == false) { instance[this.name] = []; } - instance[this.name].replace(dft); + instance[this.name].replace(dft || []); } else { instance[this.name] = dft; } diff --git a/js/transform.js b/js/transform.js index 310c3ee2..447f54bd 100644 --- a/js/transform.js +++ b/js/transform.js @@ -217,7 +217,7 @@ const Vertexsnap = { if (id == 100) { mesh.rotation.y += Math.PI/4; } - mesh.cube = cube + mesh.element = cube mesh.isVertex = true mesh.vertex_id = id mesh.material.transparent = true;