Created
December 12, 2024 10:02
-
-
Save drindt/e4cb12602e5ad404b796e90928915576 to your computer and use it in GitHub Desktop.
This file contains hidden or 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
/* | |
* 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