forked from nova/jmonkey-test
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
604 lines
18 KiB
604 lines
18 KiB
package wow.doge.mygame
|
|
|
|
import com.jme3.app.SimpleApplication
|
|
import com.jme3.app.state.AppStateManager
|
|
import scala.reflect.ClassTag
|
|
import com.jme3.app.state.AppState
|
|
import com.jme3.scene.Node
|
|
import com.jme3.scene.Spatial
|
|
import com.simsilica.es.EntityData
|
|
import com.simsilica.es.EntityComponent
|
|
import com.simsilica.es.EntityId
|
|
import akka.actor.typed.ActorRef
|
|
import akka.util.Timeout
|
|
import akka.actor.typed.Scheduler
|
|
import monix.bio.Task
|
|
import com.jme3.input.InputManager
|
|
import com.jme3.input.controls.Trigger
|
|
import com.jme3.input.controls.InputListener
|
|
import com.jme3.math.Vector3f
|
|
import wow.doge.mygame.math.ImVector3f
|
|
import com.jme3.scene.Geometry
|
|
import wow.doge.mygame.state.CardinalDirection
|
|
import wow.doge.mygame.subsystems.movement.CanMove
|
|
import com.jme3.renderer.Camera
|
|
import scala.jdk.CollectionConverters._
|
|
import wow.doge.mygame.utils.JFXConsoleStreamable
|
|
import com.jme3.app.Application
|
|
import com.jme3.scene.SceneGraphVisitor
|
|
import monix.reactive.Observable
|
|
import com.jme3.asset.AssetManager
|
|
import com.jme3.asset.AssetLocator
|
|
import com.jme3.input.controls.ActionListener
|
|
import monix.reactive.OverflowStrategy
|
|
import monix.execution.Ack
|
|
import monix.execution.Cancelable
|
|
import monix.execution.cancelables.SingleAssignCancelable
|
|
import com.jme3.input.Action
|
|
import com.jme3.bullet.PhysicsSpace
|
|
import com.jme3.bullet.collision.PhysicsCollisionListener
|
|
import com.jme3.bullet.collision.PhysicsCollisionEvent
|
|
import com.jme3.bullet.PhysicsTickListener
|
|
import monix.reactive.observers.Subscriber
|
|
import monix.execution.Ack.Continue
|
|
import monix.execution.Ack.Stop
|
|
import com.jme3.bullet.BulletAppState
|
|
import wow.doge.mygame.state.MyBaseState
|
|
import monix.bio.UIO
|
|
import com.jme3.bullet.control.BetterCharacterControl
|
|
import com.jme3.scene.control.AbstractControl
|
|
import com.jme3.scene.CameraNode
|
|
import com.jme3.scene.control.CameraControl.ControlDirection
|
|
import com.jme3.bullet.control.AbstractPhysicsControl
|
|
import com.jme3.scene.control.Control
|
|
import com.typesafe.scalalogging.Logger
|
|
import com.typesafe.scalalogging.LazyLogging
|
|
import com.jme3.input.controls.AnalogListener
|
|
import com.jme3.math.Quaternion
|
|
import com.jme3.math.FastMath
|
|
import wow.doge.mygame.subsystems.movement.RotateDir
|
|
|
|
case class ActionEvent(binding: Action, value: Boolean, tpf: Float)
|
|
case class AnalogEvent(binding: Action, value: Float, tpf: Float)
|
|
case class PhysicsTickEvent(space: PhysicsSpace, tpf: Float)
|
|
|
|
package object implicits {
|
|
type PrePhysicsTickEvent = PhysicsTickEvent
|
|
type PhysicsTickObservable =
|
|
Observable[Either[PrePhysicsTickEvent, PhysicsTickEvent]]
|
|
|
|
implicit class JMEAppExt(private val app: Application) extends AnyVal {
|
|
|
|
// /**
|
|
// * Blocking task. Execute on a thread pool meant for blocking operations.
|
|
// * Prefer [[wow.doge.mygame.implicits.JMEAppExt#enqueueT]] instead.
|
|
// *
|
|
// * @param cb
|
|
// * @param ec
|
|
// * @return
|
|
// */
|
|
// def enqueueF[T](cb: () => T)(implicit ec: ExecutionContext): Future[T] =
|
|
// Future {
|
|
// app
|
|
// .enqueue(new Callable[T]() {
|
|
// override def call(): T = cb()
|
|
// })
|
|
// .get()
|
|
// }
|
|
|
|
// /**
|
|
// * Blocking task. Execute on a thread pool meant for blocking operations.
|
|
// * Same as enqueue, but returns a Monix Task instead of Future
|
|
// * @param cb
|
|
// * @param ec
|
|
// * @return
|
|
// */
|
|
// def enqueueL[T](cb: () => T): Task[T] =
|
|
// Task
|
|
// .deferFutureAction(implicit s => enqueueF(cb))
|
|
|
|
def enqueueF[T](cb: => T) =
|
|
app.enqueue(new Runnable {
|
|
override def run() = cb
|
|
})
|
|
|
|
def enqueueT(cb: => Unit) =
|
|
Task(enqueueF(cb))
|
|
}
|
|
implicit class StateManagerExt(private val sm: AppStateManager)
|
|
extends AnyVal {
|
|
def state[S <: AppState]()(implicit c: ClassTag[S]): S =
|
|
sm.getState(c.runtimeClass.asInstanceOf[Class[S]])
|
|
|
|
}
|
|
|
|
implicit class SimpleApplicationExt[T <: SimpleApplication](private val sa: T)
|
|
extends AnyVal {
|
|
def stateManager: AppStateManager = sa.getStateManager()
|
|
def inputManager: InputManager = sa.getInputManager()
|
|
def assetManager: AssetManager = sa.getAssetManager()
|
|
def guiNode = sa.getGuiNode()
|
|
def flyCam = Option(sa.getFlyByCamera())
|
|
def camera = sa.getCamera()
|
|
def viewPort = sa.getViewPort()
|
|
def rootNode = sa.getRootNode()
|
|
|
|
def observableTick: Observable[Float] =
|
|
Observable.create(OverflowStrategy.Unbounded) { sub =>
|
|
val c = SingleAssignCancelable()
|
|
val as = new MyBaseState {
|
|
|
|
override def init(): Unit = {}
|
|
|
|
override def update(tpf: Float) = {
|
|
if (sub.onNext(tpf) == Ack.Stop)
|
|
c.cancel()
|
|
}
|
|
|
|
override def stop(): Unit = {}
|
|
|
|
override def onEnable() = {}
|
|
override def onDisable() = {}
|
|
}
|
|
sa.stateManager.attach(as)
|
|
c := Cancelable(() => sa.stateManager.detach(as))
|
|
c
|
|
}
|
|
}
|
|
|
|
implicit class NodeExt[T <: Node](private val n: T) extends AnyVal {
|
|
|
|
/**
|
|
* Attaches the given child
|
|
*
|
|
* @param s
|
|
* @return
|
|
*/
|
|
def withChild(s: Spatial): Node = {
|
|
n.attachChild(s)
|
|
n
|
|
}
|
|
|
|
/**
|
|
* Gets the list of children as a monix observable
|
|
*
|
|
* @return
|
|
*/
|
|
// def children = n.getChildren().asScala.toSeq
|
|
def observableChildren =
|
|
Observable.fromIterable(n.getChildren().asScala)
|
|
|
|
def children = LazyList.from(n.getChildren().asScala)
|
|
|
|
/**
|
|
* Attach given children
|
|
*
|
|
* @param lst
|
|
*/
|
|
def withChildren(lst: Spatial*): Node = {
|
|
for (c <- lst) n.withChild(c)
|
|
n
|
|
}
|
|
|
|
def +=(spatial: Spatial) = n.attachChild(spatial)
|
|
|
|
def depthFirst(cb: Spatial => Unit) =
|
|
n.depthFirstTraversal(new SceneGraphVisitor() {
|
|
override def visit(s: Spatial) = cb(s)
|
|
})
|
|
|
|
def observableDepthFirst(): Observable[Spatial] = {
|
|
def loop(
|
|
subscriber: Subscriber[Spatial],
|
|
spatial: Spatial
|
|
): Task[Unit] = {
|
|
//spatial can be either a node or a geometry, but it's not a sealed trait
|
|
spatial match {
|
|
|
|
case node: Node =>
|
|
Task.deferFuture(subscriber.onNext(node)).flatMap {
|
|
case Ack.Continue => {
|
|
//modifying a node's children list is forbidden
|
|
val children = node.children
|
|
if (!children.isEmpty) {
|
|
Task.sequence(
|
|
children.map(c => loop(subscriber, c))
|
|
) >> Task.unit
|
|
} else {
|
|
Task.unit
|
|
}
|
|
|
|
}
|
|
case Ack.Stop => Task.unit
|
|
|
|
}
|
|
//geomtries do not/cannot have children
|
|
case g: Geometry =>
|
|
Task.deferFuture(subscriber.onNext(g)) >> Task.unit
|
|
case _ => Task.unit
|
|
}
|
|
}
|
|
|
|
Observable.create(OverflowStrategy.Unbounded) { sub =>
|
|
implicit val sched = sub.scheduler
|
|
loop(sub, n).runToFuture
|
|
}
|
|
}
|
|
|
|
def breadthFirst(cb: Spatial => Unit) =
|
|
n.breadthFirstTraversal(new SceneGraphVisitor() {
|
|
override def visit(s: Spatial) = cb(s)
|
|
})
|
|
|
|
def observableBreadthFirst(): Observable[Spatial] = {
|
|
def loop(
|
|
subscriber: Subscriber[Spatial],
|
|
spatials: LazyList[Spatial]
|
|
): Task[Unit] =
|
|
spatials match {
|
|
// spatial can be either a node or a geometry, but it's not a sealed trait
|
|
case head #:: tail =>
|
|
head match {
|
|
case g: Geometry =>
|
|
Task.deferFuture(subscriber.onNext(g)).flatMap {
|
|
case Continue =>
|
|
loop(subscriber, tail)
|
|
case Stop => Task.unit
|
|
}
|
|
case node: Node =>
|
|
val children = node.children
|
|
Task.deferFuture(subscriber.onNext(node)).flatMap {
|
|
case Continue =>
|
|
loop(subscriber, tail #::: children)
|
|
case Stop => Task.unit
|
|
}
|
|
// case _ => loop(subscriber, tail)
|
|
}
|
|
case LazyList() => Task.unit
|
|
}
|
|
|
|
Observable.create(OverflowStrategy.Unbounded) { sub =>
|
|
implicit val sched = sub.scheduler
|
|
loop(sub, LazyList(n)).runToFuture
|
|
}
|
|
}
|
|
|
|
def withControl[C <: Control](ctrl: C) = {
|
|
n.addControl(ctrl)
|
|
n
|
|
}
|
|
|
|
def withLocalTranslation(dir: ImVector3f) = {
|
|
n.setLocalTranslation(dir.mutable)
|
|
n
|
|
}
|
|
|
|
def withRotate(xAngle: Float, yAngle: Float, zAngle: Float) = {
|
|
n.rotate(xAngle, yAngle, zAngle)
|
|
n
|
|
}
|
|
|
|
def localRotation = n.getLocalRotation()
|
|
|
|
def localTranslation = n.getLocalTranslation()
|
|
|
|
}
|
|
|
|
implicit class CameraNodeExt(private val cn: CameraNode) {
|
|
def withControlDir(controlDir: ControlDirection) = {
|
|
cn.setControlDir(controlDir)
|
|
cn
|
|
}
|
|
|
|
def withLookAt(position: ImVector3f, upVector: ImVector3f) = {
|
|
cn.lookAt(position.mutable, upVector.mutable)
|
|
cn
|
|
}
|
|
}
|
|
|
|
implicit class EntityDataExt(private val ed: EntityData) extends AnyVal {
|
|
|
|
def query = new EntityQuery(ed)
|
|
|
|
// def entities[T <: EntityComponent](entities: Seq[T])
|
|
}
|
|
|
|
implicit class EntityExt(private val e: EntityId) extends AnyVal {
|
|
def withComponents(classes: EntityComponent*)(implicit
|
|
ed: EntityData
|
|
): EntityId = {
|
|
ed.setComponents(e, classes: _*)
|
|
e
|
|
}
|
|
}
|
|
|
|
implicit class ActorRefExt[Req](private val a: ActorRef[Req]) extends AnyVal {
|
|
import akka.actor.typed.scaladsl.AskPattern._
|
|
|
|
/**
|
|
* @param replyTo
|
|
* @param timeout
|
|
* @param scheduler
|
|
* @return
|
|
*/
|
|
def askL[Res](
|
|
replyTo: ActorRef[Res] => Req
|
|
)(implicit timeout: Timeout, scheduler: Scheduler): Task[Res] = {
|
|
Task.deferFuture(a.ask(replyTo)(timeout, scheduler))
|
|
}
|
|
def ??[Res](
|
|
replyTo: ActorRef[Res] => Req
|
|
)(implicit timeout: Timeout, scheduler: Scheduler): Task[Res] =
|
|
askL(replyTo)
|
|
|
|
/**
|
|
* Same as [[tell]], but wrapped in a Task
|
|
*
|
|
* @param msg
|
|
* @return
|
|
*/
|
|
def tellL(msg: Req) = UIO(a.tell(msg))
|
|
def !!(msg: Req) = tellL(msg)
|
|
|
|
}
|
|
// def ?[Res](replyTo: ActorRef[Res] => Req)(implicit timeout: Timeout, scheduler: Scheduler): Future[Res] = {
|
|
// ask(replyTo)(timeout, scheduler)
|
|
// }
|
|
|
|
implicit class InputManagerExt(private val inputManager: InputManager)
|
|
extends AnyVal {
|
|
def withMapping(mapping: String, triggers: Trigger*): InputManager = {
|
|
inputManager.addMapping(mapping, triggers: _*)
|
|
inputManager
|
|
}
|
|
|
|
def withListener(listener: InputListener, mappings: String*) = {
|
|
inputManager.addListener(listener, mappings: _*)
|
|
inputManager
|
|
}
|
|
|
|
def observableAction(mappingNames: String*): Observable[ActionEvent] = {
|
|
|
|
Observable.create(OverflowStrategy.DropOld(10)) { sub =>
|
|
val c = SingleAssignCancelable()
|
|
val al = new ActionListener {
|
|
override def onAction(
|
|
binding: String,
|
|
value: Boolean,
|
|
tpf: Float
|
|
): Unit = {
|
|
if (
|
|
sub.onNext(ActionEvent(Action(binding), value, tpf)) == Ack.Stop
|
|
)
|
|
c.cancel()
|
|
}
|
|
}
|
|
|
|
inputManager.addListener(al, mappingNames: _*)
|
|
|
|
c := Cancelable(() => inputManager.removeListener(al))
|
|
c
|
|
}
|
|
}
|
|
def analogObservable(mappingNames: String*): Observable[AnalogEvent] = {
|
|
|
|
Observable.create(OverflowStrategy.DropOld(100)) { sub =>
|
|
val c = SingleAssignCancelable()
|
|
val al = new AnalogListener {
|
|
override def onAnalog(
|
|
binding: String,
|
|
value: Float,
|
|
tpf: Float
|
|
): Unit = {
|
|
if (
|
|
sub.onNext(AnalogEvent(Action(binding), value, tpf)) == Ack.Stop
|
|
)
|
|
c.cancel()
|
|
}
|
|
}
|
|
|
|
inputManager.addListener(al, mappingNames: _*)
|
|
|
|
c := Cancelable(() => inputManager.removeListener(al))
|
|
c
|
|
}
|
|
}
|
|
}
|
|
|
|
implicit class PhysicsSpaceExt(private val space: PhysicsSpace)
|
|
extends AnyVal {
|
|
|
|
def collisionObservable(): Observable[PhysicsCollisionEvent] = {
|
|
|
|
Observable.create(OverflowStrategy.Unbounded) { sub =>
|
|
val c = SingleAssignCancelable()
|
|
val cl = new PhysicsCollisionListener {
|
|
override def collision(event: PhysicsCollisionEvent): Unit = {
|
|
|
|
if (sub.onNext(event) == Ack.Stop)
|
|
c.cancel()
|
|
|
|
}
|
|
}
|
|
|
|
space.addCollisionListener(cl)
|
|
|
|
c := Cancelable(() => space.removeCollisionListener(cl))
|
|
c
|
|
}
|
|
}
|
|
|
|
def physicsTickObservable(): PhysicsTickObservable = {
|
|
|
|
Observable.create(OverflowStrategy.Unbounded) { sub =>
|
|
val c = SingleAssignCancelable()
|
|
val cl = new PhysicsTickListener {
|
|
|
|
override def prePhysicsTick(space: PhysicsSpace, tpf: Float): Unit = {
|
|
val event = PhysicsTickEvent(space, tpf)
|
|
if (sub.onNext(Left(event)) == Ack.Stop)
|
|
c.cancel()
|
|
}
|
|
|
|
override def physicsTick(space: PhysicsSpace, tpf: Float): Unit = {
|
|
val event = PhysicsTickEvent(space, tpf)
|
|
if (sub.onNext(Right(event)) == Ack.Stop)
|
|
c.cancel()
|
|
}
|
|
|
|
}
|
|
|
|
space.addTickListener(cl)
|
|
|
|
c := Cancelable(() => space.removeTickListener(cl))
|
|
c
|
|
}
|
|
}
|
|
|
|
//TODO Create a typeclass for this
|
|
def +=(anyObject: Any) = space.add(anyObject)
|
|
|
|
def +=(spatial: Spatial) = space.addAll(spatial)
|
|
|
|
}
|
|
|
|
implicit class AssetManagerExt(private val am: AssetManager) extends AnyVal {
|
|
def registerLocator(
|
|
assetPath: os.RelPath,
|
|
locator: Class[_ <: AssetLocator]
|
|
): Unit = {
|
|
am.registerLocator(assetPath.toString(), locator)
|
|
}
|
|
|
|
def loadModel(assetPath: os.RelPath): Spatial = {
|
|
am.loadModel(assetPath.toString())
|
|
}
|
|
}
|
|
|
|
implicit class BulletAppStateExt(private val bas: BulletAppState)
|
|
extends AnyVal {
|
|
def physicsSpace = bas.getPhysicsSpace()
|
|
def speed = bas.getSpeed()
|
|
}
|
|
|
|
implicit class BetterCharacterControlExt(
|
|
private val bcc: BetterCharacterControl
|
|
) {
|
|
def withJumpForce(force: ImVector3f) = {
|
|
bcc.setJumpForce(force.mutable)
|
|
bcc
|
|
}
|
|
}
|
|
|
|
implicit class Vector3fExt(private val v: Vector3f) extends AnyVal {
|
|
//TODO add more operations
|
|
def +=(that: Vector3f) = v.addLocal(that)
|
|
def +=(that: ImVector3f) = v.addLocal(that.x, that.y, that.z)
|
|
def +=:(that: ImVector3f) = v += that
|
|
def *=(that: Vector3f) = v.multLocal(that)
|
|
def -=(that: Vector3f) = v.subtractLocal(that)
|
|
def /=(that: Vector3f) = v.divideLocal(that)
|
|
def unary_- = v.negateLocal()
|
|
def immutable = ImVector3f(v.x, v.y, v.z)
|
|
}
|
|
|
|
implicit class ImVector3fExt(private val v: ImVector3f) extends AnyVal {
|
|
def +(that: ImVector3f) = v.copy(v.x + that.x, v.y + that.y, v.z + that.z)
|
|
def *(that: ImVector3f) = v.copy(v.x * that.x, v.y * that.y, v.z * that.z)
|
|
def *(f: Float): ImVector3f =
|
|
v.copy(v.x * f, v.y * f, v.z * f)
|
|
// v * ImVector3f(f, f, f)
|
|
def -(that: ImVector3f) = v.copy(v.x - that.x, v.y - that.y, v.z - that.z)
|
|
def /(that: ImVector3f) = v.copy(v.x / that.x, v.y / that.y, v.z / that.z)
|
|
def unary_- = v.copy(-v.x, -v.y, -v.z)
|
|
// def unary_-(that: ImVector3f) = this.copy(this.x, this.y, this.z)
|
|
def mutable = new Vector3f(v.x, v.y, v.z)
|
|
}
|
|
|
|
implicit val implCanMoveForBetterCharacterControl =
|
|
new CanMove[BetterCharacterControl] {
|
|
override def move(
|
|
inst: BetterCharacterControl,
|
|
direction: ImVector3f
|
|
): Unit = {
|
|
// val dir = direction.mutable
|
|
// inst.setViewDirection(dir)
|
|
// inst.setViewDirection(direction.mutable)
|
|
inst.setWalkDirection(direction.mutable.multLocal(50f))
|
|
}
|
|
override def jump(inst: BetterCharacterControl): Unit = inst.jump()
|
|
override def rotate(
|
|
inst: BetterCharacterControl,
|
|
rotateDir: RotateDir
|
|
): Unit = {
|
|
val q =
|
|
rotateDir match {
|
|
case RotateDir.Left =>
|
|
new Quaternion()
|
|
.fromAngleAxis(-10f * FastMath.DEG_TO_RAD, Vector3f.UNIT_Y)
|
|
case RotateDir.Right =>
|
|
new Quaternion()
|
|
.fromAngleAxis(10 * FastMath.DEG_TO_RAD, Vector3f.UNIT_Y)
|
|
}
|
|
|
|
val tmp = new Vector3f()
|
|
inst.getViewDirection(tmp)
|
|
inst.setViewDirection(q.mult(tmp))
|
|
}
|
|
override def stop(inst: BetterCharacterControl) =
|
|
inst.setWalkDirection(Vector3f.ZERO)
|
|
}
|
|
|
|
implicit val implCanMoveForGeom = new CanMove[Spatial] with LazyLogging {
|
|
override def move(inst: Spatial, direction: ImVector3f): Unit = {
|
|
inst.move(direction.mutable)
|
|
}
|
|
override def jump(inst: Spatial): Unit =
|
|
logger.warn("`Jump` is not implemented for type `Spatial`")
|
|
override def rotate(inst: Spatial, rotateDir: RotateDir): Unit = {
|
|
rotateDir match {
|
|
case RotateDir.Left => inst.rotate(0, -0.01f, 0)
|
|
case RotateDir.Right => inst.rotate(0, 0.01f, 0)
|
|
}
|
|
}
|
|
override def stop(inst: Spatial) = {}
|
|
}
|
|
|
|
implicit val implJFXConsoleStreamForTextArea =
|
|
new JFXConsoleStreamable[scalafx.scene.control.TextArea] {
|
|
|
|
override def println(
|
|
ta: scalafx.scene.control.TextArea,
|
|
text: String
|
|
): Unit =
|
|
ta.appendText(text + "\n")
|
|
|
|
override def print(
|
|
ta: scalafx.scene.control.TextArea,
|
|
text: String
|
|
): Unit =
|
|
ta.appendText(text)
|
|
|
|
}
|
|
|
|
// val TasktoUIO = new FunctionK[Task, UIO] {
|
|
// def apply[T](f: Task[T]): UIO[T] =
|
|
// f.hideErrors
|
|
// }
|
|
|
|
}
|
|
|
|
// Observable.create(OverflowStrategy.Unbounded) { sub =>
|
|
// // val c = SingleAssignCancelable()
|
|
// val visitor = new SceneGraphVisitor {
|
|
// override def visit(s: Spatial): Unit = {
|
|
// sub.onNext(s)
|
|
// // if (sub.onNext(s) == Ack.Stop)
|
|
// // c.cancel()
|
|
|
|
// }
|
|
// }
|
|
// n.depthFirstTraversal(visitor)
|
|
// // c := Cancelable(() => ???)
|
|
// // c
|
|
// Cancelable.empty
|