diff --git a/src/main/java/com/hbm/handler/GunConfiguration.java b/src/main/java/com/hbm/handler/GunConfiguration.java index cfac864c4..4efa68883 100644 --- a/src/main/java/com/hbm/handler/GunConfiguration.java +++ b/src/main/java/com/hbm/handler/GunConfiguration.java @@ -5,6 +5,7 @@ import java.util.HashMap; import java.util.List; import com.hbm.lib.HbmCollection.EnumGunManufacturer; +import com.hbm.particle.SpentCasingConfig; import com.hbm.render.anim.BusAnimation; import com.hbm.render.anim.HbmAnimations.AnimType; import com.hbm.render.util.RenderScreenOverlay.Crosshair; @@ -77,7 +78,7 @@ public class GunConfiguration implements Cloneable { public Crosshair crosshair; //casing eject behavior - //public Optional casingConfig = Optional.empty(); + public SpentCasingConfig casingConfig = null; public static final int MODE_NORMAL = 0; public static final int MODE_RELEASE = 1; diff --git a/src/main/java/com/hbm/handler/guncfg/Gun12GaugeFactory.java b/src/main/java/com/hbm/handler/guncfg/Gun12GaugeFactory.java index 4d8a728d8..5ad09cde8 100644 --- a/src/main/java/com/hbm/handler/guncfg/Gun12GaugeFactory.java +++ b/src/main/java/com/hbm/handler/guncfg/Gun12GaugeFactory.java @@ -12,6 +12,9 @@ import com.hbm.items.ItemAmmoEnums.Ammo12Gauge; import com.hbm.items.ModItems; import com.hbm.lib.HbmCollection; import com.hbm.lib.HbmCollection.EnumGunManufacturer; +import com.hbm.particle.SpentCasingConfig; +import com.hbm.particle.SpentCasingConfig.CasingType; +import com.hbm.particle.SpentCasingConfigBuilder; import com.hbm.potion.HbmPotion; import com.hbm.render.anim.BusAnimation; import com.hbm.render.anim.BusAnimationKeyframe; @@ -22,9 +25,38 @@ import com.hbm.render.util.RenderScreenOverlay.Crosshair; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; import net.minecraft.potion.PotionEffect; +import net.minecraft.util.Vec3; public class Gun12GaugeFactory { + static final SpentCasingConfig CASING_SPAS, CASING_SPAS_ALT, CASING_BENELLI, CASING_UBOINIK, CASING_SSG; + + static + { + final SpentCasingConfigBuilder CASING_12G_BUILDER = new SpentCasingConfigBuilder("", CasingType.SHOTGUN, false) + .setScaleX(1.5f).setScaleY(1.5f).setScaleZ(1.5f); + CASING_SPAS = CASING_12G_BUILDER.setRegistryName("spas12").setInitialMotion(Vec3.createVectorHelper(-0.4, 0.1, 0)) + .setPosOffset(Vec3.createVectorHelper(-0.35, 0, 0.5)).setPitchFactor(0.03f).setYawFactor(0.01f) + .setSmokeChance(0).setDelay(10) + .build(); + + CASING_SPAS_ALT = CASING_12G_BUILDER.setRegistryName("spas12alt").setCasingAmount(2) + .build(); + + CASING_BENELLI = CASING_12G_BUILDER.setRegistryName("benelli").setCasingAmount(1).setDelay(0) + .setInitialMotion(Vec3.createVectorHelper(-0.3, 1, 0)) + .build(); + + CASING_UBOINIK = CASING_12G_BUILDER.setRegistryName("uboinik").setOverrideColor(true) + .setBlueOverride(255).setPosOffset(Vec3.createVectorHelper(-0.35, -0.3, 0.5)) + .build(); + + CASING_SSG = CASING_12G_BUILDER.setRegistryName("ssg").setBlueOverride(0).setRedOverride(255).setCasingAmount(2) + .setPosOffset(Vec3.createVectorHelper(0.8, 0, 0)).setInitialMotion(Vec3.createVectorHelper(0.2, 0, -0.2)) + .setPitchFactor(0.05f).setYawFactor(0.02f) + .build(); + } + public static GunConfiguration getSpas12Config() { GunConfiguration config = new GunConfiguration(); @@ -66,6 +98,8 @@ public class Gun12GaugeFactory { ) ); + config.casingConfig = CASING_SPAS; + return config; } diff --git a/src/main/java/com/hbm/items/weapon/ItemGunBase.java b/src/main/java/com/hbm/items/weapon/ItemGunBase.java index fb39750dc..ef1b37507 100644 --- a/src/main/java/com/hbm/items/weapon/ItemGunBase.java +++ b/src/main/java/com/hbm/items/weapon/ItemGunBase.java @@ -15,9 +15,11 @@ import com.hbm.interfaces.IItemHUD; import com.hbm.inventory.RecipesCommon.ComparableStack; import com.hbm.items.IEquipReceiver; import com.hbm.lib.HbmCollection; +import com.hbm.main.MainRegistry; import com.hbm.packet.GunAnimationPacket; import com.hbm.packet.GunButtonPacket; import com.hbm.packet.PacketDispatcher; +import com.hbm.particle.SpentCasingConfig; import com.hbm.render.anim.BusAnimation; import com.hbm.render.anim.HbmAnimations.AnimType; import com.hbm.render.util.RenderScreenOverlay; @@ -208,6 +210,9 @@ public class ItemGunBase extends Item implements IHoldableWeapon, IItemHUD, IEqu } world.playSoundAtEntity(player, mainConfig.firingSound, 1.0F, mainConfig.firingPitch); + + if(mainConfig.casingConfig != null && !mainConfig.casingConfig.isAfterReload()) + spawnCasing(player, mainConfig.casingConfig, stack); } //unlike fire(), being called does not automatically imply success, some things may still have to be handled before spawning the projectile @@ -239,6 +244,9 @@ public class ItemGunBase extends Item implements IHoldableWeapon, IItemHUD, IEqu } world.playSoundAtEntity(player, altConfig.firingSound, 1.0F, altConfig.firingPitch); + + if(altConfig.casingConfig != null) + spawnCasing(player, altConfig.casingConfig, stack); } //spawns the actual projectile, can be overridden to change projectile entity @@ -319,6 +327,9 @@ public class ItemGunBase extends Item implements IHoldableWeapon, IItemHUD, IEqu if(hasLoaded && mainConfig.reloadSoundEnd) world.playSoundAtEntity(player, mainConfig.reloadSound, 1.0F, 1.0F); + if(mainConfig.casingConfig != null && mainConfig.casingConfig.isAfterReload()) + spawnCasing(player, mainConfig.casingConfig, stack); + InventoryUtil.tryConsumeAStack(player.inventory.mainInventory, 0, player.inventory.mainInventory.length, ammo); } else { setReloadCycle(stack, getReloadCycle(stack) - 1); @@ -692,4 +703,17 @@ public class ItemGunBase extends Item implements IHoldableWeapon, IItemHUD, IEqu player.worldObj.playSoundAtEntity(player, mainConfig.equipSound, 1, 1); } } + + protected static void spawnCasing(Entity entity, SpentCasingConfig config, ItemStack stack) { + NBTTagCompound data = new NBTTagCompound(); + data.setString("type", "casing"); + data.setDouble("posX", entity.posX); + data.setDouble("posY", entity.posY + entity.getEyeHeight()); + data.setDouble("posZ", entity.posZ); + data.setFloat("pitch", (float) Math.toRadians(entity.rotationPitch)); + data.setFloat("yaw", (float) Math.toRadians(entity.rotationYaw)); + data.setBoolean("crouched", entity.isSneaking()); + data.setString("name", config.getRegistryName()); + MainRegistry.proxy.effectNT(data); + } } diff --git a/src/main/java/com/hbm/main/ClientProxy.java b/src/main/java/com/hbm/main/ClientProxy.java index 43c6bbb6e..185e7955e 100644 --- a/src/main/java/com/hbm/main/ClientProxy.java +++ b/src/main/java/com/hbm/main/ClientProxy.java @@ -1789,6 +1789,12 @@ public class ClientProxy extends ServerProxy { RenderOverhead.queuedMarkers.put(new BlockPos(x, y, z), new Marker(color).setDist(dist).setExpire(expires > 0 ? System.currentTimeMillis() + expires : 0).withLabel(label.isEmpty() ? null : label)); } + + if("casing".equals(type)) { + SpentCasingConfig casingConfig = SpentCasingConfig.get(data.getString("name")); + for(int i = 0; i < casingConfig.getCasingAmount(); i++) + casingConfig.spawnCasing(man, world, x, y, z, data.getFloat("pitch"), data.getFloat("yaw"), data.getBoolean("crouched")); + } } private HashMap vanished = new HashMap(); diff --git a/src/main/java/com/hbm/main/ResourceManager.java b/src/main/java/com/hbm/main/ResourceManager.java index f872cebad..7945004f4 100644 --- a/src/main/java/com/hbm/main/ResourceManager.java +++ b/src/main/java/com/hbm/main/ResourceManager.java @@ -865,6 +865,7 @@ public class ResourceManager { //Projectiles public static final IModelCustom projectiles = new HFRWavefrontObject(new ResourceLocation(RefStrings.MODID, "models/projectiles/projectiles.obj")); + public static final IModelCustom casings = new HFRWavefrontObject(new ResourceLocation(RefStrings.MODID, "models/effect/casings.obj")); //Bomber public static final IModelCustom dornier = AdvancedModelLoader.loadModel(new ResourceLocation(RefStrings.MODID, "models/dornier.obj")); @@ -990,6 +991,7 @@ public class ResourceManager { public static final ResourceLocation rocket_mirv_tex = new ResourceLocation(RefStrings.MODID, "textures/models/projectiles/rocket_mirv.png"); public static final ResourceLocation mini_nuke_tex = new ResourceLocation(RefStrings.MODID, "textures/models/projectiles/mini_nuke.png"); public static final ResourceLocation mini_mirv_tex = new ResourceLocation(RefStrings.MODID, "textures/models/projectiles/mini_mirv.png"); + public static final ResourceLocation casings_tex = new ResourceLocation(RefStrings.MODID, "textures/particle/casings.png"); //Bomber public static final ResourceLocation dornier_0_tex = new ResourceLocation(RefStrings.MODID, "textures/models/dornier_0.png"); diff --git a/src/main/java/com/hbm/particle/ParticleSpentCasing.java b/src/main/java/com/hbm/particle/ParticleSpentCasing.java new file mode 100644 index 000000000..ced897913 --- /dev/null +++ b/src/main/java/com/hbm/particle/ParticleSpentCasing.java @@ -0,0 +1,209 @@ +package com.hbm.particle; + +import java.util.ArrayList; +import java.util.List; + +import org.lwjgl.opengl.GL11; +import org.lwjgl.opengl.GL12; + +import com.hbm.main.ResourceManager; +import com.hbm.util.Tuple.Pair; + +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; +import net.minecraft.client.Minecraft; +import net.minecraft.client.particle.EntityFX; +import net.minecraft.client.renderer.RenderHelper; +import net.minecraft.client.renderer.Tessellator; +import net.minecraft.client.renderer.texture.TextureManager; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.util.Vec3; +import net.minecraft.world.World; + +@SideOnly(Side.CLIENT) +public class ParticleSpentCasing extends EntityFX +{ + private static float dScale = 0.05F, smokeJitter = 0.025F, smokeAccel = 0.5F; + private static byte maxSmokeGen = 60, maxSmokeLife = 120; + + private final List> smokeNodes = new ArrayList(); + + private final TextureManager textureManager; + + private final SpentCasingConfig config; + private boolean smoke; + + private float momentumPitch, momentumYaw; + private boolean onGroundPreviously = false; + private double maxHeight; + + public ParticleSpentCasing(TextureManager textureManager, World world, double x, double y, double z, double mx, double my, double mz, float momentumPitch, float momentumYaw, SpentCasingConfig config) { + super(world, x, y, z, 0, 0, 0); + this.textureManager = textureManager; + this.momentumPitch = momentumPitch; + this.momentumYaw = momentumYaw; + this.config = config; + + particleMaxAge = 240; +// smoke = config.getSmokeChance() == 0 ? true +// : config.getSmokeChance() < 0 ? false +// : rand.nextInt(config.getSmokeChance()) == 0; + + motionX = mx; + motionY = my; + motionZ = mz; + + particleGravity = 8f; + + maxHeight = y; + } + + @Override + public int getFXLayer() { + return 3; + } + + @Override + public void onUpdate() { + super.onUpdate(); + + if(motionY > 0 && posY > maxHeight) + maxHeight = posY; + + if(!onGroundPreviously && onGround) + tryPlayBounceSound(); + + // TODO Bounce factor in config + if(!onGroundPreviously && onGround) { + + onGroundPreviously = true; + motionY = Math.log10(maxHeight - posY + 2); + momentumPitch = (float) rand.nextGaussian() * config.getPitchFactor(); + momentumYaw = (float) rand.nextGaussian() * config.getYawFactor(); + maxHeight = posY; + + } else if(onGroundPreviously && !onGround) { + onGroundPreviously = false; + } + +// if (particleAge > maxSmokeLife && !smokeNodes.isEmpty()) +// smokeNodes.clear(); + +// if (smoke && particleAge <= maxSmokeLife) +// { +// final double side = (rotationYaw - prevRotationYaw) * 0.1D; +// final Vec3 prev = Vec3.createVectorHelper(motionX, motionY, motionZ); +// prev.rotateAroundY((float) Math.toRadians(rotationYaw)); +// +// for (Pair pair : smokeNodes) +// { +// final EasyLocation node = pair.getKey(); +// +// node.posX += prev.xCoord * smokeAccel + rand.nextGaussian() * smokeJitter + side; +// node.posY += prev.yCoord + smokeAccel; +// node.posZ += prev.zCoord * smokeAccel + rand.nextGaussian() * smokeJitter; +// } +// +// if (particleAge < maxSmokeGen || inWater) +// { +// final double alpha = (particleAge / 20d); +// smokeNodes.add(new Pair(EasyLocation.getZeroLocation(), alpha)); +// } +// } + + prevRotationPitch = rotationPitch; + prevRotationYaw = rotationYaw; + +// if (motionY > gravity && !onGround) +// motionY += gravity; +// if (motionY < -0.75) +// motionY = -0.75; + + if(onGround) + rotationPitch = 0; + else { + rotationPitch += momentumPitch; + rotationYaw += momentumYaw; + } + } + + @Override + public void renderParticle(Tessellator tessellator, float interp, float x, float y, float z, float tx, float tz) { + + GL11.glPushMatrix(); + RenderHelper.enableStandardItemLighting(); + GL11.glEnable(GL11.GL_CULL_FACE); + GL11.glShadeModel(GL11.GL_SMOOTH); + GL11.glEnable(GL12.GL_RESCALE_NORMAL); + + textureManager.bindTexture(ResourceManager.casings_tex); + + EntityPlayer player = Minecraft.getMinecraft().thePlayer; + double dX = player.lastTickPosX + (player.posX - player.lastTickPosX) * (double)interp; + double dY = player.lastTickPosY + (player.posY - player.lastTickPosY) * (double)interp; + double dZ = player.lastTickPosZ + (player.posZ - player.lastTickPosZ) * (double)interp; + + GL11.glTranslated(prevPosX + (posX - prevPosX) * interp - dX, prevPosY + (posY - prevPosY) * interp - dY - this.height / 4, prevPosZ + (posZ - prevPosZ) * interp - dZ); + + GL11.glScalef(dScale, dScale, dScale); + + GL11.glRotatef(180 - rotationYaw, 0, 1, 0); + GL11.glRotatef(-rotationPitch, 1, 0, 0); + + GL11.glScalef(config.getScaleX(), config.getScaleY(), config.getScaleZ()); + + if(config.doesOverrideColor()) + GL11.glColor3b((byte) config.getRedOverride(), (byte) config.getGreenOverride(), (byte) config.getBlueOverride()); + + ResourceManager.casings.renderPart(config.getCasingType().objName); + GL11.glDisable(GL12.GL_RESCALE_NORMAL); + + /*if(!smokeNodes.isEmpty()) { + tessellator.startDrawingQuads(); + tessellator.setNormal(0F, 1F, 0F); + + for(int i = 0; i < smokeNodes.size() - 1; i++) { + final Pair node = smokeNodes.get(i), past = smokeNodes.get(i + 1); + final Vec3 nodeLoc = node.getKey(), pastLoc = past.getKey(); + final float nodeAlpha = node.getValue().floatValue(), pastAlpha = past.getValue().floatValue(), scale = config.getScaleX(); + + tessellator.setColorRGBA_F(1F, 1F, 1F, nodeAlpha); + tessellator.addVertex(nodeLoc.xCoord, nodeLoc.yCoord, nodeLoc.zCoord); + tessellator.setColorRGBA_F(1F, 1F, 1F, 0F); + tessellator.addVertex(nodeLoc.xCoord + scale, nodeLoc.yCoord, nodeLoc.zCoord); + tessellator.setColorRGBA_F(1F, 1F, 1F, 0F); + tessellator.addVertex(pastLoc.xCoord + scale, pastLoc.yCoord, pastLoc.zCoord); + tessellator.setColorRGBA_F(1F, 1F, 1F, pastAlpha); + tessellator.addVertex(pastLoc.xCoord, pastLoc.yCoord, pastLoc.zCoord); + + tessellator.setColorRGBA_F(1F, 1F, 1F, nodeAlpha); + tessellator.addVertex(nodeLoc.xCoord, nodeLoc.yCoord, nodeLoc.zCoord); + tessellator.setColorRGBA_F(1F, 1F, 1F, 0F); + tessellator.addVertex(nodeLoc.xCoord - scale, nodeLoc.yCoord, nodeLoc.zCoord); + tessellator.setColorRGBA_F(1F, 1F, 1F, 0F); + tessellator.addVertex(pastLoc.xCoord - scale, pastLoc.yCoord, pastLoc.zCoord); + tessellator.setColorRGBA_F(1F, 1F, 1F, pastAlpha); + tessellator.addVertex(pastLoc.xCoord, pastLoc.yCoord, pastLoc.zCoord); + } + + GL11.glAlphaFunc(GL11.GL_GREATER, 0F); + GL11.glEnable(GL11.GL_BLEND); + GL11.glDisable(GL11.GL_TEXTURE_2D); + tessellator.draw(); + GL11.glEnable(GL11.GL_TEXTURE_2D); + GL11.glDisable(GL11.GL_BLEND); + GL11.glAlphaFunc(GL11.GL_GEQUAL, 0.1F); + }*/ + + RenderHelper.disableStandardItemLighting(); + GL11.glShadeModel(GL11.GL_FLAT); + GL11.glPopMatrix(); + } + + private void tryPlayBounceSound() { + + if(!config.getBounceSound().isEmpty()) { + worldObj.playSoundAtEntity(this, config.getBounceSound(), 2, 1); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/hbm/particle/SpentCasingConfig.java b/src/main/java/com/hbm/particle/SpentCasingConfig.java new file mode 100644 index 000000000..89f55b076 --- /dev/null +++ b/src/main/java/com/hbm/particle/SpentCasingConfig.java @@ -0,0 +1,323 @@ +package com.hbm.particle; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Random; + +import org.lwjgl.util.vector.Matrix4f; +import org.lwjgl.util.vector.Vector3f; +import org.lwjgl.util.vector.Vector4f; + +import com.google.common.annotations.Beta; +import com.google.common.collect.ImmutableMap; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.texture.TextureManager; +import net.minecraft.util.Vec3; +import net.minecraft.world.World; + +@Beta +public class SpentCasingConfig { + + private static final Map CONFIG_MAP = new HashMap(); + private static final Random RANDOM = new Random(); + + public enum CasingType { + BRASS_STRAIGHT_WALL("Straight"), + BRASS_BOTTLENECK("Bottleneck"), + SHOTGUN("Shotgun"), + AR2("AR2"); + + public final String objName; + + private CasingType(String objName) { + this.objName = objName; + } + } + + /**Unique name used for map lookup.**/ + private final String registryName; + /**Change position of the ejecting shell.**/ + private final Vec3 posOffset; + /**Set initial motion after ejecting.**/ + private final Vec3 initialMotion; + /**Multipliers for random pitch and yaw.**/ + private final float pitchFactor, yawFactor; + /**Rescale the sprite to match the approximate scale of the rounds.**/ + private final float scaleX, scaleY, scaleZ; + /**Overrides for the sprite colors.**/ + private final int redOverride, greenOverride, blueOverride; + /**Whether or not to override the default sprite color scheme.**/ + private final boolean overrideColor; + /**The type of casing.**/ + private final CasingType casingType; + /**Amount of casings to spawn per event. Default 1.**/ + private final byte casingAmount; + /**If the casing(s) should be spawned after reloading, instead of after firing.**/ + private final boolean afterReload; + /**Sound effect for bouncing. Make empty string to have no sound.**/ + private final String bounceSound; + /**Delay before casings are actually spawned**/ + private final byte delay; + /**Chance for the casing to emit smoke. 0 for 100% chance and -1 for it to never make smoke.**/ + private final byte smokeChance; + // TODO Setting to disregard crouch effect and/or another offset specifically for crouching which can be set to null to use the default one + public SpentCasingConfig( + String registryName, Vec3 posOffset, Vec3 initialMotion, float pitchFactor, float yawFactor, + float scaleX, float scaleY, float scaleZ, int redOverride, int greenOverride, int blueOverride, + boolean overrideColor, CasingType casingType, byte casingAmount, boolean afterReload, String bounceSound, + byte delay, byte smokeChance) { + + this.registryName = registryName; + this.posOffset = posOffset; + this.initialMotion = initialMotion; + this.pitchFactor = pitchFactor; + this.yawFactor = yawFactor; + this.scaleX = scaleX; + this.scaleY = scaleY; + this.scaleZ = scaleZ; + this.redOverride = redOverride; + this.greenOverride = greenOverride; + this.blueOverride = blueOverride; + this.overrideColor = overrideColor; + this.casingType = casingType; + this.casingAmount = casingAmount; + this.afterReload = afterReload; + this.bounceSound = bounceSound; + this.delay = delay; + this.smokeChance = smokeChance; + + CONFIG_MAP.put(registryName, this); + } + + public void spawnCasing(TextureManager textureManager, World world, double x, double y, double z, float pitch, float yaw, boolean crouched) { + Vec3 rotatedMotionVec = rotateVector(getInitialMotion(), + pitch + (float) RANDOM.nextGaussian() * getPitchFactor(), yaw + (float) RANDOM.nextGaussian() * getPitchFactor(), + getPitchFactor(), getPitchFactor()); + + ParticleSpentCasing casing = new ParticleSpentCasing(textureManager, world, x, + y, z, rotatedMotionVec.xCoord, rotatedMotionVec.yCoord, rotatedMotionVec.zCoord, +// 0, 0, + (float) (getPitchFactor() * RANDOM.nextGaussian()), (float) (getYawFactor() * RANDOM.nextGaussian()), + this); + + offsetCasing(casing, getPosOffset(), pitch, yaw, crouched); + + casing.rotationPitch = (float) Math.toDegrees(pitch); + casing.rotationYaw = (float) Math.toDegrees(yaw); + + if(overrideColor) + casing.setRBGColorF(redOverride / 255f, blueOverride / 255f, greenOverride / 255f); + Minecraft.getMinecraft().effectRenderer.addEffect(casing); + } + + // Rotate a position + private static void offsetCasing(ParticleSpentCasing casing, Vec3 offset, float pitch, float yaw, boolean crouched) + { +// // x-axis offset, 0 if crouched to center +// final double oX = crouched ? 0 : offset.posX(); +// // Trigonometric operations, saved for convenience +// final double sinP = Math.sin(pitch), cosP = Math.cos(pitch), sinY = Math.sin(yaw), cosY = Math.cos(yaw); +// // New offsets +// final double newX = oX * cosY - offset.posZ() * sinY, +// newY = offset.posY() * cosP - sinP * (oX * sinY + offset.posZ() * cosY), +// newZ = offset.posZ() * sinP + cosP * (oX * sinY + offset.posZ() * cosY); +// +// // Apply +// casing.setPosition(casing.posX + newX, casing.posY + newY, casing.posZ + newZ); + + // x-axis offset, 0 if crouched to center + final float oX = (float) (crouched ? 0 : offset.xCoord); + // Create rotation matrices for pitch and yaw + final Matrix4f pitchMatrix = new Matrix4f(), yawMatrix = new Matrix4f(); + + pitchMatrix.rotate(pitch, new Vector3f(1, 0, 0)); // modify axis of rotation + yawMatrix.rotate(-yaw, new Vector3f(0, 1, 0)); + + // Multiply matrices to get combined rotation matrix + final Matrix4f rotMatrix = Matrix4f.mul(yawMatrix, pitchMatrix, null); + // Create vector representing the offset and apply rotation + final Vector4f offsetVector = new Vector4f(oX, (float) offset.yCoord, (float) offset.zCoord, 1); // set fourth coordinate to 1 + Matrix4f.transform(rotMatrix, offsetVector, offsetVector); + final Vector3f result = new Vector3f(); // create result vector + result.set(offsetVector.x, offsetVector.y, offsetVector.z); // set result vector using transformed coordinates + // Apply rotation + casing.setPosition(casing.posX + result.x, casing.posY + result.y, casing.posZ + result.z); + } + +// Rotate a vector + private static Vec3 rotateVector(Vec3 vector, float pitch, float yaw, float pitchFactor, float yawFactor) + { + // Apply randomness to vector + vector.xCoord += RANDOM.nextGaussian() * yawFactor; + vector.yCoord += RANDOM.nextGaussian() * pitchFactor; + vector.zCoord += RANDOM.nextGaussian() * yawFactor; + + final Matrix4f pitchMatrix = new Matrix4f(), yawMatrix = new Matrix4f(); + + pitchMatrix.setIdentity(); + pitchMatrix.rotate(-pitch, new Vector3f(1, 0, 0)); + + yawMatrix.setIdentity(); + yawMatrix.rotate(-yaw, new Vector3f(0, 1, 0)); + + final Vector4f vector4f = new Vector4f((float) vector.xCoord, (float) vector.yCoord, (float) vector.zCoord, 1); + + Matrix4f.transform(pitchMatrix, vector4f, vector4f); + Matrix4f.transform(yawMatrix, vector4f, vector4f); + + return Vec3.createVectorHelper(vector4f.x, vector4f.y, vector4f.z); + } + + public Vec3 getPosOffset() + { + return posOffset; + } + public Vec3 getInitialMotion() + { + return Vec3.createVectorHelper(initialMotion.xCoord, initialMotion.yCoord, initialMotion.zCoord); + } + public float getScaleX() + { + return scaleX; + } + public float getScaleY() + { + return scaleY; + } + public float getScaleZ() + { + return scaleZ; + } + public float getPitchFactor() + { + return pitchFactor; + } + public float getYawFactor() + { + return yawFactor; + } + public int getRedOverride() + { + return redOverride; + } + public int getGreenOverride() + { + return greenOverride; + } + public int getBlueOverride() + { + return blueOverride; + } + public boolean doesOverrideColor() + { + return overrideColor; + } + public CasingType getCasingType() + { + return casingType; + } + public byte getCasingAmount() + { + return casingAmount; + } + public boolean isAfterReload() + { + return afterReload; + } + public String getRegistryName() + { + return registryName; + } + public String getBounceSound() + { + return bounceSound; + } + public byte getDelay() + { + return delay; + } + public byte getSmokeChance() + { + return smokeChance; + } + + /** + * Convert to a new builder for modification. + * @param newName The new registry name. + * @return A new builder with all the settings inherited from this config, except for registry name. + */ + public SpentCasingConfigBuilder toBuilder(String newName) + { + final SpentCasingConfigBuilder builder = new SpentCasingConfigBuilder(newName, casingType, overrideColor); + builder.setAfterReload(afterReload).setBlueOverride(blueOverride).setBounceSound(bounceSound).setCasingAmount(casingAmount) + .setDelay(delay).setGreenOverride(greenOverride).setInitialMotion(getInitialMotion()).setPitchFactor(pitchFactor) + .setPosOffset(getPosOffset()).setRedOverride(redOverride).setScaleX(scaleX).setScaleY(scaleY).setScaleZ(scaleZ) + .setSmokeChance(smokeChance).setYawFactor(yawFactor); + return builder; + } + + @Override + public int hashCode() + { + return Objects.hash(afterReload, blueOverride, bounceSound, casingAmount, casingType, delay, greenOverride, + initialMotion.xCoord, initialMotion.yCoord, initialMotion.zCoord, overrideColor, pitchFactor, + posOffset, redOverride, registryName, scaleX, scaleY, scaleZ, smokeChance, yawFactor); + } + @Override + public boolean equals(Object obj) + { + if (this == obj) + return true; + if (!(obj instanceof SpentCasingConfig)) + return false; + final SpentCasingConfig other = (SpentCasingConfig) obj; + return afterReload == other.afterReload && blueOverride == other.blueOverride + && Objects.equals(bounceSound, other.bounceSound) && casingAmount == other.casingAmount + && casingType == other.casingType && delay == other.delay && greenOverride == other.greenOverride + && Double.doubleToLongBits(initialMotion.xCoord) == Double.doubleToLongBits(other.initialMotion.xCoord) + && Double.doubleToLongBits(initialMotion.yCoord) == Double.doubleToLongBits(other.initialMotion.yCoord) + && Double.doubleToLongBits(initialMotion.zCoord) == Double.doubleToLongBits(other.initialMotion.zCoord) + && overrideColor == other.overrideColor + && Float.floatToIntBits(pitchFactor) == Float.floatToIntBits(other.pitchFactor) + && Objects.equals(posOffset, other.posOffset) && redOverride == other.redOverride + && Objects.equals(registryName, other.registryName) + && Float.floatToIntBits(scaleX) == Float.floatToIntBits(other.scaleX) + && Float.floatToIntBits(scaleY) == Float.floatToIntBits(other.scaleY) + && Float.floatToIntBits(scaleZ) == Float.floatToIntBits(other.scaleZ) + && smokeChance == other.smokeChance + && Float.floatToIntBits(yawFactor) == Float.floatToIntBits(other.yawFactor); + } + @Override + public String toString() + { + final StringBuilder builder = new StringBuilder(); + builder.append("SpentCasingConfig [registryName=").append(registryName).append(", posOffset=").append(posOffset) + .append(", initialMotion=").append(initialMotion).append(", pitchFactor=").append(pitchFactor) + .append(", yawFactor=").append(yawFactor).append(", scaleX=").append(scaleX).append(", scaleY=") + .append(scaleY).append(", scaleZ=").append(scaleZ).append(", redOverride=").append(redOverride) + .append(", greenOverride=").append(greenOverride).append(", blueOverride=").append(blueOverride) + .append(", overrideColor=").append(overrideColor).append(", casingType=").append(casingType) + .append(", casingAmount=").append(casingAmount).append(", afterReload=").append(afterReload) + .append(", bounceSound=").append(bounceSound).append(", delay=").append(delay).append(", smokeChance=") + .append(smokeChance).append("]"); + return builder.toString(); + } + + public static boolean containsKey(String key) + { + return CONFIG_MAP.containsKey(key); + } + + public static SpentCasingConfig get(String key) + { + return CONFIG_MAP.get(key); + } + + public static Map getConfigMap() + { + return ImmutableMap.copyOf(CONFIG_MAP); + } + +} \ No newline at end of file diff --git a/src/main/java/com/hbm/particle/SpentCasingConfigBuilder.java b/src/main/java/com/hbm/particle/SpentCasingConfigBuilder.java new file mode 100644 index 000000000..67c30cad9 --- /dev/null +++ b/src/main/java/com/hbm/particle/SpentCasingConfigBuilder.java @@ -0,0 +1,377 @@ +package com.hbm.particle; + +import java.util.Objects; + +import com.google.common.annotations.Beta; +import com.hbm.main.MainRegistry; +import com.hbm.particle.SpentCasingConfig.CasingType; + +import net.minecraft.util.MathHelper; +import net.minecraft.util.Vec3; + +@Beta +public class SpentCasingConfigBuilder implements Cloneable +{ + /**Unique name used for map lookup.**/ + private String registryName; + /**Change position of the ejecting shell.**/ + private Vec3 posOffset = Vec3.createVectorHelper(0, 0, 0); + /**Set initial motion after ejecting.**/ + private Vec3 initialMotion = Vec3.createVectorHelper(0, 0, 0); + /**Multipliers for random pitch and yaw.**/ + private float pitchFactor, yawFactor; + /**Rescale the sprite to match the approximate scale of the rounds.**/ + private float scaleX = 1, scaleY = 1, scaleZ = 1; + /**Overrides for the sprite colors.**/ + private int redOverride, greenOverride, blueOverride; + /**Whether or not to override the default sprite color scheme.**/ + private boolean overrideColor; + /**The type of casing.**/ + private CasingType casingType = CasingType.BRASS_STRAIGHT_WALL; + /**Amount of casings to spawn per event. Default 1.**/ + private byte casingAmount = 1; + /**If the casing(s) should be spawned after reloading, instead of after firing.**/ + private boolean afterReload; + /**Sound effect for bouncing. Make empty string to have no sound.**/ + private String bounceSound = ""; + /**Delay before casings are actually spawned**/ + private byte delay; + /**Chance for the casing to emit smoke. 0 for 100% chance and -1 for it to never make smoke.**/ + private byte smokeChance = -1; + // TODO Setting to disregard crouch effect and/or another offset specifically for crouching which can be set to null to use the default one + /** + * Constructor with fields for the required bare minimum parameters.
+ * All parameters may overridden using setters at any time at your discretion, however. + * @param registryName The unique name for map lookup. Null not permitted, becomes empty string. + * @param casingType Type of casing model to use. Null not permitted, defaults to straight wall type. + * @param overrideColor Whether or not the config will override the model texture's color. + */ + public SpentCasingConfigBuilder(String registryName, CasingType casingType, boolean overrideColor) { + this.registryName = registryName == null ? "" : registryName; + this.casingType = casingType == null ? CasingType.BRASS_STRAIGHT_WALL : casingType; + this.overrideColor = overrideColor; + } + + public Vec3 getPosOffset() { + return posOffset; + } + + /** + * Set the relative x, y, z coordinate offset on spawn. + * @param posOffset Any ILocationProvider that serves as the offset. Null becomes a zero location. + * @return Itself for chaining. + */ + public SpentCasingConfigBuilder setPosOffset(Vec3 posOffset) { + this.posOffset = posOffset == null ? Vec3.createVectorHelper(0, 0, 0) : posOffset; + return this; + } + + public Vec3 getInitialMotion() { + return initialMotion; + } + + /** + * Sets the casing's initial relative x, y, z motion on spawn. + * @param initialMotion Vector representing the initial motion. Null becomes a zero vector. + * @return Itself for chaining. + */ + public SpentCasingConfigBuilder setInitialMotion(Vec3 initialMotion) { + this.initialMotion = initialMotion == null ? Vec3.createVectorHelper(0, 0, 0) : initialMotion; + return this; + } + + public double getScaleX() { + return scaleX; + } + + /** + * Scale of the model on its x-axis. + * @param scaleX The scale/stretch factor of the model on the x-axis. + * @return Itself for chaining. + */ + public SpentCasingConfigBuilder setScaleX(float scaleX) { + this.scaleX = scaleX; + return this; + } + + public double getScaleY() { + return scaleY; + } + + /** + * Scale of the model on its y-axis. + * @param scaleY The scale/stretch factor of the model on the y-axis. + * @return Itself for chaining. + */ + public SpentCasingConfigBuilder setScaleY(float scaleY) { + this.scaleY = scaleY; + return this; + } + + public float getScaleZ() { + return scaleZ; + } + + /** + * Scale of the model on its z-axis. + * @param scaleZ The scale/stretch of the model on the z-axis. + * @return Itself for chaining. + */ + public SpentCasingConfigBuilder setScaleZ(float scaleZ) { + this.scaleZ = scaleZ; + return this; + } + + public float getPitchFactor() { + return pitchFactor; + } + + /** + * Multiplier for random pitch-related motion. Recommended to keep in the thousanths (0.00X), as even with the hundreths (0.0X) the pitch can get crazy at times. + * @param pitchFactor The multiplier. + * @return Itself for chaining. + */ + public SpentCasingConfigBuilder setPitchFactor(float pitchFactor) { + this.pitchFactor = pitchFactor; + return this; + } + + public float getYawFactor() { + return yawFactor; + } + + /** + * Multiplier for random yaw-related motion. Recommended to keep in the thousanths (0.00X), as even with the hundreths (0.0X) the yaw can get crazy at times. + * @param yawFactor The multiplier. + * @return Itself for chaining. + */ + public SpentCasingConfigBuilder setYawFactor(float yawFactor) { + this.yawFactor = yawFactor; + return this; + } + + public int getRedOverride() { + return redOverride; + } + + /** + * Red color override. Clamped between 0-255, but I don't know how it actually works on the receiving end, so feel free to change. + * @param redOverride Red color override. + * @return Itself for chaining. + */ + public SpentCasingConfigBuilder setRedOverride(int redOverride) { + this.redOverride = MathHelper.clamp_int(redOverride, 0, 255); + return this; + } + + public int getGreenOverride() { + return greenOverride; + } + + /** + * Green color override. Clamped between 0-255, but I don't know how it actually works on the receiving end, so feel free to change. + * @param greenOverride Green color override. + * @return Itself for chaining. + */ + public SpentCasingConfigBuilder setGreenOverride(int greenOverride) { + this.greenOverride = MathHelper.clamp_int(greenOverride, 0, 255); + return this; + } + + public int getBlueOverride() { + return blueOverride; + } + + /** + * Blue color override. Clamped between 0-255, but I don't know how it actually works on the receiving end, so feel free to change. + * @param blueOverride Blue color override. + * @return Itself for chaining. + */ + public SpentCasingConfigBuilder setBlueOverride(int blueOverride) { + this.blueOverride = MathHelper.clamp_int(blueOverride, 0, 255); + return this; + } + + public boolean doesOverrideColor() { + return overrideColor; + } + + /** + * Sets whether or not the config will override color. If false, the integer overrides will be ignored. + * @param overrideColor True, to use the integer color overrides, false to ignore them. + * @return Itself for chaining. + */ + public SpentCasingConfigBuilder setOverrideColor(boolean overrideColor) { + this.overrideColor = overrideColor; + return this; + } + + public CasingType getCasingType() { + return casingType; + } + + /** + * Sets the model the casing will use. + * @param casingType One of the 4 casing model types. Null is not permitted, will have no effect. + * @return Itself for chaining. + */ + public SpentCasingConfigBuilder setCasingType(CasingType casingType) { + this.casingType = casingType == null ? this.casingType : casingType; + return this; + } + + public byte getCasingAmount() { + return casingAmount; + } + + /** + * Sets the amount of casings to spawn. Default is 1. + * @param casingAmount Amount of casings to spawn. Clamped to a positive byte (0 - 127). + * @return Itself for chaining. + */ + public SpentCasingConfigBuilder setCasingAmount(int casingAmount) { + this.casingAmount = (byte) MathHelper.clamp_int(casingAmount, 0, 127); + return this; + } + + public boolean isAfterReload() { + return afterReload; + } + + /** + * Sets whether or not the casing will be spawned after reloading is done or after firing. + * @param afterReload If true, casings will be spawned when reloading, false, they will be spawned after firing. + * @return Itself for chaining. + */ + public SpentCasingConfigBuilder setAfterReload(boolean afterReload) { + this.afterReload = afterReload; + return this; + } + + public String getRegistryName() { + return registryName; + } + + /** + * Resets the unique name for the config used in map lookup.
+ * As the real class is self-registering, make sure to set this in reused builders. + * @param registryName The registry name to use. Null is not permitted, becomes an empty string. + * @return Itself for chaining. + */ + public SpentCasingConfigBuilder setRegistryName(String registryName) { + this.registryName = registryName == null ? "" : registryName; + return this; + } + + public String getBounceSound() { + return bounceSound; + } + + /** + * The sound used when bouncing. If empty, no sound will be made. + * @param bounceSound The sound path. Null is not permitted, becomes an empty string. + * @return Itself for chaining. + */ + public SpentCasingConfigBuilder setBounceSound(String bounceSound) { + this.bounceSound = bounceSound == null ? "" : bounceSound; + return this; + } + + public byte getDelay() { + return delay; + } + + /** + * The delay in ticks before spawning. + * @param delay Tick spawn delay. Must be nonnegative, otherwise 0. + * @return Itself for chaining. + */ + public SpentCasingConfigBuilder setDelay(int delay) { + this.delay = (byte) (delay < 0 ? 0 : delay); + return this; + } + + public byte getSmokeChance() { + return smokeChance; + } + + /** + * Chance for the casing to emit smoke. 0 for 100% chance and -1 for it to never make smoke. + * @param smokeChance Chance to emit smoke. Clamped to a byte. + * @return Itself for chaining. + */ + public SpentCasingConfigBuilder setSmokeChance(int smokeChance) { + this.smokeChance = (byte) smokeChance; + return this; + } + + /** + * Constructs the true immutable config with all settings loaded from this builder.
+ * This builder may be reused as all non-immutable and primitive fields are copied before being passed to the constructor.
+ * The config's constructor is self-registering. + * @return The finished config with its settings specified by this builder. + */ + public SpentCasingConfig build() { + return new SpentCasingConfig(registryName, Vec3.createVectorHelper(posOffset.xCoord, posOffset.yCoord, posOffset.zCoord), + Vec3.createVectorHelper(initialMotion.xCoord, initialMotion.yCoord, initialMotion.zCoord), pitchFactor, + yawFactor, scaleX, scaleY, scaleZ, redOverride, greenOverride, blueOverride, overrideColor, casingType, + casingAmount, afterReload, bounceSound, delay, smokeChance); + } + + @Override + public int hashCode() { + return Objects.hash(afterReload, blueOverride, bounceSound, casingAmount, casingType, delay, greenOverride, + initialMotion.xCoord, initialMotion.yCoord, initialMotion.zCoord, overrideColor, pitchFactor, + posOffset, redOverride, registryName, scaleX, scaleY, scaleZ, smokeChance, yawFactor); + } + + @Override + public boolean equals(Object obj) { + + if(this == obj) return true; + if(!(obj instanceof SpentCasingConfigBuilder)) return false; + + SpentCasingConfigBuilder other = (SpentCasingConfigBuilder) obj; + return afterReload == other.afterReload && blueOverride == other.blueOverride + && Objects.equals(bounceSound, other.bounceSound) && casingAmount == other.casingAmount + && casingType == other.casingType && delay == other.delay && greenOverride == other.greenOverride + && Double.doubleToLongBits(initialMotion.xCoord) == Double.doubleToLongBits(other.initialMotion.xCoord) + && Double.doubleToLongBits(initialMotion.yCoord) == Double.doubleToLongBits(other.initialMotion.yCoord) + && Double.doubleToLongBits(initialMotion.zCoord) == Double.doubleToLongBits(other.initialMotion.zCoord) + && overrideColor == other.overrideColor + && Float.floatToIntBits(pitchFactor) == Float.floatToIntBits(other.pitchFactor) + && Objects.equals(posOffset, other.posOffset) && redOverride == other.redOverride + && Objects.equals(registryName, other.registryName) + && Float.floatToIntBits(scaleX) == Float.floatToIntBits(other.scaleX) + && Float.floatToIntBits(scaleY) == Float.floatToIntBits(other.scaleY) + && Float.floatToIntBits(scaleZ) == Float.floatToIntBits(other.scaleZ) + && smokeChance == other.smokeChance + && Float.floatToIntBits(yawFactor) == Float.floatToIntBits(other.yawFactor); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("SpentCasingConfigBuilder [registryName=").append(registryName).append(", posOffset=") + .append(posOffset).append(", initialMotion=").append(initialMotion).append(", pitchFactor=") + .append(pitchFactor).append(", yawFactor=").append(yawFactor).append(", scaleX=").append(scaleX) + .append(", scaleY=").append(scaleY).append(", scaleZ=").append(scaleZ).append(", redOverride=") + .append(redOverride).append(", greenOverride=").append(greenOverride).append(", blueOverride=") + .append(blueOverride).append(", overrideColor=").append(overrideColor).append(", casingType=") + .append(casingType).append(", casingAmount=").append(casingAmount).append(", afterReload=") + .append(afterReload).append(", bounceSound=").append(bounceSound).append(", delay=").append(delay) + .append(", smokeChance=").append(smokeChance).append(']'); + return builder.toString(); + } + + @Override + public SpentCasingConfigBuilder clone() { + try { + return (SpentCasingConfigBuilder) super.clone(); + } catch(CloneNotSupportedException e) { + MainRegistry.logger.catching(e); + return new SpentCasingConfigBuilder(registryName, casingType, overrideColor); + } + } + +} \ No newline at end of file diff --git a/src/main/resources/assets/hbm/models/effect/casings.obj b/src/main/resources/assets/hbm/models/effect/casings.obj new file mode 100644 index 000000000..2ce04cf4b --- /dev/null +++ b/src/main/resources/assets/hbm/models/effect/casings.obj @@ -0,0 +1,1084 @@ +# Blender v2.79 (sub 0) OBJ File: '' +# www.blender.org +o AR2 +v 0.254558 0.254558 -1.200000 +v 0.254558 -0.254558 -1.200000 +v -0.254558 -0.254558 -1.200000 +v 0.424264 -0.424264 -0.000000 +v 0.600000 0.000000 0.000000 +v -0.600000 0.000000 0.000000 +v 0.424264 -0.424264 -0.400000 +v -0.424264 -0.424264 -0.400000 +v -0.424264 0.424264 -0.400000 +v -0.254559 0.254558 -1.200000 +v 0.000000 0.360000 -1.200000 +v 0.360000 0.000000 -1.200000 +v 0.000000 -0.360000 -1.200000 +v -0.360000 0.000000 -1.200000 +v 0.424264 0.424264 0.000000 +v 0.000000 0.600000 0.000000 +v -0.424264 0.424264 0.000000 +v -0.424264 -0.424264 -0.000000 +v 0.000000 -0.600000 -0.000000 +v 0.000000 0.600000 -0.400000 +v 0.424264 0.424264 -0.400000 +v 0.600000 0.000000 -0.400000 +v 0.000000 -0.600000 -0.400000 +v -0.600000 0.000000 -0.400000 +v 0.000000 0.360000 -0.400000 +v 0.254558 0.254558 -1.200000 +v 0.000000 0.360000 -1.200000 +v 0.254558 0.254558 -0.400000 +v 0.360000 0.000000 -1.200000 +v 0.360000 0.000000 -0.400000 +v 0.254558 -0.254558 -1.200000 +v 0.254558 -0.254558 -0.400000 +v 0.000000 -0.360000 -1.200000 +v 0.000000 -0.360000 -0.400000 +v -0.254558 -0.254558 -1.200000 +v -0.254558 -0.254559 -0.400000 +v -0.360000 0.000000 -1.200000 +v -0.360000 0.000000 -0.400000 +v -0.254559 0.254558 -1.200000 +v -0.254559 0.254558 -0.400000 +vt 0.171253 0.570129 +vt 0.171253 0.430121 +vt 0.031246 0.430122 +vt 0.171253 0.430121 +vt 0.200250 0.500125 +vt 0.002250 0.500125 +vt 0.171253 0.430121 +vt 0.031246 0.430122 +vt 0.031246 0.570129 +vt 0.031246 0.570129 +vt 0.101250 0.599125 +vt 0.200250 0.500125 +vt 0.101250 0.401125 +vt 0.002250 0.500125 +vt 0.171253 0.570129 +vt 0.101250 0.599125 +vt 0.031246 0.570129 +vt 0.031246 0.430122 +vt 0.101250 0.401125 +vt 0.101250 0.599125 +vt 0.171253 0.570129 +vt 0.200250 0.500125 +vt 0.101250 0.401125 +vt 0.002250 0.500125 +vt 0.162500 0.566875 +vt 0.146875 0.504375 +vt 0.162500 0.504375 +vt 0.146875 0.566875 +vt 0.131250 0.504375 +vt 0.131250 0.566875 +vt 0.115625 0.504375 +vt 0.115625 0.566875 +vt 0.100000 0.504375 +vt 0.100000 0.566875 +vt 0.084375 0.504375 +vt 0.084375 0.566875 +vt 0.068750 0.504375 +vt 0.068750 0.566875 +vt 0.053125 0.504375 +vt 0.053125 0.566875 +vt 0.037500 0.504375 +vt 0.037500 0.566875 +vt 0.162500 0.566875 +vt 0.146875 0.504375 +vt 0.162500 0.504375 +vt 0.146875 0.566875 +vt 0.131250 0.504375 +vt 0.131250 0.566875 +vt 0.115625 0.504375 +vt 0.115625 0.566875 +vt 0.100000 0.504375 +vt 0.100000 0.566875 +vt 0.084375 0.504375 +vt 0.084375 0.566875 +vt 0.068750 0.504375 +vt 0.068750 0.566875 +vt 0.053125 0.504375 +vt 0.053125 0.566875 +vt 0.037500 0.504375 +vt 0.037500 0.566875 +vn 0.0000 0.0000 -1.0000 +vn 0.0000 -0.0000 1.0000 +vn 0.0000 1.0000 0.0000 +vn 0.7071 0.7071 0.0000 +vn 1.0000 0.0000 -0.0000 +vn 0.7071 -0.7071 -0.0000 +vn -0.0000 -1.0000 -0.0000 +vn -0.7071 -0.7071 -0.0000 +vn -1.0000 -0.0000 0.0000 +vn -0.7071 0.7071 0.0000 +s off +f 1/1/1 2/2/1 3/3/1 +f 4/4/2 5/5/2 6/6/2 +f 7/7/1 8/8/1 9/9/1 +f 10/10/1 11/11/1 1/1/1 +f 1/1/1 12/12/1 2/2/1 +f 2/2/1 13/13/1 3/3/1 +f 3/3/1 14/14/1 10/10/1 +f 10/10/1 1/1/1 3/3/1 +f 5/5/2 15/15/2 16/16/2 +f 16/16/2 17/17/2 5/5/2 +f 17/17/2 6/6/2 5/5/2 +f 6/6/2 18/18/2 19/19/2 +f 19/19/2 4/4/2 6/6/2 +f 9/9/1 20/20/1 21/21/1 +f 21/21/1 22/22/1 7/7/1 +f 7/7/1 23/23/1 8/8/1 +f 8/8/1 24/24/1 9/9/1 +f 9/9/1 21/21/1 7/7/1 +s 1 +f 16/25/3 21/26/4 20/27/3 +f 15/28/4 22/29/5 21/26/4 +f 5/30/5 7/31/6 22/29/5 +f 4/32/6 23/33/7 7/31/6 +f 19/34/7 8/35/8 23/33/7 +f 18/36/8 24/37/9 8/35/8 +f 6/38/9 9/39/10 24/37/9 +f 17/40/10 20/41/3 9/39/10 +f 16/25/3 15/28/4 21/26/4 +f 15/28/4 5/30/5 22/29/5 +f 5/30/5 4/32/6 7/31/6 +f 4/32/6 19/34/7 23/33/7 +f 19/34/7 18/36/8 8/35/8 +f 18/36/8 6/38/9 24/37/9 +f 6/38/9 17/40/10 9/39/10 +f 17/40/10 16/42/3 20/41/3 +f 25/43/3 26/44/4 27/45/3 +f 28/46/4 29/47/5 26/44/4 +f 30/48/5 31/49/6 29/47/5 +f 32/50/6 33/51/7 31/49/6 +f 34/52/7 35/53/8 33/51/7 +f 36/54/8 37/55/9 35/53/8 +f 38/56/9 39/57/10 37/55/9 +f 40/58/10 27/59/3 39/57/10 +f 25/43/3 28/46/4 26/44/4 +f 28/46/4 30/48/5 29/47/5 +f 30/48/5 32/50/6 31/49/6 +f 32/50/6 34/52/7 33/51/7 +f 34/52/7 36/54/8 35/53/8 +f 36/54/8 38/56/9 37/55/9 +f 38/56/9 40/58/10 39/57/10 +f 40/58/10 25/60/3 27/59/3 +o Shotgun +v -0.353356 -0.353356 1.148335 +v 0.000000 -0.499721 1.148335 +v 0.353356 -0.353356 1.148335 +v 0.000000 0.449124 -1.223040 +v 0.317579 0.317579 -1.223040 +v -0.317579 -0.317578 -1.223040 +v 0.499721 -0.000000 1.148335 +v 0.353356 0.353357 1.148335 +v 0.000000 0.499721 1.148335 +v -0.353356 0.353357 1.148335 +v -0.499721 -0.000000 1.148335 +v 0.449124 0.000000 -1.223040 +v 0.317579 -0.317578 -1.223040 +v 0.000000 -0.449123 -1.223040 +v -0.449124 0.000000 -1.223040 +v -0.317579 0.317579 -1.223040 +v 0.317579 -0.317578 1.112764 +v 0.499721 -0.000000 1.148335 +v 0.353356 -0.353356 1.148335 +v 0.000000 -0.449124 1.112764 +v -0.353356 -0.353356 1.148335 +v -0.317579 -0.317578 1.112764 +v 0.000000 -0.499721 1.148335 +v 0.000000 0.449124 1.112764 +v 0.353356 0.353357 1.148335 +v 0.317579 0.317579 1.112764 +v 0.449124 -0.000000 1.112764 +v -0.317579 0.317579 1.112764 +v -0.499721 -0.000000 1.148335 +v -0.353356 0.353357 1.148335 +v 0.000000 0.499721 1.148335 +v -0.449124 -0.000000 1.112764 +v 0.000000 -0.449124 1.112764 +v 0.317579 -0.317578 -1.223040 +v 0.317579 -0.317578 1.112764 +v -0.317579 0.317579 1.112764 +v -0.449124 0.000000 -1.223040 +v -0.449124 -0.000000 1.112764 +v 0.317579 0.317579 1.112764 +v 0.000000 0.449124 -1.223040 +v 0.000000 0.449124 1.112764 +v -0.317579 0.317579 -1.223040 +v -0.317579 -0.317578 1.112764 +v 0.000000 -0.449123 -1.223040 +v -0.317579 -0.317578 -1.223040 +v 0.449124 0.000000 -1.223040 +v 0.449124 -0.000000 1.112764 +v 0.317579 0.317579 -1.223040 +vt 0.067942 0.886801 +vt 0.086790 0.867953 +vt 0.113444 0.867953 +vt 0.960365 0.912783 +vt 0.918017 0.955131 +vt 0.858128 0.810547 +vt 0.132291 0.886801 +vt 0.132291 0.913455 +vt 0.113444 0.932302 +vt 0.086790 0.932302 +vt 0.067942 0.913455 +vt 0.858129 0.955131 +vt 0.815781 0.912784 +vt 0.815781 0.852895 +vt 0.918017 0.810547 +vt 0.960365 0.852895 +vt 0.124726 0.724156 +vt 0.120654 0.755786 +vt 0.120654 0.722469 +vt 0.145900 0.702982 +vt 0.177531 0.698910 +vt 0.175844 0.702982 +vt 0.144213 0.698910 +vt 0.175844 0.775274 +vt 0.144213 0.779346 +vt 0.145900 0.775274 +vt 0.124726 0.754100 +vt 0.197018 0.754100 +vt 0.201090 0.722469 +vt 0.201090 0.755787 +vt 0.177531 0.779346 +vt 0.197018 0.724155 +vt 0.323497 0.764346 +vt 0.293553 0.933910 +vt 0.293553 0.764346 +vt 0.468081 0.764346 +vt 0.438137 0.933910 +vt 0.438137 0.764346 +vt 0.365845 0.764346 +vt 0.344671 0.933910 +vt 0.344671 0.764346 +vt 0.489255 0.764346 +vt 0.468081 0.933910 +vt 0.344671 0.764346 +vt 0.323497 0.933910 +vt 0.416963 0.933910 +vt 0.416963 0.764346 +vt 0.416963 0.764346 +vt 0.395789 0.933910 +vt 0.395789 0.764346 +vt 0.365845 0.933910 +vt 0.489255 0.933910 +vt 0.344671 0.933910 +vt 0.416963 0.933910 +vn 0.0000 0.0000 1.0000 +vn 0.0000 0.0000 -1.0000 +vn 0.4067 -0.4067 -0.8181 +vn 0.5751 -0.0000 -0.8181 +vn -0.0000 -0.5751 -0.8181 +vn -0.4067 -0.4067 -0.8181 +vn 0.0000 0.5751 -0.8181 +vn 0.4067 0.4067 -0.8181 +vn -0.4067 0.4067 -0.8181 +vn -0.5751 -0.0000 -0.8181 +vn -0.0000 -1.0000 -0.0000 +vn 0.7071 -0.7071 -0.0000 +vn -0.7071 0.7071 0.0000 +vn -1.0000 -0.0000 -0.0000 +vn 0.7071 0.7071 0.0000 +vn 0.0000 1.0000 0.0000 +vn -0.7071 -0.7071 -0.0000 +vn 1.0000 -0.0000 0.0000 +s off +f 41/61/11 42/62/11 43/63/11 +f 44/64/12 45/65/12 46/66/12 +f 43/63/11 47/67/11 48/68/11 +f 48/68/11 49/69/11 43/63/11 +f 49/69/11 50/70/11 43/63/11 +f 50/70/11 51/71/11 43/63/11 +f 51/71/11 41/61/11 43/63/11 +f 45/65/12 52/72/12 53/73/12 +f 53/73/12 54/74/12 45/65/12 +f 54/74/12 46/66/12 45/65/12 +f 46/66/12 55/75/12 56/76/12 +f 56/76/12 44/64/12 46/66/12 +s 1 +f 57/77/13 58/78/14 59/79/13 +f 60/80/15 61/81/16 62/82/16 +f 60/80/15 59/79/13 63/83/15 +f 64/84/17 65/85/18 66/86/18 +f 66/86/18 58/78/14 67/87/14 +f 68/88/19 69/89/20 70/90/19 +f 64/84/17 70/90/19 71/91/17 +f 62/82/16 69/89/20 72/92/20 +f 73/93/21 74/94/22 75/95/22 +f 76/96/23 77/97/24 78/98/24 +f 79/99/25 80/100/26 81/101/26 +f 81/102/26 82/103/23 76/96/23 +f 83/104/27 84/105/21 73/93/21 +f 78/98/24 85/106/27 83/107/27 +f 75/108/22 86/109/28 87/110/28 +f 87/110/28 88/111/25 79/99/25 +f 57/77/13 67/87/14 58/78/14 +f 60/80/15 63/83/15 61/81/16 +f 60/80/15 57/77/13 59/79/13 +f 64/84/17 71/91/17 65/85/18 +f 66/86/18 65/85/18 58/78/14 +f 68/88/19 72/92/20 69/89/20 +f 64/84/17 68/88/19 70/90/19 +f 62/82/16 61/81/16 69/89/20 +f 73/93/21 84/105/21 74/94/22 +f 76/96/23 82/103/23 77/97/24 +f 79/99/25 88/111/25 80/100/26 +f 81/102/26 80/112/26 82/103/23 +f 83/104/27 85/113/27 84/105/21 +f 78/98/24 77/97/24 85/106/27 +f 75/108/22 74/114/22 86/109/28 +f 87/110/28 86/109/28 88/111/25 +o Straight +v 0.300002 -0.000003 0.685047 +v 0.185616 0.185613 0.685047 +v 0.262502 -0.000003 0.685047 +v -0.212132 -0.212133 0.735047 +v 0.000000 -0.262503 0.735047 +v 0.000000 -0.300001 0.735047 +v -0.212132 -0.212133 0.685047 +v 0.000000 -0.262503 0.685047 +v -0.185614 -0.185617 0.685047 +v -0.212132 0.212131 0.685047 +v 0.000000 0.262499 0.685047 +v 0.000000 0.299999 0.685047 +v 0.212132 0.212131 0.685047 +v 0.212132 -0.212133 0.685047 +v 0.185616 -0.185617 0.685047 +v -0.262500 -0.000003 0.685047 +v -0.300000 -0.000003 0.685047 +v 0.000000 -0.300001 0.685047 +v -0.185614 0.185613 0.685047 +v 0.212132 -0.212133 0.735047 +v 0.262502 -0.000003 0.735047 +v 0.300002 -0.000003 0.735047 +v 0.212132 0.212131 0.735047 +v 0.000000 0.262499 0.735047 +v 0.000000 0.299999 0.735047 +v -0.212132 0.212131 0.735047 +v -0.185614 0.185613 0.735047 +v -0.262500 -0.000003 0.735047 +v -0.185614 -0.185617 0.735047 +v 0.185616 -0.185617 0.735047 +v 0.185616 0.185613 0.735047 +v -0.300000 -0.000003 0.735047 +v -0.212132 0.212131 -0.814953 +v 0.212132 0.212131 -0.814953 +v 0.212132 -0.212133 -0.814953 +v 0.000000 -0.300001 -0.814953 +v -0.212132 -0.212133 -0.814953 +v -0.300000 -0.000003 -0.814953 +v 0.000000 0.299999 -0.814953 +v 0.300002 -0.000003 -0.814953 +v 0.212132 -0.212133 0.785047 +v 0.212132 0.212131 0.785047 +v -0.212132 0.212131 0.785047 +v -0.300000 -0.000001 0.785047 +v -0.212132 -0.212133 0.785047 +v 0.000000 -0.300001 0.785047 +v 0.300002 -0.000001 0.785047 +v 0.000000 0.299999 0.785047 +v -0.300000 -0.000003 -0.814953 +v -0.212132 -0.212133 0.685047 +v -0.300000 -0.000003 0.685047 +v -0.212132 0.212131 -0.814953 +v 0.000000 0.299999 0.685047 +v 0.000000 0.299999 -0.814953 +v -0.212132 0.212131 0.685047 +v 0.000000 -0.300001 -0.814953 +v 0.000000 -0.300001 0.685047 +v 0.212132 -0.212133 -0.814953 +v 0.300002 -0.000003 0.685047 +v 0.212132 -0.212133 0.685047 +v 0.212132 0.212131 -0.814953 +v 0.212132 0.212131 0.685047 +v -0.212132 -0.212133 -0.814953 +v 0.300002 -0.000003 -0.814953 +v -0.212132 -0.212133 0.735047 +v 0.000000 -0.300001 0.785047 +v -0.212132 -0.212133 0.785047 +v 0.300002 -0.000003 0.735047 +v 0.212132 -0.212133 0.785047 +v 0.212132 -0.212133 0.735047 +v 0.000000 0.299999 0.735047 +v 0.212132 0.212131 0.785047 +v 0.212132 0.212131 0.735047 +v -0.212132 0.212131 0.785047 +v 0.000000 0.299999 0.785047 +v -0.300000 -0.000003 0.735047 +v -0.300000 -0.000001 0.785047 +v 0.000000 -0.300001 0.735047 +v 0.300002 -0.000001 0.785047 +v -0.212132 0.212131 0.735047 +v 0.185616 -0.185617 0.735047 +v 0.000000 -0.262503 0.685047 +v 0.185616 -0.185617 0.685047 +v 0.000000 -0.262503 0.735047 +v -0.185614 -0.185617 0.685047 +v 0.262502 -0.000003 0.735047 +v 0.262502 -0.000003 0.685047 +v 0.185616 0.185613 0.735047 +v 0.185616 0.185613 0.685047 +v 0.000000 0.262499 0.735047 +v 0.000000 0.262499 0.685047 +v -0.185614 0.185613 0.735047 +v -0.185614 0.185613 0.685047 +v -0.262500 -0.000003 0.735047 +v -0.262500 -0.000003 0.685047 +v -0.185614 -0.185617 0.735047 +vt 0.555701 0.706657 +vt 0.559782 0.681299 +vt 0.559782 0.704967 +vt 0.374299 0.628133 +vt 0.353483 0.613088 +vt 0.355173 0.609007 +vt 0.621002 0.706657 +vt 0.600185 0.721702 +vt 0.616920 0.704966 +vt 0.601875 0.660482 +vt 0.576517 0.664564 +vt 0.574827 0.660482 +vt 0.555701 0.679609 +vt 0.574827 0.725783 +vt 0.576518 0.721702 +vt 0.616920 0.681299 +vt 0.621002 0.679609 +vt 0.601876 0.725783 +vt 0.600185 0.664564 +vt 0.328125 0.609007 +vt 0.313080 0.629823 +vt 0.308998 0.628133 +vt 0.308998 0.655181 +vt 0.329815 0.670226 +vt 0.328124 0.674308 +vt 0.355173 0.674308 +vt 0.353482 0.670226 +vt 0.370218 0.653491 +vt 0.370218 0.629824 +vt 0.329815 0.613088 +vt 0.313080 0.653491 +vt 0.374299 0.655181 +vt 0.834098 0.918854 +vt 0.872350 0.826504 +vt 0.964699 0.864757 +vt 0.964699 0.918854 +vt 0.926447 0.957106 +vt 0.872350 0.957106 +vt 0.834098 0.864757 +vt 0.926448 0.826504 +vt 0.078463 0.847672 +vt 0.152342 0.878274 +vt 0.121740 0.952153 +vt 0.078463 0.952153 +vt 0.047861 0.921551 +vt 0.047861 0.878274 +vt 0.121740 0.847672 +vt 0.152342 0.921551 +vt 0.555701 0.614308 +vt 0.378998 0.641356 +vt 0.378998 0.614308 +vt 0.378998 0.725783 +vt 0.555701 0.752832 +vt 0.378998 0.752832 +vt 0.555701 0.725783 +vt 0.378998 0.706657 +vt 0.555701 0.706657 +vt 0.555701 0.660482 +vt 0.378998 0.660482 +vt 0.378998 0.679609 +vt 0.555701 0.660482 +vt 0.555701 0.679609 +vt 0.555701 0.706657 +vt 0.378998 0.706657 +vt 0.555701 0.799007 +vt 0.378998 0.771958 +vt 0.555701 0.771958 +vt 0.555701 0.641356 +vt 0.378998 0.660482 +vt 0.378998 0.799007 +vt 0.578726 0.628418 +vt 0.559600 0.622528 +vt 0.578726 0.622528 +vt 0.559600 0.622528 +vt 0.578726 0.616637 +vt 0.578726 0.622528 +vt 0.605775 0.628418 +vt 0.586649 0.634308 +vt 0.586649 0.628418 +vt 0.605775 0.616637 +vt 0.578726 0.610747 +vt 0.605775 0.610747 +vt 0.605775 0.628418 +vt 0.605775 0.622528 +vt 0.605775 0.616637 +vt 0.605775 0.622528 +vt 0.559600 0.634308 +vt 0.559600 0.628418 +vt 0.578726 0.616637 +vt 0.559600 0.610747 +vt 0.572436 0.725783 +vt 0.596104 0.731673 +vt 0.572436 0.731673 +vt 0.600003 0.604857 +vt 0.616739 0.610747 +vt 0.600003 0.610747 +vt 0.555701 0.725783 +vt 0.555701 0.731673 +vt 0.583268 0.610747 +vt 0.559600 0.604857 +vt 0.583268 0.604857 +vt 0.600003 0.610747 +vt 0.600003 0.604857 +vt 0.622437 0.634308 +vt 0.646104 0.640198 +vt 0.622437 0.640198 +vt 0.605701 0.634308 +vt 0.605701 0.640198 +vt 0.616739 0.604857 +vt 0.640406 0.610747 +vt 0.559600 0.628418 +vt 0.559600 0.616637 +vt 0.605775 0.634308 +vt 0.559600 0.616637 +vt 0.596104 0.725783 +vt 0.559600 0.610747 +vt 0.646104 0.634308 +vt 0.640406 0.604857 +vn 0.0000 0.0000 1.0000 +vn 0.0000 0.0000 -1.0000 +vn -1.0000 -0.0000 -0.0000 +vn -0.7071 -0.7071 -0.0000 +vn -0.7071 0.7071 0.0000 +vn 0.0000 1.0000 0.0000 +vn 0.0000 -1.0000 -0.0000 +vn 0.7071 -0.7071 -0.0000 +vn 1.0000 -0.0000 0.0000 +vn 0.7071 0.7071 0.0000 +s off +f 89/115/29 90/116/29 91/117/29 +f 92/118/30 93/119/30 94/120/30 +f 95/121/29 96/122/29 97/123/29 +f 98/124/29 99/125/29 100/126/29 +f 101/127/29 99/125/29 90/116/29 +f 102/128/29 91/117/29 103/129/29 +f 95/121/29 104/130/29 105/131/29 +f 102/128/29 96/122/29 106/132/29 +f 105/131/29 107/133/29 98/124/29 +f 108/134/30 109/135/30 110/136/30 +f 111/137/30 112/138/30 113/139/30 +f 114/140/30 112/138/30 115/141/30 +f 92/118/30 116/142/30 117/143/30 +f 108/134/30 93/119/30 118/144/30 +f 110/136/30 119/145/30 111/137/30 +f 120/146/30 115/141/30 116/142/30 +f 89/115/29 101/127/29 90/116/29 +f 92/118/30 117/143/30 93/119/30 +f 95/121/29 106/132/29 96/122/29 +f 98/124/29 107/133/29 99/125/29 +f 101/127/29 100/126/29 99/125/29 +f 102/128/29 89/115/29 91/117/29 +f 95/121/29 97/123/29 104/130/29 +f 102/128/29 103/129/29 96/122/29 +f 105/131/29 104/130/29 107/133/29 +f 108/134/30 118/144/30 109/135/30 +f 111/137/30 119/145/30 112/138/30 +f 114/140/30 113/139/30 112/138/30 +f 92/118/30 120/146/30 116/142/30 +f 108/134/30 94/120/30 93/119/30 +f 110/136/30 109/135/30 119/145/30 +f 120/146/30 114/140/30 115/141/30 +f 121/147/30 122/148/30 123/149/30 +f 123/149/30 124/150/30 125/151/30 +f 125/151/30 126/152/30 121/147/30 +f 121/147/30 127/153/30 122/148/30 +f 122/148/30 128/154/30 123/149/30 +f 123/149/30 125/151/30 121/147/30 +f 129/155/29 130/156/29 131/157/29 +f 131/157/29 132/158/29 133/159/29 +f 133/159/29 134/160/29 129/155/29 +f 129/155/29 135/161/29 130/156/29 +f 130/156/29 136/162/29 131/157/29 +f 131/157/29 133/159/29 129/155/29 +s 1 +f 137/163/31 138/164/32 139/165/31 +f 140/166/33 141/167/34 142/168/34 +f 143/169/33 137/170/31 139/171/31 +f 138/164/32 144/172/35 145/173/35 +f 146/174/36 147/175/37 148/176/36 +f 146/174/36 145/177/35 144/178/35 +f 147/179/37 149/180/38 150/181/38 +f 142/168/34 150/181/38 149/180/38 +f 137/163/31 151/182/32 138/164/32 +f 140/166/33 143/169/33 141/167/34 +f 143/169/33 140/166/33 137/170/31 +f 138/164/32 151/182/32 144/172/35 +f 146/174/36 152/183/37 147/175/37 +f 146/174/36 148/176/36 145/177/35 +f 147/179/37 152/184/37 149/180/38 +f 142/168/34 141/167/34 150/181/38 +f 153/185/32 154/186/35 155/187/32 +f 156/188/37 157/189/36 158/190/36 +f 159/191/34 160/192/38 161/193/38 +f 159/194/34 162/195/33 163/196/34 +f 164/197/31 155/187/32 165/198/31 +f 158/190/36 154/199/35 166/200/35 +f 161/193/38 167/201/37 156/202/37 +f 168/203/33 165/204/31 162/195/33 +f 169/205/36 170/206/35 171/207/36 +f 172/208/35 173/209/32 170/210/35 +f 174/211/37 171/207/36 175/212/37 +f 176/213/38 175/214/37 177/215/38 +f 178/216/34 177/215/38 179/217/34 +f 180/218/33 179/219/34 181/220/33 +f 182/221/31 181/220/33 183/222/31 +f 184/223/32 183/224/31 173/209/32 +f 153/185/32 166/225/35 154/186/35 +f 156/188/37 167/226/37 157/189/36 +f 159/191/34 163/227/34 160/192/38 +f 159/194/34 168/203/33 162/195/33 +f 164/197/31 153/185/32 155/187/32 +f 158/190/36 157/189/36 154/199/35 +f 161/193/38 160/192/38 167/201/37 +f 168/203/33 164/228/31 165/204/31 +f 169/205/36 172/229/35 170/206/35 +f 172/208/35 184/223/32 173/209/32 +f 174/211/37 169/205/36 171/207/36 +f 176/213/38 174/230/37 175/214/37 +f 178/216/34 176/213/38 177/215/38 +f 180/218/33 178/231/34 179/219/34 +f 182/221/31 180/218/33 181/220/33 +f 184/223/32 182/232/31 183/224/31 +o Bottleneck +v 0.300002 -0.000003 0.685047 +v 0.185616 0.185613 0.685047 +v 0.262502 -0.000003 0.685047 +v -0.212132 -0.212133 0.735047 +v 0.000000 -0.262503 0.735047 +v 0.000000 -0.300001 0.735047 +v -0.212132 -0.212133 0.685047 +v 0.000000 -0.262503 0.685047 +v -0.185614 -0.185617 0.685047 +v -0.212132 0.212131 0.685047 +v 0.000000 0.262499 0.685047 +v 0.000000 0.299999 0.685047 +v 0.212132 0.212131 0.685047 +v 0.212132 -0.212133 0.685047 +v 0.185616 -0.185617 0.685047 +v -0.262500 -0.000003 0.685047 +v -0.300000 -0.000003 0.685047 +v 0.000000 -0.300001 0.685047 +v -0.185614 0.185613 0.685047 +v 0.212132 -0.212133 0.735047 +v 0.262502 -0.000003 0.735047 +v 0.300002 -0.000003 0.735047 +v 0.212132 0.212131 0.735047 +v 0.000000 0.262499 0.735047 +v 0.000000 0.299999 0.735047 +v -0.212132 0.212131 0.735047 +v -0.185614 0.185613 0.735047 +v -0.262500 -0.000003 0.735047 +v -0.185614 -0.185617 0.735047 +v 0.185616 -0.185617 0.735047 +v 0.185616 0.185613 0.735047 +v -0.300000 -0.000003 0.735047 +v -0.135595 0.135594 -1.014953 +v 0.135595 0.135594 -1.014953 +v 0.135596 -0.135596 -1.014953 +v 0.000000 -0.191762 -1.014953 +v -0.135596 -0.135597 -1.014953 +v -0.191761 -0.000003 -1.014953 +v 0.000000 0.191760 -1.014953 +v 0.191762 -0.000003 -1.014953 +v 0.212132 -0.212133 0.785047 +v 0.212132 0.212131 0.785047 +v -0.212132 0.212131 0.785047 +v -0.300000 -0.000001 0.785047 +v -0.212132 -0.212133 0.785047 +v 0.000000 -0.300001 0.785047 +v 0.300002 -0.000001 0.785047 +v 0.000000 0.299999 0.785047 +v -0.135595 0.135594 -0.764953 +v -0.135595 0.135594 -1.014953 +v -0.191761 -0.000003 -1.014953 +v 0.000000 0.191760 -0.764953 +v 0.000000 0.191760 -1.014953 +v 0.135595 0.135594 -0.764953 +v 0.191762 -0.000003 -0.764953 +v 0.191762 -0.000003 -1.014953 +v -0.135596 -0.135597 -0.764953 +v -0.135596 -0.135597 -1.014953 +v 0.000000 -0.191762 -1.014953 +v 0.135596 -0.135596 -0.764953 +v 0.135596 -0.135596 -1.014953 +v -0.191761 -0.000003 -0.764953 +v 0.135595 0.135594 -1.014953 +v 0.212132 -0.212133 -0.414953 +v 0.000000 -0.191762 -0.764953 +v 0.135596 -0.135596 -0.764953 +v -0.300000 -0.000003 -0.414953 +v -0.135595 0.135594 -0.764953 +v -0.191761 -0.000003 -0.764953 +v 0.000000 0.299999 -0.414953 +v 0.000000 0.191760 -0.764953 +v 0.212132 0.212131 -0.414953 +v 0.135595 0.135594 -0.764953 +v -0.212132 -0.212133 -0.414953 +v -0.135596 -0.135597 -0.764953 +v 0.191762 -0.000003 -0.764953 +v 0.300002 -0.000003 -0.414953 +v 0.000000 -0.191762 -0.764953 +v 0.000000 -0.300001 -0.414953 +v -0.212132 0.212131 -0.414953 +v 0.300002 -0.000003 0.685047 +v 0.212132 0.212131 -0.414953 +v 0.212132 0.212131 0.685047 +v 0.212132 -0.212133 -0.414953 +v 0.000000 -0.300001 0.685047 +v 0.000000 -0.300001 -0.414953 +v -0.300000 -0.000003 -0.414953 +v -0.212132 -0.212133 0.685047 +v -0.300000 -0.000003 0.685047 +v 0.000000 0.299999 -0.414953 +v -0.212132 0.212131 0.685047 +v 0.212132 -0.212133 0.685047 +v -0.212132 0.212131 -0.414953 +v 0.000000 0.299999 0.685047 +v 0.300002 -0.000003 -0.414953 +v -0.212132 -0.212133 -0.414953 +v 0.185616 -0.185617 0.735047 +v 0.000000 -0.262503 0.685047 +v 0.185616 -0.185617 0.685047 +v 0.000000 -0.262503 0.735047 +v -0.185614 -0.185617 0.685047 +v 0.262502 -0.000003 0.735047 +v 0.262502 -0.000003 0.685047 +v 0.185616 0.185613 0.735047 +v 0.185616 0.185613 0.685047 +v 0.000000 0.262499 0.735047 +v 0.000000 0.262499 0.685047 +v -0.185614 0.185613 0.735047 +v -0.185614 0.185613 0.685047 +v -0.262500 -0.000003 0.735047 +v -0.262500 -0.000003 0.685047 +v -0.185614 -0.185617 0.735047 +v -0.212132 -0.212133 0.735047 +v 0.000000 -0.300001 0.785047 +v -0.212132 -0.212133 0.785047 +v 0.300002 -0.000003 0.735047 +v 0.212132 -0.212133 0.785047 +v 0.212132 -0.212133 0.735047 +v 0.000000 0.299999 0.735047 +v 0.212132 0.212131 0.785047 +v 0.212132 0.212131 0.735047 +v -0.212132 0.212131 0.785047 +v 0.000000 0.299999 0.785047 +v -0.300000 -0.000003 0.735047 +v -0.300000 -0.000001 0.785047 +v 0.000000 -0.300001 0.735047 +v 0.300002 -0.000001 0.785047 +v -0.212132 0.212131 0.735047 +vt 0.630972 0.689316 +vt 0.635004 0.664263 +vt 0.635004 0.687646 +vt 0.760004 0.662592 +vt 0.739438 0.647728 +vt 0.741108 0.643696 +vt 0.695488 0.689316 +vt 0.674922 0.704180 +vt 0.691456 0.687645 +vt 0.676592 0.643696 +vt 0.651539 0.647728 +vt 0.649868 0.643696 +vt 0.630972 0.662592 +vt 0.649869 0.708212 +vt 0.651539 0.704180 +vt 0.691456 0.664263 +vt 0.695488 0.662592 +vt 0.676592 0.708212 +vt 0.674922 0.647728 +vt 0.714385 0.643696 +vt 0.699521 0.664262 +vt 0.695488 0.662592 +vt 0.695488 0.689316 +vt 0.716055 0.704180 +vt 0.714385 0.708212 +vt 0.741108 0.708212 +vt 0.739438 0.704180 +vt 0.755972 0.687645 +vt 0.755972 0.664262 +vt 0.716055 0.647728 +vt 0.699521 0.687645 +vt 0.760005 0.689316 +vt 0.818608 0.930835 +vt 0.866923 0.814195 +vt 0.983563 0.862509 +vt 0.983563 0.930836 +vt 0.935248 0.979150 +vt 0.866923 0.979150 +vt 0.818608 0.862509 +vt 0.935250 0.814194 +vt 0.078761 0.848420 +vt 0.151753 0.878654 +vt 0.121519 0.951645 +vt 0.078761 0.951645 +vt 0.048527 0.921411 +vt 0.048527 0.878654 +vt 0.121519 0.848420 +vt 0.151753 0.921411 +vt 0.396006 0.631929 +vt 0.366932 0.631929 +vt 0.366456 0.619851 +vt 0.396006 0.649011 +vt 0.366932 0.649011 +vt 0.396006 0.742065 +vt 0.396006 0.759147 +vt 0.366932 0.759147 +vt 0.396006 0.694631 +vt 0.366932 0.694631 +vt 0.366932 0.677549 +vt 0.395530 0.771225 +vt 0.366456 0.771225 +vt 0.395530 0.706709 +vt 0.366456 0.706709 +vt 0.366932 0.742065 +vt 0.366456 0.729986 +vt 0.436423 0.653832 +vt 0.396006 0.677549 +vt 0.395530 0.665471 +vt 0.436423 0.608212 +vt 0.396006 0.631929 +vt 0.395530 0.619851 +vt 0.437168 0.653832 +vt 0.396006 0.649011 +vt 0.395530 0.729986 +vt 0.437169 0.737244 +vt 0.396006 0.742065 +vt 0.437169 0.699452 +vt 0.396006 0.694631 +vt 0.436423 0.782864 +vt 0.396006 0.759147 +vt 0.437169 0.763968 +vt 0.436423 0.718348 +vt 0.395530 0.665471 +vt 0.396006 0.677549 +vt 0.395530 0.619851 +vt 0.395530 0.729986 +vt 0.437169 0.672728 +vt 0.437168 0.627109 +vt 0.436423 0.718348 +vt 0.395530 0.771225 +vt 0.395530 0.706709 +vt 0.366456 0.665471 +vt 0.565094 0.763968 +vt 0.437169 0.737244 +vt 0.565094 0.737244 +vt 0.436423 0.653832 +vt 0.565094 0.672728 +vt 0.437169 0.672728 +vt 0.436423 0.718348 +vt 0.565094 0.699452 +vt 0.564349 0.718348 +vt 0.436423 0.718348 +vt 0.565094 0.627109 +vt 0.436423 0.608212 +vt 0.564349 0.608212 +vt 0.436423 0.782864 +vt 0.564348 0.782864 +vt 0.437168 0.627109 +vt 0.565094 0.653832 +vt 0.437168 0.653832 +vt 0.437169 0.763968 +vt 0.564349 0.653832 +vt 0.437169 0.699452 +vt 0.564349 0.718348 +vt 0.816456 0.643044 +vt 0.799922 0.637881 +vt 0.816456 0.637229 +vt 0.799922 0.643696 +vt 0.776539 0.637881 +vt 0.655571 0.643696 +vt 0.639037 0.637229 +vt 0.655571 0.637881 +vt 0.678954 0.643696 +vt 0.678954 0.637881 +vt 0.695488 0.643044 +vt 0.695488 0.637229 +vt 0.783384 0.660723 +vt 0.760099 0.654527 +vt 0.783479 0.654909 +vt 0.799927 0.660341 +vt 0.800022 0.654527 +vt 0.776539 0.643696 +vt 0.760004 0.637229 +vt 0.639037 0.643044 +vt 0.760004 0.660341 +vt 0.760004 0.643044 +vt 0.714385 0.637881 +vt 0.741108 0.643696 +vt 0.714385 0.643696 +vt 0.585352 0.637881 +vt 0.566456 0.642951 +vt 0.566456 0.637136 +vt 0.630972 0.637136 +vt 0.612076 0.643696 +vt 0.612076 0.637881 +vt 0.760099 0.660723 +vt 0.786724 0.666973 +vt 0.760004 0.666537 +vt 0.695488 0.637136 +vt 0.695488 0.642951 +vt 0.760004 0.637136 +vt 0.741108 0.637881 +vt 0.585352 0.643696 +vt 0.786819 0.661159 +vt 0.805630 0.666537 +vt 0.630972 0.642951 +vt 0.760004 0.642951 +vt 0.805725 0.660723 +vn 0.0000 0.0000 1.0000 +vn 0.0000 0.0000 -1.0000 +vn -0.7071 0.7071 0.0000 +vn -1.0000 -0.0000 -0.0000 +vn 0.0000 1.0000 0.0000 +vn 0.7071 0.7071 0.0000 +vn 1.0000 -0.0000 -0.0000 +vn -0.7071 -0.7071 -0.0000 +vn 0.0000 -1.0000 -0.0000 +vn 0.7071 -0.7071 -0.0000 +vn 0.6755 -0.6755 -0.2954 +vn 0.0000 -0.9554 -0.2954 +vn -0.9554 -0.0000 -0.2954 +vn -0.6755 0.6755 -0.2954 +vn -0.0000 0.9554 -0.2954 +vn 0.6755 0.6755 -0.2954 +vn 0.6755 0.6755 -0.2955 +vn -0.6755 -0.6755 -0.2954 +vn 0.9554 -0.0000 -0.2955 +vn -0.6755 0.6755 -0.2955 +s off +f 185/233/39 186/234/39 187/235/39 +f 188/236/40 189/237/40 190/238/40 +f 191/239/39 192/240/39 193/241/39 +f 194/242/39 195/243/39 196/244/39 +f 197/245/39 195/243/39 186/234/39 +f 198/246/39 187/235/39 199/247/39 +f 191/239/39 200/248/39 201/249/39 +f 198/246/39 192/240/39 202/250/39 +f 201/249/39 203/251/39 194/242/39 +f 204/252/40 205/253/40 206/254/40 +f 207/255/40 208/256/40 209/257/40 +f 210/258/40 208/256/40 211/259/40 +f 188/236/40 212/260/40 213/261/40 +f 204/252/40 189/237/40 214/262/40 +f 206/254/40 215/263/40 207/255/40 +f 216/264/40 211/259/40 212/260/40 +f 185/233/39 197/245/39 186/234/39 +f 188/236/40 213/261/40 189/237/40 +f 191/239/39 202/250/39 192/240/39 +f 194/242/39 203/251/39 195/243/39 +f 197/245/39 196/244/39 195/243/39 +f 198/246/39 185/233/39 187/235/39 +f 191/239/39 193/241/39 200/248/39 +f 198/246/39 199/247/39 192/240/39 +f 201/249/39 200/248/39 203/251/39 +f 204/252/40 214/262/40 205/253/40 +f 207/255/40 215/263/40 208/256/40 +f 210/258/40 209/257/40 208/256/40 +f 188/236/40 216/264/40 212/260/40 +f 204/252/40 190/238/40 189/237/40 +f 206/254/40 205/253/40 215/263/40 +f 216/264/40 210/258/40 211/259/40 +f 217/265/40 218/266/40 219/267/40 +f 219/267/40 220/268/40 217/265/40 +f 220/268/40 221/269/40 217/265/40 +f 221/269/40 222/270/40 217/265/40 +f 217/265/40 223/271/40 218/266/40 +f 218/266/40 224/272/40 219/267/40 +f 225/273/39 226/274/39 227/275/39 +f 227/275/39 228/276/39 229/277/39 +f 229/277/39 230/278/39 225/273/39 +f 225/273/39 231/279/39 226/274/39 +f 226/274/39 232/280/39 227/275/39 +f 227/275/39 229/277/39 225/273/39 +s 1 +f 233/281/41 234/282/41 235/283/42 +f 236/284/43 237/285/43 234/282/41 +f 238/286/44 239/287/45 240/288/45 +f 241/289/46 242/290/46 243/291/47 +f 244/292/48 245/293/48 240/288/45 +f 246/294/42 235/295/42 242/290/46 +f 238/286/44 247/296/44 237/297/43 +f 248/298/49 249/299/50 250/300/49 +f 251/301/51 252/302/52 253/303/51 +f 252/302/52 254/304/53 255/305/53 +f 255/306/53 256/307/54 257/308/55 +f 249/299/50 258/309/56 259/310/56 +f 248/311/49 260/312/57 261/313/57 +f 251/314/51 259/310/56 258/309/56 +f 260/312/57 256/307/54 261/313/57 +f 244/315/48 262/316/47 243/291/47 +f 233/281/41 235/283/42 246/317/42 +f 236/284/43 234/282/41 233/281/41 +f 238/286/44 240/288/45 247/296/44 +f 241/289/46 243/291/47 262/316/47 +f 244/292/48 240/288/45 239/287/45 +f 246/294/42 242/290/46 241/289/46 +f 238/286/44 237/297/43 236/318/43 +f 248/298/49 263/319/50 249/299/50 +f 251/301/51 264/320/58 252/302/52 +f 252/302/52 264/320/58 254/304/53 +f 255/306/53 254/321/53 256/307/54 +f 249/299/50 263/319/50 258/309/56 +f 248/311/49 250/322/49 260/312/57 +f 251/314/51 253/323/51 259/310/56 +f 260/312/57 257/308/55 256/307/54 +f 244/315/48 243/291/47 245/324/48 +f 265/325/45 266/326/44 267/327/44 +f 268/328/48 269/329/47 270/330/47 +f 271/331/42 272/332/46 273/333/42 +f 274/334/43 267/327/44 266/326/44 +f 275/335/41 271/336/42 273/337/42 +f 268/338/48 265/325/45 276/339/48 +f 277/340/41 278/341/43 274/342/43 +f 272/332/46 270/330/47 269/329/47 +f 265/325/45 279/343/45 266/326/44 +f 268/328/48 276/344/48 269/329/47 +f 271/331/42 280/345/46 272/332/46 +f 274/334/43 278/346/43 267/327/44 +f 275/335/41 277/340/41 271/336/42 +f 268/338/48 279/343/45 265/325/45 +f 277/340/41 275/335/41 278/341/43 +f 272/332/46 280/345/46 270/330/47 +f 281/347/48 282/348/47 283/349/48 +f 284/350/47 285/351/46 282/348/47 +f 286/352/45 283/353/48 287/354/45 +f 288/355/44 287/354/45 289/356/44 +f 290/357/43 289/356/44 291/358/43 +f 292/359/41 291/360/43 293/361/41 +f 294/362/42 293/361/41 295/363/42 +f 296/364/46 295/365/42 285/351/46 +f 281/347/48 284/350/47 282/348/47 +f 284/350/47 296/364/46 285/351/46 +f 286/352/45 281/366/48 283/353/48 +f 288/355/44 286/352/45 287/354/45 +f 290/357/43 288/355/44 289/356/44 +f 292/359/41 290/367/43 291/360/43 +f 294/362/42 292/359/41 293/361/41 +f 296/364/46 294/368/42 295/365/42 +f 297/369/46 298/370/47 299/371/46 +f 300/372/45 301/373/48 302/374/48 +f 303/375/43 304/376/44 305/377/44 +f 303/378/43 306/379/41 307/380/43 +f 308/381/42 299/371/46 309/382/42 +f 302/383/48 298/370/47 310/384/47 +f 305/377/44 311/385/45 300/372/45 +f 312/386/41 309/387/42 306/379/41 +f 297/369/46 310/384/47 298/370/47 +f 300/372/45 311/385/45 301/373/48 +f 303/375/43 307/388/43 304/376/44 +f 303/378/43 312/386/41 306/379/41 +f 308/381/42 297/369/46 299/371/46 +f 302/383/48 301/389/48 298/370/47 +f 305/377/44 304/376/44 311/385/45 +f 312/386/41 308/390/42 309/387/42 diff --git a/src/main/resources/assets/hbm/textures/particle/casings.png b/src/main/resources/assets/hbm/textures/particle/casings.png new file mode 100644 index 000000000..e9ddbafa5 Binary files /dev/null and b/src/main/resources/assets/hbm/textures/particle/casings.png differ