Drawing an area on a Screen()
Drawing an area on a Screen()
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.
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.
Re: Drawing an area on a Screen()
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.
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.
Re: Drawing an area on a Screen()
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.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.
Re: Drawing an area on a Screen()
Yes (at least with PureBasic) either beforehand or realtime -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.
TransformSprites() can be used for that.
Re: Drawing an area on a Screen()
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?
Re: Drawing an area on a Screen()
Imagine a big map full of these overlapping areas, and extreme zoom in. They could probably be "filled" with oblique strokes.
Re: Drawing an area on a Screen()
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.
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)
Re: Drawing an area on a Screen()
Wow, thanks a lot! It will be very useful.
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 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.
Re: Drawing an area on a Screen()
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.
Re: Drawing an area on a Screen()
Canvas on a screen? My idea was to draw an image, then catch it in a sprite (the vector library is really cool!).
Re: Drawing an area on a Screen()
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.
Re: Drawing an area on a Screen()
Any reason you would use TransformSprite() instead of:
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!
Code: Select all
ZoomSprite(region()\spr, zoom * region()\sprW, zoom * region()\sprH)
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!
Re: Drawing an area on a Screen()
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.
Re: Drawing an area on a Screen()
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?
In other words, how to avoid the full map going to the top left when zooming out?
Re: Drawing an area on a Screen()
You can do it with my code by changing the scroll/zoom part as follows:
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.
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
((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.