STL ファイルは立体の外側から見て、時計と反対回りに3点を指定した三角形 の集まりとして表現される。 そのため、 立方体の6個の面の座標を時計と反対まわりに列挙する。 一片1の立方体の一つの頂点を原点に置き、各面に(サイコロと同じように) 番号を振る。
そして、各面毎に外側から見て時計と反対回りに頂点が並ぶように頂点を列 挙する
プログラムは多角形 Polygon を自動的に三角形分割するようにし、 Polygon のかたまりをSTLファイルとして出力できるようにする。
STL ファイルを出力するためのクラスライブラリの例を示します。 使用方法は test/CubeTest.java ファイルなどを読んでみてください。
なおすべてのファイルをまとめたもの はthreedprinter.zipとしてダウンロー ドできます。
package stl;
public class Vector implements Cloneable {
double x,y,z;
public Vector(double _x, double _y, double _z){
x=_x;
y=_y;
z=_z;
}
public Vector(float _x, float _y, float _z){
x=_x;
y=_y;
z=_z;
}
public Vector(Vector point) {
x=point.x;
y=point.y;
z=point.z;
}
public Vector() {
x=0;
y=0;
z=0;
}
@Override
public String toString() {
return x + " " + y + " " + z;
}
public void grow(float size){
x *= size;
y *= size;
z *= size;
}
@Override
public Vector clone(){
Vector result = null;
try {
result = (Vector) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return result;
}
public Vector minus(Vector point) {
Vector result = new Vector(this);
result.x-=point.x;
result.y-=point.y;
result.z-=point.z;
return result;
}
public Vector crossProduct(Vector v2) {
Vector result = new Vector();
result.x = y*v2.z-z*v2.y;
result.y = z*v2.x - x*v2.z;
result.z = x*v2.y - y*v2.x;
return result;
}
public Vector plus(Vector point) {
Vector result = new Vector(this);
result.x+=point.x;
result.y+=point.y;
result.z+=point.z;
return result;
}
// public Vector rotate(Vector normal, float d) {
// return rotate(normal, d);
// }
public Vector rotate(Vector center, Vector normal, double d) {
Vector result = minus(center);
/*result.x = y*v2.z-z*v2.y;
result.y = z*v2.x - x*v2.z;
result.z = x*v2.y - y*v2.x;
*/
//未実装
return result;
}
}
package stl;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Arrays;
public class Triangle {
Vector[] points;
public Triangle(Vector p0, Vector p1, Vector p2){
points = new Vector[3];
points[0]=p0;
points[1]=p1;
points[2]=p2;
}
// anti clockwise
@Override
public String toString() {
StringWriter sw = new StringWriter();
PrintWriter pr = new PrintWriter(sw);
pr.println("facet normal "+ normalVector());
pr.println("outer loop");
for(Vector point : points ){
pr.println("vertex " + point);
}
pr.println("endloop");
pr.println("endfacet");
pr.close();
return sw.toString();
}
private Vector normalVector() {
Vector v1 = points[1].minus(points[0]);
Vector v2 = points[2].minus(points[0]);
return v1.crossProduct(v2);
}
public void grow(float size){
Arrays.stream(points).forEach(x->x.grow(size));
}
}
package stl;
import java.util.Arrays;
public class Polygon implements Cloneable {
private Vector[] points;
private Triangle[] triangles;
public Polygon(Vector[] _points){
points = _points;
}
public Polygon(Vector center, Vector initial, int n){
// points = new Vector[n];
// Vector r = initial.minus(center);
// for(int i=0; i<n; i++){
// points[i]=center.plus(r);
// r = r.rotate(center, 2*Math.PI/n);
// }
}
@Override
public Polygon clone(){
Polygon result = null;
try {
result = (Polygon) super.clone();
result.points = points.clone();
result.triangles = triangles.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return result;
}
public void grow(float size){
Arrays.stream(points).forEach(x->x.grow(size));
if(triangles != null)
Arrays.stream(triangles).forEach(x->x.grow(size));
}
public void divideIntoTriangle(){
triangles = new Triangle[points.length-2];
for(int i=0; i<points.length-2; i++){
triangles[i]= new Triangle(points[0],points[i+1],points[i+2]);
}
}
@Override
public String toString(){
if(triangles==null) divideIntoTriangle();
StringBuilder sb = new StringBuilder();
for(Triangle t : triangles){
sb.append(t.toString());
}
return sb.toString();
}
}
package stl;
import java.util.Arrays;
import java.util.stream.Collectors;
public class Matrix {
private final static int size = 3;
private double[][] array;
public Matrix(){
array = new double[size][size];
}
public Matrix(float[][] a) {
array = new double[size][size];
for(int i=0; i<size; i++){
for(int j=0; j<size; j++){
array[i][j] = a[i][j];
}
}
}
public Matrix(double[][] a) {
array = a.clone();
}
public float det(){
float det = 0;
float rightline ;
float leftline ;
for(int i=0; i<size; i++){
rightline = 1;
leftline = 1;
for(int j=0; j<size; j++){
rightline *= array[(i+j)%size][j];
leftline *= array[(i+size-j)%size][j];
}
det = det + rightline - leftline;
}
return det;
}
public Matrix inv() {
float det = det();
if (det == 0) return null;
Matrix InvA = new Matrix();
for(int i = 0; i < size; i++) {
int i1=(i+1)%size;
int i2=(i+2)%size;
for(int j = 0; j < size; j++) {
int j1=(j+1)%size;
int j2=(j+2)%size;
InvA.set(i,j,(array[i1][j1]*array[i2][j2]
-array[i1][j2]*array[i2][j1])
/det);
}
}
return InvA;
}
public void set(int j, int i, double f) {
array[i][j]=f;
}
@Override
public String toString() {
return Arrays.stream(array).map(
x->Arrays.stream(x)
.mapToObj(y->String.valueOf(y==-0.0?0.0:y))
.collect(Collectors.joining(",","[","]")))
.collect(Collectors.joining(",","[","]"))
;
}
}
package stl;
public class Square {
public static Polygon toPolygon(float x0, float y0, float z0,
float x1, float y1, float z1,
float x2, float y2, float z2){
Vector[] point = new Vector[4];
point[0]=new Vector(x0,y0,z0);
point[1]=new Vector(x1,y1,z1);
point[2]=new Vector(x2,y2,z2);
point[3]=new Vector(x0-x1+x2,y0-y1+y2,z0-z1+z2);
return new Polygon(point);
}
}
package stl;
import java.util.Arrays;
import java.util.stream.Collectors;
public class Cube {
private Polygon[] p;
public Cube(float size){
p=new Polygon[6];
p[0]=Square.toPolygon(0, 0, 0, 0, 1, 0, 1, 1, 0);
p[1]=Square.toPolygon(0, 0, 0, 0, 0, 1, 0, 1, 1);
p[2]=Square.toPolygon(0, 0, 0, 1, 0, 0, 1, 0, 1);
p[3]=Square.toPolygon(1, 1, 1, 1, 1, 0, 0, 1, 0);
p[4]=Square.toPolygon(1, 1, 1, 1, 0, 1, 1, 0, 0);
p[5]=Square.toPolygon(1, 1, 1, 0, 1, 1, 0, 0, 1);
Arrays.stream(p).forEach(x->x.grow(size));
}
@Override
public String toString(){
String separator = System.getProperty("line.separator");
return "solid cube"
+separator
+Arrays.stream(p)
.map(x->x.toString())
.collect(Collectors.joining())
+"endsolid cube"
+separator;
}
}
package test;
import static org.junit.Assert.*;
import org.junit.Before;
import org.junit.Test;
import stl.Vector;
public class VectorTest {
private Vector p1;
private Vector p2;
private Vector p0;
@Before
public void setUp() throws Exception {
p0 = new Vector(1,0,0);
p1 = new Vector(0,0,1);
p2 = new Vector(0,1,0);
new Vector(0,0,0);
}
@Test
public void testClone(){
Vector p3 = p0.clone();
assertEquals("1.0 0.0 0.0",p3.toString());
}
@Test
public void testToString() {
assertEquals("0.0 0.0 1.0",p1.toString());
}
@Test
public void testMinus() {
Vector p3 = p1.minus(p2);
assertEquals("0.0 -1.0 1.0",p3.toString());
}
@Test
public void testPlus() {
Vector p3 = p1.plus(p2);
assertEquals("0.0 1.0 1.0",p3.toString());
}
@Test
public void testCrossProduct() {
Vector p4 = p1.crossProduct(p2);
assertEquals("-1.0 0.0 0.0",p4.toString());
}
@Test
public void testRotate(){
// Vector p3 = p0.rotate(p1, Math.PI/2.0);
// assertEquals("0.0 1.0 0.0",p3.toString());
}
}
package test;
import static org.junit.Assert.*;
import org.junit.Before;
import org.junit.Test;
import stl.Vector;
import stl.Triangle;
public class TriangleTest {
private Triangle t0;
@Before
public void setUp() throws Exception {
Vector p0 = new Vector(0,0,0);
Vector p1 = new Vector(1,0,0);
Vector p2 = new Vector(0,1,0);
t0 = new Triangle(p0,p1,p2);
}
@Test
public void testToString() {
String separator = System.getProperty("line.separator");
String expect = "facet normal 0.0 0.0 1.0"+separator
+"outer loop"+separator
+"vertex 0.0 0.0 0.0"+separator
+"vertex 1.0 0.0 0.0"+separator
+"vertex 0.0 1.0 0.0"+separator
+"endloop"+separator
+"endfacet"+separator;
assertEquals(expect,t0.toString());
}
}
package test;
import static org.junit.Assert.*;
import org.junit.Before;
import org.junit.Test;
import stl.Vector;
import stl.Polygon;
public class PolygonTest {
private Polygon p0;
private final String NL= System.getProperty("line.separator");
@Before
public void setUp() throws Exception {
Vector[] p = {new Vector(0,0,0),
new Vector(1,0,0),
new Vector(1,1,0),
new Vector(0,1,0)};
p0 = new Polygon(p);
}
@Test
public void testToString() {
String expect = "facet 0.0 0.0 1.0"+NL
+"outer loop"+NL
+"vertex 0.0 0.0 0.0"+NL
+"vertex 1.0 0.0 0.0"+NL
+"vertex 1.0 1.0 0.0"+NL
+"endloop"+NL
+"endfacet"+NL
+"facet 0.0 0.0 1.0"+NL
+"outer loop"+NL
+"vertex 0.0 0.0 0.0"+NL
+"vertex 1.0 1.0 0.0"+NL
+"vertex 0.0 1.0 0.0"+NL
+"endloop"+NL
+"endfacet"+NL;
assertEquals(expect,p0.toString());
}
}
package test;
import static org.junit.Assert.*;
import stl.Matrix;
import org.junit.Before;
import org.junit.Test;
public class MatrixTest {
private static final float e=(float) 1E-5;
private Matrix a;
private Matrix i;
private Matrix rz;
private Matrix irz;
private Matrix rx;
private Matrix irx;
private Matrix ry;
private Matrix iry;
@Before
public void setUp() throws Exception {
a = new Matrix(new float[][]{{1,2,3},{4,5,6},{7,8,9}});
i = new Matrix(new float[][]{{1,0,0},{0,1,0},{0,0,1}});
double c = Math.cos(Math.PI/2);
double s = Math.sin(Math.PI/2);
rz= new Matrix(new double[][]{{c*2,-s*2,0},{s*2,c*2,0},{0,0,2}});
irz= new Matrix(new double[][]{{c/2,s/2,0},{-s/2,c/2,0},{0,0,0.5}});
rx= new Matrix(new double[][]{{2,0,0},{0,c*2,-s*2},{0,s*2,c*2}});
irx= new Matrix(new double[][]{{0.5,0,0},{0,c/2,s/2},{0,-s/2,c/2}});
ry= new Matrix(new double[][]{{c*2,0,-s*2,0},{0,2,0},{s*2,0,c*2}});
iry= new Matrix(new double[][]{{c/2,0,s/2},{0,0.5,0},{-s/2,0,c/2}});
}
@Test
public void testDet() {
assertEquals(1,i.det(),e);
assertEquals(8,rx.det(),e);
assertEquals(0.125,irx.det(),e);
assertEquals(8,ry.det(),e);
assertEquals(0.125,iry.det(),e);
assertEquals(8,rz.det(),e);
assertEquals(0.125,irz.det(),e);
assertEquals(0,a.det(),e);
}
@Test
public void testInv() {
assertEquals(i.toString(),i.inv().toString());
assertEquals(irx.toString(),rx.inv().toString());
assertEquals(iry.toString(),ry.inv().toString());
assertEquals(irz.toString(),rz.inv().toString());
assertEquals(null,a.inv());
}
}
package test;
import static org.junit.Assert.*;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.util.stream.Collectors;
import org.junit.Before;
import org.junit.Test;
import stl.Cube;
public class CubeTest {
private Cube cube;
@Before
public void setUp() throws Exception {
cube = new Cube(2);
}
@Test
public void test() throws FileNotFoundException {
String separator = System.getProperty("line.separator");
FileReader f = new FileReader("expectcube.txt");
BufferedReader br = new BufferedReader(f);
String expect = br.lines().collect(Collectors.joining(separator))
+separator;
assertEquals(expect,cube.toString());
}
}
expectcube.txt... test.CubeTest で使用 するファイル
以下は、実装はしていませんが、有益なアルゴリズムなので紹介します。
点 を 中心点 を中心に軸ベクトル の回りに だけ回転させたい
軸ベクトルと z 軸で作る平面の法線ベクトルは軸ベクトルと との外積で 求まる。
これは x,y 平面に乗る これをz 軸回りに回転させてy 軸に一致させると、 ベクトルは x-z 平面上に乗る。
この , による行列を とし、 と置く。 は x-z 平面上に乗っているので、 y 座標は 0 であることに注意。
は x-z 上のベクトルなので、 y 軸の回りに回転させて z 軸に一致させる
以上より、次が求まる
変換後の座標を としたとき、 変換は次で得られる。
と置くと次のように書き直せる。
ここで、次元を1つ上げたアフィン変換を次のように定義する。