diff --git a/css/panels.css b/css/panels.css index a7dc04dc..fe2413b4 100644 --- a/css/panels.css +++ b/css/panels.css @@ -1233,6 +1233,44 @@ #uv_seleced_faces li { padding: 0 5px; } + + .mesh_uv_face { + position: absolute; + pointer-events: none; + } + .mesh_uv_face.selected { + z-index: 2; + } + .mesh_uv_face svg { + height: 100%; + width: 100%; + position: absolute; + } + .mesh_uv_face polygon { + pointer-events: initial; + fill: rgba(50, 70, 240, 0.3); + stroke: var(--color-text); + stroke-width: 2px; + } + .mesh_uv_face:hover polygon { + stroke: var(--color-light); + } + .mesh_uv_face.selected polygon { + stroke: var(--color-accent); + } + .uv_mesh_vertex { + position: absolute; + pointer-events: initial; + margin: -3px; + height: 8px; + width: 8px; + background-color: var(--color-text); + z-index: 3; + cursor: move; + } + .uv_mesh_vertex.selected { + background-color: var(--color-accent); + } /* #uv_size { height: 320px; diff --git a/js/interface/menu.js b/js/interface/menu.js index 1cbeb5eb..914f6d89 100644 --- a/js/interface/menu.js +++ b/js/interface/menu.js @@ -539,6 +539,7 @@ const MenuBar = { 'import_project', 'import_java_block_model', 'import_optifine_part', + 'import_obj', 'extrude_texture' ]}, {name: 'generic.export', id: 'export', icon: 'insert_drive_file', children: [ diff --git a/js/io/project.js b/js/io/project.js index d4c35445..da19865d 100644 --- a/js/io/project.js +++ b/js/io/project.js @@ -333,28 +333,39 @@ function setProjectResolution(width, height, modify_uv) { Project.texture_width = width; Project.texture_height = height; - if (modify_uv) { var multiplier = [ Project.texture_width/old_res.x, Project.texture_height/old_res.y ] - function shiftCube(cube, axis) { - if (Project.box_uv) { - cube.uv_offset[axis] *= multiplier[axis]; + function shiftElement(element, axis) { + if (!element.faces) return; + if (element instanceof Mesh) { + + for (let key in element.faces) { + let face = element.faces[key]; + face.vertices.forEach(vertex_key => { + if (face.uv[vertex_key]) { + face.uv[vertex_key][axis] *= multiplier[axis]; + } + }) + } + + } else if (Project.box_uv) { + element.uv_offset[axis] *= multiplier[axis]; } else { - for (var face in cube.faces) { - var uv = cube.faces[face]; + for (let face in element.faces) { + let {uv} = element.faces[face]; uv[axis] *= multiplier[axis]; uv[axis+2] *= multiplier[axis]; } } } if (old_res.x != Project.texture_width && Math.areMultiples(old_res.x, Project.texture_width)) { - Cube.all.forEach(cube => shiftCube(cube, 0)); + Outliner.elements.forEach(element => shiftElement(element, 0)); } if (old_res.y != Project.texture_height && Math.areMultiples(old_res.x, Project.texture_width)) { - Cube.all.forEach(cube => shiftCube(cube, 1)); + Outliner.elements.forEach(element => shiftElement(element, 1)); } } Undo.finishEdit('Changed project resolution') diff --git a/js/outliner/mesh.js b/js/outliner/mesh.js index c9341a13..97621b37 100644 --- a/js/outliner/mesh.js +++ b/js/outliner/mesh.js @@ -273,7 +273,7 @@ class Mesh extends OutlinerElement { if (faces === true) { var sides = Object.keys(this.faces); } else if (faces === undefined) { - var sides = [main_uv.face] + var sides = UVEditor.vue.selected_faces } else { var sides = faces } @@ -285,7 +285,7 @@ class Mesh extends OutlinerElement { scope.faces[side].texture = value }) if (Project.selected_elements.indexOf(this) === 0) { - main_uv.loadData() + UVEditor.loadData() } if (Prop.view_mode === 'textured') { this.preview_controller.updateFaces(this); @@ -568,7 +568,7 @@ BARS.defineActions(function() { for (var face in base_mesh.faces) { base_mesh.faces[face].texture = Texture.getDefault().uuid } - main_uv.loadData() + UVEditor.loadData() } if (Format.bone_rig) { if (group) { @@ -783,7 +783,6 @@ BARS.defineActions(function() { name: args[0], vertices: {} }) - mesh.select(); meshes.push(mesh); } if (cmd == 'v') { diff --git a/js/texturing/uv.js b/js/texturing/uv.js index d7c391f7..4a687f69 100644 --- a/js/texturing/uv.js +++ b/js/texturing/uv.js @@ -711,7 +711,6 @@ const UVEditor = { } matches.forEach(s => { selected.safePush(s) - console.log(s.size(), face_match) }); updateSelection(); } @@ -2068,7 +2067,6 @@ Interface.definePanels(function() { let {viewport} = this.$refs; let coords = {x: 0, y: 0} function dragMouseWheel(e2) { - console.log() viewport.scrollLeft -= (e2.pageX - coords.x) viewport.scrollTop -= (e2.pageY - coords.y) coords = {x: e2.pageX, y: e2.pageY} @@ -2139,8 +2137,6 @@ Interface.definePanels(function() { element.faces[key].uv[3] += y; } }) - element.uv_offset[0] += x; - element.uv_offset[1] += y; } } @@ -2226,6 +2222,72 @@ Interface.definePanels(function() { addEventListeners(document, 'mouseup touchend', stop); }, + dragVertices(element, vertex_key, event) { + if (event.which == 2 || event.which == 3) return; + + if (!this.selected_vertices[element.uuid]) this.selected_vertices[element.uuid] = []; + let sel_vertices = this.selected_vertices[element.uuid]; + if (sel_vertices.includes(vertex_key)) { + + } else if (event.shiftvertex_key || event.ctrlOrCmd || Pressing.overrides.shift || Pressing.overrides.ctrl) { + if (sel_vertices.includes(vertex_key)) { + sel_vertices.remove(vertex_key); + } else { + sel_vertices.push(vertex_key); + } + } else { + sel_vertices.replace([vertex_key]); + } + + + let scope = this; + let elements = this.mappable_elements; + Undo.initEdit({elements, uv_only: true}) + + let pos = [0, 0]; + let last_pos = [0, 0]; + + function drag(e1) { + + let step_x = (scope.inner_width / UVEditor.getResolution(0) / UVEditor.grid); + let step_y = (scope.inner_height / UVEditor.getResolution(1) / UVEditor.grid); + + pos[0] = Math.round((e1.clientX - event.clientX) / step_x) / UVEditor.grid; + pos[1] = Math.round((e1.clientY - event.clientY) / step_y) / UVEditor.grid; + + if (pos[0] != last_pos[0] || pos[1] != last_pos[1]) { + + elements.forEach(element => { + scope.selected_faces.forEach(key => { + let face = element.faces[key]; + face.vertices.forEach(vertex_key => { + if (scope.selected_vertices[element.uuid] && scope.selected_vertices[element.uuid].includes(vertex_key)) { + face.uv[vertex_key][0] += pos[0] - last_pos[0]; + face.uv[vertex_key][1] += pos[1] - last_pos[1]; + } + }) + }) + }) + + + last_pos.replace(pos); + } + UVEditor.displaySliders(); + UVEditor.loadData(); + UVEditor.vue.$forceUpdate(); + Canvas.updateView({elements: scope.mappable_elements, element_aspects: {uv: true}}); + } + + function stop(e1) { + removeEventListeners(document, 'mousemove touchmove', drag); + removeEventListeners(document, 'mouseup touchend', stop); + UVEditor.disableAutoUV() + Undo.finishEdit('Move UV') + } + addEventListeners(document, 'mousemove touchmove', drag); + addEventListeners(document, 'mouseup touchend', stop); + }, + openFaceMenu(event) { let faces = []; this.mappable_elements.forEach(element => { @@ -2247,6 +2309,39 @@ Interface.definePanels(function() { toPixels(uv_coord, offset = 0) { return (uv_coord / this.project_resolution[0] * this.inner_width + offset) + 'px' + }, + getMeshFaceOutline(face) { + let coords = []; + let uv_offset = [ + -this.getMeshFaceCorner(face, 0), + -this.getMeshFaceCorner(face, 1), + ] + face.getSortedVertices().forEach(key => { + let UV = face.uv[key]; + coords.push( + ((UV[0] + uv_offset[0]) / this.project_resolution[0] * this.inner_width + 1) + ',' + + ((UV[1] + uv_offset[1]) / this.project_resolution[0] * this.inner_width + 1) + ) + }) + return coords.join(' '); + }, + getMeshFaceCorner(face, axis) { + let val = Infinity; + face.vertices.forEach(key => { + let UV = face.uv[key]; + val = Math.min(val, UV[axis]); + }) + return val; + }, + getMeshFaceWidth(face, axis) { + let min = Infinity; + let max = 0; + face.vertices.forEach(key => { + let UV = face.uv[key]; + min = Math.min(min, UV[axis]); + max = Math.max(max, UV[axis]); + }) + return max - min; } }, /* @@ -2283,7 +2378,7 @@ Interface.definePanels(function() {