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; } }