Skip to content

Commit 4cb4e9c

Browse files
committed
Screen space brush: Line support and pixel density
1 parent 92fd4c9 commit 4cb4e9c

3 files changed

Lines changed: 122 additions & 65 deletions

File tree

js/preview/preview.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1288,8 +1288,14 @@ export class Preview {
12881288
Painter.screen_space_brush_cursor.style.top = event.offsetY + 'px';
12891289
this.node.append(Painter.screen_space_brush_cursor);
12901290
if (data && data.face) {
1291-
let r = Math.round(BarItems.slider_brush_size.get()+1)/2;
1292-
let screen_radius = 10 * r / this.calculateControlScale(data.intersects[0].point);
1291+
let pixel_density = 1;
1292+
let face = data.element.faces[data.face];
1293+
let texture = face?.getTexture();
1294+
if (texture) {
1295+
pixel_density = texture.width/texture.getUVWidth();
1296+
}
1297+
let r = BarItems.slider_brush_size.get()/2;
1298+
let screen_radius = (13.4 / this.calculateControlScale(data.intersects[0].point)) * (r / pixel_density);
12931299
Painter.screen_space_brush_cursor.style.setProperty('--radius', screen_radius);
12941300
}
12951301

js/texturing/painter.js

Lines changed: 113 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ export const Painter = {
120120
}
121121
let [x, y] = Painter.getCanvasToolPixelCoords(data.intersects[0].uv, texture);
122122
UVEditor.vue.texture = texture;
123+
Painter.current.control_scale = Preview.selected.calculateControlScale(data.intersects[0].point);
123124

124125
Painter.startPaintTool(texture, x, y, data.element.faces[data.face].uv, e, data)
125126

@@ -136,6 +137,7 @@ export const Painter = {
136137

137138
let new_face;
138139
let [x, y] = Painter.getCanvasToolPixelCoords(data.intersects[0].uv, texture);
140+
Painter.current.control_scale = Preview.selected.calculateControlScale(data.intersects[0].point);
139141

140142
let interval = Toolbox.selected.brush?.interval || 1;
141143
let delta = [x - Painter.current.x, y - Painter.current.y];
@@ -159,6 +161,8 @@ export const Painter = {
159161
Painter.current.y = y
160162
Painter.current.face = data.face
161163
Painter.current.element = data.element
164+
Painter.current.client_mouse_x = event.clientX;
165+
Painter.current.client_mouse_y = event.clientY;
162166
new_face = true
163167
UVEditor.vue.texture = texture;
164168
if (texture !== Painter.current.texture && Undo.current_save) {
@@ -223,6 +227,7 @@ export const Painter = {
223227
}
224228
Undo.initEdit(undo_aspects);
225229
Painter.current.start_event = event;
230+
Painter.current.use_screen_projection = !!data && Toolbox.selected.brush?.screen_space && BarItems.screen_space_brush_projection.value;
226231
Painter.brushChanges = false;
227232

228233
if (Toolbox.selected.id === 'draw_shape_tool' || Toolbox.selected.id === 'gradient_tool') {
@@ -242,30 +247,34 @@ export const Painter = {
242247
} else {
243248
Painter.current.face_matrices = {};
244249

245-
let is_line
250+
let is_line = (event.shiftKey || Pressing.overrides.shift);
251+
// Viewport only
246252
if (data) {
247-
is_line = (event.shiftKey || Pressing.overrides.shift)
248-
&& Painter.current.element == data.element
249-
&& (Painter.current.face == data.face ||
253+
if (is_line && !Painter.current.use_screen_projection) {
254+
// Check if on same face or UV island
255+
is_line = Painter.current.element == data.element
256+
&& (Painter.current.face == data.face ||
250257
(data.element.faces[data.face] instanceof MeshFace && Painter.getMeshUVIsland(data.face, data.element.faces[data.face]).includes(Painter.current.face))
251258
)
259+
}
252260
Painter.current.element = data.element;
253261
Painter.current.face = data.face;
254-
} else {
255-
//uv editor
256-
is_line = (event.shiftKey || Pressing.overrides.shift);
257262
}
258263
if (Toolbox.selected.brush?.line == false) is_line = false;
259264

260265
texture.edit(canvas => {
261266
if (is_line) {
262267
Painter.drawBrushLine(texture, x, y, event, false, uvTag);
263268
} else {
269+
Painter.current.client_mouse_x = event.clientX;
270+
Painter.current.client_mouse_y = event.clientY;
264271
Painter.current.x = Painter.current.y = 0
265272
Painter.useBrushlike(texture, x, y, event, uvTag)
266273
}
267274
Painter.current.x = x;
268275
Painter.current.y = y;
276+
Painter.current.client_mouse_x = event.clientX;
277+
Painter.current.client_mouse_y = event.clientY;
269278
}, {no_undo: true, use_cache: true});
270279
}
271280
},
@@ -298,12 +307,16 @@ export const Painter = {
298307
Painter.drawBrushLine(texture, x, y, event, new_face, uv);
299308
} else {
300309
Painter.current.x = Painter.current.y = 0;
310+
delete Painter.current.client_mouse_x;
311+
delete Painter.current.client_mouse_y;
301312
Painter.useBrushlike(texture, x, y, event, uv)
302313
}
303314
}, {no_undo: true, use_cache: true});
304315
}
305316
Painter.current.x = x;
306317
Painter.current.y = y;
318+
Painter.current.client_mouse_x = event.clientX;
319+
Painter.current.client_mouse_y = event.clientY;
307320
},
308321
stopPaintTool() {
309322
PointerTarget.endTarget();
@@ -407,15 +420,15 @@ export const Painter = {
407420
return rect;
408421
},
409422
useBrushlike(texture, x, y, event, uvTag, no_update, is_opposite) {
410-
if (Painter.currentPixel[0] === x && Painter.currentPixel[1] === y) return;
423+
let use_screen_projection = Painter.current.use_screen_projection;
424+
if (Painter.currentPixel[0] === x && Painter.currentPixel[1] === y && !use_screen_projection) return;
411425
Painter.currentPixel = [x, y];
412426
Painter.brushChanges = true;
413427
if (!is_opposite) {
414428
UVEditor.vue.last_brush_position.V2_set(x, y);
415429
}
416430
let uvFactorX = texture.width / texture.getUVWidth();
417431
let uvFactorY = texture.display_height / texture.getUVHeight();
418-
let use_screen_projection = Toolbox.selected.brush?.screen_space && BarItems.screen_space_brush_projection.value;
419432

420433
if (Painter.mirror_painting && !is_opposite && !use_screen_projection) {
421434
let targets = Painter.getMirrorPaintTargets(texture, x, y, uvTag);
@@ -526,13 +539,14 @@ export const Painter = {
526539
return tool.brush.changePixel(px, py, pxcolor, local_opacity, {color, opacity: b_opacity, max_opacity, ctx, x, y, size, softness, texture, event});
527540
}
528541

529-
let use_screen_projection = Toolbox.selected.brush?.screen_space && BarItems.screen_space_brush_projection.value;
530-
if (use_screen_projection) {
542+
if (Painter.current.use_screen_projection) {
531543
Painter.projectScreenSpaceBrush(ctx, {
532544
x, y, size, texture,
533545
softness: softness * 1.8,
534546
shape,
535547
event,
548+
client_x: Painter.current.client_mouse_x ?? event.clientX,
549+
client_y: Painter.current.client_mouse_y ?? event.clientY,
536550
}, run_per_pixel);
537551
} else {
538552
if (shape == 'square') {
@@ -928,57 +942,93 @@ export const Painter = {
928942
return targets;
929943
},
930944
drawBrushLine(texture, end_x, end_y, event, new_face, uv) {
931-
// TODO: Support screen space brush
932-
var start_x = (Painter.current.x == undefined ? end_x : Painter.current.x);
933-
var start_y = (Painter.current.y == undefined ? end_y : Painter.current.y);
934-
935-
var diff_x = end_x - start_x;
936-
var diff_y = end_y - start_y;
937-
938-
var length = Math.sqrt(diff_x*diff_x + diff_y*diff_y)
939-
940-
if (new_face && !length) {
941-
length = 1
942-
}
943945
let interval = Toolbox.selected.brush?.interval || 1;
944-
var i = Math.min(interval, length);
945-
var x, y;
946946
let {ctx, offset} = Painter.current;
947-
if (interval == 1) {
948-
if (Math.abs(diff_x) > Math.abs(diff_y)) {
949-
interval = Math.sqrt(Math.pow(diff_y/diff_x, 2) + 1)
950-
} else {
951-
interval = Math.sqrt(Math.pow(diff_x/diff_y, 2) + 1)
947+
948+
if (!Painter.current.use_screen_projection) {
949+
var start_x = (Painter.current.x == undefined ? end_x : Painter.current.x);
950+
var start_y = (Painter.current.y == undefined ? end_y : Painter.current.y);
951+
952+
var diff_x = end_x - start_x;
953+
var diff_y = end_y - start_y;
954+
955+
var length = Math.sqrt(diff_x*diff_x + diff_y*diff_y)
956+
957+
if (new_face && !length) {
958+
length = 1
959+
}
960+
var x, y;
961+
var i = Math.min(interval, length);
962+
if (interval == 1) {
963+
if (Math.abs(diff_x) > Math.abs(diff_y)) {
964+
interval = Math.sqrt(Math.pow(diff_y/diff_x, 2) + 1)
965+
} else {
966+
interval = Math.sqrt(Math.pow(diff_x/diff_y, 2) + 1)
967+
}
968+
}
969+
970+
if (Toolbox.selected.brush?.pixel_perfect && BarItems.pixel_perfect_drawing.value && BarItems.slider_brush_size.get() == 1) {
971+
let direction = 0;
972+
if (length == 1 && diff_x && !diff_y) {direction = 1;}
973+
if (length == 1 && !diff_x && diff_y) {direction = 2;}
974+
let image_data = ctx.getImageData(end_x - offset[0], end_y - offset[1], 1, 1);
975+
let pixel = {
976+
direction,
977+
image_data,
978+
position: [end_x - offset[0], end_y - offset[1]]
979+
};
980+
if (length == 1 && Painter.current.last_pixel && Painter.current.last_pixel.direction && direction && Painter.current.last_pixel.direction != direction) {
981+
ctx.putImageData(Painter.current.last_pixel.image_data, ...Painter.current.last_pixel.position);
982+
delete Painter.current.last_pixel;
983+
} else {
984+
Painter.current.last_pixel = pixel;
985+
}
952986
}
953-
}
954987

955-
if (Toolbox.selected.brush?.pixel_perfect && BarItems.pixel_perfect_drawing.value && BarItems.slider_brush_size.get() == 1) {
956-
let direction = 0;
957-
if (length == 1 && diff_x && !diff_y) {direction = 1;}
958-
if (length == 1 && !diff_x && diff_y) {direction = 2;}
959-
let image_data = ctx.getImageData(end_x - offset[0], end_y - offset[1], 1, 1);
960-
let pixel = {
961-
direction,
962-
image_data,
963-
position: [end_x - offset[0], end_y - offset[1]]
964-
};
965-
if (length == 1 && Painter.current.last_pixel && Painter.current.last_pixel.direction && direction && Painter.current.last_pixel.direction != direction) {
966-
ctx.putImageData(Painter.current.last_pixel.image_data, ...Painter.current.last_pixel.position);
967-
delete Painter.current.last_pixel;
968-
} else {
969-
Painter.current.last_pixel = pixel;
988+
while (i <= length+0.001) {
989+
x = length ? (start_x + diff_x / length * i) : end_x;
990+
y = length ? (start_y + diff_y / length * i) : end_y;
991+
if (!Toolbox.selected.brush || Condition(Toolbox.selected.brush.floor_coordinates)) {
992+
x = Math.round(x);
993+
y = Math.round(y);
994+
}
995+
Painter.useBrushlike(texture, x, y, event, uv, i < length-1);
996+
i += interval;
970997
}
971-
}
998+
999+
} else {
1000+
let pixel_density = texture.width/texture.getUVWidth();
1001+
const screen_pixel_size = (10 / Painter.current.control_scale) / pixel_density;
1002+
var start_x = (Painter.current.client_mouse_x ?? event.clientX);
1003+
var start_y = (Painter.current.client_mouse_y ?? event.clientY);
1004+
1005+
var diff_x = event.clientX - start_x;
1006+
var diff_y = event.clientY - start_y;
9721007

973-
while (i <= length+0.001) {
974-
x = length ? (start_x + diff_x / length * i) : end_x;
975-
y = length ? (start_y + diff_y / length * i) : end_y;
976-
if (!Toolbox.selected.brush || Condition(Toolbox.selected.brush.floor_coordinates)) {
977-
x = Math.round(x);
978-
y = Math.round(y);
1008+
var length = Math.sqrt(diff_x**2 + diff_y**2) / screen_pixel_size;
1009+
1010+
if (new_face && !length) {
1011+
length = 1
1012+
}
1013+
if (!length) {
1014+
Painter.useBrushlike(texture, end_x, end_y, event, uv, i < length-1);
1015+
return;
1016+
}
1017+
var i = Math.min(interval, length);
1018+
if (interval == 1) {
1019+
if (Math.abs(diff_x) > Math.abs(diff_y)) {
1020+
interval = Math.sqrt(Math.pow(diff_y/diff_x, 2) + 1)
1021+
} else {
1022+
interval = Math.sqrt(Math.pow(diff_x/diff_y, 2) + 1)
1023+
}
1024+
}
1025+
1026+
while (i <= length+0.001) {
1027+
Painter.current.client_mouse_x = start_x + ((diff_x / length) * i);
1028+
Painter.current.client_mouse_y = start_y + ((diff_y / length) * i);
1029+
Painter.useBrushlike(texture, end_x, end_y, event, uv, i < length-1);
1030+
i += interval;
9791031
}
980-
Painter.useBrushlike(texture, x, y, event, uv, i < length-1);
981-
i += interval;
9821032
}
9831033
},
9841034
useShapeTool(texture, x, y, event, uvTag) {
@@ -1692,14 +1742,15 @@ export const Painter = {
16921742
projectScreenSpaceBrush(ctx, args, editPx) {
16931743
let texture = args.texture;
16941744
let preview = Preview.selected;
1695-
let r = Math.round(args.size+1)/2;
1745+
let r = args.size/2;
16961746
let selection = Painter.current.texture.selection;
1747+
let pixel_density = texture.width/texture.getUVWidth();
16971748
const bounds = [texture.width, texture.height, 0, 0];
16981749

16991750
let canvas_offset = preview.canvas.getBoundingClientRect();
17001751
let mouse_canvas_offset = [
1701-
args.event.clientX - canvas_offset.left,
1702-
args.event.clientY - canvas_offset.top
1752+
args.client_x - canvas_offset.left,
1753+
args.client_y - canvas_offset.top
17031754
]
17041755
function filterObjects(elements) {
17051756
let objects = [];
@@ -1720,13 +1771,11 @@ export const Painter = {
17201771
preview.mouse.x = (mouse_canvas_offset[0] / preview.width) * 2 - 1;
17211772
preview.mouse.y = - (mouse_canvas_offset[1] / preview.height) * 2 + 1;
17221773
preview.raycaster.setFromCamera( preview.mouse, preview.camera );
1723-
let intersect = preview.raycaster.intersectObjects(objects, false)[0];
1724-
if (!intersect) return;
1725-
const screen_radius = 10 * r / preview.calculateControlScale(intersect.point);
1774+
const screen_radius = (13.4 / Painter.current.control_scale) * (r / pixel_density);
17261775

17271776
// Calculate sampling resolution
17281777
let rough_samples = r * 0.6;
1729-
let samples = r * 5;
1778+
let samples = r * 4;
17301779

17311780
const pixel_intensities = {};
17321781
const pixel_hits = {};
@@ -2703,6 +2752,7 @@ BARS.defineActions(function() {
27032752
softness: true,
27042753
opacity: true,
27052754
offset_even_radius: true,
2755+
screen_space: true,
27062756
onStrokeStart({texture, event, x, y, raycast_data}) {
27072757
if (event.ctrlOrCmd || Pressing.overrides.ctrl) {
27082758
let size = BarItems.slider_brush_size.get();
@@ -2844,6 +2894,7 @@ BARS.defineActions(function() {
28442894
size: true,
28452895
softness: true,
28462896
opacity: true,
2897+
screen_space: true,
28472898
offset_even_radius: true,
28482899
floor_coordinates: () => BarItems.slider_brush_softness.get() == 0,
28492900
get interval() {

js/uv/uv.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5053,7 +5053,7 @@ Interface.definePanels(function() {
50535053
<div v-if="helper_lines.x >= 0" class="uv_helper_line_x" :style="{left: toPixels(helper_lines.x)}"></div>
50545054
<div v-if="helper_lines.y >= 0" class="uv_helper_line_y" :style="{top: toPixels(helper_lines.y)}"></div>
50555055
5056-
<div id="uv_brush_outline" v-if="mode == 'paint' && mouse_coords.active" :class="brush_type" :style="getBrushOutlineStyle()">
5056+
<div id="uv_brush_outline" v-if="mode == 'paint' && mouse_coords.active" :class="['brush_outline', brush_type]" :style="getBrushOutlineStyle()">
50575057
<div v-if="mouse_coords.line_preview" id="uv_brush_line_preview" :style="getLinePreviewStyle()"></div>
50585058
</div>
50595059

0 commit comments

Comments
 (0)