From 1ea767d10352c610a78701fb48ab157cefd1a14b Mon Sep 17 00:00:00 2001 From: 5o1 Date: Sat, 18 Jul 2026 04:35:18 +0800 Subject: [PATCH 1/2] fix: hide collapsed k-space axes in 2D views --- src/editor/webview/assets/kspace.js | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/editor/webview/assets/kspace.js b/src/editor/webview/assets/kspace.js index acd939f..bc418b5 100644 --- a/src/editor/webview/assets/kspace.js +++ b/src/editor/webview/assets/kspace.js @@ -344,8 +344,9 @@ kCanvas.addEventListener("wheel",function(e){e.preventDefault(); var mx=(e.clientX-r.left)/dpr, my=(e.clientY-r.top)/dpr; var zf=e.deltaY<0?1.25:0.8, W=kCanvas.width/dpr, H=kCanvas.height/dpr; var dz=(1-1/zf)*dpr/kScl; + var rx=kView==="3d"?kRotX:_tRotX,ry=kView==="3d"?kRotY:_tRotY; kAutoFit=false; - setKSpaceTarget(kRotX, kRotY, kScl*zf, kCx+(mx-W/2)*dz, kCy-(my-H/2)*dz, kCz, false); + setKSpaceTarget(rx, ry, kScl*zf, kCx+(mx-W/2)*dz, kCy-(my-H/2)*dz, kCz, false); },{passive:false}); window.addEventListener("mousemove",function(e){ @@ -637,8 +638,21 @@ function drawKsOverlay(W,H,dpr,cs){ var ry=dy2*cxR-rz2*sxR; return {x:W/2+rx*kScl*invDpr, y:H/2-ry*kScl*invDpr}; } + function axisProjectionLenSq(fx,fy,fz){ + var rx=fx*cz-fz*sz; + var rz=fx*sz+fz*cz; + var ry=fy*cxR-rz*sxR; + return rx*rx+ry*ry; + } + function hideAxis(fx,fy,fz,label){ + if(kView==="xy")return label==="kz"; + if(kView==="xz")return label==="ky"; + if(kView==="yz")return label==="kx"; + return axisProjectionLenSq(fx,fy,fz)<1e-10; + } var tick=kNice(rng3); function drawAxis3D(fx,fy,fz,label,col){ + if(hideAxis(fx,fy,fz,label))return; var al=rng3*0.65; var t1=proj(fx*al,fy*al,fz*al); var t2=proj(-fx*al,-fy*al,-fz*al); From 078d5df6ff4e0bcf2c5be7dd559b16b6f68d0de0 Mon Sep 17 00:00:00 2001 From: 5o1 Date: Sat, 18 Jul 2026 05:09:28 +0800 Subject: [PATCH 2/2] fix: align k-space zoom and pan with camera axes Issue: k-space wheel zoom and pan used screen-space approximations that only matched the XY view. On high-DPI displays and in XZ/YZ/3D views, zooming around the cursor and middle/right-button panning could drift or move along the wrong world axes. Solution: convert cursor offsets and drag deltas through the same camera-plane to world-space transform used by the k-space projection, then update the view center from that shared coordinate model. Changes: add helpers for wheel zoom scaling, scale clamping, and camera-plane conversion; make wheel zoom preserve the cursor anchor from the target camera state; update mouse and touch pan to use the shared transform; sync animation targets when drag interactions interrupt an in-flight camera transition. --- src/editor/webview/assets/kspace.js | 48 +++++++++++++++++++---------- 1 file changed, 32 insertions(+), 16 deletions(-) diff --git a/src/editor/webview/assets/kspace.js b/src/editor/webview/assets/kspace.js index bc418b5..55fc305 100644 --- a/src/editor/webview/assets/kspace.js +++ b/src/editor/webview/assets/kspace.js @@ -44,6 +44,22 @@ function setKSpaceTarget(rx, ry, s, cx, cy, cz, instant) { startKSpaceAnim(); } } +function kClampScale(s){return Math.max(0.001,Math.min(1e6,s));} +function kWheelZoomFactor(e){ + var dy=e.deltaY; + if(e.deltaMode===1)dy*=16; + else if(e.deltaMode===2)dy*=(window.innerHeight||800); + return Math.max(0.2,Math.min(5,Math.exp(-dy*0.0015))); +} +function kCameraPlaneToWorld(x,y,rx,ry){ + var cy=Math.cos(ry),sy=Math.sin(ry),cx=Math.cos(rx),sx=Math.sin(rx); + var z1=-y*sx; + return{x:x*cy+z1*sy,y:y*cx,z:-x*sy+z1*cy}; +} +function kScreenDeltaToWorld(dx,dy,rx,ry,s){ + var dpr=window.devicePixelRatio||1; + return kCameraPlaneToWorld(-dx*dpr/s,dy*dpr/s,rx,ry); +} document.getElementById("kdot").oninput=function(){kDotSize=parseInt(this.value);drawKs();}; document.getElementById("kunit").onclick=function(){ kUnit=kUnit==="cyc"?"rad":"cyc";this.textContent=kUnit==="cyc"?"Unit: 1/m":"Unit: rad/m";drawKs(); @@ -335,18 +351,23 @@ kCanvas.addEventListener("mousedown",function(e){ kDragging=true;kDragPrev={x:e.clientX,y:e.clientY};kDragBtn=e.button; // Cancel any running animation so it doesn't fight the drag if(_kAnimId){cancelAnimationFrame(_kAnimId);_kAnimId=null;} + _tRotX=kRotX; _tRotY=kRotY; _tScl=kScl; _tCx=kCx; _tCy=kCy; _tCz=kCz; e.preventDefault(); }); kCanvas.addEventListener("contextmenu",function(e){e.preventDefault();}); kCanvas.addEventListener("wheel",function(e){e.preventDefault(); var r=kCanvas.getBoundingClientRect(),dpr=window.devicePixelRatio||1; - var mx=(e.clientX-r.left)/dpr, my=(e.clientY-r.top)/dpr; - var zf=e.deltaY<0?1.25:0.8, W=kCanvas.width/dpr, H=kCanvas.height/dpr; - var dz=(1-1/zf)*dpr/kScl; - var rx=kView==="3d"?kRotX:_tRotX,ry=kView==="3d"?kRotY:_tRotY; + var mx=e.clientX-r.left, my=e.clientY-r.top; + var W=kCanvas.width/dpr, H=kCanvas.height/dpr; + var rx=_tRotX,ry=_tRotY,s=_tScl; + if(!isFinite(s)||s<=0)s=kScl; + var ns=kClampScale(s*kWheelZoomFactor(e)); + if(ns===s)return; + var shift=(1-s/ns)*dpr/s; + var d=kCameraPlaneToWorld((mx-W/2)*shift,-(my-H/2)*shift,rx,ry); kAutoFit=false; - setKSpaceTarget(rx, ry, kScl*zf, kCx+(mx-W/2)*dz, kCy-(my-H/2)*dz, kCz, false); + setKSpaceTarget(rx, ry, ns, _tCx+d.x, _tCy+d.y, _tCz+d.z, false); },{passive:false}); window.addEventListener("mousemove",function(e){ @@ -359,12 +380,9 @@ window.addEventListener("mousemove",function(e){ kRotY+=dx*0.008; kRotX-=dy*0.008; _tRotY=kRotY; _tRotX=kRotX; // sync targets }else{ - // right/middle drag = instant pan - var cz=Math.cos(kRotY),sz=Math.sin(kRotY),cx=Math.cos(kRotX),sx=Math.sin(kRotX); - var dpr=window.devicePixelRatio||1; - dx/=(dpr*kScl); dy/=(dpr*kScl); - if(Math.abs(cz)>0.01){kCy+=dy/cx;kCx+=(-dx-sz*sx*(dy/cx))/cz;} - else{kCy+=dy/cx;kCz+=(dx-cz*sx*(dy/cx))/sz;} + // right/middle drag = pan in camera space + var d=kScreenDeltaToWorld(dx,dy,kRotX,kRotY,kScl); + kCx+=d.x;kCy+=d.y;kCz+=d.z; _tCy=kCy; _tCx=kCx; _tCz=kCz; // sync targets kAutoFit=false; } @@ -376,6 +394,7 @@ window.addEventListener("mouseup",function(){kDragging=false;kDragPrev=null;}); var _kTouchActive=false,_kTouchPrev=null,_kTouchPinch0=0,_kTouchMid=null,_kTouchBtn=0; kCanvas.addEventListener("touchstart",function(e){ if(_kAnimId){cancelAnimationFrame(_kAnimId);_kAnimId=null;} + _tRotX=kRotX; _tRotY=kRotY; _tScl=kScl; _tCx=kCx; _tCy=kCy; _tCz=kCz; if(e.touches.length===1){ _kTouchActive=true;_kTouchBtn=0; _kTouchPrev={x:e.touches[0].clientX,y:e.touches[0].clientY}; @@ -410,11 +429,8 @@ kCanvas.addEventListener("touchmove",function(e){ var mid=getTouchMid(e.touches); var pdx=(mid.x-_kTouchPrev.x), pdy=(mid.y-_kTouchPrev.y); _kTouchPrev={x:mid.x,y:mid.y}; - var cz=Math.cos(kRotY),sz=Math.sin(kRotY),cx=Math.cos(kRotX),sx=Math.sin(kRotX); - var dpr=window.devicePixelRatio||1; - pdx/=(dpr*kScl); pdy/=(dpr*kScl); - if(Math.abs(cz)>0.01){kCy+=pdy/cx;kCx+=(-pdx-sz*sx*(pdy/cx))/cz;} - else{kCy+=pdy/cx;kCz+=(pdx-cz*sx*(pdy/cx))/sz;} + var d=kScreenDeltaToWorld(pdx,pdy,kRotX,kRotY,kScl); + kCx+=d.x;kCy+=d.y;kCz+=d.z; _tCy=kCy; _tCx=kCx; _tCz=kCz; kAutoFit=false; drawKsFast(); }