mirror of
https://github.com/DaanVandenBosch/phantasmal-world.git
synced 2025-04-07 08:48:28 +08:00
Textures can now be turned off in the viewer.
This commit is contained in:
parent
92017ca8ec
commit
6003797f15
24
web/src/main/kotlin/world/phantasmal/web/core/Throttle.kt
Normal file
24
web/src/main/kotlin/world/phantasmal/web/core/Throttle.kt
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
package world.phantasmal.web.core
|
||||||
|
|
||||||
|
import kotlinx.browser.window
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper for limiting the amount of times a function is called within a given time frame.
|
||||||
|
*
|
||||||
|
* @param before the amount of time in ms before the function is actually called. E.g., if [before]
|
||||||
|
* is 10 and [invoke] is called once, the given function won't be called until 10ms have passed. If
|
||||||
|
* invoke is called again before the 10ms have passed, it will still only be called once after 10ms
|
||||||
|
* have passed since the first call to invoke.
|
||||||
|
*/
|
||||||
|
class Throttle(private val before: Int) {
|
||||||
|
private var timeout: Int = -1
|
||||||
|
|
||||||
|
operator fun invoke(f: () -> Unit) {
|
||||||
|
if (timeout == -1) {
|
||||||
|
timeout = window.setTimeout({
|
||||||
|
f()
|
||||||
|
timeout = -1
|
||||||
|
}, before)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -164,10 +164,10 @@ private fun xjObjectToMesh(
|
|||||||
val builder = MeshBuilder(textures, textureCache)
|
val builder = MeshBuilder(textures, textureCache)
|
||||||
ninjaObjectToMeshBuilder(xjObj, builder)
|
ninjaObjectToMeshBuilder(xjObj, builder)
|
||||||
|
|
||||||
builder.defaultMaterial(MeshBasicMaterial(obj {
|
builder.defaultMaterial(MeshLambertMaterial(obj {
|
||||||
color = Color().setHSL((index % 7) / 7.0, 1.0, .5)
|
color = Color().setHSL((index % 7) / 7.0, 1.0, .5)
|
||||||
transparent = true
|
transparent = true
|
||||||
opacity = .25
|
opacity = .5
|
||||||
side = DoubleSide
|
side = DoubleSide
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
@ -579,6 +579,7 @@ external interface MaterialParameters {
|
|||||||
var blending: Blending
|
var blending: Blending
|
||||||
var side: Side
|
var side: Side
|
||||||
var transparent: Boolean
|
var transparent: Boolean
|
||||||
|
var opacity: Double
|
||||||
}
|
}
|
||||||
|
|
||||||
open external class Material : EventDispatcher {
|
open external class Material : EventDispatcher {
|
||||||
@ -592,7 +593,6 @@ open external class Material : EventDispatcher {
|
|||||||
|
|
||||||
external interface MeshBasicMaterialParameters : MaterialParameters {
|
external interface MeshBasicMaterialParameters : MaterialParameters {
|
||||||
var color: Color
|
var color: Color
|
||||||
var opacity: Double
|
|
||||||
var map: Texture?
|
var map: Texture?
|
||||||
var wireframe: Boolean
|
var wireframe: Boolean
|
||||||
var wireframeLinewidth: Double
|
var wireframeLinewidth: Double
|
||||||
|
@ -28,6 +28,8 @@ class ViewerToolbarController(private val store: ViewerStore) : Controller() {
|
|||||||
private val _resultDialogVisible = mutableVal(false)
|
private val _resultDialogVisible = mutableVal(false)
|
||||||
private val _result = mutableVal<PwResult<*>?>(null)
|
private val _result = mutableVal<PwResult<*>?>(null)
|
||||||
|
|
||||||
|
val applyTexturesEnabled: Val<Boolean> = store.applyTexturesEnabled
|
||||||
|
val applyTextures: Val<Boolean> = store.applyTextures
|
||||||
val showSkeletonEnabled: Val<Boolean> = store.showSkeletonEnabled
|
val showSkeletonEnabled: Val<Boolean> = store.showSkeletonEnabled
|
||||||
val showSkeleton: Val<Boolean> = store.showSkeleton
|
val showSkeleton: Val<Boolean> = store.showSkeleton
|
||||||
val playAnimation: Val<Boolean> = store.animationPlaying
|
val playAnimation: Val<Boolean> = store.animationPlaying
|
||||||
@ -44,6 +46,10 @@ class ViewerToolbarController(private val store: ViewerStore) : Controller() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun setApplyTextures(apply: Boolean) {
|
||||||
|
store.setApplyTextures(apply)
|
||||||
|
}
|
||||||
|
|
||||||
fun setShowSkeleton(show: Boolean) {
|
fun setShowSkeleton(show: Boolean) {
|
||||||
store.setShowSkeleton(show)
|
store.setShowSkeleton(show)
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ import world.phantasmal.core.math.degToRad
|
|||||||
import world.phantasmal.lib.fileFormats.ninja.NinjaObject
|
import world.phantasmal.lib.fileFormats.ninja.NinjaObject
|
||||||
import world.phantasmal.lib.fileFormats.ninja.NjMotion
|
import world.phantasmal.lib.fileFormats.ninja.NjMotion
|
||||||
import world.phantasmal.lib.fileFormats.ninja.NjObject
|
import world.phantasmal.lib.fileFormats.ninja.NjObject
|
||||||
|
import world.phantasmal.web.core.Throttle
|
||||||
import world.phantasmal.web.core.boundingSphere
|
import world.phantasmal.web.core.boundingSphere
|
||||||
import world.phantasmal.web.core.isSkinnedMesh
|
import world.phantasmal.web.core.isSkinnedMesh
|
||||||
import world.phantasmal.web.core.rendering.*
|
import world.phantasmal.web.core.rendering.*
|
||||||
@ -23,6 +24,7 @@ class MeshRenderer(
|
|||||||
createThreeRenderer: (HTMLCanvasElement) -> DisposableThreeRenderer,
|
createThreeRenderer: (HTMLCanvasElement) -> DisposableThreeRenderer,
|
||||||
) : Renderer() {
|
) : Renderer() {
|
||||||
private val clock = Clock()
|
private val clock = Clock()
|
||||||
|
private val throttleRebuildMesh = Throttle(before = 10)
|
||||||
|
|
||||||
private var obj3d: Object3D? = null
|
private var obj3d: Object3D? = null
|
||||||
private var skeletonHelper: SkeletonHelper? = null
|
private var skeletonHelper: SkeletonHelper? = null
|
||||||
@ -45,13 +47,14 @@ class MeshRenderer(
|
|||||||
override val inputManager = addDisposable(OrbitalCameraInputManager(
|
override val inputManager = addDisposable(OrbitalCameraInputManager(
|
||||||
context.canvas,
|
context.canvas,
|
||||||
context.camera,
|
context.camera,
|
||||||
Vector3(),
|
position = Vector3(.0, .0, .0),
|
||||||
screenSpacePanning = true,
|
screenSpacePanning = true,
|
||||||
))
|
))
|
||||||
|
|
||||||
init {
|
init {
|
||||||
observe(viewerStore.currentNinjaGeometry) { ninjaGeometryOrXvmChanged() }
|
observe(viewerStore.currentNinjaGeometry) { rebuildMesh(resetCamera = true) }
|
||||||
observe(viewerStore.currentTextures) { ninjaGeometryOrXvmChanged() }
|
observe(viewerStore.currentTextures) { rebuildMesh(resetCamera = true) }
|
||||||
|
observe(viewerStore.applyTextures) { rebuildMesh(resetCamera = false) }
|
||||||
observe(viewerStore.currentNinjaMotion, ::ninjaMotionChanged)
|
observe(viewerStore.currentNinjaMotion, ::ninjaMotionChanged)
|
||||||
observe(viewerStore.showSkeleton) { skeletonHelper?.visible = it }
|
observe(viewerStore.showSkeleton) { skeletonHelper?.visible = it }
|
||||||
observe(viewerStore.animationPlaying, ::animationPlayingChanged)
|
observe(viewerStore.animationPlaying, ::animationPlayingChanged)
|
||||||
@ -80,82 +83,87 @@ class MeshRenderer(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun ninjaGeometryOrXvmChanged() {
|
private fun rebuildMesh(resetCamera: Boolean) {
|
||||||
// Remove the previous mesh.
|
throttleRebuildMesh {
|
||||||
obj3d?.let { mesh ->
|
// Remove the previous mesh.
|
||||||
disposeObject3DResources(mesh)
|
obj3d?.let { mesh ->
|
||||||
context.scene.remove(mesh)
|
disposeObject3DResources(mesh)
|
||||||
}
|
context.scene.remove(mesh)
|
||||||
|
}
|
||||||
|
|
||||||
// Remove the previous skeleton.
|
// Remove the previous skeleton.
|
||||||
skeletonHelper?.let {
|
skeletonHelper?.let {
|
||||||
context.scene.remove(it)
|
context.scene.remove(it)
|
||||||
skeletonHelper = null
|
skeletonHelper = null
|
||||||
}
|
}
|
||||||
|
|
||||||
val ninjaGeometry = viewerStore.currentNinjaGeometry.value
|
val ninjaGeometry = viewerStore.currentNinjaGeometry.value
|
||||||
val textures = viewerStore.currentTextures.value
|
val textures =
|
||||||
|
if (viewerStore.applyTextures.value) viewerStore.currentTextures.value
|
||||||
|
else emptyList()
|
||||||
|
|
||||||
// Stop and clean up previous animation and store animation time.
|
// Stop and clean up previous animation and store animation time.
|
||||||
var animationTime: Double? = null
|
var animationTime: Double? = null
|
||||||
|
|
||||||
animation?.let {
|
animation?.let {
|
||||||
animationTime = it.action.time
|
animationTime = it.action.time
|
||||||
it.dispose()
|
it.dispose()
|
||||||
this.animation = null
|
this.animation = null
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a new mesh if necessary.
|
// Create a new mesh if necessary.
|
||||||
if (ninjaGeometry != null) {
|
if (ninjaGeometry != null) {
|
||||||
val obj3d = when (ninjaGeometry) {
|
val obj3d = when (ninjaGeometry) {
|
||||||
is NinjaGeometry.Object -> {
|
is NinjaGeometry.Object -> {
|
||||||
val obj = ninjaGeometry.obj
|
val obj = ninjaGeometry.obj
|
||||||
|
|
||||||
if (obj is NjObject) {
|
if (obj is NjObject) {
|
||||||
ninjaObjectToSkinnedMesh(obj, textures, boundingVolumes = true)
|
ninjaObjectToSkinnedMesh(obj, textures, boundingVolumes = true)
|
||||||
} else {
|
} else {
|
||||||
ninjaObjectToMesh(obj, textures, boundingVolumes = true)
|
ninjaObjectToMesh(obj, textures, boundingVolumes = true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
is NinjaGeometry.Render -> renderGeometryToGroup(ninjaGeometry.geometry,
|
||||||
|
textures)
|
||||||
|
|
||||||
|
is NinjaGeometry.Collision -> collisionGeometryToGroup(ninjaGeometry.geometry)
|
||||||
}
|
}
|
||||||
|
|
||||||
is NinjaGeometry.Render -> renderGeometryToGroup(ninjaGeometry.geometry, textures)
|
// Determine whether camera needs to be reset. Resets should always happen when the
|
||||||
|
// Ninja geometry changes except when we're switching between character class models.
|
||||||
|
val charClassActive = viewerStore.currentCharacterClass.value != null
|
||||||
|
val cameraResetNecessary = !charClassActive || !this.charClassActive
|
||||||
|
this.charClassActive = charClassActive
|
||||||
|
|
||||||
is NinjaGeometry.Collision -> collisionGeometryToGroup(ninjaGeometry.geometry)
|
if (resetCamera && cameraResetNecessary) {
|
||||||
}
|
// Compute camera position.
|
||||||
|
val bSphere = boundingSphere(obj3d)
|
||||||
|
val cameraDistFactor =
|
||||||
|
1.5 / tan(degToRad((context.camera as PerspectiveCamera).fov) / 2)
|
||||||
|
val cameraPos = CAMERA_POS * (bSphere.radius * cameraDistFactor)
|
||||||
|
inputManager.lookAt(cameraPos, bSphere.center)
|
||||||
|
}
|
||||||
|
|
||||||
// Determine whether camera needs to be reset. Resets should always happen when the
|
context.scene.add(obj3d)
|
||||||
// Ninja geometry changes except when we're switching between character class models.
|
this.obj3d = obj3d
|
||||||
val charClassActive = viewerStore.currentCharacterClass.value != null
|
|
||||||
val resetCamera = !charClassActive || !this.charClassActive
|
|
||||||
this.charClassActive = charClassActive
|
|
||||||
|
|
||||||
if (resetCamera) {
|
if (obj3d.isSkinnedMesh() && ninjaGeometry is NinjaGeometry.Object) {
|
||||||
// Compute camera position.
|
// Add skeleton.
|
||||||
val bSphere = boundingSphere(obj3d)
|
val skeletonHelper = SkeletonHelper(obj3d)
|
||||||
val cameraDistFactor =
|
skeletonHelper.visible = viewerStore.showSkeleton.value
|
||||||
1.5 / tan(degToRad((context.camera as PerspectiveCamera).fov) / 2)
|
skeletonHelper.material.unsafeCast<LineBasicMaterial>().linewidth = 3
|
||||||
val cameraPos = CAMERA_POS * (bSphere.radius * cameraDistFactor)
|
|
||||||
inputManager.lookAt(cameraPos, bSphere.center)
|
|
||||||
}
|
|
||||||
|
|
||||||
context.scene.add(obj3d)
|
context.scene.add(skeletonHelper)
|
||||||
this.obj3d = obj3d
|
this.skeletonHelper = skeletonHelper
|
||||||
|
|
||||||
if (obj3d.isSkinnedMesh() && ninjaGeometry is NinjaGeometry.Object) {
|
// Create a new animation mixer and clip.
|
||||||
// Add skeleton.
|
viewerStore.currentNinjaMotion.value?.let { njMotion ->
|
||||||
val skeletonHelper = SkeletonHelper(obj3d)
|
animation = Animation(ninjaGeometry.obj, njMotion, obj3d).also {
|
||||||
skeletonHelper.visible = viewerStore.showSkeleton.value
|
it.mixer.timeScale = viewerStore.frameRate.value / PSO_FRAME_RATE_DOUBLE
|
||||||
skeletonHelper.material.unsafeCast<LineBasicMaterial>().linewidth = 3
|
it.action.time = animationTime ?: .0
|
||||||
|
it.action.play()
|
||||||
context.scene.add(skeletonHelper)
|
}
|
||||||
this.skeletonHelper = skeletonHelper
|
|
||||||
|
|
||||||
// Create a new animation mixer and clip.
|
|
||||||
viewerStore.currentNinjaMotion.value?.let { njMotion ->
|
|
||||||
animation = Animation(ninjaGeometry.obj, njMotion, obj3d).also {
|
|
||||||
it.mixer.timeScale = viewerStore.frameRate.value / PSO_FRAME_RATE_DOUBLE
|
|
||||||
it.action.time = animationTime ?: .0
|
|
||||||
it.action.play()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -51,6 +51,7 @@ class ViewerStore(
|
|||||||
private val _currentAnimation = mutableVal<AnimationModel?>(null)
|
private val _currentAnimation = mutableVal<AnimationModel?>(null)
|
||||||
|
|
||||||
// Settings.
|
// Settings.
|
||||||
|
private val _applyTextures = mutableVal(true)
|
||||||
private val _showSkeleton = mutableVal(false)
|
private val _showSkeleton = mutableVal(false)
|
||||||
private val _animationPlaying = mutableVal(true)
|
private val _animationPlaying = mutableVal(true)
|
||||||
private val _frameRate = mutableVal(PSO_FRAME_RATE)
|
private val _frameRate = mutableVal(PSO_FRAME_RATE)
|
||||||
@ -74,6 +75,10 @@ class ViewerStore(
|
|||||||
val currentAnimation: Val<AnimationModel?> = _currentAnimation
|
val currentAnimation: Val<AnimationModel?> = _currentAnimation
|
||||||
|
|
||||||
// Settings.
|
// Settings.
|
||||||
|
val applyTexturesEnabled: Val<Boolean> = _currentNinjaGeometry.map {
|
||||||
|
it == null || it !is NinjaGeometry.Collision
|
||||||
|
}
|
||||||
|
val applyTextures: Val<Boolean> = applyTexturesEnabled and _applyTextures
|
||||||
val showSkeletonEnabled: Val<Boolean> = _currentNinjaGeometry.map {
|
val showSkeletonEnabled: Val<Boolean> = _currentNinjaGeometry.map {
|
||||||
it is NinjaGeometry.Object && it.obj is NjObject
|
it is NinjaGeometry.Object && it.obj is NjObject
|
||||||
}
|
}
|
||||||
@ -208,6 +213,10 @@ class ViewerStore(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun setApplyTextures(apply: Boolean) {
|
||||||
|
_applyTextures.value = apply
|
||||||
|
}
|
||||||
|
|
||||||
fun setShowSkeleton(show: Boolean) {
|
fun setShowSkeleton(show: Boolean) {
|
||||||
_showSkeleton.value = show
|
_showSkeleton.value = show
|
||||||
}
|
}
|
||||||
|
@ -27,6 +27,12 @@ class ViewerToolbar(private val ctrl: ViewerToolbarController) : Widget() {
|
|||||||
checked = ctrl.showSkeleton,
|
checked = ctrl.showSkeleton,
|
||||||
onChange = ctrl::setShowSkeleton,
|
onChange = ctrl::setShowSkeleton,
|
||||||
),
|
),
|
||||||
|
Checkbox(
|
||||||
|
label = "Apply textures",
|
||||||
|
enabled = ctrl.applyTexturesEnabled,
|
||||||
|
checked = ctrl.applyTextures,
|
||||||
|
onChange = ctrl::setApplyTextures,
|
||||||
|
),
|
||||||
Checkbox(
|
Checkbox(
|
||||||
label = "Play animation",
|
label = "Play animation",
|
||||||
enabled = ctrl.animationControlsEnabled,
|
enabled = ctrl.animationControlsEnabled,
|
||||||
|
Loading…
Reference in New Issue
Block a user