3D triangle rasterization into voxel grid - c++

Prologue:
This is Q&A to cover 3D triangle rasterization into voxel grid I was asked for in order to solve a different problem related to material erosion/removal during manufactoring process simulation. The main Idea behind this question is how to port scanline based 2D triangle rasterization like this into 3D voxels.
So the question is How to efficiently rasterize 3D triangle into uniform voxel grid represented as 3D array with Voxels represented as floating point value (density or color if you like).
Using C++ without any additional libs for the rasterization itself.

So first updated algorithm:
definitions
Lets the triangle be given by its 3 int 3D vertexes and one float color:
void triangle(int x0,int y0,int z0,int x1,int y1,int z1,int x2,int y2,int z2,float c);
The target voxel grid is 3D array like this:
float map[vxs][vys][vzs];
where vxs,vys,vzs is the resolution of grid. Scanline algorithm requires left and right buffers however in 3D we can not use single axis for it so instead I chose the major axis (axis which has biggest side of AABB BBOX of input triangle) so buffer size vsz should be max of the 3 resolutions. As its just 1D array its not that memory hungry so I choose this instead for convenience:
const int vsz=vxs|vys|vzs;
int lx[vsz],ly[vsz],lz[vsz]; // left buffers for filling
int rx[vsz],ry[vsz],rz[vsz]; // right buffers for filling
compute AABB and chose major axis
int X0,Y0,Z0,X1,Y1,Z1,dx,dy,dz;
// BBOX
X0=x0; X1=x0;
if (X0>x1) X0=x1; if (X1<x1) X1=x1;
if (X0>x2) X0=x2; if (X1<x2) X1=x2;
Y0=y0; Y1=y0;
if (Y0>y1) Y0=y1; if (Y1<y1) Y1=y1;
if (Y0>y2) Y0=y2; if (Y1<y2) Y1=y2;
Z0=z0; Z1=z0;
if (Z0>z1) Z0=z1; if (Z1<z1) Z1=z1;
if (Z0>z2) Z0=z2; if (Z1<z2) Z1=z2;
dx=X1-X0;
dy=Y1-Y0;
dz=Z1-Z0;
now select major axis from the value of dx,dy,dz (biggest one). So for example if dx is biggest then from now on the left right buffers index will be x coordinate.
rasterize triangle edges into left/right buffers
now to chose if the edge should go to the left or right buffer we can use the sign of the major coordiante difference. So chose which one it is first and then order the edge vertexes by major coordinate (to avoid holes on joined triangles).
For the rasterization of edge (line) simply use integer version of DDA algortihm which is easily portable to higher dimensions and its also faster than Bresenham on modern CPUs.
Now instead of rendering voxels (x,y,z) of the line into map we simply render it into left/right buffers. So if the major axis is x and target buffer is left it would be:
ly[x]=y;
lz[x]=z;
As there are 3 possible major axises we need 3 versions of this function... Also there are 3 cases of the target buffer (left, right, both) but for that you might use pointers so non need to have each code twice...
Also in case the edges might be out of voxel grid range clipping is needed to add. I used simple if condition inside DDA iteration however to improve speed its better to clip the line before that...
fill in the triangle using lines
simply go through the left/right buffers and join the vertexes at the same index they represent with line. The line itself might be simplified as we knwo the major axis will not change in it however for simplicity I used normal 3D DDA line (the same as #3 just directly renders into map instead of left/right buffers).
Putting all together:
// voxel grid resolution
const int vxs=100;
const int vys=100;
const int vzs=100;
// helper buffers size should be max(vxs,vys,vzs)
const int vsz=vxs|vys|vzs;
float map[vxs][vys][vzs]; // voxel space map
int lx[vsz],ly[vsz],lz[vsz]; // left buffers for filling
int rx[vsz],ry[vsz],rz[vsz]; // right buffers for filling
void line(int x0,int y0,int z0,int x1,int y1,int z1,float c) // rencer line
{
int i,n,cx,cy,cz,sx,sy,sz;
// line DDA parameters
x1-=x0; sx=0; if (x1>0) sx=+1; if (x1<0) { sx=-1; x1=-x1; } if (x1) x1++; n=x1;
y1-=y0; sy=0; if (y1>0) sy=+1; if (y1<0) { sy=-1; y1=-y1; } if (y1) y1++; if (n<y1) n=y1;
z1-=z0; sz=0; if (z1>0) sz=+1; if (z1<0) { sz=-1; z1=-z1; } if (z1) z1++; if (n<z1) n=z1;
// single pixel (not a line)
if (!n)
{
if ((x0>=0)&&(x0<vxs)&&(y0>=0)&&(y0<vys)&&(z0>=0)&&(z0<vzs)) map[x0][y0][z0]=c;
return;
}
// ND DDA algo i is parameter
for (cx=cy=cz=n,i=0;i<n;i++)
{
if ((x0>=0)&&(x0<vxs)&&(y0>=0)&&(y0<vys)&&(z0>=0)&&(z0<vzs)) map[x0][y0][z0]=c;
cx-=x1; if (cx<=0){ cx+=n; x0+=sx; }
cy-=y1; if (cy<=0){ cy+=n; y0+=sy; }
cz-=z1; if (cz<=0){ cz+=n; z0+=sz; }
}
}
void _plinex(int x0,int y0,int z0,int x1,int y1,int z1) // helper edge render
{
int i,n,cx,cy,cz,sx,sy,sz,*by,*bz;
// target buffer & order points by x
if (x1>=x0)
{
i=x0; x0=x1; x0=i;
i=y0; y0=y1; y0=i;
i=z0; z0=z1; z0=i; by=ly; bz=lz;
} else { by=ry; bz=rz; }
// line DDA parameters
x1-=x0; sx=0; if (x1>0) sx=+1; if (x1<0) { sx=-1; x1=-x1; } if (x1) x1++; n=x1;
y1-=y0; sy=0; if (y1>0) sy=+1; if (y1<0) { sy=-1; y1=-y1; } if (y1) y1++; if (n<y1) n=y1;
z1-=z0; sz=0; if (z1>0) sz=+1; if (z1<0) { sz=-1; z1=-z1; } if (z1) z1++; if (n<z1) n=z1;
// single pixel (not a line)
if (!n)
{
if ((x0>=0)&&(x0<vxs))
{
ly[x0]=y0; lz[x0]=z0;
ry[x0]=y0; rz[x0]=z0;
}
return;
}
// ND DDA algo i is parameter
for (cx=cy=cz=n,i=0;i<n;i++)
{
if ((x0>=0)&&(x0<vxs)){ by[x0]=y0; bz[x0]=z0; }
cx-=x1; if (cx<=0){ cx+=n; x0+=sx; }
cy-=y1; if (cy<=0){ cy+=n; y0+=sy; }
cz-=z1; if (cz<=0){ cz+=n; z0+=sz; }
}
}
void _pliney(int x0,int y0,int z0,int x1,int y1,int z1) // helper edge render
{
int i,n,cx,cy,cz,sx,sy,sz,*bx,*bz;
// target buffer & order points by y
if (y1>=y0)
{
i=x0; x0=x1; x0=i;
i=y0; y0=y1; y0=i;
i=z0; z0=z1; z0=i; bx=lx; bz=lz;
} else { bx=rx; bz=rz; }
// line DDA parameters
x1-=x0; sx=0; if (x1>0) sx=+1; if (x1<0) { sx=-1; x1=-x1; } if (x1) x1++; n=x1;
y1-=y0; sy=0; if (y1>0) sy=+1; if (y1<0) { sy=-1; y1=-y1; } if (y1) y1++; if (n<y1) n=y1;
z1-=z0; sz=0; if (z1>0) sz=+1; if (z1<0) { sz=-1; z1=-z1; } if (z1) z1++; if (n<z1) n=z1;
// single pixel (not a line)
if (!n)
{
if ((y0>=0)&&(y0<vys))
{
lx[y0]=x0; lz[y0]=z0;
rx[y0]=x0; rz[y0]=z0;
}
return;
}
// ND DDA algo i is parameter
for (cx=cy=cz=n,i=0;i<n;i++)
{
if ((y0>=0)&&(y0<vys)){ bx[y0]=x0; bz[y0]=z0; }
cx-=x1; if (cx<=0){ cx+=n; x0+=sx; }
cy-=y1; if (cy<=0){ cy+=n; y0+=sy; }
cz-=z1; if (cz<=0){ cz+=n; z0+=sz; }
}
}
void _plinez(int x0,int y0,int z0,int x1,int y1,int z1) // helper edge render
{
int i,n,cx,cy,cz,sx,sy,sz,*bx,*by;
// target buffer & order points by z
if (z1>=z0)
{
i=x0; x0=x1; x0=i;
i=y0; y0=y1; y0=i;
i=z0; z0=z1; z0=i; bx=lx; by=ly;
} else { bx=rx; by=ry; }
// line DDA parameters
x1-=x0; sx=0; if (x1>0) sx=+1; if (x1<0) { sx=-1; x1=-x1; } if (x1) x1++; n=x1;
y1-=y0; sy=0; if (y1>0) sy=+1; if (y1<0) { sy=-1; y1=-y1; } if (y1) y1++; if (n<y1) n=y1;
z1-=z0; sz=0; if (z1>0) sz=+1; if (z1<0) { sz=-1; z1=-z1; } if (z1) z1++; if (n<z1) n=z1;
// single pixel (not a line)
if (!n)
{
if ((z0>=0)&&(z0<vzs))
{
lx[z0]=x0; ly[z0]=y0;
rx[z0]=x0; ry[z0]=y0;
}
return;
}
// ND DDA algo i is parameter
for (cx=cy=cz=n,i=0;i<n;i++)
{
if ((z0>=0)&&(z0<vzs)){ bx[z0]=x0; by[z0]=y0; }
cx-=x1; if (cx<=0){ cx+=n; x0+=sx; }
cy-=y1; if (cy<=0){ cy+=n; y0+=sy; }
cz-=z1; if (cz<=0){ cz+=n; z0+=sz; }
}
}
void triangle(int x0,int y0,int z0,int x1,int y1,int z1,int x2,int y2,int z2,float c) // render triangle
{
int X0,Y0,Z0,X1,Y1,Z1,dx,dy,dz,x,y,z;
// BBOX
X0=x0; X1=x0;
if (X0>x1) X0=x1; if (X1<x1) X1=x1;
if (X0>x2) X0=x2; if (X1<x2) X1=x2;
Y0=y0; Y1=y0;
if (Y0>y1) Y0=y1; if (Y1<y1) Y1=y1;
if (Y0>y2) Y0=y2; if (Y1<y2) Y1=y2;
Z0=z0; Z1=z0;
if (Z0>z1) Z0=z1; if (Z1<z1) Z1=z1;
if (Z0>z2) Z0=z2; if (Z1<z2) Z1=z2;
dx=X1-X0;
dy=Y1-Y0;
dz=Z1-Z0;
if ((dx>=dy)&&(dx>=dz)) // x is major axis
{
// render circumference into left/right buffers
_plinex(x0,y0,z0,x1,y1,z1);
_plinex(x1,y1,z1,x2,y2,z2);
_plinex(x2,y2,z2,x0,y0,z0);
// fill the triangle
if (X0<0) X0=0; if (X1>=vxs) X1=vxs-1;
for (x=X0;x<=X1;x++)
{
y0=ly[x]; z0=lz[x];
y1=ry[x]; z1=rz[x];
line(x,y0,z0,x,y1,z1,c);
}
}
else if ((dy>=dx)&&(dy>=dz)) // y is major axis
{
// render circumference into left/right buffers
_pliney(x0,y0,z0,x1,y1,z1);
_pliney(x1,y1,z1,x2,y2,z2);
_pliney(x2,y2,z2,x0,y0,z0);
// fill the triangle
if (Y0<0) Y0=0; if (Y1>=vys) Y1=vys-1;
for (y=Y0;y<=Y1;y++)
{
x0=lx[y]; z0=lz[y];
x1=rx[y]; z1=rz[y];
line(x0,y,z0,x1,y,z1,c);
}
}
else if ((dz>=dx)&&(dz>=dy)) // z is major axis
{
// render circumference into left/right buffers
_plinez(x0,y0,z0,x1,y1,z1);
_plinez(x1,y1,z1,x2,y2,z2);
_plinez(x2,y2,z2,x0,y0,z0);
// fill the triangle
if (Z0<0) Z0=0; if (Z1>=vzs) Z1=vzs-1;
for (z=Z0;z<=Z1;z++)
{
x0=lx[z]; y0=ly[z];
x1=rx[z]; y1=ry[z];
line(x0,y0,z,x1,y1,z,c);
}
}
}
The edge functions _pline can be speeded up, if you realize the major axis is the same as iteration axis i you can have one computation less. Also the 3 incarnations can be merged to single one (just by reordering the x,y,z order of called parameters and passing the left/right buffers pointers. And the moving the clipping outside DDA iteration hould boost performance quite a lot too.
Here preview of triangle intersecting cylinder:
And here preview of material removal using this triangle rasterization:
The orange triangle is not voxel one its just overlay, the voxel one is not rendered directly but substracted from the voxel grid instead to remove material during animation ...

Related

3D interpolation between curves [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 1 year ago.
Improve this question
I have a set of curves that are temperature dependent.
I.e curve Mat1 is for temperature 310C and Mat2 is for temp 420C.
As you can see, the data looks better when put in a logarithmic scale;
Now I need to get Mat3 curve for temperature 370C by interpolating Mat1 and Mat2 curves. What is the best way to get around doing this? I'm guessing that I might need to do some sort of 3D interpolation. The nature of the data (logarithmic behavior) also needs to be considered.
Here's the data for Mat1
9.43E+06 6.00E+04
3.96E+06 6.20E+04
1.78E+06 6.40E+04
8.52E+05 6.60E+04
4.28E+05 6.80E+04
2.25E+05 7.00E+04
1.23E+05 7.20E+04
6.95E+04 7.40E+04
4.05E+04 7.60E+04
2.43E+04 7.80E+04
1.49E+04 8.00E+04
9.39E+03 8.20E+04
Here's the data for Mat2
5.14E+08 4.80E+04
1.35E+08 5.00E+04
4.36E+07 5.20E+04
1.64E+07 5.40E+04
6.90E+06 5.60E+04
3.18E+06 5.80E+04
1.58E+06 6.00E+04
8.35E+05 6.20E+04
4.64E+05 6.40E+04
2.69E+05 6.60E+04
1.62E+05 6.80E+04
1.01E+05 7.00E+04
6.47E+04 7.20E+04
4.25E+04 7.40E+04
2.86E+04 7.60E+04
1.96E+04 7.80E+04
1.37E+04 8.00E+04
9735.23 8.20E+04
Any help would be appreciated.
Edit:
I'm adding data for two additional curves;
Curve at temperature 21C
3.98E+07 6.30E+04
1.58E+07 6.40E+04
4.03E+06 6.60E+04
1.47E+06 6.80E+04
6.57E+05 7.00E+04
3.37E+05 7.20E+04
1.91E+05 7.40E+04
1.16E+05 7.60E+04
7.49E+04 7.80E+04
5.04E+04 8.00E+04
3.52E+04 8.20E+04
2.53E+04 8.40E+04
1.87E+04 8.60E+04
1.41E+04 8.80E+04
1.08E+04 9.00E+04
8.47E+03 9.20E+04
Curve at temperature 537C
7.91E+06 3.80E+04
3.29E+06 4.00E+04
1.51E+06 4.20E+04
7.48E+05 4.40E+04
3.95E+05 4.60E+04
2.20E+05 4.80E+04
1.28E+05 5.00E+04
7.77E+04 5.20E+04
4.87E+04 5.40E+04
3.14E+04 5.60E+04
2.08E+04 5.80E+04
1.41E+04 6.00E+04
9.73E+03 6.20E+04
6.85E+03 6.40E+04
More info about the curves - These are alternating stress (y axis), number of cycles to failure (x axis) curves for a material at different temperatures.
Thanks.
I managed to get simple example working. First of all your data must be ordered so the measurements must be sorted by temperature and each measurement must be ordered by y (stress). I used ascending order. First algorith:
compute BBOX
simply compute min and max x,y coordinates of all measurements together. This will be used for conversion between logarithmic and linear scale and also for aligning.
resample and align all measurements
so convert all of your measurements to form that it's samples are at the same y values (across all measurements). I used uniformly sampled y axis. So simply step is (ymax-ymin)/(n-1) where n is number of points of the resampled data. So all measurements will have the same size and all the y values will be the same across measurement on the same index. Missing x data will be filled with 0.
The resampling can be done in linear scale. I used piecewise cubic interpolation.
create new measurement for new temperature
so simply create new measurement again containing n-points. The y value is the same as before (so just copy it from any of the aligned measurements) and then just take 1 point from each of the 4 measurements corresponding to the same point as we are processing and cubicaly interpolate its position. However this must be done in logarithmic scale!
The valid range of temperature is between the 2nd and 3th measurement temperature.
Here preview using your data and 370 C:
And here C++/VCL example for this (just ignore the VCL stuff):
//$$---- Form CPP ----
//---------------------------------------------------------------------------
#include <vcl.h>
#include <math.h>
#pragma hdrstop
#include "win_main.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
int xs,ys; // screen resolution
Graphics::TBitmap *bmp; // back buffer bitmap for rendering
//---------------------------------------------------------------------------
// here starts the important stuff
//---------------------------------------------------------------------------
float in[4][40]= // input measureements format is: { temperature,x0,y0,x1,y1...,-1 }
{{ 21.0,
3.98E+07,6.30E+04,
1.58E+07,6.40E+04,
4.03E+06,6.60E+04,
1.47E+06,6.80E+04,
6.57E+05,7.00E+04,
3.37E+05,7.20E+04,
1.91E+05,7.40E+04,
1.16E+05,7.60E+04,
7.49E+04,7.80E+04,
5.04E+04,8.00E+04,
3.52E+04,8.20E+04,
2.53E+04,8.40E+04,
1.87E+04,8.60E+04,
1.41E+04,8.80E+04,
1.08E+04,9.00E+04,
8.47E+03,9.20E+04,
-1.0 },
{ 310.0,
9.43E+06,6.00E+04,
3.96E+06,6.20E+04,
1.78E+06,6.40E+04,
8.52E+05,6.60E+04,
4.28E+05,6.80E+04,
2.25E+05,7.00E+04,
1.23E+05,7.20E+04,
6.95E+04,7.40E+04,
4.05E+04,7.60E+04,
2.43E+04,7.80E+04,
1.49E+04,8.00E+04,
9.39E+03,8.20E+04,
-1.0 },
{ 420.0,
5.14E+08,4.80E+04,
1.35E+08,5.00E+04,
4.36E+07,5.20E+04,
1.64E+07,5.40E+04,
6.90E+06,5.60E+04,
3.18E+06,5.80E+04,
1.58E+06,6.00E+04,
8.35E+05,6.20E+04,
4.64E+05,6.40E+04,
2.69E+05,6.60E+04,
1.62E+05,6.80E+04,
1.01E+05,7.00E+04,
6.47E+04,7.20E+04,
4.25E+04,7.40E+04,
2.86E+04,7.60E+04,
1.96E+04,7.80E+04,
1.37E+04,8.00E+04,
9735.23 ,8.20E+04,
-1.0 },
{ 537.0,
7.91E+06,3.80E+04,
3.29E+06,4.00E+04,
1.51E+06,4.20E+04,
7.48E+05,4.40E+04,
3.95E+05,4.60E+04,
2.20E+05,4.80E+04,
1.28E+05,5.00E+04,
7.77E+04,5.20E+04,
4.87E+04,5.40E+04,
3.14E+04,5.60E+04,
2.08E+04,5.80E+04,
1.41E+04,6.00E+04,
9.73E+03,6.20E+04,
6.85E+03,6.40E+04,
-1.0 }};
//---------------------------------------------------------------------------
// temp and output data
//---------------------------------------------------------------------------
const n=40; // points to resmaple curves with
float dat[4][2+n+n]; // resampled input curves
float out[2+n+n]; // interpolated curve
float xmin,xmax,ymin,ymax; // BBOX
void resample(float *out,float *in,float y0,float y1) // resample and align y to range and n points and store it to out
{
float t,d1,d2,a0,a1,a2,a3,x,y,x0,x1,x2,x3;
int i,ii,i0,i1,i2,i3,nn;
// scan how many points in[] has
for (nn=0,i=1;in[i]>=0.0;i+=2) nn++;
// resample input curves to n points
out[0]=in[0]; // copy T
out[n+n+1]=-1; // end of data
for (i=0;i<n;i++)
{
// y uniformly distributed and aligned in the dat array
y=y0+((y1-y0)*float(i)/float(n-1));
ii=1+i +i ;
// check if range present
if ((y<in[1+1])||(y>in[1+nn-1+nn-1+1]))
{
out[ii+0]=0.0;
out[ii+1]=y;
continue;
}
// find i1 so in[i1] <= y < in[i1+1]
// linear search, can be replaced with binary search
for (i1=0;i1<nn;i1++) if (in[1+i1+i1+1]>=y) break;
if (in[1+i1+i1+1]>y) i1--;
// neigboring indexes
i0=i1-1; if (i0< 0) i0= 0;
i2=i1+1; if (i2>=nn) i2=nn-1;
i3=i1+2; if (i3>=nn) i3=nn-1;
// convert to array index
i0=1+i0+i0;
i1=1+i1+i1;
i2=1+i2+i2;
i3=1+i3+i3;
// parameter is based on y value
d1=y-in[i1+1];
d2=in[i2+1]-in[i1+1];
if (fabs(d2)>1e-6) t=d1/d2; else t=0.0;
// points to interpolate
x0=in[i0];
x1=in[i1];
x2=in[i2];
x3=in[i3];
// cubic interpoaltion of x
d1=0.5*(x2-x0);
d2=0.5*(x3-x1);
a0=x1;
a1=d1;
a2=(3.0*(x2-x1))-(2.0*d1)-d2;
a3=d1+d2+(2.0*(-x2+x1));
x=a0+(a1*t)+(a2*t*t)+(a3*t*t*t);
if (x<0.0) x=0.0; // just to be sure data is not messed up
// copy point
out[ii+0]=x;
out[ii+1]=y;
}
}
//---------------------------------------------------------------------------
void interpolate(float *out,float T) // interpolate out[] as n point curve from dat[4][] matching temperature T
{ // dat[][] must be ordered ascending by T,x,y
int i,ii; // valid T range is <dat[1][0],dat[2][0]>
float t,d1,d2,a0,a1,a2,a3,x,x0,x1,x2,x3,t0,t1,t2,t3;
out[0]=T; // copy T
out[n+n+1]=-1; // end of data
// parameter from T
t=(T-dat[1][0])/(dat[2][0]-dat[1][0]);
t0=dat[0][0];
t1=dat[1][0];
t2=dat[2][0];
t3=dat[3][0];
// cubic interpolation between curves
for (i=0;i<n;i++)
{
// points to interpolate
ii=1+i+i;
x0=dat[0][ii];
x1=dat[1][ii];
x2=dat[2][ii];
x3=dat[3][ii];
// logarithm scale
(x0>=xmin)?x0=log(x0/xmin)/log(xmax/xmin):x0=0.0;
(x1>=xmin)?x1=log(x1/xmin)/log(xmax/xmin):x1=0.0;
(x2>=xmin)?x2=log(x2/xmin)/log(xmax/xmin):x2=0.0;
(x3>=xmin)?x3=log(x3/xmin)/log(xmax/xmin):x3=0.0;
out[ii+1]=dat[0][ii+1]; // copy y
// too much missing data
if ((x1<=0.0)||(x2<=0.0)){ out[ii+0]=0; continue; }
// mirror missing data
if (x0<=0.0) x0=x1-((x2-x1)*(t1-t0)/(t2-t1));
if (x3<=0.0) x3=x2+((x2-x1)*(t3-t2)/(t2-t1));
// interpolate x
d1=0.5*(x2-x0);
d2=0.5*(x3-x1);
a0=x1;
a1=d1;
a2=(3.0*(x2-x1))-(2.0*d1)-d2;
a3=d1+d2+(2.0*(-x2+x1));
x=a0+(a1*t)+(a2*t*t)+(a3*t*t*t);
if (x<0.0) x=0.0; // just to be sure data is not messed up
else x=exp(x*log(xmax/xmin))*xmin; // back to linear scale
out[ii+0]=x;
}
}
//---------------------------------------------------------------------------
void minmax(float *dat,bool _reset) // compute BBOX of the curves
{
int i;
float x,y;
for (i=1;dat[i]>=0.0;)
{
x=dat[i]; i++;
y=dat[i]; i++;
if (x<=0.0) continue;
if (_reset){ xmin=xmax=x; ymin=ymax=y; _reset=false; }
if (xmin>x) xmin=x;
if (xmax<x) xmax=x;
if (ymin>y) ymin=y;
if (ymax<y) ymax=y;
}
}
//---------------------------------------------------------------------------
void toscr(float &x,float &y) // convert x,y from plot data to screen coordinates (just for rendering)
{
float x0,dx,y1,dy;
// range <0,1>
// x=(x-xmin)/(xmax-xmin); // linear
// y=(y-ymin)/(ymax-ymin); // linear
(x>=xmin)?x=log(x/xmin)/log(xmax/xmin):x=0.0; // logarithmic
(y>=ymin)?y=log(y/ymin)/log(ymax/ymin):y=0.0; // logarithmic
// view
x0=0.1*xs; dx=0.8*xs;
y1=0.9*ys; dy=0.8*ys;
// [pixels]
x=x0+x*dx;
y=y1-y*dy;
}
//---------------------------------------------------------------------------
void plot(float *dat,TColor col)// renders measurement data (just for rendering)
{
int i,e;
float x,y,r=2;
// curve
bmp->Canvas->Pen->Color=col;
bmp->Canvas->Font->Color=col;
for (e=1,i=1;dat[i]>=0.0;)
{
x=dat[i]; i++;
y=dat[i]; i++;
if (x<=0.0) continue;
toscr(x,y);
if (e)
{
bmp->Canvas->TextOutA(x,y,AnsiString().sprintf("%.0f C",dat[0]));
bmp->Canvas->MoveTo(x,y);
e=0;
}
else bmp->Canvas->LineTo(x,y);
}
// points
for (i=1;dat[i]>=0.0;)
{
x=dat[i]; i++;
y=dat[i]; i++;
if (x<=0.0) continue;
toscr(x,y);
bmp->Canvas->Ellipse(x-r,y-r,x+r,y+r);
}
}
//---------------------------------------------------------------------------
void draw() // just render of my App
{
bmp->Canvas->Brush->Color=clWhite;
bmp->Canvas->FillRect(TRect(0,0,xs,ys));
plot(dat[0],clRed);
plot(dat[1],clGreen);
plot(dat[2],clBlue);
plot(dat[3],clBlack);
plot(out,clMaroon);
Form1->Canvas->Draw(0,0,bmp);
// bmp->SaveToFile("out.bmp");
}
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner):TForm(Owner) // init of my app
{
// init backbuffer
bmp=new Graphics::TBitmap;
bmp->HandleType=bmDIB;
bmp->PixelFormat=pf32bit;
// here prepare data (important)
int i;
for (i=0;i<4;i++) minmax(in[i],i==0);
for (i=0;i<4;i++) resample(dat[i],in[i],ymin,ymax);
// here create new data for T=370[C]
interpolate(out,370.0);
// and also include it to the BBOX for rendering
minmax(out,false);
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormDestroy(TObject *Sender) // not important just destructor of my App
{
delete bmp;
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormResize(TObject *Sender) // not important just resize event
{
xs=ClientWidth;
ys=ClientHeight;
bmp->Width=xs;
bmp->Height=ys;
draw();
}
//-------------------------------------------------------------------------
void __fastcall TForm1::FormPaint(TObject *Sender) // not important just repaint event
{
draw();
}
//---------------------------------------------------------------------------
See function TForm1::TForm1(TComponent* Owner) on how to use this.
However physical validity is questionable You should test if this kind of interpolation leads to valid data by having 5 measurements. Use 4 to interpolate the 5th and check if they overlap If not then this might need additional tweaking like increasing the interpolation polynomial degree, or use log scale also for resampling etc ...

how to alternate colors in a circle, so that circle looks like rotating?

The expected output should be like this with the colors changing their position as well:
Expected output-:
the colors should change their positions in a circle so that it looks like they are moving without changing the position of circle.
though my code is written in codeblocks in c/c++, i will be happy to get answers in any other programming languages.
my present code
#include<graphics.h>
#include<stdlib.h>
#include<stdio.h>
#include<conio.h>
#include<math.h>
#include<string.h>
#include<iostream>
using namespace std;
void vvcircle(float xk,float yk,float radius);
int i=0;
int main()
{
float xk,yk,radius;
int gdriver=DETECT,gmode,errorcode;
initgraph(&gdriver,&gmode,"C:\\TURBOC3\\BGI");
// cout<<"enter the value of x, y and radius of circle"<<endl;
//cin>>xk>>yk>>radius;
vvcircle(200,200,100);
getch();
closegraph();
return 0;
}
void vvcircle(float xk,float yk,float radius)
{
int color[60]={0,1,2,3,4,5,6,7,8,9};
while(radius>0)
{
float xo,yo;
float P;
xo=0.0;
yo=radius;
P=1-radius;
/// vvcircle(200,200,100);
for(;xo<=yo;)
{
putpixel(xo+xk,yo+yk,1);
putpixel(yo+xk,xo+yk,1);
putpixel(-yo+xk,xo+yk,2);
putpixel(xo+xk,-yo+yk,2);
putpixel(-yo+xk,-xo+yk,4);
putpixel(-xo+xk,-yo+yk,4);
putpixel(yo+xk,-xo+yk,4);
putpixel(-xo+xk,+yo+yk,4);
if(P<0)
{
xo=xo+1;
yo=yo;
P=P+2*xo+1;
}
else
{
xo=xo+1;
yo=yo-1;
P=P+(2*xo)-(2*yo)+1;
// putpixel(xo,yo,WHITE);
}
}
radius=radius-1;
}
}
Present output-:
i get many concentric circles with colors. but i want to move the colors so that it looks like the circle is moving and it is not achieved.
How about something like this:
#include <math.h>
void my_circle(int xc,int yc,int r,float a) // center(x,y), radius, animation angle [rad]
{
const int n=4; // segments count
int x,sx,xx,x0,x1,rr=r*r,
y,sy,yy,y0,y1,i,
dx[n+1],dy[n+1], // segments edges direction vectors
c[n]={5,1,2,3}; // segments colors
float da=2.0*M_PI/float(n);
// BBOX
x0=xc-r; x1=xc+r;
y0=yc-r; y1=yc+r;
// compute segments
for (i=0;i<=n;i++,a+=da)
{
dx[i]=100.0*cos(a);
dy[i]=100.0*sin(a);
}
// all pixels in BBOX
for (sx=x0,x=sx-xc;sx<=x1;sx++,x++){ xx=x*x;
for (sy=y0,y=sy-yc;sy<=y1;sy++,y++){ yy=y*y;
// outside circle?
if (xx+yy>rr) continue;
// compute segment
for (i=0;i<n;i++)
if ((x*dy[i ])-(y*dx[i ])>=0)
if ((x*dy[i+1])-(y*dx[i+1])<=0)
break;
// render
putpixel(sx,sy,c[i]);
}}
}
It simply loop through all pixels of outscribed square to your circle, determines if pixel is inside and then detect which segment it is in and color it with segments color.
The segments are described by direction vectors from circle center towards the segments edges. So if pixel is inside it mean its CW to one edge and CCW to the other so in 2D inspecting z coordinate of the cross product between vector to pixel and vectors to edges will tell if the pixel is in or not ...
As you can see I did not use floating point math in the rendering it self, its needed only to compute the segments edge vectors prior rendering...
I used standard 256 color VGA palette (not sure what BGI uses I expect 16 col) so the colors might be different on your platform here preview:
The noise is caused by my GIF capturing tool dithering the render itself is clean ...
Do not forget to call the my_circle repeatedly with changing angle ...
PS. I encoded this in BDS2006 without BGI so in different compiler there might be some minor syntax problem related to used language quirks...
I faked the putpixel with this:
void putpixel(int x,int y,BYTE c)
{
static const DWORD pal[256]=
{
0x00000000,0x000000A8,0x0000A800,0x0000A8A8,0x00A80000,0x00A800A8,0x00A85400,0x00A8A8A8,
0x00545454,0x005454FC,0x0054FC54,0x0054FCFC,0x00FC5454,0x00FC54FC,0x00FCFC54,0x00FCFCFC,
0x00000000,0x00101010,0x00202020,0x00343434,0x00444444,0x00545454,0x00646464,0x00747474,
0x00888888,0x00989898,0x00A8A8A8,0x00B8B8B8,0x00C8C8C8,0x00DCDCDC,0x00ECECEC,0x00FCFCFC,
0x000000FC,0x004000FC,0x008000FC,0x00BC00FC,0x00FC00FC,0x00FC00BC,0x00FC0080,0x00FC0040,
0x00FC0000,0x00FC4000,0x00FC8000,0x00FCBC00,0x00FCFC00,0x00BCFC00,0x0080FC00,0x0040FC00,
0x0000FC00,0x0000FC40,0x0000FC80,0x0000FCBC,0x0000FCFC,0x0000BCFC,0x000080FC,0x000040FC,
0x008080FC,0x009C80FC,0x00BC80FC,0x00DC80FC,0x00FC80FC,0x00FC80DC,0x00FC80BC,0x00FC809C,
0x00FC8080,0x00FC9C80,0x00FCBC80,0x00FCDC80,0x00FCFC80,0x00DCFC80,0x00BCFC80,0x009CFC80,
0x0080FC80,0x0080FC9C,0x0080FCBC,0x0080FCDC,0x0080FCFC,0x0080DCFC,0x0080BCFC,0x00809CFC,
0x00B8B8FC,0x00C8B8FC,0x00DCB8FC,0x00ECB8FC,0x00FCB8FC,0x00FCB8EC,0x00FCB8DC,0x00FCB8C8,
0x00FCB8B8,0x00FCC8B8,0x00FCDCB8,0x00FCECB8,0x00FCFCB8,0x00ECFCB8,0x00DCFCB8,0x00C8FCB8,
0x00B8FCB8,0x00B8FCC8,0x00B8FCDC,0x00B8FCEC,0x00B8FCFC,0x00B8ECFC,0x00B8DCFC,0x00B8C8FC,
0x00000070,0x001C0070,0x00380070,0x00540070,0x00700070,0x00700054,0x00700038,0x0070001C,
0x00700000,0x00701C00,0x00703800,0x00705400,0x00707000,0x00547000,0x00387000,0x001C7000,
0x00007000,0x0000701C,0x00007038,0x00007054,0x00007070,0x00005470,0x00003870,0x00001C70,
0x00383870,0x00443870,0x00543870,0x00603870,0x00703870,0x00703860,0x00703854,0x00703844,
0x00703838,0x00704438,0x00705438,0x00706038,0x00707038,0x00607038,0x00547038,0x00447038,
0x00387038,0x00387044,0x00387054,0x00387060,0x00387070,0x00386070,0x00385470,0x00384470,
0x00505070,0x00585070,0x00605070,0x00685070,0x00705070,0x00705068,0x00705060,0x00705058,
0x00705050,0x00705850,0x00706050,0x00706850,0x00707050,0x00687050,0x00607050,0x00587050,
0x00507050,0x00507058,0x00507060,0x00507068,0x00507070,0x00506870,0x00506070,0x00505870,
0x00000040,0x00100040,0x00200040,0x00300040,0x00400040,0x00400030,0x00400020,0x00400010,
0x00400000,0x00401000,0x00402000,0x00403000,0x00404000,0x00304000,0x00204000,0x00104000,
0x00004000,0x00004010,0x00004020,0x00004030,0x00004040,0x00003040,0x00002040,0x00001040,
0x00202040,0x00282040,0x00302040,0x00382040,0x00402040,0x00402038,0x00402030,0x00402028,
0x00402020,0x00402820,0x00403020,0x00403820,0x00404020,0x00384020,0x00304020,0x00284020,
0x00204020,0x00204028,0x00204030,0x00204038,0x00204040,0x00203840,0x00203040,0x00202840,
0x002C2C40,0x00302C40,0x00342C40,0x003C2C40,0x00402C40,0x00402C3C,0x00402C34,0x00402C30,
0x00402C2C,0x0040302C,0x0040342C,0x00403C2C,0x0040402C,0x003C402C,0x0034402C,0x0030402C,
0x002C402C,0x002C4030,0x002C4034,0x002C403C,0x002C4040,0x002C3C40,0x002C3440,0x002C3040,
0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
};
if ((x<0)||(x>=Main->xs)) return;
if ((y<0)||(y>=Main->ys)) return;
Main->pyx[y][x]=pal[c];
}
Where Main->xs, Main->ys is my window resolution and Main->pyx is direct pixel acces to its canvas for more info see:
Graphics rendering: (#4 GDI Bitmap)

Generating outside supporters into mesh for 3D printing

Prologue
This is my attempt to re-ask the closed Generating supporters for 3D printing as it is interesting question but lacking important details ... This is intended as Q&A and currently I am working on the code for the answer but feel free to answer (I accept the best answer).
Problem description
OK here some basic info about the problem:
Supports in 3D Printing: A technology overview
As this is a huge problem I will focus on the generic mesh/support-pattern merging geometry problem.
In a nutshell If we want to print any mesh we can do it only if it is connected to the starting plane up to angle of ~45 degrees (+/- for different printing technologies). So if we got parts that are not connected to this plane we need to create a bridge that will hold/connect it to it. Something like this (image taken from the page linked above):
Of coarse we need to add as small amount of material possible and still has it strong enough to hold our mesh in place without bending. On top of all this we need to weaken the support near the mesh so it can be break off after printing easily.
Do not forget that shape and placement is dependent on many things like material and technology used, heat flow.
Question:
To narrow this huge topic to answerable question let us focus solely on this problem:
How to merge 3D triangulated mesh (boundary representation like STL) with predefined support pattern (like 3 side prism) connecting it from defined plane perpendicularly ?
Using simple C++.
OK lets start with the absolute basics.
support shape
You can use any shape to meet the specifics of used printing technology. The easiest to generate within STL is 3 side prism like shape which contains 2 triangular bases (top and bottom) and 3 sides all of which have 2 triangles. So 8 triangles total.
This shape will start at some base plane (Z=0) and will go up until it hits the mesh. However to make this work the support must have a small gap between mesh and itself where we will add our weakened joint structure with mesh latter on.
support pattern
there are a lot of options here so I chose the simplest (not foul proof however) and that is to place the supports in a uniform grid with constant distance grid between the supports.
so simply cast a ray from each grid position on the base plane in up direction and check for intersection with mesh. If found place the support at that position with height just gap below the intersection point.
Joints
The idea is to join fan of very thin supports in cone like shape connecting & covering the supported surface above main support prism with less than 45 deg angle (so the gap should be big enough to cover grid distance in such manner).
The main problem here is that we must subdivide the triangles we are connecting to so we meet the STL mesh properties. To solve the connection problem (avoid holes or breaking connection requirements of the STL) we can use different solid for supports and different for our mesh. That will also allow us to touch surfaces without re-triangulating them making this a lot easier task.
For simplicity I chose tetrahedron shape which is simple to construct from triangles and also present weakness at the mesh/support joint.
So let us take some test STL mesh and place it above our base plane:
and place our main supports:
and also the joints:
Here VCL/C++ code for this STL3D.h:
//---------------------------------------------------------------------------
//--- simple STL 3D mesh ----------------------------------------------------
//---------------------------------------------------------------------------
#ifndef _STL3D_h
#define _STL3D_h
//---------------------------------------------------------------------------
#ifdef ComctrlsHPP
TProgressBar *progress=NULL; // loading progress bar for realy big STL files
#endif
void _progress_init(int n);
void _progress (int ix);
void _progress_done();
//---------------------------------------------------------------------------
class STL3D // STL 3D mesh
{
public:
double center[3],size[3],rmax; // bbox center,half sizes, max(size[])
struct _fac
{
float p[3][3]; // triangle vertexes CCW order
float n[3]; // triangle unit normal pointing out
WORD attr;
_fac() {}
_fac(_fac& a) { *this=a; }
~_fac() {}
_fac* operator = (const _fac *a) { *this=*a; return this; }
//_fac* operator = (const _fac &a) { ...copy... return this; }
void compute() // compute normal
{
float a[3],b[3];
vectorf_sub(a,p[1],p[0]);
vectorf_sub(b,p[2],p[1]);
vectorf_mul(n,a,b);
vectorf_one(n,n);
}
double intersect_ray(double *pos,double *dir) // return -1 or distance to triangle and unit ray intersection
{
double p0[3],p1[3],p2[3]; // input triangle vertexes
double e1[3],e2[3],pp[3],qq[3],rr[3]; // dir must be unit vector !!!
double t,u,v,det,idet;
// get points
vector_ld(p0,p[0][0],p[0][1],p[0][2]);
vector_ld(p1,p[1][0],p[1][1],p[1][2]);
vector_ld(p2,p[2][0],p[2][1],p[2][2]);
//compute ray triangle intersection
vector_sub(e1,p1,p0);
vector_sub(e2,p2,p0);
// Calculate planes normal vector
vector_mul(pp,dir,e2);
det=vector_mul(e1,pp);
// Ray is parallel to plane
if (fabs(det)<1e-8) return -1.0;
idet=1.0/det;
vector_sub(rr,pos,p0);
u=vector_mul(rr,pp)*idet;
if ((u<0.0)||(u>1.0)) return -1.0;
vector_mul(qq,rr,e1);
v=vector_mul(dir,qq)*idet;
if ((v<0.0)||(u+v>1.0)) return -1.0;
// distance
t=vector_mul(e2,qq)*idet;
if (t<0.0) t=-1.0;
return t;
}
};
List<_fac> fac; // faces
STL3D() { reset(); }
STL3D(STL3D& a) { *this=a; }
~STL3D() {}
STL3D* operator = (const STL3D *a) { *this=*a; return this; }
//STL3D* operator = (const STL3D &a) { ...copy... return this; }
void reset(){ fac.num=0; compute(); } // clear STL
void draw(); // render STL mesh (OpenGL)
void draw_normals(float size); // render STL normals (OpenGL)
void compute(); // compute bbox
void compute_normals(); // recompute normals from points
void supports(reper &obj); // compute supports with obj placement above base plane z=0
void load(AnsiString name);
void save(AnsiString name);
};
//---------------------------------------------------------------------------
void STL3D::draw()
{
_fac *f; int i,j; BYTE r,g,b;
glBegin(GL_TRIANGLES);
for (f=fac.dat,i=0;i<fac.num;i++,f++)
{
glNormal3fv(f->n);
if (f->attr<32768)
{
r= f->attr &31; r<<=3;
g=(f->attr>> 5)&31; g<<=3;
b=(f->attr>>10)&31; b<<=3;
glColor3ub(r,g,b);
}
for (j=0;j<3;j++) glVertex3fv(f->p[j]);
}
glEnd();
}
//---------------------------------------------------------------------------
void STL3D::draw_normals(float size)
{
_fac *f;
int i; float a[3],b[3];
glBegin(GL_LINES);
for (f=fac.dat,i=0;i<fac.num;i++,f++)
{
vectorf_add(a,f->p[0],f->p[1]);
vectorf_add(a,a ,f->p[2]);
vectorf_mul(a,a,1.0/3.0);
vectorf_mul(b,f->n,size); glVertex3fv(a);
vectorf_add(b,b,a); glVertex3fv(b);
}
glEnd();
}
//---------------------------------------------------------------------------
void STL3D::compute()
{
_fac *f;
int i,j,k;
double p0[3],p1[3];
vector_ld(center,0.0,0.0,0.0);
vector_ld(size,0.0,0.0,0.0);
rmax=0.0;
if (fac.num==0) return;
// bbox
for (k=0;k<3;k++) p0[k]=fac.dat[0].p[0][k];
for (k=0;k<3;k++) p1[k]=fac.dat[0].p[0][k];
for (f=fac.dat,i=0;i<fac.num;i++,f++)
for (j=0;j<3;j++)
for (k=0;k<3;k++)
{
if (p0[k]>f->p[j][k]) p0[k]=f->p[j][k];
if (p1[k]<f->p[j][k]) p1[k]=f->p[j][k];
}
vector_add(center,p0,p1); vector_mul(center,center,0.5);
vector_sub(size ,p1,p0); vector_mul(size ,size ,0.5);
rmax=size[0];
if (rmax<size[1]) rmax=size[1];
if (rmax<size[2]) rmax=size[2];
// attr repair
for (f=fac.dat,i=0;i<fac.num;i++,f++)
if (f->attr==0) f->attr=32768;
}
//---------------------------------------------------------------------------
void STL3D::compute_normals()
{
_fac *f; int i;
for (f=fac.dat,i=0;i<fac.num;i++,f++) f->compute();
}
//---------------------------------------------------------------------------
void STL3D::supports(reper &obj)
{
_fac *f,ff;
int i,j,k;
double p[3],dp[3],x0,y0,h0,x1,y1,x2,y2,h1,t;
// some config values first
const WORD attr0=31<<10; // support attr should be different than joint
const WORD attr1=31<<5; // joint attr should be different than mesh,support
const double grid0=8.0; // distance between supports
const double grid1=2.0; // distance between joints
const double gap=grid0/tan(45.0*deg);// distance between main support and mesh (joint size)
const double ha=1.0; // main support side size
// do not mess with these
const double hx= ha*cos(60.0*deg); // half size of main support in x
const double hy=0.5*ha*sin(60.0*deg); // half size of main support in y
const double grid2=0.4*hy; // distance between joints bases
const double ga=2.0*grid2*grid1/grid0; // main support side size
const double gx=hx*grid2/grid0; // half size of joint support in x
const double gy=hy*grid2/grid0; // half size of joint support in y
// apply placement obj (may lose some accuracy) not needed if matrices are not used
for (f=fac.dat,i=0;i<fac.num;i++,f++)
{
for (j=0;j<3;j++)
{
for (k=0;k<3;k++) p[k]=f->p[j][k]; // float->double
obj.l2g(p,p);
for (k=0;k<3;k++) f->p[j][k]=p[k]; // double->float
}
for (k=0;k<3;k++) p[k]=f->n[k]; // float->double
obj.l2g_dir(p,p);
for (k=0;k<3;k++) f->n[k]=p[k]; // double->float
} compute();
// create supports
for (x0=center[0]-size[0]+(0.5*grid0);x0<=center[0]+size[0]-(0.5*grid0);x0+=grid0)
for (y0=center[1]-size[1]+(0.5*grid0);y0<=center[1]+size[1]-(0.5*grid0);y0+=grid0)
{
// cast ray x0,y0,0 in Z+ direction to check for mesh intersection to compute the support height h0
h0=center[2]+size[2]+1e6;
vector_ld(p,x0,y0,0.0);
vector_ld(dp,0.0,0.0,+1.0);
for (f=fac.dat,i=0;i<fac.num;i++,f++)
{
t=f->intersect_ray(p,dp);
if ((t>=0.0)&&(t<h0)) h0=t;
}
if (h0>center[2]+size[2]+1e5) continue; // skip non intersected rays
h0-=gap; if (h0<0.0) h0=0.0;
// main suport prism
ff.attr=attr0;
// sides
ff.attr=attr0;
vectorf_ld(ff.p[0],x0-hx,y0-hy,0.0);
vectorf_ld(ff.p[1],x0+hx,y0-hy,0.0);
vectorf_ld(ff.p[2],x0-hx,y0-hy, h0); ff.compute(); fac.add(ff);
vectorf_ld(ff.p[0],x0+hx,y0-hy,0.0);
vectorf_ld(ff.p[1],x0+hx,y0-hy, h0);
vectorf_ld(ff.p[2],x0-hx,y0-hy, h0); ff.compute(); fac.add(ff);
vectorf_ld(ff.p[0],x0-hx,y0-hy, h0);
vectorf_ld(ff.p[1],x0 ,y0+hy,0.0);
vectorf_ld(ff.p[2],x0-hx,y0-hy,0.0); ff.compute(); fac.add(ff);
vectorf_ld(ff.p[0],x0-hx,y0-hy, h0);
vectorf_ld(ff.p[1],x0 ,y0+hy, h0);
vectorf_ld(ff.p[2],x0 ,y0+hy,0.0); ff.compute(); fac.add(ff);
vectorf_ld(ff.p[0],x0 ,y0+hy, h0);
vectorf_ld(ff.p[1],x0+hx,y0-hy,0.0);
vectorf_ld(ff.p[2],x0 ,y0+hy,0.0); ff.compute(); fac.add(ff);
vectorf_ld(ff.p[0],x0 ,y0+hy, h0);
vectorf_ld(ff.p[1],x0+hx,y0-hy, h0);
vectorf_ld(ff.p[2],x0+hx,y0-hy,0.0); ff.compute(); fac.add(ff);
// base triangles
vectorf_ld(ff.p[0],x0 ,y0+hy,0.0);
vectorf_ld(ff.p[1],x0+hx,y0-hy,0.0);
vectorf_ld(ff.p[2],x0-hx,y0-hy,0.0); ff.compute(); fac.add(ff);
vectorf_ld(ff.p[0],x0-hx,y0-hy, h0);
vectorf_ld(ff.p[1],x0+hx,y0-hy, h0);
vectorf_ld(ff.p[2],x0 ,y0+hy, h0); ff.compute(); fac.add(ff);
// joints
for (x1=x0-(0.5*grid0),x2=x0-(0.5*grid2);x1<=x0+(0.5*grid0);x1+=grid1,x2+=ga)
for (y1=y0-(0.5*grid0),y2=y0-(1.9*grid2);y1<=y0+(0.5*grid0);y1+=grid1,y2+=ga)
{
// cast ray x1,y1,0 in Z+ direction to check for mesh intersection to compute the joint height h1
h1=h0+gap+1e6;
vector_ld(p,x1,y1,0.0);
vector_ld(dp,0.0,0.0,+1.0);
for (f=fac.dat,i=0;i<fac.num;i++,f++)
{
t=f->intersect_ray(p,dp);
if ((t>=0.0)&&(t<h1)) h1=t;
}
if (h1>h0+gap+1e5) continue; // skip non intersected rays
// tetrahedron joints
ff.attr=attr1;
// base triangle
vectorf_ld(ff.p[0],x2 ,y2+gy,h0);
vectorf_ld(ff.p[1],x2+gx,y2-gy,h0);
vectorf_ld(ff.p[2],x2-gx,y2-gy,h0); ff.compute(); fac.add(ff);
// sides
vectorf_ld(ff.p[0],x2+gx,y2-gy,h0);
vectorf_ld(ff.p[1],x2 ,y2+gy,h0);
vectorf_ld(ff.p[2],x1 ,y1 ,h1); ff.compute(); fac.add(ff);
vectorf_ld(ff.p[0],x2 ,y2+gy,h0);
vectorf_ld(ff.p[1],x2-gx,y2-gy,h0);
vectorf_ld(ff.p[2],x1 ,y1 ,h1); ff.compute(); fac.add(ff);
vectorf_ld(ff.p[0],x2+gx,y2+gy,h0);
vectorf_ld(ff.p[1],x2-gx,y2-gy,h0);
vectorf_ld(ff.p[2],x1 ,y1 ,h1); ff.compute(); fac.add(ff);
}
}
// reverse placement obj (may lose some accuracy) not needed if matrices are not used
for (f=fac.dat,i=0;i<fac.num;i++,f++)
{
for (j=0;j<3;j++)
{
for (k=0;k<3;k++) p[k]=f->p[j][k]; // float->double
obj.g2l(p,p);
for (k=0;k<3;k++) f->p[j][k]=p[k]; // double->float
}
for (k=0;k<3;k++) p[k]=f->n[k]; // float->double
obj.g2l_dir(p,p);
for (k=0;k<3;k++) f->n[k]=p[k]; // double->float
} compute();
}
//---------------------------------------------------------------------------
void STL3D::load(AnsiString name)
{
int adr,siz,hnd;
BYTE *dat;
AnsiString lin,s;
int i,j,l,n;
_fac f;
reset(); f.attr=0;
siz=0;
hnd=FileOpen(name,fmOpenRead);
if (hnd<0) return;
siz=FileSeek(hnd,0,2);
FileSeek(hnd,0,0);
dat=new BYTE[siz];
if (dat==NULL) { FileClose(hnd); return; }
FileRead(hnd,dat,siz);
FileClose(hnd);
adr=0; s=txt_load_str(dat,siz,adr,true);
// ASCII
if (s=="solid")
{
_progress_init(siz); int progress_cnt=0;
for (adr=0;adr<siz;)
{
progress_cnt++; if (progress_cnt>=128) { progress_cnt=0; _progress(adr); }
lin=txt_load_lin(dat,siz,adr,true);
for (i=1,l=lin.Length();i<=l;)
{
s=str_load_str(lin,i,true);
if (s=="solid") { name=str_load_str(lin,i,true); break; }
if (s=="endsolid") break;
if (s=="facet")
{
j=0;
s=str_load_str(lin,i,true);
f.n[0]=str2num(str_load_str(lin,i,true));
f.n[1]=str2num(str_load_str(lin,i,true));
f.n[2]=str2num(str_load_str(lin,i,true));
}
if (s=="vertex")
if (j<3)
{
f.p[j][0]=str2num(str_load_str(lin,i,true));
f.p[j][1]=str2num(str_load_str(lin,i,true));
f.p[j][2]=str2num(str_load_str(lin,i,true));
j++;
if (j==3) fac.add(f);
}
break;
}
}
}
// binary
else{
adr=80;
n=((DWORD*)(dat+adr))[0]; adr+=4;
fac.allocate(n); fac.num=0;
_progress_init(n); int progress_cnt=0;
for (i=0;i<n;i++)
{
if (adr+50>siz) break; // error
progress_cnt++; if (progress_cnt>=128) { progress_cnt=0; _progress(i); }
f.n[0]=((float*)(dat+adr))[0]; adr+=4;
f.n[1]=((float*)(dat+adr))[0]; adr+=4;
f.n[2]=((float*)(dat+adr))[0]; adr+=4;
for (j=0;j<3;j++)
{
f.p[j][0]=((float*)(dat+adr))[0]; adr+=4;
f.p[j][1]=((float*)(dat+adr))[0]; adr+=4;
f.p[j][2]=((float*)(dat+adr))[0]; adr+=4;
}
f.attr=((WORD*)(dat+adr))[0]; adr+=2; // attributes
fac.add(f);
}
}
_progress_done();
delete[] dat;
compute();
}
//---------------------------------------------------------------------------
void STL3D::save(AnsiString name)
{
// ToDo
}
//---------------------------------------------------------------------------
void _progress_init(int n)
{
#ifdef ComctrlsHPP
if (progress==NULL) return;
progress->Position=0;
progress->Max=n;
progress->Visible=true;
#endif
}
//---------------------------------------------------------------------------
void _progress (int ix)
{
#ifdef ComctrlsHPP
if (progress==NULL) return;
progress->Position=ix;
progress->Update();
#endif
}
//---------------------------------------------------------------------------
void _progress_done()
{
#ifdef ComctrlsHPP
if (progress==NULL) return;
progress->Visible=false;
#endif
}
//---------------------------------------------------------------------------
#endif
//---------------------------------------------------------------------------
Usage is simple:
#include "STL3D.h" // STL mesh (this is the important stuff)
STL3D mesh; // point cloud and tetrahedronal mesh
mesh.load("space_invader_magnet.stl");
mesh.supports(obj); // obj is object holding 4x4 uniform matrix of placement if you STL is already placed than it is not needed
I used a lot of stuff from mine OpenGL engine like dynamic List<> template:
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
or vector and matrix math (vectorf_ works with float* and vector_ with double) which is not too important. If you need the math see:
Understanding 4x4 homogenous transform matrices
If the STL is already placed (no matrix) than no placement conversions nor the obj is needed at all. The code reflects the bullets above. I wanted to keep it as simple as I could so no optimizations are there yet.
The gap and grid constants are hard-coded in the supports function and are not yet set to the valid values.
[Notes]
Now this barely cover only the very basic of the problem and there are a lot of edge cases left un-handled to keep this "short". The code itself does not check if triangles are above the 45 degree slope but that can be done with simple normal angle checking like:
if (acos(dot(normal,(0.0,0.0,1.0))<45.0*deg) continue;
There is the need to add also the supports between parts of mesh for example if your object has more layers than only the first layer will be supported from base plane. the rest must use the layer below itself ... and using weakened joint on both sides of support. That is similar to place the first layer of supports you just need to cast the ray in both directions ... or cast continuous ray going through the whole bbox and check for start/end surfaces by analyzing normal direction to ray (simple sign of dot product). For example this is mesh placement that would possibly need this (for some technologies):
While designing the supports take in mind you should meet the proper winding rule (CCW) and normal direction (out) for the printing process ...

Render filled complex polygons with large number of vertices with OpenGL

I use OpenGL to render 2D map and in the process I need to render filled polygons with large number of vertices(100,000+). To do this, I tessellated the polygons to triangles using glu tessellator and rendered the triangles with VBO.
The polygons are rendered successfully. The problem is that the tessellation process turns out to be extremely slow. For some charts with 500,000 vertices, it will take nearly 2 mins on my laptop(i5-3230M 2.6GHz, 8G RAM). This is unacceptable for my application.
Are there any other tessellation algorithm faster than glu tessellator?
Or I have done it wrong?
The following two images are the the rendering results with
glPolygonMode(GL_FRONT, GL_LINE)
EDIT : The map data is static and the original polygon data is in latitude-longitude format. I've already saved the tessellated polygon data (those triangles) in separate file.
To be more clear(Not directly related to the issue),for rendering on screen, a projection is needed to transform the LL format to screen coordinates.
The problem is that the user may have thousands of charts to install(in which the tessellation will be done). Although the tessellation will be run only once, it still takes too long.
is the map static or dynamic?
For static maps
why not store tesselated polygons in some file and not tesselate it again ...
For dynamic maps
would be may be faster using different rendering approach that do not need tesselation to convex polygons like this:
clear screen with island color
render islands outlines
not filled primitives like GL_LINE_LOOP do not need to tesselate at all.
fill in the watter
simply start from point outside any polygon and flood fill the map with watter. If the flood fill is coded right (no recursion and fill with lines instead of pixels) then it should take just few [ms]. The problem with this approach is that you need to access the rendered stuff so you need at least 2 passes of rendering. Also implementing flood filling on GPU is not easy.
There are also alternatives like storing edge points on CPU side and pre-compute the watter filling on CPU side. In that case you need to have list of x coordinates for every y scan line of image that will hold the start and end points for each land. then just fill in the gaps in single render pass...
This should be rendered in RT easily
[Edit] Grow Fill test Demo
Did some testing with iterative grow filling on your data. There are some problems with the dataset like your polygons overlap which is possibly just holes but as I do not have filling color info but just object ID instead so it is hard to say. Anyway that can be repaired too. Here small win 32 VCL/OpenGL/SW demo with approach I mentioned above (Dynamic maps):
Win32 Demo use Slow download which is free and no need for any registration just input the code.
It is Win32 stand alone no install using OpenGL+VCL
mouse wheel zooms
Shift+mouse wheel selects different polygon
mouse+Left button pans
There are few issues which can be repaired but as proof of concept it works well. I compiled your ASCII map into binary form (so it loads faster but the form is the same just count of polygons, then count of points per polygon and the points x,y as 64 bit doubles. Counts are 32bit ints)
I am using my own OpenGL engine (which I can not share) so you would need to encode the stuff (like OpenGL,FBO and Texture init/set/usage). Anyway here the C++ code for this VCL app:
//$$---- Form CPP ----
//---------------------------------------------------------------------------
#include <vcl.h>
#include <math.h>
#pragma hdrstop
#include "win_main.h"
#include "gl/OpenGL3D_double.cpp"
#include "performance.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
// VCL
TMain *Main;
// OpenGL
OpenGLtime tim;
OpenGLscreen scr;
OpenGL_FBO fbo;
GLuint txr_map=-1;
// miscel
int pn=0; // vertex count
double px0,px1,py0,py1; // bbox
double mx,my; // mouse
double view[16],iview[16]; // direct and inverse Modelview matrix
double zoom=1.0,dzoom=1.1,viewx=0.0,viewy=0.0; // view
int index=0; // selected polygon
bool _redraw=true;
DWORD cl_water=0xFFEE9040;
DWORD cl_land =0xFF70A0B0;
DWORD cl_edge =0xFF000000;
DWORD cl_sel =0xFF00FFFF;
AnsiString tcpu,tgpu;
// map
List< List<double> > polygon; // loaded polygons
List<double> water; // points with water from last frame
//---------------------------------------------------------------------------
void view_compute()
{
double x,y;
glMatrixMode(GL_MODELVIEW);
glPushMatrix();
glLoadIdentity();
x=divide(1.0,px1-px0)*scr.aspect;
y=divide(1.0,py1-py0)*scr._aspect;
if (x>y) x=y;
x*=zoom;
glTranslated(viewx,viewy,0.0);
glScaled(x,x,1.0);
glTranslated(-0.5*(px0+px1),-0.5*(py0+py1),0.0);
glGetDoublev(GL_MODELVIEW_MATRIX,view);
glPopMatrix();
matrix_inv(iview,view);
}
//---------------------------------------------------------------------------
void map_load_csv(AnsiString filename)
{
BYTE *dat;
AnsiString lin,s,s0;
int ix,i,l,hnd,siz,adr;
double x,y;
List< AnsiString > id;
id.allocate(128); id.num=0;
polygon.allocate(128); polygon.num=0;
hnd=FileOpen(filename,fmOpenRead); if (hnd<0) return;
siz=FileSeek(hnd,0,2);
FileSeek(hnd,0,0);
dat=new BYTE[siz]; if (dat==NULL) { FileClose(hnd); return; }
siz=FileRead(hnd,dat,siz);
FileClose(hnd);
adr=0; txt_load_lin(dat,siz,adr,true);
for (ix=-1,s0="";adr<siz;)
{
lin=txt_load_lin(dat,siz,adr,true);
if (lin=="") continue;
i=1; l=lin.Length();
s=str_load_str(lin,i,true); s=s.SubString(2,s.Length()-2);
if (s0!=s)
{
for (ix=0;ix<id.num;ix++) if (id[ix]==s) break;
if (ix>=id.num)
{
ix=id.num;
id.add(s);
polygon.add();
polygon[ix].allocate(256);
polygon[ix].num=0;
}
s0=s;
}
s=str_load_str(lin,i,true); s=s.SubString(2,s.Length()-2); x=str2flt(s);
s=str_load_str(lin,i,true); s=s.SubString(2,s.Length()-2); y=str2flt(s);
polygon[ix].add(x);
polygon[ix].add(y);
}
}
//---------------------------------------------------------------------------
void map_save_bin(AnsiString filename)
{
int hnd,i;
hnd=FileCreate(filename); if (hnd<0) return;
FileWrite(hnd,&polygon.num,4);
for (i=0;i<polygon.num;i++)
{
FileWrite(hnd,&polygon[i].num,4);
FileWrite(hnd,polygon[i].dat,polygon[i].num*8);
}
FileClose(hnd);
}
//---------------------------------------------------------------------------
void map_load_bin(AnsiString filename)
{
int hnd,i,n,m;
hnd=FileOpen(filename,fmOpenRead); if (hnd<0) return;
FileRead(hnd,&n,4);
polygon.allocate(n); polygon.num=n;
for (i=0;i<n;i++)
{
FileRead(hnd,&m,4);
polygon[i].allocate(m); polygon[i].num=m;
FileRead(hnd,polygon[i].dat,m*8);
}
FileClose(hnd);
}
//---------------------------------------------------------------------------
void map_bbox()
{
int ix,i,n;
double *p,a;
pn=0;
px0=px1=polygon[0][0];
py0=py1=polygon[0][1];
for (ix=0;ix<polygon.num;ix++)
{
p=polygon[ix].dat;
n=polygon[ix].num; pn+=n>>1;
for (i=0;i<n;i+=2)
{
a=*p; p++; if (px0>a) px0=a; if (px1<a) px1=a;
a=*p; p++; if (py0>a) py0=a; if (py1<a) py1=a;
}
}
}
//---------------------------------------------------------------------------
void map_draw()
{
int ix,i,n;
double *p,a;
// glLineWidth(2.0);
for (ix=0;ix<polygon.num;ix++)
{
p=polygon[ix].dat;
n=polygon[ix].num;
if (ix==index) glColor4ubv((BYTE*)&cl_sel);
else glColor4ubv((BYTE*)&cl_edge);
glBegin(GL_LINE_LOOP);
for (i=0;i<n;i+=2,p+=2) glVertex2dv(p);
glEnd();
}
// glLineWidth(1.0);
}
//---------------------------------------------------------------------------
void TMain::draw()
{
tbeg();
tim.tbeg();
// [ render outline to texture ]
fbo.bind(scr);
glClearColor(divide((cl_land)&255,255),divide((cl_land>>8)&255,255),divide((cl_land>>16)&255,255),1.0);
scr.cls();
glMatrixMode(GL_MODELVIEW);
glLoadMatrixd(view);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
if (water.num) // water start points for grow fill
{
// add water around txr border
glBegin(GL_POINTS);
glColor4ubv((BYTE*)&cl_water);
for (int i=0;i<water.num;i+=2)
glVertex2dv(water.dat+i);
glEnd();
}
map_draw();
scr.exe();
fbo.unbind(scr);
// [ copy GL texture to CPU image ]
scr.txrs.txr_ld(txr_map);
// [ create ScanLines for direct pixel access pyx[y][x] ]
int e,x,y,xs,ys; DWORD **pyx,*p,c0,c1; double a[3];
xs=scr.txrs.txr.xs; // texture resolution (rounded up to power of 2)
ys=scr.txrs.txr.ys;
pyx=new DWORD*[ys];
p=(DWORD*)scr.txrs.txr.txr; // CPU image pixel data
for (y=0;y<ys;y++,p+=xs) pyx[y]=p; // scan line pointers
// [ Grow Fill water ]
c0=rgb2bgr(cl_land);
c1=rgb2bgr(cl_water);
if (water.num==0) // first frame view must be set so water is on all borders
{
// add water around txr border
for (x= 1,y=0;y<ys;y++) pyx[y][x]=c1;
for (x=xs-2,y=0;y<ys;y++) pyx[y][x]=c1;
for (y= 1,x=0;x<xs;x++) pyx[y][x]=c1;
for (y=ys-2,x=0;x<xs;x++) pyx[y][x]=c1;
}
for (e=1;e;) // grow it
for (e=0,y=1;y<ys-1;y++)
for ( x=1;x<xs-1;x++)
if (pyx[y][x]==c0)
if ((pyx[y-1][x]==c1)
||(pyx[y+1][x]==c1)
||(pyx[y][x-1]==c1)
||(pyx[y][x+1]==c1)) { e=1; pyx[y][x]=c1; }
// create water start points for next frame
water.num=0;
e=4; // step
for (y=1;y<ys-2;y+=e)
for (x=1;x<xs-2;x+=e)
if ((pyx[y-1][x-1]==c1) // enough water around (x,y)?
&&(pyx[y-1][x ]==c1)
&&(pyx[y-1][x+1]==c1)
&&(pyx[y ][x-1]==c1)
&&(pyx[y ][x ]==c1)
&&(pyx[y ][x+1]==c1)
&&(pyx[y+1][x-1]==c1)
&&(pyx[y+1][x ]==c1)
&&(pyx[y+1][x+1]==c1))
{
// convert pixel(x,y) -> World(x,y)
a[0]=divide(2.0*x,xs)-1.0;
a[1]=divide(2.0*y,ys)-1.0;
a[2]=0.0;
matrix_mul_vector(a,iview,a);
water.add(a[0]);
water.add(a[1]);
}
// [ copy CPU image back to GL texture ]
delete[] pyx; // release ScanLines no need for them anymore
scr.txrs.txr.rgb2bgr(); // I got RGB/BGR mismatch somewhere
scr.txrs.txr_st(txr_map); // scr.txrs.txr.txr holds pointer to 32bit pixel data
scr.exe();
// [ render texture to screen ]
scr.cls();
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
scr.txrs.bind(txr_map);
glColor3f(1.0,1.0,1.0);
glBegin(GL_QUADS);
glTexCoord2f(0.0,0.0); glVertex2f(-1.0,-1.0);
glTexCoord2f(1.0,0.0); glVertex2f(+1.0,-1.0);
glTexCoord2f(1.0,1.0); glVertex2f(+1.0,+1.0);
glTexCoord2f(0.0,1.0); glVertex2f(-1.0,+1.0);
glEnd();
scr.txrs.unbind();
// [info]
glColor3f(1.0,1.0,1.0);
scr.text_init_pix(1.0);
scr.text(tcpu);
scr.text(tgpu);
scr.text_exit();
scr.exe();
scr.rfs();
tend(); tcpu=" CPU time: "+tstr(1);
tim.tend();
}
//---------------------------------------------------------------------------
void TMain::mouse(double x,double y,TShiftState sh)
{
x=divide(2.0*x,scr.xs)-1.0;
y=1.0-divide(2.0*y,scr.ys);
if (sh.Contains(ssLeft))
{
viewx+=x-mx;
viewy+=y-my;
view_compute();
_redraw=true;
}
mx=x;
my=y;
}
//---------------------------------------------------------------------------
__fastcall TMain::TMain(TComponent* Owner) : TForm(Owner)
{
scr.init(this);
txr_map=fbo.add(scr);
// map_load_csv("map.csv");
// map_save_bin("map.bin");
map_load_bin("map.bin");
map_bbox();
view_compute();
draw();
_redraw=true;
}
//---------------------------------------------------------------------------
void __fastcall TMain::FormDestroy(TObject *Sender)
{
scr.exit();
}
//---------------------------------------------------------------------------
void __fastcall TMain::FormPaint(TObject *Sender)
{
_redraw=true;
}
//---------------------------------------------------------------------------
void __fastcall TMain::FormResize(TObject *Sender)
{
scr.resize();
fbo.resize(scr);
_redraw=true;
}
//---------------------------------------------------------------------------
void __fastcall TMain::FormMouseWheel(TObject *Sender, TShiftState Shift, int WheelDelta, TPoint &MousePos, bool &Handled)
{
if (Shift.Contains(ssShift))
{
if (WheelDelta>0) index++; else index--;
if (index>=polygon.num) index=polygon.num-1;
if (index<0) index=0;
_redraw=true;
}
else{
double p[3]={ mx,my,0.0 };
view_compute();
matrix_mul_vector(p,iview,p);
if (WheelDelta>0) zoom*=dzoom; else zoom/=dzoom;
view_compute();
matrix_mul_vector(p,view,p);
viewx-=p[0]-mx;
viewy-=p[1]-my;
view_compute();
_redraw=true;
}
}
//---------------------------------------------------------------------------
void __fastcall TMain::FormMouseMove(TObject *Sender, TShiftState Shift, int X,int Y) { mouse(X,Y,Shift); }
void __fastcall TMain::FormMouseUp(TObject *Sender, TMouseButton Button, TShiftState Shift, int X, int Y) { mouse(X,Y,Shift); }
void __fastcall TMain::FormMouseDown(TObject *Sender, TMouseButton Button, TShiftState Shift, int X, int Y) { mouse(X,Y,Shift); }
//---------------------------------------------------------------------------
void __fastcall TMain::Timer1Timer(TObject *Sender)
{
tgpu=AnsiString().sprintf(" GPU time: [%8.3lf ms]",tim.time());
if (_redraw) { draw(); _redraw=false; }
}
//---------------------------------------------------------------------------
void __fastcall TMain::FormDblClick(TObject *Sender)
{
Width+=10; // ignore this had some bug in resize FBO texture and this was for debugging it
}
//---------------------------------------------------------------------------
I also use 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
If you need help with the matrix and vector math routines see this:
Understanding 4x4 homogenous transform matrices
In the linked answers at the bottom you can find even the C++ implementations I use...
You can ignore the VCL stuff the app just have single Timer on it with interval 40 ms to repaint if needed and fetch measuret GL time if ready...
The important stuff for you is just the draw() routine.
It works like this:
bind FBO to enable rendering to texture
clear it with land color
render polygons outline with edge color
if you got holes render them with watter color and after filling render them again with edge color
render watter start points with watter color
in the first frame you should have view un-zoomed so all land is surrounded with watter. So the first watter points are the border rectangle of texture.
unbind FBO and copy texture pixeldata to CPU side memory
grow fill all watter to land color pixels (stop on edge color or any other)
You can use any fill like Flood fill, segmented line fill etc but beware stack overflow for recursive approach. I decided to use:
grow fill
As it is iterative. It is not as fast (hence the big CPU times but big portion of the CPU time is due to sync while transfering texture between GPU/CPU) but can be speed-up significantly by subdividing image into "square" areas and propagate filling if needed.
create watter start points from this image
so scan whole image (with some step do not need to scan all point) and if found watter add it as watter start point watter for next frame (in world coordinates). This works well until your view will not change too much from frame to frame so limit zoom change and pan step ...
Here comes an gfx issue when new frame contains watter without any start points and not accessible to other watter. This need some thinking/testing but I think it should be solvable by some static predefined watter start points (few for each polygon) obtained from the first frame.
render CPU side image directly or pass it back to GL texture and render it.
Here preview:
A couple of thoughts that I have... can you split the tesselation into organized chunks, ... kind of like a grid? Then if things move you could intelligently only re-tesselate the parts that changed.

Problems with Bounding Box Frustum Culling

I am having trouble getting correct frustum culling to work. I got a great suggestion from someone about AABB point testing and it works great but I have gotten to the point where a max and a min point testing do not always work because often they won't be in the frustum but a plane from one of the sides will be. So I need to move onto full bounding box detection. I already have the code which checks the bounding box against the planes of the frustum, just need to set up the bounding box.
Does anyone have a code example or an idea about how to do this. My two points I have with which to create my bounding box are a minimum x y and z and a maximum x y and z.
Thank you! :)
You need to have the planes of the frustrum looking inside (their normals must point to the center). Then the rendering discard can be done by checking if the object is completelly outside of one of any of the six planes of the frustrum. You can do that with a sphere or with a AABB, or any other container where you can calculate the distance (and the side) to the plane.
From my own plane code (so if any plane return -1 discard the rendering), I added some comments so it is easier to understand:
int Side(const Sphere &e) const {
float d=Distance(e.center);
if(d-e.radius>Epsilon) return 1; // inside
if(d+e.radius<-Epsilon) return -1; // outside
return 0; // crossing the plane
}
int Side(const Vector3f &v) const {
float d=Distance(v);
if(d>Epsilon) return 1;
if(d<-Epsilon) return -1;
return 0;
}
int Side(const Box3f &c) const {
Vector3f a,b;
if(Normal.x>=0.0) { a.x=c.min.x; b.x=c.max.x; }
else { b.x=c.min.x; a.x=c.max.x; }
if(Normal.y>=0.0) { a.y=c.min.y; b.y=c.max.y; }
else { b.y=c.min.y; a.y=c.max.y; }
if(Normal.z>=0.0) { a.z=c.min.z; b.z=c.max.z; }
else { b.z=c.min.z; a.z=c.max.z; }
int l1 = Side(a), l2= Side(b);
if(l1==l2) return l1; // both in the same side
return 0; // we have the object crossing the plane
}