2012-02-17

GetFiles, GetDirectories, EnumerateDirectories, EnumerateFiles - UnauthorizedAccessException

Funkcje służąca do wyliczenia plików i katalogów w połączeniu z parametrem SearchOption.AllDirectories (a być może także bez niego) mają jedną wadę potrafią zwrócić wyjątek UnauthorizedAccessException, w przypadku katalogu do którego nie mamy praw. Najgorzej wygląda to właśnie podczas rekurencyjnego przeszukiwania całego drzewa. Jeśli gdzieś w tym drzewie jest plik/katalog do którego nie mamy praw to dostaniemy ten wyjątek.

Wydaje się, że powinno to zostać trochę bardziej elegancko rozwiązane. W praktyce oznacza to, że na podanych wyżej funkcjach nie można polegać. Bo taki katalog/plik może trafić się wszędzie.

Przy czym nie jestem pewien czy bardziej nie chodzi tutaj o katalogi niż o pliki. Tego nie sprawdzałem.

Moja wersja zwracająca wszystkie pliki w w drzewie katalogu i pomijająca te do których nie mamy praw:

public static void GetFiles(DirectoryInfo a_dir, List<string> a_files)
{
    try
    {
        foreach (var file in a_dir.GetFiles())
            a_files.Add(file.FullName);
    }
    catch
    {
        System.Console.WriteLine("dir ex: {0}", a_dir.FullName);
    }

    try
    {
        foreach (var dir in a_dir.GetDirectories())
            GetFiles(dir, a_files);
    }
    catch (UnauthorizedAccessException)
    {
        System.Console.WriteLine("dir2 ex: {0}", a_dir.FullName);
    }
}

2012-02-16

RestrictedFrequencyAction

Klasa jak sama nazwa wskazuje albo i nie służy do wywoływania delegatów z ograniczeniem: nie częściej niż pewien zadany czasy. Przydatna kiedy bardzo wiele wątków wprost bombarduje GUI żądaniami odświeżenia. W przypadku gdy pomiędzy kolejnymi akcjami nie upłynął określony czas aktualna akcja jest zapamiętywana jako kandydat do wywołania po upłynięciu czasu blokady. Należy pamiętać o tym, że z wszystkich akcji zablokowanych zostanie wywołana tylko ostania.

Nawet jeśli takie zjawisko w naszej aplikacji nie występuje, a może się zdarzyć, warto wykorzystać taką klasę by uniknąć problemów z czasem reakcji GUI.

Ja osobiście z tego modelu zrezygnowałem. Dlatego klasę utrwalam na blogu. Timer co 0.5s wystarcza w moim wypadku aż za nadto. Ponieważ wątki wykorzystują połączenia sieciowe real-time nie jest potrzebne.

public class RestrictedFrequencyAction
{
    private TimeSpan m_update_delta;
    private DateTime LastPerform;
    private volatile bool m_scheduled;
    private Object m_lock = new Object();
    private volatile Action m_action;

    public RestrictedFrequencyAction(int a_update_delta_ms)
    {
        m_update_delta = new TimeSpan(0, 0, 0, 0, a_update_delta_ms);
        LastPerform = DateTime.Now - m_update_delta - m_update_delta;
    }

    public void Perform(Action a_action)
    {
        lock (m_lock)
        {
            int t = (int)(m_update_delta - 
                (DateTime.Now - LastPerform)).TotalMilliseconds;

            if (t < 0)
            {
                LastPerform = DateTime.Now;
                a_action();
            }
            else
            {
                m_action = a_action;

                if (!m_scheduled)
                {
                    m_scheduled = true;

                    new Task(() =>
                    {
                        Thread.Sleep(t);
                        lock (m_lock)
                        {
                            LastPerform = DateTime.Now;
                            m_scheduled = false;
                            m_action();
                        }

                    }).Start();
                }
            }
        }
    }
}

RichTextBoxAppender

Jak w tytule, appender dla RichTextBox-a. W NLog dostępny w standardzie.

Powstał na podstawie wielu podobnych implementacji znalezionych w sieci.

Większość z nich używa Invoke() do publikowani danych na kontrolce, co może prowadzić do deadlock-a, w sytuacji gdy wątek GUI i jakiś inny razem starają się coś zalogować. Zastąpienie Invoke() wersją asynchroniczną BeginInvoke rozwiązuje problem.

Czemu czegoś takiego nie ma w standardzie log4net, nie wiem. Wersja dla NLog oferuje znacznie więcej od tej tutaj (kolorowanie elementów linii loga, tworzenie osobnego okna z kontrolką, integracja z kontrolką na formatce z poziomu xml).

public class RichTextBoxAppender : AppenderSkeleton
{
    private RichTextBox m_rich_text_box = null;
    private LevelMapping m_level_mapping = new LevelMapping();
    public int MaxLines = 100000;

    private delegate void UpdateControlDelegate(LoggingEvent a_logging_event);

    public RichTextBoxAppender(RichTextBox a_rich_text_box)
        : base()
    {
        m_rich_text_box = a_rich_text_box;
    }

    private void UpdateControl(LoggingEvent a_logging_event)
    {
        LevelTextStyle selectedStyle = 
            m_level_mapping.Lookup(a_logging_event.Level) as LevelTextStyle;
        if (selectedStyle != null)
        {
            m_rich_text_box.SelectionBackColor = selectedStyle.BackColor;
            m_rich_text_box.SelectionColor = selectedStyle.TextColor;
            m_rich_text_box.SelectionFont = 
                new Font(m_rich_text_box.Font, selectedStyle.FontStyle);
        }

        m_rich_text_box.AppendText(RenderLoggingEvent(a_logging_event));

        // Clear if too big.
        if (MaxLines > 0)
        {
            if (m_rich_text_box.Lines.Length > MaxLines)
            {
                int pos = m_rich_text_box.GetFirstCharIndexFromLine(1);
                m_rich_text_box.Select(0, pos);
                m_rich_text_box.SelectedText = String.Empty;
            }
        }

        // Autoscroll.
        m_rich_text_box.Select(m_rich_text_box.TextLength, 0);
        m_rich_text_box.ScrollToCaret();
    }

    protected override void Append(LoggingEvent a_logging_event)
    {
        if (m_rich_text_box.InvokeRequired)
        {
            m_rich_text_box.BeginInvoke(
                new UpdateControlDelegate(UpdateControl),
                new object[] { a_logging_event });
        }
        else
        {
            UpdateControl(a_logging_event);
        }
    }

    public void AddMapping(LevelTextStyle a_mapping)
    {
        m_level_mapping.Add(a_mapping);
    }

    public override void ActivateOptions()
    {
        base.ActivateOptions();

        m_level_mapping.ActivateOptions();
    }

    protected override bool RequiresLayout 
    { 
        get 
        { 
            return true; 
        }
    }
}

public class LevelTextStyle : LevelMappingEntry
{
    public bool Bold;
    public bool Italic;
    public Color TextColor;
    public Color BackColor;
    public FontStyle FontStyle { get; private set; }

    public override void ActivateOptions()
    {
        base.ActivateOptions();

        if (Bold)
            FontStyle |= FontStyle.Bold;
        if (Italic)
            FontStyle |= FontStyle.Italic;
    }
}

2012-02-14

Failed to load toolbox item 'controle name' it will be removed from the toolbox

Taki ostatnio napotkałem błąd. Tym samym nie możemy też bez dostania wyjątku otworzyć otworzyć formy z taką kontrolką jak i samej wizualizacji kontrolki.

Problem pojawił się kiedy do projektu dodałem DLL-ki korzystające z mixed-mode i tym samym zmieniłem platformę z Any CPU na x64. Jak szukałem rozwiązania to tego nie wiedziałem, teraz jestem mądrzejszy po fakcie.

Rozwiązanie polega na przerzuceniu kontrolek do assembly skofigurowanego na Any CPU.

NHibernate - mapowanie

NHibernate możemy poinformować o sposobie w jaki powinien mapować obiekty na struktury bazy danych na kilka sposobów

1.
Mapowanie przez kod, w przypadku gdy nasze mapowanie jest rozdzielone od obiektów:

public class Customer 
{
    public int Id { get; set; }
    public string FirstName { get; set; }
}

public class CustomerMapping : ClassMapping<Customer>
{
    public CustomerMapping()
    {
        Lazy(false);
        Id<int>(x => x.Id, map => map.Generator(Generators.HighLow));
        Property<string>(c => c.FirstName, 
            map => { map.Length(10); map.NotNullable(true); });
    }
}

ModelMapper mapper = new ModelMapper();
mapper.AddMapping<CustomerMapping>();
HbmMapping mapping = mapper.CompileMappingFor(new[] { typeof(Customer) });

Nasz obiekt nie jest w żaden sposób związany z klasami NHibernate.

2.
Mapowanie przez kod, w przypadku gdy nie chcemy tworzyć osobnych obiektów mapujących.

public interface IClassMapping
{
    void Map(ModelMapper a_mapper);
}

public class Customer : IClassMapping
{
    public int Id { get; set; }
    public string FirstName { get; set; }

    public void Map(ModelMapper a_mapper)
    {
        a_mapper.Class<Customer>(m =>
        {
            m.Id(x => x.Id, map => map.Generator(Generators.HighLow));
            m.Property(c => c.FirstName, 
                map => { map.Length(10); map.NotNullable(true); });
            m.Lazy(false);
        });
    }
}

Interfejs jest nam w tym przypadku potrzebny do automatyczneo namierzania klas:

private static void AddMappings()
{
    ModelMapper mapper = new ModelMapper();

    foreach (var asm in AssembliesToMap)
    {
        var types = from type in asm.GetTypes()
                    where !type.IsInterface
                    where type.IsImplementInterface(typeof(IClassMapping))
                    where !type.IsAbstract
                    select type;

        foreach (var type in types)
            (Activator.CreateInstance(type) as IClassMapping).Map(mapper);
    }

    HbmMapping mapping = mapper.CompileMappingForAllExplicitlyAddedEntities();
    Configuration.AddDeserializedMapping(mapping, "MangaCrawler"); 
}

3.
Mapowanie z wykorzystaniem atrybutów.

4.
Mapowanie z wykorzystaniem XML.

5.
Wykorzystanie zdarzeń klasy ModelMapper do dekorowania. Dzięki temu możemy jak mi się wydaje możemy obejść się dla większości elementów bez ręcznego mapowania. Np. możemy zdefiniować konwencję nazewnictwa kolumn w bazie, automatycznie podłączyć generatory id... Dokładnie tego nie sprawdzałem, być może jeszcze wiele innych rzeczy można ustandaryzować.

2012-02-11

NHibernate - metody czyszczenia bazy

1.
Jeśli tylko możemy możemy skasować plik, pliki bazy. Musimy mieć możliwość odtworzenia bazy. Po tej operacji należy jeszcze odtworzyć strukturę bazy.

2.
FluentNHibernate.SessionSource.BuildSchema(Session)

odtwarza strukturę bazy z ustawionego mapowania przy tym prawdopodobnie najpierw próbuje usunąć istniejące tabele.

3.
Buduje schemat bazy z mapowania, na podstawie niego usuwa z bazy tabele i je odtwarza.

var export = new SchemaExport(cfg.BuildConfiguration());
export.Drop(false, true);
export.Create(false, true);


4.
new SchemaExport(cfg).Execute(false, true, false);

5.
Chyba właściwe dla SQLite, z bazą łączymy się za pomocą: "db.db;New=True". Po tej operacji należy jeszcze odtworzyć strukturę bazy.

Podsumowanie
2,3,4 robią dokładnie to samo. Najpierw tabele są usuwane, a następnie odtwarzane.
1,5 niszczą fizycznie bazę, która wymaga po takiej operacji odtworzenia struktury.

2012-02-10

SQLLite

Dla jednego z moich projektów: Manga Crawler postanowiłem podczas rozbudowy programu przejść na plikobazę SQLLite. Dostęp do niej zamierzam uzyskać poprzez jakiś ORM (prawdopodobnie NHibernate). Na razie chciałem uruchomić tylko dostęp poprzez ADO z wykorzystaniem System.Data.SQLite.

Jak zawsze staram się wszystko kompilować z źródeł. System.Data.SQLite jest pod tym względem wyjątkowo oporna. W solucji mamy trzy projekty:

SQLite.Interop.2010 - SQLLite + dodatkowy kod odpowiedzialny za rozszerzania, szyfrowanie, i zmienę zachowania pewnych funkcji z uwagi na GC w .NET. Wszystko jest kompilowane w C++.

System.Data.SQLite.2010 - źródła wrappera ADO w C#.

System.Data.SQLite.Module.2010 - dołączona jako moduł do pierwszego. Źródła takie same jak projekt 2. Zmieniane są tylko opcje kompilacji.

Z uwagi na to, że projekt 2 i 3 mają wspólne źródła ktoś wpadł na pomysł, że można części wspólne projektu powyłączać do osobnych plików i dołączać je. Nie jest to zły pomysł, tylko, że VS nie radzi sobie z tym i w explorerze nie ma żadnych plików.

Wszystkie 3 pliki projektów powinniśmy uważnie przestudiować gdyż mają one dużo niewidocznego z GUI kodu.

Moduł 3 dołączany do 1 to tak naprawdę skompilowane źródła (do języka pośredniego), moduły takie mogą się składać na assembly.

Jak to się dzieje, że nasz projekt 1 po dołączeniu 3 staje się assembly (w trybie mixed-mode, który powala na łączenie kodu niezarządzanego i zarządzanego). Cały projekt choć kompilowany jest zwykły projekt C++ ma poustawiane opcje tak, że w rzeczywistości jest mixed mode DLL.

W czym więc problem.

Jako referencje w naszym programie powinniśmy wskazać projekt 1, ale nie zawiera on zarządzanych źródeł z projektów 2 i 3. Tak więc nasze referencje w kodzie nie zostaną rozwiązane. Jeśli wskażemy projekt 2 to podczas uruchomienia dostaniemy błąd. Projekt drugi kompiluje się do System.Data.SQLite.2010.DLL, wykorzystuje natywne funkcje z SQLite.Interop.2010.DLL. Tak więc dostaniemy błąd o braku SQLite.Interop.2010.DLL. Kiedy kod z 2 jako 3 zostanie umieszczony w 1 żadnego błędu nie dostaniemy, wszystko jest w jednej bibliotece i kod zarządzany importuje symbole z niezarządzanej części tej samej DLLki.

Możemy oczywiście dodawać SQLite.Interop.2010.DLL do miejsca gdzie siedzi exe. Musimy to robić w post-build projektu. VS nie pozwala nam dodać referencję do niezarządanego projektu.

Teraz jak to pisze to przychodzi mi na myśl, że być może projekt 1 powinniśmy ustawić na zarządzalny, cały kod objąć klauzulą UNSAFE. Wtedy taki projekt moglibyśmy dodać do projektu z exe.

Jeśli projekt 2 skopiujemy tam gdzie exe w post-build to zawiera on powielony kod z 2. Nie wiem co się stanie jak załadujemy taką dllke, teoretycznie będziemy ją ładować jako niezarządzaną. Takie rozwiązanie wymaga trzech projektów w mojej solucji, których współdziałanie bez analizy będzie wywoływało wiele pytań. Po co 3, po co w dwóch to samo.

Osobiście zdecydowałem się na przerobienie projektów. Projekt 1 zawiera tylko źródła niezarządzalne i dodawany jest do głównego projektu w post-build. Do głównego projektu dodawany jest projekt 2. Nazwa dllki projektu 1 jest taka jakiej poszukuje projekt 2. Przy okazji powłączałem do plików projektów wszystkie includy.

Niby nic ale z uwagi na to jak to wszystko jest poplątane poszedł na to cały dzień. Osobiście nie jestem fanem takiego mieszania w pliku projektu, by mieszać nasz kod z kodem VS. Co najwyżej tu i tam dodać include do naszych elementów i tyle.

Całkiem przyjemną rzeczą byłaby możliwość ustawiania naszych opcji ustawianych w pliku projektu. Jak na razie jedyną możliwością ich zmiany są parametry podczas kompilacji z linii poleceń.

Przy okazji wywaliłem też jakieś bitmapy, zbędne jak mi się wydaje.

Zobaczymy czy będzie działać.

O ile podczas kolejnych wydań nie będą modyfikowane pliki projektów cała aktualizacja na nowy kod powinna przejść gładko.

2012-02-02

How to center control in panel

This solution doesn't depend on any WinForms features like TableLayout, AutoScroll, Anchoring, etc. It requires panel with one control. It reacts on resizing of control and panel resizing. It doesn't change control size. When there is no sufficient place for control then scroll bar appears. Control left, top borders never go outside left, top borders of panel.

public static void CenterControlInPanel(Panel a_panel)
{
    if (a_panel.Controls.Count != 1)
        throw new InvalidOperationException();

    Control control = a_panel.Controls[0];
            
    a_panel.AutoScroll = false;
    a_panel.AutoSize = false;

    HScrollBar horz_bar = new HScrollBar();
    VScrollBar vert_bar = new VScrollBar();

    a_panel.Controls.Add(horz_bar);
    a_panel.Controls.Add(vert_bar);

    horz_bar.Dock = DockStyle.Bottom;
    vert_bar.Dock = DockStyle.Right;

    Panel panel = new Panel();
    a_panel.Controls.Add(panel);
    panel.Dock = DockStyle.Fill;

    a_panel.Controls.Remove(control);
    panel.Controls.Add(control);

    Action on_scroll = () =>
    {
        if (panel.ClientSize.Width < control.Width)
        {
            if (horz_bar.Value > 0)
                control.Left = -horz_bar.Value;
            else
                control.Left = 0;
        }
        else
            control.Left = (panel.ClientSize.Width - control.Width) / 2;


        if (panel.ClientSize.Height < control.Height)
        {
            if (vert_bar.Value > 0)
                control.Top = -vert_bar.Value;
            else
                control.Top = 0;
        }
        else
            control.Top = (panel.ClientSize.Height - control.Height) / 2;

    };

    Action on_resized = () =>
    {
        if (panel.ClientRectangle.Width < control.Width)
            horz_bar.Visible = true;
        else
            horz_bar.Visible = false;

        if (panel.ClientRectangle.Height < control.Height)
            vert_bar.Visible = true;
        else
            vert_bar.Visible = false;

        if (panel.ClientRectangle.Width < control.Width)
            horz_bar.Visible = true;
        else
            horz_bar.Visible = false;

        vert_bar.Minimum = 0;
        vert_bar.Maximum = control.Height;
        vert_bar.LargeChange = panel.ClientRectangle.Height;

        horz_bar.Minimum = 0;
        horz_bar.Maximum = control.Width;
        horz_bar.LargeChange = panel.ClientRectangle.Width;

        if (horz_bar.LargeChange + horz_bar.Value - 1 > control.Width)
            horz_bar.Value = control.Width - horz_bar.LargeChange + 1;
        if (vert_bar.LargeChange + vert_bar.Value - 1 > control.Height)
            vert_bar.Value = control.Height - vert_bar.LargeChange + 1;

        on_scroll();
    };

    panel.Resize += (s, e) => on_resized();
    horz_bar.Scroll += (s, e) => on_scroll();
    vert_bar.Scroll += (s, e) => on_scroll();
    control.Resize += (s, e) => on_resized();

    on_resized();
}

2012-02-01

Sekwencja Hammersley'a

Jest to pewna modyfikacja sekwencji Halton'a. Dla jednego z wymiarów sekwencja ma postać: $\displaystyle\frac{n-0.5}{N}$, gdzie n to numer kolejnej próbki. N to liczba próbek. Sekwencja ta ma mniejszą dyspersję niż sekwencja Haltona, za cenę określenia z góry ilości generowanych próbek.

-0.5 w wzorze zapewnia nam centrowanie punktów na środkach pikseli.

Sekwencja ta degraduje się dla wyższych wymiarów. W przypadku 2D bardzo szybko.

Przykłady:

2


3


4

5


Widzimy, że być może i jest to sekwencja o niskiej dyspersji, ale punkty zdecydowanie układają się w wzór, który jak każda powtarzalność w próbkowaniu podatny jest na aliasing. Widać to też na poniższym periodogramie.

Przykład pierodogramu dla bazy 3:


Kod:

public static class LowDiscrepancyMath
{
    public static double RadicalInverse(int a_n, int a_base)
    {
        double y = 0;
        double b = a_base;
        while (a_n > 0)
        {
            int d_i = a_n % a_base;
            y += d_i / b;
            a_n /= a_base;
            b *= a_base;
        }
        return y;
    }
}

public class HammersleySampler : LowDiscrepancySequenceSampler
{
    private int m_n = 1;
    private List<Vector2>[,] m_samples;
    private int m_count;

    public int BaseY = 3;

    private double NextX()
    {
        return (m_n - 0.5) / m_count;
    }

    public override IEnumerable<Vector2> GetSamples(Rectangle a_rect)
    {
        foreach (var p in a_rect.EnumPixels())
        {
            if (m_samples[p.X, p.Y] == null)
                continue;

            foreach (var s in m_samples[p.X, p.Y])
                yield return s;
        }
    }

    public override SamplerType SamplerType
    {
        get 
        {
            return SamplerType.Hammersley;
        }
    }

    internal override void RenderStart(RenderStartPhase a_phase)
    {
        base.RenderStart(a_phase);

        if (a_phase == RenderStartPhase.PrepareObjectToRender)
            PrepareSamples();
    }

    private void PrepareSamples()
    {
        m_samples = new List<Vector2>[Film.Width, Film.Height];
        m_count = Film.Width * Film.Height * Subresolution * Subresolution;

        for (int i=0; i<m_count; i++)
        {
            Vector2 s = new Vector2(
                NextX() * Film.Width, 
                LowDiscrepancyMath.RadicalInverse(m_n, BaseY) * Film.Height);
            Point p = new Point((int)s.X, (int)s.Y);

            if (m_samples[p.X, p.Y] == null)
                m_samples[p.X, p.Y] = new List<Vector2>();
            m_samples[p.X, p.Y].Add(s);

            m_n++;
        }
    }
}

Superellipsoid

Superellipsoida jest bryłą zamkniętą o powierzchni, która nie zawsze jest ciągła (zależnie od parametrów).

Równanie Superellipsoidy w postatci uwikłanej:

$ \left( \left|x\right|^{R} + \left|y\right|^{R} \right)^{\frac{T}{R}} + \left|z\right|^{T} = 1$

, gdzie $T \in \mathbb{R}$ i $R \in \mathbb{R}$

Bryła jest zamknięta w sześcianie (-1, 1). Bryła jest symetryczna względem płaszczyzn układu współrzędnych. Punkt znajduje się we wnętrzu bryły jeśli:

$ \left( \left|x\right|^{R} + \left|y\right|^{R} \right)^{\frac{T}{R}} + \left|z\right|^{T} < 1$

Gradient z funkcji uwikłanej definiującej powierzchnię:

$\displaystyle\frac{df(x,y,z)}{dx} = T\cdot\left(\left|x\right|^R+\left|y\right|^R\right)^{\frac{T}{R}-1}\cdot sign(x) \cdot \left|x\right|^{R-1}$

$\displaystyle\frac{df(x,y,z)}{dx} = T\cdot\left(\left|x\right|^R+\left|y\right|^R\right)^{\frac{T}{R}-1}\cdot sign(x) \cdot \left|x\right|^{R-1}$

$\displaystyle\frac{df(x,y,z)}{dx} = T\cdot\left(\left|x\right|^R+\left|y\right|^R\right)^{\frac{T}{R}-1}\cdot sign(y) \cdot \left|y\right|^{R-1}$


$\displaystyle\frac{df(x,y,z)}{dx} = T \cdot sign(z) \cdot \left|z\right|^{T-1}$

Gradient dla punktu powierzchni bryły jest równy normalnej w tym punkcie.

Równanie parametryczne ma postać:

$\begin{align} x(u,v) &{}= A c\left(v,\frac{2}{t}\right) c\left(u,\frac{2}{r}\right) \\ y(u,v) &{}= B c\left(v,\frac{2}{t}\right) s\left(u,\frac{2}{r}\right) \\ z(u,v) &{}= C s\left(v,\frac{2}{t}\right) \\ & -\pi/2 \le v \le \pi/2, \quad -\pi \le u < \pi ,\end{align}$

,gdzie:

$\begin{align} c(\omega,m) &{}= \text{sign}(\cos \omega) |\cos \omega|^m \\ s(\omega,m) &{}= \text{sign}(\sin \omega) |\sin \omega|^m\end{align}$

Kod generujący z tego równania siatkę w Matlabie:

function [ ] = plot_se( )

R = 2.3;
T = 0.5;
scale = [1 1 1];

n = 200;
etamax = pi / 2;
etamin = -pi / 2;
wmax = pi;
wmin = -pi;
delta = (etamax - etamin) / n;
dw = (wmax - wmin) / n;
[i,j] = meshgrid(1:n+1, 1:n+1);
eta = etamin + (i-1) * delta;
w = wmin + (j-1) * dw;
x = scale(1) .* sign(cos(eta)) .* abs(cos(eta)).^(2/T) .* ...
sign(cos(w)) .* abs(cos(w)).^(2/R);
y = scale(2) .* sign(cos(eta)) .* abs(cos(eta)).^(2/T) .* ...
sign(sin(w)) .* abs(sin(w)).^(2/R);
z = scale(3) .* sign(sin(eta)) .* abs(sin(eta)).^(2/T);

z = z - 2;

meshc(x,y,z);
title(sprintf('R: %.1f, T: %.1f', R, T));

end


Przykłady:



Cały kod jest podobny do kodu dla SuperQuadric. Zmieniamy tylko zawartość funkcji Norm i Gradient i zmieniay kod tak by zamiast Exponent wykorzystywane był dwie zmienne T i R.

Kod:

public class SuperellipsoidObject : RenderableObject
{
    private RootFinder m_root_finder;
    private double m_R = 1;
    private double m_T = 1;
    private double m_TR = 1;

    private double Norm(Vector3 a_point)
    {
        return Math.Pow(Math.Pow(Math.Abs(a_point.X), R) + 
            Math.Pow(Math.Abs(a_point.Y), R), TR) + 
            Math.Pow(Math.Abs(a_point.Z), T);
    }

    [YAXNode]
    public double R
    {
        get
        {
            return m_R;
        }
        set
        {
            m_R = value;
            m_TR = T / R;
        }
    }

    [YAXNode]
    public double T
    {
        get
        {
            return m_T;
        }
        set
        {
            m_T = value;
            m_TR = T / R;
        }
    }

    private double TR
    {
        get
        {
            return m_TR;
        }
    }

    public SuperellipsoidObject(Vector3 a_right, Vector3 a_up) :
        base(a_right, a_up)
    {
        Name = "Superellipsoid";
        Closed = true;
        m_uv_mapper = new SphericalUVMapper();
        Update(UpdateFlags.All);
    }

    private Vector3 Gradient(Vector3 a_point)
    {
        double ax = Math.Abs(a_point.X);
        double ay = Math.Abs(a_point.Y);
        double xR1 = Math.Pow(ax, R - 1);
        double yR1 = Math.Pow(ay, R - 1);
        double xryr = Math.Pow(xR1 * ax + yR1 * ay, TR - 1);

        return new Vector3(
            xryr * Math.Sign(a_point.X) * T * xR1,
            xryr * Math.Sign(a_point.Y) * T * yR1, 
            Math.Sign(a_point.Z) * T * Math.Pow(Math.Abs(a_point.Z), T - 1));   
    }

    private double GetDistToNearestAxisOrOneOneBoxPlaneAndInsideOneOneBox(
        Vector3 a_ray_start, Vector3 a_ray_dir)
    {
        double dist_min = Double.PositiveInfinity;

        double dist = -(a_ray_start.Z / a_ray_dir.Z);
        if (dist &gt; 0)
        {
            Vector3 p = (a_ray_start + a_ray_dir * dist);
            if (p.Abs() &lt;= Vector3.ONE)
                dist_min = dist;
        }

        dist = -(a_ray_start.X / a_ray_dir.X);
        if ((dist &gt; 0) &amp;&amp; (dist &lt; dist_min))
        {
            Vector3 p = (a_ray_start + a_ray_dir * dist);
            if (p.Abs() &lt;= Vector3.ONE)
                dist_min = dist;
        }

        dist = -(a_ray_start.Y / a_ray_dir.Y);
        if ((dist &gt; 0) &amp;&amp; (dist &lt; dist_min))
        {
            Vector3 p = (a_ray_start + a_ray_dir * dist);
            if (p.Abs() &lt;= Vector3.ONE)
                dist_min = dist;
        }

        dist = Plane.GetDist(a_ray_start, a_ray_dir, Vector3.XAXIS, Vector3.XAXIS);
        if ((dist &gt; 0) &amp;&amp; (dist &lt; dist_min))
        {
            Vector3 p = (a_ray_start + a_ray_dir * dist);
            if (p.Abs().IsAlmostLessThen(Vector3.ONE))
                dist_min = dist;
        }

        dist = Plane.GetDist(a_ray_start, a_ray_dir, Vector3.XAXIS, -Vector3.XAXIS);
        if ((dist &gt; 0) &amp;&amp; (dist &lt; dist_min))
        {
            Vector3 p = (a_ray_start + a_ray_dir * dist);
            if (p.Abs().IsAlmostLessThen(Vector3.ONE))
                dist_min = dist;
        }

        dist = Plane.GetDist(a_ray_start, a_ray_dir, Vector3.YAXIS, Vector3.YAXIS);
        if ((dist &gt; 0) &amp;&amp; (dist &lt; dist_min))
        {
            Vector3 p = (a_ray_start + a_ray_dir * dist);
            if (p.Abs().IsAlmostLessThen(Vector3.ONE))
                dist_min = dist;
        }

        dist = Plane.GetDist(a_ray_start, a_ray_dir, Vector3.YAXIS, -Vector3.YAXIS);
        if ((dist &gt; 0) &amp;&amp; (dist &lt; dist_min))
        {
            Vector3 p = (a_ray_start + a_ray_dir * dist);
            if (p.Abs().IsAlmostLessThen(Vector3.ONE))
                dist_min = dist;
        }

        dist = Plane.GetDist(a_ray_start, a_ray_dir, Vector3.ZAXIS, Vector3.ZAXIS);
        if ((dist &gt; 0) &amp;&amp; (dist &lt; dist_min))
        {
            Vector3 p = (a_ray_start + a_ray_dir * dist);
            if (p.Abs().IsAlmostLessThen(Vector3.ONE))
                dist_min = dist;
        }

        dist = Plane.GetDist(a_ray_start, a_ray_dir, Vector3.ZAXIS, -Vector3.ZAXIS);
        if ((dist &gt; 0) &amp;&amp; (dist &lt; dist_min))
        {
            Vector3 p = (a_ray_start + a_ray_dir * dist);
            if (p.Abs().IsAlmostLessThen(Vector3.ONE))
                dist_min = dist;
        }

        return dist_min;
    }

    private double GetMinimalDistanceFromRayToSurface(Vector3 a_ray_start, 
        Vector3 a_ray_dir, double a_a, double a_b)
    {
        bool start_inside = Inside(a_ray_start);
        bool inside = start_inside;
        double min_t = Double.PositiveInfinity;

        for (int i = 0; i &lt; Constants.ROOT_FINDER_MAXIMUM_ITERATIONS; i++)
        {
            if (Math.Abs(a_a - a_b) &lt; Constants.ROOT_FINDER_ABSOLUTE_ERROR)
                break;

            min_t = (a_a + a_b) / 2;
            Vector3 point = a_ray_start + a_ray_dir * min_t;
            Vector3 mid_grad = Gradient(point);

            double norm = Norm(point);

            if (start_inside)
            {
                if (norm &gt; 1)
                {
                    inside = false;
                    break;
                }
            }
            else
            {
                if (norm &lt; 1)
                {
                    inside = true;
                    break;
                }
            }

            double mid_dot = mid_grad * a_ray_dir;

            if (mid_dot &lt; 0)
                a_a = min_t;
            else
                a_b = min_t;
        }

        if (inside == start_inside)
            return Double.PositiveInfinity;

        return min_t;
    }

    private bool Inside(Vector3 a_local_point)
    {
        return !Norm(a_local_point).IsAlmostGreaterThen(1);
    }

    internal override Intersection GetIntersection(
        Intersection a_source_ray_intersection, Ray a_ray)
    {
        Vector3 local_dir;
        Vector3 local_start;
        double near_t;
        double far_t;

        TransformToLocal(a_ray, out local_dir, out local_start);

        if (!GetLocalBoundBox().GetIntersections(
            local_start, local_dir, out near_t, out far_t))
        {
            return Scene.NoIntersection;
        }

        Func<double, double=""> f = (t) =&gt; Norm(local_start + local_dir * t) - 1;

        bool back_hit = false;

        if (near_t &lt; 0)
        {
            if (Inside(local_start))
                back_hit = true;
            if (Norm(local_start).IsAlmostEquals(1))
            {
                Vector3 normal = -Gradient(local_start).Normalized;
                if (normal * local_dir &gt; 0)
                    back_hit = true;

                local_start = local_start + local_dir * Constants.MINIMAL_DISTANT;
                far_t -= Constants.MINIMAL_DISTANT;
                near_t -= Constants.MINIMAL_DISTANT;
            }
        }

        if (back_hit &amp;&amp; OneSide)
            return Scene.NoIntersection;

        if (near_t &gt; 0)
        {
            local_start = local_start + local_dir * 
                (near_t - Constants.MINIMAL_DISTANT * 2);
            far_t = far_t - (near_t - Constants.MINIMAL_DISTANT * 2);
            near_t = Constants.MINIMAL_DISTANT * 2;
        }

        double local_dist = Double.PositiveInfinity;

        for (; ; )
        {
            double plane_t = GetDistToNearestAxisOrOneOneBoxPlaneAndInsideOneOneBox(
                local_start, local_dir);

            if (plane_t == Double.PositiveInfinity)
                break;

            double min_t = GetMinimalDistanceFromRayToSurface(
                local_start, local_dir, 0, plane_t);

            if (min_t != Double.PositiveInfinity)
            {
                local_dist = m_root_finder.FindRoot(0, min_t, f);

                if (local_dist == Double.PositiveInfinity)
                    local_dist = m_root_finder.FindRoot(min_t, plane_t, f);
            }
            else
                local_dist = m_root_finder.FindRoot(0, plane_t, f);

            if (local_dist != Double.PositiveInfinity)
                break;

            local_start = local_start + local_dir * plane_t;
            far_t -= plane_t;
        }

        if (local_dist == Double.PositiveInfinity)
            return Scene.NoIntersection;

        Vector3 local_pos = local_start + local_dir * local_dist;
        Vector3 world_pos = LocalToWorld * local_pos;
        double world_dist = (world_pos - a_ray.Start).Length;

        Vector3 local_norm = -Gradient(local_pos).Normalized;

        Intersection intersection = new Intersection()
        {
            PrevIntersection = a_source_ray_intersection,
            SceneObject = this,
            SourceRay = a_ray,
            LocalPos = local_pos,
            Dist = world_dist,
            Scene = Scene,
            BackHit = back_hit,
            Pos = world_pos
        };

        // Numerical errors.
        if (intersection.Normal * intersection.SourceRay.Dir &gt;= 0)
            return Scene.NoIntersection;

        return intersection;
    }

    protected override AABB GetLocalBoundBox()
    {
        return new AABB(-Vector3.ONE, Vector3.ONE);
    }

    internal override Vector3 GetNormal(Intersection a_intersection)
    {
        if (a_intersection.BackHit)
        {
            return (LocalToWorldNormal * 
                -Gradient(a_intersection.LocalPos)).Normalized;
        }
        else
        {
            return (LocalToWorldNormal * 
                Gradient(a_intersection.LocalPos)).Normalized;
        }
    }

    internal override Vector3 GetUVW(Intersection a_intersection)
    {
        return base.GetUVW(a_intersection) * 0.5 + Vector3.HALF;
    }

    public override string ToString()
    {
        return String.Format("Superellipsoid: {0}, R: {1}, T: {2}", Name, R, T);
    }

    internal override void GetTangents(Intersection a_intersection, 
        out Vector3 a_tangent_x, out Vector3 a_tangent_y)
    {
        a_tangent_x = Vector3.CrossProduct(Up, a_intersection.Pos).Normalized;

        if (a_intersection.BackHit)
        {
            a_tangent_y = Vector3.CrossProduct(a_intersection.Normal, 
                a_tangent_x).Normalized;
        }
        else
        {
            a_tangent_y = Vector3.CrossProduct(a_tangent_x, 
                a_intersection.Normal).Normalized;
        }
    }

    protected override void RenderStart(RenderStartPhase a_phase)
    {
        base.RenderStart(a_phase);

        if (a_phase == RenderStartPhase.PrepareObjectToRender)
        {
            m_root_finder = RootFinder.Create(
                Scene.RenderOptions.RootFindingMethod);
        }
    }

    public override void ScaleAbsolute(double a_scale)
    {
        Scale *= a_scale;

        base.ScaleAbsolute(a_scale);
    }
}

Przykłady:

R=2.3, T=0.5


R=0.5, T=0.5

2012-01-30

Sekwencje Van Der Corput'a i Halton'a

W kolejnych postach zostanie omówionych kilka sekwencji, użytecznych dla samplowania. Wszystkie są quasi-losowe, czyli nie losowe w sensie pseudo-losowe, ale powtarzalne, wykazujące pewien wzór, ale wyglądające na losowe. Wszystkie sekwencje generują liczby z zakresu $<0, 1)$. Punkty sekwencji są w miarę równomiernie od siebie oddalone i w miarę równomiernie wypełniają przestrzeń (niska dyspersja). Warunek ten jest spełniony po wygenerowaniu dowolnych n wyrazów sekwenji. Większość sekwencji liczona jest wprost, jedynym parametrem jest numer wyrazu sekwencji.

Pierwszą z sekwencji jest sekwencja Van Der Corput'a. Parametrem do generacji serii punktów jest Base - podstawa systemu liczbowego dla której będzie generowana sekwencja, czyli powinna być większa od 1 (2 dla binarnego, 10 dla dziesiętnego).

Sekwencja wielowymiarowe, których bazy są liczbami relatywnie pierwszymi dla siebie to sekwencja Haltona.

Parametrem do generacji poszczególnych liczb w sekwencji jest $n \in {1,2,3,4,...}$. Dla sekwencji wielowymiarowych dla każdego wymiaru powinniśmy użyć inną bazę by uniknąć degradacji. Poza tym by uniknąć degradacji bazy sekwencji wielowymiarowych nie powinny mieć wspólnych podzielników - powinny być liczbami relatywnie pierwszymi (coprime).

Generowanie sekwencji opisowo wygląda tak. Weźmy kolejną liczbę dodatnią N. Potraktujmy ją jako liczbę w systemie o zadanej bazie $abcd_{BASE}$. Odpowiednik w sekwencji ma postać: $\frac{d}{BASE}+\frac{c}{BASE^2}+\frac{b}{BASE^3}+\frac{a}{BASE^4}$. Np. dla systemu dziesiętnego: $214 \rightarrow 0.412$.

Kod:

public static class LowDiscrepancyMath
{
    public static double RadicalInverse(int a_n, int a_base)
    {
        double y = 0;
        double b = a_base;
        while (a_n > 0)
        {
            int d_i = a_n % a_base;
            y += d_i / b;
            a_n /= a_base;
            b *= a_base;
        }
        return y;
    }  
}

public abstract class LowDiscrepancySequenceSampler : NonUniformSampler
{
    public int Subresolution = 3;

    public override IEnumerable<Vector2> GetSamples(Rectangle a_rect)
    {
        int samples_x = a_rect.Width * Subresolution;
        int samples_y = a_rect.Height * Subresolution;

        for (int y = 0; y < samples_y; y++)
        {
            for (int x = 0; x < samples_x; x++)
            {
                Vector2 sample = NextSample();
                yield return new Vector2(
                    a_rect.Left + sample.X * a_rect.Width,
                    a_rect.Top + sample.Y * a_rect.Height);
            }
        }
    }

    protected abstract Vector2 NextSample();
}

public class HaltonSampler : LowDiscrepancySequenceSampler
{
    protected int m_n = 1;

    public int BaseX = 2;
    public int BaseY = 3;

    protected override Vector2 NextSample()
    {
        int n = m_n;
        m_n++;

        return new Vector2(
            LowDiscrepancyMath.RadicalInverse(n, BaseX),
            LowDiscrepancyMath.RadicalInverse(n, BaseY));
    }

    public override SamplerType SamplerType
    {
        get
        {
            return SamplerType.Halton;
        }
    }
}

Przykłady sekwencji:

(2,3)


(2,4)


(2,5)


Poniższy przykład zakłada sekwencję, w której na każdy piksel przypada jedna próbka. W przypadku samplowania typu Grid byłby to biały prostokąt. Tutaj możemy zauważyć jak dla tej sekwencji co pewną ilość punktów następuje duża zmiana w rozkładzie próbek utrzymująca się przez pewien czas.


Periodogram dla (2,3):


Widać na nim pewną powtarzalność i tym samym podatność na aliasing.

Przybliżenie wartości pochodnej

Dla niektórych metod poszukiwania miejsc zerowych potrzebujemy wartości pochodnych, z reguły 1, 2 i 3 rzędu. Samą badaną funkcję nie zawsze mamy daną wzorem analitycznym by te pochodne policzyć, czasami jest to zbyt kłopotliwe. Wtedy nie pozostaje nam nic innego jak przybliżyć wartość pochodnej korzystając z wzoru:

$f'(x_{0}) = \displaystyle \lim _{h\to 0}\frac{f(x_{0}+h) - f(x_{0})}{h}$

Wartość drugiej otrzymamy poprzez podstawienie do podanego wzoru wartości pierwszej pochodnej.

Możemy wyróżnić trzy metody doboru przedziału. Centralnie wokół punktu x, na prawo od punktu x i na lewo od punktu x:

$\delta_h[f](x) = f(x+\tfrac12h)-f(x-\tfrac12h)$

$\nabla_h[f](x) = f(x) - f(x-h)$

$\delta_h[f](x) = f(x+\tfrac12h)-f(x-\tfrac12h)$

Ja skorzystałem z metody pierwszej. Kod na pierwszą i drugą pochodną jest następujący:

public class CentralDifferenceMethod
{
    private double m_h;
    private Func<double, double> m_func;

    public CentralDifferenceMethod()
        : base()
    {
    }
        
    public CentralDifferenceMethod(Func<double, double> a_func, 
        double a_h = Constants.DOUBLE_PRECISION * 2)
    {
        m_h = a_h;
        m_func = a_func;
    }

    public double Derive1(double a_x)
    {
        return (m_func(a_x + m_h) - m_func(a_x - m_h)) / (2 * m_h);
    }

    public double Derive2(double a_x)
    {
        return (m_func(a_x + m_h) - 2 * m_func(a_x) + 
            m_func(a_x - m_h)) / (m_h * m_h);
    }

    public static  Func<double, double> Derive1(
        Func<double, double> a_func, double a_h)
    {
        CentralDifferenceMethod cdm = 
            new CentralDifferenceMethod(a_func, a_h);

        return (x) =>
        {
            return cdm.Derive1(x);
        };
    }

    public static Func<double, double> Derive2(
        Func<double, double> a_func, double a_h)
    {
        CentralDifferenceMethod cdm = 
            new CentralDifferenceMethod(a_func, a_h);

        return (x) =>
        {
            return cdm.Derive2(x);
        };
    }

    public static Func<double, double> Derive2(
        Func<double, double> a_func, 
        Func<double, double> a_d1, double a_h)
    {
        CentralDifferenceMethod cdm = 
            new CentralDifferenceMethod(a_d1, a_h);

        return (x) =>
        {
            return cdm.Derive1(x);
        };
    }
}

Zauważmy, że pierwsza pochodna wymaga dwóch punktów, druga trzech, itd.

Superquadric

Superquadric to rodzaj bryły, której powierzchnia nie jest gładka, zamkniętej w przedziale (-1,1). Równanie jej powierzchni:

$\left|x\right|^{\displaystyle r }+ \left|y\right|^{\displaystyle s} + \left|z\right|^{\displaystyle t}=1$

Wektor (r,s,t) definiuje nam kształt bryły. Generalnie eksponenta < 1 to wklęsłość, e = 1 to linia prosta. e = 2 to sfera, dla dużych e bryła przybliża się do sześcianu. Jeśli $ \left|x\right|^r + \left|y\right|^s + \left|z\right|^t < 1$ to punkt znajduje się wewnątrz bryły. Bryła jest zawsze symetryczna wobec płaszczyzn układu współrzędnych. Równanie parametryczne powierzchni:

$ \begin{align} x(u,v) &{}= A c\left(v,\frac{2}{r}\right) c\left(u,\frac{2}{r}\right) \\ y(u,v) &{}= B c\left(v,\frac{2}{s}\right) s\left(u,\frac{2}{s}\right) \\ z(u,v) &{}= C s\left(v,\frac{2}{t}\right) \\ & -\frac{\pi}{2} \le v \le \frac{\pi}{2}, \quad -\pi \le u < \pi ,\end{align} $

, gdzie:

$\begin{align} c(\omega,m) &{}= \text{sign}(\cos \omega) |\cos \omega|^m \\ s(\omega,m) &{}= \text{sign}(\sin \omega) |\sin \omega|^m\end{align}$

Kod Matlaba generujący siatkę z równania parametrycznego:
function [ ] = plot_sq(  )

  exponent = [ 0.5, 4, 1]; 
  scale = [1 1 1]; 

  n = 50;
  etamax = pi / 2;
  etamin = -pi / 2;
  wmax = pi;
  wmin = -pi;
  delta = (etamax - etamin) / n;
  dw = (wmax - wmin) / n;
  [i,j] = meshgrid(1:n+1, 1:n+1);
  eta = etamin + (i-1) * delta;
  w = wmin + (j-1) * dw;
  x = scale(1) .* sign(cos(eta)) .* abs(cos(eta)).^(2/exponent(1)) .* ... 
      sign(cos(w)) .* abs(cos(w)).^(2/exponent(1));
  y = scale(2) .* sign(cos(eta)) .* abs(cos(eta)).^(2/exponent(2)) .* ... 
      sign(sin(w)) .* abs(sin(w)).^(2/exponent(2));
  z = scale(3) .* sign(sin(eta)) .* abs(sin(eta)).^(2/exponent(3));
  
  z = z - 2;
  
  meshc(x,y,z);
 
end

Przykłady:

(0.5, 4, 1)


(0.5, 400, 0.5)


(1, 1, 0.5)


Najwięcej czasu zajęło mi napisanie prawidłowego kodu na intersekcję. Po pierwsze korzystamy z faktu, że nasza płaszczyzny układu współrzędnych oraz płaszczyzny BoundBox-a dzielą bryłę na 8 ćwiartek. Dla każdej ćwiartki znajdujemy miejsce zerowe osobno. W razie jego braku przemieszczamy się do następnej ćwiartki. Robimy tak długo aż opuścimy BoundBox (3 razy maksymalnie). Tym sposobem ograniczamy możliwość znalezienia nie tego miejsca zerowego co trzeba. Ale ciągle jeśli np. ćwiartka jest wypukła lub wkłęsła promień może ją przeciąć w dwóch miejscach i RootFinder, który znajduje jedno z miejsc zerowych i nie jest powiedziane, które to będzie, znajdzie to drugie, dalsze. Musimy znowu podzielić naszą przestrzeń poszukiwań. Służy do tego funkcja GetMinimalDistanceFromRayToSurface. Jest to trochę zmodyfikowany BisectionRootFinder. Poszukujemy punktu w którym promień jest najbliżej powierzchni bryły. Poszukujemy w niej minimum iloczynu skalarnego normalnej Superquadric (liczonej poza powierzchnią bryły wzdłuż promienia) i kierunku promienia. W miejscu gdzie te dwa wektory są prostopadłe, czyli czubku wypukłości iloczyn ten wynosi zero. Równocześnie na bieżąco w każdej iteracji sprawdzamy, czy czasem nie przeszliśmy przez powierzchnię bryły, co może się zdarzyć jako że zmierzamy do punktu wypukłości. Jeśli tak przerywamy iterację, gdyż mamy zakres do poszukiwań w którym promień przechodzi przez bryłę. No i ostatnią modyfikacją by uniknąć detekcji uderzenia w punkt wyjścia było przesunięcie minimalnego dystansu z zero na Constants.MINIMAL_DISTANT.

Gradient dla punktu poza powierzchnią bryły to taki wektor, że prosta będąca jego przedłużeniem przetnie powierzchnię pod kątem prostym. Wektor gradientu jest w punkcie przecięcia prostej normalną powierzchni.

Jak na mój gust cały kod chyba nie robi błędów. Jak promień nie przecinałby ćwiartki odległość promienia od powierzchni będzie funkcją z jednym ekstremum. Tak mi się wydaje....

Kod klasy:

public class SuperquadricObject : RenderableObject
{
    private RootFinder m_root_finder;

    [YAXNode]
    public Vector3 Exponents = new Vector3(1, 1, 1);

    public SuperquadricObject(Vector3 a_right, Vector3 a_up) :
        base(a_right, a_up)
    {
        Name = "Superquadric";
        Closed = true;
        m_uv_mapper = new SphericalUVMapper();
        Update(UpdateFlags.All);
    }

    private double Norm(Vector3 a_point)
    {
        return Math.Pow(Math.Abs(a_point.X), Exponents.X) +
                Math.Pow(Math.Abs(a_point.Y), Exponents.Y) +
                Math.Pow(Math.Abs(a_point.Z), Exponents.Z);
    }

    private Vector3 Gradient(Vector3 a_point)
    {
        return new Vector3(
            Exponents.X * Math.Sign(a_point.X) * 
                Math.Pow(Math.Abs(a_point.X), Exponents.X - 1),
            Exponents.Y * Math.Sign(a_point.Y) * 
                Math.Pow(Math.Abs(a_point.Y), Exponents.Y - 1),
            Exponents.Z * Math.Sign(a_point.Z) * 
                Math.Pow(Math.Abs(a_point.Z), Exponents.Z - 1));
    }

    private double GetDistToNearestAxisOrOneOneBoxPlaneAndInsideOneOneBox(
        Vector3 a_ray_start, Vector3 a_ray_dir)
    {
        double dist_min = Double.PositiveInfinity;

        double dist = -(a_ray_start.Z / a_ray_dir.Z);
        if (dist > 0)
        {
            Vector3 p = (a_ray_start + a_ray_dir * dist);
            if (p.Abs() <= Vector3.ONE)
                dist_min = dist;
        }

        dist = -(a_ray_start.X / a_ray_dir.X);
        if ((dist > 0) && (dist < dist_min))
        {
            Vector3 p = (a_ray_start + a_ray_dir * dist);
            if (p.Abs() <= Vector3.ONE)
                dist_min = dist;
        }

        dist = -(a_ray_start.Y / a_ray_dir.Y);
        if ((dist > 0) && (dist < dist_min))
        {
            Vector3 p = (a_ray_start + a_ray_dir * dist);
            if (p.Abs() <= Vector3.ONE)
                dist_min = dist;
        }

        dist = Plane.GetDist(a_ray_start, a_ray_dir, Vector3.XAXIS, Vector3.XAXIS);
        if ((dist > 0) && (dist < dist_min))
        {
            Vector3 p = (a_ray_start + a_ray_dir * dist);
            if (p.Abs().IsAlmostLessThen(Vector3.ONE))
                dist_min = dist;
        }

        dist = Plane.GetDist(a_ray_start, a_ray_dir, Vector3.XAXIS, -Vector3.XAXIS);
        if ((dist > 0) && (dist < dist_min))
        {
            Vector3 p = (a_ray_start + a_ray_dir * dist);
            if (p.Abs().IsAlmostLessThen(Vector3.ONE))
                dist_min = dist;
        }

        dist = Plane.GetDist(a_ray_start, a_ray_dir, Vector3.YAXIS, Vector3.YAXIS);
        if ((dist > 0) && (dist < dist_min))
        {
            Vector3 p = (a_ray_start + a_ray_dir * dist);
            if (p.Abs().IsAlmostLessThen(Vector3.ONE))
                dist_min = dist;
        }

        dist = Plane.GetDist(a_ray_start, a_ray_dir, Vector3.YAXIS, -Vector3.YAXIS);
        if ((dist > 0) && (dist < dist_min))
        {
            Vector3 p = (a_ray_start + a_ray_dir * dist);
            if (p.Abs().IsAlmostLessThen(Vector3.ONE))
                dist_min = dist;
        }

        dist = Plane.GetDist(a_ray_start, a_ray_dir, Vector3.ZAXIS, Vector3.ZAXIS);
        if ((dist > 0) && (dist < dist_min))
        {
            Vector3 p = (a_ray_start + a_ray_dir * dist);
            if (p.Abs().IsAlmostLessThen(Vector3.ONE))
                dist_min = dist;
        }

        dist = Plane.GetDist(a_ray_start, a_ray_dir, Vector3.ZAXIS, -Vector3.ZAXIS);
        if ((dist > 0) && (dist < dist_min))
        {
            Vector3 p = (a_ray_start + a_ray_dir * dist);
            if (p.Abs().IsAlmostLessThen(Vector3.ONE))
                dist_min = dist;
        }

        return dist_min;
    }

    private double GetMinimalDistanceFromRayToSurface(
        Vector3 a_ray_start, Vector3 a_ray_dir,
        double a_a, double a_b)
    {
        bool start_inside = Inside(a_ray_start);
        bool inside = start_inside;
        double min_t = Double.PositiveInfinity;

        for (int i = 0; i < Constants.ROOT_FINDER_MAXIMUM_ITERATIONS; i++)
        {
            if (Math.Abs(a_a - a_b) < Constants.ROOT_FINDER_ABSOLUTE_ERROR)
                break;

            min_t = (a_a + a_b) / 2;
            Vector3 point = a_ray_start + a_ray_dir * min_t;
            Vector3 mid_grad = Gradient(point);

            // Norm(point)
            double norm = (mid_grad * (point / Exponents)); 

            if (start_inside)
            {
                if (norm > 1)
                {
                    inside = false;
                    break;
                }
            }
            else
            {
                if (norm < 1)
                {
                    inside = true;
                    break;
                }
            }

            double mid_dot = mid_grad * a_ray_dir;

            if (mid_dot < 0)
                a_a = min_t;
            else
                a_b = min_t;
        }

        if (inside == start_inside)
            return Double.PositiveInfinity;

        return min_t;
    }

    private bool Inside(Vector3 a_local_point)
    {
        return !Norm(a_local_point).IsAlmostGreaterThen(1);
    }

    internal override Intersection GetIntersection(
        Intersection a_source_ray_intersection, Ray a_ray)
    {
        Vector3 local_dir;
        Vector3 local_start;
        double near_t;
        double far_t;

        TransformToLocal(a_ray, out local_dir, out local_start);

        if (!GetLocalBoundBox().GetIntersections(local_start, 
            local_dir, out near_t, out far_t))
        {
            return Scene.NoIntersection;
        }

        Func<double, double> f = (t) => Norm(local_start + local_dir * t) - 1;

        bool back_hit = false;

        if (near_t < 0)
        {
            if (Inside(local_start))
                back_hit = true;
            if (Norm(local_start).IsAlmostEquals(1))
            {
                Vector3 normal = -Gradient(local_start).Normalized;
                if (normal * local_dir > 0)
                    back_hit = true;

                local_start = local_start + local_dir * Constants.MINIMAL_DISTANT;
                far_t -= Constants.MINIMAL_DISTANT;
                near_t -= Constants.MINIMAL_DISTANT;
            }
        }

        if (back_hit && OneSide)
            return Scene.NoIntersection;

        if (near_t > 0)
        {
            local_start = local_start + local_dir * 
                (near_t - Constants.MINIMAL_DISTANT * 2);
            far_t = far_t - (near_t - Constants.MINIMAL_DISTANT * 2);
            near_t = Constants.MINIMAL_DISTANT * 2;
        }

        double local_dist = Double.PositiveInfinity;

        for (; ; )
        {
            double plane_t = 
                GetDistToNearestAxisOrOneOneBoxPlaneAndInsideOneOneBox(
                    local_start, local_dir);

            if (plane_t == Double.PositiveInfinity)
                break;

            double min_t = 
                GetMinimalDistanceFromRayToSurface(
                    local_start, local_dir, 0, plane_t);

            if (min_t != Double.PositiveInfinity)
            {
                local_dist = m_root_finder.FindRoot(0, min_t, f);

                if (local_dist == Double.PositiveInfinity)
                    local_dist = m_root_finder.FindRoot(min_t, plane_t, f);
            }
            else
                local_dist = m_root_finder.FindRoot(0, plane_t, f);

            if (local_dist != Double.PositiveInfinity)
                break;

            local_start = local_start + local_dir * plane_t;
            far_t -= plane_t;
        }

        if (local_dist == Double.PositiveInfinity)
            return Scene.NoIntersection;

        Vector3 local_pos = local_start + local_dir * local_dist;
        Vector3 world_pos = LocalToWorld * local_pos;
        double world_dist = (world_pos - a_ray.Start).Length;

        Vector3 local_norm = -Gradient(local_pos).Normalized;

        Intersection intersection = new Intersection()
        {
            PrevIntersection = a_source_ray_intersection,
            SceneObject = this,
            SourceRay = a_ray,
            LocalPos = local_pos,
            Dist = world_dist,
            Scene = Scene,
            BackHit = back_hit,
            Pos = world_pos
        };

        // Numerical errors.
        if (intersection.Normal * intersection.SourceRay.Dir >= 0)
            return Scene.NoIntersection;

        return intersection;
    }

    protected override AABB GetLocalBoundBox()
    {
        return new AABB(-Vector3.ONE, Vector3.ONE);
    }

    internal override Vector3 GetNormal(Intersection a_intersection)
    {
        if (a_intersection.BackHit)
        {
            return (LocalToWorldNormal *          
                -Gradient(a_intersection.LocalPos)).Normalized;
        }
        else
        {
            return (LocalToWorldNormal * 
                Gradient(a_intersection.LocalPos)).Normalized;
        }
    }

    internal override Vector3 GetUVW(Intersection a_intersection)
    {
        return base.GetUVW(a_intersection) * 0.5 + Vector3.HALF;
    }

    public override string ToString()
    {
        return String.Format("Superquadric: {0}", Name, Exponents);
    }

    internal override void GetTangents(Intersection a_intersection, 
        out Vector3 a_tangent_x, out Vector3 a_tangent_y)
    {
        a_tangent_x = Vector3.CrossProduct(Up, a_intersection.Pos).Normalized;

        if (a_intersection.BackHit)
        {
            a_tangent_y = Vector3.CrossProduct(a_intersection.Normal, 
                a_tangent_x).Normalized;
        }
        else
        {
            a_tangent_y = Vector3.CrossProduct(a_tangent_x, 
                a_intersection.Normal).Normalized;
        }
    }

    protected override void RenderStart(RenderStartPhase a_phase)
    {
        base.RenderStart(a_phase);

        if (a_phase == RenderStartPhase.PrepareObjectToRender)
        {
            m_root_finder = RootFinder.Create(
                Scene.RenderOptions.RootFindingMethod);
        }
    }

    public override void ScaleAbsolute(double a_scale)
    {
        Scale *= a_scale;

        base.ScaleAbsolute(a_scale);
    }
}

Dla przyspieszenia obliczeń, zrezygnowałem z detekcji intersekcji bryły z AABB w współrzędnych świata, zamiast tego robię to z AABB w układzie lokalnym, który dużo lepiej opina bryłę. Cały kod przyspiesza o jakieś 25% zależnie od położenia Superquadric.

Normalna liczona jest z definicji gradientu.

Mam nadzieję, że reszta kodu jest jasna. Także dla mnie. Co okaże się za jakieś pół roku, gdy trzeba będzie do tego wrócić.

Przykłady wygenerowane przy użyciu powyższego kodu:

(4.4, 4.2, 0.5)


(0.5, 0.5, 0.5)


(2.3, 0.5, 1)

2012-01-27

Skalowanie obrazu z wykorzystaniem filtrów.

Skalowanie w dwóch wymiarach możemy zrealizować jako superpozycję skalowania w poszczególnych wymiarach osobno. Np. najpierw w poziomie, a później w pionie. Samo skalowanie to splot odpowiedzi impulsowej filtra z pikselami poszczególnych linii i kolumn. Splot po stronie wymiaru przestrzennego oznacza mnożenie po stronie częstotliwości.

Przy pomniejszaniu każdy piksel wyjściowy rekontrujemy z pewnego zbioru pikseli wejściowych. Jeśli promień filtra to 2, a współczynnik pomniejszania to 3 to bierzemy pod uwagę 12 pikseli źródła.

Przy powiększaniu bierzemy 4 piksele.

Na te 12 i 4 piksele rozciągamy odpowiednio filtr.

Ponieważ dla każdego wiersza i każdej kolumny współczynniki filtra są takie same warto je wstępnie policzyć.

Pojawia się też pytanie: co zrobić dla skrajnych pikseli, dla których obszar do filtrowania wychodzi poza bitmapę. Tutaj mamy generalnie 3 rozwiązania: kopiować skrajny piksel, dokonać lustrzanego odbicia lub zawinąć. To ostanie przydaje się kiedy zamierzamy teksturować przedmiot wielokrotnie tą samą teksturą. Pierwsze nadaje zbyt dużego znaczenia skrajnym pikselowi. Ja z reguły dla samplowania bitmap wykorzystuję metodę drugą.

Szerokość efektywna filtra (nasze 12 i 4) może być liczbą zmiennoprzecinkową. Sam środek naszego filtra także. Bardzo ważne jest tutaj takie dobranie jego szerokości całkowitej, by wartości filtra wewnątrz obszaru były różne od zera, a poza równe mu.

Na samym końcu warto jeszcze raz przejrzeć wagi i wyeliminować z nich te o wadze 0, które mimo wszystko mogą się pojawić. Poza tym możemy także znormalizować nasz filtr tak by pole powierzchni pod nim była równa jeden (suma wag filtra), dzięki temu unikniemy małych zmian w jasności.

Samego filtrowania powinniśmy dokonywać w liniowej przestrzeni kolorów.

Kod metody wyliczającej wagi:

public class PixelWeight : IComparable<PixelWeight>
{
    public double Weight;
    public int Pos;

    public override string ToString()
    {
        return String.Format("pos: {0}; weight: {1}", Pos, Weight);
    }

    public int CompareTo(PixelWeight a_other)
    {
        return Pos - a_other.Pos;
    }
}

public static void PrecalculateWeights(int a_src_width, int a_src_height,
    int a_dest_width, int a_dest_height, 
    out List<List<PixelWeight>> a_horz_weights,
    out List<List<PixelWeight>> a_vert_weights, Filter a_filter, 
    bool a_optimize = true)
{
    double scalex = 1.0 * a_dest_width / a_src_width;
    double scaley = 1.0 * a_dest_height / a_src_height;

    double src_ray_x = a_filter.Ray / scalex;
    double src_ray_y = a_filter.Ray / scaley;

    a_horz_weights = new List<List<PixelWeight>>(a_dest_width);

    if (scalex < 1)
    {
        OverlayCorrector overlay_corrector = OverlayCorrector.Create(
            OverlayMethod.Mirror,
            a_src_width, a_src_height, (int)(src_ray_x + 2));

        for (int x = 0; x < a_dest_width; x++)
        {
            double src_center_x = x / scalex;

            int start_xx = (int)Math.Floor(src_center_x - src_ray_x);
            int end_xx = (int)Math.Ceiling(src_center_x + src_ray_x);

            if (a_filter.FilterType == FilterType.NearestNeighbour)
                src_center_x = src_center_x.Round();

            List<PixelWeight> weights = new List<PixelWeight>();

            for (int xx = start_xx; xx <= end_xx; xx++)
            {
                weights.Add(new PixelWeight()
                {
                    Pos = overlay_corrector.CorrectX(xx),
                    Weight = a_filter.Evaluate((src_center_x - xx) * scalex) * scalex
                });
            }

            Debug.Assert(a_filter.Evaluate(
                (src_center_x - (start_xx - 1)) * scalex).IsAlmostEquals(0));
            Debug.Assert(a_filter.Evaluate(
                (src_center_x - (end_xx + 1)) * scalex).IsAlmostEquals(0));

            a_horz_weights.Add(weights);
        }
    }
    else
    {
        OverlayCorrector overlay_corrector = OverlayCorrector.Create(
            OverlayMethod.Mirror,
            a_src_width, a_src_height, (int)(a_filter.Ray + 2));

        for (int x = 0; x < a_dest_width; x++)
        {
            double src_center_x = x / scalex;

            int start_xx = (int)Math.Floor(src_center_x - a_filter.Ray);
            int end_xx = (int)Math.Ceiling(src_center_x + a_filter.Ray);

            List<PixelWeight> weights = new List<PixelWeight>();

            if (a_filter.FilterType == FilterType.NearestNeighbour)
                src_center_x = src_center_x.Round();

            for (int xx = start_xx; xx <= end_xx; xx++)
            {
                weights.Add(new PixelWeight()
                {
                    Pos = overlay_corrector.CorrectX(xx),
                    Weight = a_filter.Evaluate(src_center_x - xx)
                });
            }

            Debug.Assert(a_filter.Evaluate(
                src_center_x - (start_xx - 1)).IsAlmostEquals(0));
            Debug.Assert(a_filter.Evaluate(
                src_center_x - (end_xx + 1)).IsAlmostEquals(0));

            a_horz_weights.Add(weights);
        }
    }

    a_vert_weights = new List<List<PixelWeight>>(a_dest_height);

    if (scaley < 1)
    {
        OverlayCorrector overlay_corrector = OverlayCorrector.Create(
            OverlayMethod.Mirror,
            a_src_width, a_src_height, (int)(src_ray_y + 2));

        for (int y = 0; y < a_dest_height; y++)
        {
            double src_center_y = y / scaley;

            int start_yy = (int)Math.Floor(src_center_y - src_ray_y);
            int end_yy = (int)Math.Ceiling(src_center_y + src_ray_y);

            List<PixelWeight> weights = new List<PixelWeight>();

            if (a_filter.FilterType == FilterType.NearestNeighbour)
                src_center_y = src_center_y.Round();

            for (int yy = start_yy; yy <= end_yy; yy++)
            {
                weights.Add(new PixelWeight()
                {
                    Pos = overlay_corrector.CorrectY(yy),
                    Weight = a_filter.Evaluate((src_center_y - yy) * scaley) * scaley
                });
            }

            Debug.Assert(a_filter.Evaluate(
                (src_center_y - (start_yy - 1)) * scaley).IsAlmostEquals(0));
            Debug.Assert(a_filter.Evaluate(
                (src_center_y - (end_yy + 1)) * scaley).IsAlmostEquals(0));

            a_vert_weights.Add(weights);
        }
    }
    else
    {
        OverlayCorrector overlay_corrector = OverlayCorrector.Create(
            OverlayMethod.Mirror,
            a_src_width, a_src_height, (int)(a_filter.Ray + 2));

        for (int y = 0; y < a_dest_height; y++)
        {
            double src_center_y = y / scaley;

            int start_yy = (int)Math.Floor(src_center_y - a_filter.Ray);
            int end_yy = (int)Math.Ceiling(src_center_y + a_filter.Ray);

            List<PixelWeight> weights = new List<PixelWeight>();

            if (a_filter.FilterType == FilterType.NearestNeighbour)
                src_center_y = src_center_y.Round();

            for (int yy = start_yy; yy <= end_yy; yy++)
            {
                weights.Add(new PixelWeight()
                {
                    Pos = overlay_corrector.CorrectY(yy),
                    Weight = a_filter.Evaluate(src_center_y - yy)
                });
            }

            Debug.Assert(a_filter.Evaluate(
                src_center_y - (start_yy - 1)).IsAlmostEquals(0));
            Debug.Assert(a_filter.Evaluate(
                src_center_y - (end_yy + 1)).IsAlmostEquals(0));

            a_vert_weights.Add(weights);
        }
    }

    if (a_optimize)
    {
        foreach (var ws in a_vert_weights.Concat(a_horz_weights))
        {
            double sum = ws.Sum(w => w.Weight);

            foreach (var w in ws)
                w.Weight /= sum;

            for (int i = ws.Count - 1; i >= 0; i--)
            {
                if (ws[i].Weight.IsAlmostEquals(0))
                    ws.RemoveAt(i);
            }

            ws.Sort();
        }
    }
}

Kod samej metody reskalującej:

public void Resize(ColorArrayFloat a_dest,
    FilterType a_filter)
{
    List<List<PixelWeight>> horz_weights, vert_weights;
    ResizerResampler.PrecalculateWeights(Width, Height, a_dest.Width, a_dest.Height, 
        out horz_weights, out vert_weights, Filter.Create(a_filter));

    ColorArrayFloat temp_bd = new ColorArrayFloat(a_dest.Width, Height);

    for (int x = 0; x < temp_bd.Width; x++)
    {
        List<PixelWeight> weights = horz_weights[x];

        for (int y = 0; y < temp_bd.Height; y++)
        {
            ColorFloat color = new ColorFloat();

            foreach (PixelWeight pw in weights)
                color += GetColor(pw.Pos, y) * pw.Weight;

            temp_bd.SetColor(x, y, color.AboveZero);
        }
    }

    for (int y = 0; y < a_dest.Height; y++)
    {
        List<PixelWeight> weights = vert_weights[y];

        for (int x = 0; x < a_dest.Width; x++)
        {
            ColorFloat color = new ColorFloat();

            foreach (PixelWeight pw in weights)
                color += temp_bd.GetColor(x, pw.Pos) * pw.Weight;

            a_dest.SetColor(x, y, color.AboveZero);
        }
    }
}

Przykład klasy filtra:

public class LanczosFilter : Filter
{
    public double Tau = 3;

    public override double Ray
    {
        get
        {
            return Tau;
        }
    }

    public override double Evaluate(double a_value)
    {
        double v = Math.Abs(a_value);

        if (v > Tau)
            return 0;

        if (v < Constants.DOUBLE_PRECISION)
            return 1;

        v = v * MathExtensions.PI;
        double p3 = v / Tau;
        return Math.Sin(v) * Math.Sin(p3) / (v * p3);
    }
}

Root Finder - podsumowanie

Najgorzej sprawuje się metoda Secant, która potrafi zrenderować niepoprawnie torus. Pozostałe metody renderują go bezbłędnie. Miejsca w których metody nie polegające na pochodnej zawodzą to renderowanie prosto ze wzoru brył typu superelipsoida, supertoroid, superegg. Wydaje się, że o wiele lepiej powinny się tam sprawdzić metody polegające na pochodnej.

Porównanie czasów renderowania typowej sceny, w której funkcja dana jest wielomianem (intersekcja z torusem):

Bisection: 7.9
Modified Regula Falsi: 7.4
Secant: 7.7
Newton: 7.4
Halley: 7.7

Widzimy więc, że najszybsza metoda Modified Regula Falsi jest zarazem tą najmniej problematyczną - zawsze znajduje rozwiązanie. Po zaimplementowaniu bryły Superquadric mogłem trochę dokładniej przetestować algorytmy znajdowania miejsc zerowych. Okazało się, że Newton i Halley i prawdopodobnie wszystkie oparte na analizie pochodnych nie działają. Raz, że nie powinny, gdyż bryła ta nie ma powierzchni ciągłej. Dwa, że Root Finder-y tego typu bardzo łatwo potrafią wyskoczyć z lokalnego minimum i zacząć aproksymować do sąsiedniego miejsca zerowego. Na razie je zostawiłem, ale generalnie nie nadają się one w tej chwili do niczego. Próbowałem także zaimplementować Root Finder, który możemy nazwać Brute Forcem. Idziemy co krok rzędu 1e-4 i jeśli namierzymy zmianę znaku to szukamy w takim małym przedziale miejsca zerowego. Działa, ale jest niezwykle wolny, niepraktyczny.

Wszystkie metody znajdowania miejsc zerowych wykorzystują bezwzględny test na przerwanie iteracji. Test względny wymagał by znajomości wielkości obiektu. W moim przypadku większość renderowanych brył jest ograniczona w małym przedziale, typowo (-1,1), dzięki czemu RootFinder-y nie wymagają podawania względnych warunków na zakończenie testów.

Secant Root Finder

Kolejna metoda na znajdowanie pierwiastków równania. Do rozpoczęcia wymagane są dwa punkty a i b, które powinny być dostatecznie blisko miejsca zerowego. Nie jest wymagane by miejsce zerowe znajdowało się wewnątrz (a,b), ale wtedy wzrasta ryzyko, że metoda może nie znaleźć miejsca zerowego. Podobnie dzieje się jeśli w zakresie (a,b) istnieje ekstremum nie będące miejscem zerowym. Tak więc możemy powiedzieć, że zakres (a,b) jest mniejszy tym lepiej.

Przybliżanie następuje rekurencyjnie. W dowolnym momencie iteracji mamy dwa punkty $y_0=f(x_0)$ i $y_1=f(y_1)$. Wyznaczamy równanie linii przez nie przechodzące:

$y=\displaystyle\frac{f(x_1)-f(x_0)}{x_1-x_0}(x-x_1) + f(x_1)$

Wyznaczym punkt przecięcia się naszej funkcji z linią:

$0=\displaystyle\frac{f(x_1)-f(x_0)}{x_1-x_0}(x-x_1) + f(x_1)$

$x_2=\displaystyle x_1-f(x_1)\frac{x_1-x_0}{f(x_1)-f(x_0)}$

Bierzemy nowy przedział $(x_1, x_2)$ i powtarzamy iterację. Ponieważ metoda nie zawsze znajduje rozwiązanie powinniśmy wyznaczyć górną ilość iteracji. Normalnie metoda powinna zakończyć działanie po znalezieniu takie x dla, którego f(x) jest w granicach precyzji.

Kod:

public override double FindRoot(double a_a, double a_b, Func<double, double> a_func)
{
    Debug.Assert(a_a < a_b);

    double ya = a_func(a_a);
    double yb = a_func(a_b);

    if (Math.Abs(ya) < Constants.ROOT_FINDER_PRECISION)
        return a_a;

    if (Math.Abs(yb) < Constants.ROOT_FINDER_PRECISION)
        return a_b;

    if (Math.Sign(ya) * Math.Sign(yb) > 0.0)
        return Double.PositiveInfinity;

    double x = 0;

    for (int i = 0; i < Constants.ROOT_FINDER_MAXIMUM_ITERATIONS; i++)
    {
        x = a_a - ya * (a_a - a_b) / (ya - yb);

        Debug.Assert(x.IsNumber());

        double y = a_func(x);

        if (Math.Abs(x) > Constants.ROOT_FINDER_PRECISION)
        {
            if (Math.Abs(y / x) < Constants.ROOT_FINDER_PRECISION)
                return x;
        }
        else if (Math.Abs(y) < Constants.ROOT_FINDER_PRECISION)
            return x;

        yb = ya;
        a_b = a_a;

        ya = y;
        a_a = x;

        if (ya == yb)
            return Double.PositiveInfinity;
    }

    return x;
}

Halley Root Finder

Kolejna metoda znajdowania miejsc zerowych. W założeniach bardzo podobna do metody Newtona. Od funkcji wymaga się ciągłości pierwszej i drugiej pochodnej. Musimy znać wzory na te pochodne, by je ciągle przeliczać. Jest to metoda iteracyjna. Krok iteracji ma postać:

$x_1 = x_0 - \displaystyle \frac {2 f(x_0) f'(x_0)} {2 {[f'(x_0)]}^2 - f(x_0) f''(x_0)}$

Uzasadnienie jest dla mnie dość pokrętne. Weźmy funkcję:

$g(x) = \displaystyle\frac {f(x)} {\sqrt{|f'(x)|}}$

Miejsca zerowe g(x) to miejsca zerowe f(x). Miejsca zerowe f(x) o ile nie są miejscami zerowymi $f'(x)$ są miejscami zerowymi g(x). Funkcja g(x) ma wartości nieoznaczone w ekstremach. Funkcja ta została zbudowana w taki sposób nieprzypadkowo. Jeśli podstawimy ją do wzoru na metodę Newtona to otrzymamy:

$x_1=x_0-\displaystyle\frac{g(x_0)}{g'(x_0)}=\frac{f(x_0)}{\sqrt{\left|f'(x_0)\right|}}\left(\frac{\sqrt{\left|f'(x_0)\right|}}{f(x_0)}\right)'$
$x_1=\displaystyle\frac{f(x_0)}{\sqrt{\left|f'(x_0)\right|}} \frac{2 f'(x) \sqrt{|f'(x)|}} {2 {[f'(x)]}^2 - f(x) f''(x)}$

Podobnie jak w metodzie Newtona staramy się poszukiwania tą metodą przeprowadzić dwa razy, za każdym razem za punkt startowy obierając inny koniec zakresu (a,b). I podobnie jak w metodzie Newtona nie ma gwarancji na znalezienie miejsca zerowego.

public class CentralDifferenceMethod
{
    private double m_h;
    private Func<double, double> m_func;

    public CentralDifferenceMethod()
        : base()
    {
    }
        
    public CentralDifferenceMethod(Func<double, double> a_func, 
        double a_h = Constants.DOUBLE_PRECISION * 2)
    {
        m_h = a_h;
        m_func = a_func;
    }

    public double Derive1(double a_x)
    {
        return (m_func(a_x + m_h) - m_func(a_x - m_h)) / (2 * m_h);
    }

    public double Derive2(double a_x)
    {
        return (m_func(a_x + m_h) - 2 * m_func(a_x) + 
            m_func(a_x - m_h)) / (m_h * m_h);
    }

    public static  Func<double, double> Derive1(
        Func<double, double> a_func, double a_h)
    {
        CentralDifferenceMethod cdm = 
            new CentralDifferenceMethod(a_func, a_h);

        return (x) =>
        {
            return cdm.Derive1(x);
        };
    }

    public static Func<double, double> Derive2(
        Func<double, double> a_func, double a_h)
    {
        CentralDifferenceMethod cdm = 
            new CentralDifferenceMethod(a_func, a_h);

        return (x) =>
        {
            return cdm.Derive2(x);
        };
    }

    public static Func<double, double> Derive2(
        Func<double, double> a_func, 
        Func<double, double> a_d1, double a_h)
    {
        CentralDifferenceMethod cdm = 
            new CentralDifferenceMethod(a_d1, a_h);

        return (x) =>
        {
            return cdm.Derive1(x);
        };
    }
}

public abstract class RootFinder
{
    public int MaxIterations;
    public double AbsoluteError;

    public RootFinder()
    {
        MaxIterations = Constants.ROOT_FINDER_MAXIMUM_ITERATIONS;
        AbsoluteError = Constants.ROOT_FINDER_ABSOLUTE_ERROR;
    }

    public static RootFinder Create(RootFindingMethod a_method)
    {
        switch (a_method)
        {
            case RootFindingMethod.Bisection: 
                return new BisectionRootFinder();
            case RootFindingMethod.ModifedRegulaFalsi: 
                return new ModifiedRegulaFalsiRootFinder();
            case RootFindingMethod.RegulaFalsi: 
                return new RegulaFalsiRootFinder();
            case RootFindingMethod.Secant: 
                return new SecantRootFinder();
            case RootFindingMethod.Newton: 
                return new NewtonRootFinder();
            case RootFindingMethod.Halley: 
                return new HalleyRootFinder();
            default: throw new NotImplementedException();
        }
    }

    public abstract double FindRoot(double a_a, double a_b, 
        Polynomial a_poly);
    public abstract double FindRoot(double a_a, double a_b, 
        Func<double, double> a_func);
    public abstract double FindRoot(double a_a, double a_b, 
        Func<double, double> a_func, Func<double, double> a_d1);
    public abstract double FindRoot(double a_a, double a_b, 
        Func<double, double> a_func, Func<double, double> a_d1, 
        Func<double, double> a_d2);
}

public abstract class DerivativeRootFinder : RootFinder
{
    protected struct RootState
    {
        public double Root;
        public bool Finded;

        public RootState(double a_x, bool a_finded = true)
        {
            Debug.Assert(a_x.IsNumber());

            Root = a_x;
            Finded = a_finded;
        }
    }

    protected double FindRootFromBothSide(double a_a, double a_b, 
        Func<double, double> a_func, Func<double, 
        RootState> a_root_finder)
    {
        Debug.Assert(a_a < a_b);
        Debug.Assert(a_a.IsNumber());
        Debug.Assert(a_b.IsNumber());

        double ya = a_func(a_a);
        double yb = a_func(a_b);

        if (Math.Abs(ya) < AbsoluteError)
            return a_a;
        if (Math.Abs(yb) < AbsoluteError)
            return a_b;

        if (Math.Sign(ya) * Math.Sign(yb) > 0)
            return Double.PositiveInfinity;

        RootState x1 = a_root_finder(a_a);
        if (x1.Finded && (x1.Root > a_a) && (x1.Root < a_b))
            return x1.Root;

        RootState x2 = a_root_finder(a_b);
        if (x2.Finded && (x2.Root > a_a) && (x2.Root < a_b))
            return x2.Root;

        return Double.PositiveInfinity;
    }
}

public class HalleyRootFinder : DerivativeRootFinder
{
    public override double FindRoot(double a_a, double a_b, 
        Func<double, double> a_func)
    {
        return FindRoot(a_a, a_b, a_func, 
            CentralDifferenceMethod.Derive1(a_func, AbsoluteError),
            CentralDifferenceMethod.Derive2(a_func, AbsoluteError));
    }

    public override double FindRoot(double a_a, double a_b, 
        Func<double, double> a_func, Func<double, double> a_d1)
    {
        return FindRoot(a_a, a_b, a_func, a_d1, 
            CentralDifferenceMethod.Derive2(a_func, a_d1, AbsoluteError));
    }

    public override double FindRoot(double a_a, double a_b, 
        Func<double, double> a_func, Func<double, double> a_d1, 
        Func<double, double> a_d2)
    {
        return FindRootFromBothSide(a_a, a_b, a_func, 
            (s) => FindRoot(s, a_func, a_d1, a_d2));
    }

    private RootState FindRoot(double a_x, Func<double, double> a_func,
        Func<double, double> a_d1, Func<double, double> a_d2)
    {
        double prev_y = a_func(a_x);

        for (int i = 0; i < MaxIterations; i++)
        {
            double dy = a_d1(a_x);
            double ddy = a_d2(a_x);

            double den = 2 * dy * dy - prev_y * ddy;
            if (den.IsAlmostEquals(0))
                return new RootState(a_x);

            a_x = a_x - 2 * prev_y * dy / den;

            double y = a_func(a_x);
            prev_y = y;

            if (Math.Abs(a_x) > AbsoluteError)
            {
                if (Math.Abs(y / a_x) < AbsoluteError)
                    return new RootState(a_x);
            }
            else if (Math.Abs(y) < AbsoluteError)
                return new RootState(a_x);
        }

        return new RootState(a_x, false);
    }

    public override double FindRoot(double a_a, double a_b, 
        Polynomial a_poly)
    {
        Polynomial d1 = a_poly.Differentiate();
        Polynomial d2 = d1.Differentiate();
        return FindRoot(a_a, a_b, (x) => a_poly.Evaluate(x),
            (x) => d1.Evaluate(x), (x) => d2.Evaluate(x));
    }
}

Newton Root Finder

Nie wymaga zakresu do poszukiwania miejsca zerowego, tylko punktu startowego. Wymaga za to obliczania wartości pochodnej, czyli także ciągłości funkcji. Metodę tą ciężko zastosować jeśli nie możemy podać funkcji na pochodną. Jest to także metoda iteracyjna. Krok iteracji ma postać:

$x_{1} = x_0 - \frac{f(x_0)}{f'(x_0)}$

Metoda ta prawie na pewno nie poradzi sobie z ekstremami funkcji - zacznie wokół nich oscylować. Może się zdarzyć, że zostanie znaleziony pierwiastek z poza zakresu, gdyż w jego kierunku iteracja zacznie podążać wzdłuż krzywej.

Poniższy kod stara się poszukuje miejsca zerowego dwa razy, obierając jako punkt startu a i b zakresu poszukiwań. Tym samym zwiększamy trochę szansę znalezienia pierwiastków. Ale ciągle nie radzi sobie z ekstremami.

Kod:

public class CentralDifferenceMethod
{
    private double m_h;
    private Func<double, double> m_func;

    public CentralDifferenceMethod()
        : base()
    {
    }
        
    public CentralDifferenceMethod(Func<double, double> a_func, 
        double a_h = Constants.DOUBLE_PRECISION * 2)
    {
        m_h = a_h;
        m_func = a_func;
    }

    public double Derive1(double a_x)
    {
        return (m_func(a_x + m_h) - m_func(a_x - m_h)) / (2 * m_h);
    }

    public double Derive2(double a_x)
    {
        return (m_func(a_x + m_h) - 2 * m_func(a_x) + 
            m_func(a_x - m_h)) / (m_h * m_h);
    }

    public static  Func<double, double> Derive1(
        Func<double, double> a_func, double a_h)
    {
        CentralDifferenceMethod cdm = 
            new CentralDifferenceMethod(a_func, a_h);

        return (x) =>
        {
            return cdm.Derive1(x);
        };
    }

    public static Func<double, double> Derive2(
        Func<double, double> a_func, double a_h)
    {
        CentralDifferenceMethod cdm = 
            new CentralDifferenceMethod(a_func, a_h);

        return (x) =>
        {
            return cdm.Derive2(x);
        };
    }

    public static Func<double, double> Derive2(
        Func<double, double> a_func, 
        Func<double, double> a_d1, double a_h)
    {
        CentralDifferenceMethod cdm = 
            new CentralDifferenceMethod(a_d1, a_h);

        return (x) =>
        {
            return cdm.Derive1(x);
        };
    }
}

public abstract class RootFinder
{
    public int MaxIterations;
    public double AbsoluteError;

    public RootFinder()
    {
        MaxIterations = Constants.ROOT_FINDER_MAXIMUM_ITERATIONS;
        AbsoluteError = Constants.ROOT_FINDER_ABSOLUTE_ERROR;
    }

    public static RootFinder Create(RootFindingMethod a_method)
    {
        switch (a_method)
        {
            case RootFindingMethod.Bisection: 
                return new BisectionRootFinder();
            case RootFindingMethod.ModifedRegulaFalsi: 
                return new ModifiedRegulaFalsiRootFinder();
            case RootFindingMethod.RegulaFalsi: 
                return new RegulaFalsiRootFinder();
            case RootFindingMethod.Secant: 
                return new SecantRootFinder();
            case RootFindingMethod.Newton: 
                return new NewtonRootFinder();
            case RootFindingMethod.Halley: 
                return new HalleyRootFinder();
            default: throw new NotImplementedException();
        }
    }

    public abstract double FindRoot(double a_a, double a_b, 
        Polynomial a_poly);
    public abstract double FindRoot(double a_a, double a_b, 
        Func<double, double> a_func);
    public abstract double FindRoot(double a_a, double a_b, 
        Func<double, double> a_func, Func<double, double> a_d1);
    public abstract double FindRoot(double a_a, double a_b, 
        Func<double, double> a_func, Func<double, double> a_d1, 
        Func<double, double> a_d2);
}

public abstract class DerivativeRootFinder : RootFinder
{
    protected struct RootState
    {
        public double Root;
        public bool Finded;

        public RootState(double a_x, bool a_finded = true)
        {
            Debug.Assert(a_x.IsNumber());

            Root = a_x;
            Finded = a_finded;
        }
    }

    protected double FindRootFromBothSide(double a_a, double a_b, 
        Func<double, double> a_func, Func<double, 
        RootState> a_root_finder)
    {
        Debug.Assert(a_a < a_b);
        Debug.Assert(a_a.IsNumber());
        Debug.Assert(a_b.IsNumber());

        double ya = a_func(a_a);
        double yb = a_func(a_b);

        if (Math.Abs(ya) < AbsoluteError)
            return a_a;
        if (Math.Abs(yb) < AbsoluteError)
            return a_b;

        if (Math.Sign(ya) * Math.Sign(yb) > 0)
            return Double.PositiveInfinity;

        RootState x1 = a_root_finder(a_a);
        if (x1.Finded && (x1.Root > a_a) && (x1.Root < a_b))
            return x1.Root;

        RootState x2 = a_root_finder(a_b);
        if (x2.Finded && (x2.Root > a_a) && (x2.Root < a_b))
            return x2.Root;

        return Double.PositiveInfinity;
    }
}

public class NewtonRootFinder : DerivativeRootFinder
{
    public override double FindRoot(double a_a, double a_b, 
        Func<double, double> a_func)
    {
        return FindRoot(a_a, a_b, a_func, 
            CentralDifferenceMethod.Derive1(a_func, AbsoluteError));
    }

    public override double FindRoot(double a_a, double a_b, 
        Func<double, double> a_func, Func<double, double> a_d1, 
        Func<double, double> a_d2)
    {
        return FindRoot(a_a, a_b, a_func, a_d1);
    }

    public override double FindRoot(double a_a, double a_b, 
        Func<double, double> a_func, Func<double, double> a_d1)
    {
        return FindRootFromBothSide(a_a, a_b, a_func, 
            (s) => FindRoot(s, a_func, a_d1));
    }

    private RootState FindRoot(double a_x, Func<double, double> a_func,
        Func<double, double> a_d1)
    {
        double prev_y = a_func(a_x);

        for (int i = 0; i < MaxIterations; i++)
        {
            double dy = a_d1(a_x);
            if (dy.IsAlmostEquals(0))
                return new RootState(a_x);

            a_x = a_x - prev_y / dy;
            double y = a_func(a_x);
            prev_y = y;

            if (Math.Abs(a_x) > AbsoluteError)
            {
                if (Math.Abs(y / a_x) < AbsoluteError)
                    return new RootState(a_x);
            }
            else if (Math.Abs(y) < AbsoluteError)
                return new RootState(a_x);
        }

        return new RootState(a_x, false);
    }

    public override double FindRoot(double a_a, double a_b, 
        Polynomial a_poly)
    {
        Polynomial d1 = a_poly.Differentiate();
        return FindRoot(a_a, a_b, (x) => a_poly.Evaluate(x),
            (x) => d1.Evaluate(x));
    }
}

Root Finder Modifed Regula Falsi

Metoda szybsza od dwóch pierwszej Bisection. Zawsze gwarantująca rozwiązanie w przeciwieństwie do metody Secant.

Wymaga przedziału startowego (a,b) takiego, że znaki f(a) i f(b) są różne, co oznacza, że w przedziale (a,b) istnieje miejsce zerowe. By tak się stało funkcja powinna być ciągła.

Jest to metoda rekurencyjna, w której stopniowo przybliżamy miejsce zerowe. W przedziale (a,b) mogą istnieć ekstrema nie będące miejsca miejscami zerowymi albo parzyste miejsca zerowe. Metoda ta nie zawsze gwarantuje nam znalezienie rozwiązania.

W dowolnym kroku iteracji mamy przedział $(x_0, x_1)$, podobnie jak w metodzie Secant wyznaczamy linię przebiegającą przez punkty $(x_0,f(x_0))$ i $(x_1,f(x_1))$. Znajdujemy jej miejsce zerowe:

$x_2=x_1-f(x_1)\frac{x_1-x_0}{f(x_1)-f(x_0)}=\frac{f(y_1)x_0-f(y_0)x_1}{f(x_1)-f(x_0)}$

Teraz wybieramy nowy zakres tak by znaki funkcji na końcach zakresu miały przeciwny znak. Cały proces powtarzamy.

Kod:

public override double FindRoot(
    double a_a, double a_b, Func a_func)
{
    Debug.Assert(a_a < a_b);

    double ya = a_func(a_a);
    double yb = a_func(a_b);

    if (Math.Abs(ya) < Constants.ROOT_FINDER_PRECISION)
        return a_a;

    if (Math.Abs(yb) < Constants.ROOT_FINDER_PRECISION)
        return a_b;

    if (Math.Sign(ya) * Math.Sign(yb) > 0.0)
        return Double.PositiveInfinity;

    double x = 0;

    for (int i = 0; i < Constants.ROOT_FINDER_MAXIMUM_ITERATIONS; i++)
    {
        x = (yb * a_a - ya * a_b) / (yb - ya);
        double y = a_func(x);

        if (Math.Abs(x) > Constants.ROOT_FINDER_PRECISION)
        {
            if (Math.Abs(y / x) < Constants.ROOT_FINDER_PRECISION)
                return x;
        }
        else if (Math.Abs(y) < Constants.ROOT_FINDER_PRECISION)
            return x;

        if ((Math.Sign(ya) * Math.Sign(y)) < 0)
        {
            a_b = x;
            yb = y;
        }
        else
        {
            a_a = x;
            ya = y;
        }
    }

    return x;
}
Metoda ta nie zawsze gwarantuje nam zbieżność. Istnieje jej modyfikacja która zawsze gwarantuje nam zbieżność. To dzielenie przez 2 ma uzasadnienie teorii.
public override double FindRoot(
    double a_a, double a_b, Func<double, double> a_func)
{
    Debug.Assert(a_a < a_b);

    double ya = a_func(a_a);
    double yb = a_func(a_b);

    if (Math.Sign(ya) * Math.Sign(yb) > 0.0)
        return Double.PositiveInfinity;

    if (Math.Abs(ya) < Constants.ROOT_FINDER_PRECISION)
        return a_a;

    if (Math.Abs(yb) < Constants.ROOT_FINDER_PRECISION)
        return a_b;

    double prev_y = ya;
    double x = 0;

    for (int i = 0; i < Constants.ROOT_FINDER_MAXIMUM_ITERATIONS; i++)
    {
        x = (yb * a_a - ya * a_b) / (yb - ya);
        double y = a_func(x);

        if (Math.Abs(x) > Constants.ROOT_FINDER_PRECISION)
        {
            if (Math.Abs(y / x) < Constants.ROOT_FINDER_PRECISION)
                return x;
        }
        else if (Math.Abs(y) < Constants.ROOT_FINDER_PRECISION)
            return x;

        if ((Math.Sign(ya) * Math.Sign(y)) < 0)
        {
            a_b = x;
            yb = y;
            if ((prev_y * y) > 0)
                ya /= 2;
        }
        else
        {
            a_a = x;
            ya = y;
            if ((prev_y * y) > 0)
                yb /= 2;
        }

        prev_y = y;
    }

    return x;
}