Skip to content

Commit 5de5c2b

Browse files
authored
Merge pull request #3549 from JannisX11/projected-brush
Screen Space-projected brush
2 parents 08bf458 + 2694baf commit 5de5c2b

8 files changed

Lines changed: 335 additions & 69 deletions

File tree

css/panels.css

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2454,7 +2454,7 @@ span.controller_state_section_info {
24542454
#uv_texture_grid path.bold_grid {
24552455
stroke-width: 0.86px;
24562456
}
2457-
div#uv_brush_outline {
2457+
div.brush_outline {
24582458
border: 2px solid white;
24592459
width: calc(var(--radius) * 2px);
24602460
height: calc(var(--radius) * 2px);
@@ -2465,7 +2465,7 @@ span.controller_state_section_info {
24652465
mix-blend-mode: difference;
24662466
z-index: 1;
24672467
}
2468-
div#uv_brush_outline.circle {
2468+
div.brush_outline.circle {
24692469
border-radius: 50%;
24702470
}
24712471
div#uv_copy_brush_outline {

js/interface/actions.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -651,6 +651,10 @@ export interface BrushOptions {
651651
* Enable the input for opacity when this tool is selected
652652
*/
653653
opacity: boolean
654+
/**
655+
* Enable the toggle for screen space when this tool is selected
656+
*/
657+
screen_space: boolean
654658
/**
655659
* When the brush size is an even number, offset the snapping by half a pixel so that even size brush strokes can be correctly centered
656660
*/

js/interface/setup_settings.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,7 @@ function setupSettings() {
208208
BarItems.layer_opacity.update();
209209
}});
210210
new Setting('limit_brush_opacity_per_stroke', {category: 'paint', value: true});
211+
new Setting('projected_brush_sample_rate', {category: 'paint', type: 'number', value: 4, min: 2});
211212
new Setting('paint_with_stylus_only', {category: 'paint', value: false});
212213
new Setting('brush_opacity_modifier', {category: 'paint', value: 'none', type: 'select', options: {
213214
'pressure': tl('settings.brush_modifier.pressure'),

js/preview/preview.ts

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -469,6 +469,12 @@ export class Preview {
469469
addEventListeners(this.canvas, 'mouseenter', (event: MouseEvent) => {
470470
this.controls.hasMoved = true
471471
}, false);
472+
addEventListeners(this.canvas, 'mouseleave', (event: MouseEvent) => {
473+
if (Painter.screen_space_brush_cursor) {
474+
Painter.screen_space_brush_cursor.remove();
475+
delete Painter.screen_space_brush_cursor;
476+
}
477+
}, false)
472478

473479
Preview.all.push(this);
474480
}
@@ -1340,10 +1346,13 @@ export class Preview {
13401346
Canvas.updateCubeHighlights(data && data.element);
13411347
}
13421348

1349+
let brush_cursor_3d = Toolbox.selected.brush?.size && Settings.get('brush_cursor_3d');
1350+
let use_screen_projection = Toolbox.selected.brush?.screen_space && (BarItems.screen_space_brush_projection as Toggle).value;
1351+
13431352
brush_cursor:
1344-
if (Toolbox.selected.brush?.size && Settings.get('brush_cursor_3d')) {
1353+
if (brush_cursor_3d && !use_screen_projection) {
13451354
if (!data) {
1346-
scene.remove(Canvas.brush_outline);
1355+
Canvas.scene.remove(Canvas.brush_outline);
13471356
break brush_cursor;
13481357
}
13491358
if (!data.element.faces) break brush_cursor;
@@ -1352,10 +1361,10 @@ export class Preview {
13521361
if ('texelToLocalMatrix' in face == false) return;
13531362
let texture = face.getTexture();
13541363
if (!texture) {
1355-
scene.remove(Canvas.brush_outline);
1364+
Canvas.scene.remove(Canvas.brush_outline);
13561365
break brush_cursor;
13571366
}
1358-
scene.add(Canvas.brush_outline);
1367+
Canvas.scene.add(Canvas.brush_outline);
13591368

13601369
let intersect = data.intersects[0];
13611370
let world_quaternion = intersect.object.getWorldQuaternion(Reusable.quat1)
@@ -1412,11 +1421,39 @@ export class Preview {
14121421
}
14131422
Canvas.brush_outline.matrix = brush_matrix;
14141423
}
1424+
1425+
screen_space_brush_cursor:
1426+
if (brush_cursor_3d && use_screen_projection) {
1427+
Painter.screen_space_brush_cursor ??= Interface.createElement('div', {
1428+
id: 'viewport_brush_outline',
1429+
class: 'brush_outline'
1430+
});
1431+
Painter.screen_space_brush_cursor.classList.toggle('circle', BarItems.brush_shape.value == 'circle');
1432+
Painter.screen_space_brush_cursor.style.left = event.offsetX + 'px';
1433+
Painter.screen_space_brush_cursor.style.top = event.offsetY + 'px';
1434+
this.node.append(Painter.screen_space_brush_cursor);
1435+
if (data && data.face) {
1436+
let pixel_density = 1;
1437+
let face = data.element.faces[data.face];
1438+
let texture = face?.getTexture();
1439+
if (texture) {
1440+
pixel_density = texture.width/texture.getUVWidth();
1441+
}
1442+
let r = BarItems.slider_brush_size.get()/2;
1443+
let screen_radius = (13.4 / this.calculateControlScale(data.intersects[0].point)) * (r / pixel_density);
1444+
Painter.screen_space_brush_cursor.style.setProperty('--radius', screen_radius.toString());
1445+
}
1446+
1447+
} else if (Painter.screen_space_brush_cursor) {
1448+
Painter.screen_space_brush_cursor.remove();
1449+
delete Painter.screen_space_brush_cursor;
1450+
}
14151451

14161452
if (Toolbox.selected.onCanvasMouseMove) {
14171453
if (!data) data = this.raycast(event);
14181454
Toolbox.selected.onCanvasMouseMove(data);
14191455
}
1456+
// Hover helpers
14201457
if (Condition(BarItems.selection_mode.condition) && Mesh.hasAny() && data && data.element instanceof Mesh) {
14211458
if (BarItems.selection_mode.value == 'edge' && data.type == 'line' && data.vertices) {
14221459
let pos_1 = Reusable.vec1.fromArray(data.element.vertices[data.vertices[0]]);

0 commit comments

Comments
 (0)