Skip to content

Instantly share code, notes, and snippets.

@ThomasGorisse
Created September 16, 2022 11:54
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ThomasGorisse/4f9981047a8a3bf8002eaf58f4026475 to your computer and use it in GitHub Desktop.
Save ThomasGorisse/4f9981047a8a3bf8002eaf58f4026475 to your computer and use it in GitHub Desktop.
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