Skip to content

Commit

Permalink
Adding tests for the nineslice functions
Browse files Browse the repository at this point in the history
  • Loading branch information
davesmith00000 committed Nov 23, 2024
1 parent c759458 commit 8031533
Show file tree
Hide file tree
Showing 3 changed files with 111 additions and 136 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)))
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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)
)
)
)
Expand Down

0 comments on commit 8031533

Please sign in to comment.