Skip to content

Commit

Permalink
Add #715 Move Texture with UV
Browse files Browse the repository at this point in the history
  • Loading branch information
JannisX11 committed Feb 17, 2022
1 parent e7b3ced commit 46be6fa
Show file tree
Hide file tree
Showing 8 changed files with 199 additions and 37 deletions.
8 changes: 8 additions & 0 deletions css/panels.css
Original file line number Diff line number Diff line change
Expand Up @@ -1185,6 +1185,14 @@
mix-blend-mode: difference;
z-index: 1;
}
canvas.move_texture_with_uv {
position: absolute;
pointer-events: none;
width: 100%;
height: 100%;
top: 0;
left: 0;
}

.cube_box_uv {
position: absolute;
Expand Down
4 changes: 4 additions & 0 deletions js/interface/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -1977,6 +1977,7 @@ const BARS = {
Toolbars.uv_editor = new Toolbar({
id: 'uv_editor',
children: [
'uv_apply_all',
'uv_apply_all',
'uv_maximize',
'uv_auto',
Expand All @@ -1993,6 +1994,9 @@ const BARS = {
Toolbars.uv_editor.add(BarItems.uv_mirror_x, -2);
Toolbars.uv_editor.add(BarItems.uv_mirror_y, -2);
})
Blockbench.onUpdateTo('4.1.0-beta.0', () => {
Toolbars.uv_editor.add(BarItems.move_texture_with_uv, 0);
})
//Animations
Toolbars.animations = new Toolbar({
id: 'animations',
Expand Down
67 changes: 67 additions & 0 deletions js/outliner/mesh.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,73 @@ class MeshFace extends Face {
})
return getRectangle(min_x, min_y, max_x, max_y);
}
getOccupationMatrix(texture_space = false, start_offset = [0, 0], matrix = {}) {
let face = this;
let rect = this.getBoundingRect();
let texture = texture_space && this.getTexture();
let sorted_vertices = this.getSortedVertices();
let factor_x = texture ? (texture.width / Project.texture_width) : 1;
let factor_y = texture ? (texture.height / Project.texture_height) : 1;

if (texture_space && texture) {
rect.ax *= factor_x;
rect.ay *= factor_y;
rect.bx *= factor_x;
rect.by *= factor_y;
}
function vSub(a, b) {
return [a[0]-b[0], a[1]-b[1]];
}
function getSide(a, b) {
let cosine_sign = a[0]*b[1] - a[1]*b[0];
if (cosine_sign > 0) return 1;
if (cosine_sign < 0) return -1;
}
function pointInsidePolygon(x, y) {
let previous_side;
let i = 0;
for (let vkey of sorted_vertices) {
let a = face.uv[vkey];
let b = face.uv[sorted_vertices[i+1]] || face.uv[sorted_vertices[0]];
a[0] *= factor_x;
a[1] *= factor_y;
b[0] *= factor_x;
b[1] *= factor_y;

let affine_segment = vSub(b, a);
let affine_point = vSub([x, y], a);
let side = getSide(affine_segment, affine_point);
if (!side) return false;
if (!previous_side) previous_side = side;
if (side !== previous_side) return false;
i++;
}
return true;
}
for (let x = Math.floor(rect.ax); x < Math.ceil(rect.bx); x++) {
for (let y = Math.floor(rect.ay); y < Math.ceil(rect.by); y++) {
let matrix_x = x-start_offset[0];
let matrix_y = y-start_offset[1];

let inside = ( pointInsidePolygon(x+0.00001, y+0.00001)
|| pointInsidePolygon(x+0.99999, y+0.00001)
|| pointInsidePolygon(x+0.00001, y+0.99999)
|| pointInsidePolygon(x+0.99999, y+0.99999));
if (!inside) {
for (let vkey of sorted_vertices) {
if (pointInRectangle(face.uv[vkey], [x, y], [x+0.99999, y+0.99999])) {
inside = true; break;
}
}
}
if (inside) {
if (!matrix[matrix_x]) matrix[matrix_x] = {};
matrix[matrix_x][matrix_y] = true;
}
}
}
return matrix;
}
invert() {
if (this.vertices.length < 3) return this;
[this.vertices[0], this.vertices[1]] = [this.vertices[1], this.vertices[0]];
Expand Down
36 changes: 14 additions & 22 deletions js/texturing/painter.js
Original file line number Diff line number Diff line change
Expand Up @@ -393,9 +393,9 @@ const Painter = {
}

if (element instanceof Cube && fill_mode === 'element') {
ctx.beginPath();
for (var face in element.faces) {
var tag = element.faces[face]
ctx.beginPath();
if (tag.getTexture() === texture) {
var face_rect = getRectangle(
tag.uv[0] * uvFactorX,
Expand All @@ -410,35 +410,27 @@ const Painter = {
Math.ceil(face_rect.bx) - Math.floor(face_rect.ax),
Math.ceil(face_rect.by) - Math.floor(face_rect.ay)
)
ctx.fill()
}
}
ctx.fill()

} else if (element instanceof Mesh && fill_mode === 'element') {
} else if (element instanceof Mesh && (fill_mode === 'element' || fill_mode === 'face')) {
ctx.beginPath();
for (var fkey in element.faces) {
var face = element.faces[fkey];
if (fill_mode === 'face' && fkey !== Painter.current.face) continue;
if (face.vertices.length <= 2 || face.getTexture() !== texture) continue;
ctx.beginPath();

let min_x = Project.texture_width;
let min_y = Project.texture_height;
let max_x = 0;
let max_y = 0;
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]);
})
ctx.rect(
Math.floor(min_x) * uvFactorX,
Math.floor(min_y) * uvFactorY,
(Math.ceil(max_x) - Math.floor(min_x)) * uvFactorX,
(Math.ceil(max_y) - Math.floor(min_y)) * uvFactorY,
)
ctx.fill()
let matrix = face.getOccupationMatrix(true, [0, 0]);
for (let x in matrix) {
for (let y in matrix[x]) {
if (!matrix[x][y]) continue;
x = parseInt(x); y = parseInt(y);
ctx.rect(x, y, 1, 1);
}
}
}
ctx.fill()

} else if (fill_mode === 'face') {
ctx.fill()
Expand Down
13 changes: 1 addition & 12 deletions js/texturing/texture_generator.js
Original file line number Diff line number Diff line change
Expand Up @@ -897,18 +897,7 @@ const TextureGenerator = {
let rect = face.getBoundingRect();
let face_matrix;
if (face instanceof MeshFace) {
let vertex_uv_face = [];
face.getSortedVertices().forEach(vkey => {
vertex_uv_face.push([
face.uv[vkey][0] - rect.ax,
face.uv[vkey][1] - rect.ay
])
})
face_matrix = getPolygonOccupationMatrix(
[vertex_uv_face],
Math.ceil(rect.bx) - Math.floor(rect.ax),
Math.ceil(rect.by) - Math.floor(rect.ay)
);
face_matrix = face.getOccupationMatrix(false, [Math.floor(rect.ax), Math.floor(rect.ay)])
}

for (let x = Math.floor(rect.ax); x < Math.ceil(rect.bx); x++) {
Expand Down
104 changes: 102 additions & 2 deletions js/texturing/uv.js
Original file line number Diff line number Diff line change
Expand Up @@ -1590,6 +1590,11 @@ BARS.defineActions(function() {
UVEditor.vue.uv_overlay = value;
}
})
new Toggle('move_texture_with_uv', {
icon: 'fas.fa-link',
category: 'uv',
condition: {modes: ['edit']}
})
})

Interface.definePanels(function() {
Expand Down Expand Up @@ -2087,14 +2092,87 @@ Interface.definePanels(function() {

if (face_key) this.selectFace(face_key, event, true);
let elements = UVEditor.getMappableElements();
Undo.initEdit({elements, uv_only: true})
Undo.initEdit({
elements,
uv_only: true,

});
let total_diff = [0, 0];
let do_move_uv = !!(BarItems.move_texture_with_uv.value && this.texture);

UVEditor.getMappableElements().forEach(el => {
if (el instanceof Mesh) {
delete Project.selected_vertices[el.uuid];
}
})

let overlay_canvas;
if (do_move_uv) {
Undo.initEdit({
elements,
uv_only: true,
bitmap: true,
textures: [this.texture]
});

overlay_canvas = Interface.createElement('canvas', {class: 'move_texture_with_uv'});
let ctx = overlay_canvas.getContext('2d');
overlay_canvas.width = this.texture.width;
overlay_canvas.height = this.texture.height;

this.texture.edit(canvas => {
let tex_ctx = canvas.getContext('2d');
ctx.beginPath();
tex_ctx.beginPath();
UVEditor.getMappableElements().forEach(el => {
if (el instanceof Mesh) {
for (var fkey in el.faces) {
var face = el.faces[fkey];
if (!this.selected_faces.includes(fkey)) continue;
if (face.vertices.length <= 2 || face.getTexture() !== this.texture) continue;

let matrix = face.getOccupationMatrix(true, [0, 0]);
for (let x in matrix) {
for (let y in matrix[x]) {
if (!matrix[x][y]) continue;
x = parseInt(x); y = parseInt(y);
ctx.rect(x, y, 1, 1);
tex_ctx.rect(x, y, 1, 1);
}
}
}
} else {
let factor_x = this.texture.width / Project.texture_width;
let factor_y = this.texture.height / Project.texture_height;
for (var fkey in el.faces) {
var face = el.faces[fkey];
if (!this.selected_faces.includes(fkey) && !this.box_uv) continue;
if (face.getTexture() !== this.texture) continue;

let rect = face.getBoundingRect();
let canvasRect = [
Math.floor(rect.ax * factor_x),
Math.floor(rect.ay * factor_y),
Math.ceil(rect.bx * factor_x) - Math.floor(rect.ax * factor_x),
Math.ceil(rect.by * factor_y) - Math.floor(rect.ay * factor_y),
]
ctx.rect(...canvasRect);
tex_ctx.rect(...canvasRect);
}
}
})
ctx.clip();
ctx.drawImage(this.texture.img, 0, 0);
tex_ctx.clip();
tex_ctx.clearRect(0, 0, canvas.width, canvas.height);
}, {no_undo: true})

UVEditor.vue.$refs.frame.append(overlay_canvas);

} else {
Undo.initEdit({elements, uv_only: true});
}

this.drag({
event,
onDrag: (diff_x, diff_y) => {
Expand Down Expand Up @@ -2152,11 +2230,33 @@ Interface.definePanels(function() {
})
}
})
if (do_move_uv) {
total_diff[0] += diff_x;
total_diff[1] += diff_y;
overlay_canvas.style.left = this.toPixels(total_diff[0]);
overlay_canvas.style.top = this.toPixels(total_diff[1]);
}
return [diff_x, diff_y]
},
onEnd: () => {
UVEditor.disableAutoUV()
Undo.finishEdit('Move UV')
if (do_move_uv) {
this.texture.edit((canvas) => {
canvas.getContext('2d').drawImage(
overlay_canvas,
total_diff[0] * this.texture.width / Project.texture_width,
total_diff[1] * this.texture.height / Project.texture_height
);
}, {no_undo: true})
overlay_canvas.remove();
Canvas.updateView({elements, element_aspects: {uv: true}});
}
Undo.finishEdit('Move UV');
},
onAbort: () => {
if (do_move_uv) {
overlay_canvas.remove();
}
}
})
},
Expand Down
2 changes: 1 addition & 1 deletion js/webpack/bundle.js

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1253,6 +1253,8 @@
"action.paint_mode_uv_overlay.desc": "Display the UV map as an overlay in paint mode",
"action.remove_blank_faces": "Remove Blank Faces",
"action.remove_blank_faces.desc": "Deletes all untextured faces of the selection",
"action.move_texture_with_uv": "Move Texture with UV",
"action.move_texture_with_uv.desc": "Move the texture of the face along when dragging UV faces",

"action.add_animation": "Add Animation",
"action.add_animation.desc": "Create a blank animation",
Expand Down

0 comments on commit 46be6fa

Please sign in to comment.