###############################################################################ഀ ## burn-console.mshഀ ##ഀ ## Create a fire effect in MSH, using the Console text buffer as the renderingഀ ## surface.ഀ ##ഀ ## Great overview of the fire effect algorithm here: ഀ ## http://freespace.virgin.net/hugo.elias/models/m_fire.htmഀ ##ഀ ###############################################################################ഀ ഀ function mainഀ {ഀ write-debug "ENTER main"ഀ ## Rather than a simple red fire, we'll introduce oranges and yellowsഀ ## by including Yellow as one of the base coloursഀ $colours = "Yellow","Red","DarkRed","Black"ഀ ഀ ## The four characters that we use to dither with, along with the ഀ ## percentage of the foreground colour that they showഀ $dithering = "█","▓","▒","░"ഀ $ditherFactor = 1,0.75,0.5,0.25ഀ ഀ ## Hold the palette. We actually store each entry as a BufferCell,ഀ ## since we need to retain a foreground colour, background colour,ഀ ## and dithering character.ഀ $palette = new-object System.Management.Automation.Host.BufferCell[] 256ഀ ഀ ## Resize the console to 70, 61 so we have a consistent bufferഀ ## size for performance comparison.ഀ $bufferSize = new-object System.Management.Automation.Host.Size 70,61ഀ $host.UI.RawUI.WindowSize = $bufferSizeഀ ഀ ## Retrieve some commonly used dimensionsഀ $windowWidth = $host.UI.RawUI.WindowSize.Widthഀ $windowHeight = $host.UI.RawUI.WindowSize.Heightഀ $origin = `ഀ new-object System.Management.Automation.Host.Coordinates 0,0ഀ $dimensions = `ഀ new-object System.Management.Automation.Host.Rectangle `ഀ 0,0,$windowWidth,$windowHeightഀ ഀ ## Create our random number generatorഀ $random = new-object Randomഀ $workingBuffer = new-object System.Int32[] ($windowHeight * $windowWidth)ഀ $screenBuffer = new-object System.Int32[] ($windowHeight * $windowWidth)ഀ ഀ clear-hostഀ ഀ ## Generate the paletteഀ generatePaletteഀ # displayPaletteഀ # return;ഀ ഀ ## Update the buffer, then update the screen until the user presses a key. ഀ ## Keep track of the total time and frames generated to let us displayഀ ## performance statistics.ഀ $frameCount = 0ഀ $totalTime = time-expression {ഀ while(! $host.UI.RawUI.KeyAvailable)ഀ {ഀ updateBufferഀ updateScreenഀ $frameCount++ഀ }ഀ }ഀ ഀ ## Clean up and exitഀ $host.UI.RawUI.ForegroundColor = "Gray"ഀ $host.UI.RawUI.BackgroundColor = "Black"ഀ ഀ write-hostഀ write-host "$($frameCount / $totalTime.TotalSeconds) frames per second."ഀ write-debug "EXIT"ഀ }ഀ ഀ ## Update a back-buffer to hold all of the information we want to display onഀ ## the screen. To do this, we first re-generate the fire pixels on the bottom ഀ ## row. With that done, we visit every pixel in the screen buffer, and figureഀ ## out the average heat of its neighbors. Once we have that average, we moveഀ ## that average heat one pixel up.ഀ function updateBufferഀ {ഀ write-debug "ENTER startFireLastRow"ഀ ## Start fire on the last row of the screen bufferഀ for($column = 0; $column -lt $windowWidth; $column++)ഀ {ഀ ## There is an 80% chance that a pixel on the bottom row willഀ ## start new fire.ഀ if($random.NextDouble() -ge 0.20)ഀ {ഀ ## The chosen pixel gets a random amount of heat. This givesഀ ## us a lot of nice colour variation.ഀ $screenBuffer[($windowHeight - 2) * ($windowWidth) + $column] = `ഀ [int] ($random.NextDouble() * 255)ഀ }ഀ }ഀ write-debug "EXIT"ഀ ഀ $tempWorkingBuffer = $screenBuffer.Clone()ഀ ഀ write-debug "ENTER propigate"ഀ ## Propigate the fireഀ $baseOffset = $windowWidth + 1ഀ for($row = 1; $row -lt ($windowHeight - 1); $row++)ഀ {ഀ for($column = 1; $column -lt ($windowWidth - 1); $column++)ഀ { ഀ ## Get the average colour from the four pixels surroundingഀ ## the current pixelഀ $colour = ($screenBuffer[$baseOffset] +ഀ $screenBuffer[$baseOffset - 1] +ഀ $screenBuffer[$baseOffset + 1] +ഀ $screenBuffer[$baseOffset + $windowWidth]) / 4.0ഀ ഀ ## Cool it off a little. We apply uneven cooling, otherwiseഀ ## the cool dark red tends to stretch up for too long.ഀ if($colour -gt 0)ഀ {ഀ if($colour -gt 70)ഀ { ഀ $colour -= 1 ഀ }ഀ elseഀ {ഀ $colour -= 3ഀ ഀ if($colour -lt 1)ഀ { ഀ $colour = 0 ഀ }ഀ elseif($colour -lt 20)ഀ { ഀ $colour -= 1 ഀ }ഀ }ഀ }ഀ ഀ ## Store the result into the previous row -- that is, one buffer ഀ ## cell up.ഀ $tempWorkingBuffer[$baseOffset - $windowWidth] = `ഀ [int] [Math]::Floor($colour)ഀ ഀ $baseOffset++ഀ }ഀ ഀ $baseOffset += 2ഀ }ഀ write-debug "EXIT"ഀ ഀ $SCRIPT:workingBuffer = $tempWorkingBufferഀ }ഀ ഀ ## Take the contents of our working buffer and blit it to the screenഀ ## We do this in one highly-efficent step (the SetBufferContents) so thatഀ ## users don't see each individial pixel get updated.ഀ function updateScreenഀ {ഀ write-debug "ENTER updateScreen"ഀ ## Create a working buffer to hold the next screen that we want toഀ ## create.ഀ $nextScreen = $host.UI.RawUI.GetBufferContents($dimensions)ഀ ഀ ## Go through our working buffer (that holds our next animation frame)ഀ ## and place its contents into the buffer that we will soon blast intoഀ ## the real RawUIഀ for($row = 0; $row -lt $windowHeight; $row++)ഀ {ഀ for($column = 0; $column -lt $windowWidth; $column++)ഀ {ഀ $nextScreen[$row, $column] = `ഀ $palette[$workingBuffer[($row * $windowWidth) + $column]]ഀ }ഀ }ഀ ഀ ## Bulk update the RawUI's buffer with the contents of our next screenഀ $host.UI.RawUI.SetBufferContents($origin, $nextScreen)ഀ ഀ ## And finally update our representation of the screen buffer to holdഀ ## what actually is on the screenഀ $SCRIPT:screenBuffer = $workingBuffer.Clone()ഀ ഀ write-debug "EXIT"ഀ }ഀ ഀ ## Generates a palette of 256 colours. We create every combination of ഀ ## foreground colour, background colour, and dithering character, and thenഀ ## order them by their visual intensity.ഀ ##ഀ ## The visual intensity of a colour can be expressed by the NTSC luminance ഀ ## formula. That formula depicts the apparent brightness of a colour based on ഀ ## our eyes' sensitivity to different wavelengths that compose that colour.ഀ ## http://en.wikipedia.org/wiki/Luminance_%28video%29ഀ function generatePaletteഀ {ഀ ## The apparent intensities of our four primary colours.ഀ ## However, the formula under-represents the intensity of our straightഀ ## red colour, so we artificially inflate it.ഀ $luminances = 225.93,106.245,38.272,0ഀ $apparentBrightnesses = @{}ഀ ഀ ## Cycle through each foreground, background, and dither characterഀ ## combination. For each combination, find the apparent intensity of the ഀ ## foreground, and the apparent intensity of the background. Finally,ഀ ## weight the contribution of each based on how much of each colour theഀ ## dithering character shows.ഀ ## This provides an intensity range between zero and some maximum.ഀ ## For each apparent intensity, we store the colours and charactersഀ ## that create that intensity.ഀ $maxBrightness = 0ഀ for($fgColour = 0; $fgColour -lt $colours.Count; $fgColour++)ഀ {ഀ for($bgColour = 0; $bgColour -lt $colours.Count; $bgColour++)ഀ {ഀ for($ditherCharacter = 0; ഀ $ditherCharacter -lt $dithering.Count; ഀ $ditherCharacter++)ഀ {ഀ $apparentBrightness = `ഀ $luminances[$fgColour] * $ditherFactor[$ditherCharacter] +ഀ $luminances[$bgColour] *ഀ (1 - $ditherFactor[$ditherCharacter])ഀ ഀ if($apparentBrightness -gt $maxBrightness) ഀ { ഀ $maxBrightness = $apparentBrightness ഀ }ഀ ഀ $apparentBrightnesses[$apparentBrightness] = `ഀ "$fgColour$bgColour$ditherCharacter"ഀ }ഀ }ഀ }ഀ ഀ ## Finally, we normalize our computed intesities into a pallete ofഀ ## 0 to 255. If a given intensity is 30% towards our maximum intensity,ഀ ## then it should be in the palette at 30% of index 255.ഀ $paletteIndex = 0ഀ foreach($key in ($apparentBrightnesses.Keys | sort))ഀ {ഀ $keyValue = $apparentBrightnesses[$key]ഀ doഀ {ഀ $character = $dithering[[Int32]::Parse($keyValue[2])]ഀ $fgColour = $colours[[Int32]::Parse($keyValue[0])]ഀ $bgColour = $colours[[Int32]::Parse($keyValue[1])]ഀ ഀ $bufferCell = `ഀ new-object System.Management.Automation.Host.BufferCell `ഀ $character,ഀ $fgColour,ഀ $bgColour,ഀ "Complete"ഀ ഀ $palette[$paletteIndex] = $bufferCellഀ $paletteIndex++ഀ } while(($paletteIndex / 256) -lt ($key / $maxBrightness))ഀ }ഀ }ഀ ഀ ## Dump the palette to the screen.ഀ function displayPaletteഀ {ഀ for($paletteIndex = 254; $paletteIndex -ge 0; $paletteIndex--)ഀ {ഀ $bufferCell = $palette[$paletteIndex]ഀ $fgColor = $bufferCell.ForegroundColorഀ $bgColor = $bufferCell.BackgroundColorഀ $character = $bufferCell.Characterഀ ഀ $host.UI.RawUI.ForegroundColor = $fgColorഀ $host.UI.RawUI.BackgroundColor = $bgColorഀ write-host -noNewLine $characterഀ }ഀ ഀ write-hostഀ }ഀ ഀ . main