Skip to content

Commit

Permalink
fixed loading of multi-file gltf models from non default AssetLoader
Browse files Browse the repository at this point in the history
  • Loading branch information
fabmax committed Mar 23, 2024
1 parent 6e0ba13 commit fb2f51e
Show file tree
Hide file tree
Showing 19 changed files with 196 additions and 55 deletions.
4 changes: 4 additions & 0 deletions kool-core/src/commonMain/kotlin/de/fabmax/kool/Assets.kt
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,8 @@ object Assets : CoroutineScope {
assetPath.startsWith("https://", true) ||
assetPath.startsWith("data:", true)

fun isDataUri(uri: String) = uri.startsWith("data:", true)

/**
* Loads the texture data from the given byte buffer using the image type specified in [mimeType] to decode the
* image (e.g. 'image/png') and returns the image as [TextureData].
Expand Down Expand Up @@ -323,6 +325,8 @@ object Assets : CoroutineScope {

expect fun fileSystemAssetLoader(baseDir: FileSystemDirectory): AssetLoader

expect suspend fun decodeDataUri(dataUri: String): Uint8Buffer

data class FileFilterItem(val name: String, val fileExtensions: String)

internal expect fun PlatformAssets(): PlatformAssets
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package de.fabmax.kool.editor.api

import de.fabmax.kool.AssetLoader
import de.fabmax.kool.Assets
import de.fabmax.kool.modules.gltf.GltfFile
import de.fabmax.kool.modules.gltf.loadGltfFile
Expand All @@ -9,19 +10,26 @@ import de.fabmax.kool.pipeline.ibl.EnvironmentMaps
import de.fabmax.kool.util.logE

interface AppAssetsLoader {
val assetLoader: AssetLoader

suspend fun loadHdriEnvironment(path: String): EnvironmentMaps?
suspend fun loadModel(modelPath: String): GltfFile?
suspend fun loadTexture2d(path: String): Texture2d?
}

object AppAssets : AppAssetsLoader {
var impl: AppAssetsLoader = DefaultLoader("assets")
override val assetLoader: AssetLoader
get() = impl.assetLoader

override suspend fun loadHdriEnvironment(path: String): EnvironmentMaps? = impl.loadHdriEnvironment(path)
override suspend fun loadModel(modelPath: String): GltfFile? = impl.loadModel(modelPath)
override suspend fun loadTexture2d(path: String): Texture2d? = impl.loadTexture2d(path)

class DefaultLoader(val pathPrefix: String) : AppAssetsLoader {
override val assetLoader: AssetLoader
get() = Assets.defaultLoader

override suspend fun loadHdriEnvironment(path: String): EnvironmentMaps? {
val prefixed = "${pathPrefix}/${path}"
return try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,8 @@ class ModelComponent(nodeModel: SceneNodeModel, override val componentData: Mode
scrSpcAmbientOcclusionMap = ssao,
maxNumberOfLights = sceneModel.maxNumLightsState.value
),
applyMaterials = material == null
applyMaterials = material == null,
assetLoader = AppAssets.assetLoader
)
isIblShaded = ibl != null
isSsaoEnabled = ssao != null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ class GizmoNode(name: String = "gizmo") : Node(name), InputStack.PointerListener
}

override fun handlePointer(pointerState: PointerState, ctx: KoolContext) {
if (!isVisible) {
if (!isVisibleRecursive()) {
return
}

Expand Down Expand Up @@ -243,6 +243,17 @@ class GizmoNode(name: String = "gizmo") : Node(name), InputStack.PointerListener
}
}

private fun isVisibleRecursive(): Boolean {
var it: Node? = this
while (it != null) {
if (!it.isVisible) {
return false
}
it = it.parent
}
return true
}

companion object {
const val DEFAULT_GIZMO_DRAW_GROUP = 1000
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,23 +11,24 @@ import kotlinx.coroutines.async

suspend fun AssetLoader.loadGltfFile(assetPath: String): GltfFile = loadGltfFileAsync(assetPath).await()

fun AssetLoader.loadGltfFileAsync(assetPath: String): Deferred<GltfFile> = Assets.async {
val blob = loadBlobAsset(assetPath)
GltfFile(blob, assetPath, this@loadGltfFileAsync)
}

suspend fun AssetLoader.loadGltfModel(
assetPath: String,
modelCfg: GltfLoadConfig = GltfLoadConfig(),
scene: Int = 0
): Model = loadGltfModelAsync(assetPath, modelCfg, scene).await()

fun AssetLoader.loadGltfFileAsync(assetPath: String): Deferred<GltfFile> = Assets.async {
val blob = loadBlobAsset(assetPath)
GltfFile(blob, assetPath)
}

fun AssetLoader.loadGltfModelAsync(
assetPath: String,
modelCfg: GltfLoadConfig = GltfLoadConfig(),
scene: Int = 0
): Deferred<Model> = Assets.async {
loadGltfFileAsync(assetPath).await().makeModel(modelCfg, scene)
val cfg = if (modelCfg.assetLoader == null) modelCfg.copy(assetLoader = this@loadGltfModelAsync) else modelCfg
loadGltfFileAsync(assetPath).await().makeModel(cfg, scene)
}

suspend fun Assets.loadGltfFile(assetPath: String): GltfFile = defaultLoader.loadGltfFileAsync(assetPath).await()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
package de.fabmax.kool.modules.gltf

import de.fabmax.kool.AssetLoader
import de.fabmax.kool.modules.ksl.KslPbrShader
import de.fabmax.kool.pipeline.Attribute
import de.fabmax.kool.pipeline.Texture2d
import de.fabmax.kool.pipeline.ibl.EnvironmentMaps
import de.fabmax.kool.util.ShadowMap

class GltfLoadConfig(
data class GltfLoadConfig(
val generateNormals: Boolean = false,
val applyMaterials: Boolean = true,
val materialConfig: GltfMaterialConfig = GltfMaterialConfig(),
Expand All @@ -19,10 +20,11 @@ class GltfLoadConfig(
val mergeMeshesByMaterial: Boolean = false,
val sortNodesByAlpha: Boolean = true,
val addInstanceAttributes: List<Attribute> = emptyList(),
val assetLoader: AssetLoader? = null,
val pbrBlock: (KslPbrShader.Config.Builder.(GltfMesh.Primitive) -> Unit)? = null
)

class GltfMaterialConfig(
data class GltfMaterialConfig(
val shadowMaps: List<ShadowMap> = emptyList(),
val scrSpcAmbientOcclusionMap: Texture2d? = null,
val environmentMaps: EnvironmentMaps? = null,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package de.fabmax.kool.modules.gltf

import de.fabmax.kool.AssetLoader
import de.fabmax.kool.Assets
import de.fabmax.kool.math.*
import de.fabmax.kool.modules.ksl.KslPbrShader
Expand All @@ -14,7 +15,7 @@ import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import kotlin.math.min

suspend fun GltfFile(data: Uint8Buffer, filePath: String): GltfFile {
suspend fun GltfFile(data: Uint8Buffer, filePath: String, assetLoader: AssetLoader = Assets.defaultLoader): GltfFile {
val gltfData = if (filePath.lowercase().endsWith(".gz")) {
data.inflate()
} else {
Expand All @@ -33,8 +34,8 @@ suspend fun GltfFile(data: Uint8Buffer, filePath: String): GltfFile {
gltfFile.let { m ->
m.buffers.filter { it.uri != null }.forEach {
val uri = it.uri!!
val bufferPath = if (uri.startsWith("data:", true)) { uri } else { "$modelBasePath/$uri" }
it.data = Assets.loadBlobAsset(bufferPath)
val bufferUri = if (uri.startsWith("data:", true)) { uri } else { "$modelBasePath/$uri" }
it.data = assetLoader.loadBlobAsset(bufferUri)
}
m.images.filter { it.uri != null }.forEach { it.uri = "$modelBasePath/${it.uri}" }
m.updateReferences()
Expand Down Expand Up @@ -620,7 +621,7 @@ data class GltfFile(
val pbrConfig = DeferredKslPbrShader.Config.Builder().apply {
val material = prim.materialRef
if (material != null) {
material.applyTo(this, useVertexColor, this@GltfFile)
material.applyTo(this, useVertexColor, this@GltfFile, cfg.assetLoader ?: Assets.defaultLoader)
} else {
color {
uniformColor(Color.GRAY.toLinear())
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package de.fabmax.kool.modules.gltf

import de.fabmax.kool.AssetLoader
import de.fabmax.kool.modules.ksl.KslPbrShader
import de.fabmax.kool.modules.ksl.blocks.ColorBlockConfig
import de.fabmax.kool.modules.ksl.blocks.PropertyBlockConfig
Expand Down Expand Up @@ -38,13 +39,13 @@ data class GltfMaterial(
val doubleSided: Boolean = false
) {

fun applyTo(cfg: KslPbrShader.Config.Builder, useVertexColor: Boolean, gltfFile: GltfFile) {
val baseColorTexture: Texture2d? = pbrMetallicRoughness.baseColorTexture?.getTexture(gltfFile)
val emissiveTexture: Texture2d? = emissiveTexture?.getTexture(gltfFile)
val normalTexture: Texture2d? = this.normalTexture?.getTexture(gltfFile)
val metallicTexture: Texture2d? = pbrMetallicRoughness.metallicRoughnessTexture?.getTexture(gltfFile)
val roughnessTexture: Texture2d? = pbrMetallicRoughness.metallicRoughnessTexture?.getTexture(gltfFile)
val occlusionTexture: Texture2d? = occlusionTexture?.getTexture(gltfFile)
fun applyTo(cfg: KslPbrShader.Config.Builder, useVertexColor: Boolean, gltfFile: GltfFile, assetLoader: AssetLoader) {
val baseColorTexture: Texture2d? = pbrMetallicRoughness.baseColorTexture?.getTexture(gltfFile, assetLoader)
val emissiveTexture: Texture2d? = emissiveTexture?.getTexture(gltfFile, assetLoader)
val normalTexture: Texture2d? = this.normalTexture?.getTexture(gltfFile, assetLoader)
val metallicTexture: Texture2d? = pbrMetallicRoughness.metallicRoughnessTexture?.getTexture(gltfFile, assetLoader)
val roughnessTexture: Texture2d? = pbrMetallicRoughness.metallicRoughnessTexture?.getTexture(gltfFile, assetLoader)
val occlusionTexture: Texture2d? = occlusionTexture?.getTexture(gltfFile, assetLoader)
val colorFac = pbrMetallicRoughness.baseColorFactor

cfg.alphaMode = when (alphaMode) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package de.fabmax.kool.modules.gltf

import de.fabmax.kool.AssetLoader
import de.fabmax.kool.Assets
import de.fabmax.kool.pipeline.FilterMethod
import de.fabmax.kool.pipeline.SamplerSettings
Expand Down Expand Up @@ -31,7 +32,7 @@ data class GltfTexture(
@Transient
private var createdTex: Texture2d? = null

fun makeTexture(): Texture2d {
fun makeTexture(assetLoader: AssetLoader): Texture2d {
if (createdTex == null) {
val uri = imageRef.uri
val name = if (uri != null && !uri.startsWith("data:", true)) {
Expand All @@ -54,7 +55,7 @@ data class GltfTexture(
name
) {
if (uri != null) {
Assets.loadTextureData(uri)
assetLoader.loadTextureData(uri)
} else {
Assets.loadTextureDataFromBuffer(imageRef.bufferViewRef!!.getData(), imageRef.mimeType!!)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package de.fabmax.kool.modules.gltf

import de.fabmax.kool.AssetLoader
import de.fabmax.kool.pipeline.Texture2d
import kotlinx.serialization.Serializable

Expand All @@ -18,7 +19,7 @@ data class GltfTextureInfo(
val texCoord: Int = 0,
val scale: Float = 1f
) {
fun getTexture(gltfFile: GltfFile): Texture2d {
return gltfFile.textures[index].makeTexture()
fun getTexture(gltfFile: GltfFile, assetLoader: AssetLoader): Texture2d {
return gltfFile.textures[index].makeTexture(assetLoader)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import java.io.ByteArrayInputStream
import java.io.File
import java.io.IOException
import java.io.InputStream
import java.util.*
import kotlin.math.roundToInt

internal actual fun PlatformAssets(): PlatformAssets = PlatformAssetsImpl
Expand Down Expand Up @@ -173,4 +174,9 @@ object PlatformAssetsImpl : PlatformAssets {

return ImageDecoder.loadBufferedImage(img, props)
}
}

actual suspend fun decodeDataUri(dataUri: String): Uint8Buffer {
val dataIdx = dataUri.indexOf(";base64,") + 8
return Uint8BufferImpl(Base64.getDecoder().decode(dataUri.substring(dataIdx)))
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import de.fabmax.kool.modules.filesystem.FileSystemDirectory
import de.fabmax.kool.modules.filesystem.getFileOrNull
import de.fabmax.kool.pipeline.TextureData2d
import de.fabmax.kool.platform.imageAtlasTextureData
import de.fabmax.kool.util.Uint8Buffer
import java.io.ByteArrayInputStream

actual fun fileSystemAssetLoader(baseDir: FileSystemDirectory): AssetLoader {
Expand All @@ -13,8 +14,8 @@ actual fun fileSystemAssetLoader(baseDir: FileSystemDirectory): AssetLoader {

class FileSystemAssetLoader(val baseDir: FileSystemDirectory): AssetLoader() {
override suspend fun loadBlob(blobRef: BlobAssetRef): LoadedBlobAsset {
val blob = baseDir.getFileOrNull(blobRef.path)
return LoadedBlobAsset(blobRef, blob?.read())
val blob = loadData(blobRef.path)
return LoadedBlobAsset(blobRef, blob)
}

override suspend fun loadTexture(textureRef: TextureAssetRef): LoadedTextureAsset {
Expand All @@ -33,8 +34,8 @@ class FileSystemAssetLoader(val baseDir: FileSystemDirectory): AssetLoader() {
}

override suspend fun loadTextureData2d(textureData2dRef: TextureData2dRef): LoadedTextureAsset {
val tex = baseDir.getFileOrNull(textureData2dRef.path)
val texData = tex?.read()?.toArray()?.let { bytes ->
val tex = loadData(textureData2dRef.path)
val texData = tex?.toArray()?.let { bytes ->
ByteArrayInputStream(bytes).use {
PlatformAssetsImpl.readImageData(it, MimeType.forFileName(textureData2dRef.path), textureData2dRef.props)
}
Expand All @@ -49,4 +50,12 @@ class FileSystemAssetLoader(val baseDir: FileSystemDirectory): AssetLoader() {
}
return LoadedAudioClipAsset(audioRef, clip)
}

private suspend fun loadData(path: String): Uint8Buffer? {
return if (Assets.isDataUri(path)) {
decodeDataUri(path)
} else {
baseDir.getFileOrNull(path)?.read()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ import de.fabmax.kool.pipeline.TextureData2d
import de.fabmax.kool.pipeline.TextureProps
import de.fabmax.kool.platform.HttpCache
import de.fabmax.kool.platform.imageAtlasTextureData
import de.fabmax.kool.util.Uint8Buffer
import de.fabmax.kool.util.Uint8BufferImpl
import de.fabmax.kool.util.logE
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.io.FileInputStream
import java.io.InputStream
import java.util.*

class NativeAssetLoader(val basePath: String) : AssetLoader() {
override suspend fun loadBlob(blobRef: BlobAssetRef): LoadedBlobAsset {
Expand All @@ -23,7 +23,7 @@ class NativeAssetLoader(val basePath: String) : AssetLoader() {
}

private suspend fun loadLocalBlob(localRawRef: BlobAssetRef): LoadedBlobAsset {
var data: Uint8BufferImpl? = null
var data: Uint8Buffer? = null
withContext(Dispatchers.IO) {
try {
openLocalStream(localRawRef.path)?.use { data = Uint8BufferImpl(it.readBytes()) }
Expand All @@ -35,9 +35,9 @@ class NativeAssetLoader(val basePath: String) : AssetLoader() {
}

private suspend fun loadHttpBlob(httpRawRef: BlobAssetRef): LoadedBlobAsset {
var data: Uint8BufferImpl? = null
var data: Uint8Buffer? = null
if (httpRawRef.path.startsWith("data:", true)) {
data = decodeDataUrl(httpRawRef.path)
data = decodeDataUri(httpRawRef.path)
} else {
withContext(Dispatchers.IO) {
try {
Expand All @@ -52,11 +52,6 @@ class NativeAssetLoader(val basePath: String) : AssetLoader() {
return LoadedBlobAsset(httpRawRef, data)
}

private fun decodeDataUrl(dataUrl: String): Uint8BufferImpl {
val dataIdx = dataUrl.indexOf(";base64,") + 8
return Uint8BufferImpl(Base64.getDecoder().decode(dataUrl.substring(dataIdx)))
}

private fun openLocalStream(assetPath: String): InputStream? {
return try {
val resPath = (KoolSystem.configJvm.classloaderAssetPath + "/" + assetPath.replace('\\', '/'))
Expand Down
7 changes: 7 additions & 0 deletions kool-core/src/jsMain/kotlin/de/fabmax/kool/Assets.js.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import kotlinx.browser.document
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.await
import org.khronos.webgl.ArrayBuffer
import org.khronos.webgl.Uint8Array
import org.w3c.dom.Image
import org.w3c.dom.ImageBitmap
import org.w3c.files.Blob
Expand Down Expand Up @@ -128,3 +129,9 @@ external interface Response {
fun blob(): Promise<Blob>
fun text(): Promise<String>
}

actual suspend fun decodeDataUri(dataUri: String): Uint8Buffer {
val response = fetch(dataUri).await()
val arrayBuffer = response.arrayBuffer().await()
return Uint8BufferImpl(Uint8Array(arrayBuffer))
}
Loading

0 comments on commit fb2f51e

Please sign in to comment.