package org.osm2world.core.target.common.material; import static java.lang.Math.abs; import java.util.ArrayList; import java.util.List; import org.osm2world.core.math.TriangleXYZ; import org.osm2world.core.math.VectorXYZ; import org.osm2world.core.math.VectorXZ; import org.osm2world.core.target.Target; import org.osm2world.core.target.common.TextureData; /** * several useful {@link TexCoordFunction} implementations. * They can be referenced by name in style definition files. * {@link Target}s may also provide special implementations for them * (e.g. as a specialized shader). */ public enum NamedTexCoordFunction implements TexCoordFunction { /** * uses x and z vertex coords together with the texture's width and height * to place a texture. This function works for all geometries, * but steep inclines or even vertical walls produce odd-looking results. */ GLOBAL_X_Z, /** * like {@link #GLOBAL_X_Z}, but uses y instead of z dimension. * Better suited for certain vertical surfaces. */ GLOBAL_X_Y, /** * creates texture coordinates for individual triangles that * orient the texture based on each triangle's downward slope. * * TODO: introduce face requirement? */ SLOPED_TRIANGLES, /** * creates texture coordinates for a triangle strip (alternating between * upper and lower vertex), based on the length along a wall from the * starting point, height of the vertex, and texture size. * * This only works for vertices forming a triangle strip, * alternating between upper and lower vertex. */ STRIP_WALL, /** * creates texture coordinates for a triangle strip (alternating between * upper and lower vertex), based on the length along a wall from the * starting point. * * Similar to {@link #STRIP_WALL}, except that one texture coordinate * dimension alternates between 1 and 0 instead of being based on height. */ STRIP_FIT_HEIGHT, /** * stretches the texture exactly once onto a triangle strip (alternating * between upper and lower vertex). * * Most commonly used to texture a rectangle represented as a * triangle strip with 2 triangles. */ STRIP_FIT; @Override public List<VectorXZ> apply(List<VectorXYZ> vs, TextureData textureData) { List<VectorXZ> result = new ArrayList<VectorXZ>(vs.size()); switch (this) { case GLOBAL_X_Z: case GLOBAL_X_Y: for (VectorXYZ v : vs) { result.add(new VectorXZ( v.x / textureData.width, (this == GLOBAL_X_Y ? v.y : v.z) / textureData.height)); } break; case SLOPED_TRIANGLES: if (vs.size() % 3 != 0) { throw new IllegalArgumentException("not a set of triangles"); } List<Double> knownAngles = new ArrayList<Double>(); for (int i = 0; i < vs.size() / 3; i++) { //TODO avoid creating a temporary triangle TriangleXYZ triangle = new TriangleXYZ(vs.get(3*i), vs.get(3*i+1), vs.get(3*i+2)); VectorXZ normalXZProjection = triangle.getNormal().xz(); double downAngle = 0; if (normalXZProjection.x != 0 || normalXZProjection.z != 0) { downAngle = normalXZProjection.angle(); //try to avoid differences between triangles of the same face Double similarKnownAngle = null; for (double knownAngle : knownAngles) { if (abs(downAngle - knownAngle) < 0.02) { similarKnownAngle = knownAngle; break; } } if (similarKnownAngle == null) { knownAngles.add(downAngle); } else { downAngle = similarKnownAngle; } } for (VectorXYZ v : triangle.getVertices()) { VectorXZ baseTexCoord = v.rotateY(-downAngle).xz(); result.add(new VectorXZ( -baseTexCoord.x / textureData.width, -baseTexCoord.z / textureData.height)); } } break; case STRIP_WALL: case STRIP_FIT_HEIGHT: case STRIP_FIT: if (vs.size() % 2 == 1) { throw new IllegalArgumentException("not a triangle strip wall"); } /* calculate length of the wall (if needed later) */ double totalLength = 0; if (this == STRIP_FIT) { for (int i = 0; i+1 < vs.size(); i++) { totalLength += vs.get(i).distanceToXZ(vs.get(i+1)); } } /* calculate texture coordinate list */ double accumulatedLength = 0; for (int i = 0; i < vs.size(); i++) { VectorXYZ v = vs.get(i); // increase accumulated length after every second vector if (i > 0 && i % 2 == 0) { accumulatedLength += v.xz().distanceTo(vs.get(i-2).xz()); } // calculate texture coords double s, t; if (this != STRIP_FIT) { s = accumulatedLength / textureData.width; } else { s = accumulatedLength / totalLength; } if (this == STRIP_WALL) { t = (i % 2 == 0) ? (v.distanceTo(vs.get(i+1))) / textureData.height : 0; } else { t = (i % 2 == 0) ? 1 : 0; } result.add(new VectorXZ(s, t)); } break; default: throw new Error("unimplemented texture coordinate function"); } return result; } }