blockbench/js/outliner/mesh.js
2023-07-19 23:43:56 +02:00

1264 lines
39 KiB
JavaScript

class MeshFace extends Face {
constructor(mesh, data) {
super(data);
this.mesh = mesh;
this.uv = {};
this.texture = false;
if (data) {
this.extend(data);
}
}
extend(data) {
super.extend(data);
this.vertices.forEach(key => {
if (!this.uv[key]) this.uv[key] = [0, 0];
if (data.uv && data.uv[key] instanceof Array) {
this.uv[key].replace(data.uv[key]);
}
})
for (let key in this.uv) {
if (!this.vertices.includes(key)) {
delete this.uv[key];
}
}
return this;
}
getNormal(normalize) {
let vertices = this.getSortedVertices();
if (vertices.length < 3) return [0, 0, 0];
let a = [
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[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],
a[2] * b[0] - a[0] * b[2],
a[0] * b[1] - a[1] * b[0],
]
if (normalize) {
let length = Math.sqrt(direction[0] * direction[0] + direction[1] * direction[1] + direction[2] * direction[2]);
return direction.map(dir => dir / length || 0);
} else {
return direction
}
}
getBoundingRect() {
let min_x = Project.texture_width, min_y = Project.texture_height, max_x = 0, max_y = 0;
this.vertices.forEach(vkey => {
min_x = Math.min(min_x, this.uv[vkey][0]); max_x = Math.max(max_x, this.uv[vkey][0]);
min_y = Math.min(min_y, this.uv[vkey][1]); max_y = Math.max(max_y, this.uv[vkey][1]);
})
return getRectangle(min_x, min_y, max_x, max_y);
}
getOccupationMatrix(texture_space = false, start_offset = [0, 0], matrix = {}) {
let face = this;
let rect = this.getBoundingRect();
let texture = texture_space && this.getTexture();
let sorted_vertices = this.getSortedVertices();
let factor_x = texture ? (texture.width / Project.texture_width) : 1;
let factor_y = texture ? (texture.display_height / Project.texture_height) : 1;
if (texture_space && texture) {
rect.ax *= factor_x;
rect.ay *= factor_y;
rect.bx *= factor_x;
rect.by *= factor_y;
}
function vSub(a, b) {
return [a[0]-b[0], a[1]-b[1]];
}
function getSide(a, b) {
let cosine_sign = a[0]*b[1] - a[1]*b[0];
if (cosine_sign > 0) return 1;
if (cosine_sign < 0) return -1;
}
function pointInsidePolygon(x, y) {
let previous_side;
let i = 0;
for (let vkey of sorted_vertices) {
let a = face.uv[vkey];
let b = face.uv[sorted_vertices[i+1]] || face.uv[sorted_vertices[0]];
if (factor_x !== 1 || factor_y !== 1) {
a = a ? [a[0] * factor_x, a[1] * factor_y] : [0, 0];
b = b ? [b[0] * factor_x, b[1] * factor_y] : [0, 0];
}
let affine_segment = vSub(b, a);
let affine_point = vSub([x, y], a);
let side = getSide(affine_segment, affine_point);
if (!side) return false;
if (!previous_side) previous_side = side;
if (side !== previous_side) return false;
i++;
}
return true;
}
for (let x = Math.floor(rect.ax); x < Math.ceil(rect.bx); x++) {
for (let y = Math.floor(rect.ay); y < Math.ceil(rect.by); y++) {
let matrix_x = x-start_offset[0];
let matrix_y = y-start_offset[1];
let inside = ( pointInsidePolygon(x+0.00001, y+0.00001)
|| pointInsidePolygon(x+0.99999, y+0.00001)
|| pointInsidePolygon(x+0.00001, y+0.99999)
|| pointInsidePolygon(x+0.99999, y+0.99999));
if (!inside) {
let i = 0;
let px_rect = [[x, y], [x+0.99999, y+0.99999]]
for (let vkey of sorted_vertices) {
let vkey_b = sorted_vertices[i+1] || sorted_vertices[0]
if (pointInRectangle(face.uv[vkey], ...px_rect)) {
inside = true; break;
}
if (lineIntersectsReactangle(face.uv[vkey], face.uv[vkey_b], ...px_rect)) {
inside = true; break;
}
i++;
}
}
if (inside) {
if (!matrix[matrix_x]) matrix[matrix_x] = {};
matrix[matrix_x][matrix_y] = true;
}
}
}
return matrix;
}
getUVIsland() {
let keys = [this.getFaceKey()];
function crawl(face) {
for (let i = 0; i < face.vertices.length; i++) {
let adjacent = face.getAdjacentFace(i);
if (!adjacent) continue;
if (keys.includes(adjacent.key)) continue;
let epsilon = 0.2;
let uv_a1 = adjacent.face.uv[adjacent.edge[0]];
let uv_a2 = face.uv[adjacent.edge[0]];
if (!Math.epsilon(uv_a1[0], uv_a2[0], epsilon) || !Math.epsilon(uv_a1[1], uv_a2[1], epsilon)) continue;
let uv_b1 = adjacent.face.uv[adjacent.edge[1]];
let uv_b2 = face.uv[adjacent.edge[1]];
if (!Math.epsilon(uv_b1[0], uv_b2[0], epsilon) || !Math.epsilon(uv_b1[1], uv_b2[1], epsilon)) continue;
keys.push(adjacent.key);
crawl(adjacent.face);
}
}
crawl(this);
return keys;
}
getAngleTo(other) {
let a = new THREE.Vector3().fromArray(this.getNormal());
let b = new THREE.Vector3().fromArray(other instanceof Array ? other : other.getNormal());
return Math.radToDeg(a.angleTo(b));
}
invert() {
if (this.vertices.length < 3) return this;
[this.vertices[0], this.vertices[1]] = [this.vertices[1], this.vertices[0]];
}
isSelected(fkey) {
return !!Project.mesh_selection[this.mesh.uuid] && Project.mesh_selection[this.mesh.uuid].faces.includes(fkey || this.getFaceKey());
}
getSortedVertices() {
if (this.vertices.length < 4) return this.vertices;
// Test if point "check" is on the other side of the line between "base1" and "base2", compared to "top"
function test(base1, base2, top, check) {
base1 = Canvas.temp_vectors[0].fromArray(base1);
base2 = Canvas.temp_vectors[1].fromArray(base2);
top = Canvas.temp_vectors[2].fromArray(top);
check = Canvas.temp_vectors[3].fromArray(check);
// Construct a plane with coplanar points "base1" and "base2" with a normal towards "top"
let normal = Canvas.temp_vectors[4];
new THREE.Line3(base1, base2).closestPointToPoint(top, false, normal);
normal.sub(top);
let plane = new THREE.Plane().setFromNormalAndCoplanarPoint(normal, base2);
let distance = plane.distanceToPoint(check);
return distance > 0;
}
let {mesh, vertices} = this;
if (test(mesh.vertices[vertices[1]], mesh.vertices[vertices[2]], mesh.vertices[vertices[0]], mesh.vertices[vertices[3]])) {
return [vertices[2], vertices[0], vertices[1], vertices[3]];
} else if (test(mesh.vertices[vertices[0]], mesh.vertices[vertices[1]], mesh.vertices[vertices[2]], mesh.vertices[vertices[3]])) {
return [vertices[0], vertices[2], vertices[1], vertices[3]];
}
return vertices;
}
getEdges() {
let vertices = this.getSortedVertices();
if (vertices.length == 2) {
return vertices;
} else if (vertices.length > 2) {
return vertices.map((vkey1, i) => {
let vkey2 = vertices[i+1] || vertices[0];
return [vkey1, vkey2];
})
} else {
return [];
}
}
getAdjacentFace(side_index = 0) {
let vertices = this.getSortedVertices();
side_index = side_index % this.vertices.length;
let side_vertices = [
vertices[side_index],
vertices[side_index+1] || vertices[0]
]
for (let fkey in this.mesh.faces) {
let face = this.mesh.faces[fkey];
if (face === this) continue;
if (face.vertices.includes(side_vertices[0]) && face.vertices.includes(side_vertices[1])) {
let f_vertices = face.getSortedVertices();
let index_a = f_vertices.indexOf(side_vertices[0]);
let index_b = f_vertices.indexOf(side_vertices[1]);
if (index_b - index_a == -1 || (index_b - index_a == f_vertices.length-1)) {
return {
face,
key: fkey,
index: index_b,
edge: side_vertices
}
}
}
}
return null;
}
getFaceKey() {
for (let fkey in this.mesh.faces) {
if (this.mesh.faces[fkey] == this) return fkey;
}
}
UVToLocal(uv, vertices = this.getSortedVertices()) {
let vert_a = vertices[0];
let vert_b = vertices[1];
let vert_c = vertices[2];
if (vertices[3]) {
let is_in_tri = pointInTriangle(uv, this.uv[vert_a], this.uv[vert_b], this.uv[vert_c]);
if (!is_in_tri) {
vert_a = vertices[0];
vert_b = vertices[2];
vert_c = vertices[3];
}
}
let p0 = this.uv[vert_a];
let p1 = this.uv[vert_b];
let p2 = this.uv[vert_c];
let vertexa = this.mesh.vertices[vert_a];
let vertexb = this.mesh.vertices[vert_b];
let vertexc = this.mesh.vertices[vert_c];
let b0 = (p1[0] - p0[0]) * (p2[1] - p0[1]) - (p2[0] - p0[0]) * (p1[1] - p0[1])
let b1 = ((p1[0] - uv[0]) * (p2[1] - uv[1]) - (p2[0] - uv[0]) * (p1[1] - uv[1])) / b0
let b2 = ((p2[0] - uv[0]) * (p0[1] - uv[1]) - (p0[0] - uv[0]) * (p2[1] - uv[1])) / b0
let b3 = ((p0[0] - uv[0]) * (p1[1] - uv[1]) - (p1[0] - uv[0]) * (p0[1] - uv[1])) / b0
let local_space = new THREE.Vector3(
vertexa[0] * b1 + vertexb[0] * b2 + vertexc[0] * b3,
vertexa[1] * b1 + vertexb[1] * b2 + vertexc[1] * b3,
vertexa[2] * b1 + vertexb[2] * b2 + vertexc[2] * b3,
)
return local_space;
}
localToUV(vector, vertices = this.vertices) {
let va = new THREE.Vector3().fromArray(this.mesh.vertices[vertices[0]]);
let vb = new THREE.Vector3().fromArray(this.mesh.vertices[vertices[1]]);
let vc = new THREE.Vector3().fromArray(this.mesh.vertices[vertices[2]]);
let uva = new THREE.Vector2().fromArray(this.uv[vertices[0]]);
let uvb = new THREE.Vector2().fromArray(this.uv[vertices[1]]);
let uvc = new THREE.Vector2().fromArray(this.uv[vertices[2]]);
let uv = THREE.Triangle.getUV(vector, va, vb, vc, uva, uvb, uvc, new THREE.Vector2());
return uv.toArray();
}
getCenter() {
let center = [0, 0, 0];
this.vertices.forEach(vkey => {
let vertex = this.mesh.vertices[vkey];
center.V3_add(vertex);
})
center.V3_divide(this.vertices.length);
return center;
}
}
new Property(MeshFace, 'array', 'vertices', {default: 0});
class Mesh extends OutlinerElement {
constructor(data, uuid) {
super(data, uuid)
this.vertices = {};
this.faces = {};
this.seams = {};
if (!data.vertices) {
this.addVertices([2, 4, 2], [2, 4, -2], [2, 0, 2], [2, 0, -2], [-2, 4, 2], [-2, 4, -2], [-2, 0, 2], [-2, 0, -2]);
let vertex_keys = Object.keys(this.vertices);
this.addFaces(new MeshFace( this, {vertices: [vertex_keys[0], vertex_keys[2], vertex_keys[1], vertex_keys[3]]} )); // East
this.addFaces(new MeshFace( this, {vertices: [vertex_keys[4], vertex_keys[5], vertex_keys[6], vertex_keys[7]]} )); // West
this.addFaces(new MeshFace( this, {vertices: [vertex_keys[0], vertex_keys[1], vertex_keys[4], vertex_keys[5]]} )); // Up
this.addFaces(new MeshFace( this, {vertices: [vertex_keys[2], vertex_keys[6], vertex_keys[3], vertex_keys[7]]} )); // Down
this.addFaces(new MeshFace( this, {vertices: [vertex_keys[0], vertex_keys[4], vertex_keys[2], vertex_keys[6]]} )); // South
this.addFaces(new MeshFace( this, {vertices: [vertex_keys[1], vertex_keys[3], vertex_keys[5], vertex_keys[7]]} )); // North
for (let key in this.faces) {
let face = this.faces[key];
face.uv[face.vertices[0]] = [0, 0];
face.uv[face.vertices[1]] = [0, 16];
face.uv[face.vertices[2]] = [16, 0];
face.uv[face.vertices[3]] = [16, 16];
}
}
for (var key in Mesh.properties) {
Mesh.properties[key].reset(this);
}
if (data && typeof data === 'object') {
this.extend(data)
}
}
get position() {
return this.origin;
}
get vertice_list() {
return Object.keys(this.vertices).map(key => this.vertices[key]);
}
setSeam(edge, value) {
let key = edge.slice(0, 2).sort().join('_');
if (value) {
this.seams[key] = value;
} else {
delete this.seams[key];
}
}
getSeam(edge) {
let key = edge.slice(0, 2).sort().join('_');
return this.seams[key];
}
getWorldCenter(ignore_mesh_selection) {
let m = this.mesh;
let pos = Reusable.vec1.set(0, 0, 0);
let vertex_count = 0;
for (let key in this.vertices) {
if (ignore_mesh_selection || !Project.mesh_selection[this.uuid] || (Project.mesh_selection[this.uuid] && Project.mesh_selection[this.uuid].vertices.includes(key))) {
let vector = this.vertices[key];
pos.x += vector[0];
pos.y += vector[1];
pos.z += vector[2];
vertex_count++;
}
}
if (vertex_count) {
pos.x /= vertex_count;
pos.y /= vertex_count;
pos.z /= vertex_count;
}
if (m) {
let r = m.getWorldQuaternion(Reusable.quat1);
pos.applyQuaternion(r);
pos.add(THREE.fastWorldPosition(m, Reusable.vec2));
}
return pos;
}
addVertices(...vectors) {
return vectors.map(vector => {
let key;
while (!key || this.vertices[key]) {
key = bbuid(4);
}
this.vertices[key] = [vector[0] || 0, vector[1] || 0, vector[2] || 0];
return key;
})
}
addFaces(...faces) {
return faces.map(face => {
let key;
while (!key || this.faces[key]) {
key = bbuid(8);
}
this.faces[key] = face;
return key;
})
}
extend(object) {
for (var key in Mesh.properties) {
Mesh.properties[key].merge(this, object)
}
if (typeof object.vertices == 'object') {
for (let key in this.vertices) {
if (!object.vertices[key]) {
delete this.vertices[key];
}
}
if (object.vertices instanceof Array) {
this.addVertices(...object.vertices);
} else {
for (let key in object.vertices) {
if (!this.vertices[key]) this.vertices[key] = [];
this.vertices[key].replace(object.vertices[key]);
}
}
}
if (typeof object.faces == 'object') {
for (let key in this.faces) {
if (!object.faces[key]) {
delete this.faces[key];
}
}
for (let key in object.faces) {
if (this.faces[key]) {
this.faces[key].extend(object.faces[key])
} else {
this.faces[key] = new MeshFace(this, object.faces[key]);
}
}
}
if (typeof object.seams == 'object') {
this.seams = {};
for (let key in object.seams) {
this.seams[key] = object.seams[key];
}
}
this.sanitizeName();
return this;
}
getUndoCopy(aspects = {}) {
var copy = new Mesh(this)
if (aspects.uv_only) {
copy = {
faces: copy.faces,
}
}
copy.uuid = this.uuid;
copy.type = this.type;
delete copy.parent;
for (let fkey in copy.faces) {
delete copy.faces[fkey].mesh;
}
return copy;
}
getSaveCopy(project) {
var el = {}
for (var key in Mesh.properties) {
Mesh.properties[key].copy(this, el)
}
if (Object.keys(this.seams).length) {
el.seams = {};
for (let key in this.seams) {
el.seams[key] = this.seams[key];
}
}
el.vertices = {};
for (let key in this.vertices) {
el.vertices[key] = this.vertices[key].slice();
}
el.faces = {};
for (let key in this.faces) {
el.faces[key] = this.faces[key].getSaveCopy(project);
}
el.type = 'mesh';
el.uuid = this.uuid
return el;
}
getSelectedVertices(make) {
if (make && !Project.mesh_selection[this.uuid]) Project.mesh_selection[this.uuid] = {vertices: [], edges: [], faces: []};
return Project.mesh_selection[this.uuid]?.vertices || [];
}
getSelectedEdges(make) {
if (make && !Project.mesh_selection[this.uuid]) Project.mesh_selection[this.uuid] = {vertices: [], edges: [], faces: []};
return Project.mesh_selection[this.uuid]?.edges || [];
}
getSelectedFaces(make) {
if (make && !Project.mesh_selection[this.uuid]) Project.mesh_selection[this.uuid] = {vertices: [], edges: [], faces: []};
return Project.mesh_selection[this.uuid]?.faces || [];
}
getSelectionRotation() {
if (Transformer.dragging) {
return Transformer.rotation_selection;
}
let faces = this.getSelectedFaces().map(fkey => this.faces[fkey]);
if (!faces[0]) {
let selected_vertices = this.getSelectedVertices();
this.forAllFaces((face) => {
let weight = face.vertices.filter(vkey => selected_vertices.includes(vkey)).length;
if (weight) {
faces.push({face, weight});
}
})
faces.sort((a, b) => b.weight-a.weight);
faces = faces.map(f => f.face);
}
if (faces[0]) {
let normal = faces[0].getNormal(true);
let normal2 = faces[1] && faces[1].getNormal(true);
if (normal2) {
let object = new THREE.Object3D();
object.up.set(...normal);
object.lookAt(...normal2);
return object.rotation;
} else {
var y = Math.atan2(normal[0], normal[2]);
var x = Math.atan2(normal[1], Math.sqrt(Math.pow(normal[0], 2) + Math.pow(normal[2], 2)));
return new THREE.Euler(-x, y, 0, 'YXZ');
}
}
return new THREE.Euler();
}
getCenter(global) {
let center = [0, 0, 0];
let len = 0;
for (let vkey in this.vertices) {
center.V3_add(this.vertices[vkey]);
len++;
}
center.V3_divide(len);
if (global) {
return this.mesh.localToWorld(Reusable.vec1.set(...center)).toArray();
} else {
return center;
}
}
getSize(axis, selection_only) {
if (selection_only) {
let selected_vertices = Project.mesh_selection[this.uuid]?.vertices || Object.keys(this.vertices);
if (!selected_vertices.length) return 0;
let range = [Infinity, -Infinity];
let {vec1, vec2} = Reusable;
let rotation_inverted = new THREE.Euler().copy(Transformer.rotation_selection).invert();
selected_vertices.forEach(key => {
vec1.fromArray(this.vertices[key]).applyEuler(rotation_inverted);
range[0] = Math.min(range[0], vec1.getComponent(axis));
range[1] = Math.max(range[1], vec1.getComponent(axis));
})
return range[1] - range[0];
} else {
let range = [Infinity, -Infinity];
for (let vkey in this.vertices) {
range[0] = Math.min(range[0], this.vertices[vkey][axis]);
range[1] = Math.max(range[1], this.vertices[vkey][axis]);
}
return range[1] - range[0];
}
}
forAllFaces(cb) {
for (let fkey in this.faces) {
cb(this.faces[fkey], fkey);
}
}
transferOrigin(origin, update = true) {
if (!this.mesh) return;
var q = new THREE.Quaternion().copy(this.mesh.quaternion);
var shift = new THREE.Vector3(
this.origin[0] - origin[0],
this.origin[1] - origin[1],
this.origin[2] - origin[2],
)
shift.applyQuaternion(q.invert());
shift = shift.toArray();
for (let vkey in this.vertices) {
this.vertices[vkey].V3_add(shift);
}
this.origin.V3_set(origin);
this.preview_controller.updateTransform(this);
this.preview_controller.updateGeometry(this);
return this;
}
setColor(index) {
this.color = index;
if (this.visibility) {
this.preview_controller.updateFaces(this);
}
}
roll(axis, steps, origin_arg) {
function rotateCoord(array, rotation_origin) {
var a, b;
array.forEach(function(s, i) {
if (i == axis) {
//
} else {
if (a == undefined) {
a = s - rotation_origin[i]
b = i
} else {
array[b] = s - rotation_origin[i]
array[b] = rotation_origin[b] - array[b]
array[i] = rotation_origin[i] + a;
}
}
})
return array
}
while (steps > 0) {
steps--;
for (let vkey in this.vertices) {
rotateCoord(this.vertices[vkey], [0, 0, 0]);
}
if (origin_arg) {
rotateCoord(this.origin, origin_arg)
}
}
//Rotations
var i = 0;
var temp_rot = undefined;
var temp_i = undefined;
while (i < 3) {
if (i !== axis) {
if (temp_rot === undefined) {
temp_rot = this.rotation[i]
temp_i = i
} else {
this.rotation[temp_i] = -this.rotation[i]
this.rotation[i] = temp_rot
}
}
i++;
}
this.preview_controller.updateTransform(this);
this.preview_controller.updateGeometry(this);
return this;
}
flip(axis, center) {
for (let vkey in this.vertices) {
this.vertices[vkey][axis] *= -1;
}
for (let key in this.faces) {
this.faces[key].invert();
}
this.origin[axis] *= -1;
this.rotation.forEach((n, i) => {
if (i != axis) this.rotation[i] = -n;
})
this.preview_controller.updateTransform(this);
this.preview_controller.updateGeometry(this);
this.preview_controller.updateUV(this);
return this;
}
flipSelection(axis, center) {
let object_mode = BarItems.selection_mode.value == 'object' || !!Group.selected;
let selected_vertices = this.getSelectedVertices();
for (let vkey in this.vertices) {
if (object_mode || selected_vertices.includes(vkey)) {
this.vertices[vkey][axis] *= -1;
}
}
for (let key in this.faces) {
if (object_mode || this.faces[key].isSelected(key)) {
this.faces[key].invert();
}
}
if (object_mode) {
this.origin[axis] *= -1;
this.rotation.forEach((n, i) => {
if (i != axis) this.rotation[i] = -n;
})
this.preview_controller.updateTransform(this);
}
this.preview_controller.updateGeometry(this);
this.preview_controller.updateUV(this);
return this;
}
moveVector(arr, axis, update = true) {
if (typeof arr == 'number') {
var n = arr;
arr = [0, 0, 0];
arr[axis||0] = n;
} else if (arr instanceof THREE.Vector3) {
arr = arr.toArray();
}
arr.forEach((val, i) => {
this.origin[i] += val;
})
if (update) {
this.preview_controller.updateTransform(this);
}
TickUpdates.selection = true;
}
resize(val, axis, negative, allow_negative, bidirectional) {
let source_vertices = typeof val == 'number' ? this.oldVertices : this.vertices;
let selected_vertices = Project.mesh_selection[this.uuid]?.vertices || Object.keys(this.vertices);
let range = [Infinity, -Infinity];
let {vec1, vec2} = Reusable;
let rotation_inverted = new THREE.Euler().copy(Transformer.rotation_selection).invert();
selected_vertices.forEach(key => {
vec1.fromArray(source_vertices[key]).applyEuler(rotation_inverted);
range[0] = Math.min(range[0], vec1.getComponent(axis));
range[1] = Math.max(range[1], vec1.getComponent(axis));
})
let center = bidirectional ? (range[0] + range[1]) / 2 : (negative ? range[1] : range[0]);
let size = Math.abs(range[1] - range[0]);
if (typeof val !== 'number') {
val = val(size) - size;
if (bidirectional) val /= 2;
}
let scale = (size + val * (negative ? -1 : 1) * (bidirectional ? 2 : 1)) / size;
if (isNaN(scale) || Math.abs(scale) == Infinity) scale = 1;
if (scale < 0 && !allow_negative) scale = 0;
selected_vertices.forEach(key => {
vec1.fromArray(source_vertices[key]).applyEuler(rotation_inverted);
vec2.fromArray(this.vertices[key]).applyEuler(rotation_inverted);
vec2.setComponent(axis, (vec1.getComponent(axis) - center) * scale + center);
vec2.applyEuler(Transformer.rotation_selection);
this.vertices[key].replace(vec2.toArray())
})
this.preview_controller.updateGeometry(this);
}
applyTexture(texture, faces) {
var scope = this;
if (faces === true) {
var sides = Object.keys(this.faces);
} else if (faces === undefined) {
var sides = UVEditor.vue.selected_faces
} else {
var sides = faces
}
var value = false;
if (texture) {
value = texture.uuid
}
sides.forEach(function(side) {
scope.faces[side].texture = value
})
if (Project.selected_elements.indexOf(this) === 0) {
UVEditor.loadData()
}
this.preview_controller.updateFaces(this);
this.preview_controller.updateUV(this);
}
}
Mesh.prototype.title = tl('data.mesh');
Mesh.prototype.type = 'mesh';
Mesh.prototype.icon = 'fa far fa-gem';
Mesh.prototype.movable = true;
Mesh.prototype.resizable = true;
Mesh.prototype.rotatable = true;
Mesh.prototype.needsUniqueName = false;
Mesh.prototype.menu = new Menu([
new MenuSeparator('mesh_edit'),
'extrude_mesh_selection',
'inset_mesh_selection',
'loop_cut',
'create_face',
'invert_face',
'switch_face_crease',
'merge_vertices',
'dissolve_edges',
new MenuSeparator('mesh_combination'),
'split_mesh',
'merge_meshes',
...Outliner.control_menu_group,
new MenuSeparator('settings'),
'allow_element_mirror_modeling',
{name: 'menu.cube.color', icon: 'color_lens', children() {
return markerColors.map((color, i) => {return {
icon: 'bubble_chart',
color: color.standard,
name: color.name || 'cube.color.'+color.id,
click(cube) {
cube.forSelected(function(obj){
obj.setColor(i)
}, 'change color')
}
}})
}},
{name: 'menu.cube.texture', icon: 'collections', condition: () => !Format.single_texture, children: function() {
var arr = [
{icon: 'crop_square', name: 'menu.cube.texture.blank', click: function(cube) {
cube.forSelected(function(obj) {
obj.applyTexture(false, true)
}, 'texture blank')
}}
]
Texture.all.forEach(function(t) {
arr.push({
name: t.name,
icon: (t.mode === 'link' ? t.img : t.source),
click: function(cube) {
cube.forSelected(function(obj) {
obj.applyTexture(t, true)
}, 'apply texture')
}
})
})
return arr;
}},
'element_render_order',
new MenuSeparator('manage'),
'rename',
'toggle_visibility',
'delete'
]);
Mesh.prototype.buttons = [
Outliner.buttons.export,
Outliner.buttons.locked,
Outliner.buttons.visibility,
];
new Property(Mesh, 'string', 'name', {default: 'mesh'})
new Property(Mesh, 'number', 'color', {default: Math.floor(Math.random()*markerColors.length)});
new Property(Mesh, 'vector', 'origin');
new Property(Mesh, 'vector', 'rotation');
new Property(Mesh, 'boolean', 'visibility', {default: true});
new Property(Mesh, 'boolean', 'locked');
new Property(Mesh, 'enum', 'render_order', {default: 'default', values: ['default', 'behind', 'in_front']});
OutlinerElement.registerType(Mesh, 'mesh');
new NodePreviewController(Mesh, {
setup(element) {
var mesh = new THREE.Mesh(new THREE.BufferGeometry(1, 1, 1), Canvas.emptyMaterials[0]);
Project.nodes_3d[element.uuid] = mesh;
mesh.name = element.uuid;
mesh.type = element.type;
mesh.isElement = true;
mesh.geometry.setAttribute('highlight', new THREE.BufferAttribute(new Uint8Array(24), 1));
// Outline
let outline = new THREE.LineSegments(new THREE.BufferGeometry(), Canvas.meshOutlineMaterial);
outline.geometry.setAttribute('color', new THREE.Float32BufferAttribute(new Array(240).fill(1), 3));
outline.no_export = true;
outline.name = element.uuid+'_outline';
outline.visible = element.selected;
outline.renderOrder = 2;
outline.frustumCulled = false;
mesh.outline = outline;
mesh.add(outline);
outline.vertex_order = [];
// Vertex Points
let points = new THREE.Points(new THREE.BufferGeometry(), Canvas.meshVertexMaterial);
points.element_uuid = element.uuid;
points.geometry.setAttribute('color', new THREE.Float32BufferAttribute(new Array(24).fill(1), 3));
mesh.vertex_points = points;
outline.add(points);
// Update
this.updateTransform(element);
this.updateGeometry(element);
this.updateFaces(element);
this.updateUV(element);
this.updateRenderOrder(element);
mesh.visible = element.visibility;
this.dispatchEvent('setup', {element});
},
updateGeometry(element) {
let {mesh} = element;
let point_position_array = [];
let position_array = [];
let normal_array = [];
let indices = [];
let outline_positions = [];
mesh.outline.vertex_order.empty();
for (let key in element.vertices) {
let vector = element.vertices[key];
point_position_array.push(...vector);
}
for (let key in element.faces) {
let face = element.faces[key];
if (face.vertices.length == 2) {
// Outline
mesh.outline.vertex_order.push(face.vertices[0]);
mesh.outline.vertex_order.push(face.vertices[1]);
} else if (face.vertices.length == 3) {
// Tri
face.vertices.forEach((key, i) => {
indices.push(position_array.length / 3);
position_array.push(...element.vertices[key])
})
let normal = face.getNormal();
normal_array.push(...normal, ...normal, ...normal);
// Outline
face.vertices.forEach((key, i) => {
mesh.outline.vertex_order.push(key);
if (i) {
mesh.outline.vertex_order.push(key);
}
})
mesh.outline.vertex_order.push(face.vertices[0]);
} else if (face.vertices.length == 4) {
let index_offset = position_array.length / 3;
let face_indices = {};
face.vertices.forEach((vkey, i) => {
if (!element.vertices[vkey]) {
throw new Error(`Face "${key}" in mesh "${element.name}" contains an invalid vertex key "${vkey}"`, face)
}
position_array.push(...element.vertices[vkey])
face_indices[vkey] = index_offset + i;
})
let normal = face.getNormal(true);
normal_array.push(...normal, ...normal, ...normal, ...normal);
let sorted_vertices = face.getSortedVertices();
indices.push(face_indices[sorted_vertices[0]]);
indices.push(face_indices[sorted_vertices[1]]);
indices.push(face_indices[sorted_vertices[2]]);
indices.push(face_indices[sorted_vertices[0]]);
indices.push(face_indices[sorted_vertices[2]]);
indices.push(face_indices[sorted_vertices[3]]);
// Outline
sorted_vertices.forEach((key, i) => {
mesh.outline.vertex_order.push(key);
if (i != 0) mesh.outline.vertex_order.push(key);
})
mesh.outline.vertex_order.push(sorted_vertices[0]);
}
}
mesh.outline.vertex_order.forEach(key => {
outline_positions.push(...element.vertices[key]);
})
mesh.vertex_points.geometry.setAttribute('position', new THREE.BufferAttribute(new Float32Array(point_position_array), 3));
mesh.geometry.setAttribute('position', new THREE.BufferAttribute(new Float32Array(position_array), 3));
mesh.geometry.setAttribute('normal', new THREE.BufferAttribute(new Float32Array(normal_array), 3));
mesh.geometry.setIndex(indices);
mesh.outline.geometry.setAttribute('position', new THREE.BufferAttribute(new Float32Array(outline_positions), 3));
mesh.geometry.setAttribute('highlight', new THREE.BufferAttribute(new Uint8Array(outline_positions.length/3).fill(mesh.geometry.attributes.highlight.array[0]), 1));
mesh.geometry.computeBoundingBox();
mesh.geometry.computeBoundingSphere();
mesh.vertex_points.geometry.computeBoundingSphere();
mesh.outline.geometry.computeBoundingSphere();
Mesh.preview_controller.updateHighlight(element);
if (Modes.paint) {
Mesh.preview_controller.updatePaintingGrid(element);
}
this.dispatchEvent('update_geometry', {element});
},
updateFaces(element) {
let {mesh} = element;
if (Project.view_mode === 'solid') {
mesh.material = Canvas.solidMaterial
} else if (Project.view_mode === 'wireframe') {
mesh.material = Canvas.wireframeMaterial
} else if (Project.view_mode === 'normal') {
mesh.material = Canvas.normalHelperMaterial
} else if (Project.view_mode === 'uv') {
mesh.material = Canvas.uvHelperMaterial
} else if (Format.single_texture && Texture.all.length >= 2 && Texture.all.find(t => t.render_mode == 'layered')) {
mesh.material = Canvas.getLayeredMaterial();
} else if (Format.single_texture) {
let tex = Texture.getDefault();
mesh.material = tex ? tex.getMaterial() : Canvas.emptyMaterials[element.color];
} else {
var materials = []
for (let key in element.faces) {
if (element.faces[key].vertices.length < 3) continue;
var tex = element.faces[key].getTexture()
if (tex && tex.uuid) {
materials.push(Project.materials[tex.uuid])
} else {
materials.push(Canvas.emptyMaterials[element.color])
}
}
if (materials.allEqual(materials[0])) materials = materials[0];
mesh.geometry.groups.empty();
// Generate material groups
if (materials instanceof Array) {
let current_mat;
let i = 0;
let index = 0;
let switch_index = 0;
let reduced_materials = [];
for (let key in element.faces) {
if (element.faces[key].vertices.length < 3) continue;
let face = element.faces[key];
let material = materials[i];
if (current_mat != material) {
if (index) {
mesh.geometry.addGroup(switch_index, index - switch_index, reduced_materials.length);
reduced_materials.push(current_mat);
}
current_mat = material;
switch_index = index;
}
i++;
if (face.vertices.length == 3) index += 3;
if (face.vertices.length == 4) index += 6;
}
mesh.geometry.addGroup(switch_index, index - switch_index, reduced_materials.length);
reduced_materials.push(current_mat);
materials = reduced_materials;
}
mesh.material = materials;
if (!mesh.material) mesh.material = Canvas.transparentMaterial;
}
this.dispatchEvent('update_faces', {element});
},
updateUV(element) {
var {mesh} = element;
if (mesh === undefined || !mesh.geometry) return;
let uv_array = [];
for (let key in element.faces) {
let face = element.faces[key];
if (face.vertices.length <= 2) continue;
let stretch = 1;
let frame = 0;
let tex = face.getTexture();
if (tex instanceof Texture && tex.frameCount > 1) {
stretch = tex.frameCount
frame = tex.currentFrame || 0;
}
face.vertices.forEach((key, i) => {
let u = (face.uv[key] ? face.uv[key][0] : 0) / Project.texture_width;
let v = (face.uv[key] ? face.uv[key][1] : 0) / Project.texture_height;
if (stretch > 1) {
v = (v + frame) / stretch;
}
uv_array.push(u, 1-v);
})
}
mesh.geometry.setAttribute('uv', new THREE.BufferAttribute(new Float32Array(uv_array), 2)),
mesh.geometry.attributes.uv.needsUpdate = true;
this.dispatchEvent('update_uv', {element});
return mesh.geometry;
},
updateSelection(element) {
NodePreviewController.prototype.updateSelection.call(this, element);
let mesh = element.mesh;
let white = new THREE.Color(0xffffff);
let join = new THREE.Color(0x16d606);
let divide = new THREE.Color(0xff4400);
let join_selected = new THREE.Color(0x6bffcb);
let divide_selected = new THREE.Color(0xff8c69);
let selected_vertices = element.getSelectedVertices();
let selected_edges = element.getSelectedEdges();
let selected_faces = element.getSelectedFaces();
if (BarItems.selection_mode.value == 'vertex') {
let colors = [];
for (let key in element.vertices) {
let color;
if (selected_vertices.includes(key)) {
color = white;
} else {
color = gizmo_colors.grid;
}
colors.push(color.r, color.g, color.b);
}
mesh.vertex_points.geometry.setAttribute('color', new THREE.Float32BufferAttribute(colors, 3));
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) ];
let color;
let selected;
if (!Modes.edit || BarItems.selection_mode.value == 'object') {
color = gizmo_colors.outline;
} 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') && face_outlines[key] && face_outlines[key].has(key_b)) {
color = white;
selected = true;
} else {
color = gizmo_colors.grid;
}
if (Toolbox.selected.id === 'seam_tool') {
let seam = element.getSeam([key, key_b]);
if (selected) {
if (seam == 'join') color = join_selected;
if (seam == 'divide') color = divide_selected;
} else {
if (seam == 'join') color = join;
if (seam == 'divide') color = divide;
}
}
line_colors.push(color.r, color.g, color.b);
})
mesh.outline.geometry.setAttribute('color', new THREE.Float32BufferAttribute(line_colors, 3));
mesh.outline.geometry.needsUpdate = true;
mesh.vertex_points.visible = Mode.selected.id == 'edit' && BarItems.selection_mode.value == 'vertex';
this.dispatchEvent('update_selection', {element});
},
updateHighlight(element, hover_cube, force_off) {
var mesh = element.mesh;
let highlighted = (
Settings.get('highlight_cubes') &&
((hover_cube == element && !Transformer.dragging) || element.selected) &&
Modes.edit &&
!force_off
) ? 1 : 0;
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 (selected_faces.indexOf(fkey) != -1 && (selection_mode == 'face' || selection_mode == 'cluster')) {
for (let j = 0; j < face.vertices.length; j++) {
array[i] = 2;
i++;
}
} else {
i += face.vertices.length;
}
}
}
mesh.geometry.attributes.highlight.array.set(array);
mesh.geometry.attributes.highlight.needsUpdate = true;
this.dispatchEvent('update_highlight', {element});
},
updatePaintingGrid(element) {
var mesh = element.mesh;
if (mesh === undefined) return;
mesh.remove(mesh.grid_box);
if (element.visibility == false) return;
if (!Modes.paint || !settings.painting_grid.value) return;
var positions = [];
for (let fkey in element.faces) {
let face = element.faces[fkey];
if (face.vertices.length <= 2) continue;
let offset = face.getNormal(true).V3_multiply(0.01);
let texture = face.getTexture();
var psize_x = texture ? Project.texture_width / texture.width : 1;
var psize_y = texture ? Project.texture_height / texture.display_height : 1;
let vertices = face.getSortedVertices();
let tris = vertices.length == 3 ? [vertices] : [vertices.slice(0, 3), [vertices[0], vertices[2], vertices[3]]];
tris.forEach(tri_vertices => {
let x_memory = {};
let y_memory = {};
tri_vertices.forEach((vkey1, i) => {
let vkey2 = tri_vertices[i+1] || tri_vertices[0];
let uv1 = face.uv[vkey1].slice();
let uv2 = face.uv[vkey2].slice();
let range_x = (uv1[0] > uv2[0]) ? [uv2[0], uv1[0]] : [uv1[0], uv2[0]];
let range_y = (uv1[1] > uv2[1]) ? [uv2[1], uv1[1]] : [uv1[1], uv2[1]];
for (let x = Math.ceil(range_x[0] / psize_x) * psize_x; x < range_x[1]; x += psize_x) {
if (!x_memory[x]) x_memory[x] = [];
let y = uv1[1] + (uv2[1] - uv1[1]) * Math.getLerp(uv1[0], uv2[0], x);
x_memory[x].push(face.UVToLocal([x, y], tri_vertices).toArray().V3_add(offset));
}
for (let y = Math.ceil(range_y[0] / psize_y) * psize_y; y < range_y[1]; y += psize_y) {
if (!y_memory[y]) y_memory[y] = [];
let x = uv1[0] + (uv2[0] - uv1[0]) * Math.getLerp(uv1[1], uv2[1], y);
y_memory[y].push(face.UVToLocal([x, y], tri_vertices).toArray().V3_add(offset));
}
})
for (let key in x_memory) {
let points = x_memory[key];
if (points.length == 2) {
positions.push(...points[0], ...points[1]);
}
}
for (let key in y_memory) {
let points = y_memory[key];
if (points.length == 2) {
positions.push(...points[0], ...points[1]);
}
}
})
}
var geometry = new THREE.BufferGeometry();
geometry.setAttribute( 'position', new THREE.Float32BufferAttribute( positions, 3 ) );
let box = new THREE.LineSegments(geometry, new THREE.LineBasicMaterial({color: gizmo_colors.grid}));
box.no_export = true;
box.name = element.uuid+'_grid_box';
box.renderOrder = 2;
box.frustumCulled = false;
mesh.grid_box = box;
mesh.add(box);
this.dispatchEvent('update_painting_grid', {element});
}
})