From 946d7f4c6484ba6ef063903f123c33e206fd9544 Mon Sep 17 00:00:00 2001 From: JannisX11 Date: Mon, 30 Dec 2024 00:39:10 +0100 Subject: [PATCH] Center on Selection: Optional zoom modifier Closes #1447 --- js/preview/canvas.js | 14 +++++++++++ js/preview/preview.js | 33 +++++++++++++++++++++---- js/texturing/uv.js | 56 ++++++++++++++----------------------------- lang/en.json | 1 + 4 files changed, 62 insertions(+), 42 deletions(-) diff --git a/js/preview/canvas.js b/js/preview/canvas.js index a3a5fec3..6e020484 100644 --- a/js/preview/canvas.js +++ b/js/preview/canvas.js @@ -1391,6 +1391,20 @@ const Canvas = { if (height === Infinity) height = 0; return [width, height] + }, + getSelectionBounds() { + let pivot_marker_parent = Canvas.pivot_marker.parent; + let visible_box = new THREE.Box3(); + if (pivot_marker_parent) pivot_marker_parent.remove(Canvas.pivot_marker); + Canvas.withoutGizmos(() => { + Outliner.selected.forEach(element => { + if (element.visibility && element.mesh && element.mesh.geometry) { + visible_box.expandByObject(element.mesh); + } + }) + }) + if (pivot_marker_parent) pivot_marker_parent.add(Canvas.pivot_marker); + return visible_box; } } var buildGrid = Canvas.buildGrid; diff --git a/js/preview/preview.js b/js/preview/preview.js index 7ce2a6fc..b3e3f144 100644 --- a/js/preview/preview.js +++ b/js/preview/preview.js @@ -2268,14 +2268,19 @@ BARS.defineActions(function() { icon: 'center_focus_weak', category: 'view', condition: () => !Format.image_editor, - keybind: new Keybind({}, {rotate_only: 'shift'}), + keybind: new Keybind({}, { + rotate_only: 'shift', + zoom: 'ctrl' + }), variations: { - rotate_only: {name: 'action.focus_on_selection.rotate_only'} + rotate_only: {name: 'action.focus_on_selection.rotate_only'}, + zoom: {name: 'action.focus_on_selection.zoom'} }, click(event = 0) { if (!Project) return; + let zoom = this.keybind.additionalModifierTriggered(event, 'zoom'); if (Prop.active_panel == 'uv') { - UVEditor.focusOnSelection() + UVEditor.focusOnSelection(zoom) } else { let preview = Preview.selected; @@ -2288,8 +2293,24 @@ BARS.defineActions(function() { Transformer.getWorldPosition(center) } + let zoom_offset; let difference = new THREE.Vector3().copy(preview.controls.target).sub(center); - difference.divideScalar(6) + let cam_boom = center.clone().sub(preview.camera.position).add(difference); + difference.divideScalar(6); + + if (zoom) { + let bounds = Canvas.getSelectionBounds(); + let radius = Math.max( + Math.abs(bounds.min.x-center.x), Math.abs(bounds.max.x-center.x), + Math.abs(bounds.min.z-center.z), Math.abs(bounds.max.z-center.z), + ); + let height = Math.max(Math.abs(bounds.min.y-center.y), Math.abs(bounds.max.y-center.y)); + let focal_length = preview.camera.getFocalLength(); + let cam_distance = cam_boom.length(); + let target_distance = Math.max(radius, height) * (focal_length / 10); + zoom_factor = target_distance / cam_distance; + zoom_offset = cam_boom.multiplyScalar((zoom_factor-1) / 6); + } let i = 0; let interval = setInterval(() => { @@ -2298,6 +2319,10 @@ BARS.defineActions(function() { if (this.keybind.additionalModifierTriggered(event) != 'rotate_only' || preview.angle != null) { preview.camera.position.sub(difference); } + if (zoom_offset) { + preview.camera.position.sub(zoom_offset); + } + Transformer.update(); i++; if (i == 6) clearInterval(interval); diff --git a/js/texturing/uv.js b/js/texturing/uv.js index 8f0cdd6c..dffcf046 100644 --- a/js/texturing/uv.js +++ b/js/texturing/uv.js @@ -257,40 +257,20 @@ const UVEditor = { } this.vue.selection_outline = outline; }, - focusOnSelection() { - let min_x = UVEditor.getUVWidth(); - let min_y = UVEditor.getUVHeight(); - let max_x = 0; - let max_y = 0; - let elements = UVEditor.getMappableElements(); - elements.forEach(element => { - if (element instanceof Cube && element.box_uv) { - let size = element.size(undefined, Format.box_uv_float_size != 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.getSelectedFaces(element, false).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]); - }) - } - } - } - }) + async focusOnSelection(zoom) { + if (zoom instanceof Event) { + zoom = BarItems.focus_on_selection.keybind.additionalModifierTriggered(zoom, 'zoom'); + } + let [min_x, min_y, max_x, max_y] = this.vue.getSelectedUVBoundingBox(); + if (zoom) { + let width = (max_x-min_x) / UVEditor.getUVWidth(); + let height = (max_y-min_y) / UVEditor.getUVHeight(); + let target_zoom_factor = 1/Math.max(width, height); + let target_zoom = Math.clamp(UVEditor.zoom, target_zoom_factor * 0.618, Math.max(1, target_zoom_factor * 0.84)); + UVEditor.setZoom(target_zoom); + await new Promise(Vue.nextTick); + } + let pixel_size = UVEditor.inner_width / UVEditor.vue.uv_resolution[0]; let focus = [min_x+max_x, min_y+max_y].map(v => v * 0.5 * pixel_size); let {viewport} = UVEditor.vue.$refs; @@ -299,7 +279,7 @@ const UVEditor = { scrollLeft: focus[0] + margin[0] - UVEditor.width / 2, scrollTop: focus[1] + margin[1] - UVEditor.height / 2, }, { - duration: 100, + duration: zoom ? 0 : 100, complete: () => { UVEditor.updateUVNavigator(); } @@ -4132,8 +4112,8 @@ Interface.definePanels(function() { height: this.toPixels(box[3] - box[1], 0), }; }, - focusOnSelection() { - UVEditor.focusOnSelection(); + focusOnSelection(event) { + UVEditor.focusOnSelection(event); }, showTransparentFaceText() { return UVEditor.getSelectedFaces(this.mappable_elements[0]).length; @@ -4540,7 +4520,7 @@ Interface.definePanels(function() { -
+
navigation
diff --git a/lang/en.json b/lang/en.json index 1d4f9a4f..3cc0a0b8 100644 --- a/lang/en.json +++ b/lang/en.json @@ -1756,6 +1756,7 @@ "action.focus_on_selection": "Center View on Selection", "action.focus_on_selection.desc": "Align the camera to face the center of the current selection", "action.focus_on_selection.rotate_only": "Rotate only", + "action.focus_on_selection.zoom": "Zoom to fit", "action.edit_reference_images": "Edit Reference Images", "action.edit_reference_images.desc": "Turn on reference image mode to add or edit reference images and blueprints",