diff --git a/doc/tutorial/11_fractal.cpp b/doc/tutorial/11_fractal.cpp index 3c79b289..95537408 100644 --- a/doc/tutorial/11_fractal.cpp +++ b/doc/tutorial/11_fractal.cpp @@ -20,6 +20,8 @@ #include #include "loldebug.h" +#define USE_REAL 0 + using namespace lol; LOLFX_RESOURCE_DECLARE(11_fractal); @@ -28,6 +30,7 @@ class Fractal : public WorldEntity { public: Fractal(ivec2 const &size) + : m_julia(false) { /* Ensure texture size is a multiple of 16 for better aligned * data access. Store the dimensions of a texel for our shader, @@ -41,7 +44,8 @@ public: m_controller = new Controller("Default"); m_profile << InputProfile::MouseKey(0, "Left") << InputProfile::MouseKey(1, "Right") - << InputProfile::MouseKey(2, "Middle"); + << InputProfile::MouseKey(2, "Middle") + << InputProfile::Keyboard(3, "Space"); m_controller->Init(m_profile); m_mouse = InputDevice::GetMouse(); @@ -70,10 +74,10 @@ public: m_deltascale[i] = real("1"); m_dirty[i] = 2; } - m_center = rcmplx(-0.75, 0.0); + m_view.center = rcmplx(-0.75, 0.0); m_zoom_speed = 0.0; - m_translate = rcmplx(0.0, 0.0); - m_radius = 5.0; + m_view.translate = rcmplx(0.0, 0.0); + m_view.radius = 5.0; m_ready = false; m_drag = false; @@ -81,9 +85,9 @@ public: { double f = (double)i / PALETTE_STEP; - vec3 hsv(lol::fmod(i * 0.002f, 1.f), - 0.4 * lol::sin(f * 0.27 + 2.0) + 0.4, - 0.4 * lol::sin(f * 0.21 - 2.6) + 0.4); + vec3 hsv(lol::fmod(i * 0.001f, 1.f), + 0.3 * lol::sin(f * 0.27 + 2.0) + 0.3, + 0.3 * lol::sin(f * 0.21 - 2.6) + 0.6); vec3 rgb = Color::HSVToRGB(hsv); if (f < 7.0) @@ -102,17 +106,17 @@ public: } #if !defined __native_client__ + m_zoomtext = new Text("", "data/font/ascii.png"); + m_zoomtext->SetPos(vec3(5, (float)m_window_size.y - 15, 1)); + Ticker::Ref(m_zoomtext); + m_centertext = new Text("", "data/font/ascii.png"); - m_centertext->SetPos(vec3(5, (float)m_window_size.y - 15, 1)); + m_centertext->SetPos(vec3(5, (float)m_window_size.y - 29, 1)); Ticker::Ref(m_centertext); m_mousetext = new Text("", "data/font/ascii.png"); - m_mousetext->SetPos(vec3(5, (float)m_window_size.y - 29, 1)); + m_mousetext->SetPos(vec3(5, (float)m_window_size.y - 43, 1)); Ticker::Ref(m_mousetext); - - m_zoomtext = new Text("", "data/font/ascii.png"); - m_zoomtext->SetPos(vec3(5, (float)m_window_size.y - 43, 1)); - Ticker::Ref(m_zoomtext); #endif m_position = vec3::zero; @@ -139,7 +143,6 @@ public: m_donequeue.pop(); #endif - //Input::UntrackMouse(this); #if !defined __native_client__ Ticker::Unref(m_centertext); Ticker::Unref(m_mousetext); @@ -147,20 +150,20 @@ public: #endif } - inline dcmplx TexelToWorldOffset(vec2 texel) + inline f128cmplx TexelToWorldOffset(vec2 texel) { double dx = (0.5 + texel.x - m_size.x / 2) * m_texel2world.x; double dy = (0.5 + m_size.y / 2 - texel.y) * m_texel2world.y; - return m_radius * dcmplx(dx, dy); + return m_view.radius * f128cmplx(dx, dy); } - inline dcmplx ScreenToWorldOffset(vec2 pixel) + inline f128cmplx ScreenToWorldOffset(vec2 pixel) { /* No 0.5 offset here, because we want to be able to position the * mouse at (0,0) exactly. */ double dx = pixel.x - m_window_size.x / 2; double dy = m_window_size.y / 2 - pixel.y; - return m_radius * m_window2world * dcmplx(dx, dy); + return m_view.radius * m_window2world * f128cmplx(dx, dy); } virtual void TickGame(float seconds) @@ -172,8 +175,23 @@ public: int prev_frame = (m_frame + 4) % 4; m_frame = (m_frame + 1) % 4; - rcmplx worldmouse = m_center - + rcmplx(ScreenToWorldOffset((vec2)mousepos)); + if (m_controller->WasKeyPressedThisFrame(3)) + { + m_julia = !m_julia; + if (m_julia) + { + m_saved_view = m_view; + m_view.r0 = m_view.center + rcmplx(ScreenToWorldOffset((vec2)mousepos)); + } + else + { + m_view = m_saved_view; + } + for (auto & flag : m_dirty) + flag = 2; + } + + rcmplx worldmouse = m_view.center + rcmplx(ScreenToWorldOffset((vec2)mousepos)); if (m_controller->IsKeyPressed(2)) { @@ -182,24 +200,24 @@ public: m_oldmouse = mousepos; m_drag = true; } - m_translate = rcmplx(ScreenToWorldOffset((vec2)m_oldmouse) - - ScreenToWorldOffset((vec2)mousepos)); + m_view.translate = rcmplx(ScreenToWorldOffset((vec2)m_oldmouse) + - ScreenToWorldOffset((vec2)mousepos)); /* XXX: the purpose of this hack is to avoid translating by * an exact number of pixels. If this were to happen, the step() * optimisation for i915 cards in our shader would behave * incorrectly because a quarter of the pixels in the image * would have tied rankings in the distance calculation. */ - m_translate *= real(1023.0 / 1024.0); + m_view.translate *= real(1023.0 / 1024.0); m_oldmouse = mousepos; } else { m_drag = false; - if (m_translate != rcmplx(0.0, 0.0)) + if (m_view.translate != rcmplx(0.0, 0.0)) { - m_translate *= real(std::pow(2.0, -seconds * 5.0)); - if ((double)norm(m_translate) < m_radius * 1e-4) - m_translate = rcmplx(0.0, 0.0); + m_view.translate *= real(std::pow(2.0, -seconds * 5.0)); + if ((double)norm(m_view.translate) < m_view.radius * 1e-4) + m_view.translate = rcmplx(0.0, 0.0); } } @@ -219,34 +237,35 @@ public: m_zoom_speed = 0.0; } - if (m_zoom_speed || m_translate != rcmplx(0.0, 0.0)) + if (m_zoom_speed || m_view.translate != rcmplx(0.0, 0.0)) { - rcmplx oldcenter = m_center; - double oldradius = m_radius; + rcmplx oldcenter = m_view.center; + double oldradius = m_view.radius; double zoom = std::pow(2.0, seconds * 1e3f * m_zoom_speed); - if (m_radius * zoom > 8.0) + if (m_view.radius * zoom > 8.0) { m_zoom_speed *= -1.0; - zoom = 8.0 / m_radius; + zoom = 8.0 / m_view.radius; } - else if (m_radius * zoom < 1e-14) + else if (m_view.radius * zoom < MAX_ZOOM) { m_zoom_speed *= -1.0; - zoom = 1e-14 / m_radius; + zoom = MAX_ZOOM / m_view.radius; } - m_radius *= zoom; - m_center += m_translate; - m_center = (m_center - worldmouse) * real(zoom) + worldmouse; - worldmouse = m_center - + rcmplx(ScreenToWorldOffset((vec2)mousepos)); + m_view.radius *= zoom; + m_view.center += m_view.translate; + m_view.center = (m_view.center - worldmouse) * real(zoom) + worldmouse; + worldmouse = m_view.center + + rcmplx(ScreenToWorldOffset((vec2)mousepos)); /* Store the transformation properties to go from m_frame - 1 * to m_frame. */ - m_deltashift[prev_frame] = (m_center - oldcenter) / real(oldradius); + m_deltashift[prev_frame] = (m_view.center - oldcenter) / real(oldradius); m_deltashift[prev_frame].x /= m_size.x * m_texel2world.x; m_deltashift[prev_frame].y /= m_size.y * m_texel2world.y; - m_deltascale[prev_frame] = m_radius / oldradius; - m_dirty[0] = m_dirty[1] = m_dirty[2] = m_dirty[3] = 2; + m_deltascale[prev_frame] = m_view.radius / oldradius; + for (auto & flag : m_dirty) + flag = 2; } else { @@ -283,16 +302,16 @@ public: #if !defined __native_client__ char buf[256]; std::sprintf(buf, "center: "); - m_center.x.sprintf(buf + strlen(buf), 30); + m_view.center.x.sprintf(buf + strlen(buf), 30); std::sprintf(buf + strlen(buf), " "); - m_center.y.sprintf(buf + strlen(buf), 30); + m_view.center.y.sprintf(buf + strlen(buf), 30); m_centertext->SetText(buf); std::sprintf(buf, " mouse: "); worldmouse.x.sprintf(buf + strlen(buf), 30); std::sprintf(buf + strlen(buf), " "); worldmouse.y.sprintf(buf + strlen(buf), 30); m_mousetext->SetText(buf); - std::sprintf(buf, " zoom: %g", 1.0 / m_radius); + std::sprintf(buf, "[%s] zoom: %g", m_julia ? "Julia" : "Mandelbrot", 1.0 / m_view.radius); m_zoomtext->SetText(buf); #endif @@ -339,38 +358,49 @@ public: u8vec4 *pixelstart = &m_pixels[0] + m_size.x * (m_size.y / 4 * m_frame + line / 4); - dcmplx c = (dcmplx)m_center; +#if USE_REAL + rcmplx c = (rcmplx)m_view.center; + rcmplx jr0 = (rcmplx)m_view.r0; +#else + f128cmplx c = (f128cmplx)m_view.center; + f128cmplx jr0 = (f128cmplx)m_view.r0; +#endif for (int j = jmin; j < jmax; j += 2) for (int i = m_frame % 2; i < m_size.x; i += 2) { - double xr, yr, x0, y0, x1, y1, x2, y2, x3, y3; - dcmplx z0 = c + TexelToWorldOffset(vec2(ivec2(i, j))); - //dcmplx r0(0.28693186889504513, 0.014286693904085048); - //dcmplx r0(0.001643721971153, 0.822467633298876); - //dcmplx r0(-1.207205434596, 0.315432814901); - //dcmplx r0(-0.79192956889854, -0.14632423080102); - //dcmplx r0(0.3245046418497685, 0.04855101129280834); - dcmplx r0 = z0; +#if USE_REAL + real xr, yr, x0, y0, x1, y1, x2, y2, x3, y3; + real sqx0, sqy0, sqx1, sqy1, sqx2, sqy2, sqx3, sqy3; + rcmplx z0 = c + rcmplx(TexelToWorldOffset(vec2(ivec2(i, j)))); + rcmplx r0 = m_julia ? jr0 : z0; +#else + ldouble xr, yr, x0, y0, x1, y1, x2, y2, x3, y3; + ldouble sqx0, sqy0, sqx1, sqy1, sqx2, sqy2, sqx3, sqy3; + f128cmplx z0 = c + TexelToWorldOffset(vec2(ivec2(i, j))); + f128cmplx r0 = m_julia ? jr0 : z0; +#endif x0 = z0.x; y0 = z0.y; xr = r0.x; yr = r0.y; + sqx0 = x0 * x0; sqy0 = y0 * y0; int iter = MAX_ITERATIONS - 4; for (;;) { /* Unroll the loop: tests are more expensive to do at each - * iteration than the few extra multiplications. */ - x1 = x0 * x0 - y0 * y0 + xr; - y1 = x0 * y0 + x0 * y0 + yr; - x2 = x1 * x1 - y1 * y1 + xr; - y2 = x1 * y1 + x1 * y1 + yr; - x3 = x2 * x2 - y2 * y2 + xr; - y3 = x2 * y2 + x2 * y2 + yr; - x0 = x3 * x3 - y3 * y3 + xr; - y0 = x3 * y3 + x3 * y3 + yr; - - if (x0 * x0 + y0 * y0 >= maxsqlen) + * iteration than the few extra multiplications, at least + * with floats/doubles. */ + x1 = sqx0 - sqy0 + xr; y1 = x0 * y0 + x0 * y0 + yr; + sqx1 = x1 * x1; sqy1 = y1 * y1; + x2 = sqx1 - sqy1 + xr; y2 = x1 * y1 + x1 * y1 + yr; + sqx2 = x2 * x2; sqy2 = y2 * y2; + x3 = sqx2 - sqy2 + xr; y3 = x2 * y2 + x2 * y2 + yr; + sqx3 = x3 * x3; sqy3 = y3 * y3; + x0 = sqx3 - sqy3 + xr; y0 = x3 * y3 + x3 * y3 + yr; + sqx0 = x0 * x0; sqy0 = y0 * y0; + + if ((double)sqx0 + (double)sqy0 >= maxsqlen) break; iter -= 4; if (iter < 4) @@ -379,19 +409,19 @@ public: if (iter) { - double n = x0 * x0 + y0 * y0; + double n = (double)sqx0 + (double)sqy0; - if (x1 * x1 + y1 * y1 >= maxsqlen) + if ((double)sqx1 + (double)sqy1 >= maxsqlen) { - iter += 3; n = x1 * x1 + y1 * y1; + iter += 3; n = (double)sqx1 + (double)sqy1; } - else if (x2 * x2 + y2 * y2 >= maxsqlen) + else if ((double)sqx2 + (double)sqy2 >= maxsqlen) { - iter += 2; n = x2 * x2 + y2 * y2; + iter += 2; n = (double)sqx2 + (double)sqy2; } - else if (x3 * x3 + y3 * y3 >= maxsqlen) + else if ((double)sqx3 + (double)sqy3 >= maxsqlen) { - iter += 1; n = x3 * x3 + y3 * y3; + iter += 1; n = (double)sqx3 + (double)sqy3; } if (n > maxsqlen * maxsqlen) @@ -399,7 +429,7 @@ public: /* Approximate log(sqrt(n))/log(sqrt(maxsqlen)) */ double f = iter; - union { double n; uint64_t x; } u = { n }; + union { double n; uint64_t x; } u = { (double)n }; double k = (double)(u.x >> 42) - (((1 << 10) - 1) << 10); k *= k1; @@ -509,11 +539,14 @@ public: } private: - static int const MAX_ITERATIONS = 340; + static int const MAX_ITERATIONS = 400; static int const PALETTE_STEP = 32; static int const MAX_THREADS = 8; static int const MAX_LINES = 8; + // 1e-14 for doubles, 1e-17 for long doubles + static double constexpr MAX_ZOOM = 1e-17; + ivec2 m_size, m_window_size, m_oldmouse; double m_window2world; dvec2 m_texel2world; @@ -530,9 +563,18 @@ private: int m_frame, m_slices, m_dirty[4]; bool m_ready, m_drag; - rcmplx m_deltashift[4], m_center, m_translate; + struct view_settings + { + rcmplx center, translate, r0; + double radius; + }; + + view_settings m_view, m_saved_view; + + rcmplx m_deltashift[4]; real m_deltascale[4]; - double m_zoom_speed, m_radius; + double m_zoom_speed; + bool m_julia; vec4 m_texel_settings, m_screen_settings; mat4 m_zoom_settings;