/// <summary> /// Determines the overall geometry of a single <see cref="Tileset"/> visual layer. This involves /// tile boundaries in source and target data, as well as texture sizes and similar. /// </summary> /// <param name="renderInput"></param> /// <param name="layerData"></param> private LayerGeometry CalculateLayerGeometry(TilesetRenderInput renderInput, PixelData layerData) { LayerGeometry geometry; // What's the space requirement for each tile? geometry.SourceTileAdvance = renderInput.SourceTileAdvance; geometry.TargetTileAdvance = renderInput.TargetTileAdvance; // How many tiles will we have? Point2 tileCount = renderInput.GetSourceTileCount(layerData.Width, layerData.Height); geometry.SourceTilesPerRow = tileCount.X; geometry.SourceTilesPerColumn = tileCount.Y; geometry.SourceTileCount = geometry.SourceTilesPerRow * geometry.SourceTilesPerColumn; geometry.TargetTileCount = geometry.SourceTileCount + this.generateTileSchedule.Count; // What's the optimal texture size to include them all? int minTilesPerLine = MathF.Max(1, (int)MathF.Sqrt(geometry.TargetTileCount)); geometry.TargetTextureSize.X = MathF.NextPowerOfTwo(geometry.TargetTileAdvance.X * minTilesPerLine); int actualTilesPerLine = geometry.TargetTextureSize.X / geometry.TargetTileAdvance.X; int requiredLineCount = 1 + (geometry.TargetTileCount / actualTilesPerLine); geometry.TargetTextureSize.Y = MathF.NextPowerOfTwo(geometry.TargetTileAdvance.Y * requiredLineCount); return(geometry); }
/// <summary> /// Initializes local working data for processing the specified <see cref="TilesetCompilerInput"/>. /// </summary> /// <param name="input"></param> private void InitWorkingData(TilesetCompilerInput input) { // Determine how many source tiles we have. This will be the smallest overlap // of available rendering input layers this.inputTileCount = int.MaxValue; for (int renderInputIndex = 0; renderInputIndex < input.RenderConfig.Count; renderInputIndex++) { TilesetRenderInput renderInput = input.RenderConfig[renderInputIndex] ?? DefaultRenderInput; PixelData sourceLayerData = (renderInput.SourceData.Res ?? Pixmap.Checkerboard.Res).MainLayer; LayerGeometry layerGeometry = this.CalculateLayerGeometry(renderInput, sourceLayerData); this.inputTileCount = Math.Min(this.inputTileCount, layerGeometry.SourceTileCount); } if (input.RenderConfig.Count == 0) { this.inputTileCount = 0; } // Initialize intermediate tile data to be big enough for the source data this.outputTileCount = this.inputTileCount; this.tiles.Count = this.inputTileCount; TileInfo[] data = this.tiles.Data; for (int i = 0; i < this.inputTileCount; i++) { data[i].IsVisuallyEmpty = true; } // Copy input data to our intermediate data structures TileInput[] inputData = input.TileInput.Data; for (int i = MathF.Min(this.inputTileCount, input.TileInput.Count); i >= 0; i--) { data[i].DepthOffset = inputData[i].DepthOffset; data[i].IsVertical = inputData[i].IsVertical; data[i].Collision = inputData[i].Collision; } }
/// <summary> /// Looks up the source pixel coordinates for the specified input layer and tile. /// </summary> /// <param name="renderConfigIndex"></param> /// <param name="tileIndex"></param> /// <param name="pos"></param> /// <param name="size"></param> public void LookupTileSourceRect(int renderConfigIndex, int tileIndex, out Point2 pos, out Point2 size) { TilesetRenderInput input = this.renderConfig[renderConfigIndex]; Point2 advance = input.SourceTileAdvance; Point2 tileCount = input.GetSourceTileCount( input.SourceData.Res.Width, input.SourceData.Res.Height); Point2 tileCoords = new Point2( tileIndex % tileCount.X, tileIndex / tileCount.X); size = input.SourceTileSize; pos = new Point2( tileCoords.X * advance.X, tileCoords.Y * advance.Y); }
/// <summary> /// Generates pixel and atlas data for a single <see cref="Tileset"/> visual layer. /// </summary> /// <param name="renderInput"></param> /// <param name="sourceData"></param> /// <param name="geometry"></param> /// <param name="tileData"></param> /// <returns></returns> private LayerPixelData GenerateLayerPixelData(TilesetRenderInput renderInput, PixelData sourceData, LayerGeometry geometry, RawList<TileInfo> tileData) { // Create a buffer for writing target pixel data LayerPixelData target; target.PixelData = new PixelData(geometry.TargetTextureSize.X, geometry.TargetTextureSize.Y); target.Atlas = new List<Rect>(); // Iterate over tiles and move each tile from source to target Point2 targetTilePos = new Point2(0, 0); for (int tileIndex = 0; tileIndex < geometry.SourceTileCount; tileIndex++) { // Initialize a new tile info when necessary if (tileIndex >= tileData.Count) { tileData.Count++; tileData.Data[tileIndex].IsVisuallyEmpty = true; } // Determine where on the source buffer the tile is located Point2 sourceTilePos = new Point2( geometry.SourceTileAdvance.X * (tileIndex % geometry.SourceTilesPerRow), geometry.SourceTileAdvance.Y * (tileIndex / geometry.SourceTilesPerRow)); // Draw the source tile onto the target buffer, including its spacing / border Point2 targetContentPos = new Point2( targetTilePos.X + renderInput.TargetTileMargin, targetTilePos.Y + renderInput.TargetTileMargin); sourceData.DrawOnto(target.PixelData, BlendMode.Solid, targetContentPos.X, targetContentPos.Y, renderInput.SourceTileSize.X, renderInput.SourceTileSize.Y, sourceTilePos.X, sourceTilePos.Y); // Fill up the target spacing area with similar pixels if (renderInput.TargetTileMargin > 0) { FillTileSpacing(target.PixelData, renderInput.TargetTileMargin, targetContentPos, renderInput.SourceTileSize); } // Update whether the tile is considered visually empty if (tileData.Data[tileIndex].IsVisuallyEmpty) { bool isLayerVisuallyEmpty = IsCompletelyTransparent( sourceData, sourceTilePos, renderInput.SourceTileSize); if (!isLayerVisuallyEmpty) tileData.Data[tileIndex].IsVisuallyEmpty = false; } // Add an entry to the generated atlas Rect atlasRect = new Rect( targetTilePos.X + renderInput.TargetTileMargin, targetTilePos.Y + renderInput.TargetTileMargin, geometry.TargetTileAdvance.X - renderInput.TargetTileMargin * 2, geometry.TargetTileAdvance.Y - renderInput.TargetTileMargin * 2); target.Atlas.Add(atlasRect); // Advance the target tile position targetTilePos.X += geometry.TargetTileAdvance.X; if (targetTilePos.X + geometry.TargetTileAdvance.X > target.PixelData.Width) { targetTilePos.X = 0; targetTilePos.Y += geometry.TargetTileAdvance.Y; } } return target; }
/// <summary> /// Determines the overall geometry of a single <see cref="Tileset"/> visual layer. This involves /// tile boundaries in source and target data, as well as texture sizes and similar. /// </summary> /// <param name="renderInput"></param> /// <param name="layerData"></param> /// <returns></returns> private LayerGeometry CalculateLayerGeometry(TilesetRenderInput renderInput, PixelData layerData) { LayerGeometry geometry; // What's the space requirement for each tile? geometry.SourceTileAdvance = renderInput.SourceTileAdvance; geometry.TargetTileAdvance = renderInput.TargetTileAdvance; // How many tiles will we have? Point2 tileCount = renderInput.GetSourceTileCount(layerData.Width, layerData.Height); geometry.SourceTilesPerRow = tileCount.X; geometry.SourceTilesPerColumn = tileCount.Y; geometry.SourceTileCount = geometry.SourceTilesPerRow * geometry.SourceTilesPerColumn; geometry.TargetTileCount = geometry.SourceTileCount; // ToDo: Account for expanded AutoTiles // What's the optimal texture size to include them all? int minTilesPerLine = MathF.Max(1, (int)MathF.Sqrt(geometry.TargetTileCount)); geometry.TargetTextureSize.X = MathF.NextPowerOfTwo(geometry.TargetTileAdvance.X * minTilesPerLine); int actualTilesPerLine = geometry.TargetTextureSize.X / geometry.TargetTileAdvance.X; int requiredLineCount = 1 + (geometry.TargetTileCount / actualTilesPerLine); geometry.TargetTextureSize.Y = MathF.NextPowerOfTwo(geometry.TargetTileAdvance.Y * requiredLineCount); return geometry; }
/// <inheritdoc /> public override void AddLayer() { base.AddLayer(); Tileset tileset = this.SelectedTileset.Res; if (tileset == null) return; // Determine which texture IDs are already present, so we can // derive which one we'll pick as a default for creating a new one. bool hasMainTex = false; int highestCustomTex = -1; for (int i = 0; i < tileset.RenderConfig.Count; i++) { if (tileset.RenderConfig[i].Id == TilesetRenderInput.MainTexId) { hasMainTex = true; } else if (tileset.RenderConfig[i].Id.StartsWith(TilesetRenderInput.CustomTexId)) { string customTexIndexString = tileset.RenderConfig[i].Id.Substring( TilesetRenderInput.CustomTexId.Length, tileset.RenderConfig[i].Id.Length - TilesetRenderInput.CustomTexId.Length); int customTexIndex; if (!int.TryParse(customTexIndexString, out customTexIndex)) customTexIndex = 0; highestCustomTex = Math.Max(highestCustomTex, customTexIndex); } } // Decide upon an id for our new layer string layerId; string layerName; if (!hasMainTex) { layerId = TilesetRenderInput.MainTexId; layerName = TilesetRenderInput.MainTexName; } else { layerId = TilesetRenderInput.CustomTexId + (highestCustomTex + 1).ToString(); layerName = TilesetRenderInput.CustomTexName; } // Create a new layer using an UndoRedo action TilesetRenderInput newLayer = new TilesetRenderInput { Id = layerId, Name = layerName }; UndoRedoManager.Do(new AddTilesetConfigLayerAction<TilesetRenderInput>( tileset, TilemapsReflectionInfo.Property_Tileset_RenderConfig, newLayer)); // Select the newly created visual layer VisualLayerNode modelNode = this.treeModel .Nodes .OfType<VisualLayerNode>() .FirstOrDefault(n => n.VisualLayer == newLayer); this.SelectLayer(modelNode); }
public VisualLayerNode(TilesetRenderInput layer) : base() { this.layer = layer; this.Image = Properties.TilemapsResCache.IconTilesetSingleVisualLayer; }
/// <summary> /// Compiles a <see cref="Tileset"/> using its specified source data, in order to /// generate optimized target data for rendering and collision detection. /// </summary> public TilesetCompilerOutput Compile(TilesetCompilerInput input) { // Clear private working data before beginning a new operation this.ClearWorkingData(); // Initialize private working data with the required space for our new input this.InitWorkingData(input); // Gather data on AutoTiles that are partially transformed from the input tileset, // and partially generated from existing tiles as part of the compilation. this.GatherAutoTileData(input.AutoTileConfig); // Generate metadata for scheduled tiles this.GenerateTileMetadata(); // Draw output pixel data for (int renderInputIndex = 0; renderInputIndex < input.RenderConfig.Count; renderInputIndex++) { TilesetRenderInput renderInput = input.RenderConfig[renderInputIndex] ?? DefaultRenderInput; PixelData sourceLayerData = (renderInput.SourceData.Res ?? Pixmap.Checkerboard.Res).MainLayer; // Determine overall geometry values for this layer, such as tile bounds and texture sizes LayerGeometry layerGeometry = this.CalculateLayerGeometry(renderInput, sourceLayerData); // Generate pixel data and atlas values for this layer's texture LayerPixelData targetLayerData = this.ComposeLayerPixelData( renderInput, sourceLayerData, layerGeometry); // Create the texture to be used for this rendering input using (Pixmap targetPixmap = new Pixmap(targetLayerData.PixelData)) { targetPixmap.Atlas = targetLayerData.Atlas; Texture targetTexture = new Texture( targetPixmap, TextureSizeMode.Enlarge, renderInput.TargetMagFilter, renderInput.TargetMinFilter, TextureWrapMode.Clamp, TextureWrapMode.Clamp, renderInput.TargetFormat); this.renderData.Add(targetTexture); } } // Retrieve texture atlas data for quick lookup during rendering if (this.renderData.Count > 0) { for (int i = 0; i < this.tiles.Count; i++) { this.renderData[0].LookupAtlas(i, out this.tiles.Data[i].TexCoord0); } } // Prepare output, keeping existing structures to avoid allocations TilesetCompilerOutput output = input.ExistingOutput; output.TileCount = this.outputTileCount; output.TileData = output.TileData ?? new RawList <TileInfo>(input.TileInput.Count); output.TileData.Clear(); this.tiles.CopyTo(output.TileData, 0, this.tiles.Count); output.RenderData = output.RenderData ?? new List <Texture>(this.renderData); output.RenderData.Clear(); output.RenderData.AddRange(this.renderData); output.AutoTileData = output.AutoTileData ?? new List <TilesetAutoTileInfo>(); output.AutoTileData.Clear(); this.TransformAutoTileData(output.AutoTileData); output.EmptyTileIndex = this.GetEmptyTileIndex(); this.ClearWorkingData(); return(output); }
/// <summary> /// Composes pixel and atlas data for a single <see cref="Tileset"/> visual layer. /// </summary> private LayerPixelData ComposeLayerPixelData(TilesetRenderInput renderInput, PixelData sourceData, LayerGeometry geometry) { // Create a buffer for writing target pixel data LayerPixelData target; target.PixelData = new PixelData(geometry.TargetTextureSize.X, geometry.TargetTextureSize.Y); target.Atlas = new List <Rect>(); // Iterate over source tiles and copy each tile from source to target Point2 targetTilePos = new Point2(0, 0); for (int tileIndex = 0; tileIndex < geometry.SourceTileCount; tileIndex++) { // Determine source and target positions on pixel data / buffer Point2 sourceTilePos = geometry.GetSourceTilePos(tileIndex); Point2 targetContentPos = new Point2( targetTilePos.X + renderInput.TargetTileMargin, targetTilePos.Y + renderInput.TargetTileMargin); // Draw the source tile onto the target buffer, including its spacing / border sourceData.DrawOnto(target.PixelData, BlendMode.Solid, targetContentPos.X, targetContentPos.Y, renderInput.SourceTileSize.X, renderInput.SourceTileSize.Y, sourceTilePos.X, sourceTilePos.Y); // Fill up the target spacing area with similar pixels if (renderInput.TargetTileMargin > 0) { FillTileSpacing(target.PixelData, renderInput.TargetTileMargin, targetContentPos, renderInput.SourceTileSize); } // Update whether the tile is considered visually empty if (this.tiles.Data[tileIndex].IsVisuallyEmpty) { bool isLayerVisuallyEmpty = IsCompletelyTransparent( sourceData, sourceTilePos, renderInput.SourceTileSize); if (!isLayerVisuallyEmpty) { this.tiles.Data[tileIndex].IsVisuallyEmpty = false; } } // Add an entry to the generated atlas Rect atlasRect = new Rect( targetTilePos.X + renderInput.TargetTileMargin, targetTilePos.Y + renderInput.TargetTileMargin, geometry.TargetTileAdvance.X - renderInput.TargetTileMargin * 2, geometry.TargetTileAdvance.Y - renderInput.TargetTileMargin * 2); target.Atlas.Add(atlasRect); // Advance the target tile position targetTilePos.X += geometry.TargetTileAdvance.X; if (targetTilePos.X + geometry.TargetTileAdvance.X > target.PixelData.Width) { targetTilePos.X = 0; targetTilePos.Y += geometry.TargetTileAdvance.Y; } } // Generate additional target tiles as scheduled foreach (GeneratedQuadTile tile in this.generateTileSchedule) { // Determine source and target positions on pixel data / buffer Point2 sourceTilePosTopLeft = geometry.GetSourceTilePos(tile.SourceTopLeftIndex); Point2 sourceTilePosTopRight = geometry.GetSourceTilePos(tile.SourceTopRightIndex); Point2 sourceTilePosBottomRight = geometry.GetSourceTilePos(tile.SourceBottomRightIndex); Point2 sourceTilePosBottomLeft = geometry.GetSourceTilePos(tile.SourceBottomLeftIndex); Point2 targetContentPos = new Point2( targetTilePos.X + renderInput.TargetTileMargin, targetTilePos.Y + renderInput.TargetTileMargin); // Draw the source tile onto the target buffer, including its spacing / border Point2 quadSize = renderInput.SourceTileSize / 2; sourceData.DrawOnto(target.PixelData, BlendMode.Solid, targetContentPos.X, targetContentPos.Y, quadSize.X, quadSize.Y, sourceTilePosTopLeft.X, sourceTilePosTopLeft.Y); sourceData.DrawOnto(target.PixelData, BlendMode.Solid, targetContentPos.X + quadSize.X, targetContentPos.Y, quadSize.X, quadSize.Y, sourceTilePosTopRight.X + quadSize.X, sourceTilePosTopRight.Y); sourceData.DrawOnto(target.PixelData, BlendMode.Solid, targetContentPos.X + quadSize.X, targetContentPos.Y + quadSize.Y, quadSize.X, quadSize.Y, sourceTilePosBottomRight.X + quadSize.X, sourceTilePosBottomRight.Y + quadSize.Y); sourceData.DrawOnto(target.PixelData, BlendMode.Solid, targetContentPos.X, targetContentPos.Y + quadSize.Y, quadSize.X, quadSize.Y, sourceTilePosBottomLeft.X, sourceTilePosBottomLeft.Y + quadSize.Y); // Fill up the target spacing area with similar pixels if (renderInput.TargetTileMargin > 0) { FillTileSpacing(target.PixelData, renderInput.TargetTileMargin, targetContentPos, renderInput.SourceTileSize); } // Add an entry to the generated atlas Rect atlasRect = new Rect( targetTilePos.X + renderInput.TargetTileMargin, targetTilePos.Y + renderInput.TargetTileMargin, geometry.TargetTileAdvance.X - renderInput.TargetTileMargin * 2, geometry.TargetTileAdvance.Y - renderInput.TargetTileMargin * 2); target.Atlas.Add(atlasRect); // Advance the target tile position targetTilePos.X += geometry.TargetTileAdvance.X; if (targetTilePos.X + geometry.TargetTileAdvance.X > target.PixelData.Width) { targetTilePos.X = 0; targetTilePos.Y += geometry.TargetTileAdvance.Y; } } // Update which tiles are considered visually empty for (int tileIndex = 0; tileIndex < this.outputTileCount; tileIndex++) { // Determine target positions on pixel data / buffer Rect targetRect = target.Atlas[tileIndex]; // Update whether the tile is considered visually empty if (this.tiles.Data[tileIndex].IsVisuallyEmpty) { bool isLayerVisuallyEmpty = IsCompletelyTransparent( target.PixelData, (Point2)targetRect.Pos, (Point2)targetRect.Size); if (!isLayerVisuallyEmpty) { this.tiles.Data[tileIndex].IsVisuallyEmpty = false; } } } return(target); }
/// <summary> /// Compiles a <see cref="Tileset"/> using its specified source data, in order to /// generate optimized target data for rendering and collision detection. /// </summary> public TilesetCompilerOutput Compile(TilesetCompilerInput input) { TilesetCompilerOutput output = input.ExistingOutput; output.TileData = output.TileData ?? new RawList <TileInfo>(input.TileInput.Count); output.RenderData = output.RenderData ?? new List <Texture>(); output.AutoTileData = output.AutoTileData ?? new List <TilesetAutoTileInfo>(); // Clear existing data, but keep the sufficiently big data structures output.TileData.Clear(); output.RenderData.Clear(); output.AutoTileData.Clear(); // Determine how many source tiles we have int sourceTileCount = int.MaxValue; for (int renderInputIndex = 0; renderInputIndex < input.RenderConfig.Count; renderInputIndex++) { TilesetRenderInput renderInput = input.RenderConfig[renderInputIndex] ?? DefaultRenderInput; PixelData sourceLayerData = (renderInput.SourceData.Res ?? Pixmap.Checkerboard.Res).MainLayer; LayerGeometry layerGeometry = this.CalculateLayerGeometry(renderInput, sourceLayerData); sourceTileCount = Math.Min(sourceTileCount, layerGeometry.SourceTileCount); } if (input.RenderConfig.Count == 0) { sourceTileCount = 0; } // Transform AutoTile data for (int autoTileIndex = 0; autoTileIndex < input.AutoTileConfig.Count; autoTileIndex++) { TilesetAutoTileInput autoTileInput = input.AutoTileConfig[autoTileIndex]; TilesetAutoTileInfo autoTileInfo = this.TransformAutoTileData( autoTileIndex, autoTileInput, output.TileData, sourceTileCount); output.AutoTileData.Add(autoTileInfo); } // Initialize all tiles to being visually empty. They will be subtractively updated // during output pixel data generation in the next step. { int tileDataCount = output.TileData.Count; TileInfo[] tileData = output.TileData.Data; for (int i = 0; i < tileDataCount; i++) { tileData[i].IsVisuallyEmpty = true; } } // Generate output pixel data for (int renderInputIndex = 0; renderInputIndex < input.RenderConfig.Count; renderInputIndex++) { TilesetRenderInput renderInput = input.RenderConfig[renderInputIndex] ?? DefaultRenderInput; PixelData sourceLayerData = (renderInput.SourceData.Res ?? Pixmap.Checkerboard.Res).MainLayer; // Determine overal geometry values for this layer, such as tile bounds and texture sizes LayerGeometry layerGeometry = this.CalculateLayerGeometry(renderInput, sourceLayerData); // Generate pixel data and atlas values for this layer's texture LayerPixelData targetLayerData = this.GenerateLayerPixelData( renderInput, sourceLayerData, layerGeometry, output.TileData); // Create the texture to be used for this rendering input using (Pixmap targetPixmap = new Pixmap(targetLayerData.PixelData)) { targetPixmap.Atlas = targetLayerData.Atlas; Texture targetTexture = new Texture( targetPixmap, TextureSizeMode.Enlarge, renderInput.TargetMagFilter, renderInput.TargetMinFilter, TextureWrapMode.Clamp, TextureWrapMode.Clamp, renderInput.TargetFormat); output.RenderData.Add(targetTexture); } } // Generate additional per-tile data this.TransformTileData(input.TileInput, output.TileData, output.RenderData); // Apply global tileset stats output.TileCount = sourceTileCount; return(output); }
/// <summary> /// Generates pixel and atlas data for a single <see cref="Tileset"/> visual layer. /// </summary> /// <param name="renderInput"></param> /// <param name="sourceData"></param> /// <param name="geometry"></param> /// <param name="tileData"></param> /// <returns></returns> private LayerPixelData GenerateLayerPixelData(TilesetRenderInput renderInput, PixelData sourceData, LayerGeometry geometry, RawList <TileInfo> tileData) { // Create a buffer for writing target pixel data LayerPixelData target; target.PixelData = new PixelData(geometry.TargetTextureSize.X, geometry.TargetTextureSize.Y); target.Atlas = new List <Rect>(); // Iterate over tiles and move each tile from source to target Point2 targetTilePos = new Point2(0, 0); for (int tileIndex = 0; tileIndex < geometry.SourceTileCount; tileIndex++) { // Initialize a new tile info when necessary if (tileIndex >= tileData.Count) { tileData.Count++; tileData.Data[tileIndex].IsVisuallyEmpty = true; } // Determine where on the source buffer the tile is located Point2 sourceTilePos = new Point2( geometry.SourceTileAdvance.X * (tileIndex % geometry.SourceTilesPerRow), geometry.SourceTileAdvance.Y * (tileIndex / geometry.SourceTilesPerRow)); // Draw the source tile onto the target buffer, including its spacing / border Point2 targetContentPos = new Point2( targetTilePos.X + renderInput.TargetTileMargin, targetTilePos.Y + renderInput.TargetTileMargin); sourceData.DrawOnto(target.PixelData, BlendMode.Solid, targetContentPos.X, targetContentPos.Y, renderInput.SourceTileSize.X, renderInput.SourceTileSize.Y, sourceTilePos.X, sourceTilePos.Y); // Fill up the target spacing area with similar pixels if (renderInput.TargetTileMargin > 0) { FillTileSpacing(target.PixelData, renderInput.TargetTileMargin, targetContentPos, renderInput.SourceTileSize); } // Update whether the tile is considered visually empty if (tileData.Data[tileIndex].IsVisuallyEmpty) { bool isLayerVisuallyEmpty = IsCompletelyTransparent( sourceData, sourceTilePos, renderInput.SourceTileSize); if (!isLayerVisuallyEmpty) { tileData.Data[tileIndex].IsVisuallyEmpty = false; } } // Add an entry to the generated atlas Rect atlasRect = new Rect( targetTilePos.X + renderInput.TargetTileMargin, targetTilePos.Y + renderInput.TargetTileMargin, geometry.TargetTileAdvance.X - renderInput.TargetTileMargin * 2, geometry.TargetTileAdvance.Y - renderInput.TargetTileMargin * 2); target.Atlas.Add(atlasRect); // Advance the target tile position targetTilePos.X += geometry.TargetTileAdvance.X; if (targetTilePos.X + geometry.TargetTileAdvance.X > target.PixelData.Width) { targetTilePos.X = 0; targetTilePos.Y += geometry.TargetTileAdvance.Y; } } return(target); }