diff --git a/src/main/java/com/simibubi/create/modules/contraptions/receivers/constructs/MechanicalBearingTileEntity.java b/src/main/java/com/simibubi/create/modules/contraptions/receivers/constructs/MechanicalBearingTileEntity.java index fd9f60b17..4dabe49cf 100644 --- a/src/main/java/com/simibubi/create/modules/contraptions/receivers/constructs/MechanicalBearingTileEntity.java +++ b/src/main/java/com/simibubi/create/modules/contraptions/receivers/constructs/MechanicalBearingTileEntity.java @@ -3,10 +3,133 @@ package com.simibubi.create.modules.contraptions.receivers.constructs; import com.simibubi.create.AllTileEntities; import com.simibubi.create.modules.contraptions.base.KineticTileEntity; -public class MechanicalBearingTileEntity extends KineticTileEntity { +import net.minecraft.block.BlockState; +import net.minecraft.block.Blocks; +import net.minecraft.nbt.CompoundNBT; +import net.minecraft.state.properties.BlockStateProperties; +import net.minecraft.tileentity.ITickableTileEntity; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.Direction; +import net.minecraft.util.math.AxisAlignedBB; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.MathHelper; +import net.minecraft.world.gen.feature.template.Template.BlockInfo; + +public class MechanicalBearingTileEntity extends KineticTileEntity implements ITickableTileEntity { + + protected RotationConstruct movingConstruct; + protected float angle; + protected boolean running; + protected boolean assembleNextTick; public MechanicalBearingTileEntity() { super(AllTileEntities.MECHANICAL_BEARING.type); } + @Override + public AxisAlignedBB getRenderBoundingBox() { + return INFINITE_EXTENT_AABB; + } + + @Override + public CompoundNBT write(CompoundNBT tag) { + tag.putBoolean("Running", running); + tag.putFloat("Angle", angle); + if (running) + tag.put("Construct", movingConstruct.writeNBT()); + + return super.write(tag); + } + + @Override + public void read(CompoundNBT tag) { + running = tag.getBoolean("Running"); + angle = tag.getFloat("Angle"); + if (running) + movingConstruct = RotationConstruct.fromNBT(tag.getCompound("Construct")); + + super.read(tag); + } + + public float getInterpolatedAngle(float partialTicks) { + return MathHelper.lerp(partialTicks, angle, angle + getAngularSpeed()); + } + + @Override + public void onSpeedChanged() { + super.onSpeedChanged(); + assembleNextTick = true; + } + + public float getAngularSpeed() { + return speed / 2048; + } + + public void assembleConstruct() { + Direction direction = getBlockState().get(BlockStateProperties.FACING); + + // Collect Construct + movingConstruct = RotationConstruct.getAttachedForRotating(getWorld(), getPos(), direction); + if (movingConstruct == null) + return; + + // Run + running = true; + angle = 0; + sendData(); + + for (BlockInfo info : movingConstruct.blocks.values()) { + getWorld().setBlockState(info.pos.add(pos), Blocks.AIR.getDefaultState(), 67); + } + } + + public void disassembleConstruct() { + if (!running) + return; + + for (BlockInfo block : movingConstruct.blocks.values()) { + BlockPos targetPos = block.pos.add(pos); + BlockState state = block.state; + + for (Direction face : Direction.values()) + state = state.updatePostPlacement(face, world.getBlockState(targetPos.offset(face)), world, targetPos, + targetPos.offset(face)); + + world.destroyBlock(targetPos, world.getBlockState(targetPos).getCollisionShape(world, targetPos).isEmpty()); + getWorld().setBlockState(targetPos, state, 3); + TileEntity tileEntity = world.getTileEntity(targetPos); + if (tileEntity != null && block.nbt != null) { + ((ChassisTileEntity) tileEntity).setRange(block.nbt.getInt("Range")); + } + } + + running = false; + movingConstruct = null; + angle = 0; + sendData(); + } + + @Override + public void tick() { + if (!world.isRemote && assembleNextTick) { + assembleNextTick = false; + if (running) { + if (speed == 0 && (Math.abs(angle) < Math.PI / 4f || Math.abs(angle) > 7 * Math.PI / 4f)) { + disassembleConstruct(); + } + return; + } else { + assembleConstruct(); + } + return; + } + + if (!running) + return; + + float angularSpeed = getAngularSpeed(); + float newAngle = angle + angularSpeed; + angle = (float) (newAngle % (2 * Math.PI)); + } + } diff --git a/src/main/java/com/simibubi/create/modules/contraptions/receivers/constructs/MechanicalBearingTileEntityRenderer.java b/src/main/java/com/simibubi/create/modules/contraptions/receivers/constructs/MechanicalBearingTileEntityRenderer.java index 08a08cfb0..0ba2ffdc6 100644 --- a/src/main/java/com/simibubi/create/modules/contraptions/receivers/constructs/MechanicalBearingTileEntityRenderer.java +++ b/src/main/java/com/simibubi/create/modules/contraptions/receivers/constructs/MechanicalBearingTileEntityRenderer.java @@ -1,22 +1,40 @@ package com.simibubi.create.modules.contraptions.receivers.constructs; +import java.util.Random; +import java.util.concurrent.TimeUnit; + +import org.lwjgl.opengl.GL11; + +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; import com.simibubi.create.AllBlocks; +import com.simibubi.create.modules.contraptions.base.IRotate; import com.simibubi.create.modules.contraptions.base.KineticTileEntity; import com.simibubi.create.modules.contraptions.base.KineticTileEntityRenderer; import net.minecraft.block.BlockState; import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.BlockModelRenderer; +import net.minecraft.client.renderer.BlockRendererDispatcher; import net.minecraft.client.renderer.BufferBuilder; +import net.minecraft.client.renderer.model.IBakedModel; +import net.minecraft.client.renderer.vertex.DefaultVertexFormats; import net.minecraft.state.properties.BlockStateProperties; import net.minecraft.util.Direction; import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Vec3i; +import net.minecraft.world.gen.feature.template.Template.BlockInfo; import net.minecraftforge.client.model.animation.Animation; +import net.minecraftforge.client.model.data.EmptyModelData; public class MechanicalBearingTileEntityRenderer extends KineticTileEntityRenderer { + protected static Cache cachedConstructs; + @Override public void renderTileEntityFast(KineticTileEntity te, double x, double y, double z, float partialTicks, int destroyStage, BufferBuilder buffer) { + MechanicalBearingTileEntity bearingTe = (MechanicalBearingTileEntity) te; final Direction facing = te.getBlockState().get(BlockStateProperties.FACING); final BlockPos pos = te.getPos(); float time = Animation.getWorldTime(Minecraft.getInstance().world, partialTicks); @@ -33,9 +51,56 @@ public class MechanicalBearingTileEntityRenderer extends KineticTileEntityRender angle += offset; angle = angle / 180f * (float) Math.PI; + float interpolatedAngle = bearingTe.getInterpolatedAngle(partialTicks); renderFromCache(buffer, shaftState, (float) x, (float) y, (float) z, pos, facing.getAxis(), angle); - renderFromCache(buffer, capState, (float) x, (float) y, (float) z, pos, facing.getAxis(), angle); + renderFromCache(buffer, capState, (float) x, (float) y, (float) z, pos, facing.getAxis(), interpolatedAngle); + + if (!bearingTe.running) + return; + + cacheConstructIfMissing(bearingTe.movingConstruct); + renderConstructFromCache(bearingTe.movingConstruct, bearingTe, x, y, z, partialTicks, buffer); + } + + protected void cacheConstructIfMissing(RotationConstruct c) { + if (cachedConstructs == null) + cachedConstructs = CacheBuilder.newBuilder().expireAfterAccess(1, TimeUnit.SECONDS).build(); + if (cachedConstructs.getIfPresent(c) != null) + return; + + BlockRendererDispatcher dispatcher = Minecraft.getInstance().getBlockRendererDispatcher(); + BlockModelRenderer blockRenderer = dispatcher.getBlockModelRenderer(); + Random random = new Random(); + BufferBuilder builder = new BufferBuilder(0); + builder.begin(GL11.GL_QUADS, DefaultVertexFormats.BLOCK); + builder.setTranslation(0, 255, 0); + + for (BlockPos localPos : c.blocks.keySet()) { + BlockInfo info = c.blocks.get(localPos); + IBakedModel originalModel = dispatcher.getModelForState(info.state); + blockRenderer.renderModel(getWorld(), originalModel, info.state, info.pos.down(255), builder, true, random, + 42, EmptyModelData.INSTANCE); + } + + builder.finishDrawing(); + cachedConstructs.put(c, new RotationConstructVertexBuffer(builder.getByteBuffer())); + } + + protected void renderConstructFromCache(RotationConstruct c, MechanicalBearingTileEntity te, double x, double y, + double z, float partialTicks, BufferBuilder buffer) { + float zfightBonus = 1 / 128f; + Direction direction = te.getBlockState().get(BlockStateProperties.FACING); + Vec3i vec = direction.getDirectionVec(); + buffer.putBulkData(cachedConstructs.getIfPresent(c).getTransformed(te, (float) (x) + vec.getX() * zfightBonus, + (float) (y) + vec.getY() * zfightBonus, (float) (z) + vec.getZ() * zfightBonus, + te.getInterpolatedAngle(partialTicks), direction.getAxis())); + } + + @Override + protected BlockState getRenderedBlockState(KineticTileEntity te) { + return AllBlocks.SHAFT.block.getDefaultState().with(BlockStateProperties.AXIS, + ((IRotate) te.getBlockState().getBlock()).getRotationAxis(te.getBlockState())); } } diff --git a/src/main/java/com/simibubi/create/modules/contraptions/receivers/constructs/MechanicalPistonTileEntity.java b/src/main/java/com/simibubi/create/modules/contraptions/receivers/constructs/MechanicalPistonTileEntity.java index 28cdbf11e..6252f06e0 100644 --- a/src/main/java/com/simibubi/create/modules/contraptions/receivers/constructs/MechanicalPistonTileEntity.java +++ b/src/main/java/com/simibubi/create/modules/contraptions/receivers/constructs/MechanicalPistonTileEntity.java @@ -35,11 +35,6 @@ public class MechanicalPistonTileEntity extends KineticTileEntity implements ITi super(AllTileEntities.MECHANICAL_PISTON.type); } - @Override - public boolean hasFastRenderer() { - return true; - } - @Override public void onSpeedChanged() { super.onSpeedChanged(); diff --git a/src/main/java/com/simibubi/create/modules/contraptions/receivers/constructs/MechanicalPistonTileEntityRenderer.java b/src/main/java/com/simibubi/create/modules/contraptions/receivers/constructs/MechanicalPistonTileEntityRenderer.java index a9045791c..5f3a327c3 100644 --- a/src/main/java/com/simibubi/create/modules/contraptions/receivers/constructs/MechanicalPistonTileEntityRenderer.java +++ b/src/main/java/com/simibubi/create/modules/contraptions/receivers/constructs/MechanicalPistonTileEntityRenderer.java @@ -26,7 +26,7 @@ import net.minecraftforge.client.model.data.EmptyModelData; public class MechanicalPistonTileEntityRenderer extends KineticTileEntityRenderer { - protected static Cache cachedConstructs; + protected static Cache cachedConstructs; @Override public void renderTileEntityFast(KineticTileEntity te, double x, double y, double z, float partialTicks, @@ -64,7 +64,7 @@ public class MechanicalPistonTileEntityRenderer extends KineticTileEntityRendere } builder.finishDrawing(); - cachedConstructs.put(c, new ConstructVertexBuffer(builder.getByteBuffer())); + cachedConstructs.put(c, new TranslationConstructVertexBuffer(builder.getByteBuffer())); } protected void renderConstructFromCache(TranslationConstruct c, MechanicalPistonTileEntity te, double x, double y, double z, diff --git a/src/main/java/com/simibubi/create/modules/contraptions/receivers/constructs/RotationConstruct.java b/src/main/java/com/simibubi/create/modules/contraptions/receivers/constructs/RotationConstruct.java new file mode 100644 index 000000000..002fa8832 --- /dev/null +++ b/src/main/java/com/simibubi/create/modules/contraptions/receivers/constructs/RotationConstruct.java @@ -0,0 +1,192 @@ +package com.simibubi.create.modules.contraptions.receivers.constructs; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import com.simibubi.create.AllBlocks; + +import net.minecraft.block.BlockState; +import net.minecraft.block.PistonBlock; +import net.minecraft.nbt.CompoundNBT; +import net.minecraft.nbt.ListNBT; +import net.minecraft.nbt.NBTUtil; +import net.minecraft.state.properties.BlockStateProperties; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.Direction; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; +import net.minecraft.world.gen.feature.template.Template.BlockInfo; + +public class RotationConstruct { + + public static final int MAX_CHAINED_CHASSIS = 10; + + protected Map blocks; + + public RotationConstruct() { + blocks = new HashMap<>(); + } + + public static RotationConstruct getAttachedForRotating(World world, BlockPos pos, Direction direction) { + RotationConstruct construct = new RotationConstruct(); + + if (!construct.collectAttached(world, pos, direction)) + return null; + + return construct; + } + + protected boolean collectAttached(World world, BlockPos pos, Direction direction) { + + // Find chassis + List chassis = collectChassis(world, pos, direction); + if (chassis == null) + return false; + + // Get single block + if (chassis.isEmpty()) { + BlockPos blockPos = pos.offset(direction); + BlockState state = world.getBlockState(pos.offset(direction)); + + if (state.getMaterial().isReplaceable() || state.isAir(world, blockPos)) + return true; + if (state.getCollisionShape(world, blockPos).isEmpty()) + return true; + if (!canRotate(world, blockPos, direction)) + return false; + + blocks.put(blockPos, new BlockInfo(blockPos.subtract(pos), state, null)); + + // Get attached blocks by chassis + } else { + List attachedBlocksByChassis = getAttachedBlocksByChassis(world, direction, chassis); + if (attachedBlocksByChassis == null) + return false; + attachedBlocksByChassis.forEach(info -> { + blocks.put(info.pos, new BlockInfo(info.pos.subtract(pos), info.state, info.nbt)); + }); + } + + return true; + } + + private List getAttachedBlocksByChassis(World world, Direction direction, List chassis) { + List blocks = new ArrayList<>(); + RotationChassisBlock def = (RotationChassisBlock) AllBlocks.ROTATION_CHASSIS.block; + + for (BlockInfo chassisBlock : chassis) { + blocks.add(chassisBlock); + BlockState state = chassisBlock.state; + BlockPos currentPos = chassisBlock.pos; + TileEntity tileEntity = world.getTileEntity(currentPos); + + if (!(tileEntity instanceof ChassisTileEntity)) + return null; + + int chassisRange = ((ChassisTileEntity) tileEntity).getRange(); + Set visited = new HashSet<>(); + + for (Direction facing : Direction.values()) { + if (facing.getAxis() == direction.getAxis()) + continue; + if (!state.get(def.getGlueableSide(state, facing))) + continue; + + BlockPos startPos = currentPos.offset(facing); + List frontier = new LinkedList<>(); + frontier.add(startPos); + CompoundNBT nbt = new CompoundNBT(); + nbt.putInt("Range", chassisRange); + + while (!frontier.isEmpty()) { + BlockPos searchPos = frontier.remove(0); + BlockState searchedState = world.getBlockState(searchPos); + + if (visited.contains(searchPos)) + continue; + if (!searchPos.withinDistance(currentPos, chassisRange + .5f)) + continue; + if (searchedState.getMaterial().isReplaceable() || state.isAir(world, searchPos)) + continue; + if (searchedState.getCollisionShape(world, searchPos).isEmpty()) + continue; + if (!canRotate(world, searchPos, direction)) + return null; + + visited.add(searchPos); + + blocks.add(new BlockInfo(searchPos, searchedState, + AllBlocks.ROTATION_CHASSIS.typeOf(searchedState) ? nbt : null)); + + for (Direction offset : Direction.values()) { + if (offset.getAxis() == direction.getAxis()) + continue; + if (searchPos.equals(currentPos) && offset != facing) + continue; + + frontier.add(searchPos.offset(offset)); + } + } + } + } + return blocks; + } + + private List collectChassis(World world, BlockPos pos, Direction direction) { + List chassis = new ArrayList<>(); + for (int distance = 1; distance <= MAX_CHAINED_CHASSIS; distance++) { + BlockPos currentPos = pos.offset(direction, distance); + if (!world.isBlockPresent(currentPos)) + return chassis; + + BlockState state = world.getBlockState(currentPos); + if (!AllBlocks.ROTATION_CHASSIS.typeOf(state)) + return chassis; + if (direction.getAxis() != state.get(BlockStateProperties.AXIS)) + return chassis; + + chassis.add(new BlockInfo(currentPos, state, null)); + } + return chassis; + } + + public CompoundNBT writeNBT() { + CompoundNBT nbt = new CompoundNBT(); + ListNBT blocks = 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); + blocks.add(c); + } + + nbt.put("Blocks", blocks); + return nbt; + } + + public static RotationConstruct fromNBT(CompoundNBT nbt) { + RotationConstruct construct = new RotationConstruct(); + 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); + construct.blocks.put(info.pos, info); + }); + + return construct; + } + + private static boolean canRotate(World world, BlockPos pos, Direction direction) { + return PistonBlock.canPush(world.getBlockState(pos), world, pos, direction, true, direction) + || AllBlocks.ROTATION_CHASSIS.typeOf(world.getBlockState(pos)); + } + +} diff --git a/src/main/java/com/simibubi/create/modules/contraptions/receivers/constructs/RotationConstructVertexBuffer.java b/src/main/java/com/simibubi/create/modules/contraptions/receivers/constructs/RotationConstructVertexBuffer.java new file mode 100644 index 000000000..a99f50a58 --- /dev/null +++ b/src/main/java/com/simibubi/create/modules/contraptions/receivers/constructs/RotationConstructVertexBuffer.java @@ -0,0 +1,42 @@ +package com.simibubi.create.modules.contraptions.receivers.constructs; + +import java.nio.ByteBuffer; + +import com.simibubi.create.foundation.utility.BufferManipulator; + +import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.Direction.Axis; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.MathHelper; + +public class RotationConstructVertexBuffer extends BufferManipulator { + + public RotationConstructVertexBuffer(ByteBuffer original) { + super(original); + } + + public ByteBuffer getTransformed(TileEntity te, float x, float y, float z, float angle, Axis axis) { + original.rewind(); + mutable.rewind(); + + float cos = MathHelper.cos(angle); + float sin = MathHelper.sin(angle); + + for (int vertex = 0; vertex < vertexCount(original); vertex++) { + float xL = getX(original, vertex) -.5f; + float yL = getY(original, vertex) -.5f; + float zL = getZ(original, vertex) -.5f; + + float xL2 = rotateX(xL, yL, zL, sin, cos, axis) + .5f; + float yL2 = rotateY(xL, yL, zL, sin, cos, axis) + .5f; + float zL2 = rotateZ(xL, yL, zL, sin, cos, axis) + .5f; + + putPos(mutable, vertex, xL2 + x, yL2 + y, zL2 + z); + BlockPos pos = new BlockPos(te.getPos().getX() + xL2, te.getPos().getY() + yL2, te.getPos().getZ() + zL2); + putLight(mutable, vertex, te.getWorld().getCombinedLight(pos, 0)); + } + + return mutable; + } + +} diff --git a/src/main/java/com/simibubi/create/modules/contraptions/receivers/constructs/ConstructVertexBuffer.java b/src/main/java/com/simibubi/create/modules/contraptions/receivers/constructs/TranslationConstructVertexBuffer.java similarity index 86% rename from src/main/java/com/simibubi/create/modules/contraptions/receivers/constructs/ConstructVertexBuffer.java rename to src/main/java/com/simibubi/create/modules/contraptions/receivers/constructs/TranslationConstructVertexBuffer.java index babbfefe7..d562ea39f 100644 --- a/src/main/java/com/simibubi/create/modules/contraptions/receivers/constructs/ConstructVertexBuffer.java +++ b/src/main/java/com/simibubi/create/modules/contraptions/receivers/constructs/TranslationConstructVertexBuffer.java @@ -8,9 +8,9 @@ import net.minecraft.tileentity.TileEntity; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.Vec3d; -public class ConstructVertexBuffer extends BufferManipulator { +public class TranslationConstructVertexBuffer extends BufferManipulator { - public ConstructVertexBuffer(ByteBuffer original) { + public TranslationConstructVertexBuffer(ByteBuffer original) { super(original); }