diff --git a/src/java/com/threerings/miso/client/util/ObjectSet.java b/src/java/com/threerings/miso/client/util/ObjectSet.java
new file mode 100644
index 000000000..a2460184f
--- /dev/null
+++ b/src/java/com/threerings/miso/client/util/ObjectSet.java
@@ -0,0 +1,132 @@
+//
+// $Id: ObjectSet.java,v 1.1 2002/09/23 21:53:23 mdb Exp $
+
+package com.threerings.miso.scene.util;
+
+import java.util.Arrays;
+import java.util.Comparator;
+
+import com.samskivert.util.ArrayUtil;
+import com.samskivert.util.ListUtil;
+
+import com.threerings.miso.scene.SceneObject;
+
+/**
+ * Used to store an (arbitrarily) ordered, low-impact iteratable (doesn't
+ * require object creation), set of {@link SceneObject} instances.
+ */
+public class ObjectSet
+{
+ /**
+ * Inserts the supplied scene object into the set.
+ *
+ * @return true if it was inserted, false if the object was already in
+ * the set.
+ */
+ public boolean insert (SceneObject scobj)
+ {
+ // bail if it's already in the set
+ int ipos = indexOf(scobj);
+ if (ipos >= 0) {
+ return false;
+ }
+
+ // otherwise insert it
+ ipos = -(ipos+1);
+ _scobjs = ListUtil.insert(_scobjs, ipos, scobj);
+ _size++;
+ return true;
+ }
+
+ /**
+ * Returns true if the specified object is in the set, false if it is
+ * not.
+ */
+ public boolean contains (SceneObject scobj)
+ {
+ return (indexOf(scobj) >= 0);
+ }
+
+ /**
+ * Returns the number of scene objects in this set.
+ */
+ public int size ()
+ {
+ return _size;
+ }
+
+ /**
+ * Returns the scene object with the specified index. The index must &
+ * be between 0 and {@link #size}-1.
+ */
+ public SceneObject get (int index)
+ {
+ return (SceneObject)_scobjs[index];
+ }
+
+ /**
+ * Removes the object at the specified index.
+ */
+ public void remove (int index)
+ {
+ ListUtil.remove(_scobjs, index);
+ _size--;
+ }
+
+ /**
+ * Removes the specified scene object from the set.
+ *
+ * @return true if it was removed, false if it was not in the set.
+ */
+ public boolean remove (SceneObject scobj)
+ {
+ int opos = indexOf(scobj);
+ if (opos >= 0) {
+ remove(opos);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Clears out the contents of this set.
+ */
+ public void clear ()
+ {
+ _size = 0;
+ Arrays.fill(_scobjs, null);
+ }
+
+ /**
+ * Returns the index of the object or it's insertion index if it is
+ * not in the set.
+ */
+ protected final int indexOf (SceneObject scobj)
+ {
+ return ArrayUtil.binarySearch(_scobjs, 0, _size, scobj, SCOBJ_COMP);
+ }
+
+ /** Our sorted array of scene objects. */
+ protected Object[] _scobjs = new Object[DEFAULT_SIZE];
+
+ /** The number of objects in the set. */
+ protected int _size;
+
+ /** We simply sort the scene objects in order of their hash code. We
+ * don't care about their orderer, it simply needs to exist to support
+ * binary search. */
+ protected static final Comparator SCOBJ_COMP = new Comparator() {
+ public int compare (Object o1, Object o2) {
+ return o1.hashCode() - o2.hashCode();
+ }
+
+ public boolean equals (Object other) {
+ return this == other;
+ }
+ };
+
+ /** We start big because we know these will in general contain at
+ * least in the tens of objects. */
+ protected static final int DEFAULT_SIZE = 16;
+}