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.
514 lines
15 KiB
514 lines
15 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.state.CanMove
|
|
import com.jme3.renderer.Camera
|
|
import scala.jdk.CollectionConverters._
|
|
import wow.doge.mygame.utils.JFXConsoleStreamable
|
|
import com.jme3.app.Application
|
|
import java.util.concurrent.Callable
|
|
import scala.concurrent.Future
|
|
import scala.concurrent.ExecutionContext
|
|
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
|
|
|
|
case class ActionEvent(binding: Action, value: Boolean, tpf: Float)
|
|
case class PhysicsTickEvent(space: PhysicsSpace, tpf: Float)
|
|
|
|
package object implicits {
|
|
type PrePhysicsTickEvent = PhysicsTickEvent
|
|
type PhysicsTickObservable =
|
|
Observable[Either[PrePhysicsTickEvent, PhysicsTickEvent]]
|
|
|
|
implicit class JMEAppExt(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(val sm: AppStateManager) extends AnyVal {
|
|
def state[S <: AppState]()(implicit c: ClassTag[S]): S =
|
|
sm.getState(c.runtimeClass.asInstanceOf[Class[S]])
|
|
|
|
}
|
|
|
|
implicit class SimpleApplicationExt(val sa: SimpleApplication)
|
|
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()
|
|
}
|
|
|
|
implicit class NodeExt(val n: Node) extends AnyVal {
|
|
|
|
/**
|
|
* Attaches the given child
|
|
*
|
|
* @param s
|
|
* @return
|
|
*/
|
|
def child(s: Spatial): Node = {
|
|
n.attachChild(s)
|
|
n
|
|
}
|
|
|
|
/**
|
|
* Gets the list of children as a scala collection
|
|
*
|
|
* @return
|
|
*/
|
|
// def children = n.getChildren().asScala.toSeq
|
|
def children = Observable.fromIterable(n.getChildren().asScala)
|
|
|
|
/**
|
|
* Attach given children
|
|
*
|
|
* @param lst
|
|
*/
|
|
def children(lst: Iterable[Spatial]): Unit = {
|
|
for (c <- lst) n.child(c)
|
|
}
|
|
|
|
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.getChildren().asScala.to(LazyList)
|
|
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] = {
|
|
// spatial can be either a node or a geometry, but it's not a sealed trait
|
|
spatials match {
|
|
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.getChildren().asScala.to(LazyList)
|
|
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
|
|
}
|
|
}
|
|
}
|
|
|
|
implicit class EntityDataExt(val ed: EntityData) extends AnyVal {
|
|
|
|
def query = new EntityQuery(ed)
|
|
|
|
// def entities[T <: EntityComponent](entities: Seq[T])
|
|
}
|
|
|
|
implicit class EntityExt(val e: EntityId) extends AnyVal {
|
|
def withComponents(classes: EntityComponent*)(implicit
|
|
ed: EntityData
|
|
): EntityId = {
|
|
ed.setComponents(e, classes: _*)
|
|
e
|
|
}
|
|
}
|
|
|
|
implicit class ActorRefExt[Req](val a: ActorRef[Req]) extends AnyVal {
|
|
import akka.actor.typed.scaladsl.AskPattern._
|
|
def askT[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): Future[Res] = {
|
|
// ask(replyTo)(timeout, scheduler)
|
|
// }
|
|
|
|
implicit class InputManagerExt(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.Unbounded) { 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
|
|
}
|
|
}
|
|
}
|
|
|
|
implicit class PhysicsSpaceExt(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
|
|
}
|
|
}
|
|
}
|
|
|
|
implicit class AssetManagerExt(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 Vector3fExt(val v: Vector3f) extends AnyVal {
|
|
def +=(that: Vector3f) = v.addLocal(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(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.x * 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 implVector3fForVector3 = new Vector3[Vector3f] {
|
|
// override def +(implicit v: Vector3f, that: com.jme3.math.Vector3f): Unit =
|
|
// v += that
|
|
|
|
// override def *(implicit v: Vector3f, that: Vector3f): Unit = v *= that
|
|
|
|
// override def -(implicit v: Vector3f, that: Vector3f): Unit = v -= that
|
|
|
|
// override def /(implicit v: Vector3f, that: Vector3f): Unit = v /= that
|
|
|
|
// }
|
|
|
|
// implicit val implImVector3fForVector3 = new Vector3[ImVector3f] {
|
|
// override def +(implicit v: ImVector3f, that: ImVector3f): Unit =
|
|
// v + that
|
|
|
|
// override def *(implicit v: ImVector3f, that: ImVector3f): Unit = v * that
|
|
|
|
// override def -(implicit v: ImVector3f, that: ImVector3f): Unit = v - that
|
|
|
|
// override def /(implicit v: ImVector3f, that: ImVector3f): Unit = v / that
|
|
|
|
// }
|
|
|
|
// def test[T](v: T)(implicit ev: Vector3[T]) = {
|
|
// import ev._
|
|
// ev.+
|
|
// }
|
|
|
|
implicit val implCanMoveForGeom = new CanMove[Spatial] {
|
|
|
|
override def move(inst: Spatial, direction: ImVector3f): Unit = {
|
|
// val v = inst.getLocalTranslation()
|
|
// inst match {
|
|
// case n: Node => println(n.getChildren())
|
|
// case _ =>
|
|
// }
|
|
inst.move(direction.mutable)
|
|
}
|
|
|
|
override def getDirection(
|
|
cam: Camera,
|
|
cardinalDir: CardinalDirection
|
|
): ImVector3f = {
|
|
// val camDir =
|
|
// cam.getDirection().immutable * 0.6f
|
|
// val camLeft = cam.getLeft().immutable * 0.4f
|
|
|
|
// val zero = ImVector3f.ZERO
|
|
// val dir = cardinalDir
|
|
// val walkDir = {
|
|
// val mutWalkDir = new Vector3f()
|
|
// if (dir.left) {
|
|
// // ctx.log.trace("left")
|
|
// mutWalkDir += (zero + camLeft).mutable
|
|
// }
|
|
// if (dir.right) {
|
|
// // ctx.log.trace("right")
|
|
// mutWalkDir += (zero + -camLeft).mutable
|
|
// }
|
|
// if (dir.up) {
|
|
// // ctx.log.trace("up")
|
|
// mutWalkDir += (zero + camDir).mutable
|
|
// }
|
|
// if (dir.down) {
|
|
// // ctx.log.trace("down")
|
|
// mutWalkDir += (zero + -camDir).mutable
|
|
// }
|
|
// mutWalkDir.immutable
|
|
// }
|
|
// walkDir
|
|
|
|
// val camDir =
|
|
// cam.getDirection().immutable * 0.6f
|
|
// val camLeft = cam.getLeft().immutable * 0.4f
|
|
|
|
val zero = ImVector3f.ZERO
|
|
val dir = cardinalDir
|
|
val walkDir = {
|
|
val mutWalkDir = new Vector3f()
|
|
if (dir.left) {
|
|
// ctx.log.trace("left")
|
|
mutWalkDir += (zero + ImVector3f(-1, 0, 0)).mutable
|
|
}
|
|
if (dir.right) {
|
|
// ctx.log.trace("right")
|
|
mutWalkDir += (zero + ImVector3f(1, 0, 0)).mutable
|
|
}
|
|
if (dir.up) {
|
|
// ctx.log.trace("up")
|
|
mutWalkDir += (zero + ImVector3f(0, 0, -1)).mutable
|
|
}
|
|
if (dir.down) {
|
|
// ctx.log.trace("down")
|
|
mutWalkDir += (zero + ImVector3f(0, 0, 1)).mutable
|
|
}
|
|
mutWalkDir.immutable
|
|
}
|
|
walkDir
|
|
}
|
|
}
|
|
|
|
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
|