Skip to content

Instantly share code, notes, and snippets.

@drindt
Created December 12, 2024 10:02
Show Gist options
  • Save drindt/e4cb12602e5ad404b796e90928915576 to your computer and use it in GitHub Desktop.
Save drindt/e4cb12602e5ad404b796e90928915576 to your computer and use it in GitHub Desktop.
/*
* Copyright (c) 2013-2024 The Coloryzer® Project
*
* All rights reserved. Unauthorized copying of this file, via any medium
* is strictly prohibited Proprietary and confidential.
*
* Viselabs Inc.
* Mahlsdorfer Straße 61b
* 15366 Hoppegarten, Germany
*/
package com.coloryzer.core.ui.component
import android.content.Context
import android.graphics.PixelFormat
import android.view.SurfaceHolder
import android.view.SurfaceView
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.viewinterop.AndroidView
import com.bmwgroup.ramses.ClearColor
import com.bmwgroup.ramses.RamsesThread
/**
* Displays a Ramses scene within a Jetpack Compose application.
*
* This composable function integrates a Ramses thread into the Compose UI, allowing you to render
* and interact with scenes defined by Ramses and RLogic files. It utilizes a `SurfaceView`
* to display the rendered content.
*
* @param onSceneLoadFail Callback function executed if the scene fails to load.
* @param modifier Modifier to apply to the underlying `SurfaceView`. Defaults to `Modifier`.
* @param ramsesFile The path to the Ramses file defining the scene structure. Defaults to "default.ramses".
* @param rlogicFile The path to the RLogic file defining the scene's logic. Defaults to "default.rlogic".
* @param onLogicUpdate Callback function invoked when the scene's logic is updated. Defaults to an empty lambda.
* @param onSceneLoad Callback function invoked when the scene is successfully loaded, providing access to the `RamsesThread`. Defaults to an empty lambda.
* @param onSceneUpdate Callback function invoked whenever the scene is updated (e.g., during animations). Defaults to an empty lambda.
* @param onViewPortResize Callback function invoked when the viewport size of the `SurfaceView` changes, providing the new width and height. Defaults to an empty lambda.
*
* **Scene Loading and Initialization:**
* - The `ramsesFile` and `rlogicFile` parameters specify the Ramses and RLogic files, respectively,
* used to define the scene's structure and logic. These files should be located in the `assets` folder of your Android project.
* - Upon successful scene loading, the `onSceneLoad` callback is invoked, providing access to
* the initialized `RamsesThread`. You can use this thread to interact with the scene, for example, to control animations or modify scene elements.
* - If an error occurs during scene loading (e.g., if the files are not found or are invalid), the `onSceneLoadFail` callback is triggered.
*
* **Scene Updates and Interactions:**
* - The `onSceneUpdate` callback is invoked whenever the scene is updated, such as during animations or when user interactions modify the scene.
* - The `onLogicUpdate` callback is triggered when the logic of the scene is updated, typically driven by the RLogic file.
*/
@Composable
fun RamsesScene(
onSceneLoadFail: () -> Unit,
modifier: Modifier = Modifier,
ramsesFile: String = "default.ramses",
rlogicFile: String = "default.rlogic",
onLogicUpdate: () -> Unit = {},
onSceneLoad: (thread: RamsesThread) -> Unit = {},
onSceneUpdate: () -> Unit = {},
onViewPortResize: (width: Int, height: Int) -> Unit = { _, _ -> },
) {
AndroidView(
factory = { context ->
val thread = SceneRenderThread(
context = context,
onLogicUpdate = onLogicUpdate,
onSceneLoad = onSceneLoad,
onSceneLoadFail = onSceneLoadFail,
onSceneUpdate = onSceneUpdate,
onViewPortResize = onViewPortResize,
threadName = SceneRenderThread::class.java.simpleName,
)
thread.initRamsesThreadAndLoadScene(context.assets, ramsesFile, rlogicFile)
SurfaceView(context).apply {
setZOrderOnTop(true)
holder.addCallback(
RamsesSceneSurfaceHolder(
thread,
Color.Transparent.toClearColor(),
),
)
holder.setFormat(PixelFormat.RGBA_8888)
}
},
modifier = modifier,
)
}
/**
* Converts this [Color] instance to a [ClearColor] instance.
*
* This extension function creates a new [ClearColor] object using the red, green, blue,
* and alpha components of the current [Color] object.
*
* @return A [ClearColor] instance with the same color values as this [Color].
*/
fun Color.toClearColor() = ClearColor(red, green, blue, alpha)
private class RamsesSceneSurfaceHolder(
private val thread: SceneRenderThread,
private val clearColor: ClearColor = Color.Transparent.toClearColor(),
) : SurfaceHolder.Callback {
override fun surfaceCreated(holder: SurfaceHolder) {
runCatching {
thread.apply {
createDisplayAndShowScene(holder.surface, clearColor)
addRunnableToThreadQueue {
if (isDisplayCreated && !isRendering) {
startRendering()
}
}
}
}
}
override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
runCatching {
thread.resizeDisplay(width, height)
}
}
override fun surfaceDestroyed(holder: SurfaceHolder) {
runCatching {
thread.destroyDisplay()
}
}
}
private class SceneRenderThread(
context: Context,
private val onLogicUpdate: () -> Unit,
private val onSceneLoad: (thread: SceneRenderThread) -> Unit,
private val onSceneLoadFail: () -> Unit,
private val onSceneUpdate: () -> Unit,
private val onViewPortResize: (width: Int, height: Int) -> Unit,
threadName: String,
) : RamsesThread(threadName, context) {
override fun onSceneLoaded() {
onSceneLoad(this)
}
override fun onUpdate() {
onSceneUpdate()
}
override fun onSceneLoadFailed() {
runCatching {
onSceneLoadFail()
}
}
override fun onLogicUpdated() {
onLogicUpdate()
}
override fun onDisplayResize(width: Int, height: Int) {
onViewPortResize(width, height)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment