Aetheria Cut 8a needs int2(float2), clamp(int, int, int), and frac(double). Fill them in CultMath as dxc compiles them: - generate-swizzles.ps1 emits same-size conversion constructors and math functions between float, int, and bool vectors. dxc lowers float to int as fptosi (toward zero; NaN and out-of-range undefined), numeric to bool as != 0 (fcmp une, NaN is true), bool to numeric as 0 or 1. floatN(int) splat keeps floatN(0) from going ambiguous against floatN(intN). - Int and int-vector clamp are min(max(x, a), b), matching dxc's IMin(IMax(x, a), b); inverted bounds return b. - frac(double) keeps double precision. dxc demotes double frac to float, as it does exp and lerp; CultMath's existing double overloads already diverge that way, and design.md now says so. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
159 lines
6.7 KiB
PowerShell
159 lines
6.7 KiB
PowerShell
param(
|
|
[string]$OutputPath = ""
|
|
)
|
|
|
|
# Writes src\CultMath\Swizzles.g.cs: HLSL swizzles and mixed constructors for
|
|
# CultMath's vector types. The output is checked in; rerun after changing the
|
|
# family table below and commit both files together.
|
|
|
|
$ErrorActionPreference = "Stop"
|
|
|
|
if (-not $OutputPath) {
|
|
$OutputPath = Join-Path $PSScriptRoot "..\src\CultMath\Swizzles.g.cs"
|
|
}
|
|
|
|
# Functions: emit math.<type>(...) constructor functions. double has none because
|
|
# a math.double3 method group would hide the double3 type name under
|
|
# `using static CultMath.math;` and HLSL shader code does not construct doubles.
|
|
$families = @(
|
|
@{ Scalar = "float"; Sizes = @(2, 3, 4); Functions = $true },
|
|
@{ Scalar = "int"; Sizes = @(2, 3, 4); Functions = $true },
|
|
@{ Scalar = "bool"; Sizes = @(2, 3, 4); Functions = $true },
|
|
@{ Scalar = "double"; Sizes = @(2, 3); Functions = $false }
|
|
)
|
|
# Same-size vector conversions, as dxc lowers them: float to int is fptosi (toward zero; NaN
|
|
# and out-of-range are undefined in DXIL), int to float is sitofp, numeric to bool is `!= 0`
|
|
# (fcmp une, so NaN is true), bool to numeric is 0 or 1. Keyed "target<-source"; {0} is a
|
|
# source component. Identity forms (intN(intN), ...) come from the composition loop, which
|
|
# also keeps an int vector argument off the lossy intN(floatN) overload.
|
|
$conversions = [ordered]@{
|
|
"float<-int" = "{0}"
|
|
"float<-bool" = "{0} ? 1.0f : 0.0f"
|
|
"int<-float" = "(int){0}"
|
|
"int<-bool" = "{0} ? 1 : 0"
|
|
"bool<-float" = "{0} != 0.0f"
|
|
"bool<-int" = "{0} != 0"
|
|
}
|
|
$componentFields = @("x", "y", "z", "w")
|
|
$namings = @(@("x", "y", "z", "w"), @("r", "g", "b", "a"))
|
|
|
|
function Get-Compositions([int]$total) {
|
|
# Ordered part sizes summing to $total, excluding the all-scalar form that the
|
|
# hand-written base constructor already owns.
|
|
$results = [System.Collections.Generic.List[object]]::new()
|
|
function Expand([int]$remaining, [int[]]$prefix) {
|
|
if ($remaining -eq 0) {
|
|
if (@($prefix | Where-Object { $_ -ne 1 }).Count -gt 0) {
|
|
$results.Add($prefix)
|
|
}
|
|
return
|
|
}
|
|
for ($part = 1; $part -le $remaining; $part++) {
|
|
Expand ($remaining - $part) ($prefix + $part)
|
|
}
|
|
}
|
|
Expand $total @()
|
|
return , $results
|
|
}
|
|
|
|
$builder = [System.Text.StringBuilder]::new()
|
|
function Line([string]$text = "") { [void]$builder.Append($text).Append("`n") }
|
|
|
|
Line "// <auto-generated>"
|
|
Line "// Generated by tools/generate-swizzles.ps1. Do not edit by hand."
|
|
Line "// </auto-generated>"
|
|
Line ""
|
|
Line "namespace CultMath;"
|
|
|
|
$functionLines = [System.Collections.Generic.List[string]]::new()
|
|
|
|
foreach ($family in $families) {
|
|
$scalar = $family.Scalar
|
|
$maxSize = ($family.Sizes | Measure-Object -Maximum).Maximum
|
|
foreach ($size in $family.Sizes) {
|
|
$type = "$scalar$size"
|
|
Line ""
|
|
Line "public partial struct $type"
|
|
Line "{"
|
|
|
|
foreach ($composition in (Get-Compositions $size)) {
|
|
$parameters = @()
|
|
$arguments = @()
|
|
$offset = 0
|
|
foreach ($part in $composition) {
|
|
$name = -join $componentFields[$offset..($offset + $part - 1)]
|
|
if ($part -eq 1) {
|
|
$parameters += "$scalar $name"
|
|
$arguments += $name
|
|
} else {
|
|
$parameters += "$scalar$part $name"
|
|
for ($i = 0; $i -lt $part; $i++) { $arguments += "$name.$($componentFields[$i])" }
|
|
}
|
|
$offset += $part
|
|
}
|
|
$parameterList = $parameters -join ", "
|
|
Line " public $type($parameterList) : this($($arguments -join ', ')) { }"
|
|
if ($family.Functions) {
|
|
$names = @($parameters | ForEach-Object { ($_ -split " ")[1] })
|
|
$functionLines.Add(" public static $type $type($parameterList) => new($($names -join ', '));")
|
|
}
|
|
}
|
|
|
|
foreach ($source in @("float", "int", "bool")) {
|
|
$expression = $conversions["$scalar<-$source"]
|
|
if (-not $expression) { continue }
|
|
$converted = for ($i = 0; $i -lt $size; $i++) { $expression -f "value.$($componentFields[$i])" }
|
|
Line " public $type($source$size value) : this($($converted -join ', ')) { }"
|
|
if ($family.Functions) {
|
|
$functionLines.Add(" public static $type $type($source$size value) => new(value);")
|
|
}
|
|
}
|
|
if ($scalar -eq "float" -and $family.Functions) {
|
|
# floatN(int) splat: without it floatN(0) is ambiguous between floatN(float) and floatN(intN).
|
|
$functionLines.Add(" public static $type $type(int value) => new($((@('value') * $size) -join ', '));")
|
|
}
|
|
|
|
# Single-component color aliases; x/y/z/w are the fields themselves.
|
|
$letters = $namings[1]
|
|
for ($i = 0; $i -lt $size; $i++) {
|
|
Line " public $scalar $($letters[$i]) { readonly get => $($componentFields[$i]); set => $($componentFields[$i]) = value; }"
|
|
}
|
|
|
|
foreach ($letters in $namings) {
|
|
for ($length = 2; $length -le $maxSize; $length++) {
|
|
$count = [math]::Pow($size, $length)
|
|
for ($index = 0; $index -lt $count; $index++) {
|
|
[int[]]$digits = @()
|
|
[int]$remainder = $index
|
|
for ($d = 0; $d -lt $length; $d++) {
|
|
$digits = @([int]($remainder % $size)) + $digits
|
|
$remainder = [int][math]::Floor($remainder / $size)
|
|
}
|
|
$name = -join ($digits | ForEach-Object { $letters[$_] })
|
|
$getter = ($digits | ForEach-Object { $componentFields[$_] }) -join ", "
|
|
$resultType = "$scalar$length"
|
|
$unique = @($digits | Select-Object -Unique).Count -eq $length
|
|
if ($unique) {
|
|
$assignments = for ($d = 0; $d -lt $length; $d++) { "$($componentFields[$digits[$d]]) = value.$($componentFields[$d]);" }
|
|
Line " public $resultType $name { readonly get => new($getter); set { $($assignments -join ' ') } }"
|
|
} else {
|
|
Line " public readonly $resultType $name => new($getter);"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Line "}"
|
|
}
|
|
}
|
|
|
|
Line ""
|
|
Line "public static partial class math"
|
|
Line "{"
|
|
foreach ($functionLine in $functionLines) { Line $functionLine }
|
|
Line "}"
|
|
|
|
$fullPath = [System.IO.Path]::GetFullPath($OutputPath)
|
|
$content = $builder.ToString() -replace "`n", "`r`n"
|
|
[System.IO.File]::WriteAllText($fullPath, $content, [System.Text.UTF8Encoding]::new($false))
|
|
Write-Host "Wrote $fullPath ($((Get-Item -LiteralPath $fullPath).Length) bytes)"
|