package codechicken.lib.render;
import codechicken.lib.colour.ColourRGBA;
import codechicken.lib.lighting.LC;
import codechicken.lib.lighting.LightMatrix;
import codechicken.lib.util.Copyable;
import codechicken.lib.vec.Rotation;
import codechicken.lib.vec.Transformation;
import codechicken.lib.vec.Vector3;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.OpenGlHelper;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.client.renderer.WorldRenderer;
import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
import net.minecraft.client.renderer.vertex.VertexFormat;
import net.minecraft.util.BlockPos;
import net.minecraft.util.ResourceLocation;
import net.minecraft.world.IBlockAccess;
import java.util.ArrayList;
/**
* The core of the CodeChickenLib render system.
* Rendering operations are written to avoid object allocations by reusing static variables.
*/
public class CCRenderState {
private static int nextOperationIndex;
public static int registerOperation() {
return nextOperationIndex++;
}
public static int operationCount() {
return nextOperationIndex;
}
/**
* Represents an operation to be run for each vertex that operates on and modifies the current state
*/
public static interface IVertexOperation {
/**
* Load any required references and add dependencies to the pipeline based on the current model (may be null)
* Return false if this operation is redundant in the pipeline with the given model
*/
public boolean load();
/**
* Perform the operation on the current render state
*/
public void operate();
/**
* Get the unique id representing this type of operation. Duplicate operation IDs within the pipeline may have unexpected results.
* ID shoulld be obtained from CCRenderState.registerOperation() and stored in a static variable
*/
public int operationID();
}
private static ArrayList<VertexAttribute<?>> vertexAttributes = new ArrayList<VertexAttribute<?>>();
private static int registerVertexAttribute(VertexAttribute<?> attr) {
vertexAttributes.add(attr);
return vertexAttributes.size() - 1;
}
public static VertexAttribute<?> getAttribute(int index) {
return vertexAttributes.get(index);
}
/**
* Management class for a vertex attrute such as colour, normal etc
* This class should handle the loading of the attrute from an array provided by IVertexSource.getAttributes or the computation of this attrute from others
*
* @param <T> The array type for this attrute eg. int[], Vector3[]
*/
public static abstract class VertexAttribute<T> implements IVertexOperation {
public final int attributeIndex = registerVertexAttribute(this);
private final int operationIndex = registerOperation();
/**
* Set to true when the attrute is part of the pipeline. Should only be managed by CCRenderState when constructing the pipeline
*/
public boolean active = false;
/**
* Construct a new array for storage of vertex attrutes in a model
*/
public abstract T newArray(int length);
@Override
public int operationID() {
return operationIndex;
}
}
public static void arrayCopy(Object src, int srcPos, Object dst, int destPos, int length) {
System.arraycopy(src, srcPos, dst, destPos, length);
if (dst instanceof Copyable[]) {
Object[] oa = (Object[]) dst;
Copyable<Object>[] c = (Copyable[]) dst;
for (int i = destPos; i < destPos + length; i++) {
if (c[i] != null) {
oa[i] = c[i].copy();
}
}
}
}
public static <T> T copyOf(VertexAttribute<T> attr, T src, int length) {
T dst = attr.newArray(length);
arrayCopy(src, 0, dst, 0, ((Object[]) src).length);
return dst;
}
public static interface IVertexSource {
public Vertex5[] getVertices();
/**
* Gets an array of vertex attrutes
*
* @param attr The vertex attrute to get
* @param <T> The attrute array type
* @return An array, or null if not computed
*/
public <T> T getAttributes(VertexAttribute<T> attr);
/**
* @return True if the specified attrute is provided by this model, either by returning an array from getAttributes or by setting the state in prepareVertex
*/
public boolean hasAttribute(VertexAttribute<?> attr);
/**
* Callback to set CCRenderState for a vertex before the pipeline runs
*/
public void prepareVertex();
}
public static VertexAttribute<Vector3[]> normalAttrib = new VertexAttribute<Vector3[]>() {
private Vector3[] normalRef;
@Override
public Vector3[] newArray(int length) {
return new Vector3[length];
}
@Override
public boolean load() {
normalRef = model.getAttributes(this);
if (model.hasAttribute(this)) {
return normalRef != null;
}
if (model.hasAttribute(sideAttrib)) {
pipeline.addDependency(sideAttrib);
return true;
}
throw new IllegalStateException("Normals requested but neither normal or side attrutes are provided by the model");
}
@Override
public void operate() {
if (normalRef != null) {
setNormal(normalRef[vertexIndex]);
} else {
setNormal(Rotation.axes[side]);
}
}
};
public static VertexAttribute<int[]> colourAttrib = new VertexAttribute<int[]>() {
private int[] colourRef;
@Override
public int[] newArray(int length) {
return new int[length];
}
@Override
public boolean load() {
colourRef = model.getAttributes(this);
return colourRef != null || !model.hasAttribute(this);
}
@Override
public void operate() {
if (colourRef != null) {
setColour(ColourRGBA.multiply(baseColour, colourRef[vertexIndex]));
} else {
setColour(baseColour);
}
}
};
public static VertexAttribute<int[]> lightingAttrib = new VertexAttribute<int[]>() {
private int[] colourRef;
@Override
public int[] newArray(int length) {
return new int[length];
}
@Override
public boolean load() {
if (!computeLighting || !useColour || !model.hasAttribute(this)) {
return false;
}
colourRef = model.getAttributes(this);
if (colourRef != null) {
pipeline.addDependency(colourAttrib);
return true;
}
return false;
}
@Override
public void operate() {
setColour(ColourRGBA.multiply(colour, colourRef[vertexIndex]));
}
};
public static VertexAttribute<int[]> sideAttrib = new VertexAttribute<int[]>() {
private int[] sideRef;
@Override
public int[] newArray(int length) {
return new int[length];
}
@Override
public boolean load() {
sideRef = model.getAttributes(this);
if (model.hasAttribute(this)) {
return sideRef != null;
}
pipeline.addDependency(normalAttrib);
return true;
}
@Override
public void operate() {
if (sideRef != null) {
side = sideRef[vertexIndex];
} else {
side = CCModel.findSide(normal);
}
}
};
/**
* Uses the position of the lightmatrix to compute LC if not provided
*/
public static VertexAttribute<LC[]> lightCoordAttrib = new VertexAttribute<LC[]>() {
private LC[] lcRef;
private Vector3 vec = new Vector3();//for computation
private Vector3 pos = new Vector3();
@Override
public LC[] newArray(int length) {
return new LC[length];
}
@Override
public boolean load() {
lcRef = model.getAttributes(this);
if (model.hasAttribute(this)) {
return lcRef != null;
}
pos.set(lightMatrix.pos.x, lightMatrix.pos.y, lightMatrix.pos.z);
pipeline.addDependency(sideAttrib);
pipeline.addRequirement(Transformation.operationIndex);
return true;
}
@Override
public void operate() {
if (lcRef != null) {
lc.set(lcRef[vertexIndex]);
} else {
lc.compute(vec.set(vert.vec).sub(pos), side);
}
}
};
//pipeline state
public static IVertexSource model;
public static int firstVertexIndex;
public static int lastVertexIndex;
public static int vertexIndex;
public static CCRenderPipeline pipeline = new CCRenderPipeline();
//context
public static int baseColour;
public static int alphaOverride;
public static boolean useNormals;
public static boolean computeLighting;
public static boolean useColour;
public static LightMatrix lightMatrix = new LightMatrix();
//vertex outputs
public static Vertex5 vert = new Vertex5();
public static boolean hasNormal;
public static Vector3 normal = new Vector3();
public static boolean hasColour;
public static int colour;
public static boolean hasBrightness;
public static int brightness;
//attrute storage
public static int side;
public static LC lc = new LC();
public static void reset() {
model = null;
pipeline.reset();
useNormals = hasNormal = hasBrightness = hasColour = false;
useColour = computeLighting = true;
baseColour = alphaOverride = -1;
}
public static void setPipeline(IVertexOperation... ops) {
pipeline.setPipeline(ops);
}
public static void setPipeline(IVertexSource model, int start, int end, IVertexOperation... ops) {
pipeline.reset();
setModel(model, start, end);
pipeline.setPipeline(ops);
}
public static void bindModel(IVertexSource model) {
if (CCRenderState.model != model) {
CCRenderState.model = model;
pipeline.rebuild();
}
}
public static void setModel(IVertexSource source) {
setModel(source, 0, source.getVertices().length);
}
public static void setModel(IVertexSource source, int start, int end) {
bindModel(source);
setVertexRange(start, end);
}
public static void setVertexRange(int start, int end) {
firstVertexIndex = start;
lastVertexIndex = end;
}
public static void render(IVertexOperation... ops) {
setPipeline(ops);
render();
}
public static void render() {
Vertex5[] verts = model.getVertices();
for (vertexIndex = firstVertexIndex; vertexIndex < lastVertexIndex; vertexIndex++) {
model.prepareVertex();
vert.set(verts[vertexIndex]);
runPipeline();
writeVert();
}
}
public static void runPipeline() {
pipeline.operate();
}
public static void writeVert() {
WorldRenderer r = Tessellator.getInstance().getWorldRenderer();
if (hasNormal) {
r.normal((float) normal.x, (float) normal.y, (float) normal.z);
}
if (hasColour) {
r.color(colour >>> 24, colour >> 16 & 0xFF, colour >> 8 & 0xFF, alphaOverride >= 0 ? alphaOverride : colour & 0xFF);
}
if (hasBrightness) {
r.lightmap(brightness >> 16 & 65535, brightness & 65535);
}
}
public static void setNormal(double x, double y, double z) {
hasNormal = true;
normal.set(x, y, z);
}
public static void setNormal(Vector3 n) {
hasNormal = true;
normal.set(n);
}
public static void setColour(int c) {
hasColour = true;
colour = c;
}
public static void setBrightness(int b) {
hasBrightness = true;
brightness = b;
}
public static void setBrightness(IBlockAccess world, BlockPos pos) {
setBrightness(world.getBlockState(pos).getBlock().getMixedBrightnessForBlock(world, pos));
}
public static void pullLightmap() {
setBrightness((int) OpenGlHelper.lastBrightnessY << 16 | (int) OpenGlHelper.lastBrightnessX);
}
public static void pushLightmap() {
OpenGlHelper.setLightmapTextureCoords(OpenGlHelper.lightmapTexUnit, brightness & 0xFFFF, brightness >>> 16);
}
/**
* Compact helper for setting dynamic rendering context. Uses normals and doesn't compute lighting
*/
public static void setDynamic() {
useNormals = true;
computeLighting = false;
}
public static void changeTexture(String texture) {
changeTexture(new ResourceLocation(texture));
}
public static void changeTexture(ResourceLocation texture) {
Minecraft.getMinecraft().renderEngine.bindTexture(texture);
}
public static WorldRenderer startDrawing() {
return startDrawing(7, DefaultVertexFormats.POSITION_TEX);
}
public static WorldRenderer startDrawing(VertexFormat format) {
return startDrawing(7, format);
}
public static WorldRenderer startDrawing(int mode, VertexFormat format) {
WorldRenderer r = Tessellator.getInstance().getWorldRenderer();
r.begin(mode, format);
if (hasColour) {
r.color(colour >>> 24, colour >> 16 & 0xFF, colour >> 8 & 0xFF, alphaOverride >= 0 ? alphaOverride : colour & 0xFF);
}
if (hasBrightness) {
r.lightmap(brightness >> 16 & 65535, brightness & 65535);
}
return r;
}
public static void draw() {
Tessellator.getInstance().draw();
}
}