diff --git a/indigo/indigo/src/main/scala/indigo/shared/shader/library/TileAndStretch.scala b/indigo/indigo/src/main/scala/indigo/shared/shader/library/TileAndStretch.scala index 1d46fcc51..227d2fdda 100644 --- a/indigo/indigo/src/main/scala/indigo/shared/shader/library/TileAndStretch.scala +++ b/indigo/indigo/src/main/scala/indigo/shared/shader/library/TileAndStretch.scala @@ -61,13 +61,20 @@ object TileAndStretch: ): vec2 = // Delegates - def _mapUVToRegionAndTile: (vec2, vec4, vec4, vec2, vec2) => vec2 = - (uv, entityRegion, textureRegion, unscaledEntityRegionSize, unscaledTextureRegionSize) => - mapUVToRegionAndTile(uv, entityRegion, textureRegion, unscaledEntityRegionSize, unscaledTextureRegionSize) + def _mapUVToRegionUV: (vec2, vec4) => vec2 = + (uv, entityRegionUV) => mapUVToRegionUV(uv, entityRegionUV) - def _normalisedUVsToTextureCoords: (vec2, vec4, vec2, vec2) => vec2 = - (uv, textureRegion, channelPos, channelSize) => - normalisedUVsToTextureCoords(uv, textureRegion, channelPos, channelSize) + def _tileUV: (vec2, vec2, vec2) => vec2 = + (regionalUV, unscaledEntityRegionSize, unscaledTextureRegionSize) => + tileUV(regionalUV, unscaledEntityRegionSize, unscaledTextureRegionSize) + + def mapUVToRegionAndTile: (vec2, vec4, vec2, vec2) => vec2 = + (uv, entityRegionUV, unscaledEntityRegionSize, unscaledTextureRegionSize) => + _tileUV(_mapUVToRegionUV(uv, entityRegionUV), unscaledEntityRegionSize, unscaledTextureRegionSize) + + def _tiledUVsToTextureCoords: (vec2, vec4, vec2, vec2) => vec2 = + (tiledUVs, textureRegion, channelPos, channelSize) => + tiledUVsToTextureCoords(tiledUVs, textureRegion, channelPos, channelSize) // Scale the coords and the size of the region by the size of the entity/texture def _regionToUV: (vec4, vec2) => vec4 = (region, size) => regionToUV(region, size) @@ -190,67 +197,81 @@ object TileAndStretch: val coords: vec2 = if _regionContainsUV(entityRegionTLUV, originalUV) then val uv = - _mapUVToRegionAndTile(originalUV, entityRegionTLUV, textureRegionTLUV, entityRegionTL.zw, textureRegionTL.zw) - _normalisedUVsToTextureCoords(uv, textureRegionTLUV, channelPos, channelSize) + mapUVToRegionAndTile(originalUV, entityRegionTLUV, entityRegionTL.zw, textureRegionTL.zw) + _tiledUVsToTextureCoords(uv, textureRegionTLUV, channelPos, channelSize) else if _regionContainsUV(entityRegionTMUV, originalUV) then val uv = - _mapUVToRegionAndTile(originalUV, entityRegionTMUV, textureRegionTMUV, entityRegionTM.zw, textureRegionTM.zw) - _normalisedUVsToTextureCoords(uv, textureRegionTMUV, channelPos, channelSize) + mapUVToRegionAndTile(originalUV, entityRegionTMUV, entityRegionTM.zw, textureRegionTM.zw) + _tiledUVsToTextureCoords(uv, textureRegionTMUV, channelPos, channelSize) else if _regionContainsUV(entityRegionTRUV, originalUV) then val uv = - _mapUVToRegionAndTile(originalUV, entityRegionTRUV, textureRegionTRUV, entityRegionTR.zw, textureRegionTR.zw) - _normalisedUVsToTextureCoords(uv, textureRegionTRUV, channelPos, channelSize) + mapUVToRegionAndTile(originalUV, entityRegionTRUV, entityRegionTR.zw, textureRegionTR.zw) + _tiledUVsToTextureCoords(uv, textureRegionTRUV, channelPos, channelSize) else if _regionContainsUV(entityRegionMLUV, originalUV) then val uv = - _mapUVToRegionAndTile(originalUV, entityRegionMLUV, textureRegionMLUV, entityRegionML.zw, textureRegionML.zw) - _normalisedUVsToTextureCoords(uv, textureRegionMLUV, channelPos, channelSize) - else if regionContainsUV(entityRegionMMUV, originalUV) then + mapUVToRegionAndTile(originalUV, entityRegionMLUV, entityRegionML.zw, textureRegionML.zw) + _tiledUVsToTextureCoords(uv, textureRegionMLUV, channelPos, channelSize) + else if _regionContainsUV(entityRegionMMUV, originalUV) then val uv = - _mapUVToRegionAndTile(originalUV, entityRegionMMUV, textureRegionMMUV, entityRegionMM.zw, textureRegionMM.zw) - _normalisedUVsToTextureCoords(uv, textureRegionMMUV, channelPos, channelSize) + mapUVToRegionAndTile(originalUV, entityRegionMMUV, entityRegionMM.zw, textureRegionMM.zw) + _tiledUVsToTextureCoords(uv, textureRegionMMUV, channelPos, channelSize) else if _regionContainsUV(entityRegionMRUV, originalUV) then val uv = - _mapUVToRegionAndTile(originalUV, entityRegionMRUV, textureRegionMRUV, entityRegionMR.zw, textureRegionMR.zw) - _normalisedUVsToTextureCoords(uv, textureRegionMRUV, channelPos, channelSize) + mapUVToRegionAndTile(originalUV, entityRegionMRUV, entityRegionMR.zw, textureRegionMR.zw) + _tiledUVsToTextureCoords(uv, textureRegionMRUV, channelPos, channelSize) else if _regionContainsUV(entityRegionBLUV, originalUV) then val uv = - _mapUVToRegionAndTile(originalUV, entityRegionBLUV, textureRegionBLUV, entityRegionBL.zw, textureRegionBL.zw) - _normalisedUVsToTextureCoords(uv, textureRegionBLUV, channelPos, channelSize) + mapUVToRegionAndTile(originalUV, entityRegionBLUV, entityRegionBL.zw, textureRegionBL.zw) + _tiledUVsToTextureCoords(uv, textureRegionBLUV, channelPos, channelSize) else if _regionContainsUV(entityRegionBMUV, originalUV) then val uv = - _mapUVToRegionAndTile(originalUV, entityRegionBMUV, textureRegionBMUV, entityRegionBM.zw, textureRegionBM.zw) - _normalisedUVsToTextureCoords(uv, textureRegionBMUV, channelPos, channelSize) + mapUVToRegionAndTile(originalUV, entityRegionBMUV, entityRegionBM.zw, textureRegionBM.zw) + _tiledUVsToTextureCoords(uv, textureRegionBMUV, channelPos, channelSize) else if _regionContainsUV(entityRegionBRUV, originalUV) then val uv = - _mapUVToRegionAndTile(originalUV, entityRegionBRUV, textureRegionBRUV, entityRegionBR.zw, textureRegionBR.zw) - _normalisedUVsToTextureCoords(uv, textureRegionBRUV, channelPos, channelSize) + mapUVToRegionAndTile(originalUV, entityRegionBRUV, entityRegionBR.zw, textureRegionBR.zw) + _tiledUVsToTextureCoords(uv, textureRegionBRUV, channelPos, channelSize) else vec2(0.0f) coords // Helper functions - inline def mapUVToRegionAndTile( + // Map the original UV to a UV in the region of the entity, so that 0.0 is the start of the region and 1.0 is the end + inline def mapUVToRegionUV( uv: vec2, - entityRegion: vec4, - textureRegion: vec4, + entityRegionUV: vec4 + ): vec2 = + (uv - entityRegionUV.xy) / entityRegionUV.zw + + // Tile the UVs in the region of the entity so that the texture tiles correctly + inline def tileUV( + regionalUV: vec2, unscaledEntityRegionSize: vec2, unscaledTextureRegionSize: vec2 ): vec2 = - val regionalUV = (uv - entityRegion.xy) / entityRegion.zw - // scaleToEntityRegion(uv, entityRegion) - val tiledUV = fract(regionalUV * (unscaledEntityRegionSize / unscaledTextureRegionSize)) - // tiledUVs(regionalUV, unscaledEntityRegionSize, unscaledTextureRegionSize) - - tiledUV - - inline def normalisedUVsToTextureCoords(uv: vec2, textureRegion: vec4, channelPos: vec2, channelSize: vec2): vec2 = - channelPos + ((uv * textureRegion.zw + textureRegion.xy) * channelSize) + fract(regionalUV * (unscaledEntityRegionSize / unscaledTextureRegionSize)) + + /** Convert tiled UVs to texture coords + * + * This is deceptively complicated. At this point we have UVs that are tiling from 0.0 to 1.0 within the region, at + * the correct ratio for the segment of the nine slice we care about. We happens here is that those 0-1 UVs are + * translated to the UVs in the texture for the region we care about, then, the channel position and size are used to + * convert that to the correct texture coords. + */ + inline def tiledUVsToTextureCoords( + tiledUVs: vec2, + textureRegion: vec4, + channelPos: vec2, + channelSize: vec2 + ): vec2 = + channelPos + ((textureRegion.xy + (tiledUVs * textureRegion.zw)) * channelSize) - // Scale the coords and the size of the region by the size of the entity/texture + // Scale the coords and the size of the region by the size of the entity/texture, to get the region in UV space inline def regionToUV(region: vec4, size: vec2): vec4 = region / vec4(size, size) + // Check if a UV is inside a region where the region is in UV space inline def regionContainsUV(region: vec4, uv: vec2): Boolean = uv.x >= region.x && uv.x < region.x + region.z && uv.y >= region.y && uv.y < region.y + region.w diff --git a/indigo/indigo/src/test/scala/indigo/shared/shader/library/TileAndStretchTests.scala b/indigo/indigo/src/test/scala/indigo/shared/shader/library/TileAndStretchTests.scala index 1cc5abd9d..cdefc373d 100644 --- a/indigo/indigo/src/test/scala/indigo/shared/shader/library/TileAndStretchTests.scala +++ b/indigo/indigo/src/test/scala/indigo/shared/shader/library/TileAndStretchTests.scala @@ -78,117 +78,71 @@ class TileAndStretchTests extends munit.FunSuite { } - test("should correctly render tile and stretch code") { + test("regionToUV") { + val actual = + TileAndStretch.regionToUV(vec4(vec2(16.0f), vec2(32.0f)), vec2(64.0f)) - inline def fragment = - Shader { - import TileAndStretch.* + val expected = + vec4(vec2(0.25f), vec2(0.5f)) - // Delegates - val _tileAndStretchChannel: (Int, vec4, sampler2D.type, vec2, vec2, vec2, vec2, vec2, vec4) => vec4 = - tileAndStretchChannel - - val fillType: Int = 0 - val fallback: vec4 = vec4(1.0) - val srcChannel: sampler2D.type = sampler2D - val channelPos: vec2 = vec2(2.0) - val channelSize: vec2 = vec2(3.0) - val uv: vec2 = vec2(4.0) - val entitySize: vec2 = vec2(5.0) - val textureSize: vec2 = vec2(6.0) - val nineSliceCenter: vec4 = vec4(7.0) - - _tileAndStretchChannel( - fillType, // env.FILLTYPE.toInt, - fallback, // env.CHANNEL_0, - srcChannel, // env.SRC_CHANNEL, - channelPos, // env.CHANNEL_0_POSITION, - channelSize, // env.CHANNEL_0_SIZE, - uv, // env.UV, - entitySize, // env.SIZE, - textureSize, // env.TEXTURE_SIZE - nineSliceCenter // env.NINE_SLICE_CENTER - ) - } + assertEquals(actual, expected) + } - val actual = - fragment.toGLSL[WebGL2].toOutput.code + test("regionContainsUV") { + val uvRegion = TileAndStretch.regionToUV(vec4(vec2(16.0f), vec2(32.0f)), vec2(64.0f)) - // println(actual) + assert(clue(!TileAndStretch.regionContainsUV(uvRegion, vec2(0.0f, 0.0f)))) + assert(clue(!TileAndStretch.regionContainsUV(uvRegion, vec2(0.24f, 0.24f)))) + assert(clue(TileAndStretch.regionContainsUV(uvRegion, vec2(0.25f, 0.25f)))) + assert(clue(TileAndStretch.regionContainsUV(uvRegion, vec2(0.5f, 0.5f)))) + assert(clue(TileAndStretch.regionContainsUV(uvRegion, vec2(0.74f, 0.74f)))) + assert(clue(!TileAndStretch.regionContainsUV(uvRegion, vec2(0.75f, 0.75f)))) + assert(clue(!TileAndStretch.regionContainsUV(uvRegion, vec2(1.0f, 1.0f)))) + } - assertEquals( - actual, - s""" - |vec4 def0(in int fillType,in vec4 fallback,in sampler2D srcChannel,in vec2 channelPos,in vec2 channelSize,in vec2 uv,in vec2 entitySize,in vec2 textureSize){ - | vec4 val0; - | switch(fillType){ - | case 1: - | val0=texture(srcChannel,channelPos+(uv*channelSize)); - | break; - | case 2: - | val0=texture(srcChannel,channelPos+((fract(uv*(entitySize/textureSize)))*channelSize)); - | break; - | case 3: - | // maddness ensues - | break; - | default: - | val0=fallback; - | break; - | } - | return val0; - |} - |int fillType=0; - |vec4 fallback=vec4(1.0); - |sampler2D srcChannel=sampler2D; - |vec2 channelPos=vec2(2.0); - |vec2 channelSize=vec2(3.0); - |vec2 uv=vec2(4.0); - |vec2 entitySize=vec2(5.0); - |vec2 textureSize=vec2(6.0); - |def0(fillType,fallback,srcChannel,channelPos,channelSize,uv,entitySize,textureSize); - |""".stripMargin.trim - ) + test("mapUVToRegionUV") { + val entityRegionUV = TileAndStretch.regionToUV(vec4(vec2(16.0f), vec2(32.0f)), vec2(64.0f)) + def forUV(uv: vec2): vec2 = + TileAndStretch.mapUVToRegionUV(uv, entityRegionUV) + + assertEquals(clue(forUV(vec2(0.0f))), clue(vec2(-0.5f))) + assertEquals(clue(forUV(vec2(0.25f))), clue(vec2(0.0f))) + assertEquals(clue(forUV(vec2(0.5f))), clue(vec2(0.5f))) + assertEquals(clue(forUV(vec2(0.75f))), clue(vec2(1.0f))) + assertEquals(clue(forUV(vec2(1.0f))), clue(vec2(1.5f))) } - test("should correctly render nineslice code") { + test("tileUV") { + val unscaledEntityRegionSize = vec2(64.0f) + val unscaledTextureRegionSize = vec2(32.0f) - inline def fragment = - Shader { - import TileAndStretch.* + // Maybe split this function in two? - val uv: vec2 = vec2(1.0) - val channelPos: vec2 = vec2(2.0) - val channelSize: vec2 = vec2(3.0) - val entitySize: vec2 = vec2(128.0) - val textureSize: vec2 = vec2(64.0) - val nineSliceCenter: vec4 = vec4(16.0f, 16.0f, 32.0f, 32.0f) - - def doNineSlice(): vec2 = - nineSliceUVs( - uv, // env.UV, - channelPos, // env.CHANNEL_0_POSITION, - channelSize, // env.CHANNEL_0_SIZE, - entitySize, // env.SIZE, - textureSize, // env.TEXTURE_SIZE - nineSliceCenter // env.NINE_SLICE_CENTER - ) - - doNineSlice() - } + def forUV(uv: vec2): vec2 = + TileAndStretch.tileUV(uv, unscaledEntityRegionSize, unscaledTextureRegionSize) - val actual = - fragment.toGLSL[WebGL2].toOutput.code + assertEquals(clue(forUV(vec2(0.0f))), clue(vec2(0.0f))) + assertEquals(clue(forUV(vec2(0.25f))), clue(vec2(0.5f))) + assertEquals(clue(forUV(vec2(0.5f))), clue(vec2(0.0f))) + assertEquals(clue(forUV(vec2(0.75f))), clue(vec2(0.5f))) + // We never see 1.0 because that's the wrap point. In practice this doesnt matter. + assertEquals(clue(forUV(vec2(1.0f))), clue(vec2(0.0f))) + } - // println(actual) + test("tiledUVsToTextureCoords") { + val channelSize = vec2(64.0f) + val textureRegion = TileAndStretch.regionToUV(vec4(vec2(16.0f), vec2(32.0f)), channelSize) + val channelPos = vec2(10.0f) - assertEquals( - actual, - s""" - |fish - |""".stripMargin.trim - ) + def forUV(tileUV: vec2): vec2 = + TileAndStretch.tiledUVsToTextureCoords(tileUV, textureRegion, channelPos, channelSize) + assertEquals(clue(forUV(vec2(0.0f))), clue(vec2(16.0f) + vec2(10.0f))) + assertEquals(clue(forUV(vec2(0.25f))), clue(vec2(24.0f) + vec2(10.0f))) + assertEquals(clue(forUV(vec2(0.5f))), clue(vec2(32.0f) + vec2(10.0f))) + assertEquals(clue(forUV(vec2(0.75f))), clue(vec2(40.0f) + vec2(10.0f))) + assertEquals(clue(forUV(vec2(1.0f))), clue(vec2(48.0f) + vec2(10.0f))) } } diff --git a/indigo/sandbox/src/main/scala/com/example/sandbox/scenes/TextureTileScene.scala b/indigo/sandbox/src/main/scala/com/example/sandbox/scenes/TextureTileScene.scala index 73c9c4835..cf5abee53 100644 --- a/indigo/sandbox/src/main/scala/com/example/sandbox/scenes/TextureTileScene.scala +++ b/indigo/sandbox/src/main/scala/com/example/sandbox/scenes/TextureTileScene.scala @@ -69,7 +69,7 @@ object TextureTileScene extends Scene[SandboxStartupData, SandboxGameModel, Sand boxSize(context.frame.time.running), boxSize(context.frame.time.running), Material.Bitmap(SandboxAssets.nineSlice).nineSlice(Rectangle(16, 16, 32, 32)) - ).moveTo(125, 125) + ).moveTo(10, 50) ) ) )