diff --git a/kool-core/src/commonMain/kotlin/de/fabmax/kool/Assets.kt b/kool-core/src/commonMain/kotlin/de/fabmax/kool/Assets.kt index f6e6e050f..4a735a834 100644 --- a/kool-core/src/commonMain/kotlin/de/fabmax/kool/Assets.kt +++ b/kool-core/src/commonMain/kotlin/de/fabmax/kool/Assets.kt @@ -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]. @@ -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 diff --git a/kool-core/src/commonMain/kotlin/de/fabmax/kool/editor/api/AppAssets.kt b/kool-core/src/commonMain/kotlin/de/fabmax/kool/editor/api/AppAssets.kt index 722f74860..c627804e8 100644 --- a/kool-core/src/commonMain/kotlin/de/fabmax/kool/editor/api/AppAssets.kt +++ b/kool-core/src/commonMain/kotlin/de/fabmax/kool/editor/api/AppAssets.kt @@ -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 @@ -9,6 +10,8 @@ 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? @@ -16,12 +19,17 @@ interface AppAssetsLoader { 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 { diff --git a/kool-core/src/commonMain/kotlin/de/fabmax/kool/editor/components/ModelComponent.kt b/kool-core/src/commonMain/kotlin/de/fabmax/kool/editor/components/ModelComponent.kt index 94ec8dea2..ed32c716f 100644 --- a/kool-core/src/commonMain/kotlin/de/fabmax/kool/editor/components/ModelComponent.kt +++ b/kool-core/src/commonMain/kotlin/de/fabmax/kool/editor/components/ModelComponent.kt @@ -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 diff --git a/kool-core/src/commonMain/kotlin/de/fabmax/kool/modules/gizmo/GizmoNode.kt b/kool-core/src/commonMain/kotlin/de/fabmax/kool/modules/gizmo/GizmoNode.kt index 3923d74d7..6136630ba 100644 --- a/kool-core/src/commonMain/kotlin/de/fabmax/kool/modules/gizmo/GizmoNode.kt +++ b/kool-core/src/commonMain/kotlin/de/fabmax/kool/modules/gizmo/GizmoNode.kt @@ -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 } @@ -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 } diff --git a/kool-core/src/commonMain/kotlin/de/fabmax/kool/modules/gltf/GltfAssetManagerExtensions.kt b/kool-core/src/commonMain/kotlin/de/fabmax/kool/modules/gltf/GltfAssetManagerExtensions.kt index 6d03e174f..e6eaa3aea 100644 --- a/kool-core/src/commonMain/kotlin/de/fabmax/kool/modules/gltf/GltfAssetManagerExtensions.kt +++ b/kool-core/src/commonMain/kotlin/de/fabmax/kool/modules/gltf/GltfAssetManagerExtensions.kt @@ -11,23 +11,24 @@ import kotlinx.coroutines.async suspend fun AssetLoader.loadGltfFile(assetPath: String): GltfFile = loadGltfFileAsync(assetPath).await() +fun AssetLoader.loadGltfFileAsync(assetPath: String): Deferred = 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 = Assets.async { - val blob = loadBlobAsset(assetPath) - GltfFile(blob, assetPath) -} - fun AssetLoader.loadGltfModelAsync( assetPath: String, modelCfg: GltfLoadConfig = GltfLoadConfig(), scene: Int = 0 ): Deferred = 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() diff --git a/kool-core/src/commonMain/kotlin/de/fabmax/kool/modules/gltf/GltfConfig.kt b/kool-core/src/commonMain/kotlin/de/fabmax/kool/modules/gltf/GltfConfig.kt index 14266b3a2..5729bef3e 100644 --- a/kool-core/src/commonMain/kotlin/de/fabmax/kool/modules/gltf/GltfConfig.kt +++ b/kool-core/src/commonMain/kotlin/de/fabmax/kool/modules/gltf/GltfConfig.kt @@ -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(), @@ -19,10 +20,11 @@ class GltfLoadConfig( val mergeMeshesByMaterial: Boolean = false, val sortNodesByAlpha: Boolean = true, val addInstanceAttributes: List = emptyList(), + val assetLoader: AssetLoader? = null, val pbrBlock: (KslPbrShader.Config.Builder.(GltfMesh.Primitive) -> Unit)? = null ) -class GltfMaterialConfig( +data class GltfMaterialConfig( val shadowMaps: List = emptyList(), val scrSpcAmbientOcclusionMap: Texture2d? = null, val environmentMaps: EnvironmentMaps? = null, diff --git a/kool-core/src/commonMain/kotlin/de/fabmax/kool/modules/gltf/GltfFile.kt b/kool-core/src/commonMain/kotlin/de/fabmax/kool/modules/gltf/GltfFile.kt index 7a3e23b56..e34771a45 100644 --- a/kool-core/src/commonMain/kotlin/de/fabmax/kool/modules/gltf/GltfFile.kt +++ b/kool-core/src/commonMain/kotlin/de/fabmax/kool/modules/gltf/GltfFile.kt @@ -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 @@ -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 { @@ -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() @@ -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()) diff --git a/kool-core/src/commonMain/kotlin/de/fabmax/kool/modules/gltf/GltfMaterial.kt b/kool-core/src/commonMain/kotlin/de/fabmax/kool/modules/gltf/GltfMaterial.kt index 495891621..02ae7d8cf 100644 --- a/kool-core/src/commonMain/kotlin/de/fabmax/kool/modules/gltf/GltfMaterial.kt +++ b/kool-core/src/commonMain/kotlin/de/fabmax/kool/modules/gltf/GltfMaterial.kt @@ -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 @@ -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) { diff --git a/kool-core/src/commonMain/kotlin/de/fabmax/kool/modules/gltf/GltfTexture.kt b/kool-core/src/commonMain/kotlin/de/fabmax/kool/modules/gltf/GltfTexture.kt index 882b779eb..b83c9c2cc 100644 --- a/kool-core/src/commonMain/kotlin/de/fabmax/kool/modules/gltf/GltfTexture.kt +++ b/kool-core/src/commonMain/kotlin/de/fabmax/kool/modules/gltf/GltfTexture.kt @@ -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 @@ -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)) { @@ -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!!) } diff --git a/kool-core/src/commonMain/kotlin/de/fabmax/kool/modules/gltf/GltfTextureInfo.kt b/kool-core/src/commonMain/kotlin/de/fabmax/kool/modules/gltf/GltfTextureInfo.kt index ba3e52bf2..aa722a2a6 100644 --- a/kool-core/src/commonMain/kotlin/de/fabmax/kool/modules/gltf/GltfTextureInfo.kt +++ b/kool-core/src/commonMain/kotlin/de/fabmax/kool/modules/gltf/GltfTextureInfo.kt @@ -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 @@ -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) } } \ No newline at end of file diff --git a/kool-core/src/desktopMain/kotlin/de/fabmax/kool/Assets.desktop.kt b/kool-core/src/desktopMain/kotlin/de/fabmax/kool/Assets.desktop.kt index f448b8a24..31056fe81 100644 --- a/kool-core/src/desktopMain/kotlin/de/fabmax/kool/Assets.desktop.kt +++ b/kool-core/src/desktopMain/kotlin/de/fabmax/kool/Assets.desktop.kt @@ -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 @@ -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))) } \ No newline at end of file diff --git a/kool-core/src/desktopMain/kotlin/de/fabmax/kool/FileSystemAssetLoader.kt b/kool-core/src/desktopMain/kotlin/de/fabmax/kool/FileSystemAssetLoader.kt index a15bcd6ad..f287d4fb7 100644 --- a/kool-core/src/desktopMain/kotlin/de/fabmax/kool/FileSystemAssetLoader.kt +++ b/kool-core/src/desktopMain/kotlin/de/fabmax/kool/FileSystemAssetLoader.kt @@ -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 { @@ -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 { @@ -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) } @@ -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() + } + } } \ No newline at end of file diff --git a/kool-core/src/desktopMain/kotlin/de/fabmax/kool/NativeAssetLoader.kt b/kool-core/src/desktopMain/kotlin/de/fabmax/kool/NativeAssetLoader.kt index 7968fce7d..b421815fd 100644 --- a/kool-core/src/desktopMain/kotlin/de/fabmax/kool/NativeAssetLoader.kt +++ b/kool-core/src/desktopMain/kotlin/de/fabmax/kool/NativeAssetLoader.kt @@ -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 { @@ -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()) } @@ -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 { @@ -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('\\', '/')) diff --git a/kool-core/src/jsMain/kotlin/de/fabmax/kool/Assets.js.kt b/kool-core/src/jsMain/kotlin/de/fabmax/kool/Assets.js.kt index 1d9b5d3e5..c1418c686 100644 --- a/kool-core/src/jsMain/kotlin/de/fabmax/kool/Assets.js.kt +++ b/kool-core/src/jsMain/kotlin/de/fabmax/kool/Assets.js.kt @@ -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 @@ -128,3 +129,9 @@ external interface Response { fun blob(): Promise fun text(): Promise } + +actual suspend fun decodeDataUri(dataUri: String): Uint8Buffer { + val response = fetch(dataUri).await() + val arrayBuffer = response.arrayBuffer().await() + return Uint8BufferImpl(Uint8Array(arrayBuffer)) +} \ No newline at end of file diff --git a/kool-core/src/jsMain/kotlin/de/fabmax/kool/FileSystemAssetLoader.kt b/kool-core/src/jsMain/kotlin/de/fabmax/kool/FileSystemAssetLoader.kt index 6a5f54bca..06ba7d8df 100644 --- a/kool-core/src/jsMain/kotlin/de/fabmax/kool/FileSystemAssetLoader.kt +++ b/kool-core/src/jsMain/kotlin/de/fabmax/kool/FileSystemAssetLoader.kt @@ -7,6 +7,7 @@ import de.fabmax.kool.pipeline.TextureData2d import de.fabmax.kool.pipeline.TextureProps import de.fabmax.kool.platform.ImageAtlasTextureData import de.fabmax.kool.platform.ImageTextureData +import de.fabmax.kool.util.Uint8Buffer import de.fabmax.kool.util.Uint8BufferImpl import kotlinx.coroutines.await import org.w3c.dom.url.URL @@ -18,21 +19,21 @@ 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 { - val data = baseDir.getFileOrNull(textureRef.path)?.let { texData -> - PlatformAssetsImpl.loadTextureDataFromBuffer(texData.read(), MimeType.forFileName(texData.name), textureRef.props) + val data = loadData(textureRef.path)?.let { texData -> + PlatformAssetsImpl.loadTextureDataFromBuffer(texData, MimeType.forFileName(textureRef.path), textureRef.props) } return LoadedTextureAsset(textureRef, data) } override suspend fun loadTextureAtlas(textureRef: TextureAtlasAssetRef): LoadedTextureAsset { val resize = textureRef.props?.resolveSize - val data = baseDir.getFileOrNull(textureRef.path)?.let { texData -> - val bytes = (texData.read() as Uint8BufferImpl).buffer + val data = loadData(textureRef.path)?.let { texData -> + val bytes = (texData as Uint8BufferImpl).buffer val imgBlob = Blob(arrayOf(bytes)) val bmp = createImageBitmap(imgBlob, ImageBitmapOptions(resize)).await() ImageAtlasTextureData( @@ -63,12 +64,20 @@ class FileSystemAssetLoader(val baseDir: FileSystemDirectory): AssetLoader() { } override suspend fun loadAudioClip(audioRef: AudioClipRef): LoadedAudioClipAsset { - val clip = baseDir.getFileOrNull(audioRef.path)?.let { audioData -> - val bytes = (audioData.read() as Uint8BufferImpl).buffer + val clip = loadData(audioRef.path)?.let { audioData -> + val bytes = (audioData as Uint8BufferImpl).buffer val audioBlob = Blob(arrayOf(bytes)) val url = URL.createObjectURL(audioBlob) AudioClipImpl(url) } return LoadedAudioClipAsset(audioRef, clip) } + + private suspend fun loadData(path: String): Uint8Buffer? { + return if (Assets.isDataUri(path)) { + decodeDataUri(path) + } else { + baseDir.getFileOrNull(path)?.read() + } + } } \ No newline at end of file diff --git a/kool-editor/src/commonMain/kotlin/de/fabmax/kool/editor/CachedAppAssets.kt b/kool-editor/src/commonMain/kotlin/de/fabmax/kool/editor/CachedAppAssets.kt index 6e225915f..135c9257b 100644 --- a/kool-editor/src/commonMain/kotlin/de/fabmax/kool/editor/CachedAppAssets.kt +++ b/kool-editor/src/commonMain/kotlin/de/fabmax/kool/editor/CachedAppAssets.kt @@ -11,7 +11,7 @@ import de.fabmax.kool.pipeline.ibl.EnvironmentHelper import de.fabmax.kool.pipeline.ibl.EnvironmentMaps import de.fabmax.kool.util.logE -class CachedAppAssets(val loader: AssetLoader) : AppAssetsLoader { +class CachedAppAssets(override val assetLoader: AssetLoader) : AppAssetsLoader { private val loadedHdris = mutableMapOf>() private val loadedModels = mutableMapOf>() private val loadedTextures2d = mutableMapOf>() @@ -19,7 +19,7 @@ class CachedAppAssets(val loader: AssetLoader) : AppAssetsLoader { override suspend fun loadHdriEnvironment(path: String): EnvironmentMaps? { val hdriState = loadedHdris.getOrPut(path) { mutableStateOf(null) } return try { - val hdriTex = loader.loadTexture2d(path) + val hdriTex = assetLoader.loadTexture2d(path) hdriState.value ?: EnvironmentHelper.hdriEnvironment(hdriTex).also { hdriState.set(it) } } catch (e: Exception) { logE { "Failed loading HDRI: $path" } @@ -30,7 +30,7 @@ class CachedAppAssets(val loader: AssetLoader) : AppAssetsLoader { override suspend fun loadModel(modelPath: String): GltfFile? { val modelState = loadedModels.getOrPut(modelPath) { mutableStateOf(null) } return try { - modelState.value ?: loader.loadGltfFile(modelPath).also { modelState.set(it) } + modelState.value ?: assetLoader.loadGltfFile(modelPath).also { modelState.set(it) } } catch (e: Exception) { logE { "Failed loading model: $modelPath" } null @@ -40,7 +40,7 @@ class CachedAppAssets(val loader: AssetLoader) : AppAssetsLoader { override suspend fun loadTexture2d(path: String): Texture2d? { val texState = loadedTextures2d.getOrPut(path) { mutableStateOf(null) } return try { - texState.value ?: loader.loadTexture2d(path).also { texState.set(it) } + texState.value ?: assetLoader.loadTexture2d(path).also { texState.set(it) } } catch (e: Exception) { logE { "Failed loading texture: $path" } null diff --git a/kool-editor/src/commonMain/kotlin/de/fabmax/kool/editor/ui/EditorUi.kt b/kool-editor/src/commonMain/kotlin/de/fabmax/kool/editor/ui/EditorUi.kt index 36bea10a3..fc5c13da6 100644 --- a/kool-editor/src/commonMain/kotlin/de/fabmax/kool/editor/ui/EditorUi.kt +++ b/kool-editor/src/commonMain/kotlin/de/fabmax/kool/editor/ui/EditorUi.kt @@ -1,3 +1,5 @@ +@file:Suppress("UnusedReceiverParameter") + package de.fabmax.kool.editor.ui import de.fabmax.kool.Assets diff --git a/kool-editor/src/commonMain/kotlin/de/fabmax/kool/editor/ui/SceneView.kt b/kool-editor/src/commonMain/kotlin/de/fabmax/kool/editor/ui/SceneView.kt index c5fae38c1..e86539971 100644 --- a/kool-editor/src/commonMain/kotlin/de/fabmax/kool/editor/ui/SceneView.kt +++ b/kool-editor/src/commonMain/kotlin/de/fabmax/kool/editor/ui/SceneView.kt @@ -93,17 +93,78 @@ class SceneView(ui: EditorUi) : EditorPanel("Scene View", IconMap.medium.CAMERA, if (isShowExportButton.use()) { divider(colors.strongDividerColor, marginStart = sizes.largeGap, marginEnd = sizes.largeGap, verticalMargin = sizes.gap) - iconTextButton( + var isHovered by remember(false) + val button = iconTextButton( icon = IconMap.small.DOWNLOAD, - text = "Save Project Files", + text = "Save Project", bgColor = colors.componentBg, bgColorHovered = colors.componentBgHovered, bgColorClicked = colors.elevatedComponentBgHovered, - width = sizes.baseSize * 4.5f, - margin = sizes.gap + width = sizes.baseSize * 3.5f, + margin = sizes.gap, + boxBlock = { + modifier + .onEnter { isHovered = true } + .onExit { isHovered = false } + } ) { editor.exportProject() } + + if (isHovered) { + saveProjectTooltip(button) + } + } + } + + private fun UiScope.saveProjectTooltip(button: UiScope) = Popup( + screenPxX = button.uiNode.rightPx - 250.dp.px, + screenPxY = button.uiNode.bottomPx + sizes.gap.px, + width = 250.dp, + height = FitContent, + layout = CellLayout + ) { + modifier + .background(RoundRectBackground(colors.background, sizes.smallGap)) + .border(RoundRectBorder(colors.componentBg, sizes.smallGap, sizes.borderWidth)) + + Column(width = Grow.Std) { + modifier.margin(sizes.gap) + + Text("Download Project Files") { + modifier.font(sizes.boldText) + } + + Text("Save project and unzip it. Then open the unzipped folder in a terminal:") { + modifier + .width(Grow.Std) + .isWrapText(true) + .margin(vertical = sizes.largeGap) + } + + Text("Run editor locally:") { modifier.margin(top = sizes.largeGap) } + + Text("./gradlew runEditor") { + modifier + .font(ui.consoleFont.use()) + .margin(top = sizes.smallGap) + .width(Grow.Std) + .padding(sizes.smallGap) + .background(RoundRectBackground(colors.backgroundVariant, sizes.smallGap)) + .border(RoundRectBorder(colors.componentBg, sizes.smallGap, sizes.borderWidth)) + } + + Text("Run app:") { modifier.margin(top = sizes.largeGap) } + + Text("./gradlew runApp") { + modifier + .font(ui.consoleFont.use()) + .margin(top = sizes.smallGap) + .width(Grow.Std) + .padding(sizes.smallGap) + .background(RoundRectBackground(colors.backgroundVariant, sizes.smallGap)) + .border(RoundRectBorder(colors.componentBg, sizes.smallGap, sizes.borderWidth)) + } } } diff --git a/kool-editor/src/desktopMain/kotlin/de/fabmax/kool/editor/PlatformFunctions.desktop.kt b/kool-editor/src/desktopMain/kotlin/de/fabmax/kool/editor/PlatformFunctions.desktop.kt index f7ff7b9ab..08b57a7fa 100644 --- a/kool-editor/src/desktopMain/kotlin/de/fabmax/kool/editor/PlatformFunctions.desktop.kt +++ b/kool-editor/src/desktopMain/kotlin/de/fabmax/kool/editor/PlatformFunctions.desktop.kt @@ -8,8 +8,10 @@ import de.fabmax.kool.platform.Lwjgl3Context import de.fabmax.kool.util.logD import de.fabmax.kool.util.logE import de.fabmax.kool.util.logW +import kotlinx.coroutines.runBlocking import java.io.File import java.io.IOException +import kotlin.io.path.Path import kotlin.io.path.pathString @Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") @@ -87,4 +89,23 @@ actual object PlatformFunctions { } return null } -} \ No newline at end of file +} + +fun KoolEditor(projectRoot: String, ctx: KoolContext): KoolEditor { + return runBlocking { + val rootPath = Path(projectRoot) + val fs = PhysicalFileSystem( + rootPath, + excludePaths = setOf( + rootPath.resolve(".gradle"), + rootPath.resolve(".editor"), + rootPath.resolve(".httpCache"), + rootPath.resolve("kotlin-js-store"), + rootPath.resolve("build") + ), + isLaunchWatchService = true + ) + val projFiles = ProjectFiles.createSafe(fs) + KoolEditor(projFiles, ctx) + } +}