Related
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 ...
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)
I need to generate a visualization of an object, for this, I plan to use a GPU ray casting technique. The information of this object is described in two txt files. One file describes the structure of this object through a regular cell grid, and the other file has the information about intensity for each cell in the grid.
The grid is described by a list of x,y,z float point in cartesian axes, which form the cells, and the second file is simply a list o float that describes the intensity for each cell.
How can I generate a 3d texture with these files for later create a volume visualization witch "volume ray casting"?
[Edit1 by Spektre]
Here sample input file:
1
0
0
0
1 1 1
32 32 32
-1.495980e+14
-1.402481e+14
-1.308982e+14
-1.215484e+14
-1.121985e+14
-1.028486e+14
-9.349875e+13
-8.414888e+13
-7.479900e+13
-6.544912e+13
-5.609925e+13
-4.674938e+13
-3.739950e+13
-2.804962e+13
-1.869975e+13
-9.349875e+12
0.000000e+00
9.349875e+12
1.869975e+13
2.804962e+13
3.739950e+13
4.674938e+13
5.609925e+13
6.544912e+13
7.479900e+13
8.414888e+13
9.349875e+13
1.028486e+14
1.121985e+14
1.215484e+14
1.308982e+14
1.402481e+14
1.495980e+14
-1.495980e+14
-1.402481e+14
-1.308982e+14
-1.215484e+14
-1.121985e+14
-1.028486e+14
-9.349875e+13
-8.414888e+13
-7.479900e+13
-6.544912e+13
-5.609925e+13
-4.674938e+13
-3.739950e+13
-2.804962e+13
-1.869975e+13
-9.349875e+12
0.000000e+00
9.349875e+12
1.869975e+13
2.804962e+13
3.739950e+13
4.674938e+13
5.609925e+13
6.544912e+13
7.479900e+13
8.414888e+13
9.349875e+13
1.028486e+14
1.121985e+14
1.215484e+14
1.308982e+14
1.402481e+14
1.495980e+14
-1.495980e+14
-1.402481e+14
-1.308982e+14
-1.215484e+14
-1.121985e+14
-1.028486e+14
-9.349875e+13
-8.414888e+13
-7.479900e+13
-6.544912e+13
-5.609925e+13
-4.674938e+13
-3.739950e+13
-2.804962e+13
-1.869975e+13
-9.349875e+12
0.000000e+00
9.349875e+12
1.869975e+13
2.804962e+13
3.739950e+13
4.674938e+13
5.609925e+13
6.544912e+13
7.479900e+13
8.414888e+13
9.349875e+13
1.028486e+14
1.121985e+14
1.215484e+14
1.308982e+14
1.402481e+14
1.495980e+14
And file format info:
For a regular grid, without grid refinement, the amr grid.inp looks like:
iformat <=== Typically 1 at present 0 <=== Grid style (regular = 0)
coordsystem gridinfo incl_x incl_y incl_z nx ny nz
xi[1] xi[2] xi[3] .....… xi[nx+1]
yi[1] yi[2] yi[3] .....… yi[ny+1]
zi[1] zi[2] zi[3] .....… zi[nz+1]
The meaning of the entries are:
iformat: The format number, at present 1. For unformatted files this
must be 4-byte integer.
coordsystem: If coordsystem <100 the coordinate system is cartesian.
If 100<= coordsystem <200 the coordinate system is spherical (polar).
If 200<= coordsystem <300 the coordinate system is cylindrical.For
unformatted files this must be 4-byte integer.
gridinfo: If gridinfo ==1 there will be abundant grid information
written into this file, possibly useful for post-processing routines.
Typically this is redundant information, so it is advised to set
gridinfo =0 to save disk space. In the following we will assume that
gridinfo =0. For unformatted files this must be 4-byte integer.
incl x, incl y, incl z: These are either 0 or 1. If 0 then this
dimension is not active (so upon grid refinement no refinement in this
dimension is done). If 1 this dimension is fully active, even if the
number of base grid cells in this direction is just 1. Upon refinement
the cell will also be splitted in this dimension. For unformatted
files these numbers must be 4-byte integer.
nx, ny, nz: These are the number of grid cells on the base grid in
each of these dimensions. For unformatted files these numbers must be
4-byte integer.
xi[1] ... xi[nx+1]: The edges of the cells of the base grid in
x-direction. For nx grid cells we have nx+1 cell walls, hence nx+1
cell wall positions. For unformatted files these numbers must be
8-byte reals (=doubleprecision).
yi[1] ... yi[ny+1]: Same as above, but now for y-direction.
zi[1] ... zi[nz+1]: Same as above, but now for z-direction.
Example of a simple 2x2x2 regular grid in cartesian coordinates: 1 0 1
0 1 1 1 2 2 2
-1. 0. 1.
-1. 0. 1.
-1. 0. 1.
was curious and based on your sample grid this is what I come up with:
//---------------------------------------------------------------------------
//--- AMR class ver: 1.000 --------------------------------------------------
//---------------------------------------------------------------------------
#ifndef _amr_grid_h
#define _amr_grid_h
//---------------------------------------------------------------------------
#include "gl\OpenGL3D_double.cpp"
//---------------------------------------------------------------------------
// https://stackoverflow.com/q/55319835/2521214
//---------------------------------------------------------------------------
class amr_grid
{
public:
// BVH file
int iformat; // 0,1
int gridstyle; // 0 regular
int coordsystem; // <0,100) cartesian, <100<200) spherical/polar, <200,300) cylindrical
int gridinfo; // 0: 1: additional info added to file
int en[3]; // enable x,y,z?
int sz[3]; // size of grid [cells]
List<double> p[3]; // walls per dimension
// misc
double min[3],max[3]; // BBOX
double dl; // avg cell size
amr_grid(){ reset(); }
amr_grid(amr_grid& a) { *this=a; }
~amr_grid(){}
amr_grid* operator = (const amr_grid *a) { *this=*a; return this; }
//bvh* operator = (const bvh &a) { ...copy... return this; }
// render/set/load
void reset(); // clear whole skeleton data
void draw(); // render actual set frame
void load(AnsiString name); // load from AMR grid file
};
//---------------------------------------------------------------------------
void amr_grid::reset()
{
iformat=1;
gridstyle=0;
coordsystem=0;
gridinfo=0;
en[0]=1; sz[0]=0; p[0].num=0; min[0]=0.0; max[0]=0.0;
en[1]=1; sz[1]=0; p[1].num=0; min[1]=0.0; max[1]=0.0;
en[2]=1; sz[2]=0; p[2].num=0; min[2]=0.0; max[2]=0.0;
dl=0.0;
}
//---------------------------------------------------------------------------
void amr_grid::draw()
{
int i,x,y,z;
glColor3f(0.0,1.0,0.5);
glBegin(GL_LINES);
if ((gridstyle>=0)&&(gridstyle<100)) // cartesian
{
if ((en[0])&&(en[1])&&(en[2])) // 3D
{
for (x=0;x<=sz[0];x++)
for (y=0;y<=sz[1];y++)
{
glVertex3d(p[0][x],p[1][y],min[2]);
glVertex3d(p[0][x],p[1][y],max[2]);
}
for (x=0;x<=sz[0];x++)
for (z=0;z<=sz[2];z++)
{
glVertex3d(p[0][x],min[1],p[2][z]);
glVertex3d(p[0][x],max[1],p[2][z]);
}
for (y=0;y<=sz[1];y++)
for (z=0;z<=sz[2];z++)
{
glVertex3d(min[0],p[1][y],p[2][z]);
glVertex3d(max[0],p[1][y],p[2][z]);
}
}
}
glEnd();
}
//---------------------------------------------------------------------------
void amr_grid::load(AnsiString name)
{
int hnd,siz,adr,i,j,k;
AnsiString lin,s;
BYTE *txt=NULL;
reset();
// file -> memory
hnd=FileOpen(name,fmOpenRead);
if (hnd<0) return;
siz=FileSeek(hnd,0,2);
FileSeek(hnd,0,0);
txt=new BYTE[siz];
if (txt==NULL) { FileClose(hnd); return; }
siz=FileRead(hnd,txt,siz);
FileClose(hnd);
// memory -> amr_grid data
adr=0;
iformat=str2int(txt_load_lin(txt,siz,adr,true));
gridstyle=str2int(txt_load_lin(txt,siz,adr,true));
coordsystem=str2int(txt_load_lin(txt,siz,adr,true));
gridinfo=str2int(txt_load_lin(txt,siz,adr,true));
lin=txt_load_lin(txt,siz,adr,true); i=1; for (j=0;j<3;j++) en[j]=str2num(str_load_str(lin,i,true));
lin=txt_load_lin(txt,siz,adr,true); i=1; for (j=0;j<3;j++) sz[j]=str2num(str_load_str(lin,i,true));
for (j=0;j<3;j++) for (p[j].num=0,i=0;i<=sz[j];i++) p[j].add(str2num(txt_load_str(txt,siz,adr,true)));
// BBOX
for (j=0;j<3;j++) for (min[j]=max[j]=p[j][0],i=0;i<=sz[j];i++)
{
if (min[j]>p[j][i]) min[j]=p[j][i];
if (max[j]<p[j][i]) max[j]=p[j][i];
}
double q[3];
vector_sub(q,max,min);
dl=3.0*vector_len(q)/double(sz[0]+sz[1]+sz[2]);
}
//---------------------------------------------------------------------------
#endif
//---------------------------------------------------------------------------
This just loads and draws the grid. However I expect you got the 3D texture file containing the cells intensities so simply use that as a color for a box and change the draw routine that will render cells as a box with color from your texture. the corner points of a box for any cell are 8 combination of these coordinates:
x: p[0][i],p[0][i+1]
y: p[1][i],p[1][i+1]
z: p[2][i],p[2][i+1]
you can adapt mine glBox (beware its float but code above uses double ...):
void glBox(GLfloat x0,GLfloat y0,GLfloat z0,GLfloat xs,GLfloat ys,GLfloat zs)
{
xs*=0.5;
ys*=0.5;
zs*=0.5;
glBegin(GL_QUADS);
glNormal3f(+1.0,0.0,0.0);
glVertex3f(x0+xs,y0-ys,z0-zs);
glVertex3f(x0+xs,y0+ys,z0-zs);
glVertex3f(x0+xs,y0+ys,z0+zs);
glVertex3f(x0+xs,y0-ys,z0+zs);
glNormal3f(-1.0,0.0,0.0);
glVertex3f(x0-xs,y0-ys,z0+zs);
glVertex3f(x0-xs,y0+ys,z0+zs);
glVertex3f(x0-xs,y0+ys,z0-zs);
glVertex3f(x0-xs,y0-ys,z0-zs);
glNormal3f(0.0,+1.0,0.0);
glVertex3f(x0-xs,y0+ys,z0+zs);
glVertex3f(x0+xs,y0+ys,z0+zs);
glVertex3f(x0+xs,y0+ys,z0-zs);
glVertex3f(x0-xs,y0+ys,z0-zs);
glNormal3f(0.0,-1.0,0.0);
glVertex3f(x0-xs,y0-ys,z0-zs);
glVertex3f(x0+xs,y0-ys,z0-zs);
glVertex3f(x0+xs,y0-ys,z0+zs);
glVertex3f(x0-xs,y0-ys,z0+zs);
glNormal3f(0.0,0.0,+1.0);
glVertex3f(x0+xs,y0-ys,z0+zs);
glVertex3f(x0+xs,y0+ys,z0+zs);
glVertex3f(x0-xs,y0+ys,z0+zs);
glVertex3f(x0-xs,y0-ys,z0+zs);
glNormal3f(0.0,0.0,-1.0);
glVertex3f(x0-xs,y0-ys,z0-zs);
glVertex3f(x0-xs,y0+ys,z0-zs);
glVertex3f(x0+xs,y0+ys,z0-zs);
glVertex3f(x0+xs,y0-ys,z0-zs);
glEnd();
}
to x0,y0,z0,x1,y1,z1 form or write an VBO or whatever ...
PS.
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
and the C++ code is VCL based so you need to rewrite AnsiString to string type you got at your disposal. Also I used mine own string routines for loading lines,string and converting to numbers so you need to port that too but I assume you already got a loader ...
btw this is the output for the simple sample file in the fileformat description:
I’m trying to convert a 2D map created in my map editor to a 3D plotting with OpenGL. This is my map generated in my map editor:
Those vertices are relative to my Cartesian origin world coordinate (top up of the picture) and I’m applying this formula to convert it to an OpenGL object coordinate:
World size: 800x600
x = (X / 800) -0.5
y = (Y / 600) -0.5
Getting this result:
(First object face)
−0.48625, 0.068333333
0.12625, 0.07
0.12875, −0.481666667
−0.4875, −0.486666667
Plotting this vertex buffer in OpenGL, I got a very weird result. So how can I get a 3D model from those vertex positions? Like this picture:
I’m rendering OpenGL in triangles mode and using this example as the start point: https://github.com/JoeyDeVries/LearnOpenGL/blob/master/src/1.getting_started/7.4.camera_class/camera_class.cpp
Using the conversion formula + the Earcut tessellation (https://github.com/mapbox/earcut.hpp), I've finally got this rectangle rendering correctly inside OpenGL. With two planes with just the Z-axis different, now the problem is how to render its laterals since Earcut just works with 2D coordinates...
If I get it right you got some planar 2D polygon and what to add some constant thickness to it (as a 3D mesh). That is doable fairly easily. As you correctly assumed you need to triangulate first. So you should have this input:
table of points pnt[pnts]
list of all points of your object.
polygon pol[pols] (circumference of your object)
just ordered list of points indexes referencing to table of points
triangulation result fac[facs]
ordered list of 3 point indexes representing all the triangles.
Now to make a mesh from it we need to do this:
copy all the points and extrude them by some translation.
all this new points will be added to current pnt[pnts] table. Do not forget to remember original table size pnts0 as it will be needed later.
copy/reverse the triangulation.
The opposite side of the triangulated polygon will be the same just in reverse polygon winding. So just copy it to fac[facs] as new triangles in reverse index order ... Do not forget to add the original point table size to all the new faces. This will use the new points ... From the images of yours you got to this point already.
create the missing side faces.
For this we can exploit the original polygon. As we just copied the points then we know that pnt[3*i] is opposite to pnt[pnts0+3*i]. So we just create triangle faces joining the opposite edges of the polygon.
Here small C++ example I busted for this right now:
//---------------------------------------------------------------------------
#include <vcl.h>
#include <math.h>
#pragma hdrstop
#include "Unit1.h"
#include "gl_simple.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
const int N=128;
int pnts=6*3; // 3* number of points
float pnt[N]= // x,y per each point
{
-0.5,-0.5,0.0, // 6 ------ 9
-0.4, 0.0,0.0, // + +
-0.5,+0.5,0.0, // 3 12
+0.5,+0.5,0.0, // + +
+0.4, 0.0,0.0, // 0 ----- 15
+0.5,-0.5,0.0,
};
int pol[N]={ 0,3,6,9,12,15 }, pols=6; // original polygon (3*pnt index), number of its vertexes
int fac[N]= // triangulation result (3*pnt index)
{
0,3,15,
3,12,15,
3,6,12,
6,9,12,
}, facs=4*3; // number of triangles*3
//---------------------------------------------------------------------------
void extrude(float dz)
{
int i,i0,pnts0=pnts;
// copy and reverse triangulation
for (i=0;i<facs;i++)
fac[facs+facs-1-i]=fac[i]+pnts; facs+=facs;
// duplicate points
for (i=0;i<pnts;i++) pnt[pnts0+i]=pnt[i]; pnts+=pnts;
// extrude points
for (i= 2;i<pnts0;i+=3) pnt[i]-=dz;
for ( ;i<pnts ;i+=3) pnt[i]+=dz;
// side faces
for (i0=pols-1,i=0;i<pols;i0=i,i++)
{
fac[facs]=pol[i ]+pnts0; facs++;
fac[facs]=pol[i ]; facs++;
fac[facs]=pol[i0]; facs++;
fac[facs]=pol[i0]+pnts0; facs++;
fac[facs]=pol[i ]+pnts0; facs++;
fac[facs]=pol[i0]; facs++;
}
}
//---------------------------------------------------------------------------
void gl_draw()
{
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
glDisable(GL_TEXTURE_2D);
glEnable(GL_DEPTH_TEST);
glEnable(GL_LIGHTING);
glEnable(GL_LIGHT0);
glEnable(GL_CULL_FACE);
glFrontFace(GL_CCW);
glEnable(GL_COLOR_MATERIAL);
/*
glPolygonMode(GL_FRONT,GL_FILL);
glPolygonMode(GL_BACK,GL_LINE);
glDisable(GL_CULL_FACE);
*/
// set view
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glTranslatef(0.0,0.0,-5.0);
static float ang=0.0;
glRotatef(ang,0.2,0.7,0.1); ang+=5.0; if (ang>=360.0) ang-=360.0;
// render mesh
float *p0,*p1,*p2,n[3],a[3],b[3],c;
glColor3f(0.7,0.7,0.7);
glBegin(GL_TRIANGLES);
for (int i=0;i+3<=facs;i+=3)
{
// points
p0=pnt+fac[i+0];
p1=pnt+fac[i+1];
p2=pnt+fac[i+2];
// compute normal
a[0]=p1[0]-p0[0]; a[1]=p1[1]-p0[1]; a[2]=p1[2]-p0[2];
b[0]=p2[0]-p1[0]; b[1]=p2[1]-p1[1]; b[2]=p2[2]-p1[2];
n[0]=(a[1]*b[2])-(a[2]*b[1]);
n[1]=(a[2]*b[0])-(a[0]*b[2]);
n[2]=(a[0]*b[1])-(a[1]*b[0]);
c=1.0/sqrt((n[0]*n[0])+(n[1]*n[1])+(n[2]*n[2]));
n[0]*=c; n[1]*=c; n[2]*=c;
// render
glNormal3fv(n);
glVertex3fv(p0);
glVertex3fv(p1);
glVertex3fv(p2);
}
glEnd();
// glFlush();
glFinish();
SwapBuffers(hdc);
}
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner):TForm(Owner)
{
// Init of program
gl_init(Handle); // init OpenGL
extrude(0.2);
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormDestroy(TObject *Sender)
{
// Exit of program
gl_exit();
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormPaint(TObject *Sender)
{
// repaint
gl_draw();
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormResize(TObject *Sender)
{
// resize
gl_resize(ClientWidth,ClientHeight);
}
//---------------------------------------------------------------------------
void __fastcall TForm1::tim_redrawTimer(TObject *Sender)
{
gl_draw();
}
//---------------------------------------------------------------------------
It is VCL based so ignore all the VCL stuff and port the events you want/need and GL context stuff to your style of programing. The only important stuff here are:
the tables pnt,fac,pol which holds the input and latter also output. The extrude(dz) will create the mesh (call it just once!) and gl_draw will render the tables as mesh (Using the old style GL api for simplicity).
For the GL stuff I used my gl_simple.h which you can find in this related QA:
complete GL+GLSL+VAO/VBO C++ example
Here is preview of the code above:
the choppynes is due to my GIF capture the rendering is smooth. I used static allocation and on the run normal computation so the code is simple and easy to understand. Of coarse for the real deal you need to implement dynamic lists and VAO/VBO ... if you want good performance
It's hard to know for sure, but it seems your object has only thwo faces rendered because you didn't add the other faces to the index.
Because you have your vertices, but you also need to tell have a triangles for the sides. If they are triangles you should end up with 16 triangles to draw.
If you don't use an index, you need to duplicate your vertices, for each triangle and end up with 48 vertices to draw.
As to get the earing algorithm to work in 3D, if you know for sure that your polygon has all it's point in the same plan, you can take 3 vertices, deduce it's plan, and create a transformation matrix to bring all these points to (x,y,0) which is like 2D coordinates.
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 ...