From b735335a99c1443728fb2ed979331856d2fd745b Mon Sep 17 00:00:00 2001 From: Song Minjae Date: Thu, 1 Sep 2016 21:36:44 +0900 Subject: [PATCH] CIELab and CIELch colour util Former-commit-id: f8b0413223c2c968e4627e7c251220d32e2c6bf5 Former-commit-id: 2bce3479a8ad95ac06fbbd6c35cf73967a49568d --- src/net/torvald/colourutil/CIELabUtil.kt | 99 +++++++++++++++++++ src/net/torvald/colourutil/CIELchUtil.kt | 65 ++++++++++++ src/net/torvald/colourutil/ColourUtil.kt | 13 +++ src/net/torvald/colourutil/HSV.kt | 15 --- src/net/torvald/colourutil/HSVUtil.kt | 18 +++- src/net/torvald/terrarum/StateInGame.kt | 18 ++-- src/net/torvald/terrarum/Terrarum.kt | 2 +- .../terrarum/gameactors/ActorWithBody.kt | 12 ++- .../terrarum/gameactors/ProjectileSimple.kt | 18 +++- .../torvald/terrarum/weather/WeatherMixer.kt | 21 +--- 10 files changed, 228 insertions(+), 53 deletions(-) create mode 100644 src/net/torvald/colourutil/CIELabUtil.kt create mode 100644 src/net/torvald/colourutil/CIELchUtil.kt delete mode 100644 src/net/torvald/colourutil/HSV.kt diff --git a/src/net/torvald/colourutil/CIELabUtil.kt b/src/net/torvald/colourutil/CIELabUtil.kt new file mode 100644 index 000000000..bcf165892 --- /dev/null +++ b/src/net/torvald/colourutil/CIELabUtil.kt @@ -0,0 +1,99 @@ +package net.torvald.colourutil + +import com.jme3.math.FastMath +import org.newdawn.slick.Color + +/** + * RGB in this code is always sRGB. + * reference: http://www.brucelindbloom.com/index.html?Equations.html + * + * Created by minjaesong on 16-09-01. + */ +object CIELabUtil { + fun Color.brighterLab(scale: Float): Color { + val brighten = scale + 1f + + val lab = this.toLab() + lab.L *= brighten + return lab.toRGB() + } + + fun Color.darkerLab(scale: Float): Color { + val darken = 1f - scale + + val lab = this.toLab() + lab.L *= darken + return lab.toRGB() + } + + /** Sweet Lab linear gradient */ + fun getGradient(scale: Float, fromCol: Color, toCol: Color): Color { + val from = fromCol.toLab() + val to = toCol.toLab() + val newL = FastMath.interpolateLinear(scale, from.L, to.L) + val newA = FastMath.interpolateLinear(scale, from.a, to.a) + val newB = FastMath.interpolateLinear(scale, from.b, to.b) + val newAlpha = FastMath.interpolateLinear(scale, from.alpha, to.alpha) + + return CIELab(newL, newA, newB, newAlpha).toRGB() + } + + private fun Color.toLab() = this.toXYZ().toLab() + private fun CIELab.toRGB() = this.toXYZ().toRGB() + + fun Color.toXYZ(): CIEXYZ { + val x = 0.4124564f * r + 0.3575761f * g + 0.1804375f * b + val y = 0.2126729f * r + 0.7151522f * g + 0.0721750f * b + val z = 0.0193339f * r + 0.1191920f * g + 0.9503041f * b + + return CIEXYZ(x, y, z, a) + } + + fun CIEXYZ.toRGB(): Color { + val r = 3.2404542f * x + -1.5371385f * y + -0.4985314f * z + val g = -0.9692660f * x + 1.8760108f * y + 0.0415560f * z + val b = 0.0556434f * x + -0.2040259f * y + 1.0572252f * z + + return Color(r, g, b, alpha) + } + + fun CIEXYZ.toLab(): CIELab { + val x = pivotXYZ(x / whitePoint.x) + val y = pivotXYZ(y / whitePoint.y) + val z = pivotXYZ(z / whitePoint.z) + + val L = Math.max(0f, 116 * y - 16) + val a = 500 * (x - y) + val b = 200 * (y - z) + + return CIELab(L, a, b, alpha) + } + + fun CIELab.toXYZ(): CIEXYZ { + val y = L.plus(16).div(116f) + val x = a / 500f + y + val z = y - b / 200f + + val x3 = x.cube() + val z3 = z.cube() + + return CIEXYZ( + whitePoint.x * if (x3 > epsilon) x3 else (x - 16f / 116f) / 7.787f, + whitePoint.y * if (L > kappa * epsilon) (L.plus(16f) / 116f).cube() else L / kappa, + whitePoint.z * if (z3 > epsilon) z3 else (z - 16f / 116f) / 7.787f, + alpha + ) + } + + private fun pivotXYZ(n: Float) = if (n > epsilon) n.cbrt() else (kappa * n + 16f) / 116f + + val epsilon = 0.008856f + val kappa = 903.3f + val whitePoint = CIEXYZ(95.047f, 100f, 108.883f) + + private fun Float.cbrt() = FastMath.pow(this, 1f / 3f) + private fun Float.cube() = this * this * this +} + +data class CIEXYZ(var x: Float = 0f, var y: Float = 0f, var z: Float = 0f, val alpha: Float = 1f) +data class CIELab(var L: Float = 0f, var a: Float = 0f, var b: Float = 0f, val alpha: Float = 1f) diff --git a/src/net/torvald/colourutil/CIELchUtil.kt b/src/net/torvald/colourutil/CIELchUtil.kt new file mode 100644 index 000000000..0773b5460 --- /dev/null +++ b/src/net/torvald/colourutil/CIELchUtil.kt @@ -0,0 +1,65 @@ +package net.torvald.colourutil + +import com.jme3.math.FastMath +import net.torvald.colourutil.CIELabUtil.toLab +import net.torvald.colourutil.CIELabUtil.toRGB +import net.torvald.colourutil.CIELabUtil.toXYZ +import org.newdawn.slick.Color + +/** + * RGB in this code is always sRGB. + * reference: http://www.brucelindbloom.com/index.html?Equations.html + * + * Created by minjaesong on 16-09-01. + */ + +object CIELchUtil { + + /** Sweet Lch linear gradient */ + fun getGradient(scale: Float, fromCol: Color, toCol: Color): Color { + val from = fromCol.toLch() + val to = toCol.toLch() + val newL = FastMath.interpolateLinear(scale, from.L, to.L) + val newC = FastMath.interpolateLinear(scale, from.c, to.c) + val newAlpha = FastMath.interpolateLinear(scale, from.alpha, to.alpha) + val newH: Float + + if ((from.h - to.h).abs() == FastMath.PI) // exact opposite colour + return CIELabUtil.getGradient(scale, fromCol, toCol) + else if ((from.h - to.h).abs() > FastMath.PI) // reflex angle + newH = FastMath.interpolateLinear(scale, from.h, to.h + FastMath.TWO_PI) + else + newH = FastMath.interpolateLinear(scale, from.h, to.h) + + return CIELch(newL, newC, newH, newAlpha).toRGB() + } + + fun CIELab.toLch(): CIELch { + val c = (a.sqr() + b.sqr()).sqrt() + val h = FastMath.atan2(b, a) + + return CIELch(L, c, h, alpha) + } + + fun CIELch.toLab(): CIELab { + val a = c * FastMath.cos(h) + val b = c * FastMath.sin(h) + + return CIELab(L, a, b, alpha) + } + + private fun Color.toLch() = this.toXYZ().toLab().toLch() + private fun CIELch.toRGB() = this.toLab().toXYZ().toRGB() + + private fun Float.sqr() = this * this + private fun Float.sqrt() = Math.sqrt(this.toDouble()).toFloat() + + private fun Float.abs() = FastMath.abs(this) +} + +/** + * @param L : Luminosity in 0.0 - 1.0 + * @param c : Chroma (saturation) in 0.0 - 1.0 + * @param h : Hue in radian (-pi to pi) + */ +data class CIELch(var L: Float = 0f, var c: Float = 0f, var h: Float = 0f, var alpha: Float = 1f) diff --git a/src/net/torvald/colourutil/ColourUtil.kt b/src/net/torvald/colourutil/ColourUtil.kt index 798fb8d93..3be83ead8 100644 --- a/src/net/torvald/colourutil/ColourUtil.kt +++ b/src/net/torvald/colourutil/ColourUtil.kt @@ -1,5 +1,6 @@ package net.torvald.colourutil +import com.jme3.math.FastMath import org.newdawn.slick.Color /** @@ -7,4 +8,16 @@ import org.newdawn.slick.Color */ object ColourUtil { fun toSlickColor(r: Int, g: Int, b: Int) = Color(r.shl(16) or g.shl(8) or b) + + /** + * Use CIELabUtil.getGradient for natural-looking colour + */ + fun getGradient(scale: Float, fromCol: Color, toCol: Color): Color { + val r = FastMath.interpolateLinear(scale, fromCol.r, toCol.r) + val g = FastMath.interpolateLinear(scale, fromCol.g, toCol.g) + val b = FastMath.interpolateLinear(scale, fromCol.b, toCol.b) + val a = FastMath.interpolateLinear(scale, fromCol.a, toCol.a) + + return Color(r, g, b, a) + } } \ No newline at end of file diff --git a/src/net/torvald/colourutil/HSV.kt b/src/net/torvald/colourutil/HSV.kt deleted file mode 100644 index bc475b988..000000000 --- a/src/net/torvald/colourutil/HSV.kt +++ /dev/null @@ -1,15 +0,0 @@ -package net.torvald.colourutil - -/** - * Created by minjaesong on 16-03-10. - */ -/** - * @param h : Hue 0-359 - * @param s : Saturation 0-1 - * @param v : Value (brightness in Adobe Photoshop(TM)) 0-1 - */ -data class HSV( - var h: Float, - var s: Float, - var v: Float -) diff --git a/src/net/torvald/colourutil/HSVUtil.kt b/src/net/torvald/colourutil/HSVUtil.kt index 5cdbae28c..63153a3be 100644 --- a/src/net/torvald/colourutil/HSVUtil.kt +++ b/src/net/torvald/colourutil/HSVUtil.kt @@ -4,6 +4,8 @@ import com.jme3.math.FastMath import org.newdawn.slick.Color /** + * OBSOLETE; use CIELchUtil for natural-looking colour + * * Created by minjaesong on 16-01-16. */ object HSVUtil { @@ -20,7 +22,7 @@ object HSVUtil { * * * @link http://www.rapidtables.com/convert/color/hsv-to-rgb.htm */ - fun toRGB(H: Float, S: Float, V: Float): Color { + fun toRGB(H: Float, S: Float, V: Float, alpha: Float = 1f): Color { var H = H H %= 360f @@ -64,12 +66,11 @@ object HSVUtil { B_prime = X } - return Color( - R_prime + m, G_prime + m, B_prime + m) + return Color(R_prime + m, G_prime + m, B_prime + m, alpha) } fun toRGB(hsv: HSV): Color { - return toRGB(hsv.h, hsv.s, hsv.v) + return toRGB(hsv.h * 360, hsv.s, hsv.v, hsv.alpha) } fun fromRGB(color: Color): HSV { @@ -104,7 +105,14 @@ object HSVUtil { h *= 60f if (h < 0) h += 360f - return HSV(h, s, v) + return HSV(h.div(360f), s, v, color.a) } } + +/** + * @param h : Hue in 0.0 - 1.0 (360 deg) + * @param s : Saturation in 0.0 - 1.0 + * @param v : Value in 0.0 - 1.0 + */ +data class HSV(var h: Float = 0f, var s: Float = 0f, var v: Float = 0f, var alpha: Float = 1f) diff --git a/src/net/torvald/terrarum/StateInGame.kt b/src/net/torvald/terrarum/StateInGame.kt index bfa904a42..127948213 100644 --- a/src/net/torvald/terrarum/StateInGame.kt +++ b/src/net/torvald/terrarum/StateInGame.kt @@ -200,7 +200,7 @@ constructor() : BasicGameState() { // determine whether the inactive actor should be re-active wakeDormantActors() // determine whether the actor should be active or dormant - InactivateDistantActors() + KillOrKnockdownActors() updateActors(gc, delta) // TODO thread pool(?) CollisionSolver.process() @@ -424,17 +424,21 @@ constructor() : BasicGameState() { * If the actor must be dormant, the target actor will be put to the list specifically for them. * if the actor is not to be dormant, it will be just ignored. */ - fun InactivateDistantActors() { + fun KillOrKnockdownActors() { var actorContainerSize = actorContainer.size var i = 0 while (i < actorContainerSize) { // loop through actorContainer val actor = actorContainer[i] val actorIndex = i - if (actor is Visible && !actor.inUpdateRange()) { - // inactive instead of delete, if not flagged to delete - if (!actor.flagDespawn) - actorContainerInactive.add(actor) // naïve add; duplicates are checked when the actor is re-activated - + // kill actors flagged to despawn + if (actor.flagDespawn) { + actorContainer.removeAt(actorIndex) + actorContainerSize -= 1 + i-- // array removed 1 elem, so we also decrement counter by 1 + } + // inactivate distant actors + else if (actor is Visible && !actor.inUpdateRange()) { + actorContainerInactive.add(actor) // naïve add; duplicates are checked when the actor is re-activated actorContainer.removeAt(actorIndex) actorContainerSize -= 1 i-- // array removed 1 elem, so we also decrement counter by 1 diff --git a/src/net/torvald/terrarum/Terrarum.kt b/src/net/torvald/terrarum/Terrarum.kt index 5c45ab273..d0a44ff78 100644 --- a/src/net/torvald/terrarum/Terrarum.kt +++ b/src/net/torvald/terrarum/Terrarum.kt @@ -100,7 +100,7 @@ constructor(gamename: String) : StateBasedGame(gamename) { gc.graphics.clear() // clean up any 'dust' in the buffer //addState(StateSplash()) - addState(StateMonitorCheck()) + //addState(StateMonitorCheck()) //addState(StateFontTester()) ingame = StateInGame() addState(ingame) diff --git a/src/net/torvald/terrarum/gameactors/ActorWithBody.kt b/src/net/torvald/terrarum/gameactors/ActorWithBody.kt index 59705986f..d0af8ec46 100644 --- a/src/net/torvald/terrarum/gameactors/ActorWithBody.kt +++ b/src/net/torvald/terrarum/gameactors/ActorWithBody.kt @@ -186,9 +186,9 @@ open class ActorWithBody : Actor(), Visible { @Transient internal val BASE_FRICTION = 0.3 - @Transient val KINEMATIC = 1 // does not be budged by external forces - @Transient val DYNAMIC = 2 - @Transient val STATIC = 3 // does not be budged by external forces, target of collision + @Transient val KINEMATIC = 1 // does not displaced by external forces + @Transient val DYNAMIC = 2 // displaced by external forces + @Transient val STATIC = 3 // does not displaced by external forces, target of collision var collisionType = DYNAMIC @Transient private val CCD_TICK = 1.0 / 16.0 @@ -214,7 +214,11 @@ open class ActorWithBody : Actor(), Visible { internal var walledLeft = false internal var walledRight = false + /** + * true: This actor had just made collision + */ var ccdCollided = false + private set var isWalkingH = false var isWalkingV = false @@ -945,6 +949,8 @@ open class ActorWithBody : Actor(), Visible { assertPrinted = true } + internal fun flagDespawn() { flagDespawn = true } + companion object { @Transient private val TSIZE = MapDrawer.TILE_SIZE diff --git a/src/net/torvald/terrarum/gameactors/ProjectileSimple.kt b/src/net/torvald/terrarum/gameactors/ProjectileSimple.kt index 3a961b94f..79ef07492 100644 --- a/src/net/torvald/terrarum/gameactors/ProjectileSimple.kt +++ b/src/net/torvald/terrarum/gameactors/ProjectileSimple.kt @@ -1,6 +1,6 @@ package net.torvald.terrarum.gameactors -import net.torvald.terrarum.gameactors.ActorWithBody +import net.torvald.colourutil.CIELabUtil.brighterLab import org.dyn4j.geometry.Vector2 import org.newdawn.slick.Color import org.newdawn.slick.GameContainer @@ -34,18 +34,28 @@ open class ProjectileSimple( damage = bulletDatabase[type][0] as Int displayColour = bulletDatabase[type][1] as Color + + collisionType = KINEMATIC } override fun update(gc: GameContainer, delta: Int) { - // hit something and despawn! (use ```flagDespawn = true```) - + // hit something and despawn + if (ccdCollided) flagDespawn() super.update(gc, delta) } override fun drawBody(gc: GameContainer, g: Graphics) { // draw trail of solid colour (Terraria style maybe?) - + g.lineWidth = 3f + g.drawGradientLine( + nextHitbox.centeredX.toFloat(), + nextHitbox.centeredY.toFloat(), + displayColour, + hitbox.centeredX.toFloat(), + hitbox.centeredY.toFloat(), + displayColour.brighterLab(0.8f) + ) } companion object { diff --git a/src/net/torvald/terrarum/weather/WeatherMixer.kt b/src/net/torvald/terrarum/weather/WeatherMixer.kt index 8cd9e3bfe..1834a3ea0 100644 --- a/src/net/torvald/terrarum/weather/WeatherMixer.kt +++ b/src/net/torvald/terrarum/weather/WeatherMixer.kt @@ -2,6 +2,7 @@ package net.torvald.terrarum.weather import com.jme3.math.FastMath import net.torvald.JsonFetcher +import net.torvald.colourutil.CIELchUtil import net.torvald.colourutil.ColourUtil import net.torvald.random.HQRNG import net.torvald.terrarum.Terrarum @@ -108,11 +109,8 @@ object WeatherMixer { // interpolate R, G and B val scale = (timeInSec % dataPointDistance).toFloat() / dataPointDistance // [0.0, 1.0] - val r = interpolateLinear(scale, colourThis.red, colourNext.red) - val g = interpolateLinear(scale, colourThis.green, colourNext.green) - val b = interpolateLinear(scale, colourThis.blue, colourNext.blue) - - val newCol = ColourUtil.toSlickColor(r, g, b) + //val newCol = ColourUtil.getGradient(scale, colourThis, colourNext) + val newCol = CIELchUtil.getGradient(scale, colourThis, colourNext) /* // very nice monitor code // 65 -> 66 | 300 | 19623 | RGB8(255, 0, 255) -[41%]-> RGB8(193, 97, 23) | * `230`40`160` @@ -127,19 +125,6 @@ object WeatherMixer { fun Color.toStringRGB() = "RGB8(${this.red}, ${this.green}, ${this.blue})" - fun interpolateLinear(scale: Float, startValue: Int, endValue: Int): Int { - if (startValue == endValue) { - return startValue - } - if (scale <= 0f) { - return startValue - } - if (scale >= 1f) { - return endValue - } - return Math.round((1f - scale) * startValue + scale * endValue) - } - fun getWeatherList(classification: String) = weatherList[classification]!! fun getRandomWeather(classification: String) = getWeatherList(classification)[HQRNG().nextInt(getWeatherList(classification).size)]