mirror of
https://github.com/JannisX11/blockbench.git
synced 2025-02-17 16:20:13 +08:00
Add loop cuts
Improve mesh rendering Improve mesh editing and deleting
This commit is contained in:
parent
330721d361
commit
39d92d7393
@ -1535,6 +1535,15 @@ const BARS = {
|
||||
if (face.vertices.length > 2) {
|
||||
face.vertices.remove(vertex_key);
|
||||
delete face.uv[vertex_key];
|
||||
|
||||
if (face.vertices.length == 2) {
|
||||
for (let fkey2 in mesh.faces) {
|
||||
if (fkey2 != key && !face.vertices.find(vkey => !mesh.faces[fkey2].vertices.includes(vkey))) {
|
||||
delete mesh.faces[key];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
delete mesh.faces[key];
|
||||
}
|
||||
@ -1544,7 +1553,7 @@ const BARS = {
|
||||
})
|
||||
|
||||
Undo.finishEdit('Delete mesh part')
|
||||
Canvas.updateView({elements: Mesh.selected, selection: true, element_aspects: {geometry: true, faces: true}})
|
||||
Canvas.updateView({elements: Mesh.selected, selection: true, element_aspects: {geometry: true, faces: true, uv: Mesh.selected.length > 0}})
|
||||
|
||||
} else if ((Modes.edit || Modes.paint) && (selected.length || Group.selected)) {
|
||||
|
||||
|
@ -26,16 +26,17 @@ class MeshFace extends Face {
|
||||
return this;
|
||||
}
|
||||
getNormal(normalize) {
|
||||
if (this.vertices.length < 3) return [0, 0, 0];
|
||||
let vertices = this.getSortedVertices();
|
||||
if (vertices.length < 3) return [0, 0, 0];
|
||||
let a = [
|
||||
this.mesh.vertices[this.vertices[1]][0] - this.mesh.vertices[this.vertices[0]][0],
|
||||
this.mesh.vertices[this.vertices[1]][1] - this.mesh.vertices[this.vertices[0]][1],
|
||||
this.mesh.vertices[this.vertices[1]][2] - this.mesh.vertices[this.vertices[0]][2],
|
||||
this.mesh.vertices[vertices[1]][0] - this.mesh.vertices[vertices[0]][0],
|
||||
this.mesh.vertices[vertices[1]][1] - this.mesh.vertices[vertices[0]][1],
|
||||
this.mesh.vertices[vertices[1]][2] - this.mesh.vertices[vertices[0]][2],
|
||||
]
|
||||
let b = [
|
||||
this.mesh.vertices[this.vertices[2]][0] - this.mesh.vertices[this.vertices[0]][0],
|
||||
this.mesh.vertices[this.vertices[2]][1] - this.mesh.vertices[this.vertices[0]][1],
|
||||
this.mesh.vertices[this.vertices[2]][2] - this.mesh.vertices[this.vertices[0]][2],
|
||||
this.mesh.vertices[vertices[2]][0] - this.mesh.vertices[vertices[0]][0],
|
||||
this.mesh.vertices[vertices[2]][1] - this.mesh.vertices[vertices[0]][1],
|
||||
this.mesh.vertices[vertices[2]][2] - this.mesh.vertices[vertices[0]][2],
|
||||
]
|
||||
let direction = [
|
||||
a[1] * b[2] - a[2] * b[1],
|
||||
@ -236,6 +237,18 @@ class Mesh extends OutlinerElement {
|
||||
el.uuid = this.uuid
|
||||
return el;
|
||||
}
|
||||
getSelectedVertices() {
|
||||
return Project.selected_vertices[this.uuid] || [];
|
||||
}
|
||||
getSelectedFaces() {
|
||||
let faces = [];
|
||||
for (let key in this.faces) {
|
||||
if (this.faces[key].isSelected()) {
|
||||
faces.push(key);
|
||||
}
|
||||
}
|
||||
return faces;
|
||||
}
|
||||
setColor(index) {
|
||||
this.color = index;
|
||||
if (this.visibility) {
|
||||
@ -310,6 +323,11 @@ class Mesh extends OutlinerElement {
|
||||
Mesh.prototype.rotatable = true;
|
||||
Mesh.prototype.needsUniqueName = false;
|
||||
Mesh.prototype.menu = new Menu([
|
||||
'extrude_mesh_selection',
|
||||
'split_faces',
|
||||
'create_face',
|
||||
'invert_face',
|
||||
'_',
|
||||
'group_elements',
|
||||
'_',
|
||||
'copy',
|
||||
@ -453,7 +471,7 @@ new NodePreviewController(Mesh, {
|
||||
face_indices[key] = index_offset + i;
|
||||
})
|
||||
|
||||
let normal = face.getNormal();
|
||||
let normal = face.getNormal(true);
|
||||
normal_array.push(...normal, ...normal, ...normal, ...normal);
|
||||
|
||||
let sorted_vertices = face.getSortedVertices();
|
||||
@ -487,7 +505,6 @@ new NodePreviewController(Mesh, {
|
||||
|
||||
mesh.outline.geometry.setAttribute('position', new THREE.BufferAttribute(new Float32Array(outline_positions), 3));
|
||||
|
||||
mesh.geometry.computeVertexNormals();
|
||||
mesh.geometry.computeBoundingBox();
|
||||
mesh.geometry.computeBoundingSphere();
|
||||
},
|
||||
@ -674,7 +691,7 @@ BARS.defineActions(function() {
|
||||
icon: 'fas.fa-draw-polygon',
|
||||
category: 'edit',
|
||||
keybind: new Keybind({key: 'f', shift: true}),
|
||||
condition: () => (Modes.edit && Format.meshes),
|
||||
condition: () => (Modes.edit && Format.meshes && Mesh.selected[0] && Mesh.selected[0].getSelectedVertices().length > 1),
|
||||
click() {
|
||||
Undo.initEdit({elements: Mesh.selected});
|
||||
Mesh.selected.forEach(mesh => {
|
||||
@ -790,10 +807,10 @@ BARS.defineActions(function() {
|
||||
})
|
||||
new Action({
|
||||
id: 'invert_face',
|
||||
icon: 'fas.fa-draw-polygon',
|
||||
icon: 'flip_to_back',
|
||||
category: 'edit',
|
||||
keybind: new Keybind({key: 'i', shift: true}),
|
||||
condition: () => (Modes.edit && Format.meshes),
|
||||
condition: () => (Modes.edit && Format.meshes && Mesh.selected[0] && Mesh.selected[0].getSelectedFaces().length),
|
||||
click() {
|
||||
Undo.initEdit({elements: Mesh.selected});
|
||||
Mesh.selected.forEach(mesh => {
|
||||
@ -813,7 +830,7 @@ BARS.defineActions(function() {
|
||||
icon: 'upload',
|
||||
category: 'edit',
|
||||
keybind: new Keybind({key: 'e', shift: true}),
|
||||
condition: () => (Modes.edit && Format.meshes),
|
||||
condition: () => (Modes.edit && Format.meshes && Mesh.selected[0] && Mesh.selected[0].getSelectedVertices().length),
|
||||
click() {
|
||||
Undo.initEdit({elements: Mesh.selected});
|
||||
Mesh.selected.forEach(mesh => {
|
||||
@ -886,6 +903,129 @@ BARS.defineActions(function() {
|
||||
Canvas.updateView({elements: Mesh.selected, element_aspects: {geometry: true, uv: true, faces: true}, selection: true})
|
||||
}
|
||||
})
|
||||
new Action({
|
||||
id: 'split_faces',
|
||||
icon: 'carpenter',
|
||||
category: 'edit',
|
||||
keybind: new Keybind({key: 'e', shift: true}),
|
||||
condition: () => (Modes.edit && Format.meshes && Mesh.selected[0] && Mesh.selected[0].getSelectedVertices().length > 1),
|
||||
click() {
|
||||
Undo.initEdit({elements: Mesh.selected});
|
||||
Mesh.selected.forEach(mesh => {
|
||||
let selected_vertices = mesh.getSelectedVertices();
|
||||
let start_face;
|
||||
for (let fkey in mesh.faces) {
|
||||
let face = mesh.faces[fkey];
|
||||
if (face.vertices.length < 3) continue;
|
||||
let vertices = face.vertices.filter(vkey => selected_vertices.includes(vkey))
|
||||
if (vertices.length >= 2) {
|
||||
start_face = face;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!start_face) return;
|
||||
let processed_faces = [start_face];
|
||||
let center_vertices = {};
|
||||
|
||||
function getCenterVertex(vertices) {
|
||||
let existing_key = center_vertices[vertices[0]] || center_vertices[vertices[1]];
|
||||
if (existing_key) return existing_key;
|
||||
|
||||
let vector = mesh.vertices[vertices[0]].slice().V3_add(mesh.vertices[vertices[1]]).V3_divide(2);
|
||||
let [vkey] = mesh.addVertices(vector);
|
||||
center_vertices[vertices[0]] = center_vertices[vertices[1]] = vkey;
|
||||
return vkey;
|
||||
}
|
||||
|
||||
function splitFace(face, side_vertices, double_side) {
|
||||
processed_faces.push(face);
|
||||
|
||||
if (face.vertices.length == 4) {
|
||||
|
||||
let sorted_vertices = face.getSortedVertices();
|
||||
let opposite_vertices = sorted_vertices.filter(vkey => !side_vertices.includes(vkey));
|
||||
|
||||
let side_index_diff = sorted_vertices.indexOf(side_vertices[0]) - sorted_vertices.indexOf(side_vertices[1]);
|
||||
if (side_index_diff == -1 || side_index_diff > 2) side_vertices.reverse();
|
||||
|
||||
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();
|
||||
|
||||
let center_vertices = [
|
||||
getCenterVertex(side_vertices),
|
||||
getCenterVertex(opposite_vertices)
|
||||
]
|
||||
|
||||
let c1_uv_coords = [
|
||||
(face.uv[side_vertices[0]][0] + face.uv[side_vertices[1]][0]) / 2,
|
||||
(face.uv[side_vertices[0]][1] + face.uv[side_vertices[1]][1]) / 2,
|
||||
];
|
||||
let c2_uv_coords = [
|
||||
(face.uv[opposite_vertices[0]][0] + face.uv[opposite_vertices[1]][0]) / 2,
|
||||
(face.uv[opposite_vertices[0]][1] + face.uv[opposite_vertices[1]][1]) / 2,
|
||||
];
|
||||
|
||||
let new_face = new MeshFace(mesh, face).extend({
|
||||
vertices: [side_vertices[1], center_vertices[0], center_vertices[1], opposite_vertices[1]],
|
||||
uv: {
|
||||
[side_vertices[1]]: face.uv[side_vertices[1]],
|
||||
[center_vertices[0]]: c1_uv_coords,
|
||||
[center_vertices[1]]: c2_uv_coords,
|
||||
[opposite_vertices[1]]: face.uv[opposite_vertices[1]],
|
||||
}
|
||||
})
|
||||
face.extend({
|
||||
vertices: [opposite_vertices[0], center_vertices[0], center_vertices[1], side_vertices[0]],
|
||||
uv: {
|
||||
[opposite_vertices[0]]: face.uv[opposite_vertices[0]],
|
||||
[center_vertices[0]]: c1_uv_coords,
|
||||
[center_vertices[1]]: c2_uv_coords,
|
||||
[side_vertices[0]]: face.uv[side_vertices[0]],
|
||||
}
|
||||
})
|
||||
mesh.addFaces(new_face);
|
||||
|
||||
// Find next (and previous) face
|
||||
for (let fkey in mesh.faces) {
|
||||
let ref_face = mesh.faces[fkey];
|
||||
if (ref_face.vertices.length < 3 || processed_faces.includes(ref_face)) continue;
|
||||
let vertices = ref_face.vertices.filter(vkey => opposite_vertices.includes(vkey))
|
||||
if (vertices.length >= 2) {
|
||||
splitFace(ref_face, opposite_vertices);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (double_side) {
|
||||
for (let fkey in mesh.faces) {
|
||||
let ref_face = mesh.faces[fkey];
|
||||
if (ref_face.vertices.length < 3 || processed_faces.includes(ref_face)) continue;
|
||||
let vertices = ref_face.vertices.filter(vkey => side_vertices.includes(vkey))
|
||||
if (vertices.length >= 2) {
|
||||
splitFace(ref_face, side_vertices);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
//splitFace(start_face, );
|
||||
}
|
||||
|
||||
let start_vertices = start_face.vertices.filter((vkey, i) => selected_vertices.includes(vkey)).slice(0, 2);
|
||||
splitFace(start_face, start_vertices, start_face.vertices.length == 4);
|
||||
|
||||
selected_vertices.empty();
|
||||
for (let key in center_vertices) {
|
||||
selected_vertices.safePush(center_vertices[key]);
|
||||
}
|
||||
})
|
||||
Undo.finishEdit('Extrude mesh selection')
|
||||
Canvas.updateView({elements: Mesh.selected, element_aspects: {geometry: true, uv: true, faces: true}, selection: true})
|
||||
}
|
||||
})
|
||||
new Action({
|
||||
id: 'merge_meshes',
|
||||
icon: 'upload',
|
||||
|
@ -353,6 +353,9 @@ class Preview {
|
||||
if (element.mesh.vertex_points && element.mesh.vertex_points.visible) {
|
||||
objects.push(element.mesh.vertex_points);
|
||||
}
|
||||
if (element instanceof Mesh && element.mesh.outline.visible && BarItems.selection_mode.value == 'vertex') {
|
||||
objects.push(element.mesh.outline);
|
||||
}
|
||||
}
|
||||
})
|
||||
if (Vertexsnap.vertexes.children.length) {
|
||||
@ -371,16 +374,14 @@ class Preview {
|
||||
}
|
||||
var intersects = this.raycaster.intersectObjects( objects );
|
||||
if (intersects.length > 0) {
|
||||
if (intersects.length > 1 && intersects.find(intersect => intersect.object.type == 'Points')) {
|
||||
var intersect = intersects.find(intersect => intersect.object.type == 'Points')
|
||||
} else {
|
||||
var intersect = intersects[0]
|
||||
}
|
||||
let mesh_gizmo = intersects.find(intersect => intersect.object.type == 'Points' || intersect.object.type == 'LineSegments');
|
||||
let intersect = mesh_gizmo || intersects[0];
|
||||
let intersect_object = intersect.object
|
||||
|
||||
if (intersect_object.isElement) {
|
||||
var element = OutlinerNode.uuids[intersect_object.name]
|
||||
let face;
|
||||
console.log(intersects)
|
||||
if (element instanceof Cube) {
|
||||
face = Canvas.face_order[Math.floor(intersect.faceIndex / 2)];
|
||||
} else if (element instanceof Mesh) {
|
||||
@ -415,6 +416,17 @@ class Preview {
|
||||
intersect,
|
||||
vertex
|
||||
}
|
||||
} else if (intersect_object.type == 'LineSegments') {
|
||||
var element = OutlinerNode.uuids[intersect_object.parent.name];
|
||||
let vertices = intersect_object.vertex_order.slice(intersect.index, intersect.index+2);
|
||||
return {
|
||||
event,
|
||||
type: 'line',
|
||||
element,
|
||||
intersects,
|
||||
intersect,
|
||||
vertices
|
||||
}
|
||||
} else if (intersect_object.isVertex) {
|
||||
return {
|
||||
event,
|
||||
@ -746,6 +758,24 @@ class Preview {
|
||||
list.replace([data.vertex]);
|
||||
}
|
||||
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];
|
||||
|
||||
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);
|
||||
} else {
|
||||
list.remove(...data.vertices);
|
||||
list.push(...data.vertices);
|
||||
}
|
||||
} else {
|
||||
list.replace(data.vertices);
|
||||
}
|
||||
updateSelection();
|
||||
}
|
||||
if (typeof Toolbox.selected.onCanvasClick === 'function') {
|
||||
Toolbox.selected.onCanvasClick(data)
|
||||
@ -1441,6 +1471,7 @@ Blockbench.on('update_camera_position', e => {
|
||||
Preview.all.forEach(preview => {
|
||||
if (preview.canvas.isConnected && Mesh.all.length) {
|
||||
preview.raycaster.params.Points.threshold = scale * 0.6;
|
||||
preview.raycaster.params.Line.threshold = scale * 0.6;
|
||||
}
|
||||
})
|
||||
})
|
||||
|
17
js/util.js
17
js/util.js
@ -347,14 +347,15 @@ Array.prototype.equals = function (array) {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
Array.prototype.remove = function (item) { {
|
||||
var index = this.indexOf(item)
|
||||
if (index > -1) {
|
||||
this.splice(index, 1)
|
||||
return index;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
Array.prototype.remove = function (...items) {
|
||||
items.forEach(item => {
|
||||
var index = this.indexOf(item)
|
||||
if (index > -1) {
|
||||
this.splice(index, 1)
|
||||
return index;
|
||||
}
|
||||
return false;
|
||||
})
|
||||
}
|
||||
Array.prototype.empty = function() {
|
||||
this.splice(0, Infinity);
|
||||
|
@ -1008,8 +1008,8 @@
|
||||
"action.selection_mode": "Selection Mode",
|
||||
"action.selection_mode.desc": "Change how elements can be selected in the viewport",
|
||||
"action.selection_mode.object": "Object",
|
||||
"action.selection_mode.vertex": "Vertex",
|
||||
"action.selection_mode.face": "Face",
|
||||
"action.selection_mode.vertex": "Vertex/Line",
|
||||
|
||||
"action.add_display_preset": "New Preset",
|
||||
"action.add_display_preset.desc": "Add a new display setting preset",
|
||||
|
Loading…
Reference in New Issue
Block a user