fix normals for vertices

- now stl output can be porcessed directly by slic3r without intermediate fix wihtin blender
- created Wall3dStream instead of keeping all within Wall3d
- can save only stl output ( 'Save stl' )
This commit is contained in:
philippe lhardy
2020-10-11 22:14:10 +02:00
parent 0f1a15916c
commit f07767dcfd
3 changed files with 216 additions and 121 deletions

View File

@@ -33,6 +33,7 @@ import javax.swing.event.ChangeListener;
import org.artisanlogiciel.games.*; import org.artisanlogiciel.games.*;
import org.artisanlogiciel.games.stl.Wall3d; import org.artisanlogiciel.games.stl.Wall3d;
import org.artisanlogiciel.games.stl.Wall3dStream;
import org.artisanlogiciel.util.UTF8Control; import org.artisanlogiciel.util.UTF8Control;
import org.artisanlogiciel.graphics.Drawing; import org.artisanlogiciel.graphics.Drawing;
@@ -332,7 +333,7 @@ public class Display extends JFrame {
final JButton saveSvgButton = new JButton(labels.getString("save") + " svg"); final JButton saveSvgButton = new JButton(labels.getString("save") + " svg");
Action saveSvgAction = new AbstractAction() { Action saveSvgAction = new AbstractAction() {
public void actionPerformed(ActionEvent evt) { public void actionPerformed(ActionEvent evt) {
writeSentence("save png"); writeSentence("save svg");
setMazeName(saveName.getText()); setMazeName(saveName.getText());
saveSvg(); saveSvg();
} }
@@ -359,12 +360,25 @@ public class Display extends JFrame {
} }
}; };
saveImcButton.addActionListener(saveImcAction); saveImcButton.addActionListener(saveImcAction);
final JButton saveStlButton = new JButton(labels.getString("save") + " stl");
Action saveStlAction = new AbstractAction() {
public void actionPerformed(ActionEvent evt) {
//
System.out.println("save stl");
setMazeName(saveName.getText());
MazeParamsFixed p = (MazeParamsFixed) params;
saveStl(p, model);
}
};
saveStlButton.addActionListener(saveStlAction);
JMenu saveMenu = new JMenu("Save"); JMenu saveMenu = new JMenu("Save");
saveMenu.add(saveName); saveMenu.add(saveName);
saveMenu.add(saveSvgButton); saveMenu.add(saveSvgButton);
saveMenu.add(savePngButton); saveMenu.add(savePngButton);
saveMenu.add(saveButton); saveMenu.add(saveButton);
saveMenu.add(saveStlButton);
saveMenu.add(saveImcButton); saveMenu.add(saveImcButton);
return saveMenu; return saveMenu;
@@ -997,6 +1011,24 @@ public class Display extends JFrame {
Display display = new Display(model, W, H, params); Display display = new Display(model, W, H, params);
} }
public static void saveStl(MazeParamsFixed params, LabyModel model) {
File outfile = new File(params.getSaveDir(), params.getName() + ".stl");
if (!outfile.exists()) {
System.out.println("Saving to " + outfile + " ...");
try {
FileOutputStream out = new FileOutputStream(outfile);
new Wall3dStream(params.getName(), model, out, false).stream(10,10,10);
out.close();
System.out.println("... Done.");
} catch (IOException io) {
io.printStackTrace(System.err);
}
} else {
System.out.println("" + outfile + " already exists");
}
}
public static void save(MazeParamsFixed params, LabyModel model) { public static void save(MazeParamsFixed params, LabyModel model) {
File outfile = new File(params.getSaveDir(), params.getName() + ".raw"); File outfile = new File(params.getSaveDir(), params.getName() + ".raw");
if (!outfile.exists()) { if (!outfile.exists()) {
@@ -1013,24 +1045,9 @@ public class Display extends JFrame {
} else { } else {
System.out.println("" + outfile + " already exists"); System.out.println("" + outfile + " already exists");
} }
outfile = new File(params.getSaveDir(), params.getName() + ".stl");
if (!outfile.exists()) {
System.out.println("Saving to " + outfile + " ...");
try {
FileOutputStream out = new FileOutputStream(outfile);
Wall3d.streamWallsOut(params.getName(), model, out);
out.flush();
out.close();
System.out.println("... Done.");
} catch (IOException io) {
io.printStackTrace(System.err);
}
} else {
System.out.println("" + outfile + " already exists");
}
} }
public static void main(String pArgs[]) { public static void main(String pArgs[]) {
LabyModel model = null; LabyModel model = null;
int W = 600; int W = 600;

View File

@@ -11,25 +11,62 @@ import java.io.IOException;
/** /**
* Wall3d to create walls in 3d for stl conversion South, West North East... * Wall3d to create walls in 3d for stl conversion South, West North East...
*
* wall3D will create a rectangular cuboid with 2 triangle vertex for each face
**/ **/
public class Wall3d { public class Wall3d {
// 4 triangles in 2 dim space reused 3 times // 4 triangles in 2 dim space reused 3 times
final static int BASE[][][] = {{{0, 0}, {1, 0}, {0, 1}}, {{1, 0}, {1, 1}, {0, 1}}, // in facts order matters...
{{0, 0}, {1, 0}, {1, 1}}, {{0, 0}, {1, 1}, {0, 1}}}; final static int BASE[][][] = {
// lower left
{{0, 0}, {1, 0}, {0, 1}},
// higher right
{{1, 0}, {1, 1}, {0, 1}},
// lower right
{{0, 0}, {1, 0}, {1, 1}},
// higher left
{{0, 0}, {1, 1}, {0, 1}}};
final static short X = 1; final static short X = 1;
final static short Y = 2; final static short Y = 2;
final static short Z = 4; final static short Z = 4;
// final static short AXIS[][]= {{X,Y},{-Z,Y},{X,Y},{Z,Y},{X,-Z},{X,-Z}}; // final static short AXIS[][]= {{X,Y},{-Z,Y},{X,Y},{Z,Y},{X,-Z},{X,-Z}};
final static short AXIS[][] = {{X, Y, 0}, {Z, Y, 0}, {X, Y, 1}, {Z, Y, 1}, {X, Z, 0}, {X, Z, 1}};
public final static Wall3d South = new Wall3d(10, 1, 10, 0, 0, 0); // [face][axis]
public final static Wall3d West = new Wall3d(1, 10, 10, 0, 0, 0); final static short AXIS[][] = {
public final static Wall3d North = new Wall3d(10, 1, 10, 0, 10, 0); // face 0 (x,y) back
public final static Wall3d East = new Wall3d(1, 10, 10, 10, 0, 0); {X, Y, 0},
public final static Wall3d HighGround = new Wall3d(10, 10, 3, 0, 0, 0); // face 1 (z,y) left
public final static Wall3d LowGround = new Wall3d(10, 10, 2, 0, 0, 0); {Z, Y, 0},
// face 2 (x,y,1)
{X, Y, 1},
// face 3
{Z, Y, 1},
// face 4
{X, Z, 0},
// face 5
{X, Z, 1}};
final static String normalx = "1.0 0.0 0.0";
final static String normaly = "0.0 1.0 0.0";
final static String normalz = "0.0 0.0 1.0";
final static String normalmx = "-1.0 0.0 0.0";
final static String normalmy = "0.0 -1.0 0.0";
final static String normalmz = "0.0 0.0 -1.0";
// final static short NORMAL[] = {Z,X,Z,X,Y,Y};
final static String normalstr[] =
{ normalmz, normalx,
normalz, normalmx,
normaly, normalmy
};
boolean reverse_vertex[] = {
true,false,false,true,false,true
};
int triangle[][][] = null; int triangle[][][] = null;
@@ -46,18 +83,27 @@ public class Wall3d {
triangle = new int[12][3][3]; triangle = new int[12][3][3];
int[] factor = {xl, yl, zl}; int[] factor = {xl, yl, zl};
int[] translate = {dx, dy, dz}; int[] translate = {dx, dy, dz};
for (int i = 0; i < 12; i++) { for (int facet = 0; facet < 12; facet++) {
// point in a triangle // point in a triangle / facet
for (int p = 0; p < 3; p++) { for (int p = 0; p < 3; p++) {
short uaxis = 0; short uaxis = 0;
// a square face is two facets.
int face = facet / 2;
// first two axis ( projected on x,y,z depending on AXIS[face][] )
for (int axis = 0; axis < 2; axis++) { for (int axis = 0; axis < 2; axis++) {
short caxis = AXIS[i / 2][axis]; short caxis = AXIS[face][axis];
if (caxis > 0) { if (caxis > 0) {
f = 1; f = 1;
} else if (caxis < 0) { } else if (caxis < 0) {
// wait wait in AXIS there is no negative value ...
// so we never enter here, what was the purpose ? might be history.
f = -1; f = -1;
caxis = (short) -caxis; caxis = (short) -caxis;
} }
// what if caxis == 0 ? f is kept as its previous value ?
// which is 1 in facts due to AXIS[..][0] > 0
// uaxis keep track of used axes for this face to find last missing axis
uaxis |= caxis; uaxis |= caxis;
if (caxis == X) { if (caxis == X) {
caxis = 0; caxis = 0;
@@ -66,22 +112,20 @@ public class Wall3d {
} else { } else {
caxis = 2; caxis = 2;
} }
// if ( f == 0 ) triangle[facet][p][caxis] = translate[caxis] + BASE[facet % 4][p][axis] * f * factor[caxis];
// {
// System.out.println("ERROR");
// }
// System.out.println("i " + i + " p " + p + " a " + caxis +
// " , " + BASE[i%4][p][axis] );
triangle[i][p][caxis] = translate[caxis] + BASE[i % 4][p][axis] * f * factor[caxis];
} }
// last remaining axis
if ((uaxis & X) == 0) { if ((uaxis & X) == 0) {
// no X was used => X
uaxis = 0; uaxis = 0;
} else if ((uaxis & Y) == 0) { } else if ((uaxis & Y) == 0) {
// X was used byt not Y => Y
uaxis = 1; uaxis = 1;
} else { } else {
// default X, and Y were used => Z
uaxis = 2; uaxis = 2;
} }
triangle[i][p][uaxis] = translate[uaxis] + AXIS[i / 2][2] * factor[uaxis]; triangle[facet][p][uaxis] = translate[uaxis] + AXIS[facet / 2][2] * factor[uaxis];
} }
} }
} }
@@ -90,11 +134,11 @@ public class Wall3d {
int[] translate = {dx, dy, dz}; int[] translate = {dx, dy, dz};
int t[][][] = new int[12][3][3]; int t[][][] = new int[12][3][3];
for (int i = 0; i < 12; i++) { for (int facet = 0; facet < 12; facet ++) {
// point in a triangle // point in a triangle
for (int p = 0; p < 3; p++) { for (int p = 0; p < 3; p++) {
for (int axis = 0; axis < 3; axis++) { for (int axis = 0; axis < 3; axis++) {
t[i][p][axis] = translate[axis] + triangle[i][p][axis]; t[facet][p][axis] = translate[axis] + triangle[facet][p][axis];
} }
} }
} }
@@ -107,14 +151,23 @@ public class Wall3d {
**/ **/
public String toString() { public String toString() {
String s = ""; String s = "";
for (int t = 0; t < 12; t++) { for (int facet = 0; facet < 12; facet++) {
s += "facet normal 0 0 0\nouter loop\n"; int face = facet / 2;
// most software don't care normal but just rely on vertex order ( right hand ).
String normal = normalstr[face];
s += "facet normal " + normal + "\nouter loop\n";
boolean reverse = reverse_vertex[face];
for (int p = 0; p < 3; p++) { for (int p = 0; p < 3; p++) {
s += "vertex"; s += "vertex";
// p that accept right hand
int rhp = p;
if ( reverse )
{
// reverse 2 and 1 ; 0 => 0, 1 => 2 , 2 => 1
rhp = ( p * 5 ) % 3;
}
for (int a = 0; a < 3; a++) { for (int a = 0; a < 3; a++) {
// s+=" t "+ t + " p " + p + " a " + a + "=" + s += " " + triangle[facet][rhp][a];
// triangle[t][p][a];
s += " " + triangle[t][p][a];
} }
s += "\n"; s += "\n";
} }
@@ -123,81 +176,4 @@ public class Wall3d {
return s; return s;
} }
public static void prepare() {
System.out.println(South.toString());
System.out.println(East.toString());
System.out.println(North.toString());
System.out.println(West.toString());
}
public static void streamWallsOut(String name, LabyModel provider, OutputStream stream) throws IOException {
int width = provider.getWidth();
int height = provider.getHeight();
int xl = 10;
int yl = 10;
int zl = 10;
// WARNING DOWN - UP reversed ( in 2D Y is oriented to lower, in 3D it
// is to upper ).
stream.write(("solid " + name + "\n").getBytes());
for (int x = 0; x < width; x++) {
short walls = provider.getWalls(x, 0);
if ((walls & Brick.UP) != 0) {
stream.write(new Wall3d(South, x * xl, 0, 0).toString().getBytes());
}
if ((walls & Brick.LEFT) != 0) {
stream.write(new Wall3d(West, x * xl, 0, 0).toString().getBytes());
}
}
for (int y = 0; y < height; y++) {
short walls = provider.getWalls(0, y);
if ((walls & Brick.LEFT) != 0) {
stream.write(new Wall3d(West, 0, y * yl, 0).toString().getBytes());
}
for (int x = 0; x < width; x++) {
// south and east
walls = provider.getWalls(x, y);
if ((walls & Brick.DOWN) != 0) {
stream.write(new Wall3d(North, x * xl, y * yl, 0).toString().getBytes());
}
if ((walls & Brick.RIGHT) != 0) {
stream.write(new Wall3d(East, x * xl, y * yl, 0).toString().getBytes());
}
short path = provider.getPath(x, y);
if ((path & LabyModel.SOLVED) == LabyModel.SOLVED)
// if ( (walls & ( Brick.GOAL | Brick.ENTRY ) ) == 0 )
{
// if ( ( (x+y) % 2) == 0 )
stream.write(new Wall3d(LowGround, x * xl, y * yl, 0).toString().getBytes());
} else {
stream.write(new Wall3d(HighGround, x * xl, y * yl, 0).toString().getBytes());
}
}
}
stream.write("endsolid wall\n\n".getBytes());
}
public static void main(String args[]) {
Scanner console = new Scanner(System.in);
int xl = console.nextInt();
int yl = console.nextInt();
int zl = console.nextInt();
int dx = console.nextInt();
int dy = console.nextInt();
int dz = console.nextInt();
String s = "solid wall\n";
Wall3d instance = new Wall3d(xl, yl, zl, dx, dy, dz);
prepare();
s += "endsolid wall\n\n";
System.out.println(s);
}
} }

View File

@@ -0,0 +1,102 @@
package org.artisanlogiciel.games.stl;
import org.artisanlogiciel.games.Brick;
import org.artisanlogiciel.games.LabyModel;
import java.io.IOException;
import java.io.OutputStream;
public class Wall3dStream
{
public final static Wall3d South = new Wall3d(10, 1, 10, 0, 0, 0);
public final static Wall3d West = new Wall3d(1, 10, 10, 0, 0, 0);
public final static Wall3d North = new Wall3d(10, 1, 10, 0, 10, 0);
public final static Wall3d East = new Wall3d(1, 10, 10, 10, 0, 0);
public final static Wall3d HighGround = new Wall3d(10, 10, 3, 0, 0, 0);
public final static Wall3d LowGround = new Wall3d(10, 10, 2, 0, 0, 0);
String name;
LabyModel provider;
OutputStream stream;
boolean reverse;
/**
* reverse : means that resolved path will be HighGround actual making maze more difficult to play with
*
* @param name
* @param provider
* @param stream
* @param reverse
*/
public Wall3dStream(String name, LabyModel provider, OutputStream stream, boolean reverse)
{
this.name = name;
this.provider = provider;
this.stream = stream;
this.reverse = reverse;
}
public static void prepare() {
System.out.println(South.toString());
System.out.println(East.toString());
System.out.println(North.toString());
System.out.println(West.toString());
}
private void writeWall3D(Wall3d wall3d)
throws IOException
{
stream.write(wall3d.toString().getBytes());
}
public void stream(int xl, int yl, int zl) throws IOException {
int width = provider.getWidth();
int height = provider.getHeight();
// WARNING DOWN - UP reversed ( in 2D Y is oriented to lower, in 3D it
// is to upper ).
stream.write(("solid " + name + "\n").getBytes());
for (int x = 0; x < width; x++) {
short walls = provider.getWalls(x, 0);
if ((walls & Brick.UP) != 0) {
writeWall3D(new Wall3d(South, x * xl, 0, 0));
}
if ((walls & Brick.LEFT) != 0) {
writeWall3D(new Wall3d(West, x * xl, 0, 0));
}
}
for (int y = 0; y < height; y++) {
short walls = provider.getWalls(0, y);
if ((walls & Brick.LEFT) != 0) {
writeWall3D(new Wall3d(West, 0, y * yl, 0));
}
for (int x = 0; x < width; x++) {
// south and east
walls = provider.getWalls(x, y);
if ((walls & Brick.DOWN) != 0) {
writeWall3D(new Wall3d(North, x * xl, y * yl, 0));
}
if ((walls & Brick.RIGHT) != 0) {
writeWall3D(new Wall3d(East, x * xl, y * yl, 0));
}
short path = provider.getPath(x, y);
// where resolved path is leaked to stl model.
Wall3d ground = reverse ? LowGround : HighGround;
if ((path & LabyModel.SOLVED) == LabyModel.SOLVED)
// if ( (walls & ( Brick.GOAL | Brick.ENTRY ) ) == 0 )
{
// if ( ( (x+y) % 2) == 0 )
ground = reverse ? HighGround : LowGround;
}
writeWall3D(new Wall3d(ground, x * xl, y * yl, 0));
}
}
stream.write("endsolid wall\n\n".getBytes());
stream.flush();
}
}