[WIP] major rewrite, grass and leaves working already
This commit is contained in:
@@ -0,0 +1,138 @@
|
||||
package mods.betterfoliage.render.lighting
|
||||
|
||||
import mods.betterfoliage.util.get
|
||||
import mods.betterfoliage.util.mapArray
|
||||
import mods.betterfoliage.util.perpendiculars
|
||||
import net.minecraft.util.Direction
|
||||
import net.minecraft.util.Direction.*
|
||||
|
||||
typealias BoxCorner = Triple<Direction, Direction, Direction>
|
||||
fun BoxCorner.equalsUnordered(other: BoxCorner) = contains(other.first) && contains(other.second) && contains(other.third)
|
||||
|
||||
fun BoxCorner.contains(dir: Direction) = first == dir || second == dir || third == dir
|
||||
|
||||
fun Array<BoxCorner>.findIdx(corner: BoxCorner): Int? {
|
||||
forEachIndexed { idx, test -> if (test.contains(corner.first) && test.contains(corner.second) && test.contains(corner.third)) return idx }
|
||||
return null
|
||||
}
|
||||
fun Array<BoxCorner>.findIdx(predicate: (BoxCorner)->Boolean): Int? {
|
||||
forEachIndexed { idx, test -> if (predicate(test)) return idx }
|
||||
return null
|
||||
}
|
||||
|
||||
class AoSideHelper private constructor(face: Direction) {
|
||||
val sides = faceSides[face]
|
||||
val cornerSideDirections = faceCorners[face]
|
||||
val aoIndex = faceCornersIdx.mapArray { corner ->
|
||||
boxCornersDirIdx[face][sides[corner.first]][sides[corner.second]]!!
|
||||
}
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Indexing for undirected box corners (component order does not matter).
|
||||
* Array contains [Direction] triplets fully defining the corner.
|
||||
*/
|
||||
@JvmField
|
||||
val boxCornersUndir = Array(8) { idx -> Triple(
|
||||
if (idx and 1 != 0) EAST else WEST,
|
||||
if (idx and 2 != 0) UP else DOWN,
|
||||
if (idx and 4 != 0) SOUTH else NORTH
|
||||
) }
|
||||
|
||||
/**
|
||||
* Reverse lookup for [boxCornersUndir]. Index 3 times with the corner's cardinal directions.
|
||||
* A null value indicates an invalid corner (multiple indexing along the same axis)
|
||||
*/
|
||||
@JvmField
|
||||
val boxCornersUndirIdx = Array(6) { idx1 -> Array(6) { idx2 -> Array(6) { idx3 ->
|
||||
boxCornersUndir.findIdx(BoxCorner(
|
||||
Direction.values()[idx1],
|
||||
Direction.values()[idx2],
|
||||
Direction.values()[idx3]
|
||||
))
|
||||
} } }
|
||||
|
||||
/**
|
||||
* Indexing for directed face sides
|
||||
* First index is the face, second is index of side on face
|
||||
*/
|
||||
@JvmField
|
||||
val faceSides = Array(6) { faceIdx -> Array(4) { sideIdx ->
|
||||
Direction.values()[faceIdx].perpendiculars[sideIdx]
|
||||
} }
|
||||
|
||||
/**
|
||||
* Pairs of [faceSides] side indexes that form a valid pair describing a corner
|
||||
*/
|
||||
@JvmField
|
||||
val faceCornersIdx = arrayOf(0 to 2, 0 to 3, 1 to 2, 1 to 3)
|
||||
|
||||
/**
|
||||
* Indexing for directed face corners
|
||||
* First index is the face, second is index of corner on face
|
||||
*/
|
||||
@JvmField
|
||||
val faceCorners = Array(6) { faceIdx -> Array(4) { cornerIdx ->
|
||||
faceCornersIdx[cornerIdx].let { faceSides[faceIdx][it.first] to faceSides[faceIdx][it.second] }
|
||||
} }
|
||||
|
||||
/**
|
||||
* Indexing scheme for directed box corners.
|
||||
* The first direction - the face - matters, the other two are unordered.
|
||||
* 1:1 correspondence with possible AO values.
|
||||
* Array contains triplets defining the corner fully.
|
||||
*/
|
||||
@JvmField
|
||||
val boxCornersDir = Array(24) { idx ->
|
||||
val faceIdx = idx / 4; val face = Direction.values()[faceIdx]
|
||||
val cornerIdx = idx % 4; val corner = faceCorners[faceIdx][cornerIdx]
|
||||
BoxCorner(face, corner.first, corner.second)
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse lookup for [boxCornersDir]. Index 3 times with the corner's cardinal directions.
|
||||
* The first direction - the face - matters, the other two are unordered.
|
||||
* A null value indicates an invalid corner (multiple indexing along the same axis)
|
||||
*/
|
||||
@JvmField
|
||||
val boxCornersDirIdx = Array(6) { face -> Array(6) { side1 -> Array(6) { side2 ->
|
||||
boxCornersDir.findIdx { boxCorner ->
|
||||
boxCorner.first.ordinal == face && boxCorner.equalsUnordered(BoxCorner(
|
||||
Direction.values()[face],
|
||||
Direction.values()[side1],
|
||||
Direction.values()[side2]
|
||||
))
|
||||
}
|
||||
} } }
|
||||
|
||||
/**
|
||||
* Reverse lookup for [cornersDir].
|
||||
* 1st index: primary face
|
||||
* 2nd index: undirected corner index.
|
||||
* value: directed corner index
|
||||
* A null value indicates an invalid corner (primary face not shared by corner).
|
||||
*/
|
||||
@JvmField
|
||||
val boxCornersDirFromUndir = Array(6) { faceIdx -> Array(8) { undirIdx ->
|
||||
val face = Direction.values()[faceIdx]
|
||||
val corner = boxCornersUndir[undirIdx]
|
||||
if (!corner.contains(face)) null
|
||||
else boxCornersDir.findIdx { it.first == face && it.equalsUnordered(corner) }
|
||||
} }
|
||||
|
||||
@JvmField
|
||||
val forSide = Direction.values().mapArray { AoSideHelper(it) }
|
||||
|
||||
/**
|
||||
* Get corner index for vertex coordinates
|
||||
*/
|
||||
@JvmStatic
|
||||
fun getCornerUndir(x: Double, y: Double, z: Double): Int {
|
||||
var result = 0
|
||||
if (x > 0.0) result += 1
|
||||
if (y > 0.0) result += 2
|
||||
if (z > 0.0) result += 4
|
||||
return result
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package mods.betterfoliage.render.lighting
|
||||
|
||||
interface ForgeVertexLighterAccess {
|
||||
var vertexLighter: ForgeVertexLighter
|
||||
}
|
||||
|
||||
interface ForgeVertexLighter {
|
||||
fun updateVertexLightmap(normal: FloatArray, lightmap: FloatArray, x: Float, y: Float, z: Float)
|
||||
fun updateVertexColor(normal: FloatArray, color: FloatArray, x: Float, y: Float, z: Float, tint: Float, multiplier: Int)
|
||||
}
|
||||
|
||||
fun ForgeVertexLighter.grass() = object: ForgeVertexLighter {
|
||||
override fun updateVertexLightmap(normal: FloatArray, lightmap: FloatArray, x: Float, y: Float, z: Float) {
|
||||
this@grass.updateVertexLightmap(normal, lightmap, x * 0.5f, 1.0f, z * 0.5f)
|
||||
}
|
||||
|
||||
override fun updateVertexColor(normal: FloatArray, color: FloatArray, x: Float, y: Float, z: Float, tint: Float, multiplier: Int) {
|
||||
this@grass.updateVertexColor(normal, color, x * 0.5f, 1.0f, z * 0.5f, tint, multiplier
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun ForgeVertexLighter.grassSimple() = object: ForgeVertexLighter {
|
||||
val normalUp = floatArrayOf(0.0f, 1.0f, 0.0f, 0.0f)
|
||||
override fun updateVertexLightmap(normal: FloatArray, lightmap: FloatArray, x: Float, y: Float, z: Float) {
|
||||
this@grassSimple.updateVertexLightmap(normalUp, lightmap, 0.0f, 1.0f, 0.0f)
|
||||
}
|
||||
|
||||
override fun updateVertexColor(normal: FloatArray, color: FloatArray, x: Float, y: Float, z: Float, tint: Float, multiplier: Int) {
|
||||
this@grassSimple.updateVertexColor(normalUp, color, 0.0f, 1.0f, 0.0f, tint, multiplier
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,158 +0,0 @@
|
||||
package mods.betterfoliage.render.lighting
|
||||
|
||||
import mods.betterfoliage.render.old.Quad
|
||||
import mods.betterfoliage.render.old.Vertex
|
||||
import mods.betterfoliage.util.Double3
|
||||
import mods.betterfoliage.util.Rotation
|
||||
import mods.betterfoliage.util.axes
|
||||
import mods.betterfoliage.util.boxEdges
|
||||
import mods.betterfoliage.util.boxFaces
|
||||
import mods.betterfoliage.util.face
|
||||
import mods.betterfoliage.util.get
|
||||
import mods.betterfoliage.util.nearestAngle
|
||||
import mods.betterfoliage.util.nearestPosition
|
||||
import mods.betterfoliage.util.perpendiculars
|
||||
import mods.betterfoliage.util.vec
|
||||
import net.minecraft.util.Direction
|
||||
import net.minecraft.util.Direction.*
|
||||
import java.lang.Math.min
|
||||
|
||||
typealias EdgeShaderFactory = (Direction, Direction) -> ModelLighter
|
||||
typealias CornerShaderFactory = (Direction, Direction, Direction) -> ModelLighter
|
||||
typealias ShaderFactory = (Quad, Vertex) -> ModelLighter
|
||||
|
||||
/** Holds lighting values for block corners as calculated by vanilla Minecraft rendering. */
|
||||
class CornerLightData {
|
||||
var valid = false
|
||||
var brightness = 0
|
||||
var red: Float = 0.0f
|
||||
var green: Float = 0.0f
|
||||
var blue: Float = 0.0f
|
||||
|
||||
fun reset() { valid = false }
|
||||
|
||||
fun set(brightness: Int, red: Float, green: Float, blue: Float) {
|
||||
if (valid) return
|
||||
this.valid = true
|
||||
this.brightness = brightness
|
||||
this.red = red
|
||||
this.green = green
|
||||
this.blue = blue
|
||||
}
|
||||
|
||||
fun set(brightness: Int, colorMultiplier: Float) {
|
||||
this.valid = true
|
||||
this.brightness = brightness
|
||||
this.red = colorMultiplier
|
||||
this.green = colorMultiplier
|
||||
this.blue = colorMultiplier
|
||||
}
|
||||
|
||||
companion object {
|
||||
val black: CornerLightData get() = CornerLightData()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Instances of this interface are associated with [Model] vertices, and used to apply brightness and color
|
||||
* values to a [RenderVertex].
|
||||
*/
|
||||
interface ModelLighter {
|
||||
/**
|
||||
* Set shading values of a [RenderVertex]
|
||||
*
|
||||
* @param[context] context that can be queried for lighting data in a [Model]-relative frame of reference
|
||||
* @param[vertex] the [RenderVertex] to manipulate
|
||||
*/
|
||||
fun shade(context: LightingCtx, vertex: RenderVertex)
|
||||
|
||||
/**
|
||||
* Return a new rotated version of this [ModelLighter]. Used during [Model] setup when rotating the model itself.
|
||||
*/
|
||||
fun rotate(rot: Rotation): ModelLighter
|
||||
|
||||
/** Set all lighting values on the [RenderVertex] to match the given [CornerLightData]. */
|
||||
fun RenderVertex.shade(shading: CornerLightData) {
|
||||
brightness = shading.brightness; red = shading.red; green = shading.green; blue = shading.blue
|
||||
}
|
||||
|
||||
/** Set the lighting values on the [RenderVertex] to a weighted average of the two [CornerLightData] instances. */
|
||||
fun RenderVertex.shade(shading1: CornerLightData, shading2: CornerLightData, weight1: Float = 0.5f, weight2: Float = 0.5f) {
|
||||
red = min(shading1.red * weight1 + shading2.red * weight2, 1.0f)
|
||||
green = min(shading1.green * weight1 + shading2.green * weight2, 1.0f)
|
||||
blue = min(shading1.blue * weight1 + shading2.blue * weight2, 1.0f)
|
||||
brightness = brWeighted(shading1.brightness, weight1, shading2.brightness, weight2)
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the lighting values on the [RenderVertex] directly.
|
||||
*
|
||||
* @param[brightness] packed brightness value
|
||||
* @param[color] packed color value
|
||||
*/
|
||||
fun RenderVertex.shade(brightness: Int, color: Int) {
|
||||
this.brightness = brightness; setColor(color)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a [ModelLighter] resolver for quads that point towards one of the 6 block faces.
|
||||
* The resolver works the following way:
|
||||
* - determines which face the _quad_ normal points towards (if not overridden)
|
||||
* - determines the distance of the _vertex_ to the corners and edge midpoints on that block face
|
||||
* - if _corner_ is given, and the _vertex_ is closest to a block corner, returns the [ModelLighter] created by _corner_
|
||||
* - if _edge_ is given, and the _vertex_ is closest to an edge midpoint, returns the [ModelLighter] created by _edge_
|
||||
*
|
||||
* @param[overrideFace] assume the given face instead of going by the _quad_ normal
|
||||
* @param[corner] [ModelLighter] instantiation lambda for corner vertices
|
||||
* @param[edge] [ModelLighter] instantiation lambda for edge midpoint vertices
|
||||
*/
|
||||
fun faceOrientedAuto(overrideFace: Direction? = null,
|
||||
corner: CornerShaderFactory? = null,
|
||||
edge: EdgeShaderFactory? = null) =
|
||||
fun(quad: Quad, vertex: Vertex): ModelLighter {
|
||||
val quadFace = overrideFace ?: quad.normal.nearestCardinal
|
||||
val nearestCorner = nearestPosition(vertex.xyz, boxFaces[quadFace].allCorners) {
|
||||
(quadFace.vec + it.first.vec + it.second.vec) * 0.5
|
||||
}
|
||||
val nearestEdge = nearestPosition(vertex.xyz, quadFace.perpendiculars) {
|
||||
(quadFace.vec + it.vec) * 0.5
|
||||
}
|
||||
if (edge != null && (nearestEdge.second < nearestCorner.second || corner == null))
|
||||
return edge(quadFace, nearestEdge.first)
|
||||
else return corner!!(quadFace, nearestCorner.first.first, nearestCorner.first.second)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a ModelLighter resolver for quads that point towards one of the 12 block edges.
|
||||
* The resolver works the following way:
|
||||
* - determines which edge the _quad_ normal points towards (if not overridden)
|
||||
* - determines which face midpoint the _vertex_ is closest to, of the 2 block faces that share this edge
|
||||
* - determines which block corner _of this face_ the _vertex_ is closest to
|
||||
* - returns the [ModelLighter] created by _corner_
|
||||
*
|
||||
* @param[overrideEdge] assume the given edge instead of going by the _quad_ normal
|
||||
* @param[corner] ModelLighter instantiation lambda
|
||||
*/
|
||||
fun edgeOrientedAuto(overrideEdge: Pair<Direction, Direction>? = null,
|
||||
corner: CornerShaderFactory
|
||||
) =
|
||||
fun(quad: Quad, vertex: Vertex): ModelLighter {
|
||||
val edgeDir = overrideEdge ?: nearestAngle(quad.normal, boxEdges) { it.first.vec + it.second.vec }.first
|
||||
val nearestFace = nearestPosition(vertex.xyz, edgeDir.toList()) { it.vec }.first
|
||||
val nearestCorner = nearestPosition(vertex.xyz, boxFaces[nearestFace].allCorners) {
|
||||
(nearestFace.vec + it.first.vec + it.second.vec) * 0.5
|
||||
}.first
|
||||
return corner(nearestFace, nearestCorner.first, nearestCorner.second)
|
||||
}
|
||||
|
||||
fun faceOrientedInterpolate(overrideFace: Direction? = null) =
|
||||
fun(quad: Quad, vertex: Vertex): ModelLighter {
|
||||
val resolver = faceOrientedAuto(overrideFace, edge = { face, edgeDir ->
|
||||
val axis = axes.find { it != face.axis && it != edgeDir.axis }!!
|
||||
val vec = Double3((axis to AxisDirection.POSITIVE).face)
|
||||
val pos = vertex.xyz.dot(vec)
|
||||
EdgeInterpolateFallback(face, edgeDir, pos)
|
||||
})
|
||||
return resolver(quad, vertex)
|
||||
}
|
||||
@@ -1,118 +0,0 @@
|
||||
package mods.betterfoliage.render.lighting
|
||||
|
||||
import mods.betterfoliage.render.old.BlockCtx
|
||||
import mods.betterfoliage.util.Int3
|
||||
import mods.betterfoliage.util.Rotation
|
||||
import mods.betterfoliage.util.allDirections
|
||||
import mods.betterfoliage.util.boxFaces
|
||||
import mods.betterfoliage.util.get
|
||||
import mods.betterfoliage.util.offset
|
||||
import mods.betterfoliage.util.plus
|
||||
import mods.betterfoliage.util.rotate
|
||||
import net.minecraft.client.Minecraft
|
||||
import net.minecraft.client.renderer.BlockModelRenderer
|
||||
import net.minecraft.client.renderer.WorldRenderer
|
||||
import net.minecraft.util.Direction
|
||||
import java.util.*
|
||||
|
||||
val Direction.aoMultiplier: Float get() = when(this) {
|
||||
Direction.UP -> 1.0f
|
||||
Direction.DOWN -> 0.5f
|
||||
Direction.NORTH, Direction.SOUTH -> 0.8f
|
||||
Direction.EAST, Direction.WEST -> 0.6f
|
||||
}
|
||||
|
||||
interface LightingCtx {
|
||||
val modelRotation: Rotation
|
||||
val blockContext: BlockCtx
|
||||
val aoEnabled: Boolean
|
||||
|
||||
val brightness get() = brightness(Int3.zero)
|
||||
val color get() = color(Int3.zero)
|
||||
fun brightness(face: Direction) = brightness(face.offset)
|
||||
fun color(face: Direction) = color(face.offset)
|
||||
|
||||
fun brightness(offset: Int3) = offset.rotate(modelRotation).let {
|
||||
WorldRenderer.getCombinedLight(blockContext.world, blockContext.pos + it)
|
||||
}
|
||||
fun color(offset: Int3) = blockContext.offset(offset.rotate(modelRotation)).let { Minecraft.getInstance().blockColors.getColor(it.state, it.world, it.pos, 0) }
|
||||
|
||||
fun lighting(face: Direction, corner1: Direction, corner2: Direction): CornerLightData
|
||||
}
|
||||
|
||||
class DefaultLightingCtx(blockContext: BlockCtx) : LightingCtx {
|
||||
override var modelRotation = Rotation.identity
|
||||
|
||||
override var aoEnabled = false
|
||||
protected set
|
||||
override var blockContext: BlockCtx = blockContext
|
||||
protected set
|
||||
override var brightness = brightness(Int3.zero)
|
||||
protected set
|
||||
override var color = color(Int3.zero)
|
||||
protected set
|
||||
|
||||
override fun brightness(face: Direction) = brightness(face.offset)
|
||||
override fun color(face: Direction) = color(face.offset)
|
||||
|
||||
// smooth lighting stuff
|
||||
val lightingData = Array(6) { FaceLightData(allDirections[it]) }
|
||||
override fun lighting(face: Direction, corner1: Direction, corner2: Direction): CornerLightData = lightingData[face.rotate(modelRotation)].let { faceData ->
|
||||
if (!faceData.isValid) faceData.update(blockContext, faceData.face.aoMultiplier)
|
||||
return faceData[corner1.rotate(modelRotation), corner2.rotate(modelRotation)]
|
||||
}
|
||||
|
||||
fun reset(blockContext: BlockCtx) {
|
||||
this.blockContext = blockContext
|
||||
brightness = brightness(Int3.zero)
|
||||
color = color(Int3.zero)
|
||||
modelRotation = Rotation.identity
|
||||
lightingData.forEach { it.isValid = false }
|
||||
aoEnabled = Minecraft.isAmbientOcclusionEnabled()
|
||||
// allDirections.forEach { lightingData[it].update(blockContext, it.aoMultiplier) }
|
||||
}
|
||||
}
|
||||
|
||||
private val vanillaAOFactory = BlockModelRenderer.AmbientOcclusionFace::class.java.let {
|
||||
it.getDeclaredConstructor(BlockModelRenderer::class.java).apply { isAccessible = true }
|
||||
}.let { ctor -> { ctor.newInstance(Minecraft.getInstance().blockRendererDispatcher.blockModelRenderer) } }
|
||||
|
||||
class FaceLightData(val face: Direction) {
|
||||
val topDir = boxFaces[face].top
|
||||
val leftDir = boxFaces[face].left
|
||||
|
||||
val topLeft = CornerLightData()
|
||||
val topRight = CornerLightData()
|
||||
val bottomLeft = CornerLightData()
|
||||
val bottomRight = CornerLightData()
|
||||
|
||||
val vanillaOrdered = when(face) {
|
||||
Direction.DOWN -> listOf(topLeft, bottomLeft, bottomRight, topRight)
|
||||
Direction.UP -> listOf(bottomRight, topRight, topLeft, bottomLeft)
|
||||
Direction.NORTH -> listOf(bottomLeft, bottomRight, topRight, topLeft)
|
||||
Direction.SOUTH -> listOf(topLeft, bottomLeft, bottomRight, topRight)
|
||||
Direction.WEST -> listOf(bottomLeft, bottomRight, topRight, topLeft)
|
||||
Direction.EAST -> listOf(topRight, topLeft, bottomLeft, bottomRight)
|
||||
}
|
||||
|
||||
val delegate = vanillaAOFactory()
|
||||
var isValid = false
|
||||
|
||||
fun update(blockCtx: BlockCtx, multiplier: Float) {
|
||||
val quadBounds = FloatArray(12)
|
||||
val flags = BitSet(3).apply { set(0) }
|
||||
// delegate.updateVertexBrightness(blockCtx.world, blockCtx.state, blockCtx.pos, face, quadBounds, flags)
|
||||
vanillaOrdered.forEachIndexed { idx, corner -> corner.set(delegate.vertexBrightness[idx], delegate.vertexColorMultiplier[idx] * multiplier) }
|
||||
isValid = true
|
||||
}
|
||||
|
||||
operator fun get(dir1: Direction, dir2: Direction): CornerLightData {
|
||||
val isTop = topDir == dir1 || topDir == dir2
|
||||
val isLeft = leftDir == dir1 || leftDir == dir2
|
||||
return if (isTop) {
|
||||
if (isLeft) topLeft else topRight
|
||||
} else {
|
||||
if (isLeft) bottomLeft else bottomRight
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,162 +0,0 @@
|
||||
package mods.betterfoliage.render.lighting
|
||||
|
||||
import mods.betterfoliage.util.Int3
|
||||
import mods.betterfoliage.util.Rotation
|
||||
import mods.betterfoliage.util.axes
|
||||
import mods.betterfoliage.util.boxFaces
|
||||
import mods.betterfoliage.util.face
|
||||
import mods.betterfoliage.util.get
|
||||
import mods.betterfoliage.util.offset
|
||||
import mods.betterfoliage.util.rotate
|
||||
import net.minecraft.util.Direction
|
||||
|
||||
|
||||
const val defaultCornerDimming = 0.5f
|
||||
const val defaultEdgeDimming = 0.8f
|
||||
|
||||
// ================================
|
||||
// Shader instantiation lambdas
|
||||
// ================================
|
||||
fun cornerAo(fallbackAxis: Direction.Axis): CornerShaderFactory = { face, dir1, dir2 ->
|
||||
val fallbackDir = listOf(face, dir1, dir2).find { it.axis == fallbackAxis }!!
|
||||
CornerSingleFallback(face, dir1, dir2, fallbackDir)
|
||||
}
|
||||
val cornerFlat = { face: Direction, dir1: Direction, dir2: Direction -> FaceFlat(face) }
|
||||
fun cornerAoTri(func: (CornerLightData, CornerLightData)-> CornerLightData) = { face: Direction, dir1: Direction, dir2: Direction ->
|
||||
CornerTri(face, dir1, dir2, func)
|
||||
}
|
||||
val cornerAoMaxGreen = cornerAoTri { s1, s2 -> if (s1.green > s2.green) s1 else s2 }
|
||||
|
||||
fun cornerInterpolate(edgeAxis: Direction.Axis, weight: Float, dimming: Float): CornerShaderFactory = { dir1, dir2, dir3 ->
|
||||
val edgeDir = listOf(dir1, dir2, dir3).find { it.axis == edgeAxis }!!
|
||||
val faceDirs = listOf(dir1, dir2, dir3).filter { it.axis != edgeAxis }
|
||||
CornerInterpolateDimming(faceDirs[0], faceDirs[1], edgeDir, weight, dimming)
|
||||
}
|
||||
|
||||
// ================================
|
||||
// Shaders
|
||||
// ================================
|
||||
object NoLighting : ModelLighter {
|
||||
override fun shade(context: LightingCtx, vertex: RenderVertex) = vertex.shade(CornerLightData.black)
|
||||
override fun rotate(rot: Rotation) = this
|
||||
}
|
||||
|
||||
class CornerSingleFallback(val face: Direction, val dir1: Direction, val dir2: Direction, val fallbackDir: Direction, val fallbackDimming: Float = defaultCornerDimming) :
|
||||
ModelLighter {
|
||||
val offset = Int3(fallbackDir)
|
||||
override fun shade(context: LightingCtx, vertex: RenderVertex) {
|
||||
val shading = context.lighting(face, dir1, dir2)
|
||||
if (shading.valid)
|
||||
vertex.shade(shading)
|
||||
else {
|
||||
vertex.shade(context.brightness(offset) brMul fallbackDimming, context.color(offset) colorMul fallbackDimming)
|
||||
}
|
||||
}
|
||||
override fun rotate(rot: Rotation) = CornerSingleFallback(face.rotate(rot), dir1.rotate(rot), dir2.rotate(rot), fallbackDir.rotate(rot), fallbackDimming)
|
||||
}
|
||||
|
||||
inline fun accumulate(v1: CornerLightData?, v2: CornerLightData?, func: ((CornerLightData, CornerLightData)-> CornerLightData)): CornerLightData? {
|
||||
val v1ok = v1 != null && v1.valid
|
||||
val v2ok = v2 != null && v2.valid
|
||||
if (v1ok && v2ok) return func(v1!!, v2!!)
|
||||
if (v1ok) return v1
|
||||
if (v2ok) return v2
|
||||
return null
|
||||
}
|
||||
|
||||
class CornerTri(val face: Direction, val dir1: Direction, val dir2: Direction,
|
||||
val func: ((CornerLightData, CornerLightData)-> CornerLightData)) : ModelLighter {
|
||||
override fun shade(context: LightingCtx, vertex: RenderVertex) {
|
||||
var acc = accumulate(
|
||||
context.lighting(face, dir1, dir2),
|
||||
context.lighting(dir1, face, dir2),
|
||||
func)
|
||||
acc = accumulate(
|
||||
acc,
|
||||
context.lighting(dir2, face, dir1),
|
||||
func)
|
||||
vertex.shade(acc ?: CornerLightData.black)
|
||||
}
|
||||
override fun rotate(rot: Rotation) = CornerTri(face.rotate(rot), dir1.rotate(rot), dir2.rotate(rot), func)
|
||||
}
|
||||
|
||||
class EdgeInterpolateFallback(val face: Direction, val edgeDir: Direction, val pos: Double, val fallbackDimming: Float = defaultEdgeDimming):
|
||||
ModelLighter {
|
||||
val offset = Int3(edgeDir)
|
||||
val edgeAxis = axes.find { it != face.axis && it != edgeDir.axis }!!
|
||||
val weightN = (0.5 - pos).toFloat()
|
||||
val weightP = (0.5 + pos).toFloat()
|
||||
|
||||
override fun shade(context: LightingCtx, vertex: RenderVertex) {
|
||||
val shadingP = context.lighting(face, edgeDir, (edgeAxis to Direction.AxisDirection.POSITIVE).face)
|
||||
val shadingN = context.lighting(face, edgeDir, (edgeAxis to Direction.AxisDirection.NEGATIVE).face)
|
||||
if (!shadingP.valid && !shadingN.valid) {
|
||||
return vertex.shade(context.brightness(offset) brMul fallbackDimming, context.color(offset) colorMul fallbackDimming)
|
||||
}
|
||||
if (!shadingP.valid) return vertex.shade(shadingN)
|
||||
if (!shadingN.valid) return vertex.shade(shadingP)
|
||||
vertex.shade(shadingP, shadingN, weightP, weightN)
|
||||
}
|
||||
override fun rotate(rot: Rotation) = EdgeInterpolateFallback(face.rotate(rot), edgeDir.rotate(rot), pos)
|
||||
}
|
||||
|
||||
class CornerInterpolateDimming(val face1: Direction, val face2: Direction, val edgeDir: Direction,
|
||||
val weight: Float, val dimming: Float, val fallbackDimming: Float = defaultCornerDimming
|
||||
) : ModelLighter {
|
||||
val offset = Int3(edgeDir)
|
||||
override fun shade(context: LightingCtx, vertex: RenderVertex) {
|
||||
var shading1 = context.lighting(face1, edgeDir, face2)
|
||||
var shading2 = context.lighting(face2, edgeDir, face1)
|
||||
var weight1 = weight
|
||||
var weight2 = 1.0f - weight
|
||||
if (!shading1.valid && !shading2.valid) {
|
||||
return vertex.shade(context.brightness(offset) brMul fallbackDimming, context.color(offset) colorMul fallbackDimming)
|
||||
}
|
||||
if (!shading1.valid) { shading1 = shading2; weight1 *= dimming }
|
||||
if (!shading2.valid) { shading2 = shading1; weight2 *= dimming }
|
||||
vertex.shade(shading1, shading2, weight1, weight2)
|
||||
}
|
||||
|
||||
override fun rotate(rot: Rotation) =
|
||||
CornerInterpolateDimming(face1.rotate(rot), face2.rotate(rot), edgeDir.rotate(rot), weight, dimming, fallbackDimming)
|
||||
}
|
||||
|
||||
class FaceCenter(val face: Direction): ModelLighter {
|
||||
override fun shade(context: LightingCtx, vertex: RenderVertex) {
|
||||
vertex.red = 0.0f; vertex.green = 0.0f; vertex.blue = 0.0f;
|
||||
val b = IntArray(4)
|
||||
boxFaces[face].allCorners.forEachIndexed { idx, corner ->
|
||||
val shading = context.lighting(face, corner.first, corner.second)
|
||||
vertex.red += shading.red
|
||||
vertex.green += shading.green
|
||||
vertex.blue += shading.blue
|
||||
b[idx] = shading.brightness
|
||||
}
|
||||
vertex.apply { red *= 0.25f; green *= 0.25f; blue *= 0.25f }
|
||||
vertex.brightness = brSum(0.25f, *b)
|
||||
}
|
||||
override fun rotate(rot: Rotation) = FaceCenter(face.rotate(rot))
|
||||
}
|
||||
|
||||
class FaceFlat(val face: Direction): ModelLighter {
|
||||
override fun shade(context: LightingCtx, vertex: RenderVertex) {
|
||||
vertex.shade(context.brightness(face.offset), context.color(Int3.zero))
|
||||
}
|
||||
override fun rotate(rot: Rotation): ModelLighter = FaceFlat(face.rotate(rot))
|
||||
}
|
||||
|
||||
class FlatOffset(val offset: Int3): ModelLighter {
|
||||
override fun shade(context: LightingCtx, vertex: RenderVertex) {
|
||||
vertex.brightness = context.brightness(offset)
|
||||
vertex.setColor(context.color(offset))
|
||||
}
|
||||
override fun rotate(rot: Rotation): ModelLighter = this
|
||||
}
|
||||
|
||||
class FlatOffsetNoColor(val offset: Int3): ModelLighter {
|
||||
override fun shade(context: LightingCtx, vertex: RenderVertex) {
|
||||
vertex.brightness = context.brightness(offset)
|
||||
vertex.red = 1.0f; vertex.green = 1.0f; vertex.blue = 1.0f
|
||||
}
|
||||
override fun rotate(rot: Rotation): ModelLighter = this
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
@file:JvmName("PixelFormat")
|
||||
package mods.betterfoliage.render.lighting
|
||||
|
||||
@@ -0,0 +1,147 @@
|
||||
package mods.betterfoliage.render.lighting
|
||||
|
||||
import mods.betterfoliage.chunk.BlockCtx
|
||||
import net.minecraft.block.BlockState
|
||||
import net.minecraft.client.renderer.BlockModelRenderer
|
||||
import net.minecraft.util.Direction
|
||||
import net.minecraft.util.math.BlockPos
|
||||
import net.minecraft.world.ILightReader
|
||||
|
||||
data class LightingData(
|
||||
@JvmField var packedLight: Int = 0,
|
||||
@JvmField var colorMultiplier: Float = 1.0f
|
||||
) {
|
||||
fun mixFrom(corner: LightingData, side1: LightingData, side2: LightingData, center: LightingData) {
|
||||
colorMultiplier =
|
||||
(center.colorMultiplier + side1.colorMultiplier + side2.colorMultiplier + corner.colorMultiplier) * 0.25f
|
||||
packedLight = (
|
||||
center.packedLight +
|
||||
(side1.packedLight.takeUnless { it == 0 } ?: center.packedLight) +
|
||||
(side2.packedLight.takeUnless { it == 0 } ?: center.packedLight) +
|
||||
(corner.packedLight.takeUnless { it == 0 } ?: center.packedLight)
|
||||
).let { sum -> (sum shr 2) and 0xFF00FF }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Replacement for [BlockModelRenderer.AmbientOcclusionFace]
|
||||
* This gets called on a LOT, so object instantiation is avoided.
|
||||
* Not thread-safe, always use a [ThreadLocal] instance
|
||||
*/
|
||||
class VanillaAoCalculator {
|
||||
lateinit var world: ILightReader
|
||||
|
||||
/** [blockPos] is used to get block-related information (i.e. tint, opacity, etc.)
|
||||
* [lightPos] is used to get light-related information
|
||||
* this facilitates masquerade rendering of blocks */
|
||||
lateinit var blockPos: BlockPos
|
||||
lateinit var lightPos: BlockPos
|
||||
|
||||
private val probe = LightProbe(BlockModelRenderer.CACHE_COMBINED_LIGHT.get())
|
||||
|
||||
val isValid = BooleanArray(6)
|
||||
val aoData = Array(24) { LightingData() }
|
||||
|
||||
// scratchpad values used during calculation
|
||||
private val centerAo = LightingData()
|
||||
private val sideAo = Array(4) { LightingData() }
|
||||
private val cornerAo = Array(4) { LightingData() }
|
||||
private val isOccluded = BooleanArray(4)
|
||||
|
||||
fun reset(ctx: BlockCtx) {
|
||||
world = ctx.world; blockPos = ctx.pos; lightPos = ctx.pos
|
||||
(0 until 6).forEach { isValid[it] = false }
|
||||
}
|
||||
|
||||
fun fillLightData(lightFace: Direction, isOpaque: Boolean? = null) {
|
||||
if (!isValid[lightFace.ordinal]) calculate(lightFace, isOpaque)
|
||||
}
|
||||
|
||||
/**
|
||||
* Replicate [BlockModelRenderer.AmbientOcclusionFace.updateVertexBrightness]
|
||||
* Does not handle interpolation for non-cubic models, that should be
|
||||
* done in a [VanillaVertexLighter]
|
||||
* @param lightFace face of the block to calculate
|
||||
* @param forceFull force full-block status for lighting calculation, null for auto
|
||||
*/
|
||||
private fun calculate(lightFace: Direction, forceFull: Boolean?) {
|
||||
if (isValid[lightFace.ordinal]) return
|
||||
val sideHelper = AoSideHelper.forSide[lightFace.ordinal]
|
||||
|
||||
// Bit 0 of the bitset in vanilla calculations
|
||||
// true if the block model is planar with the block boundary
|
||||
val isFullBlock = forceFull ?: world.getBlockState(blockPos).isCollisionShapeOpaque(world, blockPos)
|
||||
|
||||
val lightOrigin = if (isFullBlock) lightPos.offset(lightFace) else lightPos
|
||||
|
||||
// AO calculation for the face center
|
||||
probe.position { setPos(lightOrigin) }.writeTo(centerAo)
|
||||
if (!isFullBlock && !probe.position { move(lightFace) }.state.isOpaqueCube(world, probe.pos)) {
|
||||
// if the neighboring block in the lightface direction is
|
||||
// transparent (non-opaque), use its packed light instead of our own
|
||||
// (if our block is a full block, we are already using this value)
|
||||
centerAo.packedLight = probe.packedLight
|
||||
}
|
||||
|
||||
// AO calculation for the 4 sides
|
||||
sideHelper.sides.forEachIndexed { sideIdx, sideDir ->
|
||||
// record light data in the block 1 step to the side
|
||||
probe.position { setPos(lightOrigin).move(sideDir) }.writeTo(sideAo[sideIdx])
|
||||
// side is considered occluded if the block 1 step to that side and
|
||||
// 1 step forward (in the lightface direction) is not fully transparent
|
||||
isOccluded[sideIdx] = probe.position { move(lightFace) }.isNonTransparent
|
||||
}
|
||||
|
||||
// AO Calculation for the 4 corners
|
||||
AoSideHelper.faceCornersIdx.forEachIndexed { cornerIdx, sideIndices ->
|
||||
val bothOccluded = isOccluded[sideIndices.first] && isOccluded[sideIndices.second]
|
||||
if (bothOccluded) cornerAo[cornerIdx].apply {
|
||||
// if both sides are occluded, just use the packed light for one of the sides instead
|
||||
val copyFrom = sideAo[sideIndices.first]
|
||||
packedLight = copyFrom.packedLight; colorMultiplier = copyFrom.colorMultiplier
|
||||
}
|
||||
else {
|
||||
// lookup actual packed light from the cornering block in the world
|
||||
probe.position {
|
||||
setPos(lightOrigin)
|
||||
.move(sideHelper.sides[sideIndices.first])
|
||||
.move(sideHelper.sides[sideIndices.second])
|
||||
}.writeTo(cornerAo[cornerIdx])
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate and store final interpolated value for each corner
|
||||
AoSideHelper.faceCornersIdx.forEachIndexed { cornerIdx, sideIndices ->
|
||||
val aoIdx = sideHelper.aoIndex[cornerIdx]
|
||||
aoData[aoIdx].mixFrom(
|
||||
cornerAo[cornerIdx],
|
||||
sideAo[sideIndices.first],
|
||||
sideAo[sideIndices.second],
|
||||
centerAo
|
||||
)
|
||||
}
|
||||
isValid[lightFace.ordinal] = true
|
||||
}
|
||||
|
||||
inner class LightProbe(
|
||||
val cache: BlockModelRenderer.Cache
|
||||
) {
|
||||
lateinit var state: BlockState
|
||||
val pos = BlockPos.Mutable()
|
||||
|
||||
val packedLight: Int get() = cache.getPackedLight(state, world, pos)
|
||||
val colorMultiplier: Float get() = cache.getBrightness(state, world, pos)
|
||||
val isNonTransparent: Boolean get() = state.getOpacity(world, pos) > 0
|
||||
|
||||
fun writeTo(data: LightingData) {
|
||||
data.packedLight = packedLight
|
||||
data.colorMultiplier = colorMultiplier
|
||||
}
|
||||
|
||||
inline fun position(func: BlockPos.Mutable.() -> Unit): LightProbe {
|
||||
pos.func()
|
||||
state = world.getBlockState(pos)
|
||||
return this
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
package mods.betterfoliage.render.lighting
|
||||
|
||||
import mods.betterfoliage.render.old.HalfBakedQuad
|
||||
import mods.betterfoliage.util.Double3
|
||||
import mods.betterfoliage.util.EPSILON
|
||||
import mods.betterfoliage.util.minBy
|
||||
import net.minecraft.client.renderer.color.BlockColors
|
||||
import net.minecraft.util.Direction
|
||||
import net.minecraft.util.Direction.*
|
||||
import net.minecraft.util.Direction.Axis
|
||||
import net.minecraftforge.client.model.pipeline.LightUtil
|
||||
import kotlin.math.abs
|
||||
|
||||
class VanillaQuadLighting {
|
||||
val packedLight = IntArray(4)
|
||||
val colorMultiplier = FloatArray(4)
|
||||
val tint = FloatArray(3)
|
||||
|
||||
val calc = VanillaAoCalculator()
|
||||
lateinit var blockColors: BlockColors
|
||||
|
||||
fun updateBlockTint(tintIndex: Int) {
|
||||
if (tintIndex == -1) {
|
||||
tint[0] = 1.0f; tint[1] = 1.0f; tint[2] = 1.0f
|
||||
} else {
|
||||
val state = calc.world.getBlockState(calc.blockPos)
|
||||
blockColors.getColor(state, calc.world, calc.blockPos, tintIndex).let { blockTint ->
|
||||
tint[0] = (blockTint shr 16 and 255).toFloat() / 255.0f
|
||||
tint[1] = (blockTint shr 8 and 255).toFloat() / 255.0f
|
||||
tint[2] = (blockTint and 255).toFloat() / 255.0f
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun applyDiffuseLighting(face: Direction) {
|
||||
val factor = LightUtil.diffuseLight(face)
|
||||
tint[0] *= factor; tint[1] *= factor; tint[2] *= factor
|
||||
}
|
||||
}
|
||||
|
||||
abstract class VanillaVertexLighter {
|
||||
abstract fun updateLightmapAndColor(quad: HalfBakedQuad, lighting: VanillaQuadLighting)
|
||||
|
||||
inline fun VanillaQuadLighting.updateWithCornerAo(quad: HalfBakedQuad, func: (Double3)->Int?) {
|
||||
quad.raw.verts.forEachIndexed { idx, vertex ->
|
||||
func(vertex.xyz)?.let {
|
||||
packedLight[idx] = calc.aoData[it].packedLight
|
||||
colorMultiplier[idx] = calc.aoData[it].colorMultiplier
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object VanillaFullBlockLighting : VanillaVertexLighter() {
|
||||
override fun updateLightmapAndColor(quad: HalfBakedQuad, lighting: VanillaQuadLighting) {
|
||||
// TODO bounds checking
|
||||
val face = quad.raw.face()
|
||||
lighting.calc.fillLightData(face, true)
|
||||
lighting.updateWithCornerAo(quad) { nearestCornerOnFace(it, face) }
|
||||
lighting.updateBlockTint(quad.baked.tintIndex)
|
||||
if (quad.baked.shouldApplyDiffuseLighting()) lighting.applyDiffuseLighting(face)
|
||||
}
|
||||
}
|
||||
|
||||
object RoundLeafLighting : VanillaVertexLighter() {
|
||||
override fun updateLightmapAndColor(quad: HalfBakedQuad, lighting: VanillaQuadLighting) {
|
||||
val angles = getAngles45(quad)?.let { normalFaces ->
|
||||
lighting.calc.fillLightData(normalFaces.first)
|
||||
lighting.calc.fillLightData(normalFaces.second)
|
||||
if (normalFaces.first != UP && normalFaces.second != UP) lighting.calc.fillLightData(UP)
|
||||
lighting.updateWithCornerAo(quad) { vertex ->
|
||||
val isUp = vertex.y > 0.5f
|
||||
val cornerUndir = AoSideHelper.getCornerUndir(vertex.x, vertex.y, vertex.z)
|
||||
val preferredFace = if (isUp) UP else normalFaces.minBy { faceDistance(it, vertex) }
|
||||
AoSideHelper.boxCornersDirFromUndir[preferredFace.ordinal][cornerUndir]
|
||||
}
|
||||
lighting.updateBlockTint(quad.baked.tintIndex)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class LightingPreferredFace(val face: Direction) : VanillaVertexLighter() {
|
||||
override fun updateLightmapAndColor(quad: HalfBakedQuad, lighting: VanillaQuadLighting) {
|
||||
lighting.calc.fillLightData(face)
|
||||
lighting.updateWithCornerAo(quad) { nearestCornerOnFace(it, face) }
|
||||
lighting.updateBlockTint(quad.baked.tintIndex)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the directed box corner index for the corner nearest the given vertex,
|
||||
* which is on the given face. May return null if the vertex is closest to
|
||||
* one of the opposite 4 corners
|
||||
*/
|
||||
fun nearestCornerOnFace(pos: Double3, face: Direction): Int? {
|
||||
val cornerUndir = AoSideHelper.getCornerUndir(pos.x, pos.y, pos.z)
|
||||
return AoSideHelper.boxCornersDirFromUndir[face.ordinal][cornerUndir]
|
||||
}
|
||||
|
||||
/**
|
||||
* If the quad normal approximately bisects 2 axes at a 45 degree angle,
|
||||
* and is approximately perpendicular to the third, returns the 2 directions
|
||||
* the quad normal points towards.
|
||||
* Returns null otherwise.
|
||||
*/
|
||||
fun getAngles45(quad: HalfBakedQuad): Pair<Direction, Direction>? {
|
||||
val normal = quad.raw.normal
|
||||
// one of the components must be close to zero
|
||||
val zeroAxis = when {
|
||||
abs(normal.x) < EPSILON -> Axis.X
|
||||
abs(normal.y) < EPSILON -> Axis.Y
|
||||
abs(normal.z) < EPSILON -> Axis.Z
|
||||
else -> return null
|
||||
}
|
||||
// the other two must be of similar magnitude
|
||||
val diff = when(zeroAxis) {
|
||||
Axis.X -> abs(abs(normal.y) - abs(normal.z))
|
||||
Axis.Y -> abs(abs(normal.x) - abs(normal.z))
|
||||
Axis.Z -> abs(abs(normal.x) - abs(normal.y))
|
||||
}
|
||||
if (diff > EPSILON) return null
|
||||
return when(zeroAxis) {
|
||||
Axis.X -> Pair(if (normal.y > 0.0f) UP else DOWN, if (normal.z > 0.0f) SOUTH else NORTH)
|
||||
Axis.Y -> Pair(if (normal.x > 0.0f) EAST else WEST, if (normal.z > 0.0f) SOUTH else NORTH)
|
||||
Axis.Z -> Pair(if (normal.x > 0.0f) EAST else WEST, if (normal.y > 0.0f) UP else DOWN)
|
||||
}
|
||||
}
|
||||
|
||||
fun faceDistance(face: Direction, pos: Double3) = when(face) {
|
||||
WEST -> pos.x; EAST -> 1.0 - pos.x
|
||||
DOWN -> pos.y; UP -> 1.0 - pos.y
|
||||
NORTH -> pos.z; SOUTH -> 1.0 - pos.z
|
||||
}
|
||||
@@ -1,146 +0,0 @@
|
||||
package mods.betterfoliage.render.lighting
|
||||
|
||||
import mods.betterfoliage.render.old.CombinedContext
|
||||
import mods.betterfoliage.render.old.Quad
|
||||
import mods.betterfoliage.render.old.Vertex
|
||||
import mods.betterfoliage.util.Double3
|
||||
import mods.betterfoliage.util.Rotation
|
||||
import net.minecraft.client.renderer.texture.TextureAtlasSprite
|
||||
import net.minecraft.util.Direction.*
|
||||
import java.awt.Color
|
||||
|
||||
typealias QuadIconResolver = (CombinedContext, Int, Quad) -> TextureAtlasSprite?
|
||||
typealias PostProcessLambda = RenderVertex.(CombinedContext, Int, Quad, Int, Vertex) -> Unit
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
class RenderVertex {
|
||||
var x: Double = 0.0
|
||||
var y: Double = 0.0
|
||||
var z: Double = 0.0
|
||||
var u: Double = 0.0
|
||||
var v: Double = 0.0
|
||||
var brightness: Int = 0
|
||||
var red: Float = 0.0f
|
||||
var green: Float = 0.0f
|
||||
var blue: Float = 0.0f
|
||||
|
||||
val rawData = IntArray(7)
|
||||
|
||||
fun init(vertex: Vertex, rot: Rotation, trans: Double3): RenderVertex {
|
||||
val result = vertex.xyz.rotate(rot) + trans
|
||||
x = result.x; y = result.y; z = result.z
|
||||
return this
|
||||
}
|
||||
fun init(vertex: Vertex): RenderVertex {
|
||||
x = vertex.xyz.x; y = vertex.xyz.y; z = vertex.xyz.z;
|
||||
u = vertex.uv.u; v = vertex.uv.v
|
||||
return this
|
||||
}
|
||||
fun translate(trans: Double3): RenderVertex { x += trans.x; y += trans.y; z += trans.z; return this }
|
||||
fun rotate(rot: Rotation): RenderVertex {
|
||||
if (rot === Rotation.identity) return this
|
||||
val rotX = rot.rotatedComponent(EAST, x, y, z)
|
||||
val rotY = rot.rotatedComponent(UP, x, y, z)
|
||||
val rotZ = rot.rotatedComponent(SOUTH, x, y, z)
|
||||
x = rotX; y = rotY; z = rotZ
|
||||
return this
|
||||
}
|
||||
inline fun rotateUV(n: Int): RenderVertex {
|
||||
when (n % 4) {
|
||||
1 -> { val t = v; v = -u; u = t; return this }
|
||||
2 -> { u = -u; v = -v; return this }
|
||||
3 -> { val t = -v; v = u; u = t; return this }
|
||||
else -> { return this }
|
||||
}
|
||||
}
|
||||
inline fun mirrorUV(mirrorU: Boolean, mirrorV: Boolean) {
|
||||
if (mirrorU) u = -u
|
||||
if (mirrorV) v = -v
|
||||
}
|
||||
inline fun setIcon(icon: TextureAtlasSprite): RenderVertex {
|
||||
u = (icon.maxU - icon.minU) * (u + 0.5) + icon.minU
|
||||
v = (icon.maxV - icon.minV) * (v + 0.5) + icon.minV
|
||||
return this
|
||||
}
|
||||
|
||||
inline fun setGrey(level: Float) {
|
||||
val grey = Math.min((red + green + blue) * 0.333f * level, 1.0f)
|
||||
red = grey; green = grey; blue = grey
|
||||
}
|
||||
inline fun multiplyColor(color: Int) {
|
||||
red *= (color shr 16 and 255) / 256.0f
|
||||
green *= (color shr 8 and 255) / 256.0f
|
||||
blue *= (color and 255) / 256.0f
|
||||
}
|
||||
inline fun setColor(color: Int) {
|
||||
red = (color shr 16 and 255) / 256.0f
|
||||
green = (color shr 8 and 255) / 256.0f
|
||||
blue = (color and 255) / 256.0f
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/** List of bit-shift offsets in packed brightness values where meaningful (4-bit) data is contained. */
|
||||
var brightnessComponents = listOf(20, 4)
|
||||
|
||||
/** Multiply the components of this packed brightness value with the given [Float]. */
|
||||
infix fun Int.brMul(f: Float): Int {
|
||||
val weight = (f * 256.0f).toInt()
|
||||
var result = 0
|
||||
brightnessComponents.forEach { shift ->
|
||||
val raw = (this shr shift) and 15
|
||||
val weighted = (raw) * weight / 256
|
||||
result = result or (weighted shl shift)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/** Multiply the components of this packed color value with the given [Float]. */
|
||||
infix fun Int.colorMul(f: Float): Int {
|
||||
val weight = (f * 256.0f).toInt()
|
||||
val red = (this shr 16 and 255) * weight / 256
|
||||
val green = (this shr 8 and 255) * weight / 256
|
||||
val blue = (this and 255) * weight / 256
|
||||
return (red shl 16) or (green shl 8) or blue
|
||||
}
|
||||
|
||||
/** Sum the components of all packed brightness values given. */
|
||||
fun brSum(multiplier: Float?, vararg brightness: Int): Int {
|
||||
val sum = Array(brightnessComponents.size) { 0 }
|
||||
brightnessComponents.forEachIndexed { idx, shift -> brightness.forEach { br ->
|
||||
val comp = (br shr shift) and 15
|
||||
sum[idx] += comp
|
||||
} }
|
||||
var result = 0
|
||||
brightnessComponents.forEachIndexed { idx, shift ->
|
||||
val comp = if (multiplier == null)
|
||||
((sum[idx]) shl shift)
|
||||
else
|
||||
((sum[idx].toFloat() * multiplier).toInt() shl shift)
|
||||
result = result or comp
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
fun brWeighted(br1: Int, weight1: Float, br2: Int, weight2: Float): Int {
|
||||
val w1int = (weight1 * 256.0f + 0.5f).toInt()
|
||||
val w2int = (weight2 * 256.0f + 0.5f).toInt()
|
||||
var result = 0
|
||||
brightnessComponents.forEachIndexed { idx, shift ->
|
||||
val comp1 = (br1 shr shift) and 15
|
||||
val comp2 = (br2 shr shift) and 15
|
||||
val compWeighted = (comp1 * w1int + comp2 * w2int) / 256
|
||||
result = result or ((compWeighted and 15) shl shift)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
data class HSB(var hue: Float, var saturation: Float, var brightness: Float) {
|
||||
companion object {
|
||||
fun fromColor(color: Int): HSB {
|
||||
val hsbVals = Color.RGBtoHSB((color shr 16) and 255, (color shr 8) and 255, color and 255, null)
|
||||
return HSB(hsbVals[0], hsbVals[1], hsbVals[2])
|
||||
}
|
||||
}
|
||||
val asColor: Int get() = Color.HSBtoRGB(hue, saturation, brightness)
|
||||
}
|
||||
Reference in New Issue
Block a user