CultLib/packages/cultmath/tools/generate-swizzles.ps1
Meta Vens 5d8ef0c3de Generate every HLSL swizzle and mixed constructor; fix intrinsic types
tools/generate-swizzles.ps1 writes the checked-in src/CultMath/Swizzles.g.cs
as partial structs plus a partial math: for float2/3/4, int2/3/4 and bool2/3/4
every swizzle of length 2-4 including repeats, in xyzw and rgba naming, with
setters only on non-repeating masks, plus single-component r/g/b/a. double2/3
get lengths 2-3 (there is no double4). Every HLSL mixed constructor
(float4(float2, float2), float4(float, float2, float), copy forms, ...) exists
as a struct constructor and, for float/int/bool, a math function. The
hand-written swizzles and mixed constructors it supersedes are deleted.

Matrices store private rows and return ref float2/float3 from the row
indexer ([UnscopedRef], polyfilled for netstandard2.1), so m[1][2] = s
compiles as in HLSL; _mRC are get/set properties over the rows. Equality and
hashing stay value-based.

Intrinsics follow HLSL: sign returns int/intN and yields 0 for NaN; step is
x >= y ? 1 : 0 so NaN yields 0; any/all accept float and int vectors;
min/max/abs on int vectors return int vectors, and abs(int) returns int.

Strips the UTF-8 BOMs this branch added to math.cs, CultMath.csproj and
HlslSemanticsTests.cs.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 13:57:02 +02:00

132 lines
5.3 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 }
)
$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 ', '));")
}
}
# 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)"