Skip to content

Commit

Permalink
done algorithm, but not tested so not working
Browse files Browse the repository at this point in the history
  • Loading branch information
bananasmoothii committed Mar 30, 2023
1 parent 4e5a4f9 commit 684a50c
Show file tree
Hide file tree
Showing 12 changed files with 127 additions and 43 deletions.
15 changes: 15 additions & 0 deletions .idea/git_toolbox_prj.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion src/main/kotlin/fr/bananasmoothii/wfc/space/Coords.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,5 @@ interface Coords<D : Dimension<D>> : Dimensioned<D> {
/**
* @return the coordinates of the neighbours (adjacent tiles) of this coordinate
*/
fun getNeighbours(): List<Coords<D>> = dimension.directions.map { move(it) }
fun getNeighbours(): List<Coords<D>> = dimension.directionsAt(this).map { move(it) }
}
8 changes: 4 additions & 4 deletions src/main/kotlin/fr/bananasmoothii/wfc/space/Dimension.kt
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
package fr.bananasmoothii.wfc.space

import fr.bananasmoothii.wfc.space.d3.Dimension3D

/**
* Typically 3D: [Dimension3D], or 2D.
* Typically 3D: [Dimension3D], or 2D, but can also be anything you want, even in non-euclidean spaces.
* @param D the type of the implementing class
*/
interface Dimension<D: Dimension<D>> {
val dimension: Int

val directions: Array<out Direction<D>>
fun directionsAt(coords: Coords<D>): Array<out Direction<D>>
}
2 changes: 1 addition & 1 deletion src/main/kotlin/fr/bananasmoothii/wfc/space/Dimensioned.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@ package fr.bananasmoothii.wfc.space
/**
* Indicates that the implementing class has a certain dimension.
*/
interface Dimensioned<D: Dimension> {
interface Dimensioned<D: Dimension<D>> {
val dimension: D
}
4 changes: 4 additions & 0 deletions src/main/kotlin/fr/bananasmoothii/wfc/space/Direction.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
package fr.bananasmoothii.wfc.space

/**
* A direction in a [Dimension].
* All [Direction]s should have an unique [hashCode] for a given [Dimension].
*/
interface Direction<D: Dimension<D>>: Dimensioned<D> {
val opposite: Direction<D>
}
7 changes: 3 additions & 4 deletions src/main/kotlin/fr/bananasmoothii/wfc/space/d3/Dimension3D.kt
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
package fr.bananasmoothii.wfc.space.d3

import fr.bananasmoothii.wfc.space.Coords
import fr.bananasmoothii.wfc.space.Dimension

object Dimension3D: Dimension<Dimension3D> {
override val dimension: Int = 3

override val directions: Array<Direction3D> = Direction3D.values
}
override fun directionsAt(coords: Coords<Dimension3D>) = Direction3D.values
}
18 changes: 18 additions & 0 deletions src/main/kotlin/fr/bananasmoothii/wfc/space/d3/Direction3D.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,47 @@ package fr.bananasmoothii.wfc.space.d3

import fr.bananasmoothii.wfc.space.Direction

@Suppress("EqualsOrHashCode")
sealed class Direction3D: Direction<Dimension3D>, D3 {
object NORTH: Direction3D() {
override val opposite: SOUTH = SOUTH

override fun hashCode(): Int = 1
}

object SOUTH: Direction3D() {
override val opposite: NORTH = NORTH

override fun hashCode(): Int = 2
}

object EAST: Direction3D() {
override val opposite: WEST = WEST

override fun hashCode(): Int = 3
}

object WEST: Direction3D() {
override val opposite: EAST = EAST

override fun hashCode(): Int = 4
}

object UP: Direction3D() {
override val opposite: DOWN = DOWN

override fun hashCode(): Int = 5
}

object DOWN: Direction3D() {
override val opposite: UP = UP

override fun hashCode(): Int = 6
}

override fun equals(other: Any?): Boolean {
if (other == null) return false
return hashCode() == other.hashCode()
}

companion object {
Expand Down
8 changes: 6 additions & 2 deletions src/main/kotlin/fr/bananasmoothii/wfc/tile/Tile.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,16 @@ interface Tile<C: Rotatable<D>, D: Dimension<D>>: Dimensioned<D> {
*/
fun accepts(direction: Direction<D>, neighborId: Int): Boolean

/**
/*
* Returns a mask of the accepted neighbors in the given direction.
* Warning: for performance reasons, the returned array is the internal array of the tile, so don't modify it.
*/
*
fun getNeighborMask(direction: Direction<D>): LongArray
*/

fun addAllowedNeighborsToArray(bitMask: LongArray, direction: Direction<D>)

/*
* Applies the neighbor mask to the given array. Each bit in [currentOptions] will be set to 0 if the corresponding
* neighbor is not accepted.
Expand Down
2 changes: 2 additions & 0 deletions src/main/kotlin/fr/bananasmoothii/wfc/tile/TileSet.kt
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ abstract class TileSet<C : Rotatable<D>, D : Dimension<D>> : Dimensioned<D> {

abstract val tiles: List<Tile<C, D>>

abstract fun fromId(id: Int): Tile<C, D>

abstract fun createOrGetTile(content: C): Tile<C, D>

private var actionsAfterFinishCreation: MutableList<() -> Unit>? = mutableListOf()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,22 @@ open class SimpleTile3D<C : Rotatable3D> internal constructor(
override fun accepts(direction: Direction<Dimension3D>, neighborId: Int): Boolean =
getArray(direction).getBitAt(neighborId)

/**
/*
* Warning: this exposes the internal array. Don't modify it.
*/
*
override fun getNeighborMask(direction: Direction<Dimension3D>): LongArray {
return getArray(direction as Direction3D)
}
*/

override fun addAllowedNeighborsToArray(bitMask: LongArray, direction: Direction<Dimension3D>) {
val array = getArray(direction as Direction3D)
for (i in bitMask.indices) {
bitMask[i] = bitMask[i] or array[i]
}
}

fun applyNeighborMask(direction: Direction<Dimension3D>, currentOptions: LongArray): Boolean {
var changed = false
val array = getArray(direction)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package fr.bananasmoothii.wfc.tile.simple.d3

import fr.bananasmoothii.wfc.space.d3.D3
import fr.bananasmoothii.wfc.space.d3.Dimension3D
import fr.bananasmoothii.wfc.tile.Tile
import fr.bananasmoothii.wfc.tile.d3.Rotatable3D
import fr.bananasmoothii.wfc.tile.simple.SimpleTileSet

Expand All @@ -12,6 +13,8 @@ class SimpleTileSet3D<C : Rotatable3D> : SimpleTileSet<C, Dimension3D>(), D3 {
override val tiles: List<SimpleTile3D<C>>
get() = _tiles

override fun fromId(id: Int): Tile<C, Dimension3D> = _tiles[id]

override fun createOrGetTile(content: C): SimpleTile3D<C> {
if (!canCreateNewPieces) throw IllegalStateException("Can't create new pieces after finishPieceCreation() has been called")
val tileWithSameContent = _tiles.find { it.content == content }
Expand Down
88 changes: 59 additions & 29 deletions src/main/kotlin/fr/bananasmoothii/wfc/wavefunction/WaveFunction.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,65 +11,79 @@ import fr.bananasmoothii.wfc.util.*
import fr.bananasmoothii.wfc.wavefunction.WaveFunction.PropagationTask.Companion.addPropagationTasksInBoundsToList
import kotlin.random.Random

class WaveFunction<C : Rotatable<D>, D : Dimension<D>> private constructor(
open class WaveFunction<C : Rotatable<D>, D : Dimension<D>>(
val tileSet: TileSet<C, D>,
protected var map: MutableMap<Coords<D>, LongArray>,
private val random: Random = Random.Default,
) : Map<Coords<D>, Tile<C, D>>, Versionable {
protected val random: Random = Random.Default,
) : Iterable<Map.Entry<Coords<D>, List<Tile<C, D>>>>, Versionable {

init {
require(!tileSet.canCreateNewPieces) { "You have to finish creating the TileSet before using it in generation" }
}

private var lastCoordsWherePicked: Coords<D>? = null
private var lastTileIdPicked: Int? = null
protected var map = mutableMapOf<Coords<D>, LongArray>()

private val allTiles = tileSet.tiles
private val maxId = tileSet.maxId
private val arraySize = tileSet.arraySize
protected var lastCoordsWherePicked: Coords<D>? = null
protected var lastTileIdPicked: Int? = null

//private val dispatcher = newSingleThreadContext("Propagation thread")
protected val maxId = tileSet.maxId
protected val maxEntropyArray = tileSet.maxEntropyArray
protected val arraySize = tileSet.arraySize

constructor(tileSet: TileSet<C, D>) : this(
tileSet,
mutableMapOf<Coords<D>, LongArray>(),
)
//private val dispatcher = newSingleThreadContext("Propagation thread")

fun collapse(bounds: Bounds<D>) {
for (coords in bounds) {
if (map[coords])
val tilesAtCoordsArray = map[coords]
if (tilesAtCoordsArray?.hasSingle1Bit() == true) continue
commit()
val chosenPieceId = collapse(coords)
// there are multiple possibilities, so we pick one
lastCoordsWherePicked = coords
val tileId = chooseTile(tilesAtCoordsArray)
lastTileIdPicked = tileId
collapse(coords, tileId)
try {
propagateFrom(coords, bounds)
} catch (e: PropagationException) {
rollback()
// remove that bad choice
if (tilesAtCoordsArray == null) map[coords] = maxEntropyArray.copyOf().also { it.set0BitAt(tileId) }
else tilesAtCoordsArray.set0BitAt(tileId)
}
}
}

fun collapse(coords: Coords<D>): Int {
fun chooseTile(options: LongArray?): Int {
if (options == null) return random.nextInt(maxId + 1)
return tileSet.pickTile(options, random)
}

fun collapse(coords: Coords<D>, chosenTile: Int = chooseTile(map[coords])) {
val tilesAtCoordsArray = map[coords]
if (tilesAtCoordsArray != null)
tilesAtCoordsArray.setAll0AndSet1BitAt(tileSet.pickTile(tilesAtCoordsArray, random))
tilesAtCoordsArray.setAll0AndSet1BitAt(chosenTile)
else
map[coords] = LongArray(arraySize).also { it.set1BitAt(random.nextInt(maxId + 1)) }
map[coords] = LongArray(arraySize).also { it.set1BitAt(chosenTile) }
}

/**
* @throws PropagationException if an empty state is found
*/
fun propagateFrom(latestCollapse: Coords<D>, latestCollapseTile: Tile<C, D>, bounds: Bounds<D>) {
fun propagateFrom(latestCollapse: Coords<D>, bounds: Bounds<D>) {
val needPropagationCoords: MutableList<PropagationTask<C, D>> = mutableListOf()
latestCollapse.addPropagationTasksInBoundsToList(needPropagationCoords, bounds)

while (needPropagationCoords.isNotEmpty()) {
val needPropagationCoordsCopy = needPropagationCoords.toMutableList()
needPropagationCoords.clear()

for (task in needPropagationCoordsCopy) {
for ((center, direction) in needPropagationCoordsCopy) {
// TODO: fork (multithreading) (is it good to do it here ?)

val taskPointsTo = task.pointsTo()
val tilesAtCoordsArray = map[taskPointsTo] ?: continue
val taskPointsTo = center.move(direction)
val tilesAtCoordsArray = map[taskPointsTo] ?: maxEntropyArray.copyOf()
val currentLocationMaskForDirection = LongArray(arraySize)
for (currentLocationTile in tileSet.getTileList(map[task.center]!!)) {
currentLocationMaskForDirection bitOrEquals currentLocationTile.getNeighborMask(task.direction)
tileSet.getTileList(map[center]!!).forEach {
it.addAllowedNeighborsToArray(currentLocationMaskForDirection, direction)
}

if (tilesAtCoordsArray bitAndEquals currentLocationMaskForDirection) { // removing impossible neighbors
Expand All @@ -93,7 +107,7 @@ class WaveFunction<C : Rotatable<D>, D : Dimension<D>> private constructor(
list: MutableList<PropagationTask<C, D>>,
bounds: Bounds<D>
) {
dimension.directions.forEach { direction ->
dimension.directionsAt(this).forEach { direction ->
if (this.move(direction) in bounds)
list += PropagationTask(this, direction)
}
Expand All @@ -106,14 +120,13 @@ class WaveFunction<C : Rotatable<D>, D : Dimension<D>> private constructor(
}



private inner class State(
protected open inner class State(
val map: MutableMap<Coords<D>, LongArray>,
val lastCoordsWherePicked: Coords<D>?,
val lastTileIdPicked: Int?
)

private val states = mutableListOf<State>()
protected val states = mutableListOf<State>()

override fun commit() {
states += State(map.toMutableMap(), lastCoordsWherePicked, lastTileIdPicked)
Expand All @@ -128,4 +141,21 @@ class WaveFunction<C : Rotatable<D>, D : Dimension<D>> private constructor(
lastTileIdPicked = state.lastTileIdPicked
}

override fun iterator(): Iterator<Map.Entry<Coords<D>, List<Tile<C, D>>>> =
map.asSequence().map { (coords, tilesAtCoordsArray) ->
object : Map.Entry<Coords<D>, List<Tile<C, D>>> {
override val key: Coords<D> = coords
override val value: List<Tile<C, D>> = tileSet.getTileList(tilesAtCoordsArray)
}
}.iterator()

fun getTileList(coords: Coords<D>): List<Tile<C, D>>? = map[coords]?.let { tileSet.getTileList(it) }

fun setTileList(coords: Coords<D>, tiles: List<Tile<C, D>>) {
val array = LongArray(arraySize)
for (tile in tiles) {
array.set1BitAt(tile.id)
}
map[coords] = array
}
}

0 comments on commit 684a50c

Please sign in to comment.