/* * Copyright (C) 2022 NotEnoughUpdates contributors * * This file is part of NotEnoughUpdates. * * NotEnoughUpdates is free software: you can redistribute it * and/or modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, either * version 3 of the License, or (at your option) any later version. * * NotEnoughUpdates 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with NotEnoughUpdates. If not, see . */ package io.github.moulberry.notenoughupdates.cosmetics; import net.minecraft.client.renderer.Tessellator; import net.minecraft.client.renderer.WorldRenderer; import net.minecraft.client.renderer.vertex.DefaultVertexFormats; import org.lwjgl.opengl.GL11; import org.lwjgl.util.vector.Vector2f; import org.lwjgl.util.vector.Vector3f; import java.nio.FloatBuffer; import java.util.ArrayList; import java.util.HashMap; import java.util.List; public class CapeNode { private static final NEUCape.Direction[] cardinals = new NEUCape.Direction[]{ NEUCape.Direction.UP, NEUCape.Direction.RIGHT, NEUCape.Direction.DOWN, NEUCape.Direction.LEFT }; public Vector3f position; public Vector3f lastPosition = new Vector3f(); public Vector3f renderPosition = new Vector3f(); public final Vector3f[] oldRenderPosition = new Vector3f[5]; public final Vector3f velocity = new Vector3f(); public Vector3f normal = null; public Vector3f sideNormal = null; public boolean fixed = false; public static final int DRAW_MASK_FRONT = 0b1; public static final int DRAW_MASK_BACK = 0b10; public static final int DRAW_MASK_SIDES = 0b100; public HashMap neighbors = new HashMap<>(); public float texU = 0; public float texV = 0; public float horzDistMult = 2f; public float vertDistMult = 0.5f; public float horzSideTexU = 0; public float horzSideTexVTop = 0; public float vertSideTexU = 0; public float vertSideTexVTop = 0; public final float gravity = 0.1f; public final float resistance = 0.5f; public static final int FLOAT_NUM = 20; public CapeNode(float x, float y, float z) { this.position = new Vector3f(x, y, z); } private List getConstaints() { List constaints = new ArrayList<>(); for (NEUCape.Direction cardinal : cardinals) { for (int i = 1; i <= 2; i++) { NEUCape.Offset offset = new NEUCape.Offset(cardinal, i); CapeNode other = neighbors.get(offset); if (other != null) { int iOffset = offset.getXOffset() + NEUCape.HORZ_NODES * offset.getYOffset(); constaints.add(new Vector2f( iOffset, i * NEUCape.targetDist * (cardinal.yOff == 0 ? horzDistMult : vertDistMult) )); } } } return constaints; } public void loadIntoBuffer(FloatBuffer buffer) { loadVec3IntoBuffer(buffer, position); List containts = getConstaints(); buffer.put(containts.size()); for (int i = 0; i < 8; i++) { if (i < containts.size()) { loadVec2IntoBuffer(buffer, containts.get(i)); } else { loadVec2IntoBuffer(buffer, new Vector2f()); } } } public void readFromBuffer(FloatBuffer buffer) { readVec3FromBuffer(buffer, position); buffer.position(buffer.position() + 17); } private void readVec3FromBuffer(FloatBuffer buffer, Vector3f vec) { vec.x = buffer.get(); vec.y = buffer.get(); vec.z = buffer.get(); } private void loadVec2IntoBuffer(FloatBuffer buffer, Vector2f vec) { buffer.put(vec.x); buffer.put(vec.y); } private void loadVec3IntoBuffer(FloatBuffer buffer, Vector3f vec) { buffer.put(vec.x); buffer.put(vec.y); buffer.put(vec.z); } public void update() { if (!fixed) { velocity.y -= gravity * (resistance) / (1 - resistance); float actualResistance = resistance; /*BlockPos pos = new BlockPos( MathHelper.floor_double(position.x), MathHelper.floor_double(position.y), MathHelper.floor_double(position.z)); Block block = Minecraft.getMinecraft().theWorld.getBlockState(pos).getBlock(); if(block.getMaterial().isLiquid()) { actualResistance = 0.8f; }*/ velocity.scale(1 - actualResistance); Vector3f.add(position, velocity, position); } } public final CapeNode getNeighbor(NEUCape.Offset offset) { return neighbors.get(offset); } public final void setNeighbor(NEUCape.Offset offset, CapeNode node) { neighbors.put(offset, node); } public void applyForce(float dX, float dY, float dZ) { velocity.x += dX; velocity.y += dY; velocity.z += dZ; } public void move(float dX, float dY, float dZ) { position.x += dX; position.y += dY; position.z += dZ; lastPosition.x = position.x; lastPosition.y = position.y; lastPosition.z = position.z; } public void resetNormal() { normal = null; sideNormal = null; } public void resolveAll(float horzDistMult, boolean opt) { resolveBend(horzDistMult, opt); //resolveShear(); resolveStruct(horzDistMult, opt); } public void resolve(CapeNode other, float targetDist, float strength, boolean opt) { double dX = position.x - other.position.x; double dY = position.y - other.position.y; double dZ = position.z - other.position.z; double distSq = dX * dX + dY * dY + dZ * dZ; double factor = (distSq - targetDist * targetDist) / (40 * distSq) * strength; factor = Math.max(-0.5f, factor); dX *= factor; dY *= factor; dZ *= factor; if (fixed || other.fixed) { dX *= 2; dY *= 2; dZ *= 2; } if (!fixed) { position.x -= dX; position.y -= dY; position.z -= dZ; } if (!other.fixed) { other.position.x += dX; other.position.y += dY; other.position.z += dZ; } } public void resolveStruct(float horzDistMult, boolean opt) { for (NEUCape.Direction cardinal : cardinals) { NEUCape.Offset offset = new NEUCape.Offset(cardinal, 1); CapeNode other = neighbors.get(offset); if (other != null) { resolve(other, NEUCape.targetDist * (cardinal.yOff == 0 ? horzDistMult : 1), 2f * 7.5f, opt); } } } public void resolveShear(float horzDistMult, boolean opt) { for (NEUCape.Direction d : new NEUCape.Direction[]{ NEUCape.Direction.DOWNLEFT, NEUCape.Direction.UPLEFT, NEUCape.Direction.DOWNRIGHT, NEUCape.Direction.DOWNLEFT }) { NEUCape.Offset o = new NEUCape.Offset(d, 1); CapeNode neighbor = getNeighbor(o); if (neighbor != null) { resolve(neighbor, 1f * NEUCape.targetDist * (d.yOff == 0 ? horzDistMult : 1f), 0.5f * 7.5f, opt); } } } public void resolveBend(float horzDistMult, boolean opt) { for (NEUCape.Direction cardinal : cardinals) { NEUCape.Offset offset = new NEUCape.Offset(cardinal, 2); CapeNode other = neighbors.get(offset); if (other != null) { resolve(other, 2f * NEUCape.targetDist * (cardinal.yOff == 0 ? horzDistMult : 1), 1f * 7.5f, opt); } } } public Vector3f normal() { if (normal != null) return normal; normal = new Vector3f(); for (int i = 0; i < cardinals.length; i++) { NEUCape.Direction dir1 = cardinals[i]; NEUCape.Direction dir2 = cardinals[(i + 1) % cardinals.length]; CapeNode node1 = getNeighbor(new NEUCape.Offset(dir1, 1)); CapeNode node2 = getNeighbor(new NEUCape.Offset(dir2, 1)); if (node1 == null || node2 == null) continue; Vector3f toCapeNode1 = Vector3f.sub(node1.renderPosition, renderPosition, null); Vector3f toCapeNode2 = Vector3f.sub(node2.renderPosition, renderPosition, null); Vector3f cross = Vector3f.cross(toCapeNode1, toCapeNode2, null); Vector3f.add(normal, cross.normalise(null), normal); } float l = normal.length(); if (l != 0) { normal.scale(1f / l); } return normal; } public Vector3f sideNormal() { if (sideNormal != null) return sideNormal; sideNormal = new Vector3f(); NEUCape.Direction[] cardinals = new NEUCape.Direction[]{ NEUCape.Direction.UP, NEUCape.Direction.RIGHT, NEUCape.Direction.DOWN, NEUCape.Direction.LEFT }; for (NEUCape.Direction cardinal : cardinals) { CapeNode nodeCardinal = getNeighbor(new NEUCape.Offset(cardinal, 1)); if (nodeCardinal == null) { NEUCape.Direction dirLeft = cardinal.rotateLeft90(); NEUCape.Direction dirRight = cardinal.rotateRight90(); CapeNode nodeLeft = getNeighbor(new NEUCape.Offset(dirLeft, 1)); CapeNode nodeRight = getNeighbor(new NEUCape.Offset(dirRight, 1)); if (nodeRight != null) { Vector3f toOther = Vector3f.sub(nodeRight.renderPosition, renderPosition, null); Vector3f cross = Vector3f.cross(normal(), toOther, null); //Inverted Vector3f.add(sideNormal, cross.normalise(null), sideNormal); } if (nodeLeft != null) { Vector3f toOther = Vector3f.sub(nodeLeft.renderPosition, renderPosition, null); Vector3f cross = Vector3f.cross(toOther, normal(), null); Vector3f.add(sideNormal, cross.normalise(null), sideNormal); } } } float l = sideNormal.length(); if (l != 0) { sideNormal.scale(0.05f / l); } return sideNormal; } public void renderNode() { renderNode(DRAW_MASK_FRONT | DRAW_MASK_BACK | DRAW_MASK_SIDES); } public void renderNode(int mask) { CapeNode nodeLeft = getNeighbor(new NEUCape.Offset(NEUCape.Direction.LEFT, 1)); CapeNode nodeUp = getNeighbor(new NEUCape.Offset(NEUCape.Direction.UP, 1)); CapeNode nodeDown = getNeighbor(new NEUCape.Offset(NEUCape.Direction.DOWN, 1)); CapeNode nodeRight = getNeighbor(new NEUCape.Offset(NEUCape.Direction.RIGHT, 1)); CapeNode nodeDownRight = getNeighbor(new NEUCape.Offset(NEUCape.Direction.DOWNRIGHT, 1)); Tessellator tessellator = Tessellator.getInstance(); WorldRenderer worldrenderer = tessellator.getWorldRenderer(); if (nodeDown != null && nodeRight != null && nodeDownRight != null) { //Back if ((mask & DRAW_MASK_BACK) != 0) { worldrenderer.begin(GL11.GL_TRIANGLE_STRIP, DefaultVertexFormats.POSITION_TEX_NORMAL); for (CapeNode node : new CapeNode[]{this, nodeDown, nodeRight, nodeDownRight}) { Vector3f nodeNorm = node.normal(); worldrenderer.pos(node.renderPosition.x, node.renderPosition.y, node.renderPosition.z) .tex(1 - node.texU, node.texV) .normal(-nodeNorm.x, -nodeNorm.y, -nodeNorm.z).endVertex(); } tessellator.draw(); } //Front (Offset by normal) if ((mask & DRAW_MASK_FRONT) != 0) { worldrenderer.begin(GL11.GL_TRIANGLE_STRIP, DefaultVertexFormats.POSITION_TEX_NORMAL); for (CapeNode node : new CapeNode[]{nodeDownRight, nodeDown, nodeRight, this}) { Vector3f nodeNorm = node.normal(); worldrenderer.pos( node.renderPosition.x + nodeNorm.x * 0.05f, node.renderPosition.y + nodeNorm.y * 0.05f, node.renderPosition.z + nodeNorm.z * 0.05f ) .tex(node.texU, node.texV) .normal(nodeNorm.x, nodeNorm.y, nodeNorm.z).endVertex(); } tessellator.draw(); } } if ((mask & DRAW_MASK_SIDES) != 0) { if (nodeLeft == null || nodeRight == null) { //Render left/right edge if (nodeDown != null) { renderEdge(nodeDown, true); } } if (nodeUp == null || nodeDown == null) { //Render up/down edge if (nodeRight != null) { renderEdge(nodeRight, false); } } } } public void renderEdge(CapeNode other, boolean lr) { float thisTexU = lr ? this.horzSideTexU : this.vertSideTexU; float thisTexV = lr ? this.horzSideTexVTop : this.vertSideTexVTop; float otherTexU = lr ? other.horzSideTexU : other.vertSideTexU; float otherTexV = lr ? other.horzSideTexVTop : other.vertSideTexVTop; Tessellator tessellator = Tessellator.getInstance(); WorldRenderer worldrenderer = tessellator.getWorldRenderer(); Vector3f thisNorm = normal(); Vector3f otherNorm = other.normal(); Vector3f thisSideNorm = sideNormal(); Vector3f otherSideNorm = other.sideNormal(); worldrenderer.begin(GL11.GL_TRIANGLE_STRIP, DefaultVertexFormats.POSITION_TEX_NORMAL); worldrenderer.pos(this.renderPosition.x, this.renderPosition.y, this.renderPosition.z) .tex(thisTexU, thisTexV + 20 / 1024f) .normal(thisSideNorm.x, thisSideNorm.y, thisSideNorm.z).endVertex(); worldrenderer.pos(other.renderPosition.x, other.renderPosition.y, other.renderPosition.z) .tex(otherTexU, otherTexV + 20 / 1024f) .normal(otherSideNorm.x, otherSideNorm.y, otherSideNorm.z).endVertex(); worldrenderer.pos( this.renderPosition.x + thisNorm.x * 0.05f, this.renderPosition.y + thisNorm.y * 0.05f, this.renderPosition.z + thisNorm.z * 0.05f ) .tex(thisTexU, thisTexV) .normal(thisSideNorm.x, thisSideNorm.y, thisSideNorm.z).endVertex(); worldrenderer.pos( other.renderPosition.x + otherNorm.x * 0.05f, other.renderPosition.y + otherNorm.y * 0.05f, other.renderPosition.z + otherNorm.z * 0.05f ) .tex(otherTexU, otherTexV) .normal(otherSideNorm.x, otherSideNorm.y, otherSideNorm.z).endVertex(); tessellator.draw(); } }