Center on Selection: Optional zoom modifier

Closes #1447
This commit is contained in:
JannisX11 2024-12-30 00:39:10 +01:00
parent 1afe9f4130
commit 946d7f4c64
4 changed files with 62 additions and 42 deletions

View File

@ -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;

View File

@ -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);

View File

@ -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() {
</svg>
</div>
<div class="uv_navigator" @click="focusOnSelection()" v-show="mode == 'uv'">
<div class="uv_navigator" @click="focusOnSelection($event)" v-show="mode == 'uv'">
<i class="material-icons icon">navigation</i>
</div>

View File

@ -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",