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);
|
clearTimeout(previewDebounce); previewDebounce = setTimeout(updatePreview, 80);
|
||||||
};
|
};
|
||||||
scaleUSlider.addEventListener('input', () => applyScaleU(posToScale(parseFloat(scaleUSlider.value))));
|
scaleUSlider.addEventListener('input', () => applyScaleU(posToScale(parseFloat(scaleUSlider.value))));
|
||||||
|
scaleUSlider.addEventListener('dblclick', () => applyScaleU(posToScale(parseFloat(scaleUSlider.defaultValue))));
|
||||||
scaleUVal.addEventListener('change', () => applyScaleU(parseFloat(scaleUVal.value)));
|
scaleUVal.addEventListener('change', () => applyScaleU(parseFloat(scaleUVal.value)));
|
||||||
|
|
||||||
// Scale V — when lock is on, mirror to U
|
// Scale V — when lock is on, mirror to U
|
||||||
@@ -304,6 +305,7 @@ function wireEvents() {
|
|||||||
clearTimeout(previewDebounce); previewDebounce = setTimeout(updatePreview, 80);
|
clearTimeout(previewDebounce); previewDebounce = setTimeout(updatePreview, 80);
|
||||||
};
|
};
|
||||||
scaleVSlider.addEventListener('input', () => applyScaleV(posToScale(parseFloat(scaleVSlider.value))));
|
scaleVSlider.addEventListener('input', () => applyScaleV(posToScale(parseFloat(scaleVSlider.value))));
|
||||||
|
scaleVSlider.addEventListener('dblclick', () => applyScaleV(posToScale(parseFloat(scaleVSlider.defaultValue))));
|
||||||
scaleVVal.addEventListener('change', () => applyScaleV(parseFloat(scaleVVal.value)));
|
scaleVVal.addEventListener('change', () => applyScaleV(parseFloat(scaleVVal.value)));
|
||||||
|
|
||||||
// Lock toggle
|
// Lock toggle
|
||||||
@@ -412,6 +414,11 @@ function wireEvents() {
|
|||||||
brushRadius = parseFloat(exclBrushRadiusSlider.value) / 2;
|
brushRadius = parseFloat(exclBrushRadiusSlider.value) / 2;
|
||||||
exclBrushRadiusVal.value = parseFloat(exclBrushRadiusSlider.value);
|
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', () => {
|
exclBrushRadiusVal.addEventListener('change', () => {
|
||||||
let diam = Math.max(0.2, Math.min(100, parseFloat(exclBrushRadiusVal.value) || 10));
|
let diam = Math.max(0.2, Math.min(100, parseFloat(exclBrushRadiusVal.value) || 10));
|
||||||
brushRadius = diam / 2;
|
brushRadius = diam / 2;
|
||||||
@@ -424,6 +431,12 @@ function wireEvents() {
|
|||||||
exclThresholdVal.value = bucketThreshold;
|
exclThresholdVal.value = bucketThreshold;
|
||||||
_lastHoverTriIdx = -1; // invalidate hover so next mousemove re-computes
|
_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', () => {
|
exclThresholdVal.addEventListener('change', () => {
|
||||||
bucketThreshold = Math.max(0, Math.min(180, parseFloat(exclThresholdVal.value) || 20));
|
bucketThreshold = Math.max(0, Math.min(180, parseFloat(exclThresholdVal.value) || 20));
|
||||||
exclThresholdSlider.value = bucketThreshold;
|
exclThresholdSlider.value = bucketThreshold;
|
||||||
@@ -907,6 +920,17 @@ function linkSlider(slider, valInput, onChangeFn, livePreview = true) {
|
|||||||
previewDebounce = setTimeout(updatePreview, 80);
|
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) {
|
if (!isSpan) {
|
||||||
valInput.addEventListener('change', () => {
|
valInput.addEventListener('change', () => {
|
||||||
const raw = parseFloat(valInput.value);
|
const raw = parseFloat(valInput.value);
|
||||||
|
|||||||
@@ -183,6 +183,32 @@ export function initViewer(canvas) {
|
|||||||
controls.enableDamping = true;
|
controls.enableDamping = true;
|
||||||
controls.dampingFactor = 0.08;
|
controls.dampingFactor = 0.08;
|
||||||
controls.screenSpacePanning = true;
|
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
|
// Resize observer
|
||||||
const resizeObserver = new ResizeObserver(() => onResize());
|
const resizeObserver = new ResizeObserver(() => onResize());
|
||||||
|
|||||||
Reference in New Issue
Block a user