diff --git a/src/java/com/threerings/whirled/spot/client/SpotService.java b/src/java/com/threerings/whirled/spot/client/SpotService.java
index 17ba1698e..d647bda56 100644
--- a/src/java/com/threerings/whirled/spot/client/SpotService.java
+++ b/src/java/com/threerings/whirled/spot/client/SpotService.java
@@ -1,5 +1,5 @@
//
-// $Id: SpotService.java,v 1.12 2003/02/12 07:23:31 mdb Exp $
+// $Id: SpotService.java,v 1.13 2003/02/13 21:55:22 mdb Exp $
package com.threerings.whirled.spot.client;
@@ -44,11 +44,11 @@ public interface SpotService extends InvocationService
* location.
*
* @param loc the location to which to move.
- * @param cluster if zero, a new cluster will be created and assigned
- * to the calling user; if -1, the calling user will be removed from
- * any cluster they currently occupy and not made to occupy a new
- * cluster; if the bodyOid of another user, the calling user will be
- * made to join the target user's cluster.
+ * @param cluster if -1, the calling user will be removed from any
+ * cluster they currently occupy and not made to occupy a new cluster;
+ * if the bodyOid of another user, the calling user will be made to
+ * join the target user's cluster, or create a cluster with the target
+ * user if they are not already in one.
*/
public void changeLoc (Client client, Location loc, int cluster,
ChangeLocListener listener);
diff --git a/src/java/com/threerings/whirled/spot/data/Cluster.java b/src/java/com/threerings/whirled/spot/data/Cluster.java
new file mode 100644
index 000000000..6bc07cab0
--- /dev/null
+++ b/src/java/com/threerings/whirled/spot/data/Cluster.java
@@ -0,0 +1,38 @@
+//
+// $Id: Cluster.java,v 1.1 2003/02/13 21:55:22 mdb Exp $
+
+package com.threerings.whirled.spot.data;
+
+import com.threerings.presents.dobj.DSet;
+
+/**
+ * Contains information on clusters.
+ */
+public class Cluster
+ implements DSet.Entry
+{
+ /** A unique identifier for this cluster (also the distributed object
+ * id of the cluster chat object). */
+ public int clusterOid;
+
+ /** The x-coordinate of the cluster in the scene. */
+ public int x;
+
+ /** The y-coordinate of the cluster in the scene. */
+ public int y;
+
+ /** The number of occupants in this cluster. */
+ public int occupants;
+
+ // documentation inherited
+ public Comparable getKey ()
+ {
+ if (_key == null) {
+ _key = new Integer(clusterOid);
+ }
+ return _key;
+ }
+
+ /** Used for {@link #geyKey}. */
+ protected transient Integer _key;
+}
diff --git a/src/java/com/threerings/whirled/spot/data/SpotSceneObject.dobj b/src/java/com/threerings/whirled/spot/data/SpotSceneObject.dobj
index 637d2fbc6..374a3e3f5 100644
--- a/src/java/com/threerings/whirled/spot/data/SpotSceneObject.dobj
+++ b/src/java/com/threerings/whirled/spot/data/SpotSceneObject.dobj
@@ -1,5 +1,5 @@
//
-// $Id: SpotSceneObject.dobj,v 1.1 2003/02/12 07:23:31 mdb Exp $
+// $Id: SpotSceneObject.dobj,v 1.2 2003/02/13 21:55:22 mdb Exp $
package com.threerings.whirled.spot.data;
@@ -14,4 +14,7 @@ public class SpotSceneObject extends SceneObject
/** A distributed set containing {@link SceneLocation} records for all
* occupants of this scene. */
public DSet occupantLocs = new DSet();
+
+ /** Contains information on all {@link Cluster}s in this scene. */
+ public DSet clusters = new DSet();
}
diff --git a/src/java/com/threerings/whirled/spot/data/SpotSceneObject.java b/src/java/com/threerings/whirled/spot/data/SpotSceneObject.java
index 81a5f4e9e..c5247a18f 100644
--- a/src/java/com/threerings/whirled/spot/data/SpotSceneObject.java
+++ b/src/java/com/threerings/whirled/spot/data/SpotSceneObject.java
@@ -1,5 +1,5 @@
//
-// $Id: SpotSceneObject.java,v 1.1 2003/02/12 07:23:31 mdb Exp $
+// $Id: SpotSceneObject.java,v 1.2 2003/02/13 21:55:22 mdb Exp $
package com.threerings.whirled.spot.data;
@@ -14,10 +14,16 @@ public class SpotSceneObject extends SceneObject
/** The field name of the occupantLocs field. */
public static final String OCCUPANT_LOCS = "occupantLocs";
+ /** The field name of the clusters field. */
+ public static final String CLUSTERS = "clusters";
+
/** A distributed set containing {@link SceneLocation} records for all
* occupants of this scene. */
public DSet occupantLocs = new DSet();
+ /** Contains information on all {@link Cluster}s in this scene. */
+ public DSet clusters = new DSet();
+
/**
* Requests that the specified entry be added to the
* occupantLocs set. The set will not change until the event is
@@ -63,4 +69,50 @@ public class SpotSceneObject extends SceneObject
this.occupantLocs = occupantLocs;
requestAttributeChange(OCCUPANT_LOCS, occupantLocs);
}
+
+ /**
+ * Requests that the specified entry be added to the
+ * clusters set. The set will not change until the event is
+ * actually propagated through the system.
+ */
+ public void addToClusters (DSet.Entry elem)
+ {
+ requestEntryAdd(CLUSTERS, elem);
+ }
+
+ /**
+ * Requests that the entry matching the supplied key be removed from
+ * the clusters set. The set will not change until the
+ * event is actually propagated through the system.
+ */
+ public void removeFromClusters (Object key)
+ {
+ requestEntryRemove(CLUSTERS, key);
+ }
+
+ /**
+ * Requests that the specified entry be updated in the
+ * clusters set. The set will not change until the event is
+ * actually propagated through the system.
+ */
+ public void updateClusters (DSet.Entry elem)
+ {
+ requestEntryUpdate(CLUSTERS, elem);
+ }
+
+ /**
+ * Requests that the clusters field be set to the
+ * specified value. Generally one only adds, updates and removes
+ * entries of a distributed set, but certain situations call for a
+ * complete replacement of the set value. The local value will be
+ * updated immediately and an event will be propagated through the
+ * system to notify all listeners that the attribute did
+ * change. Proxied copies of this object (on clients) will apply the
+ * value change when they received the attribute changed notification.
+ */
+ public void setClusters (DSet clusters)
+ {
+ this.clusters = clusters;
+ requestAttributeChange(CLUSTERS, clusters);
+ }
}
diff --git a/src/java/com/threerings/whirled/spot/server/SpotSceneManager.java b/src/java/com/threerings/whirled/spot/server/SpotSceneManager.java
index d81f8a60a..039de5e3a 100644
--- a/src/java/com/threerings/whirled/spot/server/SpotSceneManager.java
+++ b/src/java/com/threerings/whirled/spot/server/SpotSceneManager.java
@@ -1,8 +1,9 @@
//
-// $Id: SpotSceneManager.java,v 1.25 2003/02/12 07:23:31 mdb Exp $
+// $Id: SpotSceneManager.java,v 1.26 2003/02/13 21:55:22 mdb Exp $
package com.threerings.whirled.spot.server;
+import java.awt.Point;
import java.util.Iterator;
import com.samskivert.util.ArrayIntSet;
@@ -22,6 +23,7 @@ import com.threerings.crowd.server.CrowdServer;
import com.threerings.whirled.server.SceneManager;
import com.threerings.whirled.spot.Log;
+import com.threerings.whirled.spot.data.Cluster;
import com.threerings.whirled.spot.data.ClusterObject;
import com.threerings.whirled.spot.data.ClusteredBodyObject;
import com.threerings.whirled.spot.data.Location;
@@ -171,6 +173,8 @@ public class SpotSceneManager extends SceneManager
throw new InvocationException(INTERNAL_ERROR);
}
+ // TODO: make sure the location isn't too close to another user
+
// update the user's location information in the scene which will
// indicate to the client that their avatar should be moved from
// its current position to their new position
@@ -186,16 +190,10 @@ public class SpotSceneManager extends SceneManager
}
// handle the cluster situation
- switch (cluster) {
- case 0:
- createNewCluster(source);
- break;
- case -1:
+ if (cluster == -1) {
removeFromCluster(source.getOid());
- break;
- default:
- addToCluster(cluster, source);
- break;
+ } else {
+ createOrJoinCluster(cluster, source);
}
}
@@ -219,25 +217,38 @@ public class SpotSceneManager extends SceneManager
/**
* Adds the specified user to the cluster occupied by the specified
- * other user.
+ * other user or creates a new cluster for the two users if neither is
+ * already clustered.
*/
- protected void addToCluster (int memberOid, BodyObject joiner)
+ protected void createOrJoinCluster (int memberOid, BodyObject joiner)
{
ClusterRecord clrec = (ClusterRecord)_clusters.get(memberOid);
- if (clrec == null) {
- // this isn't completely impossible as the last user might
- // leave a cluster just as another user is entering it, but
- // before the entering user's client was informed that the
- // cluster went away
- Log.info("Can't add user to non-existent cluster; " +
- "creating a new one just for them " +
- "[memberOid=" + memberOid +
- ", joiner=" + joiner.who() + "].");
- clrec = new ClusterRecord();
+ if (clrec != null) {
+ // if the cluster already exists, add this user and be done
+ if (clrec.addBody(joiner)) {
+ _clusters.put(joiner.getOid(), clrec);
+ }
+ return;
}
+
+ // otherwise we have to create a new cluster and add our two
+ // charter members!
+ clrec = new ClusterRecord();
+ BodyObject member = (BodyObject)CrowdServer.omgr.getObject(memberOid);
+ if (member == null) {
+ Log.warning("Can't create cluster, missing target " +
+ "[creator=" + joiner.who() +
+ ", targetOid=" + memberOid + "].");
+ return;
+ }
+
+ // add our two lovely users to the newly created cluster
if (clrec.addBody(joiner)) {
_clusters.put(joiner.getOid(), clrec);
}
+ if (clrec.addBody(member)) {
+ _clusters.put(member.getOid(), clrec);
+ }
}
/**
@@ -269,11 +280,39 @@ public class SpotSceneManager extends SceneManager
}
}
+ /**
+ * Converts the x and y coordinates in the supplied location object to
+ * Cartesian coordinates that can be manipulated geometrically. The
+ * default implementation assumes the location coordinates are
+ * Cartesian, but systems that use different coordinate systems will
+ * want to override this method and perform the appropriate
+ * conversions.
+ */
+ protected void locationToCoords (int lx, int ly, Point coords)
+ {
+ coords.x = lx;
+ coords.y = ly;
+ }
+
+ /**
+ * Converts the supplied x and y coordinates (obtained from a prior
+ * call to {@link #locationToCoords}) to location coordinates that can
+ * be sent back to the client. The default implementation assumes the
+ * location coordinates are Cartesian, but systems that use different
+ * coordinate systems will want to override this method and perform
+ * the appropriate conversions.
+ */
+ protected void coordsToLocation (int cx, int cy, Point loc)
+ {
+ loc.x = cx;
+ loc.y = cy;
+ }
+
/**
* Used to manage clusters which are groups of users that can chat to
* one another.
*/
- protected static class ClusterRecord extends HashIntMap
+ protected class ClusterRecord extends HashIntMap
implements Subscriber
{
public ClusterRecord ()
@@ -284,11 +323,14 @@ public class SpotSceneManager extends SceneManager
public boolean addBody (BodyObject body)
{
if (body instanceof ClusteredBodyObject) {
+ put(body.getOid(), body);
+ _cluster.occupants++;
+ recomputeCenter();
if (_clobj != null) {
+ _ssobj.updateClusters(_cluster);
+ _clobj.addToOccupants(body.getOid());
((ClusteredBodyObject)body).setClusterOid(_clobj.getOid());
}
- put(body.getOid(), body);
- _clobj.addToOccupants(body.getOid());
return true;
} else {
@@ -308,7 +350,12 @@ public class SpotSceneManager extends SceneManager
", size=" + size() + ", who=" + bodyOid + "].");
} else {
body.setClusterOid(-1);
- _clobj.removeFromOccupants(bodyOid);
+ _cluster.occupants--;
+ recomputeCenter();
+ if (_clobj != null) {
+ _clobj.removeFromOccupants(bodyOid);
+ _ssobj.updateClusters(_cluster);
+ }
}
// if we've removed our last body; stick a fork in ourselves
@@ -327,11 +374,16 @@ public class SpotSceneManager extends SceneManager
// keep this feller around
_clobj = (ClusterObject)object;
+ // configure our cluster record and publish it
+ _cluster.clusterOid = _clobj.getOid();
+ _ssobj.addToClusters(_cluster);
+
// let any mapped users know about our cluster
Iterator iter = values().iterator();
while (iter.hasNext()) {
ClusteredBodyObject body = (ClusteredBodyObject)iter.next();
body.setClusterOid(_clobj.getOid());
+ _clobj.addToOccupants(((BodyObject)body).getOid());
}
// if we didn't manage to add our creating user when we first
@@ -354,6 +406,29 @@ public class SpotSceneManager extends SceneManager
}
}
+ protected void recomputeCenter ()
+ {
+ int tx = 0, ty = 0, count = 0;
+
+ // compute the average x and y position of all our cluster
+ // occupants
+ Iterator iter = _ssobj.occupantLocs.entries();
+ while (iter.hasNext()) {
+ SceneLocation sloc = (SceneLocation)iter.next();
+ if (containsKey(sloc.bodyOid)) {
+ locationToCoords(sloc.x, sloc.y, _scratch);
+ tx += _scratch.x;
+ ty += _scratch.x;
+ count++;
+ }
+ }
+
+ // convert the center back to "location" coordinates
+ coordsToLocation(tx/count, ty/count, _scratch);
+ _cluster.x = _scratch.x;
+ _cluster.y = _scratch.y;
+ }
+
protected void destroy ()
{
Log.info("Cluster empty, going away " +
@@ -362,6 +437,8 @@ public class SpotSceneManager extends SceneManager
}
protected ClusterObject _clobj;
+ protected Cluster _cluster = new Cluster();
+ protected Point _scratch = new Point();
}
/** A casted reference to our place object. */