/*
* Copyright (c) 2017 NOVA, All rights reserved.
* This library is free software, licensed under GNU Lesser General Public License version 3
*
* This file is part of NOVA.
*
* NOVA is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* NOVA 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 General Public License for more details.
*
* 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.render.pipeline;
import nova.core.component.ComponentProvider;
import nova.core.render.Color;
import nova.core.render.model.Face;
import nova.core.render.model.MeshModel;
import nova.core.render.model.Vertex;
import nova.core.render.texture.Texture;
import nova.core.util.Direction;
import nova.core.util.math.Vector2DUtil;
import org.apache.commons.math3.geometry.euclidean.twod.Vector2D;
import java.util.HashSet;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.Supplier;
/**
* An item rendering builder that generates a function that renders an item model.
*
* @author ExE Boss
*/
public class ItemRenderPipeline extends RenderPipeline {
public final ComponentProvider componentProvider;
/**
* Called to get the texture of this item.
* Returns - An optional of the texture.
*/
public Supplier<Optional<Texture>> texture = () -> Optional.empty();
/**
* Called to get the size of this item to be rendered.
* Defaults to (1, 1).
*/
public Supplier<Vector2D> size;
/**
* Gets the color of the item. This is called by the default item renderer.
*
* Returns the color
*/
public Supplier<Color> colorMultiplier = () -> Color.white;
public ItemRenderPipeline(ComponentProvider componentProvider) {
this.componentProvider = componentProvider;
size = () -> new Vector2D(1, 1);
consumer = model -> model.addChild(draw(new MeshModel()));
}
/**
* This method is called to specify a texture to use for the item.
*
* @param texture The {@link nova.core.render.texture.Texture} for the item.
* @return this
*/
public ItemRenderPipeline withTexture(Supplier<Optional<Texture>> texture) {
this.texture = texture;
return this;
}
/**
* This method is called to specify a texture to use for the item.
*
* @param texture The {@link nova.core.render.texture.Texture} to use for all sides.
* @return this
*/
public ItemRenderPipeline withTexture(Texture texture) {
Objects.requireNonNull(texture, "Texture is null, please initiate the texture before the item");
this.texture = () -> Optional.of(texture);
return this;
}
/**
* This method is called to specify the size of the item, defaults to a 1×1 square.
*
* @param size A supplier that returns
* the {@link org.apache.commons.math3.geometry.euclidean.twod.Vector2D} which specifies the item size.
* @return this
*/
public ItemRenderPipeline withSize(Supplier<Vector2D> size) {
this.size = size;
return this;
}
/**
* This method is called to specify the size of the item, defaults to a 1×1 square.
*
* @param size The {@link org.apache.commons.math3.geometry.euclidean.twod.Vector2D} which specifies the item size.
* @return this
*/
public ItemRenderPipeline withSize(Vector2D size) {
this.size = () -> size;
return this;
}
/**
* This method is called to specify a color multiplier to use for the item.
*
*
* @param colorMultiplier A supplier that returns
* the {@link nova.core.render.Color} multiplier for the item.
* @return this
*/
public ItemRenderPipeline withColor(Supplier<Color> colorMultiplier) {
this.colorMultiplier = colorMultiplier;
return this;
}
/**
* This method is called to specify a color multiplier to use for the item.
*
* @param colorMultiplier The {@link nova.core.render.Color} multiplier for the item.
* @return this
*/
public ItemRenderPipeline withColor(Color colorMultiplier) {
this.colorMultiplier = () -> colorMultiplier;
return this;
}
public MeshModel draw(MeshModel model) {
Vector2D size = this.size.get();
double minX = -size.getX() / 2;
double minY = -size.getY() / 2;
double minZ = -0.5 / 16;
double maxX = size.getX() / 2;
double maxY = size.getY() / 2;
double maxZ = 0.5 / 16;
Color color = colorMultiplier.get();
Optional<Texture> texture = this.texture.get();
Face face;
Set<Face> faces;
face = drawFront(model, minX, minY, minZ, maxX, maxY, maxZ, texture);
face.vertices.forEach(v -> v.color = color);
face = drawBack(model, minX, minY, minZ, maxX, maxY, maxZ, texture);
face.vertices.forEach(v -> v.color = color);
faces = drawUpAndDown(model, minX, minY, minZ, maxX, maxY, maxZ, texture);
faces.stream().flatMap(f -> f.vertices.stream()).forEach(v -> v.color = color);
faces = drawLeftAndRight(model, minX, minY, minZ, maxX, maxY, maxZ, texture);
faces.stream().flatMap(f -> f.vertices.stream()).forEach(v -> v.color = color);
return model;
}
public static Face drawBack(
MeshModel model,
double minX, double minY, double minZ,
double maxX, double maxY, double maxZ,
Optional<Texture> texture) {
Vector2D minUV;
Vector2D maxUV;
if (texture.isPresent()) {
minUV = texture.get().minUV;
maxUV = texture.get().maxUV;
} else {
minUV = Vector2D.ZERO;
maxUV = Vector2DUtil.ONE;
}
Face back = new Face();
back.texture = texture;
back.normal = Direction.NORTH.toVector();
//Top-left corner
back.drawVertex(new Vertex(minX, maxY, minZ, maxUV.getX(), maxUV.getY()));
//Top-right corner
back.drawVertex(new Vertex(maxX, maxY, minZ, minUV.getX(), maxUV.getY()));
//Bottom-right corner
back.drawVertex(new Vertex(maxX, minY, minZ, minUV.getX(), minUV.getY()));
//Bottom-left corner
back.drawVertex(new Vertex(minX, minY, minZ, maxUV.getX(), minUV.getY()));
model.drawFace(back);
return back;
}
public static Face drawFront(
MeshModel model,
double minX, double minY, double minZ,
double maxX, double maxY, double maxZ,
Optional<Texture> texture) {
Vector2D minUV;
Vector2D maxUV;
if (texture.isPresent()) {
minUV = texture.get().minUV;
maxUV = texture.get().maxUV;
} else {
minUV = Vector2D.ZERO;
maxUV = Vector2DUtil.ONE;
}
Face front = new Face();
front.texture = texture;
front.normal = Direction.SOUTH.toVector();
//Bottom-left corner
front.drawVertex(new Vertex(minX, minY, maxZ, maxUV.getX(), minUV.getY()));
//Bottom-right corner
front.drawVertex(new Vertex(maxX, minY, maxZ, minUV.getX(), minUV.getY()));
//Top-right corner
front.drawVertex(new Vertex(maxX, maxY, maxZ, minUV.getX(), maxUV.getY()));
//Top-left corner
front.drawVertex(new Vertex(minX, maxY, maxZ, maxUV.getX(), maxUV.getY()));
model.drawFace(front);
return front;
}
public static Set<Face> drawUpAndDown(
MeshModel model,
double minX, double minY, double minZ,
double maxX, double maxY, double maxZ,
Optional<Texture> texture) {
Vector2D minUV;
Vector2D maxUV;
Vector2D dimensions;
if (texture.isPresent()) {
minUV = texture.get().minUV;
maxUV = texture.get().maxUV;
dimensions = texture.get().dimension;
} else {
minUV = Vector2D.ZERO;
maxUV = Vector2DUtil.ONE;
dimensions = new Vector2D(1, 1);
}
Set<Face> faces = new HashSet<>();
double pixelHeight = (maxUV.getY() - minUV.getY()) / dimensions.getY();
double voxelHeight = Math.abs(maxY - minY) / dimensions.getY();
for (int i = 0; i < dimensions.getY(); i++) {
Face up = new Face();
up.texture = texture;
up.normal = Direction.UP.toVector();
//Bottom-left corner
up.drawVertex(new Vertex(maxX, interpolate(minY, maxY, i + 1, voxelHeight), minZ, minUV.getX(), interpolate(minUV.getY(), maxUV.getY(), ((int)dimensions.getY()) - i - 1, pixelHeight)));
//Bottom-right corner
up.drawVertex(new Vertex(minX, interpolate(minY, maxY, i + 1, voxelHeight), minZ, maxUV.getX(), interpolate(minUV.getY(), maxUV.getY(), ((int)dimensions.getY()) - i - 1, pixelHeight)));
//Top-right corner
up.drawVertex(new Vertex(minX, interpolate(minY, maxY, i + 1, voxelHeight), maxZ, maxUV.getX(), interpolate(minUV.getY(), maxUV.getY(), ((int)dimensions.getY()) - i, pixelHeight)));
//Top-left corner
up.drawVertex(new Vertex(maxX, interpolate(minY, maxY, i + 1, voxelHeight), maxZ, minUV.getX(), interpolate(minUV.getY(), maxUV.getY(), ((int)dimensions.getY()) - i, pixelHeight)));
model.drawFace(up);
faces.add(up);
Face down = new Face();
down.texture = texture;
down.normal = Direction.DOWN.toVector();
//Top-left corner
down.drawVertex(new Vertex(maxX, interpolate(minY, maxY, i, voxelHeight), maxZ, minUV.getX(), interpolate(minUV.getY(), maxUV.getY(), ((int)dimensions.getY()) - i, pixelHeight)));
//Top-right corner
down.drawVertex(new Vertex(minX, interpolate(minY, maxY, i, voxelHeight), maxZ, maxUV.getX(), interpolate(minUV.getY(), maxUV.getY(), ((int)dimensions.getY()) - i, pixelHeight)));
//Bottom-right corner
down.drawVertex(new Vertex(minX, interpolate(minY, maxY, i, voxelHeight), minZ, maxUV.getX(), interpolate(minUV.getY(), maxUV.getY(), ((int)dimensions.getY()) - i - 1, pixelHeight)));
//Bottom-left corner
down.drawVertex(new Vertex(maxX, interpolate(minY, maxY, i, voxelHeight), minZ, minUV.getX(), interpolate(minUV.getY(), maxUV.getY(), ((int)dimensions.getY()) - i - 1, pixelHeight)));
model.drawFace(down);
faces.add(down);
}
return faces;
}
public static Set<Face> drawLeftAndRight(
MeshModel model,
double minX, double minY, double minZ,
double maxX, double maxY, double maxZ,
Optional<Texture> texture) {
Vector2D minUV;
Vector2D maxUV;
Vector2D dimensions;
if (texture.isPresent()) {
minUV = texture.get().minUV;
maxUV = texture.get().maxUV;
dimensions = texture.get().dimension;
} else {
minUV = Vector2D.ZERO;
maxUV = Vector2DUtil.ONE;
dimensions = new Vector2D(1, 1);
}
Set<Face> faces = new HashSet<>();
double pixelWidth = (maxUV.getX() - minUV.getX()) / dimensions.getX();
double voxelWidth = Math.abs(maxX - minX) / dimensions.getX();
for (int i = 0; i < dimensions.getX(); i++) {
Face left = new Face();
left.texture = texture;
left.normal = Direction.WEST.toVector();
//Bottom-left corner
left.drawVertex(new Vertex(interpolate(minX, maxX, i, voxelWidth), minY, minZ, interpolate(minUV.getX(), maxUV.getX(), i, pixelWidth), minUV.getY()));
//Bottom-right corner
left.drawVertex(new Vertex(interpolate(minX, maxX, i, voxelWidth), minY, maxZ, interpolate(minUV.getX(), maxUV.getX(), i + 1, pixelWidth), minUV.getY()));
//Top-right corner
left.drawVertex(new Vertex(interpolate(minX, maxX, i, voxelWidth), maxY, maxZ, interpolate(minUV.getX(), maxUV.getX(), i + 1, pixelWidth), maxUV.getY()));
//Top-left corner
left.drawVertex(new Vertex(interpolate(minX, maxX, i, voxelWidth), maxY, minZ, interpolate(minUV.getX(), maxUV.getX(), i, pixelWidth), maxUV.getY()));
model.drawFace(left);
faces.add(left);
Face right = new Face();
right.texture = texture;
right.normal = Direction.EAST.toVector();
//Top-left corner
right.drawVertex(new Vertex(interpolate(minX, maxX, i + 1, voxelWidth), maxY, minZ, interpolate(minUV.getX(), maxUV.getX(), i, pixelWidth), maxUV.getY()));
//Top-right corner
right.drawVertex(new Vertex(interpolate(minX, maxX, i + 1, voxelWidth), maxY, maxZ, interpolate(minUV.getX(), maxUV.getX(), i + 1, pixelWidth), maxUV.getY()));
//Bottom-right corner
right.drawVertex(new Vertex(interpolate(minX, maxX, i + 1, voxelWidth), minY, maxZ, interpolate(minUV.getX(), maxUV.getX(), i + 1, pixelWidth), minUV.getY()));
//Bottom-left corner
right.drawVertex(new Vertex(interpolate(minX, maxX, i + 1, voxelWidth), minY, minZ, interpolate(minUV.getX(), maxUV.getX(), i, pixelWidth), minUV.getY()));
model.drawFace(right);
faces.add(right);
}
return faces;
}
private static double interpolate(double min, double max, int index, double indexSize) {
if (indexSize > 0) {
if (max > min) {
return min + indexSize * index;
} else if (max < min) {
return max + indexSize * index;
}
} else if (indexSize < 0) {
if (max > min) {
return min - indexSize * index;
} else if (max < min) {
return max - indexSize * index;
}
}
return min == max ? min : max;
}
}