diff --git a/src/main/java/com/seibel/lod/core/objects/Pos2D.java b/src/main/java/com/seibel/lod/core/objects/Pos2D.java index 13a9f224c..4e6aecdcb 100644 --- a/src/main/java/com/seibel/lod/core/objects/Pos2D.java +++ b/src/main/java/com/seibel/lod/core/objects/Pos2D.java @@ -35,6 +35,10 @@ public class Pos2D { public Pos2D subtract(Pos2D other) { return new Pos2D(x - other.x, y - other.y); } + public Pos2D subtract(int v) { + return new Pos2D(x - v, y - v); + } + public double dist(Pos2D other) { return Math.sqrt(Math.pow(x - other.x, 2) + Math.pow(y - other.y, 2)); } diff --git a/src/main/java/com/seibel/lod/core/objects/a7/LodQuadTree.java b/src/main/java/com/seibel/lod/core/objects/a7/LodQuadTree.java index f0c1eeea8..608728836 100644 --- a/src/main/java/com/seibel/lod/core/objects/a7/LodQuadTree.java +++ b/src/main/java/com/seibel/lod/core/objects/a7/LodQuadTree.java @@ -18,27 +18,30 @@ import com.seibel.lod.core.util.gridList.MovableGridRingList; * -by adding data with the lodBuilder */ public abstract class LodQuadTree { - + + /** - * TODO add static configs here - * These configs are updated someway + * //TODO add static configs here + * //These configs are updated someway + * Comment: all config value should be via the class that extends this class, and + * by implementing different abstract methods - LeeTom */ - public final int maxPossibleDetailLevel; + public final int numbersOfDetailLevels; private final MovableGridRingList[] ringLists; /** * Constructor of the quadTree - * @param viewDistance - * @param initialPlayerX - * @param initialPlayerZ + * @param viewDistance View distance in blocks + * @param initialPlayerX player x coordinate + * @param initialPlayerZ player z coordinate */ public LodQuadTree(int viewDistance, int initialPlayerX, int initialPlayerZ) { - maxPossibleDetailLevel = DetailDistanceUtil.getDetailLevelFromDistance(viewDistance*Math.sqrt(2)); - ringLists = new MovableGridRingList[maxPossibleDetailLevel]; + numbersOfDetailLevels = DetailDistanceUtil.getDetailLevelFromDistance(viewDistance*Math.sqrt(2)); + ringLists = new MovableGridRingList[numbersOfDetailLevels]; int size; - for (byte detailLevel = 0; detailLevel < maxPossibleDetailLevel; detailLevel++) { + for (byte detailLevel = 0; detailLevel < numbersOfDetailLevels; detailLevel++) { double distance = DetailDistanceUtil.getDrawDistanceFromDetail(detailLevel); int sectionCount = LodUtil.ceilDiv((int) Math.ceil(distance), DhSectionPos.getWidth(detailLevel).toBlock()) + 1; // +1 for the border during move @@ -49,19 +52,37 @@ public abstract class LodQuadTree { /** * This method return the LodSection given the Section Pos - * @param pos - * @return + * @param pos the section positon + * @return the LodSection */ public LodSection getSection(DhSectionPos pos) { return getSection(pos.detail, pos.x, pos.z); } - + + /** + * This method returns the RingList of a given detail level + * @apiNote The returned ringList should not be modified! + * @param detailLevel the detail level + * @return the RingList + */ + public MovableGridRingList getRingList(byte detailLevel) { + return ringLists[detailLevel]; + } + + /** + * This method returns the number of detail levels in the quadTree + * @return the number of detail levels + */ + public int getNumbersOfDetailLevels() { + return numbersOfDetailLevels; + } + /** * This method return the LodSection at the given detail level and level coordinate x and z - * @param detailLevel - * @param x - * @param z - * @return + * @param detailLevel detail level of the section + * @param x x coordinate of the section + * @param z z coordinate of the section + * @return the LodSection */ public LodSection getSection(byte detailLevel, int x, int z) { return ringLists[detailLevel].get(x, z); @@ -71,8 +92,8 @@ public abstract class LodQuadTree { /** * This method will compute the detail level based on player position and section pos - * @param playerPos - * @param sectionPos + * @param playerPos player position as a reference for calculating the detail level + * @param sectionPos section position * @return detail level of this section pos */ public byte calculateExpectedDetailLevel(DhBlockPos2D playerPos, DhSectionPos sectionPos) { @@ -84,7 +105,7 @@ public abstract class LodQuadTree { /** * Given a section pos at level n this method returns the parent section at level n+1 - * @param pos + * @param pos the section positon * @return the parent LodSection */ public LodSection getParentSection(DhSectionPos pos) { @@ -106,12 +127,12 @@ public abstract class LodQuadTree { /** * This function update the quadTree based on the playerPos and the current game configs (static and global) - * @param playerPos + * @param playerPos the reference position for the player */ public void tick(DhBlockPos2D playerPos) { - for (int detailLevel = 0; detailLevel < maxPossibleDetailLevel; detailLevel++) { + for (int detailLevel = 0; detailLevel < numbersOfDetailLevels; detailLevel++) { ringLists[detailLevel].move(playerPos.x >> detailLevel, playerPos.z >> detailLevel, - LodSection::immediateDispose); + LodSection::dispose); } // First tick pass: update all sections' childCount from bottom level to top level. Step: @@ -133,12 +154,12 @@ public abstract class LodQuadTree { // - set childCount to -1 (Signal that this section will be freed if not rescued) // - If targetLevel <= detail && section == null: // - Parent's childCount++ (Create parent if needed) - for (byte detailLevel = 0; detailLevel < maxPossibleDetailLevel; detailLevel++) { + for (byte detailLevel = 0; detailLevel < numbersOfDetailLevels; detailLevel++) { final MovableGridRingList ringList = ringLists[detailLevel]; final MovableGridRingList childRingList = detailLevel == 0 ? null : ringLists[detailLevel - 1]; final MovableGridRingList parentRingList = - detailLevel == maxPossibleDetailLevel - 1 ? null : ringLists[detailLevel + 1]; + detailLevel == numbersOfDetailLevels - 1 ? null : ringLists[detailLevel + 1]; final byte detail = detailLevel; ringList.forEachPosOrdered((section, pos) -> { if (detail == 0 && section != null) { @@ -150,7 +171,7 @@ public abstract class LodQuadTree { LodSection parent = parentRingList.get(pos.x >> 1, pos.y >> 1); if (parent == null) { parent = parentRingList.setChained(pos.x >> 1, pos.y >> 1, - new LodSection(section.pos.getParent())); + new LodSection(section.pos.getParent(), getRenderDataSource())); parent.childCount++; } LodUtil.assertTrue(parent.childCount <= 4 && parent.childCount > 0); @@ -159,7 +180,7 @@ public abstract class LodQuadTree { LodSection child = ringList.get(childPos.x, childPos.z); if (child == null) { child = ringList.setChained(childPos.x, childPos.z, - new LodSection(childPos)); + new LodSection(childPos, getRenderDataSource())); child.childCount = 0; } else if (child.childCount == -1) { child.childCount = 0; @@ -181,7 +202,7 @@ public abstract class LodQuadTree { LodSection parent = parentRingList.get(pos.x >> 1, pos.y >> 1); if (parent == null) { parent = parentRingList.setChained(pos.x >> 1, pos.y >> 1, - new LodSection(sectPos.getParent())); + new LodSection(sectPos.getParent(), getRenderDataSource())); } parent.childCount++; } @@ -209,12 +230,12 @@ public abstract class LodQuadTree { // if childCount == -1: // (section can be loaded or unloaded, due to fast movement) // - set this section to null (TODO: Is this needed to be first or last or don't matter for concurrency?) // - If loaded unload section - for (byte detailLevel = 0; detailLevel < maxPossibleDetailLevel; detailLevel++) { + for (byte detailLevel = 0; detailLevel < numbersOfDetailLevels; detailLevel++) { final MovableGridRingList ringList = ringLists[detailLevel]; final MovableGridRingList childRingList = detailLevel == 0 ? null : ringLists[detailLevel - 1]; final MovableGridRingList parentRingList = - detailLevel == maxPossibleDetailLevel - 1 ? null : ringLists[detailLevel + 1]; + detailLevel == numbersOfDetailLevels - 1 ? null : ringLists[detailLevel + 1]; ringList.forEachPosOrdered((section, pos) -> { LodUtil.assertTrue(section.childCount == 4 || section.childCount == 0 || section.childCount == -1); if (section.childCount == 4) LodUtil.assertTrue( @@ -229,16 +250,12 @@ public abstract class LodQuadTree { getChildSection(section.pos, 3) == null); if (section.childCount == -1) LodUtil.assertTrue( getParentSection(section.pos).childCount == 0); - if (section.childCount == 4 && section.isLoaded()) { - section.load(getRenderDataSource()); - } else if (section.childCount == 0 && !section.isLoaded()) { section.unload(); + } else if (section.childCount == 0 && !section.isLoaded()) { + section.load(); } else if (section.childCount == -1) { ringList.set(pos.x, pos.y, null); - if (section.isLoaded()) { - section.unload(); - } section.dispose(); } }); diff --git a/src/main/java/com/seibel/lod/core/objects/a7/LodSection.java b/src/main/java/com/seibel/lod/core/objects/a7/LodSection.java index c80427f9d..ce9e80652 100644 --- a/src/main/java/com/seibel/lod/core/objects/a7/LodSection.java +++ b/src/main/java/com/seibel/lod/core/objects/a7/LodSection.java @@ -14,43 +14,34 @@ public class LodSection { // (Should always be 4 after tick() is done, or 0 only if this is an unloaded node) public byte childCount = 0; - - private RenderDataContainer levelContainer; - public RenderContainer renderContainer = null; + // TODO: Should I provide a way to change the render source? + private RenderContainer renderContainer; // Create sub region - public LodSection(DhSectionPos pos) { + public LodSection(DhSectionPos pos, RenderDataSource renderSource) { this.pos = pos; - levelContainer = null; - } - LodSection(DhSectionPos pos, RenderDataContainer levelContainer) { - this.pos = pos; - this.levelContainer = levelContainer; + this.renderContainer = renderSource.createRenderData(pos); } - // Return null if data does not exist - public boolean load(RenderDataSource renderDataSource) { - if (isLoaded()) throw new IllegalStateException("LodSection is already loaded"); - levelContainer = renderDataSource.createRenderData(pos); - return levelContainer != null; + public void load() { + LodUtil.assertTrue(!isLoaded()); + renderContainer.load(); } public void unload() { - if (!isLoaded()) throw new IllegalStateException("LodSection is not loaded"); - if (renderContainer != null) renderContainer.notifyUnload(); - levelContainer = null; - } - public void dispose() { - LodUtil.assertTrue(!isLoaded()); - if (renderContainer != null) renderContainer.notifyDispose(); + LodUtil.assertTrue(isLoaded()); + renderContainer.unload(); } - public void immediateDispose() { - if (isLoaded()) unload(); - if (renderContainer != null) renderContainer.notifyDispose(); + public void dispose() { + if (renderContainer != null) renderContainer.dispose(); } public boolean isLoaded() { - return levelContainer != null; + return renderContainer != null && renderContainer.isLoaded(); + } + + public RenderContainer getRenderContainer() { + return renderContainer; } } diff --git a/src/main/java/com/seibel/lod/core/objects/a7/RenderDataSource.java b/src/main/java/com/seibel/lod/core/objects/a7/RenderDataSource.java index d2cb0c8e2..6aed9808b 100644 --- a/src/main/java/com/seibel/lod/core/objects/a7/RenderDataSource.java +++ b/src/main/java/com/seibel/lod/core/objects/a7/RenderDataSource.java @@ -1,7 +1,13 @@ package com.seibel.lod.core.objects.a7; import com.seibel.lod.core.objects.a7.pos.DhSectionPos; +import com.seibel.lod.core.objects.a7.render.RenderContainer; public interface RenderDataSource { - RenderDataContainer createRenderData(DhSectionPos pos); + /** + * Returns the render container for the given section. + * @param pos The section position. + * @return The render container. If there are no data, returns EmptyRenderContainer. + */ + RenderContainer createRenderData(DhSectionPos pos); } diff --git a/src/main/java/com/seibel/lod/core/objects/a7/data/DataHandler.java b/src/main/java/com/seibel/lod/core/objects/a7/data/DataHandler.java index 897577237..7030c358f 100644 --- a/src/main/java/com/seibel/lod/core/objects/a7/data/DataHandler.java +++ b/src/main/java/com/seibel/lod/core/objects/a7/data/DataHandler.java @@ -1,8 +1,9 @@ package com.seibel.lod.core.objects.a7.data; -import com.seibel.lod.core.objects.a7.RenderDataContainer; import com.seibel.lod.core.objects.a7.RenderDataSource; import com.seibel.lod.core.objects.a7.pos.DhSectionPos; +import com.seibel.lod.core.objects.a7.render.EmptyRenderContainer; +import com.seibel.lod.core.objects.a7.render.RenderContainer; import java.io.File; @@ -14,10 +15,8 @@ public class DataHandler implements RenderDataSource { } @Override - public RenderDataContainer createRenderData(DhSectionPos pos) { - - + public RenderContainer createRenderData(DhSectionPos pos) { //TODO - return null; + return EmptyRenderContainer.INSTANCE; } } diff --git a/src/main/java/com/seibel/lod/core/objects/a7/render/ColumnRenderContainer.java b/src/main/java/com/seibel/lod/core/objects/a7/render/ColumnRenderContainer.java index fd8d4afad..bd7a44ce6 100644 --- a/src/main/java/com/seibel/lod/core/objects/a7/render/ColumnRenderContainer.java +++ b/src/main/java/com/seibel/lod/core/objects/a7/render/ColumnRenderContainer.java @@ -5,6 +5,8 @@ import com.seibel.lod.core.objects.opengl.RenderBuffer; import com.seibel.lod.core.objects.opengl.RenderRegion; import com.seibel.lod.core.util.LodUtil; +import java.util.concurrent.atomic.AtomicReference; + public class ColumnRenderContainer extends RenderContainer { public static final int columnWidth = DhSectionPos.DATA_WIDTH_PER_SECTION; public static final int columnCount = LodUtil.pow2(DhSectionPos.DATA_WIDTH_PER_SECTION); @@ -21,6 +23,26 @@ public class ColumnRenderContainer extends RenderContainer { renderRegion = new RenderRegion(); } + @Override + public void notifyRenderable() { + + } + + @Override + public void notifyUnrenderable() { + + } + + @Override + public boolean isRenderable() { + return false; + } + + @Override + public void notifyLoad() { + + } + @Override public void notifyUnload() { @@ -32,8 +54,7 @@ public class ColumnRenderContainer extends RenderContainer { } @Override - public RenderRegion getRenderRegion() { - return null; + public boolean trySwapRenderBuffer(AtomicReference referenceSlot) { + return false; } - } diff --git a/src/main/java/com/seibel/lod/core/objects/a7/render/EmptyRenderContainer.java b/src/main/java/com/seibel/lod/core/objects/a7/render/EmptyRenderContainer.java index 95afea72c..02278d81b 100644 --- a/src/main/java/com/seibel/lod/core/objects/a7/render/EmptyRenderContainer.java +++ b/src/main/java/com/seibel/lod/core/objects/a7/render/EmptyRenderContainer.java @@ -1,6 +1,32 @@ package com.seibel.lod.core.objects.a7.render; +import com.seibel.lod.core.objects.opengl.RenderBuffer; + +import java.util.concurrent.atomic.AtomicReference; + public class EmptyRenderContainer extends RenderContainer { + public static final EmptyRenderContainer INSTANCE = new EmptyRenderContainer(); + + @Override + public void notifyRenderable() { + + } + + @Override + public void notifyUnrenderable() { + + } + + @Override + public boolean isRenderable() { + return false; + } + + @Override + public void notifyLoad() { + + } + @Override public void notifyUnload() { @@ -12,7 +38,7 @@ public class EmptyRenderContainer extends RenderContainer { } @Override - public boolean render() { - return true; //Always render successfully since there is nothing to render + public boolean trySwapRenderBuffer(AtomicReference referenceSlot) { + return false; // no swap } } diff --git a/src/main/java/com/seibel/lod/core/objects/a7/render/RenderBufferHandler.java b/src/main/java/com/seibel/lod/core/objects/a7/render/RenderBufferHandler.java new file mode 100644 index 000000000..cc12d1111 --- /dev/null +++ b/src/main/java/com/seibel/lod/core/objects/a7/render/RenderBufferHandler.java @@ -0,0 +1,144 @@ +package com.seibel.lod.core.objects.a7.render; + +import com.seibel.lod.core.objects.Pos2D; +import com.seibel.lod.core.objects.a7.LodQuadTree; +import com.seibel.lod.core.objects.a7.LodSection; +import com.seibel.lod.core.objects.a7.pos.DhSectionPos; +import com.seibel.lod.core.objects.opengl.RenderBuffer; +import com.seibel.lod.core.render.LodRenderProgram; +import com.seibel.lod.core.util.LodUtil; +import com.seibel.lod.core.util.gridList.MovableGridRingList; + +import java.util.concurrent.atomic.AtomicReference; + +public class RenderBufferHandler { + public final LodQuadTree target; + private final MovableGridRingList renderBufferNodes; + + class RenderBufferNode implements AutoCloseable { + public final DhSectionPos pos; + public volatile RenderBufferNode[] children = null; + public AtomicReference renderBufferSlot = null; + + public RenderBufferNode(DhSectionPos pos) { + this.pos = pos; + } + + public void render(LodRenderProgram renderContext) { + RenderBuffer buff = renderBufferSlot.get(); + if (buff != null) { + buff.render(renderContext); + } else { + RenderBufferNode[] childs = children; + if (childs != null) { + for (RenderBufferNode child : childs) { + child.render(renderContext); + } + } + } + } + + //TODO: In the future make this logic a bit more complex so that when children are just created, + // the buffer is only unloaded if all children's buffers are ready. This will make the + // transition between buffers no longer causing any flicker. + public void update() { + LodSection section = target.getSection(pos); + // If this fails, there may be concurrent modification of the quad tree + // (as this update() should be called from the same thread that calls update() on the quad tree) + LodUtil.assertTrue(section != null); + RenderContainer container = section.getRenderContainer(); + + // Update self's render buffer state + boolean shouldRender = section.isLoaded(); + if (!shouldRender) { + RenderBuffer buff = renderBufferSlot.getAndSet(null); + if (buff != null) { + buff.close(); + } + } else { + container.trySwapRenderBuffer(renderBufferSlot); + } + + // Update children's render buffer state + boolean shouldHaveChildren = !container.isLoaded(); + if (shouldHaveChildren) { + if (children == null) { + RenderBufferNode[] childs = new RenderBufferNode[4]; + for (int i = 0; i < 4; i++) { + childs[i] = new RenderBufferNode(pos.getChild(i)); + } + children = childs; + } + for (RenderBufferNode child : children) { + child.update(); + } + } else { + if (children != null) { + RenderBufferNode[] childs = children; + children = null; + for (RenderBufferNode child : childs) { + child.close(); + } + } + } + } + + @Override + public void close() { + if (children != null) { + for (RenderBufferNode child : children) { + child.close(); + } + } + RenderBuffer buff = renderBufferSlot.getAndSet(null); + if (buff != null) { + buff.close(); + } + } + } + + public RenderBufferHandler(LodQuadTree target) { + this.target = target; + MovableGridRingList referenceList = target.getRingList((byte) (target.getNumbersOfDetailLevels() - 1)); + Pos2D center = referenceList.getCenter(); + renderBufferNodes = new MovableGridRingList<>(referenceList.getHalfSize(), center); + } + + public void render(LodRenderProgram renderContext) { + //TODO: This might get locked by update() causing move() call. Is there a way to avoid this? + // Maybe dupe the base list and use atomic swap on render? Or is this not worth it? + renderBufferNodes.forEachOrdered(n -> n.render(renderContext)); + } + + public void update() { + byte topDetail = (byte) (target.getNumbersOfDetailLevels() - 1); + MovableGridRingList referenceList = target.getRingList(topDetail); + Pos2D center = referenceList.getCenter(); + renderBufferNodes.move(center.x, center.y, RenderBufferNode::close); // Note: may lock the list + renderBufferNodes.forEachPosOrdered((node, pos) -> { + DhSectionPos sectPos = new DhSectionPos(topDetail, pos.x, pos.y); + LodSection section = target.getSection(sectPos); + + if (section == null) { + // If section is null, but node exists, remove node + if (node != null) { + renderBufferNodes.remove(pos).close(); + } + // If section is null, continue + return; + } + + // If section is not null, but node does not exist, create node + if (node == null) { + node = renderBufferNodes.setChained(pos, new RenderBufferNode(sectPos)); + } + // Node should be not null here + // Update node + node.update(); + }); + } + + public void close() { + renderBufferNodes.clear(RenderBufferNode::close); + } +} diff --git a/src/main/java/com/seibel/lod/core/objects/a7/render/RenderContainer.java b/src/main/java/com/seibel/lod/core/objects/a7/render/RenderContainer.java index 1321cbc7f..0257ded54 100644 --- a/src/main/java/com/seibel/lod/core/objects/a7/render/RenderContainer.java +++ b/src/main/java/com/seibel/lod/core/objects/a7/render/RenderContainer.java @@ -3,11 +3,39 @@ package com.seibel.lod.core.objects.a7.render; import com.seibel.lod.core.objects.opengl.RenderBuffer; import com.seibel.lod.core.objects.opengl.RenderRegion; +import java.util.concurrent.atomic.AtomicReference; + public abstract class RenderContainer { - public abstract void notifyUnload(); - public abstract void notifyDispose(); + private boolean isLoaded = false; + public final void load() { + isLoaded = true; + notifyLoad(); + } + public final void unload() { + isLoaded = false; + notifyUnload(); + } + public final void dispose() { + if (isLoaded) { + unload(); + } + notifyDispose(); + } - public abstract RenderRegion getRenderRegion(); + public final boolean isLoaded() { + return isLoaded; + } + protected abstract void notifyLoad(); // notify the container that it is now loaded and therefore may be rendered + protected abstract void notifyUnload(); // notify the container that it is now unloaded and therefore will not be rendered + protected abstract void notifyDispose(); // notify the container that the parent lodSection is now disposed + + /** + * Try and swap in new render buffer for this section. Note that before this call, there should be no other + * places storing or referencing the render buffer. + * @param referenceSlot The slot for swapping in the new buffer. + * @return True if the swap was successful. False if swap is not needed or if it is in progress. + */ + public abstract boolean trySwapRenderBuffer(AtomicReference referenceSlot); } diff --git a/src/main/java/com/seibel/lod/core/objects/opengl/RenderRegion.java b/src/main/java/com/seibel/lod/core/objects/opengl/RenderRegion.java index 463ff8caa..0d7c0c1ce 100644 --- a/src/main/java/com/seibel/lod/core/objects/opengl/RenderRegion.java +++ b/src/main/java/com/seibel/lod/core/objects/opengl/RenderRegion.java @@ -63,7 +63,8 @@ public class RenderRegion implements AutoCloseable /** stores if the region at the given x and z index needs to be regenerated */ // Use int because I need Tri state: private final AtomicInteger needRegen = new AtomicInteger(2); - + + private enum BackState { Unused, Building, diff --git a/src/main/java/com/seibel/lod/core/util/gridList/MovableGridRingList.java b/src/main/java/com/seibel/lod/core/util/gridList/MovableGridRingList.java index 0fc16ef69..643d38e91 100644 --- a/src/main/java/com/seibel/lod/core/util/gridList/MovableGridRingList.java +++ b/src/main/java/com/seibel/lod/core/util/gridList/MovableGridRingList.java @@ -41,6 +41,7 @@ public class MovableGridRingList extends ArrayList implements List { private Pos2D[] ringIteratorList = null; //TODO: Check if this needs to be synchronized + //FIXME: Make all usage of this class do stuff relative to the minPos instead of the center private void buildRingIteratorList() { ringIteratorList = null; Pos2D[] list = new Pos2D[size*size]; @@ -67,6 +68,9 @@ public class MovableGridRingList extends ArrayList implements List { pos.set(new Pos2D(centerX-halfSize, centerY-halfSize)); clear(); } + public MovableGridRingList(int halfSize, Pos2D center) { + this(halfSize, center.x, center.y); + } @Override public void clear() { @@ -173,6 +177,23 @@ public class MovableGridRingList extends ArrayList implements List { } } + public T remove(int x, int y) { + return swap(x, y, null); + } + + public T get(Pos2D p) { + return get(p.x, p.y); + } + public boolean set(Pos2D p, T t) { + return set(p.x, p.y, t); + } + public T swap(Pos2D p, T t) { + return swap(p.x, p.y, t); + } + public T remove(Pos2D p) { + return remove(p.x, p.y); + } + // TODO: Impl this /* // do a compare and set @@ -196,6 +217,9 @@ public class MovableGridRingList extends ArrayList implements List { public T setChained(int x, int y, T t) { return set(x,y,t) ? t : null; } + public T setChained(Pos2D p, T t) { + return setChained(p.x, p.y, t); + } // Return false if haven't changed. Return true if it did public boolean move(int newCenterX, int newCenterY) {