mirror of
https://github.com/JannisX11/blockbench.git
synced 2025-01-18 15:26:19 +08:00
Add auto mesh editing fixes
This commit is contained in:
parent
c62729362f
commit
a1c80bfeae
@ -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() {
|
||||
|
@ -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];
|
||||
|
@ -1618,6 +1618,7 @@
|
||||
Undo.finishEdit('Move selection')
|
||||
}
|
||||
}
|
||||
autoFixMeshEdit()
|
||||
updateSelection()
|
||||
|
||||
} else if (Modes.id === 'animate' && scope.keyframes && scope.keyframes.length && keep_changes) {
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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.",
|
||||
|
Loading…
Reference in New Issue
Block a user