mirror of
https://github.com/curioustorvald/Terrarum.git
synced 2026-06-16 21:44:05 +09:00
Tile -> Block && Map -> World
This commit is contained in:
595
src/net/torvald/terrarum/worlddrawer/BlocksDrawer.kt
Normal file
595
src/net/torvald/terrarum/worlddrawer/BlocksDrawer.kt
Normal file
@@ -0,0 +1,595 @@
|
||||
package net.torvald.terrarum.worlddrawer
|
||||
|
||||
import net.torvald.terrarum.gameworld.GameWorld
|
||||
import net.torvald.terrarum.gameworld.PairedMapLayer
|
||||
import net.torvald.terrarum.blockproperties.Block
|
||||
import net.torvald.terrarum.blockproperties.BlockCodex
|
||||
import com.jme3.math.FastMath
|
||||
import net.torvald.terrarum.*
|
||||
import net.torvald.terrarum.gameactors.roundInt
|
||||
import net.torvald.terrarum.worlddrawer.WorldCamera.x
|
||||
import net.torvald.terrarum.worlddrawer.WorldCamera.y
|
||||
import net.torvald.terrarum.worlddrawer.WorldCamera.height
|
||||
import net.torvald.terrarum.worlddrawer.WorldCamera.width
|
||||
import org.lwjgl.opengl.GL11
|
||||
import org.newdawn.slick.*
|
||||
|
||||
|
||||
/**
|
||||
* Created by minjaesong on 16-01-19.
|
||||
*/
|
||||
object BlocksDrawer {
|
||||
private val world: GameWorld = Terrarum.ingame!!.world
|
||||
private val TILE_SIZE = FeaturesDrawer.TILE_SIZE
|
||||
private val TILE_SIZEF = FeaturesDrawer.TILE_SIZE.toFloat()
|
||||
|
||||
// TODO modular
|
||||
val tilesTerrain = SpriteSheet(ModMgr.getPath("basegame", "blocks/terrain.tga.gz"), TILE_SIZE, TILE_SIZE)
|
||||
// Slick has some weird quirks with PNG's transparency. I'm using 32-bit targa here.
|
||||
// -> PNG transparency issue seems to be fixed (look at my customised ImageDataFactory), but
|
||||
// tga.gz is smaller than png, so I'd rather keep it
|
||||
val tilesWire = SpriteSheet(ModMgr.getPath("basegame", "blocks/wire.tga.gz"), TILE_SIZE, TILE_SIZE)
|
||||
|
||||
|
||||
val breakAnimSteps = 10
|
||||
|
||||
val WALL = GameWorld.WALL
|
||||
val TERRAIN = GameWorld.TERRAIN
|
||||
val WIRE = GameWorld.WIRE
|
||||
|
||||
private val NEARBY_TILE_KEY_UP = 0
|
||||
private val NEARBY_TILE_KEY_RIGHT = 1
|
||||
private val NEARBY_TILE_KEY_DOWN = 2
|
||||
private val NEARBY_TILE_KEY_LEFT = 3
|
||||
|
||||
private val NEARBY_TILE_CODE_UP = 1
|
||||
private val NEARBY_TILE_CODE_RIGHT = 2
|
||||
private val NEARBY_TILE_CODE_DOWN = 4
|
||||
private val NEARBY_TILE_CODE_LEFT = 8
|
||||
|
||||
/**
|
||||
* Connectivity group 01 : artificial tiles
|
||||
* It holds different shading rule to discriminate with group 02, index 0 is single tile.
|
||||
* These are the tiles that only connects to itself, will not connect to colour variants
|
||||
*/
|
||||
val TILES_CONNECT_SELF = arrayListOf(
|
||||
Block.ICE_MAGICAL,
|
||||
Block.GLASS_CRUDE,
|
||||
Block.GLASS_CLEAN,
|
||||
Block.ILLUMINATOR_BLACK,
|
||||
Block.ILLUMINATOR_BLUE,
|
||||
Block.ILLUMINATOR_BROWN,
|
||||
Block.ILLUMINATOR_CYAN,
|
||||
Block.ILLUMINATOR_FUCHSIA,
|
||||
Block.ILLUMINATOR_GREEN,
|
||||
Block.ILLUMINATOR_GREEN_DARK,
|
||||
Block.ILLUMINATOR_GREY_DARK,
|
||||
Block.ILLUMINATOR_GREY_LIGHT,
|
||||
Block.ILLUMINATOR_GREY_MED,
|
||||
Block.ILLUMINATOR_ORANGE,
|
||||
Block.ILLUMINATOR_PURPLE,
|
||||
Block.ILLUMINATOR_RED,
|
||||
Block.ILLUMINATOR_TAN,
|
||||
Block.ILLUMINATOR_WHITE,
|
||||
Block.ILLUMINATOR_YELLOW,
|
||||
Block.ILLUMINATOR_BLACK_OFF,
|
||||
Block.ILLUMINATOR_BLUE_OFF,
|
||||
Block.ILLUMINATOR_BROWN_OFF,
|
||||
Block.ILLUMINATOR_CYAN_OFF,
|
||||
Block.ILLUMINATOR_FUCHSIA_OFF,
|
||||
Block.ILLUMINATOR_GREEN_OFF,
|
||||
Block.ILLUMINATOR_GREEN_DARK_OFF,
|
||||
Block.ILLUMINATOR_GREY_DARK_OFF,
|
||||
Block.ILLUMINATOR_GREY_LIGHT_OFF,
|
||||
Block.ILLUMINATOR_GREY_MED_OFF,
|
||||
Block.ILLUMINATOR_ORANGE_OFF,
|
||||
Block.ILLUMINATOR_PURPLE_OFF,
|
||||
Block.ILLUMINATOR_RED_OFF,
|
||||
Block.ILLUMINATOR_TAN_OFF,
|
||||
Block.ILLUMINATOR_WHITE_OFF,
|
||||
Block.ILLUMINATOR_YELLOW,
|
||||
Block.SANDSTONE,
|
||||
Block.SANDSTONE_BLACK,
|
||||
Block.SANDSTONE_DESERT,
|
||||
Block.SANDSTONE_RED,
|
||||
Block.SANDSTONE_WHITE,
|
||||
Block.SANDSTONE_GREEN,
|
||||
Block.DAYLIGHT_CAPACITOR
|
||||
)
|
||||
|
||||
/**
|
||||
* Connectivity group 02 : natural tiles
|
||||
* It holds different shading rule to discriminate with group 01, index 0 is middle tile.
|
||||
*/
|
||||
val TILES_CONNECT_MUTUAL = arrayListOf(
|
||||
Block.STONE,
|
||||
Block.STONE_QUARRIED,
|
||||
Block.STONE_TILE_WHITE,
|
||||
Block.STONE_BRICKS,
|
||||
Block.DIRT,
|
||||
Block.GRASS,
|
||||
Block.PLANK_BIRCH,
|
||||
Block.PLANK_BLOODROSE,
|
||||
Block.PLANK_EBONY,
|
||||
Block.PLANK_NORMAL,
|
||||
Block.SAND,
|
||||
Block.SAND_WHITE,
|
||||
Block.SAND_RED,
|
||||
Block.SAND_DESERT,
|
||||
Block.SAND_BLACK,
|
||||
Block.SAND_GREEN,
|
||||
Block.GRAVEL,
|
||||
Block.GRAVEL_GREY,
|
||||
Block.SNOW,
|
||||
Block.ICE_NATURAL,
|
||||
Block.ORE_COPPER,
|
||||
Block.ORE_IRON,
|
||||
Block.ORE_GOLD,
|
||||
Block.ORE_SILVER,
|
||||
Block.ORE_ILMENITE,
|
||||
Block.ORE_AURICHALCUM,
|
||||
|
||||
Block.WATER,
|
||||
Block.WATER_1,
|
||||
Block.WATER_2,
|
||||
Block.WATER_3,
|
||||
Block.WATER_4,
|
||||
Block.WATER_5,
|
||||
Block.WATER_6,
|
||||
Block.WATER_7,
|
||||
Block.WATER_8,
|
||||
Block.WATER_9,
|
||||
Block.WATER_10,
|
||||
Block.WATER_11,
|
||||
Block.WATER_12,
|
||||
Block.WATER_13,
|
||||
Block.WATER_14,
|
||||
Block.WATER_15,
|
||||
Block.LAVA,
|
||||
Block.LAVA_1,
|
||||
Block.LAVA_2,
|
||||
Block.LAVA_3,
|
||||
Block.LAVA_4,
|
||||
Block.LAVA_5,
|
||||
Block.LAVA_6,
|
||||
Block.LAVA_7,
|
||||
Block.LAVA_8,
|
||||
Block.LAVA_9,
|
||||
Block.LAVA_10,
|
||||
Block.LAVA_11,
|
||||
Block.LAVA_12,
|
||||
Block.LAVA_13,
|
||||
Block.LAVA_14,
|
||||
Block.LAVA_15
|
||||
)
|
||||
|
||||
/**
|
||||
* Torches, levers, switches, ...
|
||||
*/
|
||||
val TILES_WALL_STICKER = arrayListOf(
|
||||
Block.TORCH,
|
||||
Block.TORCH_FROST,
|
||||
Block.TORCH_OFF,
|
||||
Block.TORCH_FROST_OFF
|
||||
)
|
||||
|
||||
/**
|
||||
* platforms, ...
|
||||
*/
|
||||
val TILES_WALL_STICKER_CONNECT_SELF = arrayListOf(
|
||||
Block.PLATFORM_BIRCH,
|
||||
Block.PLATFORM_BLOODROSE,
|
||||
Block.PLATFORM_EBONY,
|
||||
Block.PLATFORM_STONE,
|
||||
Block.PLATFORM_WOODEN
|
||||
)
|
||||
|
||||
/**
|
||||
* Tiles that half-transparent and has hue
|
||||
* will blend colour using colour multiplication
|
||||
* i.e. red hues get lost if you dive into the water
|
||||
*/
|
||||
val TILES_BLEND_MUL = arrayListOf(
|
||||
Block.WATER,
|
||||
Block.WATER_1,
|
||||
Block.WATER_2,
|
||||
Block.WATER_3,
|
||||
Block.WATER_4,
|
||||
Block.WATER_5,
|
||||
Block.WATER_6,
|
||||
Block.WATER_7,
|
||||
Block.WATER_8,
|
||||
Block.WATER_9,
|
||||
Block.WATER_10,
|
||||
Block.WATER_11,
|
||||
Block.WATER_12,
|
||||
Block.WATER_13,
|
||||
Block.WATER_14,
|
||||
Block.WATER_15,
|
||||
Block.LAVA,
|
||||
Block.LAVA_1,
|
||||
Block.LAVA_2,
|
||||
Block.LAVA_3,
|
||||
Block.LAVA_4,
|
||||
Block.LAVA_5,
|
||||
Block.LAVA_6,
|
||||
Block.LAVA_7,
|
||||
Block.LAVA_8,
|
||||
Block.LAVA_9,
|
||||
Block.LAVA_10,
|
||||
Block.LAVA_11,
|
||||
Block.LAVA_12,
|
||||
Block.LAVA_13,
|
||||
Block.LAVA_14,
|
||||
Block.LAVA_15
|
||||
)
|
||||
|
||||
fun update() {
|
||||
val player = Terrarum.ingame!!.player
|
||||
}
|
||||
|
||||
val wallOverlayColour = Color(2f/3f, 2f/3f, 2f/3f, 1f)
|
||||
|
||||
fun renderWall(g: Graphics) {
|
||||
/**
|
||||
* render to camera
|
||||
*/
|
||||
blendNormal()
|
||||
|
||||
tilesTerrain.startUse()
|
||||
drawTiles(g, WALL, false)
|
||||
tilesTerrain.endUse()
|
||||
|
||||
blendMul()
|
||||
|
||||
g.color = wallOverlayColour
|
||||
g.fillRect(WorldCamera.x.toFloat(), WorldCamera.y.toFloat(),
|
||||
WorldCamera.width.toFloat() + 1, WorldCamera.height.toFloat() + 1
|
||||
)
|
||||
|
||||
blendNormal()
|
||||
}
|
||||
|
||||
fun renderTerrain(g: Graphics) {
|
||||
/**
|
||||
* render to camera
|
||||
*/
|
||||
blendNormal()
|
||||
|
||||
tilesTerrain.startUse()
|
||||
drawTiles(g, TERRAIN, false) // regular tiles
|
||||
tilesTerrain.endUse()
|
||||
}
|
||||
|
||||
fun renderFront(g: Graphics, drawWires: Boolean) {
|
||||
/**
|
||||
* render to camera
|
||||
*/
|
||||
blendMul()
|
||||
|
||||
tilesTerrain.startUse()
|
||||
drawTiles(g, TERRAIN, true) // blendmul tiles
|
||||
tilesTerrain.endUse()
|
||||
|
||||
if (drawWires) {
|
||||
tilesWire.startUse()
|
||||
drawTiles(g, WIRE, false)
|
||||
tilesWire.endUse()
|
||||
}
|
||||
|
||||
blendNormal()
|
||||
}
|
||||
|
||||
private val tileDrawLightThreshold = 2
|
||||
|
||||
private fun drawTiles(g: Graphics, mode: Int, drawModeTilesBlendMul: Boolean) {
|
||||
val for_y_start = y / TILE_SIZE
|
||||
val for_y_end = BlocksDrawer.clampHTile(for_y_start + (height / TILE_SIZE) + 2)
|
||||
|
||||
val for_x_start = x / TILE_SIZE - 1
|
||||
val for_x_end = for_x_start + (width / TILE_SIZE) + 3
|
||||
|
||||
var zeroTileCounter = 0
|
||||
|
||||
// loop
|
||||
for (y in for_y_start..for_y_end) {
|
||||
for (x in for_x_start..for_x_end - 1) {
|
||||
|
||||
val thisTile: Int?
|
||||
if (mode % 3 == WALL)
|
||||
thisTile = world.getTileFromWall(x, y)
|
||||
else if (mode % 3 == TERRAIN)
|
||||
thisTile = world.getTileFromTerrain(x, y)
|
||||
else if (mode % 3 == WIRE)
|
||||
thisTile = world.getTileFromWire(x, y)
|
||||
else
|
||||
throw IllegalArgumentException()
|
||||
|
||||
val noDamageLayer = mode % 3 == WIRE
|
||||
|
||||
// draw a tile, but only when illuminated
|
||||
try {
|
||||
if ((mode == WALL || mode == TERRAIN) && // not an air tile
|
||||
(thisTile ?: 0) != Block.AIR) {
|
||||
// check if light level of nearby or this tile is illuminated
|
||||
if ( LightmapRenderer.getHighestRGB(x, y) ?: 0 >= tileDrawLightThreshold ||
|
||||
LightmapRenderer.getHighestRGB(x - 1, y) ?: 0 >= tileDrawLightThreshold ||
|
||||
LightmapRenderer.getHighestRGB(x + 1, y) ?: 0 >= tileDrawLightThreshold ||
|
||||
LightmapRenderer.getHighestRGB(x, y - 1) ?: 0 >= tileDrawLightThreshold ||
|
||||
LightmapRenderer.getHighestRGB(x, y + 1) ?: 0 >= tileDrawLightThreshold ||
|
||||
LightmapRenderer.getHighestRGB(x - 1, y - 1) ?: 0 >= tileDrawLightThreshold ||
|
||||
LightmapRenderer.getHighestRGB(x + 1, y + 1) ?: 0 >= tileDrawLightThreshold ||
|
||||
LightmapRenderer.getHighestRGB(x + 1, y - 1) ?: 0 >= tileDrawLightThreshold ||
|
||||
LightmapRenderer.getHighestRGB(x - 1, y + 1) ?: 0 >= tileDrawLightThreshold) {
|
||||
// blackness
|
||||
if (zeroTileCounter > 0) {
|
||||
/* unable to do anything */
|
||||
|
||||
zeroTileCounter = 0
|
||||
}
|
||||
|
||||
|
||||
val nearbyTilesInfo: Int
|
||||
if (isPlatform(thisTile)) {
|
||||
nearbyTilesInfo = getNearbyTilesInfoPlatform(x, y)
|
||||
}
|
||||
else if (isWallSticker(thisTile)) {
|
||||
nearbyTilesInfo = getNearbyTilesInfoWallSticker(x, y)
|
||||
}
|
||||
else if (isConnectMutual(thisTile)) {
|
||||
nearbyTilesInfo = getNearbyTilesInfoNonSolid(x, y, mode)
|
||||
}
|
||||
else if (isConnectSelf(thisTile)) {
|
||||
nearbyTilesInfo = getNearbyTilesInfo(x, y, mode, thisTile)
|
||||
}
|
||||
else {
|
||||
nearbyTilesInfo = 0
|
||||
}
|
||||
|
||||
|
||||
val thisTileX = if (!noDamageLayer)
|
||||
PairedMapLayer.RANGE * ((thisTile ?: 0) % PairedMapLayer.RANGE) + nearbyTilesInfo
|
||||
else
|
||||
nearbyTilesInfo
|
||||
|
||||
val thisTileY = (thisTile ?: 0) / PairedMapLayer.RANGE
|
||||
|
||||
|
||||
// draw a tile
|
||||
if (drawModeTilesBlendMul) {
|
||||
if (BlocksDrawer.isBlendMul(thisTile)) {
|
||||
drawTile(mode, x, y, thisTileX, thisTileY)
|
||||
}
|
||||
}
|
||||
else {
|
||||
// do NOT add "if (!isBlendMul(thisTile))"!
|
||||
// or else they will not look like they should be when backed with wall
|
||||
drawTile(mode, x, y, thisTileX, thisTileY)
|
||||
}
|
||||
|
||||
// draw a breakage
|
||||
if (mode == TERRAIN || mode == WALL) {
|
||||
val breakage = if (mode == TERRAIN) world.getTerrainDamage(x, y) else world.getWallDamage(x, y)
|
||||
val maxHealth = BlockCodex[world.getTileFromTerrain(x, y)].strength
|
||||
val stage = (breakage / maxHealth).times(breakAnimSteps).roundInt()
|
||||
// actual drawing
|
||||
if (stage > 0) {
|
||||
// alpha blending works, but no GL blend func...
|
||||
drawTile(mode, x, y, 5 + stage, 0)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} // end if (is illuminated)
|
||||
// draw black patch
|
||||
else {
|
||||
zeroTileCounter++ // unused for now
|
||||
|
||||
GL11.glColor4f(0f, 0f, 0f, 1f)
|
||||
|
||||
GL11.glTexCoord2f(0f, 0f)
|
||||
GL11.glVertex3f(x * TILE_SIZE.toFloat(), y * TILE_SIZE.toFloat(), 0f)
|
||||
GL11.glTexCoord2f(0f, 0f + TILE_SIZE)
|
||||
GL11.glVertex3f(x * TILE_SIZE.toFloat(), (y + 1) * TILE_SIZE.toFloat(), 0f)
|
||||
GL11.glTexCoord2f(0f + TILE_SIZE, 0f + TILE_SIZE)
|
||||
GL11.glVertex3f((x + 1) * TILE_SIZE.toFloat(), (y + 1) * TILE_SIZE.toFloat(), 0f)
|
||||
GL11.glTexCoord2f(0f + TILE_SIZE, 0f)
|
||||
GL11.glVertex3f((x + 1) * TILE_SIZE.toFloat(), y * TILE_SIZE.toFloat(), 0f)
|
||||
|
||||
GL11.glColor4f(1f, 1f, 1f, 1f)
|
||||
}
|
||||
} // end if (not an air)
|
||||
} catch (e: NullPointerException) {
|
||||
// do nothing. WARNING: This exception handling may hide erratic behaviour completely.
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
* @param x
|
||||
* *
|
||||
* @param y
|
||||
* *
|
||||
* @return binary [0-15] 1: up, 2: right, 4: down, 8: left
|
||||
*/
|
||||
fun getNearbyTilesInfo(x: Int, y: Int, mode: Int, mark: Int?): Int {
|
||||
val nearbyTiles = IntArray(4)
|
||||
nearbyTiles[NEARBY_TILE_KEY_LEFT] = world.getTileFrom(mode, x - 1, y) ?: Block.NULL
|
||||
nearbyTiles[NEARBY_TILE_KEY_RIGHT] = world.getTileFrom(mode, x + 1, y) ?: Block.NULL
|
||||
nearbyTiles[NEARBY_TILE_KEY_UP] = world.getTileFrom(mode, x , y - 1) ?: 4906
|
||||
nearbyTiles[NEARBY_TILE_KEY_DOWN] = world.getTileFrom(mode, x , y + 1) ?: Block.NULL
|
||||
|
||||
// try for
|
||||
var ret = 0
|
||||
for (i in 0..3) {
|
||||
if (nearbyTiles[i] == mark) {
|
||||
ret += 1 shl i // add 1, 2, 4, 8 for i = 0, 1, 2, 3
|
||||
}
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
fun getNearbyTilesInfoNonSolid(x: Int, y: Int, mode: Int): Int {
|
||||
val nearbyTiles = IntArray(4)
|
||||
nearbyTiles[NEARBY_TILE_KEY_LEFT] = world.getTileFrom(mode, x - 1, y) ?: Block.NULL
|
||||
nearbyTiles[NEARBY_TILE_KEY_RIGHT] = world.getTileFrom(mode, x + 1, y) ?: Block.NULL
|
||||
nearbyTiles[NEARBY_TILE_KEY_UP] = world.getTileFrom(mode, x , y - 1) ?: 4906
|
||||
nearbyTiles[NEARBY_TILE_KEY_DOWN] = world.getTileFrom(mode, x , y + 1) ?: Block.NULL
|
||||
|
||||
// try for
|
||||
var ret = 0
|
||||
for (i in 0..3) {
|
||||
try {
|
||||
if (!BlockCodex[nearbyTiles[i]].isSolid &&
|
||||
!BlockCodex[nearbyTiles[i]].isFluid) {
|
||||
ret += (1 shl i) // add 1, 2, 4, 8 for i = 0, 1, 2, 3
|
||||
}
|
||||
} catch (e: ArrayIndexOutOfBoundsException) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
fun getNearbyTilesInfoWallSticker(x: Int, y: Int): Int {
|
||||
val nearbyTiles = IntArray(4)
|
||||
val NEARBY_TILE_KEY_BACK = NEARBY_TILE_KEY_UP
|
||||
nearbyTiles[NEARBY_TILE_KEY_LEFT] = world.getTileFrom(TERRAIN, x - 1, y) ?: Block.NULL
|
||||
nearbyTiles[NEARBY_TILE_KEY_RIGHT] = world.getTileFrom(TERRAIN, x + 1, y) ?: Block.NULL
|
||||
nearbyTiles[NEARBY_TILE_KEY_DOWN] = world.getTileFrom(TERRAIN, x , y + 1) ?: Block.NULL
|
||||
nearbyTiles[NEARBY_TILE_KEY_BACK] = world.getTileFrom(WALL, x , y) ?: Block.NULL
|
||||
|
||||
try {
|
||||
if (BlockCodex[nearbyTiles[NEARBY_TILE_KEY_DOWN]].isSolid)
|
||||
// has tile on the bottom
|
||||
return 3
|
||||
else if (BlockCodex[nearbyTiles[NEARBY_TILE_KEY_RIGHT]].isSolid
|
||||
&& BlockCodex[nearbyTiles[NEARBY_TILE_KEY_LEFT]].isSolid)
|
||||
// has tile on both sides
|
||||
return 0
|
||||
else if (BlockCodex[nearbyTiles[NEARBY_TILE_KEY_RIGHT]].isSolid)
|
||||
// has tile on the right
|
||||
return 2
|
||||
else if (BlockCodex[nearbyTiles[NEARBY_TILE_KEY_LEFT]].isSolid)
|
||||
// has tile on the left
|
||||
return 1
|
||||
else if (BlockCodex[nearbyTiles[NEARBY_TILE_KEY_BACK]].isSolid)
|
||||
// has tile on the back
|
||||
return 0
|
||||
else
|
||||
return 3
|
||||
} catch (e: ArrayIndexOutOfBoundsException) {
|
||||
return if (BlockCodex[nearbyTiles[NEARBY_TILE_KEY_DOWN]].isSolid)
|
||||
// has tile on the bottom
|
||||
3 else 0
|
||||
}
|
||||
}
|
||||
|
||||
fun getNearbyTilesInfoPlatform(x: Int, y: Int): Int {
|
||||
val nearbyTiles = IntArray(4)
|
||||
nearbyTiles[NEARBY_TILE_KEY_LEFT] = world.getTileFrom(TERRAIN, x - 1, y) ?: Block.NULL
|
||||
nearbyTiles[NEARBY_TILE_KEY_RIGHT] = world.getTileFrom(TERRAIN, x + 1, y) ?: Block.NULL
|
||||
|
||||
if ((BlockCodex[nearbyTiles[NEARBY_TILE_KEY_LEFT]].isSolid &&
|
||||
BlockCodex[nearbyTiles[NEARBY_TILE_KEY_RIGHT]].isSolid) ||
|
||||
isPlatform(nearbyTiles[NEARBY_TILE_KEY_LEFT]) &&
|
||||
isPlatform(nearbyTiles[NEARBY_TILE_KEY_RIGHT])) // LR solid || LR platform
|
||||
return 0
|
||||
else if (BlockCodex[nearbyTiles[NEARBY_TILE_KEY_LEFT]].isSolid &&
|
||||
!isPlatform(nearbyTiles[NEARBY_TILE_KEY_LEFT]) &&
|
||||
!BlockCodex[nearbyTiles[NEARBY_TILE_KEY_RIGHT]].isSolid &&
|
||||
!isPlatform(nearbyTiles[NEARBY_TILE_KEY_RIGHT])) // L solid and not platform && R not solid and not platform
|
||||
return 4
|
||||
else if (BlockCodex[nearbyTiles[NEARBY_TILE_KEY_RIGHT]].isSolid &&
|
||||
!isPlatform(nearbyTiles[NEARBY_TILE_KEY_RIGHT]) &&
|
||||
!BlockCodex[nearbyTiles[NEARBY_TILE_KEY_LEFT]].isSolid &&
|
||||
!isPlatform(nearbyTiles[NEARBY_TILE_KEY_LEFT])) // R solid and not platform && L not solid and nto platform
|
||||
return 6
|
||||
else if (BlockCodex[nearbyTiles[NEARBY_TILE_KEY_LEFT]].isSolid &&
|
||||
!isPlatform(nearbyTiles[NEARBY_TILE_KEY_LEFT])) // L solid && L not platform
|
||||
return 3
|
||||
else if (BlockCodex[nearbyTiles[NEARBY_TILE_KEY_RIGHT]].isSolid &&
|
||||
!isPlatform(nearbyTiles[NEARBY_TILE_KEY_RIGHT])) // R solid && R not platform
|
||||
return 5
|
||||
else if ((BlockCodex[nearbyTiles[NEARBY_TILE_KEY_LEFT]].isSolid ||
|
||||
isPlatform(nearbyTiles[NEARBY_TILE_KEY_LEFT])) &&
|
||||
!BlockCodex[nearbyTiles[NEARBY_TILE_KEY_RIGHT]].isSolid &&
|
||||
!isPlatform(nearbyTiles[NEARBY_TILE_KEY_RIGHT])) // L solid or platform && R not solid and not platform
|
||||
return 1
|
||||
else if ((BlockCodex[nearbyTiles[NEARBY_TILE_KEY_RIGHT]].isSolid ||
|
||||
isPlatform(nearbyTiles[NEARBY_TILE_KEY_RIGHT])) &&
|
||||
!BlockCodex[nearbyTiles[NEARBY_TILE_KEY_LEFT]].isSolid &&
|
||||
!isPlatform(nearbyTiles[NEARBY_TILE_KEY_LEFT])) // R solid or platform && L not solid and not platform
|
||||
return 2
|
||||
else
|
||||
return 7
|
||||
}
|
||||
|
||||
private fun drawTile(mode: Int, tilewisePosX: Int, tilewisePosY: Int, sheetX: Int, sheetY: Int) {
|
||||
if (mode == TERRAIN || mode == WALL)
|
||||
tilesTerrain.renderInUse(
|
||||
FastMath.floor((tilewisePosX * TILE_SIZE).toFloat()),
|
||||
FastMath.floor((tilewisePosY * TILE_SIZE).toFloat()),
|
||||
sheetX, sheetY
|
||||
)
|
||||
else if (mode == WIRE)
|
||||
tilesWire.renderInUse(
|
||||
FastMath.floor((tilewisePosX * TILE_SIZE).toFloat()),
|
||||
FastMath.floor((tilewisePosY * TILE_SIZE).toFloat()),
|
||||
sheetX, sheetY
|
||||
)
|
||||
else
|
||||
throw IllegalArgumentException()
|
||||
}
|
||||
|
||||
fun clampH(x: Int): Int {
|
||||
if (x < 0) {
|
||||
return 0
|
||||
} else if (x > world.height * TILE_SIZE) {
|
||||
return world.height * TILE_SIZE
|
||||
} else {
|
||||
return x
|
||||
}
|
||||
}
|
||||
|
||||
fun clampWTile(x: Int): Int {
|
||||
if (x < 0) {
|
||||
return 0
|
||||
} else if (x > world.width) {
|
||||
return world.width
|
||||
} else {
|
||||
return x
|
||||
}
|
||||
}
|
||||
|
||||
fun clampHTile(x: Int): Int {
|
||||
if (x < 0) {
|
||||
return 0
|
||||
} else if (x > world.height) {
|
||||
return world.height
|
||||
} else {
|
||||
return x
|
||||
}
|
||||
}
|
||||
|
||||
fun getRenderStartX(): Int = x / TILE_SIZE
|
||||
fun getRenderStartY(): Int = y / TILE_SIZE
|
||||
|
||||
fun getRenderEndX(): Int = clampWTile(getRenderStartX() + (width / TILE_SIZE) + 2)
|
||||
fun getRenderEndY(): Int = clampHTile(getRenderStartY() + (height / TILE_SIZE) + 2)
|
||||
|
||||
fun isConnectSelf(b: Int?): Boolean = TILES_CONNECT_SELF.contains(b)
|
||||
fun isConnectMutual(b: Int?): Boolean = TILES_CONNECT_MUTUAL.contains(b)
|
||||
fun isWallSticker(b: Int?): Boolean = TILES_WALL_STICKER.contains(b)
|
||||
fun isPlatform(b: Int?): Boolean = TILES_WALL_STICKER_CONNECT_SELF.contains(b)
|
||||
fun isBlendMul(b: Int?): Boolean = TILES_BLEND_MUL.contains(b)
|
||||
|
||||
fun tileInCamera(x: Int, y: Int) =
|
||||
x >= WorldCamera.x.div(TILE_SIZE) && y >= WorldCamera.y.div(TILE_SIZE) &&
|
||||
x <= WorldCamera.x.plus(width).div(TILE_SIZE) && y <= WorldCamera.y.plus(width).div(TILE_SIZE)
|
||||
}
|
||||
78
src/net/torvald/terrarum/worlddrawer/FeaturesDrawer.kt
Normal file
78
src/net/torvald/terrarum/worlddrawer/FeaturesDrawer.kt
Normal file
@@ -0,0 +1,78 @@
|
||||
package net.torvald.terrarum.worlddrawer
|
||||
|
||||
import net.torvald.terrarum.Terrarum
|
||||
import net.torvald.terrarum.blockproperties.Block
|
||||
import net.torvald.terrarum.blockstats.BlockStats
|
||||
import com.jme3.math.FastMath
|
||||
import net.torvald.colourutil.ColourTemp
|
||||
import net.torvald.terrarum.blendMul
|
||||
import org.newdawn.slick.*
|
||||
|
||||
/**
|
||||
* Created by minjaesong on 15-12-31.
|
||||
*/
|
||||
object FeaturesDrawer {
|
||||
const val TILE_SIZE = 16
|
||||
|
||||
private val ENV_COLTEMP_LOWEST = 5500
|
||||
private val ENV_COLTEMP_HIGHEST = 7500
|
||||
|
||||
val ENV_COLTEMP_NOON = 6500 // 6500 == sRGB White; do not touch!
|
||||
|
||||
var colTemp: Int = 0
|
||||
private set
|
||||
|
||||
private val TILES_COLD = intArrayOf(
|
||||
Block.ICE_MAGICAL
|
||||
, Block.ICE_FRAGILE
|
||||
, Block.ICE_NATURAL
|
||||
, Block.SNOW)
|
||||
|
||||
private val TILES_WARM = intArrayOf(
|
||||
Block.SAND_DESERT
|
||||
, Block.SAND_RED)
|
||||
|
||||
fun update(gc: GameContainer, delta_t: Int) {
|
||||
}
|
||||
|
||||
fun render(gc: GameContainer, g: Graphics) {
|
||||
}
|
||||
|
||||
/**
|
||||
* A colour filter used to provide effect that makes whole screen look warmer/cooler,
|
||||
* usually targeted for the environmental temperature (desert/winterland), hence the name.
|
||||
*/
|
||||
fun drawEnvOverlay(g: Graphics) {
|
||||
val onscreen_tiles_max = FastMath.ceil(Terrarum.HEIGHT * Terrarum.WIDTH / FastMath.sqr(TILE_SIZE.toFloat())) * 2
|
||||
val onscreen_tiles_cap = onscreen_tiles_max / 4f
|
||||
val onscreen_cold_tiles = BlockStats.getCount(*TILES_COLD).toFloat()
|
||||
val onscreen_warm_tiles = BlockStats.getCount(*TILES_WARM).toFloat()
|
||||
|
||||
val colTemp_cold = colTempLinearFunc(onscreen_cold_tiles / onscreen_tiles_cap)
|
||||
val colTemp_warm = colTempLinearFunc(-(onscreen_warm_tiles / onscreen_tiles_cap))
|
||||
colTemp = colTemp_warm + colTemp_cold - ENV_COLTEMP_NOON
|
||||
val zoom = Terrarum.ingame!!.screenZoom
|
||||
|
||||
blendMul()
|
||||
|
||||
g.color = ColourTemp(colTemp)
|
||||
g.fillRect(
|
||||
WorldCamera.x * zoom,
|
||||
WorldCamera.y * zoom,
|
||||
Terrarum.WIDTH * if (zoom < 1) 1f / zoom else zoom,
|
||||
Terrarum.HEIGHT * if (zoom < 1) 1f / zoom else zoom
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
* @param x [-1 , 1], 0 for 6500K (median of ENV_COLTEMP_HIGHEST and ENV_COLTEMP_LOWEST)
|
||||
* *
|
||||
* @return
|
||||
*/
|
||||
private fun colTempLinearFunc(x: Float): Int {
|
||||
val colTempMedian = (ENV_COLTEMP_HIGHEST + ENV_COLTEMP_LOWEST) / 2
|
||||
|
||||
return Math.round((ENV_COLTEMP_HIGHEST - ENV_COLTEMP_LOWEST) / 2 * FastMath.clamp(x, -1f, 1f) + colTempMedian)
|
||||
}
|
||||
}
|
||||
725
src/net/torvald/terrarum/worlddrawer/LightmapRenderer.kt
Normal file
725
src/net/torvald/terrarum/worlddrawer/LightmapRenderer.kt
Normal file
@@ -0,0 +1,725 @@
|
||||
package net.torvald.terrarum.worlddrawer
|
||||
|
||||
import net.torvald.terrarum.gameactors.Luminous
|
||||
import net.torvald.terrarum.Terrarum
|
||||
import net.torvald.terrarum.blockproperties.BlockCodex
|
||||
import com.jme3.math.FastMath
|
||||
import net.torvald.terrarum.gameactors.ActorWithPhysics
|
||||
import net.torvald.terrarum.gameworld.GameWorld
|
||||
import net.torvald.terrarum.blockproperties.Block
|
||||
import org.newdawn.slick.Color
|
||||
import org.newdawn.slick.Graphics
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Created by minjaesong on 16-01-25.
|
||||
*/
|
||||
|
||||
object LightmapRenderer {
|
||||
private val world: GameWorld = Terrarum.ingame!!.world
|
||||
|
||||
val overscan_open: Int = Math.min(32, 256f.div(BlockCodex[Block.AIR].opacity and 0xFF).ceil())
|
||||
val overscan_opaque: Int = Math.min(8, 256f.div(BlockCodex[Block.STONE].opacity and 0xFF).ceil())
|
||||
|
||||
private val LIGHTMAP_WIDTH = Terrarum.ingame!!.ZOOM_MIN.inv().times(Terrarum.WIDTH)
|
||||
.div(FeaturesDrawer.TILE_SIZE).ceil() + overscan_open * 2 + 3
|
||||
private val LIGHTMAP_HEIGHT = Terrarum.ingame!!.ZOOM_MIN.inv().times(Terrarum.HEIGHT)
|
||||
.div(FeaturesDrawer.TILE_SIZE).ceil() + overscan_open * 2 + 3
|
||||
|
||||
/**
|
||||
* 8-Bit RGB values
|
||||
*/
|
||||
private val lightmap: Array<IntArray> = Array(LIGHTMAP_HEIGHT) { IntArray(LIGHTMAP_WIDTH) }
|
||||
private val lanternMap = ArrayList<Lantern>(Terrarum.ingame!!.ACTORCONTAINER_INITIAL_SIZE * 4)
|
||||
|
||||
private val AIR = Block.AIR
|
||||
|
||||
private val OFFSET_R = 2
|
||||
private val OFFSET_G = 1
|
||||
private val OFFSET_B = 0
|
||||
|
||||
private const val TILE_SIZE = FeaturesDrawer.TILE_SIZE
|
||||
|
||||
// color model related constants
|
||||
const val MUL = 1024 // modify this to 1024 to implement 30-bit RGB
|
||||
const val CHANNEL_MAX_DECIMAL = 1f
|
||||
const val MUL_2 = MUL * MUL
|
||||
const val CHANNEL_MAX = MUL - 1
|
||||
const val CHANNEL_MAX_FLOAT = CHANNEL_MAX.toFloat()
|
||||
const val COLOUR_RANGE_SIZE = MUL * MUL_2
|
||||
|
||||
internal var for_x_start: Int = 0
|
||||
internal var for_y_start: Int = 0
|
||||
internal var for_x_end: Int = 0
|
||||
internal var for_y_end: Int = 0
|
||||
|
||||
|
||||
fun getLightRawPos(x: Int, y: Int) = lightmap[y][x]
|
||||
|
||||
fun getLight(x: Int, y: Int): Int? {
|
||||
/*if (x !in 0..Terrarum.game.map.width - 1 || y !in 0..Terrarum.game.map.height - 1)
|
||||
// if out of range then
|
||||
null
|
||||
else
|
||||
lightmap[y][x]*/
|
||||
try {
|
||||
return lightmap[y - for_y_start + overscan_open][x - for_x_start + overscan_open]
|
||||
}
|
||||
catch (e: ArrayIndexOutOfBoundsException) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
fun setLight(x: Int, y: Int, colour: Int) {
|
||||
//lightmap[y][x] = colour
|
||||
try {
|
||||
lightmap[y - for_y_start + overscan_open][x - for_x_start + overscan_open] = colour
|
||||
}
|
||||
catch (e: ArrayIndexOutOfBoundsException) {
|
||||
}
|
||||
}
|
||||
|
||||
fun renderLightMap() {
|
||||
for_x_start = WorldCamera.x / TILE_SIZE - 1 // fix for premature lightmap rendering
|
||||
for_y_start = WorldCamera.y / TILE_SIZE - 1 // on topmost/leftmost side
|
||||
|
||||
for_x_end = for_x_start + WorldCamera.width / TILE_SIZE + 3
|
||||
for_y_end = for_y_start + WorldCamera.height / TILE_SIZE + 2 // same fix as above
|
||||
|
||||
/**
|
||||
* * true: overscanning is limited to 8 tiles in width (overscan_opaque)
|
||||
* * false: overscanning will fully applied to 32 tiles in width (overscan_open)
|
||||
*/
|
||||
val rect_width = for_x_end - for_x_start
|
||||
val rect_height_rem_hbars = for_y_end - for_y_start - 2
|
||||
val noop_mask = BitSet(2 * (rect_width) +
|
||||
2 * (rect_height_rem_hbars))
|
||||
val rect_size = noop_mask.size()
|
||||
|
||||
// get No-op mask
|
||||
fun edgeToMaskNum(i: Int): Pair<Int, Int> {
|
||||
if (i > rect_size) throw IllegalArgumentException()
|
||||
|
||||
if (i < rect_width) // top edge horizontal
|
||||
return Pair(for_x_start + i, for_y_start)
|
||||
else if (i >= rect_size - rect_width) // bottom edge horizontal
|
||||
return Pair(
|
||||
for_x_start + i.minus(rect_size - rect_width),
|
||||
for_y_end
|
||||
)
|
||||
else { // vertical edges without horizontal edge pair
|
||||
return Pair(
|
||||
if ((rect_width.even() && i.even()) || (rect_width.odd() && i.odd()))
|
||||
// if the index is on the left side of the box
|
||||
for_x_start
|
||||
else for_x_end,
|
||||
(i - rect_width).div(2) + for_y_start + 1
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun posToMaskNum(x: Int, y: Int): Int? {
|
||||
if (x in for_x_start + 1..for_x_end - 1 && y in for_y_start + 1..for_y_end - 1) {
|
||||
return null // inside of this imaginary box
|
||||
}
|
||||
else if (y <= for_y_start) { // upper edge
|
||||
if (x < for_x_start) return 0
|
||||
else if (x > for_x_end) return rect_width - 1
|
||||
else return x - for_x_start
|
||||
}
|
||||
else if (y >= for_y_end) { // lower edge
|
||||
if (x < for_x_start) return rect_size - rect_width
|
||||
else if (x > for_x_end) return rect_size - 1
|
||||
else return x - for_x_start + (rect_size - rect_width)
|
||||
}
|
||||
else { // between two edges
|
||||
if (x < for_x_start) return (y - for_y_start - 1) * 2 + rect_width
|
||||
else if (x > for_x_end) return (y - for_y_start - 1) * 2 + rect_width + 1
|
||||
else return null
|
||||
}
|
||||
}
|
||||
|
||||
fun isNoop(x: Int, y: Int): Boolean =
|
||||
if (posToMaskNum(x, y) == null)
|
||||
false
|
||||
else if (!(x in for_x_start - overscan_opaque..for_x_end + overscan_opaque &&
|
||||
x in for_y_start - overscan_opaque..for_y_end + overscan_opaque))
|
||||
// point is within the range of overscan_open but not overscan_opaque
|
||||
noop_mask.get(posToMaskNum(x, y)!!)
|
||||
else // point within the overscan_opaque must be rendered, so no no-op
|
||||
false
|
||||
|
||||
// build noop map
|
||||
for (i in 0..rect_size) {
|
||||
val point = edgeToMaskNum(i)
|
||||
val tile = Terrarum.ingame!!.world.getTileFromTerrain(point.first, point.second) ?: Block.NULL
|
||||
val isSolid = BlockCodex[tile].isSolid
|
||||
|
||||
noop_mask.set(i, isSolid)
|
||||
}
|
||||
|
||||
/**
|
||||
* Updating order:
|
||||
* +--------+ +--+-----+ +-----+--+ +--------+ -
|
||||
* |↘ | | | 3| |3 | | | ↙| ↕︎ overscan_open / overscan_opaque
|
||||
* | +-----+ | | 2 | | 2 | | +-----+ | - depending on the noop_mask
|
||||
* | |1 | → | |1 | → | 1| | → | 1| |
|
||||
* | | 2 | | +-----+ +-----+ | | 2 | |
|
||||
* | | 3| |↗ | | ↖| |3 | |
|
||||
* +--+-----+ +--------+ +--------+ +-----+--+
|
||||
* round: 1 2 3 4
|
||||
* for all staticLightMap[y][x]
|
||||
*/
|
||||
|
||||
purgeLightmap()
|
||||
buildLanternmap()
|
||||
|
||||
// O(36n) == O(n) where n is a size of the map.
|
||||
// Because of inevitable overlaps on the area, it only works with ADDITIVE blend (aka maxblend)
|
||||
|
||||
|
||||
// Round 1
|
||||
for (y in for_y_start - overscan_open..for_y_end) {
|
||||
for (x in for_x_start - overscan_open..for_x_end) {
|
||||
setLight(x, y, calculate(x, y, 1))
|
||||
}
|
||||
}
|
||||
|
||||
// Round 2
|
||||
for (y in for_y_end + overscan_open downTo for_y_start) {
|
||||
for (x in for_x_start - overscan_open..for_x_end) {
|
||||
setLight(x, y, calculate(x, y, 2))
|
||||
}
|
||||
}
|
||||
|
||||
// Round 3
|
||||
for (y in for_y_end + overscan_open downTo for_y_start) {
|
||||
for (x in for_x_end + overscan_open downTo for_x_start) {
|
||||
setLight(x, y, calculate(x, y, 3))
|
||||
}
|
||||
}
|
||||
|
||||
// Round 4
|
||||
for (y in for_y_start - overscan_open..for_y_end) {
|
||||
for (x in for_x_end + overscan_open downTo for_x_start) {
|
||||
setLight(x, y, calculate(x, y, 4))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildLanternmap() {
|
||||
lanternMap.clear()
|
||||
Terrarum.ingame!!.actorContainer.forEach { it ->
|
||||
if (it is Luminous && it is ActorWithPhysics) {
|
||||
// put lanterns to the area the luminantBox is occupying
|
||||
for (lightBox in it.lightBoxList) {
|
||||
val lightBoxX = it.hitbox.posX + lightBox.posX
|
||||
val lightBoxY = it.hitbox.posY + lightBox.posY
|
||||
val lightBoxW = lightBox.width
|
||||
val lightBoxH = lightBox.height
|
||||
for (y in lightBoxY.div(TILE_SIZE).floorInt()
|
||||
..lightBoxY.plus(lightBoxH).div(TILE_SIZE).floorInt()) {
|
||||
for (x in lightBoxX.div(TILE_SIZE).floorInt()
|
||||
..lightBoxX.plus(lightBoxW).div(TILE_SIZE).floorInt()) {
|
||||
lanternMap.add(Lantern(x, y, it.luminosity))
|
||||
// Q&D fix for Roundworld anomaly
|
||||
lanternMap.add(Lantern(x + world.width, y, it.luminosity))
|
||||
lanternMap.add(Lantern(x - world.width, y, it.luminosity))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun calculate(x: Int, y: Int, pass: Int): Int = calculate(x, y, pass, false)
|
||||
|
||||
private fun calculate(x: Int, y: Int, pass: Int, doNotCalculateAmbient: Boolean): Int {
|
||||
// O(9n) == O(n) where n is a size of the map
|
||||
// TODO devise multithreading on this
|
||||
|
||||
var lightLevelThis: Int = 0
|
||||
val thisTerrain = Terrarum.ingame!!.world.getTileFromTerrain(x, y)
|
||||
val thisWall = Terrarum.ingame!!.world.getTileFromWall(x, y)
|
||||
val thisTileLuminosity = BlockCodex[thisTerrain].luminosity
|
||||
val thisTileOpacity = BlockCodex[thisTerrain].opacity
|
||||
val sunLight = Terrarum.ingame!!.world.globalLight
|
||||
|
||||
// MIX TILE
|
||||
// open air
|
||||
if (thisTerrain == AIR && thisWall == AIR) {
|
||||
lightLevelThis = sunLight
|
||||
}
|
||||
// luminous tile on top of air
|
||||
else if (thisWall == AIR && thisTileLuminosity > 0) {
|
||||
lightLevelThis = sunLight maxBlend thisTileLuminosity // maximise to not exceed 1.0 with normal (<= 1.0) light
|
||||
}
|
||||
// opaque wall and luminous tile
|
||||
else if (thisWall != AIR && thisTileLuminosity > 0) {
|
||||
lightLevelThis = thisTileLuminosity
|
||||
}
|
||||
// END MIX TILE
|
||||
|
||||
for (i in 0..lanternMap.size - 1) {
|
||||
val lmap = lanternMap[i]
|
||||
if (lmap.posX == x && lmap.posY == y)
|
||||
lightLevelThis = lightLevelThis maxBlend lmap.luminosity // maximise to not exceed 1.0 with normal (<= 1.0) light
|
||||
}
|
||||
|
||||
|
||||
if (!doNotCalculateAmbient) {
|
||||
// calculate ambient
|
||||
/* + * +
|
||||
* * @ *
|
||||
* + * +
|
||||
* sample ambient for eight points and apply attenuation for those
|
||||
* maxblend eight values and use it
|
||||
*/
|
||||
var ambient = 0
|
||||
ambient = ambient maxBlend darkenColoured(getLight(x - 1, y - 1) ?: 0, scaleColour(thisTileOpacity, 1.4142f))
|
||||
ambient = ambient maxBlend darkenColoured(getLight(x + 1, y - 1) ?: 0, scaleColour(thisTileOpacity, 1.4142f))
|
||||
ambient = ambient maxBlend darkenColoured(getLight(x - 1, y + 1) ?: 0, scaleColour(thisTileOpacity, 1.4142f))
|
||||
ambient = ambient maxBlend darkenColoured(getLight(x + 1, y + 1) ?: 0, scaleColour(thisTileOpacity, 1.4142f))
|
||||
ambient = ambient maxBlend darkenColoured(getLight(x , y - 1) ?: 0, thisTileOpacity)
|
||||
ambient = ambient maxBlend darkenColoured(getLight(x , y + 1) ?: 0, thisTileOpacity)
|
||||
ambient = ambient maxBlend darkenColoured(getLight(x - 1, y ) ?: 0, thisTileOpacity)
|
||||
ambient = ambient maxBlend darkenColoured(getLight(x + 1, y ) ?: 0, thisTileOpacity)
|
||||
return lightLevelThis maxBlend ambient
|
||||
}
|
||||
else {
|
||||
return lightLevelThis
|
||||
}
|
||||
}
|
||||
|
||||
fun draw(g: Graphics) {
|
||||
val this_x_start = for_x_start// + overscan_open
|
||||
val this_x_end = for_x_end// + overscan_open
|
||||
val this_y_start = for_y_start// + overscan_open
|
||||
val this_y_end = for_y_end// + overscan_open
|
||||
|
||||
// draw
|
||||
try {
|
||||
// loop for "scanlines"
|
||||
for (y in this_y_start..this_y_end) {
|
||||
// loop x
|
||||
var x = this_x_start
|
||||
while (x < this_x_end) {
|
||||
// smoothing enabled and zoom is 0.75 or greater
|
||||
// (zoom of 0.5 should not smoothed, for performance)
|
||||
if (Terrarum.getConfigBoolean("smoothlighting") ?: false &&
|
||||
Terrarum.ingame!!.screenZoom >= 0.75) {
|
||||
|
||||
val thisLightLevel = getLight(x, y) ?: 0
|
||||
|
||||
if (x < this_x_end && thisLightLevel == 0
|
||||
&& getLight(x, y - 1) == 0) {
|
||||
try {
|
||||
// coalesce zero intensity blocks to one
|
||||
var zeroLevelCounter = 1
|
||||
while (getLight(x + zeroLevelCounter, y) == 0) {
|
||||
zeroLevelCounter += 1
|
||||
|
||||
if (x + zeroLevelCounter >= this_x_end) break
|
||||
}
|
||||
|
||||
g.color = Color(0)
|
||||
g.fillRect(
|
||||
(x.toFloat() * TILE_SIZE).round().toFloat(),
|
||||
(y.toFloat() * TILE_SIZE).round().toFloat(),
|
||||
(TILE_SIZE * zeroLevelCounter).toFloat(),
|
||||
(TILE_SIZE).toFloat()
|
||||
)
|
||||
|
||||
x += zeroLevelCounter - 1
|
||||
}
|
||||
catch (e: ArrayIndexOutOfBoundsException) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
}
|
||||
else {
|
||||
/** a
|
||||
* +-+-+
|
||||
* |i|j|
|
||||
* b +-+-+ c
|
||||
* |k|l|
|
||||
* +-+-+
|
||||
* d
|
||||
*/
|
||||
val a = thisLightLevel maxBlend (getLight(x, y - 1) ?: thisLightLevel)
|
||||
val d = thisLightLevel maxBlend (getLight(x, y + 1) ?: thisLightLevel)
|
||||
val b = thisLightLevel maxBlend (getLight(x - 1, y) ?: thisLightLevel)
|
||||
val c = thisLightLevel maxBlend (getLight(x + 1, y) ?: thisLightLevel)
|
||||
|
||||
val colourMapItoL = IntArray(4)
|
||||
val colMean = (a linMix d) linMix (b linMix c)
|
||||
val colDelta = thisLightLevel colSub colMean
|
||||
|
||||
colourMapItoL[0] = a linMix b colAdd colDelta
|
||||
colourMapItoL[1] = a linMix c colAdd colDelta
|
||||
colourMapItoL[2] = b linMix d colAdd colDelta
|
||||
colourMapItoL[3] = c linMix d colAdd colDelta
|
||||
|
||||
for (iy in 0..1) {
|
||||
for (ix in 0..1) {
|
||||
g.color = colourMapItoL[iy * 2 + ix].normaliseToColour()
|
||||
|
||||
g.fillRect(
|
||||
(x.toFloat() * TILE_SIZE).round()
|
||||
+ ix * TILE_SIZE / 2f,
|
||||
(y.toFloat() * TILE_SIZE).round()
|
||||
+ iy * TILE_SIZE / 2f,
|
||||
(TILE_SIZE / 2f).ceil().toFloat(),
|
||||
(TILE_SIZE / 2f).ceil().toFloat()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// smoothing disabled
|
||||
else {
|
||||
try {
|
||||
val thisLightLevel = getLight(x, y)
|
||||
|
||||
// coalesce identical intensity blocks to one
|
||||
var sameLevelCounter = 1
|
||||
while (getLight(x + sameLevelCounter, y) == thisLightLevel) {
|
||||
sameLevelCounter += 1
|
||||
|
||||
if (x + sameLevelCounter >= this_x_end) break
|
||||
}
|
||||
|
||||
g.color = (getLight(x, y) ?: 0).normaliseToColour()
|
||||
g.fillRect(
|
||||
(x.toFloat() * TILE_SIZE).round().toFloat(),
|
||||
(y.toFloat() * TILE_SIZE).round().toFloat(),
|
||||
(TILE_SIZE.toFloat().ceil() * sameLevelCounter).toFloat(),
|
||||
TILE_SIZE.toFloat().ceil().toFloat()
|
||||
)
|
||||
|
||||
x += sameLevelCounter - 1
|
||||
}
|
||||
catch (e: ArrayIndexOutOfBoundsException) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
x++
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (e: ArrayIndexOutOfBoundsException) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
val lightScalingMagic = 8f
|
||||
|
||||
/**
|
||||
* Subtract each channel's RGB value.
|
||||
*
|
||||
* @param data Raw channel value (0-255) per channel
|
||||
* @param darken (0-255) per channel
|
||||
* @return darkened data (0-255) per channel
|
||||
*/
|
||||
fun darkenColoured(data: Int, darken: Int): Int {
|
||||
if (darken < 0 || darken >= COLOUR_RANGE_SIZE)
|
||||
throw IllegalArgumentException("darken: out of range ($darken)")
|
||||
|
||||
// use equation with magic number 8.0
|
||||
// should draw somewhat exponential curve when you plot the propagation of light in-game
|
||||
|
||||
val r = data.r() * (1f - darken.r() * lightScalingMagic)
|
||||
val g = data.g() * (1f - darken.g() * lightScalingMagic)
|
||||
val b = data.b() * (1f - darken.b() * lightScalingMagic)
|
||||
|
||||
return constructRGBFromFloat(r.clampZero(), g.clampZero(), b.clampZero())
|
||||
}
|
||||
|
||||
fun scaleColour(data: Int, scale: Float): Int {
|
||||
val r = data.r() * scale
|
||||
val g = data.g() * scale
|
||||
val b = data.b() * scale
|
||||
|
||||
return constructRGBFromFloat(r.clampOne(), g.clampOne(), b.clampOne())
|
||||
}
|
||||
|
||||
/**
|
||||
* Add each channel's RGB value.
|
||||
*
|
||||
* @param data Raw channel value (0-255) per channel
|
||||
* @param brighten (0-255) per channel
|
||||
* @return brightened data (0-255) per channel
|
||||
*/
|
||||
fun brightenColoured(data: Int, brighten: Int): Int {
|
||||
if (brighten < 0 || brighten >= COLOUR_RANGE_SIZE)
|
||||
throw IllegalArgumentException("brighten: out of range ($brighten)")
|
||||
|
||||
val r = data.r() * (1f + brighten.r() * lightScalingMagic)
|
||||
val g = data.g() * (1f + brighten.g() * lightScalingMagic)
|
||||
val b = data.b() * (1f + brighten.b() * lightScalingMagic)
|
||||
|
||||
return constructRGBFromFloat(r.clampChannel(), g.clampChannel(), b.clampChannel())
|
||||
}
|
||||
|
||||
/**
|
||||
* Darken each channel by 'darken' argument
|
||||
*
|
||||
* @param data Raw channel value (0-255) per channel
|
||||
* @param darken (0-255)
|
||||
* @return
|
||||
*/
|
||||
fun darkenUniformInt(data: Int, darken: Int): Int {
|
||||
if (darken < 0 || darken > CHANNEL_MAX)
|
||||
throw IllegalArgumentException("darken: out of range ($darken)")
|
||||
|
||||
val darkenColoured = constructRGBFromInt(darken, darken, darken)
|
||||
return darkenColoured(data, darkenColoured)
|
||||
}
|
||||
|
||||
/**
|
||||
* Darken or brighten colour by 'brighten' argument
|
||||
*
|
||||
* @param data Raw channel value (0-255) per channel
|
||||
* @param brighten (-1.0 - 1.0) negative means darkening
|
||||
* @return processed colour
|
||||
*/
|
||||
fun alterBrightnessUniform(data: Int, brighten: Float): Int {
|
||||
val modifier = if (brighten < 0)
|
||||
constructRGBFromFloat(-brighten, -brighten, -brighten)
|
||||
else
|
||||
constructRGBFromFloat(brighten, brighten, brighten)
|
||||
|
||||
return if (brighten < 0)
|
||||
darkenColoured(data, modifier)
|
||||
else
|
||||
brightenColoured(data, modifier)
|
||||
}
|
||||
|
||||
/** Get each channel from two RGB values, return new RGB that has max value of each channel
|
||||
* @param rgb
|
||||
* @param rgb2
|
||||
* @return
|
||||
*/
|
||||
private infix fun Int.maxBlend(other: Int): Int {
|
||||
val r1 = this.rawR(); val r2 = other.rawR(); val newR = if (r1 > r2) r1 else r2
|
||||
val g1 = this.rawG(); val g2 = other.rawG(); val newG = if (g1 > g2) g1 else g2
|
||||
val b1 = this.rawB(); val b2 = other.rawB(); val newB = if (b1 > b2) b1 else b2
|
||||
|
||||
return constructRGBFromInt(newR, newG, newB)
|
||||
}
|
||||
|
||||
/**
|
||||
* Deprecated: Fuck it, this vittupää just doesn't want to work
|
||||
*/
|
||||
private infix fun Int.screenBlend(other: Int): Int {
|
||||
/*val r1 = this.r(); val r2 = other.r(); val newR = 1 - (1 - r1) * (1 - r2)
|
||||
val g1 = this.g(); val g2 = other.g(); val newG = 1 - (1 - g1) * (1 - g2)
|
||||
val b1 = this.b(); val b2 = other.b(); val newB = 1 - (1 - b1) * (1 - b2)*/
|
||||
|
||||
val r1 = this.r(); val r2 = other.r()
|
||||
val g1 = this.g(); val g2 = other.g()
|
||||
val b1 = this.b(); val b2 = other.b()
|
||||
|
||||
var screenR = 1f - (1f - r1).clampZero() * (1f - r2).clampZero()
|
||||
var screenG = 1f - (1f - g1).clampZero() * (1f - g2).clampZero()
|
||||
var screenB = 1f - (1f - b1).clampZero() * (1f - b2).clampZero()
|
||||
|
||||
// hax.
|
||||
val addR = if (r1 > r2) r1 else r2
|
||||
val addG = if (g1 > g2) g1 else g2
|
||||
val addB = if (b1 > b2) b1 else b2
|
||||
|
||||
val newR = Math.min(screenR, addR)
|
||||
val newG = Math.min(screenG, addG)
|
||||
val newB = Math.min(screenB, addB)
|
||||
|
||||
return constructRGBFromFloat(newR, newG, newB)
|
||||
}
|
||||
|
||||
private infix fun Int.colSub(other: Int) = constructRGBFromInt(
|
||||
(this.rawR() - other.rawR()).clampChannel() ,
|
||||
(this.rawG() - other.rawG()).clampChannel() ,
|
||||
(this.rawB() - other.rawB()).clampChannel()
|
||||
)
|
||||
|
||||
private infix fun Int.colAdd(other: Int) = constructRGBFromInt(
|
||||
(this.rawR() + other.rawR()).clampChannel() ,
|
||||
(this.rawG() + other.rawG()).clampChannel() ,
|
||||
(this.rawB() + other.rawB()).clampChannel()
|
||||
)
|
||||
|
||||
fun Int.rawR() = this / MUL_2
|
||||
fun Int.rawG() = this % MUL_2 / MUL
|
||||
fun Int.rawB() = this % MUL
|
||||
|
||||
/** 0.0 - 1.0 for 0-1023 (0.0 - 0.25 for 0-255) */
|
||||
fun Int.r(): Float = this.rawR() / CHANNEL_MAX_FLOAT
|
||||
fun Int.g(): Float = this.rawG() / CHANNEL_MAX_FLOAT
|
||||
fun Int.b(): Float = this.rawB() / CHANNEL_MAX_FLOAT
|
||||
|
||||
/**
|
||||
|
||||
* @param RGB
|
||||
* @param offset 2 = R, 1 = G, 0 = B
|
||||
* @return
|
||||
*/
|
||||
fun getRaw(RGB: Int, offset: Int): Int {
|
||||
if (offset == OFFSET_R) return RGB.rawR()
|
||||
else if (offset == OFFSET_G) return RGB.rawG()
|
||||
else if (offset == OFFSET_B) return RGB.rawB()
|
||||
else throw IllegalArgumentException("Channel offset out of range")
|
||||
}
|
||||
|
||||
private fun addRaw(rgb1: Int, rgb2: Int): Int {
|
||||
val newR = (rgb1.rawR() + rgb2.rawR()).clampChannel()
|
||||
val newG = (rgb1.rawG() + rgb2.rawG()).clampChannel()
|
||||
val newB = (rgb1.rawB() + rgb2.rawB()).clampChannel()
|
||||
|
||||
return constructRGBFromInt(newR, newG, newB)
|
||||
}
|
||||
|
||||
fun constructRGBFromInt(r: Int, g: Int, b: Int): Int {
|
||||
if (r !in 0..CHANNEL_MAX) throw IllegalArgumentException("Red: out of range ($r)")
|
||||
if (g !in 0..CHANNEL_MAX) throw IllegalArgumentException("Green: out of range ($g)")
|
||||
if (b !in 0..CHANNEL_MAX) throw IllegalArgumentException("Blue: out of range ($b)")
|
||||
return r * MUL_2 + g * MUL + b
|
||||
}
|
||||
|
||||
fun constructRGBFromFloat(r: Float, g: Float, b: Float): Int {
|
||||
if (r < 0 || r > CHANNEL_MAX_DECIMAL) throw IllegalArgumentException("Red: out of range ($r)")
|
||||
if (g < 0 || g > CHANNEL_MAX_DECIMAL) throw IllegalArgumentException("Green: out of range ($g)")
|
||||
if (b < 0 || b > CHANNEL_MAX_DECIMAL) throw IllegalArgumentException("Blue: out of range ($b)")
|
||||
|
||||
val intR = (r * CHANNEL_MAX).round()
|
||||
val intG = (g * CHANNEL_MAX).round()
|
||||
val intB = (b * CHANNEL_MAX).round()
|
||||
|
||||
return constructRGBFromInt(intR, intG, intB)
|
||||
}
|
||||
|
||||
private infix fun Int.linMix(other: Int): Int {
|
||||
val r = (this.rawR() + other.rawR()) ushr 1
|
||||
val g = (this.rawG() + other.rawG()) ushr 1
|
||||
val b = (this.rawB() + other.rawB()) ushr 1
|
||||
return constructRGBFromInt(r, g, b)
|
||||
}
|
||||
|
||||
private fun Int.clampZero() = if (this < 0) 0 else this
|
||||
|
||||
private fun Float.clampZero() = if (this < 0) 0f else this
|
||||
|
||||
private fun Int.clampChannel() = if (this < 0) 0 else if (this > CHANNEL_MAX) CHANNEL_MAX else this
|
||||
|
||||
private fun Float.clampOne() = if (this < 0) 0f else if (this > 1) 1f else this
|
||||
|
||||
private fun Float.clampChannel() = if (this > CHANNEL_MAX_DECIMAL) CHANNEL_MAX_DECIMAL else this
|
||||
|
||||
fun getValueFromMap(x: Int, y: Int): Int? = getLight(x, y)
|
||||
fun getHighestRGB(x: Int, y: Int): Int? {
|
||||
val value = getLight(x, y)
|
||||
if (value == null)
|
||||
return null
|
||||
else
|
||||
return FastMath.max(value.rawR(), value.rawG(), value.rawB())
|
||||
}
|
||||
|
||||
private fun purgeLightmap() {
|
||||
for (y in 0..LIGHTMAP_HEIGHT - 1) {
|
||||
for (x in 0..LIGHTMAP_WIDTH - 1) {
|
||||
lightmap[y][x] = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun arithmeticAverage(vararg i: Int): Int {
|
||||
var sum = 0
|
||||
for (k in i.indices) {
|
||||
sum += i[k]
|
||||
}
|
||||
return Math.round(sum / i.size.toFloat())
|
||||
}
|
||||
|
||||
private fun Int.clamp256() = if (this > 255) 255 else this
|
||||
|
||||
infix fun Float.powerOf(f: Float) = FastMath.pow(this, f)
|
||||
private fun Float.sqr() = this * this
|
||||
private fun Float.sqrt() = FastMath.sqrt(this)
|
||||
private fun Float.inv() = 1f / this
|
||||
fun Float.floor() = FastMath.floor(this)
|
||||
fun Double.floorInt() = Math.floor(this).toInt()
|
||||
fun Float.round(): Int = Math.round(this)
|
||||
fun Double.round(): Int = Math.round(this).toInt()
|
||||
fun Float.ceil() = FastMath.ceil(this)
|
||||
fun Int.even(): Boolean = this and 1 == 0
|
||||
fun Int.odd(): Boolean = this and 1 == 1
|
||||
fun Int.normaliseToColour(): Color = Color(
|
||||
Math.min(this.rawR(), 256),
|
||||
Math.min(this.rawG(), 256),
|
||||
Math.min(this.rawB(), 256)
|
||||
)
|
||||
|
||||
data class Lantern(val posX: Int, val posY: Int, val luminosity: Int)
|
||||
|
||||
val histogram: Histogram
|
||||
get() {
|
||||
var reds = IntArray(MUL) // reds[intensity] ← counts
|
||||
var greens = IntArray(MUL) // do.
|
||||
var blues = IntArray(MUL) // do.
|
||||
val render_width = for_x_end - for_x_start
|
||||
val render_height = for_y_end - for_y_start
|
||||
// excluiding overscans; only reckon echo lights
|
||||
for (y in overscan_open..render_height + overscan_open + 1) {
|
||||
for (x in overscan_open..render_width + overscan_open + 1) {
|
||||
reds[lightmap[y][x].rawR()] += 1
|
||||
greens[lightmap[y][x].rawG()] += 1
|
||||
blues[lightmap[y][x].rawB()] += 1
|
||||
}
|
||||
}
|
||||
return Histogram(reds, greens, blues)
|
||||
}
|
||||
|
||||
class Histogram(val reds: IntArray, val greens: IntArray, val blues: IntArray) {
|
||||
|
||||
val RED = 0
|
||||
val GREEN = 1
|
||||
val BLUE = 2
|
||||
|
||||
val screen_tiles: Int = (for_x_end - for_x_start + 2) * (for_y_end - for_y_start + 2)
|
||||
|
||||
val brightest: Int
|
||||
get() {
|
||||
for (i in CHANNEL_MAX downTo 1) {
|
||||
if (reds[i] > 0 || greens[i] > 0 || blues[i] > 0)
|
||||
return i
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
val brightest8Bit: Int
|
||||
get() { val b = brightest
|
||||
return if (brightest > 255) 255 else b
|
||||
}
|
||||
|
||||
val dimmest: Int
|
||||
get() {
|
||||
for (i in 0..CHANNEL_MAX) {
|
||||
if (reds[i] > 0 || greens[i] > 0 || blues[i] > 0)
|
||||
return i
|
||||
}
|
||||
return CHANNEL_MAX
|
||||
}
|
||||
|
||||
val range: Int = CHANNEL_MAX
|
||||
|
||||
fun get(index: Int): IntArray {
|
||||
return when (index) {
|
||||
RED -> reds
|
||||
GREEN -> greens
|
||||
BLUE -> blues
|
||||
else -> throw IllegalArgumentException()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
46
src/net/torvald/terrarum/worlddrawer/WorldCamera.kt
Normal file
46
src/net/torvald/terrarum/worlddrawer/WorldCamera.kt
Normal file
@@ -0,0 +1,46 @@
|
||||
package net.torvald.terrarum.worlddrawer
|
||||
|
||||
import com.jme3.math.FastMath
|
||||
import net.torvald.terrarum.Terrarum
|
||||
import net.torvald.terrarum.gameworld.GameWorld
|
||||
|
||||
/**
|
||||
* Created by minjaesong on 2016-12-30.
|
||||
*/
|
||||
object WorldCamera {
|
||||
private val world: GameWorld? = Terrarum.ingame?.world
|
||||
private val TILE_SIZE = FeaturesDrawer.TILE_SIZE
|
||||
|
||||
var x: Int = 0
|
||||
private set
|
||||
var y: Int = 0
|
||||
private set
|
||||
var width: Int = 0
|
||||
private set
|
||||
var height: Int = 0
|
||||
private set
|
||||
val xCentre: Int
|
||||
get() = x + width.ushr(1)
|
||||
val yCentre: Int
|
||||
get() = y + height.ushr(1)
|
||||
|
||||
fun update() {
|
||||
if (Terrarum.ingame != null) {
|
||||
|
||||
val player = Terrarum.ingame!!.player
|
||||
|
||||
width = FastMath.ceil(Terrarum.WIDTH / Terrarum.ingame!!.screenZoom) // div, not mul
|
||||
height = FastMath.ceil(Terrarum.HEIGHT / Terrarum.ingame!!.screenZoom)
|
||||
|
||||
// position - (WH / 2)
|
||||
x = Math.round(// X only: ROUNDWORLD implementation
|
||||
(player?.hitbox?.centeredX?.toFloat() ?: 0f) - width / 2)
|
||||
y = Math.round(FastMath.clamp(
|
||||
(player?.hitbox?.centeredY?.toFloat() ?: 0f) - height / 2,
|
||||
TILE_SIZE.toFloat(),
|
||||
world!!.height * TILE_SIZE - height - TILE_SIZE.toFloat()
|
||||
))
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user