Skip to content

Commit

Permalink
Merge pull request #282 from NOVA-Team/vertex-normals
Browse files Browse the repository at this point in the history
Implement Vertex normals
  • Loading branch information
ExE-Boss authored Apr 7, 2017
2 parents bd960b2 + 04376b9 commit c87124b
Show file tree
Hide file tree
Showing 8 changed files with 135 additions and 35 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,11 @@ public void render(Optional<IBlockAccess> access, Optional<RenderManager> entity
entityRenderManager.get().renderEngine.bindTexture(AssetConverter.instance().toNative(t));
face.vertices.forEach(
v -> {
if (v.normal.isPresent())
tessellator.setNormal((float) v.normal.get().getX(), (float) v.normal.get().getY(), (float) v.normal.get().getZ());
else
tessellator.setNormal((float) face.normal.getX(), (float) face.normal.getY(), (float) face.normal.getZ());

tessellator.setColorRGBA(v.color.red(), v.color.green(), v.color.blue(), v.color.alpha());
tessellator.addVertexWithUV(v.vec.getX(), v.vec.getY(), v.vec.getZ(), v.uv.getX(), v.uv.getY());
}
Expand All @@ -115,6 +120,11 @@ public void render(Optional<IBlockAccess> access, Optional<RenderManager> entity
IIcon icon = RenderUtility.instance.getIcon(texture);
face.vertices.forEach(
v -> {
if (v.normal.isPresent())
tessellator.setNormal((float) v.normal.get().getX(), (float) v.normal.get().getY(), (float) v.normal.get().getZ());
else
tessellator.setNormal((float) face.normal.getX(), (float) face.normal.getY(), (float) face.normal.getZ());

tessellator.setColorRGBA(v.color.red(), v.color.green(), v.color.blue(), v.color.alpha());
if (icon != null) {
tessellator.addVertexWithUV(v.vec.getX(), v.vec.getY(), v.vec.getZ(), icon.getInterpolatedU(16 * v.uv.getX()), icon.getInterpolatedV(16 * v.uv.getY()));
Expand All @@ -127,6 +137,11 @@ public void render(Optional<IBlockAccess> access, Optional<RenderManager> entity
} else {
face.vertices.forEach(
v -> {
if (v.normal.isPresent())
tessellator.setNormal((float) v.normal.get().getX(), (float) v.normal.get().getY(), (float) v.normal.get().getZ());
else
tessellator.setNormal((float) face.normal.getX(), (float) face.normal.getY(), (float) face.normal.getZ());

tessellator.setColorRGBA(v.color.red(), v.color.green(), v.color.blue(), v.color.alpha());
tessellator.addVertex(v.vec.getX(), v.vec.getY(), v.vec.getZ());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,11 @@ public void render(Optional<IBlockAccess> access, Optional<RenderManager> entity
entityRenderManager.get().renderEngine.bindTexture(AssetConverter.instance().toNative(t));
face.vertices.forEach(
v -> {
if (v.normal.isPresent())
worldRenderer.setNormal((float) v.normal.get().getX(), (float) v.normal.get().getY(), (float) v.normal.get().getZ());
else
worldRenderer.setNormal((float) face.normal.getX(), (float) face.normal.getY(), (float) face.normal.getZ());

worldRenderer.setColorRGBA(v.color.red(), v.color.green(), v.color.blue(), v.color.alpha());
worldRenderer.addVertexWithUV(v.vec.getX(), v.vec.getY(), v.vec.getZ(), v.uv.getX(), v.uv.getY());
}
Expand All @@ -94,6 +99,11 @@ public void render(Optional<IBlockAccess> access, Optional<RenderManager> entity
TextureAtlasSprite icon = RenderUtility.instance.getTexture(texture);
face.vertices.forEach(
v -> {
if (v.normal.isPresent())
worldRenderer.setNormal((float) v.normal.get().getX(), (float) v.normal.get().getY(), (float) v.normal.get().getZ());
else
worldRenderer.setNormal((float) face.normal.getX(), (float) face.normal.getY(), (float) face.normal.getZ());

worldRenderer.setColorRGBA(v.color.red(), v.color.green(), v.color.blue(), v.color.alpha());
if (icon != null) {
worldRenderer.addVertexWithUV(v.vec.getX(), v.vec.getY(), v.vec.getZ(), icon.getInterpolatedU(16 * v.uv.getX()), icon.getInterpolatedV(16 * v.uv.getY()));
Expand All @@ -106,6 +116,11 @@ public void render(Optional<IBlockAccess> access, Optional<RenderManager> entity
} else {
face.vertices.forEach(
v -> {
if (v.normal.isPresent())
worldRenderer.setNormal((float) v.normal.get().getX(), (float) v.normal.get().getY(), (float) v.normal.get().getZ());
else
worldRenderer.setNormal((float) face.normal.getX(), (float) face.normal.getY(), (float) face.normal.getZ());

worldRenderer.setColorRGBA(v.color.red(), v.color.green(), v.color.blue(), v.color.alpha());
worldRenderer.addVertex(v.vec.getX(), v.vec.getY(), v.vec.getZ());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ public FWSmartModel() {

public static int[] vertexToInts(Vertex vertex, TextureAtlasSprite texture, Vector3D normal) {
// TODO: Allow serialization of arbitrary vertex formats.
if (vertex.normal.isPresent())
normal = vertex.normal.get();
return new int[] {
Float.floatToRawIntBits((float) vertex.vec.getX()),
Float.floatToRawIntBits((float) vertex.vec.getY()),
Expand Down
21 changes: 17 additions & 4 deletions src/main/java/nova/core/render/model/Face.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,26 @@
* @author Calclavia
*/
public class Face implements Cloneable {
//The vertices that make up this face.
/**
* The vertices that make up this face.
*/
public final List<Vertex> vertices = new ArrayList<>();
//The normal (or direction) this face is facing. Normals must be unit vectors.

/**
* The normal (or direction) this face is facing.
* Normals must be unit vectors.
*/
public Vector3D normal = Vector3D.ZERO;
//The texture that is to be rendered on this face.

/**
* The texture that is to be rendered on this face.
*/
public Optional<Texture> texture = Optional.empty();
//The brightness value defines how bright the face should be rendered. The default value will let NOVA decide the brightness based on the world surroundings.

/**
* The brightness value defines how bright the face should be rendered.
* The default value will let NOVA decide the brightness based on the world surroundings.
*/
public double brightness = -1;

/**
Expand Down
5 changes: 4 additions & 1 deletion src/main/java/nova/core/render/model/MeshModel.java
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,10 @@ public Set<Model> flatten(MatrixStack matrixStack) {

transformedModel.faces.stream().forEach(f -> {
f.normal = TransformUtil.transform(f.normal, normalMatrix);
f.vertices.forEach(v -> v.vec = matrixStack.apply(v.vec));
f.vertices.forEach(v -> {
v.vec = matrixStack.apply(v.vec);
v.normal = v.normal.map(n -> TransformUtil.transform(n, normalMatrix));
});
}
);

Expand Down
23 changes: 21 additions & 2 deletions src/main/java/nova/core/render/model/Vertex.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode;
import java.util.Optional;

/**
* A Vertex contains a position and UV data.
Expand All @@ -36,20 +37,38 @@ public class Vertex implements Cloneable {
public Vector3D vec;
public Vector2D uv;

/**
* The normal (or direction) this vertex is facing.
* Normals must be unit vectors.
*/
public Optional<Vector3D> normal;

/**
* A RGB color value from 0 to 1.
*/
public Color color;

/**
* Constructor for vertex
* @param vertex coordinates in 3D sapce.
* @param vertex coordinates in 3D space.
* @param uv coordinates on the texture.
* @param normal the vertex normal.
*/
public Vertex(Vector3D vertex, Vector2D uv, Vector3D normal) {
this(vertex, uv);
this.normal = Optional.of(normal);
}

/**
* Constructor for vertex
* @param vertex coordinates in 3D space.
* @param uv coordinates on the texture.
*/
public Vertex(Vector3D vertex, Vector2D uv) {
this.vec = vertex;
this.uv = uv;
this.color = Color.white;
this.normal = Optional.empty();
}

/**
Expand All @@ -64,7 +83,6 @@ public Vertex(double x, double y, double z, double u, double v) {
this(new Vector3D(x, y, z), new Vector2D(u, v));
}


@Override
public String toString() {
MathContext cont = new MathContext(4, RoundingMode.HALF_UP);
Expand All @@ -75,6 +93,7 @@ public String toString() {
@Override
protected Vertex clone() {
Vertex vertex = new Vertex(vec, uv);
vertex.normal = normal;
vertex.color = color;
return vertex;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
package nova.core.render.model;

import nova.core.render.RenderException;
import nova.core.util.math.Vector3DUtil;
import nova.internal.core.Game;
import org.apache.commons.math3.geometry.euclidean.threed.Vector3D;
import org.apache.commons.math3.geometry.euclidean.twod.Vector2D;

Expand All @@ -43,17 +45,19 @@ public class WavefrontObjectModelProvider extends ModelProvider {
private static Pattern vertexPattern = Pattern.compile("(v( (\\-)?\\d+\\.\\d+){3,4} *\\n)|(v( (\\-)?\\d+\\.\\d+){3,4} *$)");
private static Pattern vertexNormalPattern = Pattern.compile("(vn( (\\-)?\\d+\\.\\d+){3,4} *\\n)|(vn( (\\-)?\\d+\\.\\d+){3,4} *$)");
private static Pattern textureCoordinatePattern = Pattern.compile("(vt( (\\-)?\\d+\\.\\d+){2,3} *\\n)|(vt( (\\-)?\\d+\\.\\d+){2,3} *$)");
private static Pattern face_V_VT_VN_Pattern = Pattern.compile("(f( \\d+/\\d+/\\d+){3,4} *\\n)|(f( \\d+/\\d+/\\d+){3,4} *$)");
private static Pattern face_V_VT_Pattern = Pattern.compile("(f( \\d+/\\d+){3,4} *\\n)|(f( \\d+/\\d+){3,4} *$)");
private static Pattern face_V_VN_Pattern = Pattern.compile("(f( \\d+//\\d+){3,4} *\\n)|(f( \\d+//\\d+){3,4} *$)");
private static Pattern face_V_Pattern = Pattern.compile("(f( \\d+){3,4} *\\n)|(f( \\d+){3,4} *$)");
// According to the official Wavefront OBJ specification, a face can have an unlimited amount of vertices
private static Pattern face_V_VT_VN_Pattern = Pattern.compile("(f( \\d+/\\d+/\\d+){3,} *\\n)|(f( \\d+/\\d+/\\d+){3,} *$)");
private static Pattern face_V_VT_Pattern = Pattern.compile("(f( \\d+/\\d+){3,} *\\n)|(f( \\d+/\\d+){3,} *$)");
private static Pattern face_V_VN_Pattern = Pattern.compile("(f( \\d+//\\d+){3,} *\\n)|(f( \\d+//\\d+){3,} *$)");
private static Pattern face_V_Pattern = Pattern.compile("(f( \\d+){3,} *\\n)|(f( \\d+){3,} *$)");
private static Pattern subModelPattern = Pattern.compile("([go]([^\\\\ ]*+)*\\n)|([go]( [^\\\\ ]*+) *$)");
private static Matcher globalMatcher;
//A map of all models generated with their names
private final MeshModel model = new MeshModel();
private MeshModel currentModel = null;
private ArrayList<Vector3D> vertices = new ArrayList<>();
private ArrayList<Vector2D> textureCoordinates = new ArrayList<>();
private ArrayList<Vector3D> vertexNormals = new ArrayList<>();

/**
* Creates new ModelProvider
Expand All @@ -66,8 +70,6 @@ public WavefrontObjectModelProvider(String domain, String name) {

@Override
public void load(InputStream stream) {


String currentLine;
int lineCount = 0;
try(BufferedReader reader = new BufferedReader(new InputStreamReader(stream))){
Expand All @@ -87,6 +89,14 @@ public void load(InputStream stream) {
if (textureCoordinate != null) {
textureCoordinates.add(textureCoordinate);
}
} else if (currentLine.startsWith("vn ")) {
Vector3D vertexNormal = parseToVertexNormal(currentLine, lineCount);
if (vertexNormal != null) {
vertexNormals.add(vertexNormal);
}
} else if (currentLine.startsWith("vp ")) {
// TODO: Parameter space vertices
Game.logger().warn("Model {} uses parameter space vertices", this.name);
} else if (currentLine.startsWith("f ")) {
if (currentModel == null) {
currentModel = new MeshModel("Default");
Expand All @@ -107,7 +117,7 @@ public void load(InputStream stream) {
}
}
model.children.add(currentModel);
} catch (IOException e) {
} catch (IOException | UnsupportedOperationException e) {
throw new RenderException("Model " + this.name + " could not be read", e);
} finally {
this.cleanUp();
Expand Down Expand Up @@ -172,7 +182,8 @@ private Vector3D parseToVertexNormal(String line, int lineNumber) {
String[] tokens = line.split(" ");
try {
if (tokens.length == 3) {
return new Vector3D(Float.parseFloat(tokens[0]), Float.parseFloat(tokens[1]), Float.parseFloat(tokens[2]));
// According to the official Wavefront OBJ specification, vertex normals might not be normalized.
return new Vector3D(Float.parseFloat(tokens[0]), Float.parseFloat(tokens[1]), Float.parseFloat(tokens[2])).normalize();
}
} catch (NumberFormatException e) {
throw new RenderException(String.format("Number formatting error at line %d", lineNumber), e);
Expand All @@ -195,32 +206,32 @@ private Face parseToFace(String line, int lineNumber) {
if (isValid(line, face_V_VT_VN_Pattern)) {
for (int i = 0; i < tokens.length; ++i) {
subTokens = tokens[i].split("/");
face.drawVertex(new Vertex(vertices.get(Integer.parseInt(subTokens[0]) - 1), getTexVec(Integer.parseInt(subTokens[1]) - 1)));
face.drawVertex(new Vertex(getVertex(Integer.parseInt(subTokens[0])), getTexVec(Integer.parseInt(subTokens[1])), getNormal(Integer.parseInt(subTokens[2]))));
}
face.normal = calculateNormal(face);
face.normal = Vector3DUtil.calculateNormal(face);
}
// f v1/vt1 v2/vt2 v3/vt3 ...
else if (isValid(line, face_V_VT_Pattern)) {
for (int i = 0; i < tokens.length; ++i) {
subTokens = tokens[i].split("/");
face.drawVertex(new Vertex(vertices.get(Integer.parseInt(subTokens[0]) - 1), getTexVec(Integer.parseInt(subTokens[1]) - 1)));
face.drawVertex(new Vertex(getVertex(Integer.parseInt(subTokens[0])), getTexVec(Integer.parseInt(subTokens[1]))));
}
face.normal = calculateNormal(face);
face.normal = Vector3DUtil.calculateNormal(face);
}
// f v1//vn1 v2//vn2 v3//vn3 ...
else if (isValid(line, face_V_VN_Pattern)) {
for (int i = 0; i < tokens.length; ++i) {
subTokens = tokens[i].split("//");
face.drawVertex(new Vertex(vertices.get(Integer.parseInt(subTokens[0]) - 1), Vector2D.ZERO));
face.drawVertex(new Vertex(getVertex(Integer.parseInt(subTokens[0])), Vector2D.ZERO, getNormal(Integer.parseInt(subTokens[1]))));
}
face.normal = calculateNormal(face);
face.normal = Vector3DUtil.calculateNormal(face);
}
// f v1 v2 v3 ...
else if (isValid(line, face_V_Pattern)) {
for (int i = 0; i < tokens.length; ++i) {
face.drawVertex(new Vertex(vertices.get(Integer.parseInt(tokens[i]) - 1), Vector2D.ZERO));
face.drawVertex(new Vertex(getVertex(Integer.parseInt(tokens[i])), Vector2D.ZERO));
}
face.normal = calculateNormal(face);
face.normal = Vector3DUtil.calculateNormal(face);
} else {
throw new RenderException("Error parsing entry ('" + line + "'" + ", line " + lineNumber + ") in model '" + this.name + "' - Incorrect format");
}
Expand All @@ -242,16 +253,6 @@ private MeshModel parseToModel(String line, int lineNumber) {
return null;
}

private Vector3D calculateNormal(Face face) {
Vertex firstEntry = face.vertices.get(0);
Vertex secondEntry = face.vertices.get(1);
Vertex thirdEntry = face.vertices.get(1);
Vector3D v1 = new Vector3D(secondEntry.vec.getX() - firstEntry.vec.getX(), secondEntry.vec.getY() - firstEntry.vec.getY(), secondEntry.vec.getZ() - firstEntry.vec.getZ());
Vector3D v2 = new Vector3D(thirdEntry.vec.getX() - firstEntry.vec.getX(), thirdEntry.vec.getY() - firstEntry.vec.getY(), thirdEntry.vec.getZ() - firstEntry.vec.getZ());

return v1.crossProduct(v2).normalize();
}

/**
* Verifies that the given line from the model file is a valid for a given pattern
*
Expand All @@ -268,13 +269,30 @@ private boolean isValid(String line, Pattern pattern) {
return globalMatcher.matches();
}

private Vector3D getVertex(int index) {
try {
return vertices.get(index < 0 ? index + textureCoordinates.size() : index - 1);
} catch (IndexOutOfBoundsException e) {
System.err.println("[OBJ]: Can't get vertex " + index + "! Is this model corrupted?");
return Vector3D.ZERO;
}
}

private Vector2D getTexVec(int index) {
try {
return textureCoordinates.get(index);
return textureCoordinates.get(index < 0 ? index + textureCoordinates.size() : index - 1);
} catch (IndexOutOfBoundsException e) {
System.err.println("[OBJ]: Can't get textureCoordinate " + index + "! Is this model corrupted?");
return Vector2D.ZERO;
}
}

private Vector3D getNormal(int index) {
try {
return vertexNormals.get(index < 0 ? index + textureCoordinates.size() : index - 1);
} catch (IndexOutOfBoundsException e) {
System.err.println("[OBJ]: Can't get vertexNormal " + index + "! Is this model corrupted?");
return Vector3D.ZERO;
}
}
}
17 changes: 16 additions & 1 deletion src/main/java/nova/core/util/math/Vector3DUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,12 @@
*
* You should have received a copy of the GNU General Public License
* along with NOVA. If not, see <http://www.gnu.org/licenses/>.
*/package nova.core.util.math;
*/

package nova.core.util.math;

import nova.core.render.model.Face;
import nova.core.render.model.Vertex;
import org.apache.commons.math3.geometry.euclidean.threed.Vector3D;
import org.apache.commons.math3.util.FastMath;

Expand Down Expand Up @@ -122,4 +126,15 @@ public static Vector3D floor(Vector3D vec) {
public static Vector3D abs(Vector3D vec) {
return new Vector3D(FastMath.abs(vec.getX()), FastMath.abs(vec.getY()), FastMath.abs(vec.getZ()));
}

public static Vector3D calculateNormal(Face face) {
// TODO: Possibly calculate from vertex normals
Vertex firstEntry = face.vertices.get(0);
Vertex secondEntry = face.vertices.get(1);
Vertex thirdEntry = face.vertices.get(2);
Vector3D v1 = secondEntry.vec.subtract(firstEntry.vec);
Vector3D v2 = thirdEntry.vec.subtract(firstEntry.vec);

return v1.crossProduct(v2).normalize();
}
}

0 comments on commit c87124b

Please sign in to comment.