| 1 |
module scene.fractal; |
|---|
| 2 |
|
|---|
| 3 |
import scene.common, base.math, base.math: bit_isnan; |
|---|
| 4 |
|
|---|
| 5 |
version(GNU) { |
|---|
| 6 |
import gcc.builtins; |
|---|
| 7 |
alias __builtin_logf logf; |
|---|
| 8 |
} else { |
|---|
| 9 |
alias log logf; |
|---|
| 10 |
} |
|---|
| 11 |
|
|---|
| 12 |
float ipow(int I)(float f) { |
|---|
| 13 |
version(GNU) { |
|---|
| 14 |
return __builtin_powif(f, I); |
|---|
| 15 |
} else { |
|---|
| 16 |
static if (I == 0) return 1; |
|---|
| 17 |
else static if (I == 1) return f; |
|---|
| 18 |
else static if ((I%2) == 1) return sqr(ipow!(I/2)(f))*f; |
|---|
| 19 |
else return sqr(ipow!(I/2)(f)); |
|---|
| 20 |
} |
|---|
| 21 |
} |
|---|
| 22 |
|
|---|
| 23 |
string ctPowExpand(string text) { |
|---|
| 24 |
string buf; |
|---|
| 25 |
while (true) { |
|---|
| 26 |
int pos = text.ctFind("^"); |
|---|
| 27 |
if (pos == -1) return buf ~ text; |
|---|
| 28 |
char var = text[pos-1]; |
|---|
| 29 |
buf ~= text[0 .. pos-1]; |
|---|
| 30 |
int num = pos+1; |
|---|
| 31 |
while (text[num] >= '0' && text[num] <= '9') |
|---|
| 32 |
num ++; |
|---|
| 33 |
buf ~= "z"~text[pos+1 .. num]~"."~var; |
|---|
| 34 |
text = text[num .. $]; |
|---|
| 35 |
} |
|---|
| 36 |
} |
|---|
| 37 |
|
|---|
| 38 |
float fn(vec c, int iters = 20) { |
|---|
| 39 |
auto z = c; |
|---|
| 40 |
// vec dt = vec(1, 0, 0); |
|---|
| 41 |
FP r = z.length, dl = 1; |
|---|
| 42 |
// auto ÎŽ = Spherical(0, 0, 1); |
|---|
| 43 |
int i; |
|---|
| 44 |
for (; i < iters; ++i) { |
|---|
| 45 |
auto r7 = ipow!(7)(r); |
|---|
| 46 |
dl = dl*r7*8 + 1; // Will this work, I wonder? |
|---|
| 47 |
/** |
|---|
| 48 |
z.x = |z|^8 * cos(asin(z.z / |z| )*8) * cos(atan2(z.y, z.x)*8) + c.x |
|---|
| 49 |
z.y = |z|^8 * cos(asin(z.z / |z| )*8) * sin(atan2(z.y, z.x)*8) + c.y |
|---|
| 50 |
z.z = |z|^8 * sin(asin(z.z / |z| )*8) + c.y |
|---|
| 51 |
Maxima codegen GO |
|---|
| 52 |
**/ |
|---|
| 53 |
// Funs |
|---|
| 54 |
{ |
|---|
| 55 |
vec z2 = void, z4 = void, z6 = void, z8 = void, z10 = void, z12 = void, z14 = void, z16 = void; |
|---|
| 56 |
vec z3 = void, z5 = void, z7 = void, z9 = void, z11 = void, z13 = void, z15 = void; |
|---|
| 57 |
// OH NOES HER NUMERIC COPROCESSORS |
|---|
| 58 |
for (int k = 0; k < vec.real_size; ++k) { |
|---|
| 59 |
z2.real_field[k] = z.real_field[k] * z.real_field[k]; |
|---|
| 60 |
z3.real_field[k] = z2.real_field[k] * z.real_field[k]; |
|---|
| 61 |
z4.real_field[k] = z2.real_field[k] * z2.real_field[k]; |
|---|
| 62 |
z5.real_field[k] = z4.real_field[k] * z.real_field[k]; |
|---|
| 63 |
z6.real_field[k] = z2.real_field[k] * z4.real_field[k]; |
|---|
| 64 |
z7.real_field[k] = z6.real_field[k] * z.real_field[k]; |
|---|
| 65 |
z8.real_field[k] = z4.real_field[k] * z4.real_field[k]; |
|---|
| 66 |
} |
|---|
| 67 |
for (int k = 0; k < 2; ++k) { // z only needed up to ^8 |
|---|
| 68 |
z9.real_field[k] = z8.real_field[k] * z.real_field[k]; |
|---|
| 69 |
z10.real_field[k] = z8.real_field[k] * z2.real_field[k]; |
|---|
| 70 |
z11.real_field[k] = z10.real_field[k] * z.real_field[k]; |
|---|
| 71 |
z12.real_field[k] = z8.real_field[k] * z4.real_field[k]; |
|---|
| 72 |
z13.real_field[k] = z12.real_field[k] * z.real_field[k]; |
|---|
| 73 |
z14.real_field[k] = z8.real_field[k] * z6.real_field[k]; |
|---|
| 74 |
z15.real_field[k] = z14.real_field[k] * z.real_field[k]; |
|---|
| 75 |
z16.real_field[k] = z8.real_field[k] * z8.real_field[k]; |
|---|
| 76 |
} |
|---|
| 77 |
vec nz = void; |
|---|
| 78 |
// I wish I had smart goo. |
|---|
| 79 |
// trigsimp(trigexpand(sqrt(x*x+y*y+z*z)^8*cos(asin(z/sqrt(x*x+y*y+z*z))*8)*cos(atan2(y, x)*8))); |
|---|
| 80 |
nz.x = mixin(ctPowExpand("((y^8-28*x^2*y^6+70*x^4*y^4-28*x^6*y^2+x^8)*z^8+(-28*y^10+756*x^2*y^8-1176*x^4*y^6-1176*x^6*y^4+756*x^8*y^2-28*x^10)*z^6+ |
|---|
| 81 |
(70*y^12-1820*x^2*y^10+1050*x^4*y^8+5880*x^6*y^6+1050*x^8*y^4-1820*x^10*y^2+70*x^12)*z^4+ |
|---|
| 82 |
(-28*y^14+700*x^2*y^12+308*x^4*y^10-2772*x^6*y^8-2772*x^8*y^6+308*x^10*y^4+700*x^12*y^2-28*x^14)*z^2+y^16-24*x^2*y^14-36*x^4*y^12+88*x^6*y^10+198*x^8*y^8+88*x^10*y^6-36*x^12*y^4 |
|---|
| 83 |
-24*x^14*y^2+x^16)/(y^8+4*x^2*y^6+6*x^4*y^4+4*x^6*y^2+x^8)")) + c.x; |
|---|
| 84 |
// trigsimp(trigexpand(sqrt(x*x+y*y+z*z)^8*sin(asin(z/sqrt(x*x+y*y+z*z))*8)*cos(atan2(y, x)*8))); |
|---|
| 85 |
nz.y = mixin(ctPowExpand("-((8*z.x*y^7-56*x^3*y^5+56*x^5*y^3-8*x^7*z.y)*z^8+(-224*z.x*y^9+1344*x^3*y^7-1344*x^7*y^3+224*x^9*z.y)*z^6+ |
|---|
| 86 |
(560*z.x*y^11-2800*x^3*y^9-3360*x^5*y^7+3360*x^7*y^5+2800*x^9*y^3-560*x^11*z.y)*z^4+(-224*z.x*y^13+896*x^3*y^11+2464*x^5*y^9-2464*x^9*y^5-896*x^11*y^3+224*x^13*z.y)*z^2+8*z.x*y^15- |
|---|
| 87 |
24*x^3*y^13-120*x^5*y^11-88*x^7*y^9+88*x^9*y^7+120*x^11*y^5+24*x^13*y^3-8*x^15*z.y)/(y^8+4*x^2*y^6+6*x^4*y^4+4*x^6*y^2+x^8)")) + c.y; |
|---|
| 88 |
// trigsimp(trigexpand(sqrt(x*x+y*y+z*z)^8*sin(asin(z/sqrt(x*x+y*y+z*z))*8))); |
|---|
| 89 |
nz.z = mixin(ctPowExpand("sqrt(y^2+x^2)*(-8*z^7+(56*y^2+56*x^2)*z^5 |
|---|
| 90 |
+(-56*y^4-112*x^2*y^2-56*x^4)*z^3+(8*y^6+24*x^2*y^4+24*x^4*y^2+8*x^6)*z.z)")) + c.z; |
|---|
| 91 |
z = nz; |
|---|
| 92 |
} |
|---|
| 93 |
r = z.length; |
|---|
| 94 |
if (r > 16) { |
|---|
| 95 |
break; |
|---|
| 96 |
} |
|---|
| 97 |
} |
|---|
| 98 |
// logln(c, ": estimated distance ", 0.5 * r * logf(r) / dl, " at ", i); |
|---|
| 99 |
return 0.5 * r * logf(r) / dl; |
|---|
| 100 |
} |
|---|
| 101 |
|
|---|
| 102 |
final class Fractal : SceneObject { |
|---|
| 103 |
float ε; int iters; bool adapt; |
|---|
| 104 |
static SceneObject fractal(ref string s) { |
|---|
| 105 |
float ε = 0.00001; int iters = 20; bool adapt; |
|---|
| 106 |
mixin(MatchExpr("s: [([[auto$#adapt=true$] $ε$|$#adapt=false$][, $iters$])]")); |
|---|
| 107 |
return new Fractal(ε, adapt, iters); |
|---|
| 108 |
} |
|---|
| 109 |
static this() { |
|---|
| 110 |
parse_fns["fractal"] = &fractal; |
|---|
| 111 |
builtin["fractal"] = true; |
|---|
| 112 |
help["fractal"] = ": stuff"; |
|---|
| 113 |
} |
|---|
| 114 |
this(float ε, bool adapt, int iters) { this.ε = ε; this.adapt = adapt; this.iters = iters; } |
|---|
| 115 |
Fractal dup() { return new Fractal(ε, adapt, iters); } |
|---|
| 116 |
struct FractBuffer { |
|---|
| 117 |
/*void next(ref Hit hit) { |
|---|
| 118 |
}*/ |
|---|
| 119 |
} |
|---|
| 120 |
mixin ThreadSlabSet!(FractBuffer); |
|---|
| 121 |
vec getGrad(vec v, float localε) { |
|---|
| 122 |
auto p = fn(v); |
|---|
| 123 |
return vec( |
|---|
| 124 |
fn(v + vec(localε,0,0), iters) - p, |
|---|
| 125 |
fn(v + vec(0,localε,0), iters) - p, |
|---|
| 126 |
fn(v + vec(0,0,localε), iters) - p |
|---|
| 127 |
); |
|---|
| 128 |
} |
|---|
| 129 |
override { |
|---|
| 130 |
string toString() { return "fractal"; } |
|---|
| 131 |
void iterate(void delegate(ref SceneObject) dg) { } |
|---|
| 132 |
void init(ref void* threadp) { |
|---|
| 133 |
auto c = cache_init(threadp); |
|---|
| 134 |
} |
|---|
| 135 |
void setup(ref Hit hit, ref Ray ray, ref void* threadp) { |
|---|
| 136 |
auto c = cache(threadp); |
|---|
| 137 |
// hit a sphere centered around origin radius 1 |
|---|
| 138 |
auto psq = ray.pos.lensq, dsd = ray.dir.lensq, |
|---|
| 139 |
b = (-ray.pos).dot(ray.dir) / dsd, disc = b*b + (2 - psq) / dsd; |
|---|
| 140 |
if (disc < 0) return; |
|---|
| 141 |
|
|---|
| 142 |
auto d = sqrt(disc), t1 = b - d, t2 = b + d; |
|---|
| 143 |
FP[2] bracket = void; |
|---|
| 144 |
bracket[0] = t1; |
|---|
| 145 |
bracket[1] = t2; |
|---|
| 146 |
if (bracket[1] <= HitTolerance) return; |
|---|
| 147 |
auto cur = bracket[0]; |
|---|
| 148 |
if (cur < 0) cur = 0; |
|---|
| 149 |
int x; |
|---|
| 150 |
int max_steps = iters * 2; // arbitrary choice |
|---|
| 151 |
while (true) { |
|---|
| 152 |
++x; |
|---|
| 153 |
auto dist = fn(ray.at(cur), iters); |
|---|
| 154 |
cur += dist; |
|---|
| 155 |
float localε = ε; |
|---|
| 156 |
/*logln("Local ε at ", cur, " => ", ray.at(cur), ": ", ray.radius(cur), " * ", localε, |
|---|
| 157 |
"; ray angle ", ray.angle, |
|---|
| 158 |
", ray origin ", ray.cam_origin, |
|---|
| 159 |
", distance ", (ray.cam_origin - ray.at(cur)).length |
|---|
| 160 |
);*/ |
|---|
| 161 |
if (adapt) |
|---|
| 162 |
localε *= ray.radius(cur); |
|---|
| 163 |
|
|---|
| 164 |
if (localε !> float.epsilon) |
|---|
| 165 |
localε = float.epsilon; // just to be sure |
|---|
| 166 |
|
|---|
| 167 |
if (dist < localε) { |
|---|
| 168 |
auto norm = getGrad(ray.at(cur), localε).normalized; |
|---|
| 169 |
|
|---|
| 170 |
if (norm.dot(ray.dir) > 0 && max_steps--) { |
|---|
| 171 |
cur += localε - dist; // definitely move away at least this much |
|---|
| 172 |
continue; // we're pointed AWAY. Not stopping. |
|---|
| 173 |
} |
|---|
| 174 |
// don't produce a hit if we're clearly moving away |
|---|
| 175 |
if (max_steps) hit.set(cur, true, norm); |
|---|
| 176 |
return; |
|---|
| 177 |
} |
|---|
| 178 |
if (cur !< bracket[1]) return; // left again |
|---|
| 179 |
} |
|---|
| 180 |
} |
|---|
| 181 |
Bound genBound() { |
|---|
| 182 |
return Bound(vec(-2), vec(2)); // I hope |
|---|
| 183 |
} |
|---|
| 184 |
} |
|---|
| 185 |
} |
|---|