From b9d1a586c191618faf634a034501647ab2ace0d3 Mon Sep 17 00:00:00 2001 From: Zelophed Date: Tue, 16 Feb 2021 00:48:13 +0100 Subject: [PATCH 1/3] Assisted Placement, Part IV MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - added 👻-blocks as a placement preview --- .../com/simibubi/create/CreateClient.java | 15 +- .../structureMovement/bearing/SailBlock.java | 4 +- .../relays/advanced/GantryShaftBlock.java | 1 - .../relays/advanced/SpeedControllerBlock.java | 10 +- .../relays/elementary/CogwheelBlockItem.java | 25 ++- .../simibubi/create/events/ClientEvents.java | 3 + .../foundation/command/HighlightCommand.java | 24 +-- .../command/ToggleDebugCommand.java | 7 +- .../create/foundation/config/CClient.java | 1 + .../create/foundation/utility/VecHelper.java | 61 +++++- .../utility/ghost/GhostBlockParams.java | 50 +++++ .../utility/ghost/GhostBlockRenderer.java | 172 ++++++++++++++++ .../foundation/utility/ghost/GhostBlocks.java | 82 ++++++++ .../utility/placement/IPlacementHelper.java | 24 ++- .../utility/placement/PlacementHelpers.java | 193 ++++++++++++++++-- .../utility/placement/PlacementOffset.java | 59 ++++-- .../utility/placement/util/PoleHelper.java | 8 +- .../resources/META-INF/accesstransformer.cfg | 5 +- 18 files changed, 668 insertions(+), 76 deletions(-) create mode 100644 src/main/java/com/simibubi/create/foundation/utility/ghost/GhostBlockParams.java create mode 100644 src/main/java/com/simibubi/create/foundation/utility/ghost/GhostBlockRenderer.java create mode 100644 src/main/java/com/simibubi/create/foundation/utility/ghost/GhostBlocks.java diff --git a/src/main/java/com/simibubi/create/CreateClient.java b/src/main/java/com/simibubi/create/CreateClient.java index 51f5007e5..77351c2a0 100644 --- a/src/main/java/com/simibubi/create/CreateClient.java +++ b/src/main/java/com/simibubi/create/CreateClient.java @@ -1,10 +1,5 @@ package com.simibubi.create; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.function.Function; - import com.simibubi.create.content.contraptions.base.KineticTileEntityRenderer; import com.simibubi.create.content.contraptions.components.structureMovement.ContraptionRenderer; import com.simibubi.create.content.contraptions.relays.encased.CasingConnectivity; @@ -17,8 +12,8 @@ import com.simibubi.create.foundation.block.render.SpriteShifter; import com.simibubi.create.foundation.item.CustomItemModels; import com.simibubi.create.foundation.item.CustomRenderedItems; import com.simibubi.create.foundation.utility.SuperByteBufferCache; +import com.simibubi.create.foundation.utility.ghost.GhostBlocks; import com.simibubi.create.foundation.utility.outliner.Outliner; - import net.minecraft.block.Block; import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.BlockModelShapes; @@ -36,6 +31,11 @@ import net.minecraftforge.client.model.ModelLoader; import net.minecraftforge.eventbus.api.IEventBus; import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.function.Function; + public class CreateClient { public static ClientSchematicLoader schematicSender; @@ -43,6 +43,7 @@ public class CreateClient { public static SchematicAndQuillHandler schematicAndQuillHandler; public static SuperByteBufferCache bufferCache; public static final Outliner outliner = new Outliner(); + public static GhostBlocks ghostBlocks; private static CustomBlockModels customBlockModels; private static CustomItemModels customItemModels; @@ -67,6 +68,8 @@ public class CreateClient { bufferCache.registerCompartment(KineticTileEntityRenderer.KINETIC_TILE); bufferCache.registerCompartment(ContraptionRenderer.CONTRAPTION, 20); + ghostBlocks = new GhostBlocks(); + AllKeys.register(); AllContainerTypes.registerScreenFactories(); //AllTileEntities.registerRenderers(); diff --git a/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/bearing/SailBlock.java b/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/bearing/SailBlock.java index 9c2255629..2111ebeff 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/bearing/SailBlock.java +++ b/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/bearing/SailBlock.java @@ -5,7 +5,6 @@ import com.simibubi.create.AllShapes; import com.simibubi.create.foundation.block.ProperDirectionalBlock; import com.simibubi.create.foundation.utility.DyeHelper; import com.simibubi.create.foundation.utility.Iterate; -import com.simibubi.create.foundation.utility.VecHelper; import com.simibubi.create.foundation.utility.placement.IPlacementHelper; import com.simibubi.create.foundation.utility.placement.PlacementHelpers; import com.simibubi.create.foundation.utility.placement.PlacementOffset; @@ -216,7 +215,8 @@ public class SailBlock extends ProperDirectionalBlock { @Override public void renderAt(BlockPos pos, BlockState state, BlockRayTraceResult ray, PlacementOffset offset) { - IPlacementHelper.renderArrow(VecHelper.getCenterOf(pos), VecHelper.getCenterOf(offset.getPos()), state.get(FACING)); + //IPlacementHelper.renderArrow(VecHelper.getCenterOf(pos), VecHelper.getCenterOf(offset.getPos()), state.get(FACING)); + displayGhost(offset); } } } diff --git a/src/main/java/com/simibubi/create/content/contraptions/relays/advanced/GantryShaftBlock.java b/src/main/java/com/simibubi/create/content/contraptions/relays/advanced/GantryShaftBlock.java index 05078e359..dab2b86b6 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/relays/advanced/GantryShaftBlock.java +++ b/src/main/java/com/simibubi/create/content/contraptions/relays/advanced/GantryShaftBlock.java @@ -279,7 +279,6 @@ public class GantryShaftBlock extends DirectionalKineticBlock { return PlacementOffset.success(offset.getPos(), offset.getTransform() .andThen(s -> s.with(POWERED, state.get(POWERED)))); } - } } diff --git a/src/main/java/com/simibubi/create/content/contraptions/relays/advanced/SpeedControllerBlock.java b/src/main/java/com/simibubi/create/content/contraptions/relays/advanced/SpeedControllerBlock.java index 8d15808c4..caac99749 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/relays/advanced/SpeedControllerBlock.java +++ b/src/main/java/com/simibubi/create/content/contraptions/relays/advanced/SpeedControllerBlock.java @@ -7,7 +7,6 @@ import com.simibubi.create.content.contraptions.base.HorizontalAxisKineticBlock; import com.simibubi.create.content.contraptions.relays.elementary.CogWheelBlock; import com.simibubi.create.content.contraptions.relays.elementary.CogwheelBlockItem; import com.simibubi.create.foundation.block.ITE; -import com.simibubi.create.foundation.utility.VecHelper; import com.simibubi.create.foundation.utility.placement.IPlacementHelper; import com.simibubi.create.foundation.utility.placement.PlacementHelpers; import com.simibubi.create.foundation.utility.placement.PlacementOffset; @@ -20,7 +19,6 @@ import net.minecraft.item.BlockItemUseContext; import net.minecraft.item.ItemStack; import net.minecraft.tileentity.TileEntity; import net.minecraft.util.ActionResultType; -import net.minecraft.util.Direction; import net.minecraft.util.Direction.Axis; import net.minecraft.util.Hand; import net.minecraft.util.math.BlockPos; @@ -111,9 +109,11 @@ public class SpeedControllerBlock extends HorizontalAxisKineticBlock implements @Override public void renderAt(BlockPos pos, BlockState state, BlockRayTraceResult ray, PlacementOffset offset) { - IPlacementHelper.renderArrow(VecHelper.getCenterOf(pos), VecHelper.getCenterOf(offset.getPos()), - Direction.getFacingFromAxis(Direction.AxisDirection.POSITIVE, - state.get(HORIZONTAL_AXIS) == Axis.X ? Axis.Z : Axis.X)); + //IPlacementHelper.renderArrow(VecHelper.getCenterOf(pos), VecHelper.getCenterOf(offset.getPos()), + // Direction.getFacingFromAxis(Direction.AxisDirection.POSITIVE, + // state.get(HORIZONTAL_AXIS) == Axis.X ? Axis.Z : Axis.X)); + + displayGhost(offset); } } diff --git a/src/main/java/com/simibubi/create/content/contraptions/relays/elementary/CogwheelBlockItem.java b/src/main/java/com/simibubi/create/content/contraptions/relays/elementary/CogwheelBlockItem.java index 31a886453..968f729ab 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/relays/elementary/CogwheelBlockItem.java +++ b/src/main/java/com/simibubi/create/content/contraptions/relays/elementary/CogwheelBlockItem.java @@ -7,7 +7,6 @@ import com.simibubi.create.content.contraptions.base.HorizontalKineticBlock; import com.simibubi.create.content.contraptions.base.IRotate; import com.simibubi.create.foundation.advancement.AllTriggers; import com.simibubi.create.foundation.utility.Iterate; -import com.simibubi.create.foundation.utility.VecHelper; import com.simibubi.create.foundation.utility.placement.IPlacementHelper; import com.simibubi.create.foundation.utility.placement.PlacementHelpers; import com.simibubi.create.foundation.utility.placement.PlacementOffset; @@ -150,9 +149,11 @@ public class CogwheelBlockItem extends BlockItem { @Override public void renderAt(BlockPos pos, BlockState state, BlockRayTraceResult ray, PlacementOffset offset) { - IPlacementHelper.renderArrow(VecHelper.getCenterOf(pos), VecHelper.getCenterOf(offset.getPos()), - Direction.getFacingFromAxis(Direction.AxisDirection.POSITIVE, state.get(AXIS)), - ((CogWheelBlock) state.getBlock()).isLarge ? 1.5D : 0.75D); + //IPlacementHelper.renderArrow(VecHelper.getCenterOf(pos), VecHelper.getCenterOf(offset.getPos()), + // Direction.getFacingFromAxis(Direction.AxisDirection.POSITIVE, state.get(AXIS)), + // ((CogWheelBlock) state.getBlock()).isLarge ? 1.5D : 0.75D); + + displayGhost(offset); } } @@ -227,8 +228,10 @@ public class CogwheelBlockItem extends BlockItem { @Override public void renderAt(BlockPos pos, BlockState state, BlockRayTraceResult ray, PlacementOffset offset) { - IPlacementHelper.renderArrow(VecHelper.getCenterOf(pos), VecHelper.getCenterOf(offset.getPos()), - Direction.getFacingFromAxis(Direction.AxisDirection.POSITIVE, state.get(AXIS)), 1D); + //IPlacementHelper.renderArrow(VecHelper.getCenterOf(pos), VecHelper.getCenterOf(offset.getPos()), + // Direction.getFacingFromAxis(Direction.AxisDirection.POSITIVE, state.get(AXIS)), 1D); + + displayGhost(offset); } protected boolean hitOnShaft(BlockState state, BlockRayTraceResult ray) { @@ -320,10 +323,12 @@ public class CogwheelBlockItem extends BlockItem { @Override public void renderAt(BlockPos pos, BlockState state, BlockRayTraceResult ray, PlacementOffset offset) { - IPlacementHelper.renderArrow(VecHelper.getCenterOf(pos), VecHelper.getCenterOf(offset.getPos()), - Direction.getFacingFromAxis(Direction.AxisDirection.POSITIVE, offset.getTransform() - .apply(AllBlocks.LARGE_COGWHEEL.getDefaultState()) - .get(AXIS))); + //IPlacementHelper.renderArrow(VecHelper.getCenterOf(pos), VecHelper.getCenterOf(offset.getPos()), + // Direction.getFacingFromAxis(Direction.AxisDirection.POSITIVE, offset.getTransform() + // .apply(AllBlocks.LARGE_COGWHEEL.getDefaultState()) + // .get(AXIS))); + + displayGhost(offset); } } } diff --git a/src/main/java/com/simibubi/create/events/ClientEvents.java b/src/main/java/com/simibubi/create/events/ClientEvents.java index 6ff99a9dc..333166ffd 100644 --- a/src/main/java/com/simibubi/create/events/ClientEvents.java +++ b/src/main/java/com/simibubi/create/events/ClientEvents.java @@ -104,6 +104,7 @@ public class ClientEvents { ArmInteractionPointHandler.tick(); PlacementHelpers.tick(); CreateClient.outliner.tickOutlines(); + CreateClient.ghostBlocks.tickGhosts(); } @SubscribeEvent @@ -122,6 +123,8 @@ public class ClientEvents { CouplingRenderer.renderAll(ms, buffer); CreateClient.schematicHandler.render(ms, buffer); + CreateClient.ghostBlocks.renderAll(ms, buffer); + CreateClient.outliner.renderOutlines(ms, buffer); // CollisionDebugger.render(ms, buffer); buffer.draw(); diff --git a/src/main/java/com/simibubi/create/foundation/command/HighlightCommand.java b/src/main/java/com/simibubi/create/foundation/command/HighlightCommand.java index 6071c14f9..16a0e1528 100644 --- a/src/main/java/com/simibubi/create/foundation/command/HighlightCommand.java +++ b/src/main/java/com/simibubi/create/foundation/command/HighlightCommand.java @@ -24,19 +24,7 @@ public class HighlightCommand { public static ArgumentBuilder register() { return Commands.literal("highlight") .requires(cs -> cs.hasPermissionLevel(0)) - .requires(AllCommands.sourceIsPlayer) .then(Commands.argument("pos", BlockPosArgument.blockPos()) - .requires(AllCommands.sourceIsPlayer) - .executes(ctx -> { - BlockPos pos = BlockPosArgument.getLoadedBlockPos(ctx, "pos"); - - AllPackets.channel.send( - PacketDistributor.PLAYER.with(() -> (ServerPlayerEntity) ctx.getSource().getEntity()), - new HighlightPacket(pos) - ); - - return Command.SINGLE_SUCCESS; - }) .then(Commands.argument("players", EntityArgument.players()) .executes(ctx -> { Collection players = EntityArgument.getPlayers(ctx, "players"); @@ -52,7 +40,19 @@ public class HighlightCommand { return players.size(); }) ) + //.requires(AllCommands.sourceIsPlayer) + .executes(ctx -> { + BlockPos pos = BlockPosArgument.getLoadedBlockPos(ctx, "pos"); + + AllPackets.channel.send( + PacketDistributor.PLAYER.with(() -> (ServerPlayerEntity) ctx.getSource().getEntity()), + new HighlightPacket(pos) + ); + + return Command.SINGLE_SUCCESS; + }) ) + //.requires(AllCommands.sourceIsPlayer) .executes(ctx -> { ServerPlayerEntity player = ctx.getSource().asPlayer(); return highlightAssemblyExceptionFor(player, ctx.getSource()); diff --git a/src/main/java/com/simibubi/create/foundation/command/ToggleDebugCommand.java b/src/main/java/com/simibubi/create/foundation/command/ToggleDebugCommand.java index a12ec72d2..a6adfb9f7 100644 --- a/src/main/java/com/simibubi/create/foundation/command/ToggleDebugCommand.java +++ b/src/main/java/com/simibubi/create/foundation/command/ToggleDebugCommand.java @@ -1,9 +1,9 @@ package com.simibubi.create.foundation.command; +import com.mojang.brigadier.Command; import com.mojang.brigadier.arguments.BoolArgumentType; import com.mojang.brigadier.builder.ArgumentBuilder; import com.simibubi.create.foundation.networking.AllPackets; - import net.minecraft.command.CommandSource; import net.minecraft.command.Commands; import net.minecraft.entity.player.ServerPlayerEntity; @@ -30,7 +30,8 @@ public class ToggleDebugCommand { ctx.getSource().sendFeedback(new StringTextComponent((value ? "enabled" : "disabled") + " rainbow debug"), true); - return 1; - })); + return Command.SINGLE_SUCCESS; + }) + ); } } diff --git a/src/main/java/com/simibubi/create/foundation/config/CClient.java b/src/main/java/com/simibubi/create/foundation/config/CClient.java index b4aabef30..7954b2545 100644 --- a/src/main/java/com/simibubi/create/foundation/config/CClient.java +++ b/src/main/java/com/simibubi/create/foundation/config/CClient.java @@ -15,6 +15,7 @@ public class CClient extends ConfigBase { public ConfigInt overlayOffsetX = i(20, Integer.MIN_VALUE, Integer.MAX_VALUE, "overlayOffsetX", "Offset the overlay from goggle- and hover- information by this many pixels on the X axis; Use /create overlay"); public ConfigInt overlayOffsetY = i(0, Integer.MIN_VALUE, Integer.MAX_VALUE, "overlayOffsetY", "Offset the overlay from goggle- and hover- information by this many pixels on the Y axis; Use /create overlay"); + public ConfigBool smoothPlacementIndicator = b(false, "smoothPlacementIndicator", "Use an alternative indicator when showing where the assisted placement ends up relative to your crosshair"); @Override public String getName() { diff --git a/src/main/java/com/simibubi/create/foundation/utility/VecHelper.java b/src/main/java/com/simibubi/create/foundation/utility/VecHelper.java index d5cac157a..502e176a7 100644 --- a/src/main/java/com/simibubi/create/foundation/utility/VecHelper.java +++ b/src/main/java/com/simibubi/create/foundation/utility/VecHelper.java @@ -1,9 +1,11 @@ package com.simibubi.create.foundation.utility; -import java.util.Random; - -import javax.annotation.Nullable; - +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.ActiveRenderInfo; +import net.minecraft.client.renderer.Quaternion; +import net.minecraft.client.renderer.Vector3f; +import net.minecraft.entity.Entity; +import net.minecraft.entity.player.PlayerEntity; import net.minecraft.nbt.DoubleNBT; import net.minecraft.nbt.ListNBT; import net.minecraft.util.Direction; @@ -13,6 +15,9 @@ import net.minecraft.util.math.MathHelper; import net.minecraft.util.math.Vec3d; import net.minecraft.util.math.Vec3i; +import javax.annotation.Nullable; +import java.util.Random; + public class VecHelper { public static final Vec3d CENTER_OF_ORIGIN = new Vec3d(.5, .5, .5); @@ -145,4 +150,52 @@ public class VecHelper { return origin.add(lineDirection.scale(t)); } + //https://forums.minecraftforge.net/topic/88562-116solved-3d-to-2d-conversion/?do=findComment&comment=413573 slightly modified + public static Vec3d projectToPlayerView(Vec3d target, float partialTicks) { + /* The (centered) location on the screen of the given 3d point in the world. + * Result is (dist right of center screen, dist up from center screen, if < 0, then in front of view plane) */ + ActiveRenderInfo ari = Minecraft.getInstance().gameRenderer.getActiveRenderInfo(); + Vec3d camera_pos = ari.getProjectedView(); + Quaternion camera_rotation_conj = ari.getRotation().copy(); + camera_rotation_conj.conjugate(); + + Vector3f result3f = new Vector3f((float) (camera_pos.x - target.x), + (float) (camera_pos.y - target.y), + (float) (camera_pos.z - target.z)); + result3f.func_214905_a(camera_rotation_conj); + + // ----- compensate for view bobbing (if active) ----- + // the following code adapted from GameRenderer::applyBobbing (to invert it) + Minecraft mc = Minecraft.getInstance(); + if (mc.gameSettings.viewBobbing) { + Entity renderViewEntity = mc.getRenderViewEntity(); + if (renderViewEntity instanceof PlayerEntity) { + PlayerEntity playerentity = (PlayerEntity) renderViewEntity; + float distwalked_modified = playerentity.distanceWalkedModified; + + float f = distwalked_modified - playerentity.prevDistanceWalkedModified; + float f1 = -(distwalked_modified + f * partialTicks); + float f2 = MathHelper.lerp(partialTicks, playerentity.prevCameraYaw, playerentity.cameraYaw); + Quaternion q2 = new Quaternion(Vector3f.POSITIVE_X, Math.abs(MathHelper.cos(f1 * (float) Math.PI - 0.2F) * f2) * 5.0F, true); + q2.conjugate(); + result3f.func_214905_a(q2); + + Quaternion q1 = new Quaternion(Vector3f.POSITIVE_Z, MathHelper.sin(f1 * (float) Math.PI) * f2 * 3.0F, true); + q1.conjugate(); + result3f.func_214905_a(q1); + + Vector3f bob_translation = new Vector3f((MathHelper.sin(f1 * (float) Math.PI) * f2 * 0.5F), (-Math.abs(MathHelper.cos(f1 * (float) Math.PI) * f2)), 0.0f); + bob_translation.setY(-bob_translation.getY()); // this is weird but hey, if it works + result3f.add(bob_translation); + } + } + + // ----- adjust for fov ----- + float fov = (float) mc.gameRenderer.getFOVModifier(ari, partialTicks, true); + + float half_height = (float) mc.getWindow().getScaledHeight() / 2; + float scale_factor = half_height / (result3f.getZ() * (float) Math.tan(Math.toRadians(fov / 2))); + return new Vec3d(-result3f.getX() * scale_factor, result3f.getY() * scale_factor, result3f.getZ()); + } + } diff --git a/src/main/java/com/simibubi/create/foundation/utility/ghost/GhostBlockParams.java b/src/main/java/com/simibubi/create/foundation/utility/ghost/GhostBlockParams.java new file mode 100644 index 000000000..5723d19b1 --- /dev/null +++ b/src/main/java/com/simibubi/create/foundation/utility/ghost/GhostBlockParams.java @@ -0,0 +1,50 @@ +package com.simibubi.create.foundation.utility.ghost; + +import net.minecraft.block.Block; +import net.minecraft.block.BlockState; +import net.minecraft.util.math.BlockPos; + +import java.util.function.Supplier; + +public class GhostBlockParams { + + protected final BlockState state; + protected BlockPos pos; + protected Supplier alphaSupplier; + + private GhostBlockParams(BlockState state) { + this.state = state; + this.pos = BlockPos.ZERO; + this.alphaSupplier = () -> 1f; + } + + public static GhostBlockParams of(BlockState state) { + return new GhostBlockParams(state); + } + + public static GhostBlockParams of(Block block) { + return of(block.getDefaultState()); + } + + public GhostBlockParams at(BlockPos pos) { + this.pos = pos; + return this; + } + + public GhostBlockParams at(int x, int y, int z) { + return this.at(new BlockPos(x, y, z)); + } + + public GhostBlockParams alpha(Supplier alphaSupplier) { + this.alphaSupplier = alphaSupplier; + return this; + } + + public GhostBlockParams alpha(float alpha) { + return this.alpha(() -> alpha); + } + + public GhostBlockParams breathingAlpha() { + return this.alpha(() -> (float) GhostBlocks.getBreathingAlpha()); + } +} diff --git a/src/main/java/com/simibubi/create/foundation/utility/ghost/GhostBlockRenderer.java b/src/main/java/com/simibubi/create/foundation/utility/ghost/GhostBlockRenderer.java new file mode 100644 index 000000000..5f18c34d7 --- /dev/null +++ b/src/main/java/com/simibubi/create/foundation/utility/ghost/GhostBlockRenderer.java @@ -0,0 +1,172 @@ +package com.simibubi.create.foundation.utility.ghost; + +import com.mojang.blaze3d.matrix.MatrixStack; +import com.mojang.blaze3d.vertex.IVertexBuilder; +import com.simibubi.create.foundation.renderState.SuperRenderTypeBuffer; +import com.simibubi.create.foundation.utility.VirtualEmptyModelData; +import net.minecraft.block.BlockState; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.*; +import net.minecraft.client.renderer.model.BakedQuad; +import net.minecraft.client.renderer.model.IBakedModel; +import net.minecraft.client.renderer.texture.OverlayTexture; +import net.minecraft.client.renderer.vertex.DefaultVertexFormats; +import net.minecraft.util.Direction; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.MathHelper; +import net.minecraft.util.math.Vec3i; +import org.lwjgl.system.MemoryStack; + +import javax.annotation.Nullable; +import java.nio.Buffer; +import java.nio.ByteBuffer; +import java.nio.IntBuffer; +import java.util.List; +import java.util.Random; + +public abstract class GhostBlockRenderer { + + private static final GhostBlockRenderer transparent = new TransparentGhostBlockRenderer(); + public static GhostBlockRenderer transparent() { + return transparent; + } + + private static final GhostBlockRenderer standard = new DefaultGhostBlockRenderer(); + public static GhostBlockRenderer standard() { + return standard; + } + + + public abstract void render(MatrixStack ms, SuperRenderTypeBuffer buffer, GhostBlockParams params); + + private static class DefaultGhostBlockRenderer extends GhostBlockRenderer { + + public void render(MatrixStack ms, SuperRenderTypeBuffer buffer, GhostBlockParams params) { + ms.push(); + + BlockRendererDispatcher dispatcher = Minecraft.getInstance().getBlockRendererDispatcher(); + + IBakedModel model = dispatcher.getModelForState(params.state); + + RenderType layer = RenderTypeLookup.getEntityBlockLayer(params.state); + IVertexBuilder vb = buffer.getEarlyBuffer(layer); + + BlockPos pos = params.pos; + ms.translate(pos.getX(), pos.getY(), pos.getZ()); + + dispatcher.getBlockModelRenderer().renderModel(ms.peek(), vb, params.state, model, 1f, 1f, 1f, 0xF000F0, OverlayTexture.DEFAULT_UV, VirtualEmptyModelData.INSTANCE); + + ms.pop(); + } + + } + + private static class TransparentGhostBlockRenderer extends GhostBlockRenderer { + + public void render(MatrixStack ms, SuperRenderTypeBuffer buffer, GhostBlockParams params) { + + //prepare + ms.push(); + + //RenderSystem.pushMatrix(); + + BlockRendererDispatcher dispatcher = Minecraft.getInstance().getBlockRendererDispatcher(); + + IBakedModel model = dispatcher.getModelForState(params.state); + + //RenderType layer = RenderTypeLookup.getEntityBlockLayer(params.state); + RenderType layer = RenderType.getTranslucent(); + IVertexBuilder vb = buffer.getEarlyBuffer(layer); + + BlockPos pos = params.pos; + ms.translate(pos.getX(), pos.getY(), pos.getZ()); + + //dispatcher.getBlockModelRenderer().renderModel(ms.peek(), vb, params.state, model, 1f, 1f, 1f, 0xF000F0, OverlayTexture.DEFAULT_UV, VirtualEmptyModelData.INSTANCE); + renderModel(params, ms.peek(), vb, params.state, model, 1f, 1f, 1f, 0xF000F0, OverlayTexture.DEFAULT_UV, VirtualEmptyModelData.INSTANCE); + + //buffer.draw(); + //clean + //RenderSystem.popMatrix(); + ms.pop(); + + } + + //BlockModelRenderer + public void renderModel(GhostBlockParams params, MatrixStack.Entry entry, IVertexBuilder vb, @Nullable BlockState state, IBakedModel model, float p_228804_5_, float p_228804_6_, float p_228804_7_, int p_228804_8_, int p_228804_9_, net.minecraftforge.client.model.data.IModelData modelData) { + Random random = new Random(); + + for (Direction direction : Direction.values()) { + random.setSeed(42L); + renderQuad(params, entry, vb, p_228804_5_, p_228804_6_, p_228804_7_, model.getQuads(state, direction, random, modelData), p_228804_8_, p_228804_9_); + } + + random.setSeed(42L); + renderQuad(params, entry, vb, p_228804_5_, p_228804_6_, p_228804_7_, model.getQuads(state, (Direction) null, random, modelData), p_228804_8_, p_228804_9_); + } + + //BlockModelRenderer + private static void renderQuad(GhostBlockParams params, MatrixStack.Entry p_228803_0_, IVertexBuilder p_228803_1_, float p_228803_2_, float p_228803_3_, float p_228803_4_, List p_228803_5_, int p_228803_6_, int p_228803_7_) { + Float alpha = params.alphaSupplier.get(); + + for (BakedQuad bakedquad : p_228803_5_) { + float f; + float f1; + float f2; + if (bakedquad.hasTintIndex()) { + f = MathHelper.clamp(p_228803_2_, 0.0F, 1.0F); + f1 = MathHelper.clamp(p_228803_3_, 0.0F, 1.0F); + f2 = MathHelper.clamp(p_228803_4_, 0.0F, 1.0F); + } else { + f = 1.0F; + f1 = 1.0F; + f2 = 1.0F; + } + + quad(alpha, p_228803_1_, p_228803_0_, bakedquad, new float[]{1f, 1f, 1f, 1f}, f, f1, f2, new int[]{p_228803_6_, p_228803_6_, p_228803_6_, p_228803_6_}, p_228803_7_); + } + + } + + //IVertexBuilder + static void quad(float alpha, IVertexBuilder vb, MatrixStack.Entry p_227890_1_, BakedQuad p_227890_2_, float[] p_227890_3_, float p_227890_4_, float p_227890_5_, float p_227890_6_, int[] p_227890_7_, int p_227890_8_) { + int[] aint = p_227890_2_.getVertexData(); + Vec3i vec3i = p_227890_2_.getFace().getDirectionVec(); + Vector3f vector3f = new Vector3f((float) vec3i.getX(), (float) vec3i.getY(), (float) vec3i.getZ()); + Matrix4f matrix4f = p_227890_1_.getModel(); + vector3f.transform(p_227890_1_.getNormal()); + int i = 8; + int j = aint.length / 8; + + try (MemoryStack memorystack = MemoryStack.stackPush()) { + ByteBuffer bytebuffer = memorystack.malloc(DefaultVertexFormats.BLOCK.getSize()); + IntBuffer intbuffer = bytebuffer.asIntBuffer(); + + for (int k = 0; k < j; ++k) { + ((Buffer) intbuffer).clear(); + intbuffer.put(aint, k * 8, 8); + float f = bytebuffer.getFloat(0); + float f1 = bytebuffer.getFloat(4); + float f2 = bytebuffer.getFloat(8); + float r; + float g; + float b; + + r = p_227890_3_[k] * p_227890_4_; + g = p_227890_3_[k] * p_227890_5_; + b = p_227890_3_[k] * p_227890_6_; + + + int l = vb.applyBakedLighting(p_227890_7_[k], bytebuffer); + float f9 = bytebuffer.getFloat(16); + float f10 = bytebuffer.getFloat(20); + Vector4f vector4f = new Vector4f(f, f1, f2, 1.0F); + vector4f.transform(matrix4f); + vb.applyBakedNormals(vector3f, bytebuffer, p_227890_1_.getNormal()); + vb.vertex(vector4f.getX(), vector4f.getY(), vector4f.getZ(), r, g, b, alpha, f9, f10, p_227890_8_, l, vector3f.getX(), vector3f.getY(), vector3f.getZ()); + } + } + } + + } + +} diff --git a/src/main/java/com/simibubi/create/foundation/utility/ghost/GhostBlocks.java b/src/main/java/com/simibubi/create/foundation/utility/ghost/GhostBlocks.java new file mode 100644 index 000000000..01d31b076 --- /dev/null +++ b/src/main/java/com/simibubi/create/foundation/utility/ghost/GhostBlocks.java @@ -0,0 +1,82 @@ +package com.simibubi.create.foundation.utility.ghost; + +import com.mojang.blaze3d.matrix.MatrixStack; +import com.simibubi.create.foundation.renderState.SuperRenderTypeBuffer; +import net.minecraft.block.BlockState; + +import java.util.HashMap; +import java.util.Map; + +public class GhostBlocks { + + public static double getBreathingAlpha() { + double period = 2500; + double timer = System.currentTimeMillis() % period; + double offset = Math.cos((2d/period) * Math.PI * timer); + return 0.75d - 0.2d * offset; + } + + final Map ghosts; + + public GhostBlockParams showGhostState(Object slot, BlockState state) { + return showGhostState(slot, state, 1); + } + + public GhostBlockParams showGhostState(Object slot, BlockState state, int ttl) { + Entry e = refresh(slot, GhostBlockRenderer.transparent(), GhostBlockParams.of(state), ttl); + return e.params; + } + + public GhostBlockParams showGhost(Object slot, GhostBlockRenderer ghost, GhostBlockParams params, int ttl) { + Entry e = refresh(slot, ghost, params, ttl); + return e.params; + } + + private Entry refresh(Object slot, GhostBlockRenderer ghost, GhostBlockParams params, int ttl) { + if (!ghosts.containsKey(slot)) + ghosts.put(slot, new Entry(ghost, params, ttl)); + + Entry e = ghosts.get(slot); + e.ticksToLive = ttl; + e.params = params; + e.ghost = ghost; + return e; + } + + public GhostBlocks() { + ghosts = new HashMap<>(); + } + + public void tickGhosts() { + ghosts.forEach((slot, entry) -> entry.ticksToLive--); + ghosts.entrySet().removeIf(e -> !e.getValue().isAlive()); + } + + public void renderAll(MatrixStack ms, SuperRenderTypeBuffer buffer) { + ghosts.forEach((slot, entry) -> { + GhostBlockRenderer ghost = entry.ghost; + ghost.render(ms, buffer, entry.params); + }); + } + + static class Entry { + + private GhostBlockRenderer ghost; + private GhostBlockParams params; + private int ticksToLive; + + public Entry(GhostBlockRenderer ghost, GhostBlockParams params) { + this(ghost, params, 1); + } + + public Entry(GhostBlockRenderer ghost, GhostBlockParams params, int ttl) { + this.ghost = ghost; + this.params = params; + this.ticksToLive = ttl; + } + + public boolean isAlive() { + return ticksToLive >= 0; + } + } +} diff --git a/src/main/java/com/simibubi/create/foundation/utility/placement/IPlacementHelper.java b/src/main/java/com/simibubi/create/foundation/utility/placement/IPlacementHelper.java index 082776a7c..c74301353 100644 --- a/src/main/java/com/simibubi/create/foundation/utility/placement/IPlacementHelper.java +++ b/src/main/java/com/simibubi/create/foundation/utility/placement/IPlacementHelper.java @@ -7,6 +7,7 @@ import com.simibubi.create.foundation.utility.VecHelper; import mcp.MethodsReturnNonnullByDefault; import net.minecraft.block.BlockState; import net.minecraft.block.Blocks; +import net.minecraft.item.BlockItem; import net.minecraft.item.ItemStack; import net.minecraft.util.Direction; import net.minecraft.util.math.BlockPos; @@ -46,9 +47,21 @@ public interface IPlacementHelper { */ PlacementOffset getOffset(World world, BlockState state, BlockPos pos, BlockRayTraceResult ray); + //overrides the default ghost state of the helper with the actual state of the held block item, this is used in PlacementHelpers and can be ignored in most cases + default PlacementOffset getOffset(World world, BlockState state, BlockPos pos, BlockRayTraceResult ray, ItemStack heldItem) { + PlacementOffset offset = getOffset(world, state, pos, ray); + if (heldItem.getItem() instanceof BlockItem) { + BlockItem blockItem = (BlockItem) heldItem.getItem(); + offset = offset.withGhostState(blockItem.getBlock().getDefaultState()); + } + return offset; + } + //only gets called when placementOffset is successful default void renderAt(BlockPos pos, BlockState state, BlockRayTraceResult ray, PlacementOffset offset) { - IPlacementHelper.renderArrow(VecHelper.getCenterOf(pos), VecHelper.getCenterOf(offset.getPos()), ray.getFace()); + //IPlacementHelper.renderArrow(VecHelper.getCenterOf(pos), VecHelper.getCenterOf(offset.getPos()), ray.getFace()); + + displayGhost(offset); } static void renderArrow(Vec3d center, Vec3d target, Direction arrowPlane) { @@ -67,6 +80,15 @@ public interface IPlacementHelper { CreateClient.outliner.showLine("placementArrowB" + center + target, start.add(offset), endB.add(offset)).lineWidth(1/16f); } + default void displayGhost(PlacementOffset offset) { + if (!offset.hasGhostState()) + return; + + CreateClient.ghostBlocks.showGhostState(this, offset.getTransform().apply(offset.getGhostState())) + .at(offset.getBlockPos()) + .breathingAlpha(); + } + static List orderedByDistanceOnlyAxis(BlockPos pos, Vec3d hit, Direction.Axis axis) { return orderedByDistance(pos, hit, dir -> dir.getAxis() == axis); } diff --git a/src/main/java/com/simibubi/create/foundation/utility/placement/PlacementHelpers.java b/src/main/java/com/simibubi/create/foundation/utility/placement/PlacementHelpers.java index 7645ef75f..0026d5fe6 100644 --- a/src/main/java/com/simibubi/create/foundation/utility/placement/PlacementHelpers.java +++ b/src/main/java/com/simibubi/create/foundation/utility/placement/PlacementHelpers.java @@ -1,22 +1,43 @@ package com.simibubi.create.foundation.utility.placement; +import com.mojang.blaze3d.systems.RenderSystem; +import com.simibubi.create.foundation.config.AllConfigs; +import com.simibubi.create.foundation.gui.widgets.InterpolatedChasingAngle; +import com.simibubi.create.foundation.gui.widgets.InterpolatedChasingValue; +import com.simibubi.create.foundation.utility.AngleHelper; +import com.simibubi.create.foundation.utility.VecHelper; import net.minecraft.block.BlockState; +import net.minecraft.client.MainWindow; import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.BufferBuilder; +import net.minecraft.client.renderer.Tessellator; +import net.minecraft.client.renderer.vertex.DefaultVertexFormats; import net.minecraft.client.world.ClientWorld; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.ItemStack; import net.minecraft.util.Hand; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockRayTraceResult; +import net.minecraft.util.math.Vec3d; import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.api.distmarker.OnlyIn; +import net.minecraftforge.client.event.RenderGameOverlayEvent; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.fml.common.Mod; +import org.lwjgl.opengl.GL11; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; +@Mod.EventBusSubscriber public class PlacementHelpers { private static final List helpers = new ArrayList<>(); + private static int animationTick = 0; + private static final InterpolatedChasingValue angle = new InterpolatedChasingAngle().withSpeed(0.15f); + private static BlockPos target = null; + private static BlockPos lastTarget = null; public static int register(IPlacementHelper helper) { helpers.add(helper); @@ -32,6 +53,23 @@ public class PlacementHelpers { @OnlyIn(Dist.CLIENT) public static void tick() { + setTarget(null); + checkHelpers(); + + if (target == null) { + if (animationTick > 0) + animationTick = Math.max(animationTick - 2, 0); + + return; + } + + if (animationTick < 10) + animationTick++; + + } + + @OnlyIn(Dist.CLIENT) + private static void checkHelpers() { Minecraft mc = Minecraft.getInstance(); ClientWorld world = mc.world; @@ -46,29 +84,154 @@ public class PlacementHelpers { if (mc.player == null) return; - List filteredForHeldItem = helpers.stream().filter(helper -> Arrays.stream(Hand.values()).anyMatch(hand -> helper.getItemPredicate().test(mc.player.getHeldItem(hand)))).collect(Collectors.toList()); - if (filteredForHeldItem.isEmpty()) - return; - if (mc.player.isSneaking())//for now, disable all helpers when sneaking TODO add helpers that respect sneaking but still show position return; - BlockPos pos = ray.getPos(); - BlockState state = world.getBlockState(pos); + for (Hand hand : Hand.values()) { - List filteredForState = filteredForHeldItem.stream().filter(helper -> helper.getStatePredicate().test(state)).collect(Collectors.toList()); + ItemStack heldItem = mc.player.getHeldItem(hand); + List filteredForHeldItem = helpers.stream().filter(helper -> helper.matchesItem(heldItem)).collect(Collectors.toList()); + if (filteredForHeldItem.isEmpty()) + continue; - if (filteredForState.isEmpty()) - return; + BlockPos pos = ray.getPos(); + BlockState state = world.getBlockState(pos); - for (IPlacementHelper h : filteredForState) { - PlacementOffset offset = h.getOffset(world, state, pos, ray); + List filteredForState = filteredForHeldItem.stream().filter(helper -> helper.matchesState(state)).collect(Collectors.toList()); + if (filteredForState.isEmpty()) + continue; + + boolean atLeastOneMatch = false; + for (IPlacementHelper h : filteredForState) { + PlacementOffset offset = h.getOffset(world, state, pos, ray, heldItem); + + if (offset.isSuccessful()) { + h.renderAt(pos, state, ray, offset); + setTarget(offset.getBlockPos()); + atLeastOneMatch = true; + break; + } - if (offset.isSuccessful()) { - h.renderAt(pos, state, ray, offset); - break; } + //at least one helper activated, no need to check the offhand if we are still in the mainhand + if (atLeastOneMatch) + return; + } } + + static void setTarget(BlockPos target) { + PlacementHelpers.target = target; + + if (target == null) + return; + + if (lastTarget == null) { + lastTarget = target; + return; + } + + if (!lastTarget.equals(target)) + lastTarget = target; + } + + @SubscribeEvent + @OnlyIn(Dist.CLIENT) + public static void onRender(RenderGameOverlayEvent.Pre event) { + if (event.getType() != RenderGameOverlayEvent.ElementType.CROSSHAIRS) + return; + + Minecraft mc = Minecraft.getInstance(); + PlayerEntity player = mc.player; + + if (player != null && animationTick > 0) { + MainWindow res = event.getWindow(); + //MatrixStack matrix = event.getMatrix(); + //String text = "( )"; + + //matrix.push(); + //matrix.translate(res.getScaledWidth() / 2F, res.getScaledHeight() / 2f - 4, 0); + float screenY = res.getScaledHeight() / 2f; + float screenX = res.getScaledWidth() / 2f; + //float y = screenY - 3.5f; + //float x = screenX; + //x -= mc.fontRenderer.getStringWidth(text)/2f - 0.25f; + + float progress = Math.min(animationTick / 10f/* + event.getPartialTicks()*/, 1f); + //int opacity = ((int) (255 * (progress * progress))) << 24; + + //mc.fontRenderer.drawString(text, x, y, 0xFFFFFF | opacity); + + boolean flag = AllConfigs.CLIENT.smoothPlacementIndicator.get(); + if (flag) + drawDirectionIndicator(event.getPartialTicks(), screenX, screenY, progress); + + else { + //TODO find something more in style + } + //matrix.pop(); + } + } + + @OnlyIn(Dist.CLIENT) + private static void drawDirectionIndicator(float partialTicks, float centerX, float centerY, float progress) { + float r = .8f; + float g = .8f; + float b = .8f; + float a = progress * progress; + + Vec3d projTarget = VecHelper.projectToPlayerView(VecHelper.getCenterOf(lastTarget), partialTicks); + + Vec3d target = new Vec3d(projTarget.x, projTarget.y, 0); + Vec3d norm = target.normalize(); + Vec3d ref = new Vec3d(0, 1, 0); + float targetAngle = AngleHelper.deg(Math.acos(norm.dotProduct(ref))); + + if (norm.x > 0) { + targetAngle = 360 - targetAngle; + } + + if (animationTick < 10) + angle.set(targetAngle); + + angle.target(targetAngle); + angle.tick(); + + + float length = 10; + //TOD O if the target is off screen, use length to show a meaningful distance + + RenderSystem.pushMatrix(); + RenderSystem.disableTexture(); + RenderSystem.enableBlend(); + RenderSystem.disableAlphaTest(); + RenderSystem.defaultBlendFunc(); + RenderSystem.shadeModel(GL11.GL_SMOOTH); + + RenderSystem.translated(centerX, centerY, 0); + RenderSystem.rotatef(angle.get(0.1f), 0, 0, -1); + //RenderSystem.scaled(3, 3, 3); + + Tessellator tessellator = Tessellator.getInstance(); + BufferBuilder bufferbuilder = tessellator.getBuffer(); + bufferbuilder.begin(GL11.GL_POLYGON, DefaultVertexFormats.POSITION_COLOR); + + bufferbuilder.vertex(0, - (10 + length), 0).color(r, g, b, a).endVertex(); + + bufferbuilder.vertex(-9, -3, 0).color(r, g, b, 0f).endVertex(); + bufferbuilder.vertex(-6, -6, 0).color(r, g, b, 0f).endVertex(); + bufferbuilder.vertex(-3, -8, 0).color(r, g, b, 0f).endVertex(); + bufferbuilder.vertex(0, -8.5, 0).color(r, g, b, 0f).endVertex(); + bufferbuilder.vertex(3, -8, 0).color(r, g, b, 0f).endVertex(); + bufferbuilder.vertex(6, -6, 0).color(r, g, b, 0f).endVertex(); + bufferbuilder.vertex(9, -3, 0).color(r, g, b, 0f).endVertex(); + + tessellator.draw(); + RenderSystem.shadeModel(GL11.GL_FLAT); + RenderSystem.disableBlend(); + RenderSystem.enableAlphaTest(); + RenderSystem.enableTexture(); + RenderSystem.popMatrix(); + } } diff --git a/src/main/java/com/simibubi/create/foundation/utility/placement/PlacementOffset.java b/src/main/java/com/simibubi/create/foundation/utility/placement/PlacementOffset.java index e9d50d6d5..d6251384e 100644 --- a/src/main/java/com/simibubi/create/foundation/utility/placement/PlacementOffset.java +++ b/src/main/java/com/simibubi/create/foundation/utility/placement/PlacementOffset.java @@ -27,25 +27,46 @@ import java.util.function.Function; public class PlacementOffset { private final boolean success; - private final Vec3i pos; - private final Function stateTransform; + private Vec3i pos; + private Function stateTransform; + private BlockState ghostState; - private PlacementOffset(boolean success, Vec3i pos, Function transform) { + private PlacementOffset(boolean success) { this.success = success; - this.pos = pos; - this.stateTransform = transform == null ? Function.identity() : transform; + this.pos = BlockPos.ZERO; + this.stateTransform = Function.identity(); + this.ghostState = null; } public static PlacementOffset fail() { - return new PlacementOffset(false, Vec3i.NULL_VECTOR, null); + return new PlacementOffset(false); + } + + public static PlacementOffset success() { + return new PlacementOffset(true); } public static PlacementOffset success(Vec3i pos) { - return new PlacementOffset(true, pos, null); + return success().at(pos); } public static PlacementOffset success(Vec3i pos, Function transform) { - return new PlacementOffset(true, pos, transform); + return success().at(pos).withTransform(transform); + } + + public PlacementOffset at(Vec3i pos) { + this.pos = pos; + return this; + } + + public PlacementOffset withTransform(Function stateTransform) { + this.stateTransform = stateTransform; + return this; + } + + public PlacementOffset withGhostState(BlockState ghostState) { + this.ghostState = ghostState; + return this; } public boolean isSuccessful() { @@ -56,10 +77,25 @@ public class PlacementOffset { return pos; } + public BlockPos getBlockPos() { + if (pos instanceof BlockPos) + return (BlockPos) pos; + + return new BlockPos(pos); + } + public Function getTransform() { return stateTransform; } + public boolean hasGhostState() { + return ghostState != null; + } + + public BlockState getGhostState() { + return ghostState; + } + public boolean isReplaceable(World world) { if (!success) return false; @@ -69,16 +105,15 @@ public class PlacementOffset { public ActionResultType placeInWorld(World world, BlockItem blockItem, PlayerEntity player, Hand hand, BlockRayTraceResult ray) { - ItemUseContext context = new ItemUseContext(player, hand, ray); + if (!isReplaceable(world)) + return ActionResultType.PASS; + ItemUseContext context = new ItemUseContext(player, hand, ray); BlockPos newPos = new BlockPos(pos); if (!world.isBlockModifiable(player, newPos)) return ActionResultType.PASS; - if (!isReplaceable(world)) - return ActionResultType.PASS; - BlockState state = stateTransform.apply(blockItem.getBlock().getDefaultState()); if (state.has(BlockStateProperties.WATERLOGGED)) { IFluidState fluidState = world.getFluidState(newPos); diff --git a/src/main/java/com/simibubi/create/foundation/utility/placement/util/PoleHelper.java b/src/main/java/com/simibubi/create/foundation/utility/placement/util/PoleHelper.java index f074a8d5c..fef9b0783 100644 --- a/src/main/java/com/simibubi/create/foundation/utility/placement/util/PoleHelper.java +++ b/src/main/java/com/simibubi/create/foundation/utility/placement/util/PoleHelper.java @@ -1,6 +1,5 @@ package com.simibubi.create.foundation.utility.placement.util; -import com.simibubi.create.foundation.utility.VecHelper; import com.simibubi.create.foundation.utility.placement.IPlacementHelper; import com.simibubi.create.foundation.utility.placement.PlacementOffset; import mcp.MethodsReturnNonnullByDefault; @@ -9,7 +8,6 @@ import net.minecraft.state.IProperty; import net.minecraft.util.Direction; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockRayTraceResult; -import net.minecraft.util.math.Vec3d; import net.minecraft.world.World; import java.util.List; @@ -71,7 +69,9 @@ public abstract class PoleHelper> implements IPlacementH @Override public void renderAt(BlockPos pos, BlockState state, BlockRayTraceResult ray, PlacementOffset offset) { - Vec3d centerOffset = new Vec3d(ray.getFace().getDirectionVec()).scale(.3); - IPlacementHelper.renderArrow(VecHelper.getCenterOf(pos).add(centerOffset), VecHelper.getCenterOf(offset.getPos()).add(centerOffset), ray.getFace(), 0.75D); + //Vec3d centerOffset = new Vec3d(ray.getFace().getDirectionVec()).scale(.3); + //IPlacementHelper.renderArrow(VecHelper.getCenterOf(pos).add(centerOffset), VecHelper.getCenterOf(offset.getPos()).add(centerOffset), ray.getFace(), 0.75D); + + displayGhost(offset); } } diff --git a/src/main/resources/META-INF/accesstransformer.cfg b/src/main/resources/META-INF/accesstransformer.cfg index 98de531a9..22c960e77 100644 --- a/src/main/resources/META-INF/accesstransformer.cfg +++ b/src/main/resources/META-INF/accesstransformer.cfg @@ -23,4 +23,7 @@ public net.minecraft.tileentity.BeaconTileEntity field_174909_f # beamSegments # Server Tick List (For stopping placed fluids from spilling) public net.minecraft.world.server.ServerTickList field_205374_d # pendingTickListEntriesHashSet -public net.minecraft.world.server.ServerTickList field_205375_e # pendingTickListEntriesTreeSet \ No newline at end of file +public net.minecraft.world.server.ServerTickList field_205375_e # pendingTickListEntriesTreeSet + +# GameRenderer +public net.minecraft.client.renderer.GameRenderer func_215311_a(Lnet/minecraft/client/renderer/ActiveRenderInfo;FZ)D #getFOVModifier \ No newline at end of file From bec13b8e7db02bd18f586456e3f1fcefce860fa9 Mon Sep 17 00:00:00 2001 From: Zelophed Date: Tue, 16 Feb 2021 22:50:11 +0100 Subject: [PATCH 2/3] Assisted Placement, Part IV-b --- .../create/foundation/gui/AllGuiTextures.java | 4 +- .../foundation/utility/ghost/GhostBlocks.java | 3 +- .../utility/placement/PlacementHelpers.java | 81 +++++++++++++++--- .../textures/gui/placement_indicator.png | Bin 0 -> 9385 bytes 4 files changed, 75 insertions(+), 13 deletions(-) create mode 100644 src/main/resources/assets/create/textures/gui/placement_indicator.png diff --git a/src/main/java/com/simibubi/create/foundation/gui/AllGuiTextures.java b/src/main/java/com/simibubi/create/foundation/gui/AllGuiTextures.java index 5528f791b..520cde09e 100644 --- a/src/main/java/com/simibubi/create/foundation/gui/AllGuiTextures.java +++ b/src/main/java/com/simibubi/create/foundation/gui/AllGuiTextures.java @@ -1,7 +1,6 @@ package com.simibubi.create.foundation.gui; import com.simibubi.create.Create; - import net.minecraft.client.Minecraft; import net.minecraft.client.gui.AbstractGui; import net.minecraft.client.gui.screen.Screen; @@ -79,6 +78,9 @@ public enum AllGuiTextures { INDICATOR_YELLOW("widgets.png", 54, 18, 18, 6), INDICATOR_RED("widgets.png", 72, 18, 18, 6), + // PlacementIndicator + PLACEMENT_INDICATOR_SHEET("placement_indicator.png", 0, 0, 256, 256); + ; public static final int FONT_COLOR = 0x575F7A; diff --git a/src/main/java/com/simibubi/create/foundation/utility/ghost/GhostBlocks.java b/src/main/java/com/simibubi/create/foundation/utility/ghost/GhostBlocks.java index 01d31b076..d5ae5d175 100644 --- a/src/main/java/com/simibubi/create/foundation/utility/ghost/GhostBlocks.java +++ b/src/main/java/com/simibubi/create/foundation/utility/ghost/GhostBlocks.java @@ -3,6 +3,7 @@ package com.simibubi.create.foundation.utility.ghost; import com.mojang.blaze3d.matrix.MatrixStack; import com.simibubi.create.foundation.renderState.SuperRenderTypeBuffer; import net.minecraft.block.BlockState; +import net.minecraft.util.math.MathHelper; import java.util.HashMap; import java.util.Map; @@ -12,7 +13,7 @@ public class GhostBlocks { public static double getBreathingAlpha() { double period = 2500; double timer = System.currentTimeMillis() % period; - double offset = Math.cos((2d/period) * Math.PI * timer); + double offset = MathHelper.cos((float) ((2d/period) * Math.PI * timer)); return 0.75d - 0.2d * offset; } diff --git a/src/main/java/com/simibubi/create/foundation/utility/placement/PlacementHelpers.java b/src/main/java/com/simibubi/create/foundation/utility/placement/PlacementHelpers.java index 0026d5fe6..002bc3e49 100644 --- a/src/main/java/com/simibubi/create/foundation/utility/placement/PlacementHelpers.java +++ b/src/main/java/com/simibubi/create/foundation/utility/placement/PlacementHelpers.java @@ -2,6 +2,7 @@ package com.simibubi.create.foundation.utility.placement; import com.mojang.blaze3d.systems.RenderSystem; import com.simibubi.create.foundation.config.AllConfigs; +import com.simibubi.create.foundation.gui.AllGuiTextures; import com.simibubi.create.foundation.gui.widgets.InterpolatedChasingAngle; import com.simibubi.create.foundation.gui.widgets.InterpolatedChasingValue; import com.simibubi.create.foundation.utility.AngleHelper; @@ -35,7 +36,7 @@ public class PlacementHelpers { private static final List helpers = new ArrayList<>(); private static int animationTick = 0; - private static final InterpolatedChasingValue angle = new InterpolatedChasingAngle().withSpeed(0.15f); + private static final InterpolatedChasingValue angle = new InterpolatedChasingAngle().withSpeed(0.25f); private static BlockPos target = null; private static BlockPos lastTarget = null; @@ -163,13 +164,7 @@ public class PlacementHelpers { //mc.fontRenderer.drawString(text, x, y, 0xFFFFFF | opacity); - boolean flag = AllConfigs.CLIENT.smoothPlacementIndicator.get(); - if (flag) - drawDirectionIndicator(event.getPartialTicks(), screenX, screenY, progress); - - else { - //TODO find something more in style - } + drawDirectionIndicator(event.getPartialTicks(), screenX, screenY, progress); //matrix.pop(); } } @@ -184,11 +179,17 @@ public class PlacementHelpers { Vec3d projTarget = VecHelper.projectToPlayerView(VecHelper.getCenterOf(lastTarget), partialTicks); Vec3d target = new Vec3d(projTarget.x, projTarget.y, 0); + if (projTarget.z > 0) { + target = target.inverse(); + } + Vec3d norm = target.normalize(); Vec3d ref = new Vec3d(0, 1, 0); float targetAngle = AngleHelper.deg(Math.acos(norm.dotProduct(ref))); - if (norm.x > 0) { + angle.withSpeed(0.25f); + + if (norm.x < 0) { targetAngle = 360 - targetAngle; } @@ -198,10 +199,21 @@ public class PlacementHelpers { angle.target(targetAngle); angle.tick(); + float snapSize = 22.5f; + float snappedAngle = (snapSize * Math.round(angle.get(0f) / snapSize)) % 360f; float length = 10; //TOD O if the target is off screen, use length to show a meaningful distance + boolean flag = AllConfigs.CLIENT.smoothPlacementIndicator.get(); + if (flag) + fadedArrow(centerX, centerY, r, g, b, a, length, snappedAngle); + + else + textured(centerX, centerY, a, snappedAngle); + } + + private static void fadedArrow(float centerX, float centerY, float r, float g, float b, float a, float length, float snappedAngle) { RenderSystem.pushMatrix(); RenderSystem.disableTexture(); RenderSystem.enableBlend(); @@ -210,8 +222,8 @@ public class PlacementHelpers { RenderSystem.shadeModel(GL11.GL_SMOOTH); RenderSystem.translated(centerX, centerY, 0); - RenderSystem.rotatef(angle.get(0.1f), 0, 0, -1); - //RenderSystem.scaled(3, 3, 3); + RenderSystem.rotatef(angle.get(0), 0, 0, 1); + //RenderSystem.rotatef(snappedAngle, 0, 0, 1); Tessellator tessellator = Tessellator.getInstance(); BufferBuilder bufferbuilder = tessellator.getBuffer(); @@ -234,4 +246,51 @@ public class PlacementHelpers { RenderSystem.enableTexture(); RenderSystem.popMatrix(); } + + private static void textured(float centerX, float centerY, float alpha, float snappedAngle) { + RenderSystem.pushMatrix(); + RenderSystem.enableTexture(); + //RenderSystem.disableTexture(); + AllGuiTextures.PLACEMENT_INDICATOR_SHEET.bind(); + RenderSystem.enableBlend(); + //RenderSystem.disableAlphaTest(); + RenderSystem.enableAlphaTest(); + RenderSystem.defaultBlendFunc(); + RenderSystem.color4f(1f, 1f, 1f, 1f); + RenderSystem.shadeModel(GL11.GL_SMOOTH); + + RenderSystem.translated(centerX, centerY, 0); + //RenderSystem.rotatef(angle.get(0.1f), 0, 0, -1); + //RenderSystem.translated(0, 10, 0); + //RenderSystem.rotatef(angle.get(0.1f), 0, 0, 1); + RenderSystem.scaled(12, 12, 0); + + float index = snappedAngle / 22.5f; + float tex_size = 16f/256f; + + float tx = 0; + float ty = index * tex_size; + float tw = 1f; + float th = tex_size; + + Tessellator tessellator = Tessellator.getInstance(); + BufferBuilder buffer = tessellator.getBuffer(); + buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_COLOR_TEXTURE); + + + buffer.vertex(-1, -1, 0).color(1f, 1f, 1f, alpha).texture(tx, ty).endVertex(); + buffer.vertex(-1, 1, 0).color(1f, 1f, 1f, alpha).texture(tx, ty + th).endVertex(); + buffer.vertex(1, 1, 0).color(1f, 1f, 1f, alpha).texture(tx + tw, ty + th).endVertex(); + buffer.vertex(1, -1, 0).color(1f, 1f, 1f, alpha).texture(tx + tw, ty).endVertex(); + + tessellator.draw(); + RenderSystem.shadeModel(GL11.GL_FLAT); + + //RenderSystem.enableTexture(); + + RenderSystem.disableBlend(); + //RenderSystem.enableAlphaTest(); + RenderSystem.popMatrix(); + } + } diff --git a/src/main/resources/assets/create/textures/gui/placement_indicator.png b/src/main/resources/assets/create/textures/gui/placement_indicator.png new file mode 100644 index 0000000000000000000000000000000000000000..cb53a47fcb3c027831ef62e2a077a255fb5ddeaf GIT binary patch literal 9385 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 literal 0 HcmV?d00001 From 159e298e8b1235811eb9255f56d1bbce7a71c06c Mon Sep 17 00:00:00 2001 From: Zelophed Date: Wed, 17 Feb 2021 02:34:55 +0100 Subject: [PATCH 3/3] paletted contraptions - use minecraft's palette-map during serialization and deserialization to reduce compound size in most cases - also update the placement_indicator sprite sheet --- .../structureMovement/Contraption.java | 159 ++++++++++++------ .../create/foundation/gui/AllGuiTextures.java | 2 +- .../resources/META-INF/accesstransformer.cfg | 5 +- .../textures/gui/placement_indicator.png | Bin 9385 -> 9488 bytes 4 files changed, 110 insertions(+), 56 deletions(-) diff --git a/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/Contraption.java b/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/Contraption.java index 6fc93a511..45638b9a3 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/Contraption.java +++ b/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/Contraption.java @@ -28,11 +28,7 @@ import com.simibubi.create.content.logistics.block.inventories.AdjustableCrateBl import com.simibubi.create.content.logistics.block.redstone.RedstoneContactBlock; import com.simibubi.create.foundation.config.AllConfigs; import com.simibubi.create.foundation.fluid.CombinedTankWrapper; -import com.simibubi.create.foundation.utility.BlockFace; -import com.simibubi.create.foundation.utility.Iterate; -import com.simibubi.create.foundation.utility.NBTHelper; -import com.simibubi.create.foundation.utility.NBTProcessors; -import com.simibubi.create.foundation.utility.VecHelper; +import com.simibubi.create.foundation.utility.*; import com.simibubi.create.foundation.utility.worldWrappers.WrappedWorld; import net.minecraft.block.*; import net.minecraft.block.material.PushReaction; @@ -41,6 +37,7 @@ import net.minecraft.fluid.Fluids; import net.minecraft.fluid.IFluidState; import net.minecraft.item.ItemStack; import net.minecraft.nbt.CompoundNBT; +import net.minecraft.nbt.INBT; import net.minecraft.nbt.ListNBT; import net.minecraft.nbt.NBTUtil; import net.minecraft.state.properties.BlockStateProperties; @@ -55,6 +52,7 @@ import net.minecraft.util.Rotation; import net.minecraft.util.math.AxisAlignedBB; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.Vec3d; +import net.minecraft.util.palette.PaletteHashMap; import net.minecraft.world.IWorld; import net.minecraft.world.World; import net.minecraft.world.gen.feature.template.Template.BlockInfo; @@ -67,6 +65,7 @@ import net.minecraftforge.fluids.capability.IFluidHandler.FluidAction; import net.minecraftforge.fluids.capability.templates.FluidTank; import net.minecraftforge.items.IItemHandlerModifiable; import net.minecraftforge.items.wrapper.CombinedInvWrapper; +import net.minecraftforge.registries.GameData; import org.apache.commons.lang3.tuple.MutablePair; import org.apache.commons.lang3.tuple.Pair; @@ -595,51 +594,16 @@ public abstract class Contraption { presentTileEntities.clear(); renderedTileEntities.clear(); - nbt.getList("Blocks", 10) - .forEach(c -> { - CompoundNBT comp = (CompoundNBT) c; - BlockInfo info = new BlockInfo(NBTUtil.readBlockPos(comp.getCompound("Pos")), - NBTUtil.readBlockState(comp.getCompound("Block")), - comp.contains("Data") ? comp.getCompound("Data") : null); - blocks.put(info.pos, info); - - if (world.isRemote) { - Block block = info.state.getBlock(); - CompoundNBT tag = info.nbt; - MovementBehaviour movementBehaviour = AllMovementBehaviours.of(block); - if (tag == null || (movementBehaviour != null && movementBehaviour.hasSpecialMovementRenderer())) - return; - - tag.putInt("x", info.pos.getX()); - tag.putInt("y", info.pos.getY()); - tag.putInt("z", info.pos.getZ()); - - TileEntity te = TileEntity.create(tag); - if (te == null) - return; - te.setLocation(new WrappedWorld(world) { - - @Override - public BlockState getBlockState(BlockPos pos) { - if (!pos.equals(te.getPos())) - return Blocks.AIR.getDefaultState(); - return info.state; - } - - }, te.getPos()); - if (te instanceof KineticTileEntity) - ((KineticTileEntity) te).setSpeed(0); - te.getBlockState(); - presentTileEntities.put(info.pos, te); - renderedTileEntities.add(te); - } - }); + INBT blocks = nbt.get("Blocks"); + //used to differentiate between the 'old' and the paletted serialization + boolean usePalettedDeserialization = blocks != null && blocks.getId() == 10 && ((CompoundNBT) blocks).contains("Palette"); + readBlocksCompound(blocks, world, usePalettedDeserialization); actors.clear(); nbt.getList("Actors", 10) .forEach(c -> { CompoundNBT comp = (CompoundNBT) c; - BlockInfo info = blocks.get(NBTUtil.readBlockPos(comp.getCompound("Pos"))); + BlockInfo info = this.blocks.get(NBTUtil.readBlockPos(comp.getCompound("Pos"))); MovementContext context = MovementContext.readNBT(world, info, comp, this); getActors().add(MutablePair.of(info, context)); }); @@ -704,15 +668,8 @@ public abstract class Contraption { public CompoundNBT writeNBT(boolean spawnPacket) { CompoundNBT nbt = new CompoundNBT(); nbt.putString("Type", getType().id); - ListNBT blocksNBT = new ListNBT(); - for (BlockInfo block : this.blocks.values()) { - CompoundNBT c = new CompoundNBT(); - c.put("Block", NBTUtil.writeBlockState(block.state)); - c.put("Pos", NBTUtil.writeBlockPos(block.pos)); - if (block.nbt != null) - c.put("Data", block.nbt); - blocksNBT.add(c); - } + + CompoundNBT blocksNBT = writeBlocksCompound(); ListNBT actorsNBT = new ListNBT(); for (MutablePair actor : getActors()) { @@ -789,6 +746,100 @@ public abstract class Contraption { return nbt; } + private CompoundNBT writeBlocksCompound() { + CompoundNBT compound = new CompoundNBT(); + PaletteHashMap palette = new PaletteHashMap<>(GameData.getBlockStateIDMap(), 16, (i, s) -> {throw new IllegalStateException("Palette Map index exceeded maximum");}, NBTUtil::readBlockState, NBTUtil::writeBlockState); + ListNBT blockList = new ListNBT(); + + for (BlockInfo block : this.blocks.values()) { + int id = palette.idFor(block.state); + CompoundNBT c = new CompoundNBT(); + c.putLong("Pos", block.pos.toLong()); + c.putInt("State", id); + if (block.nbt != null) + c.put("Data", block.nbt); + blockList.add(c); + } + + ListNBT paletteNBT = new ListNBT(); + palette.writePaletteToList(paletteNBT); + compound.put("Palette", paletteNBT); + compound.put("BlockList", blockList); + + return compound; + } + + private void readBlocksCompound(INBT compound, World world, boolean usePalettedDeserialization) { + PaletteHashMap palette = null; + ListNBT blockList; + if (usePalettedDeserialization) { + CompoundNBT c = ((CompoundNBT) compound); + palette = new PaletteHashMap<>(GameData.getBlockStateIDMap(), 16, (i, s) -> {throw new IllegalStateException("Palette Map index exceeded maximum");}, NBTUtil::readBlockState, NBTUtil::writeBlockState); + palette.read(c.getList("Palette", 10)); + + blockList = c.getList("BlockList", 10); + } else { + blockList = (ListNBT) compound; + } + + PaletteHashMap finalPalette = palette; + blockList.forEach(e -> { + CompoundNBT c = (CompoundNBT) e; + + BlockInfo info = usePalettedDeserialization ? readBlockInfo(c, finalPalette) : legacyReadBlockInfo(c); + + this.blocks.put(info.pos, info); + + if (world.isRemote) { + Block block = info.state.getBlock(); + CompoundNBT tag = info.nbt; + MovementBehaviour movementBehaviour = AllMovementBehaviours.of(block); + if (tag == null || (movementBehaviour != null && movementBehaviour.hasSpecialMovementRenderer())) + return; + + tag.putInt("x", info.pos.getX()); + tag.putInt("y", info.pos.getY()); + tag.putInt("z", info.pos.getZ()); + + TileEntity te = TileEntity.create(tag); + if (te == null) + return; + te.setLocation(new WrappedWorld(world) { + + @Override + public BlockState getBlockState(BlockPos pos) { + if (!pos.equals(te.getPos())) + return Blocks.AIR.getDefaultState(); + return info.state; + } + + }, te.getPos()); + if (te instanceof KineticTileEntity) + ((KineticTileEntity) te).setSpeed(0); + te.getBlockState(); + presentTileEntities.put(info.pos, te); + renderedTileEntities.add(te); + } + + }); + } + + private static BlockInfo readBlockInfo(CompoundNBT blockListEntry, PaletteHashMap palette) { + return new BlockInfo( + BlockPos.fromLong(blockListEntry.getLong("Pos")), + Objects.requireNonNull(palette.get(blockListEntry.getInt("State"))), + blockListEntry.contains("Data") ? blockListEntry.getCompound("Data") : null + ); + } + + private static BlockInfo legacyReadBlockInfo(CompoundNBT blockListEntry) { + return new BlockInfo( + NBTUtil.readBlockPos(blockListEntry.getCompound("Pos")), + NBTUtil.readBlockState(blockListEntry.getCompound("Block")), + blockListEntry.contains("Data") ? blockListEntry.getCompound("Data") : null + ); + } + public void removeBlocksFromWorld(World world, BlockPos offset) { storage.values() .forEach(MountedStorage::removeStorageFromWorld); diff --git a/src/main/java/com/simibubi/create/foundation/gui/AllGuiTextures.java b/src/main/java/com/simibubi/create/foundation/gui/AllGuiTextures.java index 520cde09e..c66f911bb 100644 --- a/src/main/java/com/simibubi/create/foundation/gui/AllGuiTextures.java +++ b/src/main/java/com/simibubi/create/foundation/gui/AllGuiTextures.java @@ -79,7 +79,7 @@ public enum AllGuiTextures { INDICATOR_RED("widgets.png", 72, 18, 18, 6), // PlacementIndicator - PLACEMENT_INDICATOR_SHEET("placement_indicator.png", 0, 0, 256, 256); + PLACEMENT_INDICATOR_SHEET("placement_indicator.png", 0, 0, 16, 256); ; diff --git a/src/main/resources/META-INF/accesstransformer.cfg b/src/main/resources/META-INF/accesstransformer.cfg index 22c960e77..254548d6f 100644 --- a/src/main/resources/META-INF/accesstransformer.cfg +++ b/src/main/resources/META-INF/accesstransformer.cfg @@ -26,4 +26,7 @@ public net.minecraft.world.server.ServerTickList field_205374_d # pendingTickLis public net.minecraft.world.server.ServerTickList field_205375_e # pendingTickListEntriesTreeSet # GameRenderer -public net.minecraft.client.renderer.GameRenderer func_215311_a(Lnet/minecraft/client/renderer/ActiveRenderInfo;FZ)D #getFOVModifier \ No newline at end of file +public net.minecraft.client.renderer.GameRenderer func_215311_a(Lnet/minecraft/client/renderer/ActiveRenderInfo;FZ)D #getFOVModifier + +# IResizeCallback +public net.minecraft.util.palette.IResizeCallback \ No newline at end of file diff --git a/src/main/resources/assets/create/textures/gui/placement_indicator.png b/src/main/resources/assets/create/textures/gui/placement_indicator.png index cb53a47fcb3c027831ef62e2a077a255fb5ddeaf..d433d9b2d532806e2c64c137b9dc8dd6ac94cb99 100644 GIT binary patch delta 5670 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 P00000NkvXXu0mjfTwE@e