Package com.threerings.stage.util

Source Code of com.threerings.stage.util.StageSceneUtil

//
// $Id$
//
// Vilya library - tools for developing networked games
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/vilya/
//
// 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.stage.util;

import java.util.Comparator;
import java.util.List;

import java.awt.Point;
import java.awt.Rectangle;

import com.google.common.collect.Lists;

import com.samskivert.util.SortableArrayList;

import com.threerings.util.DirectionCodes;
import com.threerings.util.DirectionUtil;

import com.threerings.media.tile.BaseSizableTileSet;
import com.threerings.media.tile.TileManager;
import com.threerings.media.tile.TileUtil;
import com.threerings.media.tile.TrimmedObjectTileSet;
import com.threerings.media.util.AStarPathUtil;
import com.threerings.media.util.MathUtil;

import com.threerings.miso.MisoConfig;
import com.threerings.miso.data.ObjectInfo;
import com.threerings.miso.tile.BaseTileSet;
import com.threerings.miso.util.MisoSceneMetrics;
import com.threerings.miso.util.MisoUtil;
import com.threerings.miso.util.ObjectSet;

import com.threerings.whirled.spot.data.Cluster;
import com.threerings.whirled.spot.data.SceneLocation;

import com.threerings.stage.data.StageLocation;
import com.threerings.stage.data.StageMisoSceneModel;
import com.threerings.stage.data.StageSceneModel;

import static com.threerings.stage.Log.log;

/**
* Provides scene related utility functions.
*/
public class StageSceneUtil
{
    /**
     * Value to pass to {@link #locationForObject(TileManager, ObjectInfo, int)} and
     * {@link #locationForObject(TileManager, int, int, int, int)}for orientation to indicate it
     * should use the object's orientation.
     */
    public static final int OBJECT_ORIENTATION = -1;

    /**
     * Returns the scene metrics we use to do our calculations.
     */
    public static MisoSceneMetrics getMetrics ()
    {
        return _metrics;
    }

    /**
     * Does the necessary jiggery pokery to figure out where the specified object's associated
     * location is.
     */
    public static StageLocation locationForObject (TileManager tilemgr, ObjectInfo info)
    {
        return locationForObject(tilemgr, info, -1);
    }

    /**
     * Does the necessary jiggery pokery to figure out where the specified object's associated
     * location is.
     *
     * @param orient - the orientation to use in the returned location, or
     * {@link #OBJECT_ORIENTATION} if the object's orientation should be used
     */
    public static StageLocation locationForObject (TileManager tilemgr, ObjectInfo info,
        int orient)
    {
        return locationForObject(tilemgr, info.tileId, info.x, info.y, orient);
    }

    /**
     * Does the necessary jiggery pokery to figure out where the specified object's associated
     * location is.
     *
     * @param tilemgr a tile manager that can be used to look up the tile information.
     * @param tileId the fully qualified tile id of the object tile.
     * @param tx the object's x tile coordinate.
     * @param ty the object's y tile coordinate.
     */
    public static StageLocation locationForObject (TileManager tilemgr, int tileId, int tx, int ty)
    {
        return locationForObject(tilemgr, tileId, tx, ty, -1);
    }

    /**
     * Does the necessary jiggery pokery to figure out where the specified object's associated
     * location is.
     *
     * @param tilemgr a tile manager that can be used to look up the tile information.
     * @param tileId the fully qualified tile id of the object tile.
     * @param tx the object's x tile coordinate.
     * @param ty the object's y tile coordinate.
     * @param orient - the orientation to use in the returned location, or
     * {@link #OBJECT_ORIENTATION} if the tile's orientation should be used
     */
    public static StageLocation locationForObject (TileManager tilemgr, int tileId, int tx,
        int ty, int orient)
    {
        try {
            int tsid = TileUtil.getTileSetId(tileId);
            int tidx = TileUtil.getTileIndex(tileId);
            TrimmedObjectTileSet tset = (TrimmedObjectTileSet)tilemgr.getTileSet(tsid);
            if (tset == null || (orient == OBJECT_ORIENTATION && tset.getSpotOrient(tidx) < 0)) {
                return null;
            }
            if (orient == OBJECT_ORIENTATION) {
                orient = tset.getSpotOrient(tidx);
            }

            Point opos = MisoUtil.tilePlusFineToFull(_metrics, tx, ty, tset.getXSpot(tidx),
                tset.getYSpot(tidx), new Point());

//             Log.info("Computed location [set=" + tset.getName() +
//                      ", tidx=" + tidx + ", tx=" + tx + ", ty=" + ty +
//                      ", sx=" + tset.getXSpot(tidx) +
//                      ", sy=" + tset.getYSpot(tidx) +
//                      ", lx=" + opos.x + ", ly=" + opos.y +
//                      ", fg=" + _metrics.finegran + "].");
            return new StageLocation(opos.x, opos.y, (byte)orient);

        } catch (Exception e) {
            log.warning("Unable to look up object tile for scene object", "tileId", tileId, e);
        }
        return null;
    }

    /**
     * Converts full coordinates to Cartesian coordinates.
     */
    public static void locationToCoords (int lx, int ly, Point coords)
    {
        int tx = MisoUtil.fullToTile(lx), fx = MisoUtil.fullToFine(lx);
        int ty = MisoUtil.fullToTile(ly), fy = MisoUtil.fullToFine(ly);
        coords.x = tx*_metrics.finegran+fx;
        coords.y = ty*_metrics.finegran+fy;
    }

    /**
     * Converts Cartesian coordinates back to full coordinates.
     */
    public static void coordsToLocation (int cx, int cy, Point loc)
    {
        loc.x = MisoUtil.toFull(cx/_metrics.finegran, cx%_metrics.finegran);
        loc.y = MisoUtil.toFull(cy/_metrics.finegran, cy%_metrics.finegran);
    }

    /**
     * Returns the footprint, in absolute tile coordinates, for the
     * specified object with origin as specified.
     */
    public static Rectangle getObjectFootprint (
        TileManager tilemgr, int tileId, int ox, int oy)
    {
        Rectangle foot = new Rectangle();
        getObjectFootprint(tilemgr, tileId, ox, oy, foot);
        return foot;
    }

    /**
     * Fills in the footprint, in absolute tile coordinates, for the
     * specified object with origin as specified.
     *
     * @return true if the object was successfully looked up and the
     * footprint filled in, false if an error occurred trying to look up
     * the associated object tile.
     */
    public static boolean getObjectFootprint (
        TileManager tilemgr, int tileId, int ox, int oy, Rectangle foot)
    {
        try {
            int tsid = TileUtil.getTileSetId(tileId);
            int tidx = TileUtil.getTileIndex(tileId);
            BaseSizableTileSet tset = (BaseSizableTileSet)tilemgr.getTileSet(tsid);
            if (tset == null) {
                return false;
            }

            int bwidth = tset.getBaseWidth(tidx);
            int bheight = tset.getBaseHeight(tidx);
            foot.setBounds(ox - bwidth + 1, oy - bheight + 1, bwidth, bheight);
            return true;

        } catch (Exception e) {
            log.warning("Unable to look up object tile for scene object", "tileId", tileId, e);
            return false;
        }
    }

    /**
     * Looks up the base tile set for the specified fully qualified tile
     * identifier and returns true if the associated tile is passable.
     */
    public static boolean isPassable (TileManager tilemgr, int tileId)
    {
        // non-existent tiles are not passable
        if (tileId <= 0) {
            return false;
        }

        try {
            int tsid = TileUtil.getTileSetId(tileId);
            int tidx = TileUtil.getTileIndex(tileId);
            BaseTileSet tset = (BaseTileSet)tilemgr.getTileSet(tsid);
            return tset.getPassability()[tidx];

        } catch (Exception e) {
            log.warning("Unable to look up base tile", "tileId", tileId, e);
            return true;
        }
    }

    /**
     * Computes a list of the valid locations in this cluster.
     */
    public static List<SceneLocation> getClusterLocs (Cluster cluster)
    {
        List<SceneLocation> list = Lists.newArrayList();

        // convert our tile coordinates into a cartesian coordinate system
        // with units equal to one fine coordinate in size
        int fx = cluster.x*_metrics.finegran+1,
            fy = cluster.y*_metrics.finegran+1;
        int fwid = cluster.width*_metrics.finegran-2,
            fhei = cluster.height*_metrics.finegran-2;
        int cx = fx + fwid/2, cy = fy + fhei/2;

        // if it's a 1x1 cluster, return one location in the center of the
        // cluster
        if (cluster.width == 1) {
            StageLocation loc = new StageLocation(
                MisoUtil.toFull(cluster.x, 2), MisoUtil.toFull(cluster.y, 2),
                (byte)DirectionCodes.SOUTHWEST);
            list.add(new SceneLocation(loc, 0));
            return list;
        }

        double radius = (double)fwid/2;
        int clidx = cluster.width-2;
        if (clidx >= CLUSTER_METRICS.length/2 || clidx < 0) {
            log.warning("Requested locs from invalid cluster " + cluster + ".", new Exception());
            return list;
        }

        for (double angle = CLUSTER_METRICS[clidx*2]; angle < Math.PI*2;
             angle += CLUSTER_METRICS[clidx*2+1]) {
            int sx = cx + (int)Math.round(Math.cos(angle) * radius);
            int sy = cy + (int)Math.round(Math.sin(angle) * radius);

            // obtain the orientation facing toward the center
            int orient = 2*(int)(Math.round(angle/(Math.PI/4))%8);
            orient = DirectionUtil.rotateCW(DirectionCodes.SOUTH, orient);
            orient = DirectionUtil.getOpposite(orient);

            // convert them back to full coordinates for the location
            int tx = MathUtil.floorDiv(sx, _metrics.finegran);
            sx = MisoUtil.toFull(tx, sx-(tx*_metrics.finegran));
            int ty = MathUtil.floorDiv(sy, _metrics.finegran);
            sy = MisoUtil.toFull(ty, sy-(ty*_metrics.finegran));
            StageLocation loc = new StageLocation(sx, sy, (byte) orient);
            list.add(new SceneLocation(loc, 0));
        }

        return list;
    }

//     /**
//      * Returns true if this user is available to be clustered with.
//      */
//     public static boolean isClusterable (YoOccupantInfo info)
//     {
//         switch (info.activity) {
//         case ActivityCodes.NONE:
//         case ActivityCodes.READING:
//         case ActivityCodes.IDLE:
//         case ActivityCodes.DISCONNECTED:
//             return true;
//         default:
//             return false;
//         }
//     }

    /**
     * Locates a spot to stand near the supplied rectangular footprint.
     * First a spot will be sought in a tile immediately next to the
     * footprint, then one tile removed, then two, up to the maximum
     * distance specified by <code>dist</code>.
     *
     * @param foot the tile coordinate footprint around which we are
     * attempting to stand.
     * @param dist the maximum number of tiles away from the footprint to
     * search before giving up.
     * @param pred a predicate that will be used to determine whether a
     * particular spot can be stood upon (we're hijacking the meaning of
     * "traverse" in this case, but the interface is otherwise so nice).
     * @param traverser the object that will be passed to the traversal
     * predicate.
     * @param nearto a point (in tile coordinates) which will be used to
     * select from among the valid standing spots, the one nearest to the
     * supplied point will be returned.
     * @param orient if not {@link DirectionCodes#NONE} this orientation
     * will be used to override the "natural" orientation of the spot
     * which is facing toward the footprint.
     *
     * @return the closest spot to the
     */
    public static StageLocation findStandingSpot (
        Rectangle foot, int dist, AStarPathUtil.TraversalPred pred,
        Object traverser, final Point nearto, int orient)
    {
        // generate a list of the tile coordinates of all squares around
        // this footprint
        SortableArrayList<StageLocation> spots = new SortableArrayList<StageLocation>();

        for (int dd = 1; dd <= dist; dd++) {
            int yy1 = foot.y-dd, yy2 = foot.y+foot.height+dd-1;
            int xx1 = foot.x-dd, xx2 = foot.x+foot.width+dd-1;

            // get the corners
            spots.add(
                new StageLocation(xx1, yy1, (byte)DirectionCodes.SOUTHWEST));
            spots.add(
                new StageLocation(xx1, yy2, (byte)DirectionCodes.SOUTHEAST));
            spots.add(
                new StageLocation(xx2, yy1, (byte)DirectionCodes.NORTHWEST));
            spots.add(
                new StageLocation(xx2, yy2, (byte)DirectionCodes.NORTHEAST));

            // then the sides
            for (int xx = xx1+1; xx < xx2; xx++) {
                spots.add(
                    new StageLocation(xx, yy1, (byte)DirectionCodes.WEST));
                spots.add(
                    new StageLocation(xx, yy2, (byte)DirectionCodes.EAST));
            }
            for (int yy = yy1+1; yy < yy2; yy++) {
                spots.add(
                    new StageLocation(xx1, yy, (byte)DirectionCodes.SOUTH));
                spots.add(
                    new StageLocation(xx2, yy, (byte)DirectionCodes.NORTH));
            }

            // sort them in order of closeness to the players current
            // coordinate
            spots.sort(new Comparator<StageLocation>() {
                public int compare (StageLocation o1, StageLocation o2) {
                    return dist(o1) - dist(o2);
                }
                private final int dist (StageLocation l) {
                    return Math.round(100*MathUtil.distance(
                                          l.x, l.y, nearto.x, nearto.y));
                }
            });

            // return the first spot that can be "traversed" which we're
            // taking to mean "stood upon"
            for (int ii = 0, ll = spots.size(); ii < ll; ii++) {
                StageLocation loc = spots.get(ii);
                if (pred.canTraverse(traverser, loc.x, loc.y)) {
                    // convert to full coordinates
                    loc.x = MisoUtil.toFull(loc.x, 2);
                    loc.y = MisoUtil.toFull(loc.y, 2);

                    // see if we need to override the orientation
                    if (DirectionCodes.NONE != orient) {
                        loc.orient = (byte) orient;
                    }
                    return loc;
                }
            }

            // clear this list and try one further out
            spots.clear();
        }

        return null;
    }

    /**
     * Returns an array of the objects intersected by the supplied tile
     * coordinate rectangle.
     */
    public static ObjectInfo[] getIntersectedObjects (
        TileManager tmgr, StageSceneModel model, Rectangle rect)
    {
        // first get all objects whose origin is in an expanded version of
        // our intersection rect, any object that is *so* large that its
        // origin falls outside of this rectangle but that still
        // intersects this rectangle can go to hell; it's either this or
        // we iterate over every object in the whole goddamned scene which
        // is so hairily inefficient i can't even bear to contemplate it
        ObjectSet objs = new ObjectSet();
        Rectangle orect = new Rectangle(rect);
        orect.grow(MAX_OBJECT_SIZE, MAX_OBJECT_SIZE);
        StageMisoSceneModel mmodel = StageMisoSceneModel.getSceneModel(model);
        mmodel.getObjects(orect, objs);

        // now prune from this set any and all objects that don't actually
        // overlap the specified rectangle
        Rectangle foot = new Rectangle();
        for (int ii = 0; ii < objs.size(); ii++) {
            ObjectInfo info = objs.get(ii);
            if (getObjectFootprint(tmgr, info.tileId, info.x, info.y, foot)) {
                if (!foot.intersects(rect)) {
                    objs.remove(ii--);
                }
            } else {
                log.warning("Unknown potentially intersecting object?! " +
                            "[scene=" + model.name + " (" + model.sceneId +
                            "), info=" + info + "].");
            }
        }

        return objs.toArray();
    }

    /** Our default scene metrics. */
    protected static MisoSceneMetrics _metrics = MisoConfig.getSceneMetrics();

    /** Contains the starting offset from zero radians for the first
     * occupant and the radial distance between occupants. */
    protected static final double[] CLUSTER_METRICS = {
        Math.PI/4,  Math.PI/2, // 2x
        Math.PI/4,  Math.PI/2, // 3x
        0,          Math.PI/4, // 4x
        Math.PI/12, Math.PI/6, // 5x
        0,          Math.PI/8, // 6x
        Math.PI/24, Math.PI/12, // 7x
    };

    /** The maximum footprint width or height for which we will account in
     * {@link #getIntersectedObjects}. */
    protected static final int MAX_OBJECT_SIZE = 15;
}
TOP

Related Classes of com.threerings.stage.util.StageSceneUtil

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.