From 65934f71be72cbb1d0dad43ead25e1aa67e0281a Mon Sep 17 00:00:00 2001 From: JannisX11 Date: Wed, 12 Apr 2023 23:33:02 +0200 Subject: [PATCH] Pre-compute vertex weights for proportional editing Improve mesh selection update performance --- js/modeling/mesh_editing.js | 158 ++++++++++++++++++--------------- js/modeling/transform.js | 9 +- js/modeling/transform_gizmo.js | 5 ++ js/outliner/mesh.js | 17 +++- 4 files changed, 114 insertions(+), 75 deletions(-) diff --git a/js/modeling/mesh_editing.js b/js/modeling/mesh_editing.js index 7b8cda5e..ba9c82aa 100644 --- a/js/modeling/mesh_editing.js +++ b/js/modeling/mesh_editing.js @@ -2,83 +2,99 @@ function sameMeshEdge(edge_a, edge_b) { return edge_a.equals(edge_b) || (edge_a[0] == edge_b[1] && edge_a[1] == edge_b[0]) } -function proportionallyEditMeshVertices(mesh, callback) { - if (!BarItems.proportional_editing.value) return; - - let selected_vertices = mesh.getSelectedVertices(); - let {range, falloff, selection} = StateMemory.proportional_editing_options; - let linear_distance = selection == 'linear'; +const ProportionalEdit = { + vertex_weights: {}, + calculateWeights(mesh) { + if (!BarItems.proportional_editing.value) return; - let all_mesh_connections; - if (!linear_distance) { - all_mesh_connections = {}; - for (let fkey in mesh.faces) { - let face = mesh.faces[fkey]; - face.getEdges().forEach(edge => { - if (!all_mesh_connections[edge[0]]) { - all_mesh_connections[edge[0]] = [edge[1]]; - } else { - all_mesh_connections[edge[0]].safePush(edge[1]); - } - if (!all_mesh_connections[edge[1]]) { - all_mesh_connections[edge[1]] = [edge[0]]; - } else { - all_mesh_connections[edge[1]].safePush(edge[0]); - } - }) - } - } - - for (let vkey in mesh.vertices) { - if (selected_vertices.includes(vkey)) continue; - - let distance = Infinity; - if (linear_distance) { - // Linear Distance - selected_vertices.forEach(vkey2 => { - let pos1 = mesh.vertices[vkey]; - let pos2 = mesh.vertices[vkey2]; - let distance_square = Math.pow(pos1[0] - pos2[0], 2) + Math.pow(pos1[1] - pos2[1], 2) + Math.pow(pos1[2] - pos2[2], 2); - if (distance_square < distance) { - distance = distance_square; - } - }) - distance = Math.sqrt(distance); - } else { - // Connection Distance - let found_match_depth = 0; - let scanned = []; - let frontier = [vkey]; - - depth_crawler: - for (let depth = 1; depth <= range; depth++) { - let new_frontier = []; - for (let vkey1 of frontier) { - let connections = all_mesh_connections[vkey1]?.filter(vkey2 => !scanned.includes(vkey2)); - if (!connections || connections.length == 0) continue; - scanned.push(...connections); - new_frontier.push(...connections); - } - for (let vkey2 of new_frontier) { - if (selected_vertices.includes(vkey2)) { - found_match_depth = depth; - break depth_crawler; + let selected_vertices = mesh.getSelectedVertices(); + let {range, falloff, selection} = StateMemory.proportional_editing_options; + let linear_distance = selection == 'linear'; + + let all_mesh_connections; + if (!linear_distance) { + all_mesh_connections = {}; + for (let fkey in mesh.faces) { + let face = mesh.faces[fkey]; + face.getEdges().forEach(edge => { + if (!all_mesh_connections[edge[0]]) { + all_mesh_connections[edge[0]] = [edge[1]]; + } else { + all_mesh_connections[edge[0]].safePush(edge[1]); } - } - frontier = new_frontier; - } - if (found_match_depth) { - distance = found_match_depth; + if (!all_mesh_connections[edge[1]]) { + all_mesh_connections[edge[1]] = [edge[0]]; + } else { + all_mesh_connections[edge[1]].safePush(edge[0]); + } + }) } } - if (distance > range) continue; - let blend = 1 - (distance / (linear_distance ? range : range+1)); - switch (falloff) { - case 'hermite_spline': blend = Math.hermiteBlend(blend); break; - case 'constant': blend = 1; break; + ProportionalEdit.vertex_weights[mesh.uuid] = {}; + + for (let vkey in mesh.vertices) { + if (selected_vertices.includes(vkey)) continue; + + let distance = Infinity; + if (linear_distance) { + // Linear Distance + selected_vertices.forEach(vkey2 => { + let pos1 = mesh.vertices[vkey]; + let pos2 = mesh.vertices[vkey2]; + let distance_square = Math.pow(pos1[0] - pos2[0], 2) + Math.pow(pos1[1] - pos2[1], 2) + Math.pow(pos1[2] - pos2[2], 2); + if (distance_square < distance) { + distance = distance_square; + } + }) + distance = Math.sqrt(distance); + } else { + // Connection Distance + let found_match_depth = 0; + let scanned = []; + let frontier = [vkey]; + + depth_crawler: + for (let depth = 1; depth <= range; depth++) { + let new_frontier = []; + for (let vkey1 of frontier) { + let connections = all_mesh_connections[vkey1]?.filter(vkey2 => !scanned.includes(vkey2)); + if (!connections || connections.length == 0) continue; + scanned.push(...connections); + new_frontier.push(...connections); + } + for (let vkey2 of new_frontier) { + if (selected_vertices.includes(vkey2)) { + found_match_depth = depth; + break depth_crawler; + } + } + frontier = new_frontier; + } + if (found_match_depth) { + distance = found_match_depth; + } + } + if (distance > range) continue; + + let blend = 1 - (distance / (linear_distance ? range : range+1)); + switch (falloff) { + case 'hermite_spline': blend = Math.hermiteBlend(blend); break; + case 'constant': blend = 1; break; + } + ProportionalEdit.vertex_weights[mesh.uuid][vkey] = blend; + } + }, + editVertices(mesh, per_vertex) { + if (!BarItems.proportional_editing.value) return; + + let selected_vertices = mesh.getSelectedVertices(); + for (let vkey in mesh.vertices) { + if (selected_vertices.includes(vkey)) continue; + + let blend = ProportionalEdit.vertex_weights[mesh.uuid][vkey]; + per_vertex(vkey, blend); } - callback(vkey, blend); } } diff --git a/js/modeling/transform.js b/js/modeling/transform.js index 1c537e2e..c0a7c831 100644 --- a/js/modeling/transform.js +++ b/js/modeling/transform.js @@ -113,6 +113,12 @@ function moveElementsRelative(difference, index, event) { //Multiple difference *= canvasGridSize(event.shiftKey || Pressing.overrides.shift, event.ctrlOrCmd || Pressing.overrides.ctrl); } + if (BarItems.proportional_editing.value) { + Mesh.selected.forEach(mesh => { + ProportionalEdit.calculateWeights(mesh); + }) + } + moveElementsInSpace(difference, axes[index]); updateSelection(); @@ -582,8 +588,7 @@ function moveElementsInSpace(difference, axis) { selected_vertices.forEach(key => { el.vertices[key].V3_add(difference_vec); }) - proportionallyEditMeshVertices(el, (vkey, blend) => { - console.log(vkey, blend) + ProportionalEdit.editVertices(el, (vkey, blend) => { el.vertices[vkey].V3_add(difference_vec[0] * blend, difference_vec[1] * blend, difference_vec[2] * blend); }) diff --git a/js/modeling/transform_gizmo.js b/js/modeling/transform_gizmo.js index 43db71c7..3afa2b97 100644 --- a/js/modeling/transform_gizmo.js +++ b/js/modeling/transform_gizmo.js @@ -1137,6 +1137,11 @@ } }) } + if (BarItems.proportional_editing.value) { + Mesh.selected.forEach(mesh => { + ProportionalEdit.calculateWeights(mesh); + }) + } } if (rotate_group) { diff --git a/js/outliner/mesh.js b/js/outliner/mesh.js index 6ce00a29..a3136db2 100644 --- a/js/outliner/mesh.js +++ b/js/outliner/mesh.js @@ -1039,6 +1039,18 @@ new NodePreviewController(Mesh, { mesh.outline.geometry.needsUpdate = true; } + let face_outlines = {}; + if (BarItems.selection_mode.value == 'face' || BarItems.selection_mode.value == 'cluster') { + selected_faces.forEach(fkey => { + let face = element.faces[fkey]; + face.vertices.forEach(vkey => { + if (!face_outlines[vkey]) face_outlines[vkey] = new Set(); + face.vertices.forEach(vkey2 => { + if (vkey2 != vkey) face_outlines[vkey].add(vkey2); + }) + }) + }) + } let line_colors = []; mesh.outline.vertex_order.forEach((key, i) => { let key_b = Modes.edit && mesh.outline.vertex_order[i + ((i%2) ? -1 : 1) ]; @@ -1049,7 +1061,7 @@ new NodePreviewController(Mesh, { } 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' || BarItems.selection_mode.value == 'cluster') && selected_faces.find(fkey => element.faces[fkey].vertices.includes(key) && element.faces[fkey].vertices.includes(key_b))) { + } else if ((BarItems.selection_mode.value == 'face' || BarItems.selection_mode.value == 'cluster') && face_outlines[key] && face_outlines[key].has(key_b)) { color = white; selected = true; } else { @@ -1085,13 +1097,14 @@ new NodePreviewController(Mesh, { let array = new Array(mesh.geometry.attributes.highlight.count).fill(highlighted); let selection_mode = BarItems.selection_mode.value; + let selected_faces = element.getSelectedFaces(); 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() && (selection_mode == 'face' || selection_mode == 'cluster')) { + if (selected_faces.indexOf(fkey) != -1 && (selection_mode == 'face' || selection_mode == 'cluster')) { for (let j = 0; j < face.vertices.length; j++) { array[i] = 2; i++;