diff --git a/packages/cultmath/tests/CultMath.Tests/CyclesOracleTests.cs b/packages/cultmath/tests/CultMath.Tests/CyclesOracleTests.cs index 2cafbcf5..e1a2bc8a 100644 --- a/packages/cultmath/tests/CultMath.Tests/CyclesOracleTests.cs +++ b/packages/cultmath/tests/CultMath.Tests/CyclesOracleTests.cs @@ -216,6 +216,39 @@ public sealed class CyclesOracleTests Assert.True(worst <= Math.ScaleB(1.0, -22), $"a lookup is {worst} from (index + weight) / (size - 1), more than 2^-22"); } + [Theory] + [MemberData(nameof(Targets))] + public void ResultsTheFilmCannotShowFollowTheCyclesSource(string target) + { + // Cycles' film writes 0 for an infinite or NaN result, so the fixtures cannot tell the guards that return 0 + // from the division that would return an infinity. These are the guards in svm_math (util/math_base.h): + // safe_logf, safe_divide and inversesqrtf return 0 where the plain operation is not finite. + float One(string function, params float[] arguments) => (float)Call(target, function, arguments.Cast().ToArray()); + Assert.Equal(0.0f, One("cycles_logarithm", 0.0f, 2.0f)); + Assert.Equal(0.0f, One("cycles_logarithm", -1.0f, 2.0f)); + Assert.Equal(0.0f, One("cycles_logarithm", 2.0f, 0.0f)); + Assert.Equal(0.0f, One("cycles_logarithm", 2.0f, -1.0f)); + Assert.Equal(0.0f, One("cycles_divide", 1.0f, 0.0f)); + Assert.Equal(0.0f, One("cycles_inverse_sqrt", 0.0f)); + Assert.Equal(0.0f, One("cycles_modulo", 1.0f, 0.0f)); + Assert.Equal(0.0f, One("cycles_floored_modulo", 1.0f, 0.0f)); + } + + [Theory] + [MemberData(nameof(Targets))] + public void HueSaturationAndValueOfThePrimariesFollowCycles(string target) + { + // util/color.h rgb_to_hsv: hue 0 for red, 1/3 for green, 2/3 for blue, saturation 1, value 1; a grey has + // hue 0 and saturation 0. Red's hue is exactly 0 and not 1, which hsv_to_rgb would read the same. + float[] Hsv(float r, float g, float b) => Flatten(Call(target, "cycles_rgb_to_hsv", new float3(r, g, b))); + Assert.Equal(new[] { 0.0f, 1.0f, 1.0f }, Hsv(1.0f, 0.0f, 0.0f)); + Assert.Equal(new[] { 0.0f, 0.0f, 0.5f }, Hsv(0.5f, 0.5f, 0.5f)); + Assert.Equal(1.0f / 3.0f, Hsv(0.0f, 1.0f, 0.0f)[0], 6); + Assert.Equal(2.0f / 3.0f, Hsv(0.0f, 0.0f, 1.0f)[0], 6); + var back = Flatten(Call(target, "cycles_hsv_to_rgb", new float3(0.0f, 1.0f, 1.0f))); + Assert.Equal(new[] { 1.0f, 0.0f, 0.0f }, back); + } + [Fact] public void RgbToBwCoefficientsAreTheOnesBlenderReads() { diff --git a/packages/cultmath/tools/cycles-oracle/oracle.py b/packages/cultmath/tools/cycles-oracle/oracle.py index 9fddeb30..7279eceb 100644 --- a/packages/cultmath/tools/cycles-oracle/oracle.py +++ b/packages/cultmath/tools/cycles-oracle/oracle.py @@ -241,6 +241,9 @@ EXTRA_ROWS = { # A base of -1 keeps a finite result for any exponent, so the film shows the exponent past the int range. (-1.0, 3.0e9), (-1.0, -3.0e9), (-1.0, 2147483648.0), (-1.0, 16777216.0), (-1.0, 16777215.0)], "cycles_compare": [(0.0, 1.0e-7, 0.0), (1.0, 1.0000001, 0.0), (1.0, 1.0000002, 0.0), (0.0, 1.0e-7, 1.0e-8), (5.0, 5.0, 0.0), (0.0, 2.0e-7, 1.0e-7)], + # Burn with a divisor of exactly 0 (tm + t col2 = 0) and a colour above 1: the result is 1 for the channel Cycles reads + # as 0, and an infinity the film would hide for the others. + "cycles_mix_burn": [(2.0, 1.5, 0.5, 1.0, 0.5, 0.5, 0.5), (0.5, 1.5, 2.0, 1.0, -1.0, -1.0, -1.0)], "cycles_modulo": [(0.3, 0.1), (-0.3, 0.1), (5.5, -2.0), (-5.5, -2.0), (1.0, 0.0), (1.0e7, 3.0), (7.0, 7.0), (-7.0, 7.0)], } @@ -405,7 +408,7 @@ def family_mix(): functions.append({"function": "cycles_mix_vector", "node": "ShaderNodeMix VECTOR UNIFORM", "cases": encode_cases(columns, vector_bake("mix_vector", images, build_vector))}) for function, blend in MIX_MODES: - columns = mix_columns(function, blend) + columns = with_extra(function, mix_columns(function, blend), 7) images = pack_operands(columns) def build(tree, imgs, blend=blend):