mirror of
https://github.com/JannisX11/blockbench.git
synced 2025-04-06 17:31:09 +08:00
parent
40e54713cd
commit
07d251131c
@ -313,6 +313,7 @@ export const MenuBar = {
|
||||
'apply_mesh_rotation',
|
||||
'split_mesh',
|
||||
'merge_meshes',
|
||||
'boolean_operation',
|
||||
], {icon: 'fa-gem', condition: {selected: {mesh: true}, modes: ['edit']}})
|
||||
|
||||
new BarMenu('uv', UVEditor.menu.structure, {
|
||||
|
@ -1,3 +1,7 @@
|
||||
import { CSG } from 'three-csg-ts';
|
||||
import { THREE } from '../../lib/libs';
|
||||
|
||||
|
||||
export function sameMeshEdge(edge_a, edge_b) {
|
||||
return edge_a.equals(edge_b) || (edge_a[0] == edge_b[1] && edge_a[1] == edge_b[0])
|
||||
}
|
||||
@ -3471,6 +3475,163 @@ BARS.defineActions(function() {
|
||||
Canvas.updateView({elements, element_aspects: {geometry: true, uv: true, faces: true}, selection: true})
|
||||
}
|
||||
})
|
||||
function booleanOperation(mode) {
|
||||
if (Mesh.selected.length != 2) {
|
||||
Blockbench.showQuickMessage('Select the mesh you want to edit first, then select the reference mesh.');
|
||||
return;
|
||||
}
|
||||
let mesh_a = Mesh.selected[0];
|
||||
let mesh_b = Mesh.selected[1];
|
||||
let result;
|
||||
|
||||
Undo.initEdit({elements: [mesh_a, mesh_b], outliner: true});
|
||||
|
||||
mesh_a.mesh.applyMatrix4(mesh_a.mesh.parent.matrixWorld);
|
||||
mesh_b.mesh.applyMatrix4(mesh_b.mesh.parent.matrixWorld);
|
||||
|
||||
switch (mode) {
|
||||
case 'subtract': result = CSG.subtract(mesh_a.mesh, mesh_b.mesh); break;
|
||||
case 'union': result = CSG.union(mesh_a.mesh, mesh_b.mesh); break;
|
||||
case 'intersect': result = CSG.intersect(mesh_a.mesh, mesh_b.mesh); break;
|
||||
}
|
||||
|
||||
let old_faces = Object.assign({}, mesh_a.faces);
|
||||
let old_vertices = Object.assign({}, mesh_a.vertices);
|
||||
let half_matching = {};
|
||||
for (let fkey in mesh_a.faces) {
|
||||
delete mesh_a.faces[fkey];
|
||||
}
|
||||
for (let vkey in mesh_a.vertices) {
|
||||
delete mesh_a.vertices[vkey];
|
||||
}
|
||||
|
||||
function findVertexMatch(pos, vertices) {
|
||||
vertices: for (let vkey in vertices) {
|
||||
let i = 0;
|
||||
axis: for (let val of pos) {
|
||||
if (!Math.epsilon(val, vertices[vkey][i])) {
|
||||
continue vertices;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
return vkey;
|
||||
}
|
||||
}
|
||||
function findFaceMatch(vertex_keys) {
|
||||
for (let fkey in old_faces) {
|
||||
let face = old_faces[fkey];
|
||||
if (vertex_keys.allAre(vkey => face.vertices.includes(vkey))) {
|
||||
return {fkey, face};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let attr_position = result.geometry.getAttribute('position');
|
||||
let attr_uv = result.geometry.getAttribute('uv');
|
||||
let fallback_texture = Texture.getDefault();
|
||||
for (let i = 0; i < attr_position.count/3; i++) {
|
||||
// Tri
|
||||
let vertices = [];
|
||||
let uv_data = [];
|
||||
for (let j = 0; j < 3; j++) {
|
||||
// Vertex
|
||||
let arr_offset = (3*i + j) * 3;
|
||||
let pos = [
|
||||
attr_position.array[arr_offset + 0],
|
||||
attr_position.array[arr_offset + 1],
|
||||
attr_position.array[arr_offset + 2],
|
||||
];
|
||||
vertices.push(pos);
|
||||
let uv_offset = (3*i + j) * 2;
|
||||
let uv = [
|
||||
attr_uv.array[uv_offset + 0],
|
||||
attr_uv.array[uv_offset + 1],
|
||||
]
|
||||
uv_data.push(uv);
|
||||
}
|
||||
|
||||
let vertex_keys = vertices.map(pos => {
|
||||
let original_vertex = findVertexMatch(pos, old_vertices);
|
||||
if (original_vertex) {
|
||||
mesh_a.vertices[original_vertex] = old_vertices[original_vertex].slice();
|
||||
return original_vertex;
|
||||
}
|
||||
let vkey = findVertexMatch(pos, mesh_a.vertices);
|
||||
if (vkey) {
|
||||
return vkey;
|
||||
}
|
||||
return mesh_a.addVertices(pos)[0];
|
||||
});
|
||||
let matching_face = findFaceMatch(vertex_keys);
|
||||
console.log({vertex_keys, matching_face, attr_uv});
|
||||
|
||||
function reAddFace(matching_face) {
|
||||
mesh_a.faces[matching_face.fkey] = matching_face.face;
|
||||
for (let vkey of matching_face.face.vertices) {
|
||||
if (!mesh_a.vertices[vkey]) {
|
||||
mesh_a.vertices[vkey] = old_vertices[vkey].slice();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (matching_face) {
|
||||
reAddFace(matching_face);
|
||||
let face_texture = matching_face.face.getTexture();
|
||||
if (face_texture instanceof Texture && (!fallback_texture || face_texture != fallback_texture)) {
|
||||
fallback_texture = face_texture;
|
||||
}
|
||||
} else {
|
||||
let uv = {};
|
||||
let i = 0;
|
||||
for (let vkey of vertex_keys) {
|
||||
uv[vkey] = [
|
||||
uv_data[i][0] * Project.getUVWidth(fallback_texture),
|
||||
(1-uv_data[i][1]) * Project.getUVHeight(fallback_texture),
|
||||
];
|
||||
i++;
|
||||
}
|
||||
console.log(vertex_keys);
|
||||
let new_face = new MeshFace(mesh_a, {
|
||||
vertices: vertex_keys,
|
||||
uv,
|
||||
texture: fallback_texture ? fallback_texture.uuid : undefined
|
||||
})
|
||||
let [fkey] = mesh_a.addFaces(new_face);
|
||||
}
|
||||
}
|
||||
mesh_b.remove();
|
||||
Mesh.preview_controller.updateAll(mesh_a);
|
||||
updateSelection();
|
||||
Undo.finishEdit('Mesh boolean operation', {elements: [mesh_a], outliner: true});
|
||||
}
|
||||
new Action('boolean_operation', {
|
||||
icon: 'masked_transitions',
|
||||
category: 'edit',
|
||||
condition: {modes: ['edit'], features: ['meshes'], selected: {mesh: true}},
|
||||
click() {
|
||||
new Menu(this.children).open('mouse');
|
||||
},
|
||||
children: [
|
||||
{
|
||||
id: 'subtract',
|
||||
name: 'action.boolean_operation.subtract',
|
||||
icon: 'north_east',
|
||||
click() {booleanOperation('subtract')}
|
||||
},
|
||||
{
|
||||
id: 'union',
|
||||
name: 'action.boolean_operation.union',
|
||||
icon: 'close_fullscreen',
|
||||
click() {booleanOperation('union')}
|
||||
},
|
||||
{
|
||||
id: 'intersect',
|
||||
name: 'action.boolean_operation.intersect',
|
||||
icon: 'expand_less',
|
||||
click() {booleanOperation('intersect')}
|
||||
},
|
||||
]
|
||||
})
|
||||
let import_obj_dialog;
|
||||
new Action('import_obj', {
|
||||
icon: 'fa-gem',
|
||||
|
@ -40,9 +40,15 @@ export class MeshFace extends Face {
|
||||
if (vertices.length == 4 && alt_tri) {
|
||||
indices = [0, 2, 3];
|
||||
}
|
||||
let base = this.mesh.vertices[vertices[indices[0]]];
|
||||
let a = this.mesh.vertices[vertices[indices[1]]].slice().V3_subtract(base);
|
||||
let b = this.mesh.vertices[vertices[indices[2]]].slice().V3_subtract(base);
|
||||
let base, a, b;
|
||||
try {
|
||||
base = this.mesh.vertices[vertices[indices[0]]];
|
||||
a = this.mesh.vertices[vertices[indices[1]]].slice().V3_subtract(base);
|
||||
b = this.mesh.vertices[vertices[indices[2]]].slice().V3_subtract(base);
|
||||
} catch (err) {
|
||||
console.log(this, this.getFaceKey(), this.vertices.map(vkey => this.mesh.vertices[vkey]));
|
||||
return [0, 0, 0];
|
||||
}
|
||||
let direction = [
|
||||
a[1] * b[2] - a[2] * b[1],
|
||||
a[2] * b[0] - a[0] * b[2],
|
||||
@ -923,6 +929,7 @@ export class Mesh extends OutlinerElement {
|
||||
new MenuSeparator('mesh_combination'),
|
||||
'split_mesh',
|
||||
'merge_meshes',
|
||||
'boolean_operation',
|
||||
...Outliner.control_menu_group,
|
||||
new MenuSeparator('settings'),
|
||||
'allow_element_mirror_modeling',
|
||||
|
@ -1635,6 +1635,11 @@
|
||||
"action.merge_vertices.merge_by_distance_in_center": "Merge by Distance in Center",
|
||||
"action.merge_meshes": "Merge Meshes",
|
||||
"action.merge_meshes.desc": "Merge multiple meshes into one",
|
||||
"action.boolean_operation": "Boolean Operation",
|
||||
"action.boolean_operation.desc": "Merge, subtract, or intersect the first selected mesh with another mesh",
|
||||
"action.boolean_operation.subtract": "Subtract",
|
||||
"action.boolean_operation.union": "Union",
|
||||
"action.boolean_operation.intersect": "Intersect",
|
||||
"action.proportional_editing": "Proportional Editing",
|
||||
"action.proportional_editing.desc": "Proportionally affect surrounding vertices when editing parts of a mesh",
|
||||
"action.proportional_editing_range": "Proportional Editing Range",
|
||||
|
11
package-lock.json
generated
11
package-lock.json
generated
@ -23,6 +23,7 @@
|
||||
"peerjs": "^1.5.4",
|
||||
"prismjs": "^1.29.0",
|
||||
"three": "^0.174.0",
|
||||
"three-csg-ts": "^3.2.0",
|
||||
"tinycolor2": "^1.6.0",
|
||||
"vue": "^2.7.16",
|
||||
"vue-sortable": "^0.1.3",
|
||||
@ -7655,6 +7656,16 @@
|
||||
"integrity": "sha512-p+WG3W6Ov74alh3geCMkGK9NWuT62ee21cV3jEnun201zodVF4tCE5aZa2U122/mkLRmhJJUQmLLW1BH00uQJQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/three-csg-ts": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/three-csg-ts/-/three-csg-ts-3.2.0.tgz",
|
||||
"integrity": "sha512-oTYg8kdal6qgHDbso/6VzA12Udf2ic2uXhf0XlJzuSP+Gs0OUR5gTHSZ7GotAE+M/QcVlw41eOwiWZVnJG5/8w==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@types/three": ">= 0.154.0",
|
||||
"three": ">= 0.154.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tiny-typed-emitter": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/tiny-typed-emitter/-/tiny-typed-emitter-2.1.0.tgz",
|
||||
|
@ -161,6 +161,7 @@
|
||||
"peerjs": "^1.5.4",
|
||||
"prismjs": "^1.29.0",
|
||||
"three": "^0.174.0",
|
||||
"three-csg-ts": "^3.2.0",
|
||||
"tinycolor2": "^1.6.0",
|
||||
"vue": "^2.7.16",
|
||||
"vue-sortable": "^0.1.3",
|
||||
|
Loading…
x
Reference in New Issue
Block a user