/*
* __ .__ .__ ._____.
* _/ |_ _______ __|__| ____ | | |__\_ |__ ______
* \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/
* | | ( <_> > <| \ \___| |_| || \_\ \\___ \
* |__| \____/__/\_ \__|\___ >____/__||___ /____ >
* \/ \/ \/ \/
*
* Copyright (c) 2006-2011 Karsten Schmidt
*
* 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.
*
* http://creativecommons.org/licenses/LGPL/2.1/
*
* 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
*/
package toxi.geom.mesh;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.logging.Level;
import toxi.geom.AABB;
import toxi.geom.Line3D;
import toxi.geom.Matrix4x4;
import toxi.geom.Quaternion;
import toxi.geom.ReadonlyVec3D;
import toxi.geom.Vec2D;
import toxi.geom.Vec3D;
import toxi.geom.mesh.subdiv.MidpointSubdivision;
import toxi.geom.mesh.subdiv.SubdivisionStrategy;
/**
* A class to dynamically build, manipulate & export triangle meshes. Meshes are
* build face by face. The class automatically re-uses existing vertices and can
* create smooth vertex normals. Vertices and faces are directly accessible for
* speed & convenience.
*/
public class WETriangleMesh extends TriangleMesh {
/**
* WEVertex buffer & lookup index when adding new faces
*/
public LinkedHashMap<Line3D, WingedEdge> edges;
private final Line3D edgeCheck = new Line3D(new Vec3D(), new Vec3D());
private int uniqueEdgeID;
public WETriangleMesh() {
this("untitled");
}
/**
* Creates a new mesh instance with initial default buffer sizes.
*
* @param name
* mesh name
*/
public WETriangleMesh(String name) {
this(name, DEFAULT_NUM_VERTICES, DEFAULT_NUM_FACES);
}
/**
* Creates a new mesh instance with the given initial buffer sizes. These
* numbers are no limits and the mesh can be smaller or grow later on.
* They're only used to initialise the underlying collections.
*
* @param name
* mesh name
* @param numV
* initial vertex buffer size
* @param numF
* initial face list size
*/
public WETriangleMesh(String name, int numV, int numF) {
super(name, numV, numF);
}
public WETriangleMesh addFace(Vec3D a, Vec3D b, Vec3D c) {
return addFace(a, b, c, null, null, null, null);
}
public WETriangleMesh addFace(Vec3D a, Vec3D b, Vec3D c, Vec2D uvA,
Vec2D uvB, Vec2D uvC) {
return addFace(a, b, c, null, uvA, uvB, uvC);
}
public WETriangleMesh addFace(Vec3D a, Vec3D b, Vec3D c, Vec3D n) {
return addFace(a, b, c, n, null, null, null);
}
public WETriangleMesh addFace(Vec3D a, Vec3D b, Vec3D c, Vec3D n,
Vec2D uvA, Vec2D uvB, Vec2D uvC) {
WEVertex va = checkVertex(a);
WEVertex vb = checkVertex(b);
WEVertex vc = checkVertex(c);
if (va.id == vb.id || va.id == vc.id || vb.id == vc.id) {
if (logger.isLoggable(Level.FINE)) {
logger.fine("ignorning invalid face: " + a + "," + b + "," + c);
}
} else {
if (n != null) {
Vec3D nc = va.sub(vc).crossSelf(va.sub(vb));
if (n.dot(nc) < 0) {
WEVertex t = va;
va = vb;
vb = t;
}
}
WEFace f = new WEFace(va, vb, vc, uvA, uvB, uvC);
faces.add(f);
numFaces++;
updateEdge(va, vb, f);
updateEdge(vb, vc, f);
updateEdge(vc, va, f);
}
return this;
}
/**
* Adds all faces from the given mesh to this one.
*
* @param m
* source mesh instance
*/
public WETriangleMesh addMesh(Mesh3D m) {
super.addMesh(m);
return this;
}
@Override
public AABB center(ReadonlyVec3D origin) {
super.center(origin);
rebuildIndex();
return bounds;
}
private final WEVertex checkVertex(Vec3D v) {
WEVertex vertex = (WEVertex) vertices.get(v);
if (vertex == null) {
vertex = createVertex(v, uniqueVertexID++);
vertices.put(vertex, vertex);
numVertices++;
}
return vertex;
}
/**
* Clears all counters, and vertex & face buffers.
*/
public WETriangleMesh clear() {
super.clear();
edges.clear();
return this;
}
/**
* Creates a deep clone of the mesh. The new mesh name will have "-copy" as
* suffix.
*
* @return new mesh instance
*/
public WETriangleMesh copy() {
WETriangleMesh m = new WETriangleMesh(name + "-copy", numVertices,
numFaces);
for (Face f : faces) {
m.addFace(f.a, f.b, f.c, f.normal, f.uvA, f.uvB, f.uvC);
}
return m;
}
protected WEVertex createVertex(Vec3D v, int id) {
return new WEVertex(v, id);
}
/**
* Flips the vertex ordering between clockwise and anti-clockwise. WEFace
* normals are updated automatically too.
*
* @return itself
*/
public WETriangleMesh flipVertexOrder() {
super.flipVertexOrder();
return this;
}
public WETriangleMesh flipYAxis() {
super.flipYAxis();
return this;
}
public WEVertex getClosestVertexToPoint(ReadonlyVec3D p) {
return (WEVertex) super.getClosestVertexToPoint(p);
}
public Collection<WingedEdge> getEdges() {
return edges.values();
}
public int getNumEdges() {
return edges.size();
}
public WETriangleMesh getRotatedAroundAxis(Vec3D axis, float theta) {
return copy().rotateAroundAxis(axis, theta);
}
public WETriangleMesh getRotatedX(float theta) {
return copy().rotateX(theta);
}
public WETriangleMesh getRotatedY(float theta) {
return copy().rotateY(theta);
}
public WETriangleMesh getRotatedZ(float theta) {
return copy().rotateZ(theta);
}
public WETriangleMesh getScaled(float scale) {
return copy().scale(scale);
}
public WETriangleMesh getScaled(Vec3D scale) {
return copy().scale(scale);
}
public WETriangleMesh getTranslated(Vec3D trans) {
return copy().translate(trans);
}
public WEVertex getVertexAtPoint(Vec3D v) {
return (WEVertex) vertices.get(v);
}
public WEVertex getVertexForID(int id) {
return (WEVertex) super.getVertexForID(id);
}
public WETriangleMesh init(String name, int numV, int numF) {
super.init(name, numV, numF);
edges = new LinkedHashMap<Line3D, WingedEdge>(numV, 1.5f, false);
return this;
}
/**
* Rotates the mesh in such a way so that its "forward" axis is aligned with
* the given direction. This version uses the positive Z-axis as default
* forward direction.
*
* @param dir
* new target direction to point in
* @return itself
*/
public WETriangleMesh pointTowards(ReadonlyVec3D dir) {
return transform(Quaternion.getAlignmentQuat(dir, Vec3D.Z_AXIS)
.toMatrix4x4(matrix), true);
}
/**
* Rotates the mesh in such a way so that its "forward" axis is aligned with
* the given direction. This version allows to specify the forward
* direction.
*
* @param dir
* new target direction to point in
* @param forward
* current forward axis
* @return itself
*/
public WETriangleMesh pointTowards(ReadonlyVec3D dir, ReadonlyVec3D forward) {
return transform(
Quaternion.getAlignmentQuat(dir, forward).toMatrix4x4(matrix),
true);
}
public void rebuildIndex() {
LinkedHashMap<Vec3D, Vertex> newV = new LinkedHashMap<Vec3D, Vertex>(
vertices.size());
for (Vertex v : vertices.values()) {
newV.put(v, v);
}
vertices = newV;
LinkedHashMap<Line3D, WingedEdge> newE = new LinkedHashMap<Line3D, WingedEdge>(
edges.size());
for (WingedEdge e : edges.values()) {
newE.put(e, e);
}
edges = newE;
}
protected void removeEdge(WingedEdge e) {
e.remove();
WEVertex v = (WEVertex) e.a;
if (v.edges.size() == 0) {
vertices.remove(v);
}
v = (WEVertex) e.b;
if (v.edges.size() == 0) {
vertices.remove(v);
}
for (WEFace f : e.faces) {
removeFace(f);
}
WingedEdge removed = edges.remove(edgeCheck.set(e.a, e.b));
if (removed != e) {
throw new IllegalStateException("can't remove edge");
}
}
@Override
public void removeFace(Face f) {
faces.remove(f);
for (WingedEdge e : ((WEFace) f).edges) {
e.faces.remove(f);
if (e.faces.size() == 0) {
removeEdge(e);
}
}
}
// FIXME
public void removeUnusedVertices() {
for (Iterator<Vertex> i = vertices.values().iterator(); i.hasNext();) {
Vertex v = i.next();
boolean isUsed = false;
for (Face f : faces) {
if (f.a == v || f.b == v || f.c == v) {
isUsed = true;
break;
}
}
if (!isUsed) {
logger.info("removing vertex: " + v);
i.remove();
}
}
}
public void removeVertices(Collection<Vertex> selection) {
for (Vertex v : selection) {
WEVertex wv = (WEVertex) v;
for (WingedEdge e : new ArrayList<WingedEdge>(wv.edges)) {
for (Face f : new ArrayList<Face>(e.faces)) {
removeFace(f);
}
}
}
// rebuildIndex();
}
public WETriangleMesh rotateAroundAxis(Vec3D axis, float theta) {
return transform(matrix.identity().rotateAroundAxis(axis, theta));
}
public WETriangleMesh rotateX(float theta) {
return transform(matrix.identity().rotateX(theta));
}
public WETriangleMesh rotateY(float theta) {
return transform(matrix.identity().rotateY(theta));
}
public WETriangleMesh rotateZ(float theta) {
return transform(matrix.identity().rotateZ(theta));
}
public WETriangleMesh scale(float scale) {
return transform(matrix.identity().scaleSelf(scale));
}
public WETriangleMesh scale(Vec3D scale) {
return transform(matrix.identity().scaleSelf(scale));
}
public void splitEdge(ReadonlyVec3D a, ReadonlyVec3D b,
SubdivisionStrategy subDiv) {
WingedEdge e = edges.get(edgeCheck.set(a, b));
if (e != null) {
splitEdge(e, subDiv);
}
}
public void splitEdge(WingedEdge e, SubdivisionStrategy subDiv) {
List<Vec3D> mid = subDiv.computeSplitPoints(e);
splitFace(e.faces.get(0), e, mid);
if (e.faces.size() > 1) {
splitFace(e.faces.get(1), e, mid);
}
removeEdge(e);
}
protected void splitFace(WEFace f, WingedEdge e, List<Vec3D> midPoints) {
Vec3D p = null;
for (int i = 0; i < 3; i++) {
WingedEdge ec = f.edges.get(i);
if (!ec.equals(e)) {
if (ec.a.equals(e.a) || ec.a.equals(e.b)) {
p = ec.b;
} else {
p = ec.a;
}
break;
}
}
Vec3D prev = null;
for (int i = 0, num = midPoints.size(); i < num; i++) {
Vec3D mid = midPoints.get(i);
if (i == 0) {
addFace(p, e.a, mid, f.normal);
} else {
addFace(p, prev, mid, f.normal);
}
if (i == num - 1) {
addFace(p, mid, e.b, f.normal);
}
prev = mid;
}
}
public void subdivide() {
subdivide(0);
}
public void subdivide(float minLength) {
subdivide(new MidpointSubdivision(), minLength);
}
public void subdivide(SubdivisionStrategy subDiv) {
subdivide(subDiv, 0);
}
public void subdivide(SubdivisionStrategy subDiv, float minLength) {
subdivideEdges(new ArrayList<WingedEdge>(edges.values()), subDiv,
minLength);
}
protected void subdivideEdges(List<WingedEdge> origEdges,
SubdivisionStrategy subDiv, float minLength) {
Collections.sort(origEdges, subDiv.getEdgeOrdering());
minLength *= minLength;
for (WingedEdge e : origEdges) {
if (edges.containsKey(e)) {
if (e.getLengthSquared() >= minLength) {
splitEdge(e, subDiv);
}
}
}
}
public void subdivideFaceEdges(List<WEFace> faces,
SubdivisionStrategy subDiv, float minLength) {
List<WingedEdge> fedges = new ArrayList<WingedEdge>();
for (WEFace f : faces) {
for (WingedEdge e : f.edges) {
if (!fedges.contains(e)) {
fedges.add(e);
}
}
}
subdivideEdges(fedges, subDiv, minLength);
}
@Override
public String toString() {
return "WETriangleMesh: " + name + " vertices: " + getNumVertices()
+ " faces: " + getNumFaces() + " edges:" + getNumEdges();
}
/**
* Applies the given matrix transform to all mesh vertices and updates all
* face normals.
*
* @param mat
* @return itself
*/
public WETriangleMesh transform(Matrix4x4 mat) {
return transform(mat, true);
}
/**
* Applies the given matrix transform to all mesh vertices. If the
* updateNormals flag is true, all face normals are updated automatically,
* however vertex normals still need a manual update.
*
* @param mat
* @param updateNormals
* @return itself
*/
public WETriangleMesh transform(Matrix4x4 mat, boolean updateNormals) {
for (Vertex v : vertices.values()) {
mat.applyToSelf(v);
}
rebuildIndex();
if (updateNormals) {
computeFaceNormals();
}
return this;
}
public WETriangleMesh translate(Vec3D trans) {
return transform(matrix.identity().translateSelf(trans));
}
protected void updateEdge(WEVertex va, WEVertex vb, WEFace f) {
edgeCheck.set(va, vb);
WingedEdge e = edges.get(edgeCheck);
if (e != null) {
e.addFace(f);
} else {
e = new WingedEdge(va, vb, f, uniqueEdgeID++);
edges.put(e, e);
va.addEdge(e);
vb.addEdge(e);
}
f.addEdge(e);
}
}