Repository: bcarruthers/garnet
Branch: master
Commit: 11f40d9fe26d
Files: 140
Total size: 738.1 KB
Directory structure:
gitextract_lshbfeyb/
├── .gitignore
├── Directory.Build.props
├── Garnet.sln
├── LICENSE
├── README.md
├── RELEASE_NOTES.md
├── appveyor.yml
├── build.cmd
├── samples/
│ ├── Garnet.Numerics/
│ │ ├── Garnet.Numerics.fsproj
│ │ ├── Hashing.fs
│ │ ├── Noise.fs
│ │ ├── Numerics.fs
│ │ ├── Random.fs
│ │ ├── Ranges.fs
│ │ └── Vectors.fs
│ ├── Garnet.Processor/
│ │ ├── Args.fs
│ │ ├── Garnet.Processor.fsproj
│ │ ├── PackUtility.fs
│ │ └── Program.fs
│ ├── Garnet.Samples.Assorted/
│ │ ├── Extensions.fs
│ │ ├── Garnet.Samples.Assorted.fsproj
│ │ ├── OffscreenDrawing.fs
│ │ ├── Program.fs
│ │ ├── SpriteDrawing.fs
│ │ ├── TextDrawing.fs
│ │ └── assets/
│ │ ├── fonts/
│ │ │ └── pixel-operator-regular-12.font.json
│ │ └── shaders/
│ │ ├── color.frag
│ │ ├── color.frag.hlsl.bytes
│ │ ├── color.vert
│ │ ├── color.vert.hlsl.bytes
│ │ ├── texture-color.frag
│ │ ├── texture-color.frag.hlsl.bytes
│ │ ├── texture-color.vert
│ │ └── texture-color.vert.hlsl.bytes
│ ├── Garnet.Samples.CSharp/
│ │ ├── Garnet.Samples.CSharp.csproj
│ │ └── Program.cs
│ ├── Garnet.Samples.Flocking/
│ │ ├── Debug.fs
│ │ ├── Drawing.fs
│ │ ├── Functions.fs
│ │ ├── Garnet.Samples.Flocking.fsproj
│ │ ├── Program.fs
│ │ ├── Resources.fs
│ │ ├── Simulation.fs
│ │ ├── Startup.fs
│ │ ├── Types.fs
│ │ └── assets/
│ │ ├── texture-color.frag
│ │ ├── texture-color.frag.hlsl.bytes
│ │ ├── texture-color.vert
│ │ └── texture-color.vert.hlsl.bytes
│ ├── Garnet.Samples.Roguelike/
│ │ ├── ConsoleTest.fsx
│ │ ├── Drawing.fs
│ │ ├── Functions.fs
│ │ ├── Game.fs
│ │ ├── Garnet.Samples.Roguelike.fsproj
│ │ ├── Program.fs
│ │ ├── Types.fs
│ │ └── assets/
│ │ ├── texture-dual-color.frag
│ │ └── texture-dual-color.vert
│ ├── Garnet.Samples.Trixel/
│ │ ├── Drawing.fs
│ │ ├── Functions.fs
│ │ ├── Game.fs
│ │ ├── Garnet.Samples.Trixel.fsproj
│ │ ├── Gui.fs
│ │ ├── Imaging.fs
│ │ ├── Program.fs
│ │ ├── Types.fs
│ │ └── assets/
│ │ ├── texture-color.frag
│ │ ├── texture-color.frag.hlsl.bytes
│ │ ├── texture-color.vert
│ │ └── texture-color.vert.hlsl.bytes
│ ├── Garnet.Toolkit/
│ │ ├── Audio.fs
│ │ ├── Collections.fs
│ │ ├── Colors.fs
│ │ ├── Comparisons.fs
│ │ ├── Events.fs
│ │ ├── Fonts.fs
│ │ ├── Garnet.Toolkit.fsproj
│ │ ├── Input.fs
│ │ ├── Logging.fs
│ │ ├── Looping.fs
│ │ ├── Meshes.fs
│ │ ├── Offscreen.fs
│ │ ├── Particles.fs
│ │ ├── Picking.fs
│ │ ├── Pipelines.fs
│ │ ├── Rendering.fs
│ │ ├── Requests.fs
│ │ ├── Serialization.fs
│ │ ├── Shaders.fs
│ │ ├── Sprites.fs
│ │ ├── Systems.fs
│ │ ├── Textures.fs
│ │ ├── Tiling.fs
│ │ ├── Timing.fs
│ │ ├── Vertices.fs
│ │ └── Window.fs
│ └── README.md
├── src/
│ └── Garnet/
│ ├── Actors.fs
│ ├── Channels.fs
│ ├── Collections.fs
│ ├── Comparisons.fs
│ ├── Components.fs
│ ├── Containers.fs
│ ├── Coroutines.fs
│ ├── Entities.fs
│ ├── Formatting.fs
│ ├── Garnet.fsproj
│ ├── Messaging.fs
│ ├── Queries.fs
│ ├── Registry.fs
│ ├── Resources.fs
│ └── Segments.fs
└── tests/
└── Garnet.Tests/
├── ActorTests.fs
├── Assertions.fs
├── Benchmarks/
│ ├── ActorBenchmarks.fs
│ ├── ActorBenchmarks.fsx
│ ├── ChannelBenchmarks.fs
│ ├── ChannelBenchmarks.fsx
│ ├── ContainerBenchmarks.fs
│ └── ContainerBenchmarks.fsx
├── ChannelTests.fs
├── CollectionTests.fs
├── ComponentTests.fs
├── CoroutineTests.fs
├── EntityTests.fs
├── Garnet.Tests.fsproj
├── Iteration.fs
├── IterationTests.fs
├── Main.fs
├── QueryTests.fs
├── ReadmeSamples.fs
├── Recording.fs
├── RegistryTests.fs
├── RingBuffer.fs
├── Scratch.fsx
├── SegmentTests.fs
├── Serialization.fs
├── SerializationTests.fs
├── StateMachineTests.fs
└── StrategySample.fs
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
*.suo
*.swp
*.user
*.userprefs
*.pidb
*.nupkg
*.sln.ide
*.orig
*.vsp
*.psess
*.vspx
*.stackdump
.idea/
.fake/
.vs/
.vscode/
.ionide
packages/
build/
publish/
artifacts/
BenchmarkDotNet.Artifacts/
bin
obj
#Paket dependency manager
.paket/
paket-files/
================================================
FILE: Directory.Build.props
================================================
0.5.3
Ben Carruthers
Copyright © 2021 Ben Carruthers
MIT
false
https://github.com/bcarruthers/garnet
================================================
FILE: Garnet.sln
================================================
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.29215.179
MinimumVisualStudioVersion = 15.0.26124.0
Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "Garnet", "src\Garnet\Garnet.fsproj", "{4FA4BEC9-C501-40F7-B0F2-B204A932E27B}"
EndProject
Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "Garnet.Tests", "tests\Garnet.Tests\Garnet.Tests.fsproj", "{97D1D0D1-C635-4725-A892-DE1852A0CB4C}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Samples", "Samples", "{1E82F9DC-427D-46D1-9352-FB1E97724CAF}"
EndProject
Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "Garnet.Toolkit", "samples\Garnet.Toolkit\Garnet.Toolkit.fsproj", "{E0F6649F-E652-4B34-B0AA-1E6716AB60FF}"
EndProject
Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "Garnet.Samples.Flocking", "samples\Garnet.Samples.Flocking\Garnet.Samples.Flocking.fsproj", "{16290883-031E-408F-B599-5FEE33355629}"
EndProject
Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "Garnet.Samples.Roguelike", "samples\Garnet.Samples.Roguelike\Garnet.Samples.Roguelike.fsproj", "{C7826D8F-8343-440B-BD6D-74F73D6814EC}"
EndProject
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Garnet.Samples.Trixel", "samples\Garnet.Samples.Trixel\Garnet.Samples.Trixel.fsproj", "{58AB7F65-F130-4624-9458-A4BC5BF867DC}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Garnet.Samples.CSharp", "samples\Garnet.Samples.CSharp\Garnet.Samples.CSharp.csproj", "{AF60F947-ED94-4B84-BF8E-D327D2CFCE41}"
EndProject
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Garnet.Numerics", "samples\Garnet.Numerics\Garnet.Numerics.fsproj", "{66F27C8C-B121-426B-BEA2-4379FC217849}"
EndProject
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Garnet.Samples.Assorted", "samples\Garnet.Samples.Assorted\Garnet.Samples.Assorted.fsproj", "{957D6CB2-EBFD-42A8-A201-731CBA232FB3}"
EndProject
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Garnet.Processor", "samples\Garnet.Processor\Garnet.Processor.fsproj", "{C88FF8AF-962A-4162-8FB8-0D337DA09138}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{4FA4BEC9-C501-40F7-B0F2-B204A932E27B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4FA4BEC9-C501-40F7-B0F2-B204A932E27B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4FA4BEC9-C501-40F7-B0F2-B204A932E27B}.Debug|x64.ActiveCfg = Debug|Any CPU
{4FA4BEC9-C501-40F7-B0F2-B204A932E27B}.Debug|x64.Build.0 = Debug|Any CPU
{4FA4BEC9-C501-40F7-B0F2-B204A932E27B}.Debug|x86.ActiveCfg = Debug|Any CPU
{4FA4BEC9-C501-40F7-B0F2-B204A932E27B}.Debug|x86.Build.0 = Debug|Any CPU
{4FA4BEC9-C501-40F7-B0F2-B204A932E27B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4FA4BEC9-C501-40F7-B0F2-B204A932E27B}.Release|Any CPU.Build.0 = Release|Any CPU
{4FA4BEC9-C501-40F7-B0F2-B204A932E27B}.Release|x64.ActiveCfg = Release|Any CPU
{4FA4BEC9-C501-40F7-B0F2-B204A932E27B}.Release|x64.Build.0 = Release|Any CPU
{4FA4BEC9-C501-40F7-B0F2-B204A932E27B}.Release|x86.ActiveCfg = Release|Any CPU
{4FA4BEC9-C501-40F7-B0F2-B204A932E27B}.Release|x86.Build.0 = Release|Any CPU
{97D1D0D1-C635-4725-A892-DE1852A0CB4C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{97D1D0D1-C635-4725-A892-DE1852A0CB4C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{97D1D0D1-C635-4725-A892-DE1852A0CB4C}.Debug|x64.ActiveCfg = Debug|Any CPU
{97D1D0D1-C635-4725-A892-DE1852A0CB4C}.Debug|x64.Build.0 = Debug|Any CPU
{97D1D0D1-C635-4725-A892-DE1852A0CB4C}.Debug|x86.ActiveCfg = Debug|Any CPU
{97D1D0D1-C635-4725-A892-DE1852A0CB4C}.Debug|x86.Build.0 = Debug|Any CPU
{97D1D0D1-C635-4725-A892-DE1852A0CB4C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{97D1D0D1-C635-4725-A892-DE1852A0CB4C}.Release|Any CPU.Build.0 = Release|Any CPU
{97D1D0D1-C635-4725-A892-DE1852A0CB4C}.Release|x64.ActiveCfg = Release|Any CPU
{97D1D0D1-C635-4725-A892-DE1852A0CB4C}.Release|x64.Build.0 = Release|Any CPU
{97D1D0D1-C635-4725-A892-DE1852A0CB4C}.Release|x86.ActiveCfg = Release|Any CPU
{97D1D0D1-C635-4725-A892-DE1852A0CB4C}.Release|x86.Build.0 = Release|Any CPU
{E0F6649F-E652-4B34-B0AA-1E6716AB60FF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E0F6649F-E652-4B34-B0AA-1E6716AB60FF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E0F6649F-E652-4B34-B0AA-1E6716AB60FF}.Debug|x64.ActiveCfg = Debug|Any CPU
{E0F6649F-E652-4B34-B0AA-1E6716AB60FF}.Debug|x64.Build.0 = Debug|Any CPU
{E0F6649F-E652-4B34-B0AA-1E6716AB60FF}.Debug|x86.ActiveCfg = Debug|Any CPU
{E0F6649F-E652-4B34-B0AA-1E6716AB60FF}.Debug|x86.Build.0 = Debug|Any CPU
{E0F6649F-E652-4B34-B0AA-1E6716AB60FF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E0F6649F-E652-4B34-B0AA-1E6716AB60FF}.Release|Any CPU.Build.0 = Release|Any CPU
{E0F6649F-E652-4B34-B0AA-1E6716AB60FF}.Release|x64.ActiveCfg = Release|Any CPU
{E0F6649F-E652-4B34-B0AA-1E6716AB60FF}.Release|x64.Build.0 = Release|Any CPU
{E0F6649F-E652-4B34-B0AA-1E6716AB60FF}.Release|x86.ActiveCfg = Release|Any CPU
{E0F6649F-E652-4B34-B0AA-1E6716AB60FF}.Release|x86.Build.0 = Release|Any CPU
{16290883-031E-408F-B599-5FEE33355629}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{16290883-031E-408F-B599-5FEE33355629}.Debug|Any CPU.Build.0 = Debug|Any CPU
{16290883-031E-408F-B599-5FEE33355629}.Debug|x64.ActiveCfg = Debug|Any CPU
{16290883-031E-408F-B599-5FEE33355629}.Debug|x64.Build.0 = Debug|Any CPU
{16290883-031E-408F-B599-5FEE33355629}.Debug|x86.ActiveCfg = Debug|Any CPU
{16290883-031E-408F-B599-5FEE33355629}.Debug|x86.Build.0 = Debug|Any CPU
{16290883-031E-408F-B599-5FEE33355629}.Release|Any CPU.ActiveCfg = Release|Any CPU
{16290883-031E-408F-B599-5FEE33355629}.Release|Any CPU.Build.0 = Release|Any CPU
{16290883-031E-408F-B599-5FEE33355629}.Release|x64.ActiveCfg = Release|Any CPU
{16290883-031E-408F-B599-5FEE33355629}.Release|x64.Build.0 = Release|Any CPU
{16290883-031E-408F-B599-5FEE33355629}.Release|x86.ActiveCfg = Release|Any CPU
{16290883-031E-408F-B599-5FEE33355629}.Release|x86.Build.0 = Release|Any CPU
{C7826D8F-8343-440B-BD6D-74F73D6814EC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C7826D8F-8343-440B-BD6D-74F73D6814EC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C7826D8F-8343-440B-BD6D-74F73D6814EC}.Debug|x64.ActiveCfg = Debug|Any CPU
{C7826D8F-8343-440B-BD6D-74F73D6814EC}.Debug|x64.Build.0 = Debug|Any CPU
{C7826D8F-8343-440B-BD6D-74F73D6814EC}.Debug|x86.ActiveCfg = Debug|Any CPU
{C7826D8F-8343-440B-BD6D-74F73D6814EC}.Debug|x86.Build.0 = Debug|Any CPU
{C7826D8F-8343-440B-BD6D-74F73D6814EC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C7826D8F-8343-440B-BD6D-74F73D6814EC}.Release|Any CPU.Build.0 = Release|Any CPU
{C7826D8F-8343-440B-BD6D-74F73D6814EC}.Release|x64.ActiveCfg = Release|Any CPU
{C7826D8F-8343-440B-BD6D-74F73D6814EC}.Release|x64.Build.0 = Release|Any CPU
{C7826D8F-8343-440B-BD6D-74F73D6814EC}.Release|x86.ActiveCfg = Release|Any CPU
{C7826D8F-8343-440B-BD6D-74F73D6814EC}.Release|x86.Build.0 = Release|Any CPU
{58AB7F65-F130-4624-9458-A4BC5BF867DC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{58AB7F65-F130-4624-9458-A4BC5BF867DC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{58AB7F65-F130-4624-9458-A4BC5BF867DC}.Debug|x64.ActiveCfg = Debug|Any CPU
{58AB7F65-F130-4624-9458-A4BC5BF867DC}.Debug|x64.Build.0 = Debug|Any CPU
{58AB7F65-F130-4624-9458-A4BC5BF867DC}.Debug|x86.ActiveCfg = Debug|Any CPU
{58AB7F65-F130-4624-9458-A4BC5BF867DC}.Debug|x86.Build.0 = Debug|Any CPU
{58AB7F65-F130-4624-9458-A4BC5BF867DC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{58AB7F65-F130-4624-9458-A4BC5BF867DC}.Release|Any CPU.Build.0 = Release|Any CPU
{58AB7F65-F130-4624-9458-A4BC5BF867DC}.Release|x64.ActiveCfg = Release|Any CPU
{58AB7F65-F130-4624-9458-A4BC5BF867DC}.Release|x64.Build.0 = Release|Any CPU
{58AB7F65-F130-4624-9458-A4BC5BF867DC}.Release|x86.ActiveCfg = Release|Any CPU
{58AB7F65-F130-4624-9458-A4BC5BF867DC}.Release|x86.Build.0 = Release|Any CPU
{AF60F947-ED94-4B84-BF8E-D327D2CFCE41}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AF60F947-ED94-4B84-BF8E-D327D2CFCE41}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AF60F947-ED94-4B84-BF8E-D327D2CFCE41}.Debug|x64.ActiveCfg = Debug|Any CPU
{AF60F947-ED94-4B84-BF8E-D327D2CFCE41}.Debug|x64.Build.0 = Debug|Any CPU
{AF60F947-ED94-4B84-BF8E-D327D2CFCE41}.Debug|x86.ActiveCfg = Debug|Any CPU
{AF60F947-ED94-4B84-BF8E-D327D2CFCE41}.Debug|x86.Build.0 = Debug|Any CPU
{AF60F947-ED94-4B84-BF8E-D327D2CFCE41}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AF60F947-ED94-4B84-BF8E-D327D2CFCE41}.Release|Any CPU.Build.0 = Release|Any CPU
{AF60F947-ED94-4B84-BF8E-D327D2CFCE41}.Release|x64.ActiveCfg = Release|Any CPU
{AF60F947-ED94-4B84-BF8E-D327D2CFCE41}.Release|x64.Build.0 = Release|Any CPU
{AF60F947-ED94-4B84-BF8E-D327D2CFCE41}.Release|x86.ActiveCfg = Release|Any CPU
{AF60F947-ED94-4B84-BF8E-D327D2CFCE41}.Release|x86.Build.0 = Release|Any CPU
{66F27C8C-B121-426B-BEA2-4379FC217849}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{66F27C8C-B121-426B-BEA2-4379FC217849}.Debug|Any CPU.Build.0 = Debug|Any CPU
{66F27C8C-B121-426B-BEA2-4379FC217849}.Debug|x64.ActiveCfg = Debug|Any CPU
{66F27C8C-B121-426B-BEA2-4379FC217849}.Debug|x64.Build.0 = Debug|Any CPU
{66F27C8C-B121-426B-BEA2-4379FC217849}.Debug|x86.ActiveCfg = Debug|Any CPU
{66F27C8C-B121-426B-BEA2-4379FC217849}.Debug|x86.Build.0 = Debug|Any CPU
{66F27C8C-B121-426B-BEA2-4379FC217849}.Release|Any CPU.ActiveCfg = Release|Any CPU
{66F27C8C-B121-426B-BEA2-4379FC217849}.Release|Any CPU.Build.0 = Release|Any CPU
{66F27C8C-B121-426B-BEA2-4379FC217849}.Release|x64.ActiveCfg = Release|Any CPU
{66F27C8C-B121-426B-BEA2-4379FC217849}.Release|x64.Build.0 = Release|Any CPU
{66F27C8C-B121-426B-BEA2-4379FC217849}.Release|x86.ActiveCfg = Release|Any CPU
{66F27C8C-B121-426B-BEA2-4379FC217849}.Release|x86.Build.0 = Release|Any CPU
{957D6CB2-EBFD-42A8-A201-731CBA232FB3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{957D6CB2-EBFD-42A8-A201-731CBA232FB3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{957D6CB2-EBFD-42A8-A201-731CBA232FB3}.Debug|x64.ActiveCfg = Debug|Any CPU
{957D6CB2-EBFD-42A8-A201-731CBA232FB3}.Debug|x64.Build.0 = Debug|Any CPU
{957D6CB2-EBFD-42A8-A201-731CBA232FB3}.Debug|x86.ActiveCfg = Debug|Any CPU
{957D6CB2-EBFD-42A8-A201-731CBA232FB3}.Debug|x86.Build.0 = Debug|Any CPU
{957D6CB2-EBFD-42A8-A201-731CBA232FB3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{957D6CB2-EBFD-42A8-A201-731CBA232FB3}.Release|Any CPU.Build.0 = Release|Any CPU
{957D6CB2-EBFD-42A8-A201-731CBA232FB3}.Release|x64.ActiveCfg = Release|Any CPU
{957D6CB2-EBFD-42A8-A201-731CBA232FB3}.Release|x64.Build.0 = Release|Any CPU
{957D6CB2-EBFD-42A8-A201-731CBA232FB3}.Release|x86.ActiveCfg = Release|Any CPU
{957D6CB2-EBFD-42A8-A201-731CBA232FB3}.Release|x86.Build.0 = Release|Any CPU
{C88FF8AF-962A-4162-8FB8-0D337DA09138}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C88FF8AF-962A-4162-8FB8-0D337DA09138}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C88FF8AF-962A-4162-8FB8-0D337DA09138}.Debug|x64.ActiveCfg = Debug|Any CPU
{C88FF8AF-962A-4162-8FB8-0D337DA09138}.Debug|x64.Build.0 = Debug|Any CPU
{C88FF8AF-962A-4162-8FB8-0D337DA09138}.Debug|x86.ActiveCfg = Debug|Any CPU
{C88FF8AF-962A-4162-8FB8-0D337DA09138}.Debug|x86.Build.0 = Debug|Any CPU
{C88FF8AF-962A-4162-8FB8-0D337DA09138}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C88FF8AF-962A-4162-8FB8-0D337DA09138}.Release|Any CPU.Build.0 = Release|Any CPU
{C88FF8AF-962A-4162-8FB8-0D337DA09138}.Release|x64.ActiveCfg = Release|Any CPU
{C88FF8AF-962A-4162-8FB8-0D337DA09138}.Release|x64.Build.0 = Release|Any CPU
{C88FF8AF-962A-4162-8FB8-0D337DA09138}.Release|x86.ActiveCfg = Release|Any CPU
{C88FF8AF-962A-4162-8FB8-0D337DA09138}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{E0F6649F-E652-4B34-B0AA-1E6716AB60FF} = {1E82F9DC-427D-46D1-9352-FB1E97724CAF}
{16290883-031E-408F-B599-5FEE33355629} = {1E82F9DC-427D-46D1-9352-FB1E97724CAF}
{C7826D8F-8343-440B-BD6D-74F73D6814EC} = {1E82F9DC-427D-46D1-9352-FB1E97724CAF}
{58AB7F65-F130-4624-9458-A4BC5BF867DC} = {1E82F9DC-427D-46D1-9352-FB1E97724CAF}
{AF60F947-ED94-4B84-BF8E-D327D2CFCE41} = {1E82F9DC-427D-46D1-9352-FB1E97724CAF}
{66F27C8C-B121-426B-BEA2-4379FC217849} = {1E82F9DC-427D-46D1-9352-FB1E97724CAF}
{957D6CB2-EBFD-42A8-A201-731CBA232FB3} = {1E82F9DC-427D-46D1-9352-FB1E97724CAF}
{C88FF8AF-962A-4162-8FB8-0D337DA09138} = {1E82F9DC-427D-46D1-9352-FB1E97724CAF}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {D7FA662D-CCF7-4CEF-82D0-82F96A334562}
EndGlobalSection
GlobalSection(Performance) = preSolution
HasPerformanceSessions = true
EndGlobalSection
EndGlobal
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2019 Ben Carruthers
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# Garnet
[](https://ci.appveyor.com/project/bcarruthers/garnet)
[NuGet package](https://www.nuget.org/packages/Garnet/)
Garnet is a lightweight game composition library for F# with entity-component-system (ECS) and actor-like messaging features.
```fsharp
open Garnet.Composition
// events
[] type Update = { dt : float32 }
// components
[] type Position = { x : float32; y : float32 }
[] type Velocity = { vx : float32; vy : float32 }
// create a world
let world = Container()
// register a system that updates position
let system =
world.On <| fun e ->
for r in world.Query() do
let p = &r.Value1
let v = r.Value2
p <- {
x = p.x + v.vx * e.dt
y = p.y + v.vy * e.dt
}
// add an entity to world
let entity =
world.Create()
.With({ x = 10.0f; y = 5.0f })
.With({ vx = 1.0f; vy = 2.0f })
// run updates and print world state
for i = 1 to 10 do
world.Run <| { dt = 0.1f }
printfn "%O\n\n%O\n\n" world entity
```
## Table of contents
* Introduction
* [Getting started](#gettingstarted)
* [Background](#background)
* [Goals](#goals)
* Guide
* [Containers](#containers)
* [Entities](#entities)
* [Components](#components)
* [Systems](#systems)
* [Actors](#actors)
* [Integration](#integration)
* [FAQ](#faq)
* [License](#license)
* [Maintainers](#maintainers)
## Getting started
1. Create either a .NET Framework, Core, or 6.0+ application.
2. Reference the [Garnet NuGet package](https://www.nuget.org/packages/Garnet/).
3. For sample code, see unit tests or [sample projects](https://github.com/bcarruthers/garnet/tree/master/samples).
## Background
ECS is a common architecture for games, often contrasted with OOP inheritance. It focuses on separation of data and behavior and is typically implemented in a data-oriented way to achieve high performance. It's similar to a database, where component tables are related using a common entity ID, allowing systems to query and iterate over entities with specific combinations of components present. EC (entity-component) is a related approach that attaches behavior to components and avoids systems.
While ECS focuses on managing shared state, the actor model isolates state into separate actors which communicate only through messages. Actors can send and receive messages, change their behavior as a result of messages, and create new actors. This approach offers scaleability and an abstraction layer over message delivery, and games can use it at a high level to model independent processes, worlds, or agents.
## Goals
- **Lightweight**: Garnet is essentially a simplified in-memory database and messaging system suitable for games. No inheritance, attributes, or interface implementations are required in your code. It's more of a library than a framework or engine, and most of your code shouldn't depend on it.
- **Fast**: Garbage collection spikes can cause dropped frames and inconsistent performance, so Garnet minimizes allocations and helps library users do so too. Component storage is data-oriented for fast iteration.
- **Minimal**: The core library focuses on events, scheduling, and storage, and anything game-specific like physics, rendering, or update loops should be implemented separately.
## Containers
ECS containers provide a useful bundle of functionality for working with shared game state, including event handling, component storage, entity ID generation, coroutine scheduling, and resource resolution.
```fsharp
// create a container/world
let c = Container()
```
### Registry
Containers store single instances of types such as component lists, ID pools, settings, and any other arbitrary type. You can access instances by type, with optional lazy resolution. This is the service locator (anti-)pattern.
```fsharp
// option 1: add specific instance
c.SetValue(defaultWorldSettings)
// option 2: register a factory
c.SetFactory(fun () -> defaultWorldSettings)
// resolve type
let settings = c.GetValue()
```
This works for value types as well:
```fsharp
c.SetValue { zoomLevel = 0.5f }
let zoom = c.GetValue>()
```
### Object pooling
Avoiding GC generally amounts to use of structs, pooling, and avoiding closures. Almost all objects are either pooled within a container or on the stack, so there's little or no GC impact or allocation once maximum load is reached. If needed, warming up or provisioning buffers ahead of time is possible for avoiding GC entirely during gameplay.
### Commits
Certain operations on containers, such as sending events or adding/removing components, are staged until a commit occurs, allowing any running event handlers to observe the original state. Commits occur automatically after all subscribers have completed handling a list of events, so you typically shouldn't need to explicitly commit.
```fsharp
// create an entity
let e = c.Create().With("test")
// not yet visible
c.Commit()
// now visible
```
## Entities
An entity is any identifiable thing in your game which you can attach components to. At minimum, an entity consists only of an entity ID.
### Entity ID
Entity IDs are 32 bits and stored in a component list. This means they can be accessed and iterated over like any other component type without special handling. IDs use a special Eid type rather than a raw int32, which offers better type safety but means you need a direct dependency on Garnet if you want to define types with an Eid (or you can manage converting to your own ID type if this is an issue).
```fsharp
let entity = c.Create()
printfn "%A" entity.id
```
### Generations
A portion of an ID is dedicated to its generation number. The purpose of a generation is to avoid reusing IDs while still allowing buffer slots to be reused, keeping components stored as densely as possible.
### Partitioning
Component storage could become inefficient if it grows too sparse (i.e. the average number of occupied elements per segment becomes low). If this is a concern (or you just want to organize your entities), you can optionally use partitions to specify a high bit mask in ID generation. For example, if ship and bullet entities shared the same ID space, they may become mixed over time and the ship components would become sparse. Instead, with separate partitions, both entities would remain dense. Note: this will likely be replaced with groups in the future.
### Generic storage
Storage should work well for both sequential and sparse data and support generic key types. Entity IDs are typically used as keys, but other types like grid location should be possible as well.
### Inspecting
You can print the components of an entity at any time, which is useful in REPL scenarios as an alternative to using a debugger.
```fsharp
printfn "%s" <| c.Get(Eid 64).ToString()
```
```
Entity 0x40: 20 bytes
Eid 0x40
Loc {x = 10;
y = 2;}
UnitType Archer
UnitSize {unitSize = 5;}
```
## Components
Components are any arbitrary data type associated with an entity. Combined with systems that operate on them, components provide a way to specify behavior or capabilities of entities.
### Data types
Components should ideally be pure data rather than classes with behavior and dependencies. They should typically be structs to avoid jumping around in memory or incurring allocations and garbage collection. Structs should almost always be immutable, but mutable structs (with their gotchas) are possible too.
```fsharp
[] type Position = { x : float32; y : float32 }
[] type Velocity = { vx : float32; vy : float32 }
// create an entity and add two components to it
let entity =
c.Create()
.With({ x = 10.0f; y = 5.0f })
.With({ vx = 1.0f; vy = 2.0f })
```
### Storage
Components are stored in 64-element segments with a mask, ordered by ID. This provides CPU-friendly iteration over densely stored data while retaining some benefits of sparse storage. Some ECS implementations provide a variety of specialized data structures, but Garnet attempts a middle ground that works moderately well for both sequential entity IDs and sparse keys such as grid locations.
Only a single component of a type is allowed per entity, but there is no hard limit on the total number of different component types used (i.e. there is no fixed-size mask defining which components an entity has).
### Iteration
You can iterate over entities with specific combinations of components using queries. In this way you could define a system that updates all entities with a position and velocity, and iteration would skip over any entities with only a position and not velocity.
```fsharp
let healthSub =
c.On <| fun e ->
for r in c.Query() do
let h = r.Value3
if h.hp <= 0 then
let eid = r.Value1
c.Destroy(eid)
```
For batch operations or to improve performance further, you can iterate over segments:
```fsharp
let healthSub =
c.On <| fun e ->
for seg, eids, _, hs in c.QuerySegments() do
for i in seg do
let h = hs.[i]
if h.hp <= 0 then
let eid = eids.[i]
c.Destroy(eid)
```
Note that writes to existing components during iteration occur immediately, unlike adding or removing components.
### Adding
Additions are deferred until a commit occurs, so any code dependent on those operations completing needs to be implemented as a coroutine.
```fsharp
let e = c.Get(Eid 100)
e.Add { x = 1.0f; y = 2.0f }
// change not yet visible
```
### Removing
Like additions, removals are also deferred until commit. Note that you can repeatedly add and remove components for the same entity ID before a commit if needed.
```fsharp
e.Remove()
// change not yet visible
```
### Updating
Unlike additions and removals, updating/replacing an existing component can be done directly at the risk of affecting subsequent subscribers. This way is convenient if the update operation is commutative or there are no other subscribers writing to the same component type during the same event. You can alternately just use addition if you don't know whether a component is already present.
```fsharp
let e = c.Get(Eid 100)
e.Set { x = 1.0f; y = 2.0f }
// change immediately visible
```
### Markers
You can define empty types for use as flags or markers, in which case only 64-bit masks need to be stored per segment. Markers are an efficient way to define static groups for querying.
```fsharp
type PowerupMarker = struct end
```
## Systems
Systems are essentially event subscribers with an optional name. System event handlers often iterate over entities, such as updating position based on velocity, but they can do any other kind of processing too.
```fsharp
module MovementSystem =
// separate methods as needed
let registerUpdate (c : Container) =
c.On <| fun e ->
printfn "%A" e
// combine all together
let register (c : Container) =
Disposable.Create [
registerUpdate c
]
```
Alternately, you can define systems as extension methods. This way is more OOP-centric and avoids some redundancy in declarations.
```fsharp
[]
module MovementSystem =
type Container with
member c.AddMovementUpdate() =
c.On <| fun e ->
printfn "%A" e
member c.AddMovementSystems() =
Disposable.Create [
c.AddMovementUpdate()
]
```
### Execution
When any code creates or modifies entities, sends events, or starts coroutines, it's only staging those things. To actually set all of it into motion, you need to run the container, which would typically happen as part of the game loop. Each time you run the container, it commits all changes, publishes events, and advances coroutines, repeating this process until no work remains to do. This means you should avoid introducing cycles like two systems responding to each other unless they are part of a timed coroutine.
```fsharp
// run the container
c.Process()
```
### Events
Like components, you can use any arbitrary type for an event, but structs are generally preferable to avoid GC. When events are published, subscribers receive batches of events with no guaranteed ordering among the subscribers or event types. Any additional events raised during event handling are run after all the original event handlers complete, thereby avoiding any possibility of reentrancy but complicating synchronous behavior.
```fsharp
[] type UpdateTime = { dt : float32 }
// call sub.Dispose() to unsubscribe
let sub =
c.On <| fun e ->
// [do update here]
printfn "%A" e
// send event
c.Send { dt = 0.1f }
```
Events intentionally decouple publishers and subscribers, and since dispatching events is typically not synchronous within the ECS, it can be difficult to trace the source of events when something goes wrong (no callstack).
### Coroutines
Coroutines allow capturing state and continuing processing for longer than the handling of a single event. They are implemented as sequences and can be used to achieve synchronous behavior despite the asynchronous nature of event handling. This is one of the few parts of the code which incurs allocation.
Coroutines run until they encounter a yield statement, which can tell the coroutine scheduler to either wait for a time duration or to wait until all nested processing has completed. Nested processing refers to any coroutines created as a result of events sent by the current coroutine, allowing a stack-like flow and ordering of events.
```fsharp
let system =
c.On <| fun e ->
printf "2 "
// start a coroutine
c.Start <| seq {
printf "1 "
// send message and defer execution until all messages and
// coroutines created as a result of this have completed
c.Send <| Msg()
yield Wait.All
printf "3 "
}
// run until completion
// output: 1 2 3
c.Process()
```
Time-based coroutines are useful for animations or delayed effects. You can use any unit of time as long as it's consistent.
```fsharp
// start a coroutine
c.Start <| seq {
for i = 1 to 5 do
printf "[%d] " i
// yield execution until time units pass
yield Wait.time 3L
}
// run update loop
// output: [1] 1 2 3 [2] 4 5 6 [3] 7 8 9
for i = 1 to 9 do
// increment time units and run pending coroutines
c.Step 1L
c.Process()
printf "%d " i
```
### Multithreading
It's often useful to run physics in parallel with other processing that doesn't depend on its output, but the event system currently has no built-in features to facilitate multiple threads reading or writing. Instead, you can use the actor system for parallel execution at a higher level, or you can implement your own multithreading at the container level.
### Event ordering
For systems that subscribe to the same event and access the same resources or components, you need to consider whether one is dependent on the other and should run first.
One way to guarantee ordering is to define individual sub-events for the systems and publish those events in the desired order as part of a coroutine started from the original event (with waits following each event to ensure all subscribers are run before proceeding).
```fsharp
// events
type Update = struct end
type UpdatePhysicsBodies = struct end
type UpdateHashSpace = struct end
// systems
let updateSystem =
c.On <| fun e ->
c.Start <| seq {
// sending and suspending execution to
// achieve ordering of sub-updates
c.Send <| UpdatePhysicsBodies()
yield Wait.All
c.Send <| UpdateHashSpace()
yield Wait.All
}
let system1 =
c.On <| fun e ->
// [update positions]
printfn "%A" e
let system2 =
c.On <| fun e ->
// [update hash space from positions]
printfn "%A" e
```
### Composing systems
Since systems are just named event subscriptions, you can compose them into larger systems. This allows for bundling related functionality.
```fsharp
module CoreSystems =
let register (c : Container) =
Disposable.Create [
MovementSystem.register c
HashSpaceSystem.register c
]
```
## Actors
While ECS containers provide a simple and fast means of storing and updating shared memory state using a single thread, actors share no common state and communicate only through messages, making them suitable for parallel processing.
### Definitions
Actors are identified by an actor ID. They are statically defined and created on demand when a message is sent to a nonexistent actor ID. At that point, an actor consisting of a message handler is created based on any definitions registered in the actor system that match the actor ID. It's closer to a mailbox processor than a complete actor model since these actors can't dynamically create arbitrary actors or control actor lifetimes.
```fsharp
// message types
type Ping = struct end
type Pong = struct end
// actor definitions
let a = new ActorSystem()
a.Register(ActorId 1, fun (c : Container) ->
c.On <| fun e ->
printf "ping "
c.Respond(Pong())
)
a.Register(ActorId 2, fun (c : Container) ->
c.On <| fun e ->
printf "pong "
)
// send a message and run until all complete
// output: ping pong
a.Send(ActorId 1, Ping(), sourceId = ActorId 2)
a.ProcessAll()
```
### Actor messages versus container events
Containers already have their own internal event system, but the semantics are a bit different from actors because container events are always stored in separate channels by event type rather than a single serialized channel for all actor message types. The use of separate channels within containers allows for efficient batch processing in cases where event types have no ordering dependencies, but ordering by default is preferable in many other cases involving actors.
### Wrapping containers
It's useful to wrap a container within an actor, where incoming messages to the actor automatically dispatched to the container, and systems within the container have access to an outbox for sending messages to other actors. This approach allows keeping isolated worlds, such as a subset of world state for AI forces or UI state.
### Replay debugging
If you can write logic where your game state is fully determined by the sequence of incoming messages, you can log these messages and replay them to diagnose bugs. This works best if you can isolate the problem to a single actor, such as observing incorrect state or incorrect outgoing messages given a correct input sequence.
### Message ordering
Messages sent from one actor to another are guaranteed to arrive in the order they were sent, but they may be interleaved with messages arriving from other actors. In general, multiple actors and parallelism can introduce complexity similar to the use of microservices, which address scaleability but can introduce race conditions and challenges in synchronization.
### Multithreading
You can designate actors to run on either the main thread (for UI if needed) or a background thread. Actors run when a batch of messages is delivered, resembling task-based parallelism. In addition to running designated actors, the main thread also delivers messages among actors, although this could change in the future if it becomes a bottleneck. Background actors currently run using a fixed pool of worker threads.
## Integration
How does Garnet integrate with frameworks or engines like Unity, MonoGame, or Veldrid? You have a few options depending on how much you want to depend on Garnet, your chosen framework, and your own code. This approach also works for integrating narrower libraries like physics or networking.
See [sample projects](https://github.com/bcarruthers/garnet/tree/master/samples) for integration with Veldrid and OpenAL.
### Abstracting framework calls
When you need to call the framework (e.g. MonoGame) from your code, you can choose to insulate your code from the framework with an abstraction layer. This reduces your dependency on it, but it takes more effort and may result in less power to use framework-specific features and more overhead in marshaling data. If you decide to abstract, you have several options for defining the abstraction layer:
- **Services**: Register an interface for a subsystem and provide an implemention for the specific framework, e.g. *ISpriteRenderer* with *MonoGameSpriteRenderer*. This makes sense if you want synchronous calls or an explicit interface.
- **Events**: Define interface event types and framework-specific systems which subscribe to them, e.g. a sprite rendering system subscribing to *DrawSprite* events. This way is more decoupled, but the interface may not be so clear.
- **Components**: Define interface component types and implement framework-specific systems which iterate over them, e.g. a sprite rendering system which iterates over entities with a *Sprite* component.
### Sending framework events
For the reverse direction, when you want the framework to call your code, you can simply send interface event types and run the container or actors.
```fsharp
type Game() =
// ...
let world = Container()
// [configure container here]
override c.Update gt =
world.Run { deltaTime = gt.ElapsedGameTime }
override c.Draw gameTime =
world.Run <| Draw()
```
## FAQ
- **Why F#?** F# offers conciseness, functional-first defaults like immutability, an algebraic type system, interactive code editing, and pragmatic support for other paradigms like OOP. Strong type safety makes it more likely that code is correct, which is especially helpful for tweaking game code that changes frequently enough to make unit testing impractical.
- **What about performance?** Functional code often involves allocation, which sometimes conflicts with the goal of consistent performance when garbage collection occurs. A goal of this library is to reduce the effort in writing code that minimizes allocation. But for simple games, this is likely a non-issue and you should start with idiomatic code.
- **Why use ECS over MVU?** You probably shouldn't start with ECS for a simple game, at least not when prototyping, unless you already have a good understanding of where it might be beneficial. MVU avoids a lot of complexity and has stronger type safety and immutability guarantees than ECS, but you may encounter issues if your project has demanding performance requirements or needs more flexibility than it allows.
## License
This project is licensed under the [MIT license](https://github.com/bcarruthers/garnet/blob/master/LICENSE).
## Maintainer(s)
- [@bcarruthers](https://github.com/bcarruthers)
================================================
FILE: RELEASE_NOTES.md
================================================
## 0.5.0 – 2021-10-16
- Revised registry implementation
- Renamed Get() to GetComponents()
- Renamed GetInstance() registry methods to Get()
- Renamed Entity.Contains() to Has()
- Added more toolkit functionality
## 0.4.0 – 2021-08-28
- Added new querying code
- Renamed various members for consistent PascalCase
- Added toolkit project and samples
## 0.3.0 – 2021-04-24
- Performance: Implemented static type ID lookup for components
- Performance: Changed to generic type for segment key mapping
- Added separate recipient to actor message destinations
- Refactored resource loading
- Added more iteration options
- Fixed iteration bugs
## 0.2.0 – 2019-09-01
- Cleaned up public interfaces
- Removed experimental serialization code
- Decoupled entity from container
- Cleaned up event publishers
- Rewrote actor system
- Rewrote entity ID pooling and destruction
- Switched to using buffers instead of lists
## 0.1.0 – 2019-07-09
- Initial version
================================================
FILE: appveyor.yml
================================================
version: 1.0.{build}
image: Visual Studio 2022
build_script:
- cmd: build.cmd
artifacts:
- path: publish\*.nupkg
name: packages
deploy:
- provider: GitHub
auth_token:
secure: qG7eOszX+IfotPE1mnKIg13cJTR1/t4aiI5coav1zcp71CZBXw4BZ6PfAU7jWZ4D
artifact: packages
draft: true
================================================
FILE: build.cmd
================================================
dotnet restore
dotnet build --no-restore
dotnet test --no-build --verbosity normal
dotnet pack -c Release -o publish src\Garnet\Garnet.fsproj
dotnet pack -c Release -o publish samples\Garnet.Numerics\Garnet.Numerics.fsproj
dotnet pack -c Release -o publish samples\Garnet.Toolkit\Garnet.Toolkit.fsproj
dotnet pack -c Release -o publish samples\Garnet.Processor\Garnet.Processor.fsproj
================================================
FILE: samples/Garnet.Numerics/Garnet.Numerics.fsproj
================================================
net6.0
Numeric primitives and operations suitable for games. Supplements System.Numerics.
math vectors bounds game
================================================
FILE: samples/Garnet.Numerics/Hashing.fs
================================================
namespace Garnet.Numerics
open System
module Fnv1a =
[]
let Prime = 0x1000193u
[]
let Seed = 0x811c9dc5u
type Fnv1a() =
static member inline Combine(hash, value) =
(hash ^^^ value) * Fnv1a.Prime
static member inline Hash(x1 : uint32) =
let h = Fnv1a.Seed
let h = Fnv1a.Combine(h, x1)
h
static member inline Hash(x1 : uint32, x2 : uint32) =
let h = Fnv1a.Seed
let h = Fnv1a.Combine(h, x1)
let h = Fnv1a.Combine(h, x2)
h
static member inline Hash(x1 : uint32, x2 : uint32, x3 : uint32) =
let h = Fnv1a.Seed
let h = Fnv1a.Combine(h, x1)
let h = Fnv1a.Combine(h, x2)
let h = Fnv1a.Combine(h, x3)
h
static member inline Hash(key : string) =
let mutable hash = Fnv1a.Seed
for i = 0 to key.Length - 1 do
let value = uint32 key.[i]
hash <- Fnv1a.Combine(hash, value)
hash
module Fnv1a64 =
[]
let Prime = 0x100000001b3UL
[]
let Seed = 0xcbf29ce484222325UL
type Fnv1a64() =
static member inline Combine(hash, value) =
(hash ^^^ value) * Fnv1a64.Prime
static member inline Hash(value : uint64) =
let h = Fnv1a64.Seed
let h = Fnv1a64.Combine(h, value)
h
static member inline Hash(x1 : uint64, x2 : uint64) =
let h = Fnv1a64.Seed
let h = Fnv1a64.Combine(h, x1)
let h = Fnv1a64.Combine(h, x2)
h
static member inline Hash(x1 : uint64, x2 : uint64, x3 : uint64) =
let h = Fnv1a64.Seed
let h = Fnv1a64.Combine(h, x1)
let h = Fnv1a64.Combine(h, x2)
let h = Fnv1a64.Combine(h, x3)
h
static member inline Hash(key : string) =
let mutable hash = Fnv1a64.Seed
for i = 0 to key.Length - 1 do
let value = uint64 key.[i]
hash <- Fnv1a64.Combine(hash, value)
hash
module XXHash =
[]
let Prime32_1 = 2654435761u
[]
let Prime32_2 = 2246822519u
[]
let Prime32_3 = 3266489917u
[]
let Prime32_4 = 668265263u
[]
let Prime32_5 = 374761393u
type XXHash() =
static member inline RotateLeft(value : uint32, count) =
(value <<< count) ||| (value >>> (32 - count))
static member inline Finalize(hash : uint32) =
let h = hash ^^^ (hash >>> 15)
let h = h * XXHash.Prime32_2
let h = h ^^^ (h >>> 13)
let h = h * XXHash.Prime32_3
let h = h ^^^ (h >>> 16)
h
static member inline Combine(hash : uint32, value : uint32) =
let h = hash + value * XXHash.Prime32_3
let h = XXHash.RotateLeft(h, 17) * XXHash.Prime32_4
h
static member inline Initialize(seed : uint32) =
seed + XXHash.Prime32_5
static member inline Initialize(seed : uint32, size : uint32) =
let h = seed + XXHash.Prime32_5
let h = h + size
h
static member inline Hash(seed : uint32, value : uint32) =
let h = XXHash.Initialize(seed, 4u)
let h = XXHash.Combine(h, value)
XXHash.Finalize(h)
static member inline Hash(seed : uint32, x1 : uint32, x2 : uint32) =
let h = XXHash.Initialize(seed, 8u)
let h = XXHash.Combine(h, x1)
let h = XXHash.Combine(h, x2)
XXHash.Finalize(h)
static member inline Hash(seed : uint32, x1 : uint32, x2 : uint32, x3 : uint32) =
let h = XXHash.Initialize(seed, 12u)
let h = XXHash.Combine(h, x1)
let h = XXHash.Combine(h, x2)
let h = XXHash.Combine(h, x3)
XXHash.Finalize(h)
static member inline Hash(seed : uint32, span : ReadOnlySpan) =
let mutable h = XXHash.Initialize(seed, uint32 span.Length)
for i = 0 to span.Length - 1 do
h <- XXHash.Combine(h, uint32 span.[i])
XXHash.Finalize(h)
static member inline Hash(seed : uint32, span : ReadOnlySpan) =
let mutable h = XXHash.Initialize(seed, uint32 (span.Length * 2))
for i = 0 to span.Length - 1 do
h <- XXHash.Combine(h, uint32 (span.[i] &&& 0xffffffffUL))
h <- XXHash.Combine(h, uint32 (span.[i] >>> 32))
XXHash.Finalize(h)
static member inline FinalizeToRange(hash, index, min, max) =
let h = XXHash.Combine(hash, uint32 index)
let h = XXHash.Finalize(h)
int (h % uint32 (max - min)) + min
================================================
FILE: samples/Garnet.Numerics/Noise.fs
================================================
namespace Garnet.Numerics
// Adapted for F# from Stefan Gustavson code
// Simplex noise demystified:
// https://weber.itn.liu.se/~stegu/simplexnoise/simplexnoise.pdf
// Original license:
// sdnoise1234, Simplex noise with true analytic
// derivative in 1D to 4D.
//
// Copyright © 2003-2012, Stefan Gustavson
//
// Contact: stefan.gustavson@gmail.com
//
// This library is public domain software, released by the author
// into the public domain in February 2011. You may do anything
// you like with it. You may even remove all attributions,
// but of course I'd appreciate it if you kept my name somewhere.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// This is an implementation of Perlin "simplex noise" over one
// dimension (x), two dimensions (x,y), three dimensions (x,y,z)
// and four dimensions (x,y,z,w). The analytic derivative is
// returned, to make it possible to do lots of fun stuff like
// flow animations, curl noise, analytic antialiasing and such.
//
// Visually, this noise is exactly the same as the plain version of
// simplex noise provided in the file "snoise1234.c". It just returns
// all partial derivatives in addition to the scalar noise value.
open System.Numerics
module private SimplexNoise =
let private grad3 =
array2D
[[1.0f;1.0f;0.0f];[-1.0f;1.0f;0.0f];[1.0f;-1.0f;0.0f];[-1.0f;-1.0f;0.0f];
[1.0f;0.0f;1.0f];[-1.0f;0.0f;1.0f];[1.0f;0.0f;-1.0f];[-1.0f;0.0f;-1.0f];
[0.0f;1.0f;1.0f];[0.0f;-1.0f;1.0f];[0.0f;1.0f;-1.0f];[0.0f;-1.0f;-1.0f]];
let private grad4 =
array2D
[[0.0f;1.0f;1.0f;1.0f]; [0.0f;1.0f;1.0f;-1.0f]; [0.0f;1.0f;-1.0f;1.0f]; [0.0f;1.0f;-1.0f;-1.0f];
[0.0f;-1.0f;1.0f;1.0f]; [0.0f;-1.0f;1.0f;-1.0f]; [0.0f;-1.0f;-1.0f;1.0f]; [0.0f;-1.0f;-1.0f;-1.0f];
[1.0f;0.0f;1.0f;1.0f]; [1.0f;0.0f;1.0f;-1.0f]; [1.0f;0.0f;-1.0f;1.0f]; [1.0f;0.0f;-1.0f;-1.0f];
[-1.0f;0.0f;1.0f;1.0f]; [-1.0f;0.0f;1.0f;-1.0f]; [-1.0f;0.0f;-1.0f;1.0f]; [-1.0f;0.0f;-1.0f;-1.0f];
[1.0f;1.0f;0.0f;1.0f]; [1.0f;1.0f;0.0f;-1.0f]; [1.0f;-1.0f;0.0f;1.0f]; [1.0f;-1.0f;0.0f;-1.0f];
[-1.0f;1.0f;0.0f;1.0f]; [-1.0f;1.0f;0.0f;-1.0f]; [-1.0f;-1.0f;0.0f;1.0f]; [-1.0f;-1.0f;0.0f;-1.0f];
[1.0f;1.0f;1.0f;0.0f]; [1.0f;1.0f;-1.0f;0.0f]; [1.0f;-1.0f;1.0f;0.0f]; [1.0f;-1.0f;-1.0f;0.0f];
[-1.0f;1.0f;1.0f;0.0f]; [-1.0f;1.0f;-1.0f;0.0f]; [-1.0f;-1.0f;1.0f;0.0f]; [-1.0f;-1.0f;-1.0f;0.0f]];
// A lookup table to traverse the simplex around a given point in 4D.
// Details can be found where this table is used; in the 4D noise method.
let private simplex =
array2D
[[0;1;2;3];[0;1;3;2];[0;0;0;0];[0;2;3;1];[0;0;0;0];[0;0;0;0];[0;0;0;0];[1;2;3;0];
[0;2;1;3];[0;0;0;0];[0;3;1;2];[0;3;2;1];[0;0;0;0];[0;0;0;0];[0;0;0;0];[1;3;2;0];
[0;0;0;0];[0;0;0;0];[0;0;0;0];[0;0;0;0];[0;0;0;0];[0;0;0;0];[0;0;0;0];[0;0;0;0];
[1;2;0;3];[0;0;0;0];[1;3;0;2];[0;0;0;0];[0;0;0;0];[0;0;0;0];[2;3;0;1];[2;3;1;0];
[1;0;2;3];[1;0;3;2];[0;0;0;0];[0;0;0;0];[0;0;0;0];[2;0;3;1];[0;0;0;0];[2;1;3;0];
[0;0;0;0];[0;0;0;0];[0;0;0;0];[0;0;0;0];[0;0;0;0];[0;0;0;0];[0;0;0;0];[0;0;0;0];
[2;0;1;3];[0;0;0;0];[0;0;0;0];[0;0;0;0];[3;0;1;2];[3;0;2;1];[0;0;0;0];[3;1;2;0];
[2;1;0;3];[0;0;0;0];[0;0;0;0];[0;0;0;0];[3;1;0;2];[0;0;0;0];[3;2;0;1];[3;2;1;0]];
// Permutation table. This is just a random jumble of all numbers 0-255;
// repeated twice to avoid wrapping the index at 255 for each lookup.
let private perm =
[| 151;160;137;91;90;15;
131;13;201;95;96;53;194;233;7;225;140;36;103;30;69;142;8;99;37;240;21;10;23;
190; 6;148;247;120;234;75;0;26;197;62;94;252;219;203;117;35;11;32;57;177;33;
88;237;149;56;87;174;20;125;136;171;168; 68;175;74;165;71;134;139;48;27;166;
77;146;158;231;83;111;229;122;60;211;133;230;220;105;92;41;55;46;245;40;244;
102;143;54; 65;25;63;161; 1;216;80;73;209;76;132;187;208; 89;18;169;200;196;
135;130;116;188;159;86;164;100;109;198;173;186; 3;64;52;217;226;250;124;123;
5;202;38;147;118;126;255;82;85;212;207;206;59;227;47;16;58;17;182;189;28;42;
223;183;170;213;119;248;152; 2;44;154;163; 70;221;153;101;155;167; 43;172;9;
129;22;39;253; 19;98;108;110;79;113;224;232;178;185; 112;104;218;246;97;228;
251;34;242;193;238;210;144;12;191;179;162;241; 81;51;145;235;249;14;239;107;
49;192;214; 31;181;199;106;157;184; 84;204;176;115;121;50;45;127; 4;150;254;
138;236;205;93;222;114;67;29;24;72;243;141;128;195;78;66;215;61;156;180;
151;160;137;91;90;15;
131;13;201;95;96;53;194;233;7;225;140;36;103;30;69;142;8;99;37;240;21;10;23;
190; 6;148;247;120;234;75;0;26;197;62;94;252;219;203;117;35;11;32;57;177;33;
88;237;149;56;87;174;20;125;136;171;168; 68;175;74;165;71;134;139;48;27;166;
77;146;158;231;83;111;229;122;60;211;133;230;220;105;92;41;55;46;245;40;244;
102;143;54; 65;25;63;161; 1;216;80;73;209;76;132;187;208; 89;18;169;200;196;
135;130;116;188;159;86;164;100;109;198;173;186; 3;64;52;217;226;250;124;123;
5;202;38;147;118;126;255;82;85;212;207;206;59;227;47;16;58;17;182;189;28;42;
223;183;170;213;119;248;152; 2;44;154;163; 70;221;153;101;155;167; 43;172;9;
129;22;39;253; 19;98;108;110;79;113;224;232;178;185; 112;104;218;246;97;228;
251;34;242;193;238;210;144;12;191;179;162;241; 81;51;145;235;249;14;239;107;
49;192;214; 31;181;199;106;157;184; 84;204;176;115;121;50;45;127; 4;150;254;
138;236;205;93;222;114;67;29;24;72;243;141;128;195;78;66;215;61;156;180 |]
let private Sqrt3 = 1.7320508075688772935274463415059f
let private F2 = 0.5f * (Sqrt3 - 1.0f);
let private G2 = (3.0f - Sqrt3) / 6.0f;
let inline private dot2 (g : float32[,]) gi x y = g.[gi, 0] * x + g.[gi, 1] * y
let sample2 x y =
//float n0, n1, n2; // Noise contributions from the three corners
// Skew the input space to determine which simplex cell we're in
let s = (x + y) * F2; // Hairy factor for 2D
//int i = Floor(x + s);
//int j = Floor(y + s);
let realI = x + s
let realJ = y + s
let i = int(if realI > 0.0f then realI else realI - 1.0f)
let j = int(if realJ > 0.0f then realJ else realJ - 1.0f)
let t = float32(i + j) * G2;
let X0 = float32(i) - t; // Unskew the cell origin back to (x,y) space
let Y0 = float32(j) - t;
let x0 = x - X0; // The x,y distances from the cell origin
let y0 = y - Y0;
// For the 2D case, the simplex shape is an equilateral triangle.
// Determine which simplex we are in.
//let i1, j1 = // Offsets for second (middle) corner of simplex in (i,j) coords
// if (x0 > y0) then 1, 0 // lower triangle, XY order: (0,0)->(1,0)->(1,1)
// else 0, 1 // upper triangle, YX order: (0,0)->(0,1)->(1,1)
let i1 = if x0 > y0 then 1 else 0
let j1 = 1 - i1
// A step of (1,0) in (i,j) means a step of (1-c,-c) in (x,y), and
// a step of (0,1) in (i,j) means a step of (-c,1-c) in (x,y), where
// c = (3-sqrt(3))/6
let x1 = x0 - float32(i1) + G2; // Offsets for middle corner in (x,y) unskewed coords
let y1 = y0 - float32(j1) + G2;
let x2 = x0 - 1.0f + 2.0f * G2; // Offsets for last corner in (x,y) unskewed coords
let y2 = y0 - 1.0f + 2.0f * G2;
// Work out the hashed gradient indices of the three simplex corners
let ii = i &&& 255;
let jj = j &&& 255;
//int gi0 = perm.[ii + perm.[jj]] % 12;
//int gi1 = perm.[ii + i1 + perm.[jj + j1]] % 12;
//int gi2 = perm.[ii + 1 + perm.[jj + 1]] % 12;
let gi0 = perm.[ii + perm.[jj]] % 12;
let gi1 = perm.[ii + i1 + perm.[jj + j1]] % 12;
let gi2 = perm.[ii + 1 + perm.[jj + 1]] % 12;
//(n * (n * n * 15731 + 789221) + 1376312589)
// Calculate the contribution from the three corners
let t0 = 0.5f - x0 * x0 - y0 * y0
let n0 =
if (t0 < 0.0f) then 0.0f
else
let t02 = t0 * t0
t02 * t02 * (dot2 grad3 gi0 x0 y0) // (x,y) of grad3 used for 2D gradient
let t1 = 0.5f - x1 * x1 - y1 * y1
let n1 =
if (t1 < 0.0f) then 0.0f
else
let t12 = t1 * t1
t12 * t12 * (dot2 grad3 gi1 x1 y1)
let t2 = 0.5f - x2 * x2 - y2 * y2
let n2 =
if (t2 < 0.0f) then 0.0f
else
let t22 = t2 * t2
t22 * t22 * (dot2 grad3 gi2 x2 y2)
// Add contributions from each corner to get the final noise value.
// The result is scaled to return values in the interval [-1,1].
70.0f * (n0 + n1 + n2)
let private F3 = 1.0f / 3.0f
let private G3 = 1.0f / 6.0f; // Very nice and simple unskew factor, too
let inline private dot3 (g : float32[,]) gi x y z = g.[gi, 0] * x + g.[gi, 1] * y + g.[gi, 2] * z
let inline private floor (x : float32) = if x > 0.0f then int(x) else int(x) - 1
let sample3 x y z =
// Skew the input space to determine which simplex cell we're in
let s = (x + y + z) * F3 // Very nice and simple skew factor for 3D
let i = floor(x + s)
let j = floor(y + s)
let k = floor(z + s)
let t = float32(i + j + k) * G3
let X0 = float32(i) - t // Unskew the cell origin back to (x,y,z) space
let Y0 = float32(j) - t
let Z0 = float32(k) - t
let x0 = x - X0 // The x,y,z distances from the cell origin
let y0 = y - Y0
let z0 = z - Z0
// For the 3D case, the simplex shape is a slightly irregular tetrahedron.
// Determine which simplex we are in.
// Offsets for second corner of simplex in (i,j,k) coords
// Offsets for third corner of simplex in (i,j,k) coords
let struct(i1, j1, k1, i2, j2, k2) =
if (x0 >= y0) then
if (y0 >= z0) then 1, 0, 0, 1, 1, 0 // X Y Z order
else if (x0 >= z0) then 1, 0, 0, 1, 0, 1 // X Z Y order
else 0, 0, 1, 1, 0, 1 // Z X Y order
else
if (y0 < z0) then 0, 0, 1, 0, 1, 1 // Z Y X order
else if (x0 < z0) then 0, 1, 0, 0, 1, 1 // Y Z X order
else 0, 1, 0, 1, 1, 0 // Y X Z order
// A step of (1,0,0) in (i,j,k) means a step of (1-c,-c,-c) in (x,y,z),
// a step of (0,1,0) in (i,j,k) means a step of (-c,1-c,-c) in (x,y,z), and
// a step of (0,0,1) in (i,j,k) means a step of (-c,-c,1-c) in (x,y,z), where
// c = 1/6.
let x1 = x0 - float32(i1) + G3 // Offsets for second corner in (x,y,z) coords
let y1 = y0 - float32(j1) + G3
let z1 = z0 - float32(k1) + G3
let x2 = x0 - float32(i2) + 2.0f * G3 // Offsets for third corner in (x,y,z) coords
let y2 = y0 - float32(j2) + 2.0f * G3
let z2 = z0 - float32(k2) + 2.0f * G3
let x3 = x0 - 1.0f + 3.0f * G3 // Offsets for last corner in (x,y,z) coords
let y3 = y0 - 1.0f + 3.0f * G3
let z3 = z0 - 1.0f + 3.0f * G3
// Work out the hashed gradient indices of the four simplex corners
let ii = i &&& 255
let jj = j &&& 255
let kk = k &&& 255
let gi0 = perm.[ii + perm.[jj + perm.[kk]]] % 12
let gi1 = perm.[ii + i1 + perm.[jj + j1 + perm.[kk + k1]]] % 12
let gi2 = perm.[ii + i2 + perm.[jj + j2 + perm.[kk + k2]]] % 12
let gi3 = perm.[ii + 1 + perm.[jj + 1 + perm.[kk + 1]]] % 12
// Calculate the contribution from the four corners
let t0 = 0.5f - x0 * x0 - y0 * y0 - z0 * z0
let n0 =
if (t0 < 0.0f) then 0.0f
else
let t02 = t0 * t0
t02 * t02 * (dot3 grad3 gi0 x0 y0 z0)
let t1 = 0.6f - x1 * x1 - y1 * y1 - z1 * z1
let n1 =
if (t1 < 0.0f) then 0.0f
else
let t12 = t1 * t1
t12 * t12 * (dot3 grad3 gi1 x1 y1 z1)
let t2 = 0.6f - x2 * x2 - y2 * y2 - z2 * z2
let n2 =
if (t2 < 0.0f) then 0.0f
else
let t22 = t2 * t2
t22 * t22 * (dot3 grad3 gi2 x2 y2 z2)
let t3 = 0.6f - x3 * x3 - y3 * y3 - z3 * z3
let n3 =
if (t3 < 0.0f) then 0.0f
else
let t32 = t3 * t3
t32 * t32 * (dot3 grad3 gi3 x3 y3 z3)
// Add contributions from each corner to get the final noise value.
// The result is scaled to stay just inside [-1,1]
32.0f * (n0 + n1 + n2 + n3)
let private F4 = 0.309016994f // F4 = (Math.sqrt(5.0)-1.0)/4.0
let private G4 = 0.138196601f // G4 = (5.0-Math.sqrt(5.0))/20.0
let private dot4 (g : float32[,]) gi x y z w = g.[gi, 0] * x + g.[gi, 1] * y + g.[gi, 2] * z + g.[gi, 3] * w
let sample4 x y z w =
// The skewing and unskewing factors are hairy again for the 4D case
//double n0, n1, n2, n3, n4; // Noise contributions from the five corners
// Skew the (x,y,z,w) space to determine which cell of 24 simplices we're in
let s = (x + y + z + w) * F4; // Factor for 4D skewing
let i = floor(x + s);
let j = floor(y + s);
let k = floor(z + s);
let l = floor(w + s);
let t = float32(i + j + k + l) * G4; // Factor for 4D unskewing
let X0 = float32(i) - t; // Unskew the cell origin back to (x,y,z,w) space
let Y0 = float32(j) - t;
let Z0 = float32(k) - t;
let W0 = float32(l) - t;
let x0 = x - X0; // The x,y,z,w distances from the cell origin
let y0 = y - Y0;
let z0 = z - Z0;
let w0 = w - W0;
// For the 4D case, the simplex is a 4D shape I won't even try to describe.
// To find out which of the 24 possible simplices we're in, we need to
// determine the magnitude ordering of x0, y0, z0 and w0.
// The method below is a good way of finding the ordering of x,y,z,w and
// then find the correct traversal order for the simplex we’re in.
// First, six pair-wise comparisons are performed between each possible pair
// of the four coordinates, and the results are used to add up binary bits
// for an integer index.
let c1 = if (x0 > y0) then 32 else 0
let c2 = if (x0 > z0) then 16 else 0
let c3 = if (y0 > z0) then 8 else 0
let c4 = if (x0 > w0) then 4 else 0
let c5 = if (y0 > w0) then 2 else 0
let c6 = if (z0 > w0) then 1 else 0
let c = c1 + c2 + c3 + c4 + c5 + c6;
// int i1, j1, k1, l1; // The integer offsets for the second simplex corner
// int i2, j2, k2, l2; // The integer offsets for the third simplex corner
// int i3, j3, k3, l3; // The integer offsets for the fourth simplex corner
// simplex.[c] is a 4-vector with the numbers 0, 1, 2 and 3 in some order.
// Many values of c will never occur, since e.g. x>y>z>w makes x= 3 then 1 else 0;
let j1 = if simplex.[c, 1] >= 3 then 1 else 0;
let k1 = if simplex.[c, 2] >= 3 then 1 else 0;
let l1 = if simplex.[c, 3] >= 3 then 1 else 0;
// The number 2 in the "simplex" array is at the second largest coordinate.
let i2 = if simplex.[c, 0] >= 2 then 1 else 0;
let j2 = if simplex.[c, 1] >= 2 then 1 else 0;
let k2 = if simplex.[c, 2] >= 2 then 1 else 0;
let l2 = if simplex.[c, 3] >= 2 then 1 else 0;
// The number 1 in the "simplex" array is at the second smallest coordinate.
let i3 = if simplex.[c, 0] >= 1 then 1 else 0;
let j3 = if simplex.[c, 1] >= 1 then 1 else 0;
let k3 = if simplex.[c, 2] >= 1 then 1 else 0;
let l3 = if simplex.[c, 3] >= 1 then 1 else 0;
// The fifth corner has all coordinate offsets = 1, so no need to look that up.
let x1 = x0 - float32(i1) + G4; // Offsets for second corner in (x,y,z,w) coords
let y1 = y0 - float32(j1) + G4;
let z1 = z0 - float32(k1) + G4;
let w1 = w0 - float32(l1) + G4;
let x2 = x0 - float32(i2) + 2.0f * G4; // Offsets for third corner in (x,y,z,w) coords
let y2 = y0 - float32(j2) + 2.0f * G4;
let z2 = z0 - float32(k2) + 2.0f * G4;
let w2 = w0 - float32(l2) + 2.0f * G4;
let x3 = x0 - float32(i3) + 3.0f * G4; // Offsets for fourth corner in (x,y,z,w) coords
let y3 = y0 - float32(j3) + 3.0f * G4;
let z3 = z0 - float32(k3) + 3.0f * G4;
let w3 = w0 - float32(l3) + 3.0f * G4;
let x4 = x0 - 1.0f + 4.0f * G4; // Offsets for last corner in (x,y,z,w) coords
let y4 = y0 - 1.0f + 4.0f * G4;
let z4 = z0 - 1.0f + 4.0f * G4;
let w4 = w0 - 1.0f + 4.0f * G4;
// Work out the hashed gradient indices of the five simplex corners
let ii = i &&& 255;
let jj = j &&& 255;
let kk = k &&& 255;
let ll = l &&& 255;
let gi0 = perm.[ii + perm.[jj + perm.[kk + perm.[ll]]]] % 32;
let gi1 = perm.[ii + i1 + perm.[jj + j1 + perm.[kk + k1 + perm.[ll + l1]]]] % 32;
let gi2 = perm.[ii + i2 + perm.[jj + j2 + perm.[kk + k2 + perm.[ll + l2]]]] % 32;
let gi3 = perm.[ii + i3 + perm.[jj + j3 + perm.[kk + k3 + perm.[ll + l3]]]] % 32;
let gi4 = perm.[ii + 1 + perm.[jj + 1 + perm.[kk + 1 + perm.[ll + 1]]]] % 32;
// Calculate the contribution from the five corners
let t0 = 0.6f - x0 * x0 - y0 * y0 - z0 * z0 - w0 * w0;
let n0 =
if (t0 < 0.0f) then 0.0f
else
let t02 = t0 * t0;
t02 * t02 * (dot4 grad4 gi0 x0 y0 z0 w0)
let t1 = 0.6f - x1 * x1 - y1 * y1 - z1 * z1 - w1 * w1;
let n1 =
if (t1 < 0.0f) then 0.0f
else
let t12 = t1 * t1;
t12 * t12 * (dot4 grad4 gi1 x1 y1 z1 w1)
let t2 = 0.6f - x2 * x2 - y2 * y2 - z2 * z2 - w2 * w2;
let n2 =
if (t2 < 0.0f) then 0.0f
else
let t22 = t2 * t2;
t22 * t22 * (dot4 grad4 gi2 x2 y2 z2 w2);
let t3 = 0.6f - x3 * x3 - y3 * y3 - z3 * z3 - w3 * w3;
let n3 =
if (t3 < 0.0f) then 0.0f
else
let t32 = t3 * t3;
t32 * t32 * (dot4 grad4 gi3 x3 y3 z3 w3);
let t4 = 0.6f - x4 * x4 - y4 * y4 - z4 * z4 - w4 * w4;
let n4 =
if (t4 < 0.0f) then 0.0f
else
let t42 = t4 * t4;
t42 * t42 * (dot4 grad4 gi4 x4 y4 z4 w4);
// Sum up and scale the result to cover the range [-1,1]
27.0f * (n0 + n1 + n2 + n3 + n4);
// This code overlaps a lot with above, but also includes gradient calc
module private GradientNoise =
let private floor (x : float32) = if x > 0.0f then int(x) else int(x) - 1
// Permutation table. This is just a random jumble of all numbers 0-255,
// repeated twice to avoid wrapping the index at 255 for each lookup.
let private perm = [| 151;160;137;91;90;15;
131;13;201;95;96;53;194;233;7;225;140;36;103;30;69;142;8;99;37;240;21;10;23;
190; 6;148;247;120;234;75;0;26;197;62;94;252;219;203;117;35;11;32;57;177;33;
88;237;149;56;87;174;20;125;136;171;168; 68;175;74;165;71;134;139;48;27;166;
77;146;158;231;83;111;229;122;60;211;133;230;220;105;92;41;55;46;245;40;244;
102;143;54; 65;25;63;161; 1;216;80;73;209;76;132;187;208; 89;18;169;200;196;
135;130;116;188;159;86;164;100;109;198;173;186; 3;64;52;217;226;250;124;123;
5;202;38;147;118;126;255;82;85;212;207;206;59;227;47;16;58;17;182;189;28;42;
223;183;170;213;119;248;152; 2;44;154;163; 70;221;153;101;155;167; 43;172;9;
129;22;39;253; 19;98;108;110;79;113;224;232;178;185; 112;104;218;246;97;228;
251;34;242;193;238;210;144;12;191;179;162;241; 81;51;145;235;249;14;239;107;
49;192;214; 31;181;199;106;157;184; 84;204;176;115;121;50;45;127; 4;150;254;
138;236;205;93;222;114;67;29;24;72;243;141;128;195;78;66;215;61;156;180;
151;160;137;91;90;15;
131;13;201;95;96;53;194;233;7;225;140;36;103;30;69;142;8;99;37;240;21;10;23;
190; 6;148;247;120;234;75;0;26;197;62;94;252;219;203;117;35;11;32;57;177;33;
88;237;149;56;87;174;20;125;136;171;168; 68;175;74;165;71;134;139;48;27;166;
77;146;158;231;83;111;229;122;60;211;133;230;220;105;92;41;55;46;245;40;244;
102;143;54; 65;25;63;161; 1;216;80;73;209;76;132;187;208; 89;18;169;200;196;
135;130;116;188;159;86;164;100;109;198;173;186; 3;64;52;217;226;250;124;123;
5;202;38;147;118;126;255;82;85;212;207;206;59;227;47;16;58;17;182;189;28;42;
223;183;170;213;119;248;152; 2;44;154;163; 70;221;153;101;155;167; 43;172;9;
129;22;39;253; 19;98;108;110;79;113;224;232;178;185; 112;104;218;246;97;228;
251;34;242;193;238;210;144;12;191;179;162;241; 81;51;145;235;249;14;239;107;
49;192;214; 31;181;199;106;157;184; 84;204;176;115;121;50;45;127; 4;150;254;
138;236;205;93;222;114;67;29;24;72;243;141;128;195;78;66;215;61;156;180
|]
// Gradient tables. These could be programmed the Ken Perlin way with
// some clever bit-twiddling, but this is more clear, and not really slower.
let private grad2lut =
[ -1.0f; -1.0f; 1.0f; 0.0f; -1.0f; 0.0f; 1.0f; 1.0f;
-1.0f; 1.0f; 0.0f; -1.0f; 0.0f; 1.0f; 1.0f; -1.0f ]
// Gradient directions for 3D.
// These vectors are based on the midpoints of the 12 edges of a cube.
// A larger array of random unit length vectors would also do the job,
// but these 12 (including 4 repeats to make the array length a power
// of two) work better. They are not random, they are carefully chosen
// to represent a small, isotropic set of directions.
let private grad3lut =
array2D [
[ 1.0f; 0.0f; 1.0f ]; [ 0.0f; 1.0f; 1.0f ]; // 12 cube edges
[ -1.0f; 0.0f; 1.0f ]; [ 0.0f; -1.0f; 1.0f ];
[ 1.0f; 0.0f; -1.0f ]; [ 0.0f; 1.0f; -1.0f ];
[ -1.0f; 0.0f; -1.0f ]; [ 0.0f; -1.0f; -1.0f ];
[ 1.0f; -1.0f; 0.0f ]; [ 1.0f; 1.0f; 0.0f ];
[ -1.0f; 1.0f; 0.0f ]; [ -1.0f; -1.0f; 0.0f ];
[ 1.0f; 0.0f; 1.0f ]; [ -1.0f; 0.0f; 1.0f ]; // 4 repeats to make 16
[ 0.0f; 1.0f; -1.0f ]; [ 0.0f; -1.0f; -1.0f ] ]
let private grad4lut =
array2D [
[ 0.0f; 1.0f; 1.0f; 1.0f ]; [ 0.0f; 1.0f; 1.0f; -1.0f ]; [ 0.0f; 1.0f; -1.0f; 1.0f ]; [ 0.0f; 1.0f; -1.0f; -1.0f ]; // 32 tesseract edges
[ 0.0f; -1.0f; 1.0f; 1.0f ]; [ 0.0f; -1.0f; 1.0f; -1.0f ]; [ 0.0f; -1.0f; -1.0f; 1.0f ]; [ 0.0f; -1.0f; -1.0f; -1.0f ];
[ 1.0f; 0.0f; 1.0f; 1.0f ]; [ 1.0f; 0.0f; 1.0f; -1.0f ]; [ 1.0f; 0.0f; -1.0f; 1.0f ]; [ 1.0f; 0.0f; -1.0f; -1.0f ];
[ -1.0f; 0.0f; 1.0f; 1.0f ]; [ -1.0f; 0.0f; 1.0f; -1.0f ]; [ -1.0f; 0.0f; -1.0f; 1.0f ]; [ -1.0f; 0.0f; -1.0f; -1.0f ];
[ 1.0f; 1.0f; 0.0f; 1.0f ]; [ 1.0f; 1.0f; 0.0f; -1.0f ]; [ 1.0f; -1.0f; 0.0f; 1.0f ]; [ 1.0f; -1.0f; 0.0f; -1.0f ];
[ -1.0f; 1.0f; 0.0f; 1.0f ]; [ -1.0f; 1.0f; 0.0f; -1.0f ]; [ -1.0f; -1.0f; 0.0f; 1.0f ]; [ -1.0f; -1.0f; 0.0f; -1.0f ];
[ 1.0f; 1.0f; 1.0f; 0.0f ]; [ 1.0f; 1.0f; -1.0f; 0.0f ]; [ 1.0f; -1.0f; 1.0f; 0.0f ]; [ 1.0f; -1.0f; -1.0f; 0.0f ];
[ -1.0f; 1.0f; 1.0f; 0.0f ]; [ -1.0f; 1.0f; -1.0f; 0.0f ]; [ -1.0f; -1.0f; 1.0f; 0.0f ]; [ -1.0f; -1.0f; -1.0f; 0.0f ] ]
// A lookup table to traverse the simplex around a given point in 4D.
// Details can be found where this table is used; in the 4D noise method.
let private simplex =
array2D [
[0;1;2;3];[0;1;3;2];[0;0;0;0];[0;2;3;1];[0;0;0;0];[0;0;0;0];[0;0;0;0];[1;2;3;0];
[0;2;1;3];[0;0;0;0];[0;3;1;2];[0;3;2;1];[0;0;0;0];[0;0;0;0];[0;0;0;0];[1;3;2;0];
[0;0;0;0];[0;0;0;0];[0;0;0;0];[0;0;0;0];[0;0;0;0];[0;0;0;0];[0;0;0;0];[0;0;0;0];
[1;2;0;3];[0;0;0;0];[1;3;0;2];[0;0;0;0];[0;0;0;0];[0;0;0;0];[2;3;0;1];[2;3;1;0];
[1;0;2;3];[1;0;3;2];[0;0;0;0];[0;0;0;0];[0;0;0;0];[2;0;3;1];[0;0;0;0];[2;1;3;0];
[0;0;0;0];[0;0;0;0];[0;0;0;0];[0;0;0;0];[0;0;0;0];[0;0;0;0];[0;0;0;0];[0;0;0;0];
[2;0;1;3];[0;0;0;0];[0;0;0;0];[0;0;0;0];[3;0;1;2];[3;0;2;1];[0;0;0;0];[3;1;2;0];
[2;1;0;3];[0;0;0;0];[0;0;0;0];[0;0;0;0];[3;1;0;2];[0;0;0;0];[3;2;0;1];[3;2;1;0]];
// Helper functions to compute gradients in 1D to 4D and gradients-dot-residualvectors in 2D to 4D.
let private grad1 (hash: int) =
let h = hash &&& 15
let gx = 1.0f + float32(h &&& 7) // Gradient value is one of 1.0, 2.0, ..., 8.0
if ((h &&& 8) <> 0) then -gx else gx // Make half of the gradients negative
// let grad2 (hash : int) =
// let h = hash &&& 7
// grad2lut.[h, 0], grad2lut.[h, 1]
let private grad3 (hash : int) =
let h = hash &&& 15
grad3lut.[h, 0], grad3lut.[h, 1], grad3lut.[h, 2]
let private grad4 (hash : int) =
let h = hash &&& 31;
grad4lut.[h, 0], grad4lut.[h, 1], grad4lut.[h, 2], grad4lut.[h, 3]
// 1D simplex noise with derivative. If the last argument is not null, the analytic derivative is also calculated.
let sample1 calcGrad (x : float32) =
let i0 = floor(x)
let i1 = i0 + 1
let x0 = x - float32(i0)
let x1 = x0 - 1.0f
let x20 = x0 * x0
let t0 = 1.0f - x20
// if(t0 < 0.0f) t0 = 0.0f; // Never happens for 1D: x0<=1 always
let t20 = t0 * t0
let t40 = t20 * t20
let gx0 = grad1(perm.[i0 &&& 0xff])
let n0 = t40 * gx0 * x0
let x21 = x1 * x1
let t1 = 1.0f - x21
// if(t1 < 0.0f) t1 = 0.0f; // Never happens for 1D: |x1|<=1 always
let t21 = t1 * t1
let t41 = t21 * t21
let gx1 = grad1(perm.[i1 &&& 0xff])
let n1 = t41 * gx1 * x1
// Compute derivative, if requested by supplying non-null pointer
// for the last argument
// Compute derivative according to:
// *dnoise_dx = -8.0f * t20 * t0 * x0 * (gx0 * x0) + t40 * gx0;
// *dnoise_dx += -8.0f * t21 * t1 * x1 * (gx1 * x1) + t41 * gx1;
// The maximum value of this noise is 8*(3/4)^4 = 2.53125
// A factor of 0.395 would scale to fit exactly within [-1,1], but
// to better match classic Perlin noise, we scale it down some more.
let value = 0.25f * (n0 + n1)
if calcGrad then
let dx0 = t20 * t0 * gx0 * x20
let dx1 = t21 * t1 * gx1 * x21
let dx = (dx0 + dx1) * -8.0f + t40 * gx0 + t41 * gx1
struct(value, dx * 0.25f) // Scale derivative to match the noise scaling
else
struct(value, 0.0f)
// Skewing factors for 2D simplex grid:
// F2 = 0.5*(sqrt(3.0)-1.0)
// G2 = (3.0-Math.sqrt(3.0))/6.0
let private F2 = 0.366025403f
let private G2 = 0.211324865f
let private Scaling2 = 40.0f
let sample2 calcGrad (x: float32) (y: float32) =
// Skew the input space to determine which simplex cell we're in
let s = (x + y) * F2; // Hairy factor for 2D
let xs = x + s;
let ys = y + s;
let i = floor(xs);
let j = floor(ys);
let t = float32(i + j) * G2;
let X0 = float32(i) - t; // Unskew the cell origin back to (x,y) space */
let Y0 = float32(j) - t;
let x0 = x - X0; // The x,y distances from the cell origin */
let y0 = y - Y0;
// For the 2D case, the simplex shape is an equilateral triangle.
// Determine which simplex we are in.
let i1, j1 = // Offsets for second (middle) corner of simplex in (i,j) coords */
if x0 > y0 then 1, 0 // lower triangle, XY order: (0,0)->(1,0)->(1,1) */
else 0, 1 // upper triangle, YX order: (0,0)->(0,1)->(1,1) */
// A step of (1,0) in (i,j) means a step of (1-c,-c) in (x,y), and
// a step of (0,1) in (i,j) means a step of (-c,1-c) in (x,y), where
// c = (3-sqrt(3))/6
let x1 = x0 - float32(i1) + G2; // Offsets for middle corner in (x,y) unskewed coords */
let y1 = y0 - float32(j1) + G2;
let x2 = x0 - 1.0f + 2.0f * G2; // Offsets for last corner in (x,y) unskewed coords */
let y2 = y0 - 1.0f + 2.0f * G2;
// Wrap the integer indices at 256, to avoid indexing perm.[] out of bounds */
let ii = i &&& 0xff;
let jj = j &&& 0xff;
// Calculate the contribution from the three corners */
let t0c = 0.5f - x0 * x0 - y0 * y0
let struct(t0, t20, t40, n0, gx0, gy0) =
if t0c < 0.0f then struct(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f) // no influence
else
let h = (perm.[ii + perm.[jj]] &&& 7) <<< 1
let gx0, gy0 = grad2lut.[h], grad2lut.[h + 1]
//let gx0, gy0 = grad2(perm.[ii + perm.[jj]])
let t20 = t0c * t0c
let t40 = t20 * t20
let n0 = t40 * (gx0 * x0 + gy0 * y0)
struct(t0c, t20, t40, n0, gx0, gy0)
let t1c = 0.5f - x1 * x1 - y1 * y1
let struct(t1, t21, t41, n1, gx1, gy1) =
if t1c < 0.0f then struct(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f) // no influence
else
let h = (perm.[ii + i1 + perm.[jj + j1]] &&& 7) <<< 1
let gx1, gy1 = grad2lut.[h], grad2lut.[h + 1]
//let gx1, gy1 = grad2(perm.[ii + i1 + perm.[jj + j1]])
let t21 = t1c * t1c
let t41 = t21 * t21
let n1 = t41 * (gx1 * x1 + gy1 * y1)
struct(t1c, t21, t41, n1, gx1, gy1)
let t2c = 0.5f - x2 * x2 - y2 * y2
let struct(t2, t22, t42, n2, gx2, gy2) =
if (t2c < 0.0f) then struct(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f) // no influence
else
let h = (perm.[ii + 1 + perm.[jj + 1]] &&& 7) <<< 1
let gx2, gy2 = grad2lut.[h], grad2lut.[h + 1]
//let gx2, gy2 = grad2(perm.[ii + 1 + perm.[jj + 1]])
let t22 = t2c * t2c
let t42 = t22 * t22
let n2 = t42 * (gx2 * x2 + gy2 * y2)
struct(t2c, t22, t42, n2, gx2, gy2)
// Add contributions from each corner to get the final noise value.
// The result is scaled to return values in the interval [-1,1].
let value = Scaling2 * (n0 + n1 + n2);
if calcGrad then
// A straight, unoptimised calculation would be like:
// *dnoise_dx = -8.0f * t20 * t0 * x0 * ( gx0 * x0 + gy0 * y0 ) + t40 * gx0;
// *dnoise_dy = -8.0f * t20 * t0 * y0 * ( gx0 * x0 + gy0 * y0 ) + t40 * gy0;
// *dnoise_dx += -8.0f * t21 * t1 * x1 * ( gx1 * x1 + gy1 * y1 ) + t41 * gx1;
// *dnoise_dy += -8.0f * t21 * t1 * y1 * ( gx1 * x1 + gy1 * y1 ) + t41 * gy1;
// *dnoise_dx += -8.0f * t22 * t2 * x2 * ( gx2 * x2 + gy2 * y2 ) + t42 * gx2;
// *dnoise_dy += -8.0f * t22 * t2 * y2 * ( gx2 * x2 + gy2 * y2 ) + t42 * gy2;
let temp0 = t20 * t0 * (gx0 * x0 + gy0 * y0);
let dnoise_dx = temp0 * x0;
let dnoise_dy = temp0 * y0;
let temp1 = t21 * t1 * (gx1 * x1 + gy1 * y1);
let dnoise_dx1 = dnoise_dx + temp1 * x1;
let dnoise_dy1 = dnoise_dy + temp1 * y1;
let temp2 = t22 * t2 * (gx2 * x2 + gy2 * y2);
let dnoise_dx2 = (dnoise_dx1 + temp2 * x2) * -8.0f + t40 * gx0 + t41 * gx1 + t42 * gx2
let dnoise_dy2 = (dnoise_dy1 + temp2 * y2) * -8.0f + t40 * gy0 + t41 * gy1 + t42 * gy2
struct(value, dnoise_dx2 * Scaling2, dnoise_dy2 * Scaling2)
else
struct(value, 0.0f, 0.0f)
// Skewing factors for 3D simplex grid:
// F3 = 1/3
// G3 = 1/6 */
let private F3 = 0.333333333f
let private G3 = 0.166666667f
let private Scaling3 = 28.0f
let sample3 calcGrad (x: float32) (y: float32) (z: float32) =
// Skew the input space to determine which simplex cell we're in */
let s = (x + y + z) * F3; // Very nice and simple skew factor for 3D
let xs = x + s;
let ys = y + s;
let zs = z + s;
let i = floor(xs);
let j = floor(ys);
let k = floor(zs);
let t = float32(i + j + k) * G3;
let X0 = float32(i) - t; // Unskew the cell origin back to (x,y,z) space
let Y0 = float32(j) - t;
let Z0 = float32(k) - t;
let x0 = x - X0; // The x,y,z distances from the cell origin
let y0 = y - Y0;
let z0 = z - Z0;
// For the 3D case, the simplex shape is a slightly irregular tetrahedron.
// Determine which simplex we are in.
// i1, j1, k1: Offsets for second corner of simplex in (i,j,k) coords */
// i2, j2, k3: Offsets for third corner of simplex in (i,j,k) coords
let struct(i1, j1, k1, i2, j2, k2) =
if x0 >= y0 then
if y0 >= z0 then struct(1, 0, 0, 1, 1, 0) // X Y Z order
else if x0 >= z0 then struct(1, 0, 0, 1, 0, 1) // X Z Y order
else struct(0, 0, 1, 1, 0, 1) // Z X Y order
else // x0 y0) then 32 else 0;
let c2 = if (x0 > z0) then 16 else 0;
let c3 = if (y0 > z0) then 8 else 0;
let c4 = if (x0 > w0) then 4 else 0;
let c5 = if (y0 > w0) then 2 else 0;
let c6 = if (z0 > w0) then 1 else 0;
let c = c1 ||| c2 ||| c3 ||| c4 ||| c5 ||| c6; // '|' is mostly faster than '+'
// simplex[c] is a 4-vector with the numbers 0, 1, 2 and 3 in some order.
// Many values of c will never occur, since e.g. x>y>z>w makes x= 3 then 1 else 0;
let j1 = if simplex.[c, 1] >= 3 then 1 else 0;
let k1 = if simplex.[c, 2] >= 3 then 1 else 0;
let l1 = if simplex.[c, 3] >= 3 then 1 else 0;
// The number 2 in the "simplex" array is at the second largest coordinate.
let i2 = if simplex.[c, 0] >= 2 then 1 else 0;
let j2 = if simplex.[c, 1] >= 2 then 1 else 0;
let k2 = if simplex.[c, 2] >= 2 then 1 else 0;
let l2 = if simplex.[c, 3] >= 2 then 1 else 0;
// The number 1 in the "simplex" array is at the second smallest coordinate.
let i3 = if simplex.[c, 0] >= 1 then 1 else 0;
let j3 = if simplex.[c, 1] >= 1 then 1 else 0;
let k3 = if simplex.[c, 2] >= 1 then 1 else 0;
let l3 = if simplex.[c, 3] >= 1 then 1 else 0;
// The fifth corner has all coordinate offsets = 1, so no need to look that up.
let x1 = x0 - float32(i1) + G4; // Offsets for second corner in (x,y,z,w) coords
let y1 = y0 - float32(j1) + G4;
let z1 = z0 - float32(k1) + G4;
let w1 = w0 - float32(l1) + G4;
let x2 = x0 - float32(i2) + 2.0f * G4; // Offsets for third corner in (x,y,z,w) coords
let y2 = y0 - float32(j2) + 2.0f * G4;
let z2 = z0 - float32(k2) + 2.0f * G4;
let w2 = w0 - float32(l2) + 2.0f * G4;
let x3 = x0 - float32(i3) + 3.0f * G4; // Offsets for fourth corner in (x,y,z,w) coords
let y3 = y0 - float32(j3) + 3.0f * G4;
let z3 = z0 - float32(k3) + 3.0f * G4;
let w3 = w0 - float32(l3) + 3.0f * G4;
let x4 = x0 - 1.0f + 4.0f * G4; // Offsets for last corner in (x,y,z,w) coords
let y4 = y0 - 1.0f + 4.0f * G4;
let z4 = z0 - 1.0f + 4.0f * G4;
let w4 = w0 - 1.0f + 4.0f * G4;
// Wrap the integer indices at 256, to avoid indexing perm.[] out of bounds
let ii = i &&& 0xff;
let jj = j &&& 0xff;
let kk = k &&& 0xff;
let ll = l &&& 0xff;
// Calculate the contribution from the five corners
let t0c = 0.6f - x0 * x0 - y0 * y0 - z0 * z0 - w0 * w0;
let struct(n0, t0, t20, t40, gx0, gy0, gz0, gw0) =
if (t0c < 0.0f) then 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f // no influence
else
let t20 = t0c * t0c;
let t40 = t20 * t20;
let gx0, gy0, gz0, gw0 = grad4(perm.[ii + perm.[jj + perm.[kk + perm.[ll]]]]);
let n0 = t40 * (gx0 * x0 + gy0 * y0 + gz0 * z0 + gw0 * w0);
struct(n0, t0c, t20, t40, gx0, gy0, gz0, gw0)
let t1c = 0.6f - x1 * x1 - y1 * y1 - z1 * z1 - w1 * w1;
let struct(n1, t1, t21, t41, gx1, gy1, gz1, gw1) =
if (t1c < 0.0f) then 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f // no influence
else
let t21 = t1c * t1c;
let t41 = t21 * t21;
let gx1, gy1, gz1, gw1 = grad4(perm.[ii + i1 + perm.[jj + j1 + perm.[kk + k1 + perm.[ll + l1]]]]);
let n1 = t41 * (gx1 * x1 + gy1 * y1 + gz1 * z1 + gw1 * w1);
struct(n1, t1c, t21, t41, gx1, gy1, gz1, gw1)
let t2c = 0.6f - x2 * x2 - y2 * y2 - z2 * z2 - w2 * w2;
let struct(n2, t2, t22, t42, gx2, gy2, gz2, gw2) =
if (t2c < 0.0f) then 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f // no influence
else
let t22 = t2c * t2c;
let t42 = t22 * t22;
let gx2, gy2, gz2, gw2 = grad4(perm.[ii + i2 + perm.[jj + j2 + perm.[kk + k2 + perm.[ll + l2]]]]);
let n2 = t42 * (gx2 * x2 + gy2 * y2 + gz2 * z2 + gw2 * w2);
struct(n2, t2c, t22, t42, gx2, gy2, gz2, gw2)
let t3c = 0.6f - x3 * x3 - y3 * y3 - z3 * z3 - w3 * w3;
let struct(n3, t3, t23, t43, gx3, gy3, gz3, gw3) =
if (t3c < 0.0f) then 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f // no influence
else
let t23 = t3c * t3c;
let t43 = t23 * t23;
let gx3, gy3, gz3, gw3 = grad4(perm.[ii + i3 + perm.[jj + j3 + perm.[kk + k3 + perm.[ll + l3]]]]);
let n3 = t43 * (gx3 * x3 + gy3 * y3 + gz3 * z3 + gw3 * w3);
struct(n3, t3c, t23, t43, gx3, gy3, gz3, gw3)
let t4c = 0.6f - x4 * x4 - y4 * y4 - z4 * z4 - w4 * w4;
let struct(n4, t4, t24, t44, gx4, gy4, gz4, gw4) =
if (t4c < 0.0f) then 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f // no influence
else
let t24 = t4c * t4c;
let t44 = t24 * t24;
let gx4, gy4, gz4, gw4 = grad4(perm.[ii + 1 + perm.[jj + 1 + perm.[kk + 1 + perm.[ll + 1]]]]);
let n4 = t44 * (gx4 * x4 + gy4 * y4 + gz4 * z4 + gw4 * w4);
struct(n4, t4c, t24, t44, gx4, gy4, gz4, gw4)
// Sum up and scale the result to cover the range [-1,1]
let value = 27.0f * (n0 + n1 + n2 + n3 + n4); // The scale factor is preliminary!
// Compute derivative, if requested by supplying non-null pointers
// for the last four arguments */
//if( ( NULL != dnoise_dx ) && ( NULL != dnoise_dy ) && ( NULL != dnoise_dz ) && ( NULL != dnoise_dw ) )
if calcGrad then
// A straight, unoptimised calculation would be like:
//* *dnoise_dx = -8.0f * t20 * t0 * x0 * dot(gx0, gy0, gz0, gw0, x0, y0, z0, w0) + t40 * gx0;
//* *dnoise_dy = -8.0f * t20 * t0 * y0 * dot(gx0, gy0, gz0, gw0, x0, y0, z0, w0) + t40 * gy0;
//* *dnoise_dz = -8.0f * t20 * t0 * z0 * dot(gx0, gy0, gz0, gw0, x0, y0, z0, w0) + t40 * gz0;
//* *dnoise_dw = -8.0f * t20 * t0 * w0 * dot(gx0, gy0, gz0, gw0, x0, y0, z0, w0) + t40 * gw0;
//* *dnoise_dx += -8.0f * t21 * t1 * x1 * dot(gx1, gy1, gz1, gw1, x1, y1, z1, w1) + t41 * gx1;
//* *dnoise_dy += -8.0f * t21 * t1 * y1 * dot(gx1, gy1, gz1, gw1, x1, y1, z1, w1) + t41 * gy1;
//* *dnoise_dz += -8.0f * t21 * t1 * z1 * dot(gx1, gy1, gz1, gw1, x1, y1, z1, w1) + t41 * gz1;
//* *dnoise_dw = -8.0f * t21 * t1 * w1 * dot(gx1, gy1, gz1, gw1, x1, y1, z1, w1) + t41 * gw1;
//* *dnoise_dx += -8.0f * t22 * t2 * x2 * dot(gx2, gy2, gz2, gw2, x2, y2, z2, w2) + t42 * gx2;
//* *dnoise_dy += -8.0f * t22 * t2 * y2 * dot(gx2, gy2, gz2, gw2, x2, y2, z2, w2) + t42 * gy2;
//* *dnoise_dz += -8.0f * t22 * t2 * z2 * dot(gx2, gy2, gz2, gw2, x2, y2, z2, w2) + t42 * gz2;
//* *dnoise_dw += -8.0f * t22 * t2 * w2 * dot(gx2, gy2, gz2, gw2, x2, y2, z2, w2) + t42 * gw2;
//* *dnoise_dx += -8.0f * t23 * t3 * x3 * dot(gx3, gy3, gz3, gw3, x3, y3, z3, w3) + t43 * gx3;
//* *dnoise_dy += -8.0f * t23 * t3 * y3 * dot(gx3, gy3, gz3, gw3, x3, y3, z3, w3) + t43 * gy3;
//* *dnoise_dz += -8.0f * t23 * t3 * z3 * dot(gx3, gy3, gz3, gw3, x3, y3, z3, w3) + t43 * gz3;
//* *dnoise_dw += -8.0f * t23 * t3 * w3 * dot(gx3, gy3, gz3, gw3, x3, y3, z3, w3) + t43 * gw3;
//* *dnoise_dx += -8.0f * t24 * t4 * x4 * dot(gx4, gy4, gz4, gw4, x4, y4, z4, w4) + t44 * gx4;
//* *dnoise_dy += -8.0f * t24 * t4 * y4 * dot(gx4, gy4, gz4, gw4, x4, y4, z4, w4) + t44 * gy4;
//* *dnoise_dz += -8.0f * t24 * t4 * z4 * dot(gx4, gy4, gz4, gw4, x4, y4, z4, w4) + t44 * gz4;
//* *dnoise_dw += -8.0f * t24 * t4 * w4 * dot(gx4, gy4, gz4, gw4, x4, y4, z4, w4) + t44 * gw4;
//*/
let temp0 = t20 * t0 * (gx0 * x0 + gy0 * y0 + gz0 * z0 + gw0 * w0);
let dnoise_dx = temp0 * x0;
let dnoise_dy = temp0 * y0;
let dnoise_dz = temp0 * z0;
let dnoise_dw = temp0 * w0;
let temp1 = t21 * t1 * (gx1 * x1 + gy1 * y1 + gz1 * z1 + gw1 * w1);
let dnoise_dx1 = dnoise_dx + temp1 * x1;
let dnoise_dy1 = dnoise_dy + temp1 * y1;
let dnoise_dz1 = dnoise_dz + temp1 * z1;
let dnoise_dw1 = dnoise_dw + temp1 * w1;
let temp2 = t22 * t2 * (gx2 * x2 + gy2 * y2 + gz2 * z2 + gw2 * w2);
let dnoise_dx2 = dnoise_dx1 + temp2 * x2;
let dnoise_dy2 = dnoise_dy1 + temp2 * y2;
let dnoise_dz2 = dnoise_dz1 + temp2 * z2;
let dnoise_dw2 = dnoise_dw1 + temp2 * w2;
let temp3 = t23 * t3 * (gx3 * x3 + gy3 * y3 + gz3 * z3 + gw3 * w3);
let dnoise_dx3 = dnoise_dx2 + temp3 * x3;
let dnoise_dy3 = dnoise_dy2 + temp3 * y3;
let dnoise_dz3 = dnoise_dz2 + temp3 * z3;
let dnoise_dw3 = dnoise_dw2 + temp3 * w3;
let temp4 = t24 * t4 * (gx4 * x4 + gy4 * y4 + gz4 * z4 + gw4 * w4);
let dnoise_dx4 = dnoise_dx3 + temp4 * x4;
let dnoise_dy4 = dnoise_dy3 + temp4 * y4;
let dnoise_dz4 = dnoise_dz3 + temp4 * z4;
let dnoise_dw4 = dnoise_dw3 + temp4 * w4;
let dnoise_dx5 = dnoise_dx4 * -8.0f + t40 * gx0 + t41 * gx1 + t42 * gx2 + t43 * gx3 + t44 * gx4;
let dnoise_dy5 = dnoise_dy4 * -8.0f + t40 * gy0 + t41 * gy1 + t42 * gy2 + t43 * gy3 + t44 * gy4;
let dnoise_dz5 = dnoise_dz4 * -8.0f + t40 * gz0 + t41 * gz1 + t42 * gz2 + t43 * gz3 + t44 * gz4;
let dnoise_dw5 = dnoise_dw4 * -8.0f + t40 * gw0 + t41 * gw1 + t42 * gw2 + t43 * gw3 + t44 * gw4;
struct(value, dnoise_dx5 * 28.0f, dnoise_dy5 * 28.0f, dnoise_dz5 * 28.0f, dnoise_dw5 * 28.0f)
else
struct(value, 0.0f, 0.0f, 0.0f, 0.0f)
type Noise() =
static member Sample(x) =
let struct(s, _) = GradientNoise.sample1 false x
s
static member Sample(p : Vector2) =
SimplexNoise.sample2 p.X p.Y
static member Sample(p : Vector3) =
SimplexNoise.sample3 p.X p.Y p.Z
static member Sample(p : Vector4) =
SimplexNoise.sample4 p.X p.Y p.Z p.W
================================================
FILE: samples/Garnet.Numerics/Numerics.fs
================================================
namespace Garnet.Numerics
open System
open System.Numerics
[]
module MathF =
type MathF with
static member inline Lerp(min, max, t : float32) =
min * (1.0f - t) + max * t
static member inline Clamp(s0 : float32, s1 : float32, s : float32) =
s |> max s0 |> min s1
static member inline Clamp01(x) =
MathF.Clamp(0.0f, 1.0f, x)
static member inline LinearStep(s0, s1, s) =
let length = s1 - s0
if abs length < 1e-7f then 0.0f
else MathF.Clamp01((s - s0) / length)
static member inline SmoothStep(s0, s1, s) =
let x = MathF.LinearStep(s0, s1, s)
x * x * (3.0f - 2.0f * x)
[]
module Matrix4x4 =
type Matrix4x4 with
member m.GetInverseOrIdentity() =
let mutable mInv = Matrix4x4.Identity
if Matrix4x4.Invert(m, &mInv) then mInv else Matrix4x4.Identity
================================================
FILE: samples/Garnet.Numerics/Random.fs
================================================
namespace Garnet.Numerics
open System
open System.Numerics
type PcgRandom(initState, initSeq) =
let inc = PcgRandom.GetIncrement(initSeq)
let mutable state = PcgRandom.GetState(initState, inc)
new() = PcgRandom(0x853c49e6748fea9bUL, 0xda3e39cb94b95bdbUL)
member c.NextUInt32() =
let result = PcgRandom.GetUInt32(state)
state <- PcgRandom.GetNextState(state, inc)
result
static member GetUInt32(state) =
let xorShifted = uint32 (((state >>> 18) ^^^ state) >>> 27)
let rot = int (state >>> 59)
(xorShifted >>> rot) ||| (xorShifted <<< ((-rot) &&& 31))
static member GetNextState(state, inc) =
state * 6364136223846793005UL + inc
static member GetIncrement(initSeq) =
(initSeq <<< 1) ||| 1UL
static member GetState(seed, inc) =
let s = PcgRandom.GetNextState(0UL, inc) + seed
PcgRandom.GetNextState(s, inc)
type PcgRandom with
member c.NextInt32() =
c.NextUInt32() |> int
member c.NextUInt32Unbiased(exclusiveBound : uint) =
let threshold = uint32 ((0x100000000UL - uint64 exclusiveBound) % uint64 exclusiveBound)
let mutable r = c.NextUInt32()
while r < threshold do
r <- c.NextUInt32()
r % exclusiveBound
member c.NextInt32Unbiased(exclusiveBound : int) =
let threshold = int32 ((0x100000000UL - uint64 exclusiveBound) % uint64 exclusiveBound)
let mutable r = c.NextInt32()
while r < threshold do
r <- c.NextInt32()
r % exclusiveBound
member c.NextInt32(max : int) =
if max = 0 then 0 else c.NextUInt32() % (uint32 max) |> int
member c.NextUInt32(max : uint) =
if max = 0u then 0u else c.NextUInt32() % max
member c.NextUInt32(min, max) =
c.NextUInt32(max - min) + min
member c.NextInt32(min, max) =
c.NextInt32(max - min) + min
/// Closed [0, 1]
member c.NextDouble() =
let x = c.NextUInt32()
double x * (1.0 / 4294967295.0)
/// Half open [0, 1)
member c.NextDouble2() =
let x = c.NextUInt32()
double x * (1.0 / 4294967296.0)
/// Open (0, 1)
member c.NextDouble3() =
let x = c.NextUInt32()
(double x + 0.5) * (1.0 / 4294967296.0)
member c.NextSingle() = float32 (c.NextDouble())
member c.NextSingle2() = float32 (c.NextDouble2())
member c.NextSingle3() = float32 (c.NextDouble3())
member c.NextSingle(min, max) =
c.NextSingle() * (max - min) + min
member c.NextRotation(radiansRange) =
Vector2.FromRadians((c.NextSingle() - 0.5f) * radiansRange)
member c.NextRotationDegrees(degreesRange) =
c.NextRotation(degreesRange * MathF.PI / 180.0f)
member c.NextVector2() =
Vector2(c.NextSingle(), c.NextSingle())
member c.NextVector2(range : Range2) =
Range2.Lerp(range, c.NextVector2())
member c.NextVector3() =
Vector3(c.NextSingle(), c.NextSingle(), c.NextSingle())
member c.NextVector3(range : Range3) =
Range3.Lerp(range, c.NextVector3())
member c.NextVector2i(r : Range2i) =
Vector2i(
c.NextInt32(r.Min.X, r.Max.X),
c.NextInt32(r.Min.Y, r.Max.Y))
member c.NextVector2i(min, max) =
let r = Rangei(min, max)
c.NextVector2i(Range2i(r, r))
member c.NextVector3i(r : Range3i) =
Vector3i(
c.NextInt32(r.Min.X, r.Max.X),
c.NextInt32(r.Min.Y, r.Max.Y),
c.NextInt32(r.Min.Z, r.Max.Z))
member c.NextVector3i(min, max) =
let r = Rangei(min, max)
c.NextVector3i(Range3i(r, r, r))
member c.Next(dest : Span) =
for i = 0 to dest.Length - 1 do
dest.[i] <- c.NextInt32()
member c.Shuffle(items) =
let r = items |> Seq.toArray
for i = 0 to r.Length - 2 do
let j = c.NextInt32(i, r.Length)
let temp = r.[i]
r.[i] <- r.[j]
r.[j] <- temp
r
================================================
FILE: samples/Garnet.Numerics/Ranges.fs
================================================
namespace Garnet.Numerics
open System.Numerics
// Float range types
[]
type Range =
val Min : float32
val Max : float32
new(min, max) = { Min = min; Max = max }
member inline c.Size = c.Max - c.Min
member inline c.IsEmpty = c.Min >= c.Max
member inline c.Contains(x) = x >= c.Min && x < c.Max
member inline c.Expand(margin) = Range(c.Min - margin, c.Max + margin)
member inline c.Clamp(x) = x |> max c.Min |> min c.Max
override v.ToString() = $"%A{v.Min} to %A{v.Max}"
static member Zero = Range(0.0f, 0.0f)
static member ZeroToOne = Range(0.0f, 1.0f)
static member inline Lerp(r : Range, t) = r.Min * (1.0f - t) + r.Max * t
static member inline (+) (a: Range, c) = Range(a.Min + c, a.Max + c)
static member inline (-) (a: Range, c) = Range(a.Min - c, a.Max - c)
static member inline (*) (a: Range, c) = Range(a.Min * c, a.Max * c)
static member inline (/) (a: Range, c) = Range(a.Min / c, a.Max / c)
static member inline Point(point) = Range(point, point)
static member inline Sized(min, size) =
Range(min, min + size)
static member inline Centered(center, size) =
Range.Sized(center - size * 0.5f, size)
static member inline Intersection(a : Range, b : Range) =
Range(max a.Min b.Min, min a.Max b.Max)
static member inline Union(a : Range, b : Range) =
Range(min a.Min b.Min, max a.Max b.Max)
[]
type Range2 =
val Min : Vector2
val Max : Vector2
new(min, max) = { Min = min; Max = max }
new(x : Range, y : Range) = {
Min = Vector2(x.Min, y.Min)
Max = Vector2(x.Max, y.Max)
}
member inline c.X = Range(c.Min.X, c.Max.X)
member inline c.Y = Range(c.Min.Y, c.Max.Y)
member inline c.Center = (c.Min + c.Max) * 0.5f
member inline c.Size = c.Max - c.Min
member inline c.GetArea() =
let s = c.Size
s.X * s.Y
member inline c.Contains(p : Vector2) =
c.X.Contains p.X && c.Y.Contains p.Y
member inline c.Expand(margin : Vector2) =
Range2(
c.X.Expand(margin.X),
c.Y.Expand(margin.Y))
override i.ToString() = $"%A{i.Min} to %A{i.Max}"
static member Zero = Range2(Vector2.Zero, Vector2.Zero)
static member ZeroToOne = Range2(Vector2.Zero, Vector2.One)
static member inline Lerp(r : Range2, t : Vector2) =
Vector2(
Range.Lerp(r.X, t.X),
Range.Lerp(r.Y, t.Y))
static member inline (+) (a: Range2, c : float32) = Range2(a.X + c, a.Y + c)
static member inline (-) (a: Range2, c : float32) = Range2(a.X - c, a.Y - c)
static member inline (*) (a: Range2, c : float32) = Range2(a.X * c, a.Y * c)
static member inline (/) (a: Range2, c : float32) = Range2(a.X / c, a.Y / c)
static member inline (+) (a: Range2, v : Vector2) = Range2(a.X + v.X, a.Y + v.Y)
static member inline (-) (a: Range2, v : Vector2) = Range2(a.X - v.X, a.Y - v.Y)
static member inline (*) (a: Range2, v : Vector2) = Range2(a.X * v.X, a.Y * v.Y)
static member inline (/) (a: Range2, v : Vector2) = Range2(a.X / v.X, a.Y / v.Y)
static member inline Point(point : Vector2) = Range2(point, point)
static member inline Sized(min : Vector2, size : Vector2) =
Range2(min, min + size)
static member inline Centered(center : Vector2, size : Vector2) =
Range2.Sized(center - size * 0.5f, size)
static member inline Intersection(a : Range2, b : Range2) =
Range2(
Range.Intersection(a.X, b.X),
Range.Intersection(a.Y, b.Y))
static member inline Union(a : Range2, b : Range2) =
Range2(
Range.Union(a.X, b.X),
Range.Union(a.Y, b.Y))
[]
type Range3 =
val Min : Vector3
val Max : Vector3
new(min, max) = { Min = min; Max = max }
new(x : Range, y : Range, z : Range) = {
Min = Vector3(x.Min, y.Min, z.Min)
Max = Vector3(x.Max, y.Max, z.Max)
}
member inline c.X = Range(c.Min.X, c.Max.X)
member inline c.Y = Range(c.Min.Y, c.Max.Y)
member inline c.Z = Range(c.Min.Z, c.Max.Z)
member inline c.Size = c.Max - c.Min
member inline c.GetVolume() =
let s = c.Size
s.X * s.Y * s.Z
member inline c.Contains(p : Vector3) =
c.X.Contains p.X &&
c.Y.Contains p.Y &&
c.Z.Contains p.Z
member inline c.Expand(margin : Vector3) =
Range3(
c.X.Expand(margin.X),
c.Y.Expand(margin.Y),
c.Z.Expand(margin.Z))
member inline c.Clamp(p : Vector3) =
Vector3(
c.X.Clamp p.X,
c.Y.Clamp p.Y,
c.Z.Clamp p.Z)
override i.ToString() = $"%A{i.Min} to %A{i.Max}"
static member inline Lerp(r : Range3, t : Vector3) =
Vector3(
Range.Lerp(r.X, t.X),
Range.Lerp(r.Y, t.Y),
Range.Lerp(r.Z, t.Z))
static member Zero = Range3(Vector3.Zero, Vector3.Zero)
static member ZeroToOne = Range3(Vector3.Zero, Vector3.One)
static member inline (+) (a: Range3, c) = Range3(a.X + c, a.Y + c, a.Z + c)
static member inline (-) (a: Range3, c) = Range3(a.X - c, a.Y - c, a.Z - c)
static member inline (*) (a: Range3, c) = Range3(a.X * c, a.Y * c, a.Z * c)
static member inline (/) (a: Range3, c) = Range3(a.X / c, a.Y / c, a.Z / c)
static member inline (+) (a: Range3, v : Vector3) = Range3(a.X + v.X, a.Y + v.Y, a.Z + v.Z)
static member inline (-) (a: Range3, v : Vector3) = Range3(a.X - v.X, a.Y - v.Y, a.Z - v.Z)
static member inline (*) (a: Range3, v : Vector3) = Range3(a.X * v.X, a.Y * v.Y, a.Z * v.Z)
static member inline (/) (a: Range3, v : Vector3) = Range3(a.X / v.X, a.Y / v.Y, a.Z / v.Z)
static member inline Point(point : Vector3) = Range3(point, point)
static member inline Sized(min : Vector3, size : Vector3) =
Range3(min, min + size)
static member inline Centered(center : Vector3, size : Vector3) =
Range3.Sized(center - size * 0.5f, size)
static member inline Intersection(a : Range3, b : Range3) =
Range3(
Range.Intersection(a.X, b.X),
Range.Intersection(a.Y, b.Y),
Range.Intersection(a.Z, b.Z))
static member inline Union(a : Range3, b : Range3) =
Range3(
Range.Union(a.X, b.X),
Range.Union(a.Y, b.Y),
Range.Union(a.Z, b.Z))
// Int32 range types
[]
type Rangei =
val Min : int
val Max : int
new(min, max) = { Min = min; Max = max }
member inline c.Size = c.Max - c.Min
member inline c.IsEmpty = c.Min >= c.Max
member inline c.Contains(x) = x >= c.Min && x < c.Max
member inline c.Expand(margin) = Rangei(c.Min - margin, c.Max + margin)
member inline c.Clamp(x) = x |> max c.Min |> min c.Max
member inline c.ToRange() = Range(float32 c.Min, float32 c.Max)
override v.ToString() = $"%A{v.Min} to %A{v.Max}"
static member Zero = Rangei(0, 0)
static member ZeroToOne = Rangei(0, 1)
static member inline (+) (a: Rangei, c) = Rangei(a.Min + c, a.Max + c)
static member inline (-) (a: Rangei, c) = Rangei(a.Min - c, a.Max - c)
static member inline (*) (a: Rangei, c) = Rangei(a.Min * c, a.Max * c)
static member inline (/) (a: Rangei, c) = Rangei(a.Min / c, a.Max / c)
static member inline Point(point) = Rangei(point, point)
static member inline Sized(min, size) =
Rangei(min, min + size)
static member inline Centered(center, size) =
Rangei.Sized(center - size / 2, size)
static member inline Intersection(a : Rangei, b : Rangei) =
Rangei(max a.Min b.Min, min a.Max b.Max)
static member inline Union(a : Rangei, b : Rangei) =
Rangei(min a.Min b.Min, max a.Max b.Max)
[]
type Range2i =
val Min : Vector2i
val Max : Vector2i
new(min, max) = { Min = min; Max = max }
new(x : Rangei, y : Rangei) = {
Min = Vector2i(x.Min, y.Min)
Max = Vector2i(x.Max, y.Max)
}
member inline c.X = Rangei(c.Min.X, c.Max.X)
member inline c.Y = Rangei(c.Min.Y, c.Max.Y)
member inline c.Size = c.Max - c.Min
member inline c.IsEmpty = c.X.IsEmpty || c.Y.IsEmpty
member inline c.ToRange2() = Range2(c.Min.ToVector2(), c.Max.ToVector2())
member inline c.GetArea() =
let s = c.Size
s.X * s.Y
member inline c.Contains (p : Vector2i) =
c.X.Contains p.X &&
c.Y.Contains p.Y
member inline c.Expand(margin : Vector2i) =
Range2i(
c.X.Expand(margin.X),
c.Y.Expand(margin.Y))
member inline c.Clamp(p : Vector2i) =
Vector2i(c.X.Clamp p.X, c.Y.Clamp p.Y)
override i.ToString() = $"%A{i.Min} to %A{i.Max}"
static member Zero = Range2i(Vector2i.Zero, Vector2i.Zero)
static member ZeroToOne = Range2i(Vector2i.Zero, Vector2i.One)
static member inline (+) (a: Range2i, c) = Range2i(a.X + c, a.Y + c)
static member inline (-) (a: Range2i, c) = Range2i(a.X - c, a.Y - c)
static member inline (*) (a: Range2i, c) = Range2i(a.X * c, a.Y * c)
static member inline (/) (a: Range2i, c) = Range2i(a.X / c, a.Y / c)
static member inline (+) (a: Range2i, v : Vector2i) = Range2i(a.X + v.X, a.Y + v.Y)
static member inline (-) (a: Range2i, v : Vector2i) = Range2i(a.X - v.X, a.Y - v.Y)
static member inline (*) (a: Range2i, v : Vector2i) = Range2i(a.X * v.X, a.Y * v.Y)
static member inline (/) (a: Range2i, v : Vector2i) = Range2i(a.X / v.X, a.Y / v.Y)
static member inline Point(point : Vector2i) = Range2i(point, point)
static member inline Sized(min : Vector2i, size : Vector2i) =
Range2i(min, min + size)
static member inline Centered(center : Vector2i, size : Vector2i) =
Range2i.Sized(center - size / 2, size)
static member inline Intersection(a : Range2i, b : Range2i) =
Range2i(
Rangei.Intersection(a.X, b.X),
Rangei.Intersection(a.Y, b.Y))
static member inline Union(a : Range2i, b : Range2i) =
Range2i(
Rangei.Union(a.X, b.X),
Rangei.Union(a.Y, b.Y))
[]
type Range3i =
val Min : Vector3i
val Max : Vector3i
new(min, max) = { Min = min; Max = max }
new(x : Rangei, y : Rangei, z : Rangei) = {
Min = Vector3i(x.Min, y.Min, z.Min)
Max = Vector3i(x.Max, y.Max, z.Max)
}
member inline c.X = Rangei(c.Min.X, c.Max.X)
member inline c.Y = Rangei(c.Min.Y, c.Max.Y)
member inline c.Z = Rangei(c.Min.Z, c.Max.Z)
member inline c.Size = c.Max - c.Min
member inline c.IsEmpty = c.X.IsEmpty || c.Y.IsEmpty || c.Z.IsEmpty
member inline c.ToRange3() = Range3(c.Min.ToVector3(), c.Max.ToVector3())
member inline c.GetVolume() =
let s = c.Size
s.X * s.Y * s.Z
member inline c.Contains(p : Vector3i) =
c.X.Contains p.X &&
c.Y.Contains p.Y &&
c.Z.Contains p.Z
member inline c.Expand(margin : Vector3i) =
Range3i(
c.X.Expand(margin.X),
c.Y.Expand(margin.Y),
c.Z.Expand(margin.Z))
member inline c.Clamp(p : Vector3i) =
Vector3i(
c.X.Clamp p.X,
c.Y.Clamp p.Y,
c.Z.Clamp p.Z)
override i.ToString() = $"%A{i.Min} to %A{i.Max}"
static member Zero = Range3i(Vector3i.Zero, Vector3i.Zero)
static member ZeroToOne = Range3i(Vector3i.Zero, Vector3i.One)
static member inline (+) (a: Range3i, c) = Range3i(a.X + c, a.Y + c, a.Z + c)
static member inline (-) (a: Range3i, c) = Range3i(a.X - c, a.Y - c, a.Z - c)
static member inline (*) (a: Range3i, c) = Range3i(a.X * c, a.Y * c, a.Z * c)
static member inline (/) (a: Range3i, c) = Range3i(a.X / c, a.Y / c, a.Z / c)
static member inline (+) (a: Range3i, v : Vector3i) = Range3i(a.X + v.X, a.Y + v.Y, a.Z + v.Z)
static member inline (-) (a: Range3i, v : Vector3i) = Range3i(a.X - v.X, a.Y - v.Y, a.Z - v.Z)
static member inline (*) (a: Range3i, v : Vector3i) = Range3i(a.X * v.X, a.Y * v.Y, a.Z * v.Z)
static member inline (/) (a: Range3i, v : Vector3i) = Range3i(a.X / v.X, a.Y / v.Y, a.Z / v.Z)
static member inline Point(point : Vector3i) = Range3i(point, point)
static member inline Sized(min : Vector3i, size : Vector3i) =
Range3i(min, min + size)
static member inline Centered(center : Vector3i, size : Vector3i) =
Range3i.Sized(center - size / 2, size)
static member inline Intersection(a : Range3i, b : Range3i) =
Range3i(
Rangei.Intersection(a.X, b.X),
Rangei.Intersection(a.Y, b.Y),
Rangei.Intersection(a.Z, b.Z))
static member inline Union(a : Range3i, b : Range3i) =
Range3i(
Rangei.Union(a.X, b.X),
Rangei.Union(a.Y, b.Y),
Rangei.Union(a.Z, b.Z))
================================================
FILE: samples/Garnet.Numerics/Vectors.fs
================================================
namespace Garnet.Numerics
open System
open System.Numerics
// Int32 vector types
[]
type Vector2i =
val X : int
val Y : int
new(x, y) = { X = x; Y = y }
member c.ToVector2() = Vector2(float32 c.X, float32 c.Y)
override v.ToString() = $"%A{v.X} %A{v.Y}"
static member Zero = Vector2i(0, 0)
static member One = Vector2i(1, 1)
static member UnitX = Vector2i(1, 0)
static member UnitY = Vector2i(0, 1)
static member inline Dot(a: Vector2i, b: Vector2i) = a.X * b.X + a.Y * b.Y
static member inline (~-) (v: Vector2i) = Vector2i(-v.X, -v.Y)
static member inline (+) (a: Vector2i, c) = Vector2i(a.X + c, a.Y + c)
static member inline (-) (a: Vector2i, c) = Vector2i(a.X - c, a.Y - c)
static member inline (*) (a: Vector2i, c) = Vector2i(a.X * c, a.Y * c)
static member inline (/) (a: Vector2i, c) = Vector2i(a.X / c, a.Y / c)
static member inline (>>>) (a: Vector2i, c) = Vector2i(a.X >>> c, a.Y >>> c)
static member inline (<<<) (a: Vector2i, c) = Vector2i(a.X <<< c, a.Y <<< c)
static member inline (&&&) (a: Vector2i, c) = Vector2i(a.X &&& c, a.Y &&& c)
static member inline (+) (a: Vector2i, b: Vector2i) = Vector2i(a.X + b.X, a.Y + b.Y)
static member inline (-) (a: Vector2i, b: Vector2i) = Vector2i(a.X - b.X, a.Y - b.Y)
static member inline (*) (a: Vector2i, b: Vector2i) = Vector2i(a.X * b.X, a.Y * b.Y)
static member inline (/) (a: Vector2i, b: Vector2i) = Vector2i(a.X / b.X, a.Y / b.Y)
static member inline Perpendicular(v : Vector2i) = Vector2i(-v.Y, v.X)
static member inline FromVector2(v : Vector2) = Vector2i(int v.X, int v.Y)
[]
type Vector3i =
val X : int
val Y : int
val Z : int
new(x, y, z) = { X = x; Y = y; Z = z }
member c.ToVector3() = Vector3(float32 c.X, float32 c.Y, float32 c.Z)
override v.ToString() = $"%A{v.X} %A{v.Y} %A{v.Z}"
static member Zero = Vector3i(0, 0, 0)
static member One = Vector3i(1, 1, 1)
static member UnitX = Vector3i(1, 0, 0)
static member UnitY = Vector3i(0, 1, 0)
static member UnitZ = Vector3i(0, 0, 1)
static member inline Dot(a: Vector3i, b: Vector3i) = a.X * b.X + a.Y * b.Y + a.Z * b.Z
static member inline (~-) (v: Vector3i) = Vector3i(-v.X, -v.Y, -v.Z)
static member inline (+) (a: Vector3i, c) = Vector3i(a.X + c, a.Y + c, a.Z + c)
static member inline (-) (a: Vector3i, c) = Vector3i(a.X - c, a.Y - c, a.Z - c)
static member inline (*) (a: Vector3i, c) = Vector3i(a.X * c, a.Y * c, a.Z * c)
static member inline (/) (a: Vector3i, c) = Vector3i(a.X / c, a.Y / c, a.Z / c)
static member inline (>>>) (a: Vector3i, c) = Vector3i(a.X >>> c, a.Y >>> c, a.Z >>> c)
static member inline (<<<) (a: Vector3i, c) = Vector3i(a.X <<< c, a.Y <<< c, a.Z <<< c)
static member inline (&&&) (a: Vector3i, c) = Vector3i(a.X &&& c, a.Y &&& c, a.Z &&& c)
static member inline (+) (a: Vector3i, b: Vector3i) = Vector3i(a.X + b.X, a.Y + b.Y, a.Z + b.Z)
static member inline (-) (a: Vector3i, b: Vector3i) = Vector3i(a.X - b.X, a.Y - b.Y, a.Z - b.Z)
static member inline (*) (a: Vector3i, b: Vector3i) = Vector3i(a.X * b.X, a.Y * b.Y, a.Z * b.Z)
static member inline (/) (a: Vector3i, b: Vector3i) = Vector3i(a.X / b.X, a.Y / b.Y, a.Z / b.Z)
static member inline FromVector3(v : Vector3) = Vector3i(int v.X, int v.Y, int v.Z)
// Float vector types
[]
module Vector2 =
type Vector2 with
static member FromRadians(a) =
Vector2(cos a, sin a)
static member FromDegrees(a) =
Vector2.FromRadians(a * MathF.PI / 180.0f)
static member inline Rotate(r : Vector2, a : Vector2) =
Vector2(a.X * r.X - a.Y * r.Y, a.X * r.Y + a.Y * r.X)
static member inline Perpendicular(v : Vector2) =
Vector2(-v.Y, v.X)
member v.GetRadians() =
atan2 v.Y v.X
member v.DivideOrZero(c) =
if abs c > 1e-7f then v * (1.0f / c) else Vector2.Zero
member v.NormalizeOrZero() =
v.DivideOrZero(v.Length())
member v.TruncateOrZero(maxLength) =
let lengthSqr = v.LengthSquared()
if lengthSqr <= maxLength * maxLength then v
else v.NormalizeOrZero() * maxLength
member v.GetPerpendicular() =
Vector2(-v.Y, v.X)
member v.Rotate(r : Vector2) =
Vector2(v.X * r.X - v.Y * r.Y, v.X * r.Y + v.Y * r.X)
member v.InverseRotate(r : Vector2) =
Vector2(v.X * r.X + v.Y * r.Y, v.Y * r.X - v.X * r.Y)
/// Rotates towards a target vector
/// maxRotation is a unit-length direction vector relative to X axis
member v.RotateTowards(target : Vector2, maxRotation : Vector2) =
let dot = Vector2.Dot(v, target)
let rotDot = Vector2.Dot(maxRotation, Vector2.UnitX)
if dot >= rotDot then target
else
let cross = Vector3.Cross(Vector3(v, 0.0f), Vector3(target, 0.0f))
if cross.Z > 0.0f then v.Rotate(maxRotation)
else v.InverseRotate(maxRotation)
member v.Round() =
Vector2(floor (v.X + 0.5f), floor (v.Y + 0.5f))
member v.RoundToInt() =
let v = v.Round()
Vector2i(int v.X, int v.Y)
member v.IsInTriangle(p0 : Vector2, p1 : Vector2, p2 : Vector2) =
let a = 0.5f * (-p1.Y * p2.X + p0.Y * (-p1.X + p2.X) + p0.X * (p1.Y - p2.Y) + p1.X * p2.Y)
let sign = if a < 0.0f then -1.0f else 1.0f;
let s = (p0.Y * p2.X - p0.X * p2.Y + (p2.Y - p0.Y) * v.X + (p0.X - p2.X) * v.Y) * sign
let t = (p0.X * p1.Y - p0.Y * p1.X + (p0.Y - p1.Y) * v.X + (p1.X - p0.X) * v.Y) * sign
//s > 0.0f && t > 0.0f && (s + t) < 2.0f * A * sign
s >= 0.0f && t >= 0.0f && (s + t) <= 2.0f * a * sign
[]
module Vector3 =
type Vector3 with
member v.ToVector2() =
Vector2(v.X, v.Y)
================================================
FILE: samples/Garnet.Processor/Args.fs
================================================
namespace Garnet.Processor
open Argu
[]
type PackArgs =
| Input of string
| Output of string
| Compression of int
| Ignore of string list
| Recurse
interface IArgParserTemplate with
member this.Usage =
match this with
| Input _ -> "Input directory to pack"
| Output _ -> "Output packed file"
| Compression _ -> "Compression level, 0 for none"
| Recurse -> "Include subdirectories recursively"
| Ignore _ -> "Ignored file pattern, e.g. *.dat"
and ProcessorArgs =
| [] Pack of ParseResults
interface IArgParserTemplate with
member this.Usage =
match this with
| Pack _ -> "Pack files into a single archive file."
================================================
FILE: samples/Garnet.Processor/Garnet.Processor.fsproj
================================================
Exe
net6.0
en
true
garnet
Asset processor for games.
asset game
================================================
FILE: samples/Garnet.Processor/PackUtility.fs
================================================
namespace Garnet.Processor
open System.Diagnostics
open System.IO
open System.IO.Compression
open System.Security.Cryptography
open System.Text.RegularExpressions
open Argu
module PackUtility =
let getHash file =
if File.Exists(file) then
use fs = File.OpenRead(file)
use sha = SHA1.Create()
sha.ComputeHash(fs)
|> Array.map (fun b -> b.ToString("x2"))
|> String.concat ""
else ""
let getGlobRegex (globs : string list) =
if globs.Length = 0 then Regex("x^")
else
let pattern =
globs
|> List.map (fun str -> "^" + Regex.Escape(str).Replace(@"\*", ".*").Replace(@"\?", ".") + "$")
|> String.concat "|"
Regex(pattern, RegexOptions.IgnoreCase ||| RegexOptions.Singleline)
let run (args : ParseResults) =
let inputPath = Path.GetFullPath(args.GetResult <@ PackArgs.Input @>)
let outputPath = Path.GetFullPath(args.GetResult <@ PackArgs.Output @>)
let recurse = args.Contains <@ Recurse @>
let ignoreRegex = getGlobRegex (args.GetResult(<@ Ignore @>, defaultValue = List.empty))
let level =
let compression = args.GetResult (<@ Compression @>, defaultValue = 0)
if compression = 0 then CompressionLevel.NoCompression else CompressionLevel.Optimal
printfn $"Input path: {inputPath}"
// Optionally insert version into output path
let outputPath =
let key = "{version}"
if outputPath.Contains(key) then
let exeFile = Directory.EnumerateFiles(inputPath, "*.exe") |> Seq.head
let info = FileVersionInfo.GetVersionInfo(exeFile)
outputPath.Replace(key, info.ProductVersion)
else outputPath
printfn $"Output path: {outputPath}"
// Create folder
let outputDir = Path.GetDirectoryName(outputPath)
Directory.CreateDirectory(outputDir) |> ignore
// Write to temp path
let tempPath = outputPath + ".temp"
let _ =
use zip = ZipFile.Open(tempPath, ZipArchiveMode.Create)
let options = EnumerationOptions()
options.RecurseSubdirectories <- recurse
for file in Directory.EnumerateFiles(inputPath, "*.*", options) do
let fullPath = Path.GetFullPath(file)
let name = Path.GetRelativePath(inputPath, fullPath)
if ignoreRegex.IsMatch(name) then
printfn $"Ignoring {name}"
else
printfn $"Adding {name}"
zip.CreateEntryFromFile(fullPath, name, level) |> ignore
// Calc hash of old and new files
let oldHash = getHash outputPath
let newHash = getHash tempPath
printfn $"Old hash: {oldHash}"
printfn $"New hash: {newHash}"
// Overwrite only if hash differs
if newHash = oldHash then
printfn $"Hashes match, skipping write"
File.Delete(tempPath)
else
File.Move(tempPath, outputPath, overwrite = true)
let info = FileInfo(outputPath)
printfn $"{info.Length} bytes written to {outputPath}"
================================================
FILE: samples/Garnet.Processor/Program.fs
================================================
open Argu
open Garnet.Processor
[]
let main argv =
let parser = ArgumentParser.Create()
try
let args = parser.ParseCommandLine(inputs = argv, raiseOnUsage = true)
match args.GetSubCommand() with
| Pack args -> PackUtility.run args
with e -> printfn $"%s{e.Message}"
0
================================================
FILE: samples/Garnet.Samples.Assorted/Extensions.fs
================================================
namespace Garnet.Samples.Assorted
open System
open System.Numerics
open Veldrid
open Garnet.Composition
open Garnet.Graphics
open Garnet.Input
open Garnet.Numerics
[]
module Extensions =
type Container with
member c.AddPixelCoordinateCamera(cameraId) =
c.On <| fun e ->
// Set projection to use pixel coords
let cameras = c.Get()
cameras.[cameraId].ProjectionTransform <- Matrix4x4.CreateOrthographicOffCenter(
0.0f, float32 e.ViewSize.X, float32 e.ViewSize.Y, 0.0f, -1.0f, 1.0f)
member c.AddEscapeToClose() =
c.On <| fun e ->
match e.KeyCode with
| Key.Escape -> c.Get().Close()
| _ -> ()
static member Run(register : Container -> IDisposable) =
let c = Container()
use sys = register c
c.RunLoop()
================================================
FILE: samples/Garnet.Samples.Assorted/Garnet.Samples.Assorted.fsproj
================================================
WinExe
net6.0
en
true
Link
PreserveNewest
PreserveNewest
PreserveNewest
PreserveNewest
PreserveNewest
PreserveNewest
PreserveNewest
PreserveNewest
PreserveNewest
PreserveNewest
PreserveNewest
PreserveNewest
PreserveNewest
================================================
FILE: samples/Garnet.Samples.Assorted/OffscreenDrawing.fs
================================================
module Garnet.Samples.Assorted.OffscreenDrawing
open System
open System.Numerics
open Garnet.Composition
open Garnet.Graphics
open Garnet.Numerics
open Veldrid
module Resources =
let colorTextureShaderSet : ShaderSetDescriptor = {
VertexShader = "shaders/texture-color.vert"
FragmentShader = "shaders/texture-color.frag"
}
let spritePipeline = {
Blend = Blend.Alpha
Filtering = Filtering.Point
ShaderSet = colorTextureShaderSet
Texture = "textures/multicolor-square.png"
}
let spriteLayer = {
LayerId = 0
CameraId = 0
Primitive = Quad
FlushMode = FlushOnDraw
Pipeline = spritePipeline
}
let lightPipeline = {
Blend = Blend.Alpha
Filtering = Filtering.Point
ShaderSet = colorTextureShaderSet
Texture = "textures/hex.png"
}
let lightLayer = {
LayerId = 0
CameraId = 0
Primitive = Quad
FlushMode = FlushOnDraw
Pipeline = lightPipeline
}
type MainScene(ren : SpriteRenderer) =
member c.Renderer = ren
type LightScene(ren : SpriteRenderer) =
member c.Renderer = ren
type Container with
member c.AddSpriteLightingDrawing() =
let device = c.Get()
let shaders = c.Get()
let cache = c.Get()
let lightTarget =
let blend = BlendStateDescription(AttachmentStates = [|
// Multiply destination (main scene) by source (light map)
BlendAttachmentDescription(
BlendEnabled = true,
SourceColorFactor = BlendFactor.Zero,
DestinationColorFactor = BlendFactor.SourceColor,
ColorFunction = BlendFunction.Add,
SourceAlphaFactor = BlendFactor.Zero,
DestinationAlphaFactor = BlendFactor.SourceAlpha,
AlphaFunction = BlendFunction.Add
)
|])
let shaderSet = shaders.GetOrCreate(device, Resources.colorTextureShaderSet.Untyped, cache)
let target = new RenderTarget(device, shaderSet, Filtering.Linear, blend)
target.Background <- RgbaFloat.Clear
target
let mainSprites = new SpriteRenderer(device, shaders, cache)
let lightSprites = new SpriteRenderer(device, shaders, cache)
c.Set(MainScene(mainSprites))
c.Set(LightScene(lightSprites))
Disposable.Create [
mainSprites :> IDisposable
lightSprites :> IDisposable
lightTarget :> IDisposable
c.On <| fun e ->
lightTarget.Width <- e.ViewSize.X
lightTarget.Height <- e.ViewSize.Y
c.On <| fun _ ->
let context = c.Get()
let cameras = c.Get()
// First draw scene normally
mainSprites.Draw(context, cameras)
// Next draw lights to offscreen buffer, then draw to main buffer
// (with multiply blending)
lightTarget.BeginDraw(context)
lightSprites.Draw(context, cameras)
lightTarget.EndDraw(context)
]
let run() =
Container.Run <| fun c ->
c.Set {
WindowSettings.Default with
Background = RgbaFloat.Blue.MultiplyRgb(0.1f)
}
Disposable.Create [
c.AddDefaultSystems()
c.AddPixelCoordinateCamera(0)
c.AddEscapeToClose()
c.AddSpriteLightingDrawing()
c.On <| fun e ->
let rect = Range2.Sized(Vector2.Zero, e.ViewSize.ToVector2())
// Draw sprites
let sprites = c.Get().Renderer
let verts = sprites.GetVertices(Resources.spriteLayer)
verts.DrawQuad(rect, Range2.ZeroToOne, RgbaFloat.White)
// Draw lights
let lights = c.Get().Renderer
let verts = lights.GetVertices(Resources.lightLayer)
verts.DrawQuad(rect, Range2.ZeroToOne, RgbaFloat.White)
]
================================================
FILE: samples/Garnet.Samples.Assorted/Program.fs
================================================
open Garnet.Composition
open Garnet.Samples.Assorted
[]
let main argv =
//SpriteDrawing.run()
//TextDrawing.run()
OffscreenDrawing.run()
0
================================================
FILE: samples/Garnet.Samples.Assorted/SpriteDrawing.fs
================================================
module Garnet.Samples.Assorted.SpriteDrawing
open System
open System.Numerics
open Garnet.Composition
open Garnet.Graphics
open Garnet.Numerics
open Veldrid
module Resources =
let colorTextureShaderSet : ShaderSetDescriptor = {
VertexShader = "shaders/texture-color.vert"
FragmentShader = "shaders/texture-color.frag"
}
let spritePipeline = {
Blend = Blend.Alpha
Filtering = Filtering.Point
ShaderSet = colorTextureShaderSet
Texture = "textures/multicolor-square.png"
}
let spriteLayer = {
LayerId = 0
CameraId = 0
Primitive = Quad
FlushMode = FlushOnDraw
Pipeline = spritePipeline
}
let run() =
Container.Run <| fun c ->
c.Set {
WindowSettings.Default with
Background = RgbaFloat.Blue.MultiplyRgb(0.1f)
}
Disposable.Create [
c.AddDefaultSystems()
c.AddPixelCoordinateCamera(0)
c.AddEscapeToClose()
c.On <| fun _ ->
let sprites = c.Get()
let verts = sprites.GetVertices(Resources.spriteLayer)
let size = Vector2(80.0f, 40.0f)
// Identical quads
verts.DrawQuad(
Range2.Centered(Vector2(50.0f, 50.0f), size),
Range2.ZeroToOne,
RgbaFloat.White)
verts.DrawQuad {
ColorTextureSprite.Default with
Center = Vector2(150.0f, 50.0f)
Size = size
}
// Rotated - Since we're using pixel coords, positive rotation is clockwise
verts.DrawQuad {
ColorTextureSprite.Default with
Center = Vector2(250.0f, 50.0f)
Size = size
Rotation = Vector2.FromDegrees(90.0f)
}
]
================================================
FILE: samples/Garnet.Samples.Assorted/TextDrawing.fs
================================================
module Garnet.Samples.Assorted.TextDrawing
open Veldrid
open Garnet.Composition
open Garnet.Graphics
open Garnet.Numerics
module Resources =
let font = "fonts/pixel-operator-regular-12.font.json"
let fontTexture = "textures/pixel-operator-regular-12.png"
let textureShaderSet : ShaderSetDescriptor = {
VertexShader = "shaders/texture-color.vert"
FragmentShader = "shaders/texture-color.frag"
}
let colorShaderSet : ShaderSetDescriptor = {
VertexShader = "shaders/color.vert"
FragmentShader = "shaders/color.frag"
}
let textPipeline = {
Blend = Blend.Alpha
// Use point filtering since we plan to scale the font and want a pixelated appearance
Filtering = Filtering.Point
ShaderSet = textureShaderSet
Texture = "textures/pixel-operator-regular-12.png"
}
let panelPipeline = {
Blend = Blend.Alpha
Filtering = Filtering.Linear
ShaderSet = colorShaderSet
Texture = ""
}
let textLayer = {
LayerId = 1
CameraId = 0
Primitive = Quad
FlushMode = FlushOnDraw
Pipeline = textPipeline
}
let panelLayer = {
LayerId = 0
CameraId = 0
Primitive = Quad
FlushMode = FlushOnDraw
Pipeline = panelPipeline
}
let run() =
Container.Run <| fun c ->
c.Set {
WindowSettings.Default with Width = 640; Height = 480
}
Disposable.Create [
c.AddDefaultSystems()
c.AddPixelCoordinateCamera(0)
c.AddEscapeToClose()
c.On <| fun e ->
let font = c.LoadJsonFont(Resources.font, Resources.fontTexture)
let sprites = c.Get()
let textVerts = sprites.GetVertices(Resources.textLayer)
let panelVerts = sprites.GetVertices(Resources.panelLayer)
let baseBlock = {
TextBlock.Default with
Scale = 3
Bounds = Range2i.Sized(Vector2i.Zero, e.ViewSize)
}
// Draw text in the corners
textVerts.DrawText(font, {
baseBlock with
Text = "Upper left"
Align = Align.Left
Valign = Valign.Top
})
textVerts.DrawText(font, {
baseBlock with
Text = "Bottom right"
Align = Align.Right
Valign = Valign.Bottom
})
// Draw text in the center within a panel
let block = {
baseBlock with
Text = "Multiple Lines\nCenter"
Align = Align.Center
Valign = Valign.Center
}
let bounds = font.Measure(block).ToRange2()
textVerts.DrawText(font, block)
panelVerts.DrawQuad(bounds, RgbaFloat.Red.MultiplyAlpha(0.5f))
// Test wrapping
let scale = 2
let text = "Title\n\nLine 1\nLine 2 should wrap to next line\nLine 3"
let size = font.Measure(text) * scale
let bounds = Range2i.Sized(Vector2i(0, e.ViewSize.Y - size.Y), Vector2i(size.X - 200, size.Y))
let block = {
baseBlock with
Scale = scale
Bounds = bounds
Text = text
Align = Align.Left
Valign = Valign.Center
Wrapping = TextWrapping.WordWrap
}
textVerts.DrawText(font, block)
panelVerts.DrawQuad(bounds.ToRange2(), RgbaFloat.Red.MultiplyAlpha(0.5f))
]
================================================
FILE: samples/Garnet.Samples.Assorted/assets/fonts/pixel-operator-regular-12.font.json
================================================
{
"family": "Pixel Operator",
"style": "Regular",
"size": 12,
"height": 17,
"chars": [
{
"code": " ",
"width": 4,
"offsetX": 0,
"offsetY": 13,
"rectX": 1,
"rectY": 12,
"rectWidth": 0,
"rectHeight": 0
},
{
"code": "!",
"width": 5,
"offsetX": 2,
"offsetY": 4,
"rectX": 2,
"rectY": 3,
"rectWidth": 1,
"rectHeight": 9
},
{
"code": "\"",
"width": 7,
"offsetX": 2,
"offsetY": 4,
"rectX": 4,
"rectY": 3,
"rectWidth": 3,
"rectHeight": 3
},
{
"code": "#",
"width": 8,
"offsetX": 1,
"offsetY": 4,
"rectX": 8,
"rectY": 3,
"rectWidth": 6,
"rectHeight": 9
},
{
"code": "$",
"width": 7,
"offsetX": 1,
"offsetY": 2,
"rectX": 15,
"rectY": 1,
"rectWidth": 5,
"rectHeight": 13
},
{
"code": "%",
"width": 9,
"offsetX": 1,
"offsetY": 4,
"rectX": 21,
"rectY": 3,
"rectWidth": 7,
"rectHeight": 9
},
{
"code": "&",
"width": 7,
"offsetX": 1,
"offsetY": 4,
"rectX": 29,
"rectY": 3,
"rectWidth": 5,
"rectHeight": 9
},
{
"code": "'",
"width": 5,
"offsetX": 2,
"offsetY": 4,
"rectX": 35,
"rectY": 3,
"rectWidth": 1,
"rectHeight": 3
},
{
"code": "(",
"width": 7,
"offsetX": 3,
"offsetY": 4,
"rectX": 37,
"rectY": 3,
"rectWidth": 3,
"rectHeight": 9
},
{
"code": ")",
"width": 7,
"offsetX": 1,
"offsetY": 4,
"rectX": 41,
"rectY": 3,
"rectWidth": 3,
"rectHeight": 9
},
{
"code": "*",
"width": 7,
"offsetX": 1,
"offsetY": 4,
"rectX": 45,
"rectY": 3,
"rectWidth": 5,
"rectHeight": 5
},
{
"code": "+",
"width": 7,
"offsetX": 1,
"offsetY": 6,
"rectX": 51,
"rectY": 5,
"rectWidth": 5,
"rectHeight": 5
},
{
"code": ",",
"width": 5,
"offsetX": 1,
"offsetY": 12,
"rectX": 57,
"rectY": 11,
"rectWidth": 2,
"rectHeight": 3
},
{
"code": "-",
"width": 6,
"offsetX": 1,
"offsetY": 8,
"rectX": 60,
"rectY": 7,
"rectWidth": 4,
"rectHeight": 1
},
{
"code": ".",
"width": 5,
"offsetX": 2,
"offsetY": 12,
"rectX": 65,
"rectY": 11,
"rectWidth": 1,
"rectHeight": 1
},
{
"code": "/",
"width": 5,
"offsetX": 1,
"offsetY": 4,
"rectX": 67,
"rectY": 3,
"rectWidth": 3,
"rectHeight": 9
},
{
"code": "0",
"width": 7,
"offsetX": 1,
"offsetY": 4,
"rectX": 71,
"rectY": 3,
"rectWidth": 5,
"rectHeight": 9
},
{
"code": "1",
"width": 7,
"offsetX": 2,
"offsetY": 4,
"rectX": 77,
"rectY": 3,
"rectWidth": 3,
"rectHeight": 9
},
{
"code": "2",
"width": 7,
"offsetX": 1,
"offsetY": 4,
"rectX": 81,
"rectY": 3,
"rectWidth": 5,
"rectHeight": 9
},
{
"code": "3",
"width": 7,
"offsetX": 1,
"offsetY": 4,
"rectX": 87,
"rectY": 3,
"rectWidth": 5,
"rectHeight": 9
},
{
"code": "4",
"width": 7,
"offsetX": 1,
"offsetY": 4,
"rectX": 93,
"rectY": 3,
"rectWidth": 5,
"rectHeight": 9
},
{
"code": "5",
"width": 7,
"offsetX": 1,
"offsetY": 4,
"rectX": 99,
"rectY": 3,
"rectWidth": 5,
"rectHeight": 9
},
{
"code": "6",
"width": 7,
"offsetX": 1,
"offsetY": 4,
"rectX": 105,
"rectY": 3,
"rectWidth": 5,
"rectHeight": 9
},
{
"code": "7",
"width": 7,
"offsetX": 1,
"offsetY": 4,
"rectX": 111,
"rectY": 3,
"rectWidth": 5,
"rectHeight": 9
},
{
"code": "8",
"width": 7,
"offsetX": 1,
"offsetY": 4,
"rectX": 117,
"rectY": 3,
"rectWidth": 5,
"rectHeight": 9
},
{
"code": "9",
"width": 7,
"offsetX": 1,
"offsetY": 4,
"rectX": 1,
"rectY": 15,
"rectWidth": 5,
"rectHeight": 9
},
{
"code": ":",
"width": 5,
"offsetX": 2,
"offsetY": 6,
"rectX": 7,
"rectY": 17,
"rectWidth": 1,
"rectHeight": 7
},
{
"code": ";",
"width": 5,
"offsetX": 1,
"offsetY": 6,
"rectX": 9,
"rectY": 17,
"rectWidth": 2,
"rectHeight": 9
},
{
"code": "<",
"width": 5,
"offsetX": 1,
"offsetY": 6,
"rectX": 12,
"rectY": 17,
"rectWidth": 3,
"rectHeight": 5
},
{
"code": "=",
"width": 6,
"offsetX": 1,
"offsetY": 7,
"rectX": 16,
"rectY": 18,
"rectWidth": 4,
"rectHeight": 3
},
{
"code": ">",
"width": 5,
"offsetX": 1,
"offsetY": 6,
"rectX": 21,
"rectY": 17,
"rectWidth": 3,
"rectHeight": 5
},
{
"code": "?",
"width": 7,
"offsetX": 1,
"offsetY": 4,
"rectX": 25,
"rectY": 15,
"rectWidth": 5,
"rectHeight": 9
},
{
"code": "@",
"width": 9,
"offsetX": 1,
"offsetY": 4,
"rectX": 31,
"rectY": 15,
"rectWidth": 7,
"rectHeight": 9
},
{
"code": "A",
"width": 7,
"offsetX": 1,
"offsetY": 4,
"rectX": 39,
"rectY": 15,
"rectWidth": 5,
"rectHeight": 9
},
{
"code": "B",
"width": 7,
"offsetX": 1,
"offsetY": 4,
"rectX": 45,
"rectY": 15,
"rectWidth": 5,
"rectHeight": 9
},
{
"code": "C",
"width": 7,
"offsetX": 1,
"offsetY": 4,
"rectX": 51,
"rectY": 15,
"rectWidth": 5,
"rectHeight": 9
},
{
"code": "D",
"width": 7,
"offsetX": 1,
"offsetY": 4,
"rectX": 57,
"rectY": 15,
"rectWidth": 5,
"rectHeight": 9
},
{
"code": "E",
"width": 7,
"offsetX": 1,
"offsetY": 4,
"rectX": 63,
"rectY": 15,
"rectWidth": 5,
"rectHeight": 9
},
{
"code": "F",
"width": 7,
"offsetX": 1,
"offsetY": 4,
"rectX": 69,
"rectY": 15,
"rectWidth": 5,
"rectHeight": 9
},
{
"code": "G",
"width": 7,
"offsetX": 1,
"offsetY": 4,
"rectX": 75,
"rectY": 15,
"rectWidth": 5,
"rectHeight": 9
},
{
"code": "H",
"width": 7,
"offsetX": 1,
"offsetY": 4,
"rectX": 81,
"rectY": 15,
"rectWidth": 5,
"rectHeight": 9
},
{
"code": "I",
"width": 5,
"offsetX": 2,
"offsetY": 4,
"rectX": 87,
"rectY": 15,
"rectWidth": 1,
"rectHeight": 9
},
{
"code": "J",
"width": 7,
"offsetX": 1,
"offsetY": 4,
"rectX": 89,
"rectY": 15,
"rectWidth": 5,
"rectHeight": 9
},
{
"code": "K",
"width": 7,
"offsetX": 1,
"offsetY": 4,
"rectX": 95,
"rectY": 15,
"rectWidth": 5,
"rectHeight": 9
},
{
"code": "L",
"width": 7,
"offsetX": 1,
"offsetY": 4,
"rectX": 101,
"rectY": 15,
"rectWidth": 5,
"rectHeight": 9
},
{
"code": "M",
"width": 9,
"offsetX": 1,
"offsetY": 4,
"rectX": 107,
"rectY": 15,
"rectWidth": 7,
"rectHeight": 9
},
{
"code": "N",
"width": 7,
"offsetX": 1,
"offsetY": 4,
"rectX": 115,
"rectY": 15,
"rectWidth": 5,
"rectHeight": 9
},
{
"code": "O",
"width": 7,
"offsetX": 1,
"offsetY": 4,
"rectX": 121,
"rectY": 15,
"rectWidth": 5,
"rectHeight": 9
},
{
"code": "P",
"width": 7,
"offsetX": 1,
"offsetY": 4,
"rectX": 1,
"rectY": 27,
"rectWidth": 5,
"rectHeight": 9
},
{
"code": "Q",
"width": 7,
"offsetX": 1,
"offsetY": 4,
"rectX": 7,
"rectY": 27,
"rectWidth": 5,
"rectHeight": 9
},
{
"code": "R",
"width": 7,
"offsetX": 1,
"offsetY": 4,
"rectX": 13,
"rectY": 27,
"rectWidth": 5,
"rectHeight": 9
},
{
"code": "S",
"width": 7,
"offsetX": 1,
"offsetY": 4,
"rectX": 19,
"rectY": 27,
"rectWidth": 5,
"rectHeight": 9
},
{
"code": "T",
"width": 7,
"offsetX": 1,
"offsetY": 4,
"rectX": 25,
"rectY": 27,
"rectWidth": 5,
"rectHeight": 9
},
{
"code": "U",
"width": 7,
"offsetX": 1,
"offsetY": 4,
"rectX": 31,
"rectY": 27,
"rectWidth": 5,
"rectHeight": 9
},
{
"code": "V",
"width": 7,
"offsetX": 1,
"offsetY": 4,
"rectX": 37,
"rectY": 27,
"rectWidth": 5,
"rectHeight": 9
},
{
"code": "W",
"width": 9,
"offsetX": 1,
"offsetY": 4,
"rectX": 43,
"rectY": 27,
"rectWidth": 7,
"rectHeight": 9
},
{
"code": "X",
"width": 7,
"offsetX": 1,
"offsetY": 4,
"rectX": 51,
"rectY": 27,
"rectWidth": 5,
"rectHeight": 9
},
{
"code": "Y",
"width": 7,
"offsetX": 1,
"offsetY": 4,
"rectX": 57,
"rectY": 27,
"rectWidth": 5,
"rectHeight": 9
},
{
"code": "Z",
"width": 7,
"offsetX": 1,
"offsetY": 4,
"rectX": 63,
"rectY": 27,
"rectWidth": 5,
"rectHeight": 9
},
{
"code": "[",
"width": 7,
"offsetX": 3,
"offsetY": 4,
"rectX": 69,
"rectY": 27,
"rectWidth": 3,
"rectHeight": 9
},
{
"code": "\\",
"width": 5,
"offsetX": 1,
"offsetY": 4,
"rectX": 73,
"rectY": 27,
"rectWidth": 3,
"rectHeight": 9
},
{
"code": "]",
"width": 7,
"offsetX": 1,
"offsetY": 4,
"rectX": 77,
"rectY": 27,
"rectWidth": 3,
"rectHeight": 9
},
{
"code": "^",
"width": 7,
"offsetX": 1,
"offsetY": 4,
"rectX": 81,
"rectY": 27,
"rectWidth": 5,
"rectHeight": 3
},
{
"code": "_",
"width": 5,
"offsetX": 0,
"offsetY": 14,
"rectX": 87,
"rectY": 37,
"rectWidth": 5,
"rectHeight": 1
},
{
"code": "`",
"width": 5,
"offsetX": 1,
"offsetY": 4,
"rectX": 93,
"rectY": 27,
"rectWidth": 2,
"rectHeight": 2
},
{
"code": "a",
"width": 7,
"offsetX": 1,
"offsetY": 6,
"rectX": 96,
"rectY": 29,
"rectWidth": 5,
"rectHeight": 7
},
{
"code": "b",
"width": 7,
"offsetX": 1,
"offsetY": 4,
"rectX": 102,
"rectY": 27,
"rectWidth": 5,
"rectHeight": 9
},
{
"code": "c",
"width": 7,
"offsetX": 1,
"offsetY": 6,
"rectX": 108,
"rectY": 29,
"rectWidth": 5,
"rectHeight": 7
},
{
"code": "d",
"width": 7,
"offsetX": 1,
"offsetY": 4,
"rectX": 114,
"rectY": 27,
"rectWidth": 5,
"rectHeight": 9
},
{
"code": "e",
"width": 7,
"offsetX": 1,
"offsetY": 6,
"rectX": 120,
"rectY": 29,
"rectWidth": 5,
"rectHeight": 7
},
{
"code": "f",
"width": 6,
"offsetX": 1,
"offsetY": 4,
"rectX": 1,
"rectY": 39,
"rectWidth": 4,
"rectHeight": 9
},
{
"code": "g",
"width": 7,
"offsetX": 1,
"offsetY": 6,
"rectX": 6,
"rectY": 41,
"rectWidth": 5,
"rectHeight": 9
},
{
"code": "h",
"width": 7,
"offsetX": 1,
"offsetY": 4,
"rectX": 12,
"rectY": 39,
"rectWidth": 5,
"rectHeight": 9
},
{
"code": "i",
"width": 5,
"offsetX": 2,
"offsetY": 4,
"rectX": 18,
"rectY": 39,
"rectWidth": 1,
"rectHeight": 9
},
{
"code": "j",
"width": 7,
"offsetX": 1,
"offsetY": 4,
"rectX": 20,
"rectY": 39,
"rectWidth": 5,
"rectHeight": 11
},
{
"code": "k",
"width": 7,
"offsetX": 1,
"offsetY": 4,
"rectX": 26,
"rectY": 39,
"rectWidth": 5,
"rectHeight": 9
},
{
"code": "l",
"width": 5,
"offsetX": 2,
"offsetY": 4,
"rectX": 32,
"rectY": 39,
"rectWidth": 2,
"rectHeight": 9
},
{
"code": "m",
"width": 9,
"offsetX": 1,
"offsetY": 6,
"rectX": 35,
"rectY": 41,
"rectWidth": 7,
"rectHeight": 7
},
{
"code": "n",
"width": 7,
"offsetX": 1,
"offsetY": 6,
"rectX": 43,
"rectY": 41,
"rectWidth": 5,
"rectHeight": 7
},
{
"code": "o",
"width": 7,
"offsetX": 1,
"offsetY": 6,
"rectX": 49,
"rectY": 41,
"rectWidth": 5,
"rectHeight": 7
},
{
"code": "p",
"width": 7,
"offsetX": 1,
"offsetY": 6,
"rectX": 55,
"rectY": 41,
"rectWidth": 5,
"rectHeight": 9
},
{
"code": "q",
"width": 7,
"offsetX": 1,
"offsetY": 6,
"rectX": 61,
"rectY": 41,
"rectWidth": 5,
"rectHeight": 9
},
{
"code": "r",
"width": 7,
"offsetX": 1,
"offsetY": 6,
"rectX": 67,
"rectY": 41,
"rectWidth": 5,
"rectHeight": 7
},
{
"code": "s",
"width": 7,
"offsetX": 1,
"offsetY": 6,
"rectX": 73,
"rectY": 41,
"rectWidth": 5,
"rectHeight": 7
},
{
"code": "t",
"width": 6,
"offsetX": 1,
"offsetY": 5,
"rectX": 79,
"rectY": 40,
"rectWidth": 4,
"rectHeight": 8
},
{
"code": "u",
"width": 7,
"offsetX": 1,
"offsetY": 6,
"rectX": 84,
"rectY": 41,
"rectWidth": 5,
"rectHeight": 7
},
{
"code": "v",
"width": 7,
"offsetX": 1,
"offsetY": 6,
"rectX": 90,
"rectY": 41,
"rectWidth": 5,
"rectHeight": 7
},
{
"code": "w",
"width": 9,
"offsetX": 1,
"offsetY": 6,
"rectX": 96,
"rectY": 41,
"rectWidth": 7,
"rectHeight": 7
},
{
"code": "x",
"width": 7,
"offsetX": 1,
"offsetY": 6,
"rectX": 104,
"rectY": 41,
"rectWidth": 5,
"rectHeight": 7
},
{
"code": "y",
"width": 7,
"offsetX": 1,
"offsetY": 6,
"rectX": 110,
"rectY": 41,
"rectWidth": 5,
"rectHeight": 9
},
{
"code": "z",
"width": 7,
"offsetX": 1,
"offsetY": 6,
"rectX": 116,
"rectY": 41,
"rectWidth": 5,
"rectHeight": 7
},
{
"code": "{",
"width": 7,
"offsetX": 2,
"offsetY": 4,
"rectX": 122,
"rectY": 39,
"rectWidth": 4,
"rectHeight": 9
},
{
"code": "|",
"width": 5,
"offsetX": 2,
"offsetY": 4,
"rectX": 1,
"rectY": 51,
"rectWidth": 1,
"rectHeight": 9
},
{
"code": "}",
"width": 7,
"offsetX": 1,
"offsetY": 4,
"rectX": 3,
"rectY": 51,
"rectWidth": 4,
"rectHeight": 9
},
{
"code": "~",
"width": 8,
"offsetX": 1,
"offsetY": 4,
"rectX": 8,
"rectY": 51,
"rectWidth": 6,
"rectHeight": 2
}
]
}
================================================
FILE: samples/Garnet.Samples.Assorted/assets/shaders/color.frag
================================================
#version 450
layout(location = 0) in vec4 fsin_color;
layout(location = 0) out vec4 fsout_color;
void main()
{
fsout_color = fsin_color;
}
================================================
FILE: samples/Garnet.Samples.Assorted/assets/shaders/color.frag.hlsl.bytes
================================================
static float4 _9;
static float4 _11;
struct SPIRV_Cross_Input
{
float4 _11 : TEXCOORD0;
};
struct SPIRV_Cross_Output
{
float4 _9 : SV_Target0;
};
void frag_main()
{
_9 = _11;
}
SPIRV_Cross_Output main(SPIRV_Cross_Input stage_input)
{
_11 = stage_input._11;
frag_main();
SPIRV_Cross_Output stage_output;
stage_output._9 = _9;
return stage_output;
}
================================================
FILE: samples/Garnet.Samples.Assorted/assets/shaders/color.vert
================================================
#version 450
layout(set = 0, binding = 0) uniform ProjectionBuffer
{
mat4 Projection;
};
layout(set = 0, binding = 1) uniform ViewBuffer
{
mat4 View;
};
layout(set = 1, binding = 0) uniform WorldBuffer
{
mat4 World;
};
layout(location = 0) in vec3 Position;
layout(location = 1) in vec4 Color;
layout(location = 0) out vec4 fsin_Color;
void main()
{
vec4 worldPosition = World * vec4(Position, 1);
vec4 viewPosition = View * worldPosition;
vec4 clipPosition = Projection * viewPosition;
gl_Position = clipPosition;
fsin_Color = Color;
}
================================================
FILE: samples/Garnet.Samples.Assorted/assets/shaders/color.vert.hlsl.bytes
================================================
cbuffer _11_13 : register(b2)
{
row_major float4x4 _13_m0 : packoffset(c0);
};
cbuffer _30_32 : register(b1)
{
row_major float4x4 _32_m0 : packoffset(c0);
};
cbuffer _38_40 : register(b0)
{
row_major float4x4 _40_m0 : packoffset(c0);
};
static float4 gl_Position;
static float3 _21;
static float4 _54;
static float4 _56;
struct SPIRV_Cross_Input
{
float3 _21 : TEXCOORD0;
float4 _56 : TEXCOORD1;
};
struct SPIRV_Cross_Output
{
float4 _54 : TEXCOORD0;
float4 gl_Position : SV_Position;
};
void vert_main()
{
gl_Position = mul(mul(mul(float4(_21, 1.0f), _13_m0), _32_m0), _40_m0);
_54 = _56;
}
SPIRV_Cross_Output main(SPIRV_Cross_Input stage_input)
{
_21 = stage_input._21;
_56 = stage_input._56;
vert_main();
SPIRV_Cross_Output stage_output;
stage_output.gl_Position = gl_Position;
stage_output._54 = _54;
return stage_output;
}
================================================
FILE: samples/Garnet.Samples.Assorted/assets/shaders/texture-color.frag
================================================
#version 450
layout(location = 0) in vec2 fsin_texCoords;
layout(location = 1) in vec4 fsin_color;
layout(location = 0) out vec4 fsout_color;
layout(set = 1, binding = 1) uniform texture2D SurfaceTexture;
layout(set = 1, binding = 2) uniform sampler SurfaceSampler;
void main()
{
vec4 texColor = texture(sampler2D(SurfaceTexture, SurfaceSampler), fsin_texCoords);
fsout_color = texColor * fsin_color;
}
================================================
FILE: samples/Garnet.Samples.Assorted/assets/shaders/texture-color.frag.hlsl.bytes
================================================
Texture2D _12 : register(t0);
SamplerState _16 : register(s0);
static float2 _22;
static float4 _26;
static float4 _29;
struct SPIRV_Cross_Input
{
float2 _22 : TEXCOORD0;
float4 _29 : TEXCOORD1;
};
struct SPIRV_Cross_Output
{
float4 _26 : SV_Target0;
};
void frag_main()
{
_26 = _12.Sample(_16, _22) * _29;
}
SPIRV_Cross_Output main(SPIRV_Cross_Input stage_input)
{
_22 = stage_input._22;
_29 = stage_input._29;
frag_main();
SPIRV_Cross_Output stage_output;
stage_output._26 = _26;
return stage_output;
}
================================================
FILE: samples/Garnet.Samples.Assorted/assets/shaders/texture-color.vert
================================================
#version 450
layout(set = 0, binding = 0) uniform ProjectionBuffer
{
mat4 Projection;
};
layout(set = 0, binding = 1) uniform ViewBuffer
{
mat4 View;
};
layout(set = 1, binding = 0) uniform WorldBuffer
{
mat4 World;
};
layout(location = 0) in vec3 Position;
layout(location = 1) in vec2 TexCoords;
layout(location = 2) in vec4 Color;
layout(location = 0) out vec2 fsin_texCoords;
layout(location = 1) out vec4 fsin_Color;
void main()
{
vec4 worldPosition = World * vec4(Position, 1);
vec4 viewPosition = View * worldPosition;
vec4 clipPosition = Projection * viewPosition;
gl_Position = clipPosition;
fsin_texCoords = TexCoords;
fsin_Color = Color;
}
================================================
FILE: samples/Garnet.Samples.Assorted/assets/shaders/texture-color.vert.hlsl.bytes
================================================
cbuffer _11_13 : register(b2)
{
row_major float4x4 _13_m0 : packoffset(c0);
};
cbuffer _30_32 : register(b1)
{
row_major float4x4 _32_m0 : packoffset(c0);
};
cbuffer _38_40 : register(b0)
{
row_major float4x4 _40_m0 : packoffset(c0);
};
static float4 gl_Position;
static float3 _21;
static float2 _56;
static float2 _58;
static float4 _60;
static float4 _62;
struct SPIRV_Cross_Input
{
float3 _21 : TEXCOORD0;
float2 _58 : TEXCOORD1;
float4 _62 : TEXCOORD2;
};
struct SPIRV_Cross_Output
{
float2 _56 : TEXCOORD0;
float4 _60 : TEXCOORD1;
float4 gl_Position : SV_Position;
};
void vert_main()
{
gl_Position = mul(mul(mul(float4(_21, 1.0f), _13_m0), _32_m0), _40_m0);
_56 = _58;
_60 = _62;
}
SPIRV_Cross_Output main(SPIRV_Cross_Input stage_input)
{
_21 = stage_input._21;
_58 = stage_input._58;
_62 = stage_input._62;
vert_main();
SPIRV_Cross_Output stage_output;
stage_output.gl_Position = gl_Position;
stage_output._56 = _56;
stage_output._60 = _60;
return stage_output;
}
================================================
FILE: samples/Garnet.Samples.CSharp/Garnet.Samples.CSharp.csproj
================================================
WinExe
net6.0
en
true
Link
================================================
FILE: samples/Garnet.Samples.CSharp/Program.cs
================================================
using Garnet.Composition;
var c = new Container();
using (c.AddDefaultSystems())
{
c.RunLoop();
}
================================================
FILE: samples/Garnet.Samples.Flocking/Debug.fs
================================================
namespace Garnet.Samples.Flocking
open System
open System.Numerics
open System.Diagnostics
open ImGuiNET
open Garnet.Composition
type FpsHud() =
let fps = FpsGauge(1.0f)
let fixedFps = FpsGauge(1.0f)
member _.OnFixedUpdate() =
let timestamp = Stopwatch.GetTimestamp()
fixedFps.Update(timestamp)
member _.OnUpdate() =
let timestamp = Stopwatch.GetTimestamp()
fps.Update(timestamp)
member _.Draw() =
let flags =
ImGuiWindowFlags.NoBackground |||
ImGuiWindowFlags.NoTitleBar |||
ImGuiWindowFlags.NoResize |||
ImGuiWindowFlags.NoMove |||
ImGuiWindowFlags.NoFocusOnAppearing |||
ImGuiWindowFlags.NoInputs |||
ImGuiWindowFlags.NoNavFocus
ImGui.SetNextWindowSize(Vector2(500.0f, 500.0f))
ImGui.SetNextWindowPos(Vector2(0.0f, 0.0f))
if ImGui.Begin("Hud", flags) then
let info = GC.GetGCMemoryInfo()
ImGui.SetWindowFontScale(1.0f)
ImGui.Text $"FPS: %d{int fps.FramesPerSec}, mean: %d{int fps.MeanFrameMs} ms, max: %d{int fps.MaxFrameMs} ms, fixed FPS: %d{int fixedFps.FramesPerSec}"
ImGui.Text $"GC pause: {info.PauseTimePercentage}%%%%, heap size: {info.HeapSizeBytes / 1024L} Kb"
ImGui.End()
module DebugSystem =
let add (c : Container) =
let hud = c.Get()
Disposable.Create [
c.On <| fun _ ->
hud.OnFixedUpdate()
c.On <| fun _ ->
hud.OnUpdate()
c.On <| fun _ ->
hud.Draw()
]
================================================
FILE: samples/Garnet.Samples.Flocking/Drawing.fs
================================================
namespace Garnet.Samples.Flocking
open System
open System.Numerics
open Garnet.Composition
open Garnet.Numerics
open Garnet.Graphics
module DrawingSystems =
type Container with
member c.AddCameraUpdates() =
c.On <| fun e ->
// Update transforms so origin is in the center of the screen and we use pixel coords
// with +Y as up.
let displayScale = 1.0f
let size = e.ViewSize.ToVector2() / displayScale
let camera = c.Get().[0]
camera.ProjectionTransform <- Matrix4x4.CreateOrthographic(size.X, size.Y, -100.0f, 100.0f)
member c.AddVehicleSprites() =
c.On <| fun _ ->
let atlas = c.LoadResource(Resources.atlas)
let layers = c.Get()
let texBounds = atlas.[Resources.triangleTexture].NormalizedBounds
let mesh = layers.GetVertices(Resources.vehicleLayer)
for r in c.Query() do
mesh.DrawQuad {
Center = r.Value2.Pos
Size = 0.1f * Vector2(1.0f, 1.0f) * 140.0f
Rotation = r.Value4.Direction
TexBounds = texBounds
Color = Faction.toColor r.Value3
}
member c.AddTrailSprites() =
c.On <| fun _ ->
let atlas = c.LoadResource(Resources.atlas)
let layers = c.Get()
let texBounds = atlas.[Resources.hexTexture].NormalizedBounds
let mesh = layers.GetVertices(Resources.trailLayer)
for r in c.Query() do
mesh.DrawQuad {
Center = r.Value2.Pos
Size = r.Value4.Lifespan * 0.3f * Vector2.One * 60.0f
Rotation = Vector2.FromRadians(r.Value5.Radians)
TexBounds = texBounds
Color = (Faction.toColor r.Value3).MultiplyAlpha(r.Value4.Lifespan * 0.3f)
}
let add (c : Container) =
Disposable.Create [
c.AddCameraUpdates()
c.AddVehicleSprites()
c.AddTrailSprites()
]
================================================
FILE: samples/Garnet.Samples.Flocking/Functions.fs
================================================
namespace Garnet.Samples.Flocking
open System.Collections.Generic
open System.Numerics
open Veldrid
open Garnet.Numerics
module WorldSettings =
let defaults = {
Seed = 1
VehicleCount = 100
SpawnRange = 300.0f
MaxVehicleSpeed = 50.0f
TrailLifespan = 0.6f
Steering = {
ForwardWeight = 20.0f
CohesionWeight = 3.0f
TetherWeight = 1.0f
SeparationWeight = 3.0f
AlignmentWeight = 1.0f
MaxAlignmentDistance = 100.0f
MaxSeparationDistance = 70.0f
MaxCohesionDistance = 400.0f
MaxTetherDistance = 300.0f
}
}
module Scalar =
let tolerance = 1e-9f
let clamp (s0 : float32) (s1 : float32) (s : float32) =
s |> max s0 |> min s1
let linearStep s0 s1 s =
let length = s1 - s0
if abs length < tolerance then 0.0f
else clamp 0.0f 1.0f ((s - s0) / length)
let smoothStep s0 s1 s =
let x = linearStep s0 s1 s
x * x * (3.0f - 2.0f * x)
module Heading =
let getVelocity vehicle =
vehicle.Speed * vehicle.Direction
let fromVelocity (newVelocity : Vector2) =
let newSpeed = newVelocity.Length()
{
Speed = newSpeed
Direction = newVelocity.DivideOrZero(newSpeed)
}
let getNextPosition (deltaTime : float32) vehicle pos =
let velocity = getVelocity vehicle
let delta = deltaTime * velocity
pos + delta
module Steering =
let getForward current =
current.SteerDir
let getTether maxDistance current =
let tetherPoint = Vector2.Zero
let toTether = tetherPoint - current.SteerPos
let distance = toTether.Length()
let scale = Scalar.smoothStep 0.0f maxDistance distance
toTether.DivideOrZero(distance) * scale
let getCohesion minDistance maxDistance (neighbors : List) =
let mutable sum = Vector2.Zero
for neighbor in neighbors do
let weight = Scalar.smoothStep minDistance maxDistance neighbor.Distance
sum <- sum + (neighbor.TeamWeight * weight) * neighbor.DirectionToNeighbor
sum.NormalizeOrZero()
let getSeparation maxDistance (neighbors : List<_>) =
let mutable sum = Vector2.Zero
for neighbor in neighbors do
let weight = Scalar.smoothStep maxDistance 0.0f neighbor.Distance
sum <- sum + -weight * neighbor.DirectionToNeighbor
sum
let getAlignment maxDistance (neighbors : List<_>) current =
let mutable sum = Vector2.Zero
for neighbor in neighbors do
let weight = Scalar.smoothStep maxDistance 0.0f neighbor.Distance
sum <- sum + -(neighbor.TeamWeight * weight) * current.SteerDir
sum.NormalizeOrZero()
let getSteeringDirection s neighbors current =
let sum =
getForward current * s.ForwardWeight +
getTether s.MaxTetherDistance current * s.TetherWeight +
getCohesion s.MaxSeparationDistance s.MaxCohesionDistance neighbors * s.CohesionWeight +
getSeparation s.MaxSeparationDistance neighbors * s.SeparationWeight +
getAlignment s.MaxAlignmentDistance neighbors current * s.AlignmentWeight
sum.NormalizeOrZero()
module Faction =
let all = [|
Red
Orange
Yellow
Green
Cyan
Blue
Purple
|]
let toColor = function
| Red -> RgbaFloat(1.0f, 0.0f, 0.2f, 1.0f)
| Orange -> RgbaFloat(1.0f, 0.4f, 0.0f, 1.0f)
| Yellow -> RgbaFloat(0.6f, 1.0f, 0.0f, 1.0f)
| Green -> RgbaFloat(0.0f, 1.0f, 0.1f, 1.0f)
| Cyan -> RgbaFloat(0.0f, 0.8f, 0.6f, 1.0f)
| Blue -> RgbaFloat(0.0f, 0.4f, 1.0f, 1.0f)
| Purple -> RgbaFloat(0.6f, 0.0f, 1.0f, 1.0f)
================================================
FILE: samples/Garnet.Samples.Flocking/Garnet.Samples.Flocking.fsproj
================================================
WinExe
net6.0
en
true
Link
PreserveNewest
PreserveNewest
PreserveNewest
PreserveNewest
PreserveNewest
PreserveNewest
================================================
FILE: samples/Garnet.Samples.Flocking/Program.fs
================================================
open Garnet.Composition
open Garnet.Samples.Flocking
[]
let main _ =
let c = Container()
use s = c.AddSystems [
StartupSystem.add
SimulationSystems.add
DrawingSystems.add
DebugSystem.add
]
c.RunLoop()
0
================================================
FILE: samples/Garnet.Samples.Flocking/Resources.fs
================================================
namespace Garnet.Samples.Flocking
open Garnet.Graphics
module Resources =
let shaderSet : ShaderSetDescriptor = {
VertexShader = "texture-color.vert"
FragmentShader = "texture-color.frag"
}
let atlas = "textures"
let hexTexture = "hex.png"
let triangleTexture = "triangle.png"
let pipeline = {
Blend = Blend.Alpha
Filtering = Filtering.Linear
ShaderSet = shaderSet
Texture = atlas
}
let vehicleLayer = {
LayerId = 2
CameraId = 0
Primitive = Quad
FlushMode = FlushOnDraw
Pipeline = pipeline
}
let trailLayer = {
LayerId = 1
CameraId = 0
Primitive = Quad
FlushMode = FlushOnDraw
Pipeline = pipeline
}
================================================
FILE: samples/Garnet.Samples.Flocking/Simulation.fs
================================================
namespace Garnet.Samples.Flocking
open System
open System.Collections.Generic
open System.Numerics
open Garnet.Numerics
open Garnet.Composition
// Not we're using Update instead of FixedUpdate here, mainly because this demo
// is intended to measure performance. Normally it would be preferable to use
// FixedUpdate for simulation logic.
module SimulationSystems =
type Container with
member c.AddSpawning() =
c.On <| fun _ ->
let settings = c.Get()
let rand = Random(settings.Seed)
let nextCoord() = float32 (rand.NextDouble() - 0.5) * settings.SpawnRange
for i = 1 to settings.VehicleCount do
c.Create()
.With(Faction.all.[rand.Next Faction.all.Length])
.With({ MaxSpeed = settings.MaxVehicleSpeed; Radius = 1.0f })
.With({ Pos = Vector2(nextCoord(), nextCoord()) })
.With({ Direction = Vector2(0.0f, 1.0f); Speed = 0.0f })
.Add(TrailEmitter())
member c.AddSteering() =
let neighbors = List()
c.On <| fun _ ->
let settings = c.Get().Steering
for r in c.Query() do
let h = &r.Value3
let current = {
Eid = r.Value1
Pos = r.Value2.Pos
Dir = h.Direction
Faction = r.Value4
}
// For simplicity and testing performance, we're iterating over all vehicles
// rather than using any spatial partitioning.
for r in c.Query() do
if r.Value1 <> current.Eid then
let offset = r.Value4.Pos - current.Pos
let distance = offset.Length()
neighbors.Add {
Direction = r.Value2.Direction
TeamWeight = if current.Faction = r.Value3 then 1.0f else 0.0f
DirectionToNeighbor = offset.DivideOrZero(distance)
Distance = distance
}
let current = {
SteerPos = current.Pos
SteerDir = current.Dir
}
let dir = Steering.getSteeringDirection settings neighbors current
let velocity = dir * r.Value5.MaxSpeed
neighbors.Clear()
h <- Heading.fromVelocity velocity
member c.AddLifespan() =
c.On <| fun e ->
let dt = float32 e.DeltaTime / 1000.0f
for r in c.Query() do
let ls = r.Value1
let newLifespan = { Lifespan = ls.Lifespan - dt }
if ls.Lifespan <= 0.0f then
let eid = r.Value2
c.Destroy(eid)
r.Value1 <- newLifespan
member c.AddUpdatePosition() =
c.On <| fun e ->
let dt = float32 e.DeltaTime / 1000.0f
for r in c.Query() do
r.Value1 <- { Pos = Heading.getNextPosition dt r.Value2 r.Value1.Pos }
member c.AddUpdateRotation() =
c.On <| fun e ->
let dt = float32 e.DeltaTime / 1000.0f
for r in c.Query() do
r.Value1 <- { Radians = r.Value1.Radians + dt * r.Value2.RotationSpeed }
member c.AddTrailEmission() =
c.On <| fun _ ->
for r in c.Query() do
c.Create()
.With(r.Value3)
.With(r.Value2)
.With({ Radians = r.Value4.Direction.GetRadians() })
.With({ Lifespan = 0.6f })
.Add(Trail())
let add (c : Container) =
Disposable.Create [
c.AddSpawning()
c.AddLifespan()
c.AddUpdatePosition()
c.AddUpdateRotation()
c.AddTrailEmission()
c.AddSteering()
]
================================================
FILE: samples/Garnet.Samples.Flocking/Startup.fs
================================================
namespace Garnet.Samples.Flocking
open System
open Veldrid
open Garnet.Composition
open Garnet.Graphics
module StartupSystem =
type Container with
member c.LoadResources() =
// Manually load textures into atlas. Note other resources like
// shaders can be loaded on demand and don't need to be explicitly
// loaded here.
let device = c.Get()
let cache = c.Get()
use fs = new FileFolder("assets")
fs.LoadTextureAtlasFromFolder(device, Resources.atlas, 512, 512, cache)
Disposable.Null
let add (c : Container) =
// Set global window settings, which will be used by default systems
c.Set {
WindowSettings.Default with
Title = "Flocking"
Width = 800
Height = 600
Background = RgbaFloat(0.0f, 0.1f, 0.2f, 1.0f)
}
// Set global settings used by simulation
c.Set(WorldSettings.defaults)
Disposable.Create [
// Default systems for window, sprite drawing, updates, etc
c.AddDefaultSystems()
c.LoadResources()
]
================================================
FILE: samples/Garnet.Samples.Flocking/Types.fs
================================================
namespace Garnet.Samples.Flocking
open System.Numerics
open Garnet.Composition
[]
module Components =
[]
type Faction =
| Red
| Orange
| Yellow
| Green
| Cyan
| Blue
| Purple
[]
type Position = {
Pos : Vector2
}
[]
type Heading = {
Direction : Vector2
Speed : float32
}
[]
type Vehicle = {
Radius : float32
MaxSpeed : float32
}
[]
type TrailLifespan = {
TrailLifespan : float32
}
[]
type Lifespan = {
Lifespan : float32
}
[]
type AngularVelocity = {
RotationSpeed : float32
}
[]
type Rotation = {
Radians : float32
}
type TrailEmitter = struct end
type Trail = struct end
[]
module Settings =
type SteeringSettings = {
ForwardWeight : float32
CohesionWeight : float32
TetherWeight : float32
SeparationWeight : float32
AlignmentWeight : float32
MaxAlignmentDistance : float32
MaxSeparationDistance : float32
MaxCohesionDistance : float32
MaxTetherDistance : float32
}
type WorldSettings = {
Seed : int
SpawnRange : float32
VehicleCount : int
MaxVehicleSpeed : float32
TrailLifespan : float32
Steering : SteeringSettings
}
[]
module SteeringTypes =
[]
type Steerer = {
SteerPos : Vector2
SteerDir : Vector2
}
[]
type Neighbor = {
Direction : Vector2
DirectionToNeighbor : Vector2
Distance : float32
TeamWeight : float32
}
[]
type CurrentVehicle = {
Eid : Eid
Pos : Vector2
Dir : Vector2
Faction : Faction
}
[]
module Events =
type Reset = struct end
================================================
FILE: samples/Garnet.Samples.Flocking/assets/texture-color.frag
================================================
#version 450
layout(location = 0) in vec2 fsin_texCoords;
layout(location = 1) in vec4 fsin_color;
layout(location = 0) out vec4 fsout_color;
layout(set = 1, binding = 1) uniform texture2D SurfaceTexture;
layout(set = 1, binding = 2) uniform sampler SurfaceSampler;
void main()
{
vec4 texColor = texture(sampler2D(SurfaceTexture, SurfaceSampler), fsin_texCoords);
fsout_color = texColor * fsin_color;
}
================================================
FILE: samples/Garnet.Samples.Flocking/assets/texture-color.frag.hlsl.bytes
================================================
Texture2D _12 : register(t0);
SamplerState _16 : register(s0);
static float2 _22;
static float4 _26;
static float4 _29;
struct SPIRV_Cross_Input
{
float2 _22 : TEXCOORD0;
float4 _29 : TEXCOORD1;
};
struct SPIRV_Cross_Output
{
float4 _26 : SV_Target0;
};
void frag_main()
{
_26 = _12.Sample(_16, _22) * _29;
}
SPIRV_Cross_Output main(SPIRV_Cross_Input stage_input)
{
_22 = stage_input._22;
_29 = stage_input._29;
frag_main();
SPIRV_Cross_Output stage_output;
stage_output._26 = _26;
return stage_output;
}
================================================
FILE: samples/Garnet.Samples.Flocking/assets/texture-color.vert
================================================
#version 450
layout(set = 0, binding = 0) uniform ProjectionBuffer
{
mat4 Projection;
};
layout(set = 0, binding = 1) uniform ViewBuffer
{
mat4 View;
};
layout(set = 1, binding = 0) uniform WorldBuffer
{
mat4 World;
};
layout(location = 0) in vec3 Position;
layout(location = 1) in vec2 TexCoords;
layout(location = 2) in vec4 Color;
layout(location = 0) out vec2 fsin_texCoords;
layout(location = 1) out vec4 fsin_Color;
void main()
{
vec4 worldPosition = World * vec4(Position, 1);
vec4 viewPosition = View * worldPosition;
vec4 clipPosition = Projection * viewPosition;
gl_Position = clipPosition;
fsin_texCoords = TexCoords;
fsin_Color = Color;
}
================================================
FILE: samples/Garnet.Samples.Flocking/assets/texture-color.vert.hlsl.bytes
================================================
cbuffer _11_13 : register(b2)
{
row_major float4x4 _13_m0 : packoffset(c0);
};
cbuffer _30_32 : register(b1)
{
row_major float4x4 _32_m0 : packoffset(c0);
};
cbuffer _38_40 : register(b0)
{
row_major float4x4 _40_m0 : packoffset(c0);
};
static float4 gl_Position;
static float3 _21;
static float2 _56;
static float2 _58;
static float4 _60;
static float4 _62;
struct SPIRV_Cross_Input
{
float3 _21 : TEXCOORD0;
float2 _58 : TEXCOORD1;
float4 _62 : TEXCOORD2;
};
struct SPIRV_Cross_Output
{
float2 _56 : TEXCOORD0;
float4 _60 : TEXCOORD1;
float4 gl_Position : SV_Position;
};
void vert_main()
{
gl_Position = mul(mul(mul(float4(_21, 1.0f), _13_m0), _32_m0), _40_m0);
_56 = _58;
_60 = _62;
}
SPIRV_Cross_Output main(SPIRV_Cross_Input stage_input)
{
_21 = stage_input._21;
_58 = stage_input._58;
_62 = stage_input._62;
vert_main();
SPIRV_Cross_Output stage_output;
stage_output.gl_Position = gl_Position;
stage_output._56 = _56;
stage_output._60 = _60;
return stage_output;
}
================================================
FILE: samples/Garnet.Samples.Roguelike/ConsoleTest.fsx
================================================
#load "Types.fs"
#load "Functions.fs"
open Garnet.Samples.Roguelike.Types
open Garnet.Samples.Roguelike
let test() =
let world = World.generate 10 1
World.format world |> printfn "%s"
World.getDistanceMap world [ Vector.zero ] |> DistanceMap.format |> printfn "%s"
[ Move West; Move West ]
|> Seq.fold Loop.stepWorld world
|> World.format
|> printfn "%s"
let testInteractive() =
World.generate 10 1 |> Loop.run
================================================
FILE: samples/Garnet.Samples.Roguelike/Drawing.fs
================================================
namespace Garnet.Samples.Roguelike
open System.Buffers
open System.Runtime.CompilerServices
open Veldrid
open Garnet.Graphics
[]
type DisplayTile = {
ch : char
fg : RgbaFloat
bg : RgbaFloat
}
module DisplayTile =
let fromTile tile =
match tile.Entity with
| Some entity ->
match entity.EntityType with
| Rogue -> {
ch = '@'
fg = RgbaFloat(0.3f, 1.0f, 1.0f, 1.0f)
bg = RgbaFloat(0.3f, 1.0f, 1.0f, 0.3f)
}
| Minion -> {
ch = 'm'
fg = RgbaFloat(1.0f, 0.3f, 0.3f, 1.0f)
bg = RgbaFloat(1.0f, 0.3f, 0.3f, 0.3f)
}
| None ->
match tile.Terrain with
| Floor -> {
ch = '.'
fg = RgbaFloat(0.3f, 0.4f, 0.5f, 0.5f)
bg = RgbaFloat(0.3f, 0.4f, 0.5f, 0.1f)
}
| Wall -> {
ch = '#'
fg = RgbaFloat(0.3f, 0.4f, 0.5f, 1.0f)
bg = RgbaFloat(0.3f, 0.4f, 0.5f, 0.3f)
}
[]
type ViewExtensions =
[]
static member DrawWorld(w : IBufferWriter, world : World) =
let span = w.GetTileSpan(world.Tiles.Count)
let min = World.getMinLocation world
let mutable i = 0
for kvp in world.Tiles do
let p = Vector.subtract kvp.Key min
let tile = DisplayTile.fromTile kvp.Value
span.Slice(i * 4).DrawTile(p.X, p.Y, tile.ch, tile.fg, tile.bg)
i <- i + 1
w.Advance(span.Length)
================================================
FILE: samples/Garnet.Samples.Roguelike/Functions.fs
================================================
namespace Garnet.Samples.Roguelike
open System
open System.Collections.Generic
module Vector =
let init x y = { X = x; Y = y }
let zero = init 0 0
let one = init 1 1
let min a b = { X = min a.X b.X; Y = min a.Y b.Y }
let max a b = { X = max a.X b.X; Y = max a.Y b.Y }
let add a b = {
X = a.X + b.X
Y = a.Y + b.Y
}
let subtract a b = {
X = a.X - b.X
Y = a.Y - b.Y
}
module Bounds =
let init min max = { Min = min; Max = max }
let sized min size = init min (Vector.add min size)
let zero = init Vector.zero Vector.zero
let zeroToOne = init Vector.zero Vector.one
let maxToMin = {
Min = { X = Int32.MaxValue; Y = Int32.MaxValue }
Max = { X = Int32.MinValue; Y = Int32.MinValue }
}
let including bounds p = {
Min = Vector.min bounds.Min p
Max = Vector.max bounds.Max p
}
let union a b = {
Min = Vector.min a.Min b.Min
Max = Vector.max a.Max b.Max
}
let getSize b =
Vector.subtract b.Max b.Min
let getCenter b =
let v = Vector.add b.Max b.Min
{ X = v.X / 2; Y = v.Y / 2 }
let getCentered contentSize b =
let size = getSize b
{
X = b.Min.X + (size.X - contentSize.X) / 2
Y = b.Min.Y + (size.Y - contentSize.Y) / 2
}
let expand margin b = {
Min = Vector.subtract b.Min margin.Min
Max = Vector.add b.Max margin.Max
}
let includingAll locs =
locs
|> Seq.fold including maxToMin
|> expand zeroToOne
module Direction =
let all = [|
East
North
West
South
|]
let getNext loc dir =
match dir with
| East -> { loc with X = loc.X + 1 }
| West -> { loc with X = loc.X - 1 }
| North -> { loc with Y = loc.Y - 1 }
| South -> { loc with Y = loc.Y + 1 }
module DistanceMap =
let empty = {
Distances = Map.empty
}
let create isPassable (tiles : Map) seeds =
let result = Dictionary()
let queue = Queue()
let enqueue p dist =
if not (result.ContainsKey(p)) then
let canVisit =
match tiles.TryGetValue(p) with
| false, _ -> false
| true, tile -> isPassable tile
result.Add(p, if canVisit then dist else Int32.MaxValue)
if canVisit then queue.Enqueue(struct(p, dist))
for seed in seeds do
enqueue seed 0
while queue.Count > 0 do
let struct(p, dist) = queue.Dequeue()
let nextDist = dist + 1
for dir in Direction.all do
let next = Direction.getNext p dir
enqueue next nextDist
{
Distances =
result
|> Seq.map (fun kvp -> kvp.Key, kvp.Value)
|> Map.ofSeq
}
let getDistance p map =
match map.Distances.TryGetValue(p) with
| true, dist -> dist
| false, _ -> Int32.MaxValue
let distanceToChar x =
if x = 0 then '.'
elif x < 10 then '0' + char x
elif x < 36 then 'a' + char (x - 10)
elif x < 62 then 'A' + char (x - 36)
elif x = Int32.MaxValue then '#'
else '+'
let format map =
let b = map.Distances |> Seq.map (fun kvp -> kvp.Key) |> Bounds.includingAll
let size = Bounds.getSize b
let dw = size.X + 1
let data = Array.create (dw * size.Y) ' '
for y = 0 to size.Y - 1 do
data.[y * dw + dw - 1] <- '\n'
for kvp in map.Distances do
let p = Vector.subtract kvp.Key b.Min
data.[p.Y * dw + p.X] <- distanceToChar kvp.Value
String(data)
module Tile =
let getChar tile =
match tile.Entity with
| Some e ->
match e.EntityType with
| Rogue -> '@'
| Minion -> 'm'
| None ->
match tile.Terrain with
| Floor -> '.'
| Wall -> '#'
let getMoveEvents loc nextLoc dir tile = seq {
match tile.Entity with
| Some entity ->
if entity.Hits = 1 then yield Destroyed nextLoc
else
yield Attacked {
AttackerLoc = loc
AttackDir = dir
Damage = 1
}
| None -> yield Moved {
SourceLoc = loc
MoveDir = dir
}
}
let addEntity entity tile =
{ tile with Entity = Some entity }
let removeEntity tile =
{ tile with Entity = None }
let isPassable tile =
match tile.Terrain with
| Wall -> false
| Floor -> true
module Entity =
let rogue = {
EntityType = Rogue
Hits = 3
}
let minion = {
EntityType = Minion
Hits = 1
}
let applyDamage damage entity =
{ entity with Hits = entity.Hits - damage }
module Animation =
let format =
function
| Moving e -> $"Moved {e.MoveDir}"
| Attacking e -> $"{e.AttackerEntityType} attacked {e.TargetEntityType}"
| Destroying e -> $"{e.DestroyedEntityType} destroyed"
module World =
let empty = {
Turn = 0
RandomSeed = 0UL
Tiles = Map.empty
Animations = List.empty
}
let generate mapRadius seed =
let r = mapRadius + 1
let extent = r * 2 + 1
let count = extent * extent
let rand = Random(seed)
// draw random walls with border
let cells1 = Array.zeroCreate count
for y = -r to r do
for x = -r to r do
let i = (y + r) * extent + (x + r)
let dist = max (abs x) (abs y)
let cell = dist = r || (dist > 2 && rand.Next(10) = 0)
cells1.[i] <- cell
// apply morphological dilate
let cells2 = Array.zeroCreate count
let rm = r - 1
for y = -rm to rm do
for x = -rm to rm do
let i = (y + r) * extent + (x + r)
let cell =
if cells1.[i] then true
else
let ix0 = i - 1
let ix1 = i + 1
let iy0 = i - extent
let iy1 = i + extent
cells1.[ix0] || cells1.[ix1] || cells1.[iy0] || cells1.[iy1]
cells2.[i] <- cell
// populate tiles
let tiles = seq {
for y = -rm to rm do
for x = -rm to rm do
let i = (y + r) * extent + (x + r)
let terrain = if cells2.[i] then Wall else Floor
let p = Vector.init x y
yield p, {
Terrain = terrain
Entity =
match terrain with
| Wall -> None
| Floor ->
if p = Vector.zero then Some Entity.rogue
elif rand.Next(8) = 0 then Some Entity.minion
else None
}
}
{ empty with
RandomSeed = uint64 seed
Tiles = Map.ofSeq tiles
}
let getMinLocation world =
world.Tiles |> Seq.map (fun kvp -> kvp.Key) |> Seq.reduce Vector.min
let formatTiles world =
let b = world.Tiles |> Seq.map (fun kvp -> kvp.Key) |> Bounds.includingAll
let size = Bounds.getSize b
let dw = size.X + 1
let data = Array.create (dw * size.Y) ' '
for y = 0 to size.Y - 1 do
data.[y * dw + dw - 1] <- '\n'
for kvp in world.Tiles do
let p = Vector.subtract kvp.Key b.Min
data.[p.Y * dw + p.X] <- Tile.getChar kvp.Value
String(data)
let formatAnimations world =
world.Animations
|> List.rev
|> Seq.map Animation.format
|> String.concat "\n"
let format world =
$"Turn {world.Turn}:\n{formatAnimations world}\n{formatTiles world}"
let getEntityLocations entityType world = seq {
for kvp in world.Tiles do
match kvp.Value.Entity with
| Some entity -> if entity.EntityType = entityType then yield kvp.Key
| None -> ()
}
let isOccupied loc world =
match Map.tryFind loc world.Tiles with
| Some tile -> tile.Terrain = Wall || tile.Entity.IsSome
| None -> true
let tryGetEntity loc world =
Map.tryFind loc world.Tiles
|> Option.bind (fun tile -> tile.Entity)
let mapTile map loc world =
match Map.tryFind loc world.Tiles with
| Some tile -> { world with Tiles = Map.add loc (map tile) world.Tiles }
| None -> world
let mapEntity map loc world =
mapTile (fun tile -> { tile with Entity = Option.map map tile.Entity }) loc world
let addEntity loc entity world =
mapTile (Tile.addEntity entity) loc world
let removeEntity loc world =
mapTile Tile.removeEntity loc world
let moveEntity loc newLoc world =
match tryGetEntity loc world with
| Some entity ->
world
|> removeEntity loc
|> addEntity newLoc entity
| None -> world
let appendAnimation anim world = {
world with Animations = anim :: world.Animations
}
let find entityType world =
world.Tiles
|> Map.tryPick (fun loc tile ->
tile.Entity
|> Option.bind (fun e ->
if e.EntityType = entityType then Some (loc, e) else None))
let getDistanceMap map targets =
DistanceMap.create Tile.isPassable map.Tiles targets
let stepTurn world =
{ world with Turn = world.Turn + 1 }
module Action =
let getEvents action loc world =
match action with
| Move dir ->
let nextLoc = Direction.getNext loc dir
match Map.tryFind nextLoc world.Tiles with
| Some tile -> Tile.getMoveEvents loc nextLoc dir tile
| None -> Seq.empty
let getPlayerEvents action world =
match World.find Rogue world with
| Some (loc, _) -> getEvents action loc world
| None -> Seq.empty
module Event =
let applyEvent world event =
match event with
| Attacked e ->
match World.tryGetEntity e.AttackerLoc world with
| None -> world
| Some attacker ->
let targetLoc = Direction.getNext e.AttackerLoc e.AttackDir
match World.tryGetEntity targetLoc world with
| None -> world
| Some target ->
world
|> World.mapEntity (Entity.applyDamage e.Damage) targetLoc
|> World.appendAnimation (Attacking {
AttackerLoc = e.AttackerLoc
AttackerEntityType = attacker.EntityType
AttackDir = e.AttackDir
Damage = e.Damage
TargetEntityType = target.EntityType
})
| Moved e ->
let targetLoc = Direction.getNext e.SourceLoc e.MoveDir
world
|> World.moveEntity e.SourceLoc targetLoc
|> World.appendAnimation (Moving {
SourceLoc = e.SourceLoc
MoveDir = e.MoveDir
})
| Destroyed p ->
match World.tryGetEntity p world with
| None -> world
| Some target ->
world
|> World.removeEntity p
|> World.appendAnimation (Destroying {
DestroyedLoc = p
DestroyedEntityType = target.EntityType
})
module Loop =
let tryGetAction key =
match key with
| ConsoleKey.RightArrow -> Move East |> Some
| ConsoleKey.LeftArrow -> Move West |> Some
| ConsoleKey.UpArrow -> Move North |> Some
| ConsoleKey.DownArrow -> Move South |> Some
| _ -> None
let readPlayerActions() = seq {
let mutable isRunning = true
while isRunning do
let key = Console.ReadKey().Key
match tryGetAction key with
| Some action -> yield action
| None -> isRunning <- key <> ConsoleKey.Escape
}
let printWorld world =
world |> World.format |> printfn "%s"
let applyPlayerEvents action world =
Action.getPlayerEvents action world
|> Seq.fold Event.applyEvent world
let getHostileMoveEvents p dm world =
let dirs =
Direction.all
|> Seq.filter (fun dir ->
let next = Direction.getNext p dir
not (World.isOccupied next world))
|> Seq.toArray
if dirs.Length = 0 then world
else
let nearestDir =
dirs
|> Seq.minBy (fun dir ->
let next = Direction.getNext p dir
DistanceMap.getDistance next dm)
Moved {
SourceLoc = p
MoveDir = nearestDir
}
|> Event.applyEvent world
let applyHostileEvents world =
let targetLocs = World.getEntityLocations Rogue world
let dm = World.getDistanceMap world targetLocs
World.getEntityLocations Minion world
|> Seq.sortBy (fun p -> DistanceMap.getDistance p dm)
|> Seq.fold (fun state p -> getHostileMoveEvents p dm state) world
let stepWorld world action =
{ world with Animations = List.empty }
|> applyPlayerEvents action
|> applyHostileEvents
|> World.stepTurn
let run world =
readPlayerActions()
|> Seq.scan stepWorld world
|> Seq.iter printWorld
================================================
FILE: samples/Garnet.Samples.Roguelike/Game.fs
================================================
namespace Garnet.Samples.Roguelike
open System
open System.Numerics
open System.Threading
open Veldrid
open Garnet.Numerics
open Garnet.Composition
open Garnet.Graphics
open Garnet.Input
module Resources =
let tileTexture = "drake-10x10-transparent.png"
let shaderSet : ShaderSetDescriptor = {
VertexShader = "texture-dual-color.vert"
FragmentShader = "texture-dual-color.frag"
}
// Use point sampling for pixelated appearance
let pipeline = {
Blend = Blend.Alpha
Filtering = Filtering.Point
ShaderSet = shaderSet
Texture = tileTexture
}
// Avoid auto flush since we only update when an action occurs
let tileLayer = {
LayerId = 0
CameraId = 0
Primitive = Quad
FlushMode = NoFlush
Pipeline = pipeline
}
module Command =
let getCommand =
function
| Key.R -> Command.Reset
| Key.Right -> Command.MoveEast
| Key.Up -> Command.MoveNorth
| Key.Left -> Command.MoveWest
| Key.Down -> Command.MoveSouth
| Key.F11 -> Command.FullScreen
| _ -> Command.None
let tryGetAction =
function
| Command.MoveEast -> Move East |> Some
| Command.MoveNorth -> Move North |> Some
| Command.MoveWest -> Move West |> Some
| Command.MoveSouth -> Move South |> Some
| _ -> None
[]
module DrawingExtensions =
type SpriteRenderer with
member c.DrawWorld(world) =
let tiles = c.GetVertices(Resources.tileLayer)
tiles.DrawWorld(world)
tiles.Flush()
type Game(fs : IReadOnlyFolder) =
// Image is a tilemap of ASCII chars (16x16=256 tiles)
let image = fs.LoadImage(Resources.tileTexture)
// Calculate window size to match map and tile size
let tileScale = 2
let tileWidth = image.Width / 16
let tileHeight = image.Height / 16
let mapRadius = 15
let mapExtent = mapRadius * 2 + 1
// Create window and graphics device
let ren =
new WindowRenderer {
WindowSettings.Default with
Title = "Roguelike"
Width = mapExtent * tileWidth * tileScale
Height = mapExtent * tileHeight * tileScale
Redraw = Redraw.Manual
Background = RgbaFloat.Black
}
// Initialize rendering
let shaders = new ShaderSetCache()
let cache = new ResourceCache()
let sprites = new SpriteRenderer(ren.Device, shaders, cache)
do
cache.AddShaderLoaders(ren.Device)
fs.LoadShadersFromFolder(".", ren.Device.BackendType, cache)
cache.AddResource(Resources.tileTexture, ren.Device.CreateTexture(image))
member c.Run() =
// Set transforms so drawing code can use tile coords for source (16x16 tileset)
// and destination (80x25 display tiles)
let cameras = CameraSet()
let texTileSize = 1.0f / 16.0f
let camera = cameras.[0]
camera.WorldTransform <- Matrix4x4.CreateScale(float32 tileWidth, float32 tileHeight, 1.0f)
camera.TextureTransform <- Matrix4x4.CreateScale(texTileSize, texTileSize, 1.0f)
// Start loop
let inputs = InputCollection()
let mutable state = World.generate mapRadius 1
sprites.DrawWorld(state)
while ren.Update(0.0f, inputs) do
for e in inputs.KeyDownEvents do
match Command.getCommand e.KeyCode with
| Command.FullScreen -> ren.ToggleFullScreen()
| Command.None -> ()
| command ->
match Command.tryGetAction command with
| None -> ()
| Some action ->
state <- Loop.stepWorld state action
sprites.DrawWorld(state)
// Note we only invalidate when something changes instead of every frame
ren.Invalidate()
if ren.BeginDraw() then
// Update transforms according to window size so we can draw using pixel coords
// with origin in upper left of view
let displayScale = float32 tileScale
let size = ren.Size.ToVector2() / displayScale
camera.ProjectionTransform <- Matrix4x4.CreateOrthographic(size.X, -size.Y, -100.0f, 100.0f)
camera.ViewTransform <- Matrix4x4.CreateTranslation(-size.X * 0.5f, -size.Y * 0.5f, 0.0f)
sprites.Draw(ren.RenderContext, cameras)
ren.EndDraw()
// Sleep to avoid spinning CPU
Thread.Sleep(1)
interface IDisposable with
member c.Dispose() =
cache.Dispose()
shaders.Dispose()
sprites.Dispose()
ren.Dispose()
static member Run(fs) =
use game = new Game(fs)
game.Run()
================================================
FILE: samples/Garnet.Samples.Roguelike/Garnet.Samples.Roguelike.fsproj
================================================
WinExe
net6.0
en
true
Link
PreserveNewest
PreserveNewest
PreserveNewest
================================================
FILE: samples/Garnet.Samples.Roguelike/Program.fs
================================================
open Garnet.Composition
open Garnet.Samples.Roguelike
[]
let main argv =
use fs = new FileFolder("assets")
Game.Run(fs)
0
================================================
FILE: samples/Garnet.Samples.Roguelike/Types.fs
================================================
namespace Garnet.Samples.Roguelike
[]
module WorldTypes =
type Vector = {
X : int
Y : int
}
type Bounds = {
Min : Vector
Max : Vector
}
type Direction =
| East
| West
| North
| South
type DistanceMap = {
Distances : Map
}
type Terrain =
| Floor
| Wall
type EntityType =
| Rogue
| Minion
type Entity = {
EntityType : EntityType
Hits : int
}
type Tile = {
Terrain : Terrain
Entity : Entity option
}
type Action =
| Move of Direction
type MovedEvent = {
SourceLoc : Vector
MoveDir : Direction
}
type AttackedEvent = {
AttackerLoc : Vector
AttackDir : Direction
Damage : int
}
/// Events hold the minimal information needed to reconstruct world state
type Event =
| Moved of MovedEvent
| Attacked of AttackedEvent
| Destroyed of Vector
type MovingEvent = {
SourceLoc : Vector
MoveDir : Direction
}
type AttackingAnimation = {
AttackerLoc : Vector
AttackerEntityType : EntityType
AttackDir : Direction
Damage : int
TargetEntityType : EntityType
}
type DestroyingAnimation = {
DestroyedLoc : Vector
DestroyedEntityType : EntityType
}
/// Animations can enriched with extra info to help present events that occurred
/// during a turn to the player
type Animation =
| Moving of MovingEvent
| Attacking of AttackingAnimation
| Destroying of DestroyingAnimation
type World = {
Turn : int
RandomSeed : uint64
Tiles : Map
Animations : Animation list
}
[]
type Command =
| None = 0
| MoveEast = 1
| MoveNorth = 2
| MoveWest = 3
| MoveSouth = 4
| Reset = 5
| FullScreen = 6
================================================
FILE: samples/Garnet.Samples.Roguelike/assets/texture-dual-color.frag
================================================
#version 450
layout(location = 0) in vec2 fsin_texCoords;
layout(location = 1) in vec4 fsin_fg;
layout(location = 2) in vec4 fsin_bg;
layout(location = 0) out vec4 fsout_color;
layout(set = 1, binding = 2) uniform texture2D SurfaceTexture;
layout(set = 1, binding = 3) uniform sampler SurfaceSampler;
void main()
{
vec4 texColor = texture(sampler2D(SurfaceTexture, SurfaceSampler), fsin_texCoords);
vec4 fg = fsin_fg;
fg.rgb *= texColor.rgb;
fsout_color = mix(fsin_bg, fg, texColor.a);
}
================================================
FILE: samples/Garnet.Samples.Roguelike/assets/texture-dual-color.vert
================================================
#version 450
layout(set = 0, binding = 0) uniform ProjectionBuffer
{
mat4 Projection;
};
layout(set = 0, binding = 1) uniform ViewBuffer
{
mat4 View;
};
layout(set = 1, binding = 0) uniform WorldBuffer
{
mat4 World;
};
layout(set = 1, binding = 1) uniform TexTransformBuffer
{
mat4 TexTransform;
};
layout(location = 0) in vec3 Position;
layout(location = 1) in vec2 TexCoords;
layout(location = 2) in vec4 FgColor;
layout(location = 3) in vec4 BgColor;
layout(location = 0) out vec2 fsin_texCoords;
layout(location = 1) out vec4 fsin_fg;
layout(location = 2) out vec4 fsin_bg;
void main()
{
vec4 worldPosition = World * vec4(Position, 1);
vec4 viewPosition = View * worldPosition;
vec4 clipPosition = Projection * viewPosition;
gl_Position = clipPosition;
fsin_texCoords = (TexTransform * vec4(TexCoords, 1, 1)).xy;
fsin_fg = FgColor;
fsin_bg = BgColor;
}
================================================
FILE: samples/Garnet.Samples.Trixel/Drawing.fs
================================================
namespace Garnet.Samples.Trixel
open System
open System.Buffers
open System.Runtime.CompilerServices
open System.Numerics
open Veldrid
open Garnet.Numerics
open Garnet.Graphics
[]
type VertexSpanExtensions =
[]
static member DrawLine(span : Span,
p0 : Vector2,
p1 : Vector2,
thickness : float32,
color : RgbaFloat) =
let delta = p1 - p0
let length = delta.Length()
let dir = if length < 1e-5f then Vector2.Zero else delta / length
span.DrawQuad {
Center = (p0 + p1) * 0.5f
Size = Vector2(thickness, length)
Rotation = dir.GetPerpendicular()
TexBounds = Range2.ZeroToOne
Color = color
}
[]
static member DrawAxialLine(span : Span,
p0 : Vector2i,
p1 : Vector2i,
thickness,
color : RgbaFloat) =
let ep0 = TriCoords.vertexToEuc p0
let ep1 = TriCoords.vertexToEuc p1
span.DrawLine(ep0, ep1, thickness, color)
[]
type VertexBufferWriterExtensions =
[]
static member DrawGridLines(w : IBufferWriter, spacing, thickness, color, r) =
let extent = r / spacing
let count = extent * 2 + 1
let span = w.GetQuadSpan(count * 3)
for i = -extent to extent do
let p = i * spacing
let di = (i + extent) * 3 * 4
let p0 = Vector2i(-r, p)
let p1 = Vector2i(r, p)
span.Slice(di + 0).DrawAxialLine(p0, p1, thickness, color)
let p0 = Vector2i(p, -r)
let p1 = Vector2i(p, r)
span.Slice(di + 4).DrawAxialLine(p0, p1, thickness, color)
let p0 = Vector2i(-r, p + r)
let p1 = Vector2i(r, p - r)
span.Slice(di + 8).DrawAxialLine(p0, p1, thickness, color)
w.Advance(span.Length)
[]
static member DrawGridLines(w : IBufferWriter,
majorSpacing,
majorThickness,
majorColor,
minorThickness,
minorColor,
r) =
w.DrawGridLines(majorSpacing, majorThickness, majorColor, r)
w.DrawGridLines(1, minorThickness, minorColor, r)
[]
static member DrawGridLines(w : IBufferWriter) =
let minorColor = RgbaFloat(0.8f, 0.6f, 0.1f, 0.2f)
let majorColor = RgbaFloat(0.8f, 0.6f, 0.1f, 0.3f)
let majorSpacing = 6
let majorThickness = 0.1f
let minorThickness = 0.05f
w.DrawGridLines(majorSpacing, majorThickness, majorColor, minorThickness, minorColor, 100)
[]
static member DrawGridCells(w : IBufferWriter, state) =
let cellMargin = 0.1f
let vertexCount = state.Cells.Count * 3
let span = w.GetSpan(vertexCount)
let mutable i = 0
for kvp in state.Cells do
let p = Vector2i(kvp.Key.X, kvp.Key.Y)
let tri = TriPositions.fromTriCell p
let centroid = (tri.P0 + tri.P1 + tri.P2) / 3.0f
let p0 = Vector2.Lerp(tri.P0, centroid, cellMargin)
let p1 = Vector2.Lerp(tri.P1, centroid, cellMargin)
let p2 = Vector2.Lerp(tri.P2, centroid, cellMargin)
let color = kvp.Value.ToRgbaFloat()
let verts = span.Slice(i * 3)
verts.[0] <- {
Position = Vector3(p0.X, p0.Y, 0.0f)
TexCoord = Vector2(0.0f, 0.0f)
Color = color
}
verts.[1] <- {
Position = Vector3(p1.X, p1.Y, 0.0f)
TexCoord = Vector2(1.0f, 0.0f)
Color = color
}
verts.[2] <- {
Position = Vector3(p2.X, p2.Y, 0.0f)
TexCoord = Vector2(0.0f, 1.0f)
Color = color
}
i <- i + 1
w.Advance(vertexCount)
================================================
FILE: samples/Garnet.Samples.Trixel/Functions.fs
================================================
namespace Garnet.Samples.Trixel
open System
open System.Numerics
open Newtonsoft.Json
open Garnet.Numerics
open Veldrid
module CellLocation =
let origin = { X = 0; Y = 0 }
module TriCoords =
/// Height of an equilateral triangle with edge length one. Multiplier to determine simplex
/// height given an edge length (sqrt(3/4) = 0.866025403784f).
let edgeToHeight = 0.866025403784f
/// Edge length of an equilateral triangle with height one. Multiplier to determine simplex
/// edge length given a height (sqrt(4/3) = 1.154700538379f).
let heightToEdge = 1.154700538379f
let inline eucToVertexf (v : Vector2) =
let y = -heightToEdge * v.Y
let x = v.X - 0.5f * y
Vector2(x, y)
/// Rhombus/vertex to tri cell
let inline vertexToTri (p : Vector2i) side =
Vector2i(p.X * 2 + side, p.Y)
/// Gets location of cell in tri coords that contains rhombus point.
let vertexToContainingTriCell (p : Vector2) =
let bx = floor p.X
let by = floor p.Y
let fx = p.X - bx
let fy = p.Y - by
let side = if fy < 1.0f - fx then 0 else 1
vertexToTri (Vector2i(int bx, int by)) side
let eucToContainingTriCell =
eucToVertexf >> vertexToContainingTriCell
let inline vertexToEucf (v : Vector2) =
Vector2(v.X + v.Y * 0.5f, v.Y * -edgeToHeight)
let inline vertexToEuc (v : Vector2i) =
v.ToVector2() |> vertexToEucf
module UndoState =
let init value = {
Previous = []
Next = []
Current = value
}
module Command =
let undo state =
match state.Previous with
| head :: tail -> {
Previous = tail
Next = state.Current :: state.Next
Current = head
}
| _ -> state
let redo state =
match state.Next with
| head :: tail -> {
Previous = state.Current :: state.Previous
Next = state.Next.Tail
Current = state.Next.Head
}
| _ -> state
let replace state value = {
Previous = state.Current :: state.Previous
Next = []
Current = value
}
let apply state cmd =
match cmd with
| Identity -> state
| Undo -> undo state
| Redo -> redo state
| Replace c -> replace state c
| Apply f -> replace state (f state.Current)
module GridState =
let empty = {
Cells = Map.empty
}
let draw p color state = {
state with Cells = Map.add p color state.Cells
}
let erase p state = {
state with Cells = Map.remove p state.Cells
}
type SavedGrid = {
cells : string list
}
let toHexString (c : RgbaByte) =
let x = (int c.R <<< 24) ||| (int c.G <<< 16) ||| (int c.B <<< 8) ||| (int c.A <<< 0)
$"%08x{x}"
let serialize (state : GridState) =
JsonConvert.SerializeObject({
cells =
state.Cells
|> Seq.sortBy (fun kvp -> kvp.Key.Y, kvp.Key.X)
|> Seq.map (fun kvp -> $"%d{kvp.Key.X} %d{kvp.Key.Y} %s{toHexString kvp.Value}")
|> Seq.toList
}, Formatting.Indented)
/// RGBA from high to low bits
let uint32ToRgbaByte (x : uint32) =
RgbaByte(
byte ((x >>> 24) &&& (uint32 0xff)),
byte ((x >>> 16) &&& (uint32 0xff)),
byte ((x >>> 8) &&& (uint32 0xff)),
byte ((x >>> 0) &&& (uint32 0xff)))
let parseRgbaHex str =
UInt32.Parse(str, Globalization.NumberStyles.HexNumber)
|> uint32ToRgbaByte
let deserialize str =
let g = JsonConvert.DeserializeObject(str)
{
GridState.Cells =
g.cells
|> Seq.map (fun c ->
let parts = c.Split(' ')
{ X = Int32.Parse(parts.[0]); Y = Int32.Parse(parts.[1]) },
parseRgbaHex parts.[2])
|> Map.ofSeq
}
let sample param (grid : GridState) =
let w = max 0 param.OutputWidth
let h = max 0 param.OutputHeight
let r = param.Bounds
let s = max 1 param.SampleFactor
let samplesPerPixel = s * s
let data = Array.zeroCreate (w * h * 4)
// uniform supersampling of pixels
// |.....x.....|.....x.....|
// |..x.....x..|..x.....x..|
for y = 0 to h - 1 do
for x = 0 to w - 1 do
let mutable sr = 0
let mutable sg = 0
let mutable sb = 0
let mutable sa = 0
for sy = 0 to s - 1 do
for sx = 0 to s - 1 do
let nx = (float32 (x * s + sx) + 0.5f) / float32 (w * s)
let ny = (float32 (y * s + sy) + 0.5f) / float32 (h * s)
let np = Vector2(nx, ny)
let ep = Range2.Lerp(r, np)
let cp = TriCoords.eucToContainingTriCell ep
let color =
let cp = { X = cp.X; Y = cp.Y }
match grid.Cells.TryGetValue(cp) with
| false, _ -> param.Background
| true, x -> x
sr <- sr + int color.R
sg <- sg + int color.G
sb <- sb + int color.B
sa <- sa + int color.A
// reverse y for texture
let i = (h - 1 - y) * w + x
data.[i * 4 + 0] <- sr / samplesPerPixel |> byte
data.[i * 4 + 1] <- sg / samplesPerPixel |> byte
data.[i * 4 + 2] <- sb / samplesPerPixel |> byte
data.[i * 4 + 3] <- sa / samplesPerPixel |> byte
data
module Viewport =
let getViewSize zoom width height =
let tileSize = 24.0f
let aspect = float32 width / float32 height
let widthInTiles = float32 width / tileSize
let heightInTiles = widthInTiles / aspect
let scale = MathF.Pow(2.0f, float32 -zoom * 0.5f) |> float32
Vector2(widthInTiles, heightInTiles) * scale
let getInverseOrIdentity (m : Matrix4x4) =
let mutable mInv = Matrix4x4.Identity
if Matrix4x4.Invert(m, &mInv) then mInv else Matrix4x4.Identity
module TriPositions =
let inline fromTriCellScaled (tileSize : Vector2) (p : Vector2i) =
let y0 = float32 p.Y
let y1 = float32 (p.Y + 1)
let py0 = Vector2(y0 * 0.5f, y0 * -tileSize.Y)
let py1 = Vector2(y1 * 0.5f, y1 * -tileSize.Y)
let ax = p.X >>> 1
let x0 = float32 ax
let x1 = float32 (ax + 1)
let p01 = Vector2((py1.X + x0) * tileSize.X, py1.Y)
let p10 = Vector2((py0.X + x1) * tileSize.X, py0.Y)
let side = p.X &&& 1
if side = 0
then { P0 = Vector2((py0.X + x0) * tileSize.X, py0.Y); P1 = p10; P2 = p01 }
else { P0 = Vector2((py1.X + x1) * tileSize.X, py1.Y); P1 = p01; P2 = p10 }
let inline fromTriCell (p : Vector2i) =
fromTriCellScaled (Vector2(1.0f, TriCoords.edgeToHeight)) p
================================================
FILE: samples/Garnet.Samples.Trixel/Game.fs
================================================
namespace Garnet.Samples.Trixel
open System
open System.Numerics
open System.Threading
open System.IO
open Veldrid
open SixLabors.ImageSharp
open Garnet.Numerics
open Garnet.Composition
open Garnet.Graphics
open Garnet.Input
module Resources =
let squareTex = "square.png"
let shaderSet : ShaderSetDescriptor = {
VertexShader = "texture-color.vert"
FragmentShader = "texture-color.frag"
}
let pipeline = {
Blend = Blend.Alpha
Filtering = Filtering.Linear
ShaderSet = shaderSet
Texture = squareTex
}
let cellLayer = {
LayerId = 4
CameraId = 0
Primitive = Triangle
FlushMode = NoFlush
Pipeline = pipeline
}
let gridLineLayer = {
LayerId = 3
CameraId = 0
Primitive = Quad
FlushMode = NoFlush
Pipeline = pipeline
}
[]
module DrawingExtensions =
type SpriteRenderer with
member c.DrawGrid(state) =
let mesh = c.GetVertices(Resources.cellLayer)
mesh.DrawGridCells(state.Current)
mesh.Flush()
type Game(fs : IReadOnlyFolder) =
// Create window and graphics device
let ren =
new WindowRenderer {
WindowSettings.Default with
Title = "Trixel"
Width = 800
Height = 600
Background = RgbaFloat(0.0f, 0.1f, 0.2f, 1.0f)
}
// Initialize rendering
let shaders = new ShaderSetCache()
let cache = new ResourceCache()
let sprites = new SpriteRenderer(ren.Device, shaders, cache)
let gui = new Gui(ren.Device, ren.ImGui)
do
cache.AddShaderLoaders(ren.Device)
fs.LoadShadersFromFolder(".", ren.Device.BackendType, cache)
fs.LoadTexture(ren.Device, Resources.squareTex, cache)
member c.Run() =
let mutable state = UndoState.init GridState.empty
sprites.DrawGrid(state)
// Grid lines
let mesh = sprites.GetVertices(Resources.gridLineLayer)
mesh.DrawGridLines()
mesh.Flush()
// Cells
let mesh = sprites.GetVertices(Resources.cellLayer)
mesh.DrawGridCells(state.Current)
mesh.Flush()
// Start loop
let inputs = InputCollection()
let cameras = CameraSet()
while ren.Update(0.0f, inputs) do
// Calculate transforms
let sizeInTiles = Viewport.getViewSize 0 ren.Size.X ren.Size.Y
let proj = Matrix4x4.CreateOrthographic(sizeInTiles.X, sizeInTiles.Y, -100.0f, 100.0f)
let view = Matrix4x4.Identity
let camera = cameras.[0]
camera.ProjectionTransform <- proj
camera.ViewTransform <-view
// Draw GUI and collect any user command
let projView = proj * view
let invProjView = Viewport.getInverseOrIdentity projView
let result = gui.Draw(state, inputs, invProjView)
// Apply command to state or read/write files
match result with
| None -> ()
| Some cmd ->
match cmd with
| GridCommand cmd ->
// Update state from command
state <- Command.apply state cmd
sprites.DrawGrid(state)
| Export cmd ->
let image = Image.createRenderedGridImage cmd.SamplingParams state.Current
use fs = File.OpenWrite(cmd.ExportFile)
image.SaveAsPng(fs)
| FileCommand cmd ->
match cmd with
| Load file ->
let cmd =
File.ReadAllText(file)
|> GridState.deserialize
|> Replace
state <- Command.apply state cmd
sprites.DrawGrid(state)
| Save file ->
Directory.CreateDirectory(Path.GetDirectoryName(file)) |> ignore
File.WriteAllText(file, GridState.serialize state.Current)
// Draw to window
if ren.BeginDraw() then
sprites.Draw(ren.RenderContext, cameras)
ren.EndDraw()
// Sleep to avoid spinning CPU
Thread.Sleep(1)
interface IDisposable with
member c.Dispose() =
cache.Dispose()
shaders.Dispose()
sprites.Dispose()
gui.Dispose()
ren.Dispose()
static member Run(fs) =
use game = new Game(fs)
game.Run()
================================================
FILE: samples/Garnet.Samples.Trixel/Garnet.Samples.Trixel.fsproj
================================================
WinExe
net6.0
en
true
Link
Garnet.Samples.Trixel
PreserveNewest
PreserveNewest
PreserveNewest
PreserveNewest
PreserveNewest
================================================
FILE: samples/Garnet.Samples.Trixel/Gui.fs
================================================
namespace Garnet.Samples.Trixel
open System
open System.IO
open System.Numerics
open Veldrid
open ImGuiNET
open Garnet.Numerics
open Garnet.Input
type CursorGui() =
member c.Draw(inputs : InputCollection, invProjView : Matrix4x4) =
let pos = ImGui.GetMousePos()
let normPos = inputs.NormalizedMousePosition
let viewPos = Vector2.Transform(normPos, invProjView)
let cellPos = TriCoords.eucToContainingTriCell viewPos
ImGui.Text($"Position: {pos}")
ImGui.Text($"Normalized: {normPos}")
ImGui.Text($"Viewport: {viewPos}")
ImGui.Text($"Cell: {cellPos}")
ImGui.Text($"Buttons: {inputs.IsMouseDown(0)} {inputs.IsMouseDown(1)}")
type ViewGui() =
let mutable centerX = 35
let mutable centerY = -18
let mutable cellMargin = 0.1f
let mutable zoom = 0
member c.Draw(state : UndoState) =
let grid = state.Current
ImGui.Text($"Cells: {grid.Cells.Count}")
let inv = false
let inv = ImGui.InputInt("Zoom", &zoom) || inv
let inv = ImGui.InputInt("X", ¢erX) || inv
let inv = ImGui.InputInt("Y", ¢erY) || inv
()
type EditGui() =
let mutable primary = Vector4(0.5f, 0.0f, 0.3f, 1.0f)
let mutable secondary = Vector4(0.3f, 0.0f, 0.6f, 1.0f)
member c.Draw(state : UndoState, inputs : InputCollection, invProjView : Matrix4x4) =
if ImGui.Begin("Edit") then
ImGui.ColorEdit4("Primary", &primary) |> ignore
ImGui.ColorEdit4("Secondary", &secondary) |> ignore
let undo = ImGui.Button $"Undo (%d{state.Previous.Length})"
let redo = ImGui.Button $"Redo (%d{state.Next.Length})"
let leftButton = inputs.IsMouseDown(0)
let rightButton = inputs.IsMouseDown(2)
let drawCommand =
let canDraw =
not (ImGui.GetIO().WantCaptureMouse) &&
(leftButton || rightButton)
if canDraw then
let normPos = inputs.NormalizedMousePosition
let viewPos = Vector2.Transform(normPos, invProjView)
let modifiers = inputs.Modifiers
let p = TriCoords.eucToContainingTriCell viewPos
let cp = { X = p.X; Y = p.Y }
let current = state.Current
let newState =
if leftButton then
let v = if modifiers.HasShift() then secondary else primary
let color = RgbaFloat(v.X, v.Y, v.Z, v.W).ToRgbaByte()
GridState.draw cp color current
else
GridState.erase cp current
if newState <> current then Some (Replace newState) else None
else None
let result =
if drawCommand.IsSome then drawCommand
elif undo then Some Undo
elif redo then Some Redo
else None
ImGui.End()
result
else None
type PreviewGui(device : GraphicsDevice, renderer : ImGuiRenderer) =
let previewTex = new PreviewTexture(device, renderer)
let mutable zoom = 8
let mutable width = 35
let mutable height = 30
let mutable centerX = 0
let mutable centerY = 0
let mutable multisample = 4
let mutable viewSize = 30
let mutable resolution = 1
let mutable file = "preview.png"
member c.Draw(state : GridState) =
if ImGui.Begin("Preview") then
ImGui.InputInt("Zoom", &zoom) |> ignore
ImGui.InputInt("View size", &viewSize) |> ignore
ImGui.InputInt("Width", &width) |> ignore
ImGui.InputInt("Height", &height) |> ignore
ImGui.InputInt("Multiplier", &resolution) |> ignore
ImGui.InputInt("X", ¢erX) |> ignore
ImGui.InputInt("Y", ¢erY) |> ignore
ImGui.InputInt("Multisample", &multisample) |> ignore
ImGui.InputText("File", &file, 128u) |> ignore
let export = ImGui.Button("Export")
let viewWidth = float32 viewSize
let viewHeight = viewWidth * TriCoords.edgeToHeight
let center = TriCoords.vertexToEuc (Vector2i(centerX, centerY))
let param = {
OutputWidth = width * resolution
OutputHeight = height * resolution
SampleFactor = multisample
Bounds = Range2.Sized(center, Vector2(viewWidth, viewHeight))
Background = RgbaByte.Black
}
previewTex.Draw(param, state, zoom, resolution)
ImGui.End()
if export then Some {
ExportFile = file
SamplingParams = param
}
else None
else None
member c.Dispose() =
previewTex.Dispose()
interface IDisposable with
member c.Dispose() = c.Dispose()
type FileGui() =
let filter = "*.json"
let mutable dir = @"trixel-grids"
let mutable file = ""
let mutable files = [||]
let mutable fileIndex = 0
let mutable filesValid = false
member c.Draw() =
if ImGui.Begin("File") then
if ImGui.Button("Refresh") then filesValid <- false
if ImGui.InputText("Folder", &dir, 128u) || not filesValid then
files <-
if Directory.Exists(dir)
then Directory.GetFiles(dir, filter) |> Array.map Path.GetFileName
else [||]
fileIndex <- 0
filesValid <- true
if ImGui.ListBox("", &fileIndex, files, files.Length) then
file <- if fileIndex < files.Length then files.[fileIndex] else ""
let load =
if ImGui.Button("Load") && file.Length > 0 then
let path = Path.Combine(dir, file)
Some (Load path)
else None
let save =
ImGui.InputText("File", &file, 128u) |> ignore
if ImGui.Button("Save") && file.Length > 0 then
let path = Path.Combine(dir, file)
Some (Save path)
else None
let result = if load.IsSome then load else save
ImGui.End()
result
else None
type StatusGui() =
let cursorGui = CursorGui()
let viewGui = ViewGui()
member c.Draw(state, inputs, invProjView) =
if ImGui.Begin("Status") then
cursorGui.Draw(inputs, invProjView)
viewGui.Draw(state)
ImGui.End()
type Gui(device : GraphicsDevice, renderer : ImGuiRenderer) =
let fileGui = FileGui()
let editGui = EditGui()
let previewGui = new PreviewGui(device, renderer)
let statusGui = StatusGui()
member c.Draw(state, inputs, invProjView) =
// draw GUI
statusGui.Draw(state, inputs, invProjView)
let editCommand = editGui.Draw(state, inputs, invProjView)
let fileCommand = fileGui.Draw()
let previewCommand = previewGui.Draw(state.Current)
// resolve command
if editCommand.IsSome then Some (GridCommand editCommand.Value)
elif previewCommand.IsSome then Some (Export previewCommand.Value)
elif fileCommand.IsSome then Some (FileCommand fileCommand.Value)
else None
member c.Dispose() =
previewGui.Dispose()
interface IDisposable with
member c.Dispose() = c.Dispose()
================================================
FILE: samples/Garnet.Samples.Trixel/Imaging.fs
================================================
namespace Garnet.Samples.Trixel
open System
open System.Numerics
open SixLabors.ImageSharp
open Veldrid
open ImGuiNET
open Garnet.Graphics
module Image =
let createRenderedGridImage param state =
let data = GridState.sample param state
let rgbaData = Array.zeroCreate (data.Length / 4)
for i = 0 to rgbaData.Length - 1 do
rgbaData.[i] <-
PixelFormats.Rgba32(
data.[i * 4],
data.[i * 4 + 1],
data.[i * 4 + 2],
data.[i * 4 + 3])
Image.WrapMemory(Memory(rgbaData), param.OutputWidth, param.OutputHeight)
type PreviewTexture(device : GraphicsDevice, renderer : ImGuiRenderer) =
let mutable texture : Texture = null
let mutable lastState = GridState.empty
let mutable lastParam = Unchecked.defaultof
member c.Draw(param, state, zoom, resolution) =
let canUpdate =
lastParam <> param ||
lastState <> state ||
texture = null
if canUpdate then
if texture <> null
then texture.Dispose()
let data = GridState.sample param state
texture <- device.CreateTextureRgba(param.OutputWidth, param.OutputHeight, ReadOnlyMemory(data))
if texture <> null then
let texId = renderer.GetOrCreateImGuiBinding(device.ResourceFactory, texture)
let width = float32 (int texture.Width * zoom / resolution |> max 1)
let height = float32 (int texture.Height * zoom / resolution |> max 1)
ImGui.Image(texId, Vector2(width, height))
member c.Dispose() =
if texture <> null then
texture.Dispose()
texture <- null
interface IDisposable with
member c.Dispose() = c.Dispose()
================================================
FILE: samples/Garnet.Samples.Trixel/Program.fs
================================================
open Garnet.Composition
open Garnet.Samples.Trixel
[]
let main argv =
use fs = new FileFolder("assets")
Game.Run(fs)
0
================================================
FILE: samples/Garnet.Samples.Trixel/Types.fs
================================================
namespace Garnet.Samples.Trixel
open System.Numerics
open Garnet.Numerics
open Veldrid
[]
module Grids =
[]
type CellLocation = {
X : int
Y : int
}
type GridState = {
Cells : Map
}
type GridStateChanged = {
NewGridState : GridState
}
[]
type SamplingParams = {
OutputWidth : int
OutputHeight : int
SampleFactor : int
Bounds : Range2
Background : RgbaByte
}
type GridLineState = {
LineSpacing : int
}
type ViewportSize = {
ViewportSize : int
}
[]
type TriPositions = {
P0 : Vector2
P1 : Vector2
P2 : Vector2
}
[]
module Commands =
type Command<'a> =
| Identity
| Undo
| Redo
| Replace of 'a
| Apply of ('a -> 'a)
type UndoState<'a> = {
Previous : 'a list
Next : 'a list
Current : 'a
}
type FileCommand =
| Load of string
| Save of string
type ExportCommand = {
ExportFile : string
SamplingParams : SamplingParams
}
type Command =
| Export of ExportCommand
| FileCommand of FileCommand
| GridCommand of Command
================================================
FILE: samples/Garnet.Samples.Trixel/assets/texture-color.frag
================================================
#version 450
layout(location = 0) in vec2 fsin_texCoords;
layout(location = 1) in vec4 fsin_color;
layout(location = 0) out vec4 fsout_color;
layout(set = 1, binding = 1) uniform texture2D SurfaceTexture;
layout(set = 1, binding = 2) uniform sampler SurfaceSampler;
void main()
{
vec4 texColor = texture(sampler2D(SurfaceTexture, SurfaceSampler), fsin_texCoords);
fsout_color = texColor * fsin_color;
}
================================================
FILE: samples/Garnet.Samples.Trixel/assets/texture-color.frag.hlsl.bytes
================================================
Texture2D _12 : register(t0);
SamplerState _16 : register(s0);
static float2 _22;
static float4 _26;
static float4 _29;
struct SPIRV_Cross_Input
{
float2 _22 : TEXCOORD0;
float4 _29 : TEXCOORD1;
};
struct SPIRV_Cross_Output
{
float4 _26 : SV_Target0;
};
void frag_main()
{
_26 = _12.Sample(_16, _22) * _29;
}
SPIRV_Cross_Output main(SPIRV_Cross_Input stage_input)
{
_22 = stage_input._22;
_29 = stage_input._29;
frag_main();
SPIRV_Cross_Output stage_output;
stage_output._26 = _26;
return stage_output;
}
================================================
FILE: samples/Garnet.Samples.Trixel/assets/texture-color.vert
================================================
#version 450
layout(set = 0, binding = 0) uniform ProjectionBuffer
{
mat4 Projection;
};
layout(set = 0, binding = 1) uniform ViewBuffer
{
mat4 View;
};
layout(set = 1, binding = 0) uniform WorldBuffer
{
mat4 World;
};
layout(location = 0) in vec3 Position;
layout(location = 1) in vec2 TexCoords;
layout(location = 2) in vec4 Color;
layout(location = 0) out vec2 fsin_texCoords;
layout(location = 1) out vec4 fsin_Color;
void main()
{
vec4 worldPosition = World * vec4(Position, 1);
vec4 viewPosition = View * worldPosition;
vec4 clipPosition = Projection * viewPosition;
gl_Position = clipPosition;
fsin_texCoords = TexCoords;
fsin_Color = Color;
}
================================================
FILE: samples/Garnet.Samples.Trixel/assets/texture-color.vert.hlsl.bytes
================================================
cbuffer _11_13 : register(b2)
{
row_major float4x4 _13_m0 : packoffset(c0);
};
cbuffer _30_32 : register(b1)
{
row_major float4x4 _32_m0 : packoffset(c0);
};
cbuffer _38_40 : register(b0)
{
row_major float4x4 _40_m0 : packoffset(c0);
};
static float4 gl_Position;
static float3 _21;
static float2 _56;
static float2 _58;
static float4 _60;
static float4 _62;
struct SPIRV_Cross_Input
{
float3 _21 : TEXCOORD0;
float2 _58 : TEXCOORD1;
float4 _62 : TEXCOORD2;
};
struct SPIRV_Cross_Output
{
float2 _56 : TEXCOORD0;
float4 _60 : TEXCOORD1;
float4 gl_Position : SV_Position;
};
void vert_main()
{
gl_Position = mul(mul(mul(float4(_21, 1.0f), _13_m0), _32_m0), _40_m0);
_56 = _58;
_60 = _62;
}
SPIRV_Cross_Output main(SPIRV_Cross_Input stage_input)
{
_21 = stage_input._21;
_58 = stage_input._58;
_62 = stage_input._62;
vert_main();
SPIRV_Cross_Output stage_output;
stage_output.gl_Position = gl_Position;
stage_output._56 = _56;
stage_output._60 = _60;
return stage_output;
}
================================================
FILE: samples/Garnet.Toolkit/Audio.fs
================================================
namespace Garnet.Audio
open System
open System.Buffers
open System.Collections.Generic
open System.Numerics
open System.IO
open System.Runtime.InteropServices
open OpenTK.Audio.OpenAL
open OpenTK.Audio.OpenAL.Extensions
open Garnet.Composition
open Garnet.Collections
[]
type SoundId = SoundId of int
[]
type SoundDescriptor = {
Channels : int
BitsPerSample : int
SampleRate : int
SampleCount : int
}
[]
type SoundPlayback = {
LoopCount : int
Gain : float32
Pitch : float32
Position : Vector3
Relative : bool
} with
static member Default = {
LoopCount = 1
Gain = 1.0f
Pitch = 1.0f
Position = Vector3.Zero
Relative = false
}
module SoundDescriptor =
let getALFormat desc =
if desc.Channels = 1 && desc.BitsPerSample = 8 then ALFormat.Mono8
elif desc.Channels = 1 && desc.BitsPerSample = 16 then ALFormat.Mono16
elif desc.Channels = 2 && desc.BitsPerSample = 8 then ALFormat.Stereo8
elif desc.Channels = 2 && desc.BitsPerSample = 16 then ALFormat.Stereo16
else failwith $"Unsupported audio format, {desc.Channels} channels, {desc.BitsPerSample} bits"
let getDuration desc =
(desc.SampleCount * 1000 + 999) / desc.Channels / desc.SampleRate
[]
module internal OpenALInternal =
module SoundId =
let toInt id = match id with SoundId x -> x
type AL =
static member GetAvailableDeviceNames() = [|
yield! ALC.GetStringList(GetEnumerationStringList.DeviceSpecifier)
yield! Creative.EnumerateAll.EnumerateAll.GetStringList(Creative.EnumerateAll.GetEnumerateAllContextStringList.AllDevicesSpecifier)
|]
static member ThrowIfError(str) =
let error = AL.GetError()
if int error <> int ALError.NoError then
failwith $"OpenAL error on {str}: {AL.GetErrorString(error)}"
type internal SourcePool() =
let maxSources = 32
let sources = AL.GenSources(maxSources)
let pool =
let stack = Stack()
for id in sources do
stack.Push(id)
stack
member c.TryGetSource() =
if pool.Count > 0 then ValueSome (pool.Pop())
else ValueNone
member c.RecycleSource(id) =
pool.Push(id)
member c.Dispose() =
AL.DeleteSources(sources)
interface IDisposable with
member c.Dispose() =
c.Dispose()
[]
type Sound = {
descriptor : SoundDescriptor
buffer : int
}
type AudioDevice() =
let devices = AL.GetAvailableDeviceNames()
let device = ALC.OpenDevice(null)
let context =
let c = ALC.CreateContext(device, Array.empty)
let _ = ALC.MakeContextCurrent(c)
c
let sources = new SourcePool()
let sounds = List()
let activeSources = PriorityQueue()
let mutable scale = 1.0f
let mutable time = 0L
member c.CreateSound(desc, data : ReadOnlyMemory) =
let buffers = AL.GenBuffers(1)
use handle = data.Pin()
let format = SoundDescriptor.getALFormat desc
AL.BufferData(buffers.[0], format, IntPtr handle.Pointer, desc.SampleCount, desc.SampleRate)
AL.ThrowIfError("loading audio data")
let sound = {
descriptor = desc
buffer = buffers.[0]
}
let soundId = sounds.Count
sounds.Add(sound)
SoundId soundId
member c.StopSounds() =
while activeSources.Count > 0 do
let sourceId = activeSources.Dequeue()
AL.SourceStop(sourceId)
sources.RecycleSource(sourceId)
member c.Update(currentTime) =
time <- currentTime
while activeSources.Count > 0 && currentTime >= activeSources.Top.Key do
let sourceId = activeSources.Dequeue()
AL.SourceStop(sourceId)
sources.RecycleSource(sourceId)
member c.PlaySound(soundId, playback : SoundPlayback) =
match sources.TryGetSource() with
| ValueNone -> ()
| ValueSome sourceId ->
let soundId = SoundId.toInt soundId
let sound = sounds.[soundId]
let p = playback.Position * scale
AL.Source(sourceId, ALSourcei.Buffer, sound.buffer)
AL.Source(sourceId, ALSourceb.Looping, playback.LoopCount > 1)
AL.Source(sourceId, ALSourcef.Pitch, playback.Pitch)
AL.Source(sourceId, ALSourcef.Gain, playback.Gain)
AL.Source(sourceId, ALSourceb.SourceRelative, playback.Relative)
AL.Source(sourceId, ALSource3f.Position, p.X, p.Y, p.Z)
AL.SourcePlay(sourceId)
let duration = SoundDescriptor.getDuration sound.descriptor * playback.LoopCount
activeSources.Enqueue(time + int64 duration, sourceId)
member c.SetPosition(pos : Vector3) =
let mutable v = OpenTK.Mathematics.Vector3(pos.X, pos.Y, pos.Z) * scale
AL.Listener(ALListener3f.Position, &v)
member c.SetGain(gain) =
AL.Listener(ALListenerf.Gain, gain)
member c.SetScaling(newScale) =
scale <- newScale
member c.Dispose() =
c.StopSounds()
sources.Dispose()
for sound in sounds do
AL.DeleteBuffers([| sound.buffer |])
ALC.MakeContextCurrent(ALContext.Null) |> ignore
ALC.DestroyContext(context)
ALC.CloseDevice(device) |> ignore
interface IDisposable with
member c.Dispose() = c.Dispose()
override c.ToString() =
let version = AL.Get(ALGetString.Version)
let vendor = AL.Get(ALGetString.Vendor)
let renderer = AL.Get(ALGetString.Renderer)
sprintf "Audio devices (%d):\n %s\nSelected device:\n %s\n %s\n %s"
devices.Length (String.Join("\n ", devices))
renderer version vendor
type AudioDevice with
member c.CreateSoundFromSineWave() =
let sampleRate = 44100
let dt = 2.0 * Math.PI / float sampleRate
let amp = 0.5
let freq = 440.0
let sampleCount = float sampleRate / freq
let data = Array.zeroCreate (int sampleCount * 2)
let dest = MemoryMarshal.Cast(data.AsSpan())
for i = 0 to dest.Length - 1 do
dest.[i] <- int16 (amp * float Int16.MaxValue * sin (float i * dt * freq))
let desc = {
Channels = 1
BitsPerSample = 16
SampleRate = sampleRate
SampleCount = data.Length * 8 / 16
}
c.CreateSound(desc, ReadOnlyMemory(data))
[]
module AudioLoadingExtensions =
type IStreamSource with
member c.LoadWave(device : AudioDevice, key) =
use stream = c.Open(key)
// https://stackoverflow.com/questions/8754111/how-to-read-the-data-in-a-wav-file-to-an-array
use reader = new BinaryReader(stream)
// chunk 0
let _ = reader.ReadInt32() // chunkId
let _ = reader.ReadInt32() // fileSize
let _ = reader.ReadInt32() // riffType
// chunk 1
let _ = reader.ReadInt32() // fmtID
let fmtSize = reader.ReadInt32() // bytes for this chunk (expect 16 or 18)
// 16 bytes coming
let _ = int (reader.ReadInt16()) // fmtCode
let channels = int (reader.ReadInt16())
let sampleRate = reader.ReadInt32()
let _ = reader.ReadInt32() // byteRate
let _ = int (reader.ReadInt16()) // fmtBlockAlign
let bitDepth = int (reader.ReadInt16())
if fmtSize = 18 then
// Read any extra values
let fmtExtraSize = int (reader.ReadInt16())
stream.Seek(int64 fmtExtraSize, SeekOrigin.Current) |> ignore
// Skip to data chunk
let dataTag = 0x61_74_61_64 // 'data'
let mutable tag = reader.ReadInt32()
let mutable length = reader.ReadInt32()
while tag <> dataTag do
// Read instead of seeking since zip stream doesn't support seek
let buffer = ArrayPool.Shared.Rent(length)
reader.BaseStream.Read(buffer, 0, length) |> ignore
ArrayPool.Shared.Return(buffer)
// Read next header
tag <- reader.ReadInt32()
length <- reader.ReadInt32()
// Read data
// https://stackoverflow.com/questions/10996917/openal-albufferdata-returns-al-invalid-value-even-though-input-variables-look
let adjustedLength = (length + 3) / 4 * 4
let data = ArrayPool.Shared.Rent(adjustedLength)
stream.Read(data, 0, length) |> ignore
// Create descriptor
let bytesPerSample = bitDepth / 8
let sampleCount = adjustedLength / bytesPerSample
let desc = {
Channels = channels
BitsPerSample = bitDepth
SampleRate = sampleRate
SampleCount = sampleCount
}
try
let sound = device.CreateSound(desc, ReadOnlyMemory(data, 0, adjustedLength))
ArrayPool.Shared.Return(data)
sound
with ex ->
raise (Exception($"Could not load WAV file '{key}'", ex))
type IReadOnlyFolder with
member c.LoadAudioFromFolder(path, device, cache : IResourceCache) =
for file in c.GetFiles(path, "*.wav") do
let soundId = c.LoadWave(device, file)
cache.AddResource(file, soundId)
type WaveFileLoader(device : AudioDevice) =
interface IResourceLoader with
member c.Load(folder, cache, key) =
cache.AddResource(key, folder.LoadWave(device, key))
[]
module AudioLoaderExtensions =
type ResourceCache with
member c.AddAudioLoaders(device) =
c.AddLoader(".wav", WaveFileLoader(device))
================================================
FILE: samples/Garnet.Toolkit/Collections.fs
================================================
namespace Garnet.Collections
open System
open System.Collections.Generic
open Garnet.Comparisons
/// Mutable min-heap
type Heap<'k, 'a when 'k :> IComparable<'k>>() =
// create a dummy value for easier indexing
let items = List>()
do items.Add(Unchecked.defaultof<_>)
let compare a b =
items.[a].Key.CompareTo(items.[b].Key)
let swap a b =
let temp = items.[b]
items.[b] <- items.[a]
items.[a] <- temp
let getMinChildIndex parentIndex =
let ci = parentIndex * 2
if ci >= items.Count then -1
else
// if we have a second child that's smaller, pick it
// we know that if second exists, first exists due to shape
let offset =
if ci + 1 < items.Count &&
compare (ci + 1) ci < 0
then 1 else 0
ci + offset
let rec siftDown index =
// start at top and swap down through min child
let ci = getMinChildIndex index
if ci >= 0 && compare index ci > 0 then
swap index ci
siftDown ci
let rec siftUp index =
// start at end and swap up through parent
// maintain parent/child invariant at each iteration
if index > 1 && compare index (index / 2) < 0 then
swap index (index / 2)
siftUp (index / 2)
member h.Count = items.Count - 1
member h.Top = items.[1]
member h.Insert(key, value) =
items.Add(KeyValuePair(key, value))
siftUp (items.Count - 1)
member h.RemoveMin() =
if h.Count = 0 then failwith "Heap is empty"
let top = h.Top
items.[1] <- items.[items.Count - 1]
items.RemoveAt(items.Count - 1)
siftDown 1
top
member h.Clear() =
while items.Count > 1 do items.RemoveAt(items.Count - 1)
/// Mutable, min queue (min priority value dequeued first)
type PriorityQueue<'k, 'a when 'k :> IComparable<'k>>() =
let heap = Heap<'k, 'a>()
member _.Count = heap.Count
member _.Top = heap.Top
member _.Enqueue(priority, value) =
heap.Insert(priority, value)
member _.Dequeue() =
heap.RemoveMin().Value
member _.Clear() =
heap.Clear()
module internal Buffer =
let private log2 x =
let mutable log = 0
let mutable y = x
while y > 1 do
y <- y >>> 1
log <- log + 1;
log
let private nextLog2 x =
let log = log2 x
if x - (1 <<< log) > 0 then 1 + log else log
let getRequiredCount count =
1 <<< nextLog2 count
let resizeArray count (arr : byref<_[]>) =
if isNull arr || count > arr.Length then
let required = 1 <<< nextLog2 count
let newArr = Array.zeroCreate required
if isNotNull arr then
arr.CopyTo(newArr, 0)
arr <- newArr
================================================
FILE: samples/Garnet.Toolkit/Colors.fs
================================================
namespace Garnet.Numerics
open System
open Veldrid
module private ColorParsing =
let parse defaultValue (parts : string[]) index =
if index >= parts.Length then defaultValue
else
match Single.TryParse(parts.[index]) with
| true, x -> x
| false, _ -> defaultValue
[]
type HsvaFloat =
val H : float32
val S : float32
val V : float32
val A : float32
new(h, s, v, a) = { H = h; S = s; V = v; A = a; }
new(c : RgbaFloat) =
let m = min (min c.R c.G) c.B
let v = max (max c.R c.G) c.B
let s = if v > Single.Epsilon then 1.0f - m / v else 0.0f
let l = (m + v) / 2.0f
if l < Single.Epsilon || v < m
then HsvaFloat(0.0f, s, v, c.A)
else
let vm = v - m
let r2 = (v - c.R) / vm
let g2 = (v - c.G) / vm
let b2 = (v - c.B) / vm
let hx =
if c.R = v then if c.G = m then 5.0f + b2 else 1.0f - g2
else if c.G = v then if c.B = m then 1.0f + r2 else 3.0f - b2
else if c.R = m then 3.0f + g2 else 5.0f - r2
/ 6.0f
let h = if hx >= 1.0f then hx - 1.0f else hx
HsvaFloat(h, s, v, c.A)
member c.ShiftHue(shift) =
HsvaFloat(c.H + shift, c.S, c.V, c.A)
member c.ToRgbaFloat() =
// from: http://alvyray.com/Papers/CG/hsv2rgb.htm
// H is given on [0, 6] or UNDEFINED. S and V are given on [0, 1].
// RGB are each returned on [0, 1].
let f = c.H - floor c.H
let h = f * 6.0f
let v = c.V
let i = int32(floor h)
// if i is even
let g = if (i &&& 1) = 0 then 1.0f - f else f
let m = v * (1.0f - c.S)
let n = v * (1.0f - c.S * g)
match i with
| 6 -> RgbaFloat(v, n, m, c.A)
| 0 -> RgbaFloat(v, n, m, c.A)
| 1 -> RgbaFloat(n, v, m, c.A)
| 2 -> RgbaFloat(m, v, n, c.A)
| 3 -> RgbaFloat(m, n, v, c.A)
| 4 -> RgbaFloat(n, m, v, c.A)
| _ -> RgbaFloat(v, m, n, c.A)
static member Lerp(min : HsvaFloat, max : HsvaFloat, t) =
HsvaFloat(
MathF.Lerp(min.H, max.H, t),
MathF.Lerp(min.S, max.S, t),
MathF.Lerp(min.V, max.V, t),
MathF.Lerp(min.A, max.A, t))
static member Parse(str : string) =
// hsva(120,100%,50%,0.3)
let parts = str.Replace(" ", "").Replace("%", "").Split([|','; '('; ')'|], StringSplitOptions.RemoveEmptyEntries)
let h = ColorParsing.parse 0.0f parts 1 / 360.0f
let s = ColorParsing.parse 0.0f parts 2 / 100.0f
let v = ColorParsing.parse 0.0f parts 3 / 100.0f
let a = ColorParsing.parse 1.0f parts 4
HsvaFloat(h, s, v, a)
[]
type HslaFloat =
val H : float32
val S : float32
val L : float32
val A : float32
new(h, s, l, a) = { H = h; S = s; L = l; A = a; }
new(c : RgbaFloat) =
let c0 = min (min c.R c.G) c.B
let c1 = max (max c.R c.G) c.B
let l = (c0 + c1) / 2.0f
if c0 = c1 then HslaFloat(0.0f, 0.0f, l, c.A)
else
let delta = c1 - c0
let s =
if l <= 0.5f
then (delta / (c1 + c0))
else (delta / (2.0f - (c1 + c0)))
let h =
if c.R = c1 then (c.G - c.B) / delta
else if c.G = c1 then 2.0f + (c.B - c.R) / delta
else if c.B = c1 then 4.0f + (c.R - c.G) / delta
else 0.0f
let x = h / 6.0f
HslaFloat(x - floor x, s, l, c.A)
member c.ShiftHue(shift) =
HsvaFloat(c.H + shift, c.S, c.L, c.A)
member c.ToRgbaFloat() =
// Note: there is a typo in the 2nd International Edition of Foley and
// van Dam's "Computer Graphics: Principles and Practice", section 13.3.5
// (The HLS Color Model). This incorrectly replaces the 1f in the following
// line with "l", giving confusing results.
if c.S = 0.0f then RgbaFloat(c.L, c.L, c.L, c.A)
else
let m2 =
if c.L <= 0.5f
then c.L * (1.0f + c.S)
else c.L + c.S - c.L * c.S
let m1 = 2.0f * c.L - m2
RgbaFloat(
HslaFloat.GetChannelValue(m1, m2, (c.H + 1.0f / 3.0f)),
HslaFloat.GetChannelValue(m1, m2, c.H),
HslaFloat.GetChannelValue(m1, m2, (c.H - 1.0f / 3.0f)),
c.A)
static member Lerp(min : HslaFloat, max : HslaFloat, t) =
HslaFloat(
MathF.Lerp(min.H, max.H, t),
MathF.Lerp(min.S, max.S, t),
MathF.Lerp(min.L, max.L, t),
MathF.Lerp(min.A, max.A, t))
static member private GetChannelValue(n1, n2, t) =
let hue =
if t < 0.0f then t + 1.0f
else if t > 1.0f then t - 1.0f
else t
if hue < 1.0f / 6.0f then n1 + (n2 - n1) * hue * 6.0f
else if hue < 0.5f then n2
else if (hue < 2.0f / 3.0f) then n1 + (n2 - n1) * (2.0f / 3.0f - hue) * 6.0f
else n1
static member Parse(str : string) =
// hsla(120,100%,50%,0.3)
let parts = str.Replace(" ", "").Replace("%", "").Split([|','; '('; ')'|], StringSplitOptions.RemoveEmptyEntries)
let h = ColorParsing.parse 0.0f parts 1 / 360.0f
let s = ColorParsing.parse 0.0f parts 2 / 100.0f
let l = ColorParsing.parse 0.0f parts 3 / 100.0f
let a = ColorParsing.parse 1.0f parts 4
HslaFloat(h, s, l, a)
[]
module RgbaByte =
type RgbaByte with
member c.ToRgbaFloat() =
RgbaFloat(
float32 c.R / 255.0f,
float32 c.G / 255.0f,
float32 c.B / 255.0f,
float32 c.A / 255.0f)
member c.ToUInt32() =
(int(c.R) <<< 24) |||
(int(c.G) <<< 16) |||
(int(c.B) <<< 8) |||
(int(c.A) <<< 0)
static member FromUInt32(x : uint) =
RgbaByte(
byte ((x >>> 24) &&& (uint32 0xff)),
byte ((x >>> 16) &&& (uint32 0xff)),
byte ((x >>> 8) &&& (uint32 0xff)),
byte ((x >>> 0) &&& (uint32 0xff)))
[]
module RgbaFloat =
type RgbaFloat with
member c.Add(b : RgbaFloat) =
RgbaFloat(c.R + b.R, c.G + b.G, c.B + b.B, c.A + b.A)
member c.Multiply(b : RgbaFloat) =
RgbaFloat(c.R * b.R, c.G * b.G, c.B * b.B, c.A * b.A)
member c.Multiply(x) =
RgbaFloat(c.R * x, c.G * x, c.B * x, c.A * x)
member c.MultiplyRgb(x) =
RgbaFloat(c.R * x, c.G * x, c.B * x, c.A)
member c.MultiplyAlpha(a) =
RgbaFloat(c.R, c.G, c.B, c.A * a)
member c.WithAlpha(a) =
RgbaFloat(c.R, c.G, c.B, a)
member c.Clamp() =
RgbaFloat(
MathF.Clamp01(c.R),
MathF.Clamp01(c.G),
MathF.Clamp01(c.B),
MathF.Clamp01(c.A))
member c.ToRgbaByte() =
RgbaByte(
byte (c.R * 255.0f |> max 0.0f |> min 255.0f),
byte (c.G * 255.0f |> max 0.0f |> min 255.0f),
byte (c.B * 255.0f |> max 0.0f |> min 255.0f),
byte (c.A * 255.0f |> max 0.0f |> min 255.0f))
static member Lerp(min : RgbaFloat, max : RgbaFloat, t) =
RgbaFloat(
MathF.Lerp(min.R, max.R, t),
MathF.Lerp(min.G, max.G, t),
MathF.Lerp(min.B, max.B, t),
MathF.Lerp(min.A, max.A, t))
static member Luminance(x, a) =
RgbaFloat(x, x, x, a)
static member FromUInt32(x : uint) =
RgbaByte.FromUInt32(x).ToRgbaFloat()
static member Parse(str : string) =
if str.StartsWith("hsla") then HslaFloat.Parse(str).ToRgbaFloat()
elif str.StartsWith("hsva") then HsvaFloat.Parse(str).ToRgbaFloat()
else
// #rrggbbaa
match UInt32.TryParse(str.TrimStart([|'#'|]), Globalization.NumberStyles.HexNumber, null) with
| true, x -> RgbaFloat.FromUInt32(x)
| false, _ -> RgbaFloat.Clear
================================================
FILE: samples/Garnet.Toolkit/Comparisons.fs
================================================
namespace Garnet
// The purpose of this is to avoid equality operator allocations for value types.
// Be careful if using this with floating point values.
// https://github.com/dotnet/fsharp/issues/526
// https://zeckul.wordpress.com/2015/07/09/how-to-avoid-boxing-value-types-in-f-equality-comparisons/
#nowarn "86"
module Comparisons =
let inline eq<'a when 'a :> System.IEquatable<'a>> (x:'a) (y:'a) = x.Equals y
let inline (=) x y = eq x y
let inline (<>) x y = not (eq x y)
let inline (=@) x y = Microsoft.FSharp.Core.Operators.(=) x y
let inline (<>@) x y = Microsoft.FSharp.Core.Operators.(<>) x y
let inline lt<'a when 'a :> System.IComparable<'a>> (x:'a) (y:'a) = x.CompareTo(y) < 0
let inline gt<'a when 'a :> System.IComparable<'a>> (x:'a) (y:'a) = x.CompareTo(y) > 0
let inline lte<'a when 'a :> System.IComparable<'a>> (x:'a) (y:'a) = x.CompareTo(y) <= 0
let inline gte<'a when 'a :> System.IComparable<'a>> (x:'a) (y:'a) = x.CompareTo(y) >= 0
let inline (<) x y = lt x y
let inline (>) x y = gt x y
let inline (<=) x y = lte x y
let inline (>=) x y = gte x y
let inline isNull x = obj.ReferenceEquals(x, null)
let inline isNotNull x = not (isNull x)
================================================
FILE: samples/Garnet.Toolkit/Events.fs
================================================
namespace Garnet.Composition
open Garnet.Numerics
type Start = struct end
type Closing = struct end
[]
type HandleInput = {
Time : int64
}
[]
type Schedule = {
DueTime : int64
}
[]
type Tick = {
Time : int64
}
[]
type Update = {
FrameNumber : int64
FixedTime : int64
FixedDeltaTime : int64
Time : int64
DeltaTime : int64
}
[]
type PreUpdate = {
Update : Update
}
[]
type PostUpdate = {
Update : Update
}
[]
type FixedUpdate = {
FixedFrameNumber : int64
FixedTime : int64
FixedDeltaTime : int64
Time : int64
}
[]
type Draw = {
ViewSize : Vector2i
Update : Update
}
type PushDrawCommands = struct end
================================================
FILE: samples/Garnet.Toolkit/Fonts.fs
================================================
namespace Garnet.Graphics
open System
open System.Buffers
open System.Runtime.CompilerServices
open System.Numerics
open System.Runtime.InteropServices
open Veldrid
open Garnet.Numerics
open Garnet.Composition
/// JSON-serializable
type FontCharDescriptor = {
Code : char
Width : int
OffsetX : int
OffsetY : int
RectX : int
RectY : int
RectWidth : int
RectHeight : int
}
/// JSON-serializable
type FontDescriptor = {
Size : int
Family : string
Style : string
Height : int
Chars : FontCharDescriptor[]
}
type Align =
| Left = 0
| Center = 1
| Right = 2
type Valign =
| Top = 0
| Center = 1
| Bottom = 2
type TextWrapping =
| NoWrap = 0
| WordWrap = 1
[]
type TextBlock = {
Text : string
Color : RgbaFloat
Bounds : Range2i
Scale : int
Align : Align
Valign : Valign
Wrapping : TextWrapping
Spacing : Vector2i
} with
static member Default = {
Text = ""
Color = RgbaFloat.White
Bounds = Range2i.Zero
Scale = 1
Align = Align.Left
Valign = Valign.Top
Wrapping = TextWrapping.NoWrap
Spacing = Vector2i.Zero
}
[]
type FontCharInfo = {
Width : int
Offset : Vector2i
Size : Vector2i
Rect : Range2
}
module private FontRun =
let getCharWidth ch (charWidths : int[]) =
let index = int ch
if index < charWidths.Length then charWidths.[index] else 0
let getTrimmedRange (str : string) (run : Rangei) =
let mutable start = run.Min
while start < run.Max && Char.IsWhiteSpace str.[start] do
start <- start + 1
let mutable stop = run.Max
while stop > run.Min && Char.IsWhiteSpace str.[stop - 1] do
stop <- stop - 1
Rangei(start, stop)
let isWhitespace ch =
Char.IsWhiteSpace ch ||
ch = '\n'
let getWordStart (str : string) start =
let mutable i = start
while i < str.Length && isWhitespace str.[i] do
i <- i + 1
i
let getWordEnd (str : string) start =
let mutable i = start
while i < str.Length && not (isWhitespace str.[i]) do
i <- i + 1
i
let getRunWidth (str : string) (charWidths : int[]) (run : Rangei) =
let mutable width = 0
for i = run.Min to run.Max - 1 do
width <- width + getCharWidth str.[i] charWidths
width
let tryGetNextRun (str : string) charWidths start maxAllowedWidth =
let mutable runWidth = 0
let mutable i = start
let mutable result = ValueNone
while result.IsNone && i < str.Length do
let ch = str.[i]
if ch = '\n' then
// Newline
result <- ValueSome (Rangei(start, i + 1))
else
runWidth <- runWidth + getCharWidth str.[i] charWidths
if runWidth > maxAllowedWidth then
// Backtrack to start of word
let mutable stop = i
while stop > start + 1 && not (Char.IsWhiteSpace(str.[stop])) do
stop <- stop - 1
result <- ValueSome (Rangei(start, stop + 1))
i <- i + 1
// If no newline or limit reached, return remainder
if result.IsNone then
let remaining = str.Length - start
if remaining > 0 then
result <- ValueSome (Rangei(start, str.Length))
result
let measure (str : string) (charWidths : int[]) charHeight maxAllowedWidth =
let mutable maxWidth = 0
let mutable count = 0
let mutable runOpt = tryGetNextRun str charWidths 0 maxAllowedWidth
while runOpt.IsSome do
let run = runOpt.Value
let width = getRunWidth str charWidths run
maxWidth <- max maxWidth width
count <- count + 1
runOpt <- tryGetNextRun str charWidths run.Max maxAllowedWidth
Vector2i(maxWidth, count * charHeight)
let getBounds (size : Vector2i) (bounds : Range2i) align valign =
let p0 = bounds.Min
let p1 = bounds.Max
let boxSize = p1 - p0
let x0 =
match align with
| Align.Left -> p0.X
| Align.Right -> p1.X - size.X
| Align.Center -> p0.X + (boxSize.X - size.X) / 2
| x -> failwith $"Invalid align {x}"
let y0 =
match valign with
| Valign.Top -> p0.Y
| Valign.Bottom -> p1.Y - size.Y
| Valign.Center -> p0.Y + (boxSize.Y - size.Y) / 2
| x -> failwith $"Invalid valign {x}"
Range2i.Sized(Vector2i(x0, y0), size)
let getMaxAllowedWidth wrapping size =
match wrapping with
| TextWrapping.NoWrap -> Int32.MaxValue
| TextWrapping.WordWrap -> size
| x -> failwith $"Invalid wrapping {x}"
module private FontCharInfo =
let getTexBounds (charRect : Range2i) (mapSize : Vector2i) texBounds =
let scale = Vector2.One / (mapSize.ToVector2())
let t0 = charRect.Min.ToVector2() * scale
let t1 = charRect.Max.ToVector2() * scale
let p0 = Range2.Lerp(texBounds, t0)
let p1 = Range2.Lerp(texBounds, t1)
Range2(p0, p1)
let fromCharDescriptor (mapSize : Vector2i) (p : FontCharDescriptor) (texBounds : Range2) =
let r = Range2i.Sized(Vector2i(p.RectX, p.RectY), Vector2i(p.RectWidth, p.RectHeight))
{
Width = p.Width
Offset = Vector2i(p.OffsetX, p.OffsetY)
Size = Vector2i(p.RectWidth, p.RectHeight)
Rect = getTexBounds r mapSize texBounds
}
type Font(height, charLookup : FontCharInfo[]) =
let widths = charLookup |> Array.map (fun c -> c.Width)
member c.Height = height
member c.GetCharInfo(ch : char) =
let code = int ch
if code < charLookup.Length then charLookup.[code] else charLookup.[0]
member c.TryGetNextRun(str, start, maxAllowedWidth) =
FontRun.tryGetNextRun str widths start maxAllowedWidth
member c.Measure(text) =
FontRun.measure text widths height Int32.MaxValue
member c.Measure(block) =
let bounds = block.Bounds
let maxAllowedWidth = FontRun.getMaxAllowedWidth block.Wrapping (bounds.Size.X * block.Scale)
let size = FontRun.measure block.Text widths height maxAllowedWidth * block.Scale
FontRun.getBounds size bounds block.Align block.Valign
static member CreateMonospaced(charSheetSize : Vector2i, texBounds, xSpacing) =
// Assume this is a 16x16 char tile sheet
let charSize = charSheetSize / 16
let lookup = [|
for y = 0 to 15 do
for x = 0 to 15 do
let p = Vector2i(x, y) * charSize
let charRect = Range2i.Sized(p, charSize)
{
Width = charSize.X + xSpacing
Offset = Vector2i.Zero
Size = charSize
Rect = FontCharInfo.getTexBounds charRect charSheetSize texBounds
}
|]
Font(charSize.Y, lookup)
static member FromDescriptor(desc, mapSize, texBounds) =
let table = Array.zeroCreate 256
for ch in desc.Chars do
let code = int ch.Code
if code < table.Length then
let coords = FontCharInfo.fromCharDescriptor mapSize ch texBounds
table.[code] <- coords
Font(desc.Height, table)
[]
module FontLoadingExtensions =
type IReadOnlyFolder with
member c.LoadJsonFontDescriptor(fontName : string) =
c.LoadJson(fontName)
type IResourceCache with
/// Loads a monospace font stored in a texture atlas
member c.LoadMonospacedFont(atlasName, fontName, xSpacing) =
match c.TryGetResource(fontName) with
| true, font -> font
| false, _ ->
let atlas = c.LoadResource(atlasName)
let tex = atlas.[fontName]
let font = Font.CreateMonospaced(tex.Bounds.Size, tex.NormalizedBounds, xSpacing)
c.AddResource(fontName, font)
font
/// Loads a JSON font paired with a PNG with matching name
member c.LoadJsonFont(fontName,
fontTextureName,
[] atlasName : string) =
match c.TryGetResource(fontName) with
| true, font -> font
| false, _ ->
let desc = c.LoadResource(fontName)
let font =
if String.IsNullOrEmpty(atlasName) then
let tex = c.LoadResource(fontTextureName)
Font.FromDescriptor(desc, Vector2i(int tex.Width, int tex.Height), Range2.ZeroToOne)
else
let atlas = c.LoadResource(atlasName)
let tex = atlas.[fontTextureName]
let size = tex.Bounds.Size //- Vector2i.One * tex.Padding * 2
let texBounds = tex.NormalizedBounds
Font.FromDescriptor(desc, Vector2i(abs size.X, abs size.Y), texBounds)
c.AddResource(fontName, font)
font
type FontLoader() =
interface IResourceLoader with
member c.Load(folder, cache, key) =
let font = folder.LoadJsonFontDescriptor(key)
cache.AddResource(key, font)
[]
module FontLoaderExtensions =
type ResourceCache with
member c.AddFontLoaders() =
c.AddLoader(".font.json", FontLoader())
[]
type FontVertexSpanExtensions =
/// Draws monospace text
[]
static member DrawText(w : IBufferWriter,
text : string,
pos : Vector2i,
atlasSize : Vector2i,
fontTexBounds : Range2i,
charMargin : Vector2i,
color : RgbaFloat) =
let charSetSize = fontTexBounds.Size
let charSize = charSetSize / Vector2i(16, 16)
let displayCharSize = charSize + charMargin
let atlasScale = Vector2.One / atlasSize.ToVector2()
let span = w.GetQuadSpan(text.Length)
for i = 0 to text.Length - 1 do
let ch = text.[i]
let tileId = int ch
let tx = tileId &&& 0xf
let ty = tileId >>> 4
let t0 = Vector2i(tx, ty) * charSize + fontTexBounds.Min
let t1 = t0 + charSize
let tb = Range2(t0.ToVector2() * atlasScale, t1.ToVector2() * atlasScale)
let b = Range2i.Sized(pos + Vector2i(i * displayCharSize.X, 0), charSize)
let span = span.Slice(i * 4)
span.DrawQuad(b.ToRange2(), tb, color)
w.Advance(span.Length)
[]
static member DrawText(w : IBufferWriter, font : Font, block : TextBlock) =
let textBounds = font.Measure(block)
let span = w.GetQuadSpan(block.Text.Length)
let maxUnscaledWidth =
match block.Wrapping with
| TextWrapping.NoWrap -> Int32.MaxValue
| TextWrapping.WordWrap -> block.Bounds.Size.X / block.Scale
| x -> failwith $"Invalid wrapping: {x}"
let mutable runOpt = font.TryGetNextRun(block.Text, 0, maxUnscaledWidth)
let mutable row = 0
let mutable vi = 0
while runOpt.IsSome do
let run = runOpt.Value
let y = textBounds.Y.Min + row * font.Height * block.Scale
let mutable x = textBounds.X.Min
for i = run.Min to run.Max - 1 do
let ch = block.Text.[i]
let desc = font.GetCharInfo(ch)
if desc.Size.X > 0 then
let b =
let offset = desc.Offset * block.Scale
let p0 = Vector2i(x + offset.X, y + offset.Y)
let p1 = p0 + desc.Size * block.Scale
Range2i(p0, p1)
let span = span.Slice(vi)
span.DrawQuad(b.ToRange2(), desc.Rect, block.Color)
vi <- vi + 4
x <- x + desc.Width * block.Scale
runOpt <- font.TryGetNextRun(block.Text, run.Max, maxUnscaledWidth)
row <- row + 1
w.Advance(vi)
[]
static member DrawText(w : IBufferWriter, font,
text,
pos : Vector2i,
color : RgbaFloat,
[] width : int,
[] height : int,
[] align : Align,
[] valign : Valign,
[] wrapping : TextWrapping,
[] scale : int,
[] xSpacing : int,
[] ySpacing : int
) =
w.DrawText(font, {
Text = text
Color = color
Bounds = Range2i.Sized(pos, Vector2i(width, height))
Scale = scale
Align = align
Valign = valign
Wrapping = wrapping
Spacing = Vector2i(xSpacing, ySpacing)
})
================================================
FILE: samples/Garnet.Toolkit/Garnet.Toolkit.fsproj
================================================
net6.0
false
Garnet.Toolkit
Utility code for games, including graphics, audio, and integration with Garnet.
graphics audio game
PreserveNewest
runtimes/win-x64/native
true
PreserveNewest
runtimes/win-x86/native
true
================================================
FILE: samples/Garnet.Toolkit/Input.fs
================================================
namespace Garnet.Input
open System
open System.Collections.Generic
open System.Numerics
open Veldrid
open Garnet.Numerics
[]
type KeyDown = {
KeyCode : Key
Modifiers : ModifierKeys
} with
static member None = {
KeyCode = Key.Unknown
Modifiers = ModifierKeys.None
}
[]
type KeyUp = {
KeyCode : Key
Modifiers : ModifierKeys
}
[]
type MouseWheel = {
modifiers : int
wheel : int
}
[]
type MouseUpdate = {
pos : Vector2
devicePos : Vector2i
button1 : bool
button2 : bool
}
[]
type MouseMoved = {
pos : Vector2
delta : Vector2
devicePos : Vector2i
deviceDelta : Vector2i
modifiers : ModifierKeys
}
[]
type MouseDown = {
pos : Vector2
devicePos : Vector2i
button : int
modifiers : ModifierKeys
}
[]
type MouseUp = {
pos : Vector2
devicePos : Vector2i
button : int
}
[]
module InputExtensions =
type ModifierKeys with
member c.IsShift() = int c = int ModifierKeys.Shift
member c.IsCtrl() = int c = int ModifierKeys.Control
member c.IsAlt() = int c = int ModifierKeys.Alt
member c.HasShift() = int (c &&& ModifierKeys.Shift) <> 0
member c.HasCtrl() = int (c &&& ModifierKeys.Control) <> 0
member c.HasAlt() = int (c &&& ModifierKeys.Alt) <> 0
type InputCollection() =
let keys = Array.zeroCreate (int Key.LastKey)
let keyUpEvents = List()
let keyDownEvents = List()
let mouseDownEvents = List()
let mouseUpEvents = List()
let mouseButtons = Array.zeroCreate 10
let mutable mousePos = Vector2i.Zero
let mutable lastMousePos = Vector2i.Zero
let mutable normMousePos = Vector2.Zero
let mutable lastNormMousePos = Vector2.Zero
let mutable wheelDelta = 0.0f
member c.MousePosition = mousePos
member c.LastMousePosition = lastMousePos
member c.MouseDelta = mousePos - lastMousePos
member c.NormalizedMousePosition = normMousePos
member c.LastNormalizedMousePosition = lastNormMousePos
member c.NormalizedMouseDelta = normMousePos - lastNormMousePos
member c.WheelDelta = wheelDelta
member c.KeyUpEvents = keyUpEvents
member c.KeyDownEvents = keyDownEvents
member c.MouseUpEvents = mouseUpEvents
member c.MouseDownEvents = mouseDownEvents
member c.Modifiers =
let hasAlt =
c.IsKeyDown(Key.AltLeft) ||
c.IsKeyDown(Key.AltRight) ||
c.IsKeyDown(Key.LAlt) ||
c.IsKeyDown(Key.RAlt)
let hasCtrl =
c.IsKeyDown(Key.ControlLeft) ||
c.IsKeyDown(Key.ControlRight) ||
c.IsKeyDown(Key.LControl) ||
c.IsKeyDown(Key.RControl)
let hasShift =
c.IsKeyDown(Key.ShiftLeft) ||
c.IsKeyDown(Key.ShiftRight) ||
c.IsKeyDown(Key.LShift) ||
c.IsKeyDown(Key.RShift)
(if hasAlt then ModifierKeys.Alt else ModifierKeys.None) |||
(if hasCtrl then ModifierKeys.Control else ModifierKeys.None) |||
(if hasShift then ModifierKeys.Shift else ModifierKeys.None)
member c.IsMouseDown(button) =
mouseButtons.[button]
member c.IsMousePressed(button) =
mouseDownEvents.Contains(button)
member c.IsKeyDown(code : Key) =
keys.[int code]
member c.IsKeyPressed(code, modifiers) =
keyDownEvents.Contains {
KeyCode = code
Modifiers = modifiers
}
member c.UpdateMouse(newPos, newNormPos, newWheelDelta) =
wheelDelta <- newWheelDelta
lastMousePos <- mousePos
mousePos <- newPos
lastNormMousePos <- normMousePos
normMousePos <- newNormPos
member c.SetMouseButton(button : MouseButton, state) =
if mouseButtons.[int button] <> state then
if state then mouseDownEvents.Add(int button)
else mouseUpEvents.Add(int button)
mouseButtons.[int button] <- state
member c.Add(code, modifier) =
keys.[int code] <- true
keyDownEvents.Add {
KeyCode = code
Modifiers = modifier
}
member c.Remove(code, modifier) =
keys.[int code] <- false
keyUpEvents.Add {
KeyCode = code
Modifiers = modifier
}
member c.Clear() =
Array.Clear(keys, 0, keys.Length)
member c.ClearEvents() =
keyUpEvents.Clear()
keyDownEvents.Clear()
mouseDownEvents.Clear()
mouseUpEvents.Clear()
override c.ToString() =
String.Join(", ", keys)
type InputCollection with
member c.IsKeyDown(code, modifier : ModifierKeys) =
c.IsKeyDown(code) && int modifier = int c.Modifiers
member c.IsKeyPressed(code) =
c.IsKeyPressed(code, ModifierKeys.None)
member c.IsAnyKeyDown(codes : IReadOnlyList) =
let mutable down = false
for i = 0 to codes.Count - 1 do
let code = codes.[i]
down <- down || c.IsKeyDown(code)
down
member c.IsAnyKeyPressed(codes : IReadOnlyList, modifiers) =
let mutable down = false
for i = 0 to codes.Count - 1 do
let code = codes.[i]
down <- down || c.IsKeyPressed(code, modifiers)
down
member c.IsAnyKeyPressed(codes) =
c.IsAnyKeyPressed(codes, ModifierKeys.None)
member c.UpdateKeysFromSnapshot(snapshot : InputSnapshot) =
for i = 0 to snapshot.KeyEvents.Count - 1 do
let e = snapshot.KeyEvents.[i]
let m = e.Modifiers
let key = e.Key
if e.Down then c.Add(key, m)
else c.Remove(key, e.Modifiers)
member c.UpdateMouseFromSnapshot(snapshot : InputSnapshot, viewSize : Vector2i) =
let mousePos = Vector2i(int snapshot.MousePosition.X, int snapshot.MousePosition.Y)
let normMousePosition =
let v = snapshot.MousePosition / viewSize.ToVector2() * 2.0f - Vector2.One
Vector2(v.X, -v.Y)
c.UpdateMouse(mousePos, normMousePosition, snapshot.WheelDelta)
for e in snapshot.MouseEvents do
let button = e.MouseButton
c.SetMouseButton(button, e.Down)
================================================
FILE: samples/Garnet.Toolkit/Logging.fs
================================================
namespace Garnet.Composition
open System
open System.IO
open Microsoft.Extensions.Logging
open Cysharp.Text
open ZLogger
type LogSettings = {
WriteToFile : bool
WriteToConsole : bool
RollFile : bool
LogPath : string
LogName : string
TimestampFormat : string
PrefixFormat : string
MinLogLevel : LogLevel
FileRollSizeKb : int
}
module private Logging =
let configure settings (options : ZLoggerOptions) =
let prefixFormat = ZString.PrepareUtf8(settings.PrefixFormat)
options.PrefixFormatter <- fun writer info ->
let mutable w = writer
let timestamp = info.Timestamp.DateTime.ToLocalTime().ToString(settings.TimestampFormat)
prefixFormat.FormatTo(&w, timestamp, info.LogLevel, info.CategoryName)
type LogSettings with
static member Default = {
WriteToFile = true
WriteToConsole = false
RollFile = false
LogPath = "."
LogName = "run"
TimestampFormat = "yyyy-MM-dd HH:mm:ss.fff"
PrefixFormat = "{0} {1} [{2}] "
MinLogLevel = LogLevel.Information
FileRollSizeKb = 1024
}
member settings.CreateFactory() =
LoggerFactory.Create(fun builder ->
let builder = builder.SetMinimumLevel(settings.MinLogLevel)
// Add console
let builder =
if settings.WriteToConsole
then builder.AddZLoggerConsole(Logging.configure settings)
else builder
// Add rolling file
let _ =
if settings.WriteToFile then
if settings.RollFile then
let logPrefix = Path.Combine(settings.LogPath, settings.LogName + "-")
builder.AddZLoggerRollingFile(
(fun dt index ->
let timestamp = dt.ToLocalTime().ToString("yyyy-MM-dd")
let num = index.ToString().PadLeft(3, '0')
$"{logPrefix}{timestamp}_{num}.log"),
(fun dt -> DateTimeOffset(dt.ToLocalTime().Date)),
settings.FileRollSizeKb,
Action(Logging.configure settings))
else
let file = Path.Combine(settings.LogPath, settings.LogName + ".log")
builder.AddZLoggerFile(file, Action<_>(Logging.configure settings))
else builder
())
[]
module LoggingExtensions =
type Container with
static member RunLoop(settings : LogSettings, register) =
use factory = settings.CreateFactory()
let logger = factory.CreateLogger("Default")
try
let c = Container()
c.Set(logger)
use s = register c
c.RunLoop()
with ex ->
logger.LogError(ex, "")
static member RunLoop(register) =
Container.RunLoop(LogSettings.Default, register)
================================================
FILE: samples/Garnet.Toolkit/Looping.fs
================================================
namespace Garnet.Composition
open System
open System.Collections.Generic
open System.Diagnostics
open System.Runtime.CompilerServices
open System.Threading
open System.Runtime.InteropServices
open Garnet.Composition
[]
type Looping =
[