Created
September 16, 2022 11:54
-
-
Save ThomasGorisse/4f9981047a8a3bf8002eaf58f4026475 to your computer and use it in GitHub Desktop.
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
package io.github.sceneview.node | |
import androidx.annotation.IntRange | |
import com.google.android.filament.Engine | |
import com.google.android.filament.MaterialInstance | |
import com.google.android.filament.View | |
import com.google.android.filament.gltfio.Animator | |
import dev.romainguy.kotlin.math.max | |
import io.github.sceneview.Entity | |
import io.github.sceneview.SceneView | |
import io.github.sceneview.components.* | |
import io.github.sceneview.managers.NodeManager | |
import io.github.sceneview.math.* | |
import io.github.sceneview.model.Model | |
import io.github.sceneview.model.ModelInstance | |
import io.github.sceneview.model.ModelLoader | |
import io.github.sceneview.model.getAnimationIndex | |
import io.github.sceneview.utils.FrameTime | |
/** | |
* Create the ModelNode from a loaded model | |
* | |
* @param engine [SceneView.engine] or your own shared [Engine.create] | |
* @param model See [SceneView.loadModel], [ModelLoader.loadModel] | |
* | |
* @see SceneView.loadModel | |
* @see ModelLoader.loadModel | |
*/ | |
open class ModelNode private constructor( | |
engine: Engine, | |
nodeManager: NodeManager, | |
val model: Model, | |
val animator: Animator, | |
val entities: List<Entity>, | |
renderableEntities: List<Entity>, | |
lightEntities: List<Entity>, | |
cameraEntities: List<Entity>, | |
) : Node(engine, nodeManager, model.root), HasTransformComponent { | |
data class PlayingAnimation(val startTime: Long = System.nanoTime(), val loop: Boolean = true) | |
val renderableNodes: List<RenderableNode> | |
val lightNodes: List<LightNode> | |
val cameraNodes: List<CameraNode> | |
var playingAnimations = mutableMapOf<Int, PlayingAnimation>() | |
init { | |
renderableNodes = renderableEntities.map { RenderableNode(model, it) } | |
lightNodes = lightEntities.map { LightNode(model, it) } | |
cameraNodes = cameraEntities.map { CameraNode(model, it) } | |
//TODO: Used by Filament ModelViewer, see if it's useful | |
setScreenSpaceContactShadows(true) | |
model.releaseSourceData() | |
} | |
/** | |
* Create the ModelNode from a loaded model | |
* | |
* @param engine [SceneView.engine] or your own shared [Engine.create] | |
* @param model See [SceneView.loadModel], [ModelLoader.loadModel] | |
* | |
* @see SceneView.loadModel | |
* @see ModelLoader.loadModel | |
*/ | |
constructor( | |
engine: Engine, | |
nodeManager: NodeManager, | |
model: Model | |
) : this( | |
engine, | |
nodeManager, | |
model, | |
model.animator, | |
model.entities.toList(), | |
model.renderableEntities.toList(), | |
model.lightEntities.toList(), | |
model.cameraEntities.toList() | |
) | |
/** | |
* Create the ModelNode from a loaded model | |
* | |
* @param engine [SceneView.engine] or your own shared [Engine.create] | |
* @param modelInstance See [SceneView.loadInstancedModel] and [SceneView.createInstance] | |
* | |
* @see SceneView.loadInstancedModel | |
* @see SceneView.createInstance | |
* @see ModelLoader.loadInstancedModel | |
* @see ModelLoader.createInstance | |
*/ | |
constructor( | |
engine: Engine, | |
nodeManager: NodeManager, | |
modelInstance: ModelInstance, | |
) : this( | |
engine, | |
nodeManager, | |
modelInstance.asset, | |
modelInstance.animator, | |
modelInstance.entities.toList(), | |
modelInstance.entities.filter { engine.renderableManager.hasComponent(it) }, | |
modelInstance.entities.filter { engine.lightManager.hasComponent(it) }, | |
modelInstance.entities.filter { engine.getCameraComponent(it) != null } | |
) | |
override fun onFrame(frameTime: FrameTime) { | |
super.onFrame(frameTime) | |
animator.apply { | |
playingAnimations.forEach { (index, animation) -> | |
val elapsedTimeSeconds = frameTime.intervalSeconds(animation.startTime) | |
applyAnimation(index, elapsedTimeSeconds.toFloat()) | |
if (!animation.loop && elapsedTimeSeconds >= getAnimationDuration(index)) { | |
playingAnimations.remove(index) | |
} | |
} | |
updateBoneMatrices() | |
} | |
} | |
/** | |
* ### Applies rotation, translation, and scale to entities that have been targeted by the given | |
* animation definition. Uses `TransformManager`. | |
* | |
* @param animationIndex Zero-based index for the `animation` of interest. | |
* | |
* @see Animator.getAnimationCount | |
*/ | |
fun playAnimation(animationIndex: Int = 0, loop: Boolean = true) { | |
if (animationIndex <= animator.animationCount) { | |
playingAnimations[animationIndex] = PlayingAnimation(loop = loop) | |
} | |
} | |
/** | |
* @see playAnimation | |
* @see Animator.getAnimationName | |
*/ | |
fun playAnimation(animationName: String, loop: Boolean = true) { | |
animator.getAnimationIndex(animationName)?.let { playAnimation(it, loop) } | |
} | |
fun stopAnimation(animationIndex: Int = 0) { | |
playingAnimations.remove(animationIndex) | |
} | |
fun stopAnimation(animationName: String) { | |
animator.getAnimationIndex(animationName)?.let { stopAnimation(it) } | |
} | |
/** | |
* Updates the vertex morphing weights on a renderable, all zeroes by default. | |
* | |
* The renderable must be built with morphing enabled. In legacy morphing mode, only the | |
* first 4 weights are considered. | |
* | |
* @see Builder.morphing | |
*/ | |
fun setMorphWeights(weights: FloatArray, @IntRange(from = 0) offset: Int) = | |
renderableNodes.forEach { it.setMorphWeights(weights, offset) } | |
/** | |
* Changes the visibility bits. | |
* | |
* @see Builder.layerMask | |
* @see View.setVisibleLayers | |
*/ | |
fun setLayerMask( | |
@IntRange(from = 0, to = 255) select: Int, | |
@IntRange(from = 0, to = 255) value: Int | |
) = renderableNodes.forEach { it.setLayerMask(select, value) } | |
/** | |
* Changes the coarse-level draw ordering. | |
* | |
* @see Builder.priority | |
*/ | |
fun setPriority(@IntRange(from = 0, to = 7) priority: Int) = | |
renderableNodes.forEach { it.setPriority(priority) } | |
/** | |
* Changes whether or not frustum culling is on. | |
* | |
* @see Builder.culling | |
*/ | |
fun setCulling(enabled: Boolean) = renderableNodes.forEach { it.setCulling(enabled) } | |
/** | |
* Changes whether or not the renderable casts shadows. | |
* | |
* @see Builder.castShadows | |
*/ | |
var isShadowCaster: Boolean | |
get() = renderableNodes.all { it.isShadowCaster } | |
set(value) = renderableNodes.forEach { it.isShadowCaster = value } | |
/** | |
* Changes whether or not the renderable can receive shadows. | |
* | |
* @see Builder.receiveShadows | |
*/ | |
var isShadowReceiver: Boolean | |
get() = renderableNodes.all { it.isShadowReceiver } | |
set(value) = renderableNodes.forEach { it.isShadowReceiver = value } | |
/** | |
* Changes whether or not the renderable can use screen-space contact shadows. | |
* | |
* @see Builder.screenSpaceContactShadows | |
*/ | |
fun setScreenSpaceContactShadows(enabled: Boolean) = | |
renderableNodes.forEach { it.setScreenSpaceContactShadows(enabled) } | |
/** | |
* Changes the material instances binding for the given primitives. | |
* | |
* @see Builder.material | |
*/ | |
var materialInstances: List<MaterialInstance> | |
get() = renderableNodes.flatMap { it.materialInstances } | |
set(value) { | |
var start = 0 | |
renderableNodes.forEach { | |
it.materialInstances = value.subList(start, start + it.primitiveCount) | |
start += it.primitiveCount | |
} | |
} | |
/** | |
* Changes the material instance binding for all primitives. | |
* | |
* @see Builder.material | |
*/ | |
fun setMaterialInstance(materialInstance: MaterialInstance) { | |
renderableNodes.forEach { it.setMaterialInstance(materialInstance) } | |
} | |
/** | |
* Changes the drawing order for blended primitives. The drawing order is either global or | |
* local (default) to this Renderable. In either case, the Renderable priority takes precedence. | |
* | |
* @see Builder.blendOrder | |
* | |
* @param blendOrder draw order number (0 by default). Only the lowest 15 bits are used. | |
*/ | |
fun setBlendOrder(@IntRange(from = 0, to = 65535) blendOrder: Int) { | |
renderableNodes.forEach { it.setBlendOrder(blendOrder) } | |
} | |
/** | |
* Changes whether the blend order is global or local to this Renderable (by default). | |
* | |
* @see Builder.globalBlendOrderEnabled | |
* | |
* @param enabled true for global, false for local blend ordering. | |
*/ | |
fun setGlobalBlendOrderEnabled(enabled: Boolean) { | |
renderableNodes.forEach { it.setGlobalBlendOrderEnabled(enabled) } | |
} | |
override fun getBoundingBox() = model.boundingBox | |
open inner class ChildNode internal constructor(val model: Model, entity: Entity) : | |
Node(engine, nodeManager, entity) { | |
val name = model.getName(entity) | |
val extras = model.getExtras(entity) | |
} | |
inner class RenderableNode internal constructor(model: Model, entity: Entity) : | |
ChildNode(model, entity), HasRenderableComponent | |
inner class LightNode internal constructor(model: Model, entity: Entity) : | |
ChildNode(model, entity), HasLightComponent | |
inner class CameraNode internal constructor(model: Model, entity: Entity) : | |
ChildNode(model, entity), HasCameraComponent | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment