Rudimentary Belts

- Second attempt at a practical mechanical belt
This commit is contained in:
simibubi 2019-08-08 16:19:16 +02:00
parent 92a63dade8
commit 3ec8afd091
20 changed files with 519 additions and 6 deletions

View file

@ -7,6 +7,7 @@ import com.simibubi.create.modules.kinetics.generators.MotorBlock;
import com.simibubi.create.modules.kinetics.receivers.TurntableBlock;
import com.simibubi.create.modules.kinetics.relays.AxisBlock;
import com.simibubi.create.modules.kinetics.relays.AxisTunnelBlock;
import com.simibubi.create.modules.kinetics.relays.BeltPulleyBlock;
import com.simibubi.create.modules.kinetics.relays.CogWheelBlock;
import com.simibubi.create.modules.kinetics.relays.GearboxBlock;
import com.simibubi.create.modules.kinetics.relays.GearshifterBlock;
@ -40,6 +41,8 @@ public enum AllBlocks {
LARGE_GEAR(new CogWheelBlock(true)),
AXIS_TUNNEL(new AxisTunnelBlock()),
GEARSHIFTER(new GearshifterBlock()),
BELT_PULLEY(new BeltPulleyBlock()),
BELT(new RenderUtilityBlock()),
TURNTABLE(new TurntableBlock()),
HALF_AXIS(new HalfAxisBlock()),

View file

@ -4,6 +4,8 @@ import com.simibubi.create.modules.curiosities.item.TreeFertilizerItem;
import com.simibubi.create.modules.curiosities.placementHandgun.BuilderGunItem;
import com.simibubi.create.modules.curiosities.placementHandgun.BuilderGunItemRenderer;
import com.simibubi.create.modules.curiosities.placementHandgun.BuilderGunModel;
import com.simibubi.create.modules.kinetics.relays.BeltItem;
import com.simibubi.create.modules.kinetics.relays.BeltPulleyTileEntityRenderer;
import com.simibubi.create.modules.schematics.item.BlueprintAndQuillItem;
import com.simibubi.create.modules.schematics.item.BlueprintItem;
import com.simibubi.create.modules.symmetry.SymmetryWandItem;
@ -12,11 +14,13 @@ import com.simibubi.create.modules.symmetry.client.SymmetryWandModel;
import net.minecraft.client.renderer.model.IBakedModel;
import net.minecraft.client.renderer.model.ModelResourceLocation;
import net.minecraft.client.renderer.model.ModelRotation;
import net.minecraft.client.renderer.tileentity.ItemStackTileEntityRenderer;
import net.minecraft.item.Item;
import net.minecraft.item.Item.Properties;
import net.minecraft.item.ItemStack;
import net.minecraft.item.Rarity;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import net.minecraftforge.client.event.ModelBakeEvent;
@ -41,6 +45,8 @@ public enum AllItems {
BLUEPRINT_AND_QUILL(new BlueprintAndQuillItem(standardProperties().maxStackSize(1))),
BLUEPRINT(new BlueprintItem(standardProperties())),
BELT(new BeltItem(standardProperties())),
;
public Item item;
@ -88,6 +94,9 @@ public enum AllItems {
ModelResourceLocation handgunLocation = getModelLocation(PLACEMENT_HANDGUN);
template = event.getModelRegistry().get(handgunLocation);
event.getModelRegistry().put(handgunLocation, new BuilderGunModel(template).loadPartials(event));
BeltPulleyTileEntityRenderer.beltModel = event.getModelLoader()
.func_217845_a(new ResourceLocation(Create.ID, "block/belt"), ModelRotation.X0_Y0);
}
protected static ModelResourceLocation getModelLocation(AllItems item) {

View file

@ -9,6 +9,8 @@ import com.simibubi.create.modules.kinetics.receivers.TurntableTileEntity;
import com.simibubi.create.modules.kinetics.relays.AxisTileEntity;
import com.simibubi.create.modules.kinetics.relays.AxisTunnelTileEntity;
import com.simibubi.create.modules.kinetics.relays.AxisTunnelTileEntityRenderer;
import com.simibubi.create.modules.kinetics.relays.BeltPulleyTileEntity;
import com.simibubi.create.modules.kinetics.relays.BeltPulleyTileEntityRenderer;
import com.simibubi.create.modules.kinetics.relays.GearboxTileEntity;
import com.simibubi.create.modules.kinetics.relays.GearboxTileEntityRenderer;
import com.simibubi.create.modules.kinetics.relays.GearshifterTileEntity;
@ -44,6 +46,7 @@ public enum AllTileEntities {
TURNTABLE(TurntableTileEntity::new, AllBlocks.TURNTABLE),
AXIS_TUNNEL(AxisTunnelTileEntity::new, AllBlocks.AXIS_TUNNEL),
GEARSHIFTER(GearshifterTileEntity::new, AllBlocks.GEARSHIFTER),
BELT_PULLEY(BeltPulleyTileEntity::new, AllBlocks.BELT_PULLEY),
;
@ -80,6 +83,7 @@ public enum AllTileEntities {
bind(AxisTunnelTileEntity.class, new AxisTunnelTileEntityRenderer());
bind(GearboxTileEntity.class, new GearboxTileEntityRenderer());
bind(GearshifterTileEntity.class, new GearshifterTileEntityRenderer());
bind(BeltPulleyTileEntity.class, new BeltPulleyTileEntityRenderer());
}
@OnlyIn(Dist.CLIENT)

View file

@ -26,6 +26,11 @@ public abstract class KineticTileEntity extends SyncedTileEntity {
source = Optional.empty();
}
@Override
public boolean hasFastRenderer() {
return true;
}
@Override
public void onLoad() {
if (!hasWorld())

View file

@ -9,9 +9,4 @@ public class AxisTileEntity extends KineticTileEntity {
super(AllTileEntities.AXIS.type);
}
@Override
public boolean hasFastRenderer() {
return true;
}
}

View file

@ -0,0 +1,116 @@
package com.simibubi.create.modules.kinetics.relays;
import com.simibubi.create.AllBlocks;
import net.minecraft.item.Item;
import net.minecraft.item.ItemUseContext;
import net.minecraft.nbt.CompoundNBT;
import net.minecraft.nbt.NBTUtil;
import net.minecraft.state.properties.BlockStateProperties;
import net.minecraft.util.ActionResultType;
import net.minecraft.util.Direction.Axis;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
public class BeltItem extends Item {
public static final int MAX_PULLEY_DISTANCE = 20;
public BeltItem(Properties properties) {
super(properties);
}
@Override
public ActionResultType onItemUse(ItemUseContext context) {
World world = context.getWorld();
BlockPos pos = context.getPos();
boolean validAxis = validateAxis(world, pos);
if (world.isRemote)
return validAxis ? ActionResultType.SUCCESS : ActionResultType.FAIL;
CompoundNBT tag = context.getItem().getOrCreateTag();
BlockPos firstPulley = null;
// Remove first if no longer existant or valid
if (tag.contains("FirstPulley")) {
firstPulley = NBTUtil.readBlockPos(tag.getCompound("FirstPulley"));
if (!validateAxis(world, firstPulley)) {
tag.remove("FirstPulley");
context.getItem().setTag(tag);
}
}
if (!validAxis)
return ActionResultType.FAIL;
if (tag.contains("FirstPulley")) {
if (!canConnect(world, firstPulley, pos))
return ActionResultType.FAIL;
if (!firstPulley.equals(pos)) {
makePulley(world, firstPulley);
makePulley(world, pos);
connectPulley(world, firstPulley, pos, true);
connectPulley(world, pos, firstPulley, false);
if (!context.getPlayer().isCreative())
context.getItem().shrink(1);
}
tag.remove("FirstPulley");
if (!context.getItem().isEmpty()) {
context.getItem().setTag(tag);
context.getPlayer().getCooldownTracker().setCooldown(this, 5);
}
return ActionResultType.SUCCESS;
}
tag.put("FirstPulley", NBTUtil.writeBlockPos(pos));
context.getItem().setTag(tag);
context.getPlayer().getCooldownTracker().setCooldown(this, 5);
return ActionResultType.SUCCESS;
}
private void makePulley(World world, BlockPos pos) {
world.setBlockState(pos, AllBlocks.BELT_PULLEY.get().getDefaultState().with(BlockStateProperties.AXIS,
world.getBlockState(pos).get(BlockStateProperties.AXIS)));
}
private void connectPulley(World world, BlockPos pos, BlockPos target, boolean controller) {
BeltPulleyTileEntity te = (BeltPulleyTileEntity) world.getTileEntity(pos);
if (te != null) {
te.setController(controller);
te.setTarget(target);
}
}
private boolean canConnect(World world, BlockPos first, BlockPos second) {
if (!world.isAreaLoaded(first, 1))
return false;
if (!world.isAreaLoaded(second, 1))
return false;
if (!second.withinDistance(first, MAX_PULLEY_DISTANCE))
return false;
BlockPos diff = second.subtract(first);
Axis axis = world.getBlockState(first).get(BlockStateProperties.AXIS);
if (axis.getCoordinate(diff.getX(), diff.getY(), diff.getZ()) != 0)
return false;
if (axis != world.getBlockState(second).get(BlockStateProperties.AXIS))
return false;
return true;
}
private boolean validateAxis(World world, BlockPos pos) {
if (!world.isAreaLoaded(pos, 1))
return false;
if (!AllBlocks.AXIS.typeOf(world.getBlockState(pos)))
return false;
return true;
}
}

View file

@ -0,0 +1,32 @@
package com.simibubi.create.modules.kinetics.relays;
import net.minecraft.block.BlockState;
import net.minecraft.block.Blocks;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.shapes.ISelectionContext;
import net.minecraft.util.math.shapes.VoxelShape;
import net.minecraft.world.IBlockReader;
public class BeltPulleyBlock extends AxisBlock {
public BeltPulleyBlock() {
super(Properties.from(Blocks.ANDESITE));
}
@Override
public VoxelShape getShape(BlockState state, IBlockReader worldIn, BlockPos pos, ISelectionContext context) {
return makeCuboidShape(-16, -16, -16, 32, 32, 32);
}
@Override
public TileEntity createTileEntity(BlockState state, IBlockReader world) {
return new BeltPulleyTileEntity();
}
@Override
protected boolean hasStaticPart() {
return false; // static addons like chutes
}
}

View file

@ -0,0 +1,51 @@
package com.simibubi.create.modules.kinetics.relays;
import com.simibubi.create.AllTileEntities;
import com.simibubi.create.modules.kinetics.base.KineticTileEntity;
import net.minecraft.nbt.CompoundNBT;
import net.minecraft.nbt.NBTUtil;
import net.minecraft.util.math.BlockPos;
public class BeltPulleyTileEntity extends KineticTileEntity {
protected boolean controller;
protected BlockPos target;
public BeltPulleyTileEntity() {
super(AllTileEntities.BELT_PULLEY.type);
controller = false;
target = BlockPos.ZERO;
}
@Override
public CompoundNBT write(CompoundNBT compound) {
compound.putBoolean("Controller", isController());
compound.put("Target", NBTUtil.writeBlockPos(target));
return super.write(compound);
}
@Override
public void read(CompoundNBT compound) {
controller = compound.getBoolean("Controller");
target = NBTUtil.readBlockPos(compound.getCompound("Target"));
super.read(compound);
}
public void setController(boolean controller) {
this.controller = controller;
}
public boolean isController() {
return controller;
}
public void setTarget(BlockPos target) {
this.target = target;
}
public BlockPos getTarget() {
return target;
}
}

View file

@ -0,0 +1,235 @@
package com.simibubi.create.modules.kinetics.relays;
import java.nio.ByteBuffer;
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.kinetics.base.KineticTileEntity;
import com.simibubi.create.modules.kinetics.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.GLAllocation;
import net.minecraft.client.renderer.model.IBakedModel;
import net.minecraft.client.renderer.texture.AtlasTexture;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
import net.minecraft.state.properties.BlockStateProperties;
import net.minecraft.util.Direction;
import net.minecraft.util.Direction.Axis;
import net.minecraft.util.Direction.AxisDirection;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.Vec3d;
import net.minecraftforge.client.model.animation.Animation;
import net.minecraftforge.client.model.data.EmptyModelData;
public class BeltPulleyTileEntityRenderer extends KineticTileEntityRenderer {
protected static Cache<BeltPulleyTileEntity, CachedBeltBuffer> cachedBelts;
public static IBakedModel beltModel;
protected class CachedBeltBuffer {
ByteBuffer original;
ByteBuffer mutable;
public CachedBeltBuffer(ByteBuffer original, float beltLength, float angle, Axis axis) {
original.rewind();
this.original = original;
this.mutable = GLAllocation.createDirectByteBuffer(original.capacity());
this.mutable.order(original.order());
int limitBefore = this.original.limit();
int integerSize = DefaultVertexFormats.BLOCK.getIntegerSize();
int amtBytesCopied = 0; // 2/*Blocks*/ * 6/*Quads*/ * 4/*Vertices*/ * integerSize;
this.original.limit(limitBefore + amtBytesCopied);
this.mutable.limit(limitBefore + amtBytesCopied);
// for (int i = 0; i < amtBytesCopied; i++)
// this.original.put(i + limitBefore, this.original.get(i));
// int vertex = limitBefore / integerSize;
// putPos(this.original, vertex, getPos(this.original, vertex).add(0,1,0));
mutable.put(this.original);
this.original.rewind();
mutable.rewind();
}
private Vec3d getPos(ByteBuffer buffer, int vertex) {
return new Vec3d(buffer.getFloat(vertex), buffer.getFloat(vertex + 4), buffer.getFloat(vertex + 8));
}
private void putPos(ByteBuffer buffer, int vertex, Vec3d pos) {
buffer.putFloat(vertex, (float) pos.x);
buffer.putFloat(vertex + 4, (float) pos.y);
buffer.putFloat(vertex + 8, (float) pos.z);
}
private Vec3d rotatePos(Vec3d pos, float angle, Axis axis) {
return rotatePos(pos, MathHelper.sin(angle), MathHelper.cos(angle), axis);
}
private Vec3d rotatePos(Vec3d pos, float sin, float cos, Axis axis) {
final float x = (float) pos.x;
final float y = (float) pos.y;
final float z = (float) pos.z;
if (axis == Axis.X)
return new Vec3d(x, y * cos - z * sin, z * cos + y * sin);
if (axis == Axis.Y)
return new Vec3d(x * cos + z * sin, y, z * cos - x * sin);
if (axis == Axis.Z)
return new Vec3d(x * cos - y * sin, y * cos + x * sin, z);
return pos;
}
public ByteBuffer getTransformed(Vec3d translation, BeltPulleyTileEntity te, boolean flipped) {
original.rewind();
mutable.rewind();
final Axis axis = te.getBlockState().get(BlockStateProperties.AXIS);
final BlockPos diff = te.getTarget().subtract(te.getPos());
float angle = 0;
if (axis == Axis.X)
angle = (float) MathHelper.atan2(-diff.getY(), diff.getZ());
if (axis == Axis.Y)
angle = (float) MathHelper.atan2(diff.getX(), diff.getZ());
if (axis == Axis.Z)
angle = (float) MathHelper.atan2(diff.getY(), diff.getX());
final float time = Animation.getWorldTime(Minecraft.getInstance().world,
Minecraft.getInstance().getRenderPartialTicks());
final float progress = ((te.getSpeed() * time / 16) % 16) / 16f;
float beltLength = (float) new Vec3d(te.getPos()).distanceTo(new Vec3d(te.getTarget())) + .5f;
final BlockState blockState = te.getBlockState();
final int packedLightCoords = blockState.getPackedLightmapCoords(getWorld(), te.getPos());
final int formatLength = DefaultVertexFormats.BLOCK.getSize();
final Vec3d half = new Vec3d(.5f, .5f, .5f);
final float cos = MathHelper.cos(angle);
final float sin = MathHelper.sin(angle);
final Vec3d offset = new Vec3d(0, .25f, -.25f + progress + (te.getSpeed() < 0 ? 0 : -1));
Vec3d trans = new Vec3d(Direction.getFacingFromAxis(AxisDirection.NEGATIVE, axis).getDirectionVec())
.scale(.5f);
TextureAtlasSprite sprite = Minecraft.getInstance().getTextureMap()
.getSprite(AtlasTexture.LOCATION_BLOCKS_TEXTURE);
final int texHeight = sprite.getHeight();
for (int i = 0; i < original.limit() / formatLength; i++) {
final int position = i * formatLength;
// Cut-off
mutable.putFloat(position + 20, original.getFloat(position + 20));
Vec3d localPos = getPos(this.original, position).add(offset);
if (localPos.z < -.25f) {
mutable.putFloat(position + 20, (float) (original.getFloat(position + 20)
- (Math.min(localPos.z + .25f, 0)) * .5f / texHeight));
localPos = new Vec3d(localPos.x, localPos.y, -.25f);
}
if (localPos.z > beltLength - .25f) {
mutable.putFloat(position + 20, (float) (original.getFloat(position + 20)
- (Math.min(localPos.z - beltLength + .25f, 1f)) * .5f / texHeight));
localPos = new Vec3d(localPos.x, localPos.y, beltLength - .25f);
}
// Transform Vertex
Vec3d pos = localPos;
if (axis == Axis.Z)
pos = rotatePos(pos.subtract(half), (float) (Math.PI / 2f), Axis.Y).add(half);
if (axis == Axis.Y)
pos = rotatePos(pos, (float) (Math.PI / 2f), Axis.Z);
pos = rotatePos(pos, sin, cos, axis).add(trans).add(half);
// Flip
if (flipped) {
pos = rotatePos(pos.subtract(half), (float) Math.PI, axis);
pos = pos.add(half).add(new Vec3d(diff));
}
putPos(mutable, position, pos.add(translation));
mutable.putInt(position + 24, packedLightCoords);
}
return mutable;
}
}
public BeltPulleyTileEntityRenderer() {
super();
cachedBelts = CacheBuilder.newBuilder().expireAfterAccess(1, TimeUnit.SECONDS).build();
}
@Override
public void renderTileEntityFast(KineticTileEntity te, double x, double y, double z, float partialTicks,
int destroyStage, BufferBuilder buffer) {
super.renderTileEntityFast(te, x, y, z, partialTicks, destroyStage, buffer);
BeltPulleyTileEntity beltPulleyTE = (BeltPulleyTileEntity) te;
if (!beltPulleyTE.isController())
return;
cacheBeltIfMissing(beltPulleyTE);
renderBeltFromCache(te, new Vec3d(x, y, z), buffer);
}
@Override
protected BlockState getRenderedBlockState(KineticTileEntity te) {
return AllBlocks.AXIS.get().getDefaultState().with(BlockStateProperties.AXIS,
te.getBlockState().get(BlockStateProperties.AXIS));
}
protected void cacheBeltIfMissing(BeltPulleyTileEntity te) {
if (cachedBelts.getIfPresent(te) != null)
return;
BlockRendererDispatcher dispatcher = Minecraft.getInstance().getBlockRendererDispatcher();
BlockModelRenderer blockRenderer = dispatcher.getBlockModelRenderer();
BufferBuilder builder = new BufferBuilder(0);
Random random = new Random();
builder.begin(GL11.GL_QUADS, DefaultVertexFormats.BLOCK);
float beltLength = (float) new Vec3d(te.getPos()).distanceTo(new Vec3d(te.getTarget())) + .5f;
int length = MathHelper.ceil(beltLength) + 1;
for (int segment = 0; segment < length; segment++) {
builder.setTranslation(0, 0, segment);
blockRenderer.renderModelFlat(getWorld(), beltModel, te.getBlockState(), BlockPos.ZERO, builder, true,
random, 42, EmptyModelData.INSTANCE);
}
builder.finishDrawing();
Axis axis = te.getBlockState().get(BlockStateProperties.AXIS);
BlockPos diff = te.getTarget().subtract(te.getPos());
float angle = 0;
if (axis == Axis.X)
angle = (float) MathHelper.atan2(-diff.getY(), diff.getZ());
if (axis == Axis.Y)
angle = (float) MathHelper.atan2(diff.getX(), diff.getZ());
if (axis == Axis.Z)
angle = (float) MathHelper.atan2(diff.getY(), diff.getX());
cachedBelts.put(te, new CachedBeltBuffer(builder.getByteBuffer(), beltLength, angle, axis));
}
public void renderBeltFromCache(KineticTileEntity te, Vec3d translation, BufferBuilder buffer) {
if (buffer == null)
return;
buffer.putBulkData(cachedBelts.getIfPresent(te).getTransformed(translation, (BeltPulleyTileEntity) te, false));
buffer.putBulkData(cachedBelts.getIfPresent(te).getTransformed(translation, (BeltPulleyTileEntity) te, true));
}
}

View file

@ -0,0 +1,9 @@
{
"forgemarker": 1,
"defaults": {
"model": "create:block/belt"
},
"variants": {
"": { "model": "create:block/belt" }
}
}

View file

@ -0,0 +1,11 @@
{
"forgemarker": 1,
"defaults": {
"model": "block/dirt"
},
"variants": {
"axis=y": { "model": "block/dirt" },
"axis=z": { "model": "block/dirt", "x": 90 },
"axis=x": { "model": "block/dirt", "x": 90, "y": 90 }
}
}

View file

@ -8,6 +8,7 @@
"item.create.chorus_chrome_cube": "Chorus Chrome",
"item.create.blueprint_and_quill": "Schematic and Quill",
"item.create.blueprint": "Schematic",
"item.create.belt": "Mechanical Belt",
"block.create.gear": "Cogwheel",
"block.create.large_gear": "Large Cogwheel",
"block.create.turntable": "Turntable",
@ -16,6 +17,7 @@
"block.create.axis_tunnel": "Encased Axis",
"block.create.axis": "Axis",
"block.create.motor": "Motor",
"block.create.belt_pulley": "Belt Pulley",
"block.create.schematicannon": "Schematicannon",
"block.create.schematic_table": "Schematic Table",
"block.create.creative_crate": "Schematicannon Creatifier",

View file

@ -0,0 +1,22 @@
{
"__comment": "Model generated using MrCrayfish's Model Creator (http://mrcrayfish.com/modelcreator/)",
"parent": "block/cube",
"textures": {
"0": "create:block/belt"
},
"elements": [
{
"name": "Belt",
"from": [ 1.0, 0.0, 0.0 ],
"to": [ 15.0, 2.0, 16.0 ],
"faces": {
"north": { "texture": "#0", "uv": [ 1.0, 0.0, 15.0, 2.0 ], "rotation": 180 },
"east": { "texture": "#0", "uv": [ 14.0, 0.0, 16.0, 16.0 ], "rotation": 90 },
"south": { "texture": "#0", "uv": [ 1.0, 14.0, 15.0, 16.0 ]},
"west": { "texture": "#0", "uv": [ 0.0, 0.0, 2.0, 16.0 ], "rotation": 270 },
"up": { "texture": "#0", "uv": [ 1.0, 0.0, 15.0, 16.0 ] },
"down": { "texture": "#0", "uv": [ 1.0, 0.0, 15.0, 16.0 ], "rotation": 180 }
}
}
]
}

View file

@ -0,0 +1,6 @@
{
"parent": "item/generated",
"textures": {
"layer0": "create:item/belt"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 197 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 250 B

View file

@ -0,0 +1,7 @@
{
"animation": {
"interpolate": false,
"frametime": 1,
"frames": [ 3, 2, 1, 0 ]
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 250 B

View file

@ -0,0 +1,6 @@
{
"animation": {
"interpolate": false,
"frametime": 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 258 B