2021-08-18 04:02:23 +08:00
|
|
|
function switchBoxUV(state) {
|
|
|
|
BARS.updateConditions()
|
|
|
|
if (state) {
|
|
|
|
Cube.all.forEach(cube => {
|
|
|
|
if (cube.faces.west.uv[2] < cube.faces.east.uv[0]) {
|
|
|
|
cube.mirror_uv = true;
|
|
|
|
cube.uv_offset[0] = cube.faces.west.uv[2];
|
|
|
|
} else {
|
|
|
|
cube.mirror_uv = false;
|
|
|
|
cube.uv_offset[0] = cube.faces.east.uv[0];
|
|
|
|
}
|
|
|
|
cube.uv_offset[1] = cube.faces.up.uv[3];
|
|
|
|
})
|
2021-11-24 06:46:57 +08:00
|
|
|
} else {
|
|
|
|
Cube.all.forEach(cube => {
|
|
|
|
for (let fkey in cube.faces) {
|
|
|
|
cube.faces[fkey].rotation = 0;
|
|
|
|
}
|
|
|
|
})
|
2019-12-16 03:04:31 +08:00
|
|
|
}
|
2021-08-19 04:58:47 +08:00
|
|
|
UVEditor.vue.box_uv = state;
|
2021-09-02 04:13:04 +08:00
|
|
|
UVEditor.setGrid(1);
|
|
|
|
Canvas.updateAllUVs();
|
2021-08-18 04:02:23 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
const UVEditor = {
|
|
|
|
face: 'north',
|
|
|
|
size: 320,
|
|
|
|
zoom: 1,
|
|
|
|
grid: 1,
|
|
|
|
max_zoom: 16,
|
|
|
|
auto_grid: true,
|
|
|
|
panel: null,
|
2021-08-19 04:58:47 +08:00
|
|
|
sliders: {},
|
2021-08-18 04:02:23 +08:00
|
|
|
|
|
|
|
get vue() {
|
|
|
|
return this.panel.inside_vue;
|
|
|
|
},
|
2019-12-16 03:04:31 +08:00
|
|
|
message(msg, vars) {
|
|
|
|
msg = tl(msg, vars)
|
2021-08-26 04:00:08 +08:00
|
|
|
let box = document.createElement('div');
|
|
|
|
box.className = 'uv_message_box'
|
|
|
|
box.textContent = msg;
|
|
|
|
this.vue.$refs.main.append(box)
|
2019-12-16 03:04:31 +08:00
|
|
|
setTimeout(function() {
|
2020-03-15 04:32:24 +08:00
|
|
|
box.remove()
|
|
|
|
}, 1200)
|
2021-08-18 04:02:23 +08:00
|
|
|
},
|
2019-12-16 03:04:31 +08:00
|
|
|
//Brush
|
|
|
|
getBrushCoordinates(event, tex) {
|
|
|
|
convertTouchEvent(event);
|
2021-12-14 20:57:49 +08:00
|
|
|
let pixel_size = this.inner_width / tex.width
|
|
|
|
let result = {};
|
|
|
|
let mouse_coords;
|
|
|
|
if (event.target.id == 'uv_frame') {
|
|
|
|
mouse_coords = [event.offsetX, event.offsetY];
|
|
|
|
} else {
|
|
|
|
let frame_pos = $('#uv_frame').offset();
|
|
|
|
mouse_coords = [
|
|
|
|
event.clientX - frame_pos.left,
|
|
|
|
event.clientY - frame_pos.top
|
|
|
|
];
|
|
|
|
}
|
2020-03-05 03:56:17 +08:00
|
|
|
|
|
|
|
if (Toolbox.selected.id === 'copy_paste_tool') {
|
2021-12-14 20:57:49 +08:00
|
|
|
result.x = Math.round(mouse_coords[0]/pixel_size*1);
|
|
|
|
result.y = Math.round(mouse_coords[1]/pixel_size*1);
|
2020-03-05 03:56:17 +08:00
|
|
|
} else {
|
|
|
|
let offset = BarItems.slider_brush_size.get()%2 == 0 && Toolbox.selected.brushTool ? 0.5 : 0;
|
2021-12-14 20:57:49 +08:00
|
|
|
result.x = Math.floor(mouse_coords[0]/pixel_size*1 + offset);
|
|
|
|
result.y = Math.floor(mouse_coords[1]/pixel_size*1 + offset);
|
2019-12-16 03:04:31 +08:00
|
|
|
}
|
2020-07-16 15:32:59 +08:00
|
|
|
if (tex.frameCount) result.y += (tex.height / tex.frameCount) * tex.currentFrame;
|
2021-10-23 19:44:43 +08:00
|
|
|
if (!tex.frameCount && tex.ratio != Project.texture_width / Project.texture_height) result.y /= tex.ratio;
|
2020-07-16 15:32:59 +08:00
|
|
|
return result;
|
2021-08-18 04:02:23 +08:00
|
|
|
},
|
2020-03-05 03:56:17 +08:00
|
|
|
startPaintTool(event) {
|
2019-12-16 03:04:31 +08:00
|
|
|
var scope = this;
|
|
|
|
Painter.active_uv_editor = scope;
|
|
|
|
|
|
|
|
var texture = scope.getTexture()
|
|
|
|
if (texture) {
|
|
|
|
var coords = scope.getBrushCoordinates(event, texture)
|
|
|
|
|
2020-03-05 03:56:17 +08:00
|
|
|
if (Toolbox.selected.id !== 'copy_paste_tool') {
|
|
|
|
Painter.startPaintTool(texture, coords.x, coords.y, undefined, event)
|
|
|
|
} else {
|
2021-12-14 20:57:49 +08:00
|
|
|
this.startSelection(coords.x, coords.y, event)
|
2020-03-05 03:56:17 +08:00
|
|
|
}
|
2019-12-16 03:04:31 +08:00
|
|
|
}
|
2021-12-14 20:57:49 +08:00
|
|
|
if (Toolbox.selected.id !== 'color_picker' && Toolbox.selected.id !== 'copy_paste_tool' && texture) {
|
2021-08-19 04:58:47 +08:00
|
|
|
addEventListeners(this.vue.$refs.frame, 'mousemove touchmove', UVEditor.movePaintTool, false );
|
|
|
|
addEventListeners(document, 'mouseup touchend', UVEditor.stopBrush, false );
|
2019-12-16 03:04:31 +08:00
|
|
|
}
|
2021-08-18 04:02:23 +08:00
|
|
|
},
|
2020-03-05 03:56:17 +08:00
|
|
|
movePaintTool(event) {
|
2019-12-16 03:04:31 +08:00
|
|
|
var scope = Painter.active_uv_editor;
|
|
|
|
var texture = scope.getTexture()
|
|
|
|
if (!texture) {
|
|
|
|
Blockbench.showQuickMessage('message.untextured')
|
|
|
|
} else {
|
2020-03-05 03:56:17 +08:00
|
|
|
var new_face;
|
|
|
|
var {x, y} = scope.getBrushCoordinates(event, texture);
|
2019-12-16 03:04:31 +08:00
|
|
|
if (texture.img.naturalWidth + texture.img.naturalHeight == 0) return;
|
|
|
|
|
|
|
|
if (x === Painter.current.x && y === Painter.current.y) {
|
|
|
|
return
|
|
|
|
}
|
2021-10-15 17:12:24 +08:00
|
|
|
if (Painter.current.face !== scope.selected_faces[0]) {
|
2019-12-16 03:04:31 +08:00
|
|
|
Painter.current.x = x
|
|
|
|
Painter.current.y = y
|
2021-10-15 17:12:24 +08:00
|
|
|
Painter.current.face = scope.selected_faces[0]
|
2019-12-16 03:04:31 +08:00
|
|
|
new_face = true;
|
2020-03-05 03:56:17 +08:00
|
|
|
if (texture !== Painter.current.texture && Undo.current_save) {
|
2019-12-16 03:04:31 +08:00
|
|
|
Undo.current_save.addTexture(texture)
|
|
|
|
}
|
|
|
|
}
|
2020-03-05 03:56:17 +08:00
|
|
|
if (Toolbox.selected.id !== 'copy_paste_tool') {
|
|
|
|
Painter.movePaintTool(texture, x, y, event, new_face)
|
|
|
|
}
|
2019-12-16 03:04:31 +08:00
|
|
|
}
|
2021-08-18 04:02:23 +08:00
|
|
|
},
|
2019-12-16 03:04:31 +08:00
|
|
|
stopBrush(event) {
|
2021-08-19 04:58:47 +08:00
|
|
|
removeEventListeners( UVEditor.vue.$refs.frame, 'mousemove touchmove', UVEditor.movePaintTool, false );
|
|
|
|
removeEventListeners( document, 'mouseup touchend', UVEditor.stopBrush, false );
|
2020-03-05 03:56:17 +08:00
|
|
|
if (Toolbox.selected.id !== 'copy_paste_tool') {
|
|
|
|
Painter.stopPaintTool()
|
|
|
|
} else {
|
2021-08-19 04:58:47 +08:00
|
|
|
UVEditor.stopSelection()
|
2020-03-05 03:56:17 +08:00
|
|
|
}
|
2021-08-18 04:02:23 +08:00
|
|
|
},
|
2020-03-05 03:56:17 +08:00
|
|
|
// Copy Paste Tool
|
2021-12-14 20:57:49 +08:00
|
|
|
startSelection(x, y, event) {
|
2020-03-05 03:56:17 +08:00
|
|
|
if (Painter.selection.overlay && event.target && event.target.id === 'uv_frame') {
|
2020-04-30 05:35:47 +08:00
|
|
|
if (open_interface) {
|
|
|
|
open_interface.confirm()
|
|
|
|
} else {
|
|
|
|
this.removePastingOverlay()
|
|
|
|
}
|
2020-03-05 03:56:17 +08:00
|
|
|
}
|
|
|
|
delete Painter.selection.calcrect;
|
|
|
|
if (!Painter.selection.overlay) {
|
2021-08-29 05:12:13 +08:00
|
|
|
$(this.vue.$refs.frame).find('#texture_selection_rect').detach();
|
|
|
|
let rect = document.createElement('div');
|
2021-12-23 17:40:05 +08:00
|
|
|
rect.style.visibility = 'hidden';
|
2021-08-29 05:12:13 +08:00
|
|
|
rect.id = 'texture_selection_rect';
|
|
|
|
this.vue.$refs.frame.append(rect)
|
2020-03-05 03:56:17 +08:00
|
|
|
Painter.selection.rect = rect;
|
|
|
|
Painter.selection.start_x = x;
|
|
|
|
Painter.selection.start_y = y;
|
2021-12-15 22:23:42 +08:00
|
|
|
UVEditor.vue.copy_overlay.width = 0;
|
|
|
|
UVEditor.vue.copy_overlay.height = 0;
|
2020-03-05 03:56:17 +08:00
|
|
|
} else {
|
|
|
|
Painter.selection.start_x = Painter.selection.x;
|
|
|
|
Painter.selection.start_y = Painter.selection.y;
|
|
|
|
Painter.selection.start_event = event;
|
|
|
|
}
|
2021-12-14 20:57:49 +08:00
|
|
|
|
|
|
|
function drag(e1) {
|
|
|
|
let texture = UVEditor.texture;
|
|
|
|
var {x, y} = UVEditor.getBrushCoordinates(e1, texture);
|
|
|
|
if (texture.img.naturalWidth + texture.img.naturalHeight == 0) return;
|
|
|
|
if (x === Painter.current.x && y === Painter.current.y) return;
|
|
|
|
Painter.current.x = x = Math.clamp(x, 0, texture.img.naturalWidth);
|
|
|
|
Painter.current.y = y = Math.clamp(y, 0, texture.img.naturalHeight);
|
|
|
|
UVEditor.dragSelection(x, y, e1);
|
|
|
|
}
|
|
|
|
function stop() {
|
|
|
|
removeEventListeners(document, 'mousemove touchmove', drag);
|
|
|
|
removeEventListeners(document, 'mouseup touchend', stop);
|
|
|
|
UVEditor.stopSelection();
|
|
|
|
}
|
|
|
|
addEventListeners(document, 'mousemove touchmove', drag);
|
|
|
|
addEventListeners(document, 'mouseup touchend', stop);
|
2021-08-18 04:02:23 +08:00
|
|
|
},
|
2021-12-14 20:57:49 +08:00
|
|
|
dragSelection(x, y, event) {
|
|
|
|
let m = UVEditor.inner_width / UVEditor.texture.width;
|
2020-03-05 03:56:17 +08:00
|
|
|
|
|
|
|
if (!Painter.selection.overlay) {
|
|
|
|
let calcrect = getRectangle(Painter.selection.start_x, Painter.selection.start_y, x, y)
|
2021-12-23 17:40:05 +08:00
|
|
|
if (!calcrect.x && !calcrect.y) return;
|
|
|
|
UVEditor.vue.copy_overlay.state = 'select';
|
2020-03-05 03:56:17 +08:00
|
|
|
Painter.selection.calcrect = calcrect;
|
|
|
|
Painter.selection.x = calcrect.ax;
|
|
|
|
Painter.selection.y = calcrect.ay;
|
2021-12-15 22:23:42 +08:00
|
|
|
UVEditor.vue.copy_overlay.width = calcrect.x;
|
|
|
|
UVEditor.vue.copy_overlay.height = calcrect.y;
|
2021-08-29 05:12:13 +08:00
|
|
|
$(Painter.selection.rect)
|
2020-03-05 03:56:17 +08:00
|
|
|
.css('left', calcrect.ax*m + 'px')
|
2021-12-14 20:57:49 +08:00
|
|
|
.css('top', (calcrect.ay%UVEditor.texture.display_height)*m + 'px')
|
2020-03-05 03:56:17 +08:00
|
|
|
.css('width', calcrect.x *m + 'px')
|
|
|
|
.css('height', calcrect.y *m + 'px')
|
2021-12-23 17:40:05 +08:00
|
|
|
.css('visibility', 'visible')
|
2021-12-14 20:57:49 +08:00
|
|
|
} else if (UVEditor.texture && Painter.selection.canvas) {
|
2020-03-05 03:56:17 +08:00
|
|
|
Painter.selection.x = Painter.selection.start_x + Math.round((event.clientX - Painter.selection.start_event.clientX) / m);
|
|
|
|
Painter.selection.y = Painter.selection.start_y + Math.round((event.clientY - Painter.selection.start_event.clientY) / m);
|
2021-12-14 20:57:49 +08:00
|
|
|
Painter.selection.x = Math.clamp(Painter.selection.x, 1-Painter.selection.canvas.width, UVEditor.texture.width -1)
|
|
|
|
Painter.selection.y = Math.clamp(Painter.selection.y, 1-Painter.selection.canvas.height, UVEditor.texture.height-1)
|
|
|
|
UVEditor.updatePastingOverlay()
|
2020-03-05 03:56:17 +08:00
|
|
|
}
|
2021-08-18 04:02:23 +08:00
|
|
|
},
|
2020-03-05 03:56:17 +08:00
|
|
|
stopSelection() {
|
|
|
|
if (Painter.selection.rect) {
|
2021-08-29 05:12:13 +08:00
|
|
|
Painter.selection.rect.remove()
|
2020-03-05 03:56:17 +08:00
|
|
|
}
|
|
|
|
if (Painter.selection.overlay || !Painter.selection.calcrect) return;
|
2021-12-23 17:40:05 +08:00
|
|
|
UVEditor.vue.copy_overlay.state = 'off';
|
2020-03-05 03:56:17 +08:00
|
|
|
if (Painter.selection.calcrect.x == 0 || Painter.selection.calcrect.y == 0) return;
|
|
|
|
|
|
|
|
let calcrect = Painter.selection.calcrect;
|
|
|
|
var canvas = document.createElement('canvas')
|
|
|
|
var ctx = canvas.getContext('2d');
|
|
|
|
canvas.width = calcrect.x;
|
|
|
|
canvas.height = calcrect.y;
|
2021-12-14 20:57:49 +08:00
|
|
|
ctx.drawImage(UVEditor.vue.texture.img, -calcrect.ax, -calcrect.ay)
|
2020-03-05 03:56:17 +08:00
|
|
|
|
|
|
|
if (isApp) {
|
|
|
|
let image = nativeImage.createFromDataURL(canvas.toDataURL())
|
|
|
|
clipboard.writeImage(image)
|
|
|
|
}
|
|
|
|
Painter.selection.canvas = canvas;
|
|
|
|
|
2021-12-14 20:57:49 +08:00
|
|
|
UVEditor.addPastingOverlay();
|
2021-08-18 04:02:23 +08:00
|
|
|
},
|
2020-03-05 03:56:17 +08:00
|
|
|
addPastingOverlay() {
|
|
|
|
if (Painter.selection.overlay) return;
|
|
|
|
let scope = this;
|
2021-12-14 20:57:49 +08:00
|
|
|
let overlay = $(Interface.createElement('div', {id: 'texture_pasting_overlay'}));
|
|
|
|
UVEditor.vue.copy_overlay.state = 'move';
|
2020-03-05 03:56:17 +08:00
|
|
|
|
|
|
|
open_interface = {
|
|
|
|
confirm() {
|
|
|
|
scope.removePastingOverlay()
|
|
|
|
if (scope.texture) {
|
|
|
|
scope.texture.edit((canvas) => {
|
|
|
|
var ctx = canvas.getContext('2d');
|
2021-11-10 03:40:01 +08:00
|
|
|
let y = (Painter.selection.y % scope.texture.display_height);
|
|
|
|
if (scope.texture.frameCount > 1) y += scope.texture.currentFrame * scope.texture.display_height;
|
|
|
|
ctx.drawImage(Painter.selection.canvas, Painter.selection.x, y)
|
2020-03-05 03:56:17 +08:00
|
|
|
})
|
|
|
|
}
|
|
|
|
},
|
|
|
|
hide() {
|
|
|
|
scope.removePastingOverlay()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
overlay.append(Painter.selection.canvas)
|
|
|
|
Painter.selection.overlay = overlay;
|
2021-08-29 05:12:13 +08:00
|
|
|
$(UVEditor.vue.$refs.frame).append(overlay)
|
2020-03-05 03:56:17 +08:00
|
|
|
Painter.selection.x = Math.clamp(Painter.selection.x, 0, this.texture.width-Painter.selection.canvas.width)
|
|
|
|
Painter.selection.y = Math.clamp(Painter.selection.y, 0, this.texture.height-Painter.selection.canvas.height)
|
2021-09-24 03:35:36 +08:00
|
|
|
UVEditor.updatePastingOverlay()
|
2020-03-05 03:56:17 +08:00
|
|
|
|
2020-04-30 05:35:47 +08:00
|
|
|
function clickElsewhere(event) {
|
2021-11-09 23:39:07 +08:00
|
|
|
if (event.button == 1) return;
|
2020-03-05 03:56:17 +08:00
|
|
|
if (!Painter.selection.overlay) {
|
2020-04-30 05:35:47 +08:00
|
|
|
removeEventListeners(document, 'mousedown touchstart', clickElsewhere)
|
2021-12-14 20:57:49 +08:00
|
|
|
} else if (Painter.selection.overlay.has(event.target).length == 0 && $(scope.vue.$refs.copy_paste_tool_control).has(event.target).length == 0) {
|
2020-04-30 05:35:47 +08:00
|
|
|
open_interface.confirm()
|
2020-03-05 03:56:17 +08:00
|
|
|
}
|
|
|
|
}
|
2020-04-30 05:35:47 +08:00
|
|
|
addEventListeners(document, 'mousedown touchstart', clickElsewhere)
|
2021-08-18 04:02:23 +08:00
|
|
|
},
|
2020-03-05 03:56:17 +08:00
|
|
|
removePastingOverlay() {
|
|
|
|
Painter.selection.overlay.detach();
|
2021-12-14 20:57:49 +08:00
|
|
|
UVEditor.vue.copy_overlay.state = 'off';
|
2020-03-05 03:56:17 +08:00
|
|
|
delete Painter.selection.overlay;
|
|
|
|
open_interface = false;
|
2021-08-18 04:02:23 +08:00
|
|
|
},
|
2020-03-05 03:56:17 +08:00
|
|
|
updatePastingOverlay() {
|
|
|
|
let m = this.inner_width/this.texture.width
|
|
|
|
$(Painter.selection.canvas)
|
|
|
|
.css('width', Painter.selection.canvas.width * m)
|
|
|
|
.css('height', Painter.selection.canvas.height * m)
|
|
|
|
Painter.selection.overlay
|
|
|
|
.css('left', Painter.selection.x * m)
|
2021-11-10 03:40:01 +08:00
|
|
|
.css('top', (Painter.selection.y%this.texture.display_height) * m);
|
2020-03-05 03:56:17 +08:00
|
|
|
return this;
|
2021-08-18 04:02:23 +08:00
|
|
|
},
|
2021-09-15 06:26:07 +08:00
|
|
|
focusOnSelection() {
|
|
|
|
let min_x = Project.texture_width;
|
|
|
|
let min_y = Project.texture_height;
|
|
|
|
let max_x = 0;
|
|
|
|
let max_y = 0;
|
|
|
|
let elements = UVEditor.getMappableElements();
|
|
|
|
elements.forEach(element => {
|
|
|
|
if (element instanceof Cube && Project.box_uv) {
|
|
|
|
let size = element.size(undefined, true)
|
|
|
|
min_x = Math.min(min_x, element.uv_offset[0]);
|
|
|
|
min_y = Math.min(min_y, element.uv_offset[1]);
|
|
|
|
max_x = Math.max(max_x, element.uv_offset[0] + (size[0] + size[2]) * 2);
|
|
|
|
max_y = Math.max(max_y, element.uv_offset[1] + size[1] + size[2]);
|
|
|
|
} else {
|
|
|
|
for (let fkey in element.faces) {
|
|
|
|
if (!UVEditor.selected_faces.includes(fkey)) continue;
|
|
|
|
let face = element.faces[fkey];
|
|
|
|
if (element instanceof Cube) {
|
|
|
|
min_x = Math.min(min_x, face.uv[0], face.uv[2]);
|
|
|
|
min_y = Math.min(min_y, face.uv[1], face.uv[3]);
|
|
|
|
max_x = Math.max(max_x, face.uv[0], face.uv[2]);
|
|
|
|
max_y = Math.max(max_y, face.uv[1], face.uv[3]);
|
|
|
|
} else if (element instanceof Mesh) {
|
|
|
|
face.vertices.forEach(vkey => {
|
|
|
|
if (!face.uv[vkey]) return;
|
|
|
|
min_x = Math.min(min_x, face.uv[vkey][0]);
|
|
|
|
min_y = Math.min(min_y, face.uv[vkey][1]);
|
|
|
|
max_x = Math.max(max_x, face.uv[vkey][0]);
|
|
|
|
max_y = Math.max(max_y, face.uv[vkey][1]);
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
let pixel_size = UVEditor.getPixelSize();
|
|
|
|
let focus = [min_x+max_x, min_y+max_y].map(v => v * 0.5 * pixel_size);
|
|
|
|
let {viewport} = UVEditor.vue.$refs;
|
|
|
|
$(viewport).animate({
|
|
|
|
scrollLeft: focus[0] - UVEditor.width / 2,
|
|
|
|
scrollTop: focus[1] - UVEditor.height / 2,
|
|
|
|
}, 100)
|
|
|
|
},
|
2019-12-16 03:04:31 +08:00
|
|
|
//Get
|
2020-10-09 20:39:55 +08:00
|
|
|
get width() {
|
2021-08-18 04:02:23 +08:00
|
|
|
return this.vue.width;
|
|
|
|
},
|
|
|
|
get height() {
|
|
|
|
return this.vue.height;
|
|
|
|
},
|
|
|
|
get zoom() {
|
|
|
|
return this.vue.zoom;
|
|
|
|
},
|
2019-12-16 03:04:31 +08:00
|
|
|
get inner_width() {
|
2021-08-26 04:00:08 +08:00
|
|
|
return this.vue.inner_width;
|
2021-08-18 04:02:23 +08:00
|
|
|
},
|
2019-12-16 03:04:31 +08:00
|
|
|
get inner_height() {
|
2021-08-26 04:00:08 +08:00
|
|
|
return this.vue.inner_height;
|
2021-08-18 04:02:23 +08:00
|
|
|
},
|
2021-09-15 06:26:07 +08:00
|
|
|
get selected_faces() {
|
|
|
|
return this.vue.selected_faces;
|
|
|
|
},
|
2021-09-21 00:45:02 +08:00
|
|
|
get texture() {
|
|
|
|
return this.vue.texture;
|
|
|
|
},
|
2019-12-16 03:04:31 +08:00
|
|
|
getPixelSize() {
|
|
|
|
if (Project.box_uv) {
|
|
|
|
return this.inner_width/Project.texture_width
|
|
|
|
} else {
|
|
|
|
return this.inner_width/ (
|
|
|
|
(typeof this.texture === 'object' && this.texture.width)
|
|
|
|
? this.texture.width
|
|
|
|
: Project.texture_width
|
|
|
|
);
|
|
|
|
}
|
2021-08-18 04:02:23 +08:00
|
|
|
},
|
2021-08-26 04:00:08 +08:00
|
|
|
getFaces(obj, event) {
|
|
|
|
let available = Object.keys(obj.faces)
|
2019-12-16 03:04:31 +08:00
|
|
|
if (event && event.shiftKey) {
|
2021-08-26 04:00:08 +08:00
|
|
|
return available;
|
2019-12-16 03:04:31 +08:00
|
|
|
} else {
|
2021-08-26 04:00:08 +08:00
|
|
|
return UVEditor.vue.selected_faces.filter(key => available.includes(key));
|
2019-12-16 03:04:31 +08:00
|
|
|
}
|
2021-08-18 04:02:23 +08:00
|
|
|
},
|
2021-08-26 04:00:08 +08:00
|
|
|
getReferenceFace() {
|
|
|
|
let el = this.getMappableElements()[0];
|
|
|
|
if (el) {
|
|
|
|
for (let key in el.faces) {
|
|
|
|
if (UVEditor.vue.selected_faces.includes(key)) {
|
|
|
|
return el.faces[key];
|
|
|
|
}
|
|
|
|
}
|
2019-12-16 03:04:31 +08:00
|
|
|
}
|
2021-08-18 04:02:23 +08:00
|
|
|
},
|
2021-08-26 04:00:08 +08:00
|
|
|
getMappableElements() {
|
2021-09-07 18:25:41 +08:00
|
|
|
return Outliner.selected.filter(el => typeof el.faces == 'object');
|
2021-08-26 04:00:08 +08:00
|
|
|
},
|
2019-12-16 03:04:31 +08:00
|
|
|
getTexture() {
|
2020-07-16 15:32:59 +08:00
|
|
|
if (Format.single_texture) return Texture.getDefault();
|
2021-08-29 05:12:13 +08:00
|
|
|
return this.vue.texture;
|
2021-08-18 04:02:23 +08:00
|
|
|
},
|
2019-12-16 03:04:31 +08:00
|
|
|
//Set
|
|
|
|
setZoom(zoom) {
|
2021-08-18 04:02:23 +08:00
|
|
|
this.vue.zoom = zoom;
|
|
|
|
return this;
|
|
|
|
},
|
2019-12-16 03:04:31 +08:00
|
|
|
setGrid(value) {
|
|
|
|
if (value == 'auto') {
|
|
|
|
this.auto_grid = true;
|
2021-08-30 16:30:46 +08:00
|
|
|
this.vue.updateTexture()
|
2019-12-16 03:04:31 +08:00
|
|
|
} else {
|
|
|
|
value = parseInt(value);
|
|
|
|
if (typeof value !== 'number') value = 1;
|
|
|
|
this.grid = Math.clamp(value, 1, 1024);
|
|
|
|
this.auto_grid = false;
|
|
|
|
}
|
2021-08-30 16:30:46 +08:00
|
|
|
this.updateSize();
|
2019-12-16 03:04:31 +08:00
|
|
|
return this;
|
2021-08-18 04:02:23 +08:00
|
|
|
},
|
2019-12-16 03:04:31 +08:00
|
|
|
updateSize() {
|
2021-08-18 04:02:23 +08:00
|
|
|
this.vue.updateSize();
|
|
|
|
},
|
2020-04-26 02:25:07 +08:00
|
|
|
setFace(face, update = true) {
|
2021-08-19 04:58:47 +08:00
|
|
|
this.vue.selected_faces.replace([face]);
|
2019-12-16 03:04:31 +08:00
|
|
|
return this;
|
2021-08-18 04:02:23 +08:00
|
|
|
},
|
2019-12-16 03:04:31 +08:00
|
|
|
//Selection
|
|
|
|
reverseSelect(event) {
|
|
|
|
var scope = this;
|
2021-08-26 04:00:08 +08:00
|
|
|
if (!this.vue.texture && !Format.single_texture) return this;
|
2019-12-16 03:04:31 +08:00
|
|
|
if (!event.target.classList.contains('uv_size_handle') && !event.target.id === 'uv_frame') {
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
var matches = [];
|
2021-08-26 04:00:08 +08:00
|
|
|
var face_matches = [];
|
2021-08-19 04:58:47 +08:00
|
|
|
var u = event.offsetX / this.vue.inner_width * this.getResolution(0);
|
|
|
|
var v = event.offsetY / this.vue.inner_height * this.getResolution(1);
|
2019-12-16 03:04:31 +08:00
|
|
|
Cube.all.forEach(cube => {
|
2021-10-26 02:23:46 +08:00
|
|
|
if (cube.locked) return;
|
2019-12-16 03:04:31 +08:00
|
|
|
for (var face in cube.faces) {
|
|
|
|
var uv = cube.faces[face].uv
|
2021-08-26 04:00:08 +08:00
|
|
|
if (uv && Math.isBetween(u, uv[0], uv[2]) && Math.isBetween(v, uv[1], uv[3]) && (cube.faces[face].getTexture() === scope.vue.texture || Format.single_texture)) {
|
2021-10-07 15:20:58 +08:00
|
|
|
matches.safePush(cube);
|
|
|
|
face_matches.safePush(face);
|
2019-12-16 03:04:31 +08:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
2021-10-07 15:20:58 +08:00
|
|
|
Mesh.all.forEach(mesh => {
|
2021-10-26 02:23:46 +08:00
|
|
|
if (mesh.locked) return;
|
2021-10-07 15:20:58 +08:00
|
|
|
for (var face in mesh.faces) {
|
|
|
|
let rect = mesh.faces[face].getBoundingRect();
|
|
|
|
if (uv && Math.isBetween(u, rect.ax, rect.bx) && Math.isBetween(v, rect.ay, rect.by) && (mesh.faces[face].getTexture() === scope.vue.texture || Format.single_texture)) {
|
|
|
|
matches.safePush(mesh);
|
|
|
|
face_matches.safePush(face);
|
|
|
|
break;
|
|
|
|
}
|
2019-12-16 03:04:31 +08:00
|
|
|
}
|
2021-10-07 15:20:58 +08:00
|
|
|
})
|
|
|
|
if (matches.length) {
|
2021-08-26 04:00:08 +08:00
|
|
|
if (!event.shiftKey && !Pressing.overrides.shift && !event.ctrlOrCmd && !Pressing.overrides.ctrl) {
|
|
|
|
Project.selected_elements.empty();
|
2021-10-07 15:20:58 +08:00
|
|
|
UVEditor.vue.selected_faces.empty();
|
|
|
|
}
|
|
|
|
if (!Project.box_uv) {
|
|
|
|
UVEditor.vue.selected_faces.safePush(...face_matches);
|
2019-12-16 03:04:31 +08:00
|
|
|
}
|
|
|
|
matches.forEach(s => {
|
2021-08-26 04:00:08 +08:00
|
|
|
Project.selected_elements.safePush(s)
|
2019-12-16 03:04:31 +08:00
|
|
|
});
|
|
|
|
updateSelection();
|
|
|
|
}
|
2021-08-26 04:00:08 +08:00
|
|
|
return matches;
|
2021-08-18 04:02:23 +08:00
|
|
|
},
|
2019-12-16 03:04:31 +08:00
|
|
|
forCubes(cb) {
|
|
|
|
var i = 0;
|
|
|
|
while (i < Cube.selected.length) {
|
|
|
|
cb(Cube.selected[i]);
|
|
|
|
i++;
|
|
|
|
}
|
2021-08-18 04:02:23 +08:00
|
|
|
},
|
2021-09-22 03:13:24 +08:00
|
|
|
forElements(cb) {
|
|
|
|
this.getMappableElements().forEach(cb);
|
|
|
|
},
|
2019-12-16 03:04:31 +08:00
|
|
|
//Load
|
|
|
|
loadData() {
|
2021-08-18 04:02:23 +08:00
|
|
|
this.vue.updateTexture();
|
2021-10-15 17:12:24 +08:00
|
|
|
this.displayTools();
|
2021-08-19 04:58:47 +08:00
|
|
|
this.displayTools();
|
2021-08-26 04:00:08 +08:00
|
|
|
this.vue.$forceUpdate();
|
2021-08-18 04:02:23 +08:00
|
|
|
return this;
|
|
|
|
},
|
2021-09-02 04:13:04 +08:00
|
|
|
applyTexture(texture) {
|
|
|
|
let elements = this.getMappableElements();
|
|
|
|
Undo.initEdit({elements, uv_only: true})
|
|
|
|
elements.forEach(el => {
|
|
|
|
this.vue.selected_faces.forEach(face => {
|
|
|
|
if (el.faces[face]) {
|
|
|
|
el.faces[face].texture = texture.uuid;
|
|
|
|
}
|
|
|
|
})
|
2019-12-16 03:04:31 +08:00
|
|
|
})
|
|
|
|
this.loadData()
|
|
|
|
Canvas.updateSelectedFaces()
|
2021-06-06 15:28:22 +08:00
|
|
|
Undo.finishEdit('Apply texture')
|
2021-08-18 04:02:23 +08:00
|
|
|
},
|
2021-10-15 17:12:24 +08:00
|
|
|
displayTools() {
|
2021-09-07 18:25:41 +08:00
|
|
|
if (!this.getMappableElements().length) return;
|
|
|
|
for (var id in UVEditor.sliders) {
|
|
|
|
var slider = UVEditor.sliders[id];
|
|
|
|
slider.node.style.setProperty('display', BARS.condition(slider.condition)?'block':'none');
|
|
|
|
slider.update();
|
|
|
|
}
|
2021-10-15 21:14:32 +08:00
|
|
|
var face = Cube.selected[0] && this.selected_faces[0] && Cube.selected[0].faces[this.selected_faces[0]]
|
2021-10-15 17:12:24 +08:00
|
|
|
if (face) {
|
|
|
|
BarItems.uv_rotation.set((face && face.rotation)||0);
|
|
|
|
BarItems.cullface.set(face.cullface||'off')
|
|
|
|
BarItems.face_tint.setIcon(face.tint !== -1 ? 'check_box' : 'check_box_outline_blank')
|
|
|
|
BarItems.slider_face_tint.update()
|
|
|
|
}
|
2021-08-18 04:02:23 +08:00
|
|
|
},
|
2019-12-16 03:04:31 +08:00
|
|
|
slidePos(modify, axis) {
|
|
|
|
var scope = this
|
|
|
|
var limit = scope.getResolution(axis);
|
|
|
|
|
|
|
|
Cube.selected.forEach(function(obj) {
|
|
|
|
if (Project.box_uv === false) {
|
2021-10-14 18:40:59 +08:00
|
|
|
scope.selected_faces.forEach(fkey => {
|
|
|
|
if (!obj.faces[fkey]) return;
|
|
|
|
let uvTag = obj.faces[fkey].uv;
|
|
|
|
var size = uvTag[axis + 2] - uvTag[axis]
|
|
|
|
|
|
|
|
var value = modify(uvTag[axis])
|
|
|
|
|
|
|
|
value = limitNumber(value, 0, limit)
|
|
|
|
value = limitNumber(value + size, 0, limit) - size
|
|
|
|
|
|
|
|
uvTag[axis] = value
|
|
|
|
uvTag[axis+2] = value + size
|
|
|
|
})
|
2019-12-16 03:04:31 +08:00
|
|
|
} else {
|
2020-05-31 21:54:04 +08:00
|
|
|
let minimum = 0;
|
2019-12-16 03:04:31 +08:00
|
|
|
if (axis === 0) {
|
2020-05-31 21:54:04 +08:00
|
|
|
var size = (obj.size(0) + (obj.size(1) ? obj.size(2) : 0))*2
|
|
|
|
if (obj.size(1) == 0) minimum = -obj.size(2);
|
2019-12-16 03:04:31 +08:00
|
|
|
} else {
|
|
|
|
var size = obj.size(2) + obj.size(1)
|
2020-05-31 21:54:04 +08:00
|
|
|
if (obj.size(0) == 0) minimum = -obj.size(2);
|
2019-12-16 03:04:31 +08:00
|
|
|
}
|
|
|
|
var value = modify(obj.uv_offset[axis])
|
|
|
|
|
2020-05-31 21:54:04 +08:00
|
|
|
value = limitNumber(value, minimum, limit)
|
|
|
|
value = limitNumber(value + size, minimum, limit) - size
|
2019-12-16 03:04:31 +08:00
|
|
|
obj.uv_offset[axis] = value
|
|
|
|
}
|
2021-08-30 16:30:46 +08:00
|
|
|
obj.preview_controller.updateUV(obj);
|
|
|
|
})
|
|
|
|
Mesh.selected.forEach(mesh => {
|
|
|
|
let selected_vertices = Project.selected_vertices[mesh.uuid];
|
|
|
|
|
|
|
|
if (!selected_vertices) {
|
|
|
|
selected_vertices = [];
|
|
|
|
UVEditor.vue.selected_faces.forEach(fkey => {
|
|
|
|
if (mesh.faces[fkey]) {
|
|
|
|
selected_vertices.safePush(...mesh.faces[fkey].vertices);
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
UVEditor.vue.selected_faces.forEach(fkey => {
|
|
|
|
if (!mesh.faces[fkey]) return
|
|
|
|
selected_vertices.forEach(vkey => {
|
|
|
|
if (!mesh.faces[fkey].vertices.includes(vkey)) return;
|
|
|
|
mesh.faces[fkey].uv[vkey][axis] = modify(mesh.faces[fkey].uv[vkey][axis]);
|
|
|
|
})
|
|
|
|
})
|
2021-09-08 03:14:33 +08:00
|
|
|
mesh.preview_controller.updateUV(mesh);
|
2019-12-16 03:04:31 +08:00
|
|
|
})
|
2021-10-15 17:12:24 +08:00
|
|
|
this.displayTools()
|
2021-08-19 21:18:01 +08:00
|
|
|
this.vue.$forceUpdate()
|
2021-08-18 04:02:23 +08:00
|
|
|
},
|
2019-12-16 03:04:31 +08:00
|
|
|
slideSize(modify, axis) {
|
|
|
|
var scope = this
|
|
|
|
var limit = scope.getResolution(axis);
|
|
|
|
|
|
|
|
Cube.selected.forEach(function(obj) {
|
|
|
|
if (Project.box_uv === false) {
|
2021-10-14 18:40:59 +08:00
|
|
|
scope.selected_faces.forEach(fkey => {
|
|
|
|
if (!obj.faces[fkey]) return;
|
|
|
|
var uvTag = obj.faces[fkey].uv;
|
|
|
|
var difference = modify(uvTag[axis+2]-uvTag[axis]) + uvTag[axis];
|
|
|
|
uvTag[axis+2] = limitNumber(difference, 0, limit);
|
|
|
|
Canvas.updateUV(obj);
|
|
|
|
})
|
2019-12-16 03:04:31 +08:00
|
|
|
}
|
|
|
|
})
|
2021-10-02 23:06:29 +08:00
|
|
|
Mesh.selected.forEach(mesh => {
|
|
|
|
mesh.forAllFaces((face, fkey) => {
|
|
|
|
if (!this.selected_faces.includes(fkey)) return;
|
|
|
|
let rect = face.getBoundingRect();
|
|
|
|
let start = (axis ? rect.ay : rect.ax);
|
|
|
|
let size = (axis ? rect.y : rect.x);
|
|
|
|
let multiplier = modify(size) / size;
|
|
|
|
face.vertices.forEach(vkey => {
|
|
|
|
if (!face.uv[vkey]) return;
|
|
|
|
face.uv[vkey][axis] = (face.uv[vkey][axis] - start) * multiplier + start;
|
|
|
|
if (isNaN(face.uv[vkey][axis])) face.uv[vkey][axis] = start;
|
|
|
|
})
|
|
|
|
})
|
|
|
|
})
|
2021-10-15 17:12:24 +08:00
|
|
|
this.displayTools()
|
2019-12-16 03:04:31 +08:00
|
|
|
this.disableAutoUV()
|
2021-08-19 21:18:01 +08:00
|
|
|
this.vue.$forceUpdate()
|
2021-08-18 04:02:23 +08:00
|
|
|
},
|
2019-12-16 03:04:31 +08:00
|
|
|
getResolution(axis, texture) {
|
|
|
|
return axis ? Project.texture_height : Project.texture_width;
|
2021-08-18 04:02:23 +08:00
|
|
|
},
|
2019-12-16 03:04:31 +08:00
|
|
|
|
|
|
|
//Events
|
2021-08-19 21:18:01 +08:00
|
|
|
selectAll() {
|
|
|
|
let selected_before = this.vue.selected_faces.length;
|
|
|
|
this.vue.mappable_elements.forEach(element => {
|
|
|
|
for (let key in element.faces) {
|
|
|
|
this.vue.selected_faces.safePush(key);
|
|
|
|
}
|
|
|
|
})
|
|
|
|
if (selected_before == this.vue.selected_faces.length) {
|
|
|
|
this.vue.selected_faces.empty();
|
|
|
|
}
|
2021-10-15 17:12:24 +08:00
|
|
|
UVEditor.displayTools();
|
2021-08-19 21:18:01 +08:00
|
|
|
},
|
2021-09-18 04:32:53 +08:00
|
|
|
moveSelection(offset, event) {
|
|
|
|
Undo.initEdit({elements: UVEditor.getMappableElements()})
|
|
|
|
let step = canvasGridSize(event.shiftKey || Pressing.overrides.shift, event.ctrlOrCmd || Pressing.overrides.ctrl) / UVEditor.grid;
|
|
|
|
UVEditor.slidePos((old_val) => {
|
|
|
|
let sign = offset[offset[0] ? 0 : 1];
|
|
|
|
return old_val + step * sign;
|
|
|
|
}, offset[0] ? 0 : 1);
|
|
|
|
Undo.finishEdit('Move UV')
|
|
|
|
},
|
2019-12-16 03:04:31 +08:00
|
|
|
disableAutoUV() {
|
|
|
|
this.forCubes(obj => {
|
|
|
|
obj.autouv = 0
|
|
|
|
})
|
2021-08-18 04:02:23 +08:00
|
|
|
},
|
2019-12-16 03:04:31 +08:00
|
|
|
toggleUV() {
|
2021-10-15 17:12:24 +08:00
|
|
|
var state = Cube.selected[0].faces[this.selected_faces[0]].enabled === false
|
2019-12-16 03:04:31 +08:00
|
|
|
this.forCubes(obj => {
|
2021-10-15 17:12:24 +08:00
|
|
|
this.selected_faces.forEach(face => {
|
|
|
|
obj.faces[face].enabled = state;
|
|
|
|
})
|
2019-12-16 03:04:31 +08:00
|
|
|
})
|
2021-08-18 04:02:23 +08:00
|
|
|
},
|
2019-12-16 03:04:31 +08:00
|
|
|
maximize(event) {
|
|
|
|
var scope = this;
|
|
|
|
this.forCubes(obj => {
|
2021-08-26 04:00:08 +08:00
|
|
|
scope.getFaces(obj, event).forEach(function(side) {
|
2019-12-16 03:04:31 +08:00
|
|
|
obj.faces[side].uv = [0, 0, scope.getResolution(0, obj.faces[side]), scope.getResolution(1, obj.faces[side])]
|
|
|
|
})
|
2021-10-09 21:44:11 +08:00
|
|
|
obj.autouv = 0;
|
2019-12-16 03:04:31 +08:00
|
|
|
Canvas.updateUV(obj)
|
|
|
|
})
|
|
|
|
this.message('uv_editor.maximized')
|
|
|
|
this.loadData()
|
2021-08-18 04:02:23 +08:00
|
|
|
},
|
2019-12-16 03:04:31 +08:00
|
|
|
turnMapping(event) {
|
|
|
|
var scope = this;
|
|
|
|
this.forCubes(obj => {
|
2021-08-26 04:00:08 +08:00
|
|
|
scope.getFaces(obj, event).forEach(function(side) {
|
2019-12-16 03:04:31 +08:00
|
|
|
var uv = obj.faces[side].uv_size;
|
|
|
|
obj.faces[side].uv_size = [uv[1], uv[0]];
|
2021-09-21 00:45:02 +08:00
|
|
|
if (uv[0] < 0) {
|
|
|
|
obj.faces[side].uv[0] += uv[0];
|
|
|
|
obj.faces[side].uv[2] += uv[0];
|
|
|
|
obj.faces[side].uv[1] -= uv[0];
|
|
|
|
obj.faces[side].uv[3] -= uv[0];
|
|
|
|
}
|
|
|
|
if (uv[1] < 0) {
|
|
|
|
obj.faces[side].uv[1] += uv[1];
|
|
|
|
obj.faces[side].uv[3] += uv[1];
|
|
|
|
obj.faces[side].uv[0] -= uv[1];
|
|
|
|
obj.faces[side].uv[2] -= uv[1];
|
|
|
|
}
|
2021-09-25 20:47:41 +08:00
|
|
|
let overlap_px = Math.clamp(Math.max(obj.faces[side].uv[0], obj.faces[side].uv[2]) - Project.texture_width, 0, Infinity);
|
|
|
|
obj.faces[side].uv[0] -= overlap_px;
|
|
|
|
obj.faces[side].uv[2] -= overlap_px;
|
|
|
|
let overlap_py = Math.clamp(Math.max(obj.faces[side].uv[1], obj.faces[side].uv[3]) - Project.texture_height, 0, Infinity);
|
|
|
|
obj.faces[side].uv[1] -= overlap_py;
|
|
|
|
obj.faces[side].uv[3] -= overlap_py;
|
2019-12-16 03:04:31 +08:00
|
|
|
})
|
|
|
|
obj.autouv = 0;
|
|
|
|
Canvas.updateUV(obj);
|
|
|
|
})
|
|
|
|
this.message('uv_editor.turned');
|
|
|
|
this.loadData();
|
2021-08-18 04:02:23 +08:00
|
|
|
},
|
2022-01-11 04:06:30 +08:00
|
|
|
setAutoSize(event, silent, face_keys) {
|
2021-09-15 06:26:07 +08:00
|
|
|
let vec1 = new THREE.Vector3(),
|
|
|
|
vec2 = new THREE.Vector3(),
|
|
|
|
vec3 = new THREE.Vector3(),
|
|
|
|
vec4 = new THREE.Vector3(),
|
|
|
|
plane = new THREE.Plane();
|
|
|
|
|
|
|
|
this.getMappableElements().forEach(obj => {
|
2021-09-15 23:50:46 +08:00
|
|
|
var top2, left2;
|
2022-01-11 04:06:30 +08:00
|
|
|
let faces = face_keys || this.getFaces(obj, event);
|
2021-09-15 06:26:07 +08:00
|
|
|
if (obj instanceof Cube) {
|
2022-01-11 04:06:30 +08:00
|
|
|
faces.forEach(function(side) {
|
2021-09-15 06:26:07 +08:00
|
|
|
let face = obj.faces[side];
|
|
|
|
let mirror_x = face.uv[0] > face.uv[2];
|
|
|
|
let mirror_y = face.uv[1] > face.uv[3];
|
|
|
|
face.uv[0] = Math.min(face.uv[0], face.uv[2]);
|
|
|
|
face.uv[1] = Math.min(face.uv[1], face.uv[3]);
|
|
|
|
if (side == 'north' || side == 'south') {
|
|
|
|
left2 = limitNumber(obj.size('0'), 0, Project.texture_width)
|
|
|
|
top2 = limitNumber(obj.size('1'), 0, Project.texture_height)
|
|
|
|
} else if (side == 'east' || side == 'west') {
|
|
|
|
left2 = limitNumber(obj.size('2'), 0, Project.texture_width)
|
|
|
|
top2 = limitNumber(obj.size('1'), 0, Project.texture_height)
|
|
|
|
} else if (side == 'up' || side == 'down') {
|
|
|
|
left2 = limitNumber(obj.size('0'), 0, Project.texture_width)
|
|
|
|
top2 = limitNumber(obj.size('2'), 0, Project.texture_height)
|
|
|
|
}
|
|
|
|
if (face.rotation % 180) {
|
|
|
|
[left2, top2] = [top2, left2];
|
|
|
|
}
|
2021-10-08 17:28:41 +08:00
|
|
|
left2 *= UVEditor.getResolution(0, face) / Project.texture_width;
|
|
|
|
top2 *= UVEditor.getResolution(1, face) / Project.texture_height;
|
2021-09-15 06:26:07 +08:00
|
|
|
face.uv_size = [left2, top2];
|
|
|
|
if (mirror_x) [face.uv[0], face.uv[2]] = [face.uv[2], face.uv[0]];
|
|
|
|
if (mirror_y) [face.uv[1], face.uv[3]] = [face.uv[3], face.uv[1]];
|
|
|
|
})
|
|
|
|
obj.autouv = 0
|
|
|
|
|
|
|
|
} else if (obj instanceof Mesh) {
|
2022-01-11 04:06:30 +08:00
|
|
|
faces.forEach(fkey => {
|
2021-09-15 06:26:07 +08:00
|
|
|
let face = obj.faces[fkey];
|
|
|
|
let vertex_uvs = {};
|
|
|
|
let uv_center = [0, 0];
|
|
|
|
let new_uv_center = [0, 0];
|
|
|
|
let normal_vec = vec1.fromArray(face.getNormal(true));
|
|
|
|
plane.setFromNormalAndCoplanarPoint(
|
|
|
|
normal_vec,
|
|
|
|
vec2.fromArray(obj.vertices[face.vertices[0]])
|
|
|
|
)
|
2021-11-28 19:21:58 +08:00
|
|
|
let rot = cameraTargetToRotation([0, 0, 0], normal_vec.toArray());
|
|
|
|
let e = new THREE.Euler(Math.degToRad(-rot[1] - 90), Math.degToRad(rot[0]), 0);
|
2021-09-15 06:26:07 +08:00
|
|
|
face.vertices.forEach(vkey => {
|
|
|
|
let coplanar_pos = plane.projectPoint(vec3.fromArray(obj.vertices[vkey]), vec4.set(0, 0, 0));
|
2021-11-28 19:21:58 +08:00
|
|
|
coplanar_pos.applyEuler(e);
|
2021-09-15 06:26:07 +08:00
|
|
|
vertex_uvs[vkey] = [
|
|
|
|
Math.roundTo(coplanar_pos.x, 4),
|
|
|
|
Math.roundTo(coplanar_pos.z, 4),
|
|
|
|
]
|
|
|
|
})
|
|
|
|
// Rotate UV to match corners
|
|
|
|
let rotation_angles = {};
|
|
|
|
let precise_rotation_angle = {};
|
|
|
|
let vertices = face.getSortedVertices();
|
|
|
|
vertices.forEach((vkey, i) => {
|
|
|
|
let vkey2 = vertices[i+1] || vertices[0];
|
|
|
|
let rot = Math.atan2(
|
|
|
|
vertex_uvs[vkey2][0] - vertex_uvs[vkey][0],
|
|
|
|
vertex_uvs[vkey2][1] - vertex_uvs[vkey][1],
|
|
|
|
)
|
|
|
|
let snap = 2;
|
|
|
|
rot = (Math.radToDeg(rot) + 360) % 90;
|
|
|
|
let rounded = Math.round(rot / snap) * snap;
|
|
|
|
if (rotation_angles[rounded]) {
|
|
|
|
rotation_angles[rounded]++;
|
|
|
|
} else {
|
|
|
|
rotation_angles[rounded] = 1;
|
|
|
|
precise_rotation_angle[rounded] = rot;
|
|
|
|
}
|
|
|
|
})
|
|
|
|
let angles = Object.keys(rotation_angles).map(k => parseInt(k));
|
|
|
|
angles.sort((a, b) => {
|
|
|
|
let diff = rotation_angles[b] - rotation_angles[a];
|
|
|
|
if (diff) {
|
|
|
|
return diff;
|
|
|
|
} else {
|
|
|
|
return a < b ? -1 : 1;
|
|
|
|
}
|
|
|
|
})
|
|
|
|
let angle = Math.degToRad(precise_rotation_angle[angles[0]]);
|
|
|
|
let s = Math.sin(angle);
|
|
|
|
let c = Math.cos(angle);
|
|
|
|
for (let vkey in vertex_uvs) {
|
|
|
|
let point = vertex_uvs[vkey].slice();
|
|
|
|
vertex_uvs[vkey][0] = point[0] * c - point[1] * s;
|
|
|
|
vertex_uvs[vkey][1] = point[0] * s + point[1] * c;
|
|
|
|
}
|
2019-12-16 03:04:31 +08:00
|
|
|
|
2021-09-15 06:26:07 +08:00
|
|
|
// Find position on UV map
|
|
|
|
let pmin_x = Infinity, pmin_y = Infinity;
|
|
|
|
face.vertices.forEach(vkey => {
|
|
|
|
pmin_x = Math.min(pmin_x, vertex_uvs[vkey][0]);
|
|
|
|
pmin_y = Math.min(pmin_y, vertex_uvs[vkey][1]);
|
|
|
|
})
|
|
|
|
face.vertices.forEach(vkey => {
|
|
|
|
uv_center[0] += face.uv[vkey] ? face.uv[vkey][0] : 0;
|
|
|
|
uv_center[1] += face.uv[vkey] ? face.uv[vkey][1] : 0;
|
|
|
|
new_uv_center[0] += vertex_uvs[vkey][0];
|
|
|
|
new_uv_center[1] += vertex_uvs[vkey][1];
|
|
|
|
})
|
|
|
|
uv_center[0] = Math.round((uv_center[0] - new_uv_center[0]) / face.vertices.length);
|
|
|
|
uv_center[1] = Math.round((uv_center[1] - new_uv_center[1]) / face.vertices.length);
|
|
|
|
|
|
|
|
let min_x = Infinity, min_y = Infinity, max_x = 0, max_y = 0;
|
|
|
|
for (let vkey in vertex_uvs) {
|
|
|
|
vertex_uvs[vkey][0] = vertex_uvs[vkey][0] - (pmin_x % 1) + uv_center[0],
|
|
|
|
vertex_uvs[vkey][1] = vertex_uvs[vkey][1] - (pmin_y % 1) + uv_center[1],
|
|
|
|
min_x = Math.min(min_x, vertex_uvs[vkey][0]);
|
|
|
|
min_y = Math.min(min_y, vertex_uvs[vkey][1]);
|
|
|
|
max_x = Math.max(max_x, vertex_uvs[vkey][0]);
|
|
|
|
max_y = Math.max(max_y, vertex_uvs[vkey][1]);
|
|
|
|
}
|
|
|
|
let offset = [
|
|
|
|
min_x < 0 ? -min_x : (max_x > Project.texture_width ? Math.floor(Project.texture_width - max_x) : 0),
|
|
|
|
min_y < 0 ? -min_y : (max_y > Project.texture_height ? Math.floor(Project.texture_height - max_y) : 0),
|
|
|
|
];
|
|
|
|
face.vertices.forEach(vkey => {
|
|
|
|
face.uv[vkey] = [
|
|
|
|
vertex_uvs[vkey][0] + offset[0],
|
|
|
|
vertex_uvs[vkey][1] + offset[1],
|
|
|
|
]
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
obj.preview_controller.updateUV(obj);
|
2019-12-16 03:04:31 +08:00
|
|
|
})
|
2021-12-11 05:54:05 +08:00
|
|
|
if (!silent) this.message('uv_editor.autouv');
|
|
|
|
this.loadData();
|
2021-08-18 04:02:23 +08:00
|
|
|
},
|
2019-12-16 03:04:31 +08:00
|
|
|
setRelativeAutoSize(event) {
|
|
|
|
var scope = this;
|
|
|
|
this.forCubes(obj => {
|
2021-08-26 04:00:08 +08:00
|
|
|
scope.getFaces(obj, event).forEach(function(side) {
|
2019-12-16 03:04:31 +08:00
|
|
|
var uv = obj.faces[side].uv,
|
|
|
|
ru = scope.getResolution(0, obj.faces[side]),
|
|
|
|
rv = scope.getResolution(1, obj.faces[side]);
|
|
|
|
switch (side) {
|
|
|
|
case 'north':
|
|
|
|
uv = [
|
|
|
|
ru - obj.to[0],
|
|
|
|
rv - obj.to[1],
|
|
|
|
ru - obj.from[0],
|
|
|
|
rv - obj.from[1],
|
|
|
|
];
|
|
|
|
break;
|
|
|
|
case 'south':
|
|
|
|
uv = [
|
|
|
|
obj.from[0],
|
|
|
|
rv - obj.to[1],
|
|
|
|
obj.to[0],
|
|
|
|
rv - obj.from[1],
|
|
|
|
];
|
|
|
|
break;
|
|
|
|
case 'west':
|
|
|
|
uv = [
|
|
|
|
obj.from[2],
|
|
|
|
rv - obj.to[1],
|
|
|
|
obj.to[2],
|
|
|
|
rv - obj.from[1],
|
|
|
|
];
|
|
|
|
break;
|
|
|
|
case 'east':
|
|
|
|
uv = [
|
|
|
|
ru - obj.to[2],
|
|
|
|
rv - obj.to[1],
|
|
|
|
ru - obj.from[2],
|
|
|
|
rv - obj.from[1],
|
|
|
|
];
|
|
|
|
break;
|
|
|
|
case 'up':
|
|
|
|
uv = [
|
|
|
|
obj.from[0],
|
|
|
|
obj.from[2],
|
|
|
|
obj.to[0],
|
|
|
|
obj.to[2],
|
|
|
|
];
|
|
|
|
break;
|
|
|
|
case 'down':
|
|
|
|
uv = [
|
|
|
|
obj.from[0],
|
|
|
|
rv - obj.to[2],
|
|
|
|
obj.to[0],
|
|
|
|
rv - obj.from[2],
|
|
|
|
];
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
uv.forEach(function(s, uvi) {
|
|
|
|
uv[uvi] = limitNumber(s, 0, uvi%2 ? rv : ru);
|
|
|
|
})
|
|
|
|
obj.faces[side].uv = uv
|
|
|
|
})
|
|
|
|
obj.autouv = 0
|
|
|
|
Canvas.updateUV(obj)
|
|
|
|
})
|
|
|
|
this.message('uv_editor.autouv')
|
|
|
|
this.loadData()
|
2021-08-18 04:02:23 +08:00
|
|
|
},
|
2019-12-16 03:04:31 +08:00
|
|
|
mirrorX(event) {
|
|
|
|
var scope = this;
|
2021-09-22 03:13:24 +08:00
|
|
|
this.forElements(obj => {
|
2021-08-26 04:00:08 +08:00
|
|
|
scope.getFaces(obj, event).forEach(function(side) {
|
2021-09-22 03:13:24 +08:00
|
|
|
if (obj instanceof Cube) {
|
|
|
|
var proxy = obj.faces[side].uv[0]
|
|
|
|
obj.faces[side].uv[0] = obj.faces[side].uv[2]
|
|
|
|
obj.faces[side].uv[2] = proxy
|
|
|
|
} else if (obj instanceof Mesh) {
|
|
|
|
let center = 0;
|
|
|
|
let count = 0;
|
|
|
|
obj.faces[side].vertices.forEach(vkey => {
|
|
|
|
center += obj.faces[side].uv[vkey][0];
|
|
|
|
count++;
|
|
|
|
})
|
|
|
|
center /= count;
|
|
|
|
obj.faces[side].vertices.forEach(vkey => {
|
|
|
|
obj.faces[side].uv[vkey][0] = center*2 - obj.faces[side].uv[vkey][0];
|
|
|
|
})
|
|
|
|
}
|
2019-12-16 03:04:31 +08:00
|
|
|
})
|
2021-09-22 03:13:24 +08:00
|
|
|
if (obj.autouv) obj.autouv = 0
|
|
|
|
obj.preview_controller.updateUV(obj);
|
2019-12-16 03:04:31 +08:00
|
|
|
})
|
|
|
|
this.message('uv_editor.mirrored')
|
|
|
|
this.loadData()
|
2021-08-18 04:02:23 +08:00
|
|
|
},
|
2019-12-16 03:04:31 +08:00
|
|
|
mirrorY(event) {
|
|
|
|
var scope = this;
|
2021-09-22 03:13:24 +08:00
|
|
|
this.forElements(obj => {
|
2021-08-26 04:00:08 +08:00
|
|
|
scope.getFaces(obj, event).forEach(function(side) {
|
2021-09-22 03:13:24 +08:00
|
|
|
if (obj instanceof Cube) {
|
|
|
|
var proxy = obj.faces[side].uv[1]
|
|
|
|
obj.faces[side].uv[1] = obj.faces[side].uv[3]
|
|
|
|
obj.faces[side].uv[3] = proxy
|
|
|
|
} else if (obj instanceof Mesh) {
|
|
|
|
let center = 0;
|
|
|
|
let count = 0;
|
|
|
|
obj.faces[side].vertices.forEach(vkey => {
|
|
|
|
center += obj.faces[side].uv[vkey][1];
|
|
|
|
count++;
|
|
|
|
})
|
|
|
|
center /= count;
|
|
|
|
obj.faces[side].vertices.forEach(vkey => {
|
|
|
|
obj.faces[side].uv[vkey][1] = center*2 - obj.faces[side].uv[vkey][1];
|
|
|
|
})
|
|
|
|
}
|
2019-12-16 03:04:31 +08:00
|
|
|
})
|
2021-10-14 18:40:59 +08:00
|
|
|
if (obj.autouv) obj.autouv = 0;
|
2021-09-22 03:13:24 +08:00
|
|
|
obj.preview_controller.updateUV(obj);
|
2019-12-16 03:04:31 +08:00
|
|
|
})
|
|
|
|
this.message('uv_editor.mirrored')
|
|
|
|
this.loadData()
|
2021-08-18 04:02:23 +08:00
|
|
|
},
|
2021-09-21 00:45:02 +08:00
|
|
|
applyAll() {
|
2019-12-16 03:04:31 +08:00
|
|
|
this.forCubes(obj => {
|
2021-09-21 00:45:02 +08:00
|
|
|
UVEditor.cube_faces.forEach(side => {
|
|
|
|
obj.faces[side].extend(obj.faces[this.selected_faces[0]])
|
2019-12-16 03:04:31 +08:00
|
|
|
})
|
|
|
|
obj.autouv = 0
|
|
|
|
})
|
|
|
|
Canvas.updateSelectedFaces()
|
|
|
|
this.message('uv_editor.to_all')
|
|
|
|
this.loadData()
|
2021-08-18 04:02:23 +08:00
|
|
|
},
|
2019-12-16 03:04:31 +08:00
|
|
|
clear(event) {
|
|
|
|
var scope = this;
|
|
|
|
Undo.initEdit({elements: Cube.selected, uv_only: true})
|
|
|
|
this.forCubes(obj => {
|
2021-08-26 04:00:08 +08:00
|
|
|
scope.getFaces(obj, event).forEach(function(side) {
|
2019-12-16 03:04:31 +08:00
|
|
|
obj.faces[side].uv = [0, 0, 0, 0]
|
|
|
|
obj.faces[side].texture = null;
|
|
|
|
})
|
2021-07-30 00:17:26 +08:00
|
|
|
obj.preview_controller.updateFaces(obj);
|
2019-12-16 03:04:31 +08:00
|
|
|
})
|
|
|
|
this.loadData()
|
|
|
|
this.message('uv_editor.transparent')
|
2021-06-06 15:28:22 +08:00
|
|
|
Undo.finishEdit('Remove face')
|
2019-12-16 03:04:31 +08:00
|
|
|
Canvas.updateSelectedFaces()
|
2021-08-18 04:02:23 +08:00
|
|
|
},
|
2019-12-16 03:04:31 +08:00
|
|
|
switchCullface(event) {
|
|
|
|
var scope = this;
|
|
|
|
Undo.initEdit({elements: Cube.selected, uv_only: true})
|
|
|
|
var val = BarItems.cullface.get()
|
|
|
|
if (val === 'off') val = false
|
|
|
|
this.forCubes(obj => {
|
2021-10-15 17:12:24 +08:00
|
|
|
this.selected_faces.forEach(face => {
|
|
|
|
obj.faces[face].cullface = val || '';
|
|
|
|
})
|
2019-12-16 03:04:31 +08:00
|
|
|
})
|
|
|
|
if (val) {
|
|
|
|
this.message('uv_editor.cullface_on')
|
|
|
|
} else {
|
|
|
|
this.message('uv_editor.cullface_off')
|
|
|
|
}
|
2021-06-06 15:28:22 +08:00
|
|
|
Undo.finishEdit('Toggle cullface')
|
2021-08-18 04:02:23 +08:00
|
|
|
},
|
2019-12-16 03:04:31 +08:00
|
|
|
switchTint(event) {
|
|
|
|
var scope = this;
|
2021-10-15 17:12:24 +08:00
|
|
|
var val = Cube.selected[0].faces[scope.selected_faces[0]].tint === -1 ? 0 : -1;
|
2019-12-16 03:04:31 +08:00
|
|
|
|
2020-03-05 03:56:17 +08:00
|
|
|
if (event === 0 || event === false) val = event
|
2019-12-16 03:04:31 +08:00
|
|
|
this.forCubes(obj => {
|
2021-10-15 17:12:24 +08:00
|
|
|
this.selected_faces.forEach(face => {
|
|
|
|
obj.faces[face].tint = val;
|
|
|
|
})
|
2019-12-16 03:04:31 +08:00
|
|
|
})
|
2020-03-05 03:56:17 +08:00
|
|
|
if (val !== -1) {
|
2019-12-16 03:04:31 +08:00
|
|
|
this.message('uv_editor.tint_on')
|
|
|
|
} else {
|
|
|
|
this.message('uv_editor.tint_off')
|
|
|
|
}
|
|
|
|
this.displayTools()
|
2021-08-18 04:02:23 +08:00
|
|
|
},
|
2020-03-05 03:56:17 +08:00
|
|
|
setTint(event, val) {
|
|
|
|
this.forCubes(obj => {
|
2021-10-15 17:12:24 +08:00
|
|
|
this.selected_faces.forEach(face => {
|
|
|
|
obj.faces[face].tint = val;
|
|
|
|
})
|
2020-03-05 03:56:17 +08:00
|
|
|
})
|
|
|
|
this.displayTools()
|
2021-08-18 04:02:23 +08:00
|
|
|
},
|
2021-10-02 23:06:29 +08:00
|
|
|
rotate(mesh_angle) {
|
2021-10-09 21:44:11 +08:00
|
|
|
var value = parseInt(BarItems.uv_rotation.get());
|
|
|
|
if (Cube.selected[0] && Cube.selected[0].faces[this.selected_faces] && Math.abs(Cube.selected[0].faces[this.selected_faces].rotation - value) % 180 == 90) {
|
|
|
|
UVEditor.turnMapping();
|
|
|
|
}
|
2019-12-16 03:04:31 +08:00
|
|
|
this.forCubes(obj => {
|
2021-10-09 21:44:11 +08:00
|
|
|
this.selected_faces.forEach(face => {
|
|
|
|
obj.faces[face].rotation = value;
|
|
|
|
})
|
|
|
|
Canvas.updateUV(obj);
|
2019-12-16 03:04:31 +08:00
|
|
|
})
|
2021-10-02 23:06:29 +08:00
|
|
|
Mesh.selected.forEach(mesh => {
|
|
|
|
mesh.forAllFaces((face, fkey) => {
|
|
|
|
if (!UVEditor.selected_faces.includes(fkey)) return;
|
|
|
|
if (face.vertices.length < 3) return;
|
|
|
|
let center = [0, 0];
|
|
|
|
face.vertices.forEach(vkey => {
|
|
|
|
if (!face.uv[vkey]) return;
|
|
|
|
center[0] += face.uv[vkey][0];
|
|
|
|
center[1] += face.uv[vkey][1];
|
|
|
|
})
|
|
|
|
center[0] /= face.vertices.length;
|
|
|
|
center[1] /= face.vertices.length;
|
|
|
|
|
|
|
|
face.vertices.forEach(vkey => {
|
|
|
|
if (!face.uv[vkey]) return;
|
|
|
|
let sin = Math.sin(Math.degToRad(mesh_angle));
|
|
|
|
let cos = Math.cos(Math.degToRad(mesh_angle));
|
|
|
|
face.uv[vkey][0] -= center[0];
|
|
|
|
face.uv[vkey][1] -= center[1];
|
|
|
|
let a = (face.uv[vkey][0] * cos - face.uv[vkey][1] * sin);
|
|
|
|
let b = (face.uv[vkey][0] * sin + face.uv[vkey][1] * cos);
|
|
|
|
face.uv[vkey][0] = Math.clamp(a + center[0], 0, Project.texture_width);
|
|
|
|
face.uv[vkey][1] = Math.clamp(b + center[1], 0, Project.texture_height);
|
|
|
|
})
|
|
|
|
})
|
2022-01-11 04:06:30 +08:00
|
|
|
Mesh.preview_controller.updateUV(mesh);
|
2021-10-02 23:06:29 +08:00
|
|
|
})
|
|
|
|
this.loadData();
|
2019-12-16 03:04:31 +08:00
|
|
|
this.message('uv_editor.rotated')
|
2021-08-18 04:02:23 +08:00
|
|
|
},
|
2019-12-16 03:04:31 +08:00
|
|
|
setRotation(value) {
|
|
|
|
var scope = this;
|
|
|
|
value = parseInt(value)
|
|
|
|
this.forCubes(obj => {
|
2021-10-15 17:12:24 +08:00
|
|
|
this.selected_faces.forEach(face => {
|
|
|
|
obj.faces[face].rotation = value;
|
|
|
|
})
|
2019-12-16 03:04:31 +08:00
|
|
|
Canvas.updateUV(obj)
|
|
|
|
})
|
|
|
|
this.loadData()
|
|
|
|
this.message('uv_editor.rotated')
|
2021-08-18 04:02:23 +08:00
|
|
|
},
|
2019-12-16 03:04:31 +08:00
|
|
|
selectGridSize(event) {
|
2021-08-18 04:02:23 +08:00
|
|
|
},
|
2019-12-16 03:04:31 +08:00
|
|
|
autoCullface(event) {
|
|
|
|
var scope = this;
|
|
|
|
this.forCubes(obj => {
|
2021-08-26 04:00:08 +08:00
|
|
|
scope.getFaces(obj, event).forEach(function(side) {
|
2019-12-16 03:04:31 +08:00
|
|
|
obj.faces[side].cullface = side
|
|
|
|
})
|
|
|
|
})
|
|
|
|
this.loadData()
|
|
|
|
this.message('uv_editor.auto_cull')
|
2021-08-18 04:02:23 +08:00
|
|
|
},
|
2019-12-16 03:04:31 +08:00
|
|
|
copy(event) {
|
2021-08-31 06:40:23 +08:00
|
|
|
let elements = this.getMappableElements();
|
|
|
|
if (!elements.length) return;
|
2019-12-16 03:04:31 +08:00
|
|
|
|
2021-08-18 04:02:23 +08:00
|
|
|
UVEditor.clipboard = []
|
2019-12-16 03:04:31 +08:00
|
|
|
|
2021-08-31 06:40:23 +08:00
|
|
|
if (Project.box_uv && Cube.selected[0]) {
|
2019-12-16 03:04:31 +08:00
|
|
|
var new_tag = {
|
|
|
|
uv: Cube.selected[0].uv_offset
|
|
|
|
}
|
2021-08-18 04:02:23 +08:00
|
|
|
UVEditor.clipboard.push(new_tag)
|
2019-12-16 03:04:31 +08:00
|
|
|
this.message('uv_editor.copied')
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-08-31 06:40:23 +08:00
|
|
|
function addToClipboard(key) {
|
2022-01-15 02:25:27 +08:00
|
|
|
let element = elements.find(el => el.faces[key]);
|
|
|
|
if (!element) return;
|
|
|
|
var tag = element.faces[key];
|
2021-08-31 06:40:23 +08:00
|
|
|
var new_face;
|
2022-01-15 02:25:27 +08:00
|
|
|
if (element instanceof Mesh) {
|
2021-08-31 06:40:23 +08:00
|
|
|
new_face = new MeshFace(null, tag);
|
2021-10-14 22:39:02 +08:00
|
|
|
new_face.vertices = tag.getSortedVertices();
|
2021-08-31 06:40:23 +08:00
|
|
|
new_face.direction = key;
|
|
|
|
} else {
|
|
|
|
new_face = new CubeFace(key, tag);
|
2019-12-16 03:04:31 +08:00
|
|
|
}
|
2021-08-31 06:40:23 +08:00
|
|
|
UVEditor.clipboard.push(new_face);
|
2019-12-16 03:04:31 +08:00
|
|
|
}
|
|
|
|
if (event.shiftKey) {
|
2021-08-31 06:40:23 +08:00
|
|
|
for (let key in elements[0].faces) {
|
|
|
|
addToClipboard(key)
|
|
|
|
}
|
2019-12-16 03:04:31 +08:00
|
|
|
} else {
|
2021-08-31 06:40:23 +08:00
|
|
|
UVEditor.vue.selected_faces.forEach(key => {
|
|
|
|
addToClipboard(key);
|
|
|
|
})
|
2019-12-16 03:04:31 +08:00
|
|
|
}
|
2021-08-18 04:02:23 +08:00
|
|
|
this.message('uv_editor.copied_x', [UVEditor.clipboard.length])
|
|
|
|
},
|
2019-12-16 03:04:31 +08:00
|
|
|
paste(event) {
|
2021-08-31 06:40:23 +08:00
|
|
|
let elements = UVEditor.getMappableElements();
|
|
|
|
if (UVEditor.clipboard === null || elements.length === 0) return;
|
2019-12-16 03:04:31 +08:00
|
|
|
|
|
|
|
|
2021-08-31 06:40:23 +08:00
|
|
|
|
|
|
|
Undo.initEdit({elements, uv_only: true})
|
|
|
|
if (Project.box_uv && UVEditor.clipboard[0].uv instanceof Array) {
|
|
|
|
Cube.selected.forEach(function(el) {
|
|
|
|
el.uv_offset = UVEditor.clipboard[0].uv.slice()
|
|
|
|
el.preview_controller.updateUV(el);
|
2019-12-16 03:04:31 +08:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2021-08-31 06:40:23 +08:00
|
|
|
function mergeFace(element, key, tag) {
|
|
|
|
if (!element.faces[key]) return;
|
|
|
|
let face = element.faces[key];
|
|
|
|
if (element instanceof Mesh) {
|
|
|
|
|
|
|
|
let uv_points = [];
|
|
|
|
tag.vertices.forEach(vkey => {
|
|
|
|
uv_points.push(tag.uv[vkey]);
|
|
|
|
})
|
2021-10-14 22:39:02 +08:00
|
|
|
face.getSortedVertices().forEach((vkey, i) => {
|
2021-08-31 06:40:23 +08:00
|
|
|
if (uv_points[i]) face.uv[vkey].replace(uv_points[i]);
|
2019-12-16 03:04:31 +08:00
|
|
|
})
|
|
|
|
} else {
|
2021-08-31 06:40:23 +08:00
|
|
|
face.extend(tag);
|
2019-12-16 03:04:31 +08:00
|
|
|
}
|
|
|
|
}
|
2021-08-31 06:40:23 +08:00
|
|
|
|
|
|
|
let shifting = (event && event.shiftKey) || Pressing.overrides.shift;
|
|
|
|
if (shifting || UVEditor.clipboard.length === 1) {
|
|
|
|
let tag = UVEditor.clipboard[0];
|
|
|
|
elements.forEach(el => {
|
|
|
|
if (Project.box_uv && el instanceof Cube) return;
|
|
|
|
if ((el instanceof Cube && tag instanceof CubeFace) || (el instanceof Mesh && tag instanceof MeshFace)) {
|
|
|
|
for (let key in el.faces) {
|
|
|
|
if (shifting || UVEditor.vue.selected_faces.includes(key)) {
|
|
|
|
mergeFace(el, key, tag);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
el.preview_controller.updateUV(el);
|
|
|
|
el.preview_controller.updateFaces(el);
|
|
|
|
}
|
|
|
|
})
|
|
|
|
} else {
|
|
|
|
UVEditor.clipboard.forEach(tag => {
|
|
|
|
elements.forEach(el => {
|
|
|
|
if (Project.box_uv && el instanceof Cube)
|
|
|
|
if ((el instanceof Cube && tag instanceof CubeFace) || (el instanceof Mesh && tag instanceof MeshFace)) {
|
|
|
|
let key = tag.direction;
|
|
|
|
if (el.faces[key]) {
|
|
|
|
mergeFace(el, key, tag);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
})
|
|
|
|
elements.forEach(el => {
|
|
|
|
el.preview_controller.updateUV(el);
|
|
|
|
el.preview_controller.updateFaces(el);
|
|
|
|
})
|
|
|
|
}
|
2019-12-16 03:04:31 +08:00
|
|
|
this.loadData()
|
|
|
|
Canvas.updateSelectedFaces()
|
|
|
|
this.message('uv_editor.pasted')
|
2021-06-06 15:28:22 +08:00
|
|
|
Undo.finishEdit('Paste UV')
|
2021-08-18 04:02:23 +08:00
|
|
|
},
|
2019-12-16 03:04:31 +08:00
|
|
|
reset(event) {
|
|
|
|
var scope = this;
|
|
|
|
this.forCubes(obj => {
|
2021-08-26 04:00:08 +08:00
|
|
|
scope.getFaces(obj, event).forEach(function(side) {
|
2019-12-16 03:04:31 +08:00
|
|
|
obj.faces[side].reset()
|
|
|
|
})
|
2021-07-30 00:17:26 +08:00
|
|
|
obj.preview_controller.updateFaces(obj);
|
2021-09-10 17:51:34 +08:00
|
|
|
if (Project.view_mode === 'textured') {
|
2021-07-30 00:17:26 +08:00
|
|
|
obj.preview_controller.updateUV(obj);
|
|
|
|
}
|
2019-12-16 03:04:31 +08:00
|
|
|
})
|
|
|
|
this.loadData()
|
|
|
|
this.message('uv_editor.reset')
|
2021-08-18 04:02:23 +08:00
|
|
|
},
|
|
|
|
|
|
|
|
// Dialog
|
|
|
|
clipboard: null,
|
|
|
|
cube_faces: ['north', 'south', 'west', 'east', 'up', 'down'],
|
2021-08-26 04:00:08 +08:00
|
|
|
forSelection(cb, event, ...args) {
|
2021-10-15 17:12:24 +08:00
|
|
|
UVEditor[cb](event, ...args);
|
2021-08-18 04:02:23 +08:00
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
menu: new Menu([
|
2021-10-03 01:51:12 +08:00
|
|
|
{name: 'menu.view.zoom', id: 'zoom', icon: 'search', children: [
|
2019-12-16 03:04:31 +08:00
|
|
|
'zoom_in',
|
|
|
|
'zoom_out',
|
|
|
|
'zoom_reset'
|
|
|
|
]},
|
2021-10-21 04:55:54 +08:00
|
|
|
{name: 'menu.uv.display_uv', id: 'display_uv', icon: 'visibility', children: () => {
|
2021-10-03 01:51:12 +08:00
|
|
|
let options = ['selected_faces', 'selected_elements', 'all_elements'];
|
|
|
|
return options.map(option => {return {
|
|
|
|
id: option,
|
|
|
|
name: `menu.uv.display_uv.${option}`,
|
|
|
|
icon: UVEditor.vue.display_uv == option ? 'radio_button_checked' : 'radio_button_unchecked',
|
|
|
|
condition: !(option == 'selected_faces' && Project.box_uv && !Mesh.selected.length),
|
|
|
|
click() {
|
|
|
|
Project.display_uv = UVEditor.vue.display_uv = option;
|
2021-10-23 19:44:43 +08:00
|
|
|
if (option == 'selected_faces') settings.show_only_selected_uv.set(true);
|
|
|
|
if (option == 'selected_elements') settings.show_only_selected_uv.set(false);
|
2021-10-03 01:51:12 +08:00
|
|
|
}
|
|
|
|
}})
|
|
|
|
}},
|
2021-09-15 06:26:07 +08:00
|
|
|
'focus_on_selection',
|
2020-03-05 03:56:17 +08:00
|
|
|
'uv_checkerboard',
|
2021-11-24 06:46:57 +08:00
|
|
|
'paint_mode_uv_overlay',
|
2019-12-16 03:04:31 +08:00
|
|
|
'_',
|
|
|
|
'copy',
|
|
|
|
'paste',
|
2021-10-02 23:06:29 +08:00
|
|
|
{icon: 'photo_size_select_large', name: 'menu.uv.mapping', condition: () => !Project.box_uv && UVEditor.getReferenceFace(), children: function(editor) {
|
2021-09-22 03:13:24 +08:00
|
|
|
let reference_face = UVEditor.getReferenceFace();
|
|
|
|
function isMirrored(axis) {
|
|
|
|
if (reference_face instanceof CubeFace) {
|
|
|
|
reference_face.uv[axis+0] > reference_face.uv[axis+2]
|
|
|
|
} else {
|
|
|
|
let vertices = reference_face.getSortedVertices();
|
|
|
|
if (vertices.length <= 2) return false;
|
|
|
|
if (!Math.epsilon(reference_face.uv[vertices[0]][axis], reference_face.uv[vertices[1]][axis], 0.01)) {
|
|
|
|
return reference_face.uv[vertices[0]][axis] > reference_face.uv[vertices[1]][axis];
|
|
|
|
} else {
|
|
|
|
return reference_face.uv[vertices[0]][axis] > reference_face.uv[vertices[2]][axis];
|
|
|
|
}
|
2019-12-16 03:04:31 +08:00
|
|
|
}
|
2021-09-22 03:13:24 +08:00
|
|
|
}
|
|
|
|
return [
|
|
|
|
{icon: reference_face.enabled!==false ? 'check_box' : 'check_box_outline_blank', name: 'generic.export', click: function() {
|
2019-12-16 03:04:31 +08:00
|
|
|
Undo.initEdit({elements: Cube.selected, uv_only: true})
|
2021-09-22 03:13:24 +08:00
|
|
|
UVEditor.toggleUV(event)
|
|
|
|
Undo.finishEdit('Toggle UV export')
|
|
|
|
}},
|
|
|
|
'uv_maximize',
|
|
|
|
'uv_auto',
|
|
|
|
'uv_rel_auto',
|
2021-09-22 19:47:46 +08:00
|
|
|
'snap_uv_to_pixels',
|
2021-10-02 23:06:29 +08:00
|
|
|
'uv_rotate_left',
|
|
|
|
'uv_rotate_right',
|
|
|
|
{icon: 'rotate_90_degrees_ccw', condition: () => reference_face instanceof CubeFace && Format.uv_rotation, name: 'menu.uv.mapping.rotation', children: function() {
|
2021-09-22 03:13:24 +08:00
|
|
|
var off = 'radio_button_unchecked'
|
|
|
|
var on = 'radio_button_checked'
|
|
|
|
return [
|
|
|
|
{icon: (!reference_face.rotation ? on : off), name: '0°', click: function() {
|
|
|
|
Undo.initEdit({elements: Cube.selected, uv_only: true})
|
|
|
|
UVEditor.setRotation(0)
|
|
|
|
Undo.finishEdit('Rotate UV')
|
|
|
|
}},
|
|
|
|
{icon: (reference_face.rotation === 90 ? on : off), name: '90°', click: function() {
|
|
|
|
Undo.initEdit({elements: Cube.selected, uv_only: true})
|
|
|
|
UVEditor.setRotation(90)
|
|
|
|
Undo.finishEdit('Rotate UV')
|
|
|
|
}},
|
|
|
|
{icon: (reference_face.rotation === 180 ? on : off), name: '180°', click: function() {
|
|
|
|
Undo.initEdit({elements: Cube.selected, uv_only: true})
|
|
|
|
UVEditor.setRotation(180)
|
|
|
|
Undo.finishEdit('Rotate UV')
|
|
|
|
}},
|
|
|
|
{icon: (reference_face.rotation === 270 ? on : off), name: '270°', click: function() {
|
|
|
|
Undo.initEdit({elements: Cube.selected, uv_only: true})
|
|
|
|
UVEditor.setRotation(270)
|
|
|
|
Undo.finishEdit('Rotate UV')
|
|
|
|
}}
|
|
|
|
]
|
|
|
|
}},
|
|
|
|
'uv_turn_mapping',
|
|
|
|
{
|
|
|
|
icon: (isMirrored(0) ? 'check_box' : 'check_box_outline_blank'),
|
|
|
|
name: 'menu.uv.mapping.mirror_x',
|
|
|
|
click: function() {
|
|
|
|
Undo.initEdit({elements: Cube.selected, uv_only: true})
|
|
|
|
UVEditor.mirrorX(event)
|
|
|
|
Undo.finishEdit('Mirror UV')
|
|
|
|
}
|
|
|
|
},
|
|
|
|
{
|
|
|
|
icon: (isMirrored(1) ? 'check_box' : 'check_box_outline_blank'),
|
|
|
|
name: 'menu.uv.mapping.mirror_y',
|
|
|
|
click: function() {
|
|
|
|
Undo.initEdit({elements: Cube.selected, uv_only: true})
|
|
|
|
UVEditor.mirrorY(event)
|
|
|
|
Undo.finishEdit('Mirror UV')
|
|
|
|
}
|
|
|
|
},
|
|
|
|
]
|
|
|
|
}},
|
2020-01-24 01:53:36 +08:00
|
|
|
'face_tint',
|
2021-10-02 23:06:29 +08:00
|
|
|
{icon: 'flip_to_back', condition: () => (Format.id == 'java_block' && Cube.selected.length), name: 'action.cullface' , children: function() {
|
2020-01-24 01:53:36 +08:00
|
|
|
var off = 'radio_button_unchecked';
|
|
|
|
var on = 'radio_button_checked';
|
2021-06-06 15:28:22 +08:00
|
|
|
function setCullface(cullface) {
|
|
|
|
Undo.initEdit({elements: Cube.selected, uv_only: true})
|
2021-08-26 04:00:08 +08:00
|
|
|
UVEditor.forCubes(obj => {
|
2021-10-15 17:12:24 +08:00
|
|
|
UVEditor.selected_faces.forEach(face => {
|
|
|
|
obj.faces[face].cullface = cullface;
|
|
|
|
})
|
2021-06-06 15:28:22 +08:00
|
|
|
})
|
|
|
|
Undo.finishEdit(cullface ? `Set cullface to ${cullface}` : 'Disable cullface');
|
|
|
|
}
|
2020-01-24 01:53:36 +08:00
|
|
|
return [
|
2021-08-26 04:00:08 +08:00
|
|
|
{icon: (!UVEditor.getReferenceFace().cullface ? on : off), name: 'uv_editor.no_faces', click: () => setCullface('')},
|
|
|
|
{icon: (UVEditor.getReferenceFace().cullface == 'north' ? on : off), name: 'face.north', click: () => setCullface('north')},
|
|
|
|
{icon: (UVEditor.getReferenceFace().cullface == 'south' ? on : off), name: 'face.south', click: () => setCullface('south')},
|
|
|
|
{icon: (UVEditor.getReferenceFace().cullface == 'west' ? on : off), name: 'face.west', click: () => setCullface('west')},
|
|
|
|
{icon: (UVEditor.getReferenceFace().cullface == 'east' ? on : off), name: 'face.east', click: () => setCullface('east')},
|
|
|
|
{icon: (UVEditor.getReferenceFace().cullface == 'up' ? on : off), name: 'face.up', click: () => setCullface('up')},
|
|
|
|
{icon: (UVEditor.getReferenceFace().cullface == 'down' ? on : off), name: 'face.down', click: () => setCullface('down')},
|
2020-01-24 01:53:36 +08:00
|
|
|
'auto_cullface'
|
|
|
|
]
|
|
|
|
}},
|
2019-12-16 03:04:31 +08:00
|
|
|
{icon: 'collections', name: 'menu.uv.texture', condition: () => !Project.box_uv, children: function() {
|
|
|
|
var arr = [
|
2021-08-26 04:00:08 +08:00
|
|
|
{icon: 'crop_square', name: 'menu.cube.texture.blank', click: function(context, event) {
|
|
|
|
let elements = UVEditor.vue.mappable_elements;
|
|
|
|
Undo.initEdit({elements})
|
|
|
|
elements.forEach((obj) => {
|
|
|
|
UVEditor.getFaces(obj, event).forEach(function(side) {
|
2019-12-16 03:04:31 +08:00
|
|
|
obj.faces[side].texture = false;
|
|
|
|
})
|
2021-07-30 00:17:26 +08:00
|
|
|
obj.preview_controller.updateFaces(obj);
|
2019-12-16 03:04:31 +08:00
|
|
|
})
|
2021-08-26 04:00:08 +08:00
|
|
|
UVEditor.loadData()
|
|
|
|
UVEditor.message('uv_editor.reset')
|
2021-10-23 19:44:43 +08:00
|
|
|
Undo.finishEdit('Apply blank texture')
|
2019-12-16 03:04:31 +08:00
|
|
|
}},
|
2021-10-23 18:45:06 +08:00
|
|
|
{icon: 'clear', name: 'menu.cube.texture.transparent', condition: () => UVEditor.getReferenceFace() instanceof CubeFace, click: function() {UVEditor.clear(event)}},
|
2019-12-16 03:04:31 +08:00
|
|
|
]
|
2021-07-08 20:30:32 +08:00
|
|
|
Texture.all.forEach(function(t) {
|
2019-12-16 03:04:31 +08:00
|
|
|
arr.push({
|
|
|
|
name: t.name,
|
|
|
|
icon: (t.mode === 'link' ? t.img : t.source),
|
2021-09-02 04:13:04 +08:00
|
|
|
click: function() {UVEditor.applyTexture(t)}
|
2019-12-16 03:04:31 +08:00
|
|
|
})
|
|
|
|
})
|
|
|
|
return arr;
|
|
|
|
}}
|
|
|
|
])
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
BARS.defineActions(function() {
|
2021-09-24 03:35:36 +08:00
|
|
|
|
2021-08-18 04:02:23 +08:00
|
|
|
|
2019-12-16 03:04:31 +08:00
|
|
|
|
|
|
|
new BarSlider('uv_rotation', {
|
|
|
|
category: 'uv',
|
2020-01-24 01:53:36 +08:00
|
|
|
condition: () => !Project.box_uv && Format.uv_rotation && Cube.selected.length,
|
2019-12-16 03:04:31 +08:00
|
|
|
min: 0, max: 270, step: 90, width: 80,
|
|
|
|
onBefore: () => {
|
|
|
|
Undo.initEdit({elements: Cube.selected, uv_only: true})
|
|
|
|
},
|
|
|
|
onChange: function(slider) {
|
2021-10-09 21:44:11 +08:00
|
|
|
UVEditor.rotate();
|
2019-12-16 03:04:31 +08:00
|
|
|
},
|
|
|
|
onAfter: () => {
|
2021-06-06 15:28:22 +08:00
|
|
|
Undo.finishEdit('Rotate UV')
|
2019-12-16 03:04:31 +08:00
|
|
|
}
|
|
|
|
})
|
|
|
|
new BarSelect('uv_grid', {
|
|
|
|
category: 'uv',
|
|
|
|
condition: () => !Project.box_uv && Cube.selected.length,
|
2020-07-16 15:32:59 +08:00
|
|
|
min_width: 68,
|
2019-12-16 03:04:31 +08:00
|
|
|
value: 'auto',
|
|
|
|
options: {
|
|
|
|
'auto': 'Pixel',
|
|
|
|
'1x': '1x',
|
|
|
|
'2x': '2x',
|
|
|
|
'3x': '3x',
|
|
|
|
'4x': '4x',
|
|
|
|
'6x': '6x',
|
|
|
|
'8x': '8x',
|
|
|
|
},
|
|
|
|
onChange: function(slider) {
|
|
|
|
var value = slider.get().replace(/x/, '');
|
2021-08-19 21:18:01 +08:00
|
|
|
UVEditor.setGrid(value);
|
2019-12-16 03:04:31 +08:00
|
|
|
}
|
|
|
|
})
|
|
|
|
new Action('uv_maximize', {
|
|
|
|
icon: 'zoom_out_map',
|
|
|
|
category: 'uv',
|
|
|
|
condition: () => !Project.box_uv && Cube.selected.length,
|
|
|
|
click: function (event) {
|
|
|
|
Undo.initEdit({elements: Cube.selected, uv_only: true})
|
2021-08-18 04:02:23 +08:00
|
|
|
UVEditor.forSelection('maximize', event)
|
2021-06-06 15:28:22 +08:00
|
|
|
Undo.finishEdit('Maximize UV')
|
2019-12-16 03:04:31 +08:00
|
|
|
}
|
|
|
|
})
|
|
|
|
new Action('uv_turn_mapping', {
|
|
|
|
icon: 'screen_rotation',
|
|
|
|
category: 'uv',
|
|
|
|
condition: () => !Project.box_uv && Cube.selected.length,
|
|
|
|
click: function (event) {
|
|
|
|
Undo.initEdit({elements: Cube.selected, uv_only: true})
|
2021-08-18 04:02:23 +08:00
|
|
|
UVEditor.forSelection('turnMapping', event)
|
2021-06-06 15:28:22 +08:00
|
|
|
Undo.finishEdit('Turn UV mapping')
|
2019-12-16 03:04:31 +08:00
|
|
|
}
|
|
|
|
})
|
|
|
|
new Action('uv_auto', {
|
|
|
|
icon: 'brightness_auto',
|
|
|
|
category: 'uv',
|
2021-09-30 01:34:03 +08:00
|
|
|
condition: () => (!Project.box_uv && Cube.selected.length) || Mesh.selected.length,
|
2019-12-16 03:04:31 +08:00
|
|
|
click: function (event) {
|
2021-09-15 06:26:07 +08:00
|
|
|
Undo.initEdit({elements: UVEditor.getMappableElements(), uv_only: true})
|
2021-08-18 04:02:23 +08:00
|
|
|
UVEditor.forSelection('setAutoSize', event)
|
2021-06-06 15:28:22 +08:00
|
|
|
Undo.finishEdit('Auto UV')
|
2019-12-16 03:04:31 +08:00
|
|
|
}
|
|
|
|
})
|
|
|
|
new Action('uv_rel_auto', {
|
|
|
|
icon: 'brightness_auto',
|
|
|
|
category: 'uv',
|
|
|
|
condition: () => !Project.box_uv && Cube.selected.length,
|
|
|
|
click: function (event) {
|
|
|
|
Undo.initEdit({elements: Cube.selected, uv_only: true})
|
2021-08-18 04:02:23 +08:00
|
|
|
UVEditor.forSelection('setRelativeAutoSize', event)
|
2021-06-06 15:28:22 +08:00
|
|
|
Undo.finishEdit('Auto UV')
|
2019-12-16 03:04:31 +08:00
|
|
|
}
|
|
|
|
})
|
|
|
|
new Action('uv_mirror_x', {
|
|
|
|
icon: 'icon-mirror_x',
|
|
|
|
category: 'uv',
|
2021-09-25 20:47:41 +08:00
|
|
|
condition: () => !Project.box_uv && UVEditor.getMappableElements().length,
|
2019-12-16 03:04:31 +08:00
|
|
|
click: function (event) {
|
|
|
|
Undo.initEdit({elements: Cube.selected, uv_only: true})
|
2021-08-18 04:02:23 +08:00
|
|
|
UVEditor.forSelection('mirrorX', event)
|
2021-06-06 15:28:22 +08:00
|
|
|
Undo.finishEdit('Mirror UV')
|
2019-12-16 03:04:31 +08:00
|
|
|
}
|
|
|
|
})
|
|
|
|
new Action('uv_mirror_y', {
|
|
|
|
icon: 'icon-mirror_y',
|
|
|
|
category: 'uv',
|
2021-09-25 20:47:41 +08:00
|
|
|
condition: () => !Project.box_uv && UVEditor.getMappableElements().length,
|
2019-12-16 03:04:31 +08:00
|
|
|
click: function (event) {
|
|
|
|
Undo.initEdit({elements: Cube.selected, uv_only: true})
|
2021-08-18 04:02:23 +08:00
|
|
|
UVEditor.forSelection('mirrorY', event)
|
2021-06-06 15:28:22 +08:00
|
|
|
Undo.finishEdit('Mirror UV')
|
2019-12-16 03:04:31 +08:00
|
|
|
}
|
|
|
|
})
|
2021-10-02 23:06:29 +08:00
|
|
|
new Action('uv_rotate_left', {
|
|
|
|
icon: 'rotate_left',
|
|
|
|
category: 'uv',
|
|
|
|
condition: () => Mesh.selected.length,
|
|
|
|
click: function (event) {
|
|
|
|
Undo.initEdit({elements: Mesh.selected, uv_only: true})
|
|
|
|
UVEditor.rotate(-90);
|
|
|
|
Undo.finishEdit('Rotate UV left');
|
|
|
|
}
|
|
|
|
})
|
|
|
|
new Action('uv_rotate_right', {
|
|
|
|
icon: 'rotate_right',
|
|
|
|
category: 'uv',
|
|
|
|
condition: () => Mesh.selected.length,
|
|
|
|
click: function (event) {
|
|
|
|
Undo.initEdit({elements: Mesh.selected, uv_only: true})
|
|
|
|
UVEditor.rotate(90);
|
|
|
|
Undo.finishEdit('Rotate UV right');
|
|
|
|
}
|
|
|
|
})
|
2019-12-16 03:04:31 +08:00
|
|
|
new Action('uv_transparent', {
|
|
|
|
icon: 'clear',
|
|
|
|
category: 'uv',
|
|
|
|
condition: () => !Project.box_uv && Cube.selected.length,
|
|
|
|
click: function (event) {
|
2021-08-18 04:02:23 +08:00
|
|
|
UVEditor.forSelection('clear', event)
|
2019-12-16 03:04:31 +08:00
|
|
|
}
|
|
|
|
})
|
|
|
|
new Action('uv_reset', {
|
|
|
|
icon: 'replay',
|
|
|
|
category: 'uv',
|
|
|
|
condition: () => !Project.box_uv && Cube.selected.length,
|
|
|
|
click: function (event) {
|
|
|
|
Undo.initEdit({elements: Cube.selected, uv_only: true})
|
2021-08-18 04:02:23 +08:00
|
|
|
UVEditor.forSelection('reset', event)
|
2021-06-06 15:28:22 +08:00
|
|
|
Undo.finishEdit('Reset UV')
|
2019-12-16 03:04:31 +08:00
|
|
|
}
|
|
|
|
})
|
|
|
|
new Action('uv_apply_all', {
|
|
|
|
icon: 'format_color_fill',
|
|
|
|
category: 'uv',
|
|
|
|
condition: () => !Project.box_uv && Cube.selected.length,
|
|
|
|
click: function (e) {
|
|
|
|
Undo.initEdit({elements: Cube.selected, uv_only: true})
|
2021-08-18 04:02:23 +08:00
|
|
|
UVEditor.applyAll(e)
|
2021-06-06 15:28:22 +08:00
|
|
|
Undo.finishEdit('Apply UV to all faces')
|
2019-12-16 03:04:31 +08:00
|
|
|
}
|
|
|
|
})
|
|
|
|
new BarSelect('cullface', {
|
|
|
|
category: 'uv',
|
2021-12-10 19:26:39 +08:00
|
|
|
condition: () => !Project.box_uv && Format.id == 'java_block' && Cube.selected.length && UVEditor.selected_faces[0],
|
2019-12-16 03:04:31 +08:00
|
|
|
label: true,
|
|
|
|
options: {
|
|
|
|
off: tl('uv_editor.no_faces'),
|
|
|
|
north: tl('face.north'),
|
|
|
|
south: tl('face.south'),
|
|
|
|
west: tl('face.west'),
|
|
|
|
east: tl('face.east'),
|
|
|
|
up: tl('face.up'),
|
|
|
|
down: tl('face.down'),
|
|
|
|
},
|
|
|
|
onChange: function(sel, event) {
|
|
|
|
Undo.initEdit({elements: Cube.selected, uv_only: true});
|
2021-08-18 04:02:23 +08:00
|
|
|
UVEditor.forSelection('switchCullface');
|
2021-06-06 15:28:22 +08:00
|
|
|
Undo.finishEdit('Set cullface');
|
2019-12-16 03:04:31 +08:00
|
|
|
}
|
|
|
|
})
|
|
|
|
new Action('auto_cullface', {
|
|
|
|
icon: 'block',
|
|
|
|
category: 'uv',
|
2021-12-10 19:26:39 +08:00
|
|
|
condition: () => !Project.box_uv && Format.id == 'java_block' && Cube.selected.length && UVEditor.selected_faces[0],
|
2019-12-16 03:04:31 +08:00
|
|
|
click: function (event) {
|
|
|
|
Undo.initEdit({elements: Cube.selected, uv_only: true})
|
2021-08-18 04:02:23 +08:00
|
|
|
UVEditor.forSelection('autoCullface', event)
|
2021-06-06 15:28:22 +08:00
|
|
|
Undo.finishEdit('Set automatic cullface')
|
2019-12-16 03:04:31 +08:00
|
|
|
}
|
|
|
|
})
|
|
|
|
new Action('face_tint', {
|
|
|
|
category: 'uv',
|
2021-12-10 19:26:39 +08:00
|
|
|
condition: () => !Project.box_uv && Format.id == 'java_block' && Cube.selected.length && UVEditor.selected_faces[0],
|
2019-12-16 03:04:31 +08:00
|
|
|
click: function (event) {
|
|
|
|
Undo.initEdit({elements: Cube.selected, uv_only: true})
|
2021-08-18 04:02:23 +08:00
|
|
|
UVEditor.forSelection('switchTint', event)
|
2021-06-06 15:28:22 +08:00
|
|
|
Undo.finishEdit('Toggle face tint')
|
2019-12-16 03:04:31 +08:00
|
|
|
}
|
|
|
|
})
|
2020-03-05 03:56:17 +08:00
|
|
|
new NumSlider('slider_face_tint', {
|
|
|
|
category: 'uv',
|
2021-12-10 19:26:39 +08:00
|
|
|
condition: () => !Project.box_uv && Format.id == 'java_block' && Cube.selected.length && UVEditor.selected_faces[0] && Cube.selected[0].faces[UVEditor.selected_faces[0]],
|
2020-03-05 03:56:17 +08:00
|
|
|
getInterval(event) {
|
|
|
|
return 1;
|
|
|
|
},
|
|
|
|
get: function() {
|
2021-10-15 17:12:24 +08:00
|
|
|
return Cube.selected[0] && Cube.selected[0].faces[UVEditor.selected_faces[0]].tint
|
2020-03-05 03:56:17 +08:00
|
|
|
},
|
|
|
|
change: function(modify) {
|
|
|
|
let number = Math.clamp(Math.round(modify(this.get())), -1)
|
2021-10-15 17:12:24 +08:00
|
|
|
UVEditor.setTint(null, number);
|
2020-03-05 03:56:17 +08:00
|
|
|
},
|
|
|
|
onBefore: function() {
|
|
|
|
Undo.initEdit({elements: Cube.selected, uv_only: true})
|
|
|
|
},
|
|
|
|
onAfter: function() {
|
2021-06-06 15:28:22 +08:00
|
|
|
Undo.finishEdit('Set face tint')
|
2020-03-05 03:56:17 +08:00
|
|
|
}
|
|
|
|
})
|
2021-09-22 19:47:46 +08:00
|
|
|
new Action('snap_uv_to_pixels', {
|
|
|
|
icon: 'grid_goldenratio',
|
|
|
|
category: 'uv',
|
2021-12-10 19:26:39 +08:00
|
|
|
condition: () => !Project.box_uv && UVEditor.getMappableElements().length,
|
2021-09-22 19:47:46 +08:00
|
|
|
click: function (event) {
|
|
|
|
let elements = UVEditor.getMappableElements();
|
|
|
|
Undo.initEdit({elements, uv_only: true})
|
|
|
|
elements.forEach(element => {
|
|
|
|
let selected_vertices = element instanceof Mesh && element.getSelectedVertices();
|
|
|
|
UVEditor.selected_faces.forEach(fkey => {
|
|
|
|
if (!element.faces[fkey]) return;
|
|
|
|
let face = element.faces[fkey];
|
|
|
|
if (element instanceof Mesh) {
|
|
|
|
face.vertices.forEach(vkey => {
|
|
|
|
if ((!selected_vertices.length || selected_vertices.includes(vkey)) && face.uv[vkey]) {
|
|
|
|
face.uv[vkey][0] = Math.clamp(Math.round(face.uv[vkey][0]), 0, Project.texture_width);
|
|
|
|
face.uv[vkey][1] = Math.clamp(Math.round(face.uv[vkey][1]), 0, Project.texture_height);
|
|
|
|
}
|
|
|
|
})
|
|
|
|
} else if (element instanceof Cube) {
|
|
|
|
face.uv[0] = Math.clamp(Math.round(face.uv[0]), 0, Project.texture_width);
|
|
|
|
face.uv[1] = Math.clamp(Math.round(face.uv[1]), 0, Project.texture_height);
|
|
|
|
face.uv[2] = Math.clamp(Math.round(face.uv[2]), 0, Project.texture_width);
|
|
|
|
face.uv[3] = Math.clamp(Math.round(face.uv[3]), 0, Project.texture_height);
|
|
|
|
}
|
|
|
|
})
|
|
|
|
element.preview_controller.updateUV(element);
|
|
|
|
})
|
|
|
|
UVEditor.loadData();
|
|
|
|
Undo.finishEdit('Set automatic cullface')
|
|
|
|
}
|
|
|
|
})
|
2021-11-24 06:46:57 +08:00
|
|
|
new Toggle('paint_mode_uv_overlay', {
|
|
|
|
icon: 'splitscreen',
|
|
|
|
category: 'animation',
|
|
|
|
condition: {modes: ['paint']},
|
|
|
|
onChange(value) {
|
|
|
|
UVEditor.vue.uv_overlay = value;
|
|
|
|
}
|
|
|
|
})
|
2019-12-16 03:04:31 +08:00
|
|
|
})
|
2020-09-22 05:23:42 +08:00
|
|
|
|
|
|
|
Interface.definePanels(function() {
|
2021-12-14 20:57:49 +08:00
|
|
|
function getCanvasCopy() {
|
|
|
|
var temp_canvas = document.createElement('canvas')
|
|
|
|
var temp_ctx = temp_canvas.getContext('2d');
|
|
|
|
temp_canvas.width = Painter.selection.canvas.width;
|
|
|
|
temp_canvas.height = Painter.selection.canvas.height;
|
|
|
|
temp_ctx.drawImage(Painter.selection.canvas, 0, 0)
|
|
|
|
return temp_canvas
|
|
|
|
}
|
|
|
|
|
|
|
|
let copy_overlay = {
|
|
|
|
state: 'off',
|
2021-12-15 22:23:42 +08:00
|
|
|
width: 0, height: 0,
|
2021-12-14 20:57:49 +08:00
|
|
|
|
|
|
|
doPlace() {
|
|
|
|
open_interface.confirm();
|
|
|
|
},
|
|
|
|
doCancel() {
|
|
|
|
open_interface.hide();
|
|
|
|
},
|
|
|
|
doCut(e) {
|
|
|
|
UVEditor.removePastingOverlay()
|
|
|
|
UVEditor.texture.edit((canvas) => {
|
|
|
|
var ctx = canvas.getContext('2d');
|
|
|
|
ctx.clearRect(Painter.selection.x, Painter.selection.y, Painter.selection.canvas.width, Painter.selection.canvas.height);
|
|
|
|
})
|
|
|
|
},
|
|
|
|
doMirror_x(e) {
|
|
|
|
let temp_canvas = getCanvasCopy()
|
|
|
|
|
|
|
|
let ctx = Painter.selection.canvas.getContext('2d');
|
|
|
|
ctx.save();
|
|
|
|
ctx.translate(ctx.canvas.width, 0);
|
|
|
|
ctx.scale(-1, 1);
|
|
|
|
|
|
|
|
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
|
|
|
|
ctx.drawImage(temp_canvas, ctx.canvas.width, 0, -ctx.canvas.width, ctx.canvas.height);
|
|
|
|
ctx.restore();
|
|
|
|
UVEditor.updatePastingOverlay()
|
|
|
|
},
|
|
|
|
doMirror_y(e) {
|
|
|
|
let temp_canvas = getCanvasCopy()
|
|
|
|
|
|
|
|
let ctx = Painter.selection.canvas.getContext('2d');
|
|
|
|
ctx.save();
|
|
|
|
ctx.translate(0, ctx.canvas.height);
|
|
|
|
ctx.scale(1, -1);
|
|
|
|
|
|
|
|
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
|
|
|
|
ctx.drawImage(temp_canvas, 0, ctx.canvas.height, ctx.canvas.width, -ctx.canvas.height);
|
|
|
|
ctx.restore();
|
|
|
|
},
|
|
|
|
doRotate(e) {
|
|
|
|
let temp_canvas = getCanvasCopy()
|
|
|
|
|
|
|
|
let ctx = Painter.selection.canvas.getContext('2d');
|
|
|
|
[ctx.canvas.width, ctx.canvas.height] = [ctx.canvas.height, ctx.canvas.width]
|
|
|
|
ctx.save();
|
|
|
|
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
|
|
|
|
|
|
|
|
ctx.translate(ctx.canvas.width/2,ctx.canvas.height/2);
|
|
|
|
ctx.rotate(Math.PI/2);
|
|
|
|
|
|
|
|
ctx.drawImage(temp_canvas,-temp_canvas.width/2,-temp_canvas.height/2);
|
|
|
|
|
|
|
|
ctx.restore();
|
|
|
|
UVEditor.updateSize()
|
|
|
|
},
|
|
|
|
}
|
2020-09-22 05:23:42 +08:00
|
|
|
|
2021-08-18 04:02:23 +08:00
|
|
|
UVEditor.panel = Interface.Panels.uv = new Panel({
|
2020-09-22 05:23:42 +08:00
|
|
|
id: 'uv',
|
|
|
|
icon: 'photo_size_select_large',
|
|
|
|
selection_only: true,
|
|
|
|
condition: {modes: ['edit', 'paint']},
|
|
|
|
toolbars: {
|
2021-08-18 04:02:23 +08:00
|
|
|
bottom: Toolbars.UVEditor
|
2020-09-22 05:23:42 +08:00
|
|
|
},
|
|
|
|
onResize: function() {
|
2021-09-15 23:50:46 +08:00
|
|
|
UVEditor.vue.updateSize();
|
|
|
|
UVEditor.vue.hidden = !this.isVisible();
|
|
|
|
},
|
|
|
|
onFold: function() {
|
|
|
|
UVEditor.vue.hidden = !this.isVisible();
|
2021-08-18 04:02:23 +08:00
|
|
|
},
|
|
|
|
component: {
|
|
|
|
data() {return {
|
|
|
|
mode: 'uv',
|
2021-09-15 23:50:46 +08:00
|
|
|
hidden: false,
|
2021-08-19 04:58:47 +08:00
|
|
|
box_uv: false,
|
2021-08-18 04:02:23 +08:00
|
|
|
width: 320,
|
|
|
|
height: 320,
|
|
|
|
zoom: 1,
|
|
|
|
checkerboard: settings.uv_checkerboard.value,
|
2021-11-24 06:46:57 +08:00
|
|
|
uv_overlay: false,
|
2021-08-19 21:18:01 +08:00
|
|
|
texture: 0,
|
2021-08-29 16:44:05 +08:00
|
|
|
mouse_coords: {x: -1, y: -1},
|
2021-09-25 20:47:41 +08:00
|
|
|
helper_lines: {x: -1, y: -1},
|
2021-09-16 06:49:57 +08:00
|
|
|
selection_rect: {
|
|
|
|
pos_x: 0,
|
|
|
|
pos_y: 0,
|
|
|
|
width: 0,
|
|
|
|
height: 0,
|
|
|
|
active: false
|
|
|
|
},
|
2021-12-14 20:57:49 +08:00
|
|
|
copy_overlay,
|
2021-08-18 04:02:23 +08:00
|
|
|
|
|
|
|
project_resolution: [16, 16],
|
|
|
|
elements: [],
|
2021-08-26 04:00:08 +08:00
|
|
|
all_elements: [],
|
2021-08-18 04:02:23 +08:00
|
|
|
selected_vertices: {},
|
2021-08-19 04:58:47 +08:00
|
|
|
selected_faces: [],
|
2021-10-03 01:51:12 +08:00
|
|
|
display_uv: 'selected_elements',
|
2021-08-19 04:58:47 +08:00
|
|
|
|
|
|
|
face_names: {
|
|
|
|
north: tl('face.north'),
|
|
|
|
south: tl('face.south'),
|
|
|
|
west: tl('face.west'),
|
|
|
|
east: tl('face.east'),
|
|
|
|
up: tl('face.up'),
|
|
|
|
down: tl('face.down'),
|
|
|
|
}
|
2021-08-18 04:02:23 +08:00
|
|
|
}},
|
|
|
|
computed: {
|
|
|
|
inner_width() {
|
|
|
|
return this.width * this.zoom;
|
|
|
|
},
|
|
|
|
inner_height() {
|
2021-08-26 04:00:08 +08:00
|
|
|
return this.width * (this.project_resolution[1] / this.project_resolution[0]) * this.zoom;
|
2021-08-18 04:02:23 +08:00
|
|
|
},
|
|
|
|
mappable_elements() {
|
2021-10-26 02:23:46 +08:00
|
|
|
return this.elements.filter(element => element.faces && !element.locked);
|
2021-08-26 04:00:08 +08:00
|
|
|
},
|
|
|
|
all_mappable_elements() {
|
2021-10-26 02:23:46 +08:00
|
|
|
return this.all_elements.filter(element => element.faces && !element.locked);
|
2021-08-26 04:00:08 +08:00
|
|
|
}
|
|
|
|
},
|
|
|
|
watch: {
|
|
|
|
project_resolution: {
|
|
|
|
deep: true,
|
|
|
|
handler() {
|
|
|
|
let min_zoom = Math.min(1, this.inner_width/this.inner_height);
|
|
|
|
if (this.zoom < min_zoom) this.zoom = 1;
|
|
|
|
}
|
2021-08-18 04:02:23 +08:00
|
|
|
}
|
|
|
|
},
|
|
|
|
methods: {
|
|
|
|
projectResolution() {
|
|
|
|
BarItems.project_window.trigger()
|
|
|
|
},
|
|
|
|
updateSize() {
|
2021-08-19 04:58:47 +08:00
|
|
|
if (!this.$refs.viewport) return;
|
2021-08-18 04:02:23 +08:00
|
|
|
let old_size = this.width;
|
|
|
|
let size = Math.floor(Math.clamp(UVEditor.panel.width - 10, 64, 1e5));
|
|
|
|
this.width = size;
|
2021-08-26 04:00:08 +08:00
|
|
|
this.height = size * Math.clamp(this.project_resolution[1] / this.project_resolution[0], 0.5, 1);
|
2021-08-18 04:02:23 +08:00
|
|
|
this.$refs.viewport.scrollLeft = Math.round(this.$refs.viewport.scrollLeft * (size / old_size));
|
|
|
|
this.$refs.viewport.scrollTop = Math.round(this.$refs.viewport.scrollTop * (size / old_size));
|
|
|
|
|
2021-08-19 21:18:01 +08:00
|
|
|
for (var id in UVEditor.sliders) {
|
|
|
|
var slider = UVEditor.sliders[id];
|
|
|
|
slider.setWidth(size/(Project.box_uv?2:4)-1)
|
|
|
|
}
|
2021-08-18 04:02:23 +08:00
|
|
|
},
|
|
|
|
setMode(mode) {
|
|
|
|
this.mode = mode;
|
|
|
|
},
|
|
|
|
updateTexture() {
|
|
|
|
let texture;
|
|
|
|
if (Format.single_texture) {
|
|
|
|
texture = Texture.getDefault();
|
|
|
|
} else {
|
2021-11-18 02:00:53 +08:00
|
|
|
let elements = UVEditor.getMappableElements();
|
2021-12-11 05:03:34 +08:00
|
|
|
if (elements.length) {
|
2021-08-18 04:02:23 +08:00
|
|
|
for (let element of elements) {
|
2021-12-11 20:20:44 +08:00
|
|
|
let face = element.faces[ this.selected_faces[0] || Object.keys(element.faces)[0] ];
|
|
|
|
if (face) texture = face.getTexture() || texture;
|
2021-12-11 05:03:34 +08:00
|
|
|
if (texture) break;
|
2021-08-18 04:02:23 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-09-02 04:13:04 +08:00
|
|
|
if (texture === null) {
|
2021-09-21 00:45:02 +08:00
|
|
|
this.texture = null;
|
2021-08-18 04:02:23 +08:00
|
|
|
} else if (texture instanceof Texture) {
|
2021-08-19 21:18:01 +08:00
|
|
|
this.texture = texture;
|
2021-08-30 16:30:46 +08:00
|
|
|
if (!Project.box_uv && UVEditor.auto_grid) {
|
|
|
|
UVEditor.grid = texture.width / Project.texture_width;
|
|
|
|
}
|
2021-08-18 04:02:23 +08:00
|
|
|
} else {
|
2021-09-21 00:45:02 +08:00
|
|
|
this.texture = 0;
|
2021-08-18 04:02:23 +08:00
|
|
|
}
|
|
|
|
},
|
2021-08-29 16:44:05 +08:00
|
|
|
updateMouseCoords(event) {
|
|
|
|
convertTouchEvent(event);
|
2021-08-30 16:30:46 +08:00
|
|
|
var pixel_size = this.inner_width / (this.texture ? this.texture.width : this.project_resolution[0]);
|
2021-08-29 16:44:05 +08:00
|
|
|
|
|
|
|
if (Toolbox.selected.id === 'copy_paste_tool') {
|
|
|
|
this.mouse_coords.x = Math.round(event.offsetX/pixel_size*1);
|
|
|
|
this.mouse_coords.y = Math.round(event.offsetY/pixel_size*1);
|
|
|
|
} else {
|
|
|
|
let offset = BarItems.slider_brush_size.get()%2 == 0 && Toolbox.selected.brushTool ? 0.5 : 0;
|
|
|
|
this.mouse_coords.x = Math.floor(event.offsetX/pixel_size*1 + offset);
|
|
|
|
this.mouse_coords.y = Math.floor(event.offsetY/pixel_size*1 + offset);
|
|
|
|
}
|
2021-08-30 16:30:46 +08:00
|
|
|
if (this.texture && this.texture.frameCount) {
|
2021-08-29 16:44:05 +08:00
|
|
|
this.mouse_coords.y += (this.texture.height / this.texture.frameCount) * this.texture.currentFrame
|
|
|
|
}
|
|
|
|
},
|
2021-08-18 04:02:23 +08:00
|
|
|
onMouseWheel(event) {
|
|
|
|
if (event.ctrlOrCmd) {
|
|
|
|
|
|
|
|
event.stopPropagation()
|
|
|
|
event.preventDefault()
|
|
|
|
|
|
|
|
var n = (event.deltaY < 0) ? 0.1 : -0.1;
|
|
|
|
n *= this.zoom
|
2021-08-26 04:00:08 +08:00
|
|
|
var number = Math.clamp(this.zoom + n, Math.min(1, this.inner_width/this.inner_height), this.max_zoom)
|
2021-08-18 04:02:23 +08:00
|
|
|
let old_zoom = this.zoom;
|
|
|
|
|
|
|
|
this.zoom = number;
|
|
|
|
|
2021-12-30 21:40:33 +08:00
|
|
|
let updateScroll = () => {
|
|
|
|
let {viewport} = this.$refs;
|
|
|
|
let offset = $(this.$refs.viewport).offset()
|
|
|
|
let offsetX = event.clientX - offset.left;
|
|
|
|
let offsetY = event.clientY - offset.top;
|
|
|
|
// Make it a bit easier to scroll into corners
|
|
|
|
offsetX = (offsetX - this.width/2) * 1.1 + this.width/2;
|
|
|
|
offsetY = (offsetY - this.height/2) * 1.1 + this.height/2;
|
|
|
|
let zoom_diff = this.zoom - old_zoom;
|
|
|
|
|
|
|
|
viewport.scrollLeft += ((viewport.scrollLeft + offsetX) * zoom_diff) / old_zoom
|
|
|
|
viewport.scrollTop += ((viewport.scrollTop + offsetY) * zoom_diff) / old_zoom
|
|
|
|
|
|
|
|
this.updateMouseCoords(event)
|
|
|
|
if (Painter.selection.overlay) UVEditor.updatePastingOverlay()
|
|
|
|
}
|
|
|
|
if (n > 0) {
|
|
|
|
Vue.nextTick(updateScroll);
|
|
|
|
} else {
|
|
|
|
updateScroll();
|
|
|
|
}
|
2021-08-29 16:44:05 +08:00
|
|
|
|
2021-08-18 04:02:23 +08:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
onMouseDown(event) {
|
2021-09-15 06:26:07 +08:00
|
|
|
setActivePanel('uv');
|
2021-08-18 04:02:23 +08:00
|
|
|
if (event.which === 2) {
|
|
|
|
let {viewport} = this.$refs;
|
|
|
|
let coords = {x: 0, y: 0}
|
|
|
|
function dragMouseWheel(e2) {
|
|
|
|
viewport.scrollLeft -= (e2.pageX - coords.x)
|
|
|
|
viewport.scrollTop -= (e2.pageY - coords.y)
|
|
|
|
coords = {x: e2.pageX, y: e2.pageY}
|
|
|
|
}
|
|
|
|
function dragMouseWheelStop(e) {
|
|
|
|
removeEventListeners(document, 'mousemove touchmove', dragMouseWheel);
|
|
|
|
removeEventListeners(document, 'mouseup touchend', dragMouseWheelStop);
|
|
|
|
}
|
|
|
|
addEventListeners(document, 'mousemove touchmove', dragMouseWheel);
|
|
|
|
addEventListeners(document, 'mouseup touchend', dragMouseWheelStop);
|
|
|
|
coords = {x: event.pageX, y: event.pageY}
|
|
|
|
event.preventDefault();
|
|
|
|
return false;
|
2021-08-19 04:58:47 +08:00
|
|
|
} else if (this.mode == 'paint' && Toolbox.selected.paintTool && (event.which === 1 || (event.touches && event.touches.length == 1))) {
|
|
|
|
UVEditor.startPaintTool(event)
|
2021-09-16 06:49:57 +08:00
|
|
|
} else if (this.mode == 'uv' && event.target.id == 'uv_frame' && (event.which === 1 || (event.touches && event.touches.length == 1))) {
|
|
|
|
|
2021-10-23 06:21:37 +08:00
|
|
|
if (event.altKey || Pressing.overrides.alt) {
|
|
|
|
return this.dragFace(null, event);
|
|
|
|
}
|
|
|
|
|
2021-09-16 06:49:57 +08:00
|
|
|
let {selection_rect} = this;
|
|
|
|
let scope = this;
|
|
|
|
let old_faces = this.selected_faces.slice();
|
2021-09-24 03:35:36 +08:00
|
|
|
let old_selected_vertices = {};
|
|
|
|
Mesh.selected.forEach(mesh => {
|
|
|
|
old_selected_vertices[mesh.uuid] = mesh.getSelectedVertices().slice();
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
let old_elements;
|
|
|
|
if (Project.box_uv) {
|
2021-11-18 02:00:53 +08:00
|
|
|
old_elements = UVEditor.getMappableElements().slice();
|
2021-09-24 03:35:36 +08:00
|
|
|
}
|
2021-09-16 06:49:57 +08:00
|
|
|
|
|
|
|
function drag(e1) {
|
|
|
|
selection_rect.active = true;
|
|
|
|
let rect = getRectangle(
|
|
|
|
event.offsetX / scope.inner_width * scope.project_resolution[0],
|
|
|
|
event.offsetY / scope.inner_height * scope.project_resolution[1],
|
|
|
|
(event.offsetX - event.clientX + e1.clientX) / scope.inner_width * scope.project_resolution[0],
|
|
|
|
(event.offsetY - event.clientY + e1.clientY) / scope.inner_height * scope.project_resolution[1],
|
|
|
|
)
|
|
|
|
selection_rect.pos_x = rect.ax;
|
|
|
|
selection_rect.pos_y = rect.ay;
|
|
|
|
selection_rect.width = rect.x;
|
|
|
|
selection_rect.height = rect.y;
|
|
|
|
|
|
|
|
if (!e1.shiftKey) {
|
|
|
|
scope.selected_faces.empty();
|
2021-09-24 03:35:36 +08:00
|
|
|
if (old_elements) Outliner.selected.empty();
|
2021-09-16 06:49:57 +08:00
|
|
|
} else {
|
|
|
|
scope.selected_faces.replace(old_faces);
|
2021-09-24 03:35:36 +08:00
|
|
|
if (old_elements) Outliner.selected.replace(old_elements);
|
|
|
|
}
|
|
|
|
|
|
|
|
let elements;
|
|
|
|
if (Project.box_uv) {
|
2021-10-26 02:23:46 +08:00
|
|
|
elements = Cube.all.filter(cube => !cube.locked);
|
2021-11-18 02:00:53 +08:00
|
|
|
elements.safePush(UVEditor.getMappableElements());
|
2021-09-24 03:35:36 +08:00
|
|
|
} else {
|
2021-11-18 02:00:53 +08:00
|
|
|
elements = UVEditor.getMappableElements();
|
2021-09-16 06:49:57 +08:00
|
|
|
}
|
|
|
|
|
2021-09-24 03:35:36 +08:00
|
|
|
elements.forEach(element => {
|
2021-09-16 06:49:57 +08:00
|
|
|
if (element instanceof Cube && !Project.box_uv) {
|
|
|
|
for (let fkey in element.faces) {
|
|
|
|
let face_rect = getRectangle(...element.faces[fkey].uv);
|
|
|
|
if (doRectanglesOverlap(rect, face_rect)) {
|
2021-09-18 04:32:53 +08:00
|
|
|
scope.selected_faces.safePush(fkey);
|
2021-09-16 06:49:57 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if (element instanceof Cube) {
|
2021-09-24 03:35:36 +08:00
|
|
|
let overlaps = false;
|
|
|
|
for (let fkey in element.faces) {
|
|
|
|
let face_rect = getRectangle(...element.faces[fkey].uv);
|
|
|
|
if (doRectanglesOverlap(rect, face_rect)) {
|
|
|
|
overlaps = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (overlaps) {
|
|
|
|
Outliner.selected.safePush(element);
|
|
|
|
}
|
2021-09-16 06:49:57 +08:00
|
|
|
} else if (element instanceof Mesh) {
|
2021-09-24 03:35:36 +08:00
|
|
|
let selected_vertices = element.getSelectedVertices(true);
|
|
|
|
if (!e1.shiftKey) {
|
|
|
|
selected_vertices.empty();
|
|
|
|
} else {
|
|
|
|
selected_vertices.replace(old_selected_vertices[element.uuid]);
|
|
|
|
}
|
2021-09-16 06:49:57 +08:00
|
|
|
for (let fkey in element.faces) {
|
|
|
|
let face = element.faces[fkey];
|
|
|
|
let vertices = face.getSortedVertices();
|
|
|
|
if (vertices.length >= 3) {
|
|
|
|
let i = 0;
|
|
|
|
for (let vkey of vertices) {
|
|
|
|
i++;
|
|
|
|
let vkey2 = vertices[i] || vertices[0];
|
|
|
|
if (lineIntersectsReactangle(face.uv[vkey], face.uv[vkey2], [rect.ax, rect.ay], [rect.bx, rect.by])) {
|
2021-09-18 04:32:53 +08:00
|
|
|
scope.selected_faces.safePush(fkey);
|
2021-09-24 03:35:36 +08:00
|
|
|
}
|
|
|
|
if (pointInRectangle(face.uv[vkey], [rect.ax, rect.ay], [rect.bx, rect.by])) {
|
|
|
|
selected_vertices.safePush(vkey);
|
2021-09-16 06:49:57 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
2021-09-24 03:35:36 +08:00
|
|
|
if (old_elements) updateSelection();
|
2021-10-15 17:12:24 +08:00
|
|
|
UVEditor.displayTools();
|
2021-09-16 06:49:57 +08:00
|
|
|
}
|
|
|
|
function stop() {
|
|
|
|
removeEventListeners(document, 'mousemove touchmove', drag);
|
|
|
|
removeEventListeners(document, 'mouseup touchend', stop);
|
|
|
|
setTimeout(() => {
|
|
|
|
selection_rect.active = false;
|
|
|
|
}, 1)
|
|
|
|
}
|
|
|
|
addEventListeners(document, 'mousemove touchmove', drag);
|
|
|
|
addEventListeners(document, 'mouseup touchend', stop);
|
2021-08-18 04:02:23 +08:00
|
|
|
}
|
|
|
|
},
|
2021-09-21 17:20:10 +08:00
|
|
|
onMouseLeave(event) {
|
|
|
|
if (this.mode == 'paint') {
|
|
|
|
this.mouse_coords.x = -1;
|
|
|
|
}
|
|
|
|
},
|
2021-08-18 04:02:23 +08:00
|
|
|
contextMenu(event) {
|
2021-09-15 06:26:07 +08:00
|
|
|
setActivePanel('uv');
|
2021-10-15 21:14:32 +08:00
|
|
|
if (!UVEditor.getReferenceFace() && !Project.box_uv) return;
|
2021-08-18 04:02:23 +08:00
|
|
|
UVEditor.menu.open(event);
|
2021-08-19 04:58:47 +08:00
|
|
|
},
|
2021-09-08 03:14:33 +08:00
|
|
|
selectFace(key, event, keep_selection, support_dragging) {
|
2021-08-19 21:18:01 +08:00
|
|
|
if (keep_selection && this.selected_faces.includes(key)) {
|
|
|
|
|
|
|
|
} else if (event.shiftKey || event.ctrlOrCmd || Pressing.overrides.shift || Pressing.overrides.ctrl) {
|
2021-08-19 04:58:47 +08:00
|
|
|
if (this.selected_faces.includes(key)) {
|
|
|
|
this.selected_faces.remove(key);
|
|
|
|
} else {
|
|
|
|
this.selected_faces.push(key);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
this.selected_faces.replace([key]);
|
|
|
|
}
|
2021-09-08 03:14:33 +08:00
|
|
|
UVEditor.vue.updateTexture();
|
2021-10-15 17:12:24 +08:00
|
|
|
UVEditor.displayTools();
|
2021-09-08 03:14:33 +08:00
|
|
|
|
|
|
|
if (support_dragging) {
|
|
|
|
let scope = this;
|
|
|
|
function drag(e1) {
|
|
|
|
if (e1.target && e1.target.nodeName == 'LI' && e1.target.parentElement.id == 'uv_cube_face_bar') {
|
|
|
|
let face = e1.target.attributes.face.value;
|
|
|
|
scope.selected_faces.safePush(face);
|
2021-10-15 17:12:24 +08:00
|
|
|
UVEditor.displayTools();
|
2021-09-08 03:14:33 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
function stop() {
|
|
|
|
removeEventListeners(document, 'mousemove touchmove', drag);
|
|
|
|
removeEventListeners(document, 'mouseup touchend', stop);
|
|
|
|
}
|
|
|
|
addEventListeners(document, 'mousemove touchmove', drag);
|
|
|
|
addEventListeners(document, 'mouseup touchend', stop);
|
|
|
|
}
|
2021-08-26 04:00:08 +08:00
|
|
|
},
|
|
|
|
selectCube(cube, event) {
|
|
|
|
if (!this.dragging_uv) {
|
|
|
|
cube.select(event);
|
|
|
|
}
|
|
|
|
UVEditor.vue.updateTexture()
|
2021-08-19 04:58:47 +08:00
|
|
|
},
|
|
|
|
reverseSelect(event) {
|
2021-09-24 03:35:36 +08:00
|
|
|
if (this.mode !== 'uv') return;
|
2021-08-19 04:58:47 +08:00
|
|
|
var offset = $(this.$refs.frame).offset();
|
|
|
|
event.offsetX = event.clientX - offset.left;
|
|
|
|
event.offsetY = event.clientY - offset.top;
|
2021-09-16 06:49:57 +08:00
|
|
|
if (!this.dragging_uv && !this.selection_rect.active && event.target.id == 'uv_frame') {
|
2021-08-26 04:00:08 +08:00
|
|
|
let results = UVEditor.reverseSelect(event)
|
|
|
|
if (!(results && results.length)) {
|
|
|
|
if (!this.box_uv) {
|
|
|
|
this.selected_faces.empty();
|
|
|
|
}
|
|
|
|
}
|
2021-08-19 04:58:47 +08:00
|
|
|
}
|
|
|
|
},
|
2021-09-21 00:45:02 +08:00
|
|
|
drag({event, onDrag, onEnd, onAbort, snap}) {
|
2021-08-19 04:58:47 +08:00
|
|
|
if (event.which == 2 || event.which == 3) return;
|
2021-10-15 21:14:32 +08:00
|
|
|
convertTouchEvent(event);
|
2021-08-19 04:58:47 +08:00
|
|
|
let scope = this;
|
2021-08-31 06:40:23 +08:00
|
|
|
|
2021-08-19 04:58:47 +08:00
|
|
|
let pos = [0, 0];
|
|
|
|
let last_pos = [0, 0];
|
|
|
|
function drag(e1) {
|
2021-10-15 21:14:32 +08:00
|
|
|
convertTouchEvent(e1);
|
2021-08-19 04:58:47 +08:00
|
|
|
|
2021-09-21 00:45:02 +08:00
|
|
|
if (snap == undefined) {
|
|
|
|
let snap = UVEditor.grid / canvasGridSize(e1.shiftKey || Pressing.overrides.shift, e1.ctrlOrCmd || Pressing.overrides.ctrl);
|
|
|
|
|
|
|
|
let step_x = (scope.inner_width / UVEditor.getResolution(0) / snap);
|
|
|
|
let step_y = (scope.inner_height / UVEditor.getResolution(1) / snap);
|
|
|
|
|
|
|
|
pos[0] = Math.round((e1.clientX - event.clientX) / step_x) / snap;
|
|
|
|
pos[1] = Math.round((e1.clientY - event.clientY) / step_y) / snap;
|
|
|
|
} else {
|
|
|
|
let step_x = snap
|
|
|
|
let step_y = snap
|
2021-09-02 04:13:04 +08:00
|
|
|
|
2021-09-21 00:45:02 +08:00
|
|
|
pos[0] = Math.round((e1.clientX - event.clientX) / step_x) / snap;
|
|
|
|
pos[1] = Math.round((e1.clientY - event.clientY) / step_y) / snap;
|
|
|
|
}
|
2021-08-19 04:58:47 +08:00
|
|
|
|
|
|
|
if (pos[0] != last_pos[0] || pos[1] != last_pos[1]) {
|
2021-09-30 01:34:03 +08:00
|
|
|
let applied_difference = onDrag(pos[0] - last_pos[0], pos[1] - last_pos[1], e1)
|
|
|
|
last_pos[0] += applied_difference[0];
|
|
|
|
last_pos[1] += applied_difference[1];
|
2021-10-15 17:12:24 +08:00
|
|
|
UVEditor.displayTools();
|
2021-09-02 04:13:04 +08:00
|
|
|
UVEditor.loadData();
|
|
|
|
UVEditor.vue.$forceUpdate();
|
|
|
|
Canvas.updateView({elements, element_aspects: {uv: true}});
|
|
|
|
scope.dragging_uv = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function stop() {
|
|
|
|
removeEventListeners(document, 'mousemove touchmove', drag);
|
|
|
|
removeEventListeners(document, 'mouseup touchend', stop);
|
|
|
|
if (scope.dragging_uv) {
|
|
|
|
onEnd();
|
|
|
|
setTimeout(() => scope.dragging_uv = false, 10);
|
|
|
|
} else {
|
2021-09-08 15:55:06 +08:00
|
|
|
if (onAbort) onAbort();
|
2021-09-02 04:13:04 +08:00
|
|
|
Undo.cancelEdit();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
addEventListeners(document, 'mousemove touchmove', drag);
|
|
|
|
addEventListeners(document, 'mouseup touchend', stop);
|
|
|
|
},
|
|
|
|
dragFace(face_key, event) {
|
|
|
|
if (event.which == 2 || event.which == 3) return;
|
|
|
|
|
|
|
|
if (face_key) this.selectFace(face_key, event, true);
|
2021-11-18 02:00:53 +08:00
|
|
|
let elements = UVEditor.getMappableElements();
|
2021-09-02 04:13:04 +08:00
|
|
|
Undo.initEdit({elements, uv_only: true})
|
|
|
|
|
2021-11-18 02:00:53 +08:00
|
|
|
UVEditor.getMappableElements().forEach(el => {
|
2021-09-02 04:13:04 +08:00
|
|
|
if (el instanceof Mesh) {
|
|
|
|
delete Project.selected_vertices[el.uuid];
|
|
|
|
}
|
|
|
|
})
|
2021-08-19 04:58:47 +08:00
|
|
|
|
2021-09-02 04:13:04 +08:00
|
|
|
this.drag({
|
|
|
|
event,
|
|
|
|
onDrag: (diff_x, diff_y) => {
|
2021-09-30 01:34:03 +08:00
|
|
|
elements.forEach(element => {
|
|
|
|
if (element instanceof Mesh) {
|
|
|
|
this.selected_faces.forEach(key => {
|
|
|
|
let face = element.faces[key];
|
|
|
|
if (!face) return;
|
|
|
|
face.vertices.forEach(vertex_key => {
|
|
|
|
diff_x = Math.clamp(diff_x, -face.uv[vertex_key][0], Project.texture_width - face.uv[vertex_key][0]);
|
|
|
|
diff_y = Math.clamp(diff_y, -face.uv[vertex_key][1], Project.texture_height - face.uv[vertex_key][1]);
|
|
|
|
})
|
|
|
|
})
|
|
|
|
} else if (Project.box_uv) {
|
|
|
|
let size = element.size(undefined, true);
|
|
|
|
let uv_size = [
|
|
|
|
size[2] + size[0] + (size[1] ? size[2] : 0) + size[0],
|
|
|
|
size[2] + size[1],
|
|
|
|
]
|
|
|
|
diff_x = Math.clamp(diff_x, -element.uv_offset[0] - (size[1] ? 0 : size[2]), Project.texture_width - element.uv_offset[0] - uv_size[0]);
|
|
|
|
diff_y = Math.clamp(diff_y, -element.uv_offset[1] - (size[0] ? 0 : size[2]), Project.texture_height - element.uv_offset[1] - uv_size[1]);
|
|
|
|
|
|
|
|
} else {
|
|
|
|
this.selected_faces.forEach(key => {
|
|
|
|
if (element.faces[key] && element instanceof Cube) {
|
|
|
|
diff_x = Math.clamp(diff_x, -element.faces[key].uv[0], Project.texture_width - element.faces[key].uv[0]);
|
|
|
|
diff_y = Math.clamp(diff_y, -element.faces[key].uv[1], Project.texture_height - element.faces[key].uv[1]);
|
|
|
|
diff_x = Math.clamp(diff_x, -element.faces[key].uv[2], Project.texture_width - element.faces[key].uv[2]);
|
|
|
|
diff_y = Math.clamp(diff_y, -element.faces[key].uv[3], Project.texture_height - element.faces[key].uv[3]);
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
})
|
2021-08-19 04:58:47 +08:00
|
|
|
elements.forEach(element => {
|
2021-08-22 07:25:50 +08:00
|
|
|
if (element instanceof Mesh) {
|
2021-09-02 04:13:04 +08:00
|
|
|
this.selected_faces.forEach(key => {
|
2021-08-22 07:25:50 +08:00
|
|
|
let face = element.faces[key];
|
|
|
|
if (!face) return;
|
|
|
|
face.vertices.forEach(vertex_key => {
|
2021-09-02 04:13:04 +08:00
|
|
|
face.uv[vertex_key][0] += diff_x;
|
|
|
|
face.uv[vertex_key][1] += diff_y;
|
2021-08-22 07:25:50 +08:00
|
|
|
})
|
|
|
|
})
|
2021-09-02 04:13:04 +08:00
|
|
|
} else if (Project.box_uv) {
|
|
|
|
element.uv_offset[0] += diff_x;
|
|
|
|
element.uv_offset[1] += diff_y;
|
2021-08-22 07:25:50 +08:00
|
|
|
} else {
|
2021-09-02 04:13:04 +08:00
|
|
|
this.selected_faces.forEach(key => {
|
|
|
|
if (element.faces[key] && element instanceof Cube) {
|
|
|
|
element.faces[key].uv[0] += diff_x;
|
|
|
|
element.faces[key].uv[1] += diff_y;
|
|
|
|
element.faces[key].uv[2] += diff_x;
|
|
|
|
element.faces[key].uv[3] += diff_y;
|
|
|
|
}
|
|
|
|
})
|
2021-08-22 07:25:50 +08:00
|
|
|
}
|
2021-08-19 04:58:47 +08:00
|
|
|
})
|
2021-09-30 01:34:03 +08:00
|
|
|
return [diff_x, diff_y]
|
2021-09-02 04:13:04 +08:00
|
|
|
},
|
|
|
|
onEnd: () => {
|
|
|
|
UVEditor.disableAutoUV()
|
|
|
|
Undo.finishEdit('Move UV')
|
2021-08-19 04:58:47 +08:00
|
|
|
}
|
2021-09-02 04:13:04 +08:00
|
|
|
})
|
2021-08-19 04:58:47 +08:00
|
|
|
},
|
2021-08-19 21:18:01 +08:00
|
|
|
resizeFace(face_key, event, x_side, y_side) {
|
|
|
|
if (event.which == 2 || event.which == 3) return;
|
2021-08-26 04:00:08 +08:00
|
|
|
event.stopPropagation();
|
2021-11-18 02:00:53 +08:00
|
|
|
let elements = UVEditor.getMappableElements();
|
2021-08-19 21:18:01 +08:00
|
|
|
Undo.initEdit({elements, uv_only: true})
|
2021-09-13 04:41:10 +08:00
|
|
|
let inverted = {};
|
|
|
|
elements.forEach(element => {
|
|
|
|
let faces = inverted[element.uuid] = {};
|
|
|
|
this.selected_faces.forEach(key => {
|
|
|
|
if (element.faces[key] && element instanceof Cube) {
|
|
|
|
faces[key] = [
|
|
|
|
element.faces[key].uv[0] > element.faces[key].uv[2],
|
|
|
|
element.faces[key].uv[1] > element.faces[key].uv[3],
|
|
|
|
]
|
|
|
|
}
|
|
|
|
})
|
|
|
|
})
|
2021-08-19 21:18:01 +08:00
|
|
|
|
2021-09-02 04:13:04 +08:00
|
|
|
this.drag({
|
|
|
|
event,
|
|
|
|
onDrag: (x, y) => {
|
2021-08-19 21:18:01 +08:00
|
|
|
elements.forEach(element => {
|
2021-09-02 04:13:04 +08:00
|
|
|
this.selected_faces.forEach(key => {
|
|
|
|
if (element.faces[key] && element instanceof Cube) {
|
2021-09-25 20:47:41 +08:00
|
|
|
if (x_side && (x_side == -1) != inverted[element.uuid][key][0]) element.faces[key].uv[0] = Math.clamp(element.faces[key].uv[0] + x, 0, Project.texture_width);
|
|
|
|
if (y_side && (y_side == -1) != inverted[element.uuid][key][1]) element.faces[key].uv[1] = Math.clamp(element.faces[key].uv[1] + y, 0, Project.texture_height);
|
|
|
|
if (x_side && (x_side == 1) != inverted[element.uuid][key][0]) element.faces[key].uv[2] = Math.clamp(element.faces[key].uv[2] + x, 0, Project.texture_width);
|
|
|
|
if (y_side && (y_side == 1) != inverted[element.uuid][key][1]) element.faces[key].uv[3] = Math.clamp(element.faces[key].uv[3] + y, 0, Project.texture_height);
|
2021-09-02 04:13:04 +08:00
|
|
|
}
|
|
|
|
})
|
2021-08-19 21:18:01 +08:00
|
|
|
})
|
2021-09-30 01:34:03 +08:00
|
|
|
return [x, y]
|
2021-09-02 04:13:04 +08:00
|
|
|
},
|
|
|
|
onEnd: () => {
|
|
|
|
UVEditor.disableAutoUV()
|
|
|
|
Undo.finishEdit('Resize UV')
|
2021-08-19 21:18:01 +08:00
|
|
|
}
|
2021-09-02 04:13:04 +08:00
|
|
|
})
|
2021-08-19 21:18:01 +08:00
|
|
|
},
|
2021-09-21 00:45:02 +08:00
|
|
|
rotateFace(face_key, event) {
|
|
|
|
if (event.which == 2 || event.which == 3) return;
|
|
|
|
event.stopPropagation();
|
2021-10-15 21:14:32 +08:00
|
|
|
convertTouchEvent(event);
|
2021-09-21 00:45:02 +08:00
|
|
|
let scope = this;
|
2021-11-18 02:00:53 +08:00
|
|
|
let elements = UVEditor.getMappableElements();
|
2021-09-21 00:45:02 +08:00
|
|
|
Undo.initEdit({elements, uv_only: true})
|
|
|
|
|
|
|
|
let face_center = [0, 0];
|
|
|
|
let points = 0;
|
|
|
|
elements.forEach(element => {
|
|
|
|
this.selected_faces.forEach(fkey => {
|
|
|
|
let face = element.faces[fkey];
|
|
|
|
if (!face) return;
|
|
|
|
if (element instanceof Cube) {
|
|
|
|
face_center[0] += face.uv[0] + face.uv[2];
|
|
|
|
face_center[1] += face.uv[1] + face.uv[3];
|
|
|
|
points += 2;
|
|
|
|
} else if (element instanceof Mesh) {
|
2021-09-25 20:47:41 +08:00
|
|
|
face.old_uv = {};
|
2021-09-21 00:45:02 +08:00
|
|
|
face.vertices.forEach(vkey => {
|
|
|
|
if (!face.uv[vkey]) return;
|
|
|
|
face_center[0] += face.uv[vkey][0];
|
|
|
|
face_center[1] += face.uv[vkey][1];
|
|
|
|
points += 1;
|
2021-09-25 20:47:41 +08:00
|
|
|
face.old_uv[vkey] = face.uv[vkey].slice();
|
2021-09-21 00:45:02 +08:00
|
|
|
})
|
|
|
|
}
|
|
|
|
})
|
|
|
|
})
|
|
|
|
face_center.forEach((v, i) => face_center[i] = v / points);
|
|
|
|
|
|
|
|
let offset = $(UVEditor.vue.$refs.frame).offset();
|
|
|
|
let center_on_screen = [
|
|
|
|
face_center[0] * UVEditor.getPixelSize() + offset.left,
|
|
|
|
face_center[1] * UVEditor.getPixelSize() + offset.top,
|
|
|
|
]
|
|
|
|
|
|
|
|
let angle = 0;
|
|
|
|
let last_angle;
|
2021-09-25 20:47:41 +08:00
|
|
|
let original_angle;
|
|
|
|
let straight_angle;
|
2021-09-21 00:45:02 +08:00
|
|
|
let snap = elements[0] instanceof Cube ? 90 : 1;
|
|
|
|
function drag(e1) {
|
2021-10-15 21:14:32 +08:00
|
|
|
convertTouchEvent(e1);
|
2021-09-21 00:45:02 +08:00
|
|
|
|
|
|
|
angle = Math.atan2(
|
|
|
|
(e1.clientY - center_on_screen[1]),
|
|
|
|
(e1.clientX - center_on_screen[0]),
|
|
|
|
)
|
|
|
|
angle = Math.round(Math.radToDeg(angle) / snap) * snap;
|
2021-09-25 20:47:41 +08:00
|
|
|
if (original_angle == undefined) original_angle = angle;
|
|
|
|
angle -= original_angle;
|
2021-09-21 00:45:02 +08:00
|
|
|
if (last_angle == undefined) last_angle = angle;
|
|
|
|
if (Math.abs(angle - last_angle) > 300) last_angle = angle;
|
|
|
|
|
2021-09-25 20:47:41 +08:00
|
|
|
if (angle != last_angle && (straight_angle == undefined || Math.abs(straight_angle - angle) > 6 || e1.ctrlOrCmd || Pressing.overrides.ctrl)) {
|
2021-09-21 00:45:02 +08:00
|
|
|
|
2021-09-25 20:47:41 +08:00
|
|
|
straight_angle = undefined;
|
|
|
|
scope.helper_lines.x = scope.helper_lines.y = -1;
|
2021-09-21 00:45:02 +08:00
|
|
|
elements.forEach(element => {
|
|
|
|
if (element instanceof Cube && Format.uv_rotation) {
|
|
|
|
scope.selected_faces.forEach(key => {
|
|
|
|
if (element.faces[key]) {
|
|
|
|
element.faces[key].rotation += 90 * Math.sign(last_angle - angle);
|
|
|
|
if (element.faces[key].rotation == 360) element.faces[key].rotation = 0;
|
|
|
|
if (element.faces[key].rotation < 0) element.faces[key].rotation += 360;
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
} else if (element instanceof Mesh) {
|
|
|
|
scope.selected_faces.forEach(fkey => {
|
|
|
|
let face = element.faces[fkey];
|
|
|
|
if (!face) return;
|
|
|
|
face.vertices.forEach(vkey => {
|
|
|
|
if (!face.uv[vkey]) return;
|
2021-09-25 20:47:41 +08:00
|
|
|
let sin = Math.sin(Math.degToRad(angle));
|
|
|
|
let cos = Math.cos(Math.degToRad(angle));
|
|
|
|
face.uv[vkey][0] = face.old_uv[vkey][0] - face_center[0];
|
|
|
|
face.uv[vkey][1] = face.old_uv[vkey][1] - face_center[1];
|
|
|
|
let a = (face.uv[vkey][0] * cos - face.uv[vkey][1] * sin);
|
|
|
|
let b = (face.uv[vkey][0] * sin + face.uv[vkey][1] * cos);
|
|
|
|
face.uv[vkey][0] = Math.clamp(a + face_center[0], 0, Project.texture_width);
|
|
|
|
face.uv[vkey][1] = Math.clamp(b + face_center[1], 0, Project.texture_height);
|
|
|
|
})
|
|
|
|
let e = 0.6;
|
|
|
|
face.vertices.forEach((vkey, i) => {
|
|
|
|
for (let j = i+1; j < face.vertices.length; j++) {
|
|
|
|
let relative_angle = Math.radToDeg(Math.PI + Math.atan2(
|
|
|
|
face.uv[vkey][1] - face.uv[face.vertices[j]][1],
|
|
|
|
face.uv[vkey][0] - face.uv[face.vertices[j]][0],
|
|
|
|
)) % 180;
|
|
|
|
if (Math.abs(relative_angle - 90) < e) {
|
|
|
|
straight_angle = angle;
|
|
|
|
if (scope.helper_lines.x == -1) scope.helper_lines.x = face.uv[vkey][0];
|
|
|
|
}
|
|
|
|
if (relative_angle < e || 180 - relative_angle < e) {
|
|
|
|
straight_angle = angle;
|
|
|
|
if (scope.helper_lines.y == -1) scope.helper_lines.y = face.uv[vkey][1];
|
|
|
|
}
|
|
|
|
}
|
2021-09-21 00:45:02 +08:00
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
})
|
|
|
|
UVEditor.turnMapping()
|
|
|
|
|
|
|
|
last_angle = angle;
|
2021-10-15 17:12:24 +08:00
|
|
|
UVEditor.displayTools();
|
2021-09-21 00:45:02 +08:00
|
|
|
UVEditor.loadData();
|
|
|
|
UVEditor.vue.$forceUpdate();
|
|
|
|
Canvas.updateView({elements, element_aspects: {uv: true}});
|
|
|
|
scope.dragging_uv = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function stop() {
|
|
|
|
removeEventListeners(document, 'mousemove touchmove', drag);
|
|
|
|
removeEventListeners(document, 'mouseup touchend', stop);
|
2021-09-25 20:47:41 +08:00
|
|
|
scope.helper_lines.x = scope.helper_lines.y = -1;
|
2021-09-21 00:45:02 +08:00
|
|
|
if (scope.dragging_uv) {
|
|
|
|
UVEditor.disableAutoUV()
|
|
|
|
Undo.finishEdit('Rotate UV')
|
|
|
|
setTimeout(() => scope.dragging_uv = false, 10);
|
|
|
|
} else {
|
|
|
|
Undo.cancelEdit();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
addEventListeners(document, 'mousemove touchmove', drag);
|
|
|
|
addEventListeners(document, 'mouseup touchend', stop);
|
|
|
|
|
|
|
|
},
|
2021-08-19 21:18:01 +08:00
|
|
|
|
2021-08-21 02:02:28 +08:00
|
|
|
dragVertices(element, vertex_key, event) {
|
|
|
|
if (event.which == 2 || event.which == 3) return;
|
|
|
|
|
|
|
|
if (!this.selected_vertices[element.uuid]) this.selected_vertices[element.uuid] = [];
|
|
|
|
let sel_vertices = this.selected_vertices[element.uuid];
|
2021-09-08 15:55:06 +08:00
|
|
|
let add_to_selection = (event.shiftKey || event.ctrlOrCmd || Pressing.overrides.shift || Pressing.overrides.ctrl);
|
2021-08-21 02:02:28 +08:00
|
|
|
if (sel_vertices.includes(vertex_key)) {
|
|
|
|
|
2021-09-08 15:55:06 +08:00
|
|
|
} else if (add_to_selection) {
|
2021-08-21 02:02:28 +08:00
|
|
|
if (sel_vertices.includes(vertex_key)) {
|
|
|
|
sel_vertices.remove(vertex_key);
|
|
|
|
} else {
|
|
|
|
sel_vertices.push(vertex_key);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
sel_vertices.replace([vertex_key]);
|
|
|
|
}
|
|
|
|
|
2021-11-18 02:00:53 +08:00
|
|
|
let elements = UVEditor.getMappableElements();
|
2021-08-21 02:02:28 +08:00
|
|
|
Undo.initEdit({elements, uv_only: true})
|
|
|
|
|
2021-09-02 04:13:04 +08:00
|
|
|
this.drag({
|
|
|
|
event,
|
2021-09-08 17:00:17 +08:00
|
|
|
onDrag: (x, y, event) => {
|
2021-08-21 02:02:28 +08:00
|
|
|
elements.forEach(element => {
|
2021-09-02 04:13:04 +08:00
|
|
|
this.selected_faces.forEach(key => {
|
2021-08-21 02:02:28 +08:00
|
|
|
let face = element.faces[key];
|
2021-10-09 05:31:17 +08:00
|
|
|
if (!face) return;
|
2021-08-21 02:02:28 +08:00
|
|
|
face.vertices.forEach(vertex_key => {
|
2021-09-02 04:13:04 +08:00
|
|
|
if (this.selected_vertices[element.uuid] && this.selected_vertices[element.uuid].includes(vertex_key)) {
|
2021-09-30 01:34:03 +08:00
|
|
|
x = Math.clamp(x, -face.uv[vertex_key][0], Project.texture_width - face.uv[vertex_key][0]);
|
|
|
|
y = Math.clamp(y, -face.uv[vertex_key][1], Project.texture_width - face.uv[vertex_key][1]);
|
|
|
|
}
|
|
|
|
})
|
|
|
|
})
|
|
|
|
})
|
|
|
|
elements.forEach(element => {
|
|
|
|
this.selected_faces.forEach(key => {
|
|
|
|
let face = element.faces[key];
|
|
|
|
let old_uv_coords = face.vertices.map(vkey => face.uv[vkey].slice())
|
|
|
|
face.vertices.forEach((vertex_key, i) => {
|
|
|
|
if (this.selected_vertices[element.uuid] && this.selected_vertices[element.uuid].includes(vertex_key)) {
|
|
|
|
let is_duplicate = face.vertices.find((vkey2, j) => {
|
|
|
|
return j > i && face.uv[vertex_key].equals(old_uv_coords[j])
|
|
|
|
})
|
|
|
|
if (is_duplicate) {
|
|
|
|
this.selected_vertices[element.uuid].remove(vertex_key);
|
|
|
|
return;
|
|
|
|
}
|
2021-09-02 04:13:04 +08:00
|
|
|
face.uv[vertex_key][0] += x;
|
|
|
|
face.uv[vertex_key][1] += y;
|
2021-09-08 17:00:17 +08:00
|
|
|
if ((event.shiftKey || Pressing.overrides.shift) && !(event.ctrlOrCmd || Pressing.overrides.ctrl)) {
|
|
|
|
let multiplier = settings.shift_size.value / 16
|
|
|
|
face.uv[vertex_key][0] = Math.round(face.uv[vertex_key][0] * multiplier) / multiplier;
|
|
|
|
face.uv[vertex_key][1] = Math.round(face.uv[vertex_key][1] * multiplier) / multiplier;
|
|
|
|
}
|
2021-08-21 02:02:28 +08:00
|
|
|
}
|
|
|
|
})
|
|
|
|
})
|
|
|
|
})
|
2021-09-30 01:34:03 +08:00
|
|
|
return [x, y]
|
2021-09-02 04:13:04 +08:00
|
|
|
},
|
|
|
|
onEnd: () => {
|
2021-08-31 06:40:23 +08:00
|
|
|
Undo.finishEdit('Move UV');
|
2021-09-08 15:55:06 +08:00
|
|
|
},
|
|
|
|
onAbort() {
|
|
|
|
if (!add_to_selection) {
|
|
|
|
sel_vertices.replace([vertex_key]);
|
|
|
|
}
|
2021-08-19 21:18:01 +08:00
|
|
|
}
|
|
|
|
})
|
2021-09-02 04:13:04 +08:00
|
|
|
},
|
2021-08-19 04:58:47 +08:00
|
|
|
|
|
|
|
toPixels(uv_coord, offset = 0) {
|
|
|
|
return (uv_coord / this.project_resolution[0] * this.inner_width + offset) + 'px'
|
2021-08-21 02:02:28 +08:00
|
|
|
},
|
|
|
|
getMeshFaceOutline(face) {
|
|
|
|
let coords = [];
|
|
|
|
let uv_offset = [
|
|
|
|
-this.getMeshFaceCorner(face, 0),
|
|
|
|
-this.getMeshFaceCorner(face, 1),
|
|
|
|
]
|
|
|
|
face.getSortedVertices().forEach(key => {
|
|
|
|
let UV = face.uv[key];
|
|
|
|
coords.push(
|
|
|
|
((UV[0] + uv_offset[0]) / this.project_resolution[0] * this.inner_width + 1) + ',' +
|
|
|
|
((UV[1] + uv_offset[1]) / this.project_resolution[0] * this.inner_width + 1)
|
|
|
|
)
|
|
|
|
})
|
|
|
|
return coords.join(' ');
|
|
|
|
},
|
|
|
|
getMeshFaceCorner(face, axis) {
|
|
|
|
let val = Infinity;
|
|
|
|
face.vertices.forEach(key => {
|
|
|
|
let UV = face.uv[key];
|
|
|
|
val = Math.min(val, UV[axis]);
|
|
|
|
})
|
|
|
|
return val;
|
|
|
|
},
|
|
|
|
getMeshFaceWidth(face, axis) {
|
|
|
|
let min = Infinity;
|
|
|
|
let max = 0;
|
|
|
|
face.vertices.forEach(key => {
|
|
|
|
let UV = face.uv[key];
|
|
|
|
min = Math.min(min, UV[axis]);
|
|
|
|
max = Math.max(max, UV[axis]);
|
|
|
|
})
|
|
|
|
return max - min;
|
2021-08-31 06:40:23 +08:00
|
|
|
},
|
2021-09-16 22:52:21 +08:00
|
|
|
filterMeshFaces(faces) {
|
|
|
|
let keys = Object.keys(faces);
|
|
|
|
if (keys.length > 800) {
|
|
|
|
let result = {};
|
|
|
|
this.selected_faces.forEach(key => {
|
|
|
|
if (faces[key]) result[key] = faces[key];
|
|
|
|
})
|
|
|
|
return result;
|
|
|
|
} else {
|
|
|
|
return faces;
|
|
|
|
}
|
|
|
|
},
|
2021-08-31 06:40:23 +08:00
|
|
|
getBrushOutlineStyle() {
|
|
|
|
if (Toolbox.selected.brushTool) {
|
|
|
|
var pixel_size = this.inner_width / (this.texture ? this.texture.width : Project.texture_width);
|
|
|
|
//pos
|
|
|
|
let offset = BarItems.slider_brush_size.get()%2 == 0 && Toolbox.selected.brushTool ? 0 : 0.5;
|
|
|
|
let left = (this.mouse_coords.x + offset) * pixel_size;
|
|
|
|
let top = (this.mouse_coords.y + offset) * pixel_size;
|
|
|
|
//size
|
|
|
|
var radius = (BarItems.slider_brush_size.get()/2) * pixel_size;
|
|
|
|
return {
|
|
|
|
'--radius': radius,
|
|
|
|
left: left+'px',
|
|
|
|
top: top+'px'
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return {display: 'none'};
|
|
|
|
}
|
2021-08-18 04:02:23 +08:00
|
|
|
}
|
|
|
|
},
|
|
|
|
template: `
|
|
|
|
<div class="UVEditor" ref="main" :class="{checkerboard_trigger: checkerboard}" id="UVEditor">
|
|
|
|
|
|
|
|
<div class="bar next_to_title" id="uv_title_bar">
|
|
|
|
<div id="project_resolution_status" @click="projectResolution()">
|
|
|
|
{{ project_resolution[0] + ' ⨉ ' + project_resolution[1] }}
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
2021-09-08 03:14:33 +08:00
|
|
|
<div class="bar" id="uv_cube_face_bar" v-if="mode != 'properties' && mappable_elements[0] && mappable_elements[0].type == 'cube' && !box_uv">
|
|
|
|
<li v-for="(face, key) in mappable_elements[0].faces" :face="key" :class="{selected: selected_faces.includes(key), disabled: mappable_elements[0].faces[key].texture === null}" @mousedown="selectFace(key, $event, false, true)">
|
2021-08-26 04:00:08 +08:00
|
|
|
{{ face_names[key] }}
|
|
|
|
</li>
|
2021-08-19 21:18:01 +08:00
|
|
|
</div>
|
|
|
|
|
2021-08-19 04:58:47 +08:00
|
|
|
<div id="uv_viewport"
|
|
|
|
@contextmenu="contextMenu($event)"
|
|
|
|
@mousedown="onMouseDown($event)"
|
|
|
|
@touchstart="onMouseDown($event)"
|
|
|
|
@mousewheel="onMouseWheel($event)"
|
2021-08-29 16:44:05 +08:00
|
|
|
@mousemove="updateMouseCoords($event)"
|
2021-09-21 17:20:10 +08:00
|
|
|
@mouseleave="onMouseLeave($event)"
|
2021-08-19 04:58:47 +08:00
|
|
|
class="checkerboard_target"
|
|
|
|
ref="viewport"
|
2021-09-16 22:52:21 +08:00
|
|
|
v-if="!hidden"
|
2021-08-26 04:00:08 +08:00
|
|
|
:style="{width: (width+8) + 'px', height: (height+8) + 'px', overflowX: (zoom > 1) ? 'scroll' : 'hidden', overflowY: (inner_height > height) ? 'scroll' : 'hidden'}"
|
2021-08-19 04:58:47 +08:00
|
|
|
>
|
2021-08-18 04:02:23 +08:00
|
|
|
|
2021-11-24 06:46:57 +08:00
|
|
|
<div id="uv_frame" @click.stop="reverseSelect($event)" ref="frame" :class="{overlay_mode: uv_overlay && mode == 'paint'}" :style="{width: inner_width + 'px', height: inner_height + 'px'}" v-if="texture !== null">
|
2021-08-18 04:02:23 +08:00
|
|
|
|
2021-12-01 00:20:37 +08:00
|
|
|
<template id="uv_allocations" v-if="mode == 'uv' || uv_overlay" v-for="element in ((display_uv === 'all_elements' || mode == 'paint') ? all_mappable_elements : mappable_elements)">
|
2021-08-19 04:58:47 +08:00
|
|
|
|
2021-08-21 02:02:28 +08:00
|
|
|
<template v-if="element.type == 'cube' && !box_uv">
|
2021-08-19 04:58:47 +08:00
|
|
|
<div class="cube_uv_face"
|
2021-09-21 17:20:10 +08:00
|
|
|
v-for="(face, key) in element.faces" :key="element.uuid + ':' + key"
|
2021-10-03 01:51:12 +08:00
|
|
|
v-if="(face.getTexture() == texture || texture == 0) && (display_uv !== 'selected_faces' || selected_faces.includes(key))"
|
2021-08-19 04:58:47 +08:00
|
|
|
:title="face_names[key]"
|
2021-10-03 01:51:12 +08:00
|
|
|
:class="{selected: selected_faces.includes(key), unselected: display_uv === 'all_elements' && !mappable_elements.includes(element)}"
|
2021-08-19 04:58:47 +08:00
|
|
|
@mousedown.prevent="dragFace(key, $event)"
|
2021-10-15 21:14:32 +08:00
|
|
|
@touchstart.prevent="dragFace(key, $event)"
|
|
|
|
@contextmenu="selectFace(key, $event, true, false)"
|
2021-08-19 21:18:01 +08:00
|
|
|
:style="{
|
|
|
|
left: toPixels(Math.min(face.uv[0], face.uv[2]), -1),
|
|
|
|
top: toPixels(Math.min(face.uv[1], face.uv[3]), -1),
|
|
|
|
'--width': toPixels(Math.abs(face.uv_size[0]), 2),
|
|
|
|
'--height': toPixels(Math.abs(face.uv_size[1]), 2),
|
|
|
|
}"
|
2021-08-19 04:58:47 +08:00
|
|
|
>
|
2021-11-24 06:46:57 +08:00
|
|
|
<template v-if="selected_faces.includes(key) && mode == 'uv' && !(display_uv === 'all_elements' && !mappable_elements.includes(element))">
|
2021-08-19 21:18:01 +08:00
|
|
|
{{ face_names[key] || '' }}
|
2021-10-15 21:14:32 +08:00
|
|
|
<div class="uv_resize_side horizontal" @mousedown="resizeFace(key, $event, 0, -1)" @touchstart.prevent="resizeFace(key, $event, 0, -1)" style="width: var(--width)"></div>
|
|
|
|
<div class="uv_resize_side horizontal" @mousedown="resizeFace(key, $event, 0, 1)" @touchstart.prevent="resizeFace(key, $event, 0, 1)" style="top: var(--height); width: var(--width)"></div>
|
|
|
|
<div class="uv_resize_side vertical" @mousedown="resizeFace(key, $event, -1, 0)" @touchstart.prevent="resizeFace(key, $event, -1, 0)" style="height: var(--height)"></div>
|
|
|
|
<div class="uv_resize_side vertical" @mousedown="resizeFace(key, $event, 1, 0)" @touchstart.prevent="resizeFace(key, $event, 1, 0)" style="left: var(--width); height: var(--height)"></div>
|
|
|
|
<div class="uv_resize_corner uv_c_nw" :class="{main_corner: !face.rotation}" @mousedown="resizeFace(key, $event, -1, -1)" @touchstart.prevent="resizeFace(key, $event, -1, -1)" style="left: 0; top: 0">
|
|
|
|
<div class="uv_rotate_field" v-if="!face.rotation" @mousedown.stop="rotateFace(key, $event)" @touchstart.prevent.stop="rotateFace(key, $event)"></div>
|
2021-09-21 00:45:02 +08:00
|
|
|
</div>
|
2021-10-15 21:14:32 +08:00
|
|
|
<div class="uv_resize_corner uv_c_ne" :class="{main_corner: face.rotation == 270}" @mousedown="resizeFace(key, $event, 1, -1)" @touchstart.prevent="resizeFace(key, $event, 1, -1)" style="left: var(--width); top: 0">
|
|
|
|
<div class="uv_rotate_field" v-if="face.rotation == 270" @mousedown.stop="rotateFace(key, $event)" @touchstart.prevent.stop="rotateFace(key, $event)"></div>
|
2021-09-21 00:45:02 +08:00
|
|
|
</div>
|
2021-10-15 21:14:32 +08:00
|
|
|
<div class="uv_resize_corner uv_c_sw" :class="{main_corner: face.rotation == 90}" @mousedown="resizeFace(key, $event, -1, 1)" @touchstart.prevent="resizeFace(key, $event, -1, 1)" style="left: 0; top: var(--height)">
|
|
|
|
<div class="uv_rotate_field" v-if="face.rotation == 90" @mousedown.stop="rotateFace(key, $event)" @touchstart.prevent.stop="rotateFace(key, $event)"></div>
|
2021-09-21 00:45:02 +08:00
|
|
|
</div>
|
2021-10-15 21:14:32 +08:00
|
|
|
<div class="uv_resize_corner uv_c_se" :class="{main_corner: face.rotation == 180}" @mousedown="resizeFace(key, $event, 1, 1)" @touchstart.prevent="resizeFace(key, $event, 1, 1)" style="left: var(--width); top: var(--height)">
|
|
|
|
<div class="uv_rotate_field" v-if="face.rotation == 180" @mousedown.stop="rotateFace(key, $event)" @touchstart.prevent.stop="rotateFace(key, $event)"></div>
|
2021-09-21 00:45:02 +08:00
|
|
|
</div>
|
2021-08-19 21:18:01 +08:00
|
|
|
</template>
|
2021-08-19 04:58:47 +08:00
|
|
|
</div>
|
2021-08-18 04:02:23 +08:00
|
|
|
</template>
|
2021-08-19 04:58:47 +08:00
|
|
|
|
|
|
|
<div v-else-if="element.type == 'cube'" class="cube_box_uv"
|
2021-09-21 17:20:10 +08:00
|
|
|
:key="element.uuid"
|
2021-08-19 04:58:47 +08:00
|
|
|
@mousedown.prevent="dragFace(null, $event)"
|
2021-10-15 21:14:32 +08:00
|
|
|
@touchstart.prevent="dragFace(null, $event)"
|
2021-08-26 04:00:08 +08:00
|
|
|
@click.prevent="selectCube(element, $event)"
|
2021-10-03 01:51:12 +08:00
|
|
|
:class="{unselected: display_uv === 'all_elements' && !mappable_elements.includes(element)}"
|
2021-08-19 04:58:47 +08:00
|
|
|
:style="{left: toPixels(element.uv_offset[0]), top: toPixels(element.uv_offset[1])}"
|
|
|
|
>
|
|
|
|
<div class="uv_fill" :style="{left: '-1px', top: toPixels(element.size(2, true), -1), width: toPixels(element.size(2, true)*2 + element.size(0, true)*2, 2), height: toPixels(element.size(1, true), 2)}" />
|
|
|
|
<div class="uv_fill" :style="{left: toPixels(element.size(2, true), -1), top: '-1px', width: toPixels(element.size(0, true)*2, 2), height: toPixels(element.size(2, true), 2), borderBottom: 'none'}" />
|
|
|
|
<div :style="{left: toPixels(element.size(2, true), -1), top: '-1px', width: toPixels(element.size(0, true), 2), height: toPixels(element.size(2, true) + element.size(1, true), 2)}" />
|
|
|
|
<div :style="{left: toPixels(element.size(2, true)*2 + element.size(0, true), -1), top: toPixels(element.size(2, true), -1), width: toPixels(element.size(0, true), 2), height: toPixels(element.size(1, true), 2)}" />
|
|
|
|
</div>
|
2021-08-21 02:02:28 +08:00
|
|
|
|
|
|
|
<template v-if="element.type == 'mesh'">
|
|
|
|
<div class="mesh_uv_face"
|
2021-09-21 17:20:10 +08:00
|
|
|
v-for="(face, key) in filterMeshFaces(element.faces)" :key="element.uuid + ':' + key"
|
2021-10-03 01:51:12 +08:00
|
|
|
v-if="face.vertices.length > 2 && (display_uv !== 'selected_faces' || selected_faces.includes(key)) && face.getTexture() == texture"
|
2021-08-21 02:02:28 +08:00
|
|
|
:class="{selected: selected_faces.includes(key)}"
|
2021-08-22 07:25:50 +08:00
|
|
|
@mousedown.prevent="dragFace(key, $event)"
|
2021-10-15 21:14:32 +08:00
|
|
|
@touchstart.prevent="dragFace(key, $event)"
|
2021-08-21 02:02:28 +08:00
|
|
|
:style="{
|
|
|
|
left: toPixels(getMeshFaceCorner(face, 0), -1),
|
|
|
|
top: toPixels(getMeshFaceCorner(face, 1), -1),
|
|
|
|
width: toPixels(getMeshFaceWidth(face, 0), 2),
|
|
|
|
height: toPixels(getMeshFaceWidth(face, 1), 2),
|
|
|
|
}"
|
|
|
|
>
|
|
|
|
<svg>
|
|
|
|
<polygon :points="getMeshFaceOutline(face)" />
|
|
|
|
</svg>
|
2021-11-24 06:46:57 +08:00
|
|
|
<template v-if="selected_faces.includes(key) && mode == 'uv'">
|
2021-09-21 00:45:02 +08:00
|
|
|
<div class="uv_mesh_vertex" v-for="(key, index) in face.vertices"
|
|
|
|
:class="{main_corner: index == 0, selected: selected_vertices[element.uuid] && selected_vertices[element.uuid].includes(key)}"
|
2021-10-15 21:14:32 +08:00
|
|
|
@mousedown.prevent.stop="dragVertices(element, key, $event)" @touchstart.prevent.stop="dragVertices(element, key, $event)"
|
2021-08-21 02:02:28 +08:00
|
|
|
:style="{left: toPixels( face.uv[key][0] - getMeshFaceCorner(face, 0) ), top: toPixels( face.uv[key][1] - getMeshFaceCorner(face, 1) )}"
|
2021-09-21 00:45:02 +08:00
|
|
|
>
|
2021-10-15 21:14:32 +08:00
|
|
|
<div class="uv_rotate_field" @mousedown.stop="rotateFace(key, $event)" @touchstart.prevent.stop="rotateFace(key, $event)" v-if="index == 0"></div>
|
2021-09-21 00:45:02 +08:00
|
|
|
</div>
|
2021-08-21 02:02:28 +08:00
|
|
|
</template>
|
|
|
|
</div>
|
|
|
|
</template>
|
|
|
|
|
2021-08-18 04:02:23 +08:00
|
|
|
</template>
|
|
|
|
|
2021-09-16 06:49:57 +08:00
|
|
|
<div class="selection_rectangle"
|
|
|
|
v-if="selection_rect.active"
|
|
|
|
:style="{
|
|
|
|
left: toPixels(selection_rect.pos_x),
|
|
|
|
top: toPixels(selection_rect.pos_y),
|
|
|
|
width: toPixels(selection_rect.width),
|
|
|
|
height: toPixels(selection_rect.height),
|
2021-09-25 20:47:41 +08:00
|
|
|
}">
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div v-if="helper_lines.x >= 0" class="uv_helper_line_x" :style="{left: toPixels(helper_lines.x)}"></div>
|
|
|
|
<div v-if="helper_lines.y >= 0" class="uv_helper_line_y" :style="{top: toPixels(helper_lines.y)}"></div>
|
2021-09-16 06:49:57 +08:00
|
|
|
|
2021-08-31 06:40:23 +08:00
|
|
|
<div id="uv_brush_outline" v-if="mode == 'paint' && mouse_coords.x >= 0" :style="getBrushOutlineStyle()"></div>
|
|
|
|
|
2021-10-23 19:44:43 +08:00
|
|
|
<img :style="{objectFit: texture.frameCount > 1 ? 'cover' : 'fill', objectPosition: \`0 -\${texture.currentFrame * inner_height}px\`}" v-if="texture && texture.error != 1" :src="texture.source">
|
|
|
|
<img style="object-fit: fill; opacity: 0.02; mix-blend-mode: screen;" v-if="texture == 0 && !box_uv" src="./assets/missing_blend.png">
|
2021-08-18 04:02:23 +08:00
|
|
|
</div>
|
2021-08-26 04:00:08 +08:00
|
|
|
|
2021-08-30 16:30:46 +08:00
|
|
|
<div class="uv_transparent_face" v-else-if="selected_faces.length">${tl('uv_editor.transparent_face')}</div>
|
2021-08-18 04:02:23 +08:00
|
|
|
</div>
|
2021-12-14 20:57:49 +08:00
|
|
|
|
2021-08-29 16:44:05 +08:00
|
|
|
<div v-show="mode == 'paint'" class="bar uv_painter_info">
|
2021-12-14 20:57:49 +08:00
|
|
|
<div v-if="copy_overlay.state == 'move'" ref="copy_paste_tool_control" class="copy_paste_tool_control">
|
2021-12-15 22:23:42 +08:00
|
|
|
<div class="tool button_cut" @click="copy_overlay.doCut"><div class="tooltip">${tl('uv_editor.copy_paste_tool.cut')}</div><i class="fa_big icon fa fas fa-cut"></i></div>
|
|
|
|
<div class="tool button_mirror_x" @click="copy_overlay.doMirror_x"><div class="tooltip">${tl('uv_editor.copy_paste_tool.mirror_x')}</div><i class="icon-mirror_x icon"></i></div>
|
|
|
|
<div class="tool button_mirror_y" @click="copy_overlay.doMirror_y"><div class="tooltip">${tl('uv_editor.copy_paste_tool.mirror_y')}</div><i class="icon-mirror_y icon"></i></div>
|
|
|
|
<div class="tool button_rotate" @click="copy_overlay.doRotate"><div class="tooltip">${tl('uv_editor.copy_paste_tool.rotate')}</div><i class="material-icons">rotate_right</i></div>
|
2021-12-14 20:57:49 +08:00
|
|
|
|
2021-12-15 22:23:42 +08:00
|
|
|
<div class="tool button_cancel" @click="copy_overlay.doCancel"><div class="tooltip">${tl('dialog.cancel')}</div><i class="material-icons">clear</i></div>
|
|
|
|
<div class="tool button_place" @click="copy_overlay.doPlace"><div class="tooltip">${tl('uv_editor.copy_paste_tool.place')}</div><i class="material-icons">check</i></div>
|
2021-12-14 20:57:49 +08:00
|
|
|
</div>
|
|
|
|
|
|
|
|
<template v-else>
|
2021-12-15 22:23:42 +08:00
|
|
|
<span v-if="copy_overlay.state == 'select'" style="color: var(--color-subtle_text);">{{ copy_overlay.width + ' ⨉ ' + copy_overlay.height }}</span>
|
|
|
|
<span v-else style="color: var(--color-subtle_text);">{{ mouse_coords.x < 0 ? '-' : (mouse_coords.x + ', ' + mouse_coords.y) }}</span>
|
2021-12-14 20:57:49 +08:00
|
|
|
<span v-if="texture">{{ texture.name }}</span>
|
|
|
|
<span style="color: var(--color-subtle_text);">{{ Math.round(this.zoom*100).toString() + '%' }}</span>
|
|
|
|
</template>
|
|
|
|
|
|
|
|
<div v-show="copy_overlay.state !== 'move'" id="toggle_uv_overlay_anchor"></div>
|
2021-09-07 18:25:41 +08:00
|
|
|
</div>
|
|
|
|
|
|
|
|
<div v-if="mode == 'properties'">
|
|
|
|
|
|
|
|
|
2021-08-18 04:02:23 +08:00
|
|
|
</div>
|
2021-08-29 16:44:05 +08:00
|
|
|
<div v-show="mode == 'uv'" class="bar uv_editor_sliders" ref="slider_bar" style="margin-left: 2px;"></div>
|
|
|
|
<div v-show="mode == 'uv'" class="toolbar_wrapper uv_editor"></div>
|
2021-08-18 04:02:23 +08:00
|
|
|
</div>
|
|
|
|
`
|
2020-09-22 05:23:42 +08:00
|
|
|
}
|
|
|
|
})
|
2021-08-18 04:02:23 +08:00
|
|
|
|
|
|
|
Toolbars.uv_editor.toPlace()
|
|
|
|
|
2021-12-01 00:20:37 +08:00
|
|
|
BarItems.paint_mode_uv_overlay.toElement('#toggle_uv_overlay_anchor');
|
|
|
|
|
2021-08-18 04:02:23 +08:00
|
|
|
let {slider_bar} = UVEditor.vue.$refs;
|
|
|
|
|
|
|
|
var onBefore = function() {
|
2021-09-08 03:14:33 +08:00
|
|
|
Undo.initEdit({elements: UVEditor.getMappableElements()})
|
2021-08-18 04:02:23 +08:00
|
|
|
}
|
|
|
|
var onAfter = function() {
|
|
|
|
Undo.finishEdit('Edit UV')
|
|
|
|
}
|
|
|
|
var getInterval = function(event) {
|
2021-09-08 03:14:33 +08:00
|
|
|
return canvasGridSize(event.shiftKey || Pressing.overrides.shift, event.ctrlOrCmd || Pressing.overrides.ctrl) / UVEditor.grid;
|
2021-08-18 04:02:23 +08:00
|
|
|
}
|
2021-08-30 16:30:46 +08:00
|
|
|
function getPos(axis) {
|
|
|
|
let elements = UVEditor.getMappableElements();
|
|
|
|
if (!elements[0]) return 0;
|
|
|
|
|
|
|
|
if (Project.box_uv && elements[0] instanceof Cube) {
|
|
|
|
return trimFloatNumber(elements[0].uv_offset[axis])
|
|
|
|
} else if (elements[0] instanceof Cube) {
|
|
|
|
var face = UVEditor.getReferenceFace();
|
|
|
|
if (face) {
|
|
|
|
return trimFloatNumber(face.uv[axis])
|
|
|
|
}
|
|
|
|
} else if (elements[0] instanceof Mesh) {
|
|
|
|
var face = UVEditor.getReferenceFace();
|
|
|
|
if (face) {
|
|
|
|
let selected_vertices = Project.selected_vertices[elements[0].uuid];
|
|
|
|
let has_selected_vertices = selected_vertices && face.vertices.find(vkey => selected_vertices.includes(vkey))
|
2021-09-08 03:14:33 +08:00
|
|
|
let min = Infinity;
|
2021-08-30 16:30:46 +08:00
|
|
|
face.vertices.forEach(vkey => {
|
2021-09-07 18:25:41 +08:00
|
|
|
if ((!has_selected_vertices || selected_vertices.includes(vkey)) && face.uv[vkey]) {
|
2021-08-30 16:30:46 +08:00
|
|
|
min = Math.min(min, face.uv[vkey][axis]);
|
|
|
|
}
|
|
|
|
})
|
2021-09-08 03:14:33 +08:00
|
|
|
if (min == Infinity) min = 0;
|
2021-08-30 16:30:46 +08:00
|
|
|
return trimFloatNumber(min)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return 0
|
|
|
|
}
|
2021-08-19 04:58:47 +08:00
|
|
|
UVEditor.sliders.pos_x = new NumSlider({
|
2021-08-18 04:02:23 +08:00
|
|
|
id: 'uv_slider_pos_x',
|
|
|
|
private: true,
|
2021-09-02 04:13:04 +08:00
|
|
|
condition: () => UVEditor.vue.selected_faces.length || Project.box_uv,
|
2021-08-18 04:02:23 +08:00
|
|
|
get: function() {
|
2021-08-30 16:30:46 +08:00
|
|
|
return getPos(0);
|
2021-08-18 04:02:23 +08:00
|
|
|
},
|
|
|
|
change: function(modify) {
|
2021-08-30 16:30:46 +08:00
|
|
|
UVEditor.slidePos(modify, 0);
|
2021-08-18 04:02:23 +08:00
|
|
|
},
|
|
|
|
getInterval,
|
|
|
|
onBefore,
|
|
|
|
onAfter
|
|
|
|
}).toElement(slider_bar);
|
|
|
|
|
2021-08-19 04:58:47 +08:00
|
|
|
UVEditor.sliders.pos_y = new NumSlider({
|
2021-08-18 04:02:23 +08:00
|
|
|
id: 'uv_slider_pos_y',
|
|
|
|
private: true,
|
2021-09-02 04:13:04 +08:00
|
|
|
condition: () => UVEditor.vue.selected_faces.length || Project.box_uv,
|
2021-08-18 04:02:23 +08:00
|
|
|
get: function() {
|
2021-08-30 16:30:46 +08:00
|
|
|
return getPos(1);
|
2021-08-18 04:02:23 +08:00
|
|
|
},
|
|
|
|
change: function(modify) {
|
2021-08-30 16:30:46 +08:00
|
|
|
UVEditor.slidePos(modify, 1);
|
2021-08-18 04:02:23 +08:00
|
|
|
},
|
|
|
|
getInterval,
|
|
|
|
onBefore,
|
|
|
|
onAfter
|
|
|
|
}).toElement(slider_bar);
|
|
|
|
|
2021-08-19 04:58:47 +08:00
|
|
|
UVEditor.sliders.size_x = new NumSlider({
|
2021-08-18 04:02:23 +08:00
|
|
|
id: 'uv_slider_size_x',
|
|
|
|
private: true,
|
2021-10-02 23:06:29 +08:00
|
|
|
condition: () => (!Project.box_uv && UVEditor.vue.selected_faces.length),
|
2021-08-18 04:02:23 +08:00
|
|
|
get: function() {
|
2021-08-26 04:00:08 +08:00
|
|
|
if (!Project.box_uv) {
|
|
|
|
let ref_face = UVEditor.getReferenceFace();
|
2021-08-31 06:40:23 +08:00
|
|
|
if (ref_face instanceof CubeFace) {
|
2021-08-26 04:00:08 +08:00
|
|
|
return trimFloatNumber(ref_face.uv[2] - ref_face.uv[0]);
|
2021-10-02 23:06:29 +08:00
|
|
|
} else if (ref_face instanceof MeshFace) {
|
|
|
|
let rect = ref_face.getBoundingRect();
|
|
|
|
return trimFloatNumber(rect.x);
|
2021-08-18 04:02:23 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return 0
|
|
|
|
},
|
|
|
|
change: function(modify) {
|
|
|
|
UVEditor.slideSize(modify, 0)
|
|
|
|
},
|
|
|
|
getInterval,
|
|
|
|
onBefore,
|
|
|
|
onAfter
|
|
|
|
}).toElement(slider_bar);
|
|
|
|
|
2021-08-19 04:58:47 +08:00
|
|
|
UVEditor.sliders.size_y = new NumSlider({
|
2021-08-18 04:02:23 +08:00
|
|
|
id: 'uv_slider_size_y',
|
|
|
|
private: true,
|
2021-10-02 23:06:29 +08:00
|
|
|
condition: () => (!Project.box_uv && UVEditor.vue.selected_faces.length),
|
2021-08-18 04:02:23 +08:00
|
|
|
get: function() {
|
2021-08-26 04:00:08 +08:00
|
|
|
if (!Project.box_uv) {
|
|
|
|
let ref_face = UVEditor.getReferenceFace();
|
2021-08-31 06:40:23 +08:00
|
|
|
if (ref_face instanceof CubeFace) {
|
2021-08-26 04:00:08 +08:00
|
|
|
return trimFloatNumber(ref_face.uv[3] - ref_face.uv[1]);
|
2021-10-02 23:06:29 +08:00
|
|
|
} else if (ref_face instanceof MeshFace) {
|
|
|
|
let rect = ref_face.getBoundingRect();
|
|
|
|
return trimFloatNumber(rect.y);
|
2021-08-18 04:02:23 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return 0
|
|
|
|
},
|
|
|
|
change: function(modify) {
|
|
|
|
UVEditor.slideSize(modify, 1)
|
|
|
|
},
|
|
|
|
getInterval,
|
|
|
|
onBefore,
|
|
|
|
onAfter
|
|
|
|
|
|
|
|
}).toElement(slider_bar);
|
2020-09-22 05:23:42 +08:00
|
|
|
})
|