Пример #1
0
        /// <summary>
        /// Resolves the <see cref="Index"/> values of the specified <see cref="Tile"/> grid area, given the grid's raw data block.
        /// </summary>
        /// <param name="tileGridData"></param>
        /// <param name="beginX"></param>
        /// <param name="beginY"></param>
        /// <param name="width"></param>
        /// <param name="height"></param>
        /// <param name="stride"></param>
        /// <param name="tilesetRes"></param>
        public static void ResolveIndices(Tile[] tileGridData, int stride, int beginX, int beginY, int width, int height, ContentRef <Tileset> tileset)
        {
            if (tileset.Res == null)
            {
                throw new ArgumentNullException("tileset");
            }
            if (!tileset.Res.Compiled)
            {
                throw new InvalidOperationException("The specified Tileset needs to be compiled first.");
            }

            Tileset tilesetRes = tileset.Res;

            TileInfo[] tileData  = tilesetRes.TileData.Data;
            int        tileCount = tilesetRes.TileCount;

            for (int y = beginY; y < beginY + height; y++)
            {
                for (int x = beginX; x < beginX + width; x++)
                {
                    int i                        = y * stride + x;
                    int baseIndex                = MathF.Clamp(tileGridData[i].BaseIndex, 0, tileCount - 1);
                    int autoTileIndex            = tileData[baseIndex].AutoTileLayer - 1;
                    TilesetAutoTileInfo autoTile = autoTileIndex > -1 ? tilesetRes.AutoTileData[autoTileIndex] : null;

                    tileGridData[i].ResolveIndex(autoTile);
                }
            }
        }
Пример #2
0
        /// <summary>
        /// Initializes a new tile with the specified <see cref="BaseIndex"/> and derives all other values
        /// from their defaults as specified in the provided <see cref="Tileset"/>.
        ///
        /// This guarantees that, even for AutoTiles, the specified <see cref="BaseIndex"/> will be used
        /// directly as-is, without the resolve step adjusting it. When possible, prefer other methods of
        /// initializing tiles, as this one is more expensive than the others.
        /// </summary>
        /// <param name="baseIndex"></param>
        /// <param name="defaultLookup"></param>
        public Tile(int baseIndex, ContentRef <Tileset> defaultLookup)
        {
            Tileset tileset = defaultLookup.Res;

            if (tileset == null)
            {
                throw new ArgumentNullException("defaultLookup");
            }

            // As long as not resolved otherwise, use the base index directly.
            this.Index       = baseIndex;
            this.BaseIndex   = baseIndex;
            this.DepthOffset = 0;

            // By default, pre-initialize the tile with the AutoTile connectivity state that is
            // specified for it in the Tileset. This way, when painting the tile unaltered,
            // resolving it using base index and connectivity state won't replace it with a
            // different tile.
            TileInfo tileInfo      = tileset.TileData[baseIndex];
            int      autoTileLayer = tileInfo.AutoTileLayer;

            if (autoTileLayer != 0)
            {
                TilesetAutoTileInfo autoTile = tileset.AutoTileData[autoTileLayer - 1];
                IReadOnlyList <TilesetAutoTileItem> autoTileInfo = autoTile.TileInfo;
                this.AutoTileCon = autoTileInfo[baseIndex].Neighbours;
                this.BaseIndex   = autoTile.BaseTileIndex;
            }
            else
            {
                this.AutoTileCon = TileConnection.None;
            }
        }
Пример #3
0
        /// <summary>
        /// Resolves the <see cref="Index"/> of the <see cref="Tile"/> based on
        /// its <see cref="BaseIndex"/> and <see cref="AutoTileCon"/>.
        /// </summary>
        /// <param name="tileset"></param>
        public void ResolveIndex(ContentRef <Tileset> tileset)
        {
            if (tileset.Res == null)
            {
                throw new ArgumentNullException("tileset");
            }
            if (!tileset.Res.Compiled)
            {
                throw new InvalidOperationException("The specified Tileset needs to be compiled first.");
            }

            Tileset             tilesetRes    = tileset.Res;
            int                 autoTileIndex = tilesetRes.TileData[this.BaseIndex].AutoTileLayer - 1;
            TilesetAutoTileInfo autoTile      = autoTileIndex >= 0 ? tilesetRes.AutoTileData[autoTileIndex] : null;

            this.ResolveIndex(autoTile);
        }
Пример #4
0
        /// <summary>
        /// Resolves the <see cref="Index"/> of the <see cref="Tile"/> based on
        /// its <see cref="BaseIndex"/> and <see cref="AutoTileCon"/>.
        /// </summary>
        /// <param name="autoTile"></param>
        private void ResolveIndex(TilesetAutoTileInfo autoTile)
        {
            // Non-AutoTiles always use their base index directly.
            if (autoTile == null)
            {
                this.Index = this.BaseIndex;
            }
            // AutoTiles require a dynamic lookup with their connectivity state, because
            // they might use generated tiles that do not have a consistent index across
            // different Tileset configs.
            else
            {
                // If there is no connectivity info and a non-matching base index, this tile
                // was likely painted before it was configured to be an AutoTile. In that case,
                // derive the appropriate connectivity from the specs and adjust the base index
                // to match.
                if (this.BaseIndex != autoTile.BaseTileIndex && this.AutoTileCon == TileConnection.None)
                {
                    this.AutoTileCon = autoTile.TileInfo[this.BaseIndex].Neighbours;
                    this.BaseIndex   = autoTile.BaseTileIndex;
                }

                int targetIndex = autoTile.StateToTile[(int)this.AutoTileCon];

                // If the AutoTile connectivity state already matches the one we'd get with the default
                // resolved tile index, use the current one directly and don't change it.
                // This will allow scenarios where users specify multiple tiles for a certain connectivity
                // state, without forcing them back to a single one during resolve.
                if (autoTile.TileInfo[this.BaseIndex].Neighbours == autoTile.TileInfo[targetIndex].Neighbours)
                {
                    this.Index = this.BaseIndex;
                }
                // Otherwise, lookup the expected tile using base index and connectivity. This
                // will retrieve the proper generated tile, which has an index that may change
                // between multiple compilations.
                else
                {
                    this.Index = targetIndex;
                }
            }
        }
Пример #5
0
        /// <summary>
        /// Updates the <see cref="AutoTileCon"/> state of an arbitrary region on the specified tile grid
        /// based on its connectivity state with neighbouring tiles.
        /// </summary>
        /// <param name="tileGrid"></param>
        /// <param name="updateMask"></param>
        /// <param name="beginX"></param>
        /// <param name="beginY"></param>
        /// <param name="width"></param>
        /// <param name="height"></param>
        /// <param name="tilesetRes"></param>
        public static void UpdateAutoTileCon(Grid <Tile> tileGrid, Grid <bool> updateMask, int beginX, int beginY, int width, int height, ContentRef <Tileset> tileset)
        {
            if (tileset.Res == null)
            {
                throw new ArgumentNullException("tileset");
            }
            if (tileGrid == null)
            {
                throw new ArgumentNullException("tileGrid");
            }

            Tileset tilesetRes = tileset.Res;

            TileInfo[] tileData   = tilesetRes.TileData.Data;
            Tile[]     tiles      = tileGrid.RawData;
            bool[]     maskData   = updateMask != null ? updateMask.RawData : null;
            int        tileStride = tileGrid.Width;
            int        maskStride = updateMask.Width;
            int        maxTileX   = tileGrid.Width - 1;
            int        maxTileY   = tileGrid.Height - 1;

            for (int y = 0; y < height; y++)
            {
                for (int x = 0; x < width; x++)
                {
                    // Skip tiles that have been masked away
                    int m = x + maskStride * y;
                    if (maskData != null && !maskData[m])
                    {
                        continue;
                    }

                    // Determine tilemap coordinates and index
                    int tileX = x + beginX;
                    int tileY = y + beginY;
                    int i     = tileX + tileStride * tileY;

                    // Skip non-AutoTiles
                    int autoTileIndex = tileData[tiles[i].BaseIndex].AutoTileLayer - 1;
                    if (autoTileIndex == -1)
                    {
                        continue;
                    }

                    // Lookup AutoTile data
                    TilesetAutoTileInfo autoTile = tilesetRes.AutoTileData[autoTileIndex];
                    IReadOnlyList <TilesetAutoTileItem> autoTileInfo = autoTile.TileInfo;

                    // Check neighbour connectivity
                    bool topLeft     = (tileX <= 0 || tileY <= 0) || autoTileInfo[tiles[i - 1 - tileStride].Index].ConnectsToAutoTile;
                    bool top         = (tileY <= 0) || autoTileInfo[tiles[i - tileStride].Index].ConnectsToAutoTile;
                    bool topRight    = (tileX >= maxTileX || tileY <= 0) || autoTileInfo[tiles[i + 1 - tileStride].Index].ConnectsToAutoTile;
                    bool left        = (tileX <= 0) || autoTileInfo[tiles[i - 1].Index].ConnectsToAutoTile;
                    bool right       = (tileX >= maxTileX) || autoTileInfo[tiles[i + 1].Index].ConnectsToAutoTile;
                    bool bottomLeft  = (tileX <= 0 || tileY >= maxTileY) || autoTileInfo[tiles[i - 1 + tileStride].Index].ConnectsToAutoTile;
                    bool bottom      = (tileY >= maxTileY) || autoTileInfo[tiles[i + tileStride].Index].ConnectsToAutoTile;
                    bool bottomRight = (tileX >= maxTileX || tileY >= maxTileY) || autoTileInfo[tiles[i + 1 + tileStride].Index].ConnectsToAutoTile;

                    // Create connectivity bitmask
                    TileConnection autoTileCon = TileConnection.None;
                    if (topLeft)
                    {
                        autoTileCon |= TileConnection.TopLeft;
                    }
                    if (top)
                    {
                        autoTileCon |= TileConnection.Top;
                    }
                    if (topRight)
                    {
                        autoTileCon |= TileConnection.TopRight;
                    }
                    if (left)
                    {
                        autoTileCon |= TileConnection.Left;
                    }
                    if (right)
                    {
                        autoTileCon |= TileConnection.Right;
                    }
                    if (bottomLeft)
                    {
                        autoTileCon |= TileConnection.BottomLeft;
                    }
                    if (bottom)
                    {
                        autoTileCon |= TileConnection.Bottom;
                    }
                    if (bottomRight)
                    {
                        autoTileCon |= TileConnection.BottomRight;
                    }

                    // Update connectivity and re-resolve index
                    tiles[i].AutoTileCon = autoTileCon;
                    tiles[i].ResolveIndex(autoTile);
                }
            }
        }
Пример #6
0
        /// <summary>
        /// Resolves the <see cref="Index"/> of the <see cref="Tile"/> based on 
        /// its <see cref="BaseIndex"/> and <see cref="AutoTileCon"/>.
        /// </summary>
        /// <param name="autoTile"></param>
        private void ResolveIndex(TilesetAutoTileInfo autoTile)
        {
            // Non-AutoTiles always use their base index directly.
            if (autoTile == null)
            {
                this.Index = this.BaseIndex;
            }
            // AutoTiles require a dynamic lookup with their connectivity state, because
            // they might use generated tiles that do not have a consistent index across
            // different Tileset configs.
            else
            {
                // If there is no connectivity info and a non-matching base index, this tile
                // was likely painted before it was configured to be an AutoTile. In that case,
                // derive the appropriate connectivity from the specs and adjust the base index
                // to match.
                if (this.BaseIndex != autoTile.BaseTileIndex && this.AutoTileCon == TileConnection.None)
                {
                    this.AutoTileCon = autoTile.TileInfo[this.BaseIndex].Neighbours;
                    this.BaseIndex = autoTile.BaseTileIndex;
                }

                int targetIndex = autoTile.StateToTile[(int)this.AutoTileCon];

                // If the AutoTile connectivity state already matches the one we'd get with the default
                // resolved tile index, use the current one directly and don't change it.
                // This will allow scenarios where users specify multiple tiles for a certain connectivity
                // state, without forcing them back to a single one during resolve.
                if (autoTile.TileInfo[this.BaseIndex].Neighbours == autoTile.TileInfo[targetIndex].Neighbours)
                {
                    this.Index = this.BaseIndex;
                }
                // Otherwise, lookup the expected tile using base index and connectivity. This
                // will retrieve the proper generated tile, which has an index that may change
                // between multiple compilations.
                else
                {
                    this.Index = targetIndex;
                }
            }
        }
Пример #7
0
        /// <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);
        }