2011-08-30

Kontrola dokładności obliczeń

Po przejściu z double na float na niektórych scenach pojawiły się dziwne błędy. Nie uśmiechało mi się ich debugować - scena z trzema przeźroczystymi sferami + fresnel + refrakcja + ścianki pudełka z tymi kulkami.

Ale okazało się, że podczas renderowania sceny w debug dostałem asserty. Dotyczyły one braku normalizacji wektorów, długość ustawianych wektorów nie była równa jeden. Oczywiście była bliska jeden, ale większa niż ustalona precyzja. Po zwiększeniu precyzji asserty znikły, a scena zrenderowała się bez tych błędów.

Ta sama precyzja kontroluje bowiem większość porównań.

Wniosek warto gęsto siać assertami w debug. Na pewno proste porównywanie liczb będzie generować błędne sceny. Warto używać tej samej precyzji wszędzie. Obecnie w kodzie posługuje się trzema stałymi kontrolującymi precyzję - do wszystkiego innego, do przylegania trójkątów, do sprawdzania kiedy jeden obiekt leży na tyle blisko drugiego, że można powiedzieć, że nie jest w jego cieniu.

Wszystkie trzy stałe są bardzo bliskie sobie i wydaje się, że w miarę czasu zrównają się. To znaczy obecne są niepoprawne, ale nie widać tych błędów na scenie.

Po wyjściu tego na jaw postanowiłem się przyjrzeć kodowi na intersekcję, gdzie jest wiele porównań, a tylko nieliczne robione są z precyzją. Poprzednio jak kod był na doublach zmieniając sposób porównania tylko pogarszałem sprawę. Ostatecznie zostawiając porównanie z precyzją tylko tam gdzie gdzie okazało się to niezbędne.

Samo porównywanie wygląda tak:
public static bool AlmostEqual(this float a_d1, float a_d2, float a_precision)
{
    float ad1 = Math.Abs(a_d1);
    float ad2 = Math.Abs(a_d2);

    if (ad1 < a_precision)
        a_d1 = 0;
    if (ad2 < a_precision)
        a_d2 = 0;

    if (a_d1 == a_d2)
        return true;

    return (Math.Abs(a_d1 - a_d2) / Math.Max(ad1, ad2)) < (a_precision * 4);
}

public static bool AlmostEqual(this float a_d1, float a_d2)
{
    return AlmostEqual(a_d1, a_d2, Constants.PRECISION);
}

public float SqrLen
{
    get
    {
        return X * X + Y * Y + Z * Z;
    }
}

public bool IsNormalized
{
    get
    {
        return SqrLen.AlmostEqual(1.0f);
    }
}

public Vector3 Dir
{
    get
    {
        return m_dir;
    }
    set
    {
        Debug.Assert(value.IsNormalized);
        m_dir = value;
    }
}

Brak komentarzy:

Prześlij komentarz