# Trying to use modelX() but built a spiral galaxy of points

I took the spiral galaxy points as they are without doing anything to them and made a new PShape from them. they don’t look like a spiral galaxy anymore but they illustrate the problem. To get the green shape to draw properly, I had to use shearY(angle). Using the saved modelX and modelY points, the new PShape doesn’t include the correction provided by shearY(angle).

This is better explanation of my question, Why do modelX and modelY not include the shearY correction?

Here’s a better screenshot showing the problem.

I’m living proof that if you keep working on a problem, and don’t keep doing the same thing over and over, you can solve a problem. I initially really believed I was using modelX and modelY wrong, but the problem was I wasn’t using shearY , or concat(), or subset() correctly. I got it, mostly. The final coordinates are what I was after and modelX and modelY do exactly what the processing reference says.

Once again, I spoke too soon. I saved the points to a file and drew them in another sketch and the shearY(angle) does not contribute to the final coordinates. The processing reference says "This returns the X value for a given coordinate based on the current set of transformations (scale, rotate, translate, etc.) "

I have to guess they also mean shearY but I don’t see it in the final coordinates… Oh well. This time I will have to write my own function.

1 Like

sorry, not sure this is of any help,
but i wanted just to be sure that a mix of

• translate
• rotate
• shear

together with saved modelXYZ works.

give it a try (v03)

i know it looks messy

``````// https://discourse.processing.org/t/trying-to-use-modelx-but-built-a-spiral-galaxy-of-points/6064/4
// v02 summary line cyan
// v03 show PVector data also on canvas, plus cleanup

int make=0, makemax = 10;
PVector[] remember;

//______________________________________
void setup() {
size(500, 500, P3D);
info_print();
remember = new PVector[makemax];
tumble(true);                           // make / draw axis and points and boxes ( and remember )
for ( int i=0; i<make; i++) println("i "+i+" "+remember[i]);
println(" white line connects modelXYZ memory points / cyan line Begin End");
println(" green points and box's draw relative at 0,0,0 after translate/rotate/shear");
}

//______________________________________
void remember_it() {
remember[make]= new PVector(modelX(0, 0, 0), modelY(0, 0, 0), modelZ(0, 0, 0));
//println("make "+make);
make++;
}

void a_point(boolean mem) {  //__________________points + box and optional remember
point(0, 0, 0);
box(10);
if (mem) remember_it();
}

//______________________________________
void tumble(boolean mem) {                         //_ aka drunken_monkey_in_space
//_______________________________________________axis
noFill();
stroke(200, 0, 0);
line(0, 0, 0, 30, 0, 0);
stroke(200, 200, 0);
line(0, 0, 0, 0, 30, 0);
stroke(200, 0, 200);
line(0, 0, 0, 0, 0, 30);
//_______________________________________________ in 3D
stroke(0, 200, 0);
strokeWeight(1);
pushMatrix();
// start
a_point(mem);//_______________[0]
translate(50, 60, 0);
a_point(mem);//_______________[1]
rotateX(PI/4);
a_point(mem);//_______________[2]
translate(50, 0, 0);
shearX(PI/4);
a_point(mem);//_______________[3]
translate(0, 0, -30);
a_point(mem);//_______________[4]
// end
popMatrix();
}

void show_remembered_points_as_lines() {
strokeWeight(1);
stroke(255);                           // show lines for all steps in  white
for (int i =0; i<make-1; i++) line(remember[i].x, remember[i].y, remember[i].z, remember[i+1].x, remember[i+1].y, remember[i+1].z);
stroke(0, 200, 200);                   // to show a summary we make a cyan line from NULL to END
line(0, 0, 0, remember[make-1].x, remember[make-1].y, remember[make-1].z);
}

void show_remembered_points_as_text() {
fill(0, 200, 200);
textSize(15);   // text color
for  (int i = 0; i<make; i++) text("i "+i+" PVector["+int(remember[i].x)+","+int(remember[i].y)+","+int(remember[i].z)+"]", 10, 20+20*i);
}
//______________________________________
void draw_object() {                         //_________________called by / from inside PTZ
show_remembered_points_as_lines();         // from array of PVector of modelXYZ's
tumble(false);                             // draw axis and points ( not remember again ) using original translate,rotate,shear... and a BOX each
}

//______________________________________ DRAW
void draw() {
background(0, 0, 80);
show_remembered_points_as_text();
PTZ();
}

//_____________________________________ PTZ tab
int mode = 0;
float Zmag = 5;
int Zaxis=-10;
float Xmag, Ymag = 0;
float newXmag, newYmag = 0;
int newZmag = 0;
int zoomf = 3;
float newxpos, newypos = 0;       // for PAN
float xposd, yposd = 0;           // for PAN

//_________________________________________________________________ ROTATE / TILDE and MOVE / PAN
void mousePressed() {
if      (mouseButton == LEFT) {
mode=1;
}  // ORBIT
else if (mouseButton == RIGHT) {
mode=2;
}  // PAN
// else if (mouseButton == CENTER) { mode=3; }  // zoom mouse wheel works same , presssed or not
}

//_________________________________________________________________ mouse PT end
void mouseReleased() {
mode = 0;
}

//_________________________________________________________________ mouseWheel ZOOM
void mouseWheel(MouseEvent event) {
int newZmag = event.getCount();                                     // +- 1
//if (Zmag > 10) { Zmag += newZmag * 5; } else { Zmag += newZmag; }// from 1 to 11 go in step 1 else in step 5
Zmag += newZmag*0.3;
//println("Zmag: "+nf(Zmag, 1, 2));
}

void keyPressed() {
if ( keyCode == UP   )  Ymag -= 0.1 ;
if ( keyCode == DOWN )  Ymag += 0.1 ;
if ( keyCode == RIGHT)  Xmag -= 0.1 ;
if ( keyCode == LEFT )  Xmag += 0.1 ;
if ( keyCode == 16 )    Zmag -= 0.5 ;
if ( keyCode == 11 )    Zmag += 0.5 ;
//println("key: "+key); println("keyCode: "+keyCode);
}
//_________________________________________________________________ Pan Tilde Zoom
void PTZ() {
pushMatrix();
translate(width/2, height/2, Zaxis);
// get new mouse operation
if ( mode == 2 ) {                              // PAN ( right mouse button pressed)
xposd = (mouseX-float(width/2));
yposd = (mouseY-float(height/2));
}
newxpos = xposd;// xposd=0;
newypos = yposd;// yposd = 0;
translate(newxpos, newypos, 0);          // move object
if ( mode == 1 ) {  // ORBIT ( left mouse button pressed)
newXmag = mouseX/float(width) * TWO_PI;
newYmag = mouseY/float(height) * TWO_PI;

float diff = Xmag-newXmag;
if (abs(diff) >  0.01)   Xmag -= diff/4.0;
diff = Ymag-newYmag;
if (abs(diff) >  0.01)   Ymag -= diff/4.0;
}
rotateX(-Ymag);
rotateY(-Xmag);
scale(Zmag);
draw_object();                                // THE OBJECT  see main tab
popMatrix();
}

//_______________________________________________ SETUP PRINT INFO
void info_print() {
println("PTZ info:");
println("key UP DOWN RIGHT LEFT -> rotate // key PAGE UP DOWN -> zoom");
println("mouse LEFT press drag up down right left -> rotate // mouse RIGHT press -> move // mouse WHEEL turn -> zoom");
println("_data_");
}

``````

Thank you , @kll, for taking the time to put that together. You call that a mess? Its not a mess like the kind I make but it will take me some time to understand how it proves that the transformations like shear get “remembered” by modelXYZ. I see immediately its using PVector, which could be an approach I need to try.

I tried writing a function to transform the points but it adds reflex into the shape. That could be useful eventually but not just yet.

the white line is made by the remembered modelXYZ PVectors,
and as it hits the green points ( center boxes ) i think it works???

now what is drawn does not matter, or better you can easy change inside

void tumble

and change add transformation / rotation / shear /

@kll, thanks again, your example helped me get it. I had to do some weird gymnastics, but I got it. There’s probably an easier way than how I did it.

``````
int N = 81;  //number of points
float M = 04.00000f; //2/100.0;
float[] dydx = new float[81];
float T = 12/100.000000f;
float[] dT;
float x[] = new float[81];  //divide up unit chord length by N
float y[] = new float[81];  //divide up unit chord length by N
float xU[] = new float[81];
float yU[] = new float[81];
float xL[] = new float[81];
float yL[] = new float[81];
float P = 0.4; //0.4; //40/100.0;
PShape s,s2,s3;
float a0 = 0.2969;
float a1 = -0.126;
float a2 = -0.3516;
float a3 = 0.2843;
float a4 = -0.1036;
float[] theta = new float[81];
float[] yt = new float[81];
float angle;
float[] beta = new float[81];

Controls controls;
Controls controls2;
HorizontalControl controlX;
int showControls;
boolean draggingZoomSlider = false;
boolean released = true;
float zoom = -200f;
float tzoom = 183.56482f;

PFont font;
PVector[] Xordinates;
PVector[] Yordinates;
float[] XUoord; // = new float[81];
float[] YUoord; // = new float[81];
float[] XLoord; // = new float[81];
float[] YLoord; // = new float[81];
float[] Xvalues; // = new float[162];
float[] Yvalues; // = new float[162];

Table table;
TableRow row;

void setup() {

size( 650, 300, P3D);
Xordinates = new PVector[coordinates.length];
Yordinates = new PVector[coordinates.length];
controls = new Controls();
controlX = new HorizontalControl();
showControls = 1;
Table table = new Table();

}

void draw() {

background(0, 0, 0);

getOrdinates();
Xvalues = concat(xU,xL);
Yvalues = concat(yU,yL);
XLoord = subset(Xvalues,81,80);
YLoord = subset(Yvalues,81,80);
XUoord = subset(Xvalues,0,80);
YUoord = subset(Yvalues,0,80);

if (mousePressed) {
if( (showControls == 1) && (controls.isZoomSliderEvent(mouseX, mouseY)) || ( showControls == 1 && controlX.isZoomSliderEvent(mouseX,mouseY))) {
draggingZoomSlider = true;
zoom = controls.getZoomValue(mouseY);
tzoom = controlX.getZoomValue(mouseX,mouseY);
}
else if (!draggingZoomSlider) {
if (released == true){
}
}
}
stroke(182,185,188);

strokeWeight(1);
line( width-width, height/2,width, height/2);
textFont(font, 15);
textAlign(LEFT, BOTTOM);
text("Airfoil:\nCamber  : "+M * 100.0f + "\nThickness: "  +T * 100.0f , width/2+150, height/2+120);

if (showControls == 1) {
controls.render();
controlX.render();

}
angle = sqrt( ((300 - Xvalues[0]) * (300 - Xvalues[0])) + ((300*Yvalues[80]) * (300*Yvalues[80])) );
angle = asin(300*Yvalues[80]/angle);

controls.updateZoomSlider(zoom);
controlX.updateZoomSlider(tzoom);
stroke(0,255,0);
strokeWeight(3);
translate(width/2-450/2, height/2);
stroke(255,0,0);
shearY(-angle);

for (int i = 0; i < Xordinates.length-1; i++){

if (i < 80){

point(300*XUoord[i],-300*YUoord[i]);
Xordinates[i] = new PVector(modelX(XUoord[i],YUoord[i],0), modelY(0,0,0));
Yordinates[i] = new PVector(modelX(0,0,0), modelY(XUoord[i],YUoord[i],0));
Xvalues[i] = Xordinates[i].x;
Yvalues[i] = Yordinates[i].y;

}
}

shearY(angle);
shearY(angle);
for (int i = 0; i < Xordinates.length-1; i++){

if (i < 80){

point(300*XLoord[i],-300*YLoord[i]);
Xordinates[80+i] = new PVector(modelX(XLoord[i],YLoord[i],0), modelY(0,0,0));
Yordinates[80+i] = new PVector(modelX(0,0,0), modelY(XLoord[i],YLoord[i],0));
Xvalues[80+i] = Xordinates[80+i].x;
Yvalues[80+i] = Yordinates[80+i].y;

}

}

printArray(Yvalues);
Table table = new Table();
stroke(0,255,0);
shearY(-angle);

for(int i = 0; i < Xordinates.length-1; i++){

newRow.setFloat("X/C",Xvalues[i] );
newRow.setFloat("Y/C",Yvalues[i] );
point(300*(Xvalues[i]-100),-300*(Yvalues[i]-150));
}

saveTable(table, "data/new.csv");

}

void getOrdinates(){

for (int i = 0; i < N; i++){

M = zoom/1000f;
T = tzoom/1000f;

if(i >= 0){
x[i] = (1 - cos(beta[i]))/2;
}
if(i < 40){
if (x[i] >= 0){
y[i] = ((M/(P*P) * (2*P*x[i] - x[i]*x[i])));
dydx[i] = (2*M)/(P*P) * (P - x[i]);
}
}
if(i >=40){
if (x[i] <= 1.00000f){
y[i] = (M/(1-P)*(1-P) * (1 - 2*P + 2*P*x[i] - x[i]*x[i]))*2.75; //* 2.75; //*2.686;
dydx[i] = (2*M/((1-P)*(1-P)) * (P - x[i]));
}
}
}  //end for loop

for (int i = 0; i < 81; i++){

yt[i] = (T/0.2* (sqrt(a0*x[i])+ a1*x[i] + a2*(x[i]*x[i]) + a3*(x[i]*x[i]*x[i]) + a4*(x[i]*x[i]*x[i]*x[i])));
theta[i] = (atan((dydx[i])));
xU[i] = x[i] - yt[i] * (sin(radians(theta[i])));
yU[i] = ( (y[i] + yt[i]  * (cos(radians(theta[i]))) ) );
xL[i] = x[i] + yt[i] * sin(radians(theta[i]));
yL[i] = (y[i] - yt[i] * cos(radians(theta[i])));

/*   upperX[i] = nfs(xU[i], 1, 8);
upperY[i] = nfs(yU[i], 1, 8);
lowerX[i] = nfs(xL[i], 1, 8);
lowerY[i] = nfs(yL[i], 1, 8);  */

}

for(int i = 0; i < Xordinates.length; i++){

}

}
``````
``````/*

Kepler Visualization - Controls

GUI controls added by Lon Riesberg, Laboratory for Atmospheric and Space Physics
lon@ieee.org

April, 2012

Current release consists of a vertical slider for zoom control.  The slider can be toggled
on/off by pressing the 'c' key.

Slide out controls that map to the other key bindings is currently being implemented and
will be released soon.

*/

class Controls {

int barWidth;
int barX;                          // x-coordinate of zoom control
int minY, maxY;                    // y-coordinate range of zoom control
float minZoomValue, maxZoomValue;  // values that map onto zoom control
float valuePerY;                   // zoom value of each y-pixel
int sliderY;                       // y-coordinate of current slider position
float sliderValue;                 // value that corresponds to y-coordinate of slider
int sliderWidth, sliderHeight;
int sliderX;  // x-coordinate of left-side slider edge

Controls () {

barX = 40;
barWidth = 15;

minY = 40;
maxY = minY + height/3 - sliderHeight/2;

minZoomValue = height - height;
maxZoomValue = height;   // 300 percent
valuePerY = (maxZoomValue - minZoomValue) / (maxY - minY);

sliderWidth = 25;
sliderHeight = 10;
sliderX = ((barX + (barWidth/2)) - (sliderWidth/2));
sliderValue = minZoomValue;
sliderY = minY;
}

void render() {
pushMatrix();
// strokeWeight(1.5);
strokeWeight(1);
//  stroke(105, 105, 105);  // fill(0xff33ff99);
//   stroke(0xff33ff99);  // fill(0xff33ff99);  0xffff0000
stroke(0xffff0000);

// zoom control bar
fill(0, 0, 0, 0);

rect(barX, minY, barWidth, maxY-minY);

// slider
// fill(105, 105, 105); //0x3300FF00
fill(0xffff0000); // 0xff33ff99//0x3300FF00
rect(sliderX, sliderY, sliderWidth, sliderHeight);
popMatrix();
}

float getZoomValue(int y) {
if ((y >= minY) && (y <= (maxY - sliderHeight/2))) {
sliderY = (int) (y - (sliderHeight/2));
if (sliderY < minY) {
sliderY = minY;
}
sliderValue = (y - minY) * valuePerY + minZoomValue;
}
return sliderValue;
}

void updateZoomSlider(float value) {
int tempY = (int) (value / valuePerY) + minY;
if ((tempY >= minY) && (tempY <= (maxY-sliderHeight))) {
sliderValue = value;
sliderY = tempY;
}
}

boolean isZoomSliderEvent(int x, int y) {
int slop = 50;  // number of pixels above or below slider that's acceptable.  provided for ease of use.
int sliderTop = (int) (sliderY - (sliderHeight/2)) - slop;
int sliderBottom = sliderY + sliderHeight + slop;
return ((x >= sliderX) && (x <= (sliderX    + sliderWidth)) && (y >= sliderTop)  && (y <= sliderBottom) || draggingZoomSlider );
}
}
``````
``````
/*
I modified this so the slider is horizontal.  That gives me a vertical for
tweaking altitude and horizontal for right ascension/longitude
*/

/*

Kepler Visualization - Controls

GUI controls added by Lon Riesberg, Laboratory for Atmospheric and Space Physics
lon@ieee.org

April, 2012

Current release consists of a vertical slider for zoom control.  The slider can be toggled
on/off by pressing the 'c' key.

Slide out controls that map to the other key bindings is currently being implemented and
will be released soon.

*/

class HorizontalControl {

int barHeight;
int barY;                          // y-coordinate of zoom control
int minX, maxX;                    // x-coordinate range of zoom control
float minZoomValue, maxZoomValue;  // values that map onto zoom control
float valuePerX;                   // zoom value of each y-pixel
int sliderY;                       // y-coordinate of current slider position
float sliderValue;                 // value that corresponds to y-coordinate of slider
int sliderWidth, sliderHeight;
int sliderX;                       // x-coordinate of left-side slider edge

HorizontalControl () {

barY = 15; //40;
barHeight = 40; //15;

minX = 40;
maxX = minX + width/3 - sliderWidth/2;

minZoomValue = width - width;
maxZoomValue = width;   // 300 percent
valuePerX = (maxZoomValue - minZoomValue) / (maxX - minX);

sliderWidth = 10; //25;
sliderHeight = 25; //10;
// sliderY = (barY + (barHeight/2)) - (sliderHeight/2);
sliderY = (barY - (sliderHeight/2)) + (barHeight/2);
sliderValue = minZoomValue;
sliderX = minX;
}

void render() {
pushMatrix();

// strokeWeight(1.5);
strokeWeight(1);
//  stroke(105, 105, 105);  // fill(0xff33ff99);
//   stroke(0xff33ff99);  // fill(0xff33ff99);  0xffff0000
stroke(0xffff0000);

// zoom control bar
fill(0, 0, 0, 0);

rect(minX,barHeight + height - height/4,maxX-minX, barY );
// rect(maxX-minX, barHeight/2,minX,barY + height - height/4 );

// slider
// fill(105, 105, 105); //0x3300FF00
fill(0xffff0000); // 0xff33ff99//0x3300FF00

rect(sliderX, sliderY + height - height/4 + sliderHeight/2 , sliderWidth, sliderHeight);

popMatrix();

}

float getZoomValue(int x, int y) {
if ((x >= minX) && (x <= (maxX - sliderWidth/2)) && (y > (height - height/3))) {
sliderX = (int) (x - (sliderWidth/2));
if (sliderX < minX) {
sliderX = minX;
}
sliderValue = (x - minX) * valuePerX + minZoomValue;
}
return sliderValue;
}

void updateZoomSlider(float value) {
int tempX = (int) (value / valuePerX) + minX;

if ( (tempX >= minX) && (tempX <= (maxX+sliderWidth))  ) {
sliderValue = value;
sliderX = tempX;
}
}

/*   boolean isZoomSliderEvent(int x, int y) {
int slop = 50;  // number of pixels above or below slider that's acceptable.  provided for ease of use.
int sliderTop = (int) (sliderY - (sliderHeight/2)) - slop;
int sliderBottom = sliderY + sliderHeight + slop;
return ((x >= sliderX) && (x <= (sliderX    + sliderWidth)) && (y >= sliderTop)  && (y <= sliderBottom) || draggingZoomSlider );
} */

boolean isZoomSliderEvent(int x, int y) {
int slop = 50;  // number of pixels above or below slider that's acceptable.  provided for ease of use.
int sliderLeft = (int) (sliderX - (sliderWidth/2)) - slop;
int sliderRight = sliderX + sliderWidth + slop;
//  return ((y >= sliderY + height - height/4) && (y <= (sliderY + height - height/4    + sliderHeight)) && (x >= sliderLeft)  && (x <= sliderRight) || draggingZoomSlider );
return ((y >= sliderY + height - height/4 - sliderHeight/2) && (y <= (sliderY + height - height/4 + sliderHeight*2 )) && (x >= sliderLeft )  && (x <= sliderRight ) || draggingZoomSlider );
}
}
``````

The red points are how it must look to capture the final coordinates correctly, otherwise they get written wrong. Draw wrong, write correctly? Like I said, some weird gymnastics.

Edit: I just realized if you try it, you need a new.csv file in /data and it needs to have at least 160 entries or all you;ll get is a blank screen.

wow, but looks too symmetric for a wing??

you load “data/new.csv” and at the end overwrite it?
may i have a copy to run it?

It should work now.

``````X/C,Y/C
100.0,150.0
100.000336,150.00461
100.00142,150.0095
100.003265,150.0147
100.00586,150.02016
100.009186,150.0259
100.01326,150.03188
100.01807,150.03809
100.02359,150.04451
100.02983,150.05112
100.03679,150.05788
100.04445,150.06476
100.052795,150.07175
100.06181,150.0788
100.07149,150.08586
100.08182,150.09293
100.09277,150.09995
100.104355,150.10689
100.116516,150.1137
100.12927,150.12035
100.14258,150.1268
100.15643,150.13301
100.17079,150.13895
100.18567,150.14456
100.201004,150.14984
100.21681,150.15472
100.23303,150.1592
100.249664,150.16322
100.26668,150.16676
100.28404,150.1698
100.30173,150.1723
100.31972,150.17426
100.33798,150.17564
100.35649,150.17644
100.37521,150.17662
100.394135,150.17621
100.41321,150.17516
100.43242,150.1735
100.45174,150.17119
100.471115,150.16826
100.49042,150.1668
100.50983,150.16414
100.52922,150.16118
100.548584,150.15791
100.56787,150.15436
100.58705,150.15053
100.606094,150.14644
100.624985,150.1421
100.64369,150.13754
100.662186,150.13275
100.68042,150.12779
100.698395,150.12263
100.716064,150.11734
100.73341,150.11191
100.75041,150.10637
100.767044,150.10074
100.78325,150.09505
100.79904,150.08932
100.81439,150.08357
100.829254,150.07785
100.84363,150.07214
100.8575,150.0665
100.87082,150.06094
100.883575,150.0555
100.89575,150.05017
100.90735,150.04501
100.91832,150.04004
100.92868,150.03526
100.938385,150.0307
100.94742,150.0264
100.955795,150.02235
100.963486,150.01859
100.970474,150.01514
100.976746,150.012
100.982315,150.00919
100.98715,150.00673
100.99127,150.00462
100.994644,150.00288
100.99727,150.00153
100.999146,150.00056
100.0,150.0
100.00041,149.99582
100.00159,149.99217
100.003494,149.98907
100.006165,149.98648
100.00957,149.98444
100.0137,149.98288
100.01857,149.98183
100.02417,149.98126
100.03047,149.98117
100.037476,149.9815
100.04518,149.98228
100.05357,149.98344
100.06262,149.98499
100.07234,149.98688
100.08269,149.98907
100.09367,149.99156
100.105255,149.99431
100.11743,149.99725
100.13019,150.0004
100.14349,150.00368
100.15733,150.00706
100.17168,150.01053
100.18652,150.01404
100.20183,150.01753
100.21759,150.02098
100.233765,150.02437
100.250336,150.02763
100.26729,150.03075
100.28458,150.03369
100.3022,150.0364
100.320114,150.03888
100.33829,150.04108
100.356705,150.04297
100.375336,150.04454
100.394165,150.04575
100.41315,150.0466
100.43225,150.04704
100.45146,150.04709
100.47073,150.04672
100.490204,150.04799
100.50957,150.04826
100.528915,150.0484
100.54823,150.04839
100.56746,150.04822
100.58659,150.0479
100.605606,150.04741
100.62445,150.04678
100.64311,150.046
100.661545,150.04509
100.67975,150.04404
100.69768,150.04283
100.71532,150.04152
100.73262,150.04008
100.74959,150.03854
100.766174,150.0369
100.78235,150.03517
100.79811,150.03337
100.813416,150.03151
100.82826,150.0296
100.842606,150.02765
100.85643,150.02568
100.869736,150.02371
100.88248,150.02173
100.89464,150.01978
100.906204,150.01785
100.91716,150.01598
100.92749,150.01414
100.93718,150.01239
100.94621,150.01071
100.954575,150.00912
100.96225,150.00764
100.96922,150.00627
100.975494,150.00502
100.98105,150.00389
100.985886,150.0029
100.98999,150.00206
100.99335,150.00136
100.99597,150.00081
100.99785,150.00041

``````
1 Like

sorry, i might be a little too sensible since working on raspberry pi ( OS on uSD card )
but i not think it is a good idea to write / export that array /
to file at every draw loop.

better find a way to control the program calculation and the file write thing
only at change of the 2 parameters ( M,T / by sliders )

or better, only save to file on key press.

You’re right. I’ll keep working on it. I have some bad code habits that are starting to show up.

pls confirm my understanding of the history:

• you have a airfoil calc tool you find the math at a web page

• that tool gives you raw data ( depending on M,T,P )
what can look like this:

• so the last days you work on a repair for this and found a way by
fix a P to connect the “lines”
shearY- read back (half) with PVector(modelXYZ)
shearY++ read back (other half) with PVector(modelXYZ)
amazing.

i needed some time to get into that far
( without understanding all calc and arrays and shears )
but build my own version where i for test can skip your “repair”
and see ( again ) the raw data and its problem.

program structure regarding recalc and file save
and a shorter slider version by keyboard and mousewheel

i still think there must be a fix for a ?misunderstanding?error? of the NACA math doc.
but i just now have the working environment to play with it.

test
``````// https://discourse.processing.org/t/processing-to-draw-naca-4-digit-airfoils/5739?u=kll
// http://www.airfoiltools.com/airfoil/naca4digit?MNaca4DigitForm%5Bcamber%5D=05&MNaca4DigitForm%5Bposition%5D=40&MNaca4DigitForm%5Bthick%5D=30&MNaca4DigitForm%5BnumPoints%5D=81&MNaca4DigitForm%5BcosSpace%5D=0&MNaca4DigitForm%5BcosSpace%5D=1&MNaca4DigitForm%5BcloseTe%5D=0&MNaca4DigitForm%5BcloseTe%5D=1&yt0=Plot
// https://discourse.processing.org/t/trying-to-use-modelx-but-built-a-spiral-galaxy-of-points/6064?u=kll
// v02

int N = 81;                                  //number of points
float angle;
float M = 0.00;        //  chamber//2/100.0;
float T = 0.10;        //  thickness
float P = 0.4;         //0.4; //40/100.0;
PFont font;
int lines_from_file = 161;            // that length info is used, the file content not, so i skip the reading
PVector[] Xordinates = new PVector[lines_from_file];
PVector[] Yordinates = new PVector[lines_from_file];
float[] Xvalues; // = new float[162];   // set ( returned ) by getOrdinates();
float[] Yvalues; // = new float[162];   // set ( returned ) by getOrdinates();
float[] XUoord; // = new float[81];
float[] YUoord; // = new float[81];
float[] XLoord; // = new float[81];
float[] YLoord; // = new float[81];
// by getOrdinates();
float xU[] = new float[N];
float yU[] = new float[N];
float xL[] = new float[N];
float yL[] = new float[N];

// now the program starts without the file, even without the /data dir what a write could create auto.
String outfilename  = "data/new.csv";   // kll take out of draw and make new key [s] for save to file

boolean diagarray   = false;            // kll consol print enable use: if (diagarray) println("");
boolean diag        = true;             //   if (diag) println("");
boolean recalc      = true;
float zoomit        = 300;
boolean test        = false;            // key [q] disable all afterburner code to see original calculated form

void setup() {
size( 650, 300, P3D);
//font = loadFont("Verdana-Bold-48.vlw");                  // only for me kll
println("use key [s] to save to file, key [q] for test");
println("use mouseWheel and key [t] for T, key [m] for M, key [p] for P");
}

void draw() {
background(0, 0, 0);
if ( recalc ) getOrdinates();
stroke(182, 185, 188);
strokeWeight(1);
line( width-width, height/2, width, height/2);
textFont(font, 15);
textAlign(LEFT, BOTTOM);
text("Airfoil:\nCamber  : "+nf(M * 100.0, 1, 1) + " [m]\nThickness: "  +nf(T * 100.0, 1, 1)+" [t]\nPos max: "  +nf(P * 10.0, 1, 1)+ " [p]", width/2+150, height/2+120);
strokeWeight(3);

translate(width/2-zoomit*1.5/2.0, height/2);

// return: set the globals with results
Xvalues = concat(xU, xL);
Yvalues = concat(yU, yL);

for (int i = 0; i < 160; i++) {
if (diagarray)  println("array: X "+Xvalues[i]+" Y "+Yvalues[i]);
}

if (!test ) {
// split up again??
XLoord = subset(Xvalues, 81, 80);
YLoord = subset(Yvalues, 81, 80);
XUoord = subset(Xvalues, 0, 80);
YUoord = subset(Yvalues, 0, 80);
angle = sqrt( ((300 - Xvalues[0]) * (300 - Xvalues[0])) + ((300*Yvalues[80]) * (300*Yvalues[80])) );
angle = asin(300*Yvalues[80]/angle);

stroke(255, 0, 0);
shearY(-angle);
for (int i = 0; i < 80; i++) {
point(300*XUoord[i], -300*YUoord[i]);
Xordinates[i] = new PVector(modelX(XUoord[i], YUoord[i], 0), modelY(0, 0, 0));
Yordinates[i] = new PVector(modelX(0, 0, 0), modelY(XUoord[i], YUoord[i], 0));
Xvalues[i] = Xordinates[i].x;
Yvalues[i] = Yordinates[i].y;
}

shearY(angle);
shearY(angle);
for (int i = 0; i < 80; i++) {
point(300*XLoord[i], -300*YLoord[i]);
Xordinates[80+i] = new PVector(modelX(XLoord[i], YLoord[i], 0), modelY(0, 0, 0));
Yordinates[80+i] = new PVector(modelX(0, 0, 0), modelY(XLoord[i], YLoord[i], 0));
Xvalues[80+i] = Xordinates[80+i].x;
Yvalues[80+i] = Yordinates[80+i].y;
}

shearY(-angle);
}
stroke(0, 255, 0);
for (int i = 0; i < 160; i++) {
if (test)     point(zoomit*(Xvalues[i]), -zoomit*(Yvalues[i]));
else          point(300*(Xvalues[i]-100), -300*(Yvalues[i]-150));
}
}

//_____________________________________________________________
void save_to_file() {
Table table = new Table();
String infocsv = "M="+M+",T="+T;
for (int i = 0; i < 160; i++) {
newRow.setFloat("X/C", Xvalues[i] );
newRow.setFloat("Y/C", Yvalues[i] );
}
saveTable(table, outfilename);
println("data saved to "+outfilename);
}

//_____________________________________________________________  kll new operation
void keyPressed() {
if ( key == 's' )  save_to_file();
if ( key == 'q' ) {
test = !test;
println("test: "+test);
}
}
//_____________________________________________________________
void mouseWheel(MouseEvent event) {
float e = event.getCount();
if ( keyPressed && key == 't' ) {
T += 0.005*e;
recalc = true;
}                 // T
if ( keyPressed && key == 'm' ) {
M += 0.005*e;
recalc = true;
}                 // M
if ( keyPressed && key == 'p' ) {
P += 0.05*e;
recalc = true;
}                 // P
}

//_____________________________________________________________  // rearrange local vars and alread write to globals
void getOrdinates() {
// acc NACA 4 digit airfoil generator (NACA 5430 AIRFOIL)
// make some vars local
float x[] = new float[N];  //divide up unit chord length by N
float y[] = new float[N];  //divide up unit chord length by N
float[] beta = new float[N];
float[] dydx = new float[N];
float a0 = 0.2969;
float a1 = -0.126;
float a2 = -0.3516;
float a3 = 0.2843;
float a4 = -0.1036;
float[] theta = new float[N];
float[] yt = new float[N];

for (int i = 0; i < N; i++) {
if (i >= 0) {
x[i] = (1 - cos(beta[i]))/2;
}
if (i < 40) {
if (x[i] >= 0) {
y[i] = ((M/(P*P) * (2*P*x[i] - x[i]*x[i])));
dydx[i] = (2*M)/(P*P) * (P - x[i]);
}
}
if (i >=40) {
if (x[i] <= 1.00000f) {
y[i] = (M/(1-P)*(1-P) * (1 - 2*P + 2*P*x[i] - x[i]*x[i]))*2.75; // only valid for one P //* 2.75; //*2.686;
dydx[i] = (2*M/((1-P)*(1-P)) * (P - x[i]));
}
}
}  //end for loop

for (int i = 0; i < 81; i++) {

yt[i] = (T/0.2* (sqrt(a0*x[i])+ a1*x[i] + a2*(x[i]*x[i]) + a3*(x[i]*x[i]*x[i]) + a4*(x[i]*x[i]*x[i]*x[i])));
theta[i] = atan(dydx[i]);
xU[i] = x[i] - yt[i]  * sin(radians(theta[i]));
yU[i] = y[i] + yt[i]  * cos(radians(theta[i]));
xL[i] = x[i] + yt[i]  * sin(radians(theta[i]));
yL[i] = y[i] - yt[i]  * cos(radians(theta[i]));
}

recalc = false;
}
//_______________________________

``````

Yes, sir, @kll, you understand what I’ve been doing. I get close and then another problem pops up. I’m as close to a working program as I’ve been. There is an old program called Xfoil, written in fortran and I tried my output “new.csv” file. The problem with Xfoil is that my coordinates are not in the right order. They can be clockwise or counterclockwise, but they have to be continuous from 0 to 1 and then back 1 to 0. Mine go 0 to 1 and then jump back again 0 to 1 in the middle of the file. I wrote a sketch that reverses it, but I haven’t put it in my program.

I will take a look at what you’ve done and see what I can implement. Thanks. .

pls give me some more time, i already found 3 bugs
now get a foil without your fix, but
the P understanding and shifting the array split not works nice.
( i keep the old calc code so you see the changes better )

test4
``````// https://discourse.processing.org/t/processing-to-draw-naca-4-digit-airfoils/5739?u=kll
// http://www.airfoiltools.com/airfoil/naca4digit?MNaca4DigitForm%5Bcamber%5D=05&MNaca4DigitForm%5Bposition%5D=40&MNaca4DigitForm%5Bthick%5D=30&MNaca4DigitForm%5BnumPoints%5D=81&MNaca4DigitForm%5BcosSpace%5D=0&MNaca4DigitForm%5BcosSpace%5D=1&MNaca4DigitForm%5BcloseTe%5D=0&MNaca4DigitForm%5BcloseTe%5D=1&yt0=Plot
// https://discourse.processing.org/t/trying-to-use-modelx-but-built-a-spiral-galaxy-of-points/6064
// v02 show forum https://discourse.processing.org/t/trying-to-use-modelx-but-built-a-spiral-galaxy-of-points/6064/12
// v03 erase shear fix... and mod calc tool use "p" as x[px] with px = int(P*N)
// v04 reverse the export to file and show keyboard options on canvas

int N = 81;            //number of points
float angle;
float M = 0.00;        //  chamber//2/100.0;
float T = 0.10;        //  thickness
float P = 0.5;         // but show 5.0
float p = 0;           // can be P or x[xp] try swith key [l]
PFont font;
float[] Xvalues;
float[] Yvalues;
// by getOrdinates();
float xU[] = new float[N];
float yU[] = new float[N];
float xL[] = new float[N];
float yL[] = new float[N];

// now the program starts without the file, even without the /data dir what a write could create automatic
String outfilename  = "data/new.csv";   // take out of draw and make new key [s] for save to file

boolean diag        = false;             //   if (diag) println("");
boolean recalc      = true;
float zoomit        = 300;              // airfoil norm 0 .. 1.0 for screen * 300
//boolean test        = true;             // key [q] disable all afterburner code to see original calculated form
boolean betadens    = true;             // variable density of x[i] points or not key [i]
boolean pcalc       = true;             // key [l]
void setup() {
size( 650, 300, P3D);
font = loadFont("Verdana-Bold-48.vlw");                  // only for me kll
println("use key [s] to save to file, key [a] print array, key [d] diag print, key [i] test x[i] pointdensity, key [l] switch p calc");
println("use mouseWheel and key [t] for T, key [m] for M, key [p] for P");
}

void draw() {
background(0, 0, 0);
if ( recalc ) {
getOrdinates();
Xvalues = concat(xU, xL);               // return: set the globals with results
Yvalues = concat(yU, yL);
}
stroke(182, 185, 188);
strokeWeight(1);
line( width-width, height/2, width, height/2);
textFont(font, 15);
textAlign(LEFT, BOTTOM);
text("Airfoil:\nCamber  : "+nf(M * 100.0, 1, 1) + " [m]\nThickness: "  +nf(T * 100.0, 1, 1)+" [t]\nP: "  +nf(P*10, 1, 1)+ " [p]", width/2+150, height/2+120);
text("use: [s] save file, [d] diag print, [l] use P or p, [i] pointdens, [a] print array ", 10, height/2+140);
translate(width/2-zoomit*1.5/2.0, height/2);
stroke(0, 255, 0);
strokeWeight(3);
for (int i = 0; i < 160; i++) point(zoomit*(Xvalues[i]), -zoomit*(Yvalues[i]));
}

//_____________________________________________________________
void save_to_file() {
Table table = new Table();
String infocsv = "M="+M+",T="+T+",P="+P;    // for documentation like NACA files look
for (int i = 159; i > 80; i--) {
newRow.setFloat("X/C", Xvalues[i] );
newRow.setFloat("Y/C", Yvalues[i] );
}
for (int i = 0; i < 80; i++) {
newRow.setFloat("X/C", Xvalues[i] );
newRow.setFloat("Y/C", Yvalues[i] );
}
saveTable(table, outfilename);
println("data saved to "+outfilename);
}

//_____________________________________________________________  kll new operation
void keyPressed() {
if ( key == 's' )  save_to_file();
//  if ( key == 'q' ) {
//    test = !test;
//    println("test: "+test);
//  }
if ( key == 'd' ) {
diag = !diag;
println("diag: "+diag);
}
if ( key == 'i' ) {
recalc = true;
}
if ( key == 'l' ) {
pcalc = !pcalc;
recalc = true;
println("pcalc: "+pcalc);
}

if ( key == 'a' ) {
for (int i = 0; i < 160; i++)   println("i "+i+" array: X "+Xvalues[i]+" Y "+Yvalues[i]);
}
}
//_____________________________________________________________
void mouseWheel(MouseEvent event) {
float e = event.getCount();
if ( keyPressed && ( key == 't' || key == 'm' || key == 'p' ) ) recalc = true;
if ( keyPressed && key == 't' )  T += 0.005*e;                // T
if ( keyPressed && key == 'm' )  M += 0.005*e;                // M
if ( keyPressed && key == 'p' )  P += 0.05*e;                 // P
}

//_____________________________________________________________  // rearrange local vars and alread write to globals
void getOrdinates() {
// acc NACA 4 digit airfoil generator (NACA 5430 AIRFOIL)
float x[] = new float[N];  //divide up unit chord length by N
float y[] = new float[N];  //divide up unit chord length by N
float[] dydx = new float[N];
//  float[] beta = new float[N];
final float betastep = PI/(N-1);  // 80 ( not 81 ) ends with 3.14159
int   px = 40;                // the separation left right 0 < x[px] < 1  for N(81) 0 .. 80
final float a0 = 0.2969;
final float a1 = -0.126;
final float a2 = -0.3516;
final float a3 = 0.2843;
final float a4 = -0.1036;
float[] theta = new float[N];
float[] yt = new float[N];
for (int i = 0; i < N; i++) {                   // need make extra loop first for that x[i] to get x[px]
//    if (i >= 0) {
//      beta[i] = (radians(180)/81.0) * i;
//      x[i] = (1 - cos(beta[i]))/2;

if ( betadens ) x[i] = (1 - cos(betastep * i))/2;               // 0 .. x[px] .. 1.0
else            x[i] = i/float(N-1);
//    if (diag) println("i "+i+" x[i] "+nf(x[i], 1, 3));
//    }
}
px = int(P*N);                // new idea , they want change "p" ?split position? depending on P [0 .. 1.0]
if (pcalc) p= x[px];
else       p = P;             // both not ok as we use the cos(beta) point density
if (diag)   println(" P "+P+" x["+px+"] "+nf(p, 1, 3));
for (int i = 0; i < N; i++) {
if (i < px) {
//      if (x[i] >= 0) {
//        y[i] = ((M/(P*P) * (2*P*x[i] - x[i]*x[i])));
//        dydx[i] = (2*M)/(P*P) * (P - x[i]);
y[i]    = M * (2.0 * P * x[i] - x[i]*x[i])/(p*p);
dydx[i] = 2.0 * M * (P - x[i])/(p*p);
//      }
}
if (i >=px) {
//      if (x[i] <= 1.00000f) {
//        y[i] = (M/(1-P)*(1-P) * (1 - 2*P + 2*P*x[i] - x[i]*x[i]));    //*2.75; // only valid for one P //* 2.75; //*2.686;
//        dydx[i] = (2*M/((1-P)*(1-P)) * (P - x[i]));
y[i]    = M * (1 - 2.0 * P + 2.0 * P * x[i] - x[i]*x[i])/((1-p)*(1-p));
dydx[i] = 2.0 * M * (P - x[i])/((1-p)*(1-p));
//      }
}
}  //end for loop

for (int i = 0; i < N; i++) {

//   yt[i] = (T/0.2 * (sqrt(a0*x[i])+ a1*x[i] + a2*(x[i]*x[i]) + a3*(x[i]*x[i]*x[i]) + a4*(x[i]*x[i]*x[i]*x[i])));
yt[i] = T/0.2 * ( a0*sqrt(x[i])+ a1*x[i] + a2*x[i]*x[i] + a3*x[i]*x[i]*x[i] + a4*x[i]*x[i]*x[i]*x[i] );
theta[i] = atan(dydx[i]);        // atan gives -PI/2 to PI/2
if (diag) println("i "+i+" dydx[i] "+nf(dydx[i], 1, 3)+" theta[i] " +nf(theta[i], 1, 3)+" yt[i] "+nf(yt[i], 1, 3));
//    xU[i] = x[i] - yt[i]  * sin(radians(theta[i]));
//    yU[i] = y[i] + yt[i]  * cos(radians(theta[i]));
//    xL[i] = x[i] + yt[i]  * sin(radians(theta[i]));
//    yL[i] = y[i] - yt[i]  * cos(radians(theta[i]));
xU[i] = x[i] - yt[i]  * sin(theta[i]);
yU[i] = y[i] + yt[i]  * cos(theta[i]);
xL[i] = x[i] + yt[i]  * sin(theta[i]);
yL[i] = y[i] - yt[i]  * cos(theta[i]);
}

recalc = false;
}
//_______________________________

``````
1 Like

Wow, you found that P = 0.5 draws without a step. There’s a way to tame that.

I need to study your other improvements, there’s a lot to learn there.

as that is cleaned up now not easy to see the differences
in the calculation ( so i not overwrite above and post new here )

test5
``````// https://discourse.processing.org/t/processing-to-draw-naca-4-digit-airfoils/5739
// http://www.airfoiltools.com/airfoil/naca4digit?MNaca4DigitForm%5Bcamber%5D=05&MNaca4DigitForm%5Bposition%5D=40&MNaca4DigitForm%5Bthick%5D=30&MNaca4DigitForm%5BnumPoints%5D=81&MNaca4DigitForm%5BcosSpace%5D=0&MNaca4DigitForm%5BcosSpace%5D=1&MNaca4DigitForm%5BcloseTe%5D=0&MNaca4DigitForm%5BcloseTe%5D=1&yt0=Plot
// http://www.pdas.com/refs/tm4741.pdf   //different calc
// https://discourse.processing.org/t/trying-to-use-modelx-but-built-a-spiral-galaxy-of-points/6064
// v02 show forum https://discourse.processing.org/t/trying-to-use-modelx-but-built-a-spiral-galaxy-of-points/6064/12
// v03 erase shear fix... and mod calc tool use "p" as x[px] with px = int(P*N)
// v04 reverse the export to file and show keyboard options on canvas
// v05b check switch point math again  // save tuning  // show switches status

int N = 81;            //number of points
float angle;
float M = 0.00;        //  chamber//2/100.0;
float T = 0.10;        //  thickness
float P = 0.5;         // but show 5.0
float p = 0;           // can be P or x[xp] try swith key [l]
PFont font;
float[] Xvalues;
float[] Yvalues;

// now the program starts without the file, even without the /data dir what a write could create automatic
String outfilename  = "data/new.csv";   // take out of draw and make new key [s] for save to file

boolean diag        = false;             //   if (diag) println("");
boolean recalc      = true;
float zoomit        = 300;              // airfoil norm 0 .. 1.0 for screen * 300
//boolean test        = true;             // key [q] disable all afterburner code to see original calculated form
boolean betadens    = true;             // variable density of x[i] points or not key [i]
boolean pcalc       = false;             // key [l]

void setup() {
size( 650, 300, P3D);
font = loadFont("Verdana-Bold-48.vlw");                  // only for me kll
println("use key [s] to save to file, key [a] print array, key [d] diag print, key [i] test x[i] pointdensity, key [l] switch p calc");
println("use mouseWheel and key [t] for T, key [m] for M, key [p] for P");
}

void draw() {
background(0, 0, 0);
if ( recalc )    getOrdinates();
show_text();
translate(width/2-zoomit*1.5/2.0, height/2);
stroke(0, 255, 0);
strokeWeight(3);
for (int i = 0; i < 160; i++) point(zoomit*(Xvalues[i]), -zoomit*(Yvalues[i]));
}

void show_text() {
stroke(182, 185, 188);
strokeWeight(1);
line( width-width, height/2, width, height/2);
textFont(font, 15);
textAlign(LEFT, BOTTOM);
String ds="off",ls="off",is="off";
if (diag)      ds="on";
if (pcalc)     ls="on";
text("Airfoil:\nCamber  : "+nf(M * 100.0, 1, 1) + " [m]\nThickness: "  +nf(T * 100.0, 1, 1)+" [t]\nP: "  +nf(P*10, 1, 1)+ " [p]", width/2+150, height/2+120);
text("use: [s] save file, [a] print array, [d] diag "+ds+", [l] use Porp "+ls+", [i] pdens "+is, 10, height/2+140);
}
//_____________________________________________________________
void save_to_file() {
Table table = new Table();
String infocsv = "M="+M+",T="+T+",P="+P;    // for documentation like NACA files look
for (int i = 159; i > 80; i--) {
newRow.setFloat("X/C", Xvalues[i] );
newRow.setFloat("Y/C", Yvalues[i] );
}
for (int i = 0; i < 80; i++) {
newRow.setFloat("X/C", Xvalues[i] );
newRow.setFloat("Y/C", Yvalues[i] );
}
saveTable(table, outfilename);
println("data saved to "+outfilename);
}

//_____________________________________________________________  kll new operation
void keyPressed() {
if ( key == 's' )  save_to_file();
//  if ( key == 'q' ) {
//    test = !test;
//    println("test: "+test);
//  }
if ( key == 'd' ) {
diag = !diag;
println("diag: "+diag);
}
if ( key == 'i' ) {
recalc = true;
}
if ( key == 'l' ) {
pcalc = !pcalc;
recalc = true;
println("pcalc: "+pcalc);
}

if ( key == 'a' ) {
for (int i = 0; i < 160; i++)   println("i "+i+" array: X "+Xvalues[i]+" Y "+Yvalues[i]);
}
}
//_____________________________________________________________
void mouseWheel(MouseEvent event) {
float e = event.getCount();
if ( keyPressed && ( key == 't' || key == 'm' || key == 'p' ) ) recalc = true;
if ( keyPressed && key == 't' )  T += 0.005*e;                // T
if ( keyPressed && key == 'm' )  M += 0.005*e;                // M
if ( keyPressed && key == 'p' )  P += 0.05*e;                 // P
}

//_____________________________________________________________  // rearrange local vars and alread write to globals
void getOrdinates() {
// acc NACA 4 digit airfoil generator (NACA 5430 AIRFOIL)
// check input parameter bounds from tuning:
T = constrain(T, 0.0, 1.0);
M = constrain(M, 0.0, 1.0);
P = constrain(P, 0.0, 1.0);

float x[] = new float[N];
float y[] = new float[N];
float[] dydx = new float[N];
//  float[] beta = new float[N];
final float betastep = PI/(N-1);  // 80 ( not 81 ) ends with 3.14159
int   px = 40;                // the separation left right 0 < x[px] < 1  for N(81) 0 .. 80
final float a0 = 0.2969;
final float a1 = -0.126;
final float a2 = -0.3516;
final float a3 = 0.2843;
final float a4 = -0.1015;   // -0.1036;
float[] theta = new float[N];
float[] yt = new float[N];
// by getOrdinates();
float xU[] = new float[N];
float yU[] = new float[N];
float xL[] = new float[N];
float yL[] = new float[N];

for (int i = 0; i < N; i++) {                                 // need make extra loop first for that x[i] to get x[px]
if ( betadens ) x[i] = (1 - cos(betastep * i))/2;           // 0 .. x[px] .. 1.0
else            x[i] = i/float(N-1);
}
px = int(P*(N-1));                                            // new idea , they want change "p" ?split position? depending on P [0 .. 1.0]
if (pcalc) p= x[px];
else       p = P;                                             // both not ok as we use the cos(beta) point density
if (diag)   println(" P "+P+" x["+px+"] "+nf(p, 1, 3));
// v05
float ppp = p*p;
for (int i = 0; i < N; i++) {
if (i >=px) {                                               // with this we change the calculation
ppp = (1-p)*(1-p);
}
y[i]    = M * (tailadd + 2.0 * P * x[i] - x[i]*x[i])/ppp;
dydx[i] = 2.0 * M * (P - x[i])/ppp;
}

for (int i = 0; i < N; i++) {
yt[i] = T*( a0*sqrt(x[i])+ a1*x[i] + a2*x[i]*x[i] + a3*x[i]*x[i]*x[i] + a4*x[i]*x[i]*x[i]*x[i] )/0.2;
theta[i] = atan(dydx[i]);        // atan gives -PI/2 to PI/2
if (diag) println("i "+i+" dydx[i] "+nf(dydx[i], 1, 3)+" theta[i] " +nf(theta[i], 1, 3)+" yt[i] "+nf(yt[i], 1, 3));
xU[i] = x[i] - yt[i]  * sin(theta[i]);
yU[i] = y[i] + yt[i]  * cos(theta[i]);
xL[i] = x[i] + yt[i]  * sin(theta[i]);
yL[i] = y[i] - yt[i]  * cos(theta[i]);
}

Xvalues = concat(xU, xL);               // return: set the globals with the results
Yvalues = concat(yU, yL);

recalc = false;
}
//_______________________________

``````

@kll , that is pretty darn good. You untangled my mess and made it work.

Just to verify that the equations are correct, I took Xfoil and loaded its NACA 2412 and then overlayed your NACA 2412 and any difference is not detectable by eye. Xfoil does some serious analysis of shapes.

you changed from points to shape ?curveVertex?
looks good!
ups: just see my text of switch status is confusing!
[d] diag off // actually means ( is OFF now )

do you mind posting your current code ( for public and our teamwork )?

did you verify already if the export file is usable ? i see that the official files
not have “,” only " " and my changed header line with info might give read problems (column 3)

Yeah, look closer at the picture. The Xfoil generated 2412 is white and the aqua/blue 2412 is the csv output overlayed, no difference.

Here’s the only change I made to draw:

``````void draw() {
background(0, 0, 0);
if ( recalc )    getOrdinates();
show_text();
translate(width/2-zoomit*1.5/2.0, height/2);
stroke(0, 255, 0);
strokeWeight(3);
// for (int i = 0; i < 160; i++) point(zoomit*(Xvalues[i]), zoomit*(Yvalues[i]));  ///comment out and add Shape below

s = createShape();   // also need PShape s;    before setup with other global variables up top
s.beginShape();
// s.fill(125);
s.noFill();
s.stroke(0,255,0);
s.strokeWeight(2);

for (int i = 0; i < 160; i++) s.vertex(zoomit*(Xvalues[i]), -zoomit*(Yvalues[i]));
s.endShape(CLOSE);
shape(s,0,0);   ////////this is the end of added Shape code
}
``````

sorry, we do have a array problem, we only see now with vertext ( and pdens = OFF )

so again we repair the file export to cover up a array problem inside the generator

test 6(beta)
``````// https://discourse.processing.org/t/processing-to-draw-naca-4-digit-airfoils/5739
// http://www.airfoiltools.com/airfoil/naca4digit?MNaca4DigitForm%5Bcamber%5D=05&MNaca4DigitForm%5Bposition%5D=40&MNaca4DigitForm%5Bthick%5D=30&MNaca4DigitForm%5BnumPoints%5D=81&MNaca4DigitForm%5BcosSpace%5D=0&MNaca4DigitForm%5BcosSpace%5D=1&MNaca4DigitForm%5BcloseTe%5D=0&MNaca4DigitForm%5BcloseTe%5D=1&yt0=Plot
// http://www.pdas.com/refs/tm4741.pdf   //different calc
// https://discourse.processing.org/t/trying-to-use-modelx-but-built-a-spiral-galaxy-of-points/6064
// v02 show forum https://discourse.processing.org/t/trying-to-use-modelx-but-built-a-spiral-galaxy-of-points/6064/12
// v03 erase shear fix... and mod calc tool use "p" as x[px] with px = int(P*N)
// v04 reverse the export to file and show keyboard options on canvas
// v05b check switch point math again  // safe tuning  // show switches status
// v06(beta)  feedback, change from points to vertext shape

PShape s;

int N = 81;            //number of points
float angle;
float M = 0.00;        //  chamber//2/100.0;
float T = 0.10;        //  thickness
float P = 0.5;         // but show 5.0
float p = 0;           // can be P or x[xp] try swith key [l]
PFont font;
float[] Xvalues;
float[] Yvalues;

// now the program starts without the file, even without the /data dir what a write could create automatic
String outfilename  = "data/new.csv";   // take out of draw and make new key [s] for save to file

boolean diag        = false;             //   if (diag) println("");
boolean recalc      = true;
float zoomit        = 300;               // airfoil norm 0 .. 1.0 for screen * 300
boolean betadens    = true;              // variable density of x[i] points or not key [i]
boolean pcalc       = false;             // key [l]
boolean showpoints  = false;             // key [q]

void setup() {
size( 650, 300, P3D);
font = loadFont("Verdana-Bold-48.vlw");                  // only for me kll
println("use key [s] to save to file, key [a] print array, key [d] diag print, key [i] test x[i] pointdensity, key [l] switch p calc");
println("use mouseWheel and key [t] for T, key [m] for M, key [p] for P");
}

void draw() {
background(0, 0, 0);
if ( recalc )    getOrdinates();
show_text();
//  translate(width/2-zoomit*1.5/2.0, height/2);
translate(width/2-zoomit/2.0, height/2);
s = createShape();   // also need PShape s;    before setup with other global variables up top
s.beginShape();
s.noFill();
s.stroke(0, 255, 255);
s.strokeWeight(1);
for (int i = 0; i < 160; i++) s.vertex(zoomit*(Xvalues[i]), -zoomit*(Yvalues[i]));
s.endShape(CLOSE);
shape(s,0,0);
stroke(0, 255, 0);
strokeWeight(3);
if (showpoints) for (int i = 0; i < 160; i++) point(zoomit*(Xvalues[i]), -zoomit*(Yvalues[i]));
}

void show_text() {
stroke(182, 185, 188);
strokeWeight(1);
line( width-width, height/2, width, height/2);
textFont(font, 12);
textAlign(LEFT, BOTTOM);
String ds="(off)", ls="(off)", is="(off)";
if (diag)      ds="(on)";
if (pcalc)     ls="(on)";
text("Airfoil:\nCamber  : "+nf(M * 100.0, 1, 1) + " [m]\nThickness: "  +nf(T * 100.0, 1, 1)+" [t]\nP: "  +nf(P*10, 1, 1)+ " [p]", width/2+150, height/2+120);
text("use: [s] save file, [a] print array, [d] diag "+ds+", [l] use Porp "+ls+", [i] pdens "+is, 10, height/2+140);
}
//_____________________________________________________________
void save_to_file() {
Table table = new Table();
String infocsv = "M="+M+",T="+T+",P="+P;    // for documentation like NACA files look
for (int i = 159; i > 80; i--) {
newRow.setFloat("X/C", Xvalues[i] );
newRow.setFloat("Y/C", Yvalues[i] );
}
for (int i = 0; i < 80; i++) {
newRow.setFloat("X/C", Xvalues[i] );
newRow.setFloat("Y/C", Yvalues[i] );
}
saveTable(table, outfilename);
println("data saved to "+outfilename);
}

//_____________________________________________________________  kll new operation
void keyPressed() {
if ( key == 's' )  save_to_file();
if ( key == 'q' ) {
showpoints = !showpoints;
println("showpoints: "+showpoints);
}
if ( key == 'd' ) {
diag = !diag;
println("diag: "+diag);
}
if ( key == 'i' ) {
recalc = true;
}
if ( key == 'l' ) {
pcalc = !pcalc;
recalc = true;
println("pcalc: "+pcalc);
}

if ( key == 'a' ) {
for (int i = 0; i < 160; i++)   println("i "+i+" array: X "+Xvalues[i]+" Y "+Yvalues[i]);
}
}
//_____________________________________________________________
void mouseWheel(MouseEvent event) {
float e = event.getCount();
if ( keyPressed && ( key == 't' || key == 'm' || key == 'p' ) ) recalc = true;
if ( keyPressed && key == 't' )  T += 0.005*e;                // T
if ( keyPressed && key == 'm' )  M += 0.005*e;                // M
if ( keyPressed && key == 'p' )  P += 0.05*e;                 // P
}

//_____________________________________________________________  // rearrange local vars and alread write to globals
void getOrdinates() {
// acc NACA 4 digit airfoil generator (NACA 5430 AIRFOIL)
// check input parameter bounds from tuning:
T = constrain(T, 0.0, 1.0);
M = constrain(M, 0.0, 1.0);
P = constrain(P, 0.0, 1.0);

float x[] = new float[N];
float y[] = new float[N];
float[] dydx = new float[N];
//  float[] beta = new float[N];
final float betastep = PI/(N-1);  // 80 ( not 81 ) ends with 3.14159
int   px = 40;                // the separation left right 0 < x[px] < 1  for N(81) 0 .. 80
final float a0 = 0.2969;
final float a1 = -0.126;
final float a2 = -0.3516;
final float a3 = 0.2843;
final float a4 = -0.1015;   // -0.1036;
float[] theta = new float[N];
float[] yt = new float[N];
// by getOrdinates();
float xU[] = new float[N];
float yU[] = new float[N];
float xL[] = new float[N];
float yL[] = new float[N];

for (int i = 0; i < N; i++) {                                 // need make extra loop first for that x[i] to get x[px]
if ( betadens ) x[i] = (1 - cos(betastep * i))/2;           // 0 .. x[px] .. 1.0
else            x[i] = i/float(N-1);
}
px = int(P*(N-1));                                            // new idea , they want change "p" ?split position? depending on P [0 .. 1.0]
if (pcalc) p= x[px];
else       p = P;                                             // both not ok as we use the cos(beta) point density
if (diag)   println(" P "+P+" x["+px+"] "+nf(p, 1, 3));
// v05
float ppp = p*p;
for (int i = 0; i < N; i++) {
if (i > px) {                                               // with this we change the calculation
ppp = (1-p)*(1-p);
}
y[i]    = M * (tailadd + 2.0 * P * x[i] - x[i]*x[i])/ppp;
dydx[i] = 2.0 * M * (P - x[i])/ppp;
}

for (int i = 0; i < N; i++) {
yt[i] = T*( a0*sqrt(x[i])+ a1*x[i] + a2*x[i]*x[i] + a3*x[i]*x[i]*x[i] + a4*x[i]*x[i]*x[i]*x[i] )/0.2;
theta[i] = atan(dydx[i]);        // atan gives -PI/2 to PI/2
if (diag) println("i "+i+" dydx[i] "+nf(dydx[i], 1, 3)+" theta[i] " +nf(theta[i], 1, 3)+" yt[i] "+nf(yt[i], 1, 3));
xU[i] = x[i] - yt[i]  * sin(theta[i]);
yU[i] = y[i] + yt[i]  * cos(theta[i]);
xL[i] = x[i] + yt[i]  * sin(theta[i]);
yL[i] = y[i] - yt[i]  * cos(theta[i]);
}

Xvalues = concat(xU, xL);               // return: set the globals with the results
Yvalues = concat(yU, yL);

recalc = false;
}
//_______________________________

``````