Improve the Vector prototype with static functions. Replace the usage of the Math vector functions with functions from the Vector class.

This was SVN commit r14659.
This commit is contained in:
sanderd17 2014-01-24 19:51:00 +00:00
parent 5d4f509810
commit ecaded076f
6 changed files with 154 additions and 106 deletions

View File

@ -315,42 +315,3 @@ Math.intPow = function(x, y)
};
/**
* Gets the 2D interpreted version of the cross product of two vectors.
*/
Math.VectorCross = function(p1, p2)
{
return p1.x * p2.z - p1.z * p2.x;
};
/**
* Gets the straight line distance between p1 and p2.
*/
Math.VectorDistance = function(p1, p2)
{
return Math.sqrt((p1.x - p2.x) * (p1.x - p2.x) + (p1.z - p2.z) * (p1.z - p2.z));
};
/**
* Gets the straight line distance between p1 and p2 squared.
*/
Math.VectorDistanceSquared = function(p1, p2)
{
return (p1.x - p2.x) * (p1.x - p2.x) + (p1.z - p2.z) * (p1.z - p2.z);
};
/**
* Gets the dot product of two vectors.
*/
Math.VectorDot = function(p1, p2)
{
return p1.x * p2.x + p1.z * p2.z;
};
/**
* Get the total length of a given vector p.
*/
Math.VectorLength = function(p)
{
return Math.sqrt(p.x * p.x + p.z * p.z);
};

View File

@ -16,10 +16,10 @@ function Vector2D(x, y)
this.set(0, 0);
}
Vector2D.prototype.clone = function()
{
return new Vector2D(this.x, this.y);
};
// Mutating 2D functions
//
// These functions modify the current object,
// and always return this object to allow chaining
Vector2D.prototype.set = function(x, y)
{
@ -56,6 +56,33 @@ Vector2D.prototype.div = function(f)
return this;
};
Vector2D.prototype.normalize = function()
{
var mag = this.length();
if (!mag)
return this;
return this.div(mag);
};
/**
* Rotate a radians anti-clockwise
*/
Vector2D.prototype.rotate = function(a)
{
var sin = Math.sin(a);
var cos = Math.cos(a);
var x = this.x * cos + this.y * sin;
var y = this.y * cos - this.x * sin;
this.x = x;
this.y = y;
return this;
}
// Numeric 2D info functions (non-mutating)
//
// These methods serve to get numeric info on the vector, they don't modify the vector
Vector2D.prototype.dot = function(v)
{
return this.x * v.x + this.y * v.y;
@ -104,13 +131,39 @@ Vector2D.prototype.distanceTo = function(v)
return Math.sqrt(this.distanceToSquared(v));
};
Vector2D.prototype.normalize = function()
{
var mag = this.length();
if (!mag)
return this;
// Static 2D functions
//
// Static functions that return a new vector object.
// Note that object creation is slow in JS, so use them only when necessary
return this.div(mag);
Vector2D.clone = function(v)
{
return new Vector2D(v.x, v.y);
};
Vector2D.from3D = function(v)
{
return new Vector2D(v.x, v.z);
};
Vector2D.add = function(v1, v2)
{
return new Vector2D(v1.x + v2.x, v1.y + v2.y);
};
Vector2D.sub = function(v1, v2)
{
return new Vector2D(v1.x - v2.x, v1.y - v2.y);
};
Vector2D.mult = function(v, f)
{
return new Vector2D(v.x * f, v.y * f);
};
Vector2D.div = function(v, f)
{
return new Vector2D(v.x / f, v.y / f);
};
/////////////////////////////////////////////////////////////////////
@ -123,15 +176,15 @@ Vector2D.prototype.normalize = function()
function Vector3D(x, y, z)
{
if (arguments.length == 3)
this.set(x, y, y);
this.set(x, y, z);
else
this.set(0, 0, 0);
}
Vector3D.prototype.clone = function()
{
return new Vector3D(this.x, this.y, this.z);
};
// Mutating 3D functions
//
// These functions modify the current object,
// and always return this object to allow chaining
Vector3D.prototype.set = function(x, y, z)
{
@ -173,6 +226,19 @@ Vector3D.prototype.div = function(f)
return this;
};
Vector3D.prototype.normalize = function()
{
var mag = this.length();
if (!mag)
return this;
return this.div(mag);
};
// Numeric 3D info functions (non-mutating)
//
// These methods serve to get numeric info on the vector, they don't modify the vector
Vector3D.prototype.dot = function(v)
{
return this.x * v.x + this.y * v.y + this.z * v.z;
@ -216,15 +282,49 @@ Vector3D.prototype.distanceTo = function(v)
return Math.sqrt(this.distanceToSquared(v));
};
Vector3D.prototype.normalize = function()
Vector3D.prototype.horizDistanceToSquared = function(v)
{
var mag = this.length();
if (!mag)
return this;
return this.div(mag);
var dx = this.x - v.x;
var dz = this.z - v.z;
return dx * dx + dz * dz;
};
Vector3D.prototype.horizDistanceTo = function(v)
{
return Math.sqrt(this.horizDistanceToSquared(v));
};
// Static 3D functions
//
// Static functions that return a new vector object.
// Note that object creation is slow in JS, so use them only when really necessary
Vector3D.clone = function(v)
{
return new Vector3D(v.x, v.y, v.z);
};
Vector3D.add = function(v1, v2)
{
return new Vector3D(v1.x + v2.x, v1.y + v2.y, v1.z + v2.z);
};
Vector3D.sub = function(v1, v2)
{
return new Vector3D(v1.x - v2.x, v1.y - v2.y, v1.z - v2.z);
};
Vector3D.mult = function(v, f)
{
return new Vector3D(v.x * f, v.y * f, v.z * f);
};
Vector3D.div = function(v, f)
{
return new Vector3D(v.x / f, v.y / f, v.z / f);
};
// make the prototypes easily accessible to C++
const Vector2Dprototype = Vector2D.prototype;
const Vector3Dprototype = Vector3D.prototype;

View File

@ -457,14 +457,14 @@ Attack.prototype.PerformAttack = function(type, target)
return;
var targetPosition = cmpTargetPosition.GetPosition();
var relativePosition = {"x": targetPosition.x - selfPosition.x, "z": targetPosition.z - selfPosition.z}
var relativePosition = Vector3D.sub(targetPosition, selfPosition);
var previousTargetPosition = Engine.QueryInterface(target, IID_Position).GetPreviousPosition();
var targetVelocity = {"x": (targetPosition.x - previousTargetPosition.x) / this.turnLength, "z": (targetPosition.z - previousTargetPosition.z) / this.turnLength}
var targetVelocity = Vector3D.sub(targetPosition, previousTargetPosition).div(this.turnLength);
// the component of the targets velocity radially away from the archer
var radialSpeed = Math.VectorDot(relativePosition, targetVelocity) / Math.VectorLength(relativePosition);
var radialSpeed = relativePosition.dot(targetVelocity) / relativePosition.length();
var horizDistance = Math.VectorDistance(targetPosition, selfPosition);
var horizDistance = targetPosition.horizDistanceTo(selfPosition);
// This is an approximation of the time ot the target, it assumes that the target has a constant radial
// velocity, but since units move in straight lines this is not true. The exact value would be more
@ -473,8 +473,7 @@ Attack.prototype.PerformAttack = function(type, target)
var timeToTarget = horizDistance / (horizSpeed - radialSpeed);
// Predict where the unit is when the missile lands.
var predictedPosition = {"x": targetPosition.x + targetVelocity.x * timeToTarget,
"z": targetPosition.z + targetVelocity.z * timeToTarget};
var predictedPosition = Vector3D.mult(targetVelocity, timeToTarget).add(targetPosition);
// Compute the real target point (based on spread and target speed)
var range = this.GetRange(type);
@ -483,19 +482,19 @@ Attack.prototype.PerformAttack = function(type, target)
var distanceModifiedSpread = spread * horizDistance/elevationAdaptedMaxRange;
var randNorm = this.GetNormalDistribution();
var offsetX = randNorm[0] * distanceModifiedSpread * (1 + Math.VectorLength(targetVelocity) / 20);
var offsetZ = randNorm[1] * distanceModifiedSpread * (1 + Math.VectorLength(targetVelocity) / 20);
var offsetX = randNorm[0] * distanceModifiedSpread * (1 + targetVelocity.length() / 20);
var offsetZ = randNorm[1] * distanceModifiedSpread * (1 + targetVelocity.length() / 20);
var realTargetPosition = { "x": predictedPosition.x + offsetX, "y": targetPosition.y, "z": predictedPosition.z + offsetZ };
var realTargetPosition = new Vector3D(predictedPosition.x + offsetX, targetPosition.y, predictedPosition.z + offsetZ);
// Calculate when the missile will hit the target position
var realHorizDistance = Math.VectorDistance(realTargetPosition, selfPosition);
var realHorizDistance = realTargetPosition.horizDistanceTo(selfPosition);
var timeToTarget = realHorizDistance / horizSpeed;
var missileDirection = {"x": (realTargetPosition.x - selfPosition.x) / realHorizDistance, "z": (realTargetPosition.z - selfPosition.z) / realHorizDistance};
var missileDirection = Vector3D.sub(realTargetPosition, selfPosition).div(realHorizDistance);
// Make the arrow appear to land slightly behind the target so that arrows landing next to a guys foot don't count but arrows that go through the torso do
var graphicalPosition = {"x": realTargetPosition.x + 2*missileDirection.x, "y": realTargetPosition.y + 2*missileDirection.y};
var graphicalPosition = Vector3D.mult(missileDirection, 2).add(realTargetPosition);
// Launch the graphical projectile
var cmpProjectileManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ProjectileManager);
var id = cmpProjectileManager.LaunchProjectileAtPoint(this.entity, realTargetPosition, horizSpeed, gravity);
@ -520,8 +519,9 @@ Attack.prototype.InterpolatedLocation = function(ent, lateness)
var curPos = cmpTargetPosition.GetPosition();
var prevPos = cmpTargetPosition.GetPreviousPosition();
lateness /= 1000;
return {"x": (curPos.x * (this.turnLength - lateness) + prevPos.x * lateness) / this.turnLength,
"z": (curPos.z * (this.turnLength - lateness) + prevPos.z * lateness) / this.turnLength};
return new Vector3D((curPos.x * (this.turnLength - lateness) + prevPos.x * lateness) / this.turnLength,
0,
(curPos.z * (this.turnLength - lateness) + prevPos.z * lateness) / this.turnLength);
};
// Tests whether it point is inside of ent's footprint
@ -541,19 +541,16 @@ Attack.prototype.testCollision = function(ent, point, lateness)
if (targetShape.type === 'circle')
{
// Use VectorDistanceSquared and square targetShape.radius to avoid square roots.
return (Math.VectorDistanceSquared(point, targetPosition) < (targetShape.radius * targetShape.radius));
return (point.horizDistanceTo(targetPosition) < (targetShape.radius * targetShape.radius));
}
else
{
var targetRotation = Engine.QueryInterface(ent, IID_Position).GetRotation().y;
var angle = Engine.QueryInterface(ent, IID_Position).GetRotation().y;
var dx = point.x - targetPosition.x;
var dz = point.z - targetPosition.z;
var d = Vector3D.sub(point, targetPosition);
d = Vector2D.from3D(d).rotate(-angle);
var dxr = Math.cos(targetRotation) * dx - Math.sin(targetRotation) * dz;
var dzr = Math.sin(targetRotation) * dx + Math.cos(targetRotation) * dz;
return (-targetShape.width/2 <= dxr && dxr < targetShape.width/2 && -targetShape.depth/2 <= dzr && dzr < targetShape.depth/2);
return d.x < Math.abs(targetShape.width/2) && d.y < Math.abs(targetShape.depth/2);
}
};
@ -576,7 +573,7 @@ Attack.prototype.MissileHit = function(data, lateness)
playersToDamage = cmpPlayer.GetEnemies();
}
// Damage the units.
Damage.CauseSplashDamage({"attacker":this.entity, "origin":data.position, "radius":splashRadius, "shape":splashShape, "strengths":this.GetAttackStrengths(data.type), "direction":data.direction, "playersToDamage":playersToDamage, "type":data.type});
Damage.CauseSplashDamage({"attacker":this.entity, "origin":Vector2D.from3D(data.position), "radius":splashRadius, "shape":splashShape, "strengths":this.GetAttackStrengths(data.type), "direction":data.direction, "playersToDamage":playersToDamage, "type":data.type});
}
if (this.testCollision(data.target, data.position, lateness))
@ -595,7 +592,7 @@ Attack.prototype.MissileHit = function(data, lateness)
{
// If we didn't hit the main target look for nearby units
var cmpPlayer = Engine.QueryInterface(Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager).GetPlayerByID(data.playerId), IID_Player)
var ents = Damage.EntitiesNearPoint(data.position, Math.VectorDistance(data.position, targetPosition) * 2, cmpPlayer.GetEnemies());
var ents = Damage.EntitiesNearPoint(data.position, data.position.horizDistanceTo(targetPosition) * 2, cmpPlayer.GetEnemies());
for (var i = 0; i < ents.length; i++)
{

View File

@ -1311,11 +1311,7 @@ function ClusterEntities(ents, separationDistance)
}
positions.push(cmpPosition.GetPosition2D());
for (var j = 0; j < i; j++)
{
var dx = positions[i].x - positions[j].x;
var dy = positions[i].y - positions[j].y;
matrix[i][j] = dx * dx + dy * dy;
}
matrix[i][j] = positions[i].distanceToSquared(positions[j]);
}
while (clusters.length > 1)
{

View File

@ -4,7 +4,7 @@ var Damage = {};
/**
* Damages units around a given origin.
* data.attacker = <entity id>
* data.origin = {'x':<int>, 'z':<int>}
* data.origin = <Vector2D>
* data.radius = <int>
* data.shape = <string>
* data.strengths = {'hack':<float>, 'pierce':<float>, 'crush':<float>}
@ -21,23 +21,23 @@ Damage.CauseSplashDamage = function(data)
// Cycle through all the nearby entities and damage it appropriately based on its distance from the origin.
for each (var entity in nearEnts)
{
var entityPosition = Engine.QueryInterface(entity, IID_Position).GetPosition();
var entityPosition = Engine.QueryInterface(entity, IID_Position).GetPosition2D();
if(data.shape == 'Circular') // circular effect with quadratic falloff in every direction
{
var squaredDistanceFromOrigin = Math.VectorDistanceSquared(data.origin, entityPosition);
var squaredDistanceFromOrigin = data.origin.distanceToSquared(entityPosition);
damageMultiplier == 1 - squaredDistanceFromOrigin / (data.radius * data.radius);
}
else if(data.shape == 'Linear') // linear effect with quadratic falloff in two directions (only used for certain missiles)
{
// Get position of entity relative to splash origin.
var relativePos = {"x":entityPosition.x - data.origin.x, "z":entityPosition.z - data.origin.z};
var relativePos = entityPosition.sub(data.origin);
// The width of linear splash is one fifth of the normal splash radius.
var width = data.radius/5;
// Effectivly rotate the axis to align with the missile direction.
var parallelDist = Math.VectorDot(relativePos, data.direction); // z axis
var perpDist = Math.abs(Math.VectorCross(relativePos, data.direction)); // y axis
var parallelDist = relativePos.dot(data.direction); // z axis
var perpDist = Math.abs(relativePos.cross(data.direction)); // y axis
// Check that the unit is within the distance at which it will get damaged.
if (parallelDist > -width && perpDist < width) // If in radius, quadratic falloff in both directions
@ -87,15 +87,13 @@ Damage.CauseDamage = function(data)
/**
* Gets entities near a give point for given players.
* origin = {'x':<int>, 'z':<int>}
* origin = <Vector2D>
* radius = <int>
* players = <array>
* If players is not included, entities from all players are used.
*/
Damage.EntitiesNearPoint = function(origin, radius, players)
{
// Path to dummy template.
var dummyPath = "special/dummy";
// If there is insufficient data return an empty array.
if (!origin || !radius)
return [];
@ -111,7 +109,7 @@ Damage.EntitiesNearPoint = function(origin, radius, players)
// Call RangeManager with dummy entity and return the result.
var rangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
var rangeQuery = rangeManager.ExecuteQueryAroundPos({"x": origin.x, "y": origin.z}, 0, radius, players, IID_DamageReceiver);
var rangeQuery = rangeManager.ExecuteQueryAroundPos(origin, 0, radius, players, IID_DamageReceiver);
return rangeQuery;
};

View File

@ -3,18 +3,14 @@ function DistanceBetweenEntities(first, second)
var cmpFirstPosition = Engine.QueryInterface(first, IID_Position);
if (!cmpFirstPosition || !cmpFirstPosition.IsInWorld())
return Infinity;
var firstPosition = cmpFirstPosition.GetPosition();
var firstPosition = cmpFirstPosition.GetPosition2D();
var cmpSecondPosition = Engine.QueryInterface(second, IID_Position);
if (!cmpSecondPosition || !cmpSecondPosition.IsInWorld())
return Infinity;
var secondPosition = cmpSecondPosition.GetPosition();
var secondPosition = cmpSecondPosition.GetPosition2D();
var dx = secondPosition.x - firstPosition.x;
var dz = secondPosition.z - firstPosition.z;
var horizDistance = Math.sqrt(dx * dx + dz * dz);
return horizDistance;
return firstPosition.distanceTo(secondPosition);
}
/**