Skip to content

Instantly share code, notes, and snippets.

@victorbrndls
Last active June 12, 2023 18:49
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 victorbrndls/e75f3ac2a106a423dca47985efcfb3b9 to your computer and use it in GitHub Desktop.
Save victorbrndls/e75f3ac2a106a423dca47985efcfb3b9 to your computer and use it in GitHub Desktop.
package com.victor.tutorial
import android.animation.ValueAnimator
import android.opengl.Matrix
import android.view.Choreographer
import android.view.SurfaceView
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import com.google.android.filament.MaterialInstance
import com.google.android.filament.View
import com.google.android.filament.android.UiHelper
import com.google.android.filament.utils.ModelViewer
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import java.nio.ByteBuffer
import java.nio.channels.Channels
class CubeModelRenderer {
private lateinit var surfaceView: SurfaceView
private lateinit var choreographer: Choreographer
private lateinit var uiHelper: UiHelper
private lateinit var modelViewer: ModelViewer
private lateinit var materialInstanceBlue: MaterialInstance
private lateinit var materialInstanceRed: MaterialInstance
private val frameScheduler = FrameCallback()
fun onSurfaceAvailable(surfaceView: SurfaceView) {
this.surfaceView = surfaceView
choreographer = Choreographer.getInstance()
uiHelper = UiHelper(UiHelper.ContextErrorPolicy.DONT_CHECK).apply {
isOpaque = false
}
modelViewer = ModelViewer(surfaceView, uiHelper = uiHelper)
createDefaultRenderables()
surfaceView.setOnTouchListener { _, event ->
modelViewer.onTouchEvent(event)
true
}
modelViewer.scene.skybox = null
modelViewer.view.blendMode = View.BlendMode.TRANSLUCENT
modelViewer.renderer.clearOptions = modelViewer.renderer.clearOptions.apply {
clear = true
}
val view = modelViewer.view
view.renderQuality = view.renderQuality.apply {
hdrColorBuffer = View.QualityLevel.MEDIUM
}
// FXAA is pretty cheap and helps a lot
view.antiAliasing = View.AntiAliasing.FXAA
// ambient occlusion is the cheapest effect that adds a lot of quality
view.ambientOcclusionOptions = view.ambientOcclusionOptions.apply {
enabled = false
}
// bloom is pretty expensive but adds a fair amount of realism
view.bloomOptions = view.bloomOptions.apply {
enabled = false
}
GlobalScope.launch(Dispatchers.Main) {
delay(3000)
ValueAnimator.ofFloat(0f, 90f).apply {
duration = 3_000
repeatCount = 1000
val transformMatrix = FloatArray(16)
val entities = listOf(
modelViewer.asset!!.getFirstEntityByName("Cube24"),
modelViewer.asset!!.getFirstEntityByName("Cube25"),
modelViewer.asset!!.getFirstEntityByName("Cube26"),
)
val tcm = modelViewer.engine.transformManager
val initialTransforms = entities.map { idx ->
tcm.getTransform(tcm.getInstance(idx), null as FloatArray?)
}
val materials = modelViewer.asset!!.instance.materialInstances
modelViewer.engine.renderableManager.setMaterialInstanceAt(
entities[0],
0,
materials[0]
)
modelViewer.engine.renderableManager.setMaterialInstanceAt(
entities[2],
0,
materials[2]
)
addUpdateListener { value ->
val value = value.animatedValue as Float
entities.forEachIndexed { idx, entity ->
System.arraycopy(initialTransforms[idx], 0, transformMatrix, 0, 16)
Matrix.rotateM(transformMatrix, 0, value, 0f, 0f, -1f)
tcm.setTransform(tcm.getInstance(entity), transformMatrix)
}
}
}.start()
}
}
fun onLifecycle(lifecycle: Lifecycle) {
lifecycle.addObserver(object : DefaultLifecycleObserver {
override fun onResume(owner: LifecycleOwner) {
choreographer.postFrameCallback(frameScheduler)
}
override fun onPause(owner: LifecycleOwner) {
choreographer.removeFrameCallback(frameScheduler)
}
override fun onDestroy(owner: LifecycleOwner) {
choreographer.removeFrameCallback(frameScheduler)
}
})
}
private fun createDefaultRenderables() {
val buffer = surfaceView.context.assets.open("models/cube3.glb").use { input ->
val bytes = ByteArray(input.available())
input.read(bytes)
ByteBuffer.wrap(bytes)
}
modelViewer.loadModelGlb(buffer)
modelViewer.transformToUnitCube()
}
inner class FrameCallback : Choreographer.FrameCallback {
override fun doFrame(frameTimeNanos: Long) {
choreographer.postFrameCallback(this)
modelViewer.render(frameTimeNanos)
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment