How to hide invisible elements of the 3d drawing? - algorithm

I'm trying to draw a 3d image that displays a ripple:
function myFunc(x, y) {
let zRipple =
Math.pow(2, -0.005 * (Math.abs(x) + Math.abs(y))) *
Math.cos(((x * x + y * y) * 2 * pi) / 180 / width) *
height;
return zRipple;
}
width and height here are constants that define a drawing area and are equal to 200 in my tests.
My approach is based on what I recall from an article that I read 30 years ago and trying to recall now.
The idea is to:
split the whole drawing board into the 10-pixel grid
for each 'cell' of the grid, draw a line to the nearest cell along the Y- and the X-axis' (step=10, ds=0.0
for (let x3 = width; x3 >= - width; x3 -= step) {
for (let y3 = -height; y3 <= height; y3 += step) {
for (let s = 0; s < step; s += ds) {
let x = x3 + s;
if (x < width) {
let z3 = myFunc(x, y3);
drawPixel3d(x, y3, z3);
}
}
for (let s = 0; s < step; s += ds) {
let y = y3 + s;
if (y < height) {
let z3 = myFunc(x3, y);
drawPixel3d(x3, y, z3);
}
}
}
}
}
Here is how I convert 3d coordinates to 2d:
function drawPixel3d(x3, y3, z3) {
let x2 = (x3 + y3) * Math.sin((60 * pi) / 180);
let y2 = z3 - ((x3 - y3) * Math.sin((30 * pi) / 180)) / 4;
drawPixel(x2, y2);
}
As you see from the image below, I get a decent graphic, but there is a problem: I draw ALL dots, not only those, that are VISIBLE.
Question: How do I check if any pixel needs to be displayed or not?
From what I can recall in that article, we should follow the approach:
start drawing from the front part of the scene (which I believe I do, the closest to the viewer or screen if dot with coordinates (width, -height)
for each pixel column - remember the 'Z' coordinate and only draw the new pixel if its Z-coordinate is bigger than the last recorded one
To achieve this I've modified my 'drawPixel3d' method:
function drawPixel3d(x3, y3, z3) {
let x2 = (x3 + y3) * Math.sin((60 * pi) / 180);
let y2 = z3 - ((x3 - y3) * Math.sin((30 * pi) / 180)) / 4;
let n = Math.round(x2);
let visible = false;
if (zs[n] === undefined) {
zs[n] = z3;
visible = true;
} else {
if (z3 > zs[n]) {
visible = z3 > zs[n];
zs[n] = z3;
}
}
if (visible) drawPixel(x2, y2);
}
But the result is not expected:
What do I do wrong? Or an alternative question: how to draw a simple 3d graphic?
Thanks!
P.S. The last piece of the program (that illustrates inversion of Y-coordinate during actual drawing):
function drawPixel(x: number, y: number) {
ctx.fillRect(cX + x, cY - y, 1, 1); // TS-way to draw pixel on canvas is to draw a rectangle
} // cX and cY are coordinates of the center of the drawing canvas
P.P.S. I have an idea of the algorithmic solution, so added an 'algorithm' tag: maybe someone from this community can help?

Your surface is concave which means you can not use simple methods based on dot product between face normal and camera view direction.
You got 3 obvious options for this.
use ray tracing
as you got analytical equation of the surface this might be even better way
use depth buffering to mask out the invisible stuff
As you render wireframe then you need to do this in 2 passes:
render invisible filled surface (fill just depth buffer not the screen)
render wireframe
your depth buffer condition must contain also equal values so either z<=depth[y][x] or z>=depth[y][x]
However you need to use face rendering (triangles or quads ...) and I assume this is software rendering so if you not familiar on such stuff see:
how to rasterize rotated rectangle (in 2d by setpixel)
Algorithm to fill triangle
use depth sorting by exploiting topology
If you do not have view transform so your x,y,z coordinates are directly corresponding to camera space coordinates then you can render the grid in back to front order simply by ordering the for loops and direction of iteration (its common in isometric views). This does not need depth buffering however you need to render filled QUADS in order to obtain correct output (border is set to the plot color and the inside is filled with background color).
I did go for the #2 approach. When I ported the last link into 3D I got this (C++ code):
//---------------------------------------------------------------------------
const int col_transparent=-1; // transparent color
class gfx_main
{
public:
Graphics::TBitmap *bmp; // VCL bitmap for win32 rendering
int **scr,**zed,xs,ys; // screen,depth buffers and resolution
struct pbuf // convex polygon rasterization line buffer
{
int x,z; // values to interpolate during rendering
pbuf() {}
pbuf(pbuf& a) { *this=a; }
~pbuf() {}
pbuf* operator = (const pbuf *a) { *this=*a; return this; }
//pbuf* operator = (const pbuf &a) { ...copy... return this; }
} *pl,*pr; // left,right buffers
gfx_main();
gfx_main(gfx_main& a) { *this=a; }
~gfx_main();
gfx_main* operator = (const gfx_main *a) { *this=*a; return this; }
//gfx_main* operator = (const gfx_main &a) { ...copy... return this; }
void resize(int _xs=-1,int _ys=-1);
void clear(int z,int col); // clear buffers
void pixel(int x,int y,int z,int col); // render 3D point
void line(int x0,int y0,int z0,int x1,int y1,int z1,int col); // render 3D line
void triangle(int x0,int y0,int z0,int x1,int y1,int z1,int x2,int y2,int z2,int col); // render 3D triangle
void _triangle_line(int x0,int y0,int z0,int x1,int y1,int z1); // this is just subroutine
};
//---------------------------------------------------------------------------
gfx_main::gfx_main()
{
bmp=new Graphics::TBitmap;
scr=NULL;
zed=NULL;
pl =NULL;
pr =NULL;
xs=0; ys=0;
resize(1,1);
}
//---------------------------------------------------------------------------
gfx_main::~gfx_main()
{
if (bmp) delete bmp;
if (scr) delete[] scr;
if (zed)
{
if (zed[0]) delete[] zed[0];
delete[] zed;
}
if (pl) delete[] pl;
if (pr) delete[] pr;
}
//---------------------------------------------------------------------------
void gfx_main::resize(int _xs,int _ys)
{
// release buffers
if (scr) delete[] scr;
if (zed)
{
if (zed[0]) delete[] zed[0];
delete[] zed;
}
if (pl) delete[] pl;
if (pr) delete[] pr;
// set new resolution and pixelformat
if ((_xs>0)&&(_ys>0)) bmp->SetSize(_xs,_ys);
xs=bmp->Width;
ys=bmp->Height;
bmp->HandleType=bmDIB;
bmp->PixelFormat=pf32bit;
// allocate buffers
scr=new int*[ys];
zed=new int*[ys];
zed[0]=new int[xs*ys]; // allocate depth buffer as single block
for (int y=0;y<ys;y++)
{
scr[y]=(int*)bmp->ScanLine[y]; // screen buffer point directly to VCL bitmap (back buffer)
zed[y]=zed[0]+(y*xs); // just set pointers for each depth line instead of allocating it
}
pl=new pbuf[ys];
pr=new pbuf[ys];
}
//---------------------------------------------------------------------------
int rgb2bgr(int col) // just support function reversing RGB order as VCL/GDI and its direct pixel access are not the same pixelformat
{
union
{
BYTE db[4];
int dd;
} c;
BYTE q;
c.dd=col;
q=c.db[0]; c.db[0]=c.db[2]; c.db[2]=q;
return c.dd;
}
//---------------------------------------------------------------------------
void gfx_main::clear(int z,int col)
{
// clear buffers
int x,y;
col=rgb2bgr(col);
for (y=0;y<ys;y++)
for (x=0;x<xs;x++)
{
scr[y][x]= 0x00000000; // black
zed[y][x]=-0x7FFFFFFF; // as far as posible
}
}
//---------------------------------------------------------------------------
void gfx_main::pixel(int x,int y,int z,int col)
{
col=rgb2bgr(col);
if ((x>=0)&&(x<xs)&&(y>=0)&&(y<ys)) // inside screen
if (zed[y][x]<=z) // not after something already rendered (GL_LEQUAL)
{
zed[y][x]=z; // update depth
if (col!=col_transparent) scr[y][x]=col;// update color
}
}
//---------------------------------------------------------------------------
void gfx_main::line(int x0,int y0,int z0,int x1,int y1,int z1,int col)
{
int i,n,x,y,z,kx,ky,kz,dx,dy,dz,cx,cy,cz;
// DDA variables (d)abs delta,(k)step direction
kx=0; dx=x1-x0; if (dx>0) kx=+1; if (dx<0) { kx=-1; dx=-dx; }
ky=0; dy=y1-y0; if (dy>0) ky=+1; if (dy<0) { ky=-1; dy=-dy; }
kz=0; dz=z1-z0; if (dz>0) kz=+1; if (dz<0) { kz=-1; dz=-dz; }
n=dx; if (n<dy) n=dy; if (n<dz) n=dz; if (!n) n=1;
// integer DDA
for (x=x0,y=y0,z=z0,cx=cy=cz=n,i=0;i<n;i++)
{
pixel(x,y,z,col);
cx-=dx; if (cx<=0){ cx+=n; x+=kx; }
cy-=dy; if (cy<=0){ cy+=n; y+=ky; }
cz-=dz; if (cz<=0){ cz+=n; z+=kz; }
}
}
//---------------------------------------------------------------------------
void gfx_main::triangle(int x0,int y0,int z0,int x1,int y1,int z1,int x2,int y2,int z2,int col)
{
int x,xx0,xx1,y,yy0,yy1,z,zz0,zz1,dz,dx,kz,cz;
// boundary line coordinates to buffers
_triangle_line(x0,y0,z0,x1,y1,z1);
_triangle_line(x1,y1,z1,x2,y2,z2);
_triangle_line(x2,y2,z2,x0,y0,z0);
// y range
yy0=y0; if (yy0>y1) yy0=y1; if (yy0>y2) yy0=y2;
yy1=y0; if (yy1<y1) yy1=y1; if (yy1<y2) yy1=y2;
// fill with horizontal lines
for (y=yy0;y<=yy1;y++)
if ((y>=0)&&(y<ys))
{
if (pl[y].x<pr[y].x){ xx0=pl[y].x; zz0=pl[y].z; xx1=pr[y].x; zz1=pr[y].z; }
else { xx1=pl[y].x; zz1=pl[y].z; xx0=pr[y].x; zz0=pr[y].z; }
dx=xx1-xx0;
kz=0; dz=zz1-zz0; if (dz>0) kz=+1; if (dz<0) { kz=-1; dz=-dz; }
for (cz=dx,x=xx0,z=zz0;x<=xx1;x++)
{
pixel(x,y,z,col);
cz-=dz; if (cz<=0){ cz+=dx; z+=kz; }
}
}
}
//---------------------------------------------------------------------------
void gfx_main::_triangle_line(int x0,int y0,int z0,int x1,int y1,int z1)
{
pbuf *pp;
int i,n,x,y,z,kx,ky,kz,dx,dy,dz,cx,cy,cz;
// DDA variables (d)abs delta,(k)step direction
kx=0; dx=x1-x0; if (dx>0) kx=+1; if (dx<0) { kx=-1; dx=-dx; }
ky=0; dy=y1-y0; if (dy>0) ky=+1; if (dy<0) { ky=-1; dy=-dy; }
kz=0; dz=z1-z0; if (dz>0) kz=+1; if (dz<0) { kz=-1; dz=-dz; }
n=dx; if (n<dy) n=dy; if (n<dz) n=dz; if (!n) n=1;
// target buffer according to ky direction
if (ky>0) pp=pl; else pp=pr;
// integer DDA line start point
x=x0; y=y0;
// fix endpoints just to be sure (wrong division constants by +/-1 can cause that last point is missing)
if ((y0>=0)&&(y0<ys)){ pp[y0].x=x0; pp[y0].z=z0; }
if ((y1>=0)&&(y1<ys)){ pp[y1].x=x1; pp[y1].z=z1; }
// integer DDA (into pbuf)
for (x=x0,y=y0,z=z0,cx=cy=cz=n,i=0;i<n;i++)
{
if ((y>=0)&&(y<ys))
{
pp[y].x=x;
pp[y].z=z;
}
cx-=dx; if (cx<=0){ cx+=n; x+=kx; }
cy-=dy; if (cy<=0){ cy+=n; y+=ky; }
cz-=dz; if (cz<=0){ cz+=n; z+=kz; }
}
}
//---------------------------------------------------------------------------
Just ignore/port the VCL stuff. I just added z coordinate to interpolation and rendering and also depth buffer. The rendering code looks like this:
//---------------------------------------------------------------------------
gfx_main gfx;
//---------------------------------------------------------------------------
float myFunc(float x,float y)
{
float z;
x-=gfx.xs/2;
y-=gfx.ys/2;
z=sqrt(((x*x)+(y*y))/((gfx.xs*gfx.xs)+(gfx.ys*gfx.ys))); // normalized distance from center
z=((0.25*cos(z*8.0*M_PI)*(1.0-z))+0.5)*gfx.ys;
return z;
}
//---------------------------------------------------------------------------
void view3d(int &x,int &y,int &z) // 3D -> 2D view (projection)
{
int zz=z;
z=y;
x=x +(y/2)-(gfx.xs>>2);
y=zz+(y/2)-(gfx.ys>>2);
}
//---------------------------------------------------------------------------
void draw()
{
int i,x,y,z,ds,x0,y0,z0,x1,y1,z1,x2,y2,z2,x3,y3,z3,col;
gfx.clear(-0x7FFFFFFF,0x00000000);
// render
ds=gfx.xs/50;
for (i=0;i<2;i++) // 2 passes
for (y=ds;y<gfx.ys;y+=ds)
for (x=ds;x<gfx.xs;x+=ds)
{
// 4 vertexes of a quad face
x0=x-ds; y0=y-ds; z0=myFunc(x0,y0);
x1=x; y1=y0; z1=myFunc(x1,y1);
x2=x; y2=y; z2=myFunc(x2,y2);
x3=x0; y3=y; z3=myFunc(x3,y3);
// camera transform
view3d(x0,y0,z0);
view3d(x1,y1,z1);
view3d(x2,y2,z2);
view3d(x3,y3,z3);
if (i==0) // first pass
{
// render (just to depth)
col=col_transparent;
gfx.triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,col);
gfx.triangle(x0,y0,z0,x2,y2,z2,x3,y3,z3,col);
}
if (i==1) // second pass
{
// render wireframe
col=0x00FF0000;
gfx.line(x0,y0,z0,x1,y1,z1,col);
gfx.line(x1,y1,z1,x2,y2,z2,col);
gfx.line(x2,y2,z2,x3,y3,z3,col);
gfx.line(x3,y3,z3,x0,y0,z0,col);
}
}
// here gfx.scr holds your rendered image
//---------------------------------------------------------------------------
Do not forget to call gfx.resize(xs,ys) with resolution of your view before rendering. As you can see I used different function (does not matter) here the output:
And here the same without depth condition in pixel(x,y,z,col)
The pbuf structure holds all the stuff that will be interpolated in the last rendering interpolation of the horizontal lines. So if you want gourard, textures or whatever you just add the variable to this structure and add the interpolation to the code (mimic the pbuf[].z interpolation code)
However this approach has one drawback. Your current approach interpolates one axis pixel by pixel and the other is stepping by grid size. This one is stepping both axises by grid size. So if you want to have the same behavior you might to do the first pass with 1 x 1 quads instead of ds x ds and then do the lines as you do now. In case 1 in your view is corresponding to pixel then you can do this on pixels alone without the face rendering however you risk holes in the output.

I got the idea of the solution: start drawing from the point nearest to the observer but for every combination of x2 and y2 coordinates draw the pixel only once and only when it is visible (never draw points behind others)... The only problem is that I don't draw EVERY point of the surface, I only draw a surface grid with 10 points step. As a result, part of the surface will be visible in 'between' the grid cells.
Another idea is to calculate distance from every drawing point of the surface to the observer and make sure to draw only that point that is visible of the surface that is CLOSEST to the observer... but how?

Related

I want to move a square in a circular motion in java processing

This is a school project so i cannot use a lot of functions like translate or rotate. I have to use basic trigonometry to do this. So I have made a square and I need it to move in a circular motion 360 degrees with one of it's point constant and not moving.
float rotX,rotY;
size(500,500);
fill(#B71143);
int rectX=width/4;
int rectY=height/10;
int rectSize=30;
angle=angle+0.1;
//rotX=rectX*cos(angle)-rectY*sin(angle);
//rotY=rectX*cos(angle)+rectY*sin(angle);
square(rotX,rotY,rectSize);
You are so close, at least in terms of the trigonometry part.
In terms of Processing you're only missing setup() and draw() which will draw a single frame (once you uncomment the assignments of rotX, rotY and initialise angle to 0)
Here's your code with above notes applied:
float rotX, rotY;
float angle = 0;
void setup() {
size(500, 500);
fill(#B71143);
}
void draw() {
int rectX=width/4;
int rectY=height/10;
int rectSize=30;
angle=angle+0.1;
rotX = rectX*cos(angle)-rectY*sin(angle);
rotY = rectX*cos(angle)+rectY*sin(angle);
square(rotX, rotY, rectSize);
}
Additionally, if you want to draw from the centre, you can add half the width/height to the square coordinates before rendering (equivalent to translating to centre), and if you want to draw a circle instead of an oval, use the same size for the two radii (named rectX, rectY):
float rotX, rotY;
float angle = 0;
void setup() {
size(500, 500);
fill(#B71143);
rectMode(CENTER);
}
void draw() {
int rectX=width/4;
int rectY=height/4;
int rectSize=30;
angle=angle+0.1;
rotX = rectX*cos(angle)-rectY*sin(angle);
rotY = rectX*cos(angle)+rectY*sin(angle);
// offset to center
rotX += width / 2;
rotY += height / 2;
square(rotX, rotY, rectSize);
}
Personally I'd simplify the code a bit (though your assignment requirements might differ based on your curriculum).
// initialise angle value
float angle = 0;
void setup() {
size(500, 500);
fill(#B71143);
}
void draw() {
// circular motion radius
int radius = width / 4;
// define square size
int rectSize=30;
// increment the angle (rotating around center)
angle=angle+0.1;
// convert polar (angle, radius) to cartesian(x,y) coords
float x = cos(angle) * radius;
float y = sin(angle) * radius;
// offset to center
x += width / 2;
y += height / 2;
// render the square at the rotated position
square(x, y, rectSize);
}
(In case you're after another polar to cartesian coordinates conversion formula explanation you can check out my older answer here which includes an interactive demo)

Drawing koch curves only using line function

I am trying to draw a koch curve (line) with basic trigonometric conversions.
I couldn't figure out what is the correct angle for newly generated peak point.
Here is my logic:
Given the start point of the line, angle of the line and the length for every segment, create this scheme.
After creating the schemem, treat every sub-lines starting point as new koch curves and repeat the steps.
I suspect the problem is at point 'pt' angle value.
/* Angle for turning downwards after the peak point */
float angle = 2*PI - PI/6;
void koch(Point2D start, float alpha, int d, int noi) {
Point2D p1 = new Point2D(start.x + d*cos(alpha), start.y + d*sin(alpha));
Point2D pt = new Point2D(start.x + d*sqrt(3)*cos(alpha+PI/6), start.y + d*sqrt(3)*sin(alpha+PI/6));
Point2D p2 = new Point2D(start.x + 2*d*cos(alpha), start.y + 2*d*sin(alpha));
Point2D p3 = new Point2D(start.x + 3*d*cos(alpha), start.y + 3*d*sin(alpha));
line(start.x, start.y, p1.x, p1.y);
line(p1.x, p1.y, pt.x, pt.y);
line(pt.x, pt.y, p2.x, p2.y);
line(p2.x, p2.y, p3.x, p3.y);
if(noi != 0) {
koch(start, alpha, d/3, noi-1);
koch(p1, alpha + PI/3, d/3, noi-1);
koch(pt, angle, d/3, noi-1); //Problem is here i suspect
koch(p2, alpha, d/3, noi-1);
}
return;
}
Calling this function with alpha being PI/6 and noi is 2 i get:
I want to get something like:
I was reluctant to answer as I do not code in Unity but as your question after few days still did not have any valid answer here is mine:
I do not see what I would expect in turtle graphics code. See:
Smooth Hilbert curves
and look for turtle_draw in the code. This is what I would expect:
initial string
turtle fractals are represented by a string holding turtle commands. Usual commands are:
f go forward by predetermined step
l turn left (CCW) by predetermined angle in your case 60 deg
r turn right (CW) by predetermined angle in your case 60 deg
For Koch snowflake you should start with triangle so "frrfrrf" the Koch curve starts with single line "f" instead.
iteration/recursion
for each level of iteration/recursion of the fractal you should replace each straight line command f by the triangular bump feature "flfrrflf" (make sure that last direction matches original f command). As the triangle tripled in size you should divide size of the f movement by 3 to stay at the same scale ...
render the string
simply process all the characters of the resulting string and render the lines. There are two approaches how to handle the rotations. Either remember direction angle and inc/dec it by rotation angle and compute the lines as polar coordinates increments (see the code below), or have direction in form of a 2D (or higher dimension) vector and apply rotation formula on it (see the link above).
Here small C++/VCL example of the Koch snowflake:
//---------------------------------------------------------------------------
#include <vcl.h>
#include <math.h>
#pragma hdrstop
#include "win_main.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
Graphics::TBitmap *bmp=new Graphics::TBitmap;
int xs,xs2,ys,ys2,n=0;
AnsiString str;
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
void turtle(TCanvas *scr,float x,float y,float a,float dl,AnsiString s)
{
int i;
char c;
float da=60.0*M_PI/180.0;
scr->MoveTo(x,y);
for (i=1;i<=s.Length();i++)
{
c=s[i];
if (c=='f')
{
x+=dl*cos(a);
y+=dl*sin(a);
scr->LineTo(x,y);
}
if (c=='l') a-=da;
if (c=='r') a+=da;
}
}
//---------------------------------------------------------------------------
AnsiString replace(AnsiString s0,char find,AnsiString replace)
{
int i;
char c;
AnsiString s="";
for (i=1;i<=s0.Length();i++)
{
c=s0[i];
if (c==find) s+=replace;
else s+=c;
}
return s;
}
//---------------------------------------------------------------------------
void draw()
{
str="frrfrrf"; // initial string
for (int i=0;i<n;i++) str=replace(str,'f',"flfrrflf"); // n times replacement
bmp->Canvas->Brush->Color=0x00000000; // just clear screen ...
bmp->Canvas->FillRect(TRect(0,0,xs,ys));
bmp->Canvas->Pen ->Color=0x00FFFFFF; // and some info text
bmp->Canvas->Font ->Color=0x00FFFFFF;
bmp->Canvas->TextOutA(5,5,AnsiString().sprintf("n:%i",n));
float nn=pow(3,n),a;
a=xs; if (a>ys) a=ys; a=0.75*a/nn;
turtle(bmp->Canvas,xs2-(0.5*nn*a),ys2-(0.33*nn*a),0.0,a,str); // render fractal
Form1->Canvas->Draw(0,0,bmp); // swap buffers to avoid flickering
}
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner)
{
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormDestroy(TObject *Sender)
{
delete bmp;
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormResize(TObject *Sender)
{
bmp->Width=ClientWidth;
bmp->Height=ClientHeight;
xs=ClientWidth;
ys=ClientHeight;
xs2=xs>>1;
ys2=ys>>1;
draw();
}
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
void __fastcall TForm1::FormPaint(TObject *Sender)
{
draw();
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormMouseWheel(TObject *Sender, TShiftState Shift, int WheelDelta, TPoint &MousePos, bool &Handled)
{
if (WheelDelta<0) if (n<8) n++;
if (WheelDelta>0) if (n>0) n--;
Handled=true;
draw();
}
//---------------------------------------------------------------------------
Ignore the VCL stuff. The important thing here are:
void turtle(TCanvas *scr,float x,float y,float a,float dl,AnsiString s)
which renders the string s on canvas scr (using VCL encapsulated GDI) where x,y is start position a is starting direction angle [rad] and dl is size of line.
AnsiString replace(AnsiString s0,char find,AnsiString replace)
which replace any find characters in s0 by replace pattern returned as a new string.
void draw()
which computes and render the fractal
Here few screenshots:
Now when I look at your code (just a quick look as I am too lazy to analyze your code in depth) you are generating points directly and without the incremental steps needed. Instead you are sort of hard-coding the triangular bump feature which will not work properly for next level of fractal recursion without clever indexing techniques. In your case it stop working properly even in the same level of recursion (on the next line because its oriented differently and you are not rotating but hard-coding the feature instead).
As far as I know basic Koch curve starts with a line, divides the length of step into three and puts a equilateral triangle in the middle:
There are different variations out there if you are interested in, but for basic Koch curve you can start with either two of p1, p2, p3, pt and start points that you drawn and calculate the rest respectively. In each iteration, you can go one level deeper.

Sierpinski carpet in processing

So I made the Sierpinski carpet fractal in processing using a Square data type which draw a square and has a function generate() that generates 9 equal squares out of itself and returns an ArrayList of (9-1)=8 squares removing the middle one (it is not added to the returned ArrayList) in order to generate the Sierpinski carpet.
Here is the class Square -
class Square {
PVector pos;
float r;
Square(float x, float y, float r) {
pos = new PVector(x, y);
this.r = r;
}
void display() {
noStroke();
fill(120,80,220);
rect(pos.x, pos.y, r, r);
}
ArrayList<Square> generate() {
ArrayList<Square> rects = new ArrayList<Square>();
float newR = r/3;
for (int i=0; i<3; i++) {
for (int j=0; j<3; j++) {
if (!(i==1 && j==1)) {
Square sq = new Square(pos.x+i*newR, pos.y+j*newR, newR);
rects.add(sq);
}
}
}
return rects;
}
}
This is the main sketch which moves forward the generation on mouse click -
ArrayList<Square> current;
void setup() {
size(600, 600);
current = new ArrayList<Square>();
current.add(new Square(0, 0, width));
}
void draw() {
background(255);
for (Square sq : current) {
sq.display();
}
}
void mousePressed() {
ArrayList<Square> next = new ArrayList<Square>();
for(Square sq: current) {
ArrayList<Square> rects = sq.generate();
next.addAll(rects);
}
current = next;
}
The problem :
The output that I am getting has very thin white lines which are not supposed to be there :
First generation -
Second generation -
Third generation -
My guess is that these lines are just the white background that shows up due to the calculations in generate() being off by a pixel or two. However I am not sure about how to get rid of these. Any help would be appreciated!
Here's a smaller example that demonstrates your problem:
size(1000, 100);
noStroke();
background(0);
float squareWidth = 9.9;
for(float squareX = 0; squareX < width; squareX += squareWidth){
rect(squareX, 0, squareWidth, height);
}
Notice that the black background is showing through the squares. Please try to post this kind of minimal example instead of your whole sketch in the future.
Anyway, there are three ways to fix this:
Option 1: Call the noSmooth() function.
By default, Processing uses anti-aliasing to make your drawings look smoother. Usually this is a good thing, but it can also add some fuzziness to the edges of shapes. If you disable anti-aliasing, your shapes will be more clear and you won't see the artifacts.
Option 2: Use a stroke with the same color as the fill.
As you've already discovered, this draws an outline around the shape.
Option 3: Use int values instead of float values.
You're storing your coordinates and sizes in float values, which can contain decimal places. The problem is, the screen (the actual pixels on your monitor) don't have decimal places (there is no such thing as half a pixel), so they're represented by int values. So when you convert a float value to an int, the decimal part is dropped, which can cause small gaps in your shapes.
If you just switch to using int values, the problem goes away:
size(1000, 100);
noStroke();
background(0);
int squareWidth = 10;
for(int squareX = 0; squareX < width; squareX += squareWidth){
rect(squareX, 0, squareWidth, height);
}

How to Compute OBB of Multiple Curves?

Given a number of curves,include line segments and circular arcs, how to compute the total OBB of all curves?
It seems that the union of each OBB of the individual curves does not right, it's not the minimal coverage.
Check this picture, how to compute the red box?
you should also add the input in vector form so we can test on your data ... I would approach like this:
find center of axis aligned bbox O(n)
compute max distance in each angle O(n)
just create table for enough m angles (like 5 deg step so m = 360/5) where for each angle section you remember max distant point distance only.
compute max perpendicular distance for each rotation O(m^2)
so for each angle section compute value that is:
value[actual_section] = max(distance[i]*cos(section_angle[i]-section_angle[actual_section]))
where i covers +/- 90 deg around actual section angle so now you got max perpendicular distances for each angle...
pick best solution O(m)
so look all rotations from 0 to 90 degrees and remember the one that has minimal OBB area. Just to be sure the OBB is aligned to section angle and size of axises is the value of that angle and all the 90 deg increments... around center
This will not result in optimal solution but very close to it. To improve precision you can use more angle sections or even recursively search around found solution with smaller and smaller angle step (no need to compute the other angle areas after first run.
[Edit1]
I tried to code this in C++ as proof of concept and use your image (handled as set of points) as input so here the result so you got something to compare to (for debugging purposes)
gray are detected points from your image, green rectangle is axis aligned BBox the red rectangle is found OBBox. The aqua points are found max distance per angle interval and green dots are max perpendicular distance for +/-90deg neighbor angle intervals. I used 400 angles and as you can see the result is pretty close ... 360/400 deg accuracy so this approach works well ...
Here C++ source:
//---------------------------------------------------------------------------
struct _pnt2D
{
double x,y;
// inline
_pnt2D() {}
_pnt2D(_pnt2D& a) { *this=a; }
~_pnt2D() {}
_pnt2D* operator = (const _pnt2D *a) { *this=*a; return this; }
//_pnt2D* operator = (const _pnt2D &a) { ...copy... return this; }
};
struct _ang
{
double ang; // center angle of section
double dis; // max distance of ang section
double pdis; // max perpendicular distance of +/-90deg section
// inline
_ang() {}
_ang(_ang& a) { *this=a; }
~_ang() {}
_ang* operator = (const _ang *a) { *this=*a; return this; }
//_ang* operator = (const _ang &a) { ...copy... return this; }
};
const int angs=400; // must be divisible by 4
const int angs4=angs>>2;
const double dang=2.0*M_PI/double(angs);
const double dang2=0.5*dang;
_ang ang[angs];
List<_pnt2D> pnt;
_pnt2D bbox[2],obb[4],center;
//---------------------------------------------------------------------------
void compute_OBB()
{
_pnt2D ppp[4];
int i,j; double a,b,dx,dy;
_ang *aa,*bb;
_pnt2D p,*pp; DWORD *q;
// convert bmp -> pnt[]
pnt.num=0;
Graphics::TBitmap *bmp=new Graphics::TBitmap;
bmp->LoadFromFile("in.bmp");
bmp->HandleType=bmDIB;
bmp->PixelFormat=pf32bit;
for (p.y=0;p.y<bmp->Height;p.y++)
for (q=(DWORD*)bmp->ScanLine[int(p.y)],p.x=0;p.x<bmp->Width;p.x++)
if ((q[int(p.x)]&255)<20)
pnt.add(p);
delete bmp;
// axis aligned bbox
bbox[0]=pnt[0];
bbox[1]=pnt[0];
for (pp=pnt.dat,i=0;i<pnt.num;i++,pp++)
{
if (bbox[0].x>pp->x) bbox[0].x=pp->x;
if (bbox[0].y>pp->y) bbox[0].y=pp->y;
if (bbox[1].x<pp->x) bbox[1].x=pp->x;
if (bbox[1].y<pp->y) bbox[1].y=pp->y;
}
center.x=(bbox[0].x+bbox[1].x)*0.5;
center.y=(bbox[0].y+bbox[1].y)*0.5;
// ang[] table init
for (aa=ang,a=0.0,i=0;i<angs;i++,aa++,a+=dang)
{
aa->ang=a;
aa-> dis=0.0;
aa->pdis=0.0;
}
// ang[].dis
for (pp=pnt.dat,i=0;i<pnt.num;i++,pp++)
{
dx=pp->x-center.x;
dy=pp->y-center.y;
a=atan2(dy,dx);
j=floor((a/dang)+0.5); if (j<0) j+=angs; j%=angs;
a=(dx*dx)+(dy*dy);
if (ang[j].dis<a) ang[j].dis=a;
}
for (aa=ang,i=0;i<angs;i++,aa++) aa->dis=sqrt(aa->dis);
// ang[].adis
for (aa=ang,i=0;i<angs;i++,aa++)
for (bb=ang,j=0;j<angs;j++,bb++)
{
a=fabs(aa->ang-bb->ang);
if (a>M_PI) a=(2.0*M_PI)-a;
if (a<=0.5*M_PI)
{
a=bb->dis*cos(a);
if (aa->pdis<a) aa->pdis=a;
}
}
// find best oriented bbox (the best angle is ang[j].ang)
for (b=0,j=0,i=0;i<angs;i++)
{
dx =ang[i].pdis; i+=angs4; i%=angs;
dy =ang[i].pdis; i+=angs4; i%=angs;
dx+=ang[i].pdis; i+=angs4; i%=angs;
dy+=ang[i].pdis; i+=angs4; i%=angs;
a=dx*dy; if ((b>a)||(i==0)) { b=a; j=i; }
}
// compute endpoints for OBB
i=j;
ppp[0].x=ang[i].pdis*cos(ang[i].ang);
ppp[0].y=ang[i].pdis*sin(ang[i].ang); i+=angs4; i%=angs;
ppp[1].x=ang[i].pdis*cos(ang[i].ang);
ppp[1].y=ang[i].pdis*sin(ang[i].ang); i+=angs4; i%=angs;
ppp[2].x=ang[i].pdis*cos(ang[i].ang);
ppp[2].y=ang[i].pdis*sin(ang[i].ang); i+=angs4; i%=angs;
ppp[3].x=ang[i].pdis*cos(ang[i].ang);
ppp[3].y=ang[i].pdis*sin(ang[i].ang); i+=angs4; i%=angs;
obb[0].x=center.x+ppp[0].x+ppp[3].x;
obb[0].y=center.y+ppp[0].y+ppp[3].y;
obb[1].x=center.x+ppp[1].x+ppp[0].x;
obb[1].y=center.y+ppp[1].y+ppp[0].y;
obb[2].x=center.x+ppp[2].x+ppp[1].x;
obb[2].y=center.y+ppp[2].y+ppp[1].y;
obb[3].x=center.x+ppp[3].x+ppp[2].x;
obb[3].y=center.y+ppp[3].y+ppp[2].y;
}
//---------------------------------------------------------------------------
I used mine dynamic list template so:
List<double> xxx; is the same as double xxx[];
xxx.add(5); adds 5 to end of the list
xxx[7] access array element (safe)
xxx.dat[7] access array element (unsafe but fast direct access)
xxx.num is the actual used size of the array
xxx.reset() clears the array and set xxx.num=0
xxx.allocate(100) preallocate space for 100 items
You can ignore the // convert bmp -> pnt[] VCL part as you got your data already.
I recommend to also take a look at my:
3D OBB approximation

Calculate ellipse size in relation to distance from center point

I want to achieve a slow fade in size on every collapse into itself. In other words, when the circle is at its biggest, the ellipses will be at the largest in size and conversely the opposite for the retraction. So far I am trying to achieve this affect by remapping the cSize from the distance of the center point, but somewhere along the way something is going wrong. At the moment I am getting a slow transition from small to large in ellipse size, but the inner ellipses are noticeably larger. I want an equal distribution of size amongst all ellipses in relation to center point distance.
I've simplified the code down to 4 ellipses rather than an array of rows of ellipses in order to hopefully simplify this example. This is done in the for (int x = -50; x <= 50; x+=100).
I've seen one or two examples that slightly does what I want, but is more or less static. This example is kind of similar because the ellipse size gets smaller or larger in relation to the mouse position
Distance2D
Here is an additional diagram of the grid of ellipses I am trying to create, In addition, I am trying to scale that "square grid" of ellipses by a center point.
Multiple ellipses + Scale by center
Any pointers?
float cSize;
float shrinkOrGrow;
void setup() {
size(640, 640);
noStroke();
smooth();
fill(255);
}
void draw() {
background(#202020);
translate(width/2, height/2);
if (cSize > 10) {
shrinkOrGrow = 0;
} else if (cSize < 1 ) {
shrinkOrGrow = 1;
}
if (shrinkOrGrow == 1) {
cSize += .1;
} else if (shrinkOrGrow == 0) {
cSize -= .1;
}
for (int x = -50; x <= 50; x+=100) {
for (int y = -50; y <= 50; y+=100) {
float d = dist(x, y, 0, 0);
float fromCenter = map(cSize, 0, d, 1, 10);
pushMatrix();
translate(x, y);
rotate(radians(d + frameCount));
ellipse(x, y, fromCenter, fromCenter);
popMatrix();
}
}
}
The values you're passing into the map() function don't make a lot of sense to me:
float fromCenter = map(cSize, 0, d, 1, 100);
The cSize variable bounces from 1 to 10 independent of anything else. The d variable is the distance of each ellipse to the center of the circle, but that's going to be static for each one since you're using the rotate() function to "move" the circle, which never actually moves. That's based only on the frameCount variable, which you never use to calculate the size of your ellipses.
In other words, the position of the ellipses and their size are completely unrelated in your code.
You need to refactor your code so that the size is based on the distance. I see two main options for doing this:
Option 1: Right now you're moving the circles on screen using the translate() and rotate() functions. You could think of this as the camera moving, not the ellipses moving. So if you want to base the size of the ellipse on its distance from some point, you have to get the distance of the transformed point, not the original point.
Luckily, Processing gives you the screenX() and screenY() functions for figuring out where a point will be after you transform it.
Here's an example of how you might use it:
for (int x = -50; x <= 50; x+=100) {
for (int y = -50; y <= 50; y+=100) {
pushMatrix();
//transform the point
//in other words, move the camera
translate(x, y);
rotate(radians(frameCount));
//get the position of the transformed point on the screen
float screenX = screenX(x, y);
float screenY = screenY(x, y);
//get the distance of that position from the center
float distanceFromCenter = dist(screenX, screenY, width/2, height/2);
//use that distance to create a diameter
float diameter = 141 - distanceFromCenter;
//draw the ellipse using that diameter
ellipse(x, y, diameter, diameter);
popMatrix();
}
}
Option 2: Stop using translate() and rotate(), and use the positions of the ellipses directly.
You might create a class that encapsulates everything you need to move and draw an ellipse. Then just create instances of that class and iterate over them. You'd need some basic trig to figure out the positions, but you could then use them directly.
Here's a little example of doing it that way:
ArrayList<RotatingEllipse> ellipses = new ArrayList<RotatingEllipse>();
void setup() {
size(500, 500);
ellipses.add(new RotatingEllipse(width*.25, height*.25));
ellipses.add(new RotatingEllipse(width*.75, height*.25));
ellipses.add(new RotatingEllipse(width*.75, height*.75));
ellipses.add(new RotatingEllipse(width*.25, height*.75));
}
void draw() {
background(0);
for (RotatingEllipse e : ellipses) {
e.stepAndDraw();
}
}
void mouseClicked() {
ellipses.add(new RotatingEllipse(mouseX, mouseY));
}
void mouseDragged() {
ellipses.add(new RotatingEllipse(mouseX, mouseY));
}
class RotatingEllipse {
float rotateAroundX;
float rotateAroundY;
float distanceFromRotatingPoint;
float angle;
public RotatingEllipse(float startX, float startY) {
rotateAroundX = (width/2 + startX)/2;
rotateAroundY = (height/2 + startY)/2;
distanceFromRotatingPoint = dist(startX, startY, rotateAroundX, rotateAroundY);
angle = atan2(startY-height/2, startX-width/2);
}
public void stepAndDraw() {
angle += PI/64;
float x = rotateAroundX + cos(angle)*distanceFromRotatingPoint;
float y = rotateAroundY + sin(angle)*distanceFromRotatingPoint;
float distance = dist(x, y, width/2, height/2);
float diameter = 50*(500-distance)/500;
ellipse(x, y, diameter, diameter);
}
}
Try clicking or dragging in this example. User interaction makes more sense to me using this approach, but which option you choose really depends on what fits inside your head the best.

Resources