Last active
June 12, 2023 18:49
-
-
Save victorbrndls/e75f3ac2a106a423dca47985efcfb3b9 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 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