Calculate uregular bounds of Drawpath

Hi there
I’m moving a project from FileMaker into Xojo where I use floor drawings and would like to select a room and then enter data for that specific room.

In FM I’ve set the drawing up as HTML and SVG objects using ‘aref’ to select a room. The browser automatically knows the true bounds of all rooms.

Is there a way in Xojo to get a similar function? The example protects only use a square to calculate if an object have been touched. This will work for most times but not if it’s irregular room.

Any ideas how to do this? Can it be done by storing the path of each object/room in the object class?

Can you give some example code/pictures from what you did in FM? Hard to understand what you are trying to achieve without more details.

The oldest (if not very memory efficient) way is to draw a picture (not for display!) of the same size for the irregular shape but filled with a very specific color.

Then when someone clicks in the onscreen drawing , check the pixel color at the same position in the picture and by that you know if they clicked inside that item.

-Karen

This is a HTML viewer from FM where I load a complete FloorPlan containing all rooms. Each Room is drawn to a HTML page with the following format: https://www.dropbox.com/s/pvzfscxqox2q7df/IMG_0560.png?dl=0

K001 VRK --- (K2)

If then compile all Rooms into a ‘FloorHTML’ that is sent to the HTML Container in FM.
And once a Room is clicked (the HTML viewer somehow knows the boundaries of each room) I grab the room ID and shows the relevant RoomRecord.

I would like to to this directly on the iOSCanvas, but how?

Hi Karen,
Hmm, then I would need to have a ‘Mirror’ drawing with each Room having a unique color to identify the ID of the Room??? Consumes quite a bit of Memory ?

[quote=464541:@Bobby Kurell]Hi Karen,
Hmm, then I would need to have a ‘Mirror’ drawing with each Room having a unique color to identify the ID of the Room??? Consumes quite a bit of Memory ?[/quote]

Yes. The Memory usage is the downside of that approach… The upside can be simplicity/speed. If it makes sense or not depends on a lot of specifics.

Where I have seen this approach used is in desktop apps. I have never done anything with iOS.

-Karen

Memory usage would be dependent on how big the floor plans are and the “resolution” you require.

If you can get away with 1024x768, the image would be 786,432 bytes x 4 (red, green, blue, alpha), so about 3MB. If you were able to get away with 640x480, it would be 1.2MB.

You could also downscale the colored backing image. So if the floorplan was 1000x1000 you could maybe make your backing image 100x100 and scale the coordinates when performing your hit test.

The Floor Plans can get quite big. The largest X,Y I have in the current DB is approx 8.000x8.000, so that’s a lot of data.

Question: If I just use DrawPath directly on the canvas(not picture), is it still storing it in ‘bitmap’ ?

Since your outlines are defined by paths (i.e., polygons), why not just use a routine to see whether the clicked point is inside or outside of the polygon? Here’s a simple function that I translated from Fortran (maybe from Paul Bourke’s web site?) a long time ago:

[code]

function PointInPoly(px as double, py as double, xx() as Double, yy() as Double, n as Integer) as Boolean
// description of the parameters
// px - x-coordinate of point clicked
// py - y-coordinate of point clicked.
// xx - array containing x-coordinates of vertices of polygon.
// yy - array containing y-coordinates of vertices of polygon.
// n - number of vertices in the polygon.
// inout - the signal returned:
// -1 if the point is outside of the polygon,
// 0 if the point is on an edge or at a vertex,
// 1 if the point is inside of the polygon.

Dim x(-1), y(-1), w as Double
Dim mx,my,nx,ny, inPoly as Boolean
Dim i, j, k, inout as Integer

for i=0 to n-1
x.Append (xx(i)-px)
y.Append (yy(i)-py)
next

inout=-1

For i=0 to n-1
k = i mod (n-1)
j=1+k

if x(i) >= 0.0 then
mx = True
else
mx = False
end if
if x(j) >= 0.0 then
nx = True
else
nx = False
end if
if y(i) >= 0.0 then
my = True
else
my = False
end if
if y(j) >= 0.0 then
ny = True
else
ny = False
end if

if( not ((my or ny) and (mx or nx)) or (mx and nx)) then continue

if( not (my and ny and (mx or nx) and not (mx and nx))) then
w = ((y(i)*x(j)-x(i)*y(j))/(x(j)-x(i)))
if w < 0 then
Continue
elseif w = 0 then
inout=0
exit
else // w is positive
inout=-inout
end if
else
inout=-inout
end if
next

if inout >= 0 then // if the point is on the edge of the polygon we count it as if it were inside the polygon
inPoly = True
else
inPoly = False
end if

return inPoly[/code]

This is one of many implementations of a simple algorithm where, if a vertical line through a point intersects the (closed) polygon an odd number of times the point is inside the polygon; if an even number of time than it is outside the polygon.

I use this in a lasso routine so that I can select points inside a lasso. In that case, the lasso outline is the polygon and I check each point in my target array of points to see which are inside the lasso. However, it should work for you simple case for a polygon of arbitrary complexity as long as the polygon does not cross itself.

This is my API 2.0 simplification of the above, but passing polygon x,y vertices as array of pairs x:y, like Array(5:5, 15:5, 10:30)

[code]Public Function PointInPoly(px as double, py as double, xy() as Pair) as Boolean

// Parameters:
// px - x-coordinate of point clicked
// py - y-coordinate of point clicked.
// xy - array containing pairs of x:y coordinates of vertices of the polygon.
// ----------
// Returns True if the point is inside of the polygon.

Var x(-1), y(-1), w As Double
Var mx,my,nx,ny As Boolean
Var i, j As Integer
Var inout As Integer = -1

For Each p As Pair in xy
x.AddRow (p.Left - px)
y.AddRow (p.Right - py)
Next

For i=0 to xy.LastRowIndex

j = (i mod xy.LastRowIndex)+1

mx = x(i) >= 0.0
nx = x(j) >= 0.0
my = y(i) >= 0.0
ny = y(j) >= 0.0

If (my Or ny) And (mx Or nx) And (Not mx Or Not nx) Then
  
  If my And ny And (mx Or nx) And (Not mx Or Not nx) Then
    
    inout=-inout
    
  else
    
    w = ((y(i)*x(j)-x(i)*y(j))/(x(j)-x(i)))
    
    If w > 0 Then
      
      inout=-inout
      
    ElseIf w = 0 then
      
      inout=0
      Exit
      
    End
    
  End
  
End

Next

Return inout >= 0 // The edge of the polygon (0) counts as if it were inside the polygon (>0)

End Function
[/code]

Pair ins’t available in the iOS framework.

Thanks for taking the time to modernize the code! Obviously, I didn’t take time to do more than a minimal translation, getting rid of GOTO statements, etc., without much thought for more modern data structures. I use it in an iPad mapping program I wrote and, since it worked well and quickly as is, I didn’t invest any more time in it.

In iOS, one could pass an array of Points which would accomplish much the same thing as the Pair construct.

Wow, that’s weird. Because the x:y syntax is a language feature as x+y for sum or x(i) for indexing. In such case it’s clear how it works, and someone needing an iOS compatible one, can easily adapt it to some kind of “iOS tuple” parameter, or split it again in 2 arrays.

That was the objective, speed, and an easier way of passing x,y at once. The iOS inconsistency of the language was unexpected.

Here’s Ricks simplification of my code, but passing the variables as Points rather than Pairs. I gather from the documentation that Points (which have always worked in iOS framework) are now available in the desktop frameworks as of 2019r2. Note that the Subtract operator works with points so we can simplify the first for loop a bit

[code]Public Function PointInPoly(p as Point, xy() as Point) as Boolean

// Parameters:
// p - a Point with the x- and y-coordinates of point clicked
// xy - array of Points containing the x, y coordinates of vertices of the polygon.
// ----------
// Returns True if the point is inside of the polygon.

Var w As Double
Var pt(-1) as Point
Var mx,my,nx,ny As Boolean
Var i, j As Integer
Var inout As Integer = -1

For i = 0 to xy.LastRowIndex
pt.AddRow (xy(i) - p)
Next

For i=0 to xy.LastRowIndex

j = (i mod xy.LastRowIndex)+1

mx = pt(i).X >= 0.0
nx = pt(j).X >= 0.0
my = pt(i).Y >= 0.0
ny = pt(j).Y >= 0.0

If (my Or ny) And (mx Or nx) And (Not mx Or Not nx) Then

If my And ny And (mx Or nx) And (Not mx Or Not nx) Then
  
  inout=-inout
  
else
  
  w = ((pt(i).Y * pt(j).X - pt(i).X * pt(j).Y) / (pt(j).X - pt(i).X))
  
  If w > 0 Then
    
    inout=-inout
    
  ElseIf w = 0 then
    
    inout=0
    Exit
    
  End
  
End

End

Next

Return inout >= 0 // The edge of the polygon (0) counts as if it were inside the polygon (>0)

End Function[/code]

Isn’t great the internet and collaborative work? An ancient Fortran code now lives again shorter, highly optimized, and more friendly.

For extra beauty, and who knows a clock tick, we can change the:

For i = 0 to xy.LastRowIndex pt.AddRow (xy(i) - p) Next

To:

For Each v As Point in xy pt.AddRow (v - p) Next

Obrigado! Yeah, although it is going to take me quite a while to get used to (or prefer) “AddRow” over “Append” and “LastRowIndex” over “Ubound”. But I guess that’s a different thread…

Now passing “points”, due to it’s nature needing object instantiation, seems a pain without a helper function,

Var myPoly() As Point = Array( New Point(5, 5), New Point(25, 5), New Point(10, 30) ) PointInPoly( New Point(10, 20), myPoly )

Better return X As Double, Y As double for the first parameters for a PointInPoly( 10, 20, myPoly )

And the array of Points needs a helper function like: Var myPoly() As Point = ArrPoints( 5, 5, 25, 5, 10, 30 )

[code]Public Function ArrPoints(ParamArray pv As Double) as Point()

Var pts() As Point
Var x As Double
Var isX As Boolean = False

If pv.LastRowIndex Mod 2 = 0 Then
Raise New RuntimeException(“ArrPoints needs x,y pairs”)
End

For Each v As Double in pv
isX = Not isX
If isX Then
x = v
Else
pts.AddRow New Point(x,v)
End
Next

Return pts

End Function
[/code]