@@ -162,10 +162,26 @@ export class PolygonVertexEditingOverlay extends ToolOverlay {
162162 const zoom = this . gfx . viewport . zoom ;
163163 const hitDist = VERTEX_HIT_DISTANCE / zoom ;
164164
165+ // Inverse-rotate incoming world coordinates into polygon's local space
166+ let localX = modelX ;
167+ let localY = modelY ;
168+ const rotate = model . rotate ?? 0 ;
169+ if ( rotate ) {
170+ const cx = bound . x + bound . w / 2 ;
171+ const cy = bound . y + bound . h / 2 ;
172+ const rad = - ( rotate * Math . PI ) / 180 ; // negative for inverse rotation
173+ const cos = Math . cos ( rad ) ;
174+ const sin = Math . sin ( rad ) ;
175+ const dx = modelX - cx ;
176+ const dy = modelY - cy ;
177+ localX = cx + dx * cos - dy * sin ;
178+ localY = cy + dx * sin + dy * cos ;
179+ }
180+
165181 for ( let i = 0 ; i < model . vertices . length ; i ++ ) {
166182 const [ ax , ay ] = this . _toAbsolute ( model . vertices [ i ] , bound ) ;
167- const dx = modelX - ax ;
168- const dy = modelY - ay ;
183+ const dx = localX - ax ;
184+ const dy = localY - ay ;
169185 if ( Math . sqrt ( dx * dx + dy * dy ) < hitDist ) {
170186 return i ;
171187 }
@@ -190,25 +206,39 @@ export class PolygonVertexEditingOverlay extends ToolOverlay {
190206 const zoom = this . gfx . viewport . zoom ;
191207 const snapDist = SNAP_GUIDE_DISTANCE / zoom ;
192208
193- // Collect absolute positions of all other vertices for snapping
209+ // Inverse-rotate mouse position from world space to local (unrotated) space
210+ const rotate = model . rotate ?? 0 ;
211+ let localX = modelX ;
212+ let localY = modelY ;
213+ if ( rotate ) {
214+ const cx = bound . x + bound . w / 2 ;
215+ const cy = bound . y + bound . h / 2 ;
216+ const rad = ( - rotate * Math . PI ) / 180 ; // negative for inverse
217+ const dx = modelX - cx ;
218+ const dy = modelY - cy ;
219+ localX = cx + dx * Math . cos ( rad ) - dy * Math . sin ( rad ) ;
220+ localY = cy + dx * Math . sin ( rad ) + dy * Math . cos ( rad ) ;
221+ }
222+
223+ // Collect local-space absolute positions of all other vertices for snapping
194224 const otherAbsolute : [ number , number ] [ ] = [ ] ;
195225 for ( let i = 0 ; i < model . vertices . length ; i ++ ) {
196226 if ( i === vertexIndex ) continue ;
197227 otherAbsolute . push ( this . _toAbsolute ( model . vertices [ i ] , bound ) ) ;
198228 }
199229
200- // Attempt coordinate snapping
201- let snappedX = modelX ;
202- let snappedY = modelY ;
230+ // Attempt coordinate snapping in local (unrotated) space
231+ let snappedX = localX ;
232+ let snappedY = localY ;
203233 this . snapGuideX = null ;
204234 this . snapGuideY = null ;
205235
206236 for ( const [ ox , oy ] of otherAbsolute ) {
207- if ( Math . abs ( modelX - ox ) < snapDist ) {
237+ if ( Math . abs ( localX - ox ) < snapDist ) {
208238 snappedX = ox ;
209239 this . snapGuideX = ox ;
210240 }
211- if ( Math . abs ( modelY - oy ) < snapDist ) {
241+ if ( Math . abs ( localY - oy ) < snapDist ) {
212242 snappedY = oy ;
213243 this . snapGuideY = oy ;
214244 }
@@ -359,15 +389,31 @@ export class PolygonVertexEditingOverlay extends ToolOverlay {
359389 const zoom = this . gfx . viewport . zoom ;
360390 const hitDist = MIDPOINT_HIT_DISTANCE / zoom ;
361391
392+ // Inverse-rotate incoming world coordinates into polygon's local space
393+ let localX = modelX ;
394+ let localY = modelY ;
395+ const rotate = model . rotate ?? 0 ;
396+ if ( rotate ) {
397+ const cx = bound . x + bound . w / 2 ;
398+ const cy = bound . y + bound . h / 2 ;
399+ const rad = - ( rotate * Math . PI ) / 180 ; // negative for inverse rotation
400+ const cos = Math . cos ( rad ) ;
401+ const sin = Math . sin ( rad ) ;
402+ const dx = modelX - cx ;
403+ const dy = modelY - cy ;
404+ localX = cx + dx * cos - dy * sin ;
405+ localY = cy + dx * sin + dy * cos ;
406+ }
407+
362408 for ( let i = 0 ; i < model . vertices . length ; i ++ ) {
363409 const [ ax , ay ] = this . _toAbsolute ( model . vertices [ i ] , bound ) ;
364410 const nextIdx = ( i + 1 ) % model . vertices . length ;
365411 const [ bx , by ] = this . _toAbsolute ( model . vertices [ nextIdx ] , bound ) ;
366412 const mx = ( ax + bx ) / 2 ;
367413 const my = ( ay + by ) / 2 ;
368414
369- const dx = modelX - mx ;
370- const dy = modelY - my ;
415+ const dx = localX - mx ;
416+ const dy = localY - my ;
371417 if ( Math . sqrt ( dx * dx + dy * dy ) < hitDist ) {
372418 return i ;
373419 }
@@ -386,24 +432,41 @@ export class PolygonVertexEditingOverlay extends ToolOverlay {
386432 const model = this . _getPolygonModel ( ) ;
387433 if ( ! model || ! model . vertices || ! model . smoothFlags ) return null ;
388434
435+ const bound = Bound . deserialize ( model . xywh ) ;
389436 const zoom = this . gfx . viewport . zoom ;
390437 const hitDist = BEZIER_HANDLE_RADIUS * 2 / zoom ;
391438
439+ // Inverse-rotate incoming world coordinates into polygon's local space
440+ let localX = modelX ;
441+ let localY = modelY ;
442+ const rotate = model . rotate ?? 0 ;
443+ if ( rotate ) {
444+ const cx = bound . x + bound . w / 2 ;
445+ const cy = bound . y + bound . h / 2 ;
446+ const rad = ( - rotate * Math . PI ) / 180 ;
447+ const cos = Math . cos ( rad ) ;
448+ const sin = Math . sin ( rad ) ;
449+ const dx = modelX - cx ;
450+ const dy = modelY - cy ;
451+ localX = cx + dx * cos - dy * sin ;
452+ localY = cy + dx * sin + dy * cos ;
453+ }
454+
392455 for ( let i = 0 ; i < model . vertices . length ; i ++ ) {
393456 if ( ! model . smoothFlags [ i ] ) continue ;
394457 const cp = this . getBezierControlPoints ( i ) ;
395458 if ( ! cp ) continue ;
396459
397460 // Check cp1
398- const dx1 = modelX - cp . cp1 [ 0 ] ;
399- const dy1 = modelY - cp . cp1 [ 1 ] ;
461+ const dx1 = localX - cp . cp1 [ 0 ] ;
462+ const dy1 = localY - cp . cp1 [ 1 ] ;
400463 if ( Math . sqrt ( dx1 * dx1 + dy1 * dy1 ) < hitDist ) {
401464 return { vertexIndex : i , handleIndex : 0 } ;
402465 }
403466
404467 // Check cp2
405- const dx2 = modelX - cp . cp2 [ 0 ] ;
406- const dy2 = modelY - cp . cp2 [ 1 ] ;
468+ const dx2 = localX - cp . cp2 [ 0 ] ;
469+ const dy2 = localY - cp . cp2 [ 1 ] ;
407470 if ( Math . sqrt ( dx2 * dx2 + dy2 * dy2 ) < hitDist ) {
408471 return { vertexIndex : i , handleIndex : 1 } ;
409472 }
@@ -427,6 +490,22 @@ export class PolygonVertexEditingOverlay extends ToolOverlay {
427490 const bound = Bound . deserialize ( model . xywh ) ;
428491 const count = model . vertices . length ;
429492
493+ // Inverse-rotate mouse position from world space to local (unrotated) space
494+ const rotate = model . rotate ?? 0 ;
495+ let localX = modelX ;
496+ let localY = modelY ;
497+ if ( rotate ) {
498+ const cx = bound . x + bound . w / 2 ;
499+ const cy = bound . y + bound . h / 2 ;
500+ const rad = - ( rotate * Math . PI ) / 180 ;
501+ const cos = Math . cos ( rad ) ;
502+ const sin = Math . sin ( rad ) ;
503+ const dx = modelX - cx ;
504+ const dy = modelY - cy ;
505+ localX = cx + dx * cos - dy * sin ;
506+ localY = cy + dx * sin + dy * cos ;
507+ }
508+
430509 // Initialize controlPoints array if null
431510 let controlPoints : ( number [ ] | null ) [ ] = model . controlPoints
432511 ? [ ...model . controlPoints ]
@@ -449,8 +528,8 @@ export class PolygonVertexEditingOverlay extends ToolOverlay {
449528 }
450529
451530 // Convert absolute position to normalized coordinates (in current bound)
452- const normX = ( modelX - bound . x ) / bound . w ;
453- const normY = ( modelY - bound . y ) / bound . h ;
531+ const normX = ( localX - bound . x ) / bound . w ;
532+ const normY = ( localY - bound . y ) / bound . h ;
454533
455534 // Update the specific handle
456535 const entry = [ ...controlPoints [ vertexIndex ] ! ] ;
@@ -984,6 +1063,17 @@ export class PolygonVertexEditingOverlay extends ToolOverlay {
9841063 ctx . save ( ) ;
9851064 ctx . globalAlpha = this . globalAlpha ;
9861065
1066+ // ββ Apply rotation around shape center βββββββββββββββββββββββββ
1067+ const rotate = model . rotate ?? 0 ;
1068+ if ( rotate ) {
1069+ const cx = bound . x + bound . w / 2 ;
1070+ const cy = bound . y + bound . h / 2 ;
1071+ const rad = ( rotate * Math . PI ) / 180 ;
1072+ ctx . translate ( cx , cy ) ;
1073+ ctx . rotate ( rad ) ;
1074+ ctx . translate ( - cx , - cy ) ;
1075+ }
1076+
9871077 // ββ Draw snap guides ββββββββββββββββββββββββββββββββββββββββββββ
9881078 if ( this . activeVertexIndex >= 0 ) {
9891079 ctx . setLineDash ( [ 4 / zoom , 3 / zoom ] ) ;
0 commit comments