more efficient particles

Former-commit-id: 56dad88ecd715ad6e357e33b903851a47a358dcd
Former-commit-id: c85c0b759a447c0461563d98156f59879fa95db2
This commit is contained in:
Song Minjae
2017-01-22 04:13:45 +09:00
parent 4acc797fee
commit 54b52b1b6e
22 changed files with 135 additions and 66 deletions

View File

@@ -13,7 +13,7 @@ import java.util.*
* -- I couldn't observe any significant boost in performance but this one seems * -- I couldn't observe any significant boost in performance but this one seems
* to give 3-4 more frames per second. * to give 3-4 more frames per second.
* *
* Created by SKYHi14 on 2017-01-02. * Created by minjaesong on 2017-01-02.
* *
* *
* Remarks: * Remarks:

View File

@@ -4,7 +4,7 @@ import com.jme3.math.FastMath
import org.newdawn.slick.Color import org.newdawn.slick.Color
/** /**
* Created by SKYHi14 on 2017-01-12. * Created by minjaesong on 2017-01-12.
*/ */
object CIEXYZUtil { object CIEXYZUtil {
fun Color.brighterXYZ(scale: Float): Color { fun Color.brighterXYZ(scale: Float): Color {

View File

@@ -47,7 +47,7 @@ class HistoryArray<T>(val historyMax: Int) {
/** /**
* Iterate from latest to oldest * Iterate from latest to oldest
*/ */
fun forEach(action: Consumer<T?>) = history.forEach(action) fun forEach(action: (T?) -> Unit) = history.forEach(action)
val latest: T? val latest: T?
get() = this[0] get() = this[0]

View File

@@ -0,0 +1,49 @@
package net.torvald.terrarum
/**
* Notes for particle storage:
* Particles does not need to be removed, just let it overwrite as their operation is rather
* lightweight. So, just flagDespawn = true if it need to be "deleted" so that it won't update
* anymore.
*
* Created by minjaesong on 2017-01-22.
*/
class CircularArray<T>(val size: Int) {
private val buffer: Array<T> = arrayOfNulls<Any>(size) as Array<T>
private var tail: Int = 0
private var head: Int = 0
val elemCount: Int
get() = if (tail >= head) tail - head else size
fun add(item: T) {
buffer[tail] = item // overwrites oldest item when eligible
tail = (tail + 1) % size
if (tail == head) {
head = (head + 1) % size
}
}
fun forEach(action: (T) -> Unit) {
if (tail >= head) { // queue not full
(head..tail - 1).map { buffer[it] }.forEach { action(it) }
}
else { // queue full
(0..size - 1).map { buffer[(it + head) % size] }.forEach { action(it) }
}
}
fun forEachConcurrent(action: (T) -> Unit) {
TODO()
}
fun forEachConcurrentWaitFor(action: (T) -> Unit) {
TODO()
}
override fun toString(): String {
return "CircularArray(size=" + buffer.size + ", head=" + head + ", tail=" + tail + ")"
}
}

View File

@@ -12,7 +12,7 @@ import java.nio.ByteOrder
/** /**
* Software rendering test for blur * Software rendering test for blur
* *
* Created by SKYHi14 on 2017-01-12. * Created by minjaesong on 2017-01-12.
*/ */
class StateBlurTest : BasicGameState() { class StateBlurTest : BasicGameState() {

View File

@@ -55,8 +55,10 @@ constructor() : BasicGameState() {
* list of Actors that is sorted by Actors' referenceID * list of Actors that is sorted by Actors' referenceID
*/ */
val ACTORCONTAINER_INITIAL_SIZE = 128 val ACTORCONTAINER_INITIAL_SIZE = 128
val PARTICLES_MAX = 768
val actorContainer = ArrayList<Actor>(ACTORCONTAINER_INITIAL_SIZE) val actorContainer = ArrayList<Actor>(ACTORCONTAINER_INITIAL_SIZE)
val actorContainerInactive = ArrayList<Actor>(ACTORCONTAINER_INITIAL_SIZE) val actorContainerInactive = ArrayList<Actor>(ACTORCONTAINER_INITIAL_SIZE)
val particlesContainer = CircularArray<ParticleBase>(PARTICLES_MAX)
val uiContainer = ArrayList<UIHandler>() val uiContainer = ArrayList<UIHandler>()
private val actorsRenderBehind = ArrayList<ActorVisible>(ACTORCONTAINER_INITIAL_SIZE) private val actorsRenderBehind = ArrayList<ActorVisible>(ACTORCONTAINER_INITIAL_SIZE)
@@ -224,6 +226,7 @@ constructor() : BasicGameState() {
// determine whether the actor should keep being activated or be dormant // determine whether the actor should keep being activated or be dormant
KillOrKnockdownActors() KillOrKnockdownActors()
updateActors(gc, delta) updateActors(gc, delta)
particlesContainer.forEach { it.update(gc, delta) }
// TODO thread pool(?) // TODO thread pool(?)
CollisionSolver.process() CollisionSolver.process()
} }
@@ -232,7 +235,7 @@ constructor() : BasicGameState() {
//////////////////////// ////////////////////////
// ui-related updates // // ui-related updates //
//////////////////////// ////////////////////////
uiContainer.forEach { ui -> ui.update(gc, delta) } uiContainer.forEach { it.update(gc, delta) }
consoleHandler.update(gc, delta) consoleHandler.update(gc, delta)
debugWindow.update(gc, delta) debugWindow.update(gc, delta)
notifier.update(gc, delta) notifier.update(gc, delta)
@@ -320,17 +323,19 @@ constructor() : BasicGameState() {
// draw map related stuffs // // draw map related stuffs //
///////////////////////////// /////////////////////////////
TilesDrawer.renderWall(worldG) TilesDrawer.renderWall(worldG)
actorsRenderBehind.forEach { actor -> actor.drawBody(worldG) } actorsRenderBehind.forEach { it.drawBody(worldG) }
actorsRenderBehind.forEach { actor -> actor.drawGlow(worldG) } actorsRenderBehind.forEach { it.drawGlow(worldG) }
particlesContainer.forEach { it.drawBody(worldG) }
particlesContainer.forEach { it.drawGlow(worldG) }
TilesDrawer.renderTerrain(worldG) TilesDrawer.renderTerrain(worldG)
///////////////// /////////////////
// draw actors // // draw actors //
///////////////// /////////////////
actorsRenderMiddle.forEach { actor -> actor.drawBody(worldG) } actorsRenderMiddle.forEach { it.drawBody(worldG) }
actorsRenderMidTop.forEach { actor -> actor.drawBody(worldG) } actorsRenderMidTop.forEach { it.drawBody(worldG) }
player.drawBody(worldG) player.drawBody(worldG)
actorsRenderFront.forEach { actor -> actor.drawBody(worldG) } actorsRenderFront.forEach { it.drawBody(worldG) }
// --> Change of blend mode <-- introduced by ActorVisible // // --> Change of blend mode <-- introduced by ActorVisible //
@@ -354,10 +359,10 @@ constructor() : BasicGameState() {
////////////////////// //////////////////////
// draw actor glows // // draw actor glows //
////////////////////// //////////////////////
actorsRenderMiddle.forEach { actor -> actor.drawGlow(worldG) } actorsRenderMiddle.forEach { it.drawGlow(worldG) }
actorsRenderMidTop.forEach { actor -> actor.drawGlow(worldG) } actorsRenderMidTop.forEach { it.drawGlow(worldG) }
player.drawGlow(worldG) player.drawGlow(worldG)
actorsRenderFront.forEach { actor -> actor.drawGlow(worldG) } actorsRenderFront.forEach { it.drawGlow(worldG) }
// --> blendLightenOnly() <-- introduced by ActorVisible // // --> blendLightenOnly() <-- introduced by ActorVisible //
@@ -396,7 +401,7 @@ constructor() : BasicGameState() {
////////////// //////////////
// draw UIs // // draw UIs //
////////////// //////////////
uiContainer.forEach { ui -> ui.render(gc, sbg, uiG) } uiContainer.forEach { it.render(gc, sbg, uiG) }
debugWindow.render(gc, sbg, uiG) debugWindow.render(gc, sbg, uiG)
consoleHandler.render(gc, sbg, uiG) consoleHandler.render(gc, sbg, uiG)
notifier.render(gc, sbg, uiG) notifier.render(gc, sbg, uiG)
@@ -684,6 +689,10 @@ constructor() : BasicGameState() {
} }
} }
fun addParticle(particle: ParticleBase) {
particlesContainer.add(particle)
}
/** /**
* Whether the game should display actorContainer elem number when F3 is on * Whether the game should display actorContainer elem number when F3 is on
*/ */

View File

@@ -11,7 +11,7 @@ import org.newdawn.slick.state.StateBasedGame
import java.util.* import java.util.*
/** /**
* Created by SKYHi14 on 2016-12-21. * Created by minjaesong on 2016-12-21.
*/ */
class StateNoiseTexGen : BasicGameState() { class StateNoiseTexGen : BasicGameState() {

View File

@@ -5,7 +5,7 @@ import net.torvald.terrarum.debuggerapp.ActorValueTracker
import java.util.* import java.util.*
/** /**
* Created by SKYHi14 on 2016-12-29. * Created by minjaesong on 2016-12-29.
*/ */
object AVTracker : ConsoleCommand { object AVTracker : ConsoleCommand {
private val jPanelInstances = ArrayList<ActorValueTracker>() private val jPanelInstances = ArrayList<ActorValueTracker>()

View File

@@ -5,7 +5,7 @@ import net.torvald.terrarum.debuggerapp.ActorsLister
import java.util.* import java.util.*
/** /**
* Created by SKYHi14 on 2016-12-29. * Created by minjaesong on 2016-12-29.
*/ */
object ActorsList : ConsoleCommand { object ActorsList : ConsoleCommand {
private val jPanelInstances = ArrayList<ActorsLister>() private val jPanelInstances = ArrayList<ActorsLister>()

View File

@@ -8,7 +8,7 @@ import net.torvald.terrarum.gameitem.EquipPosition
import net.torvald.terrarum.itemproperties.ItemCodex import net.torvald.terrarum.itemproperties.ItemCodex
/** /**
* Created by SKYHi14 on 2016-12-12. * Created by minjaesong on 2016-12-12.
*/ */
internal object Inventory : ConsoleCommand { internal object Inventory : ConsoleCommand {

View File

@@ -4,7 +4,7 @@ import net.torvald.terrarum.Terrarum
import net.torvald.terrarum.gameactors.ActorWithSprite import net.torvald.terrarum.gameactors.ActorWithSprite
/** /**
* Created by SKYHi14 on 2017-01-20. * Created by minjaesong on 2017-01-20.
*/ */
object SetScale : ConsoleCommand { object SetScale : ConsoleCommand {
override fun execute(args: Array<String>) { override fun execute(args: Array<String>) {

View File

@@ -6,7 +6,7 @@ import java.io.File
import java.io.FileInputStream import java.io.FileInputStream
/** /**
* Created by SKYHi14 on 2017-01-14. * Created by minjaesong on 2017-01-14.
*/ */
object SpawnTapestry : ConsoleCommand { object SpawnTapestry : ConsoleCommand {
override fun execute(args: Array<String>) { override fun execute(args: Array<String>) {

View File

@@ -6,7 +6,7 @@ import net.torvald.terrarum.gamecontroller.mouseX
import net.torvald.terrarum.gamecontroller.mouseY import net.torvald.terrarum.gamecontroller.mouseY
/** /**
* Created by SKYHi14 on 2016-12-17. * Created by minjaesong on 2016-12-17.
*/ */
object SpawnTikiTorch : ConsoleCommand { object SpawnTikiTorch : ConsoleCommand {
override fun execute(args: Array<String>) { override fun execute(args: Array<String>) {

View File

@@ -17,7 +17,7 @@ import java.awt.event.MouseListener
import javax.swing.* import javax.swing.*
/** /**
* Created by SKYHi14 on 2016-12-29. * Created by minjaesong on 2016-12-29.
*/ */
class ActorValueTracker constructor() : JFrame() { class ActorValueTracker constructor() : JFrame() {

View File

@@ -8,7 +8,7 @@ import java.util.*
import javax.swing.* import javax.swing.*
/** /**
* Created by SKYHi14 on 2016-12-29. * Created by minjaesong on 2016-12-29.
*/ */
class ActorsLister( class ActorsLister(
val actorContainer: ArrayList<Actor>, val actorContainer: ArrayList<Actor>,

View File

@@ -4,7 +4,7 @@ import org.newdawn.slick.GameContainer
import org.newdawn.slick.Graphics import org.newdawn.slick.Graphics
/** /**
* Created by SKYHi14 on 2017-01-21. * Created by minjaesong on 2017-01-21.
*/ */
abstract class ActorVisible(renderOrder: ActorOrder) : Actor(renderOrder) { abstract class ActorVisible(renderOrder: ActorOrder) : Actor(renderOrder) {
open val hitbox = Hitbox(0.0, 0.0, 0.0, 0.0) open val hitbox = Hitbox(0.0, 0.0, 0.0, 0.0)

View File

@@ -13,16 +13,14 @@ import org.newdawn.slick.Image
/** /**
* Actors with static sprites and very simple physics * Actors with static sprites and very simple physics
* *
* Created by SKYHi14 on 2017-01-20. * Created by minjaesong on 2017-01-20.
*/ */
open class ParticleBase(renderOrder: ActorOrder, maxLifeTime: Int? = null) : ActorVisible(renderOrder), Projectile { open class ParticleBase(renderOrder: ActorOrder, maxLifeTime: Int? = null) : Runnable {
override var actorValue = ActorValue() /** Will NOT actually delete from the CircularArray */
override @Volatile var flagDespawn = false @Volatile var flagDespawn = false
override fun run() { override fun run() = update(Terrarum.appgc, Terrarum.ingame.UPDATE_DELTA)
TODO("not implemented")
}
var isNoSubjectToGrav = false var isNoSubjectToGrav = false
var dragCoefficient = 3.0 var dragCoefficient = 3.0
@@ -31,38 +29,47 @@ open class ParticleBase(renderOrder: ActorOrder, maxLifeTime: Int? = null) : Act
private var lifetimeCounter = 0 private var lifetimeCounter = 0
open val velocity = Vector2(0.0, 0.0) open val velocity = Vector2(0.0, 0.0)
open val hitbox = Hitbox(0.0, 0.0, 0.0, 0.0)
open lateinit var image: Image open lateinit var body: Image
open var glow: Image? = null
init { init {
} }
override fun update(gc: GameContainer, delta: Int) { fun update(gc: GameContainer, delta: Int) {
lifetimeCounter += delta if (!flagDespawn) {
if (velocity.isZero || lifetimeCounter >= lifetimeMax || lifetimeCounter += delta
// simple stuck check if (velocity.isZero || lifetimeCounter >= lifetimeMax ||
TileCodex[Terrarum.ingame.world.getTileFromTerrain( // simple stuck check
hitbox.pointedX.div(TILE_SIZE).floorInt(), TileCodex[Terrarum.ingame.world.getTileFromTerrain(
hitbox.pointedY.div(TILE_SIZE).floorInt() hitbox.pointedX.div(TILE_SIZE).floorInt(),
) ?: Tile.STONE].isSolid) { hitbox.pointedY.div(TILE_SIZE).floorInt()
flagDespawn = true ) ?: Tile.STONE].isSolid) {
flagDespawn = true
}
// gravity, winds, etc. (external forces)
if (!isNoSubjectToGrav) {
velocity += Terrarum.ingame.world.gravitation / dragCoefficient * SI_TO_GAME_ACC
}
// combine external forces
hitbox.translate(velocity)
} }
// gravity, winds, etc. (external forces)
if (!isNoSubjectToGrav) {
velocity += Terrarum.ingame.world.gravitation / dragCoefficient * SI_TO_GAME_ACC
}
// combine external forces
hitbox.translate(velocity)
} }
override fun drawBody(g: Graphics) { fun drawBody(g: Graphics) {
g.drawImage(image, hitbox.centeredX.toFloat(), hitbox.centeredY.toFloat()) if (!flagDespawn) {
g.drawImage(body, hitbox.centeredX.toFloat(), hitbox.centeredY.toFloat())
}
} }
override fun drawGlow(g: Graphics) { fun drawGlow(g: Graphics) {
if (!flagDespawn && glow != null) {
g.drawImage(glow, hitbox.centeredX.toFloat(), hitbox.centeredY.toFloat())
}
} }
} }

View File

@@ -4,21 +4,21 @@ import org.dyn4j.geometry.Vector2
import org.newdawn.slick.Image import org.newdawn.slick.Image
/** /**
* Created by SKYHi14 on 2017-01-20. * Created by minjaesong on 2017-01-20.
*/ */
class ParticleTestRain(posX: Double, posY: Double) : ParticleBase(ActorOrder.BEHIND, 6000) { class ParticleTestRain(posX: Double, posY: Double) : ParticleBase(ActorOrder.BEHIND, 6000) {
init { init {
image = Image("./assets/graphics/weathers/raindrop.tga") body = Image("./assets/graphics/weathers/raindrop.tga")
val w = image.width.toDouble() val w = body.width.toDouble()
val h = image.height.toDouble() val h = body.height.toDouble()
hitbox.setFromWidthHeight( hitbox.setFromWidthHeight(
posX - w.times(0.5), posX - w.times(0.5),
posY - h.times(0.5), posY - h.times(0.5),
w, h w, h
) )
velocity.y = 16.0 velocity.y = 10.0
} }
} }

View File

@@ -8,7 +8,7 @@ import org.newdawn.slick.Graphics
import org.newdawn.slick.Image import org.newdawn.slick.Image
/** /**
* Created by SKYHi14 on 2017-01-07. * Created by minjaesong on 2017-01-07.
*/ */
class TapestryObject(val image: Image, val artName: String, val artAuthor: String) : FixtureBase(physics = false) { class TapestryObject(val image: Image, val artName: String, val artAuthor: String) : FixtureBase(physics = false) {

View File

@@ -5,7 +5,7 @@ import net.torvald.terrarum.Terrarum
import net.torvald.terrarum.gameworld.GameWorld import net.torvald.terrarum.gameworld.GameWorld
/** /**
* Created by SKYHi14 on 2016-12-30. * Created by minjaesong on 2016-12-30.
*/ */
object MapCamera { object MapCamera {
private val world: GameWorld = Terrarum.ingame.world private val world: GameWorld = Terrarum.ingame.world

View File

@@ -174,6 +174,8 @@ class BasicDebugInfoWindow : UICanvas {
(2 + 17*8).toFloat(), Terrarum.HEIGHT - 10f) (2 + 17*8).toFloat(), Terrarum.HEIGHT - 10f)
g.drawString("${ccY}Dormant $ccG${Terrarum.ingame.actorContainerInactive.size}", g.drawString("${ccY}Dormant $ccG${Terrarum.ingame.actorContainerInactive.size}",
(2 + 28*8).toFloat(), Terrarum.HEIGHT - 10f) (2 + 28*8).toFloat(), Terrarum.HEIGHT - 10f)
g.drawString("${ccM}Particles $ccG${Terrarum.ingame.particlesContainer.elemCount}",
(2 + 41*8).toFloat(), Terrarum.HEIGHT - 10f)
} }
private fun printLine(g: Graphics, l: Int, s: String) { private fun printLine(g: Graphics, l: Int, s: String) {

View File

@@ -79,15 +79,17 @@ object WeatherMixer {
fun update(gc: GameContainer, delta: Int) { fun update(gc: GameContainer, delta: Int) {
currentWeather = weatherList[WEATHER_GENERIC]!![0] currentWeather = weatherList[WEATHER_GENERIC]!![0]
// test rain toggled by F2
// test rain toggled by F2
if (KeyToggler.isOn(Key.F2)) { if (KeyToggler.isOn(Key.F2)) {
val playerPos = Terrarum.ingame.player.centrePosPoint val playerPos = Terrarum.ingame.player.centrePosPoint
val rainParticle = ParticleTestRain( kotlin.repeat(4) { // 4 seems good
playerPos.x + HQRNG().nextInt(Terrarum.WIDTH) - Terrarum.HALFW, val rainParticle = ParticleTestRain(
playerPos.y - Terrarum.HALFH playerPos.x + HQRNG().nextInt(Terrarum.WIDTH) - Terrarum.HALFW,
) playerPos.y - Terrarum.HALFH
Terrarum.ingame.addActor(rainParticle) )
Terrarum.ingame.addParticle(rainParticle)
}
} }
} }