diff --git a/Nibriboard/ClientFiles/Chunk.js b/Nibriboard/ClientFiles/Chunk.js
index 6f5a0ec..4beed85 100644
--- a/Nibriboard/ClientFiles/Chunk.js
+++ b/Nibriboard/ClientFiles/Chunk.js
@@ -19,6 +19,20 @@ class Chunk
this.lines = [];
}
+ /**
+ * Fetches the first line in this chunk by it's id.
+ * @param {string} lineId The target line id to search for.
+ * @return {object|null} The requested line, or null if it wasn't found.
+ */
+ getLineById(lineId)
+ {
+ for (let line of this.lines) {
+ if(line.LineId == lineId)
+ return line;
+ }
+ return null;
+ }
+
/**
* Whether this chunk is located at the specified chunk reference.
* @param {ChunkReference} otherChunkRef The chunk reference to check
@@ -33,12 +47,34 @@ class Chunk
return false;
}
+ /**
+ * Whether this chunk falls inside the specified rectangle.
+ * @param {Rectangle} area The rectangle to test against, in location-space
+ * @return {Boolean} Whether this chunk falls inside the specified rectangle.
+ */
+ isVisible(area)
+ {
+ let chunkArea = new Rectangle(
+ this.chunkRef.x * this.size,
+ this.chunkRef.y * this.size,
+ this.size, this.size
+ );
+ return area.overlaps(area);
+ }
+
update(dt)
{
}
- render(canvas, context)
+ /**
+ * Renders this chunk to the given canvas with the given context.
+ * @param {HTMLCanvasElement} canvas The canvas to render to.
+ * @param {CanvasRenderingContext2D} context The context to render with.
+ * @param {ChunkCache} chunkCache The chunk cache to use to fetch data from surrounding chunks.
+ * @param {Rectangle} chunkArea The area in which chunks are being rendered.
+ */
+ render(canvas, context, chunkCache, chunkArea)
{
var planeSpaceRef = this.chunkRef.inPlaneSpace(this.size);
@@ -47,16 +83,32 @@ class Chunk
for(let line of this.lines)
{
+ // Don't draw lines that are walked by other chunks
+ if(line.ContinuesFrom != null &&
+ chunkCache.fetchChunk(line.ContinuesFrom) != null)
+ continue;
+
+ let linePoints = line.Points;
+
+ // Fetch all the points on fragments of this line forwards from here
+ if(line.ContinuesIn != null) {
+ let nextLines = chunkCache.fetchLineFragments(line.ContinuesIn, line.LineId);
+ for (let nextLine of nextLines) {
+ linePoints = linePoints.concat(nextLine.Points);
+ }
+ }
+
context.beginPath();
context.moveTo(
- line.Points[0].x - planeSpaceRef.x,
- line.Points[0].y - planeSpaceRef.y
+ linePoints[0].x - planeSpaceRef.x,
+ linePoints[0].y - planeSpaceRef.y
);
- for(let i = 1; i < line.Points.length; i++)
+
+ for(let i = 1; i < linePoints.length; i++)
{
context.lineTo(
- line.Points[i].x - planeSpaceRef.x,
- line.Points[i].y - planeSpaceRef.y
+ linePoints[i].x - planeSpaceRef.x,
+ linePoints[i].y - planeSpaceRef.y
);
}
diff --git a/Nibriboard/ClientFiles/ChunkCache.js b/Nibriboard/ClientFiles/ChunkCache.js
index f0f8845..5ae0bbb 100644
--- a/Nibriboard/ClientFiles/ChunkCache.js
+++ b/Nibriboard/ClientFiles/ChunkCache.js
@@ -18,6 +18,48 @@ class ChunkCache
this.boardWindow.rippleLink.on("ChunkUpdate", this.handleChunkUpdate.bind(this));
}
+ /**
+ * Fetches the chunk with the specified chunk reference.
+ * @param {ChunkReference} chunkRef The chunk reference of the chunk to fetch.
+ * @return {Chunk|null} The requested chunk, or null if it isn't present in the cache.
+ */
+ fetchChunk(chunkRef)
+ {
+ if(!this.cache.has(chunkRef.toString()))
+ return null;
+
+ return this.cache.get(chunkRef.toString());
+ }
+
+ /**
+ * Walk the currently cached chunks to find all the line fragments for the
+ * specified line id, starting at the specified chunk reference.
+ * @param {ChunkReference} startingChunkRef The reference of hte chunk we should start walking at.
+ * @param {string} lineId The id of the line we should fetch the fragments for.
+ * @return {object[]} A list of line fragments found.
+ */
+ fetchLineFragments(startingChunkRef, lineId)
+ {
+ let lineFragments = [];
+ let currentChunk = this.fetchChunk(startingChunkRef);
+
+ while(currentChunk != null)
+ {
+ let nextLineFragment = currentChunk.getLineById(lineId);
+ if(nextLineFragment == null)
+ break;
+
+ lineFragments.push(nextLineFragment);
+
+ if(nextLineFragment.ContinuesIn == null)
+ break;
+
+ currentChunk = this.fetchChunk(nextLineFragment.ContinuesIn);
+ }
+
+ return lineFragments;
+ }
+
/**
* Adds the given chunk to the chunk cache.
* @param {Chunk} chunkData The chunk to add to the cache.
@@ -98,7 +140,7 @@ class ChunkCache
let chunk = this.cache.get(cChunk.toString());
if(typeof chunk != "undefined" && !chunk.requestedFromServer)
- chunk.render(canvas, context);
+ chunk.render(canvas, context, this, chunkArea);
if(this.showRenderedChunks) {
context.beginPath();
@@ -125,6 +167,18 @@ class ChunkCache
let newChunk = new Chunk(newChunkRef, chunkData.Size);
let newLines = chunkData.lines.map((line) => {
line.Points = line.Points.map((raw) => new Vector(raw.X, raw.Y));
+ if(line.ContinuesIn != null) {
+ line.ContinuesIn = new ChunkReference(
+ this.boardWindow.currentPlaneName,
+ line.ContinuesIn.X, line.ContinuesIn.Y
+ );
+ }
+ if(line.ContinuesFrom != null) {
+ line.ContinuesFrom = new ChunkReference(
+ this.boardWindow.currentPlaneName,
+ line.ContinuesFrom.X, line.ContinuesFrom.Y
+ );
+ }
return line;
});
newChunk.lines = newChunk.lines.concat(newLines);
diff --git a/Nibriboard/ClientFiles/Utilities/Rectangle.js b/Nibriboard/ClientFiles/Utilities/Rectangle.js
index da26c54..ca2f013 100644
--- a/Nibriboard/ClientFiles/Utilities/Rectangle.js
+++ b/Nibriboard/ClientFiles/Utilities/Rectangle.js
@@ -2,9 +2,9 @@
import Vector from './Vector';
-///
-/// Represents a rectangle in 2D space.
-///
+/**
+ * Represents a rectangle in 2D space.
+ */
class Rectangle
{
/**
@@ -78,6 +78,22 @@ class Rectangle
this.height = height;
}
+ /**
+ * Figures out whether this rectangle overlaps another rectangle.
+ * @param {Rectangle} otherRectangle The other rectangle to check the overlap of.
+ * @return {bool} Whether this rectangle overlaps another rectangle.
+ */
+ overlaps(otherRectangle)
+ {
+ if(this.Top > otherRectangle.Bottom ||
+ this.Bottom < otherRectangle.Top ||
+ this.Left > otherRectangle.Right ||
+ this.Right < otherRectangle.Left)
+ return false;
+
+ return true;
+ }
+
/**
* Returns a copy of this rectangle that can be safely edited without affecting the original.
* @returns {Rectangle}
diff --git a/Nibriboard/RippleSpace/DrawnLine.cs b/Nibriboard/RippleSpace/DrawnLine.cs
index 664373d..9548455 100644
--- a/Nibriboard/RippleSpace/DrawnLine.cs
+++ b/Nibriboard/RippleSpace/DrawnLine.cs
@@ -59,6 +59,20 @@ namespace Nibriboard.RippleSpace
}
}
+ ///
+ /// The chunk reference of the next chunk that this line continues in.
+ /// A value of null is present when this line doesn't continue into another chunk.
+ ///
+ [JsonProperty]
+ public ChunkReference ContinuesIn = null;
+ ///
+ /// The chunk reference of the previous chunk that contains the line fragment that
+ /// this line continues from. Is null when this line either doesn't continue from
+ /// another line fragment or doesn't span multiple chunks.
+ ///
+ [JsonProperty]
+ public ChunkReference ContinuesFrom = null;
+
///
/// Gets a reference in chunk-space ot the chunk that this line starts in.
///
@@ -120,6 +134,19 @@ namespace Nibriboard.RippleSpace
results.Add(nextLine);
}
+ // Set the ContinuesIn and ContinuesFrom properties
+ // so that clients can find the next / previous chunk line fragmentss
+ for(int i = 0; i < results.Count - 1; i++)
+ {
+ // Set the ContinuesFrom reference, but not on the first fragment in the list
+ if(i > 0)
+ results[i].ContinuesFrom = results[i - 1].ContainingChunk;
+
+ // Set the ContinuesIn reference, but not on the last fragment in the list
+ if(i < results.Count - 1)
+ results[i].ContinuesIn = results[i + 1].ContainingChunk;
+ }
+
return results;
}
}