2022-11-14 12:59:35 +01:00
using System ;
2021-12-03 19:57:26 +01:00
using System.Collections.Generic ;
2019-08-06 22:31:57 +02:00
using Microsoft.Xna.Framework ;
using Microsoft.Xna.Framework.Graphics ;
2021-12-03 21:10:19 +01:00
using MLEM.Textures ;
2019-08-06 22:31:57 +02:00
2021-11-29 21:24:08 +01:00
namespace MLEM.Graphics {
2020-05-21 17:21:34 +02:00
/// <summary>
2022-11-14 12:59:35 +01:00
/// This class contains a <see cref="DrawAutoTile"/> method that allows users to easily draw a tile with automatic connections, as well as a more complex <see cref="DrawExtendedAutoTile(Microsoft.Xna.Framework.Graphics.SpriteBatch,Microsoft.Xna.Framework.Vector2,MLEM.Textures.TextureRegion,MLEM.Textures.TextureRegion,MLEM.Graphics.AutoTiling.ConnectsTo,Microsoft.Xna.Framework.Color,Microsoft.Xna.Framework.Color,System.Nullable{Microsoft.Xna.Framework.Vector2},System.Nullable{Microsoft.Xna.Framework.Vector2},float,float)"/> method.
/// Note that <see cref="StaticSpriteBatch"/> can also be used for drawing by using the <see cref="AddAutoTile"/> and <see cref="AddExtendedAutoTile(MLEM.Graphics.StaticSpriteBatch,Microsoft.Xna.Framework.Vector2,MLEM.Textures.TextureRegion,MLEM.Textures.TextureRegion,MLEM.Graphics.AutoTiling.ConnectsTo,Microsoft.Xna.Framework.Color,Microsoft.Xna.Framework.Color,System.Nullable{Microsoft.Xna.Framework.Vector2},System.Nullable{Microsoft.Xna.Framework.Vector2},float,float,System.Collections.Generic.ICollection{MLEM.Graphics.StaticSpriteBatch.Item})"/> methods instead.
2020-05-21 17:21:34 +02:00
/// </summary>
2021-03-08 15:12:13 +01:00
public static class AutoTiling {
2019-08-06 22:31:57 +02:00
2020-05-21 17:21:34 +02:00
/// <summary>
/// This method allows for a tiled texture to be drawn in an auto-tiling mode.
/// This allows, for example, a grass patch on a tilemap to have nice looking edges that transfer over into a path without any hard edges between tiles.
///
2021-11-27 14:27:56 +01:00
/// For auto-tiling in this way to work, the tiles have to be laid out as follows: five tiles aligned horizontally within the texture file, with the following information:
/// <list type="number">
/// <item><description>The texture used for filling big areas</description></item>
/// <item><description>The texture used for straight, horizontal borders, with the borders facing away from the center</description></item>
/// <item><description>The texture used for outer corners, with the corners facing away from the center</description></item>
/// <item><description>The texture used for straight, vertical borders, with the borders facing away from the center</description></item>
/// <item><description>The texture used for inner corners, with the corners facing away from the center</description></item>
/// </list>
2021-11-27 15:03:30 +01:00
/// For more information and an example, see https://github.com/Ellpeck/MLEM/blob/main/Demos/AutoTilingDemo.cs and its source texture https://github.com/Ellpeck/MLEM/blob/main/Demos/Content/Textures/AutoTiling.png.
2020-05-21 17:21:34 +02:00
/// </summary>
2021-11-27 14:27:56 +01:00
/// <param name="batch">The sprite batch to use for drawing.</param>
/// <param name="pos">The position to draw at.</param>
2021-12-03 21:10:19 +01:00
/// <param name="texture">The texture to use for drawing, with the area set to the first texture region, as described in the summary.</param>
2021-11-27 14:27:56 +01:00
/// <param name="connectsTo">A function that determines whether two positions should connect.</param>
/// <param name="color">The color to draw with.</param>
/// <param name="origin">The origin to draw from.</param>
/// <param name="scale">The scale to draw with.</param>
/// <param name="layerDepth">The layer depth to draw with.</param>
2021-12-03 21:10:19 +01:00
public static void DrawAutoTile ( SpriteBatch batch , Vector2 pos , TextureRegion texture , ConnectsTo connectsTo , Color color , Vector2 ? origin = null , Vector2 ? scale = null , float layerDepth = 0 ) {
2021-11-27 14:27:56 +01:00
var orig = origin ? ? Vector2 . Zero ;
2019-08-07 00:32:48 +02:00
var sc = scale ? ? Vector2 . One ;
2022-06-15 11:38:11 +02:00
var ( p1 , r1 , p2 , r2 , p3 , r3 , p4 , r4 ) = AutoTiling . CalculateAutoTile ( pos , texture . Area , connectsTo , sc ) ;
2021-12-03 21:10:19 +01:00
batch . Draw ( texture . Texture , p1 , r1 , color , 0 , orig , sc , SpriteEffects . None , layerDepth ) ;
batch . Draw ( texture . Texture , p2 , r2 , color , 0 , orig , sc , SpriteEffects . None , layerDepth ) ;
batch . Draw ( texture . Texture , p3 , r3 , color , 0 , orig , sc , SpriteEffects . None , layerDepth ) ;
batch . Draw ( texture . Texture , p4 , r4 , color , 0 , orig , sc , SpriteEffects . None , layerDepth ) ;
2021-11-27 14:27:56 +01:00
}
/// <inheritdoc cref="DrawAutoTile"/>
2021-12-03 21:10:19 +01:00
public static void AddAutoTile ( StaticSpriteBatch batch , Vector2 pos , TextureRegion texture , ConnectsTo connectsTo , Color color , Vector2 ? origin = null , Vector2 ? scale = null , float layerDepth = 0 , ICollection < StaticSpriteBatch . Item > items = null ) {
2021-11-27 14:27:56 +01:00
var orig = origin ? ? Vector2 . Zero ;
var sc = scale ? ? Vector2 . One ;
2022-06-15 11:38:11 +02:00
var ( p1 , r1 , p2 , r2 , p3 , r3 , p4 , r4 ) = AutoTiling . CalculateAutoTile ( pos , texture . Area , connectsTo , sc ) ;
2021-12-03 21:10:19 +01:00
var a1 = batch . Add ( texture . Texture , p1 , r1 , color , 0 , orig , sc , SpriteEffects . None , layerDepth ) ;
var a2 = batch . Add ( texture . Texture , p2 , r2 , color , 0 , orig , sc , SpriteEffects . None , layerDepth ) ;
var a3 = batch . Add ( texture . Texture , p3 , r3 , color , 0 , orig , sc , SpriteEffects . None , layerDepth ) ;
var a4 = batch . Add ( texture . Texture , p4 , r4 , color , 0 , orig , sc , SpriteEffects . None , layerDepth ) ;
2021-12-03 19:57:26 +01:00
if ( items ! = null ) {
items . Add ( a1 ) ;
items . Add ( a2 ) ;
items . Add ( a3 ) ;
items . Add ( a4 ) ;
}
2021-11-27 14:27:56 +01:00
}
2019-08-07 00:32:48 +02:00
2021-11-27 15:03:30 +01:00
/// <summary>
/// This method allows for a tiled texture to be drawn in an auto-tiling mode.
/// This allows, for example, a grass patch on a tilemap to have nice looking edges that transfer over into a path without any hard edges between tiles.
2022-06-24 14:01:26 +02:00
///
/// This method is a more complex version of <see cref="DrawAutoTile"/> that overlays separate border textures on a background texture region, which also allows for non-rectangular texture areas to be used easily.
2021-12-03 20:59:56 +01:00
/// For auto-tiling in this way to work, the overlay sections have to be laid out as follows: 16 sections aligned horizontally within the texture file, with the following information:
2021-11-27 15:03:30 +01:00
/// <list type="number">
/// <item><description>The texture used for straight, horizontal borders, with the borders facing away from the center, split up into four parts: top left, then top right, then bottom left, then bottom right</description></item>
/// <item><description>The texture used for outer corners, with the corners facing away from the center, split up into four parts: top left, then top right, then bottom left, then bottom right</description></item>
/// <item><description>The texture used for straight, vertical borders, with the borders facing away from the center, split up into four parts: top left, then top right, then bottom left, then bottom right</description></item>
/// <item><description>The texture used for inner corners, with the corners facing away from the center, split up into four parts: top left, then top right, then bottom left, then bottom right</description></item>
/// </list>
/// For more information and an example, see https://github.com/Ellpeck/MLEM/blob/main/Demos/AutoTilingDemo.cs and its source texture https://github.com/Ellpeck/MLEM/blob/main/Demos/Content/Textures/AutoTiling.png.
/// </summary>
/// <param name="batch">The sprite batch to use for drawing.</param>
/// <param name="pos">The position to draw at.</param>
2021-12-03 21:10:19 +01:00
/// <param name="backgroundTexture">The background region, or null to skip drawing a background.</param>
/// <param name="overlayTexture">The first overlay region, as described in the summary.</param>
2021-11-27 15:03:30 +01:00
/// <param name="connectsTo">A function that determines whether two positions should connect.</param>
2021-12-03 18:04:03 +01:00
/// <param name="backgroundColor">The color to draw the texture used for filling big areas with.</param>
/// <param name="overlayColor">The color to draw border and corner textures with.</param>
2021-11-27 15:03:30 +01:00
/// <param name="origin">The origin to draw from.</param>
/// <param name="scale">The scale to draw with.</param>
/// <param name="layerDepth">The layer depth to draw with.</param>
2021-12-03 18:34:33 +01:00
/// <param name="overlayDepthOffset">An optional depth offset from <paramref name="layerDepth"/> that the overlay should be drawn with</param>
2021-12-03 21:10:19 +01:00
public static void DrawExtendedAutoTile ( SpriteBatch batch , Vector2 pos , TextureRegion backgroundTexture , TextureRegion overlayTexture , ConnectsTo connectsTo , Color backgroundColor , Color overlayColor , Vector2 ? origin = null , Vector2 ? scale = null , float layerDepth = 0 , float overlayDepthOffset = 0 ) {
2021-11-27 15:03:30 +01:00
var orig = origin ? ? Vector2 . Zero ;
var sc = scale ? ? Vector2 . One ;
2021-12-03 18:34:33 +01:00
var od = layerDepth + overlayDepthOffset ;
2022-08-20 11:39:28 +02:00
var ( r1 , r2 , r3 , r4 ) = AutoTiling . CalculateExtendedAutoTile ( overlayTexture . Area , connectsTo ) ;
2021-12-03 21:10:19 +01:00
if ( backgroundTexture ! = null )
batch . Draw ( backgroundTexture , pos , backgroundColor , 0 , orig , sc , SpriteEffects . None , layerDepth ) ;
2021-11-27 15:03:30 +01:00
if ( r1 ! = Rectangle . Empty )
2021-12-03 21:10:19 +01:00
batch . Draw ( overlayTexture . Texture , pos , r1 , overlayColor , 0 , orig , sc , SpriteEffects . None , od ) ;
2021-11-27 15:03:30 +01:00
if ( r2 ! = Rectangle . Empty )
2021-12-03 21:10:19 +01:00
batch . Draw ( overlayTexture . Texture , pos , r2 , overlayColor , 0 , orig , sc , SpriteEffects . None , od ) ;
2021-11-27 15:03:30 +01:00
if ( r3 ! = Rectangle . Empty )
2021-12-03 21:10:19 +01:00
batch . Draw ( overlayTexture . Texture , pos , r3 , overlayColor , 0 , orig , sc , SpriteEffects . None , od ) ;
2021-11-27 15:03:30 +01:00
if ( r4 ! = Rectangle . Empty )
2021-12-03 21:10:19 +01:00
batch . Draw ( overlayTexture . Texture , pos , r4 , overlayColor , 0 , orig , sc , SpriteEffects . None , od ) ;
2021-11-27 15:03:30 +01:00
}
2022-11-14 12:59:35 +01:00
/// <inheritdoc cref="DrawExtendedAutoTile(Microsoft.Xna.Framework.Graphics.SpriteBatch,Microsoft.Xna.Framework.Vector2,MLEM.Textures.TextureRegion,MLEM.Textures.TextureRegion,MLEM.Graphics.AutoTiling.ConnectsTo,Microsoft.Xna.Framework.Color,Microsoft.Xna.Framework.Color,System.Nullable{Microsoft.Xna.Framework.Vector2},System.Nullable{Microsoft.Xna.Framework.Vector2},float,float)"/>
public static void DrawExtendedAutoTile ( SpriteBatch batch , Vector2 pos , TextureRegion backgroundTexture , Func < int , TextureRegion > overlayTextures , ConnectsTo connectsTo , Color backgroundColor , Color overlayColor , Vector2 ? origin = null , Vector2 ? scale = null , float layerDepth = 0 , float overlayDepthOffset = 0 ) {
var orig = origin ? ? Vector2 . Zero ;
var sc = scale ? ? Vector2 . One ;
var od = layerDepth + overlayDepthOffset ;
var ( xUl , xUr , xDl , xDr ) = AutoTiling . CalculateExtendedAutoTileOffsets ( connectsTo ) ;
if ( backgroundTexture ! = null )
batch . Draw ( backgroundTexture , pos , backgroundColor , 0 , orig , sc , SpriteEffects . None , layerDepth ) ;
if ( xUl > = 0 )
batch . Draw ( overlayTextures ( xUl ) , pos , overlayColor , 0 , orig , sc , SpriteEffects . None , od ) ;
if ( xUr > = 0 )
batch . Draw ( overlayTextures ( xUr ) , pos , overlayColor , 0 , orig , sc , SpriteEffects . None , od ) ;
if ( xDl > = 0 )
batch . Draw ( overlayTextures ( xDl ) , pos , overlayColor , 0 , orig , sc , SpriteEffects . None , od ) ;
if ( xDr > = 0 )
batch . Draw ( overlayTextures ( xDr ) , pos , overlayColor , 0 , orig , sc , SpriteEffects . None , od ) ;
}
/// <inheritdoc cref="DrawExtendedAutoTile(Microsoft.Xna.Framework.Graphics.SpriteBatch,Microsoft.Xna.Framework.Vector2,MLEM.Textures.TextureRegion,MLEM.Textures.TextureRegion,MLEM.Graphics.AutoTiling.ConnectsTo,Microsoft.Xna.Framework.Color,Microsoft.Xna.Framework.Color,System.Nullable{Microsoft.Xna.Framework.Vector2},System.Nullable{Microsoft.Xna.Framework.Vector2},float,float)"/>
2021-12-03 21:10:19 +01:00
public static void AddExtendedAutoTile ( StaticSpriteBatch batch , Vector2 pos , TextureRegion backgroundTexture , TextureRegion overlayTexture , ConnectsTo connectsTo , Color backgroundColor , Color overlayColor , Vector2 ? origin = null , Vector2 ? scale = null , float layerDepth = 0 , float overlayDepthOffset = 0 , ICollection < StaticSpriteBatch . Item > items = null ) {
2021-11-27 15:03:30 +01:00
var orig = origin ? ? Vector2 . Zero ;
var sc = scale ? ? Vector2 . One ;
2021-12-03 18:34:33 +01:00
var od = layerDepth + overlayDepthOffset ;
2022-08-20 11:39:28 +02:00
var ( r1 , r2 , r3 , r4 ) = AutoTiling . CalculateExtendedAutoTile ( overlayTexture . Area , connectsTo ) ;
2021-12-03 21:10:19 +01:00
if ( backgroundTexture ! = null ) {
var background = batch . Add ( backgroundTexture , pos , backgroundColor , 0 , orig , sc , SpriteEffects . None , layerDepth ) ;
2021-12-03 20:59:56 +01:00
items ? . Add ( background ) ;
}
2021-12-03 19:57:26 +01:00
if ( r1 ! = Rectangle . Empty ) {
2021-12-03 21:10:19 +01:00
var o1 = batch . Add ( overlayTexture . Texture , pos , r1 , overlayColor , 0 , orig , sc , SpriteEffects . None , od ) ;
2021-12-03 19:57:26 +01:00
items ? . Add ( o1 ) ;
}
if ( r2 ! = Rectangle . Empty ) {
2021-12-03 21:10:19 +01:00
var o2 = batch . Add ( overlayTexture . Texture , pos , r2 , overlayColor , 0 , orig , sc , SpriteEffects . None , od ) ;
2021-12-03 19:57:26 +01:00
items ? . Add ( o2 ) ;
}
if ( r3 ! = Rectangle . Empty ) {
2021-12-03 21:10:19 +01:00
var o3 = batch . Add ( overlayTexture . Texture , pos , r3 , overlayColor , 0 , orig , sc , SpriteEffects . None , od ) ;
2021-12-03 19:57:26 +01:00
items ? . Add ( o3 ) ;
}
if ( r4 ! = Rectangle . Empty ) {
2021-12-03 21:10:19 +01:00
var o4 = batch . Add ( overlayTexture . Texture , pos , r4 , overlayColor , 0 , orig , sc , SpriteEffects . None , od ) ;
2021-12-03 19:57:26 +01:00
items ? . Add ( o4 ) ;
}
2021-11-27 15:03:30 +01:00
}
2022-11-14 12:59:35 +01:00
/// <inheritdoc cref="DrawExtendedAutoTile(Microsoft.Xna.Framework.Graphics.SpriteBatch,Microsoft.Xna.Framework.Vector2,MLEM.Textures.TextureRegion,Func{int,MLEM.Textures.TextureRegion},MLEM.Graphics.AutoTiling.ConnectsTo,Microsoft.Xna.Framework.Color,Microsoft.Xna.Framework.Color,System.Nullable{Microsoft.Xna.Framework.Vector2},System.Nullable{Microsoft.Xna.Framework.Vector2},float,float)"/>
public static void AddExtendedAutoTile ( StaticSpriteBatch batch , Vector2 pos , TextureRegion backgroundTexture , Func < int , TextureRegion > overlayTextures , ConnectsTo connectsTo , Color backgroundColor , Color overlayColor , Vector2 ? origin = null , Vector2 ? scale = null , float layerDepth = 0 , float overlayDepthOffset = 0 , ICollection < StaticSpriteBatch . Item > items = null ) {
var orig = origin ? ? Vector2 . Zero ;
var sc = scale ? ? Vector2 . One ;
var od = layerDepth + overlayDepthOffset ;
var ( xUl , xUr , xDl , xDr ) = AutoTiling . CalculateExtendedAutoTileOffsets ( connectsTo ) ;
if ( backgroundTexture ! = null ) {
var background = batch . Add ( backgroundTexture , pos , backgroundColor , 0 , orig , sc , SpriteEffects . None , layerDepth ) ;
items ? . Add ( background ) ;
}
if ( xUl > = 0 ) {
var o1 = batch . Add ( overlayTextures ( xUl ) , pos , overlayColor , 0 , orig , sc , SpriteEffects . None , od ) ;
items ? . Add ( o1 ) ;
}
if ( xUr > = 0 ) {
var o2 = batch . Add ( overlayTextures ( xUr ) , pos , overlayColor , 0 , orig , sc , SpriteEffects . None , od ) ;
items ? . Add ( o2 ) ;
}
if ( xDl > = 0 ) {
var o3 = batch . Add ( overlayTextures ( xDl ) , pos , overlayColor , 0 , orig , sc , SpriteEffects . None , od ) ;
items ? . Add ( o3 ) ;
}
if ( xDr > = 0 ) {
var o4 = batch . Add ( overlayTextures ( xDr ) , pos , overlayColor , 0 , orig , sc , SpriteEffects . None , od ) ;
items ? . Add ( o4 ) ;
}
}
2021-11-27 14:27:56 +01:00
private static ( Vector2 , Rectangle , Vector2 , Rectangle , Vector2 , Rectangle , Vector2 , Rectangle ) CalculateAutoTile ( Vector2 pos , Rectangle textureRegion , ConnectsTo connectsTo , Vector2 scale ) {
2019-08-06 22:31:57 +02:00
var up = connectsTo ( 0 , - 1 ) ;
var down = connectsTo ( 0 , 1 ) ;
var left = connectsTo ( - 1 , 0 ) ;
var right = connectsTo ( 1 , 0 ) ;
var xUl = up & & left ? connectsTo ( - 1 , - 1 ) ? 0 : 4 : left ? 1 : up ? 3 : 2 ;
var xUr = up & & right ? connectsTo ( 1 , - 1 ) ? 0 : 4 : right ? 1 : up ? 3 : 2 ;
var xDl = down & & left ? connectsTo ( - 1 , 1 ) ? 0 : 4 : left ? 1 : down ? 3 : 2 ;
var xDr = down & & right ? connectsTo ( 1 , 1 ) ? 0 : 4 : right ? 1 : down ? 3 : 2 ;
2022-06-24 14:01:26 +02:00
var ( w , h ) = ( textureRegion . Width , textureRegion . Height ) ;
var ( w2 , h2 ) = ( w / 2 , h / 2 ) ;
2021-11-27 14:27:56 +01:00
return (
new Vector2 ( pos . X , pos . Y ) , new Rectangle ( textureRegion . X + xUl * w , textureRegion . Y , w2 , h2 ) ,
new Vector2 ( pos . X + w2 * scale . X , pos . Y ) , new Rectangle ( textureRegion . X + w2 + xUr * w , textureRegion . Y , w2 , h2 ) ,
new Vector2 ( pos . X , pos . Y + h2 * scale . Y ) , new Rectangle ( textureRegion . X + xDl * w , textureRegion . Y + h2 , w2 , h2 ) ,
new Vector2 ( pos . X + w2 * scale . X , pos . Y + h2 * scale . Y ) , new Rectangle ( textureRegion . X + w2 + xDr * w , textureRegion . Y + h2 , w2 , h2 ) ) ;
2019-08-06 22:31:57 +02:00
}
2022-11-14 12:59:35 +01:00
private static ( int , int , int , int ) CalculateExtendedAutoTileOffsets ( ConnectsTo connectsTo ) {
2021-11-27 15:03:30 +01:00
var up = connectsTo ( 0 , - 1 ) ;
var down = connectsTo ( 0 , 1 ) ;
var left = connectsTo ( - 1 , 0 ) ;
var right = connectsTo ( 1 , 0 ) ;
2022-11-14 12:59:35 +01:00
return (
up & & left ? connectsTo ( - 1 , - 1 ) ? - 1 : 12 : left ? 0 : up ? 8 : 4 ,
up & & right ? connectsTo ( 1 , - 1 ) ? - 1 : 13 : right ? 1 : up ? 9 : 5 ,
down & & left ? connectsTo ( - 1 , 1 ) ? - 1 : 14 : left ? 2 : down ? 10 : 6 ,
down & & right ? connectsTo ( 1 , 1 ) ? - 1 : 15 : right ? 3 : down ? 11 : 7 ) ;
}
private static ( Rectangle , Rectangle , Rectangle , Rectangle ) CalculateExtendedAutoTile ( Rectangle textureRegion , ConnectsTo connectsTo ) {
var ( xUl , xUr , xDl , xDr ) = AutoTiling . CalculateExtendedAutoTileOffsets ( connectsTo ) ;
2022-06-24 14:01:26 +02:00
var ( w , h ) = ( textureRegion . Width , textureRegion . Height ) ;
2021-11-27 15:03:30 +01:00
return (
2021-12-03 19:10:10 +01:00
xUl < 0 ? Rectangle . Empty : new Rectangle ( textureRegion . X + xUl * w , textureRegion . Y , w , h ) ,
xUr < 0 ? Rectangle . Empty : new Rectangle ( textureRegion . X + xUr * w , textureRegion . Y , w , h ) ,
xDl < 0 ? Rectangle . Empty : new Rectangle ( textureRegion . X + xDl * w , textureRegion . Y , w , h ) ,
xDr < 0 ? Rectangle . Empty : new Rectangle ( textureRegion . X + xDr * w , textureRegion . Y , w , h ) ) ;
2021-11-27 15:03:30 +01:00
}
2020-05-21 17:21:34 +02:00
/// <summary>
/// A delegate function that determines if a given offset position connects to an auto-tile location.
/// </summary>
/// <param name="xOff">The x offset</param>
/// <param name="yOff">The y offset</param>
2019-08-06 22:31:57 +02:00
public delegate bool ConnectsTo ( int xOff , int yOff ) ;
}
2022-06-17 18:23:47 +02:00
}