Skip to content

Instantly share code, notes, and snippets.

@SoulFireMage
Last active December 24, 2015 11:16
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save SoulFireMage/11af769f811f1c4f36d0 to your computer and use it in GitHub Desktop.
Snowing F#! Thanks to Phil Trelford for giving me both a good starting point, random art and encouragement :).
open Microsoft.Xna.Framework
open Microsoft.Xna.Framework.Graphics
open Microsoft.Xna.Framework.Input
open System
open RandomArt
module Text =
open System.IO
open System.Drawing
open System.Windows.Forms
//Build a new bitmap with it's own F#
let getGraphics (h:int)(w:int) (p:int) =
let bitmap = new Bitmap(w + p,h + p)
let g = Graphics.FromImage(bitmap)
g.SmoothingMode <- Drawing2D.SmoothingMode.AntiAlias
g.TextRenderingHint <- Text.TextRenderingHint.ClearTypeGridFit
g.Clear(Color.Transparent)
(g ,bitmap)
let toBitmapStream text pad =
let maxFont = 20.0f
use font = new Font(FontFamily.GenericSansSerif, maxFont)
let size = TextRenderer.MeasureText(text, font)
let g, bitmap = getGraphics size.Width size.Height pad
//Each new Bitmap has it's own (x,y) so I use this fact to simply add more F#'s to get a quasi 3D effect.
let cols = [0 .. 5] |> List.zip [0 .. 5] |> List.iter(fun i -> let x,y = i
TextRenderer.DrawText(g, text,font, new Point(x,y), Color.White))
bitmapSave bitmap
let mutable flakeSize = 50
//There's a good reason I called it a pflakeBuilder - Thanks P Trelford :)
let pflakeBuilder seed =
let height,width= flakeSize,flakeSize
let flakeComplexity =(flakeSize / 25)
let g, bitmap = getGraphics height width 0
use brush = new SolidBrush(Color.Transparent)
g.FillRectangle(brush, 0, 0, bitmap.Size.Width, bitmap.Size.Height)
g.TranslateTransform((float32 bitmap.Size.Width/2.0f), (float32 bitmap.Size.Height/2.0f))
let color = Color.FromArgb(128,255,255,255)
use brush = new SolidBrush(color)
let rand = Random()
let polys =
[for i in 1..12 ->
let w = rand.Next(seed)+rand.Next(5,7) // width
let h = rand.Next(seed)+rand.Next(1,4) // height
let m = rand.Next(h) // midpoint
let s = rand.Next(seed + 10) // start
[|0,s; -w,s+m; 0,s+h; w,s+m|]
]
for i in 0.0..60.0..300.0 do
g.RotateTransform(float32 60.0)
let poly points =
let points = [|for (x,y) in points -> Point(x*flakeComplexity,y*flakeComplexity)|]
g.FillPolygon(brush,points)
polys |> List.iter poly
bitmapSave bitmap
let w, h = 1600.0, 1200.0
let rnd = System.Random()
let mutable speed = 0.5
let mutable wind = 45.0
let mutable flakeScarcity = 6
//I needed a double random min max function
let GetNextDoubleRnd min max = float32 (rnd.NextDouble() * (max - min) + min)
type innerFlake = {texture2d: Texture2D;
mutable X: float;
mutable Y: float;
mutable V: float; //Velocity
mutable A: float; //Angle or wind
scale : Nullable<Vector2>;
mutable spinfactor: float32 ; spinFun :(float32 -> float32 -> float32) }
type flake = { mutable X:float; mutable Y:float; V:float; A:float; scale:Nullable<Vector2>;mutable spinfactor: float32 ; spinFun :(float32 -> float32 -> float32) ; innerflake: innerFlake}
type Snowflakes () as this =
inherit Game ()
let graphics = new GraphicsDeviceManager(this)
do graphics.PreferredBackBufferWidth <- int w
do graphics.PreferredBackBufferHeight <- int h
let mutable spriteBatch : SpriteBatch = null
let mutable textTexture : Texture2D = null
let mutable pad = 15 //dictate size of text bitmap
let flakes = ResizeArray<_>() //Where else to store our positions, spins and speed?
let mutable LetterContent = "F#"
override this.LoadContent() =
spriteBatch <- new SpriteBatch(this.GraphicsDevice)
override this.Update(gameTime) =
use textStream = Text.toBitmapStream LetterContent pad
//use keys to see a blizzard :)
let HandleKeys K =
match K with
| Keys.Up -> if speed <= 15.0 then speed <- speed + 0.1
| Keys.Down -> if speed >= 0.1 then speed <- speed - 0.1
| Keys.Left -> if wind <= 35.0 then wind <- wind + 0.2
| Keys.Right-> if wind >= -35.0 then wind <- wind - 0.2
| Keys.Space -> if flakeScarcity >= 1 then flakeScarcity <- flakeScarcity - 1
| Keys.Add -> if Text.flakeSize < 250 then Text.flakeSize <- Text.flakeSize + 5
| Keys.OemMinus -> if Text.flakeSize > 25 then Text.flakeSize <- Text.flakeSize - 5
| Keys.F -> if flakeScarcity <= 25 then flakeScarcity <- flakeScarcity + 1
LetterContent <- "F#"
| Keys.S -> LetterContent <- "S"
| Keys.E -> LetterContent <- "Merry Xmas"
pad <- 40
| Keys.C -> LetterContent <- "Happy New Year"
pad <- 80
| _ -> wind <- wind
if Keyboard.GetState().GetPressedKeys().Length > 0 then HandleKeys (Keyboard.GetState().GetPressedKeys().[0]) |> ignore
textTexture <- Texture2D.FromStream(this.GraphicsDevice, textStream)
//Alter the scarcity to increase odds of getting more snowflakes
if (rnd.Next(flakeScarcity)= 0) then
//Lets get both the F# flake AND unique Phil flakes in here. It's a cheap hack
let flakePosition seed =
let x, y = rnd.NextDouble() * w, -16.0
let a = wind + rnd.NextDouble() * 90.0 //(wind / 2.0)
let v = (speed + rnd.NextDouble())*2.0
let sfactor = (GetNextDoubleRnd 0.05 1.5)
let spinfactor = GetNextDoubleRnd -0.01 0.01
let spinFunction = if rnd.Next(2) % 2 = 0 then fun x y-> x + (y / 100.0f) else fun x y -> x - (y / 100.0f)
let scale = System.Nullable(new Vector2( sfactor, sfactor))
(x,y,v,a, scale, spinfactor, spinFunction)
//Needed two sets of positions/scale and so on. And yes, the seed param is unused but it forces us to get a new random set of values.
let x,y,v,a,scale,spinFactor, spinFun = flakePosition 4
let x2, y2, v2, a2, scale2, spinFactor2, spinFun2 = flakePosition 26
let getPic n = if rnd.Next(3) % 2 = 0 then Text.pflakeBuilder (rnd.Next(3)) else RandomArt.getRandomArt 1
//The innerflake is merely ensuring we get a unique flake each time!
let flake = { X=x; Y=y; V=v; A=a;scale=scale;spinfactor=spinFactor;spinFun= spinFun
innerflake={texture2d=Texture2D.FromStream(this.GraphicsDevice, getPic 4 );
X=x2; Y=y2; V=v2; A=a2;scale=scale2;spinfactor=spinFactor2;spinFun=spinFun2}}
flakes.Add(flake)
//Which flakes are on the screen? Which have gone off. As there is an innerflake, lets make sure they're both in shot
let withInBounds x y = (x < (w + 50.) && x >= -50.) || (y < h + 50. && y >= -50.) //account for the size of flake as the X and Y is top left NOT centre.
let onScreen, offScreen = flakes |> Seq.toList |> List.partition (fun flake -> withInBounds flake.X flake.Y || withInBounds flake.innerflake.X flake.innerflake.Y)
for flake in onScreen do
let r = flake.A * System.Math.PI / 180.0
let r2 = flake.innerflake.A * System.Math.PI / 180.0
//Move flakes across the screen
flake.X <- flake.X + ((cos r) * flake.V)
flake.innerflake.X <- flake.innerflake.X + ((cos r2) * flake.innerflake.V)
//Move flakes down the screen
flake.Y <- flake.Y + ((sin r) * flake.V)
flake.innerflake.Y <- flake.innerflake.Y + ((sin r2) * flake.innerflake.V)
for flake in offScreen do
flakes.Remove(flake) |> ignore
override this.Draw(gameTime) =
this.GraphicsDevice.Clear Color.Black
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend)
let origin = Nullable(Vector2(float32 textTexture.Width/2.0f, float32 textTexture.Height/2.0f))
//I wanted backwards spins as well as forwards, this is a slightly complex way todo this.
for flake in flakes do
let secondFlakeValues = flake.innerflake
let secondFlake = flake.innerflake.texture2d
//decompose the spin function and value to allow incrementing
let spinFun = flake.spinFun
flake.spinfactor <- spinFun flake.spinfactor (float32 flake.V)
let spinFun2 = secondFlakeValues.spinFun
secondFlakeValues.spinfactor <- spinFun2 secondFlakeValues.spinfactor (float32 secondFlakeValues.V)
//Necessary to spin in place rather than the top left corner
let origin = Nullable(Vector2(float32 secondFlake.Width / 2.0f, float32 secondFlake.Height / 2.0f ))
let position = Nullable(Vector2(float32 flake.X,float32 flake.Y))
let position2 = Nullable(Vector2( float32 secondFlakeValues.X + float32 secondFlake.Width / 2.0f ,
float32 secondFlakeValues.Y + float32 secondFlake.Height / 2.0f))
let scale = flake.scale
let r = flake.A * Math.PI / 180.0 //used to orientate single F# in random angles.
let r2 = 60.0 * Math.PI/180.0
//Yay SPINNERS!
let layerDepth = float32 (rnd.Next(0,2) )
for i in [0.0f .. 5.0f] do //Lets just place 6 at 60 degree angles to each other, like an F# SNOWFLAKE!!
spriteBatch.Draw(textTexture, position=position, rotation=float32 r2 * i + flake.spinfactor, scale=scale, layerDepth=layerDepth)
spriteBatch.Draw( secondFlake,position=position2, rotation=float32 secondFlakeValues.spinfactor, origin=origin, scale=secondFlakeValues.scale)
spriteBatch.End()
do
use game = new Snowflakes()
game.Run()
@SoulFireMage
Copy link
Author

Try the following keys when viewing this:

Up/Down : Speed
Left/Right : Wind bias
Space/F : Increase/Decrease flake count (probability)
OemMinus/KeyPad + : Decrease/Increase flakesize & complexity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment