Skip to main content

Dragging object inside a custom region

· 2 min read

In this example, we construct a solid region with a Γ\Gamma-shaped cutout, where a small rectangle is going to be dragged by a user's mouse.

Then we utilize a very powerful standard function of a Wolfram library, RegionDistance, allowing estimation of the minimal distance between regions in a single line.

note

Region computations are supported not only for Region, Mesh-like objects, but also basic geometrical primitives such as Rectangle are absolutely valid for the input.

Region cutout

We start from cutting a hole inside a big rectangle using boolean operations:

Download original notebook
outer = RegionDifference[
  Rectangle[{-1,-1}, {1,1}],
  RegionUnion[
    Rectangle[{-0.9,-0.9}, {0.9,-0.4}]  ,
    Rectangle[{0.4,-0.9}, {0.9,0.4}]  
  ]
];

outer = Rationalize[outer, 0]; (* WL14 bug *)
RegionPlot[outer]
(*VB[*)(FrontEndRef["9d93a0de-9527-4680-9631-305ddb4d125b"])(*,*)(*"1:eJxTTMoPSmNkYGAoZgESHvk5KRCeEJBwK8rPK3HNS3GtSE0uLUlMykkNVgEKW6ZYGicapKTqWpoameuamFkY6FqaGRvqGhuYpqQkmaQYGpkmAQB53RUW"*)(*]VB*)
warning

There is a bug in the standard library of WL14. You need to apply Rationalize on the final primitive; otherwise, it won't work correctly.

Dynamic Scene

Here we assemble a dynamic scene, where we add a draggable red rectangle and update its position if the minimal distance between it and the outer region is greater than 0:

distanceOp = RegionDistance[outer, Translate[Rectangle[-{0.2,0.2}, {0.2,0.2}], #]]&;

rect = {0.65, 0.15};

RegionPlot[outer, Epilog->{
  Red, 
  Translate[EventHandler[
    Rectangle[-{0.2,0.2}, {0.2,0.2}], 
    {"dragsignal" -> Function[target,
      If[distanceOp[target] > 0, rect = target]
    ]}
  ], Offload[rect]]
}]

We use the dragsignal event instead of drag on the Rectangle to prevent the primitive from being actually dragged by the mouse, and instead update its position manually.

You may notice that the dragging behavior is a bit off our expectations. An object abruptly stops once the mouse enters the "forbidden" land.

One easy approach to fix this (but not quite efficient) is to iteratively approach the target separately by the x- and y-axis:

distanceOp = RegionDistance[outer, Translate[Rectangle[-{0.2,0.2}, {0.2,0.2}], #]]&;

rect = {0.65, 0.15};

RegionPlot[outer, Epilog->{
  Red, 
  Translate[EventHandler[
    Rectangle[-{0.2,0.2}, {0.2,0.2}], 
    {"dragsignal" -> Function[target,
      rect = {
        If[distanceOp[{target[[1]], rect[[2]]}] > 0.01,
          target[[1]]
        ,
          FixedPoint[If[distanceOp[{#, rect[[2]]}] > 0.01,
             # + 0.01 (target[[1]] - #)   
          ,
             #
          ]&, rect[[1]]] - 0.01 (target[[1]] - rect[[1]]) 
        ],
        If[distanceOp[{rect[[1]], target[[2]]}] > 0.01,
          target[[2]]
        ,
          FixedPoint[If[distanceOp[{rect[[1]], #}] > 0.01,
             # + 0.01 (target[[2]] - #)   
          ,
             #
          ]&, rect[[2]]] - 0.01 (target[[2]] - rect[[2]])  
        ]
      }
    ]}
  ], Offload[rect]]
}]
(*VB[*)(FrontEndRef["ad1a8914-bc32-4529-b601-9edc89756513"])(*,*)(*"1:eJxTTMoPSmNkYGAoZgESHvk5KRCeEJBwK8rPK3HNS3GtSE0uLUlMykkNVgEKJ6YYJlpYGproJiUbG+mamBpZ6iaZGRjqWqamJFtYmpuamRoaAwCA/xUY"*)(*]VB*)

Now it should feel more natural