Skip to content

Commit

Permalink
internal: add RedUI viewport node
Browse files Browse the repository at this point in the history
  • Loading branch information
MrTJP committed Nov 21, 2022
1 parent f6429f4 commit 5d2afff
Show file tree
Hide file tree
Showing 4 changed files with 333 additions and 0 deletions.
6 changes: 6 additions & 0 deletions src/core/scala/mrtjp/core/vec/Rect.scala
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,12 @@ case class Rect(origin:Point, size:Size)
val dy = (if (r.y < y) y-r.y else 0) + (if (r.maxY > maxY) maxY-r.maxY else 0)
new Rect(r.x + dx, r.y + dy, r.width, r.height)
}

def ndc(p:Point):Vec2 = {
val dx = ((p.x - origin.x) / width.toDouble * 2D) - 1
val dy = ((p.y - origin.y) / height.toDouble * 2D) - 1
Vec2(dx, -dy)
}
}

object Rect
Expand Down
70 changes: 70 additions & 0 deletions src/core/scala/mrtjp/projectred/redui/DebugRectNode.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package mrtjp.projectred.redui;

import codechicken.lib.colour.EnumColour;
import com.mojang.blaze3d.matrix.MatrixStack;
import mrtjp.core.gui.GuiLib;
import mrtjp.core.vec.Point;
import mrtjp.core.vec.Rect;
import mrtjp.core.vec.Size;
import net.minecraft.client.gui.AbstractGui;
import net.minecraft.util.text.StringTextComponent;
import org.lwjgl.glfw.GLFW;

import java.util.Collections;

public class DebugRectNode extends AbstractGuiNode {

private boolean clickDown = false;
private int colorArgb = EnumColour.RED.argb();
private String name = "";

public void setName(String name) {
this.name = name;
}

public void setColorArgb(int colorArgb) {
this.colorArgb = colorArgb;
}

@Override
public void drawBack(MatrixStack stack, Point mouse, float partialFrame) {
GuiLib.drawLine(stack, getFrame().x(), getFrame().y(), getFrame().maxX(), getFrame().y(), 3, EnumColour.RED.argb());
GuiLib.drawLine(stack, getFrame().maxX(), getFrame().y(), getFrame().maxX(), getFrame().maxY(), 3, EnumColour.RED.argb());
GuiLib.drawLine(stack, getFrame().maxX(), getFrame().maxY(), getFrame().x(), getFrame().maxY(), 3, EnumColour.RED.argb());
GuiLib.drawLine(stack, getFrame().x(), getFrame().maxY(), getFrame().x(), getFrame().y(), 3, EnumColour.RED.argb());

AbstractGui.fill(stack, getFrame().x(), getFrame().y(), getFrame().maxX(), getFrame().maxY(), colorArgb);
}

@Override
public void drawFront(MatrixStack stack, Point mouse, float partialFrame) {

if (!isFirstHit(mouse))
return;

// Rect around mouse position
Rect cursorRect = new Rect(mouse.subtract(3, 3), new Size(6, 6));
AbstractGui.fill(stack, cursorRect.x(), cursorRect.y(), cursorRect.maxX(), cursorRect.maxY(), EnumColour.WHITE.argb(clickDown ? 150 : 50));

// Tooltip showing name
renderTooltip(stack, mouse, Collections.singletonList(new StringTextComponent(name)));
}

@Override
public boolean mouseClicked(Point p, int glfwMouseButton, boolean consumed) {
if (!consumed&& glfwMouseButton == GLFW.GLFW_MOUSE_BUTTON_LEFT && isFirstHit(p) ) {
clickDown = true;
return true;
}
return false;
}

@Override
public boolean mouseReleased(Point p, int glfwMouseButton, long timeHeld, boolean consumed) {
if (clickDown && glfwMouseButton == GLFW.GLFW_MOUSE_BUTTON_LEFT) {
clickDown = false;
return true;
}
return false;
}
}
146 changes: 146 additions & 0 deletions src/core/scala/mrtjp/projectred/redui/PVMMatrix.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
package mrtjp.projectred.redui;

import codechicken.lib.vec.Matrix4;
import codechicken.lib.vec.Vector3;
import com.mojang.blaze3d.matrix.MatrixStack;

/**
* Calculates a view matrix based on camera position, x rotation, and y rotation.
*/
public class PVMMatrix {

private final Matrix4 projectionMatrix = new Matrix4();
private final Matrix4 viewMatrix = new Matrix4();
private final Matrix4 orientationMatrix = new Matrix4();

private final Vector3 forward = new Vector3();
private final Vector3 right = new Vector3();
private final Vector3 up = new Vector3();

private final Vector3 cameraPosition = new Vector3(0, 0, 0);

private double fovY;
private double width;
private double height;
private double zNear;
private double zFar;

public void setProjection(double fovY, double windowWidth, double windowHeight, double zNear, double zFar) {
this.fovY = fovY;
this.width = windowWidth;
this.height = windowHeight;
this.zNear = zNear;
this.zFar = zFar;
projectionMatrix.setIdentity();
perspective(projectionMatrix, fovY, windowWidth / windowHeight, zNear, zFar);
}

public void setView(double x, double y, double z, double xRot, double yRot) {
cameraPosition.set(x, y, z);

orientationMatrix.setIdentity();
orientationMatrix.rotate(-yRot, Vector3.Y_POS);
orientationMatrix.rotate(-xRot, Vector3.X_POS);

forward.set(Vector3.Z_NEG);
right.set(Vector3.X_POS);
up.set(Vector3.Y_POS);

orientationMatrix.apply(forward);
orientationMatrix.apply(right);
orientationMatrix.apply(up);

viewMatrix.setIdentity();
lookAt(viewMatrix, cameraPosition, cameraPosition.copy().add(forward), up);
viewMatrix.transpose();
}

public Matrix4 getProjectionMatrix() {
return projectionMatrix;
}

public Matrix4 getViewMatrix() {
return viewMatrix;
}

public MatrixStack getModelViewMatrixStack() {
MatrixStack stack = new MatrixStack();
stack.pushPose();
stack.last().pose().set(viewMatrix.toMatrix4f());
return stack;
}

/**
* Projects NDC coordinates [-1, 1] onto a target plane at some fixed distance
* to the camera taking into account the FOV. This target plane is assumed to be
* parallel to the near and far planes of the perspective view frustum.
*
* @param ndcX X coordinate on the viewport in [-1, 1] range
* @param ndcY Y coordinate on the viewport in [-1, 1] range
* @param planeDist Distance of the projection
* @return Projection of (ndcX, ndcY) into world-space
*/
public Vector3 ndcToWorldCoordinates(double ndcX, double ndcY, double planeDist) {
// Assume camera at 0,0,0 facing -z
// Find top and right sides of target plane visible in this viewport
double maxY = Math.tan(fovY / 2) * planeDist;
double maxX = maxY * width / height;

// Map ndc points onto plane to get look vector
double dx = ndcX * maxX;
double dy = ndcY * maxY;
Vector3 lookVec = new Vector3(dx, dy, -planeDist);

// Apply transforms to account for camera position and orientation
orientationMatrix.apply(lookVec);
lookVec.add(cameraPosition);

return lookVec;
}

public static void perspective(Matrix4 m, double fovY, double aspect, double zNear, double zFar) {
double tanHalfFovY = Math.tan(fovY / 2D);
double deltaZ = zFar - zNear;

m.m00 = 1D / (aspect * tanHalfFovY);
m.m11 = 1D / tanHalfFovY;
m.m22 = -(zFar + zNear) / deltaZ;
m.m32 = -1;
m.m23 = -2D * (zFar * zNear) / deltaZ;
m.m33 = 0;
}

public static void orthographic(Matrix4 m, double width, double height, double zNear, double zFar) {
double deltaZ = zFar - zNear;

m.m00 = 2 / width;
m.m11 = 2 / height;
m.m22 = -2 / deltaZ;
m.m33 = 1;
m.m03 = -1;
m.m13 = 1;
m.m23 = -(zFar + zNear) / deltaZ;
}

public static void lookAt(Matrix4 m, Vector3 eye, Vector3 center, Vector3 up) {
Vector3 f = center.copy().subtract(eye).normalize();
Vector3 s = f.copy().crossProduct(up).normalize();
Vector3 u = s.copy().crossProduct(f);

m.m00 = s.x;
m.m10 = s.y;
m.m20 = s.z;

m.m01 = u.x;
m.m11 = u.y;
m.m21 = u.z;

m.m02 = -f.x;
m.m12 = -f.y;
m.m22 = -f.z;

m.m30 = -s.dotProduct(eye);
m.m31 = -u.dotProduct(eye);
m.m32 = f.dotProduct(eye);
}
}
111 changes: 111 additions & 0 deletions src/core/scala/mrtjp/projectred/redui/ViewportRenderNode.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package mrtjp.projectred.redui;

import codechicken.lib.math.MathHelper;
import codechicken.lib.vec.Vector3;
import com.mojang.blaze3d.matrix.MatrixStack;
import com.mojang.blaze3d.systems.RenderSystem;
import mrtjp.core.vec.Point;
import mrtjp.core.vec.Rect;
import mrtjp.core.vec.Size;
import mrtjp.core.vec.Vec2;
import net.minecraft.util.text.ITextProperties;
import org.lwjgl.opengl.GL11;

import java.util.List;

/**
* Renders a viewport within the node frame. Overrides provided to obtain the camera position fov, etc.
*/
public abstract class ViewportRenderNode extends AbstractGuiNode {

private final PVMMatrix pvMatrix = new PVMMatrix();

protected abstract void renderInViewport(MatrixStack renderStack, Vec2 ndcMouse, float partialFrame, boolean isFirstHit);

protected abstract List<ITextProperties> getToolTip(Point mousePosition, boolean isFirstHit);

protected abstract double getTargetPlaneDistance();

protected abstract double getVerticalFOV();

protected abstract double getMaxRenderDist();

protected abstract Vector3 getCameraPosition();

protected Rect getGlFrame() {
// Convert frame to screen space anchored at bottom-left instead of top-left
Rect screenFrame = getRoot().getScreenFrame();
Rect frame = convertParentRectToScreen(getFrame());

Rect bottomLeftFrame = new Rect(
new Point(frame.x(), screenFrame.height() - frame.y() - frame.height()),
frame.size());

// Convert from GUI screen space to GL11 screen space using the Minecraft Gui Scale value
double glWScale = getRoot().getMinecraft().getWindow().getGuiScale();
double glHScale = getRoot().getMinecraft().getWindow().getGuiScale();

return new Rect(new Point((int) Math.round(bottomLeftFrame.x() * glWScale), (int) Math.round(bottomLeftFrame.y() * glHScale)),
new Size((int) Math.round(bottomLeftFrame.width() * glWScale), (int) Math.round(bottomLeftFrame.height() * glHScale)));
}

protected Vector3 mouseToWorld(Point mouse) {
return ndcMouseToWorld(getFrame().ndc(mouse));
}

protected Vector3 ndcMouseToWorld(Vec2 ndcMouse) {
return pvMatrix.ndcToWorldCoordinates(ndcMouse.dx(), ndcMouse.dy(), getTargetPlaneDistance());
}

@Override
public void drawBack(MatrixStack stack, Point mouse, float partialFrame) {

// Set up projection and view matrices
Rect glFrame = getGlFrame();
pvMatrix.setProjection(getVerticalFOV(), glFrame.width(), glFrame.height(), 0.2F, getMaxRenderDist());
pvMatrix.setView(getCameraPosition().x, getCameraPosition().y, getCameraPosition().z, 90 * MathHelper.torad, 0);

// Create a new viewport
GL11.glPushAttrib(GL11.GL_VIEWPORT_BIT);
RenderSystem.viewport(glFrame.x(), glFrame.y(), glFrame.width(), glFrame.height());

// Apply projection matrix
RenderSystem.matrixMode(GL11.GL_PROJECTION);
RenderSystem.pushMatrix();
RenderSystem.loadIdentity();
RenderSystem.multMatrix(pvMatrix.getProjectionMatrix().toMatrix4f());

RenderSystem.matrixMode(GL11.GL_MODELVIEW);
RenderSystem.pushMatrix();
RenderSystem.loadIdentity();

/*
TODO Figure out how to sandwich the viewport at this node's z position, +- some small range.
For now, this only renders properly if correctly ordered in the tree (i.e. everything
under it renders before, and everything above it renders after).
*/
RenderSystem.clear(GL11.GL_DEPTH_BUFFER_BIT, false);

// Render
renderInViewport(pvMatrix.getModelViewMatrixStack(), getFrame().ndc(mouse), partialFrame, isFirstHit(mouse));

// Restore previous matrices
RenderSystem.matrixMode(GL11.GL_PROJECTION);
RenderSystem.popMatrix();

RenderSystem.matrixMode(GL11.GL_MODELVIEW);

RenderSystem.popMatrix();

// Restore previous viewport
GL11.glPopAttrib();
}

@Override
public void drawFront(MatrixStack stack, Point mouse, float partialFrame) {

List<ITextProperties> tooltip = getToolTip(mouse, isFirstHit(mouse));

renderTooltip(stack, mouse, tooltip);
}
}

0 comments on commit 5d2afff

Please sign in to comment.