add Dart IndirectLight and Ktx1Bundle classes, and separate KTX decoding from skybox/IBL creation
This commit is contained in:
@@ -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:vector_math/vector_math_64.dart' as v64;
|
||||||
import 'package:animation_tools_dart/src/bone_animation_data.dart';
|
import 'package:animation_tools_dart/src/bone_animation_data.dart';
|
||||||
import 'package:animation_tools_dart/src/morph_animation_data.dart';
|
import 'package:animation_tools_dart/src/morph_animation_data.dart';
|
||||||
@@ -19,6 +20,9 @@ class BackgroundImage extends ThermionAsset {
|
|||||||
|
|
||||||
final FFIScene scene;
|
final FFIScene scene;
|
||||||
|
|
||||||
|
int? width;
|
||||||
|
int? height;
|
||||||
|
|
||||||
BackgroundImage._(
|
BackgroundImage._(
|
||||||
this.asset, this.scene, this.texture, this.sampler, this.mi);
|
this.asset, this.scene, this.texture, this.sampler, this.mi);
|
||||||
|
|
||||||
@@ -45,9 +49,8 @@ class BackgroundImage extends ThermionAsset {
|
|||||||
await viewer.createGeometry(GeometryHelper.fullscreenQuad());
|
await viewer.createGeometry(GeometryHelper.fullscreenQuad());
|
||||||
await imageMaterialInstance.setParameterInt("showImage", 0);
|
await imageMaterialInstance.setParameterInt("showImage", 0);
|
||||||
var transform = Matrix4.identity();
|
var transform = Matrix4.identity();
|
||||||
|
|
||||||
await imageMaterialInstance.setParameterMat4(
|
await imageMaterialInstance.setParameterMat4("transform", transform);
|
||||||
"transform", transform);
|
|
||||||
|
|
||||||
await backgroundImage.setMaterialInstanceAt(imageMaterialInstance);
|
await backgroundImage.setMaterialInstanceAt(imageMaterialInstance);
|
||||||
await scene.add(backgroundImage as FFIAsset);
|
await scene.add(backgroundImage as FFIAsset);
|
||||||
@@ -62,28 +65,74 @@ class BackgroundImage extends ThermionAsset {
|
|||||||
await mi.setParameterFloat4("backgroundColor", r, g, b, a);
|
await mi.setParameterFloat4("backgroundColor", r, g, b, a);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
///
|
||||||
|
///
|
||||||
Future hideImage() async {
|
Future hideImage() async {
|
||||||
await mi.setParameterInt("showImage", 0);
|
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 {
|
Future setImage(Uint8List imageData) async {
|
||||||
final image = await FilamentApp.instance!.decodeImage(imageData);
|
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(),
|
await image.getWidth(), await image.getHeight(),
|
||||||
textureFormat: TextureFormat.RGBA32F);
|
textureFormat: textureFormat);
|
||||||
await texture!
|
await texture.setLinearImage(image, pixelFormat, PixelDataType.FLOAT);
|
||||||
.setLinearImage(image, PixelDataFormat.RGBA, PixelDataType.FLOAT);
|
await setImageFromTexture(texture);
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
///
|
||||||
|
///
|
||||||
|
Future setImageFromTexture(Texture texture) async {
|
||||||
|
this.texture = texture;
|
||||||
sampler ??=
|
sampler ??=
|
||||||
await FilamentApp.instance!.createTextureSampler() as FFITextureSampler;
|
await FilamentApp.instance!.createTextureSampler() as FFITextureSampler;
|
||||||
|
await mi.setParameterInt("isCubeMap", 0);
|
||||||
await mi.setParameterTexture(
|
await mi.setParameterTexture(
|
||||||
"image", texture as FFITexture, sampler as FFITextureSampler);
|
"image", texture as FFITexture, sampler as FFITextureSampler);
|
||||||
await setBackgroundColor(1, 1, 1, 0);
|
await setBackgroundColor(1, 1, 1, 0);
|
||||||
await mi.setParameterInt("showImage", 1);
|
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) {
|
Future setCastShadows(bool castShadows) {
|
||||||
// TODO: implement setCastShadows
|
// TODO: implement setCastShadows
|
||||||
throw UnimplementedError();
|
throw UnimplementedError();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future setReceiveShadows(bool castShadows) {
|
Future setReceiveShadows(bool castShadows) {
|
||||||
@@ -346,6 +395,4 @@ class BackgroundImage extends ThermionAsset {
|
|||||||
Future<v64.Aabb3> getBoundingBox() {
|
Future<v64.Aabb3> getBoundingBox() {
|
||||||
throw UnimplementedError();
|
throw UnimplementedError();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import 'package:thermion_dart/src/filament/src/implementation/ffi_asset.dart';
|
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/src/filament/src/interface/scene.dart';
|
||||||
import 'package:thermion_dart/thermion_dart.dart';
|
import 'package:thermion_dart/thermion_dart.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
@@ -124,4 +125,27 @@ class FFIScene extends Scene {
|
|||||||
.info("Added stencil highlight for asset (entity ${asset.entity})");
|
.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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,3 @@
|
|||||||
import 'dart:typed_data';
|
|
||||||
|
|
||||||
import 'package:thermion_dart/src/bindings/bindings.dart';
|
|
||||||
import 'package:thermion_dart/thermion_dart.dart';
|
import 'package:thermion_dart/thermion_dart.dart';
|
||||||
|
|
||||||
class FFITexture extends Texture {
|
class FFITexture extends Texture {
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import 'package:thermion_dart/thermion_dart.dart';
|
||||||
|
|
||||||
|
abstract class Ktx1Bundle {
|
||||||
|
|
||||||
|
///
|
||||||
|
///
|
||||||
|
///
|
||||||
|
bool isCubemap();
|
||||||
|
|
||||||
|
///
|
||||||
|
///
|
||||||
|
///
|
||||||
|
Future<Texture> createTexture();
|
||||||
|
|
||||||
|
///
|
||||||
|
///
|
||||||
|
///
|
||||||
|
Float32List getSphericalHarmonics();
|
||||||
|
|
||||||
|
///
|
||||||
|
///
|
||||||
|
///
|
||||||
|
Future destroy();
|
||||||
|
}
|
||||||
@@ -1,7 +1,19 @@
|
|||||||
|
import 'package:vector_math/vector_math_64.dart';
|
||||||
|
|
||||||
enum LightType {
|
enum LightType {
|
||||||
SUN, //!< Directional light that also draws a sun's disk in the sky.
|
SUN, //!< Directional light that also draws a sun's disk in the sky.
|
||||||
DIRECTIONAL, //!< Directional light, emits light in a given direction.
|
DIRECTIONAL, //!< Directional light, emits light in a given direction.
|
||||||
POINT, //!< Point light, emits light from a position, in all directions.
|
POINT, //!< Point light, emits light from a position, in all directions.
|
||||||
FOCUSED_SPOT, //!< Physically correct spot light.
|
FOCUSED_SPOT, //!< Physically correct spot light.
|
||||||
SPOT,
|
SPOT,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
abstract class IndirectLight {
|
||||||
|
Future destroy() {
|
||||||
|
throw UnimplementedError();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future rotate(Matrix3 rotation) {
|
||||||
|
throw UnimplementedError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -21,18 +21,32 @@ abstract class Scene {
|
|||||||
///
|
///
|
||||||
Future removeEntity(ThermionEntity entity);
|
Future removeEntity(ThermionEntity entity);
|
||||||
|
|
||||||
|
|
||||||
/// Renders an outline around [entity] with the given color.
|
/// 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 g = 0.0,
|
||||||
double b = 0.0,
|
double b = 0.0,
|
||||||
int? entity,
|
int? entity,
|
||||||
int primitiveIndex = 0});
|
int primitiveIndex = 0});
|
||||||
|
|
||||||
/// Removes the outline around [entity]. Noop if there was no highlight.
|
/// Removes the outline around [entity]. Noop if there was no highlight.
|
||||||
///
|
///
|
||||||
///
|
///
|
||||||
Future removeStencilHighlight(ThermionAsset asset);
|
Future removeStencilHighlight(ThermionAsset asset);
|
||||||
|
|
||||||
|
///
|
||||||
|
///
|
||||||
|
///
|
||||||
|
Future setIndirectLight(IndirectLight? indirectLight) {
|
||||||
|
throw UnimplementedError();
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
///
|
||||||
|
///
|
||||||
|
Future<IndirectLight?> getIndirectLight() {
|
||||||
|
throw UnimplementedError();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'package:thermion_dart/src/filament/src/implementation/background_image.dart';
|
import 'package:thermion_dart/src/filament/src/implementation/background_image.dart';
|
||||||
|
import 'package:thermion_dart/src/filament/src/implementation/ffi_indirect_light.dart';
|
||||||
|
import 'package:thermion_dart/src/filament/src/implementation/ffi_ktx1_bundle.dart';
|
||||||
|
import 'package:thermion_dart/src/filament/src/implementation/ffi_swapchain.dart';
|
||||||
import 'package:thermion_dart/src/filament/src/implementation/ffi_texture.dart';
|
import 'package:thermion_dart/src/filament/src/implementation/ffi_texture.dart';
|
||||||
|
import 'package:thermion_dart/src/filament/src/interface/ktx1_bundle.dart';
|
||||||
import '../../../../filament/src/implementation/ffi_asset.dart';
|
import '../../../../filament/src/implementation/ffi_asset.dart';
|
||||||
import 'package:thermion_dart/src/filament/src/implementation/ffi_filament_app.dart';
|
import 'package:thermion_dart/src/filament/src/implementation/ffi_filament_app.dart';
|
||||||
import '../../../../filament/src/implementation/ffi_scene.dart';
|
import '../../../../filament/src/implementation/ffi_scene.dart';
|
||||||
@@ -204,7 +208,14 @@ class ThermionViewerFFI extends ThermionViewer {
|
|||||||
Future setBackgroundImage(String path, {bool fillHeight = false}) async {
|
Future setBackgroundImage(String path, {bool fillHeight = false}) async {
|
||||||
final imageData = await FilamentApp.instance!.loadResource(path);
|
final imageData = await FilamentApp.instance!.loadResource(path);
|
||||||
_backgroundImage ??= await BackgroundImage.create(this, scene);
|
_backgroundImage ??= await BackgroundImage.create(this, scene);
|
||||||
await _backgroundImage!.setImage(imageData);
|
bool isKtx = path.endsWith(".ktx");
|
||||||
|
if (isKtx) {
|
||||||
|
final bundle = await FFIKtx1Bundle.create(imageData);
|
||||||
|
await _backgroundImage!.setImageFromKtxBundle(bundle);
|
||||||
|
} else {
|
||||||
|
await _backgroundImage!.setImage(imageData);
|
||||||
|
}
|
||||||
|
return (_backgroundImage!.width!, _backgroundImage!.height!);
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
@@ -250,11 +261,9 @@ class ThermionViewerFFI extends ThermionViewer {
|
|||||||
|
|
||||||
_skyboxTextureUploadComplete =
|
_skyboxTextureUploadComplete =
|
||||||
withVoidCallback((requestId, onTextureUploadComplete) async {
|
withVoidCallback((requestId, onTextureUploadComplete) async {
|
||||||
var skyboxTexturePointer = await withPointerCallback<TTexture>((cb) {
|
var bundle = await FFIKtx1Bundle.create(data);
|
||||||
Texture_decodeKtxRenderThread(app.engine, data.address, data.length,
|
|
||||||
nullptr, requestId, onTextureUploadComplete, cb);
|
_skyboxTexture = await bundle.createTexture() as FFITexture;
|
||||||
});
|
|
||||||
_skyboxTexture = FFITexture(app.engine, skyboxTexturePointer);
|
|
||||||
|
|
||||||
_skybox = await withPointerCallback<TSkybox>((cb) {
|
_skybox = await withPointerCallback<TSkybox>((cb) {
|
||||||
Engine_buildSkyboxRenderThread(app.engine, _skyboxTexture!.pointer, cb);
|
Engine_buildSkyboxRenderThread(app.engine, _skyboxTexture!.pointer, cb);
|
||||||
@@ -267,8 +276,6 @@ class ThermionViewerFFI extends ThermionViewer {
|
|||||||
await completer.future;
|
await completer.future;
|
||||||
}
|
}
|
||||||
|
|
||||||
Pointer<TTexture>? _iblTexture;
|
|
||||||
Pointer<TIndirectLight>? _indirectLight;
|
|
||||||
Future? _iblTextureUploadComplete;
|
Future? _iblTextureUploadComplete;
|
||||||
|
|
||||||
///
|
///
|
||||||
@@ -288,23 +295,22 @@ class ThermionViewerFFI extends ThermionViewer {
|
|||||||
|
|
||||||
var data = await FilamentApp.instance!.loadResource(lightingPath);
|
var data = await FilamentApp.instance!.loadResource(lightingPath);
|
||||||
|
|
||||||
var harmonics = Float32List(27);
|
final bundle = await FFIKtx1Bundle.create(data);
|
||||||
|
|
||||||
_iblTexture = await withPointerCallback<TTexture>((cb) {
|
final texture = await bundle.createTexture();
|
||||||
Texture_decodeKtxRenderThread(app.engine, data.address, data.length,
|
final harmonics = bundle.getSphericalHarmonics();
|
||||||
harmonics.address, requestId, onTextureUploadComplete, cb);
|
|
||||||
});
|
final ibl = await FFIIndirectLight.fromIrradianceHarmonics(harmonics,
|
||||||
|
reflectionsTexture: texture, intensity: intensity);
|
||||||
|
|
||||||
|
await scene.setIndirectLight(ibl);
|
||||||
|
|
||||||
_indirectLight = await withPointerCallback<TIndirectLight>((cb) {
|
|
||||||
Engine_buildIndirectLightRenderThread(
|
|
||||||
app.engine, _iblTexture!, intensity, harmonics.address, cb);
|
|
||||||
});
|
|
||||||
if (FILAMENT_WASM) {
|
if (FILAMENT_WASM) {
|
||||||
//stackRestore(stackPtr);
|
//stackRestore(stackPtr);
|
||||||
data.free();
|
data.free();
|
||||||
}
|
}
|
||||||
data.free();
|
data.free();
|
||||||
Scene_setIndirectLight(scene.scene, _indirectLight!);
|
|
||||||
completer.complete();
|
completer.complete();
|
||||||
}).then((_) {
|
}).then((_) {
|
||||||
_iblTextureUploadComplete = null;
|
_iblTextureUploadComplete = null;
|
||||||
@@ -317,20 +323,9 @@ class ThermionViewerFFI extends ThermionViewer {
|
|||||||
///
|
///
|
||||||
@override
|
@override
|
||||||
Future rotateIbl(Matrix3 rotationMatrix) async {
|
Future rotateIbl(Matrix3 rotationMatrix) async {
|
||||||
if (_indirectLight == null) {
|
var ibl = await scene.getIndirectLight();
|
||||||
throw Exception("No IBL loaded");
|
if (ibl != null) {
|
||||||
}
|
await ibl.rotate(rotationMatrix);
|
||||||
|
|
||||||
late Pointer stackPtr;
|
|
||||||
if (FILAMENT_WASM) {
|
|
||||||
//stackPtr = stackSave();
|
|
||||||
}
|
|
||||||
|
|
||||||
IndirectLight_setRotation(_indirectLight!, rotationMatrix.storage.address);
|
|
||||||
|
|
||||||
if (FILAMENT_WASM) {
|
|
||||||
//stackRestore(stackPtr);
|
|
||||||
rotationMatrix.storage.free();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -366,24 +361,16 @@ class ThermionViewerFFI extends ThermionViewer {
|
|||||||
///
|
///
|
||||||
///
|
///
|
||||||
@override
|
@override
|
||||||
Future removeIbl() async {
|
Future removeIbl({bool destroy = true}) async {
|
||||||
|
var ibl = await scene.getIndirectLight();
|
||||||
|
await scene.setIndirectLight(null);
|
||||||
|
if (ibl != null && destroy) {
|
||||||
|
await ibl.destroy();
|
||||||
|
}
|
||||||
if (_iblTextureUploadComplete != null) {
|
if (_iblTextureUploadComplete != null) {
|
||||||
await _iblTextureUploadComplete!;
|
await _iblTextureUploadComplete!;
|
||||||
_iblTextureUploadComplete = null;
|
_iblTextureUploadComplete = null;
|
||||||
}
|
}
|
||||||
if (_indirectLight != null) {
|
|
||||||
Scene_setIndirectLight(scene.scene, nullptr);
|
|
||||||
await withVoidCallback(
|
|
||||||
(requestId, cb) => Engine_destroyIndirectLightRenderThread(
|
|
||||||
app.engine,
|
|
||||||
_indirectLight!,
|
|
||||||
requestId,
|
|
||||||
cb,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
_indirectLight = null;
|
|
||||||
_iblTexture = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final _lights = <ThermionEntity>{};
|
final _lights = <ThermionEntity>{};
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ abstract class ThermionViewer {
|
|||||||
Future loadSkybox(String skyboxPath);
|
Future loadSkybox(String skyboxPath);
|
||||||
|
|
||||||
///
|
///
|
||||||
/// Removes the skybox from the scene.
|
/// Removes the skybox from the scene and destroys all associated resources.
|
||||||
///
|
///
|
||||||
Future removeSkybox();
|
Future removeSkybox();
|
||||||
|
|
||||||
@@ -106,8 +106,10 @@ abstract class ThermionViewer {
|
|||||||
|
|
||||||
///
|
///
|
||||||
/// Removes the image-based light from the scene.
|
/// Removes the image-based light from the scene.
|
||||||
|
/// If [destroy] is true, the indirect light and all associated resources
|
||||||
|
/// (irradiance/reflection textures) will be destroyed.
|
||||||
///
|
///
|
||||||
Future removeIbl();
|
Future removeIbl({bool destroy = true});
|
||||||
|
|
||||||
///
|
///
|
||||||
/// Adds a direct light to the scene.
|
/// Adds a direct light to the scene.
|
||||||
@@ -123,7 +125,7 @@ abstract class ThermionViewer {
|
|||||||
Future removeLight(ThermionEntity light);
|
Future removeLight(ThermionEntity light);
|
||||||
|
|
||||||
///
|
///
|
||||||
/// Remove all lights (excluding IBL) from the scene.
|
/// Remove all direct lights from the scene.
|
||||||
///
|
///
|
||||||
Future destroyLights();
|
Future destroyLights();
|
||||||
|
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ extern "C"
|
|||||||
typedef struct TGltfResourceLoader TGltfResourceLoader;
|
typedef struct TGltfResourceLoader TGltfResourceLoader;
|
||||||
typedef struct TFilamentAsset TFilamentAsset;
|
typedef struct TFilamentAsset TFilamentAsset;
|
||||||
typedef struct TColorGrading TColorGrading;
|
typedef struct TColorGrading TColorGrading;
|
||||||
|
typedef struct TKtx1Bundle TKtx1Bundle;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
double x;
|
double x;
|
||||||
|
|||||||
@@ -260,11 +260,24 @@ EMSCRIPTEN_KEEPALIVE uint32_t Texture_getHeight(TTexture *tTexture, uint32_t lev
|
|||||||
EMSCRIPTEN_KEEPALIVE uint32_t Texture_getDepth(TTexture *tTexture, uint32_t level);
|
EMSCRIPTEN_KEEPALIVE uint32_t Texture_getDepth(TTexture *tTexture, uint32_t level);
|
||||||
EMSCRIPTEN_KEEPALIVE TTextureUsage Texture_getUsage(TTexture *tTexture, uint32_t level);
|
EMSCRIPTEN_KEEPALIVE TTextureUsage Texture_getUsage(TTexture *tTexture, uint32_t level);
|
||||||
EMSCRIPTEN_KEEPALIVE void Texture_generateMipMaps(TTexture *tTexture, TEngine *tEngine);
|
EMSCRIPTEN_KEEPALIVE void Texture_generateMipMaps(TTexture *tTexture, TEngine *tEngine);
|
||||||
EMSCRIPTEN_KEEPALIVE TTexture* Texture_decodeKtx(
|
EMSCRIPTEN_KEEPALIVE TKtx1Bundle* Ktx1Bundle_create(
|
||||||
TEngine *tEngine,
|
|
||||||
uint8_t *ktxData,
|
uint8_t *ktxData,
|
||||||
size_t length,
|
size_t length
|
||||||
float *sphericalHarmonics,
|
);
|
||||||
|
EMSCRIPTEN_KEEPALIVE void Ktx1Bundle_getSphericalHarmonics(
|
||||||
|
TKtx1Bundle *tBundle,
|
||||||
|
float *harmonics
|
||||||
|
);
|
||||||
|
EMSCRIPTEN_KEEPALIVE bool Ktx1Bundle_isCubemap(
|
||||||
|
TKtx1Bundle *tBundle
|
||||||
|
);
|
||||||
|
EMSCRIPTEN_KEEPALIVE void Ktx1Bundle_destroy(
|
||||||
|
TKtx1Bundle *tBundle
|
||||||
|
);
|
||||||
|
|
||||||
|
EMSCRIPTEN_KEEPALIVE TTexture* Ktx1Reader_createTexture(
|
||||||
|
TEngine *tEngine,
|
||||||
|
TKtx1Bundle *tBundle,
|
||||||
uint32_t requestId,
|
uint32_t requestId,
|
||||||
VoidCallback onTextureUploadComplete
|
VoidCallback onTextureUploadComplete
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ namespace thermion
|
|||||||
void (*onComplete)(TTexture*)
|
void (*onComplete)(TTexture*)
|
||||||
);
|
);
|
||||||
void Texture_generateMipMapsRenderThread(TTexture *tTexture, TEngine *tEngine, uint32_t requestId, VoidCallback onComplete);
|
void Texture_generateMipMapsRenderThread(TTexture *tTexture, TEngine *tEngine, uint32_t requestId, VoidCallback onComplete);
|
||||||
void Texture_decodeKtxRenderThread(TEngine *tEngine, uint8_t *ktxData, size_t length, float *sphericalHarmonics, uint32_t requestId, VoidCallback onTextureUploadComplete, void (*onComplete)(TTexture *));
|
void Ktx1Reader_createTextureRenderThread(TEngine *tEngine, TKtx1Bundle *tBundle, uint32_t requestId, VoidCallback onTextureUploadComplete, void (*onComplete)(TTexture *));
|
||||||
|
|
||||||
void Engine_destroyTextureRenderThread(TEngine *engine, TTexture* tTexture, uint32_t requestId, VoidCallback onComplete);
|
void Engine_destroyTextureRenderThread(TEngine *engine, TTexture* tTexture, uint32_t requestId, VoidCallback onComplete);
|
||||||
void Engine_createFenceRenderThread(TEngine *tEngine, void (*onComplete)(TFence*));
|
void Engine_createFenceRenderThread(TEngine *tEngine, void (*onComplete)(TFence*));
|
||||||
|
|||||||
@@ -94,33 +94,49 @@ namespace thermion
|
|||||||
return reinterpret_cast<TLinearImage *>(linearImage);
|
return reinterpret_cast<TLinearImage *>(linearImage);
|
||||||
}
|
}
|
||||||
|
|
||||||
EMSCRIPTEN_KEEPALIVE TTexture *Texture_decodeKtx(
|
EMSCRIPTEN_KEEPALIVE TKtx1Bundle *Ktx1Bundle_create(uint8_t *ktxData, size_t length)
|
||||||
TEngine *tEngine,
|
|
||||||
uint8_t *ktxData,
|
|
||||||
size_t length,
|
|
||||||
float *sphericalHarmonics,
|
|
||||||
uint32_t requestId,
|
|
||||||
VoidCallback onTextureUploadComplete)
|
|
||||||
{
|
{
|
||||||
|
auto *bundle = new image::Ktx1Bundle(ktxData, static_cast<uint32_t>(length));
|
||||||
|
return reinterpret_cast<TKtx1Bundle *>(bundle);
|
||||||
|
}
|
||||||
|
|
||||||
auto engine = reinterpret_cast<filament::Engine *>(tEngine);
|
EMSCRIPTEN_KEEPALIVE bool Ktx1Bundle_isCubemap(TKtx1Bundle *tBundle)
|
||||||
|
{
|
||||||
|
auto *bundle = reinterpret_cast<image::Ktx1Bundle *>(tBundle);
|
||||||
|
return bundle->isCubemap();
|
||||||
|
}
|
||||||
|
|
||||||
auto copy = new std::vector<uint8_t>(ktxData, ktxData + length);
|
EMSCRIPTEN_KEEPALIVE void Ktx1Bundle_destroy(
|
||||||
image::Ktx1Bundle *bundle =
|
TKtx1Bundle *tBundle)
|
||||||
new image::Ktx1Bundle(static_cast<const uint8_t *>(copy->data()),
|
{
|
||||||
static_cast<uint32_t>(length));
|
auto *bundle = reinterpret_cast<image::Ktx1Bundle *>(tBundle);
|
||||||
|
delete bundle;
|
||||||
|
}
|
||||||
|
|
||||||
|
EMSCRIPTEN_KEEPALIVE void Ktx1Bundle_getSphericalHarmonics(TKtx1Bundle *tBundle, float *sphericalHarmonics)
|
||||||
|
{
|
||||||
|
auto *bundle = reinterpret_cast<image::Ktx1Bundle *>(tBundle);
|
||||||
filament::math::float3 harmonics[9];
|
filament::math::float3 harmonics[9];
|
||||||
if (sphericalHarmonics)
|
if (sphericalHarmonics)
|
||||||
{
|
{
|
||||||
bundle->getSphericalHarmonics(harmonics);
|
bundle->getSphericalHarmonics(harmonics);
|
||||||
memcpy(sphericalHarmonics, harmonics, 27 * sizeof(float));
|
memcpy(sphericalHarmonics, harmonics, 27 * sizeof(float));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
EMSCRIPTEN_KEEPALIVE TTexture *Ktx1Reader_createTexture(
|
||||||
|
TEngine *tEngine,
|
||||||
|
TKtx1Bundle *tBundle,
|
||||||
|
uint32_t requestId,
|
||||||
|
VoidCallback onTextureUploadComplete)
|
||||||
|
{
|
||||||
|
|
||||||
|
auto engine = reinterpret_cast<filament::Engine *>(tEngine);
|
||||||
|
|
||||||
|
auto *bundle = reinterpret_cast<image::Ktx1Bundle *>(tBundle);
|
||||||
|
|
||||||
std::vector<void *> *callbackData = new std::vector<void *>{
|
std::vector<void *> *callbackData = new std::vector<void *>{
|
||||||
reinterpret_cast<void *>(onTextureUploadComplete),
|
reinterpret_cast<void *>(onTextureUploadComplete),
|
||||||
reinterpret_cast<void *>(bundle),
|
|
||||||
reinterpret_cast<void *>(copy),
|
|
||||||
reinterpret_cast<void *>(requestId)};
|
reinterpret_cast<void *>(requestId)};
|
||||||
|
|
||||||
auto *texture =
|
auto *texture =
|
||||||
@@ -130,9 +146,7 @@ namespace thermion
|
|||||||
std::vector<void*>* vec = (std::vector<void*>*)userdata;
|
std::vector<void*>* vec = (std::vector<void*>*)userdata;
|
||||||
|
|
||||||
void *callbackPtr = vec->at(0);
|
void *callbackPtr = vec->at(0);
|
||||||
image::Ktx1Bundle *bundle = reinterpret_cast<image::Ktx1Bundle *>(vec->at(1));
|
uintptr_t requestId = (uintptr_t)vec->at(2);
|
||||||
std::vector<uint8_t> *copy = reinterpret_cast<std::vector<uint8_t>*>(vec->at(2));
|
|
||||||
uintptr_t requestId = (uintptr_t)vec->at(3);
|
|
||||||
|
|
||||||
delete vec;
|
delete vec;
|
||||||
|
|
||||||
@@ -140,9 +154,7 @@ namespace thermion
|
|||||||
{
|
{
|
||||||
auto callback = ((VoidCallback)callbackPtr);
|
auto callback = ((VoidCallback)callbackPtr);
|
||||||
callback(requestId);
|
callback(requestId);
|
||||||
}
|
} },
|
||||||
delete bundle;
|
|
||||||
delete copy; },
|
|
||||||
(void *)callbackData);
|
(void *)callbackData);
|
||||||
return reinterpret_cast<TTexture *>(texture);
|
return reinterpret_cast<TTexture *>(texture);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -864,7 +864,9 @@ extern "C"
|
|||||||
#ifdef EMSCRIPTEN
|
#ifdef EMSCRIPTEN
|
||||||
static std::unordered_map<uint32_t, std::function<void(int32_t)>> _emscriptenWrappers;
|
static std::unordered_map<uint32_t, std::function<void(int32_t)>> _emscriptenWrappers;
|
||||||
|
|
||||||
static void Emscripten_voidCallback(int32_t requestId) {
|
EMSCRIPTEN_KEEPALIVE static void Emscripten_voidCallback(int32_t requestId) {
|
||||||
|
Log("Emscripten_voidCallback: requestId %d", requestId);
|
||||||
|
|
||||||
auto it = _emscriptenWrappers.find(requestId);
|
auto it = _emscriptenWrappers.find(requestId);
|
||||||
if (it != _emscriptenWrappers.end()) {
|
if (it != _emscriptenWrappers.end()) {
|
||||||
it->second(requestId);
|
it->second(requestId);
|
||||||
@@ -876,19 +878,23 @@ extern "C"
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
EMSCRIPTEN_KEEPALIVE void Texture_decodeKtxRenderThread(
|
EMSCRIPTEN_KEEPALIVE void Ktx1Reader_createTextureRenderThread(
|
||||||
TEngine *tEngine, uint8_t *ktxData, size_t length, float *sphericalHarmonics, uint32_t requestId, VoidCallback onTextureUploadComplete, void (*onComplete)(TTexture *)) {
|
TEngine *tEngine, TKtx1Bundle *tBundle, uint32_t requestId, VoidCallback onTextureUploadComplete, void (*onComplete)(TTexture *)) {
|
||||||
|
|
||||||
|
#ifdef EMSCRIPTEN
|
||||||
|
if(onTextureUploadComplete) {
|
||||||
|
_emscriptenWrappers[requestId] = [=](int32_t requestId) {
|
||||||
|
PROXY(onTextureUploadComplete(requestId));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
#endif
|
||||||
std::packaged_task<void()> lambda(
|
std::packaged_task<void()> lambda(
|
||||||
[=]() mutable
|
[=]() mutable
|
||||||
{
|
{
|
||||||
#ifdef EMSCRIPTEN
|
#ifdef EMSCRIPTEN
|
||||||
std::function<void(int32_t)> wrapper = [=](int32_t requestId) {
|
auto *texture = Ktx1Reader_createTexture(tEngine, tBundle, requestId, onTextureUploadComplete ? Emscripten_voidCallback : nullptr);
|
||||||
PROXY(onTextureUploadComplete(requestId));
|
|
||||||
};
|
|
||||||
_emscriptenWrappers[requestId] = wrapper;
|
|
||||||
auto *texture = Texture_decodeKtx(tEngine, ktxData, length, sphericalHarmonics, requestId, Emscripten_voidCallback);
|
|
||||||
#else
|
#else
|
||||||
auto *texture = Texture_decodeKtx(tEngine, ktxData, length, sphericalHarmonics, requestId, onTextureUploadComplete);
|
auto *texture = Ktx1Reader_createTexture(tEngine, tBundle, requestId, onTextureUploadComplete);
|
||||||
#endif
|
#endif
|
||||||
PROXY(onComplete(texture));
|
PROXY(onComplete(texture));
|
||||||
});
|
});
|
||||||
|
|||||||
33
thermion_dart/test/image_tests.dart
Normal file
33
thermion_dart/test/image_tests.dart
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
import 'package:thermion_dart/src/filament/src/interface/filament_app.dart';
|
||||||
|
import 'helpers.dart';
|
||||||
|
|
||||||
|
void main() async {
|
||||||
|
final testHelper = TestHelper("images");
|
||||||
|
await testHelper.setup();
|
||||||
|
test('decode KTX', () async {
|
||||||
|
await testHelper.withViewer((viewer) async {
|
||||||
|
await FilamentApp.instance!.decodeKtx(
|
||||||
|
File("${testHelper.testDir}/assets/default_env_skybox.ktx").readAsBytesSync());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('set background color', () async {
|
||||||
|
await testHelper.withViewer((viewer) async {
|
||||||
|
await viewer.setBackgroundColor(0, 1, 0, 1);
|
||||||
|
await testHelper.capture(viewer.view, "background_green");
|
||||||
|
await viewer.setBackgroundColor(1, 0, 0, 1);
|
||||||
|
await testHelper.capture(viewer.view, "background_red");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('set background image', () async {
|
||||||
|
await testHelper.withViewer((viewer) async {
|
||||||
|
await viewer.setBackgroundImage(
|
||||||
|
"file://${testHelper.testDir}/assets/cube_texture_512x512.png");
|
||||||
|
await testHelper.capture(viewer.view, "background_image");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user