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();
WizardData properties = WizardData.get(mc.player);
RayTraceResult rayTrace = WizardryUtilities.standardEntityRayTrace(mc.world, mc.player, 16, false);
RenderManager renderManager = event.getRenderer().getRenderManager();
ItemStack wand = mc.player.getHeldItemMainhand();
@@ -218,45 +217,51 @@ public final class WizardryClientEventHandler {
}
// Target selection pointer
if(mc.player.isSneaking() && wand.getItem() instanceof ItemWand && rayTrace != null && !(event.getEntity() instanceof EntityArmorStand)
&& rayTrace.entityHit == event.getEntity() && properties != null && properties.selectedMinion != null){
if(mc.player.isSneaking() && wand.getItem() instanceof ItemWand && WizardryUtilities.isLiving(event.getEntity())
&& properties != null && properties.selectedMinion != null){
// -> Moved this in here so it isn't called every tick
RayTraceResult rayTrace = WizardryUtilities.standardEntityRayTrace(mc.world, mc.player, 16, false);
if(rayTrace != null && rayTrace.entityHit == event.getEntity()){
Tessellator tessellator = Tessellator.getInstance();
BufferBuilder buffer = tessellator.getBuffer();
Tessellator tessellator = Tessellator.getInstance();
BufferBuilder buffer = tessellator.getBuffer();
GlStateManager.pushMatrix();
GlStateManager.pushMatrix();
GlStateManager.disableCull();
GlStateManager.disableLighting();
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.disableCull();
GlStateManager.disableLighting();
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.translate(event.getX(), event.getY() + event.getEntity().height + 0.5, event.getZ());
// 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);
// 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.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX);
buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX);
mc.renderEngine.bindTexture(targetPointerTexture);
mc.renderEngine.bindTexture(targetPointerTexture);
buffer.pos(-0.2, 0.24, 0).tex(0, 0).endVertex();
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();
buffer.pos(-0.2, 0.24, 0).tex(0, 0).endVertex();
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();
tessellator.draw();
GlStateManager.enableCull();
GlStateManager.enableLighting();
GlStateManager.enableDepth();
GlStateManager.enableCull();
GlStateManager.enableLighting();
GlStateManager.enableDepth();
GlStateManager.popMatrix();
GlStateManager.popMatrix();
}
}
// Summoned creature selection pointer
@@ -70,6 +70,8 @@ public abstract class SpellRay extends Spell {
protected boolean ignoreEntities = false;
/** Whether liquids count as blocks when raytracing. Defaults to 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){
this(Wizardry.MODID, name, tier, element, type, cost, cooldown, isContinuous, baseRange, sound);
@@ -147,6 +149,17 @@ public abstract class SpellRay extends Spell {
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; }
// 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,
caster.posZ);
double range = baseRange * modifiers.get(WizardryItems.range_upgrade);
// 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(!shootSpell(world, origin, look, caster, ticksInUse, modifiers)) return false;
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));
@@ -253,6 +224,46 @@ public abstract class SpellRay extends Spell {
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 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.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Random;
import java.util.UUID;
import java.util.function.Function;
import java.util.function.Predicate;
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
public static RayTraceResult standardBlockRayTrace(World world, EntityLivingBase entity, double range, boolean hitLiquids){
Vec3d start = new Vec3d(entity.posX, entity.getEntityBoundingBox().minY + entity.getEyeHeight(), entity.posZ);
Vec3d look = entity.getLookVec();
Vec3d end = start.addVector(look.x * range, look.y * range, look.z * range);
return world.rayTraceBlocks(start, end, 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 world.rayTraceBlocks(origin, endpoint, hitLiquids);
}
/**
* Helper method which does a rayTrace for entities from an entity's eye level in the direction they are looking
* with a specified range, using the tracePath method. Tidies up the code a bit. Border size defaults to 1.
* Helper method which performs a ray trace for blocks and entities from an entity's eye position in the direction
* 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 entity
* @param range
* @return
* @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. This entity will be ignored when ray tracing.
* @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
public static RayTraceResult standardEntityRayTrace(World world, EntityLivingBase entity, double range, boolean hitLiquids){
return standardEntityRayTrace(world, entity, range, 1, hitLiquids);
public static RayTraceResult standardEntityRayTrace(World world, Entity 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 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
* a specified range and radius, using the tracePath method. Tidies up the code a bit.
* Performs a ray trace for blocks and entities, starting at the given origin and finishing at the given endpoint.
* 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 entity
* @param range
* @param borderSize
* @return
*/
@Nullable
public static RayTraceResult standardEntityRayTrace(World world, EntityLivingBase entity, double range, float borderSize, boolean hitLiquids){
double dx = entity.getLookVec().x * range;
double dy = entity.getLookVec().y * range;
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 <T> The type of entities to include; all other entities will be ignored.
* @param world The world in which to perform the ray trace.
* @param origin A vector representing the coordinates of the start point of the ray trace.
* @param endpoint A vector representing the coordinates of the finish point of the ray trace.
* @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.
* @param hitLiquids Whether liquids should be ignored when ray tracing blocks
* @param entityType The class of entities to include; all other entities will be ignored.
* @param filter A {@link Predicate} which filters out entities that can be ignored; often used to exclude the
* player that is performing the ray trace.
*
* @param world
* @param x startX
* @param y startY
* @param z startZ
* @param tx endX
* @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
* @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.
*
* @see WizardryUtilities#standardEntityRayTrace(World, Entity, double, boolean)
* @see WizardryUtilities#standardBlockRayTrace(World, EntityLivingBase, double, boolean)
*/
// Interestingly enough, aimAssist can be negative, which means hits have to be in the middle of entities!
@Nullable
public static RayTraceResult tracePath(World world, float x, float y, float z, float tx, float ty, float tz,
float borderSize, HashSet<Entity> excluded, boolean collideablesOnly, boolean hitLiquids){
public static RayTraceResult rayTrace(World world, Vec3d origin, Vec3d endpoint, float aimAssist,
boolean hitLiquids, Class<? extends Entity> entityType, Predicate<? super Entity> filter){
// 1 is the standard amount of extra search volume, and aim assist needs to increase this further as well as
// expanding the entities' bounding boxes.
float borderSize = 1 + aimAssist;
Vec3d startVec = new Vec3d(x, y, z);
// Vec3d lookVec = new Vec3d(tx-x, ty-y, tz-z);
Vec3d endVec = new Vec3d(tx, ty, tz);
float minX = x < tx ? x : tx;
float minY = y < ty ? y : ty;
float minZ = z < tz ? z : tz;
float maxX = x > tx ? x : tx;
float maxY = y > ty ? y : ty;
float maxZ = z > tz ? z : tz;
AxisAlignedBB bb = new AxisAlignedBB(minX, minY, minZ, maxX, maxY, maxZ).grow(borderSize, borderSize, borderSize);
List<Entity> allEntities = world.getEntitiesWithinAABBExcludingEntity(null, bb);
RayTraceResult blockHit = world.rayTraceBlocks(startVec, endVec, hitLiquids);
startVec = new Vec3d(x, y, z);
endVec = new Vec3d(tx, ty, tz);
float maxDistance = (float)endVec.distanceTo(startVec);
if(blockHit != null){
maxDistance = (float)blockHit.hitVec.distanceTo(startVec);
// The AxisAlignedBB constructor accepts min/max coords in either order.
AxisAlignedBB searchVolume = new AxisAlignedBB(origin.x, origin.y, origin.z, endpoint.x, endpoint.y, endpoint.z)
.grow(borderSize, borderSize, borderSize);
// Gets all of the entities in the bounding box that could be collided with.
List<Entity> entities = world.getEntitiesWithinAABB(entityType, searchVolume);
// Applies the given filter to remove entities that should be ignored.
entities.removeIf(filter);
// Finds the first block hit by the ray trace, if any.
RayTraceResult result = world.rayTraceBlocks(origin, endpoint, hitLiquids);
// 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;
float closestHit = maxDistance;
float currentHit = 0.f;
AxisAlignedBB entityBb;// = ent.getBoundingBox();
Vec3d closestHitPosition = endpoint;
AxisAlignedBB entityBounds;
RayTraceResult intercept;
for(Entity ent : allEntities){
if((ent.canBeCollidedWith() || !collideablesOnly)
&& ((excluded != null && !excluded.contains(ent)) || excluded == null)){
float entBorder = ent.getCollisionBorderSize();
entityBb = ent.getEntityBoundingBox();
if(entityBb != null){
entityBb = entityBb.grow(entBorder, entBorder, entBorder);
intercept = entityBb.calculateIntercept(startVec, endVec);
if(intercept != null){
currentHit = (float)intercept.hitVec.distanceTo(startVec);
if(currentHit < closestHit || currentHit == 0){
closestHit = currentHit;
closestHitEntity = ent;
}
// Iterates through all the entities
for(Entity entity : entities){
entityBounds = entity.getEntityBoundingBox();
if(entityBounds != null){
// This is zero for everything except fireballs...
float entityBorderSize = entity.getCollisionBorderSize();
// ... meaning the following line does nothing in all other cases.
// -> Added the non-zero check to prevent unnecessary AABB object creation.
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){
blockHit = new RayTraceResult(closestHitEntity);
result = new RayTraceResult(closestHitEntity, closestHitPosition);
}
return blockHit;
return result;
}
// SECTION Rendering and GUIs