I asked around to some very knowledgeable fractal geniuses, and so far,
nobody has seen it before. To make this more on topic, here is my quick
and dirty code that generates a PPM. It uses the GLM library that can be
found here:
https://github.com/g-truc/glm
For those that are using vcpkg for MSVC just install it using:
.\vcpkg install glm
https://vcpkg.io/en/index.html
Actually, this tool makes installing packages rather easy... Ahh that is
another topic altogether. Anyway, here is my code. I only compiled it
with MSVC so far in C++14 mode. I have to try some other compilers. Can
you get it to compile and generate a ppm called "ct_p0.ppm" on your end?
Thanks everybody:
_______________________________
#include <iostream>
#include <vector>
#include <cstdio>
#include <glm/glm.hpp>
#define CT_WIDTH 1920
#define CT_HEIGHT 1080
#define CT_PI 3.14159f
#define CT_PI2 (CT_PI * 2)
// The PPM Canvas
namespace ct
{
namespace ppm
{
struct canvas
{
unsigned long m_width;
unsigned long m_height;
std::vector<glm::ivec3> m_buf;
canvas(
unsigned long width,
unsigned long height
): m_width(width),
m_height(height),
m_buf(width * height)
{
std::cout << "(" << this << ")->canvas::canvas(" <<
width << ", " << height << ")\n";
}
bool
save(
char const* fname
) {
std::FILE* fout = fopen(fname, "wb");
if (fout)
{
char const ppm_head[] =
"P6\n"
"# Chris M. Thomasson Simple 2d Plane
ver:0.0.0.0 (pre-alpha)";
std::fprintf(fout, "%s\n%lu %lu\n%u\n",
ppm_head,
m_width, m_height,
255U
);
std::size_t size = m_width * m_height;
for (std::size_t i = 0; i < size; ++i)
{
glm::ivec3& pixel = m_buf[i];
std::fprintf(fout, "%c%c%c", pixel.r, pixel.g,
pixel.b);
}
if (! std::fclose(fout))
{
std::cout << "(" << this << ")->canvas::save("
<< fname << ")\n";
return true;
}
}
return false;
}
};
}
}
// The Plane
namespace ct
{
namespace plot2d
{
glm::vec4
axes_from_point(
glm::vec2 z,
float radius
) {
return {
z.x - radius, z.x + radius,
z.y - radius, z.y + radius
};
}
struct plane
{
glm::vec4 m_axes;
glm::vec4 m_step;
plane(
glm::vec4 const& axes,
unsigned long width,
unsigned long height
) : m_axes(axes)
{
float awidth = m_axes.y - m_axes.x;
float aheight = m_axes.w - m_axes.z;
float daspect = glm::abs((float)height / width);
float waspect = glm::abs(aheight / awidth);
if (daspect > waspect)
{
float excess = aheight * (daspect / waspect - 1.0f);
m_axes.w += excess / 2.0f;
m_axes.z -= excess / 2.0f;
}
else if (daspect < waspect)
{
float excess = awidth * (waspect / daspect - 1.0f);
m_axes.y += excess / 2.0f;
m_axes.x -= excess / 2.0f;
}
m_step.x = (m_axes.y - m_axes.x) / width;
m_step.y = (m_axes.w - m_axes.z) / height;
}
};
}
}
// The Plotter
namespace ct
{
namespace plot2d
{
struct plot
{
plane m_plane;
ppm::canvas& m_canvas;
plot(
plane const& plane,
ppm::canvas& canvas
) : m_plane(plane),
m_canvas(canvas)
{
}
bool
plot_pixel(
long x,
long y,
glm::vec3 const& color
) {
if (x > -1 && x < (long)m_canvas.m_width &&
y > -1 && y < (long)m_canvas.m_height)
{
// Now, we can convert to index.
std::size_t i = x + y * m_canvas.m_width;
glm::ivec3 rgb_color = { color.x * 255, color.y *
255, color.z * 255 };
m_canvas.m_buf[i] = rgb_color;
return true;
}
return false;
}
bool
plot_point(
glm::vec2 z,
glm::vec3 const& color
) {
long x = (long)((z.x - m_plane.m_axes.x) /
m_plane.m_step.x);
long y = (long)((m_plane.m_axes.w - z.y) /
m_plane.m_step.y);
if (x > -1 && x < (long)m_canvas.m_width &&
y > -1 && y < (long)m_canvas.m_height)
{
// Now, we can convert to index.
std::size_t i = x + y * m_canvas.m_width;
glm::ivec3 rgb_color = { color.x * 255, color.y *
255, color.z * 255 };
m_canvas.m_buf[i] = rgb_color;
return true;
}
return false;
}
};
}
}
// TTR
namespace ct
{
namespace ttr
{
struct circle
{
glm::vec3 p;
float r;
};
#define SQ(a) ((a) * (a))
bool
intersect(
circle const& c0,
circle const& c1,
circle& i0,
circle& i1
) {
// load from the two input circles c0 and c1
float A = c0.p.x;
float B = c0.p.y;
float C = c0.r;
float O = c1.p.x;
float P = c1.p.y;
float F = c1.r;
// gather delta's between c0 and c1
float G = A - O;
float H = B - P;
// evaluate the formulas conditions
if ((SQ(O - A) + SQ(P - B)) == 0 ||
(4 * SQ(O - A) + 4 * SQ(P - B)) == 0 ||
(((-SQ(SQ(C) - SQ(F) + SQ(O - A) + SQ(H))) /
(4 * SQ(O - A) + 4 * SQ(P - B)) +
SQ(C)) / (SQ(O - A) + SQ(P - B))) < 0.f)
{
return false;
}
float Q = -2 * SQ(A) + 4 * A * O - 2 * SQ(B) + 4 * B * P -
2 * SQ(O) - 2 * SQ(P);
float J = glm::sqrt(((-SQ(SQ(C) - SQ(F) + SQ(G) + SQ(H))) /
(4 * SQ(G) + 4 * SQ(H)) + SQ(C)) / (SQ(G) + SQ(H)));
float K = (-G) * (SQ(F) - SQ(C)) / Q + A / 2 + O / 2;
float L = (-H) * (SQ(F) - SQ(C)) / Q + B / 2 + P / 2;
float M = K - H * J; // x i0
float R = G * J + L; // y i0
float S = H * J + K; // x i1
float T = L - G * J; // y i1
i0.p.x = M;
i0.p.y = R;
i0.p.z = 0;
i0.r = 0;
i1.p.x = S;
i1.p.y = T;
i1.p.z = 0;
i1.r = 0;
return true;
}
}
}
// Test Fractal
namespace ct
{
namespace test_fractal
{
bool
find_tangent(
ct::ttr::circle c0,
ct::ttr::circle c1,
ct::ttr::circle& t0,
ct::ttr::circle& t1,
float radius
) {
// add the radius to the circles
c0.r += radius;
c1.r += radius;
// Find the intersections
if (!ct::ttr::intersect(c0, c1, t0, t1)) return false;
// Set the radius of the intersections
t0.r = radius;
t1.r = radius;
return true;
}
void
recur(
ct::plot2d::plot& scene,
unsigned long ri,
unsigned long rn,
ct::ttr::circle c0,
ct::ttr::circle c1,
float radius,
float scale
) {
if (ri >= rn) return;
// Find the tangent circles
ct::ttr::circle t0;
ct::ttr::circle t1;
if (! find_tangent(c0, c1, t0, t1, radius))
{
return;
}
// Plot points
scene.plot_point(t0.p, { 1, 1, 1 });
scene.plot_point(t1.p, { 1, 1, 1 });
float radius_next = radius * scale;
recur(scene, ri + 1, rn, c0, t0, radius_next, scale);
recur(scene, ri + 1, rn, c1, t0, radius_next, scale);
recur(scene, ri + 1, rn, c0, t1, radius_next, scale);
recur(scene, ri + 1, rn, c1, t1, radius_next, scale);
}
}
}
// Circle Test
void
ct_plot_circle(
ct::plot2d::plot& scene,
glm::vec3 origin,
float origin_radius
) {
unsigned long n = 512;
float angle_base = CT_PI2 / n;
for (unsigned long i = 0; i < n; ++i)
{
float angle = angle_base * i;
glm::vec3 p0_normal = {
glm::cos(angle),
glm::sin(angle),
0
};
glm::vec3 p0 = origin + p0_normal * origin_radius;
scene.plot_point(p0, { 1, 0, 1 });
}
}
int
main(void)
{
std::cout << "ct_ppm_base\n";
std::cout << "________________________________\n" << std::endl;
{
// Setup plotter...
ct::ppm::canvas canvas(CT_WIDTH, CT_HEIGHT);
glm::vec4 axes = ct::plot2d::axes_from_point({ 0, 0 }, 2.f);
ct::plot2d::plane plane(axes, canvas.m_width, canvas.m_height);
ct::plot2d::plot scene(plane, canvas);
// Plot test fractal
{
ct::ttr::circle c0 = { { -.5, 0, 0 }, .5 };
ct::ttr::circle c1 = { { .5, 0, 0 }, .5 };
ct::test_fractal::recur(scene, 0, 10, c0, c1, .8f, .45f);
}
// Plot unit circle
ct_plot_circle(scene, { 0, 0, 0 }, 1.f);
scene.m_canvas.save("ct_p0.ppm");
}
return 0;
}
_______________________________
Take a look at the code and tear it apart. Tell me about any issues.
What am I doing wrong? ;^)
Thanks again.