Port Paths to AS from java. Also, port (with modifications) AStarPathUtil -> AStarPathSearch which is no longer a singleton and has fewer hidden dependencies (e.g. the restriction on the "longest" arg to getPath). Also, make MisoScenePanel actually create paths now that we have them.
git-svn-id: svn+ssh://src.earth.threerings.net/nenya/trunk@982 ed5b42cb-e716-0410-a449-f6a68f950b19
This commit is contained in:
@@ -157,6 +157,7 @@ public class CharacterSprite extends Sprite
|
||||
|
||||
public function setOrientation (orient :int) :void
|
||||
{
|
||||
var oorient :int = _orient;
|
||||
_orient = orient;
|
||||
if (orient < 0 || orient >= DirectionCodes.FINE_DIRECTION_COUNT) {
|
||||
log.info("Refusing to set invalid orientation [sprite=" + this +
|
||||
@@ -164,12 +165,16 @@ public class CharacterSprite extends Sprite
|
||||
return;
|
||||
}
|
||||
|
||||
var oorient :int = _orient;
|
||||
if (_orient != oorient) {
|
||||
updateMainSprite();
|
||||
}
|
||||
}
|
||||
|
||||
public function getOrientation () :int
|
||||
{
|
||||
return _orient;
|
||||
}
|
||||
|
||||
public function cancelMove () :void
|
||||
{
|
||||
halt();
|
||||
@@ -181,7 +186,7 @@ public class CharacterSprite extends Sprite
|
||||
setActionSequence(getFollowingPathAction());
|
||||
}
|
||||
|
||||
public function pathCompleted () :void
|
||||
public function pathCompleted (timestamp :int) :void
|
||||
{
|
||||
halt();
|
||||
}
|
||||
@@ -231,6 +236,7 @@ public class CharacterSprite extends Sprite
|
||||
if (_aframes != null) {
|
||||
_aframes.getFrames(_orient, function(frames :MultiFrameBitmap) :void {
|
||||
_framesBitmap = frames;
|
||||
DisplayUtil.removeAllChildren(_mainSprite);
|
||||
_mainSprite.addChild(frames);
|
||||
});
|
||||
} else {
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://www.threerings.net/code/nenya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.media.util {
|
||||
|
||||
import com.threerings.util.MathUtil;
|
||||
|
||||
/**
|
||||
* Searches for the shortest traversable path between locations.
|
||||
*/
|
||||
public class AStarPathSearch
|
||||
{
|
||||
/**
|
||||
* Creates a searching object using tpred to check for traversability and stepper to find
|
||||
* all the valid adjacent spots and the stepping costs & estimates.
|
||||
*/
|
||||
public function AStarPathSearch (tpred :TraversalPred,
|
||||
stepper :AStarPathSearch_Stepper = null)
|
||||
{
|
||||
_tpred = tpred;
|
||||
|
||||
if (stepper == null) {
|
||||
_stepper = new AStarPathSearch_Stepper();
|
||||
} else {
|
||||
_stepper = stepper;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a list of <code>Point</code> objects representing a path from coordinates
|
||||
* <code>(ax, by)</code> to <code>(bx, by)</code>, inclusive, determined by performing an
|
||||
* A* search in the given scene's base tile layer. Assumes the starting and destination nodes
|
||||
* are traversable by the specified traverser.
|
||||
*
|
||||
* @param trav the traverser to follow the path.
|
||||
* @param longest the longest allowable path in tile traversals. This arg must be small enough
|
||||
* that _stepper.getMaxCost(longest) < Integer.MAX_VALUE
|
||||
* @param ax the starting x-position in tile coordinates.
|
||||
* @param ay the starting y-position in tile coordinates.
|
||||
* @param bx the ending x-position in tile coordinates.
|
||||
* @param by the ending y-position in tile coordinates.
|
||||
* @param partial if true, a partial path will be returned that gets us as close as we can to
|
||||
* the goal in the event that a complete path cannot be located.
|
||||
*
|
||||
* @return the list of points in the path, or null if no path could be found.
|
||||
*/
|
||||
public function getPath (trav :Object, longest :int, ax :int, ay :int, bx :int, by :int,
|
||||
partial :Boolean) :Array
|
||||
{
|
||||
var info :AStarPathSearch_Info = new AStarPathSearch_Info(_tpred, trav,
|
||||
_stepper.getMaxCost(longest), bx, by);
|
||||
|
||||
// set up the starting node
|
||||
var s :AStarPathSearch_Node = info.getNode(ax, ay);
|
||||
s.g = 0;
|
||||
s.h = _stepper.getDistanceEstimate(ax, ay, bx, by);
|
||||
s.f = s.g + s.h;
|
||||
|
||||
// push starting node on the open list
|
||||
info.open.add(s);
|
||||
|
||||
// track the best path
|
||||
var bestdist :Number = Number.POSITIVE_INFINITY;
|
||||
var bestpath :AStarPathSearch_Node = null;
|
||||
|
||||
// while there are more nodes on the open list
|
||||
while (info.open.size() > 0) {
|
||||
// pop the best node so far from open
|
||||
var n :AStarPathSearch_Node = null;
|
||||
|
||||
info.open.forEach(function (val :AStarPathSearch_Node) :Boolean {
|
||||
n = val;
|
||||
return true;
|
||||
});
|
||||
info.open.remove(n);
|
||||
|
||||
// if node is a goal node
|
||||
if (n.x == bx && n.y == by) {
|
||||
// construct and return the acceptable path
|
||||
return n.getNodePath();
|
||||
|
||||
} else if (partial) {
|
||||
var pathdist :Number = MathUtil.distance(n.x, n.y, bx, by);
|
||||
if (pathdist < bestdist) {
|
||||
bestdist = pathdist;
|
||||
bestpath = n;
|
||||
}
|
||||
}
|
||||
|
||||
// consider each successor of the node
|
||||
_stepper.considerSteps(info, n, n.x, n.y);
|
||||
|
||||
// push the node on the closed list
|
||||
info.closed.add(n);
|
||||
}
|
||||
|
||||
// return the best path we could find if we were asked to do so
|
||||
if (bestpath != null) {
|
||||
return bestpath.getNodePath();
|
||||
}
|
||||
|
||||
// no path found
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/** In charge of determining if we can walk across various bits. */
|
||||
protected var _tpred :TraversalPred;
|
||||
|
||||
/** In charge of finding all the steps from a given spot. */
|
||||
protected var _stepper :AStarPathSearch_Stepper;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,178 @@
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://www.threerings.net/code/nenya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.media.util {
|
||||
|
||||
import com.threerings.util.Maps;
|
||||
|
||||
import com.threerings.util.Map;
|
||||
import com.threerings.util.Set;
|
||||
import com.threerings.util.Sets;
|
||||
|
||||
/**
|
||||
* A holding class to contain the wealth of information referenced
|
||||
* while performing an A* search for a path through a tile array.
|
||||
*/
|
||||
public class AStarPathSearch_Info
|
||||
{
|
||||
/** Knows whether or not tiles are traversable. */
|
||||
public var tpred :TraversalPred;
|
||||
|
||||
/** The tile array dimensions. */
|
||||
public var tilewid :int;
|
||||
public var tilehei :int;
|
||||
|
||||
/** The traverser moving along the path. */
|
||||
public var trav :Object;
|
||||
|
||||
/** The set of open nodes being searched. */
|
||||
// TODO - This would benefit from a more efficient implementation of SortedSet, but for now
|
||||
// it is good enough.
|
||||
public var open :Set;
|
||||
|
||||
/** The set of closed nodes being searched. */
|
||||
public var closed :Set;
|
||||
|
||||
/** The destination coordinates in the tile array. */
|
||||
public var destx :int;
|
||||
public var desty :int;
|
||||
|
||||
/** The maximum cost of any path that we'll consider. */
|
||||
public var maxcost :int;
|
||||
|
||||
public function AStarPathSearch_Info (tpred :TraversalPred, trav :Object, maxcost :int,
|
||||
destx :int, desty :int)
|
||||
{
|
||||
// save off references
|
||||
this.tpred = tpred;
|
||||
this.trav = trav;
|
||||
this.destx = destx;
|
||||
this.desty = desty;
|
||||
|
||||
// compute our maximum path cost
|
||||
this.maxcost = maxcost;
|
||||
|
||||
// construct the open and closed lists
|
||||
open = Sets.newSortedSetOf(AStarPathSearch_Node);
|
||||
closed = Sets.newSetOf(AStarPathSearch_Node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether moving from the given source to destination coordinates is a valid
|
||||
* move.
|
||||
*/
|
||||
protected function isStepValid (sx :int, sy :int, dx :int, dy :int) :Boolean
|
||||
{
|
||||
// not traversable if the destination itself fails test
|
||||
if (tpred is ExtendedTraversalPred) {
|
||||
if (!ExtendedTraversalPred(tpred).canTraverseBetween(trav, sx, sy, dx, dy)) {
|
||||
return false;
|
||||
}
|
||||
} else if (!isTraversable(dx, dy)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// if the step is diagonal, make sure the corners don't impede our progress
|
||||
if ((Math.abs(dx - sx) == 1) && (Math.abs(dy - sy) == 1)) {
|
||||
return isTraversable(dx, sy) && isTraversable(sx, dy);
|
||||
}
|
||||
|
||||
// non-diagonals are always traversable
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the given coordinate is valid and traversable.
|
||||
*/
|
||||
protected function isTraversable (x :int, y :int) :Boolean
|
||||
{
|
||||
return tpred.canTraverse(trav, x, y);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get or create the node for the specified point.
|
||||
*/
|
||||
public function getNode (x :int, y :int) :AStarPathSearch_Node
|
||||
{
|
||||
// note: this _could_ break for unusual values of x and y.
|
||||
// perhaps use a IntTuple as a key? Bleah.
|
||||
var key :int = (x << 16) | (y & 0xffff);
|
||||
var node :AStarPathSearch_Node = _nodes.get(key);
|
||||
if (node == null) {
|
||||
node = new AStarPathSearch_Node(x, y);
|
||||
_nodes.put(key, node);
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Consider the step <code>(n.x, n.y)</code> to <code>(x, y)</code> for possible inclusion
|
||||
* in the path.
|
||||
*
|
||||
* @param info the info object.
|
||||
* @param node the originating node for the step.
|
||||
* @param x the x-coordinate for the destination step.
|
||||
* @param y the y-coordinate for the destination step.
|
||||
*/
|
||||
public function considerStep (node :AStarPathSearch_Node, x :int, y :int, cost :int,
|
||||
stepper :AStarPathSearch_Stepper) :void
|
||||
{
|
||||
// skip node if it's outside the map bounds or otherwise impassable
|
||||
if (!isStepValid(node.x, node.y, x, y)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// calculate the new cost for this node
|
||||
var newg :int = node.g + cost;
|
||||
|
||||
// make sure the cost is reasonable
|
||||
if (newg > maxcost) {
|
||||
return;
|
||||
}
|
||||
|
||||
// retrieve the node corresponding to this location
|
||||
var np :AStarPathSearch_Node = getNode(x, y);
|
||||
|
||||
// skip if it's already in the open or closed list or if its
|
||||
// actual cost is less than the just-calculated cost
|
||||
if ((open.contains(np) || closed.contains(np)) && np.g <= newg) {
|
||||
return;
|
||||
}
|
||||
|
||||
// remove the node from the open list since we're about to
|
||||
// modify its score which determines its placement in the list
|
||||
open.remove(np);
|
||||
|
||||
// update the node's information
|
||||
np.parent = node;
|
||||
np.g = newg;
|
||||
np.h = stepper.getDistanceEstimate(np.x, np.y, destx, desty);
|
||||
np.f = np.g + np.h;
|
||||
|
||||
// remove it from the closed list if it's present
|
||||
closed.remove(np);
|
||||
|
||||
// add it to the open list for further consideration
|
||||
open.add(np);
|
||||
}
|
||||
|
||||
/** The nodes being considered in the path. */
|
||||
protected var _nodes :Map = Maps.newMapOf(int);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://www.threerings.net/code/nenya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.media.util {
|
||||
|
||||
import flash.geom.Point;
|
||||
|
||||
import com.threerings.util.Comparable;
|
||||
|
||||
public class AStarPathSearch_Node
|
||||
implements Comparable
|
||||
{
|
||||
/** The node coordinates. */
|
||||
public var x :int;
|
||||
public var y :int;
|
||||
|
||||
/** The actual cheapest cost of arriving here from the start. */
|
||||
public var g :int;
|
||||
|
||||
/** The heuristic estimate of the cost to the goal from here. */
|
||||
public var h :int;
|
||||
|
||||
/** The score assigned to this node. */
|
||||
public var f :int;
|
||||
|
||||
/** The node from which we reached this node. */
|
||||
public var parent :AStarPathSearch_Node;
|
||||
|
||||
/** The node's monotonically-increasing unique identifier. */
|
||||
public var id :int;
|
||||
|
||||
public function AStarPathSearch_Node (x :int, y :int)
|
||||
{
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
id = _nextid++;
|
||||
}
|
||||
|
||||
public function compareTo (o :Object) :int
|
||||
{
|
||||
var n :AStarPathSearch_Node = AStarPathSearch_Node(o);
|
||||
var bf :int = n.f;
|
||||
|
||||
// since the set contract is fulfilled using the equality results returned here, and
|
||||
// we'd like to allow multiple nodes with equivalent scores in our set, we explicitly
|
||||
// define object equivalence as the result of object.equals(), else we use the unique
|
||||
// node id since it will return a consistent ordering for the objects.
|
||||
if (f == bf) {
|
||||
return (this == n) ? 0 : (id - n.id);
|
||||
}
|
||||
|
||||
return f - bf;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an array of <code>Point</code> objects detailing the path from the first node (the
|
||||
* given node's ultimate parent) to the ending node (the given node itself.)
|
||||
*
|
||||
* @param node the ending node in the path.
|
||||
*
|
||||
* @return the list detailing the path.
|
||||
*/
|
||||
public function getNodePath () :Array
|
||||
{
|
||||
var cur :AStarPathSearch_Node = this;
|
||||
var path :Array = [];
|
||||
|
||||
while (cur != null) {
|
||||
// add to the head of the list since we're traversing from
|
||||
// the end to the beginning
|
||||
path.unshift(new Point(cur.x, cur.y));
|
||||
|
||||
// advance to the next node in the path
|
||||
cur = cur.parent;
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
/** The next unique node id. */
|
||||
protected static var _nextid :int = 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://www.threerings.net/code/nenya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.media.util {
|
||||
|
||||
/**
|
||||
* Considers all the possible steps the piece in question can take.
|
||||
*/
|
||||
public class AStarPathSearch_Stepper
|
||||
{
|
||||
public function AStarPathSearch_Stepper (considerDiagonals :Boolean = true)
|
||||
{
|
||||
_considerDiagonals = considerDiagonals;
|
||||
}
|
||||
|
||||
public function getMaxCost (longest :int) :int
|
||||
{
|
||||
return longest * ADJACENT_COST;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a heuristic estimate of the cost to get from <code>(ax, ay)</code> to
|
||||
* <code>(bx, by)</code>.
|
||||
*/
|
||||
public function getDistanceEstimate (ax :int, ay :int, bx :int, by :int) :int
|
||||
{
|
||||
// we're doing all of our cost calculations based on geometric distance times ten
|
||||
var dx :int = bx - ax;
|
||||
var dy :int = by - ay;
|
||||
return int(Math.floor(ADJACENT_COST * Math.sqrt(dx * dx + dy * dy)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Should call {@link #considerStep} in turn on all possible steps from the specified
|
||||
* coordinates. No checking must be done as to whether the step is legal, that will be
|
||||
* handled later. Just enumerate all possible steps.
|
||||
*/
|
||||
public function considerSteps (info :AStarPathSearch_Info, node :AStarPathSearch_Node,
|
||||
x :int, y :int) :void
|
||||
{
|
||||
info.considerStep(node, x, y - 1, ADJACENT_COST, this);
|
||||
info.considerStep(node, x, y + 1, ADJACENT_COST, this);
|
||||
info.considerStep(node, x - 1, y, ADJACENT_COST, this);
|
||||
info.considerStep(node, x + 1, y, ADJACENT_COST, this);
|
||||
if (_considerDiagonals) {
|
||||
info.considerStep(node, x - 1, y - 1, DIAGONAL_COST, this);
|
||||
info.considerStep(node, x + 1, y - 1, DIAGONAL_COST, this);
|
||||
info.considerStep(node, x - 1, y + 1, DIAGONAL_COST, this);
|
||||
info.considerStep(node, x + 1, y + 1, DIAGONAL_COST, this);
|
||||
}
|
||||
}
|
||||
|
||||
protected var _considerDiagonals :Boolean;
|
||||
|
||||
/** The standard cost to move between nodes. */
|
||||
protected static const ADJACENT_COST :int = 10;
|
||||
|
||||
/** The cost to move diagonally. */
|
||||
protected static const DIAGONAL_COST :int = int(Math.sqrt((ADJACENT_COST * ADJACENT_COST) * 2));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://www.threerings.net/code/nenya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.media.util {
|
||||
|
||||
/**
|
||||
* Provides extended traversibility information when computing paths.
|
||||
*/
|
||||
public interface ExtendedTraversalPred extends TraversalPred
|
||||
{
|
||||
/**
|
||||
* Requests to know if the specific traverser (which was provided in the call to
|
||||
* {@link #getPath(TraversalPred,Object,int,int,int,int,int,boolean)}) can traverse from
|
||||
* the specified source tile coordinate to the specified destination tile coordinate.
|
||||
*/
|
||||
function canTraverseBetween (traverser :Object, sx :int, sy :int, dx :int, dy :int) :Boolean;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,373 @@
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://www.threerings.net/code/nenya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.media.util {
|
||||
|
||||
import flash.geom.Point;
|
||||
import flash.geom.Rectangle;
|
||||
|
||||
import com.threerings.util.ArrayIterator;
|
||||
import com.threerings.util.DirectionCodes;
|
||||
import com.threerings.util.DirectionUtil;
|
||||
import com.threerings.util.Iterator;
|
||||
import com.threerings.util.Log;
|
||||
import com.threerings.util.MathUtil;
|
||||
import com.threerings.util.StringUtil;
|
||||
|
||||
/**
|
||||
* The line segment path is used to cause a pathable to follow a path that
|
||||
* is made up of a sequence of line segments. There must be at least two
|
||||
* nodes in any worthwhile path. The direction of the first node in the
|
||||
* path is meaningless since the pathable begins at that node and will
|
||||
* therefore never be heading towards it.
|
||||
*/
|
||||
public class LineSegmentPath
|
||||
implements Path
|
||||
{
|
||||
private var log :Log = Log.getLog(LineSegmentPath);
|
||||
|
||||
/**
|
||||
* Constructs an empty line segment path.
|
||||
*/
|
||||
public function LineSegmentPath ()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a line segment path that consists of a single segment
|
||||
* connecting the point <code>(x1, y1)</code> with <code>(x2,
|
||||
* y2)</code>. The orientation for the first node is set arbitrarily
|
||||
* and the second node is oriented based on the vector between the two
|
||||
* nodes (in top-down coordinates).
|
||||
*/
|
||||
public static function createWithInts (x1 :int, y1 :int, x2 :int, y2 :int) :LineSegmentPath
|
||||
{
|
||||
var path :LineSegmentPath = new LineSegmentPath();
|
||||
path.addNode(x1, y1, DirectionCodes.NORTH);
|
||||
var p1 :Point = new Point(x1, y1);
|
||||
var p2 :Point = new Point(x2, y2);
|
||||
var dir :int = DirectionUtil.getDirectionForPts(p1, p2);
|
||||
path.addNode(x2, y2, dir);
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a line segment path between the two nodes with the
|
||||
* specified direction.
|
||||
*/
|
||||
public static function createWithPoints (p1 :Point, p2 :Point, dir :int) :LineSegmentPath
|
||||
{
|
||||
var path :LineSegmentPath = new LineSegmentPath();
|
||||
path.addNode(p1.x, p1.y, DirectionCodes.NORTH);
|
||||
path.addNode(p2.x, p2.y, dir);
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a line segment path with the specified list of points.
|
||||
* An arbitrary direction will be assigned to the starting node.
|
||||
*/
|
||||
public static function createWithList (points :Array) :LineSegmentPath
|
||||
{
|
||||
var path :LineSegmentPath = new LineSegmentPath();
|
||||
path.createPath(points);
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the orientation the sprite will face at the end of the
|
||||
* path.
|
||||
*/
|
||||
public function getFinalOrientation () :int
|
||||
{
|
||||
return (_nodes.length == 0) ? DirectionCodes.NORTH : _nodes[_nodes.length-1].dir;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a node to the path with the specified destination point and
|
||||
* facing direction.
|
||||
*
|
||||
* @param x the x-position.
|
||||
* @param y the y-position.
|
||||
* @param dir the facing direction.
|
||||
*/
|
||||
public function addNode (x :Number, y :Number, dir :int) :void
|
||||
{
|
||||
_nodes.push(new PathNode(x, y, dir));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the requested node index in the path, or null if no such
|
||||
* index exists.
|
||||
*
|
||||
* @param idx the node index.
|
||||
*
|
||||
* @return the path node.
|
||||
*/
|
||||
public function getNode (idx :int) :PathNode
|
||||
{
|
||||
return _nodes[idx];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the number of nodes in the path.
|
||||
*/
|
||||
public function size () :int
|
||||
{
|
||||
return _nodes.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the velocity of this pathable in pixels per millisecond. The
|
||||
* velocity is measured as pixels traversed along the path that the
|
||||
* pathable is traveling rather than in the x or y directions
|
||||
* individually. Note that the pathable velocity should not be
|
||||
* changed while a path is being traversed; doing so may result in the
|
||||
* pathable position changing unexpectedly.
|
||||
*
|
||||
* @param velocity the pathable velocity in pixels per millisecond.
|
||||
*/
|
||||
public function setVelocity (velocity :Number) :void
|
||||
{
|
||||
_vel = velocity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the velocity at which the pathable will need to travel
|
||||
* along this path such that it will arrive at the destination in
|
||||
* approximately the specified number of milliseconds. Efforts are
|
||||
* taken to get the pathable there as close to the desired time as
|
||||
* possible, but framerate variation may prevent it from arriving
|
||||
* exactly on time.
|
||||
*/
|
||||
public function setDuration (millis :int) :void
|
||||
{
|
||||
// if we have only zero or one nodes, we don't have enough
|
||||
// information to compute our velocity
|
||||
var ncount :int = _nodes.length;
|
||||
if (ncount < 2) {
|
||||
log.warning("Requested to set duration of bogus path " +
|
||||
"[path=" + this + ", duration=" + millis + "].");
|
||||
return;
|
||||
}
|
||||
|
||||
// compute the total distance along our path
|
||||
var distance :Number = 0;
|
||||
var start :PathNode = _nodes[0];
|
||||
for (var ii :int = 1; ii < ncount; ii++) {
|
||||
var end :PathNode = _nodes[ii];
|
||||
distance += MathUtil.distance(start.loc.x, start.loc.y, end.loc.x, end.loc.y);
|
||||
start = end;
|
||||
}
|
||||
|
||||
// set the velocity accordingly
|
||||
setVelocity(distance/millis);
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public function init (pable :Pathable, timestamp :int) :void
|
||||
{
|
||||
// give the pathable a chance to perform any starting antics
|
||||
pable.pathBeginning();
|
||||
|
||||
// if we have only one node then let the pathable know that we're
|
||||
// done straight away
|
||||
if (size() < 2) {
|
||||
// move the pathable to the location specified by the first
|
||||
// node (assuming we have a first node)
|
||||
if (size() == 1) {
|
||||
var node :PathNode = _nodes[0];
|
||||
pable.setLocation(node.loc.x, node.loc.y);
|
||||
}
|
||||
// and let the pathable know that we're done
|
||||
pable.pathCompleted(timestamp);
|
||||
return;
|
||||
}
|
||||
|
||||
// and an enumeration of the path nodes
|
||||
_niter = new ArrayIterator(_nodes);
|
||||
|
||||
// pretend like we were previously heading to our starting position
|
||||
_dest = getNextNode();
|
||||
|
||||
// begin traversing the path
|
||||
headToNextNode(pable, timestamp, timestamp);
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public function tick (pable :Pathable, timestamp :int) :Boolean
|
||||
{
|
||||
// figure out how far along this segment we should be
|
||||
var msecs :int = timestamp - _nodestamp;
|
||||
var travpix :Number = msecs * _vel;
|
||||
var pctdone :Number = travpix / _seglength;
|
||||
|
||||
// if we've moved beyond the end of the path, we need to adjust
|
||||
// the timestamp to determine how much time we used getting to the
|
||||
// end of this node, then move to the next one
|
||||
if (pctdone >= 1.0) {
|
||||
var used :int = int(_seglength / _vel);
|
||||
return headToNextNode(pable, _nodestamp + used, timestamp);
|
||||
}
|
||||
|
||||
// otherwise we position the pathable along the path
|
||||
var ox :Number = pable.getX();
|
||||
var oy :Number = pable.getY();
|
||||
var nx :Number = _src.loc.x + (_dest.loc.x - _src.loc.x) * pctdone;
|
||||
var ny :Number = _src.loc.y + (_dest.loc.y - _src.loc.y) * pctdone;
|
||||
|
||||
// Log.info("Moving pathable [msecs=" + msecs + ", pctdone=" + pctdone +
|
||||
// ", travpix=" + travpix + ", seglength=" + _seglength +
|
||||
// ", dx=" + (nx-ox) + ", dy=" + (ny-oy) + "].");
|
||||
|
||||
// only update the pathable's location if it actually moved
|
||||
if (ox != nx || oy != ny) {
|
||||
pable.setLocation(nx, ny);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public function fastForward (timeDelta :int) :void
|
||||
{
|
||||
_nodestamp += timeDelta;
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public function wasRemoved (pable :Pathable) :void
|
||||
{
|
||||
// nothing doing
|
||||
}
|
||||
|
||||
/**
|
||||
* Place the pathable moving along the path at the end of the previous
|
||||
* path node, face it appropriately for the next node, and start it on
|
||||
* its way. Returns whether the pathable position moved.
|
||||
*/
|
||||
protected function headToNextNode (pable :Pathable, startstamp :int, now :int) :Boolean
|
||||
{
|
||||
if (_niter == null) {
|
||||
throw new Error("headToNextNode() called before init()");
|
||||
}
|
||||
|
||||
// check to see if we've completed our path
|
||||
if (!_niter.hasNext()) {
|
||||
// move the pathable to the location of our last destination
|
||||
pable.setLocation(_dest.loc.x, _dest.loc.y);
|
||||
pable.pathCompleted(now);
|
||||
return true;
|
||||
}
|
||||
|
||||
// our previous destination is now our source
|
||||
_src = _dest;
|
||||
|
||||
// pop the next node off the path
|
||||
_dest = getNextNode();
|
||||
|
||||
// adjust the pathable's orientation
|
||||
if (_dest.dir != DirectionCodes.NONE) {
|
||||
pable.setOrientation(_dest.dir);
|
||||
}
|
||||
|
||||
// make a note of when we started traversing this node
|
||||
_nodestamp = startstamp;
|
||||
|
||||
// figure out the distance from source to destination
|
||||
_seglength = MathUtil.distance(_src.loc.x, _src.loc.y, _dest.loc.x, _dest.loc.y);
|
||||
|
||||
// if we're already there (the segment length is zero), we skip to
|
||||
// the next segment
|
||||
if (_seglength == 0) {
|
||||
return headToNextNode(pable, startstamp, now);
|
||||
}
|
||||
|
||||
// now update the pathable's position based on our progress thus far
|
||||
return tick(pable, now);
|
||||
}
|
||||
|
||||
public function toString () :String
|
||||
{
|
||||
return StringUtil.toString(_nodes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate the path with the path nodes that lead the pathable from
|
||||
* its starting position to the given destination coordinates
|
||||
* following the given list of screen coordinates.
|
||||
*/
|
||||
protected function createPath (points :Array) :void
|
||||
{
|
||||
var last :Point = null;
|
||||
var size :int = points.length;
|
||||
for (var ii :int = 0; ii < size; ii++) {
|
||||
var p :Point = points[ii];
|
||||
|
||||
var dir :int = (ii == 0) ? DirectionCodes.NORTH :
|
||||
DirectionUtil.getDirectionForPts(last, p);
|
||||
addNode(p.x, p.y, dir);
|
||||
last = p;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the next node in the path.
|
||||
*/
|
||||
protected function getNextNode () :PathNode
|
||||
{
|
||||
return PathNode(_niter.next());
|
||||
}
|
||||
|
||||
/** The nodes that make up the path. */
|
||||
protected var _nodes :Array = [];
|
||||
|
||||
/** We use this when moving along this path. */
|
||||
protected var _niter :Iterator;
|
||||
|
||||
/** When moving, the pathable's source path node. */
|
||||
protected var _src :PathNode;
|
||||
|
||||
/** When moving, the pathable's destination path node. */
|
||||
protected var _dest :PathNode;
|
||||
|
||||
/** The time at which we started traversing the current node. */
|
||||
protected var _nodestamp :int;
|
||||
|
||||
/** The length in pixels of the current path segment. */
|
||||
protected var _seglength :Number;
|
||||
|
||||
/** The path velocity in pixels per millisecond. */
|
||||
protected var _vel :Number = DEFAULT_VELOCITY;
|
||||
|
||||
/** When moving, the pathable position including fractional pixels. */
|
||||
protected var _movex :Number;
|
||||
protected var _movey :Number;
|
||||
|
||||
/** When moving, the distance to move on each axis per tick. */
|
||||
protected var _incx :Number;
|
||||
protected var _incy :Number;
|
||||
|
||||
/** The distance to move on the straight path line per tick. */
|
||||
protected var _fracx :Number;
|
||||
protected var _fracy :Number;
|
||||
|
||||
/** Default pathable velocity. */
|
||||
protected static const DEFAULT_VELOCITY :Number= 0.2;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://www.threerings.net/code/nenya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.media.util {
|
||||
|
||||
/**
|
||||
* A path is used to cause a {@link Pathable} to follow a particular path along the screen. The
|
||||
* {@link Pathable} is responsible for calling {@link #tick} on the path with reasonable frequency
|
||||
* (generally as a part of the frame tick. The path is responsible for updating the position of
|
||||
* the {@link Pathable} based on the time that has elapsed since the {@link Pathable} started down
|
||||
* the path.
|
||||
*
|
||||
* <p> The path should call the appropriate callbacks on the {@link Pathable} when appropriate
|
||||
* (e.g. {@link Pathable#pathBeginning}, {@link Pathable#pathCompleted}).
|
||||
*/
|
||||
public interface Path
|
||||
{
|
||||
/**
|
||||
* Called once to let the path prepare itself for the process of animating the supplied
|
||||
* pathable. Path users should also call {@link #tick} after {@link #init} with the same
|
||||
* initialization timestamp.
|
||||
*/
|
||||
function init (pable :Pathable, tickStamp :int) :void;
|
||||
|
||||
/**
|
||||
* Called to request that this path update the position of the specified pathable based on the
|
||||
* supplied timestamp information. A path should record its initial timestamp and determine
|
||||
* the progress of the pathable along the path based on the time elapsed since the pathable
|
||||
* began down the path.
|
||||
*
|
||||
* @param pable the pathable whose position should be updated.
|
||||
* @param tickStamp the timestamp associated with this frame.
|
||||
*
|
||||
* @return true if the pathable's position was updated, false if the path determined that the
|
||||
* pathable should not move at this time.
|
||||
*/
|
||||
function tick (pable :Pathable, tickStamp :int) :Boolean;
|
||||
|
||||
/**
|
||||
* This is called if the pathable is paused for some length of time and then unpaused. Paths
|
||||
* should adjust any time stamps they are maintaining internally by the delta so that time
|
||||
* maintains the illusion of flowing smoothly forward.
|
||||
*/
|
||||
function fastForward (timeDelta :int) :void;
|
||||
|
||||
/**
|
||||
* When a path is removed from a pathable, whether that is because the path was completed or
|
||||
* because it was replaced by another path, this method will be called to let the path know
|
||||
* that it is no longer associated with this pathable.
|
||||
*/
|
||||
function wasRemoved (pable :Pathable) :void;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://www.threerings.net/code/nenya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.media.util {
|
||||
|
||||
import flash.geom.Point;
|
||||
|
||||
/**
|
||||
* A path node is a single destination point in a {@link Path}.
|
||||
*/
|
||||
public class PathNode
|
||||
{
|
||||
/** The node coordinates in screen pixels. */
|
||||
public var loc :Point;
|
||||
|
||||
/** The direction to face while heading toward the node. */
|
||||
public var dir :int;
|
||||
|
||||
/**
|
||||
* Construct a path node object.
|
||||
*
|
||||
* @param x the node x-position.
|
||||
* @param y the node y-position.
|
||||
* @param dir the facing direction.
|
||||
*/
|
||||
public function PathNode (x :Number, y :Number, dir :int)
|
||||
{
|
||||
loc = new Point(x, y);
|
||||
this.dir = dir;
|
||||
}
|
||||
|
||||
public function toString () :String
|
||||
{
|
||||
return "[x=" + loc.x + ", y=" + loc.y + ", dir=" + dir + "]";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://www.threerings.net/code/nenya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.media.util {
|
||||
|
||||
import flash.geom.Rectangle;
|
||||
|
||||
/**
|
||||
* Used in conjunction with a {@link Path}.
|
||||
*/
|
||||
public interface Pathable
|
||||
{
|
||||
/**
|
||||
* Returns the pathable's current x coordinate.
|
||||
*/
|
||||
function getX () :Number;
|
||||
|
||||
/**
|
||||
* Returns the pathable's current y coordinate.
|
||||
*/
|
||||
function getY () :Number;
|
||||
|
||||
/**
|
||||
* Updates the pathable's current coordinates.
|
||||
*/
|
||||
function setLocation (x :Number, y :Number) :void;
|
||||
|
||||
/**
|
||||
* Will be called by a path when it moves the pathable in the
|
||||
* specified direction. Pathables that wish to face in the direction
|
||||
* they are moving can take advantage of this callback.
|
||||
*
|
||||
* @see DirectionCodes
|
||||
*/
|
||||
function setOrientation (orient :int) :void;
|
||||
|
||||
/**
|
||||
* Should return the orientation of the pathable, or {@link
|
||||
* DirectionCodes#NONE} if the pathable does not support orientation.
|
||||
*/
|
||||
function getOrientation () :int;
|
||||
|
||||
/**
|
||||
* Called by a path when this pathable is made to start along a path.
|
||||
*/
|
||||
function pathBeginning () :void;
|
||||
|
||||
/**
|
||||
* Called by a path when this pathable finishes moving along its path.
|
||||
*/
|
||||
function pathCompleted (timestamp :int) :void;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://www.threerings.net/code/nenya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.media.util {
|
||||
|
||||
/**
|
||||
* Provides traversibility information when computing paths.
|
||||
*/
|
||||
public interface TraversalPred
|
||||
{
|
||||
/**
|
||||
* Requests to know if the specified traverser (which was provided in the call to
|
||||
* {@link #getPath(TraversalPred,Object,int,int,int,int,int,boolean)}) can traverse the
|
||||
* specified tile coordinate.
|
||||
*/
|
||||
function canTraverse (traverser :Object, x :int, y :int) :Boolean;
|
||||
}
|
||||
}
|
||||
@@ -37,7 +37,7 @@ import flash.events.MouseEvent;
|
||||
|
||||
import mx.core.ClassFactory;
|
||||
|
||||
import as3isolib.display.primitive.IsoBox;
|
||||
import as3isolib.core.IsoDisplayObject;
|
||||
import as3isolib.geom.Pt;
|
||||
import as3isolib.geom.IsoMath;
|
||||
import as3isolib.display.scene.IsoScene;
|
||||
@@ -59,6 +59,11 @@ import com.threerings.media.tile.Colorizer;
|
||||
import com.threerings.media.tile.NoSuchTileSetError;
|
||||
import com.threerings.media.tile.TileSet;
|
||||
import com.threerings.media.tile.TileUtil;
|
||||
import com.threerings.media.util.AStarPathSearch;
|
||||
import com.threerings.media.util.AStarPathSearch_Stepper;
|
||||
import com.threerings.media.util.LineSegmentPath;
|
||||
import com.threerings.media.util.Path;
|
||||
import com.threerings.media.util.TraversalPred;
|
||||
import com.threerings.miso.client.MisoMetricsTransformation;
|
||||
import com.threerings.miso.client.PrioritizedSceneLayoutRenderer;
|
||||
import com.threerings.miso.data.MisoSceneModel;
|
||||
@@ -69,7 +74,7 @@ import com.threerings.miso.util.ObjectSet;
|
||||
import com.threerings.miso.util.MisoSceneMetrics;
|
||||
|
||||
public class MisoScenePanel extends Sprite
|
||||
implements PlaceView
|
||||
implements PlaceView, TraversalPred
|
||||
{
|
||||
private var log :Log = Log.getLog(MisoScenePanel);
|
||||
|
||||
@@ -84,6 +89,8 @@ public class MisoScenePanel extends Sprite
|
||||
|
||||
_isoView = new IsoView();
|
||||
_isoView.setSize(DEF_WIDTH, DEF_HEIGHT);
|
||||
_vbounds = new Rectangle(0, 0,
|
||||
_isoView.size.x, _isoView.size.y);
|
||||
|
||||
_isoView.addEventListener(MouseEvent.CLICK, onClick);
|
||||
|
||||
@@ -279,6 +286,7 @@ public class MisoScenePanel extends Sprite
|
||||
// First, we add in our new blocks...
|
||||
for each (var newBlock :SceneBlock in _readyBlocks.toArray()) {
|
||||
newBlock.render();
|
||||
newBlock.addToCovered(_covered);
|
||||
_blocks.put(newBlock.getKey(), newBlock);
|
||||
}
|
||||
_readyBlocks = Sets.newSetOf(SceneBlock);
|
||||
@@ -310,7 +318,7 @@ public class MisoScenePanel extends Sprite
|
||||
}
|
||||
}
|
||||
|
||||
trace("Scene Block Resolution took: " + (getTimer() - _resStartTime) + "ms");
|
||||
log.info("Scene Block Resolution took: " + (getTimer() - _resStartTime) + "ms");
|
||||
|
||||
// Let's setup some prefetching...
|
||||
blocks.forEach(function(blockKey :int) :void {
|
||||
@@ -363,8 +371,10 @@ public class MisoScenePanel extends Sprite
|
||||
public function canTraverse (traverser :Object, tx :int, ty :int) :Boolean
|
||||
{
|
||||
var block :SceneBlock = _blocks.get(SceneBlock.getBlockKey(tx, ty));
|
||||
return (block == null) ? canTraverseUnresolved(traverser, tx, ty) :
|
||||
block.canTraverse(traverser, tx, ty);
|
||||
var baseTraversable :Boolean = (block == null) ? canTraverseUnresolved(traverser, tx, ty) :
|
||||
block.canTraverseBase(traverser, tx, ty);
|
||||
|
||||
return baseTraversable && !_covered.contains(StringUtil.toCoordsString(tx, ty));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -384,10 +394,37 @@ public class MisoScenePanel extends Sprite
|
||||
* sprite in a straight line to its final destination. This is generally only useful if the
|
||||
* the path goes "off screen".
|
||||
*/
|
||||
public function getPath (sprite :Sprite, x :int, y :int, loose :Boolean) :Object // TODO Path
|
||||
public function getPath (sprite :IsoDisplayObject, x :int, y :int, loose :Boolean) :Path
|
||||
{
|
||||
// TODO Path
|
||||
return null;
|
||||
// sanity check
|
||||
if (sprite == null) {
|
||||
throw new Error("Can't get path for null sprite [x=" + x + ", y=" + y + ".");
|
||||
}
|
||||
|
||||
// compute our longest path from the screen size
|
||||
var longestPath :int = 3 * (width / _metrics.tilewid);
|
||||
|
||||
// get a reasonable tile path through the scene
|
||||
var start :int = getTimer();
|
||||
var search :AStarPathSearch = new AStarPathSearch(this, new AStarPathSearch_Stepper());
|
||||
var points :Array = search.getPath(sprite, longestPath, int(Math.round(sprite.x)),
|
||||
int(Math.round(sprite.y)), x, y, loose);
|
||||
|
||||
// Replace the starting point with the Number values rather than the rounded version...
|
||||
points[0] = new Point(sprite.x, sprite.y);
|
||||
|
||||
var duration :int = getTimer() - start;
|
||||
|
||||
// sanity check the number of nodes searched so that we can keep an eye out for bogosity
|
||||
if (duration > 500) {
|
||||
log.warning("Considered a lot of nodes for path from " +
|
||||
StringUtil.toCoordsString(sprite.x, sprite.y) + " to " +
|
||||
StringUtil.toCoordsString(x, y) +
|
||||
" [duration=" + duration + "].");
|
||||
}
|
||||
|
||||
// construct a path object to guide the sprite on its merry way
|
||||
return (points == null) ? null : LineSegmentPath.createWithList(points);
|
||||
}
|
||||
|
||||
protected var _model :MisoSceneModel;
|
||||
@@ -433,6 +470,10 @@ public class MisoScenePanel extends Sprite
|
||||
/** If any block happens to resolve should we currently skip calling completion. */
|
||||
protected var _skipComplete :Boolean
|
||||
|
||||
protected var _vbounds :Rectangle;
|
||||
|
||||
protected var _covered :Set = Sets.newSetOf(String);
|
||||
|
||||
protected const DEF_WIDTH :int = 985;
|
||||
protected const DEF_HEIGHT :int = 560;
|
||||
|
||||
|
||||
@@ -8,7 +8,10 @@ import as3isolib.display.IsoView;
|
||||
|
||||
import com.threerings.util.Log;
|
||||
import com.threerings.util.MathUtil;
|
||||
import com.threerings.util.Set;
|
||||
import com.threerings.util.StringUtil;
|
||||
|
||||
import com.threerings.media.tile.BaseTile;
|
||||
import com.threerings.media.tile.Colorizer;
|
||||
import com.threerings.media.tile.NoSuchTileSetError;
|
||||
import com.threerings.media.tile.Tile;
|
||||
@@ -25,7 +28,7 @@ import com.threerings.miso.util.ObjectSet;
|
||||
|
||||
public class SceneBlock
|
||||
{
|
||||
private var log :Log = Log.getLog(MisoScenePanel);
|
||||
private var log :Log = Log.getLog(SceneBlock);
|
||||
|
||||
public static const BLOCK_SIZE :int = 4;
|
||||
|
||||
@@ -149,6 +152,17 @@ public class SceneBlock
|
||||
}
|
||||
}
|
||||
|
||||
public function addToCovered (covered :Set) :void
|
||||
{
|
||||
for each (var sprite :ObjectTileIsoSprite in _objSprites) {
|
||||
for (var xx :int = sprite.x; xx < sprite.x + sprite.width; xx++) {
|
||||
for (var yy :int = sprite.y; yy < sprite.y + sprite.length; yy++) {
|
||||
covered.add(StringUtil.toCoordsString(xx, yy));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes our tiles' sprites from the scenes.
|
||||
*/
|
||||
@@ -164,22 +178,29 @@ public class SceneBlock
|
||||
protected function createBaseSprite (tileSet :TileSet, x :int, y :int, tileId :int,
|
||||
panel :MisoScenePanel) :void
|
||||
{
|
||||
var fringeTile :Tile = panel.computeFringeTile(x, y);
|
||||
var fringeTile :BaseTile = panel.computeFringeTile(x, y);
|
||||
|
||||
var tile :Tile = tileSet.getTile(TileUtil.getTileIndex(tileId));
|
||||
var tile :BaseTile = BaseTile(tileSet.getTile(TileUtil.getTileIndex(tileId)));
|
||||
|
||||
var maybeLoaded :Function = function (loaded :Tile) :void {
|
||||
if (tile.getImage() != null && (fringeTile == null || fringeTile.getImage() != null)) {
|
||||
_baseScene.addChild(new BaseTileIsoSprite(x, y, tileId, tile, _metrics,
|
||||
fringeTile));
|
||||
noteTileLoaded();
|
||||
}
|
||||
};
|
||||
var bx :int = getBlockX(_key);
|
||||
var by :int = getBlockY(_key);
|
||||
_passable[(y - by) * BLOCK_SIZE + (x - bx)] =
|
||||
tile.isPassable() && (fringeTile == null || fringeTile.isPassable());
|
||||
|
||||
if (tile.getImage() != null && (fringeTile == null || fringeTile.getImage() != null)) {
|
||||
_baseScene.addChild(new BaseTileIsoSprite(x, y, tileId, tile, _metrics, fringeTile));
|
||||
} else {
|
||||
noteTileToLoad();
|
||||
|
||||
var maybeLoaded :Function = function (loaded :Tile) :void {
|
||||
if (tile.getImage() != null &&
|
||||
(fringeTile == null || fringeTile.getImage() != null)) {
|
||||
_baseScene.addChild(new BaseTileIsoSprite(x, y, tileId, tile, _metrics,
|
||||
fringeTile));
|
||||
noteTileLoaded();
|
||||
}
|
||||
};
|
||||
|
||||
if (tile.getImage() == null) {
|
||||
tile.notifyOnLoad(maybeLoaded);
|
||||
}
|
||||
@@ -209,10 +230,16 @@ public class SceneBlock
|
||||
}
|
||||
}
|
||||
|
||||
public function canTraverse (traverser :Object, tx :int, ty :int) :Boolean
|
||||
/**
|
||||
* Returns whether the base & fringe tiles are traversable here.
|
||||
*/
|
||||
public function canTraverseBase (traverser :Object, tx :int, ty :int) :Boolean
|
||||
{
|
||||
// TODO Path
|
||||
return true;
|
||||
// Only handles the base tiles since the objects cross multiple scene blocks...
|
||||
var bx :int = getBlockX(_key);
|
||||
var by :int = getBlockY(_key);
|
||||
|
||||
return _passable[(ty - by) * BLOCK_SIZE + (tx - bx)];
|
||||
}
|
||||
|
||||
protected function noteTileToLoad () :void
|
||||
@@ -234,6 +261,8 @@ public class SceneBlock
|
||||
}
|
||||
}
|
||||
|
||||
protected var _passable :Array = new Array(BLOCK_SIZE * BLOCK_SIZE);
|
||||
|
||||
protected var _baseScene :IsoScene;
|
||||
protected var _objSprites :Array;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user