/* jCAE stand for Java Computer Aided Engineering. Features are : Small CAD
modeler, Finite element mesher, Plugin architecture.
Copyright (C) 2007, by EADS France
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package org.jcae.mesh.amibe.algos3d;
import org.jcae.mesh.amibe.ds.Mesh;
import org.jcae.mesh.amibe.ds.Triangle;
import org.jcae.mesh.amibe.ds.Vertex;
import org.jcae.mesh.amibe.ds.AbstractHalfEdge;
import java.util.Map;
import java.util.HashMap;
import static org.junit.Assert.*;
import org.junit.Test;
public class QEMDecimateHalfEdgeTest
{
private Mesh mesh;
private Vertex [] v;
private Triangle [] T;
private int vertexLabel = 0;
// m Vertex on rows, n Vertex on columns
private void createMxNShell(int m, int n)
{
/* v3 v4 v5
* +---------+---------+
* | \ | \ |
* | \ T1 | \ T3 |
* | \ | \ |
* | T0 \ | T2 \ |
* +---------+---------+
* v0 v1 v2
*/
v = new Vertex[m*n];
for (int j = 0; j < n; j++)
for (int i = 0; i < m; i++)
v[m*j+i] = mesh.createVertex(i, j, 0.0);
for (int i = 0; i < v.length; i++)
v[i].setLabel(i);
vertexLabel = v.length;
T = createMxNTriangles(m, n, v);
}
private Triangle [] createMxNTriangles(int m, int n, Vertex [] vv)
{
Triangle [] tt = new Triangle[2*(m-1)*(n-1)];
for (int j = 0; j < n-1; j++)
{
for (int i = 0; i < m-1; i++)
{
tt[2*(m-1)*j+2*i] = mesh.createTriangle(vv[m*j+i], vv[m*j+i+1], vv[m*(j+1)+i]);
tt[2*(m-1)*j+2*i+1] = mesh.createTriangle(vv[m*j+i+1], vv[m*(j+1)+i+1], vv[m*(j+1)+i]);
vv[m*j+i].setLink(tt[2*(m-1)*j+2*i]);
}
vv[m*j+m-1].setLink(tt[2*(m-1)*j+2*m-3]);
}
// Last row
for (int i = 0; i < m-1; i++)
vv[m*(n-1)+i].setLink(tt[2*(m-1)*(n-2)+2*i]);
vv[m*n-1].setLink(tt[2*(m-1)*(n-1)-1]);
int cnt = mesh.getTriangles().size();
for (Triangle t: tt)
{
mesh.add(t);
t.setGroupId(cnt);
cnt++;
}
return tt;
}
private void rotateMxNShellAroundY(int m, int n, double angle)
{
// Create new vertices and append them to current mesh
assert v.length == m*n;
double [] xyz = new double[3];
Vertex [] vy = new Vertex[v.length];
vertexLabel = v.length;
double ct = Math.cos(angle*Math.PI / 180.0);
double st = Math.sin(angle*Math.PI / 180.0);
for (int i = 0; i < v.length; i++)
{
if (i%m == 0)
vy[i] = v[i];
else
{
v[i].get(xyz);
vy[i] = mesh.createVertex(ct*xyz[0]+st*xyz[2], xyz[1], -st*xyz[0]+ct*xyz[2]);
vy[i].setLabel(vertexLabel);
vertexLabel++;
}
}
createMxNTriangles(m, n, vy);
}
private void testShell(int m, int n)
{
final Map<String, String> options = new HashMap<String, String>();
options.put("size", "0.1");
mesh = new Mesh();
createMxNShell(m, n);
mesh.buildAdjacency();
assertTrue("Mesh is not valid", mesh.isValid());
int expected = 2;
new QEMDecimateHalfEdge(mesh, options).compute();
assertTrue("Mesh is not valid", mesh.isValid());
int res = AbstractAlgoHalfEdge.countInnerTriangles(mesh);
assertTrue("Final number of triangles: "+res, res == expected);
assertTrue("Mesh is not valid", mesh.isValid());
}
private void testCross(int m, int n)
{
final Map<String, String> options = new HashMap<String, String>();
options.put("size", "0.1");
mesh = new Mesh();
createMxNShell(m, n);
rotateMxNShellAroundY(m, n, 90);
rotateMxNShellAroundY(m, n, 180);
rotateMxNShellAroundY(m, n, 270);
mesh.buildAdjacency();
assertTrue("Mesh is not valid", mesh.isValid());
int expected = 8;
new QEMDecimateHalfEdge(mesh, options).compute();
assertTrue("Mesh is not valid", mesh.isValid());
int res = AbstractAlgoHalfEdge.countInnerTriangles(mesh);
assertTrue("Final number of triangles: "+res, res == expected);
}
@Test public void testNonManifold()
{
final Map<String, String> options = new HashMap<String, String>();
options.put("size", "0.1");
mesh = new Mesh();
// v3 v4 v5
// +---------+---------+
// | \ | / |
// | \ T2 | T5 / |
// | \ | / |
// | \ | / |
// | T1 + v6 T4 |
// | / | \ |
// | / | \ |
// | / | \ |
// | / T0 | T3 \ |
// +---------+---------+
// v0 v1 v2
v = new Vertex[11];
v[0] = mesh.createVertex(-1.0, 0.0, 0.0);
v[1] = mesh.createVertex(0.0, 0.0, 0.0);
v[2] = mesh.createVertex(1.0, 0.0, 0.0);
v[3] = mesh.createVertex(-1.0, 2.0, 0.0);
v[4] = mesh.createVertex(0.0, 2.0, 0.0);
v[5] = mesh.createVertex(1.0, 2.0, 0.0);
v[6] = mesh.createVertex(0.0, 1.0, 0.0);
v[7] = mesh.createVertex(0.0, 0.0, 1.0);
v[8] = mesh.createVertex(0.0, 2.0, 1.0);
v[9] = mesh.createVertex(0.0, 0.0, -1.0);
v[10] = mesh.createVertex(0.0, 2.0, -1.0);
for (int i = 0; i < v.length; i++)
v[i].setLabel(i);
T = new Triangle[12];
T[0] = mesh.createTriangle(v[0], v[1], v[6]);
T[1] = mesh.createTriangle(v[6], v[3], v[0]);
T[2] = mesh.createTriangle(v[3], v[6], v[4]);
T[3] = mesh.createTriangle(v[2], v[1], v[6]);
T[4] = mesh.createTriangle(v[5], v[2], v[6]);
T[5] = mesh.createTriangle(v[5], v[6], v[4]);
T[6] = mesh.createTriangle(v[7], v[1], v[6]);
T[7] = mesh.createTriangle(v[6], v[4], v[8]);
T[8] = mesh.createTriangle(v[7], v[6], v[8]);
T[9] = mesh.createTriangle(v[9], v[1], v[6]);
T[10] = mesh.createTriangle(v[10], v[9], v[6]);
T[11] = mesh.createTriangle(v[10], v[6], v[4]);
v[0].setLink(T[0]);
v[1].setLink(T[0]);
v[2].setLink(T[3]);
v[3].setLink(T[1]);
v[4].setLink(T[5]);
v[5].setLink(T[4]);
v[6].setLink(T[4]);
v[7].setLink(T[8]);
v[8].setLink(T[8]);
v[9].setLink(T[10]);
v[10].setLink(T[10]);
int cnt = 0;
for (Triangle t: T)
{
if (t == null)
continue;
mesh.add(t);
t.setGroupId(cnt);
cnt++;
}
mesh.buildAdjacency();
assertTrue("Mesh is not valid", mesh.isValid());
new QEMDecimateHalfEdge(mesh, options).compute();
int res = AbstractAlgoHalfEdge.countInnerTriangles(mesh);
int expected = 8;
assertTrue("Final number of triangles: "+res, res == expected);
assertTrue("Mesh is not valid", mesh.isValid());
}
@Test public void testSquare()
{
final Map<String, String> options = new HashMap<String, String>();
options.put("size", "0.1");
mesh = new Mesh();
// v2 v3
// +---------+
// | \ |
// | \ T1 |
// | \ |
// | T0 \ |
// +---------+
// v0 v1
v = new Vertex[4];
v[0] = mesh.createVertex(0.0, 0.0, 0.0);
v[1] = mesh.createVertex(1.0, 0.0, 0.0);
v[2] = mesh.createVertex(0.0, 1.0, 0.0);
v[3] = mesh.createVertex(1.0, 1.0, 0.0);
for (int i = 0; i < v.length; i++)
v[i].setLabel(i);
T = new Triangle[2];
T[0] = mesh.createTriangle(v[0], v[1], v[2]);
T[1] = mesh.createTriangle(v[3], v[2], v[1]);
v[0].setLink(T[0]);
v[1].setLink(T[0]);
v[2].setLink(T[0]);
v[3].setLink(T[1]);
int cnt = 0;
for (Triangle t: T)
{
mesh.add(t);
t.setGroupId(cnt);
cnt++;
}
mesh.buildAdjacency();
assertTrue("Mesh is not valid", mesh.isValid());
new QEMDecimateHalfEdge(mesh, options).compute();
int res = AbstractAlgoHalfEdge.countInnerTriangles(mesh);
int expected = 2;
assertTrue("Final number of triangles: "+res, res == expected);
assertTrue("Mesh is not valid", mesh.isValid());
}
@Test public void testShell3()
{
testShell(3, 3);
}
@Test public void testShellLarge()
{
testShell(30, 30);
}
@Test public void testShellNM1()
{
testCross(3, 2);
}
@Test public void testShellNM2()
{
testCross(3, 3);
}
@Test public void testShellNM3()
{
testCross(3, 4);
}
@Test public void testShellNMLarge()
{
testCross(10, 10);
}
@Test public void testShellNM3Inverted()
{
final Map<String, String> options = new HashMap<String, String>();
options.put("size", "0.1");
mesh = new Mesh();
int m = 3;
int n = 4;
createMxNShell(m, n);
rotateMxNShellAroundY(m, n, 90);
rotateMxNShellAroundY(m, n, 180);
rotateMxNShellAroundY(m, n, 270);
// Invert triangles at the right
for (int j = 0; j < n-1; j++)
{
for (int i = 0; i < m-1; i++)
{
Vertex temp = T[2*(m-1)*j+2*i].getV1();
T[2*(m-1)*j+2*i].setV(1, T[2*(m-1)*j+2*i].getV2());
T[2*(m-1)*j+2*i].setV(2, temp);
temp = T[2*(m-1)*j+2*i+1].getV1();
T[2*(m-1)*j+2*i+1].setV(1, T[2*(m-1)*j+2*i+1].getV2());
T[2*(m-1)*j+2*i+1].setV(2, temp);
}
}
mesh.buildAdjacency();
assertTrue("Mesh is not valid", mesh.isValid());
int expected = 8;
new QEMDecimateHalfEdge(mesh, options).compute();
assertTrue("Mesh is not valid", mesh.isValid());
int res = AbstractAlgoHalfEdge.countInnerTriangles(mesh);
assertTrue("Mesh is not valid", mesh.isValid());
assertTrue("Final number of triangles: "+res, res == expected);
}
@Test public void testShell3WithGroups()
{
final Map<String, String> options = new HashMap<String, String>();
options.put("size", "0.1");
mesh = new Mesh();
createMxNShell(3, 3);
mesh.buildAdjacency();
int cnt = 0;
for (Triangle t : T)
{
t.setGroupId(cnt / 4);
cnt++;
}
mesh.tagGroupBoundaries(AbstractHalfEdge.IMMUTABLE);
assertTrue("Mesh is not valid", mesh.isValid());
int expected = 6;
new QEMDecimateHalfEdge(mesh, options).compute();
assertTrue("Mesh is not valid", mesh.isValid());
int res = AbstractAlgoHalfEdge.countInnerTriangles(mesh);
assertTrue("Final number of triangles: "+res, res == expected);
assertTrue("Mesh is not valid", mesh.isValid());
}
@Test public void testShell7WithGroupBoundaries()
{
final Map<String, String> options = new HashMap<String, String>();
options.put("size", "0.5");
mesh = new Mesh();
createMxNShell(7, 7);
mesh.buildAdjacency();
for (Triangle t : T)
t.setGroupId(0);
for (int i = 28; i < 32; i++)
T[i].setGroupId(1);
for (int i = 40; i < 44; i++)
T[i].setGroupId(1);
mesh.buildGroupBoundaries();
assertTrue("Mesh is not valid", mesh.isValid());
int expected = 10;
new QEMDecimateHalfEdge(mesh, options).compute();
assertTrue("Mesh is not valid", mesh.isValid());
int res = AbstractAlgoHalfEdge.countInnerTriangles(mesh);
assertTrue("Final number of triangles: "+res, res == expected);
assertTrue("Mesh is not valid", mesh.isValid());
}
@Test public void testShellWithImmutableInteriorNodes()
{
final Map<String, String> options = new HashMap<String, String>();
options.put("size", "0.1");
mesh = new Mesh();
int m = 10;
createMxNShell(m, m);
rotateMxNShellAroundY(m, m, 90);
for (Triangle t: mesh.getTriangles())
t.setGroupId(2);
for (Triangle t: T)
t.setGroupId(1);
mesh.buildAdjacency();
mesh.buildGroupBoundaries();
// Make interior vertices non mutable
int cnt = 0;
for(Triangle t: mesh.getTriangles())
{
if (t.hasAttributes(AbstractHalfEdge.OUTER))
continue;
for(int i = 0; i < 3; i++)
{
Vertex vertex = t.getV(i);
if(vertex.getRef() == 0)
{
vertex.setMutable(false);
cnt++;
}
}
}
assertTrue("Mesh is not valid", mesh.isValid());
new QEMDecimateHalfEdge(mesh, options).compute();
assertTrue("Mesh is not valid", mesh.isValid());
int expected = 2 * (2*(m-3)*(m-3) + 4*(m-2));
int res = AbstractAlgoHalfEdge.countInnerTriangles(mesh);
assertTrue("Final number of triangles: "+res, res == expected);
// try { org.jcae.mesh.xmldata.MeshWriter.writeObject3D(mesh, "XXX-0", null); } catch (java.io.IOException ex) { ex.printStackTrace(); throw new RuntimeException(ex); }
}
@Test public void testShellWithNonManifoldCorner()
{
final Map<String, String> options = new HashMap<String, String>();
options.put("maxtriangles", "1");
mesh = new Mesh();
createMxNShell(5, 5);
Vertex v0 = T[0].getV0();
T[0].setV(0, T[0].getV1());
T[0].setV(1, v0);
mesh.buildAdjacency();
assertTrue("Mesh is not valid", mesh.isValid());
int expected = 2;
// try { org.jcae.mesh.xmldata.MeshWriter.writeObject3D(mesh, "XXX-0", null); } catch (java.io.IOException ex) { ex.printStackTrace(); throw new RuntimeException(ex); }
new QEMDecimateHalfEdge(mesh, options).compute();
assertTrue("Mesh is not valid", mesh.isValid());
int res = AbstractAlgoHalfEdge.countInnerTriangles(mesh);
// try { org.jcae.mesh.xmldata.MeshWriter.writeObject3D(mesh, "XXX-1", null); } catch (java.io.IOException ex) { ex.printStackTrace(); throw new RuntimeException(ex); }
assertTrue("Final number of triangles: "+res, res == expected);
assertTrue("Mesh is not valid", mesh.isValid());
}
}