-
Notifications
You must be signed in to change notification settings - Fork 39
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
6d80680
commit 01d520d
Showing
20 changed files
with
2,517 additions
and
963 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,195 @@ | ||
package com.heledron.spideranimation | ||
|
||
import com.heledron.spideranimation.components.Leg | ||
import org.bukkit.util.Vector | ||
|
||
interface SpiderBodyPlan { | ||
val scale: Double | ||
val legs: List<LegPlan> | ||
fun initialize(spider: Spider) | ||
fun canMoveLeg(leg: Leg): Boolean | ||
fun legsInPolygonalOrder(): List<Leg> | ||
fun legsInUpdateOrder(): List<Leg> | ||
} | ||
|
||
class LegPlan( | ||
val attachmentPosition: Vector, | ||
val restPosition: Vector, | ||
val segments: List<SegmentPlan>, | ||
) | ||
|
||
class SegmentPlan( | ||
val length: Double, | ||
val thickness: Double, | ||
) | ||
|
||
class SymmetricalBodyPlan(override val scale: Double, override val legs: List<LegPlan>): SpiderBodyPlan { | ||
lateinit var spider: Spider | ||
|
||
private var legsInPolygonalOrder = listOf<Leg>() | ||
private var legsInUpdateOrder = listOf<Leg>() | ||
|
||
override fun initialize(spider: Spider) { | ||
this.spider = spider | ||
|
||
val diagonals1 = arrayListOf<Leg>() | ||
val diagonals2 = arrayListOf<Leg>() | ||
|
||
val lefts = arrayListOf<Leg>() | ||
val rights = arrayListOf<Leg>() | ||
for (i in spider.body.legs.indices step 2) { | ||
val left = spider.body.legs[i] | ||
val right = spider.body.legs[i + 1] | ||
lefts.add(left) | ||
rights.add(right) | ||
|
||
if (i % 4 == 0) { | ||
diagonals1.add(left) | ||
diagonals2.add(right) | ||
} else { | ||
diagonals2.add(left) | ||
diagonals1.add(right) | ||
} | ||
} | ||
|
||
legsInPolygonalOrder = lefts + rights.reversed() | ||
legsInUpdateOrder = diagonals1 + diagonals2 | ||
} | ||
|
||
override fun legsInPolygonalOrder(): List<Leg> { | ||
return legsInPolygonalOrder | ||
} | ||
|
||
override fun legsInUpdateOrder(): List<Leg> { | ||
return legsInUpdateOrder | ||
} | ||
|
||
override fun canMoveLeg(leg: Leg): Boolean { | ||
if (leg.target.isGrounded) return true | ||
|
||
val cooldownLegs: List<Leg> | ||
if (spider.isGalloping) { | ||
// always move if uncomfortable | ||
if (leg.uncomfortable) return true | ||
|
||
cooldownLegs = listOf(horizontal(leg)) | ||
} else { | ||
// only move when the adjacent legs are grounded | ||
for (adjacent in adjacent(leg)) { | ||
if (adjacent.isMoving) return false | ||
} | ||
|
||
cooldownLegs = diagonal(leg) | ||
} | ||
|
||
// cooldown | ||
for (opposite in cooldownLegs) { | ||
if (opposite.isMoving && !opposite.target.isGrounded && opposite.moveTime < spider.gait.legMoveCooldown) { | ||
return false | ||
} | ||
} | ||
|
||
return true | ||
} | ||
|
||
// x . | ||
// . x | ||
// x . | ||
private fun horizontal(leg: Leg): Leg { | ||
val index = spider.body.legs.indexOf(leg) | ||
val out = spider.body.legs.getOrNull(index + if (index % 2 == 0) 1 else -1) | ||
return out ?: leg | ||
} | ||
|
||
private fun diagonal(leg: Leg): List<Leg> { | ||
val index = spider.body.legs.indexOf(leg) | ||
val (front, back) = if (index % 2 == 0) -1 to 3 else -3 to 1 | ||
return listOfNotNull(spider.body.legs.getOrNull(index + front), spider.body.legs.getOrNull(index + back)) | ||
} | ||
|
||
private fun adjacent(leg: Leg): List<Leg> { | ||
val pair = horizontal(leg) | ||
return diagonal(pair) + pair | ||
} | ||
} | ||
|
||
class SymmetricalBodyPlanBuilder(var scale: Double = 1.0, var legs: MutableList<LegPlan> = arrayListOf()) { | ||
fun addPair(rootX: Double, rootZ: Double, restX: Double, restZ: Double, segmentLength: Double, segmentCount: Int) { | ||
val segmentPlan = SegmentPlan(segmentLength, .1) | ||
legs.add(LegPlan(Vector(rootX, 0.0, rootZ), Vector(restX, 0.0, restZ), List(segmentCount) { segmentPlan })) | ||
legs.add(LegPlan(Vector(-rootX, 0.0, rootZ), Vector(-restX, 0.0, restZ), List(segmentCount) { segmentPlan })) | ||
} | ||
|
||
fun autoAssignThickness() { | ||
val maxThickness = 1.5/16 * 4 | ||
val minThickness = 1.5/16 * 1 | ||
|
||
legs = legs.map { legPlan -> | ||
val segmentCount = legs.first().segments.size | ||
val segments = legPlan.segments.mapIndexed { i, segmentPlan -> | ||
val thickness = (segmentCount - i - 1) * (maxThickness - minThickness) / segmentCount + minThickness | ||
SegmentPlan(segmentPlan.length, thickness) | ||
} | ||
LegPlan(legPlan.attachmentPosition, legPlan.restPosition, segments) | ||
}.toMutableList() | ||
} | ||
|
||
fun scale(scale: Double) { | ||
this.scale *= scale | ||
legs = legs.map { legPlan -> | ||
val attachmentPosition = legPlan.attachmentPosition.clone().multiply(scale) | ||
val restPosition = legPlan.restPosition.clone().multiply(scale) | ||
val segments = legPlan.segments.map { SegmentPlan(it.length * scale, it.thickness * scale) } | ||
LegPlan(attachmentPosition, restPosition, segments) | ||
}.toMutableList() | ||
} | ||
|
||
// fun addSpace(left: Double, right: Double, forward: Double, backward: Double) { | ||
// legs.forEach { legPlan -> | ||
// val z = .5 * if (legPlan.restPosition.z > 0) forward else -backward | ||
// val x = .5 * if (legPlan.restPosition.x > 0) left else -right | ||
// legPlan.attachmentPosition.add(Vector(x, 0.0, z)) | ||
// legPlan.restPosition.add(Vector(x, 0.0, z)) | ||
// } | ||
// } | ||
// | ||
// fun liftAttachment(front: Double, back: Double) { | ||
// legs.forEach { legPlan -> | ||
// val y = .5 * if (legPlan.restPosition.z > 0) front else back | ||
// legPlan.attachmentPosition.y += y | ||
// } | ||
// } | ||
|
||
fun create(): SpiderBodyPlan { | ||
return SymmetricalBodyPlan(scale, legs) | ||
} | ||
} | ||
|
||
fun quadripedBodyPlan(segmentLength: Double, segmentCount: Int): SymmetricalBodyPlanBuilder { | ||
return SymmetricalBodyPlanBuilder().apply { | ||
addPair(.0, .0, 0.9, 0.9, 0.9 * segmentLength, segmentCount) | ||
addPair(.0, .0, 1.0, -1.1, 1.2 * segmentLength, segmentCount) | ||
autoAssignThickness() | ||
// addSpace(.5, .5, 1.0, 1.0) | ||
// liftAttachment(-.1, .3) | ||
} | ||
} | ||
|
||
fun hexapodBodyPlan(segmentLength: Double, segmentCount: Int): SymmetricalBodyPlanBuilder { | ||
return SymmetricalBodyPlanBuilder().apply { | ||
addPair(.0, 0.1, 1.0, 1.1, 1.1 * segmentLength, segmentCount) | ||
addPair(.0, 0.0, 1.3, -0.3, 1.1 * segmentLength, segmentCount) | ||
addPair(.0, -.1, 1.2, -2.0, 1.6 * segmentLength, segmentCount) | ||
autoAssignThickness() | ||
} | ||
} | ||
|
||
fun octopodBodyPlan(segmentLength: Double, segmentCount: Int): SymmetricalBodyPlanBuilder { | ||
return SymmetricalBodyPlanBuilder().apply { | ||
addPair(.0, 0.1, 1.0, 1.6, 1.1 * segmentLength, segmentCount) | ||
addPair(.0, 0.0, 1.3, 0.4, 1.0 * segmentLength, segmentCount) | ||
addPair(.0, -.1, 1.3, -0.9, 1.1 * segmentLength, segmentCount) | ||
addPair(.0, -.2, 1.1, -2.5, 1.6 * segmentLength, segmentCount) | ||
autoAssignThickness() | ||
} | ||
} |
151 changes: 151 additions & 0 deletions
151
src/main/java/com/heledron/spideranimation/EntityRenderer.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,151 @@ | ||
package com.heledron.spideranimation | ||
|
||
import org.bukkit.Location | ||
import org.bukkit.entity.BlockDisplay | ||
import org.bukkit.entity.Entity | ||
import org.bukkit.util.Vector | ||
import org.joml.Matrix4f | ||
import java.io.Closeable | ||
|
||
class EntityRendererTemplate <T : Entity> ( | ||
val clazz : Class<T>, | ||
val location : Location, | ||
val init : (T) -> Unit = {}, | ||
val update : (T) -> Unit = {} | ||
) | ||
|
||
fun blockTemplate( | ||
location: Location, | ||
init: (BlockDisplay) -> Unit = {}, | ||
update: (BlockDisplay) -> Unit = {} | ||
) = EntityRendererTemplate( | ||
clazz = BlockDisplay::class.java, | ||
location = location, | ||
init = init, | ||
update = update | ||
) | ||
|
||
fun targetTemplate( | ||
location: Location | ||
) = blockTemplate( | ||
location = location, | ||
init = { | ||
it.block = org.bukkit.Material.REDSTONE_BLOCK.createBlockData() | ||
it.teleportDuration = 1 | ||
it.brightness = org.bukkit.entity.Display.Brightness(15, 15) | ||
it.transformation = centredTransform(.25f, .25f, .25f) | ||
} | ||
) | ||
|
||
fun lineTemplate( | ||
location: Location, | ||
vector: Vector, | ||
upVector: Vector = if (vector.x + vector.z != 0.0) UP_VECTOR else Vector(0, 0, 1), | ||
thickness: Float = .1f, | ||
interpolation: Int = 1, | ||
init: (BlockDisplay) -> Unit = {}, | ||
update: (BlockDisplay) -> Unit = {} | ||
) = blockTemplate( | ||
location = location, | ||
init = { | ||
it.teleportDuration = interpolation | ||
it.interpolationDuration = interpolation | ||
init(it) | ||
}, | ||
update = { | ||
val matrix = Matrix4f().rotateTowards(vector.toVector3f(), upVector.toVector3f()) | ||
.translate(-thickness / 2, -thickness / 2, 0f) | ||
.scale(thickness, thickness, vector.length().toFloat()) | ||
|
||
applyTransformationWithInterpolation(it, matrix) | ||
update(it) | ||
} | ||
) | ||
|
||
class EntityRenderer<T : Entity>: Closeable { | ||
var entity: T? = null | ||
|
||
fun render(template: EntityRendererTemplate<T>) { | ||
entity = (entity ?: spawnEntity(template.location, template.clazz) { | ||
template.init(it) | ||
}).apply { | ||
this.teleport(template.location) | ||
template.update(this) | ||
} | ||
} | ||
|
||
fun renderIf(predicate: Boolean, template: EntityRendererTemplate<T>) { | ||
if (predicate) render(template) else close() | ||
} | ||
|
||
override fun close() { | ||
entity?.remove() | ||
entity = null | ||
} | ||
} | ||
|
||
class MultiEntityRenderer: Closeable { | ||
val rendered = mutableMapOf<Any, Entity>() | ||
|
||
val used = mutableSetOf<Any>() | ||
|
||
override fun close() { | ||
for (entity in rendered.values) { | ||
entity.remove() | ||
} | ||
rendered.clear() | ||
used.clear() | ||
} | ||
|
||
fun beginRender() { | ||
if (used.isNotEmpty()) { | ||
throw IllegalStateException("beginRender called without finishRender") | ||
} | ||
} | ||
|
||
fun keepAlive(id: Any) { | ||
used.add(id) | ||
} | ||
|
||
fun finishRender() { | ||
val toRemove = rendered.keys - used | ||
for (key in toRemove) { | ||
val entity = rendered[key]!! | ||
entity.remove() | ||
rendered.remove(key) | ||
} | ||
used.clear() | ||
} | ||
|
||
fun <T: Entity>render(id: Any, template: EntityRendererTemplate<T>) { | ||
used.add(id) | ||
|
||
val oldEntity = rendered[id] | ||
if (oldEntity != null) { | ||
// check if the entity is of the same type | ||
if (oldEntity.type.entityClass == template.clazz) { | ||
oldEntity.teleport(template.location) | ||
@Suppress("UNCHECKED_CAST") | ||
template.update(oldEntity as T) | ||
return | ||
} | ||
|
||
oldEntity.remove() | ||
rendered.remove(id) | ||
} | ||
|
||
val entity = spawnEntity(template.location, template.clazz) { | ||
template.init(it) | ||
template.update(it) | ||
} | ||
rendered[id] = entity | ||
} | ||
|
||
fun renderList(id: Any, list: List<EntityRendererTemplate<*>>) { | ||
for ((i, template) in list.withIndex()) { | ||
render(id to i, template) | ||
} | ||
} | ||
} | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.