diff --git a/gradle.properties b/gradle.properties index 6c4cf6e..7ec156a 100644 --- a/gradle.properties +++ b/gradle.properties @@ -7,7 +7,7 @@ loader_version=0.14.21 #Fabric api fabric_version=0.84.0+1.20.1 -mod_version = 1.0.0-beta.9 +mod_version = 1.0.0-beta.10 maven_group = io.github.foundationgames archives_base_name = phonos diff --git a/src/main/java/io/github/foundationgames/phonos/client/render/CableBounds.java b/src/main/java/io/github/foundationgames/phonos/client/render/CableBounds.java new file mode 100644 index 0000000..458bc94 --- /dev/null +++ b/src/main/java/io/github/foundationgames/phonos/client/render/CableBounds.java @@ -0,0 +1,38 @@ +package io.github.foundationgames.phonos.client.render; + +import net.minecraft.client.render.Frustum; + +public class CableBounds { + private double minX, minY, minZ, maxX, maxY, maxZ; + + public void clear() { + minX = Float.POSITIVE_INFINITY; + minY = Float.POSITIVE_INFINITY; + minZ = Float.POSITIVE_INFINITY; + maxX = Float.NEGATIVE_INFINITY; + maxY = Float.NEGATIVE_INFINITY;; + maxZ = Float.NEGATIVE_INFINITY;; + } + + public void fit(double x, double y, double z) { + minX = Math.min(minX, x); + minY = Math.min(minY, y); + minZ = Math.min(minZ, z); + maxX = Math.max(maxX, x); + maxY = Math.max(maxY, y); + maxZ = Math.max(maxZ, z); + } + + public void fitTwo(double x0, double y0, double z0, double x1, double y1, double z1) { + minX = Math.min(x0, x1); + minY = Math.min(y0, y1); + minZ = Math.min(z0, z1); + maxX = Math.max(x0, x1); + maxY = Math.max(y0, y1); + maxZ = Math.max(z0, z1); + } + + public boolean visible(Frustum frustum) { + return frustum.isVisible(minX, minY, minZ, maxX, maxY, maxZ); + } +} diff --git a/src/main/java/io/github/foundationgames/phonos/client/render/CableRenderer.java b/src/main/java/io/github/foundationgames/phonos/client/render/CableRenderer.java index 94f3d36..e8ce110 100644 --- a/src/main/java/io/github/foundationgames/phonos/client/render/CableRenderer.java +++ b/src/main/java/io/github/foundationgames/phonos/client/render/CableRenderer.java @@ -7,10 +7,7 @@ import io.github.foundationgames.phonos.world.sound.CablePlugPoint; import net.minecraft.client.MinecraftClient; import net.minecraft.client.model.Model; -import net.minecraft.client.render.BufferBuilder; -import net.minecraft.client.render.Tessellator; -import net.minecraft.client.render.VertexConsumer; -import net.minecraft.client.render.WorldRenderer; +import net.minecraft.client.render.*; import net.minecraft.client.util.math.MatrixStack; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.MathHelper; @@ -67,8 +64,9 @@ private static void lerpCableEnd(Vector4f[] out, Vector4f[] begin, Vector4f[] en } } - public static void renderConnection(@Nullable CableVBOContainer vboContainer, PhonosClientConfig config, World world, CableConnection conn, - MatrixStack matrices, VertexConsumer immediate, Model cableEndModel, int overlay, float tickDelta) { + public static void renderConnection(@Nullable CableVBOContainer vboContainer, PhonosClientConfig config, World world, + CableConnection conn, @Nullable CableBounds bounds, Frustum frustum, MatrixStack matrices, + VertexConsumer immediate, Model cableEndModel, int overlay, float tickDelta) { int startLight, endLight; // Connection plug points are always rendered immediate @@ -97,12 +95,25 @@ public static void renderConnection(@Nullable CableVBOContainer vboContainer, Ph if (conn.isStatic() && vboContainer != null) { // Vbo can be used for this connection if (vboContainer.rebuild) { + if (bounds != null) { + bounds.fit(cableStPt.x, cableStPt.y, cableStPt.z); + bounds.fit(cableEnPt.x, cableEnPt.y, cableEnPt.z); + } + BufferBuilder buffer = Tessellator.getInstance().getBuffer(); matrices = new MatrixStack(); buildCableGeometry(conn, matrices, buffer, segments, length, detail, startLight, endLight, overlay); } } else { // Connection must be rendered immediate + if (bounds != null && config.cableCulling) { + bounds.fitTwo(cableStPt.x, cableStPt.y, cableStPt.z, cableEnPt.x, cableEnPt.y, cableEnPt.z); + + if (!bounds.visible(frustum)) { + return; + } + } + if (config.cableLODs) { float cx = (cableStPt.x + cableEnPt.x) * 0.5f; float cy = (cableStPt.y + cableEnPt.y) * 0.5f; diff --git a/src/main/java/io/github/foundationgames/phonos/client/render/CableVBOContainer.java b/src/main/java/io/github/foundationgames/phonos/client/render/CableVBOContainer.java index bd22460..91fbab1 100644 --- a/src/main/java/io/github/foundationgames/phonos/client/render/CableVBOContainer.java +++ b/src/main/java/io/github/foundationgames/phonos/client/render/CableVBOContainer.java @@ -19,6 +19,8 @@ public class CableVBOContainer { public @Nullable VertexBuffer buffer = null; private List cachedCons = new ArrayList<>(); + private CableBounds bounds = new CableBounds(); + public void refresh(ConnectionCollection conns) { List connections = new ArrayList<>(); conns.forEach((i, conn) -> { @@ -41,7 +43,7 @@ public void refresh(ConnectionCollection conns) { } } - public void render(MatrixStack matrices, VertexConsumer immediate, RenderLayer layer, BasicModel cableEndModel, + public void render(MatrixStack matrices, VertexConsumer immediate, RenderLayer layer, BasicModel cableEndModel, Frustum frustum, ConnectionCollection conns, PhonosClientConfig config, World world, int overlay, float tickDelta) { boolean rebuild = this.buffer == null || this.rebuild; @@ -53,11 +55,14 @@ public void render(MatrixStack matrices, VertexConsumer immediate, RenderLayer l builder.begin(VertexFormat.DrawMode.QUADS, VertexFormats.POSITION_COLOR_TEXTURE_OVERLAY_LIGHT_NORMAL); this.buffer = vbo; + + this.bounds.clear(); } // Render each connection point in immediate mode, and render cables into the given vertex buffer conns.forEach((i, conn) -> - CableRenderer.renderConnection(this, config, world, conn, matrices, immediate, cableEndModel, overlay, tickDelta)); + CableRenderer.renderConnection(this, config, world, conn, rebuild ? bounds : null, frustum, + matrices, immediate, cableEndModel, overlay, tickDelta)); var vbo = this.buffer; @@ -69,7 +74,9 @@ public void render(MatrixStack matrices, VertexConsumer immediate, RenderLayer l this.rebuild = false; } - + if (config.cableCulling && !bounds.visible(frustum)) { + return; + } // Set up the render state for this render phase (and texture) layer.startDrawing(); diff --git a/src/main/java/io/github/foundationgames/phonos/client/render/block/CableOutputBlockEntityRenderer.java b/src/main/java/io/github/foundationgames/phonos/client/render/block/CableOutputBlockEntityRenderer.java index aac0c7c..c701f9c 100644 --- a/src/main/java/io/github/foundationgames/phonos/client/render/block/CableOutputBlockEntityRenderer.java +++ b/src/main/java/io/github/foundationgames/phonos/client/render/block/CableOutputBlockEntityRenderer.java @@ -3,11 +3,14 @@ import io.github.foundationgames.phonos.Phonos; import io.github.foundationgames.phonos.PhonosClient; import io.github.foundationgames.phonos.client.model.BasicModel; +import io.github.foundationgames.phonos.client.render.CableBounds; import io.github.foundationgames.phonos.client.render.CableRenderer; import io.github.foundationgames.phonos.client.render.CableVBOContainer; import io.github.foundationgames.phonos.config.PhonosClientConfig; +import io.github.foundationgames.phonos.mixin.WorldRendererAccess; import io.github.foundationgames.phonos.world.sound.block.OutputBlockEntity; import net.minecraft.block.entity.BlockEntity; +import net.minecraft.client.MinecraftClient; import net.minecraft.client.render.VertexConsumerProvider; import net.minecraft.client.render.block.entity.BlockEntityRenderer; import net.minecraft.client.render.block.entity.BlockEntityRendererFactory; @@ -18,6 +21,8 @@ public class CableOutputBlockEntityRenderer - CableRenderer.renderConnection(null, config, entity.getWorld(), conn, matrices, immediate, cableEndModel, overlay, tickDelta)); + CableRenderer.renderConnection(null, config, entity.getWorld(), conn, boundCache, frustum, + matrices, immediate, cableEndModel, overlay, tickDelta)); } matrices.pop(); diff --git a/src/main/java/io/github/foundationgames/phonos/sound/MultiSourceSoundInstance.java b/src/main/java/io/github/foundationgames/phonos/sound/MultiSourceSoundInstance.java index b31c635..3ed5748 100644 --- a/src/main/java/io/github/foundationgames/phonos/sound/MultiSourceSoundInstance.java +++ b/src/main/java/io/github/foundationgames/phonos/sound/MultiSourceSoundInstance.java @@ -1,5 +1,6 @@ package io.github.foundationgames.phonos.sound; +import com.google.common.util.concurrent.AtomicDouble; import io.github.foundationgames.phonos.config.PhonosClientConfig; import io.github.foundationgames.phonos.sound.emitter.SoundEmitterTree; import net.minecraft.client.MinecraftClient; @@ -8,16 +9,19 @@ import net.minecraft.sound.SoundCategory; import net.minecraft.sound.SoundEvent; import net.minecraft.util.Identifier; +import net.minecraft.util.math.MathHelper; import net.minecraft.util.math.random.Random; import org.joml.Vector3d; +import org.spongepowered.asm.mixin.injection.At; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; public class MultiSourceSoundInstance extends AbstractSoundInstance implements TickableSoundInstance { public final AtomicReference emitters; - private double x; - private double y; - private double z; + private double camX, camZ; + private double x, y, z; + protected float volMultiplier = 1; private boolean done; @@ -37,7 +41,7 @@ public MultiSourceSoundInstance(SoundEmitterTree tree, SoundEvent sound, SoundCa @Override public float getVolume() { - return (float) (super.getVolume() * PhonosClientConfig.get().phonosMasterVolume); + return (float) (super.getVolume() * PhonosClientConfig.get().phonosMasterVolume * volMultiplier); } @Override @@ -61,23 +65,28 @@ private void updatePosition() { var pos = new Vector3d(); - double[] emWeights = {0}; - boolean[] foundSources = {false}; + AtomicDouble emWeights = new AtomicDouble(0); + AtomicDouble minDist = new AtomicDouble(Double.POSITIVE_INFINITY); + AtomicBoolean foundSources = new AtomicBoolean(false); var emPos = new Vector3d(); this.x = 0; - this.y = 999; + this.y = camPos.y + 999; this.z = 0; this.emitters.get().forEachSource(mc.world, em -> { double weight; - foundSources[0] = true; + foundSources.set(true); emPos.set(em.x(), em.y(), em.z()); double dist = emPos.distance(camPos.x, camPos.y, camPos.z); + if (dist < minDist.get()) { + minDist.set(dist); + } + if (dist <= 1.4135) { weight = -0.05 * Math.pow(dist, 4) + 1; } else { @@ -85,16 +94,41 @@ private void updatePosition() { } pos.add(emPos.mul(weight)); - emWeights[0] += weight; + emWeights.addAndGet(weight); }); - if (foundSources[0]) { - pos.div(emWeights[0]); + if (foundSources.get()) { + pos.div(emWeights.get()); + + // Avoid headache by biasing the sound into the player head when moving fast + double camVel = Vector3d.length(camPos.x - this.camX, 0, camPos.z - this.camZ); + + if (camVel > 0.3) { + float camWeight = MathHelper.clamp((float) ((-0.0333 / (camVel - 0.2666)) + 1), 0, 1); + + pos.mul(1 - camWeight); + pos.add(camWeight * camPos.x, camWeight * camPos.y + 2, camWeight * camPos.z); + } this.x = pos.x(); this.y = pos.y(); this.z = pos.z(); } + + double errorDist = minDist.get() - pos.distance(camPos.x, camPos.y, camPos.z); + + if (errorDist > 0) { + float range = this.volume * 16; + errorDist = Math.min(errorDist, range); + + // Fake distance fade, I don't have a good reason for this function other than it works well enough + this.volMultiplier = (float) MathHelper.clamp(1 - (Math.pow(errorDist / range, 0.25)), 0, 1); + } else { + this.volMultiplier = 1; + } + + this.camX = camPos.x; + this.camZ = camPos.z; } @Override diff --git a/src/main/java/io/github/foundationgames/phonos/world/sound/block/BlockEntityOutputs.java b/src/main/java/io/github/foundationgames/phonos/world/sound/block/BlockEntityOutputs.java index b4c40d6..712292f 100644 --- a/src/main/java/io/github/foundationgames/phonos/world/sound/block/BlockEntityOutputs.java +++ b/src/main/java/io/github/foundationgames/phonos/world/sound/block/BlockEntityOutputs.java @@ -105,8 +105,6 @@ public boolean purge(Consumer purgeAction) { } if (connections[i] != null && connections[i].shouldRemove(this.blockEntity.getWorld())) { - System.out.println("CAN EXIST: " + connections[i].end.canPlugExist(this.blockEntity.getWorld())); - purgeAction.accept(connections[i]); connections[i] = null; changed = true;