package org.osm2world.core.math.algorithms; import static java.util.Arrays.asList; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import org.osm2world.core.math.TriangleXYZ; import org.osm2world.core.math.TriangleXYZWithNormals; import org.osm2world.core.math.VectorXYZ; public final class NormalCalculationUtil { /** prevents instantiation */ private NormalCalculationUtil() {} /** * calculates normals for a collection of triangles */ public static final List<VectorXYZ> calculateTriangleNormals( List<VectorXYZ> vertices, boolean smooth) { assert vertices.size() % 3 == 0; VectorXYZ[] normals = new VectorXYZ[vertices.size()]; //TODO: implement smooth case if (/*!smooth*/ true) { //flat for (int triangle = 0; triangle < vertices.size() / 3; triangle++) { int i = triangle * 3 + 1; VectorXYZ vBefore = vertices.get(i-1); VectorXYZ vAt = vertices.get(i); VectorXYZ vAfter = vertices.get(i+1); VectorXYZ toBefore = vBefore.subtract(vAt); VectorXYZ toAfter = vAfter.subtract(vAt); normals[i] = toBefore.crossNormalized(toAfter); normals[i-1] = normals[i]; normals[i+1] = normals[i]; } } return asList(normals); } public static final List<VectorXYZ> calculateTriangleStripNormals( List<VectorXYZ> vertices, boolean smooth) { assert vertices.size() >= 3; VectorXYZ[] normals = calculatePerTriangleNormals(vertices, false); return asList(normals); //TODO: implement smooth case } public static final List<VectorXYZ> calculateTriangleFanNormals( List<VectorXYZ> vertices, boolean smooth) { assert vertices.size() >= 3; VectorXYZ[] normals = calculatePerTriangleNormals(vertices, true); return asList(normals); //TODO: implement smooth case } /** * calculates "flat" lighting normals for triangle strips and triangle fans * * @param vertices fan/strip vertices * @param fan true for fans, false for strips */ private static VectorXYZ[] calculatePerTriangleNormals( List<VectorXYZ> vertices, boolean fan) { VectorXYZ[] normals = new VectorXYZ[vertices.size()]; for (int triangle = 0; triangle < vertices.size() - 2; triangle++) { int i = triangle + 1; VectorXYZ vBefore = vertices.get( fan ? 0 : (i-1) ); VectorXYZ vAt = vertices.get(i); VectorXYZ vAfter = vertices.get(i+1); VectorXYZ toBefore = vBefore.subtract(vAt); VectorXYZ toAfter = vAfter.subtract(vAt); if (triangle % 2 == 0 || fan) { normals[i+1] = toBefore.crossNormalized(toAfter); } else { normals[i+1] = toAfter.crossNormalized(toBefore); } } normals[0] = normals[2]; normals[1] = normals[2]; return normals; } private static final double MAX_ANGLE_RADIANS = Math.toRadians(75); /** * calculates normals for vertices that are shared by multiple triangles. */ public static final Collection<TriangleXYZWithNormals> calculateTrianglesWithNormals( Collection<TriangleXYZ> triangles) { Map<VectorXYZ, List<TriangleXYZ>> adjacentTriangles = calculateAdjacentTriangles(triangles); Collection<TriangleXYZWithNormals> result = new ArrayList<TriangleXYZWithNormals>(triangles.size()); for (TriangleXYZ triangle : triangles) { result.add(new TriangleXYZWithNormals(triangle, calculateNormal(triangle.v1, triangle, adjacentTriangles), calculateNormal(triangle.v2, triangle, adjacentTriangles), calculateNormal(triangle.v3, triangle, adjacentTriangles))); } return result; } private static VectorXYZ calculateNormal(VectorXYZ v, TriangleXYZ triangle, Map<VectorXYZ, List<TriangleXYZ>> adjacentTrianglesMap) { /* find adjacent triangles whose normals are close enough to that of t * and save their normal vectors */ List<VectorXYZ> relevantNormals = new ArrayList<VectorXYZ>(); for (TriangleXYZ t2 : adjacentTrianglesMap.get(v)) { if (triangle == t2 || triangle.getNormal().angleTo(t2.getNormal()) <= MAX_ANGLE_RADIANS) { //add, unless one of the existing normals is very similar boolean notCoplanar = true; for (VectorXYZ n : relevantNormals) { if (n.angleTo(t2.getNormal()) < 0.01) { notCoplanar = false; break; } } if (notCoplanar) { relevantNormals.add(t2.getNormal()); } } } /* calculate sum of relevant normals, * normalize it and set the result as normal for the vertex */ VectorXYZ normal = new VectorXYZ(0, 0, 0); for (VectorXYZ addNormal : relevantNormals) { normal = normal.add(addNormal); } return normal.normalize(); } private static Map<VectorXYZ, List<TriangleXYZ>> calculateAdjacentTriangles( Collection<TriangleXYZ> triangles) { Map<VectorXYZ, List<TriangleXYZ>> result = new HashMap<VectorXYZ, List<TriangleXYZ>>(); for (TriangleXYZ triangle : triangles) { for (VectorXYZ vertex : triangle.getVertices()) { List<TriangleXYZ> triangleList = result.get(vertex); if (triangleList == null) { triangleList = new ArrayList<TriangleXYZ>(); result.put(vertex, triangleList); } triangleList.add(triangle); } } return result; } }