From 3398ba62eea0ffe6b0e94267cb45ccc5415cbb66 Mon Sep 17 00:00:00 2001
From: DBotThePony <dbotthepony@yandex.ru>
Date: Mon, 4 Sep 2023 08:35:31 +0700
Subject: [PATCH] Cleaner version of previous commit

---
 .../kstarbound/client/ClientChunk.kt          | 18 ++---
 .../kstarbound/client/render/TileRenderer.kt  |  7 +-
 .../dbotthepony/kstarbound/world/CellView.kt  | 71 -----------------
 .../ru/dbotthepony/kstarbound/world/Chunk.kt  | 67 ++++++++--------
 .../dbotthepony/kstarbound/world/Helpers.kt   |  4 +-
 .../kstarbound/world/Raycasting.kt            |  1 -
 .../ru/dbotthepony/kstarbound/world/World.kt  | 44 ++++-------
 .../kstarbound/world/api/ICellAccess.kt       | 42 ++++++++++
 .../kstarbound/world/api/IChunk.kt            | 46 -----------
 .../kstarbound/world/api/ITileAccess.kt       | 78 +++++++++++--------
 10 files changed, 148 insertions(+), 230 deletions(-)
 delete mode 100644 src/main/kotlin/ru/dbotthepony/kstarbound/world/CellView.kt
 delete mode 100644 src/main/kotlin/ru/dbotthepony/kstarbound/world/api/IChunk.kt

diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/ClientChunk.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/ClientChunk.kt
index 46f9de2a..9c7e5ba8 100644
--- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/ClientChunk.kt
+++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/ClientChunk.kt
@@ -18,7 +18,7 @@ import ru.dbotthepony.kstarbound.world.*
 import ru.dbotthepony.kstarbound.world.api.CHUNK_SIZE
 import ru.dbotthepony.kstarbound.world.api.CHUNK_SIZEd
 import ru.dbotthepony.kstarbound.world.api.CHUNK_SIZEf
-import ru.dbotthepony.kstarbound.world.api.ITileChunk
+import ru.dbotthepony.kstarbound.world.api.ITileAccess
 import ru.dbotthepony.kstarbound.world.entities.Entity
 import ru.dbotthepony.kvector.arrays.Matrix4fStack
 import ru.dbotthepony.kvector.arrays.Matrix4f
@@ -41,7 +41,7 @@ const val Z_LEVEL_LIQUID = 10000
 class ClientChunk(world: ClientWorld, pos: ChunkPos) : Chunk<ClientWorld, ClientChunk>(world, pos), Closeable {
 	val state: GLStateTracker get() = world.client.gl
 
-	private inner class TileLayerRenderer(private val view: ITileChunk, private val isBackground: Boolean) : AutoCloseable {
+	private inner class TileLayerRenderer(private val view: ITileAccess, private val isBackground: Boolean) : AutoCloseable {
 		private val layers = TileLayerList()
 		val bakedMeshes = LinkedList<Pair<ConfiguredStaticMesh, Int>>()
 		var isDirty = true
@@ -153,8 +153,8 @@ class ClientChunk(world: ClientWorld, pos: ChunkPos) : Chunk<ClientWorld, Client
 	val debugCollisions get() = world.client.settings.debugCollisions
 	val posVector2d = Vector2d(x = pos.x * CHUNK_SIZEd, y = pos.y * CHUNK_SIZEd)
 
-	private val foregroundRenderer = TileLayerRenderer(localizedForegroundView, isBackground = false)
-	private val backgroundRenderer = TileLayerRenderer(localizedBackgroundView, isBackground = true)
+	private val foregroundRenderer = TileLayerRenderer(worldForegroundView, isBackground = false)
+	private val backgroundRenderer = TileLayerRenderer(worldBackgroundView, isBackground = true)
 
 	override fun foregroundChanges() {
 		super.foregroundChanges()
@@ -221,20 +221,20 @@ class ClientChunk(world: ClientWorld, pos: ChunkPos) : Chunk<ClientWorld, Client
 
 			for (x in this.x * SHADOW_GEOMETRY_SQUARE_SIZE until (this.x + 1) * SHADOW_GEOMETRY_SQUARE_SIZE) {
 				for (y in this.y * SHADOW_GEOMETRY_SQUARE_SIZE until (this.y + 1) * SHADOW_GEOMETRY_SQUARE_SIZE) {
-					if (foregroundView.getTile(x, y).material?.renderParameters?.lightTransparent == false) {
-						if (x == 0 || foregroundView.getTile(x - 1, y).material?.renderParameters?.lightTransparent != true) {
+					if (localForegroundView.getTile(x, y).material?.renderParameters?.lightTransparent == false) {
+						if (x == 0 || localForegroundView.getTile(x - 1, y).material?.renderParameters?.lightTransparent != true) {
 							line(builder, x.toFloat(), y + 1f, x.toFloat(), y.toFloat())
 						}
 
-						if (x == CHUNK_SIZE - 1 || foregroundView.getTile(x + 1, y).material?.renderParameters?.lightTransparent != true) {
+						if (x == CHUNK_SIZE - 1 || localForegroundView.getTile(x + 1, y).material?.renderParameters?.lightTransparent != true) {
 							line(builder, x + 1f, y.toFloat(), x + 1f, y + 1f)
 						}
 
-						if (y == 0 || foregroundView.getTile(x, y - 1).material?.renderParameters?.lightTransparent != true) {
+						if (y == 0 || localForegroundView.getTile(x, y - 1).material?.renderParameters?.lightTransparent != true) {
 							line(builder, x.toFloat(), y.toFloat(), x + 1f, y.toFloat())
 						}
 
-						if (y == CHUNK_SIZE - 1 || foregroundView.getTile(x, y + 1).material?.renderParameters?.lightTransparent != true) {
+						if (y == CHUNK_SIZE - 1 || localForegroundView.getTile(x, y + 1).material?.renderParameters?.lightTransparent != true) {
 							line(builder, x + 1f, y + 1f, x.toFloat(), y + 1f)
 						}
 					}
diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/TileRenderer.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/TileRenderer.kt
index d86cdf9f..170b6468 100644
--- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/TileRenderer.kt
+++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/TileRenderer.kt
@@ -12,7 +12,6 @@ import ru.dbotthepony.kstarbound.client.gl.vertex.GLAttributeList
 import ru.dbotthepony.kstarbound.client.gl.vertex.*
 import ru.dbotthepony.kstarbound.defs.tile.*
 import ru.dbotthepony.kstarbound.world.api.ITileAccess
-import ru.dbotthepony.kstarbound.world.api.ITileChunk
 import ru.dbotthepony.kstarbound.world.api.ITileState
 import ru.dbotthepony.kstarbound.world.api.TileColor
 import ru.dbotthepony.kvector.vector.RGBAColor
@@ -202,7 +201,7 @@ class TileRenderer(val renderers: TileRenderers, val def: IRenderableTile) {
 	val bakedBackgroundProgramState = renderers.background(texture)
 	// private var notifiedDepth = false
 
-	private fun tesselateAt(self: ITileState, piece: RenderPiece, getter: ITileChunk, builder: VertexBuilder, pos: Vector2i, offset: Vector2i = Vector2i.ZERO, isModifier: Boolean) {
+	private fun tesselateAt(self: ITileState, piece: RenderPiece, getter: ITileAccess, builder: VertexBuilder, pos: Vector2i, offset: Vector2i = Vector2i.ZERO, isModifier: Boolean) {
 		val fx = pos.x.toFloat()
 		val fy = pos.y.toFloat()
 
@@ -246,7 +245,7 @@ class TileRenderer(val renderers: TileRenderers, val def: IRenderableTile) {
 	private fun tesselatePiece(
 		self: ITileState,
 		matchPiece: RenderMatch,
-		getter: ITileChunk,
+		getter: ITileAccess,
 		layers: TileLayerList,
 		pos: Vector2i,
 		thisBuilder: VertexBuilder,
@@ -295,7 +294,7 @@ class TileRenderer(val renderers: TileRenderers, val def: IRenderableTile) {
 	 *
 	 * Тесселирует тайлы в нужный VertexBuilder с масштабом согласно константе [PIXELS_IN_STARBOUND_UNITf]
 	 */
-	fun tesselate(self: ITileState, getter: ITileChunk, layers: TileLayerList, pos: Vector2i, background: Boolean = false, isModifier: Boolean = false) {
+	fun tesselate(self: ITileState, getter: ITileAccess, layers: TileLayerList, pos: Vector2i, background: Boolean = false, isModifier: Boolean = false) {
 		// если у нас нет renderTemplate
 		// то мы просто не можем его отрисовать
 		val template = def.renderTemplate.value ?: return
diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/CellView.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/CellView.kt
deleted file mode 100644
index f45ad5e9..00000000
--- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/CellView.kt
+++ /dev/null
@@ -1,71 +0,0 @@
-package ru.dbotthepony.kstarbound.world
-
-import ru.dbotthepony.kstarbound.world.api.BackgroundChunkView
-import ru.dbotthepony.kstarbound.world.api.CHUNK_SIZE
-import ru.dbotthepony.kstarbound.world.api.ForegroundChunkView
-import ru.dbotthepony.kstarbound.world.api.IChunk
-import ru.dbotthepony.kstarbound.world.api.IChunkCell
-import ru.dbotthepony.kvector.arrays.Object2DArray
-
-/**
- * Предоставляет доступ к чанку и его соседям
- *
- * В основном для использования в местах, где нужен не мир, а определённый чанк мира,
- * и при этом координаты проверяются относительно чанка и могут спокойно выйти за его пределы,
- * с желанием получить тайл из соседнего чанка
- */
-class CellView(
-	override val pos: ChunkPos,
-
-	val left: IChunk?,
-	val center: IChunk?,
-	val right: IChunk?,
-
-	val topRight: IChunk?,
-	val top: IChunk?,
-	val topLeft: IChunk?,
-
-	val bottomLeft: IChunk?,
-	val bottom: IChunk?,
-	val bottomRight: IChunk?,
-) : IChunk {
-	val backgroundView = BackgroundChunkView(this)
-	val foregroundView = ForegroundChunkView(this)
-
-	override fun getCell(x: Int, y: Int): IChunkCell {
-		val ix = x + CHUNK_SIZE
-		val iy = y + CHUNK_SIZE
-
-		if (ix in 0 until CHUNK_SIZE * 3 - 1 && iy in 0 until CHUNK_SIZE * 3 - 1) {
-			return indices[ix, iy].invoke(this) ?: IChunkCell.Companion
-		} else {
-			return IChunkCell.Companion
-		}
-	}
-
-	companion object {
-		private val indices = Object2DArray.nulls<(CellView) -> IChunkCell?>(CHUNK_SIZE * 3, CHUNK_SIZE * 3) as Object2DArray<(CellView) -> IChunkCell?>
-
-		private fun put(half: (CellView) -> IChunk?, x: Int, y: Int) {
-			for (ix in 0 until CHUNK_SIZE) {
-				for (iy in 0 until CHUNK_SIZE) {
-					indices[x + ix, y + iy] = { half(it)?.getCell(ix, iy) }
-				}
-			}
-		}
-
-		init {
-			put(CellView::bottomLeft,   0,              0)
-			put(CellView::bottom,       CHUNK_SIZE,     0)
-			put(CellView::bottomRight,  CHUNK_SIZE * 2, 0)
-
-			put(CellView::left,         0,              CHUNK_SIZE)
-			put(CellView::center,       CHUNK_SIZE,     CHUNK_SIZE)
-			put(CellView::right,        CHUNK_SIZE * 2, CHUNK_SIZE)
-
-			put(CellView::topLeft,      0,              CHUNK_SIZE * 2)
-			put(CellView::top,          CHUNK_SIZE,     CHUNK_SIZE * 2)
-			put(CellView::topRight,     CHUNK_SIZE * 2, CHUNK_SIZE * 2)
-		}
-	}
-}
diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/Chunk.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/Chunk.kt
index e9803ab0..b0c18bf6 100644
--- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/Chunk.kt
+++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/Chunk.kt
@@ -7,19 +7,17 @@ import ru.dbotthepony.kbox2d.dynamics.B2Fixture
 import ru.dbotthepony.kstarbound.defs.tile.LiquidDefinition
 import ru.dbotthepony.kstarbound.defs.tile.MaterialModifier
 import ru.dbotthepony.kstarbound.defs.tile.TileDefinition
-import ru.dbotthepony.kstarbound.world.api.BackgroundChunkView
-import ru.dbotthepony.kstarbound.world.api.BackgroundLocalizedView
 import ru.dbotthepony.kstarbound.world.api.CHUNK_SIZE_BITS
 import ru.dbotthepony.kstarbound.world.api.CHUNK_SIZE
 import ru.dbotthepony.kstarbound.world.api.CHUNK_SIZE_FF
 import ru.dbotthepony.kstarbound.world.api.CHUNK_SIZEd
-import ru.dbotthepony.kstarbound.world.api.ForegroundChunkView
-import ru.dbotthepony.kstarbound.world.api.ForegroundLocalizedView
-import ru.dbotthepony.kstarbound.world.api.IChunk
+import ru.dbotthepony.kstarbound.world.api.ICellAccess
 import ru.dbotthepony.kstarbound.world.api.IChunkCell
 import ru.dbotthepony.kstarbound.world.api.ILiquidState
 import ru.dbotthepony.kstarbound.world.api.ITileState
+import ru.dbotthepony.kstarbound.world.api.OffsetCellAccess
 import ru.dbotthepony.kstarbound.world.api.TileColor
+import ru.dbotthepony.kstarbound.world.api.TileView
 import ru.dbotthepony.kstarbound.world.entities.Entity
 import ru.dbotthepony.kstarbound.world.phys.RectTileFlooderDepthFirst
 import ru.dbotthepony.kstarbound.world.phys.RectTileFlooderSizeFirst
@@ -40,7 +38,7 @@ import kotlin.collections.HashSet
  *
  * Весь игровой мир будет измеряться в Starbound Unit'ах
  */
-abstract class Chunk<WorldType : World<WorldType, This>, This : Chunk<WorldType, This>>(val world: WorldType, final override val pos: ChunkPos) : IChunk {
+abstract class Chunk<WorldType : World<WorldType, This>, This : Chunk<WorldType, This>>(val world: WorldType, val pos: ChunkPos) : ICellAccess {
 	var changeset = 0
 		private set
 	var tileChangeset = 0
@@ -57,6 +55,35 @@ abstract class Chunk<WorldType : World<WorldType, This>, This : Chunk<WorldType,
 	var backgroundChangeset = 0
 		private set
 
+	protected val cells = Object2DArray(CHUNK_SIZE, CHUNK_SIZE, ::Cell)
+
+	override fun getCell(x: Int, y: Int): IChunkCell {
+		return cells[x, y]
+	}
+
+	// local cells' tile access
+	val localBackgroundView = TileView.background(this)
+	val localForegroundView = TileView.foreground(this)
+
+	// relative world cells access (accessing 0, 0 will lookup cell in world, relative to this chunk)
+	val worldView = OffsetCellAccess(world.chunkMap, pos)
+	val worldBackgroundView = TileView.background(worldView)
+	val worldForegroundView = TileView.foreground(worldView)
+
+	val aabb = aabbBase + Vector2d(pos.x * CHUNK_SIZE.toDouble(), pos.y * CHUNK_SIZE.toDouble())
+
+	var isPhysicsDirty = false
+
+	private val collisionCache = ArrayList<AABB>()
+	private val collisionCacheView = Collections.unmodifiableCollection(collisionCache)
+
+	private val body = world.physics.createBody(BodyDef(
+		position = pos.firstTile.toDoubleVector(),
+		userData = this
+	))
+
+	private val collisionChains = ArrayList<B2Fixture>()
+
 	protected open fun foregroundChanges() {
 		changeset++
 		cellChangeset++
@@ -86,10 +113,6 @@ abstract class Chunk<WorldType : World<WorldType, This>, This : Chunk<WorldType,
 		world.chunkMap[pos.bottomRight]?.let(block)
 	}
 
-	val aabb = aabbBase + Vector2d(pos.x * CHUNK_SIZE.toDouble(), pos.y * CHUNK_SIZE.toDouble())
-
-	var isPhysicsDirty = false
-
 	fun markPhysicsDirty() {
 		if (isPhysicsDirty)
 			return
@@ -98,21 +121,6 @@ abstract class Chunk<WorldType : World<WorldType, This>, This : Chunk<WorldType,
 		world.dirtyPhysicsChunks.add(this as This)
 	}
 
-	protected val cells = Object2DArray(CHUNK_SIZE, CHUNK_SIZE, ::Cell)
-
-	val backgroundView = BackgroundChunkView(this)
-	val foregroundView = ForegroundChunkView(this)
-
-	val localizedBackgroundView = BackgroundLocalizedView(pos, world.chunkMap)
-	val localizedForegroundView = ForegroundLocalizedView(pos, world.chunkMap)
-
-	override fun getCell(x: Int, y: Int): IChunkCell {
-		return cells[x, y]
-	}
-
-	private val collisionCache = ArrayList<AABB>()
-	private val collisionCacheView = Collections.unmodifiableCollection(collisionCache)
-
 	inner class Cell(val x: Int, val y: Int) : IChunkCell {
 		inner class Tile(private val foreground: Boolean) : ITileState {
 			private fun change() {
@@ -254,13 +262,6 @@ abstract class Chunk<WorldType : World<WorldType, This>, This : Chunk<WorldType,
 			get() = false
 	}
 
-	private val body = world.physics.createBody(BodyDef(
-		position = pos.firstTile.toDoubleVector(),
-		userData = this
-	))
-
-	private val collisionChains = ArrayList<B2Fixture>()
-
 	fun bakeCollisions() {
 		if (collisionChangeset == changeset)
 			return
@@ -320,7 +321,7 @@ abstract class Chunk<WorldType : World<WorldType, This>, This : Chunk<WorldType,
 	}
 
 	override fun randomLongFor(x: Int, y: Int): Long {
-		return super.randomLongFor(x, y) xor world.seed
+		return world.chunkMap.randomLongFor(x or pos.x shl CHUNK_SIZE_BITS, y or pos.y shl CHUNK_SIZE_BITS)
 	}
 
 	protected val entities = HashSet<Entity>()
diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/Helpers.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/Helpers.kt
index 62ee1749..6d729610 100644
--- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/Helpers.kt
+++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/Helpers.kt
@@ -1,13 +1,13 @@
 package ru.dbotthepony.kstarbound.world
 
 import ru.dbotthepony.kstarbound.world.api.CHUNK_SIZE
-import ru.dbotthepony.kstarbound.world.api.IChunk
+import ru.dbotthepony.kstarbound.world.api.ICellAccess
 import ru.dbotthepony.kstarbound.world.api.IChunkCell
 import ru.dbotthepony.kstarbound.world.api.ITileAccess
 import ru.dbotthepony.kstarbound.world.api.ITileState
 import ru.dbotthepony.kvector.vector.Vector2i
 
-fun IChunk.iterate(fromX: Int = 0, fromY: Int = 0, toX: Int = fromX + CHUNK_SIZE, toY: Int = fromY + CHUNK_SIZE): Iterator<Pair<Vector2i, IChunkCell>> {
+fun ICellAccess.iterate(fromX: Int = 0, fromY: Int = 0, toX: Int = fromX + CHUNK_SIZE, toY: Int = fromY + CHUNK_SIZE): Iterator<Pair<Vector2i, IChunkCell>> {
 	return object : Iterator<Pair<Vector2i, IChunkCell>> {
 		private var x = fromX
 		private var y = fromY
diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/Raycasting.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/Raycasting.kt
index 86d794b8..86e939a2 100644
--- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/Raycasting.kt
+++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/Raycasting.kt
@@ -14,7 +14,6 @@ import kotlin.math.cos
 import kotlin.math.roundToInt
 import kotlin.math.sin
 
-
 const val EARTH_FREEFALL_ACCELERATION = 9.8312 / METRES_IN_STARBOUND_UNIT
 
 data class RayCastResult(
diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/World.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/World.kt
index c0fc5be3..09acddd5 100644
--- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/World.kt
+++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/World.kt
@@ -11,13 +11,12 @@ import ru.dbotthepony.kbox2d.dynamics.B2World
 import ru.dbotthepony.kbox2d.dynamics.contact.AbstractContact
 import ru.dbotthepony.kstarbound.math.*
 import ru.dbotthepony.kstarbound.util.Timer
-import ru.dbotthepony.kstarbound.world.api.BackgroundAccessView
 import ru.dbotthepony.kstarbound.world.api.CHUNK_SIZE
 import ru.dbotthepony.kstarbound.world.api.CHUNK_SIZE_BITS
 import ru.dbotthepony.kstarbound.world.api.CHUNK_SIZE_MASK
-import ru.dbotthepony.kstarbound.world.api.ForegroundAccessView
 import ru.dbotthepony.kstarbound.world.api.ICellAccess
 import ru.dbotthepony.kstarbound.world.api.IChunkCell
+import ru.dbotthepony.kstarbound.world.api.TileView
 import ru.dbotthepony.kstarbound.world.entities.Entity
 import ru.dbotthepony.kvector.api.IStruct2d
 import ru.dbotthepony.kvector.api.IStruct2i
@@ -128,8 +127,12 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
 		fun inBounds(x: Int, y: Int) = this.x.inBounds(x) && this.y.inBounds(y)
 		fun inBounds(value: IStruct2i) = this.x.inBounds(value.component1()) && this.y.inBounds(value.component2())
 
-		val backgroundView = BackgroundAccessView(this)
-		val foregroundView = ForegroundAccessView(this)
+		override fun randomLongFor(x: Int, y: Int): Long {
+			return super.randomLongFor(x, y) xor seed
+		}
+
+		val background = TileView.background(this)
+		val foreground = TileView.foreground(this)
 
 		abstract operator fun get(x: Int, y: Int): ChunkType?
 		operator fun get(pos: ChunkPos) = get(pos.x, pos.y)
@@ -394,21 +397,6 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
 
 	protected abstract fun chunkFactory(pos: ChunkPos): ChunkType
 
-	fun getView(pos: ChunkPos): CellView {
-		return CellView(
-			pos = 				pos,
-			center = 			chunkMap[pos],
-			left = 				chunkMap[pos.left],
-			top = 				chunkMap[pos.top],
-			topLeft = 			chunkMap[pos.topLeft],
-			topRight = 			chunkMap[pos.topRight],
-			right = 			chunkMap[pos.right],
-			bottom = 			chunkMap[pos.bottom],
-			bottomLeft = 		chunkMap[pos.bottomLeft],
-			bottomRight = 		chunkMap[pos.bottomRight],
-		)
-	}
-
 	/**
 	 * Возвращает все чанки, которые пересекаются с заданным [boundingBox]
 	 */
@@ -480,7 +468,6 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
 
 	private fun floodLightInto(
 		lightmap: Int2DArray,
-		view: CellView,
 		thisIntensity: Int,
 		lightBlockerStrength: Int,
 		posX: Int,
@@ -492,7 +479,7 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
 			return 1
 		}
 
-		val tile = view.getCell(worldPosX, worldPosY)
+		val tile = chunkMap.getCell(worldPosX, worldPosY)
 
 		val newIntensity: Int
 
@@ -508,7 +495,7 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
 			var c = 1
 
 			c += floodLightInto(
-				lightmap, view, newIntensity, lightBlockerStrength,
+				lightmap, newIntensity, lightBlockerStrength,
 				posX + 1,
 				worldPosX + 1,
 				posY,
@@ -516,7 +503,7 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
 			)
 
 			c += floodLightInto(
-				lightmap, view, newIntensity, lightBlockerStrength,
+				lightmap, newIntensity, lightBlockerStrength,
 				posX - 1,
 				worldPosX - 1,
 				posY,
@@ -524,7 +511,7 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
 			)
 
 			c += floodLightInto(
-				lightmap, view, newIntensity, lightBlockerStrength,
+				lightmap, newIntensity, lightBlockerStrength,
 				posX,
 				worldPosX,
 				posY + 1,
@@ -532,7 +519,7 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
 			)
 
 			c += floodLightInto(
-				lightmap, view, newIntensity, lightBlockerStrength,
+				lightmap, newIntensity, lightBlockerStrength,
 				posX,
 				worldPosX,
 				posY - 1,
@@ -564,17 +551,14 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
 
 		val lightmap = Int2DArray.allocate(lightIntensity * 2 + 1, lightIntensity * 2 + 1)
 
-		val view = getView(ChunkPos.fromPosition(lightPosition))
-
 		floodLightInto(
 			lightmap,
-			view,
 			lightIntensity,
 			lightBlockerStrength,
 			lightIntensity,
-			lightPosition.x - view.pos.tileX,
+			lightPosition.x,
 			lightIntensity,
-			lightPosition.y - view.pos.tileY,
+			lightPosition.y,
 		)
 
 		return lightmap
diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/api/ICellAccess.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/api/ICellAccess.kt
index 876f2cf6..6d579164 100644
--- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/api/ICellAccess.kt
+++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/api/ICellAccess.kt
@@ -1,9 +1,51 @@
 package ru.dbotthepony.kstarbound.world.api
 
+import ru.dbotthepony.kstarbound.world.ChunkPos
 import ru.dbotthepony.kvector.api.IStruct2i
+import ru.dbotthepony.kvector.vector.Vector2i
 
 interface ICellAccess {
 	// relative
 	fun getCell(x: Int, y: Int): IChunkCell
 	fun getCell(pos: IStruct2i) = getCell(pos.component1(), pos.component2())
+
+	/**
+	 * Возвращает псевдослучайное Long для заданной позиции
+	 *
+	 * Для использования в рендерах и прочих вещах, которым нужно стабильное число на основе своей позиции
+	 */
+	fun randomLongFor(x: Int, y: Int): Long {
+		var long = x * 738548L + y * 2191293543L
+		long = long xor 8339437585692L
+		long = (long ushr 4) or (long shl 52)
+		long *= 7848344324L
+		long = (long ushr 12) or (long shl 44)
+		return long
+	}
+
+	/**
+	 * Возвращает псевдослучайное нормализированное Double для заданной позиции
+	 *
+	 * Для использования в рендерах и прочих вещах, которым нужно стабильное число на основе своей позиции
+	 */
+	fun randomDoubleFor(x: Int, y: Int): Double {
+		return (randomLongFor(x, y) / 9.223372036854776E18) / 2.0 + 0.5
+	}
+
+	fun randomLongFor(pos: Vector2i) = randomLongFor(pos.x, pos.y)
+	fun randomDoubleFor(pos: Vector2i) = randomDoubleFor(pos.x, pos.y)
+}
+
+class OffsetCellAccess(private val parent: ICellAccess, private val x: Int, private val y: Int) : ICellAccess {
+	constructor(parent: ICellAccess, offset: IStruct2i) : this(parent, offset.component1(), offset.component2())
+	constructor(parent: ICellAccess, offset: ChunkPos) : this(parent, offset.firstTile)
+
+	override fun getCell(x: Int, y: Int): IChunkCell {
+		return parent.getCell(x + this.x, y + this.y)
+	}
+
+	override fun randomLongFor(x: Int, y: Int) = parent.randomLongFor(x + this.x, y + this.y)
+	override fun randomDoubleFor(x: Int, y: Int) = parent.randomDoubleFor(x + this.x, y + this.y)
+	override fun randomLongFor(pos: Vector2i) = parent.randomLongFor(pos.x + this.x, pos.y + this.y)
+	override fun randomDoubleFor(pos: Vector2i) = parent.randomDoubleFor(pos.x + this.x, pos.y + this.y)
 }
diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/api/IChunk.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/api/IChunk.kt
deleted file mode 100644
index d4b0d489..00000000
--- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/api/IChunk.kt
+++ /dev/null
@@ -1,46 +0,0 @@
-package ru.dbotthepony.kstarbound.world.api
-
-import ru.dbotthepony.kstarbound.world.ChunkPos
-import ru.dbotthepony.kvector.api.IStruct2i
-import ru.dbotthepony.kvector.vector.Vector2i
-
-interface IChunk : ICellAccess {
-	val pos: ChunkPos
-
-	/**
-	 * Возвращает псевдослучайное Long для заданной позиции
-	 *
-	 * Для использования в рендерах и прочих вещах, которым нужно стабильное число на основе своей позиции
-	 */
-	fun randomLongFor(x: Int, y: Int): Long {
-		var long = (x or (pos.x shl CHUNK_SIZE_BITS)) * 738548L + (y or (pos.y shl CHUNK_SIZE_BITS)) * 2191293543L
-		long = long xor 8339437585692L
-		long = (long ushr 4) or (long shl 52)
-		long *= 7848344324L
-		long = (long ushr 12) or (long shl 44)
-		return long
-	}
-
-	/**
-	 * Возвращает псевдослучайное нормализированное Double для заданной позиции
-	 *
-	 * Для использования в рендерах и прочих вещах, которым нужно стабильное число на основе своей позиции
-	 */
-	fun randomDoubleFor(x: Int, y: Int): Double {
-		return (randomLongFor(x, y) / 9.223372036854776E18) / 2.0 + 0.5
-	}
-
-	/**
-	 * Возвращает псевдослучайное Long для заданной позиции
-	 *
-	 * Для использования в рендерах и прочих вещах, которым нужно стабильное число на основе своей позиции
-	 */
-	fun randomLongFor(pos: Vector2i) = randomLongFor(pos.x, pos.y)
-
-	/**
-	 * Возвращает псевдослучайное нормализированное Double для заданной позиции
-	 *
-	 * Для использования в рендерах и прочих вещах, которым нужно стабильное число на основе своей позиции
-	 */
-	fun randomDoubleFor(pos: Vector2i) = randomDoubleFor(pos.x, pos.y)
-}
diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/api/ITileAccess.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/api/ITileAccess.kt
index 41d4952d..c3243470 100644
--- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/api/ITileAccess.kt
+++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/api/ITileAccess.kt
@@ -10,40 +10,50 @@ interface ITileAccess : ICellAccess {
 	fun getTile(pos: IStruct2i) = getTile(pos.component1(), pos.component2())
 }
 
-interface ITileChunk : IChunk, ITileAccess
+sealed class TileView(parent: ICellAccess) : ITileAccess, ICellAccess by parent {
+	private class Foreground(parent: ICellAccess) : TileView(parent) {
+		override fun getTile(x: Int, y: Int): ITileState {
+			return getCell(x, y).foreground
+		}
+	}
 
-class ForegroundChunkView(private val parent: IChunk) : ITileChunk, IChunk by parent {
-	override fun getTile(x: Int, y: Int): ITileState {
-		return parent.getCell(x, y).foreground
-	}
-}
-
-class BackgroundChunkView(private val parent: IChunk) : ITileChunk, IChunk by parent {
-	override fun getTile(x: Int, y: Int): ITileState {
-		return parent.getCell(x, y).background
-	}
-}
-
-class ForegroundLocalizedView(override val pos: ChunkPos, private val parent: ICellAccess) : ITileChunk, ICellAccess by parent {
-	override fun getTile(x: Int, y: Int): ITileState {
-		return parent.getCell(pos.tileX + x, pos.tileY + y).foreground
-	}
-}
-
-class BackgroundLocalizedView(override val pos: ChunkPos, private val parent: ICellAccess) : ITileChunk, ICellAccess by parent {
-	override fun getTile(x: Int, y: Int): ITileState {
-		return parent.getCell(pos.tileX + x, pos.tileY + y).background
-	}
-}
-
-class ForegroundAccessView(private val parent: ICellAccess) : ITileAccess, ICellAccess by parent {
-	override fun getTile(x: Int, y: Int): ITileState {
-		return parent.getCell(x, y).foreground
-	}
-}
-
-class BackgroundAccessView(private val parent: ICellAccess) : ITileAccess, ICellAccess by parent {
-	override fun getTile(x: Int, y: Int): ITileState {
-		return parent.getCell(x, y).background
+	private class Background(parent: ICellAccess) : TileView(parent) {
+		override fun getTile(x: Int, y: Int): ITileState {
+			return getCell(x, y).background
+		}
+	}
+
+	companion object {
+		fun foreground(parent: ICellAccess, xOffset: Int = 0, yOffset: Int = 0): TileView {
+			if (xOffset == 0 && yOffset == 0) {
+				return Foreground(parent)
+			} else {
+				return Foreground(OffsetCellAccess(parent, xOffset, yOffset))
+			}
+		}
+
+		fun foreground(parent: ICellAccess, offset: IStruct2i): TileView {
+			return Foreground(OffsetCellAccess(parent, offset))
+		}
+
+		fun foreground(parent: ICellAccess, offset: ChunkPos): TileView {
+			return Foreground(OffsetCellAccess(parent, offset))
+		}
+
+		fun background(parent: ICellAccess, xOffset: Int = 0, yOffset: Int = 0): TileView {
+			if (xOffset == 0 && yOffset == 0) {
+				return Background(parent)
+			} else {
+				return Background(OffsetCellAccess(parent, xOffset, yOffset))
+			}
+		}
+
+		fun background(parent: ICellAccess, offset: IStruct2i): TileView {
+			return Background(OffsetCellAccess(parent, offset))
+		}
+
+		fun background(parent: ICellAccess, offset: ChunkPos): TileView {
+			return Background(OffsetCellAccess(parent, offset))
+		}
 	}
 }