mirror of
https://github.com/CNCKitchen/stlTexturizer.git
synced 2026-04-07 22:11:32 +00:00
feat: implement cursor-centric zoom functionality in viewer
This commit is contained in:
+24
@@ -292,6 +292,7 @@ function wireEvents() {
|
||||
clearTimeout(previewDebounce); previewDebounce = setTimeout(updatePreview, 80);
|
||||
};
|
||||
scaleUSlider.addEventListener('input', () => applyScaleU(posToScale(parseFloat(scaleUSlider.value))));
|
||||
scaleUSlider.addEventListener('dblclick', () => applyScaleU(posToScale(parseFloat(scaleUSlider.defaultValue))));
|
||||
scaleUVal.addEventListener('change', () => applyScaleU(parseFloat(scaleUVal.value)));
|
||||
|
||||
// Scale V — when lock is on, mirror to U
|
||||
@@ -304,6 +305,7 @@ function wireEvents() {
|
||||
clearTimeout(previewDebounce); previewDebounce = setTimeout(updatePreview, 80);
|
||||
};
|
||||
scaleVSlider.addEventListener('input', () => applyScaleV(posToScale(parseFloat(scaleVSlider.value))));
|
||||
scaleVSlider.addEventListener('dblclick', () => applyScaleV(posToScale(parseFloat(scaleVSlider.defaultValue))));
|
||||
scaleVVal.addEventListener('change', () => applyScaleV(parseFloat(scaleVVal.value)));
|
||||
|
||||
// Lock toggle
|
||||
@@ -412,6 +414,11 @@ function wireEvents() {
|
||||
brushRadius = parseFloat(exclBrushRadiusSlider.value) / 2;
|
||||
exclBrushRadiusVal.value = parseFloat(exclBrushRadiusSlider.value);
|
||||
});
|
||||
exclBrushRadiusSlider.addEventListener('dblclick', () => {
|
||||
exclBrushRadiusSlider.value = exclBrushRadiusSlider.defaultValue;
|
||||
brushRadius = parseFloat(exclBrushRadiusSlider.value) / 2;
|
||||
exclBrushRadiusVal.value = parseFloat(exclBrushRadiusSlider.value);
|
||||
});
|
||||
exclBrushRadiusVal.addEventListener('change', () => {
|
||||
let diam = Math.max(0.2, Math.min(100, parseFloat(exclBrushRadiusVal.value) || 10));
|
||||
brushRadius = diam / 2;
|
||||
@@ -424,6 +431,12 @@ function wireEvents() {
|
||||
exclThresholdVal.value = bucketThreshold;
|
||||
_lastHoverTriIdx = -1; // invalidate hover so next mousemove re-computes
|
||||
});
|
||||
exclThresholdSlider.addEventListener('dblclick', () => {
|
||||
exclThresholdSlider.value = exclThresholdSlider.defaultValue;
|
||||
bucketThreshold = parseFloat(exclThresholdSlider.value);
|
||||
exclThresholdVal.value = bucketThreshold;
|
||||
_lastHoverTriIdx = -1;
|
||||
});
|
||||
exclThresholdVal.addEventListener('change', () => {
|
||||
bucketThreshold = Math.max(0, Math.min(180, parseFloat(exclThresholdVal.value) || 20));
|
||||
exclThresholdSlider.value = bucketThreshold;
|
||||
@@ -907,6 +920,17 @@ function linkSlider(slider, valInput, onChangeFn, livePreview = true) {
|
||||
previewDebounce = setTimeout(updatePreview, 80);
|
||||
}
|
||||
});
|
||||
// Double-click resets to default value
|
||||
slider.addEventListener('dblclick', () => {
|
||||
slider.value = slider.defaultValue;
|
||||
const v = parseFloat(slider.value);
|
||||
const display = onChangeFn(v);
|
||||
if (isSpan) valInput.textContent = display; else valInput.value = display;
|
||||
if (livePreview) {
|
||||
clearTimeout(previewDebounce);
|
||||
previewDebounce = setTimeout(updatePreview, 80);
|
||||
}
|
||||
});
|
||||
if (!isSpan) {
|
||||
valInput.addEventListener('change', () => {
|
||||
const raw = parseFloat(valInput.value);
|
||||
|
||||
@@ -183,6 +183,32 @@ export function initViewer(canvas) {
|
||||
controls.enableDamping = true;
|
||||
controls.dampingFactor = 0.08;
|
||||
controls.screenSpacePanning = true;
|
||||
controls.enableZoom = false; // we handle zoom ourselves for cursor-centric behaviour
|
||||
|
||||
// Cursor-centric zoom: zoom toward the mouse pointer instead of screen centre
|
||||
renderer.domElement.addEventListener('wheel', (e) => {
|
||||
e.preventDefault();
|
||||
const rect = renderer.domElement.getBoundingClientRect();
|
||||
const ndcX = ((e.clientX - rect.left) / rect.width) * 2 - 1;
|
||||
const ndcY = -((e.clientY - rect.top) / rect.height) * 2 + 1;
|
||||
|
||||
// World position under cursor before zoom
|
||||
const before = new THREE.Vector3(ndcX, ndcY, 0).unproject(camera);
|
||||
|
||||
// Apply zoom
|
||||
const factor = e.deltaY > 0 ? 1 / 1.1 : 1.1;
|
||||
camera.zoom = Math.max(0.05, Math.min(200, camera.zoom * factor));
|
||||
camera.updateProjectionMatrix();
|
||||
|
||||
// World position under cursor after zoom
|
||||
const after = new THREE.Vector3(ndcX, ndcY, 0).unproject(camera);
|
||||
|
||||
// Shift camera + target so the world point stays under the cursor
|
||||
const delta = before.clone().sub(after);
|
||||
camera.position.add(delta);
|
||||
controls.target.add(delta);
|
||||
controls.update();
|
||||
}, { passive: false });
|
||||
|
||||
// Resize observer
|
||||
const resizeObserver = new ResizeObserver(() => onResize());
|
||||
|
||||
Reference in New Issue
Block a user