Differential D2079 Diff 10885 ps/trunk/binaries/data/mods/public/simulation/helpers/ObstructionSnap.js
Changeset View
Changeset View
Standalone View
Standalone View
ps/trunk/binaries/data/mods/public/simulation/helpers/ObstructionSnap.js
Property | Old Value | New Value |
---|---|---|
svn:eol-style | null | native \ No newline at end of property |
/** | |||||
* The class allows the player to position structures so that they are aligned | |||||
* with nearby structures. | |||||
*/ | |||||
class ObstructionSnap | |||||
{ | |||||
getValidEdges(allEdges, position, maxSide) | |||||
{ | |||||
let edges = []; | |||||
let dir1 = new Vector2D(); | |||||
let dir2 = new Vector2D(); | |||||
for (let edge of allEdges) | |||||
{ | |||||
let signedDistance = Vector2D.dot(edge.normal, position) - | |||||
Vector2D.dot(edge.normal, edge.begin); | |||||
// Negative signed distance means that the template position | |||||
// lays behind the edge. | |||||
if (signedDistance < -this.MinimalDistanceToSnap - maxSide || | |||||
signedDistance > this.MinimalDistanceToSnap + maxSide) | |||||
continue; | |||||
dir1.setFrom(edge.begin).sub(edge.end).normalize(); | |||||
dir2.setFrom(dir1).mult(-1); | |||||
let offsetDistance = Math.max( | |||||
Vector2D.dot(dir1, position) - Vector2D.dot(dir1, edge.begin), | |||||
Vector2D.dot(dir2, position) - Vector2D.dot(dir2, edge.end)); | |||||
if (offsetDistance > this.MinimalDistanceToSnap + maxSide) | |||||
continue; | |||||
// If a projection of the template position on the edge is | |||||
// lying inside the edge then obviously we don't need to | |||||
// account the offset distance. | |||||
if (offsetDistance < 0) | |||||
offsetDistance = 0; | |||||
edge.signedDistance = signedDistance; | |||||
edge.offsetDistance = offsetDistance; | |||||
edges.push(edge); | |||||
} | |||||
return edges; | |||||
} | |||||
// We need a small padding to avoid unnecessary collisions | |||||
// because of loss of accuracy. | |||||
getPadding(edge) | |||||
{ | |||||
const snapPadding = 0.05; | |||||
// We don't need to padding for edges with normals directed inside | |||||
// its entity, as we try to snap from an internal side of the edge. | |||||
return edge.order == "ccw" ? 0 : snapPadding; | |||||
} | |||||
// Pick a base edge, it will be the first axis and fix the angle. | |||||
// We can't just pick an edge by signed distance, because we might have | |||||
// a case when one segment is closer by signed distance than another | |||||
// one but much farther by actual (euclid) distance. | |||||
compareEdges(a, b) | |||||
{ | |||||
const behindA = a.signedDistance < -this.EPS; | |||||
const behindB = b.signedDistance < -this.EPS; | |||||
const scoreA = Math.abs(a.signedDistance) + a.offsetDistance; | |||||
const scoreB = Math.abs(b.signedDistance) + b.offsetDistance; | |||||
if (Math.abs(scoreA - scoreB) < this.EPS) | |||||
{ | |||||
if (behindA != behindB) | |||||
return behindA - behindB; | |||||
if (!behindA) | |||||
return a.offsetDistance - b.offsetDistance; | |||||
return -a.signedDistance - -b.signedDistance; | |||||
} | |||||
return scoreA - scoreB; | |||||
} | |||||
getPosition(data, template) | |||||
{ | |||||
if (!data.snapToEdges || !template.Obstruction || !template.Obstruction.Static) | |||||
return undefined; | |||||
let width = template.Obstruction.Static["@depth"] / 2; | |||||
let depth = template.Obstruction.Static["@width"] / 2; | |||||
const maxSide = Math.max(width, depth); | |||||
let templatePos = Vector2D.from3D(data); | |||||
let templateAngle = data.angle || 0; | |||||
let edges = this.getValidEdges(data.snapToEdges, templatePos, maxSide); | |||||
if (!edges.length) | |||||
return undefined; | |||||
let baseEdge = edges[0]; | |||||
for (let edge of edges) | |||||
if (this.compareEdges(edge, baseEdge) < 0) | |||||
baseEdge = edge; | |||||
// Now we have the normal, we need to determine an angle, | |||||
// which side will be snapped first. | |||||
for (let dir = 0; dir < 4; ++dir) | |||||
{ | |||||
const angleCandidate = baseEdge.angle + dir * Math.PI / 2; | |||||
// We need to find a minimal angle difference. | |||||
let difference = Math.abs(angleCandidate - templateAngle); | |||||
difference = Math.min(difference, Math.PI * 2 - difference); | |||||
if (difference < Math.PI / 4 + this.EPS) | |||||
{ | |||||
// We need to swap sides for orthogonal cases. | |||||
if (dir % 2 == 0) | |||||
[width, depth] = [depth, width]; | |||||
templateAngle = angleCandidate; | |||||
break; | |||||
} | |||||
} | |||||
let distance = Vector2D.dot(baseEdge.normal, templatePos) - Vector2D.dot(baseEdge.normal, baseEdge.begin); | |||||
templatePos.sub(Vector2D.mult(baseEdge.normal, distance - width - this.getPadding(baseEdge))); | |||||
edges = this.getValidEdges(data.snapToEdges, templatePos, maxSide); | |||||
if (edges.length > 1) | |||||
{ | |||||
let pairedEdges = []; | |||||
for (let edge of edges) | |||||
{ | |||||
// We have to place a rectangle, so the angle between | |||||
// edges should be 90 degrees. | |||||
if (Math.abs(Vector2D.dot(baseEdge.normal, edge.normal)) > this.EPS) | |||||
continue; | |||||
let newEdge = { | |||||
"begin": edge.end, | |||||
"end": edge.begin, | |||||
"normal": Vector2D.mult(edge.normal, -1), | |||||
"signedDistance": -edge.signedDistance, | |||||
"offsetDistance": edge.offsetDistance, | |||||
"order": "ccw", | |||||
}; | |||||
pairedEdges.push(edge); | |||||
pairedEdges.push(newEdge); | |||||
} | |||||
pairedEdges.sort(this.compareEdges.bind(this)); | |||||
if (pairedEdges.length) | |||||
{ | |||||
let secondEdge = pairedEdges[0]; | |||||
for (let edge of pairedEdges) | |||||
if (this.compareEdges(edge, secondEdge) < 0) | |||||
secondEdge = edge; | |||||
let distance = Vector2D.dot(secondEdge.normal, templatePos) - Vector2D.dot(secondEdge.normal, secondEdge.begin); | |||||
templatePos.sub(Vector2D.mult(secondEdge.normal, distance - depth - this.getPadding(secondEdge))); | |||||
} | |||||
} | |||||
return { | |||||
"x": templatePos.x, | |||||
"z": templatePos.y, | |||||
"angle": templateAngle | |||||
}; | |||||
} | |||||
} | |||||
ObstructionSnap.prototype.MinimalDistanceToSnap = 5; | |||||
ObstructionSnap.prototype.EPS = 1e-3; | |||||
Engine.RegisterGlobal("ObstructionSnap", ObstructionSnap); |
Wildfire Games · Phabricator