add Dart IndirectLight and Ktx1Bundle classes, and separate KTX decoding from skybox/IBL creation

This commit is contained in:
Nick Fisher
2025-06-12 11:31:44 +08:00
parent f252c86152
commit ad26fc4563
16 changed files with 424 additions and 104 deletions

View File

@@ -1,3 +1,4 @@
import 'package:thermion_dart/src/filament/src/interface/ktx1_bundle.dart';
import 'package:vector_math/vector_math_64.dart' as v64;
import 'package:animation_tools_dart/src/bone_animation_data.dart';
import 'package:animation_tools_dart/src/morph_animation_data.dart';
@@ -19,6 +20,9 @@ class BackgroundImage extends ThermionAsset {
final FFIScene scene;
int? width;
int? height;
BackgroundImage._(
this.asset, this.scene, this.texture, this.sampler, this.mi);
@@ -45,9 +49,8 @@ class BackgroundImage extends ThermionAsset {
await viewer.createGeometry(GeometryHelper.fullscreenQuad());
await imageMaterialInstance.setParameterInt("showImage", 0);
var transform = Matrix4.identity();
await imageMaterialInstance.setParameterMat4(
"transform", transform);
await imageMaterialInstance.setParameterMat4("transform", transform);
await backgroundImage.setMaterialInstanceAt(imageMaterialInstance);
await scene.add(backgroundImage as FFIAsset);
@@ -62,28 +65,74 @@ class BackgroundImage extends ThermionAsset {
await mi.setParameterFloat4("backgroundColor", r, g, b, a);
}
///
///
///
Future hideImage() async {
await mi.setParameterInt("showImage", 0);
}
///
///
///
Future setImageFromKtxBundle(Ktx1Bundle bundle) async {
final texture = await bundle.createTexture();
if (bundle.isCubemap()) {
sampler ??=
await FilamentApp.instance!.createTextureSampler() as FFITextureSampler;
this.texture = texture;
await mi.setParameterTexture(
"cubeMap", texture as FFITexture, sampler as FFITextureSampler);
await setBackgroundColor(1, 1, 1, 0);
await mi.setParameterInt("showImage", 1);
await mi.setParameterInt("isCubeMap", 1);
width = await texture.getWidth();
height = await texture.getHeight();
} else {
await setImageFromTexture(texture);
}
}
///
///
///
Future setImage(Uint8List imageData) async {
final image = await FilamentApp.instance!.decodeImage(imageData);
final channels = await image.getChannels();
final textureFormat = channels == 4
? TextureFormat.RGBA32F
: channels == 3
? TextureFormat.RGB32F
: throw UnimplementedError();
final pixelFormat = channels == 4
? PixelDataFormat.RGBA
: channels == 3
? PixelDataFormat.RGB
: throw UnimplementedError();
texture ??= await FilamentApp.instance!.createTexture(
final texture = await FilamentApp.instance!.createTexture(
await image.getWidth(), await image.getHeight(),
textureFormat: TextureFormat.RGBA32F);
await texture!
.setLinearImage(image, PixelDataFormat.RGBA, PixelDataType.FLOAT);
textureFormat: textureFormat);
await texture.setLinearImage(image, pixelFormat, PixelDataType.FLOAT);
await setImageFromTexture(texture);
}
///
///
///
Future setImageFromTexture(Texture texture) async {
this.texture = texture;
sampler ??=
await FilamentApp.instance!.createTextureSampler() as FFITextureSampler;
await mi.setParameterInt("isCubeMap", 0);
await mi.setParameterTexture(
"image", texture as FFITexture, sampler as FFITextureSampler);
await setBackgroundColor(1, 1, 1, 0);
await mi.setParameterInt("showImage", 1);
width = await texture.getWidth();
height = await texture.getHeight();
}
///
@@ -128,7 +177,7 @@ class BackgroundImage extends ThermionAsset {
Future setCastShadows(bool castShadows) {
// TODO: implement setCastShadows
throw UnimplementedError();
}
}
@override
Future setReceiveShadows(bool castShadows) {
@@ -346,6 +395,4 @@ class BackgroundImage extends ThermionAsset {
Future<v64.Aabb3> getBoundingBox() {
throw UnimplementedError();
}
}

View File

@@ -0,0 +1,90 @@
import 'package:thermion_dart/src/filament/src/implementation/ffi_filament_app.dart';
import 'package:thermion_dart/src/filament/src/implementation/ffi_texture.dart';
import 'package:thermion_dart/thermion_dart.dart';
class FFIIndirectLight extends IndirectLight {
final Pointer<TEngine> engine;
final Pointer<TIndirectLight> pointer;
final Texture? _irradianceTexture;
final Texture? _reflectionsTexture;
FFIIndirectLight._(this.engine, this.pointer, this._irradianceTexture,
this._reflectionsTexture);
static Future<FFIIndirectLight> fromIrradianceTexture(
Texture irradianceTexture,
{Texture? reflectionsTexture,
double intensity = 30000}) async {
final engine = (FilamentApp.instance as FFIFilamentApp).engine;
var indirectLight = await withPointerCallback<TIndirectLight>((cb) {
Engine_buildIndirectLightFromIrradianceTextureRenderThread(
engine,
(reflectionsTexture as FFITexture?)?.pointer ?? nullptr,
(irradianceTexture as FFITexture).pointer,
intensity,
cb);
});
if (indirectLight == nullptr) {
throw Exception("Failed to create indirect light");
}
return FFIIndirectLight._(
engine, indirectLight, irradianceTexture, reflectionsTexture);
}
static Future<FFIIndirectLight> fromIrradianceHarmonics(
Float32List irradianceHarmonics,
{Texture? reflectionsTexture,
double intensity = 30000}) async {
final engine = (FilamentApp.instance as FFIFilamentApp).engine;
var indirectLight = await withPointerCallback<TIndirectLight>((cb) {
Engine_buildIndirectLightFromIrradianceHarmonicsRenderThread(
engine,
(reflectionsTexture as FFITexture?)?.pointer ?? nullptr,
irradianceHarmonics.address,
intensity,
cb);
});
if (indirectLight == nullptr) {
throw Exception("Failed to create indirect light");
}
return FFIIndirectLight._(engine, indirectLight, null, reflectionsTexture);
}
Future rotate(Matrix3 rotation) async {
late Pointer stackPtr;
if (FILAMENT_WASM) {
//stackPtr = stackSave();
}
IndirectLight_setRotation(this.pointer, rotation.storage.address);
if (FILAMENT_WASM) {
//stackRestore(stackPtr);
rotation.storage.free();
}
}
Future destroy() async {
await withVoidCallback(
(requestId, cb) => Engine_destroyIndirectLightRenderThread(
engine,
pointer,
requestId,
cb,
),
);
if (_irradianceTexture != null) {
await withVoidCallback((requestId, cb) =>
Engine_destroyTextureRenderThread(engine,
(_irradianceTexture as FFITexture).pointer, requestId, cb));
}
if (_reflectionsTexture != null) {
await withVoidCallback((requestId, cb) =>
Engine_destroyTextureRenderThread(engine,
(_reflectionsTexture as FFITexture).pointer, requestId, cb));
}
}
}

View File

@@ -0,0 +1,58 @@
import 'package:thermion_dart/src/filament/src/implementation/ffi_filament_app.dart';
import 'package:thermion_dart/src/filament/src/implementation/ffi_texture.dart';
import 'package:thermion_dart/src/filament/src/interface/ktx1_bundle.dart';
import 'package:thermion_dart/thermion_dart.dart';
class FFIKtx1Bundle extends Ktx1Bundle {
final Pointer<TKtx1Bundle> pointer;
FFIKtx1Bundle(this.pointer);
///
///
///
bool isCubemap() {
return Ktx1Bundle_isCubemap(pointer);
}
///
///
///
Future destroy() async {
Ktx1Bundle_destroy(pointer);
}
///
///
///
Float32List getSphericalHarmonics() {
var harmonics = Float32List(27);
Ktx1Bundle_getSphericalHarmonics(pointer, harmonics.address);
return harmonics;
}
///
///
///
static Future<Ktx1Bundle> create(Uint8List data) async {
var bundle = Ktx1Bundle_create(data.address, data.length);
if (bundle == nullptr) {
throw Exception("Failed to decode KTX texture");
}
return FFIKtx1Bundle(bundle);
}
Future<Texture> createTexture() async {
final texturePtr = await withPointerCallback<TTexture>((cb) {
Ktx1Reader_createTextureRenderThread(
(FilamentApp.instance as FFIFilamentApp).engine,
pointer,
0,
nullptr,
cb);
});
return FFITexture(FilamentApp.instance!.engine, texturePtr);
}
}

View File

@@ -1,4 +1,5 @@
import 'package:thermion_dart/src/filament/src/implementation/ffi_asset.dart';
import 'package:thermion_dart/src/filament/src/implementation/ffi_indirect_light.dart';
import 'package:thermion_dart/src/filament/src/interface/scene.dart';
import 'package:thermion_dart/thermion_dart.dart';
import 'package:logging/logging.dart';
@@ -124,4 +125,27 @@ class FFIScene extends Scene {
.info("Added stencil highlight for asset (entity ${asset.entity})");
}
}
IndirectLight? _indirectLight;
///
///
///
Future setIndirectLight(IndirectLight? indirectLight) async {
if (indirectLight == null) {
Scene_setIndirectLight(scene, nullptr);
_indirectLight = null;
} else {
Scene_setIndirectLight(
scene, (indirectLight as FFIIndirectLight).pointer);
_indirectLight = indirectLight;
}
}
///
///
///
Future<IndirectLight?> getIndirectLight() async {
return _indirectLight;
}
}

View File

@@ -1,6 +1,3 @@
import 'dart:typed_data';
import 'package:thermion_dart/src/bindings/bindings.dart';
import 'package:thermion_dart/thermion_dart.dart';
class FFITexture extends Texture {

View File

@@ -0,0 +1,24 @@
import 'package:thermion_dart/thermion_dart.dart';
abstract class Ktx1Bundle {
///
///
///
bool isCubemap();
///
///
///
Future<Texture> createTexture();
///
///
///
Float32List getSphericalHarmonics();
///
///
///
Future destroy();
}

View File

@@ -1,7 +1,19 @@
import 'package:vector_math/vector_math_64.dart';
enum LightType {
SUN, //!< Directional light that also draws a sun's disk in the sky.
DIRECTIONAL, //!< Directional light, emits light in a given direction.
POINT, //!< Point light, emits light from a position, in all directions.
FOCUSED_SPOT, //!< Physically correct spot light.
SPOT,
}
}
abstract class IndirectLight {
Future destroy() {
throw UnimplementedError();
}
Future rotate(Matrix3 rotation) {
throw UnimplementedError();
}
}

View File

@@ -21,18 +21,32 @@ abstract class Scene {
///
Future removeEntity(ThermionEntity entity);
/// Renders an outline around [entity] with the given color.
///
///
Future setStencilHighlight(ThermionAsset asset,{double r = 1.0,
///
Future setStencilHighlight(ThermionAsset asset,
{double r = 1.0,
double g = 0.0,
double b = 0.0,
int? entity,
int primitiveIndex = 0});
/// Removes the outline around [entity]. Noop if there was no highlight.
///
///
///
///
Future removeStencilHighlight(ThermionAsset asset);
///
///
///
Future setIndirectLight(IndirectLight? indirectLight) {
throw UnimplementedError();
}
///
///
///
Future<IndirectLight?> getIndirectLight() {
throw UnimplementedError();
}
}