Drawing an area on a Screen()

Advanced game related topics
Joubarbe
Enthusiast
Enthusiast
Posts: 555
Joined: Wed Sep 18, 2013 11:54 am
Location: France

Drawing an area on a Screen()

Post by Joubarbe »

Hi,

What is the best way to draw an irregular area over a map in 2D drawing? It's said in the documentation that displaying a sprite that is bigger than the screen is not recommended. Then how do you zoom in this area? because my idea is to have very big areas with details only appearing on a close zoom level. Should I directly draw on Screen, like I'd do on a Canvas? (it doesn't seem recommended either)

By irregular area, I mean picking random points and joining them using several Bézier curves.
User avatar
Mijikai
Addict
Addict
Posts: 1360
Joined: Sun Sep 11, 2016 2:17 pm

Re: Drawing an area on a Screen()

Post by Mijikai »

U could cut one big image/sprite into small pieces and display only those that are inside the render area.
Or store offsets of the points u want to connect (multiplied by the zoom level) -
connect all lines inside the render area to the fist point that lays outside.
Joubarbe
Enthusiast
Enthusiast
Posts: 555
Joined: Wed Sep 18, 2013 11:54 am
Location: France

Re: Drawing an area on a Screen()

Post by Joubarbe »

Mijikai wrote:Or store offsets of the points u want to connect (multiplied by the zoom level) -
connect all lines inside the render area to the fist point that lays outside.
That would mean recreating the sprites at each new zoom, right? I have hard times understanding how sprites are supposed to be used when they display graphic elements bigger than the screen.
User avatar
Mijikai
Addict
Addict
Posts: 1360
Joined: Sun Sep 11, 2016 2:17 pm

Re: Drawing an area on a Screen()

Post by Mijikai »

Joubarbe wrote: That would mean recreating the sprites at each new zoom, right? I have hard times understanding how sprites are supposed to be used when they display graphic elements bigger than the screen.
Yes (at least with PureBasic) either beforehand or realtime -
TransformSprites() can be used for that.
User avatar
Demivec
Addict
Addict
Posts: 4086
Joined: Mon Jul 25, 2005 3:51 pm
Location: Utah, USA

Re: Drawing an area on a Screen()

Post by Demivec »

I'd like to help but I am having some difficulty picturing what you would like to do. Can you supply a workup of what the effect might look like, either a hand drawn image or a mockup that illustrates your goal?
Joubarbe
Enthusiast
Enthusiast
Posts: 555
Joined: Wed Sep 18, 2013 11:54 am
Location: France

Re: Drawing an area on a Screen()

Post by Joubarbe »

Image

Imagine a big map full of these overlapping areas, and extreme zoom in. They could probably be "filled" with oblique strokes.
#NULL
Addict
Addict
Posts: 1440
Joined: Thu Aug 30, 2007 11:54 pm
Location: right here

Re: Drawing an area on a Screen()

Post by #NULL »

Maybe use an additional tiling layer for the regions, similar to a tilemap. So the shapes are split up into tiles and you only draw the parts that are in screen.
I made a little test/demo. Moving the mouse to scroll, mouse buttons to zoom. Keys 1, 2, 3 for mode.
I used simple circles as regions.
Mode 1 draws all regions drectly to the screen in a single drawing block.
Mode 2 creates a sprite per region
Mode 3 tiles the world up into equal sized areas (sprites) each containing only parts of the circles

If your world is very large that will create a lot of sprites though, even if each of them is smaller than the screen and only the visible one are actually displayed. Anyway, just zooming sprites may not be enough if you want very large scale/zoom ratios or even infinite zoom. You may need a completely different approach without buffers/sprites, only store abstract shapes (like beziers curves) and rasterize it to pixels as a last step by drawing directly to screen.

Code: Select all

EnableExplicit

InitSprite()
InitMouse()
InitKeyboard()

RandomSeed(1)

Define ww=800
Define wh=600
Define style
style | #PB_Window_ScreenCentered
style | #PB_Window_SystemMenu
style | #PB_Window_MinimizeGadget
Define win, event, em, quit
Define i, k

If 01
  win=OpenWindow(#PB_Any, 50,100, ww,wh, "", style)
  AddKeyboardShortcut(win, #PB_Shortcut_Escape, 10)
  OpenWindowedScreen(WindowID(win), 0,0, ww, wh)
  ;SetFrameRate(9999)
Else
  OpenScreen(ww,wh,32,"", #PB_Screen_NoSynchronization)
EndIf

#tileSize = 32
#tileMapSize = 100

#regionSize = 128
#regionMapSize = #tileMapSize * #tileSize / #regionSize

Define cursor = CreateSprite(#PB_Any, 10, 10, #PB_Sprite_AlphaBlending)
StartDrawing(SpriteOutput(cursor))
  DrawingMode(#PB_2DDrawing_AllChannels)
  Box(0, 0, 5, 5, $ffffffff)
StopDrawing()

Define tile = CreateSprite(#PB_Any, #tileSize, #tileSize, #PB_Sprite_AlphaBlending)
StartDrawing(SpriteOutput(tile))
  DrawingMode(#PB_2DDrawing_AllChannels)
  Box(1, 1, #tileSize-2, #tileSize-2, $ff886600)
StopDrawing()

Dim tileMap(#tileMapSize, #tileMapSize)
For k=0 To #tileMapSize
  For i=0 To #tileMapSize
    tileMap(k, i) = Random(1) * tile
  Next
Next

Dim regionMap(#regionMapSize, #regionMapSize)
For k=0 To #regionMapSize
  For i=0 To #regionMapSize
    regionMap(k, i) = CreateSprite(#PB_Any, #regionSize, #regionSize, #PB_Sprite_AlphaBlending)
  Next
Next

Define sprFR = CreateSprite(#PB_Any, 400,20)
Define FR, FRt, FR$

Define.f offsetX
Define.f offsetY
Define.f zoom.f = 1.0
Define mode = 1

Structure sRegion
  x.f
  y.f
  r.f
  
  ; only used by some modes
  spr.i
  sprW.i
  sprH.i
EndStructure

NewList region.sRegion()
For k=0 To #tileMapSize
  For i=0 To #tileMapSize
    If k%8=0 And i%8=0
      AddElement(region())
      region()\x = i * #tileSize - 20 + Random(40)
      region()\y = k * #tileSize - 20 + Random(40)
      region()\r = Random(#tileSize * 6)
    EndIf
  Next
Next

Define.f x
Define.f y
Define initialized2
Define initialized3
Define RegionMinX
Define RegionMaxX
Define RegionMinY
Define RegionMaxY


Repeat
  ExamineMouse()
  ExamineKeyboard()
  
  If KeyboardPushed(#PB_Key_1) : mode = 1 : EndIf
  If KeyboardPushed(#PB_Key_2) : mode = 2 : EndIf
  If KeyboardPushed(#PB_Key_3) : mode = 3 : EndIf
  
  ; events
  If IsWindow(win) ;{
    Repeat
      event = WindowEvent()
      em = EventMenu()
      Select event
        Case #PB_Event_CloseWindow
          quit = #True
        Case #PB_Event_Menu
          Select em
          Case 10
            quit = #True
          EndSelect
      EndSelect
    Until Not event
    ;}
  EndIf
  
  ; scroll/zoom
  offsetX + MouseDeltaX()
  offsetY + MouseDeltaY()
  If MouseButton(1)
    zoom + 0.01
  ElseIf MouseButton(2)
    zoom - 0.01
  EndIf
  
  TransformSprite(tile,
                  0, 0,
                  zoom * #tileSize, 0,
                  zoom * #tileSize, zoom * #tileSize,
                  0, zoom * #tileSize)
  
  ; draw tilemap
  For k=0 To #tileMapSize
    For i=0 To #tileMapSize
      x = zoom * i * #tileSize - offsetX
      y = zoom * k * #tileSize - offsetY
      If tileMap(k, i)
        If (x >= zoom*-#tileSize) And (x <= ww) And (y >= zoom*-#tileSize) And (y <= wh)
          DisplayTransparentSprite(tileMap(k, i), x, y)
        EndIf
      EndIf        
    Next
  Next
  
  Select mode
      
    Case 1
      ; draw all regions directly
      
      StartDrawing(ScreenOutput())
      ForEach region()
        DrawingMode(#PB_2DDrawing_Outlined)
        Circle(zoom * region()\x - offsetX, 
               zoom * region()\y - offsetY, 
               zoom * region()\r, 
               $ffffbbbb)
      Next
      StopDrawing()
      
    Case 2
      ; use sprite per region
      
      If Not initialized2
        initialized2 = #True
        ; create sprite for each region
        ForEach region()
          region()\sprW = 2 * region()\r + 1
          region()\sprH = 2 * region()\r + 1
          region()\spr = CreateSprite(#PB_Any, region()\sprW, region()\sprH, #PB_Sprite_AlphaBlending)
          StartDrawing(SpriteOutput(region()\spr))
          DrawingMode(#PB_2DDrawing_Outlined | #PB_2DDrawing_AllChannels)
          Box(0, 0, OutputWidth(), OutputHeight(), $aa00aa00)
          Circle(region()\r, 
                 region()\r, 
                 region()\r, 
                 $ffffbbbb)
          StopDrawing()
        Next
      EndIf
      
      ; display each region sprite
      ForEach region()
        TransformSprite(region()\spr,
                        0, 0,
                        zoom * region()\sprW, 0,
                        zoom * region()\sprW, zoom * region()\sprH,
                        0, zoom * region()\sprH)
        DisplayTransparentSprite(region()\spr, 
                                 zoom*(region()\x - region()\sprW/2) - offsetX, 
                                 zoom*(region()\y - region()\sprH/2) - offsetY)
      Next
      
    Case 3
      ; use regionmap
      
      If Not initialized3
        initialized3 = #True
        ; create regionmap
        For k=0 To #regionMapSize
          For i=0 To #regionMapSize
            If regionMap(k, i)
              x = zoom * i * #regionSize
              y = zoom * k * #regionSize
              StartDrawing(SpriteOutput(regionMap(k, i)))
              DrawingMode(#PB_2DDrawing_Outlined | #PB_2DDrawing_AllChannels)
              Box(0, 0, OutputWidth(), OutputHeight(), $aa00aa00)
              ForEach region()
                RegionMinX = region()\x - region()\r - 1
                RegionMaxX = region()\x + region()\r + 1
                RegionMinY = region()\y - region()\r - 1
                RegionMaxY = region()\y + region()\r + 1
                ; draw region if in current regionmap area
                If (x <= RegionMaxX) And ((x+#regionSize) >= RegionMinX) And (y <= RegionMaxY) And ((y+#regionSize) >= RegionMinY)
                  Circle(region()\x - x, 
                         region()\y - y, 
                         region()\r,
                         $ffffbbbb)
                EndIf
              Next
              StopDrawing()
            EndIf
          Next
        Next
      EndIf
      
      ; display each regionmap area sprite
      For k=0 To #regionMapSize
        For i=0 To #regionMapSize
          If regionMap(k, i)
            x.f = zoom * i * #regionSize - offsetX
            y.f = zoom * k * #regionSize - offsetY
            If (x >= zoom*-#regionSize) And (x <= ww) And (y >= zoom*-#regionSize) And (y <= wh)
              
              TransformSprite(regionMap(k, i),
                              0, 0,
                              zoom * #regionSize, 0,
                              zoom * #regionSize, zoom * #regionSize,
                              0, zoom * #regionSize)
              
              DisplayTransparentSprite(regionMap(k, i), x, y)
            EndIf
          EndIf
        Next
      Next
      
  EndSelect
  
  ; display cursor
  DisplayTransparentSprite(cursor, MouseX(), MouseY())
  
  ; update fps/info sprite
  FR+1
  If ElapsedMilliseconds()>FRt
    FRt=ElapsedMilliseconds()+500
    FR$=Str(FR*2)
    FR=0
    StartDrawing(SpriteOutput(sprFR))
      DrawingMode(#PB_2DDrawing_Transparent)
      Box(0,0, OutputWidth(), OutputHeight(), $ff0000)
      DrawText(0,0, "fps:" + FR$ + " mode:" + mode)
    StopDrawing()
  EndIf
  
  ; display fps/info
  DisplaySprite(sprFR, 0,0)
  
  FlipBuffers()
  ClearScreen($333333)
  If IsWindow(win)
    ;Delay(10)
  EndIf
Until quit Or KeyboardPushed(#PB_Key_Escape)
    
Joubarbe
Enthusiast
Enthusiast
Posts: 555
Joined: Wed Sep 18, 2013 11:54 am
Location: France

Re: Drawing an area on a Screen()

Post by Joubarbe »

Wow, thanks a lot! It will be very useful.
#NULL wrote:If your world is very large that will create a lot of sprites though, even if each of them is smaller than the screen and only the visible one are actually displayed. Anyway, just zooming sprites may not be enough if you want very large scale/zoom ratios or even infinite zoom. You may need a completely different approach without buffers/sprites, only store abstract shapes (like beziers curves) and rasterize it to pixels as a last step by drawing directly to screen.
About that... What is the problem with drawing on screen exactly? Instinctively, I'd say this abstract approach seems better for my project, the map being gigantic (it supposedly takes place in a galaxy).
#NULL
Addict
Addict
Posts: 1440
Joined: Thu Aug 30, 2007 11:54 pm
Location: right here

Re: Drawing an area on a Screen()

Post by #NULL »

Sprites are already in video memory so they are very efficient. But you can absolutely do stuff by drawing, especially if you have only a single drawing block. You can also use a canvas and the vector drawing library so you get nice antialiasing.
Joubarbe
Enthusiast
Enthusiast
Posts: 555
Joined: Wed Sep 18, 2013 11:54 am
Location: France

Re: Drawing an area on a Screen()

Post by Joubarbe »

Canvas on a screen? My idea was to draw an image, then catch it in a sprite (the vector library is really cool!).
#NULL
Addict
Addict
Posts: 1440
Joined: Thu Aug 30, 2007 11:54 pm
Location: right here

Re: Drawing an area on a Screen()

Post by #NULL »

No, you would use just a canvas in a window, without a screen. You can't use sprites then, but could still draw images. And Yes, you can also verctordraw to an image and put it on a sprite, then display the sprite, or draw the image directly to the screen.
Joubarbe
Enthusiast
Enthusiast
Posts: 555
Joined: Wed Sep 18, 2013 11:54 am
Location: France

Re: Drawing an area on a Screen()

Post by Joubarbe »

Any reason you would use TransformSprite() instead of:

Code: Select all

ZoomSprite(region()\spr, zoom * region()\sprW, zoom * region()\sprH)
Less headaches :)

Anyway, ScreenOutput() has really poor performances, and canvas too when it comes to real time (+ they have no alpha), I think the second mode is the way to go!
#NULL
Addict
Addict
Posts: 1440
Joined: Thu Aug 30, 2007 11:54 pm
Location: right here

Re: Drawing an area on a Screen()

Post by #NULL »

I don't know if ZoomSprite() is just a convenience function or what's the difference. TransformSprite can do other things like skewing and mirroring/flipping. As for zooming only I guess ZoomSprite() is appropriate. :)
Joubarbe
Enthusiast
Enthusiast
Posts: 555
Joined: Wed Sep 18, 2013 11:54 am
Location: France

Re: Drawing an area on a Screen()

Post by Joubarbe »

Sorry to bother again, I'm really not good at that... How would you do in your example to keep the camera centered? When you zoom in and out while keeping the top left at the center of the screen, the top left (0, 0) remains at the center. How to do that when another point is at the center of the screen?

In other words, how to avoid the full map going to the top left when zooming out?
#NULL
Addict
Addict
Posts: 1440
Joined: Thu Aug 30, 2007 11:54 pm
Location: right here

Re: Drawing an area on a Screen()

Post by #NULL »

You can do it with my code by changing the scroll/zoom part as follows:

Code: Select all

  ; scroll/zoom
  offsetX + MouseDeltaX()
  offsetY + MouseDeltaY()
  Define.f zoomPrevious = zoom
  If MouseButton(1)
    zoom + 0.01
  ElseIf MouseButton(2)
    zoom - 0.01
  EndIf
  If zoom <> zoomPrevious
    offsetX - ((((offsetX + ww/2) / zoom) - ((offsetX + ww/2) / zoomPrevious)) * zoom)
    offsetY - ((((offsetY + wh/2) / zoom) - ((offsetY + wh/2) / zoomPrevious)) * zoom)
  EndIf
I added a variable zoomPrevious, and if the zoom changed I adapt the offset to center again the previously centered world coordinate.
((offsetX + ww/2) / zoom) is the world coordinate that is currently centered in the screen with the current/new zoom. Then I get the offset difference by substracting ((offsetX + ww/2) / zoomPrevious)), the world coordinate that was centered with the previous/old zoom. That difference is than rescaled back to the current zoom by * zoom and the offset is corrected by that value.
Last edited by #NULL on Mon Jun 03, 2019 1:00 pm, edited 2 times in total.
Post Reply