mirror of
https://github.com/JannisX11/blockbench.git
synced 2024-11-27 04:21:46 +08:00
1576 lines
45 KiB
JavaScript
1576 lines
45 KiB
JavaScript
class CubeFace extends Face {
|
|
constructor(direction, data, cube) {
|
|
super();
|
|
this.texture = false;
|
|
this.direction = direction || 'north';
|
|
this.cube = cube;
|
|
this.uv = [0, 0, canvasGridSize(), canvasGridSize()]
|
|
this.rotation = 0;
|
|
|
|
if (data) {
|
|
this.extend(data)
|
|
}
|
|
}
|
|
get uv_size() {
|
|
return [
|
|
this.uv[2] - this.uv[0],
|
|
this.uv[3] - this.uv[1]
|
|
]
|
|
}
|
|
set uv_size(arr) {
|
|
this.uv[2] = arr[0] + this.uv[0];
|
|
this.uv[3] = arr[1] + this.uv[1];
|
|
}
|
|
get element() {
|
|
return this.cube;
|
|
}
|
|
extend(data) {
|
|
super.extend(data);
|
|
if (data.uv) {
|
|
Merge.number(this.uv, data.uv, 0)
|
|
Merge.number(this.uv, data.uv, 1)
|
|
Merge.number(this.uv, data.uv, 2)
|
|
Merge.number(this.uv, data.uv, 3)
|
|
}
|
|
return this;
|
|
}
|
|
reset() {
|
|
super.reset();
|
|
this.rotation = 0;
|
|
return this;
|
|
}
|
|
getBoundingRect() {
|
|
return getRectangle(...this.uv);
|
|
}
|
|
getVertexIndices() {
|
|
switch (this.direction) {
|
|
case 'north': return [1, 4, 6, 3];
|
|
case 'east': return [0, 1, 3, 2];
|
|
case 'south': return [5, 0, 2, 7];
|
|
case 'west': return [4, 5, 7, 6];
|
|
case 'up': return [4, 1, 0, 5];
|
|
case 'down': return [7, 2, 3, 6];
|
|
}
|
|
}
|
|
UVToLocal(point) {
|
|
let from = this.cube.from.slice()
|
|
let to = this.cube.to.slice()
|
|
adjustFromAndToForInflateAndStretch(from, to, this.cube);
|
|
|
|
let vector = new THREE.Vector3().fromArray(from);
|
|
|
|
let lerp_x = Math.getLerp(this.uv[0], this.uv[2], point[0]);
|
|
let lerp_y = Math.getLerp(this.uv[1], this.uv[3], point[1]);
|
|
|
|
for (let i = 0; i < this.rotation; i += 90) {
|
|
[lerp_x, lerp_y] = [1-lerp_y, lerp_x];
|
|
}
|
|
|
|
if (this.direction == 'east') {
|
|
vector.x = to[0];
|
|
vector.y = Math.lerp(to[1], from[1], lerp_y);
|
|
vector.z = Math.lerp(to[2], from[2], lerp_x);
|
|
}
|
|
if (this.direction == 'west') {
|
|
vector.y = Math.lerp(to[1], from[1], lerp_y);
|
|
vector.z = Math.lerp(from[2], to[2], lerp_x);
|
|
}
|
|
if (this.direction == 'up') {
|
|
vector.y = to[1];
|
|
vector.z = Math.lerp(from[2], to[2], lerp_y);
|
|
vector.x = Math.lerp(from[0], to[0], lerp_x);
|
|
}
|
|
if (this.direction == 'down') {
|
|
vector.z = Math.lerp(to[2], from[2], lerp_y);
|
|
vector.x = Math.lerp(from[0], to[0], lerp_x);
|
|
}
|
|
if (this.direction == 'south') {
|
|
vector.z = to[2];
|
|
vector.y = Math.lerp(to[1], from[1], lerp_y);
|
|
vector.x = Math.lerp(from[0], to[0], lerp_x);
|
|
}
|
|
if (this.direction == 'north') {
|
|
vector.y = Math.lerp(to[1], from[1], lerp_y);
|
|
vector.x = Math.lerp(to[0], from[0], lerp_x);
|
|
}
|
|
vector.x -= this.cube.origin[0];
|
|
vector.y -= this.cube.origin[1];
|
|
vector.z -= this.cube.origin[2];
|
|
return vector;
|
|
}
|
|
}
|
|
new Property(CubeFace, 'number', 'rotation', {default: 0});
|
|
new Property(CubeFace, 'number', 'tint', {default: -1});
|
|
new Property(CubeFace, 'enum', 'cullface', {values: ['', 'north', 'south', 'west', 'east', 'up', 'down']});
|
|
new Property(CubeFace, 'string', 'material_name');
|
|
new Property(CubeFace, 'boolean', 'enabled', {default: true});
|
|
|
|
CubeFace.opposite = {
|
|
north: 'south',
|
|
south: 'north',
|
|
east: 'west',
|
|
west: 'east',
|
|
down: 'up',
|
|
up: 'down'
|
|
}
|
|
|
|
class Cube extends OutlinerElement {
|
|
constructor(data, uuid) {
|
|
super(data, uuid)
|
|
let size = Settings.get('default_cube_size');
|
|
this.shade = true;
|
|
this.mirror_uv = false;
|
|
this.color = Math.floor(Math.random()*markerColors.length)
|
|
this.uv_offset = [0,0]
|
|
this.inflate = 0;
|
|
this.stretch = [1, 1, 1];
|
|
this.visibility = true;
|
|
this.autouv = 0;
|
|
|
|
for (var key in Cube.properties) {
|
|
Cube.properties[key].reset(this);
|
|
}
|
|
this._static = Object.freeze({
|
|
properties: {
|
|
faces: {
|
|
north: new CubeFace('north', null, this),
|
|
east: new CubeFace('east', null, this),
|
|
south: new CubeFace('south', null, this),
|
|
west: new CubeFace('west', null, this),
|
|
up: new CubeFace('up', null, this),
|
|
down: new CubeFace('down', null, this)
|
|
},
|
|
from: [0, 0, 0],
|
|
to: [size, size, size],
|
|
rotation: [0, 0, 0],
|
|
origin: [0, 0, 0],
|
|
}
|
|
})
|
|
|
|
this.box_uv = Project.box_uv;
|
|
if (data && typeof data === 'object') {
|
|
this.extend(data)
|
|
}
|
|
}
|
|
get faces() {return this._static.properties.faces};
|
|
get from() {return this._static.properties.from};
|
|
get to() {return this._static.properties.to};
|
|
get rotation() {return this._static.properties.rotation};
|
|
get origin() {return this._static.properties.origin};
|
|
set faces(v) {this._static.properties.faces = v};
|
|
set from(v) {this._static.properties.from = v};
|
|
set to(v) {this._static.properties.to = v};
|
|
set rotation(v) {this._static.properties.rotation = v};
|
|
set origin(v) {this._static.properties.origin = v};
|
|
extend(object) {
|
|
for (var key in Cube.properties) {
|
|
Cube.properties[key].merge(this, object)
|
|
}
|
|
|
|
this.sanitizeName();
|
|
Merge.boolean(this, object, 'shade')
|
|
Merge.boolean(this, object, 'mirror_uv')
|
|
Merge.number(this, object, 'inflate')
|
|
Merge.number(this, object, 'autouv')
|
|
Merge.number(this, object, 'color')
|
|
Merge.boolean(this, object, 'export')
|
|
Merge.boolean(this, object, 'visibility')
|
|
if (object.from) {
|
|
Merge.number(this.from, object.from, 0)
|
|
Merge.number(this.from, object.from, 1)
|
|
Merge.number(this.from, object.from, 2)
|
|
}
|
|
if (object.to) {
|
|
Merge.number(this.to, object.to, 0)
|
|
Merge.number(this.to, object.to, 1)
|
|
Merge.number(this.to, object.to, 2)
|
|
}
|
|
if (object.size) {
|
|
if (typeof object.size[0] == 'number' && !isNaN(object.size[0])) this.to[0] = this.from[0] + object.size[0]
|
|
if (typeof object.size[1] == 'number' && !isNaN(object.size[1])) this.to[1] = this.from[1] + object.size[1]
|
|
if (typeof object.size[2] == 'number' && !isNaN(object.size[2])) this.to[2] = this.from[2] + object.size[2]
|
|
}
|
|
if (object.uv_offset) {
|
|
Merge.number(this.uv_offset, object.uv_offset, 0)
|
|
Merge.number(this.uv_offset, object.uv_offset, 1)
|
|
}
|
|
if (object.stretch) {
|
|
Merge.number(this.stretch, object.stretch, 0)
|
|
Merge.number(this.stretch, object.stretch, 1)
|
|
Merge.number(this.stretch, object.stretch, 2)
|
|
}
|
|
if (typeof object.rotation === 'object' && object.rotation.constructor.name === 'Object') {
|
|
if (object.rotation.angle && object.rotation.axis) {
|
|
var axis = getAxisNumber(object.rotation.axis)
|
|
if (axis >= 0) {
|
|
this.rotation.V3_set(0)
|
|
this.rotation[axis] = object.rotation.angle
|
|
}
|
|
}
|
|
if (object.rotation.origin) {
|
|
Merge.number(this.origin, object.rotation.origin, 0)
|
|
Merge.number(this.origin, object.rotation.origin, 1)
|
|
Merge.number(this.origin, object.rotation.origin, 2)
|
|
}
|
|
Merge.boolean(this, object.rotation, 'rescale')
|
|
if (typeof object.rotation.axis === 'string') {
|
|
this.rotation_axis = object.rotation.axis
|
|
}
|
|
} else if (object.rotation) {
|
|
Merge.number(this.rotation, object.rotation, 0)
|
|
Merge.number(this.rotation, object.rotation, 1)
|
|
Merge.number(this.rotation, object.rotation, 2)
|
|
}
|
|
if (object.rotated) {
|
|
Merge.number(this.rotation, object.rotated, 0)
|
|
Merge.number(this.rotation, object.rotated, 1)
|
|
Merge.number(this.rotation, object.rotated, 2)
|
|
}
|
|
if (object.origin) {
|
|
Merge.number(this.origin, object.origin, 0)
|
|
Merge.number(this.origin, object.origin, 1)
|
|
Merge.number(this.origin, object.origin, 2)
|
|
}
|
|
Merge.string(this, object, 'rotation_axis', (v) => (v === 'x' || v === 'y' || v === 'z'))
|
|
if (object.faces) {
|
|
for (var face in this.faces) {
|
|
if (this.faces.hasOwnProperty(face) && object.faces.hasOwnProperty(face)) {
|
|
this.faces[face].extend(object.faces[face])
|
|
}
|
|
}
|
|
}
|
|
return this;
|
|
}
|
|
init() {
|
|
super.init();
|
|
if (Format.single_texture && Texture.getDefault()) {
|
|
for (var face in this.faces) {
|
|
if (this.faces[face].texture !== null) {
|
|
this.faces[face].texture = Texture.getDefault().uuid
|
|
}
|
|
}
|
|
}
|
|
return this;
|
|
}
|
|
size(axis, floored) {
|
|
var scope = this;
|
|
let epsilon = 0.0000001;
|
|
function getA(axis) {
|
|
if (floored == true) {
|
|
return Math.floor(scope.to[axis] - scope.from[axis] + epsilon);
|
|
|
|
} else if (floored == 'box_uv' && Format.box_uv_float_size != true) {
|
|
return Math.floor(scope.to[axis] - scope.from[axis] + epsilon);
|
|
|
|
} else {
|
|
return scope.to[axis] - scope.from[axis]
|
|
}
|
|
}
|
|
if (axis !== undefined) {
|
|
return getA(axis);
|
|
} else {
|
|
return [
|
|
getA(0),
|
|
getA(1),
|
|
getA(2)
|
|
]
|
|
}
|
|
}
|
|
getSize(axis, selection_only) {
|
|
return this.size(axis);
|
|
}
|
|
rotationAxis() {
|
|
for (var axis = 0; axis < 3; axis++) {
|
|
if (this.rotation[axis] !== 0) {
|
|
this.rotation_axis = getAxisLetter(axis);
|
|
return this.rotation_axis;
|
|
}
|
|
}
|
|
return this.rotation_axis;
|
|
}
|
|
getMesh() {
|
|
return this.mesh;
|
|
}
|
|
get mesh() {
|
|
return Project.nodes_3d[this.uuid];
|
|
}
|
|
getUndoCopy(aspects = 0) {
|
|
let copy = {};
|
|
|
|
for (var key in Cube.properties) {
|
|
Cube.properties[key].copy(this, copy);
|
|
}
|
|
|
|
copy.from = this.from.slice();
|
|
copy.to = this.to.slice();
|
|
copy.stretch = this.stretch.slice();
|
|
copy.inflate = this.inflate;
|
|
copy.rotation = this.rotation.slice();
|
|
copy.origin = this.origin.slice();
|
|
copy.uv_offset = this.uv_offset.slice();
|
|
copy.autouv = this.autouv;
|
|
copy.color = this.color;
|
|
copy.visibility = this.visibility;
|
|
copy.export = this.export;
|
|
copy.shade = this.shade;
|
|
copy.mirror_uv = this.mirror_uv;
|
|
|
|
copy.faces = {};
|
|
for (let face_id in this.faces) {
|
|
copy.faces[face_id] = this.faces[face_id].getUndoCopy();
|
|
}
|
|
|
|
copy.uuid = this.uuid
|
|
copy.type = this.type;
|
|
return copy;
|
|
}
|
|
getSaveCopy(project) {
|
|
var el = {}
|
|
|
|
for (var key in Cube.properties) {
|
|
Cube.properties[key].copy(this, el)
|
|
}
|
|
|
|
el.from = this.from;
|
|
el.to = this.to;
|
|
el.autouv = this.autouv;
|
|
el.color = this.color;
|
|
|
|
if (!this.visibility) el.visibility = false;
|
|
if (!this.export) el.export = false;
|
|
if (!this.shade) el.shade = false;
|
|
if (this.mirror_uv) el.mirror_uv = true;
|
|
if (this.inflate) el.inflate = this.inflate;
|
|
if (this.isStretched()) el.stretch = this.stretch;
|
|
if (!this.rotation.allEqual(0)) el.rotation = this.rotation;
|
|
el.origin = this.origin;
|
|
if (!this.uv_offset.allEqual(0)) el.uv_offset = this.uv_offset;
|
|
el.faces = {}
|
|
for (var face in this.faces) {
|
|
el.faces[face] = this.faces[face].getSaveCopy(project)
|
|
}
|
|
el.type = this.type;
|
|
el.uuid = this.uuid;
|
|
return el;
|
|
}
|
|
roll(axis, steps, origin) {
|
|
if (!origin) {origin = this.origin}
|
|
function rotateCoord(array) {
|
|
if (origin === undefined) {
|
|
origin = [8, 8, 8]
|
|
}
|
|
var a, b;
|
|
array.forEach(function(s, i) {
|
|
if (i == axis) {
|
|
//
|
|
} else {
|
|
if (a == undefined) {
|
|
a = s - origin[i]
|
|
b = i
|
|
} else {
|
|
array[b] = s - origin[i]
|
|
array[b] = origin[b] - array[b]
|
|
array[i] = origin[i] + a;
|
|
}
|
|
}
|
|
})
|
|
return array
|
|
}
|
|
|
|
// Check limits
|
|
if (Format.cube_size_limiter && !settings.deactivate_size_limit.value) {
|
|
let from = this.from.slice(), to = this.to.slice();
|
|
for (let check_steps = steps; check_steps > 0; check_steps--) {
|
|
switch(axis) {
|
|
case 0: [from[2], to[2]] = [to[2], from[2]]; break;
|
|
case 1: [from[2], to[2]] = [to[2], from[2]]; break;
|
|
case 2: [from[1], to[1]] = [to[1], from[1]]; break;
|
|
}
|
|
from.V3_set(rotateCoord(from));
|
|
to.V3_set(rotateCoord(to));
|
|
}
|
|
if (Format.cube_size_limiter.test(this, {from, to})) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
//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++;
|
|
}
|
|
|
|
function rotateUVFace(number, iterations) {
|
|
if (!Format.uv_rotation) return 0;
|
|
if (!number) number = 0;
|
|
number += iterations * 90;
|
|
return number % 360;
|
|
}
|
|
while (steps > 0) {
|
|
steps--;
|
|
//Swap coordinate thingy
|
|
switch(axis) {
|
|
case 0: [this.from[2], this.to[2]] = [this.to[2], this.from[2]]; break;
|
|
case 1: [this.from[2], this.to[2]] = [this.to[2], this.from[2]]; break;
|
|
case 2: [this.from[1], this.to[1]] = [this.to[1], this.from[1]]; break;
|
|
}
|
|
this.from.V3_set(rotateCoord(this.from))
|
|
this.to.V3_set(rotateCoord(this.to))
|
|
if (origin != this.origin) {
|
|
this.origin.V3_set(rotateCoord(this.origin))
|
|
}
|
|
if (!this.box_uv) {
|
|
if (axis === 0) {
|
|
this.faces.west.rotation = rotateUVFace(this.faces.west.rotation, 1)
|
|
this.faces.east.rotation = rotateUVFace(this.faces.east.rotation, 3)
|
|
this.faces.north.rotation= rotateUVFace(this.faces.north.rotation, 2)
|
|
this.faces.down.rotation = rotateUVFace(this.faces.down.rotation, 2)
|
|
|
|
var temp = new CubeFace(true, this.faces.north)
|
|
this.faces.north.extend(this.faces.down)
|
|
this.faces.down.extend(this.faces.south)
|
|
this.faces.south.extend(this.faces.up)
|
|
this.faces.up.extend(temp)
|
|
|
|
} else if (axis === 1) {
|
|
|
|
this.faces.up.rotation= rotateUVFace(this.faces.up.rotation, 1)
|
|
this.faces.down.rotation = rotateUVFace(this.faces.down.rotation, 3)
|
|
|
|
var temp = new CubeFace(true, this.faces.north)
|
|
this.faces.north.extend(this.faces.west)
|
|
this.faces.west.extend(this.faces.south)
|
|
this.faces.south.extend(this.faces.east)
|
|
this.faces.east.extend(temp)
|
|
|
|
} else if (axis === 2) {
|
|
|
|
this.faces.north.rotation = rotateUVFace(this.faces.north.rotation, 1)
|
|
this.faces.south.rotation= rotateUVFace(this.faces.south.rotation, 3)
|
|
|
|
this.faces.up.rotation= rotateUVFace(this.faces.up.rotation, 3)
|
|
this.faces.east.rotation= rotateUVFace(this.faces.east.rotation, 3)
|
|
this.faces.west.rotation = rotateUVFace(this.faces.west.rotation, 3)
|
|
this.faces.down.rotation = rotateUVFace(this.faces.down.rotation, 3)
|
|
|
|
var temp = new CubeFace(true, this.faces.east)
|
|
this.faces.east.extend(this.faces.down)
|
|
this.faces.down.extend(this.faces.west)
|
|
this.faces.west.extend(this.faces.up)
|
|
this.faces.up.extend(temp)
|
|
}
|
|
}
|
|
}
|
|
this.preview_controller.updateTransform(this);
|
|
this.preview_controller.updateGeometry(this);
|
|
this.preview_controller.updateFaces(this);
|
|
this.preview_controller.updateUV(this);
|
|
return this;
|
|
}
|
|
flip(axis, center, skipUV) {
|
|
var scope = this;
|
|
|
|
this.rotation[(axis+1)%3] *= -1
|
|
this.rotation[(axis+2)%3] *= -1
|
|
|
|
var from = this.from[axis]
|
|
this.from[axis] = center - (this.to[axis] - center)
|
|
this.to[axis] = center - (from - center)
|
|
this.origin[axis] = center - (this.origin[axis] - center)
|
|
|
|
flipNameOnAxis(this, axis);
|
|
|
|
if (!skipUV) {
|
|
|
|
if (this.box_uv && axis === 0) {
|
|
this.mirror_uv = !this.mirror_uv;
|
|
}
|
|
function mirrorUVX(face, skip_rot) {
|
|
var f = scope.faces[face]
|
|
if (skip_rot) {}
|
|
if (!skip_rot && (f.rotation == 90 || f.rotation == 270)) {
|
|
return mirrorUVY(face, true)
|
|
}
|
|
return [f.uv[2], f.uv[1], f.uv[0], f.uv[3]]
|
|
}
|
|
function mirrorUVY(face, skip_rot) {
|
|
var f = scope.faces[face]
|
|
if (skip_rot) {}
|
|
if (!skip_rot && (f.rotation == 90 || f.rotation == 270)) {
|
|
return mirrorUVX(face, true)
|
|
}
|
|
return [f.uv[0], f.uv[3], f.uv[2], f.uv[1]]
|
|
}
|
|
//Faces
|
|
var switchFaces;
|
|
switch(axis) {
|
|
case 0: switchFaces = ['west', 'east']; break;
|
|
case 1: switchFaces = ['up', 'down']; break;
|
|
case 2: switchFaces = ['south', 'north']; break;
|
|
}
|
|
var x = new CubeFace(switchFaces[1], this.faces[switchFaces[0]])
|
|
this.faces[switchFaces[0]].extend(this.faces[switchFaces[1]])
|
|
this.faces[switchFaces[1]].extend(x)
|
|
|
|
//UV
|
|
if (axis === 1) {
|
|
this.faces.north.uv = mirrorUVY('north')
|
|
this.faces.south.uv = mirrorUVY('south')
|
|
this.faces.east.uv = mirrorUVY('east')
|
|
this.faces.west.uv = mirrorUVY('west')
|
|
} else {
|
|
this.faces.north.uv = mirrorUVX('north')
|
|
this.faces.south.uv = mirrorUVX('south')
|
|
this.faces.east.uv = mirrorUVX('east')
|
|
this.faces.west.uv = mirrorUVX('west')
|
|
}
|
|
if (axis === 0) {
|
|
this.faces.up.uv = mirrorUVX('up')
|
|
this.faces.down.uv = mirrorUVX('down')
|
|
} else {
|
|
this.faces.up.uv = mirrorUVY('up')
|
|
this.faces.down.uv = mirrorUVY('down')
|
|
}
|
|
}
|
|
this.preview_controller.updateTransform(this);
|
|
this.preview_controller.updateGeometry(this);
|
|
this.preview_controller.updateFaces(this);
|
|
this.preview_controller.updateUV(this);
|
|
}
|
|
transferOrigin(origin, update = true) {
|
|
if (!this.mesh) return;
|
|
var q = Reusable.quat1.copy(this.mesh.quaternion)
|
|
var shift = Reusable.vec1.set(
|
|
this.origin[0] - origin[0],
|
|
this.origin[1] - origin[1],
|
|
this.origin[2] - origin[2],
|
|
)
|
|
var dq = Reusable.vec2.copy(shift)
|
|
dq.applyQuaternion(q)
|
|
shift.sub(dq)
|
|
shift.applyQuaternion(q.invert())
|
|
|
|
this.moveVector(shift, null, update)
|
|
|
|
this.origin.V3_set(origin);
|
|
|
|
this.preview_controller.updateTransform(this);
|
|
this.preview_controller.updateGeometry(this);
|
|
return this;
|
|
}
|
|
getWorldCenter() {
|
|
var m = this.mesh;
|
|
var pos = new THREE.Vector3(
|
|
this.from[0] + this.size(0)/2,
|
|
this.from[1] + this.size(1)/2,
|
|
this.from[2] + this.size(2)/2
|
|
)
|
|
pos.x = (pos.x - this.origin[0]) * m.scale.x;
|
|
pos.y = (pos.y - this.origin[1]) * m.scale.y;
|
|
pos.z = (pos.z - this.origin[2]) * m.scale.z;
|
|
|
|
if (m) {
|
|
var r = m.getWorldQuaternion(Reusable.quat1)
|
|
pos.applyQuaternion(r)
|
|
pos.add(THREE.fastWorldPosition(m, Reusable.vec2))
|
|
}
|
|
return pos;
|
|
}
|
|
getGlobalVertexPositions() {
|
|
var adjustedFrom = this.from.slice();
|
|
var adjustedTo = this.to.slice();
|
|
adjustFromAndToForInflateAndStretch(adjustedFrom, adjustedTo, this);
|
|
|
|
let vertices = [
|
|
[adjustedTo[0] , adjustedTo[1] , adjustedTo[2] ],
|
|
[adjustedTo[0] , adjustedTo[1] , adjustedFrom[2]],
|
|
[adjustedTo[0] , adjustedFrom[1], adjustedTo[2] ],
|
|
[adjustedTo[0] , adjustedFrom[1], adjustedFrom[2]],
|
|
[adjustedFrom[0], adjustedTo[1] , adjustedFrom[2]],
|
|
[adjustedFrom[0], adjustedTo[1] , adjustedTo[2] ],
|
|
[adjustedFrom[0], adjustedFrom[1], adjustedFrom[2]],
|
|
[adjustedFrom[0], adjustedFrom[1], adjustedTo[2] ],
|
|
];
|
|
let vec = new THREE.Vector3();
|
|
return vertices.map(coords => {
|
|
vec.set(...coords.V3_subtract(this.origin));
|
|
vec.applyMatrix4( this.mesh.matrixWorld );
|
|
let arr = vec.toArray();
|
|
arr.V3_add(8, 8, 8);
|
|
return arr;
|
|
})
|
|
}
|
|
setUVMode(box_uv) {
|
|
if (this.box_uv == !!box_uv) return this;
|
|
this.box_uv = !!box_uv;
|
|
if (this.box_uv) {
|
|
if (this.faces.west.uv[2] < this.faces.east.uv[0]) {
|
|
this.mirror_uv = true;
|
|
this.uv_offset[0] = this.faces.west.uv[2];
|
|
} else {
|
|
this.mirror_uv = false;
|
|
this.uv_offset[0] = this.faces.east.uv[0];
|
|
}
|
|
this.uv_offset[1] = this.faces.up.uv[3];
|
|
let texture = Texture.getDefault();
|
|
for (let fkey in this.faces) {
|
|
if (this.faces[fkey].texture) {
|
|
texture = this.faces[fkey].getTexture();
|
|
}
|
|
}
|
|
for (let fkey in this.faces) {
|
|
if (this.faces[fkey].texture === null) {
|
|
this.faces[fkey].extend({texture: texture || false});
|
|
}
|
|
}
|
|
this.preview_controller.updateFaces(this);
|
|
|
|
} else {
|
|
for (let fkey in this.faces) {
|
|
this.faces[fkey].rotation = 0;
|
|
}
|
|
}
|
|
this.preview_controller.updateUV(this);
|
|
return this;
|
|
}
|
|
setColor(index) {
|
|
this.color = index;
|
|
if (this.visibility) {
|
|
this.preview_controller.updateFaces(this);
|
|
}
|
|
return this;
|
|
}
|
|
applyTexture(texture, faces) {
|
|
if (faces === true || this.box_uv) {
|
|
var sides = ['north', 'east', 'south', 'west', 'up', 'down']
|
|
} else if (faces === undefined) {
|
|
var sides = [UVEditor.face]
|
|
} else {
|
|
var sides = faces
|
|
}
|
|
let value = null;
|
|
if (texture) {
|
|
value = texture.uuid
|
|
} else if (texture === false || texture === null) {
|
|
value = texture;
|
|
}
|
|
sides.forEach((side) => {
|
|
if (this.faces[side].texture !== null) {
|
|
this.faces[side].texture = value;
|
|
}
|
|
})
|
|
if (selected.indexOf(this) === 0) {
|
|
UVEditor.loadData()
|
|
}
|
|
this.preview_controller.updateFaces(this);
|
|
this.preview_controller.updateUV(this);
|
|
}
|
|
mapAutoUV() {
|
|
if (this.box_uv) return;
|
|
var scope = this;
|
|
var pw = Project.texture_width;
|
|
var ph = Project.texture_height;
|
|
if (scope.autouv === 2) {
|
|
//Relative UV
|
|
var all_faces = ['north', 'south', 'west', 'east', 'up', 'down']
|
|
all_faces.forEach(function(side) {
|
|
var uv = scope.faces[side].uv.slice()
|
|
switch (side) {
|
|
case 'north':
|
|
uv = [
|
|
pw - scope.to[0],
|
|
ph - scope.to[1],
|
|
pw - scope.from[0],
|
|
ph - scope.from[1],
|
|
];
|
|
break;
|
|
case 'south':
|
|
uv = [
|
|
scope.from[0],
|
|
ph - scope.to[1],
|
|
scope.to[0],
|
|
ph - scope.from[1],
|
|
];
|
|
break;
|
|
case 'west':
|
|
uv = [
|
|
scope.from[2],
|
|
ph - scope.to[1],
|
|
scope.to[2],
|
|
ph - scope.from[1],
|
|
];
|
|
break;
|
|
case 'east':
|
|
uv = [
|
|
pw - scope.to[2],
|
|
ph - scope.to[1],
|
|
pw - scope.from[2],
|
|
ph - scope.from[1],
|
|
];
|
|
break;
|
|
case 'up':
|
|
uv = [
|
|
scope.from[0],
|
|
scope.from[2],
|
|
scope.to[0],
|
|
scope.to[2],
|
|
];
|
|
break;
|
|
case 'down':
|
|
uv = [
|
|
scope.from[0],
|
|
ph - scope.to[2],
|
|
scope.to[0],
|
|
ph - scope.from[2],
|
|
];
|
|
break;
|
|
}
|
|
// Clamp to UV map boundaries
|
|
if (Math.max(uv[0], uv[2]) > Project.texture_width) {
|
|
let offset = Math.max(uv[0], uv[2]) - Project.texture_width;
|
|
uv[0] -= offset;
|
|
uv[2] -= offset;
|
|
}
|
|
if (Math.min(uv[0], uv[2]) < 0) {
|
|
let offset = Math.min(uv[0], uv[2]);
|
|
uv[0] = Math.clamp(uv[0] - offset, 0, Project.texture_width);
|
|
uv[2] = Math.clamp(uv[2] - offset, 0, Project.texture_width);
|
|
}
|
|
if (Math.max(uv[1], uv[3]) > Project.texture_height) {
|
|
let offset = Math.max(uv[1], uv[3]) - Project.texture_height;
|
|
uv[1] -= offset;
|
|
uv[3] -= offset;
|
|
}
|
|
if (Math.min(uv[1], uv[3]) < 0) {
|
|
let offset = Math.min(uv[1], uv[3]);
|
|
uv[1] = Math.clamp(uv[1] - offset, 0, Project.texture_height);
|
|
uv[3] = Math.clamp(uv[3] - offset, 0, Project.texture_height);
|
|
}
|
|
scope.faces[side].uv = uv;
|
|
})
|
|
Canvas.updateUV(scope)
|
|
} else if (scope.autouv === 1) {
|
|
|
|
function calcAutoUV(face, size) {
|
|
size[0] = Math.abs(size[0]);
|
|
size[1] = Math.abs(size[1]);
|
|
var sx = scope.faces[face].uv[0];
|
|
var sy = scope.faces[face].uv[1];
|
|
var rot = scope.faces[face].rotation;
|
|
|
|
//Match To Rotation
|
|
if (rot === 90 || rot === 270) {
|
|
size.reverse()
|
|
}
|
|
//Limit Input to 16
|
|
size[0] = Math.clamp(size[0], -Project.texture_width, Project.texture_width)
|
|
size[1] = Math.clamp(size[1], -Project.texture_height, Project.texture_height)
|
|
|
|
//Calculate End Points
|
|
var x = sx + size[0]
|
|
var y = sy + size[1]
|
|
//Prevent Over 16
|
|
if (x > Project.texture_width) {
|
|
sx = Project.texture_width - (x - sx)
|
|
x = Project.texture_width
|
|
}
|
|
if (y > Project.texture_height) {
|
|
sy = Project.texture_height - (y - sy)
|
|
y = Project.texture_height
|
|
}
|
|
//Prevent Negative
|
|
if (sx < 0) sx = 0
|
|
if (sy < 0) sy = 0
|
|
//Prevent Mirroring
|
|
if (x < sx) x = sx
|
|
if (y < sy) y = sy
|
|
//Return
|
|
return [sx, sy, x, y]
|
|
}
|
|
scope.faces.north.uv = calcAutoUV('north', [scope.size(0), scope.size(1)])
|
|
scope.faces.east.uv = calcAutoUV('east', [scope.size(2), scope.size(1)])
|
|
scope.faces.south.uv = calcAutoUV('south', [scope.size(0), scope.size(1)])
|
|
scope.faces.west.uv = calcAutoUV('west', [scope.size(2), scope.size(1)])
|
|
scope.faces.up.uv = calcAutoUV('up', [scope.size(0), scope.size(2)])
|
|
scope.faces.down.uv = calcAutoUV('down', [scope.size(0), scope.size(2)])
|
|
|
|
Canvas.updateUV(scope)
|
|
}
|
|
}
|
|
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();
|
|
}
|
|
var scope = this;
|
|
var in_box = true;
|
|
arr.forEach((val, i) => {
|
|
|
|
var size = scope.size(i);
|
|
val += scope.from[i];
|
|
|
|
var val_before = val;
|
|
if (Math.abs(val_before - val) >= 1e-4) in_box = false;
|
|
val -= scope.from[i]
|
|
|
|
scope.from[i] += val;
|
|
scope.to[i] += val;
|
|
})
|
|
if (Format.cube_size_limiter && !settings.deactivate_size_limit.value) {
|
|
Format.cube_size_limiter.move(this);
|
|
}
|
|
if (update) {
|
|
this.mapAutoUV()
|
|
this.preview_controller.updateTransform(this);
|
|
this.preview_controller.updateGeometry(this);
|
|
}
|
|
TickUpdates.selection = true;
|
|
return in_box;
|
|
}
|
|
resize(val, axis, negative, allow_negative, bidirectional) {
|
|
let before = this.oldScale != undefined ? this.oldScale : this.size(axis);
|
|
if (before instanceof Array) before = before[axis];
|
|
let is_inverted = before < 0;
|
|
if (is_inverted) negative = !negative;
|
|
let modify = val instanceof Function ? val : n => (n + val);
|
|
|
|
if (bidirectional) {
|
|
|
|
let center = this.oldCenter[axis] || 0;
|
|
let difference = modify(before) - before;
|
|
if (negative) difference *= -1;
|
|
|
|
let from = center - (before/2) - difference;
|
|
let to = center + (before/2) + difference;
|
|
|
|
if (Format.integer_size) {
|
|
from = Math.round(from-this.from[axis])+this.from[axis];
|
|
to = Math.round(to-this.to[axis])+this.to[axis];
|
|
}
|
|
this.from[axis] = from;
|
|
this.to[axis] = to;
|
|
if (from > to && !(settings.negative_size.value || allow_negative)) {
|
|
this.from[axis] = this.to[axis] = (from + to) / 2;
|
|
}
|
|
|
|
} else if (!negative) {
|
|
let pos = this.from[axis] + modify(before);
|
|
if (Format.integer_size) {
|
|
pos = Math.round(pos-this.from[axis])+this.from[axis];
|
|
}
|
|
if (pos >= this.from[axis] || settings.negative_size.value || allow_negative) {
|
|
this.to[axis] = pos;
|
|
} else {
|
|
this.to[axis] = this.from[axis];
|
|
}
|
|
} else {
|
|
let pos = this.to[axis] + modify(-before);
|
|
if (Format.integer_size) {
|
|
pos = Math.round(pos-this.to[axis])+this.to[axis];
|
|
}
|
|
if (pos <= this.to[axis] || settings.negative_size.value || allow_negative) {
|
|
this.from[axis] = pos;
|
|
} else {
|
|
this.from[axis] = this.to[axis];
|
|
}
|
|
}
|
|
if (Format.cube_size_limiter && !settings.deactivate_size_limit.value) {
|
|
Format.cube_size_limiter.clamp(this, {}, axis, bidirectional ? null : !!negative);
|
|
}
|
|
this.mapAutoUV();
|
|
if (this.box_uv) {
|
|
if (axis == 2) {
|
|
let difference = before - this.size(axis);
|
|
if (!Format.box_uv_float_size) difference = Math.ceil(difference);
|
|
this.uv_offset[0] = (this.oldUVOffset ? this.oldUVOffset[0] : this.uv_offset[0]) + difference;
|
|
this.uv_offset[1] = (this.oldUVOffset ? this.oldUVOffset[1] : this.uv_offset[1]) + difference;
|
|
} else if (axis == 0 && (!negative || bidirectional)) {
|
|
let difference = before - this.size(axis);
|
|
if (!Format.box_uv_float_size) difference = Math.ceil(difference);
|
|
this.uv_offset[0] = (this.oldUVOffset ? this.oldUVOffset[0] : this.uv_offset[0]) + difference;
|
|
}
|
|
Canvas.updateUV(this);
|
|
}
|
|
this.preview_controller.updateGeometry(this);
|
|
TickUpdates.selection = true;
|
|
return this;
|
|
}
|
|
isStretched() {
|
|
return !this.stretch.allEqual(1);
|
|
}
|
|
}
|
|
Cube.prototype.title = tl('data.cube');
|
|
Cube.prototype.type = 'cube';
|
|
Cube.prototype.icon = 'fa-cube';
|
|
Cube.prototype.movable = true;
|
|
Cube.prototype.resizable = true;
|
|
Cube.prototype.rotatable = true;
|
|
Cube.prototype.needsUniqueName = false;
|
|
Cube.prototype.menu = new Menu([
|
|
...Outliner.control_menu_group,
|
|
new MenuSeparator('settings'),
|
|
'convert_to_mesh',
|
|
'update_autouv',
|
|
'cube_uv_mode',
|
|
'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 && !Format.per_group_texture, children: function() {
|
|
var arr = [
|
|
{icon: 'crop_square', name: Format.single_texture_default ? 'menu.cube.texture.default' : 'menu.cube.texture.blank', click(cube) {
|
|
cube.forSelected(function(obj) {
|
|
obj.applyTexture(false, true)
|
|
}, 'texture blank', Format.per_group_texture ? 'all_in_group' : null)
|
|
}}
|
|
]
|
|
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', Format.per_group_texture ? 'all_in_group' : null)
|
|
}
|
|
})
|
|
})
|
|
return arr;
|
|
}},
|
|
'edit_material_instances',
|
|
'element_render_order',
|
|
new MenuSeparator('manage'),
|
|
'rename',
|
|
'toggle_visibility',
|
|
'delete'
|
|
]);
|
|
Cube.prototype.buttons = [
|
|
Outliner.buttons.autouv,
|
|
Outliner.buttons.mirror_uv,
|
|
Outliner.buttons.shade,
|
|
Outliner.buttons.export,
|
|
Outliner.buttons.locked,
|
|
Outliner.buttons.visibility,
|
|
];
|
|
|
|
new Property(Cube, 'string', 'name', {default: 'cube'});
|
|
new Property(Cube, 'boolean', 'box_uv', {merge_validation: (value) => Format.optional_box_uv || value === Format.box_uv});
|
|
new Property(Cube, 'boolean', 'rescale');
|
|
new Property(Cube, 'boolean', 'locked');
|
|
new Property(Cube, 'enum', 'render_order', {default: 'default', values: ['default', 'behind', 'in_front']});
|
|
|
|
OutlinerElement.registerType(Cube, 'cube');
|
|
|
|
function adjustFromAndToForInflateAndStretch(from, to, element) {
|
|
var halfSize = element.size().slice();
|
|
halfSize.forEach((v, i) => {
|
|
halfSize[i] /= 2;
|
|
});
|
|
var center = [
|
|
element.from[0] + halfSize[0],
|
|
element.from[1] + halfSize[1],
|
|
element.from[2] + halfSize[2]
|
|
];
|
|
|
|
for (let i = 0; i < from.length; i++) {
|
|
from[i] = center[i] - (halfSize[i] + element.inflate) * element.stretch[i];
|
|
to[i] = center[i] + (halfSize[i] + element.inflate) * element.stretch[i];
|
|
}
|
|
}
|
|
|
|
new NodePreviewController(Cube, {
|
|
setup(element) {
|
|
let mesh = new THREE.Mesh(new THREE.BoxGeometry(1, 1, 1), Canvas.emptyMaterials[0]);
|
|
Project.nodes_3d[element.uuid] = mesh;
|
|
mesh.name = element.uuid;
|
|
mesh.type = 'cube';
|
|
mesh.isElement = true;
|
|
mesh.visible = element.visibility;
|
|
mesh.rotation.order = 'ZYX'
|
|
|
|
mesh.geometry.setAttribute('highlight', new THREE.BufferAttribute(new Uint8Array(24).fill(0), 1));
|
|
|
|
// Outline
|
|
let geometry = new THREE.BufferGeometry();
|
|
let line = new THREE.Line(geometry, Canvas.outlineMaterial);
|
|
line.no_export = true;
|
|
line.name = element.uuid+'_outline';
|
|
line.visible = element.selected;
|
|
line.renderOrder = 2;
|
|
line.frustumCulled = false;
|
|
mesh.outline = line;
|
|
mesh.add(line);
|
|
|
|
// Update
|
|
this.updateTransform(element);
|
|
this.updateGeometry(element);
|
|
this.updateFaces(element);
|
|
this.updateUV(element);
|
|
this.updateRenderOrder(element);
|
|
|
|
this.dispatchEvent('setup', {element});
|
|
},
|
|
updateTransform(element) {
|
|
NodePreviewController.prototype.updateTransform.call(this, element);
|
|
|
|
let mesh = element.mesh;
|
|
|
|
if (Format.rotate_cubes && element.rescale === true) {
|
|
var axis = element.rotationAxis()||'y';
|
|
var rescale = getRescalingFactor(element.rotation[getAxisNumber(axis)]);
|
|
mesh.scale.set(rescale, rescale, rescale);
|
|
mesh.scale[axis] = 1;
|
|
}
|
|
|
|
this.dispatchEvent('update_transform', {element});
|
|
},
|
|
updateGeometry(element) {
|
|
if (element.resizable) {
|
|
let mesh = element.mesh;
|
|
var from = element.from.slice()
|
|
var to = element.to.slice()
|
|
|
|
adjustFromAndToForInflateAndStretch(from, to, element);
|
|
|
|
from.forEach((v, i) => {
|
|
from[i] -= element.origin[i];
|
|
})
|
|
to.forEach((v, i) => {
|
|
to[i] -= element.origin[i];
|
|
if (from[i] === to[i]) {
|
|
to[i] += 0.001
|
|
}
|
|
})
|
|
mesh.geometry.setShape(from, to)
|
|
mesh.geometry.computeBoundingBox()
|
|
mesh.geometry.computeBoundingSphere()
|
|
|
|
// Update outline
|
|
var vs = [0,1,2,3,4,5,6,7].map(i => {
|
|
return mesh.geometry.attributes.position.array.slice(i*3, i*3 + 3)
|
|
});
|
|
let points = [
|
|
vs[2], vs[3],
|
|
vs[6], vs[7],
|
|
vs[2], vs[0],
|
|
vs[1], vs[4],
|
|
vs[5], vs[0],
|
|
vs[5], vs[7],
|
|
vs[6], vs[4],
|
|
vs[1], vs[3]
|
|
].map(a => new THREE.Vector3().fromArray(a))
|
|
mesh.outline.geometry.setFromPoints(points);
|
|
}
|
|
|
|
this.updatePixelGrid(element);
|
|
|
|
this.dispatchEvent('update_geometry', {element});
|
|
},
|
|
updateFaces(element) {
|
|
let {mesh} = element;
|
|
|
|
let indices = [];
|
|
let j = 0;
|
|
mesh.geometry.faces = [];
|
|
mesh.geometry.clearGroups();
|
|
let last_tex;
|
|
Canvas.face_order.forEach((fkey, i) => {
|
|
if (element.faces[fkey].texture !== null) {
|
|
indices.push(0 + i*4, 2 + i*4, 1 + i*4, 2 + i*4, 3 + i*4, 1 + i*4);
|
|
if (last_tex && element.faces[fkey].texture === last_tex) {
|
|
mesh.geometry.groups[mesh.geometry.groups.length-1].count += 6;
|
|
} else {
|
|
mesh.geometry.addGroup(j*6, 6, j)
|
|
last_tex = element.faces[fkey].texture;
|
|
}
|
|
mesh.geometry.faces.push(fkey)
|
|
j++;
|
|
}
|
|
})
|
|
mesh.geometry.setIndex(indices)
|
|
|
|
if (Project.view_mode === 'solid') {
|
|
mesh.material = Canvas.monochromaticSolidMaterial
|
|
|
|
} else if (Project.view_mode === 'colored_solid') {
|
|
mesh.material = Canvas.coloredSolidMaterials[element.color % Canvas.emptyMaterials.length]
|
|
|
|
} 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 % Canvas.emptyMaterials.length];
|
|
|
|
} else {
|
|
let materials = [];
|
|
Canvas.face_order.forEach(function(face) {
|
|
if (element.faces[face].texture !== null) {
|
|
let tex = element.faces[face].getTexture();
|
|
if (tex && tex.uuid) {
|
|
materials.push(Project.materials[tex.uuid])
|
|
} else {
|
|
materials.push(Canvas.emptyMaterials[element.color % Canvas.emptyMaterials.length])
|
|
}
|
|
}
|
|
})
|
|
if (materials.allEqual(materials[0])) materials = materials[0];
|
|
mesh.material = materials;
|
|
}
|
|
if (!mesh.material) mesh.material = Canvas.transparentMaterial;
|
|
|
|
Cube.preview_controller.dispatchEvent('update_faces', {element});
|
|
},
|
|
updateUV(element, animation = true) {
|
|
let mesh = element.mesh
|
|
if (mesh === undefined || !mesh.geometry) return;
|
|
|
|
if (element.box_uv) {
|
|
|
|
let size = element.size(undefined, Format.box_uv_float_size != true);
|
|
|
|
let face_list = [
|
|
{face: 'east', from: [0, size[2]], size: [size[2], size[1]]},
|
|
{face: 'west', from: [size[2] + size[0], size[2]], size: [size[2], size[1]]},
|
|
{face: 'up', from: [size[2]+size[0], size[2]], size: [-size[0], -size[2]]},
|
|
{face: 'down', from: [size[2]+size[0]*2, 0], size: [-size[0], size[2]]},
|
|
{face: 'south', from: [size[2]*2 + size[0], size[2]], size: [size[0], size[1]]},
|
|
{face: 'north', from: [size[2], size[2]], size: [size[0], size[1]]},
|
|
]
|
|
|
|
if (element.mirror_uv) {
|
|
face_list.forEach(function(f) {
|
|
f.from[0] += f.size[0]
|
|
f.size[0] *= -1
|
|
})
|
|
//East+West
|
|
|
|
let p = {}
|
|
|
|
p.from = face_list[0].from.slice()
|
|
p.size = face_list[0].size.slice()
|
|
|
|
face_list[0].from = face_list[1].from.slice()
|
|
face_list[0].size = face_list[1].size.slice()
|
|
|
|
face_list[1].from = p.from.slice()
|
|
face_list[1].size = p.size.slice()
|
|
|
|
}
|
|
face_list.forEach(function(f, fIndex) {
|
|
|
|
if (element.faces[f.face].texture == null) return;
|
|
|
|
let uv= [
|
|
f.from[0] + element.uv_offset[0],
|
|
f.from[1] + element.uv_offset[1],
|
|
f.from[0] + f.size[0] + element.uv_offset[0],
|
|
f.from[1] + f.size[1] + element.uv_offset[1]
|
|
]
|
|
uv.forEach(function(s, si) {
|
|
uv[si] *= 1
|
|
})
|
|
|
|
element.faces[f.face].uv[0] = uv[0]
|
|
element.faces[f.face].uv[1] = uv[1]
|
|
element.faces[f.face].uv[2] = uv[2]
|
|
element.faces[f.face].uv[3] = uv[3]
|
|
})
|
|
|
|
}
|
|
|
|
Canvas.face_order.forEach((fkey, index) => {
|
|
let face = element.faces[fkey];
|
|
if (face.texture === null) return;
|
|
|
|
let stretch = 1;
|
|
let frame = 0;
|
|
let tex = face.getTexture();
|
|
let uv = face.uv;
|
|
let vertex_uvs = mesh.geometry.attributes.uv;
|
|
let pw = Project.texture_width;
|
|
let ph = Project.texture_height;
|
|
if (tex && Format.per_texture_uv_size && Project.view_mode !== 'uv') {
|
|
pw = tex.getUVWidth();
|
|
ph = tex.getUVHeight();
|
|
}
|
|
|
|
if (tex instanceof Texture && tex.frameCount !== 1) {
|
|
stretch = tex.frameCount || 1;
|
|
if (animation === true && tex.currentFrame) {
|
|
frame = tex.currentFrame;
|
|
}
|
|
}
|
|
stretch *= -1;
|
|
|
|
// Box UV fight texture bleeding
|
|
if (element.box_uv) {
|
|
uv = uv.slice();
|
|
for (let si = 0; si < 2; si++) {
|
|
let margin = 1/64;
|
|
if (uv[si] > uv[si+2]) {
|
|
margin = -margin
|
|
}
|
|
uv[si] += margin
|
|
uv[si+2] -= margin
|
|
}
|
|
}
|
|
|
|
let arr = [
|
|
[uv[0]/pw, (uv[1]/ph)/stretch+1],
|
|
[uv[2]/pw, (uv[1]/ph)/stretch+1],
|
|
[uv[0]/pw, (uv[3]/ph)/stretch+1],
|
|
[uv[2]/pw, (uv[3]/ph)/stretch+1],
|
|
]
|
|
if (frame > 0 && stretch !== -1) {
|
|
//Animate
|
|
let offset = (1/stretch) * frame
|
|
arr[0][1] += offset
|
|
arr[1][1] += offset
|
|
arr[2][1] += offset
|
|
arr[3][1] += offset
|
|
}
|
|
let rot = (face.rotation+0)
|
|
while (rot > 0) {
|
|
let a = arr[0];
|
|
arr[0] = arr[2];
|
|
arr[2] = arr[3];
|
|
arr[3] = arr[1];
|
|
arr[1] = a;
|
|
rot = rot-90;
|
|
}
|
|
vertex_uvs.array.set(arr[0], index*8 + 0); //0,1
|
|
vertex_uvs.array.set(arr[1], index*8 + 2); //1,1
|
|
vertex_uvs.array.set(arr[2], index*8 + 4); //0,0
|
|
vertex_uvs.array.set(arr[3], index*8 + 6); //1,0
|
|
})
|
|
|
|
mesh.geometry.attributes.uv.needsUpdate = true;
|
|
|
|
this.dispatchEvent('update_uv', {element});
|
|
|
|
this.updatePixelGrid(element);
|
|
|
|
return mesh.geometry;
|
|
},
|
|
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;
|
|
|
|
if (mesh.geometry.attributes.highlight.array[0] != highlighted) {
|
|
mesh.geometry.attributes.highlight.array.set(Array(mesh.geometry.attributes.highlight.count).fill(highlighted));
|
|
mesh.geometry.attributes.highlight.needsUpdate = true;
|
|
}
|
|
|
|
this.dispatchEvent('update_highlight', {element});
|
|
},
|
|
updatePixelGrid(cube) {
|
|
var mesh = cube.mesh;
|
|
if (mesh === undefined) return;
|
|
mesh.remove(mesh.grid_box);
|
|
if (mesh.grid_box?.geometry) mesh.grid_box.geometry.dispose();
|
|
if (cube.visibility == false) return;
|
|
|
|
let grid_enabled = (Modes.paint && settings.painting_grid.value) || (Modes.edit && settings.pixel_grid.value)
|
|
if (!grid_enabled) return;
|
|
|
|
var from = cube.from.slice();
|
|
var to = cube.to.slice();
|
|
if (cube.inflate || cube.isStretched()) {
|
|
adjustFromAndToForInflateAndStretch(from, to, cube);
|
|
}
|
|
|
|
var vertices = [];
|
|
var epsilon = 0.0001
|
|
function getVector2(arr, axis) {
|
|
switch (axis) {
|
|
case 0: return [arr[1], arr[2]]; break;
|
|
case 1: return [arr[0], arr[2]]; break;
|
|
case 2: return [arr[0], arr[1]]; break;
|
|
}
|
|
}
|
|
function addVector(u, v, axis, w) {
|
|
switch (axis) {
|
|
case 0: vertices.push(w, u, v); break;
|
|
case 1: vertices.push(u, w, v); break;
|
|
case 2: vertices.push(u, v, w); break;
|
|
}
|
|
}
|
|
|
|
function addFace(name, uv_offset, axis, side) {
|
|
|
|
var start = getVector2(from, axis)
|
|
var end = getVector2(to, axis)
|
|
var face = cube.faces[name];
|
|
var texture = face.getTexture();
|
|
if (texture === null) return;
|
|
|
|
var px_x = texture ? texture.uv_width / texture.width : 1;
|
|
var px_y = texture ? texture.uv_height / texture.height : 1;
|
|
var uv_size = [
|
|
Math.abs(face.uv_size[0]),
|
|
Math.abs(face.uv_size[1])
|
|
]
|
|
uv_offset = [
|
|
uv_offset[0] == true
|
|
? (face.uv_size[0] > 0 ? (px_x-face.uv[2]) : ( face.uv[2]))
|
|
: (face.uv_size[0] > 0 ? ( face.uv[0]) : (px_x-face.uv[0])),
|
|
uv_offset[1] == true
|
|
? (face.uv_size[1] > 0 ? (px_y-face.uv[3]) : ( face.uv[3]))
|
|
: (face.uv_size[1] > 0 ? ( face.uv[1]) : (px_y-face.uv[1]))
|
|
]
|
|
uv_offset[0] = uv_offset[0] % px_x;
|
|
uv_offset[1] = uv_offset[1] % px_y;
|
|
|
|
if ((face.rotation % 180 == 90) != (axis == 0)) {
|
|
uv_size.reverse();
|
|
uv_offset.reverse();
|
|
}
|
|
|
|
var w = side == 0 ? from[axis] : to[axis]
|
|
|
|
//Columns
|
|
var width = end[0]-start[0];
|
|
var step = Math.abs( width / uv_size[0] );
|
|
if (texture) step *= texture.uv_width / texture.width;
|
|
if (step < epsilon) step = epsilon;
|
|
|
|
for (var col = start[0] - uv_offset[0]; col <= end[0]; col += step) {
|
|
if (col >= start[0]) {
|
|
addVector(col, start[1], axis, w);
|
|
addVector(col, end[1], axis, w);
|
|
}
|
|
}
|
|
|
|
//lines
|
|
var height = end[1]-start[1];
|
|
var step = Math.abs( height / uv_size[1] );
|
|
if (texture) {
|
|
let tex_height = texture.frameCount ? (texture.height / texture.frameCount) : texture.height;
|
|
step *= texture.uv_height / tex_height;
|
|
}
|
|
if (step < epsilon) step = epsilon;
|
|
|
|
for (var line = start[1] - uv_offset[1]; line <= end[1]; line += step) {
|
|
if (line >= start[1]) {
|
|
addVector(start[0], line, axis, w);
|
|
addVector(end[0], line, axis, w);
|
|
}
|
|
}
|
|
}
|
|
|
|
addFace('north', [true, true], 2, 0);
|
|
addFace('south', [false, true], 2, 1);
|
|
addFace('west', [false, true], 0, 0);
|
|
addFace('east', [true, true], 0, 1);
|
|
addFace('down', [false, true], 1, 0);
|
|
addFace('up', [false, false], 1, 1);
|
|
|
|
|
|
var geometry = new THREE.BufferGeometry();
|
|
geometry.setAttribute( 'position', new THREE.Float32BufferAttribute( vertices, 3 ) );
|
|
|
|
let box = new THREE.LineSegments(geometry, new THREE.LineBasicMaterial({color: gizmo_colors.grid}));
|
|
box.geometry.translate(-cube.origin[0], -cube.origin[1], -cube.origin[2]);
|
|
box.no_export = true;
|
|
|
|
box.name = cube.uuid+'_grid_box';
|
|
box.renderOrder = 2;
|
|
box.frustumCulled = false;
|
|
mesh.grid_box = box;
|
|
mesh.add(box);
|
|
|
|
this.dispatchEvent('update_painting_grid', {element: cube});
|
|
}
|
|
})
|
|
|
|
BARS.defineActions(function() {
|
|
new Action({
|
|
id: 'add_cube',
|
|
icon: 'add_box',
|
|
category: 'edit',
|
|
keybind: new Keybind({key: 'n', ctrl: true}),
|
|
condition: () => Modes.edit,
|
|
click: function () {
|
|
|
|
Undo.initEdit({outliner: true, elements: [], selection: true});
|
|
var base_cube = new Cube({
|
|
autouv: (settings.autouv.value ? 1 : 0)
|
|
}).init()
|
|
if (!base_cube.box_uv) base_cube.mapAutoUV()
|
|
let group = getCurrentGroup();
|
|
if (group) {
|
|
base_cube.addTo(group)
|
|
base_cube.color = group.color;
|
|
}
|
|
|
|
if (Texture.all.length && Format.single_texture) {
|
|
for (var face in base_cube.faces) {
|
|
base_cube.faces[face].texture = Texture.getDefault().uuid
|
|
}
|
|
UVEditor.loadData()
|
|
}
|
|
if (Format.bone_rig) {
|
|
var pos1 = group ? group.origin.slice() : [0, 0, 0];
|
|
let size = Settings.get('default_cube_size');
|
|
if (size % 2 == 0) {
|
|
base_cube.extend({
|
|
from:[ pos1[0] - size/2, pos1[1] - 0, pos1[2] - size/2 ],
|
|
to:[ pos1[0] + size/2, pos1[1] + size, pos1[2] + size/2 ],
|
|
origin: pos1.slice()
|
|
})
|
|
} else {
|
|
base_cube.extend({
|
|
from:[ pos1[0], pos1[1], pos1[2] ],
|
|
to:[ pos1[0]+size, pos1[1]+size, pos1[2]+size ],
|
|
origin: pos1.slice()
|
|
})
|
|
}
|
|
}
|
|
|
|
if (Group.selected) Group.selected.unselect()
|
|
base_cube.select()
|
|
Canvas.updateView({elements: [base_cube], element_aspects: {transform: true, geometry: true}})
|
|
Undo.finishEdit('Add cube', {outliner: true, elements: selected, selection: true});
|
|
Blockbench.dispatchEvent( 'add_cube', {object: base_cube} )
|
|
|
|
Vue.nextTick(function() {
|
|
if (settings.create_rename.value) {
|
|
base_cube.rename()
|
|
}
|
|
})
|
|
return base_cube
|
|
}
|
|
})
|
|
|
|
new Action({
|
|
id: 'edit_material_instances',
|
|
icon: 'fas.fa-adjust',
|
|
category: 'edit',
|
|
condition: {modes: ['edit'], formats: ['bedrock_block'], method: () => Cube.selected.length && !Cube.selected.find(cube => cube.box_uv)},
|
|
click: function () {
|
|
let form = {};
|
|
|
|
let first = Cube.selected[0];
|
|
for (var key in first.faces) {
|
|
let face = first.faces[key];
|
|
if (face.texture != null) {
|
|
form[key] = {
|
|
label: `face.${key}`,
|
|
value: face.material_name
|
|
}
|
|
}
|
|
}
|
|
|
|
let dialog = new Dialog({
|
|
id: 'material_instances',
|
|
title: 'dialog.material_instances.title',
|
|
width: 460,
|
|
form,
|
|
onConfirm: form_data => {
|
|
dialog.hide();
|
|
|
|
Undo.initEdit({elements: Cube.selected});
|
|
Cube.selected.forEach(cube => {
|
|
for (var key in cube.faces) {
|
|
let face = cube.faces[key];
|
|
if (face.texture != null && typeof form_data[key] == 'string') {
|
|
face.material_name = form_data[key];
|
|
}
|
|
}
|
|
})
|
|
Undo.finishEdit('Edit material instances')
|
|
}
|
|
})
|
|
dialog.show();
|
|
}
|
|
})
|
|
|
|
new BarSelect('cube_uv_mode', {
|
|
name: 'dialog.project.uv_mode',
|
|
category: 'uv',
|
|
condition: () => Cube.selected.length && Format.optional_box_uv,
|
|
options: {
|
|
face_uv: 'dialog.project.uv_mode.face_uv',
|
|
box_uv: 'dialog.project.uv_mode.box_uv',
|
|
},
|
|
onChange() {
|
|
let box_uv = this.value == 'box_uv';
|
|
Undo.initEdit({elements: Cube.selected, uv_only: true});
|
|
Cube.selected.forEach(cube => {
|
|
cube.setUVMode(box_uv);
|
|
})
|
|
Undo.finishEdit('Change UV mode')
|
|
updateSelection();
|
|
}
|
|
})
|
|
Blockbench.on('update_selection', () => {
|
|
if (Condition(BarItems.cube_uv_mode)) {
|
|
BarItems.cube_uv_mode.set(Cube.selected[0].box_uv ? 'box_uv' : 'face_uv');
|
|
}
|
|
})
|
|
|
|
new BarSelect('element_render_order', {
|
|
name: 'action.element_render_order',
|
|
category: 'edit',
|
|
condition: () => Outliner.selected.find(e => e.render_order) && Texture.all.length,
|
|
options: {
|
|
default: 'action.element_render_order.default',
|
|
behind: 'action.element_render_order.behind',
|
|
in_front: 'action.element_render_order.in_front',
|
|
},
|
|
onChange() {
|
|
let elements = Outliner.selected.filter(e => e.render_order);
|
|
Undo.initEdit({elements});
|
|
elements.forEach(element => {
|
|
element.render_order = this.value;
|
|
element.preview_controller.updateRenderOrder(element);
|
|
})
|
|
Undo.finishEdit('Change render order')
|
|
updateSelection();
|
|
}
|
|
})
|
|
Blockbench.on('update_selection', () => {
|
|
let element = Outliner.selected.find(e => e.render_order);
|
|
if (element) {
|
|
BarItems.element_render_order.set(element.render_order);
|
|
}
|
|
})
|
|
}) |