/* * __ .__ .__ ._____. * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ * |__| \____/__/\_ \__|\___ >____/__||___ /____ > * \/ \/ \/ \/ * * 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); } }