Add auto mesh editing fixes

This commit is contained in:
JannisX11 2024-03-01 01:21:07 +01:00
parent c62729362f
commit a1c80bfeae
6 changed files with 235 additions and 0 deletions

View File

@ -98,6 +98,186 @@ const ProportionalEdit = {
}
}
async function autoFixMeshEdit() {
let meshes = Mesh.selected;
if (!meshes.length || !Modes.edit || BarItems.selection_mode.value == 'object') return;
// Merge Vertices
let overlaps = {};
let e = 0.004;
meshes.forEach(mesh => {
let mesh_overlaps = {};
let vertices = mesh.getSelectedVertices();
for (let vkey of vertices) {
let vertex = mesh.vertices[vkey];
let matches = [];
for (let vkey2 in mesh.vertices) {
if (vkey2 == vkey || mesh_overlaps[vkey2]) continue;
let vertex2 = mesh.vertices[vkey2];
let same_spot = Math.epsilon(vertex[0], vertex2[0], e) && Math.epsilon(vertex[1], vertex2[1], e) && Math.epsilon(vertex[2], vertex2[2], e);
if (same_spot) {
matches.push(vkey2);
}
}
if (matches.length) {
mesh_overlaps[vkey] = matches;
}
}
if (Object.keys(mesh_overlaps).length) overlaps[mesh.uuid] = mesh_overlaps;
})
if (Object.keys(overlaps).length) {
await new Promise(resolve => {Blockbench.showMessageBox({
title: 'message.auto_fix_mesh_edit.title',
message: 'message.auto_fix_mesh_edit.overlapping_vertices',
commands: {
merge: 'message.auto_fix_mesh_edit.merge_vertices',
revert: 'message.auto_fix_mesh_edit.revert'
},
buttons: ['dialog.ignore']
}, result => {
if (result == 'revert') {
Undo.undo();
} else if (result == 'merge') {
let meshes = Mesh.selected.filter(m => overlaps[m.uuid]);
Undo.initEdit({ elements: meshes });
let merge_counter = 0;
let cluster_counter = 0;
for (let mesh_id in overlaps) {
let mesh = Mesh.selected.find(m => m.uuid == mesh_id);
let selected_vertices = mesh.getSelectedVertices(true);
for (let first_vertex in overlaps[mesh_id]) {
let other_vertices = overlaps[mesh_id][first_vertex];
cluster_counter++;
for (let vkey of other_vertices) {
for (let fkey in mesh.faces) {
let face = mesh.faces[fkey];
let index = face.vertices.indexOf(vkey);
if (index === -1) continue;
if (face.vertices.includes(first_vertex)) {
face.vertices.remove(vkey);
delete face.uv[vkey];
if (face.vertices.length < 2) {
delete mesh.faces[fkey];
} else if (face.vertices.length == 2) {
// Find face that overlaps the remaining edge
for (let fkey2 in mesh.faces) {
let face2 = mesh.faces[fkey2];
if (face2.vertices.length >= 3 && face2.vertices.includes(face.vertices[0]) && face2.vertices.includes(face.vertices[1])) {
delete mesh.faces[fkey];
}
}
}
} else {
let uv = face.uv[vkey];
face.vertices.splice(index, 1, first_vertex);
face.uv[first_vertex] = uv;
delete face.uv[vkey];
}
}
delete mesh.vertices[vkey];
selected_vertices.remove(vkey);
merge_counter++;
}
}
}
Undo.finishEdit('Auto-merge vertices')
Canvas.updateView({elements: meshes, element_aspects: {geometry: true, uv: true, faces: true}, selection: true});
Blockbench.showQuickMessage(tl('message.merged_vertices', [merge_counter, cluster_counter]), 2000);
}
resolve();
})})
}
// Concave quads
let concave_faces = {};
meshes.forEach(mesh => {
let selected_faces = mesh.getSelectedFaces();
let selected_vertices = mesh.getSelectedVertices();
let concave_faces_mesh = [];
for (let fkey in mesh.faces) {
let face = mesh.faces[fkey];
if (face.vertices.length != 4) continue;
// Check if face is selected or touches selection
if (!selected_faces.includes(fkey) && !face.vertices.find(vkey => selected_vertices.includes(vkey))) continue;
//let vertices = face.getSortedVertices().map(vkey => mesh.vertices[vkey]);
let concave = face.isConcave();
if (concave != false) {
concave_faces_mesh.push([fkey, concave]);
}
}
if (concave_faces_mesh.length) concave_faces[mesh.uuid] = concave_faces_mesh;
})
if (Object.keys(concave_faces).length) {
await new Promise(resolve => {Blockbench.showMessageBox({
title: 'message.auto_fix_mesh_edit.title',
message: 'message.auto_fix_mesh_edit.concave_quads',
commands: {
split: 'message.auto_fix_mesh_edit.split_quads',
revert: 'message.auto_fix_mesh_edit.revert'
},
buttons: ['dialog.ignore']
}, result => {
if (result == 'revert') {
Undo.undo();
} else if (result == 'split') {
let meshes = Mesh.selected.filter(m => concave_faces[m.uuid]);
Undo.initEdit({ elements: meshes });
for (let mesh of meshes) {
for (let [fkey, concave_vkey] of concave_faces[mesh.uuid]) {
let face = mesh.faces[fkey];
// Find the edge that needs to be connected
let sorted_vertices = face.getSortedVertices();
let edges = [
[sorted_vertices[0], sorted_vertices[1]],
[sorted_vertices[0], sorted_vertices[2]],
[sorted_vertices[0], sorted_vertices[3]],
[sorted_vertices[1], sorted_vertices[2]],
[sorted_vertices[1], sorted_vertices[3]],
[sorted_vertices[2], sorted_vertices[3]],
]
edges = edges.filter(edge => {
for (let fkey2 in mesh.faces) {
if (fkey2 == fkey) continue;
let face2 = mesh.faces[fkey2];
if (face2.vertices.includes(edge[0]) && face2.vertices.includes(edge[1])) {
return false;
}
}
return true;
})
let off_corners = edges.find(edge => !edge.includes(concave_vkey))
if (!off_corners) return;
let new_face = new MeshFace(mesh, face);
new_face.vertices.remove(off_corners[0]);
delete new_face.uv[off_corners[0]];
face.vertices.remove(off_corners[1]);
delete face.uv[off_corners[1]];
let [face_key] = mesh.addFaces(new_face);
UVEditor.selected_faces.push(face_key);
if (face.getAngleTo(new_face) > 90) {
new_face.invert();
}
}
}
Undo.finishEdit('Auto-fix concave quads');
Canvas.updateView({elements: meshes, element_aspects: {geometry: true, uv: true, faces: true}, selection: true});
}
resolve();
})})
}
}
SharedActions.add('delete', {
condition: () => Modes.edit && Prop.active_panel == 'preview' && Mesh.selected[0] && Project.mesh_selection[Mesh.selected[0].uuid],
run() {

View File

@ -123,6 +123,7 @@ function moveElementsRelative(difference, index, event) { //Multiple
updateSelection();
Undo.finishEdit('Move elements')
autoFixMeshEdit()
}
//Rotate
function rotateSelected(axis, steps) {
@ -223,6 +224,7 @@ function mirrorSelected(axis) {
})
updateSelection()
Undo.finishEdit('Flip selection')
autoFixMeshEdit()
}
}
@ -490,6 +492,7 @@ const Vertexsnap = {
}
Canvas.updateView(update_options);
Undo.finishEdit('Use vertex snap');
autoFixMeshEdit()
Vertexsnap.step1 = true;
}
}
@ -1039,6 +1042,7 @@ BARS.defineActions(function() {
},
onAfter: function() {
Undo.finishEdit('Change element position')
autoFixMeshEdit()
}
})
new NumSlider('slider_pos_y', {
@ -1059,6 +1063,7 @@ BARS.defineActions(function() {
},
onAfter: function() {
Undo.finishEdit('Change element position')
autoFixMeshEdit()
}
})
new NumSlider('slider_pos_z', {
@ -1079,6 +1084,7 @@ BARS.defineActions(function() {
},
onAfter: function() {
Undo.finishEdit('Change element position')
autoFixMeshEdit()
}
})
let slider_vector_pos = [BarItems.slider_pos_x, BarItems.slider_pos_y, BarItems.slider_pos_z];
@ -1118,6 +1124,7 @@ BARS.defineActions(function() {
},
onAfter: function() {
Undo.finishEdit('Change element size')
autoFixMeshEdit()
}
})
new NumSlider('slider_size_y', {
@ -1142,6 +1149,7 @@ BARS.defineActions(function() {
},
onAfter: function() {
Undo.finishEdit('Change element size')
autoFixMeshEdit()
}
})
new NumSlider('slider_size_z', {
@ -1166,6 +1174,7 @@ BARS.defineActions(function() {
},
onAfter: function() {
Undo.finishEdit('Change element size')
autoFixMeshEdit()
}
})
let slider_vector_size = [BarItems.slider_size_x, BarItems.slider_size_y, BarItems.slider_size_z];

View File

@ -1618,6 +1618,7 @@
Undo.finishEdit('Move selection')
}
}
autoFixMeshEdit()
updateSelection()
} else if (Modes.id === 'animate' && scope.keyframes && scope.keyframes.length && keep_changes) {

View File

@ -196,6 +196,41 @@ class MeshFace extends Face {
}
return vertices;
}
isConcave() {
if (this.vertices.length < 4) return false;
let {vec1, vec2, vec3, vec4} = Reusable;
let normal_vec = vec1.fromArray(this.getNormal(true));
let plane = new THREE.Plane().setFromNormalAndCoplanarPoint(
normal_vec,
vec2.fromArray(this.mesh.vertices[this.vertices[0]])
)
let sorted_vertices = this.getSortedVertices();
let rot = cameraTargetToRotation([0, 0, 0], normal_vec.toArray());
let e = new THREE.Euler(Math.degToRad(rot[1] - 90), Math.degToRad(rot[0] + 180), 0);
let flat_positions = sorted_vertices.map(vkey => {
let coplanar_pos = plane.projectPoint(vec3.fromArray(this.mesh.vertices[vkey]), vec4);
coplanar_pos.applyEuler(e);
return [coplanar_pos.x, coplanar_pos.z];
})
let angles = [];
for (let i = 0; i < sorted_vertices.length; i++) {
let a = flat_positions[i];
let b = flat_positions[(i+1) % 4];
let direction = b.slice().V2_subtract(a);
let angle = Math.atan2(direction[1], direction[0]);
angles.push(angle);
}
for (let i = 0; i < sorted_vertices.length; i++) {
let a = angles[i];
let b = angles[(i+1) % 4];
let difference = Math.trimRad(b - a);
if (difference > 0) {
return sorted_vertices[(i+1) % 4];
}
}
return false;
}
getEdges() {
let vertices = this.getSortedVertices();
if (vertices.length == 2) {

View File

@ -50,6 +50,9 @@ Math.epsilon = function(a, b, epsilon = 0.001) {
Math.trimDeg = function(a) {
return (a+180*15)%360-180
}
Math.trimRad = function(a) {
return (a+Math.PI*15)%(Math.PI*2)-Math.PI
}
Math.isPowerOfTwo = function(x) {
return (x > 1) && ((x & (x - 1)) == 0);
}

View File

@ -378,6 +378,13 @@
"message.small_face_dimensions.message": "The selection contains faces that are smaller than 1 unit in one direction. The Box UV mapping system considers any faces below that threshold as 0 pixels wide. The texture on those faces therefore may not work correctly.",
"message.small_face_dimensions.face_uv": "The current format supports per-face UV maps which can handle small face dimensions. Go to \"File\" > \"Project...\" and change \"UV Mode\" to \"Per-face UV\".",
"message.auto_fix_mesh_edit.title": "Auto Fix",
"message.auto_fix_mesh_edit.revert": "Revert Edit",
"message.auto_fix_mesh_edit.overlapping_vertices": "Certain vertices have been moved into the same spot. How would you like to proceed?",
"message.auto_fix_mesh_edit.concave_quads": "One or more quad faces are now concave, which can cause issues in UV-mapping and rendering. Blockbench can automatically fix it for you:",
"message.auto_fix_mesh_edit.merge_vertices": "Merge Vertices",
"message.auto_fix_mesh_edit.split_quads": "Split Quads",
"message.merged_vertices": "Found and merged %0 vertices in %1 locations",
"message.preview_scene_load_failed": "Failed to load preview scene. Check your internet connection.",