Koch Snowflake

Developed or developing a new product in PureBasic? Tell the world about it.
Little John
Addict
Addict
Posts: 4527
Joined: Thu Jun 07, 2007 3:25 pm
Location: Berlin, Germany

Koch Snowflake

Post by Little John »

Hi all,

just for fun, here is a small program that generates a Koch Snowflake. This is one of the earliest fractal curves which have been described.
The program also allows for "negative iterations" which means that the smaller triangles are drawn on the inside of the bigger ones rather than on the outside. ( For iterations = -1, you might be reminded of a certain make of car. :-) )

Download (Windows 32 bit EXE, 194 KB)
davido
Addict
Addict
Posts: 1890
Joined: Fri Nov 09, 2012 11:04 pm
Location: Uttoxeter, UK

Re: Koch Snowflake

Post by davido »

@Little John,

Very nice. The negative iterations are a nice touch. :D

Thank you.
DE AA EB
Little John
Addict
Addict
Posts: 4527
Joined: Thu Jun 07, 2007 3:25 pm
Location: Berlin, Germany

Re: Koch Snowflake

Post by Little John »

Thank you, davido. :)
User avatar
aston
User
User
Posts: 64
Joined: Wed Nov 18, 2009 11:18 pm

Re: Koch Snowflake

Post by aston »

Great program Little John
do we can see source code if is not a problem.
thanks
Little John
Addict
Addict
Posts: 4527
Joined: Thu Jun 07, 2007 3:25 pm
Location: Berlin, Germany

Re: Koch Snowflake

Post by Little John »

Hi aston,

you are welcome!

Here is a short code that contains the core parts of the program.
Is this sufficient?

//edit 2014-10-05:
  • Fixed: SpinGadget events were not handled correctly on Linux.
  • Made some cosmetic changes.

Code: Select all

; Jürgen Lüthje, 2014-10-05
; tested with
; - PB 5.23 LTS (x64) and PB 5.31 beta 1 (x64) on Windows 7
; - PB 5.31 beta 1 (x64) on Linux Mint 17 Cinnamon

EnableExplicit

CompilerIf #PB_Compiler_OS = #PB_OS_Windows
   #DefaultDPIx = 96.0     ; default DPI on Windows
   #DefaultDPIy = 96.0
   
   Global g_ScaleDPIx.f = 1.0
   Global g_ScaleDPIy.f = 1.0
   
   Procedure _InitScaleDPI()
      ; Windows 5.0 or higher needed for minimum functionality of this procedure
      ; [simplified after <http://www.purebasic.fr/english/viewtopic.php?f=12&t=40507>, 2010-01-02
      ;  see also <http://msdn.microsoft.com/en-us/library/windows/desktop/dd464660%28v=vs.85%29.aspx>]
      Protected *IsProcessDPIAware, *SetProcessDPIAware
      Protected dll.i, dc.i, lpx.i, lpy.i, dpiaware.i=#False
      
      ; Only use this in EXEs, as DLLs inherit DPI from the calling process.
      CompilerIf #PB_Compiler_ExecutableFormat = #PB_Compiler_Executable
         dll = OpenLibrary(#PB_Any, "user32.dll")
         If dll
            *IsProcessDPIAware = GetFunction(dll, "IsProcessDPIAware")
            If *IsProcessDPIAware
               dpiaware = CallFunctionFast(*IsProcessDPIAware)
            EndIf
            If Not dpiaware
               *SetProcessDPIAware = GetFunction(dll, "SetProcessDPIAware")
               If *SetProcessDPIAware
                  CallFunctionFast(*SetProcessDPIAware)
               EndIf
            EndIf
            CloseLibrary(dll)
         EndIf
      CompilerEndIf
      
      dc = GetDC_(#Null)
      If dc
         lpx = GetDeviceCaps_(dc, #LOGPIXELSX)
         lpy = GetDeviceCaps_(dc, #LOGPIXELSY)
         If lpx > 0
            g_ScaleDPIx = lpx/#DefaultDPIx
         EndIf
         If lpy > 0
            g_ScaleDPIy = lpy/#DefaultDPIy
         EndIf
         ReleaseDC_(#Null, dc)
      EndIf
   EndProcedure
   
   _InitScaleDPI()
   
CompilerElse
   #Black = $000000
   #White = $FFFFFF
   
   Global g_ScaleDPIx.f = 1.25   ; adjust this value to your system
   Global g_ScaleDPIy.f = 1.25   ; adjust this value to your system
CompilerEndIf   


Macro DPIx (_x_)
   (_x_) * g_ScaleDPIx
EndMacro
Macro DPIy (_y_)
   (_y_) * g_ScaleDPIy
EndMacro


; -- Text
#ProgVersion$ = "Koch Snowflake 1.01 light"
#GuiTextIterations$ = "Iterations" + #LF$ + "(-5 to +5):"

Enumeration Windows
   #winMain
EndEnumeration

Enumeration Gadgets
   #gdgKoch
   #spnText
   #spnCtrl
EndEnumeration

Enumeration Images
   #imgKoch
EndEnumeration

#Min_Iterations = -5
#Max_Iterations =  5

;----------------
#Win_Width  = 300                ; change this value if desired
#Win_Height = 380                ; change this value if desired
;----------------

#Image_Size   = #Win_Width - 20
#FlakeSideLen = 0.8*#Image_Size - 20
#FlakeBase_X  = (#Image_Size - #FlakeSideLen) / 2.0
#FlakeBase_Y  = #Image_Size/2.0 + #FlakeSideLen * 0.8660254/3.0   ; 0.8660254 = Sin(Radian(60.0))
#SpinText_X   = 10
#SpinText_Y   = #Win_Height - 75
#SpinCtrl_X   = #SpinText_X + 60
#SpinCtrl_Y   = #Win_Height - 70


Macro Polar2Rect_X (_distance_, _angle_)
   ; -- convert polar coordinates to rectangular coordinates
   ; in : _distance_: must be >= 0
   ;      _angle_   : expressed in rad
   ;                  (positive means counter-clockwise)
   ; out: corresponding x coordinate
   
   ((_distance_) * Cos(_angle_))
EndMacro

Macro Polar2Rect_Y (_distance_, _angle_)
   ; -- convert polar coordinates to rectangular coordinates
   ; in : _distance_: must be >= 0
   ;      _angle_   : expressed in rad
   ;                  (positive means counter-clockwise)
   ; out: corresponding y coordinate
   
   (-(_distance_) * Sin(_angle_))
EndMacro


Procedure DrawCurve (img.i, x1.d, y1.d, angle1.d, sideLength.d, iterations.i)
   ; -- draw *one* Koch curve
   ; in: img       : number of image to draw on
   ;     {x1,y1}   : starting point of current section
   ;     angle1    : angle (in rad) between positive x-axis and current section
   ;                 (positive means counter-clockwise)
   ;     sideLength: length of current section
   ;     iterations: number of sections = 4^iterations
   ;                 (0 iterations means straight line)
   Protected.d x2, y2, angle2, x3, y3, angle3, x4, y4
   
   If iterations > 0
      sideLength / 3.0
      iterations - 1
      
      ; first section
      DrawCurve(img, x1, y1, angle1, sideLength, iterations)
      
      ; second section
      x2 = x1 + Polar2Rect_X(sideLength, angle1)
      y2 = y1 + Polar2Rect_Y(sideLength, angle1)
      angle2 = angle1 - #PI/3.0
      DrawCurve(img, x2, y2, angle2, sideLength, iterations)
      
      ; third section
      x3 = x2 + Polar2Rect_X(sideLength, angle2)
      y3 = y2 + Polar2Rect_Y(sideLength, angle2)
      angle3 = angle1 + #PI/3.0
      DrawCurve(img, x3, y3, angle3, sideLength, iterations)
      
      ; fourth section
      x4 = x3 + Polar2Rect_X(sideLength, angle3)
      y4 = y3 + Polar2Rect_Y(sideLength, angle3)
      DrawCurve(img, x4, y4, angle1, sideLength, iterations)
      
   Else
      ; draw section
      x2 = x1 + Polar2Rect_X(sideLength, angle1)
      y2 = y1 + Polar2Rect_Y(sideLength, angle1)
      StartDrawing(ImageOutput(img))
      LineXY(Int(x1), Int(y1), Int(x2), Int(y2), #Black)
      StopDrawing()
   EndIf
EndProcedure


Procedure DrawSnowFlake (x.i, y.i, length.i, iterations.i)
   ; -- draw Koch snowflake (which consists of 3 Koch curves)
   ; in: {x,y}     : lower left vertex of main triangle
   ;     length    : side length of main triangle
   ;     iterations: number of desired iterations
   
   CreateImage(#imgKoch, DPIx(#Image_Size), DPIy(#Image_Size), 24, #White)
   
   If iterations >= 0
      DrawCurve(#imgKoch, x,            y,                              0.0 , length, iterations)   ; bottom, from left to right
      DrawCurve(#imgKoch, x+length,     y,                     Radian(120.0), length, iterations)   ; right , from bottom to top
      DrawCurve(#imgKoch, x+length/2.0, y-length*Sin(#PI/3.0), Radian(240.0), length, iterations)   ; left  , from top to bottom
   Else
      DrawCurve(#imgKoch, x,            y,                     Radian( 60.0), length, -iterations)  ; left  , from bottom to top
      DrawCurve(#imgKoch, x+length/2.0, y-length*Sin(#PI/3.0), Radian(-60.0), length, -iterations)  ; right , from top to bottom
      DrawCurve(#imgKoch, x+length,     y,                     Radian(180.0), length, -iterations)  ; bottom, from right to left
   EndIf
EndProcedure


Procedure On_ChangeIterations()
   Protected.i iterations
   
   Select EventType()
      Case #PB_EventType_Up, #PB_EventType_Down
         iterations = GetGadgetState(#spnCtrl)
         SetGadgetText(#spnCtrl, Str(iterations))
         
      Case #PB_EventType_Change
         iterations = Val(GetGadgetText(#spnCtrl))
         If iterations < #Min_Iterations
            iterations = #Min_Iterations
            SetGadgetText(#spnCtrl, Str(iterations))
         ElseIf iterations > #Max_Iterations
            iterations = #Max_Iterations
            SetGadgetText(#spnCtrl, Str(iterations))
         EndIf
         SetGadgetState(#spnCtrl, iterations)
   EndSelect
   
   FreeImage(#imgKoch)
   DrawSnowFlake(DPIx(#FlakeBase_X), DPIy(#FlakeBase_Y), DPIx(#FlakeSideLen), iterations)
   SetGadgetState(#gdgKoch, ImageID(#imgKoch))
EndProcedure


;===========================
;-- START OF MAIN PROGRAM
;===========================

Define ev.i, iterations.i=0

; -- Create window
If OpenWindow(#winMain, #PB_Ignore, #PB_Ignore, DPIx(#Win_Width), DPIy(#Win_Height), #ProgVersion$,
              #PB_Window_SystemMenu | #PB_Window_ScreenCentered) = 0
   MessageRequester(#ProgVersion$, "Fatal error: Can't create main window.")
   End
EndIf

; -- Create gadgets
DrawSnowFlake(DPIx(#FlakeBase_X), DPIy(#FlakeBase_Y), DPIx(#FlakeSideLen), iterations)
ImageGadget(#gdgKoch, DPIx(10), DPIy(10), DPIx(#Image_Size), DPIy(#Image_Size), ImageID(#imgKoch))
TextGadget(#spnText, DPIx(#SpinText_X), DPIy(#SpinText_Y), DPIx(55), DPIy(30), #GuiTextIterations$)
SpinGadget(#spnCtrl, DPIx(#SpinCtrl_X), DPIy(#SpinCtrl_Y), DPIx(40), DPIy(20), #Min_Iterations, #Max_Iterations)
SetGadgetState(#spnCtrl, iterations) : SetGadgetText(#spnCtrl, Str(iterations))
CreateStatusBar(0, WindowID(#winMain))
AddStatusBarField(#PB_Ignore)
StatusBarText(0, 0, " Image: " + Str(ImageWidth(#imgKoch)) + " x " + Str(ImageHeight(#imgKoch)))
SetActiveGadget(#spnCtrl)

; -- Bind events
BindGadgetEvent(#spnCtrl, @On_ChangeIterations())

; -- Main loop
Repeat
   ev = WaitWindowEvent()
Until ev = #PB_Event_CloseWindow
Last edited by Little John on Sun Oct 05, 2014 12:11 pm, edited 1 time in total.
User avatar
aston
User
User
Posts: 64
Joined: Wed Nov 18, 2009 11:18 pm

Re: Koch Snowflake

Post by aston »

Hi...and thanks
I think that would be ok :)
davido
Addict
Addict
Posts: 1890
Joined: Fri Nov 09, 2012 11:04 pm
Location: Uttoxeter, UK

Re: Koch Snowflake

Post by davido »

@Little John,

Thank you for disclosing the code. Its always nice to learn how it should be done. :D

Checked it on my wife's MacBook Pro + PureBasic 5.30. It worked perfectly.
DE AA EB
Little John
Addict
Addict
Posts: 4527
Joined: Thu Jun 07, 2007 3:25 pm
Location: Berlin, Germany

Re: Koch Snowflake

Post by Little John »

Hi davido,

I don't have the opportunity to check code on a Mac computer myself.
It's good to know that it works. :-)

Thank you!
Little John
Addict
Addict
Posts: 4527
Joined: Thu Jun 07, 2007 3:25 pm
Location: Berlin, Germany

Re: Koch Snowflake

Post by Little John »

Code slightly improved (see above).
Post Reply