From 98b59b7ca7e99a76f877f5ca6db1f86f019e362e Mon Sep 17 00:00:00 2001 From: JannisX11 Date: Sun, 4 Dec 2022 20:45:25 +0100 Subject: [PATCH] Change mesh selection system Faces and edges can now be selected individually rather than vertex based Closes #1050 --- js/interface/actions.js | 70 ++++++++++++++------------ js/io/formats/bbmodel.js | 4 +- js/io/project.js | 7 ++- js/misc.js | 30 +++++++---- js/modeling/mesh_editing.js | 34 +++++++------ js/modeling/transform.js | 8 +-- js/modeling/transform_gizmo.js | 8 ++- js/outliner/mesh.js | 40 ++++++++------- js/outliner/outliner.js | 2 +- js/preview/preview.js | 91 +++++++++++++++++++--------------- js/texturing/uv.js | 8 +-- js/undo.js | 10 ++-- 12 files changed, 177 insertions(+), 135 deletions(-) diff --git a/js/interface/actions.js b/js/interface/actions.js index b27eb677..a77923f4 100644 --- a/js/interface/actions.js +++ b/js/interface/actions.js @@ -1695,31 +1695,30 @@ const BARS = { icon: 'delete', category: 'edit', keybind: new Keybind({key: 46}), - click: function () { + click() { + let mesh_selection = Project.mesh_selection[Mesh.selected[0].uuid]; if (Prop.active_panel == 'textures' && Texture.selected) { Texture.selected.remove() } else if (Prop.active_panel == 'color' && ['palette', 'both'].includes(ColorPanel.vue._data.open_tab)) { if (ColorPanel.vue._data.palette.includes(ColorPanel.vue._data.main_color)) { ColorPanel.vue._data.palette.remove(ColorPanel.vue._data.main_color) } - } else if (Modes.edit && Mesh.selected.length && Project.selected_vertices[Mesh.selected[0].uuid] && Project.selected_vertices[Mesh.selected[0].uuid].length < Mesh.selected[0].vertice_list.length) { + } else if (Modes.edit && Mesh.selected.length && mesh_selection) { Undo.initEdit({elements: Mesh.selected}) Mesh.selected.forEach(mesh => { - let has_selected_faces = false; let selected_vertices = mesh.getSelectedVertices(); - for (let key in mesh.faces) { - has_selected_faces = has_selected_faces || mesh.faces[key].isSelected(); - } - if (BarItems.selection_mode.value == 'face' && has_selected_faces) { - for (let key in mesh.faces) { - let face = mesh.faces[key]; - if (face.isSelected()) { - delete mesh.faces[key]; - } - } - selected_vertices.forEach(vertex_key => { + let selected_edges = mesh.getSelectedEdges(); + let selected_faces = mesh.getSelectedFaces(); + + if (BarItems.selection_mode.value == 'face' && selected_faces.length < Object.keys(mesh.faces).length) { + let affected_vertices = []; + selected_faces.forEach(fkey => { + affected_vertices.safePush(...mesh.faces[fkey].vertices); + delete mesh.faces[fkey]; + }) + affected_vertices.forEach(vertex_key => { let used = false; for (let key in mesh.faces) { let face = mesh.faces[key]; @@ -1729,31 +1728,35 @@ const BARS = { delete mesh.vertices[vertex_key]; } }) - } else if (BarItems.selection_mode.value == 'edge' && selected_vertices.length) { + } else if (BarItems.selection_mode.value == 'edge') { for (let key in mesh.faces) { let face = mesh.faces[key]; let sorted_vertices = face.getSortedVertices(); - let selected_corners = sorted_vertices.filter(vkey => selected_vertices.includes(vkey)); - if (selected_corners.length >= 2) { - let index_diff = (sorted_vertices.indexOf(selected_corners[0]) - sorted_vertices.indexOf(selected_corners[1])) % sorted_vertices.length; - if ((sorted_vertices.length < 4 || Math.abs(index_diff) !== 2)) { - delete mesh.faces[key]; - } + let has_edge = sorted_vertices.find((vkey_a, i) => { + let vkey_b = sorted_vertices[i+1] || sorted_vertices[0]; + let edge = [vkey_a, vkey_b]; + return selected_edges.find(edge2 => sameMeshEdge(edge, edge2)) + }) + if (has_edge) { + delete mesh.faces[key]; } } - selected_vertices.forEach(vertex_key => { - let used = false; - for (let key in mesh.faces) { - let face = mesh.faces[key]; - if (face.vertices.includes(vertex_key)) used = true; - } - if (!used) { - delete mesh.vertices[vertex_key]; - } + selected_edges.forEachReverse(edge => { + edge.forEach(vkey => { + let used = false; + for (let key in mesh.faces) { + let face = mesh.faces[key]; + if (face.vertices.includes(vkey)) used = true; + } + if (!used) { + delete mesh.vertices[vkey]; + selected_vertices.remove(vkey); + selected_edges.remove(edge); + } + }) }) - } else { - let selected_vertices = Project.selected_vertices[mesh.uuid]; + } else if (BarItems.selection_mode.value == 'vertex' && selected_vertices.length < Object.keys(mesh.vertices).length) { selected_vertices.forEach(vertex_key => { delete mesh.vertices[vertex_key]; @@ -1777,6 +1780,9 @@ const BARS = { } } }) + } else { + Mesh.selected.remove(mesh); + mesh.remove(false); } }) diff --git a/js/io/formats/bbmodel.js b/js/io/formats/bbmodel.js index 0b799953..f17a0be8 100644 --- a/js/io/formats/bbmodel.js +++ b/js/io/formats/bbmodel.js @@ -147,7 +147,7 @@ var codec = new Codec('project', { selected_elements: Project.selected_elements.map(e => e.uuid), selected_group: Project.selected_group?.uuid, - selected_vertices: JSON.parse(JSON.stringify(Project.selected_vertices)), + mesh_selection: JSON.parse(JSON.stringify(Project.mesh_selection)), selected_faces: Project.selected_faces, selected_texture: Project.selected_texture?.uuid, }; @@ -399,7 +399,7 @@ var codec = new Codec('project', { }) Group.selected = (state.selected_group && Group.all.find(g => g.uuid == state.selected_group)); for (let key in state.selected_vertices) { - Project.selected_vertices[key] = state.selected_vertices[key]; + Project.mesh_selection[key] = state.mesh_selection[key]; } Project.selected_faces.replace(state.selected_faces); (state.selected_texture && Texture.all.find(t => t.uuid == state.selected_texture))?.select(); diff --git a/js/io/project.js b/js/io/project.js index 50a49c0b..697dd0b3 100644 --- a/js/io/project.js +++ b/js/io/project.js @@ -48,7 +48,7 @@ class ModelProject { this.groups = []; this.selected_elements = []; this.selected_group = null; - this.selected_vertices = {}; + this.mesh_selection = {}; this.selected_faces = []; this.textures = []; this.selected_texture = null; @@ -178,10 +178,13 @@ class ModelProject { UVEditor.vue.elements = this.selected_elements; UVEditor.vue.all_elements = this.elements; - UVEditor.vue.selected_vertices = this.selected_vertices; + UVEditor.vue.selected_vertices = {}; UVEditor.vue.selected_faces = this.selected_faces; UVEditor.vue.box_uv = this.box_uv; UVEditor.vue.display_uv = this.display_uv; + for (let uuid in this.mesh_selection) { + UVEditor.vue.selected_vertices[uuid] = this.mesh_selection[uuid].vertices; + } Interface.Panels.textures.inside_vue.textures = Texture.all; scene.add(this.model_3d); diff --git a/js/misc.js b/js/misc.js index c9045d48..ff3f217d 100644 --- a/js/misc.js +++ b/js/misc.js @@ -110,15 +110,25 @@ function updateSelection(options = {}) { obj.unselect() } if (obj instanceof Mesh) { - if (Project.selected_vertices[obj.uuid]) { - Project.selected_vertices[obj.uuid].forEachReverse(vkey => { + if (Project.mesh_selection[obj.uuid]) { + Project.mesh_selection[obj.uuid].vertices.forEachReverse(vkey => { if (vkey in obj.vertices == false) { - Project.selected_vertices[obj.uuid].remove(vkey); + Project.mesh_selection[obj.uuid].vertices.remove(vkey); + } + }) + Project.mesh_selection[obj.uuid].edges.forEachReverse(edge => { + if (!obj.vertices[edge[0]] || !obj.vertices[edge[1]]) { + Project.mesh_selection[obj.uuid].edges.remove(edge); + } + }) + Project.mesh_selection[obj.uuid].faces.forEachReverse(fkey => { + if (fkey in obj.faces == false) { + Project.mesh_selection[obj.uuid].faces.remove(fkey); } }) } - if (Project.selected_vertices[obj.uuid] && (Project.selected_vertices[obj.uuid].length == 0 || !obj.selected)) { - delete Project.selected_vertices[obj.uuid]; + if (Project.mesh_selection[obj.uuid] && (Project.mesh_selection[obj.uuid].vertices.length == 0 || !obj.selected)) { + delete Project.mesh_selection[obj.uuid]; } } }) @@ -192,13 +202,13 @@ function updateSelection(options = {}) { if (settings.highlight_cubes.value || (Mesh.all[0])) updateCubeHighlights(); if (Toolbox.selected.id == 'seam_tool' && Mesh.selected[0]) { let value; - let selected_vertices = Mesh.selected[0].getSelectedVertices(); + let selected_edges = Mesh.selected[0].getSelectedEdges(); Mesh.selected[0].forAllFaces((face) => { if (value == '') return; let vertices = face.getSortedVertices(); vertices.forEach((vkey_a, i) => { let vkey_b = vertices[i+1] || vertices[0]; - if (selected_vertices.includes(vkey_a) && selected_vertices.includes(vkey_b)) { + if (selected_edges.find(edge => sameMeshEdge(edge, [vkey_a, vkey_b]))) { let seam = Mesh.selected[0].getSeam([vkey_a, vkey_b]) || 'auto'; if (value == undefined) { value = seam; @@ -231,7 +241,7 @@ function selectAll() { let unselect = Mesh.selected[0].getSelectedVertices().length == Object.keys(Mesh.selected[0].vertices).length; Mesh.selected.forEach(mesh => { if (unselect) { - delete Project.selected_vertices[mesh.uuid]; + delete Project.mesh_selection[mesh.uuid]; } else { mesh.getSelectedVertices(true).replace(Object.keys(mesh.vertices)); } @@ -261,8 +271,8 @@ function unselectAll() { Group.all.forEach(function(s) { s.selected = false }) - for (let key in Project.selected_vertices) { - delete Project.selected_vertices[key]; + for (let key in Project.mesh_selection) { + delete Project.mesh_selection[key]; } TickUpdates.selection = true; } diff --git a/js/modeling/mesh_editing.js b/js/modeling/mesh_editing.js index 857920c3..ec51e68e 100644 --- a/js/modeling/mesh_editing.js +++ b/js/modeling/mesh_editing.js @@ -1,3 +1,7 @@ +function sameMeshEdge(edge_a, edge_b) { + return edge_a.equals(edge_b) || (edge_a[0] == edge_b[1] && edge_a[1] == edge_b[0]) +} + BARS.defineActions(function() { let add_mesh_dialog = new Dialog({ id: 'add_primitive', @@ -319,7 +323,7 @@ BARS.defineActions(function() { onChange({value}) { if (value === 'object') { Mesh.selected.forEach(mesh => { - delete Project.selected_vertices[mesh.uuid]; + delete Project.mesh_selection[mesh.uuid]; }) } else if (value === 'face') { UVEditor.vue.selected_faces.empty(); @@ -653,19 +657,16 @@ BARS.defineActions(function() { Undo.initEdit({elements: Mesh.selected, selection: true}, amended); Mesh.selected.forEach(mesh => { - let original_vertices = Project.selected_vertices[mesh.uuid].slice(); + let original_vertices = mesh.getSelectedVertices().slice(); let new_vertices; let new_face_keys = []; - let selected_faces = []; - let selected_face_keys = []; + let selected_face_keys = mesh.getSelectedFaces(); + let selected_faces = selected_face_keys.map(fkey => mesh.faces[fkey]); let combined_direction; - for (let fkey in mesh.faces) { - let face = mesh.faces[fkey]; - if (face.isSelected()) { - selected_faces.push(face); - selected_face_keys.push(fkey); - } - } + + selected_faces.forEach(face => { + original_vertices.safePush(...face.vertices); + }) if (original_vertices.length >= 3 && !selected_faces.length) { let [a, b, c] = original_vertices.slice(0, 3).map(vkey => mesh.vertices[vkey].slice()); @@ -738,7 +739,7 @@ BARS.defineActions(function() { vector.V3_add(direction.map(v => v * extend)); return vector; })) - Project.selected_vertices[mesh.uuid].replace(new_vertices); + Project.mesh_selection[mesh.uuid].vertices.replace(new_vertices); // Move Faces selected_faces.forEach(face => { @@ -850,7 +851,7 @@ BARS.defineActions(function() { function runEdit(amended, offset = 50) { Undo.initEdit({elements: Mesh.selected, selection: true}, amended); Mesh.selected.forEach(mesh => { - let original_vertices = Project.selected_vertices[mesh.uuid].slice(); + let original_vertices = mesh.getSelectedVertices(); if (original_vertices.length < 3) return; let new_vertices; let selected_faces = []; @@ -897,7 +898,7 @@ BARS.defineActions(function() { }).filter(vec => vec instanceof Array)) if (!new_vertices.length) return; - Project.selected_vertices[mesh.uuid].replace(new_vertices); + Project.mesh_selection[mesh.uuid].vertices.replace(new_vertices); // Move Faces selected_faces.forEach(face => { @@ -1458,6 +1459,7 @@ BARS.defineActions(function() { Mesh.selected.forEach(mesh => { let selected_vertices = mesh.getSelectedVertices(); + let mesh_selection = Project.mesh_selection[mesh.uuid]; let copy = new Mesh(mesh); elements.push(copy); @@ -1494,8 +1496,8 @@ BARS.defineActions(function() { copy.name += '_selection' copy.sortInBefore(mesh, 1).init(); - delete Project.selected_vertices[mesh.uuid]; - Project.selected_vertices[copy.uuid] = selected_vertices; + delete Project.mesh_selection[mesh.uuid]; + Project.mesh_selection[copy.uuid] = mesh_selection; mesh.preview_controller.updateGeometry(mesh); selected[selected.indexOf(mesh)] = copy; }) diff --git a/js/modeling/transform.js b/js/modeling/transform.js index eeb0b165..194cccbb 100644 --- a/js/modeling/transform.js +++ b/js/modeling/transform.js @@ -346,7 +346,7 @@ const Vertexsnap = { Vertexsnap.move_origin = typeof data.vertex !== 'string' && data.vertex.allEqual(0); Vertexsnap.elements = Outliner.selected.slice(); Vertexsnap.group = Group.selected; - Vertexsnap.selected_vertices = JSON.parse(JSON.stringify(Project.selected_vertices)); + Vertexsnap.selected_vertices = JSON.parse(JSON.stringify(Project.mesh_selection)); Vertexsnap.clearVertexGizmos() $('#preview').css('cursor', (Vertexsnap.step1 ? 'copy' : 'alias')) @@ -435,7 +435,7 @@ const Vertexsnap = { var cube_pos = new THREE.Vector3().copy(global_delta) if (obj instanceof Mesh && Vertexsnap.selected_vertices && Vertexsnap.selected_vertices[obj.uuid]) { - let vertices = Vertexsnap.selected_vertices[obj.uuid]; + let vertices = Vertexsnap.selected_vertices[obj.uuid].vertices; var q = obj.mesh.getWorldQuaternion(Reusable.quat1).invert(); cube_pos.applyQuaternion(q); let cube_pos_array = cube_pos.toArray(); @@ -948,7 +948,7 @@ function rotateOnAxis(modify, axis, slider) { } } - if (!Group.selected && obj instanceof Mesh && Project.selected_vertices[obj.uuid] && Project.selected_vertices[obj.uuid].length > 0) { + if (!Group.selected && obj instanceof Mesh && Project.mesh_selection[obj.uuid] && Project.mesh_selection[obj.uuid].vertices.length > 0) { let normal = axis == 0 ? THREE.NormalX : (axis == 1 ? THREE.NormalY : THREE.NormalZ) let rotWorldMatrix = new THREE.Matrix4(); @@ -970,7 +970,7 @@ function rotateOnAxis(modify, axis, slider) { let vector = new THREE.Vector3(); let local_pivot = obj.mesh.worldToLocal(new THREE.Vector3().copy(Transformer.position)) - Project.selected_vertices[obj.uuid].forEach(key => { + Project.mesh_selection[obj.uuid].vertices.forEach(key => { vector.fromArray(obj.vertices[key]); vector.sub(local_pivot); vector.applyQuaternion(q); diff --git a/js/modeling/transform_gizmo.js b/js/modeling/transform_gizmo.js index db47b0c2..dfaf3b0f 100644 --- a/js/modeling/transform_gizmo.js +++ b/js/modeling/transform_gizmo.js @@ -910,7 +910,13 @@ //Center if (Toolbox.selected.id === 'rotate_tool' || Toolbox.selected.id === 'pivot_tool') { - if (rotation_object instanceof Mesh && Toolbox.selected.id === 'rotate_tool' && Project.selected_vertices[rotation_object.uuid] && Project.selected_vertices[rotation_object.uuid].length > 0) { + if (rotation_object instanceof Mesh && Toolbox.selected.id === 'rotate_tool' && + Project.mesh_selection[rotation_object.uuid] && ( + Project.mesh_selection[rotation_object.uuid].vertices.length > 0 || + Project.mesh_selection[rotation_object.uuid].edges.length > 0 || + Project.mesh_selection[rotation_object.uuid].faces.length > 0 + ) + ) { this.position.copy(rotation_object.getWorldCenter()) } else if (rotation_object.mesh) { rotation_object.mesh.getWorldPosition(this.position); diff --git a/js/outliner/mesh.js b/js/outliner/mesh.js index 237b3ddd..c27a024e 100644 --- a/js/outliner/mesh.js +++ b/js/outliner/mesh.js @@ -161,10 +161,7 @@ class MeshFace extends Face { [this.vertices[0], this.vertices[1]] = [this.vertices[1], this.vertices[0]]; } isSelected() { - let selected_vertices = Project.selected_vertices[this.mesh.uuid]; - return selected_vertices - && selected_vertices.length > 1 - && !this.vertices.find(key => !selected_vertices.includes(key)) + return !!Project.mesh_selection[this.mesh.uuid] && Project.mesh_selection[this.mesh.uuid].faces.includes(this.getFaceKey()); } getSortedVertices() { if (this.vertices.length < 4) return this.vertices; @@ -322,13 +319,13 @@ class Mesh extends OutlinerElement { let key = edge.slice(0, 2).sort().join('_'); return this.seams[key]; } - getWorldCenter(ignore_selected_vertices) { + getWorldCenter(ignore_mesh_selection) { let m = this.mesh; let pos = Reusable.vec1.set(0, 0, 0); let vertice_count = 0; for (let key in this.vertices) { - if (ignore_selected_vertices || !Project.selected_vertices[this.uuid] || (Project.selected_vertices[this.uuid] && Project.selected_vertices[this.uuid].includes(key))) { + if (ignore_mesh_selection || !Project.mesh_selection[this.uuid] || (Project.mesh_selection[this.uuid] && Project.mesh_selection[this.uuid].vertices.includes(key))) { let vector = this.vertices[key]; pos.x += vector[0]; pos.y += vector[1]; @@ -439,17 +436,16 @@ class Mesh extends OutlinerElement { return el; } getSelectedVertices(make) { - if (make && !Project.selected_vertices[this.uuid]) Project.selected_vertices[this.uuid] = []; - return Project.selected_vertices[this.uuid] || []; + if (make && !Project.mesh_selection[this.uuid]) Project.mesh_selection[this.uuid] = {vertices: [], edges: [], faces: []}; + return Project.mesh_selection[this.uuid]?.vertices || []; } - getSelectedFaces() { - let faces = []; - for (let key in this.faces) { - if (this.faces[key].isSelected()) { - faces.push(key); - } - } - return faces; + getSelectedEdges(make) { + if (make && !Project.mesh_selection[this.uuid]) Project.mesh_selection[this.uuid] = {vertices: [], edges: [], faces: []}; + return Project.mesh_selection[this.uuid]?.edges || []; + } + getSelectedFaces(make) { + if (make && !Project.mesh_selection[this.uuid]) Project.mesh_selection[this.uuid] = {vertices: [], edges: [], faces: []}; + return Project.mesh_selection[this.uuid]?.faces || []; } getSelectionRotation() { if (Transformer.dragging) { @@ -621,7 +617,7 @@ class Mesh extends OutlinerElement { TickUpdates.selection = true; } resize(val, axis, negative, allow_negative, bidirectional) { - let selected_vertices = Project.selected_vertices[this.uuid] || Object.keys(this.vertices); + let selected_vertices = Project.mesh_selection[this.uuid]?.vertices || Object.keys(this.vertices); let range = [Infinity, -Infinity]; let {vec1, vec2} = Reusable; let rotation_inverted = new THREE.Euler().copy(Transformer.rotation_selection).invert(); @@ -988,6 +984,8 @@ new NodePreviewController(Mesh, { let join_selected = new THREE.Color(0x6bffcb); let divide_selected = new THREE.Color(0xff8c69); let selected_vertices = element.getSelectedVertices(); + let selected_edges = element.getSelectedEdges(); + let selected_faces = element.getSelectedFaces(); if (BarItems.selection_mode.value == 'vertex') { let colors = []; @@ -1011,7 +1009,10 @@ new NodePreviewController(Mesh, { let selected; if (!Modes.edit || BarItems.selection_mode.value == 'object') { color = gizmo_colors.outline; - } else if (selected_vertices.includes(key) && selected_vertices.includes(key_b)) { + } else if (BarItems.selection_mode.value == 'edge' && selected_edges.find(edge => sameMeshEdge([key, key_b], edge))) { + color = white; + selected = true; + } else if (BarItems.selection_mode.value == 'face' && selected_faces.find(fkey => element.faces[fkey].vertices.includes(key) && element.faces[fkey].vertices.includes(key_b))) { color = white; selected = true; } else { @@ -1046,13 +1047,14 @@ new NodePreviewController(Mesh, { ) ? 1 : 0; let array = new Array(mesh.geometry.attributes.highlight.count).fill(highlighted); + let selection_mode = BarItems.selection_mode.value; if (!force_off && element.selected && Modes.edit) { let i = 0; for (let fkey in element.faces) { let face = element.faces[fkey]; if (face.vertices.length < 3) continue; - if (face.isSelected()) { + if (face.isSelected() && selection_mode == 'face') { for (let j = 0; j < face.vertices.length; j++) { array[i] = 2; i++; diff --git a/js/outliner/outliner.js b/js/outliner/outliner.js index 6da64ed6..31c5dccc 100644 --- a/js/outliner/outliner.js +++ b/js/outliner/outliner.js @@ -1202,7 +1202,7 @@ BARS.defineActions(function() { } else if (Modes.edit && Mesh.selected.length && Mesh.selected.length === Outliner.selected.length && BarItems.selection_mode.value !== 'object') { Mesh.selected.forEach(mesh => { - delete Project.selected_vertices[mesh.uuid]; + delete Project.mesh_selection[mesh.uuid]; }) updateSelection(); diff --git a/js/preview/preview.js b/js/preview/preview.js index 71d052eb..c7d840bd 100644 --- a/js/preview/preview.js +++ b/js/preview/preview.js @@ -803,6 +803,8 @@ class Preview { let mesh = data.element; let selected_vertices = mesh.getSelectedVertices(true); + let selected_edges = mesh.getSelectedEdges(true); + let selected_faces = mesh.getSelectedFaces(true); if (event.altKey || Pressing.overrides.alt) { @@ -829,21 +831,22 @@ class Preview { if (!(event.ctrlOrCmd || Pressing.overrides.ctrl || event.shiftKey || Pressing.overrides.shift)) { selected_vertices.empty(); + selected_edges.empty(); + selected_faces.empty(); UVEditor.vue.selected_faces.empty(); } processed_faces.forEach(face => { - Project.selected_vertices[data.element.uuid].safePush(...face.vertices); + selected_vertices.safePush(...face.vertices); let fkey = face.getFaceKey(); UVEditor.vue.selected_faces.push(fkey); + selected_faces.push(fkey); }); } else { - if (!(event.ctrlOrCmd || Pressing.overrides.ctrl || event.shiftKey || Pressing.overrides.shift)) { - } let face_vkeys = data.element.faces[data.face].vertices; if (event.ctrlOrCmd || Pressing.overrides.ctrl || event.shiftKey || Pressing.overrides.shift) { - if (face_vkeys.allAre(vkey => selected_vertices.includes(vkey))) { + if (selected_faces.includes(data.face)) { let selected_faces = data.element.getSelectedFaces(); let vkeys_to_remove = face_vkeys.filter(vkey => { return !selected_faces.find(fkey => { @@ -853,13 +856,17 @@ class Preview { if (vkeys_to_remove.length == 0) vkeys_to_remove.push(face_vkeys[0]); selected_vertices.remove(...vkeys_to_remove); UVEditor.vue.selected_faces.remove(data.face); + selected_faces.remove(data.face); } else { selected_vertices.safePush(...face_vkeys); UVEditor.vue.selected_faces.safePush(data.face); + selected_faces.safePush(data.face); } } else { + selected_edges.empty(); selected_vertices.replace(face_vkeys); UVEditor.vue.selected_faces.replace([data.face]); + selected_faces.replace([data.face]); } } @@ -878,34 +885,38 @@ class Preview { } else if (data.type == 'vertex' && Toolbox.selected.id !== 'vertex_snap_tool') { - if (!Project.selected_vertices[data.element.uuid]) { - Project.selected_vertices[data.element.uuid] = []; - } - let list = Project.selected_vertices[data.element.uuid]; + let list = data.element.getSelectedVertices(true); + let edges = data.element.getSelectedEdges(true); + let faces = data.element.getSelectedEdges(true); if (event.ctrlOrCmd || Pressing.overrides.ctrl || event.shiftKey || Pressing.overrides.shift) { list.toggle(data.vertex); } else { - unselectOtherNodes() + unselectOtherNodes(); list.replace([data.vertex]); + edges.empty(); + faces.empty(); } updateSelection(); } else if (data.type == 'line') { - if (!Project.selected_vertices[data.element.uuid]) { - Project.selected_vertices[data.element.uuid] = []; - } - let list = Project.selected_vertices[data.element.uuid]; + let vertices = data.element.getSelectedVertices(true); + let edges = data.element.getSelectedEdges(true); + let faces = data.element.getSelectedEdges(true); if (event.ctrlOrCmd || Pressing.overrides.ctrl || event.shiftKey || Pressing.overrides.shift) { - if (list.includes(data.vertices[0]) && list.includes(data.vertices[1])) { - list.remove(...data.vertices); + let index = edges.findIndex(edge => sameMeshEdge(edge, data.vertices)) + if (index >= 0) { + vertices.remove(...data.vertices); + edges.splice(index, 1); } else { - list.remove(...data.vertices); - list.push(...data.vertices); + edges.push(data.vertices); + vertices.push(...data.vertices); } } else { - list.replace(data.vertices); + faces.empty(); + edges.splice(0, Infinity, data.vertices); + vertices.replace(data.vertices); unselectOtherNodes(); } if (event.altKey || Pressing.overrides.alt) { @@ -935,7 +946,8 @@ class Preview { let opposite_index_diff = sorted_vertices.indexOf(opposite_vertices[0]) - sorted_vertices.indexOf(opposite_vertices[1]); if (opposite_index_diff == 1 || opposite_index_diff < -2) opposite_vertices.reverse(); - list.safePush(...side_vertices); + vertices.safePush(...side_vertices); + edges.push(side_vertices); // Find next (and previous) face function doNextFace(index) { @@ -1080,10 +1092,7 @@ class Preview { $(this.node).append(this.selection.box) this.selection.activated = false; this.selection.old_selected = Outliner.selected.slice(); - this.selection.old_vertices_selected = {}; - for (let uuid in Project.selected_vertices) { - this.selection.old_vertices_selected[uuid] = Project.selected_vertices[uuid].slice(); - } + this.selection.old_mesh_selection = JSON.parse(JSON.stringify(Project.mesh_selection)); this.moveSelRect(event) } @@ -1163,15 +1172,15 @@ class Preview { if (element instanceof Mesh && (selection_mode == 'object' || scope.selection.old_selected.includes(element))) { - let selected_vertices; + let mesh_selection; if (selection_mode != 'object') { isSelected = true; - if (!Project.selected_vertices[element.uuid]) { - selected_vertices = Project.selected_vertices[element.uuid] = []; + if (!Project.mesh_selection[element.uuid]) { + mesh_selection = Project.mesh_selection[element.uuid] = {vertices: [], edges: [], faces: []}; } else { - selected_vertices = Project.selected_vertices[element.uuid]; + mesh_selection = Project.mesh_selection[element.uuid]; } - if (!extend_selection) selected_vertices.empty(); + if (!extend_selection) mesh_selection.vertices.empty(); } let vertex_points = {}; @@ -1183,19 +1192,17 @@ class Preview { is_on_screen = true; } } - if (is_on_screen && extend_selection) { - for (let vkey in element.vertices) { - if (extend_selection && this.selection.old_vertices_selected[element.uuid] && this.selection.old_vertices_selected[element.uuid].includes(vkey)) { - selected_vertices.safePush(vkey); - } - } + if (extend_selection && this.selection.old_mesh_selection[element.uuid]) { + mesh_selection.vertices.safePush(...this.selection.old_mesh_selection[element.uuid].vertices); + mesh_selection.edges.safePush(...this.selection.old_mesh_selection[element.uuid].edges); + mesh_selection.faces.safePush(...this.selection.old_mesh_selection[element.uuid].faces); } if (!is_on_screen) { } else if (selection_mode == 'vertex') { for (let vkey in element.vertices) { let point = vertex_points[vkey]; - if (!selected_vertices.includes(vkey) && pointInRectangle(point, rect_start, rect_end)) { - selected_vertices.push(vkey); + if (!mesh_selection.vertices.includes(vkey) && pointInRectangle(point, rect_start, rect_end)) { + mesh_selection.vertices.push(vkey); } } @@ -1209,7 +1216,11 @@ class Preview { let p1 = vertex_points[vkey]; let p2 = vertex_points[vkey2]; if (lineIntersectsReactangle(p1, p2, rect_start, rect_end)) { - selected_vertices.safePush(vkey); + mesh_selection.vertices.safePush(vkey); + let edge = [vkey, vkey2]; + if (!mesh_selection.edges.find(edge2 => sameMeshEdge(edge, edge2))) { + mesh_selection.edges.push(edge); + } } } } @@ -1229,16 +1240,18 @@ class Preview { break; } } - if (face_intersects && selection_mode == 'object') { + if (selection_mode == 'object') { if (face_intersects) { isSelected = true; break; } } else { if (face_intersects) { - selected_vertices.safePush(...face.vertices); + mesh_selection.vertices.safePush(...face.vertices); + mesh_selection.faces.safePush(fkey); UVEditor.vue.selected_faces.safePush(fkey); } else { + mesh_selection.faces.remove(fkey); UVEditor.vue.selected_faces.remove(fkey); } } diff --git a/js/texturing/uv.js b/js/texturing/uv.js index ee3a0b0c..eae0346c 100644 --- a/js/texturing/uv.js +++ b/js/texturing/uv.js @@ -551,9 +551,9 @@ const UVEditor = { obj.preview_controller.updateUV(obj); }) Mesh.selected.forEach(mesh => { - let selected_vertices = Project.selected_vertices[mesh.uuid]; + let selected_vertices = mesh.getSelectedVertices(); - if (selected_vertices) { + if (selected_vertices.length) { UVEditor.vue.selected_faces.forEach(fkey => { if (!mesh.faces[fkey]) return selected_vertices.forEach(vkey => { @@ -2344,7 +2344,7 @@ Interface.definePanels(function() { UVEditor.getMappableElements().forEach(el => { if (el instanceof Mesh) { - delete Project.selected_vertices[el.uuid]; + delete Project.mesh_selection[el.uuid]; } }) @@ -3328,7 +3328,7 @@ Interface.definePanels(function() { } else if (elements[0] instanceof Mesh) { var face = UVEditor.getReferenceFace(); if (face) { - let selected_vertices = Project.selected_vertices[elements[0].uuid]; + let selected_vertices = elements[0].getSelectedVertices(); let has_selected_vertices = selected_vertices && face.vertices.find(vkey => selected_vertices.includes(vkey)) let min = Infinity; face.vertices.forEach(vkey => { diff --git a/js/undo.js b/js/undo.js index 3ec76207..dd04736c 100644 --- a/js/undo.js +++ b/js/undo.js @@ -292,8 +292,8 @@ class UndoSystem { elements.forEach(function(obj) { if (save.selection.includes(obj.uuid)) { obj.selectLow() - if (save.selected_vertices[obj.uuid]) { - Project.selected_vertices[obj.uuid] = save.selected_vertices[obj.uuid]; + if (save.mesh_selection[obj.uuid]) { + Project.mesh_selection[obj.uuid] = save.mesh_selection[obj.uuid]; } } }) @@ -474,11 +474,11 @@ UndoSystem.save = class { if (aspects.selection) { this.selection = []; - this.selected_vertices = {}; + this.mesh_selection = {}; selected.forEach(obj => { this.selection.push(obj.uuid); - if (obj instanceof Mesh) { - this.selected_vertices[obj.uuid] = Mesh.selected[0].getSelectedVertices().slice(); + if (obj instanceof Mesh && Project.mesh_selection[obj.uuid]) { + this.mesh_selection[obj.uuid] = JSON.parse(JSON.stringify(Project.mesh_selection[obj.uuid])); } }) if (Group.selected) {