mirror of
https://gitea.wildfiregames.com/0ad/0ad
synced 2026-08-15 14:43:32 -07:00
Sort units spatially for freehand selection
Some checks are pending
Some checks are pending
This commit is contained in:
parent
139b08fe6d
commit
91a37e32f6
2 changed files with 47 additions and 27 deletions
|
|
@ -442,3 +442,17 @@ Vector3D.div = function(v, f)
|
||||||
{
|
{
|
||||||
return new Vector3D(v.x / f, v.y / f, v.z / f);
|
return new Vector3D(v.x / f, v.y / f, v.z / f);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Linear interpolation between this vector and another.
|
||||||
|
* @param {Vector2D} v - The target vector.
|
||||||
|
* @param {number} t - Interpolation factor (0 = this, 1 = v).
|
||||||
|
* @returns {Vector2D} A new vector at the interpolated position.
|
||||||
|
*/
|
||||||
|
Vector2D.prototype.lerp = function(v, t)
|
||||||
|
{
|
||||||
|
return new Vector2D(
|
||||||
|
this.x + (v.x - this.x) * t,
|
||||||
|
this.y + (v.y - this.y) * t
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -1441,6 +1441,7 @@ function positionUnitsFreehandSelectionMouseUp(ev)
|
||||||
for (let i = 1; i < inputLine.length; ++i)
|
for (let i = 1; i < inputLine.length; ++i)
|
||||||
lengthOfLine += inputLine[i].distanceTo(inputLine[i - 1]);
|
lengthOfLine += inputLine[i].distanceTo(inputLine[i - 1]);
|
||||||
|
|
||||||
|
// Filter units that can move
|
||||||
const selection = g_Selection.filter(ent => !!GetEntityState(ent).unitAI).sort((a, b) => a - b);
|
const selection = g_Selection.filter(ent => !!GetEntityState(ent).unitAI).sort((a, b) => a - b);
|
||||||
|
|
||||||
// Checking the line for a minimum length to save performance.
|
// Checking the line for a minimum length to save performance.
|
||||||
|
|
@ -1450,39 +1451,43 @@ function positionUnitsFreehandSelectionMouseUp(ev)
|
||||||
return !!action && doAction(action, ev);
|
return !!action && doAction(action, ev);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Even distribution of the units on the line.
|
// Sort units by their projection onto the line direction (parallel sorting)
|
||||||
let p0 = inputLine[0];
|
const sortedSelection = sortEntitiesAlongLine(selection, inputLine[0], inputLine[inputLine.length - 1], true);
|
||||||
let entityDistribution = [p0];
|
|
||||||
const distanceBetweenEnts = lengthOfLine / (selection.length - 1);
|
|
||||||
let freeDist = -distanceBetweenEnts;
|
|
||||||
|
|
||||||
for (let i = 1; i < inputLine.length; ++i)
|
const selectionCount = sortedSelection.length;
|
||||||
|
const targetPositions = [];
|
||||||
|
|
||||||
|
// Distribute units evenly along the polyline
|
||||||
|
for (let i = 0; i < selectionCount; ++i)
|
||||||
{
|
{
|
||||||
const p1 = inputLine[i];
|
const t = i / (selectionCount - 1);
|
||||||
freeDist += inputLine[i - 1].distanceTo(p1);
|
const targetDistance = t * lengthOfLine;
|
||||||
|
|
||||||
while (freeDist >= 0)
|
let accumulated = 0;
|
||||||
|
let targetPoint = inputLine[0];
|
||||||
|
for (let j = 1; j < inputLine.length; ++j)
|
||||||
{
|
{
|
||||||
p0 = Vector2D.sub(p0, p1).normalize().mult(freeDist).add(p1);
|
const segStart = inputLine[j - 1];
|
||||||
entityDistribution.push(p0);
|
const segEnd = inputLine[j];
|
||||||
freeDist -= distanceBetweenEnts;
|
const segLength = segEnd.distanceTo(segStart);
|
||||||
|
|
||||||
|
if (accumulated + segLength >= targetDistance)
|
||||||
|
{
|
||||||
|
// Interpolate within this segment
|
||||||
|
const remaining = targetDistance - accumulated;
|
||||||
|
const frac = remaining / segLength;
|
||||||
|
targetPoint = segStart.lerp(segEnd, frac);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
accumulated += segLength;
|
||||||
}
|
}
|
||||||
|
targetPositions.push(targetPoint);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rounding errors can lead to missing or too many points.
|
|
||||||
entityDistribution = entityDistribution.slice(0, selection.length);
|
|
||||||
entityDistribution = entityDistribution.concat(new Array(selection.length - entityDistribution.length).fill(inputLine[inputLine.length - 1]));
|
|
||||||
|
|
||||||
if (Vector2D.from3D(GetEntityState(selection[0]).position).distanceTo(entityDistribution[0]) +
|
|
||||||
Vector2D.from3D(GetEntityState(selection[selection.length - 1]).position).distanceTo(entityDistribution[selection.length - 1]) >
|
|
||||||
Vector2D.from3D(GetEntityState(selection[0]).position).distanceTo(entityDistribution[selection.length - 1]) +
|
|
||||||
Vector2D.from3D(GetEntityState(selection[selection.length - 1]).position).distanceTo(entityDistribution[0]))
|
|
||||||
entityDistribution.reverse();
|
|
||||||
|
|
||||||
Engine.PostNetworkCommand({
|
Engine.PostNetworkCommand({
|
||||||
"type": isAttackMovePressed() ? "attack-walk-custom" : "walk-custom",
|
"type": isAttackMovePressed() ? "attack-walk-custom" : "walk-custom",
|
||||||
"entities": selection,
|
"entities": sortedSelection,
|
||||||
"targetPositions": entityDistribution.map(pos => pos.toFixed(2)),
|
"targetPositions": targetPositions.map(pos => pos.toFixed(2)),
|
||||||
"targetClasses": Engine.HotkeyIsPressed("session.attackmoveUnit") ? { "attack": ["Unit"] } : { "attack": ["Unit", "Structure"] },
|
"targetClasses": Engine.HotkeyIsPressed("session.attackmoveUnit") ? { "attack": ["Unit"] } : { "attack": ["Unit", "Structure"] },
|
||||||
"queued": Engine.HotkeyIsPressed("session.queue"),
|
"queued": Engine.HotkeyIsPressed("session.queue"),
|
||||||
"pushFront": Engine.HotkeyIsPressed("session.pushorderfront"),
|
"pushFront": Engine.HotkeyIsPressed("session.pushorderfront"),
|
||||||
|
|
@ -1490,9 +1495,10 @@ function positionUnitsFreehandSelectionMouseUp(ev)
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add target markers with a minimum distance of 5 to each other.
|
// Add target markers with a minimum distance of 5 to each other.
|
||||||
const entitiesBetweenMarker = Math.ceil(5 / distanceBetweenEnts);
|
const stepDistance = lengthOfLine / (selectionCount - 1);
|
||||||
for (let i = 0; i < entityDistribution.length; i += entitiesBetweenMarker)
|
const entitiesBetweenMarker = Math.max(1, Math.ceil(5 / stepDistance));
|
||||||
DrawTargetMarker({ "x": entityDistribution[i].x, "z": entityDistribution[i].y });
|
for (let i = 0; i < targetPositions.length; i += entitiesBetweenMarker)
|
||||||
|
DrawTargetMarker({ "x": targetPositions[i].x, "z": targetPositions[i].y });
|
||||||
|
|
||||||
Engine.GuiInterfaceCall("PlaySound", {
|
Engine.GuiInterfaceCall("PlaySound", {
|
||||||
"name": "order_walk",
|
"name": "order_walk",
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue