Rewrite raytracing methods and optimise their usage

This commit is contained in:
Electroblob
2018-06-24 16:02:34 +01:00
parent ff87729ee8
commit 76473f7185
3 changed files with 202 additions and 159 deletions
@@ -208,7 +208,6 @@ public final class WizardryClientEventHandler {
Minecraft mc = Minecraft.getMinecraft(); Minecraft mc = Minecraft.getMinecraft();
WizardData properties = WizardData.get(mc.player); WizardData properties = WizardData.get(mc.player);
RayTraceResult rayTrace = WizardryUtilities.standardEntityRayTrace(mc.world, mc.player, 16, false);
RenderManager renderManager = event.getRenderer().getRenderManager(); RenderManager renderManager = event.getRenderer().getRenderManager();
ItemStack wand = mc.player.getHeldItemMainhand(); ItemStack wand = mc.player.getHeldItemMainhand();
@@ -218,45 +217,51 @@ public final class WizardryClientEventHandler {
} }
// Target selection pointer // Target selection pointer
if(mc.player.isSneaking() && wand.getItem() instanceof ItemWand && rayTrace != null && !(event.getEntity() instanceof EntityArmorStand) if(mc.player.isSneaking() && wand.getItem() instanceof ItemWand && WizardryUtilities.isLiving(event.getEntity())
&& rayTrace.entityHit == event.getEntity() && properties != null && properties.selectedMinion != null){ && properties != null && properties.selectedMinion != null){
Tessellator tessellator = Tessellator.getInstance(); // -> Moved this in here so it isn't called every tick
BufferBuilder buffer = tessellator.getBuffer(); RayTraceResult rayTrace = WizardryUtilities.standardEntityRayTrace(mc.world, mc.player, 16, false);
GlStateManager.pushMatrix(); if(rayTrace != null && rayTrace.entityHit == event.getEntity()){
GlStateManager.disableCull(); Tessellator tessellator = Tessellator.getInstance();
GlStateManager.disableLighting(); BufferBuilder buffer = tessellator.getBuffer();
OpenGlHelper.setLightmapTextureCoords(OpenGlHelper.lightmapTexUnit, 240f, 240f);
// Disabling depth test allows it to be seen through everything.
GlStateManager.disableDepth();
GlStateManager.color(1, 1, 1, 1);
GlStateManager.translate(event.getX(), event.getY() + event.getEntity().height + 0.5, event.getZ()); GlStateManager.pushMatrix();
// This counteracts the reverse rotation behaviour when in front f5 view. GlStateManager.disableCull();
// Fun fact: this is a bug with vanilla too! Look at a snowball in front f5 view, for example. GlStateManager.disableLighting();
float yaw = mc.gameSettings.thirdPersonView == 2 ? renderManager.playerViewX : -renderManager.playerViewX; OpenGlHelper.setLightmapTextureCoords(OpenGlHelper.lightmapTexUnit, 240f, 240f);
GlStateManager.rotate(180 - renderManager.playerViewY, 0.0F, 1.0F, 0.0F); // Disabling depth test allows it to be seen through everything.
GlStateManager.rotate(yaw, 1.0F, 0.0F, 0.0F); GlStateManager.disableDepth();
GlStateManager.color(1, 1, 1, 1);
buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX); GlStateManager.translate(event.getX(), event.getY() + event.getEntity().height + 0.5, event.getZ());
mc.renderEngine.bindTexture(targetPointerTexture); // This counteracts the reverse rotation behaviour when in front f5 view.
// Fun fact: this is a bug with vanilla too! Look at a snowball in front f5 view, for example.
float yaw = mc.gameSettings.thirdPersonView == 2 ? renderManager.playerViewX : -renderManager.playerViewX;
GlStateManager.rotate(180 - renderManager.playerViewY, 0.0F, 1.0F, 0.0F);
GlStateManager.rotate(yaw, 1.0F, 0.0F, 0.0F);
buffer.pos(-0.2, 0.24, 0).tex(0, 0).endVertex(); buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX);
buffer.pos(0.2, 0.24, 0).tex(9f / 16f, 0).endVertex();
buffer.pos(0.2, -0.24, 0).tex(9f / 16f, 11f / 16f).endVertex();
buffer.pos(-0.2, -0.24, 0).tex(0, 11f / 16f).endVertex();
tessellator.draw(); mc.renderEngine.bindTexture(targetPointerTexture);
GlStateManager.enableCull(); buffer.pos(-0.2, 0.24, 0).tex(0, 0).endVertex();
GlStateManager.enableLighting(); buffer.pos(0.2, 0.24, 0).tex(9f / 16f, 0).endVertex();
GlStateManager.enableDepth(); buffer.pos(0.2, -0.24, 0).tex(9f / 16f, 11f / 16f).endVertex();
buffer.pos(-0.2, -0.24, 0).tex(0, 11f / 16f).endVertex();
GlStateManager.popMatrix(); tessellator.draw();
GlStateManager.enableCull();
GlStateManager.enableLighting();
GlStateManager.enableDepth();
GlStateManager.popMatrix();
}
} }
// Summoned creature selection pointer // Summoned creature selection pointer
@@ -70,6 +70,8 @@ public abstract class SpellRay extends Spell {
protected boolean ignoreEntities = false; protected boolean ignoreEntities = false;
/** Whether liquids count as blocks when raytracing. Defaults to false. */ /** Whether liquids count as blocks when raytracing. Defaults to false. */
protected boolean hitLiquids = false; protected boolean hitLiquids = false;
/** The aim assist to use when raytracing. Defaults to 0. */
protected float aimAssist = 0;
public SpellRay(String name, Tier tier, Element element, SpellType type, int cost, int cooldown, boolean isContinuous, double baseRange, SoundEvent sound){ public SpellRay(String name, Tier tier, Element element, SpellType type, int cost, int cooldown, boolean isContinuous, double baseRange, SoundEvent sound){
this(Wizardry.MODID, name, tier, element, type, cost, cooldown, isContinuous, baseRange, sound); this(Wizardry.MODID, name, tier, element, type, cost, cooldown, isContinuous, baseRange, sound);
@@ -147,6 +149,17 @@ public abstract class SpellRay extends Spell {
return this; return this;
} }
/**
* Sets the aim assist to use when raytracing.
* @param particleVelocity The aim assist to use when raytracing. See {@link WizardryUtilities#rayTrace(World, Vec3d,
* Vec3d, float, boolean, Class, java.util.function.Predicate)} for more details.
* @return The spell instance, allowing this method to be chained onto the constructor.
*/
public SpellRay aimAssist(float aimAssist){
this.aimAssist = aimAssist;
return this;
}
@Override public boolean canBeCastByNPCs(){ return true; } @Override public boolean canBeCastByNPCs(){ return true; }
// Finally everything in here is standardised and written in a form that's actually readable - it was long overdue! // Finally everything in here is standardised and written in a form that's actually readable - it was long overdue!
@@ -157,49 +170,7 @@ public abstract class SpellRay extends Spell {
Vec3d origin = new Vec3d(caster.posX, caster.getEntityBoundingBox().minY + caster.getEyeHeight() - Y_OFFSET, Vec3d origin = new Vec3d(caster.posX, caster.getEntityBoundingBox().minY + caster.getEyeHeight() - Y_OFFSET,
caster.posZ); caster.posZ);
double range = baseRange * modifiers.get(WizardryItems.range_upgrade); if(!shootSpell(world, origin, look, caster, ticksInUse, modifiers)) return false;
// The first method will hit the first block it touches, passing through entities as if they weren't there.
// The second method will hit the first thing it touches, whether that's a block or an entity.
// Note that when it hits a block, the exact position hit is returned in hitVec, whereas when it hits an
// entity, it just returns the entity's position in hitVec.
RayTraceResult rayTrace = ignoreEntities
? WizardryUtilities.standardBlockRayTrace(world, caster, range, hitLiquids)
: WizardryUtilities.standardEntityRayTrace(world, caster, range, hitLiquids);
boolean flag = false;
if(rayTrace != null){
// Doesn't matter which way round these are, they're mutually exclusive
if(rayTrace.typeOfHit == RayTraceResult.Type.ENTITY){
Entity target = rayTrace.entityHit;
// Do whatever the spell does when it hits an entity
flag = onEntityHit(world, target, caster, ticksInUse, modifiers);
// If the spell succeeded, clip the particles to the correct distance so they don't go through the entity
if(flag){
// The most pragmatic solution is to use the target's centre point for reasons explained earlier
double dx = origin.x - target.posX;
double dy = origin.y - (target.getEntityBoundingBox().minY + target.height/2);
double dz = origin.z - target.posZ;
range = MathHelper.sqrt(dx*dx + dy*dy + dz*dz);
}
}else if(rayTrace.typeOfHit == RayTraceResult.Type.BLOCK){
// Do whatever the spell does when it hits an block
flag = onBlockHit(world, rayTrace.getBlockPos(), rayTrace.sideHit, caster, ticksInUse, modifiers);
// If the spell succeeded, clip the particles to the correct distance so they don't go through the block
if(flag) range = origin.distanceTo(rayTrace.hitVec);
}
}
// If flag is false, either the spell missed or the relevant entity/block hit method returned false
if(!flag && !onMiss(world, caster, ticksInUse, modifiers)) return false;
// Particle spawning
if(world.isRemote){
spawnParticleRay(world, origin, look, range);
}
if(!isContinuous) caster.swingArm(hand); // Bit of a dirty fix but I don't think it'll be a problem! if(!isContinuous) caster.swingArm(hand); // Bit of a dirty fix but I don't think it'll be a problem!
if(sound != null) WizardryUtilities.playSoundAtPlayer(caster, sound, volume, pitch + pitchVariation * (world.rand.nextFloat() - 0.5f)); if(sound != null) WizardryUtilities.playSoundAtPlayer(caster, sound, volume, pitch + pitchVariation * (world.rand.nextFloat() - 0.5f));
@@ -253,6 +224,46 @@ public abstract class SpellRay extends Spell {
return true; return true;
} }
/** Player and dispenser casting are almost identical so this takes care of the shared stuff. */
private boolean shootSpell(World world, Vec3d origin, Vec3d direction, @Nullable EntityPlayer caster, int ticksInUse, SpellModifiers modifiers){
double range = baseRange * modifiers.get(WizardryItems.range_upgrade);
Vec3d endpoint = origin.add(direction.scale(range));
// The first method will hit the first block it touches, passing through entities as if they weren't there.
// The second method will hit the first thing it touches, whether that's a block or an entity. This method now
// returns the exact hit position for entities as well as blocks.
RayTraceResult rayTrace = WizardryUtilities.rayTrace(world, origin, endpoint, aimAssist, hitLiquids, Entity.class, e -> e == caster);
boolean flag = false;
if(rayTrace != null){
// Doesn't matter which way round these are, they're mutually exclusive
if(rayTrace.typeOfHit == RayTraceResult.Type.ENTITY){
// Do whatever the spell does when it hits an entity
flag = onEntityHit(world, rayTrace.entityHit, caster, ticksInUse, modifiers);
// If the spell succeeded, clip the particles to the correct distance so they don't go through the entity
if(flag) range = origin.distanceTo(rayTrace.hitVec);
}else if(rayTrace.typeOfHit == RayTraceResult.Type.BLOCK){
// Do whatever the spell does when it hits an block
flag = onBlockHit(world, rayTrace.getBlockPos(), rayTrace.sideHit, caster, ticksInUse, modifiers);
// If the spell succeeded, clip the particles to the correct distance so they don't go through the block
if(flag) range = origin.distanceTo(rayTrace.hitVec);
}
}
// If flag is false, either the spell missed or the relevant entity/block hit method returned false
if(!flag && !onMiss(world, caster, ticksInUse, modifiers)) return false;
// Particle spawning
if(world.isRemote){
spawnParticleRay(world, origin, direction, range);
}
return true;
}
// Private helper method, no-one will need to override it since it's pretty much the whole point of this class. // Private helper method, no-one will need to override it since it's pretty much the whole point of this class.
private void spawnParticleRay(World world, Vec3d origin, Vec3d direction, double distance){ private void spawnParticleRay(World world, Vec3d origin, Vec3d direction, double distance){
@@ -4,13 +4,13 @@ import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.Random; import java.util.Random;
import java.util.UUID; import java.util.UUID;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Predicate;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@@ -553,118 +553,145 @@ public final class WizardryUtilities {
// =============================================================================================================== // ===============================================================================================================
/** /**
* Does a block ray trace (NOT entities) from an entity's eyes (i.e. properly...) * Helper method which performs a ray trace for <b>blocks only</b> from an entity's eye position in the direction
* they are looking, over a specified range, using {@link World#rayTraceBlocks(Vec3d, Vec3d, boolean)}.
*
* @param world The world in which to perform the ray trace.
* @param entity The entity from which to perform the ray trace. The ray trace will start from this entity's eye
* position and proceed in the direction the entity is looking.
* @param range The distance over which the ray trace will be performed.
* @param hitLiquids True to return hits on the surfaces of liquids, false to ignore liquid blocks as if they were
* not there.
* @return A {@link RayTraceResult} representing the object that was hit, which may be either a block or nothing.
* Returns {@code null} only if the origin and endpoint are within the same block.
*/ */
@Nullable @Nullable
public static RayTraceResult standardBlockRayTrace(World world, EntityLivingBase entity, double range, boolean hitLiquids){ public static RayTraceResult standardBlockRayTrace(World world, EntityLivingBase entity, double range, boolean hitLiquids){
// This method does not apply an offset like ray spells do, since it is not desirable in most other use cases.
Vec3d start = new Vec3d(entity.posX, entity.getEntityBoundingBox().minY + entity.getEyeHeight(), entity.posZ); Vec3d origin = new Vec3d(entity.posX, entity.getEntityBoundingBox().minY + entity.getEyeHeight(), entity.posZ);
Vec3d look = entity.getLookVec(); Vec3d endpoint = origin.add(entity.getLookVec().scale(range));
Vec3d end = start.addVector(look.x * range, look.y * range, look.z * range); return world.rayTraceBlocks(origin, endpoint, hitLiquids);
return world.rayTraceBlocks(start, end, hitLiquids);
} }
/** /**
* Helper method which does a rayTrace for entities from an entity's eye level in the direction they are looking * Helper method which performs a ray trace for blocks and entities from an entity's eye position in the direction
* with a specified range, using the tracePath method. Tidies up the code a bit. Border size defaults to 1. * they are looking, over a specified range, using {@link WizardryUtilities#rayTrace(World, Vec3d, Vec3d, float,
* boolean, Class, Predicate)}. Aim assist is zero, the entity type is simply {@code Entity} (all entities), and the
* filter removes the given entity and allows all others.
* *
* @param world * @param world The world in which to perform the ray trace.
* @param entity * @param entity The entity from which to perform the ray trace. The ray trace will start from this entity's eye
* @param range * position and proceed in the direction the entity is looking. This entity will be ignored when ray tracing.
* @return * @param range The distance over which the ray trace will be performed.
* @param hitLiquids True to return hits on the surfaces of liquids, false to ignore liquid blocks as if they were
* not there.
* @return A {@link RayTraceResult} representing the object that was hit, which may be an entity, a block or
* nothing. Returns {@code null} only if the origin and endpoint are within the same block and no entity was hit.
*/ */
@Nullable @Nullable
public static RayTraceResult standardEntityRayTrace(World world, EntityLivingBase entity, double range, boolean hitLiquids){ public static RayTraceResult standardEntityRayTrace(World world, Entity entity, double range, boolean hitLiquids){
return standardEntityRayTrace(world, entity, range, 1, hitLiquids); // This method does not apply an offset like ray spells do, since it is not desirable in most other use cases.
Vec3d origin = new Vec3d(entity.posX, entity.getEntityBoundingBox().minY + entity.getEyeHeight(), entity.posZ);
Vec3d endpoint = origin.add(entity.getLookVec().scale(range));
return WizardryUtilities.rayTrace(world, origin, endpoint, 0, hitLiquids, Entity.class, e -> e == entity);
} }
/** /**
* Helper method which does a rayTrace for entities from a entity's eye level in the direction they are looking with * Performs a ray trace for blocks and entities, starting at the given origin and finishing at the given endpoint.
* a specified range and radius, using the tracePath method. Tidies up the code a bit. * As of wizardry 4.2, the ray tracing methods have been rewritten to be more user-friendly and implement proper
* aim assist.
* <p>
* <i>N.B. It is possible to ignore entities entirely by passing in a {@code Predicate} that is always false;
* however, in this specific case it is more efficient to use
* {@link World#rayTraceBlocks(Vec3d, Vec3d, boolean, boolean, boolean)} or one of its overloads.</i>
* *
* @param world * @param <T> The type of entities to include; all other entities will be ignored.
* @param entity * @param world The world in which to perform the ray trace.
* @param range * @param origin A vector representing the coordinates of the start point of the ray trace.
* @param borderSize * @param endpoint A vector representing the coordinates of the finish point of the ray trace.
* @return * @param aimAssist In addition to direct hits, the ray trace will also hit entities that are up to this distance
*/ * from its path. For a normal ray trace, this should be 0. Values greater than 0 will give an 'aim assist' effect.
@Nullable * @param hitLiquids Whether liquids should be ignored when ray tracing blocks
public static RayTraceResult standardEntityRayTrace(World world, EntityLivingBase entity, double range, float borderSize, boolean hitLiquids){ * @param entityType The class of entities to include; all other entities will be ignored.
double dx = entity.getLookVec().x * range; * @param filter A {@link Predicate} which filters out entities that can be ignored; often used to exclude the
double dy = entity.getLookVec().y * range; * player that is performing the ray trace.
double dz = entity.getLookVec().z * range;
HashSet<Entity> hashset = new HashSet<Entity>(1);
hashset.add(entity);
return WizardryUtilities.tracePath(world, (float)entity.posX,
(float)(entity.getEntityBoundingBox().minY + entity.getEyeHeight()), (float)entity.posZ,
(float)(entity.posX + dx), (float)(entity.posY + entity.getEyeHeight() + dy), (float)(entity.posZ + dz),
borderSize, hashset, false, hitLiquids);
}
/**
* Method for ray tracing entities. You can also use this for seeking.
* *
* @param world * @return A {@link RayTraceResult} representing the object that was hit, which may be an entity, a block or
* @param x startX * nothing. Returns {@code null} only if the origin and endpoint are within the same block and no entity was hit.
* @param y startY *
* @param z startZ * @see WizardryUtilities#standardEntityRayTrace(World, Entity, double, boolean)
* @param tx endX * @see WizardryUtilities#standardBlockRayTrace(World, EntityLivingBase, double, boolean)
* @param ty endY
* @param tz endZ
* @param borderSize extra area to examine around line for entities
* @param excluded any excluded entities (the player, etc)
* @return a RayTraceResult of either the block hit (no entity hit), the entity hit (hit an entity), or null for
* nothing hit
*/ */
// Interestingly enough, aimAssist can be negative, which means hits have to be in the middle of entities!
@Nullable @Nullable
public static RayTraceResult tracePath(World world, float x, float y, float z, float tx, float ty, float tz, public static RayTraceResult rayTrace(World world, Vec3d origin, Vec3d endpoint, float aimAssist,
float borderSize, HashSet<Entity> excluded, boolean collideablesOnly, boolean hitLiquids){ boolean hitLiquids, Class<? extends Entity> entityType, Predicate<? super Entity> filter){
Vec3d startVec = new Vec3d(x, y, z); // 1 is the standard amount of extra search volume, and aim assist needs to increase this further as well as
// Vec3d lookVec = new Vec3d(tx-x, ty-y, tz-z); // expanding the entities' bounding boxes.
Vec3d endVec = new Vec3d(tx, ty, tz); float borderSize = 1 + aimAssist;
float minX = x < tx ? x : tx;
float minY = y < ty ? y : ty; // The AxisAlignedBB constructor accepts min/max coords in either order.
float minZ = z < tz ? z : tz; AxisAlignedBB searchVolume = new AxisAlignedBB(origin.x, origin.y, origin.z, endpoint.x, endpoint.y, endpoint.z)
float maxX = x > tx ? x : tx; .grow(borderSize, borderSize, borderSize);
float maxY = y > ty ? y : ty;
float maxZ = z > tz ? z : tz; // Gets all of the entities in the bounding box that could be collided with.
AxisAlignedBB bb = new AxisAlignedBB(minX, minY, minZ, maxX, maxY, maxZ).grow(borderSize, borderSize, borderSize); List<Entity> entities = world.getEntitiesWithinAABB(entityType, searchVolume);
List<Entity> allEntities = world.getEntitiesWithinAABBExcludingEntity(null, bb); // Applies the given filter to remove entities that should be ignored.
RayTraceResult blockHit = world.rayTraceBlocks(startVec, endVec, hitLiquids); entities.removeIf(filter);
startVec = new Vec3d(x, y, z);
endVec = new Vec3d(tx, ty, tz); // Finds the first block hit by the ray trace, if any.
float maxDistance = (float)endVec.distanceTo(startVec); RayTraceResult result = world.rayTraceBlocks(origin, endpoint, hitLiquids);
if(blockHit != null){
maxDistance = (float)blockHit.hitVec.distanceTo(startVec); // Clips the entity search range to the part of the ray trace before the block hit, if it hit a block.
if(result != null){
endpoint = result.hitVec;
} }
// Search variables
Entity closestHitEntity = null; Entity closestHitEntity = null;
float closestHit = maxDistance; Vec3d closestHitPosition = endpoint;
float currentHit = 0.f; AxisAlignedBB entityBounds;
AxisAlignedBB entityBb;// = ent.getBoundingBox();
RayTraceResult intercept; RayTraceResult intercept;
for(Entity ent : allEntities){
if((ent.canBeCollidedWith() || !collideablesOnly) // Iterates through all the entities
&& ((excluded != null && !excluded.contains(ent)) || excluded == null)){ for(Entity entity : entities){
float entBorder = ent.getCollisionBorderSize();
entityBb = ent.getEntityBoundingBox(); entityBounds = entity.getEntityBoundingBox();
if(entityBb != null){
entityBb = entityBb.grow(entBorder, entBorder, entBorder); if(entityBounds != null){
intercept = entityBb.calculateIntercept(startVec, endVec);
if(intercept != null){ // This is zero for everything except fireballs...
currentHit = (float)intercept.hitVec.distanceTo(startVec); float entityBorderSize = entity.getCollisionBorderSize();
if(currentHit < closestHit || currentHit == 0){ // ... meaning the following line does nothing in all other cases.
closestHit = currentHit; // -> Added the non-zero check to prevent unnecessary AABB object creation.
closestHitEntity = ent; if(entityBorderSize != 0) entityBounds = entityBounds.grow(entityBorderSize, entityBorderSize, entityBorderSize);
}
// Aim assist expands the bounding box to hit entities within the specified distance of the ray trace.
if(aimAssist != 0) entityBounds = entityBounds.grow(aimAssist, aimAssist, aimAssist);
// Finds the first point at which the ray trace intercepts the entity's bounding box, if any.
intercept = entityBounds.calculateIntercept(origin, endpoint);
// If the ray trace hit the entity...
if(intercept != null){
// Decides whether the entity that was hit is the closest so far, and if so, overwrites the old one.
float currentHitDistance = (float)intercept.hitVec.distanceTo(origin);
float closestHitDistance = (float)closestHitPosition.distanceTo(origin);
if(currentHitDistance < closestHitDistance){
closestHitEntity = entity;
closestHitPosition = intercept.hitVec;
} }
} }
} }
} }
// If the ray trace hit an entity, return that entity; otherwise return the result of the block ray trace.
if(closestHitEntity != null){ if(closestHitEntity != null){
blockHit = new RayTraceResult(closestHitEntity); result = new RayTraceResult(closestHitEntity, closestHitPosition);
} }
return blockHit;
return result;
} }
// SECTION Rendering and GUIs // SECTION Rendering and GUIs