Compare commits

..

1 Commits

Author SHA1 Message Date
Nick Fisher
3dce7a61a8 chore(release): publish packages
- thermion_dart@0.2.1-dev.0.0.12
 - thermion_flutter@0.2.1-dev.12
 - thermion_flutter_web@0.2.0+3
 - thermion_flutter_platform_interface@0.2.1-dev.12
 - thermion_flutter_ffi@0.2.1-dev.12
2024-10-25 11:25:11 +08:00
40 changed files with 1124 additions and 487 deletions

View File

@@ -13,72 +13,30 @@ on:
jobs: jobs:
thermion_dart: thermion_dart:
name: thermion_dart name: thermion_dart
runs-on: windows-2019 runs-on: macos-13
defaults: defaults:
run: run:
working-directory: thermion_dart # Adjust this path working-directory: thermion_dart # Adjust this path
shell: bash # This helps ensure consistent behavior across platforms
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
# Enable debug logging for Actions
- name: Enable debug logging - uses: dart-lang/setup-dart@9a04e6d73cca37bd455e0608d7e5092f881fd603
run: echo "ACTIONS_STEP_DEBUG=true" >> $GITHUB_ENV
# Setup Visual Studio environment
- name: Setup MSVC
uses: ilammy/msvc-dev-cmd@v1
with: with:
arch: x64 sdk: '3.6.0-326.0.dev'
- uses: subosito/flutter-action@v2 channel: 'dev'
with:
flutter-version: '3.26.0-0.1.pre' # Specify Flutter version
channel: 'master' # or 'beta', 'dev', 'master'
cache: true # Caches dependencies between runs
architecture: x64 # Explicitly specify x64 architecture
- name: Install dependencies - name: Install dependencies
run: flutter pub get # even though this is a Dart package, it has as dev_dependency on objective_c for testing which for some reason has a Flutter dependency run: dart pub get
# Ensure Visual C++ runtime is available
- name: Install Visual C++ redistributable
run: |
choco install visualstudio2019-workload-vctools -y
choco install vcredist140 -y
# Uncomment this step to verify the use of 'dart format' on each commit. # Uncomment this step to verify the use of 'dart format' on each commit.
# - name: Verify formatting # - name: Verify formatting
# run: dart format --output=none --set-exit-if-changed . # run: dart format --output=none --set-exit-if-changed .
#- name: Analyze project source - name: Analyze project source
# run: dart analyze run: dart analyze
- name: Build and Test - name: Run tests
shell: cmd run: dart --enable-experiment=native-assets test
run: |
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat"
dart --enable-experiment=native-assets test test/dummy_tests.dart
# Upload logs on failure
- name: Upload logs
if: failure() || steps.build.outcome == 'failure'
uses: actions/upload-artifact@v4
with:
name: build-logs
path: |
D:\a\thermion\thermion\thermion_dart\.dart_tool\thermion_dart\log\build.log
# /Users/runner/work/thermion/thermion/thermion_dart//.dart_tool/thermion_dart/log/build.log
retention-days: 5
# Capture crash dumps if they exist
- name: Collect crash dumps
if: failure()
uses: actions/upload-artifact@v3
with:
name: crash-dumps
path: |
${{ runner.temp }}/*.dmp
${{ env.LOCALAPPDATA }}/Temp/*.dmp
${{ env.LOCALAPPDATA }}/CrashDumps/*.dmp
# thermion_flutter: # thermion_flutter:
# name: thermion_flutter # name: thermion_flutter

View File

@@ -3,81 +3,6 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## 2024-10-30
### Changes
---
Packages with breaking changes:
- There are no breaking changes in this release.
Packages with other changes:
- [`thermion_dart` - `v0.2.1-dev.0.0.14`](#thermion_dart---v021-dev0014)
- [`thermion_flutter` - `v0.2.1-dev.14`](#thermion_flutter---v021-dev14)
- [`thermion_flutter_platform_interface` - `v0.2.1-dev.14`](#thermion_flutter_platform_interface---v021-dev14)
- [`thermion_flutter_ffi` - `v0.2.1-dev.14`](#thermion_flutter_ffi---v021-dev14)
- [`thermion_flutter_web` - `v0.2.0+5`](#thermion_flutter_web---v0205)
Packages with dependency updates only:
> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project.
- `thermion_flutter` - `v0.2.1-dev.14`
- `thermion_flutter_platform_interface` - `v0.2.1-dev.14`
- `thermion_flutter_ffi` - `v0.2.1-dev.14`
- `thermion_flutter_web` - `v0.2.0+5`
---
#### `thermion_dart` - `v0.2.1-dev.0.0.14`
- **FIX**: reduce size of pick functor for compatibility with armeabi-v7a.
- **FEAT**: sanitize file paths in build.dart for Windows compatibility.
- **FEAT**: pass through fragment coordinates for picking.
- **FEAT**: pass through fragment coordinates for picking.
## 2024-10-29
### Changes
---
Packages with breaking changes:
- There are no breaking changes in this release.
Packages with other changes:
- [`thermion_dart` - `v0.2.1-dev.0.0.13`](#thermion_dart---v021-dev0013)
- [`thermion_flutter_web` - `v0.2.0+4`](#thermion_flutter_web---v0204)
- [`thermion_flutter` - `v0.2.1-dev.13`](#thermion_flutter---v021-dev13)
- [`thermion_flutter_ffi` - `v0.2.1-dev.13`](#thermion_flutter_ffi---v021-dev13)
- [`thermion_flutter_platform_interface` - `v0.2.1-dev.13`](#thermion_flutter_platform_interface---v021-dev13)
Packages with dependency updates only:
> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project.
- `thermion_flutter_web` - `v0.2.0+4`
- `thermion_flutter` - `v0.2.1-dev.13`
- `thermion_flutter_ffi` - `v0.2.1-dev.13`
- `thermion_flutter_platform_interface` - `v0.2.1-dev.13`
---
#### `thermion_dart` - `v0.2.1-dev.0.0.13`
- **FIX**: properly pass through loadResourcesAsync flag for loadGlbFromBuffer.
- **FIX**: properly pass through loadResourcesAsync flag for loadGlbFromBuffer.
- **FEAT**: pass through fragment coordinates for picking.
- **FEAT**: add SCALE2_MOVE InputType.
- **FEAT**: add SCALE2_MOVE InputType.
## 2024-10-25 ## 2024-10-25
### Changes ### Changes

View File

@@ -22,16 +22,6 @@ dev_dependencies:
sdk: flutter sdk: flutter
flutter_lints: ^4.0.0 flutter_lints: ^4.0.0
dependency_overrides:
thermion_dart:
path: ../../../thermion_dart
thermion_flutter:
path: ../../../thermion_flutter/thermion_flutter
thermion_flutter_ffi:
path: ../../../thermion_flutter/thermion_flutter_ffi
thermion_flutter_platform_interface:
path: ../../../thermion_flutter/thermion_flutter_platform_interface
flutter: flutter:
uses-material-design: true uses-material-design: true
assets: assets:

View File

@@ -87,7 +87,6 @@
93162ABED09E78126BD3CDE5 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 93162ABED09E78126BD3CDE5 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
9491FF37DFB0425E4EA0B397 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 9491FF37DFB0425E4EA0B397 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = "<group>"; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = "<group>"; };
DB0270912CD09BDD00E05CE4 /* RunnerDebug.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = RunnerDebug.entitlements; sourceTree = "<group>"; };
E5EDF04F05E94A7EC21CBB16 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = "<group>"; }; E5EDF04F05E94A7EC21CBB16 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = "<group>"; };
/* End PBXFileReference section */ /* End PBXFileReference section */
@@ -189,7 +188,6 @@
33FAB671232836740065AC1E /* Runner */ = { 33FAB671232836740065AC1E /* Runner */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
DB0270912CD09BDD00E05CE4 /* RunnerDebug.entitlements */,
33CC10F02044A3C60003C045 /* AppDelegate.swift */, 33CC10F02044A3C60003C045 /* AppDelegate.swift */,
33CC11122044BFA00003C045 /* MainFlutterWindow.swift */, 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */,
33E51913231747F40026EE4D /* DebugProfile.entitlements */, 33E51913231747F40026EE4D /* DebugProfile.entitlements */,
@@ -705,7 +703,7 @@
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/RunnerDebug.entitlements; CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES; COMBINE_HIDPI_IMAGES = YES;
INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_FILE = Runner/Info.plist;

View File

@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.allow-jit</key>
<true/>
</dict>
</plist>

View File

@@ -16,16 +16,6 @@ dependencies:
web: 1.0.0 web: 1.0.0
thermion_flutter: ^0.2.1-dev.7 thermion_flutter: ^0.2.1-dev.7
dependency_overrides:
thermion_dart:
path: ../../../thermion_dart
thermion_flutter:
path: ../../../thermion_flutter/thermion_flutter
thermion_flutter_ffi:
path: ../../../thermion_flutter/thermion_flutter_ffi
thermion_flutter_platform_interface:
path: ../../../thermion_flutter/thermion_flutter_platform_interface
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter

View File

@@ -1,18 +1,3 @@
## 0.2.1-dev.0.0.14
- **FIX**: reduce size of pick functor for compatibility with armeabi-v7a.
- **FEAT**: sanitize file paths in build.dart for Windows compatibility.
- **FEAT**: pass through fragment coordinates for picking.
- **FEAT**: pass through fragment coordinates for picking.
## 0.2.1-dev.0.0.13
- **FIX**: properly pass through loadResourcesAsync flag for loadGlbFromBuffer.
- **FIX**: properly pass through loadResourcesAsync flag for loadGlbFromBuffer.
- **FEAT**: pass through fragment coordinates for picking.
- **FEAT**: add SCALE2_MOVE InputType.
- **FEAT**: add SCALE2_MOVE InputType.
## 0.2.1-dev.0.0.12 ## 0.2.1-dev.0.0.12
- **FIX**: properly pass through loadResourcesAsync flag for loadGlbFromBuffer. - **FIX**: properly pass through loadResourcesAsync flag for loadGlbFromBuffer.

View File

@@ -3,15 +3,11 @@ import 'package:archive/archive.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:native_assets_cli/native_assets_cli.dart'; import 'package:native_assets_cli/native_assets_cli.dart';
import 'package:native_toolchain_c/native_toolchain_c.dart'; import 'package:native_toolchain_c/native_toolchain_c.dart';
import 'package:path/path.dart' as path;
void main(List<String> args) async { void main(List<String> args) async {
await build(args, (config, output) async { await build(args, (config, output) async {
var logFile = File(
var pkgRootFilePath = config.packageRoot.toFilePath(windows: Platform.isWindows); "${config.packageRoot.toFilePath()}.dart_tool/thermion_dart/log/build.log");
var logPath = path.join(pkgRootFilePath, ".dart_tool", "thermion_dart", "log", "build.log");
var logFile = File(logPath);
if (!logFile.parent.existsSync()) { if (!logFile.parent.existsSync()) {
logFile.parent.createSync(recursive: true); logFile.parent.createSync(recursive: true);
} }
@@ -54,16 +50,16 @@ void main(List<String> args) async {
final packageName = config.packageName; final packageName = config.packageName;
final sources = Directory(path.join(pkgRootFilePath, "native", "src")) final sources = Directory("${config.packageRoot.toFilePath()}/native/src")
.listSync(recursive: true) .listSync(recursive: true)
.whereType<File>() .whereType<File>()
.map((f) => f.path) .map((f) => f.path)
.toList(); .toList();
sources.addAll([ sources.addAll([
path.join(pkgRootFilePath, "native", "include", "material", "gizmo_material.c"), "${config.packageRoot.toFilePath()}/native/include/material/gizmo_material.c",
path.join(pkgRootFilePath, "native", "include", "material", "image.c"), "${config.packageRoot.toFilePath()}/native/include/material/image.c",
path.join(pkgRootFilePath, "native", "include", "material", "grid.c"), "${config.packageRoot.toFilePath()}/native/include/material/grid.c",
path.join(pkgRootFilePath, "native", "include", "material", "unlit.c"), "${config.packageRoot.toFilePath()}/native/include/material/unlit.c",
]); ]);
var libs = [ var libs = [
@@ -95,9 +91,9 @@ void main(List<String> args) async {
]; ];
if (platform == "windows") { if (platform == "windows") {
libDir = Directory(libDir).uri.toFilePath(windows: config.targetOS == OS.windows); libDir = Directory(libDir).uri.toFilePath();
libs = libs.map((lib) => path.join(libDir, "${lib}.lib")).toList(); libs = libs.map((lib) => "${libDir}${lib}.lib").toList();
libs.addAll([path.join(libDir,"bluevk.lib"), path.join(libDir,"bluegl.lib")]); libs.addAll(["${libDir}bluevk.lib", "${libDir}bluegl.lib"]);
libs.addAll([ libs.addAll([
"gdi32.lib", "gdi32.lib",
"user32.lib", "user32.lib",
@@ -176,8 +172,8 @@ void main(List<String> args) async {
if (platform != "windows") ...libs.map((lib) => "-l$lib"), if (platform != "windows") ...libs.map((lib) => "-l$lib"),
if (platform != "windows") "-L$libDir", if (platform != "windows") "-L$libDir",
if (platform == "windows") ...[ if (platform == "windows") ...[
"/I${path.join(pkgRootFilePath, "native", "include")}", "/I${config.packageRoot.toFilePath()}\\native\\include",
"/I${path.join(pkgRootFilePath, "native", "include", "filament")}", "/I${config.packageRoot.toFilePath()}native\\include\\filament",
...sources, ...sources,
'/link', '/link',
"/LIBPATH:$libDir", "/LIBPATH:$libDir",
@@ -236,7 +232,8 @@ void main(List<String> args) async {
name: "thermion_dart.dll", name: "thermion_dart.dll",
linkMode: DynamicLoadingBundled(), linkMode: DynamicLoadingBundled(),
os: config.targetOS, os: config.targetOS,
file: Uri.file(path.join(pkgRootFilePath, "thermion_dart.dll")), file: Uri.file(
config.outputDirectory.toFilePath() + "/thermion_dart.dll"),
architecture: config.targetArchitecture), architecture: config.targetArchitecture),
linkInPackage: config.packageName); linkInPackage: config.packageName);
} }
@@ -266,9 +263,9 @@ Future<Directory> getLibDir(BuildConfig config, Logger logger) async {
if (platform == "windows") { if (platform == "windows") {
mode = config.buildMode == BuildMode.debug ? "debug" : "release"; mode = config.buildMode == BuildMode.debug ? "debug" : "release";
} }
var libDir = Directory( var libDir = Directory(
path.join(config.packageRoot.toFilePath(windows:Platform.isWindows), ".dart_tool", "thermion_dart", "lib", _FILAMENT_VERSION, platform, mode)); "${config.packageRoot.toFilePath()}/.dart_tool/thermion_dart/lib/${_FILAMENT_VERSION}/$platform/$mode/");
if (platform == "android") { if (platform == "android") {
final archExtension = switch (config.targetArchitecture) { final archExtension = switch (config.targetArchitecture) {
@@ -278,7 +275,7 @@ Future<Directory> getLibDir(BuildConfig config, Logger logger) async {
Architecture.ia32 => "x86", Architecture.ia32 => "x86",
_ => throw FormatException('Invalid') _ => throw FormatException('Invalid')
}; };
libDir = Directory(path.join(libDir.path, archExtension)); libDir = Directory("${libDir.path}/$archExtension/");
} else if (platform == "windows") { } else if (platform == "windows") {
if (config.targetArchitecture != Architecture.x64) { if (config.targetArchitecture != Architecture.x64) {
throw Exception( throw Exception(
@@ -295,8 +292,8 @@ Future<Directory> getLibDir(BuildConfig config, Logger logger) async {
// We will write an empty file called success to the unzip directory after successfully downloading/extracting the prebuilt libraries. // We will write an empty file called success to the unzip directory after successfully downloading/extracting the prebuilt libraries.
// If this file already exists, we assume everything has been successfully extracted and skip // If this file already exists, we assume everything has been successfully extracted and skip
final unzipDir = platform == "android" ? libDir.parent.path : libDir.path; final unzipDir = platform == "android" ? libDir.parent.path : libDir.path;
final successToken = File(path.join(unzipDir, "success")); final successToken = File("$unzipDir/success");
final libraryZip = File(path.join(unzipDir, filename)); final libraryZip = File("$unzipDir/$filename");
if (!successToken.existsSync()) { if (!successToken.existsSync()) {
if (libraryZip.existsSync()) { if (libraryZip.existsSync()) {

View File

@@ -29,7 +29,7 @@ class FFIGizmo extends BaseGizmo {
} }
void _onPickResult(DartEntityId entityId, int x, int y, Pointer<TView> view) { void _onPickResult(DartEntityId entityId, int x, int y, Pointer<TView> view) {
_callback?.call((entity: entityId, x: x, y: y, depth: 0, fragX: 0, fragY: 0, fragZ: 0)); _callback?.call((entity: entityId, x: x, y: y));
} }
/// ///

View File

@@ -150,15 +150,8 @@ external void Viewer_setViewRenderable(
ffi.Int, ffi.Int,
ffi.Pointer< ffi.Pointer<
ffi.NativeFunction< ffi.NativeFunction<
ffi.Void Function( ffi.Void Function(EntityId entityId, ffi.Int x, ffi.Int y,
EntityId entityId, ffi.Pointer<TView> tView)>>)>(isLeaf: true)
ffi.Int x,
ffi.Int y,
ffi.Pointer<TView> tView,
ffi.Float depth,
ffi.Float fragX,
ffi.Float fragY,
ffi.Float fragZ)>>)>(isLeaf: true)
external void Viewer_pick( external void Viewer_pick(
ffi.Pointer<TViewer> viewer, ffi.Pointer<TViewer> viewer,
ffi.Pointer<TView> tView, ffi.Pointer<TView> tView,
@@ -166,24 +159,11 @@ external void Viewer_pick(
int y, int y,
ffi.Pointer< ffi.Pointer<
ffi.NativeFunction< ffi.NativeFunction<
ffi.Void Function( ffi.Void Function(EntityId entityId, ffi.Int x, ffi.Int y,
EntityId entityId, ffi.Pointer<TView> tView)>>
ffi.Int x,
ffi.Int y,
ffi.Pointer<TView> tView,
ffi.Float depth,
ffi.Float fragX,
ffi.Float fragY,
ffi.Float fragZ)>>
callback, callback,
); );
@ffi.Native<ffi.Bool Function(ffi.Pointer<TViewer>, EntityId)>(isLeaf: true)
external bool Viewer_isNonPickableEntity(
ffi.Pointer<TViewer> viewer,
int entityId,
);
@ffi.Native<ffi.Pointer<TEngine> Function(ffi.Pointer<TViewer>)>(isLeaf: true) @ffi.Native<ffi.Pointer<TEngine> Function(ffi.Pointer<TViewer>)>(isLeaf: true)
external ffi.Pointer<TEngine> Viewer_getEngine( external ffi.Pointer<TEngine> Viewer_getEngine(
ffi.Pointer<TViewer> viewer, ffi.Pointer<TViewer> viewer,
@@ -438,6 +418,23 @@ external ffi.Pointer<TMaterialInstance> create_material_instance(
TMaterialKey materialConfig, TMaterialKey materialConfig,
); );
@ffi.Native<
ffi.Pointer<TMaterialInstance> Function(
ffi.Pointer<TSceneManager>)>(isLeaf: true)
external ffi.Pointer<TMaterialInstance> SceneManager_createUnlitMaterialInstance(
ffi.Pointer<TSceneManager> sceneManager,
);
@ffi.Native<
ffi.Pointer<TMaterialInstance> Function(
ffi.Pointer<TSceneManager>, ffi.Pointer<ffi.NativeFunction<ffi.Void Function(ffi.Pointer<TMaterialInstance>)>>)>(isLeaf: true)
external ffi.Pointer<TMaterialInstance> SceneManager_createUnlitMaterialInstanceRenderThread(
ffi.Pointer<TSceneManager> sceneManager,
ffi.Pointer<ffi.NativeFunction<ffi.Void Function(ffi.Pointer<TMaterialInstance>)>>
onComplete
);
@ffi.Native< @ffi.Native<
ffi.Void Function(ffi.Pointer<TSceneManager>, ffi.Void Function(ffi.Pointer<TSceneManager>,
ffi.Pointer<TMaterialInstance>)>(isLeaf: true) ffi.Pointer<TMaterialInstance>)>(isLeaf: true)
@@ -621,43 +618,6 @@ external int get_bone(
int boneIndex, int boneIndex,
); );
@ffi.Native<
EntityId Function(
ffi.Pointer<TSceneManager>,
ffi.Pointer<ffi.Float>,
ffi.Int,
ffi.Pointer<ffi.Float>,
ffi.Int,
ffi.Pointer<ffi.Float>,
ffi.Int,
ffi.Pointer<ffi.Uint16>,
ffi.Int,
ffi.Int,
ffi.Pointer<TMaterialInstance>,
ffi.Bool)>(isLeaf: true)
external int SceneManager_createGeometry(
ffi.Pointer<TSceneManager> sceneManager,
ffi.Pointer<ffi.Float> vertices,
int numVertices,
ffi.Pointer<ffi.Float> normals,
int numNormals,
ffi.Pointer<ffi.Float> uvs,
int numUvs,
ffi.Pointer<ffi.Uint16> indices,
int numIndices,
int primitiveType,
ffi.Pointer<TMaterialInstance> materialInstance,
bool keepData,
);
@ffi.Native<
ffi.Pointer<TMaterialInstance> Function(
ffi.Pointer<TSceneManager>)>(isLeaf: true)
external ffi.Pointer<TMaterialInstance>
SceneManager_createUnlitMaterialInstance(
ffi.Pointer<TSceneManager> sceneManager,
);
@ffi.Native< @ffi.Native<
ffi.Bool Function(ffi.Pointer<TSceneManager>, EntityId, ffi.Bool Function(ffi.Pointer<TSceneManager>, EntityId,
ffi.Pointer<ffi.Double>)>(isLeaf: true) ffi.Pointer<ffi.Double>)>(isLeaf: true)
@@ -996,6 +956,35 @@ external void remove_animation_component(
int entityId, int entityId,
); );
@ffi.Native<
EntityId Function(
ffi.Pointer<TSceneManager>,
ffi.Pointer<ffi.Float>,
ffi.Int,
ffi.Pointer<ffi.Float>,
ffi.Int,
ffi.Pointer<ffi.Float>,
ffi.Int,
ffi.Pointer<ffi.Uint16>,
ffi.Int,
ffi.Int,
ffi.Pointer<TMaterialInstance>,
ffi.Bool)>(isLeaf: true)
external int SceneManager_createGeometry(
ffi.Pointer<TSceneManager> sceneManager,
ffi.Pointer<ffi.Float> vertices,
int numVertices,
ffi.Pointer<ffi.Float> normals,
int numNormals,
ffi.Pointer<ffi.Float> uvs,
int numUvs,
ffi.Pointer<ffi.Uint16> indices,
int numIndices,
int primitiveType,
ffi.Pointer<TMaterialInstance> materialInstance,
bool keepData,
);
@ffi.Native<EntityId Function(ffi.Pointer<TSceneManager>, EntityId)>( @ffi.Native<EntityId Function(ffi.Pointer<TSceneManager>, EntityId)>(
isLeaf: true) isLeaf: true)
external int get_parent( external int get_parent(
@@ -1538,38 +1527,6 @@ external void remove_skybox_render_thread(
ffi.Pointer<TViewer> viewer, ffi.Pointer<TViewer> viewer,
); );
@ffi.Native<
ffi.Void Function(
ffi.Pointer<TSceneManager>,
ffi.Pointer<ffi.Float>,
ffi.Int,
ffi.Pointer<ffi.Float>,
ffi.Int,
ffi.Pointer<ffi.Float>,
ffi.Int,
ffi.Pointer<ffi.Uint16>,
ffi.Int,
ffi.Int,
ffi.Pointer<TMaterialInstance>,
ffi.Bool,
ffi.Pointer<ffi.NativeFunction<ffi.Void Function(EntityId)>>)>(
isLeaf: true)
external void SceneManager_createGeometryRenderThread(
ffi.Pointer<TSceneManager> sceneManager,
ffi.Pointer<ffi.Float> vertices,
int numVertices,
ffi.Pointer<ffi.Float> normals,
int numNormals,
ffi.Pointer<ffi.Float> uvs,
int numUvs,
ffi.Pointer<ffi.Uint16> indices,
int numIndices,
int primitiveType,
ffi.Pointer<TMaterialInstance> materialInstance,
bool keepData,
ffi.Pointer<ffi.NativeFunction<ffi.Void Function(EntityId)>> callback,
);
@ffi.Native< @ffi.Native<
ffi.Void Function( ffi.Void Function(
ffi.Pointer<TSceneManager>, ffi.Pointer<TSceneManager>,
@@ -1594,20 +1551,6 @@ external void SceneManager_loadGlbFromBufferRenderThread(
ffi.Pointer<ffi.NativeFunction<ffi.Void Function(EntityId)>> callback, ffi.Pointer<ffi.NativeFunction<ffi.Void Function(EntityId)>> callback,
); );
@ffi.Native<
ffi.Void Function(
ffi.Pointer<TSceneManager>,
ffi.Pointer<
ffi.NativeFunction<
ffi.Void Function(
ffi.Pointer<TMaterialInstance>)>>)>(isLeaf: true)
external void SceneManager_createUnlitMaterialInstanceRenderThread(
ffi.Pointer<TSceneManager> sceneManager,
ffi.Pointer<
ffi.NativeFunction<ffi.Void Function(ffi.Pointer<TMaterialInstance>)>>
callback,
);
@ffi.Native< @ffi.Native<
ffi.Void Function( ffi.Void Function(
ffi.Pointer<TSceneManager>, ffi.Pointer<TSceneManager>,
@@ -1808,6 +1751,38 @@ external void reset_to_rest_pose_render_thread(
ffi.Pointer<ffi.NativeFunction<ffi.Void Function()>> callback, ffi.Pointer<ffi.NativeFunction<ffi.Void Function()>> callback,
); );
@ffi.Native<
ffi.Void Function(
ffi.Pointer<TSceneManager>,
ffi.Pointer<ffi.Float>,
ffi.Int,
ffi.Pointer<ffi.Float>,
ffi.Int,
ffi.Pointer<ffi.Float>,
ffi.Int,
ffi.Pointer<ffi.Uint16>,
ffi.Int,
ffi.Int,
ffi.Pointer<TMaterialInstance>,
ffi.Bool,
ffi.Pointer<ffi.NativeFunction<ffi.Void Function(EntityId)>>)>(
isLeaf: true)
external void SceneManager_createGeometryRenderThread(
ffi.Pointer<TSceneManager> sceneManager,
ffi.Pointer<ffi.Float> vertices,
int numVertices,
ffi.Pointer<ffi.Float> normals,
int numNormals,
ffi.Pointer<ffi.Float> uvs,
int numUvs,
ffi.Pointer<ffi.Uint16> indices,
int numIndices,
int primitiveType,
ffi.Pointer<TMaterialInstance> materialInstance,
bool keepData,
ffi.Pointer<ffi.NativeFunction<ffi.Void Function(EntityId)>> callback,
);
@ffi.Native< @ffi.Native<
ffi.Void Function( ffi.Void Function(
ffi.Pointer<TViewer>, ffi.Pointer<TViewer>,

View File

@@ -84,7 +84,7 @@ class ThermionViewerFFI extends ThermionViewer {
_onPickResultCallable = NativeCallable< _onPickResultCallable = NativeCallable<
Void Function(EntityId entityId, Int x, Int y, Void Function(EntityId entityId, Int x, Int y,
Pointer<TView> view, Float depth, Float fragX, Float fragY, Float fragZ)>.listener(_onPickResult); Pointer<TView> view)>.listener(_onPickResult);
_initialize(); _initialize();
} }
@@ -1509,23 +1509,23 @@ class ThermionViewerFFI extends ThermionViewer {
} }
void _onPickResult( void _onPickResult(
ThermionEntity entityId, int x, int y, Pointer<TView> viewPtr, double depth, double fragX, double fragY, double fragZ) async { ThermionEntity entityId, int x, int y, Pointer<TView> viewPtr) async {
final view = FFIView(viewPtr, _viewer!); final view = FFIView(viewPtr, _viewer!);
final viewport = await view.getViewport(); final viewport = await view.getViewport();
_pickResultController _pickResultController
.add((entity: entityId, x: x.ceil(), y: (viewport.height - y).ceil(), depth: depth, fragX: fragX, fragY: viewport.height - fragY, fragZ: fragZ )); .add((entity: entityId, x: x.ceil(), y: (viewport.height - y).ceil()));
} }
late NativeCallable< late NativeCallable<
Void Function(EntityId entityId, Int x, Int y, Pointer<TView> view, Float depth, Float fragX, Float fragY, Float fragZ)> Void Function(EntityId entityId, Int x, Int y, Pointer<TView> view)>
_onPickResultCallable; _onPickResultCallable;
/// ///
/// ///
/// ///
@override @override
Future pick(int x, int y) async { void pick(int x, int y) async {
final view = (await getViewAt(0)) as FFIView; final view = (await getViewAt(0)) as FFIView;
var viewport = await view.getViewport(); var viewport = await view.getViewport();
y = (viewport.height - y).ceil(); y = (viewport.height - y).ceil();

View File

@@ -1,17 +1,5 @@
// "picking" means clicking/tapping on the viewport, and unprojecting the X/Y coordinate to determine whether any renderable entities were present at those coordinates.
import '../../viewer.dart'; import '../../viewer.dart';
/// The result of a picking operation (see [ThermionViewer.pick] for more details). typedef FilamentPickResult = ({ThermionEntity entity, int x, int y});
/// [x] and [y] refer to the original screen coordinates used to call [pick]; this should
/// match the values of [fragX] and [fragY]. [fragZ] is the depth value in screen coordinates,
/// [depth] is the value in the depth buffer (i.e. fragZ = 1.0 - depth).
///
typedef FilamentPickResult = ({
ThermionEntity entity,
int x,
int y,
double depth,
double fragX,
double fragY,
double fragZ
});
typedef PickResult = FilamentPickResult; typedef PickResult = FilamentPickResult;

View File

@@ -695,7 +695,7 @@ abstract class ThermionViewer {
/// This is asynchronous and will require 2-3 frames to complete - subscribe to the [pickResult] stream to receive the results of this method. /// This is asynchronous and will require 2-3 frames to complete - subscribe to the [pickResult] stream to receive the results of this method.
/// [x] and [y] must be in local logical coordinates (i.e. where 0,0 is at top-left of the ThermionWidget). /// [x] and [y] must be in local logical coordinates (i.e. where 0,0 is at top-left of the ThermionWidget).
/// ///
Future pick(int x, int y); void pick(int x, int y);
/// ///
/// Retrieves the name assigned to the given ThermionEntity (usually corresponds to the glTF mesh name). /// Retrieves the name assigned to the given ThermionEntity (usually corresponds to the glTF mesh name).

View File

@@ -98,15 +98,7 @@ namespace thermion
void clearBackgroundImage(); void clearBackgroundImage();
void setBackgroundImagePosition(float x, float y, bool clamp, uint32_t width, uint32_t height); void setBackgroundImagePosition(float x, float y, bool clamp, uint32_t width, uint32_t height);
typedef void (*PickCallback)(EntityId entityId, int x, int y, View *view, float depth, float fragX, float fragY, float fragZ); void pick(View *view, uint32_t x, uint32_t y, void (*callback)(EntityId entityId, int x, int y, View *view));
///
/// Returns true if the specified entity is a gizmo, grid or background image entity.
///
bool isNonPickableEntity(EntityId entityId);
void pick(View *view, uint32_t x, uint32_t y, PickCallback callback);
Engine* getEngine() { Engine* getEngine() {
return _engine; return _engine;
} }

View File

@@ -85,8 +85,7 @@ extern "C"
EMSCRIPTEN_KEEPALIVE void Viewer_setMainCamera(TViewer *tViewer, TView *tView); EMSCRIPTEN_KEEPALIVE void Viewer_setMainCamera(TViewer *tViewer, TView *tView);
EMSCRIPTEN_KEEPALIVE TSwapChain* Viewer_getSwapChainAt(TViewer *tViewer, int index); EMSCRIPTEN_KEEPALIVE TSwapChain* Viewer_getSwapChainAt(TViewer *tViewer, int index);
EMSCRIPTEN_KEEPALIVE void Viewer_setViewRenderable(TViewer *viewer, TSwapChain *swapChain, TView* view, bool renderable); EMSCRIPTEN_KEEPALIVE void Viewer_setViewRenderable(TViewer *viewer, TSwapChain *swapChain, TView* view, bool renderable);
EMSCRIPTEN_KEEPALIVE void Viewer_pick(TViewer *viewer, TView* tView, int x, int y, void (*callback)(EntityId entityId, int x, int y, TView *tView, float depth, float fragX, float fragY, float fragZ)); EMSCRIPTEN_KEEPALIVE void Viewer_pick(TViewer *viewer, TView* tView, int x, int y, void (*callback)(EntityId entityId, int x, int y, TView *tView));
EMSCRIPTEN_KEEPALIVE bool Viewer_isNonPickableEntity(TViewer *viewer, EntityId entityId);
// Engine // Engine
EMSCRIPTEN_KEEPALIVE TEngine *Viewer_getEngine(TViewer* viewer); EMSCRIPTEN_KEEPALIVE TEngine *Viewer_getEngine(TViewer* viewer);

View File

@@ -11,6 +11,8 @@
#pragma comment(lib, "bluevk.lib") #pragma comment(lib, "bluevk.lib")
#pragma comment(lib, "bluegl.lib") #pragma comment(lib, "bluegl.lib")
#pragma comment(lib, "backend.lib") #pragma comment(lib, "backend.lib")
#pragma comment(lib, "filameshio.lib")
#pragma comment(lib, "viewer.lib")
#pragma comment(lib, "filamat.lib") #pragma comment(lib, "filamat.lib")
#pragma comment(lib, "geometry.lib") #pragma comment(lib, "geometry.lib")
#pragma comment(lib, "utils.lib") #pragma comment(lib, "utils.lib")

View File

@@ -1191,16 +1191,23 @@ namespace thermion
return _engine->getCameraComponent(Entity::import(entity)); return _engine->getCameraComponent(Entity::import(entity));
} }
bool FilamentViewer::isNonPickableEntity(EntityId entityId) { void FilamentViewer::pick(View *view, uint32_t x, uint32_t y, void (*callback)(EntityId entityId, int x, int y, View *view))
auto renderable = Entity::import(entityId);
return _sceneManager->isGizmoEntity(renderable) || renderable == _imageEntity || renderable == _sceneManager->_gridOverlay->sphere() || _sceneManager->_gridOverlay->grid();
}
void FilamentViewer::pick(View *view, uint32_t x, uint32_t y, PickCallback callback)
{ {
view->pick(x, y, [=](filament::View::PickingQueryResult const &result) { view->pick(x, y, [=](filament::View::PickingQueryResult const &result) {
callback(Entity::smuggle(result.renderable), x, y, view, result.depth, result.fragCoords.x, result.fragCoords.y, result.fragCoords.z);
}); if(_sceneManager->isGizmoEntity(result.renderable)) {
Log("Gizmo entity, ignoring");
return;
}
std::unordered_set<Entity, Entity::Hasher> nonPickableEntities = {
_imageEntity,
_sceneManager->_gridOverlay->sphere(),
_sceneManager->_gridOverlay->grid(),
};
if (nonPickableEntities.find(result.renderable) == nonPickableEntities.end()) {
callback(Entity::smuggle(result.renderable), x, y, view);
} });
} }
void FilamentViewer::unprojectTexture(EntityId entityId, uint8_t *input, uint32_t inputWidth, uint32_t inputHeight, uint8_t *out, uint32_t outWidth, uint32_t outHeight) void FilamentViewer::unprojectTexture(EntityId entityId, uint8_t *input, uint32_t inputWidth, uint32_t inputHeight, uint8_t *out, uint32_t outWidth, uint32_t outHeight)

View File

@@ -49,16 +49,11 @@ extern "C"
viewer->destroyRenderTarget(renderTarget); viewer->destroyRenderTarget(renderTarget);
} }
EMSCRIPTEN_KEEPALIVE void Viewer_pick(TViewer *tViewer, TView* tView, int x, int y, void (*callback)(EntityId entityId, int x, int y, TView *tView, float depth, float fragX, float fragY, float fragZ)) EMSCRIPTEN_KEEPALIVE void Viewer_pick(TViewer *tViewer, TView* tView, int x, int y, void (*callback)(EntityId entityId, int x, int y, TView *tView))
{ {
auto *viewer = reinterpret_cast<FilamentViewer*>(tViewer); auto *viewer = reinterpret_cast<FilamentViewer*>(tViewer);
auto *view = reinterpret_cast<View*>(tView); auto *view = reinterpret_cast<View*>(tView);
((FilamentViewer *)viewer)->pick(view, static_cast<uint32_t>(x), static_cast<uint32_t>(y), reinterpret_cast<FilamentViewer::PickCallback>(callback)); ((FilamentViewer *)viewer)->pick(view, static_cast<uint32_t>(x), static_cast<uint32_t>(y), reinterpret_cast<void (*)(EntityId entityId, int x, int y, View *view)>(callback));
}
EMSCRIPTEN_KEEPALIVE bool Viewer_isNonPickableEntity(TViewer *tViewer, EntityId entityId) {
auto *viewer = reinterpret_cast<FilamentViewer*>(tViewer);
return viewer->isNonPickableEntity(entityId);
} }
EMSCRIPTEN_KEEPALIVE void destroy_filament_viewer(TViewer *viewer) EMSCRIPTEN_KEEPALIVE void destroy_filament_viewer(TViewer *viewer)

View File

@@ -1,6 +1,6 @@
name: thermion_dart name: thermion_dart
description: 3D rendering toolkit for Dart. description: 3D rendering toolkit for Dart.
version: 0.2.1-dev.0.0.14 version: 0.2.1-dev.0.0.12
homepage: https://thermion.dev homepage: https://thermion.dev
repository: https://github.com/nmfisher/thermion repository: https://github.com/nmfisher/thermion
@@ -18,7 +18,6 @@ dependencies:
web: ^1.0.0 web: ^1.0.0
logging: ^1.2.0 logging: ^1.2.0
http: ^1.2.2 http: ^1.2.2
path: ^1.9.0
dev_dependencies: dev_dependencies:
ffigen: ^13.0.0 ffigen: ^13.0.0

View File

@@ -1,6 +1,10 @@
import 'dart:async'; import 'dart:async';
import 'dart:io';
import 'dart:math';
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:animation_tools_dart/animation_tools_dart.dart'; import 'package:animation_tools_dart/animation_tools_dart.dart';
import 'package:thermion_dart/src/viewer/src/events.dart';
import 'package:thermion_dart/thermion_dart.dart';
import 'package:test/test.dart'; import 'package:test/test.dart';
import 'package:vector_math/vector_math_64.dart'; import 'package:vector_math/vector_math_64.dart';
import 'helpers.dart'; import 'helpers.dart';

View File

@@ -1,7 +1,3 @@
// ignore_for_file: unused_local_variable
import 'dart:math';
import 'package:thermion_dart/thermion_dart.dart'; import 'package:thermion_dart/thermion_dart.dart';
import 'package:test/test.dart'; import 'package:test/test.dart';
import 'package:vector_math/vector_math_64.dart'; import 'package:vector_math/vector_math_64.dart';
@@ -11,6 +7,7 @@ void main() async {
final testHelper = TestHelper("integration"); final testHelper = TestHelper("integration");
group('camera', () { group('camera', () {
test('getCameraModelMatrix, getCameraPosition, rotation', () async { test('getCameraModelMatrix, getCameraPosition, rotation', () async {
var viewer = await testHelper.createViewer(); var viewer = await testHelper.createViewer();
var matrix = await viewer.getCameraModelMatrix(); var matrix = await viewer.getCameraModelMatrix();
@@ -27,7 +24,7 @@ void main() async {
expect(position.x, 2.0); expect(position.x, 2.0);
expect(position.y, 2.0); expect(position.y, 2.0);
expect(position.z, 2.0); expect(position.z, 2.0);
await viewer.dispose(); await viewer.dispose();
}); });
@@ -53,6 +50,7 @@ void main() async {
expect(position.y, closeTo(4.0, 1e-6)); expect(position.y, closeTo(4.0, 1e-6));
expect(position.z, closeTo(5.0, 1e-6)); expect(position.z, closeTo(5.0, 1e-6));
await viewer.dispose(); await viewer.dispose();
}); });
test('getCameraProjectionMatrix', () async { test('getCameraProjectionMatrix', () async {
@@ -64,11 +62,11 @@ void main() async {
test('getCameraCullingProjectionMatrix', () async { test('getCameraCullingProjectionMatrix', () async {
throw Exception("TODO"); throw Exception("TODO");
// ignore: dead_code
var viewer = await testHelper.createViewer(); var viewer = await testHelper.createViewer();
var matrix = await viewer.getCameraCullingProjectionMatrix(); var matrix = await viewer.getCameraCullingProjectionMatrix();
print(matrix); print(matrix);
await viewer.dispose(); await viewer.dispose();
}); });
test('getCameraFrustum', () async { test('getCameraFrustum', () async {
@@ -94,48 +92,24 @@ void main() async {
var camera = await viewer.getMainCamera(); var camera = await viewer.getMainCamera();
await viewer.createGeometry(GeometryHelper.cube()); await viewer.createGeometry(GeometryHelper.cube());
await camera.setProjection( await camera.setProjection(Projection.Orthographic, -0.05, 0.05, -0.05, 0.05, 0.05, 10000);
Projection.Orthographic, -0.05, 0.05, -0.05, 0.05, 0.05, 10000); await testHelper.capture(
await testHelper.capture(viewer, "camera_set_orthographic_projection"); viewer, "camera_set_orthographic_projection");
await viewer.dispose(); await viewer.dispose();
}); });
test('set perspective projection/culling matrix', () async { test('set custom projection/culling matrix', () async {
var viewer = await testHelper.createViewer( var viewer = await testHelper.createViewer(
bg: kRed, cameraPosition: Vector3(0, 0, 4)); bg: kRed, cameraPosition: Vector3(0, 0, 4));
var camera = await viewer.getMainCamera(); var camera = await viewer.getMainCamera();
final cube = await viewer.createGeometry(GeometryHelper.cube()); final cube = await viewer.createGeometry(GeometryHelper.cube());
var fovY = pi / 2;
await camera.setProjectionMatrixWithCulling(
makePerspectiveMatrix(fovY, 1.0, 0.05, 10000), 0.05, 10000);
await testHelper.capture(
viewer, "camera_set_perspective_projection_culling_matrix_object_fov90");
// cube no longer visible when the far plane is moved closer to camera so cube is outside
fovY = 2*(pi/3);
await camera.setProjectionMatrixWithCulling(
makePerspectiveMatrix(fovY, 1.0, 0.05, 10000), 0.05, 10000);
await testHelper.capture(
viewer, "camera_set_perspective_projection_culling_matrix_object_fov120");
await viewer.dispose();
});
test('set custom projection/culling matrix (orthographic)', () async {
var viewer = await testHelper.createViewer(
bg: kRed, cameraPosition: Vector3(0, 0, 4));
var camera = await viewer.getMainCamera();
final cube = await viewer.createGeometry(GeometryHelper.cube());
// cube is visible when inside the frustum, cube is visible // cube is visible when inside the frustum, cube is visible
var projectionMatrix = var projectionMatrix =
makeOrthographicMatrix(-10.0, 10.0, -10.0, 10.0, 0.05, 10000); makeOrthographicMatrix(-10.0, 10.0, -10.0, 10.0, 0.05, 10000);
await camera.setProjectionMatrixWithCulling( await camera.setProjectionMatrixWithCulling(
projectionMatrix, 0.05, 10000); projectionMatrix, 0.05, 10000);
await testHelper.capture( await testHelper.capture(
viewer, "camera_projection_culling_matrix_object_in_frustum"); viewer, "camera_projection_culling_matrix_object_in_frustum");
@@ -145,7 +119,7 @@ void main() async {
await camera.setProjectionMatrixWithCulling(projectionMatrix, 0.05, 1); await camera.setProjectionMatrixWithCulling(projectionMatrix, 0.05, 1);
await testHelper.capture( await testHelper.capture(
viewer, "camera_projection_culling_matrix_object_outside_frustum"); viewer, "camera_projection_culling_matrix_object_outside_frustum");
await viewer.dispose(); await viewer.dispose();
}); });

View File

@@ -1,16 +0,0 @@
// just for investigating crashes on GitHub actions
import 'package:test/test.dart';
import 'helpers.dart';
void main() async {
group("test", () {
test("test", () async {
print("Creating test helper");
final testHelper = TestHelper("dummy");
var viewer = await testHelper.createViewer();
expect(1, 1);
});
});
}

View File

@@ -1,5 +1,3 @@
// ignore_for_file: unused_local_variable
import 'dart:io'; import 'dart:io';
import 'dart:math'; import 'dart:math';
import 'package:thermion_dart/src/viewer/src/ffi/src/thermion_viewer_ffi.dart'; import 'package:thermion_dart/src/viewer/src/ffi/src/thermion_viewer_ffi.dart';

View File

@@ -1,5 +1,3 @@
// ignore_for_file: unused_local_variable
import 'package:test/test.dart'; import 'package:test/test.dart';
import 'helpers.dart'; import 'helpers.dart';

View File

@@ -1,5 +1,3 @@
// ignore_for_file: unused_local_variable
import 'dart:io'; import 'dart:io';
import 'package:thermion_dart/thermion_dart.dart'; import 'package:thermion_dart/thermion_dart.dart';
import 'package:test/test.dart'; import 'package:test/test.dart';

View File

@@ -1,5 +1,3 @@
// ignore_for_file: unused_local_variable
import 'dart:ffi'; import 'dart:ffi';
import 'dart:io'; import 'dart:io';
import 'dart:math'; import 'dart:math';
@@ -12,6 +10,7 @@ import 'package:thermion_dart/src/utils/src/dart_resources.dart';
import 'package:thermion_dart/src/viewer/src/ffi/src/thermion_dart.g.dart'; import 'package:thermion_dart/src/viewer/src/ffi/src/thermion_dart.g.dart';
import 'package:thermion_dart/src/viewer/src/ffi/src/thermion_viewer_ffi.dart'; import 'package:thermion_dart/src/viewer/src/ffi/src/thermion_viewer_ffi.dart';
import 'package:thermion_dart/src/viewer/src/ffi/thermion_viewer_ffi.dart'; import 'package:thermion_dart/src/viewer/src/ffi/thermion_viewer_ffi.dart';
import 'package:thermion_dart/src/viewer/src/shared_types/view.dart';
import 'package:thermion_dart/thermion_dart.dart'; import 'package:thermion_dart/thermion_dart.dart';
import 'package:vector_math/vector_math_64.dart'; import 'package:vector_math/vector_math_64.dart';
import 'package:path/path.dart' as p; import 'package:path/path.dart' as p;
@@ -73,31 +72,28 @@ class TestHelper {
TestHelper(String dir) { TestHelper(String dir) {
final packageUri = findPackageRoot('thermion_dart'); final packageUri = findPackageRoot('thermion_dart');
print("Package URIL $packageUri");
testDir = Directory("${packageUri.toFilePath()}/test").path; testDir = Directory("${packageUri.toFilePath()}/test").path;
print("Test dir : $packageUri");
outDir = Directory("$testDir/output/${dir}"); outDir = Directory("$testDir/output/${dir}");
print("Out dir : $packageUri");
// outDir.deleteSync(recursive: true); // outDir.deleteSync(recursive: true);
outDir.createSync(recursive: true); outDir.createSync(recursive: true);
print("Created out dir : $packageUri"); if(Platform.isMacOS) {
if (Platform.isMacOS) {
DynamicLibrary.open('${testDir}/libThermionTextureSwift.dylib'); DynamicLibrary.open('${testDir}/libThermionTextureSwift.dylib');
} }
} }
Future capture(ThermionViewer viewer, String outputFilename, Future capture(ThermionViewer viewer, String outputFilename,
{View? view, SwapChain? swapChain, RenderTarget? renderTarget}) async { {View? view, SwapChain? swapChain, RenderTarget? renderTarget}) async {
await Future.delayed(Duration(milliseconds: 10)); await Future.delayed(Duration(milliseconds: 10));
var outPath = p.join(outDir.path, "$outputFilename.bmp"); var outPath = p.join(outDir.path, "$outputFilename.bmp");
var pixelBuffer = await viewer.capture( var pixelBuffer = await viewer.capture(
view: view, view: view,
swapChain: swapChain ?? this.swapChain, swapChain: swapChain ?? this.swapChain,
renderTarget: renderTarget); renderTarget: renderTarget);
await viewer.render(); await viewer.render();
view ??= await viewer.getViewAt(0); view ??= await viewer.getViewAt(0);
var vp = await view.getViewport(); var vp = await view.getViewport();
await savePixelBufferToBmp(pixelBuffer, vp.width, vp.height, outPath); await savePixelBufferToBmp(pixelBuffer, vp.width, vp.height, outPath);
@@ -105,6 +101,9 @@ class TestHelper {
} }
Future<ThermionTextureSwift> createTexture(int width, int height) async { Future<ThermionTextureSwift> createTexture(int width, int height) async {
final packageUri = findPackageRoot('thermion_dart');
var testDir = Directory("${packageUri.toFilePath()}/test").path;
final object = ThermionTextureSwift.new1(); final object = ThermionTextureSwift.new1();
object.initWithWidth_height_(width, height); object.initWithWidth_height_(width, height);
return object; return object;
@@ -115,29 +114,24 @@ class TestHelper {
Vector3? cameraPosition, Vector3? cameraPosition,
viewportDimensions = (width: 500, height: 500)}) async { viewportDimensions = (width: 500, height: 500)}) async {
final resourceLoader = calloc<ResourceLoaderWrapper>(1); final resourceLoader = calloc<ResourceLoaderWrapper>(1);
print("resourceLoader");
var loadToOut = NativeCallable< var loadToOut = NativeCallable<
Void Function(Pointer<Char>, Void Function(Pointer<Char>,
Pointer<ResourceBuffer>)>.listener(DartResourceLoader.loadResource); Pointer<ResourceBuffer>)>.listener(DartResourceLoader.loadResource);
print("loadToOut");
resourceLoader.ref.loadToOut = loadToOut.nativeFunction; resourceLoader.ref.loadToOut = loadToOut.nativeFunction;
print("set ref");
var freeResource = NativeCallable<Void Function(ResourceBuffer)>.listener( var freeResource = NativeCallable<Void Function(ResourceBuffer)>.listener(
DartResourceLoader.freeResource); DartResourceLoader.freeResource);
print("freeResource");
resourceLoader.ref.freeResource = freeResource.nativeFunction; resourceLoader.ref.freeResource = freeResource.nativeFunction;
print("set freeResource ref");
var viewer = ThermionViewerFFI(resourceLoader: resourceLoader.cast<Void>()); var viewer = ThermionViewerFFI(resourceLoader: resourceLoader.cast<Void>());
print("created viewer");
await viewer.initialized; await viewer.initialized;
swapChain = await viewer.createHeadlessSwapChain( swapChain = await viewer.createHeadlessSwapChain(
viewportDimensions.width, viewportDimensions.height); viewportDimensions.width, viewportDimensions.height);
print("created headless swapchain");
await viewer.updateViewportAndCameraProjection( await viewer.updateViewportAndCameraProjection(
viewportDimensions.width.toDouble(), viewportDimensions.width.toDouble(),
viewportDimensions.height.toDouble()); viewportDimensions.height.toDouble());
print("updated camera");
if (bg != null) { if (bg != null) {
await viewer.setBackgroundColor( await viewer.setBackgroundColor(
bg.r.toDouble(), bg.g.toDouble(), bg.b.toDouble(), bg.a.toDouble()); bg.r.toDouble(), bg.g.toDouble(), bg.b.toDouble(), bg.a.toDouble());
@@ -197,7 +191,7 @@ Future<Uint8List> pixelBufferToBmp(
} }
Future<Uint8List> pixelsToPng(Uint8List pixelBuffer, int width, int height, Future<Uint8List> pixelsToPng(Uint8List pixelBuffer, int width, int height,
{bool linearToSrgb = false, bool invertAces = false}) async { {bool linearToSrgb = false}) async {
final image = img.Image(width: width, height: height); final image = img.Image(width: width, height: height);
for (int y = 0; y < height; y++) { for (int y = 0; y < height; y++) {
@@ -209,6 +203,7 @@ Future<Uint8List> pixelsToPng(Uint8List pixelBuffer, int width, int height,
int a = pixelBuffer[pixelIndex + 3]; int a = pixelBuffer[pixelIndex + 3];
// Apply inverse ACES tone mapping // Apply inverse ACES tone mapping
bool invertAces = false;
if (invertAces) { if (invertAces) {
r = _inverseACESToneMapping(r); r = _inverseACESToneMapping(r);
g = _inverseACESToneMapping(g); g = _inverseACESToneMapping(g);

View File

@@ -0,0 +1,782 @@
import 'dart:async';
import 'dart:io';
import 'dart:math';
import 'package:thermion_dart/src/viewer/src/events.dart';
import 'package:thermion_dart/src/viewer/src/ffi/src/thermion_viewer_ffi.dart';
import 'package:thermion_dart/thermion_dart.dart';
import 'package:test/test.dart';
import 'package:vector_math/vector_math_64.dart';
import 'helpers.dart';
void main() async {
final testHelper = TestHelper("integration");
group('background', () {
test('set background color to solid green', () async {
var viewer = await testHelper.createViewer();
await viewer.setBackgroundColor(0.0, 1.0, 0.0, 1.0);
await testHelper.capture(viewer, "set_background_color_to_solid_green");
await viewer.dispose();
});
test('set background color to full transparency', () async {
var viewer = await testHelper.createViewer();
await viewer.setBackgroundColor(0.0, 1.0, 0.0, 0.0);
await testHelper.capture(
viewer, "set_background_color_to_transparent_green");
await viewer.dispose();
});
test('set background image', () async {
var viewer = await testHelper.createViewer();
await viewer.setBackgroundImage(
"file:///${testHelper.testDir}/assets/cube_texture_512x512.png");
await viewer.setPostProcessing(true);
await viewer.setToneMapping(ToneMapper.LINEAR);
await testHelper.capture(viewer, "set_background_image");
await viewer.dispose();
});
});
group("scene update events", () {
test('add light fires SceneUpdateEvent', () async {
var viewer = await testHelper.createViewer();
final success = Completer<bool>();
var light = DirectLight(
type: LightType.POINT,
color: 6500,
intensity: 1000000,
position: Vector3(0, 0.6, 0.6),
direction: Vector3(0, 0, 0),
falloffRadius: 2.0);
late StreamSubscription listener;
listener = viewer.sceneUpdated.listen((updateEvent) {
var wasSuccess = updateEvent.eventType == EventType.EntityAdded &&
updateEvent.addedEntityType == EntityType.DirectLight &&
updateEvent.getDirectLight() == light;
success.complete(wasSuccess);
listener.cancel();
});
await viewer.addDirectLight(light);
expect(await success.future, true);
});
test('remove light fires SceneUpdateEvent', () async {
var viewer = await testHelper.createViewer();
final success = Completer<bool>();
var light = await viewer.addDirectLight(DirectLight.point());
late StreamSubscription listener;
listener = viewer.sceneUpdated.listen((updateEvent) {
var wasSuccess = updateEvent.eventType == EventType.EntityRemoved &&
updateEvent.entity == light;
success.complete(wasSuccess);
listener.cancel();
});
await viewer.removeLight(light);
expect(await success.future, true);
});
test('add geometry fires SceneUpdateEvent', () async {
var viewer = await testHelper.createViewer();
final success = Completer<bool>();
var geometry = GeometryHelper.cube();
late StreamSubscription listener;
listener = viewer.sceneUpdated.listen((updateEvent) {
var wasSuccess = updateEvent.eventType == EventType.EntityAdded &&
updateEvent.addedEntityType == EntityType.Geometry &&
updateEvent.getAsGeometry() == geometry;
success.complete(wasSuccess);
listener.cancel();
});
await viewer.createGeometry(geometry);
expect(await success.future, true);
});
test('remove geometry fires SceneUpdateEvent', () async {
var viewer = await testHelper.createViewer();
var geometry = await viewer.createGeometry(GeometryHelper.cube());
final success = Completer<bool>();
late StreamSubscription listener;
listener = viewer.sceneUpdated.listen((updateEvent) {
var wasSuccess = updateEvent.eventType == EventType.EntityRemoved &&
updateEvent.entity == geometry;
success.complete(wasSuccess);
listener.cancel();
});
await viewer.removeEntity(geometry);
expect(await success.future, true);
});
test('loadGlb fires SceneUpdateEvent', () async {
var viewer = await testHelper.createViewer();
final success = Completer<bool>();
late StreamSubscription listener;
final uri = "${testHelper.testDir}/cube.glb";
listener = viewer.sceneUpdated.listen((updateEvent) {
var wasSuccess = updateEvent.eventType == EventType.EntityAdded &&
updateEvent.addedEntityType == EntityType.Gltf &&
updateEvent.getAsGLTF().uri == uri;
success.complete(wasSuccess);
listener.cancel();
});
await viewer.loadGlb(uri, keepData: false);
expect(await success.future, true);
});
test('remove glb fires SceneUpdateEvent', () async {
var viewer = await testHelper.createViewer();
final uri = "${testHelper.testDir}/cube.glb";
var entity = await viewer.loadGlb(uri, keepData: false);
final success = Completer<bool>();
late StreamSubscription listener;
listener = viewer.sceneUpdated.listen((updateEvent) {
var wasSuccess = updateEvent.eventType == EventType.EntityRemoved &&
updateEvent.entity == entity;
success.complete(wasSuccess);
listener.cancel();
});
await viewer.removeEntity(entity);
expect(await success.future, true);
});
});
group("MaterialInstance", () {
test('disable depth write', () async {
var viewer = await testHelper.createViewer();
await viewer.setBackgroundColor(1.0, 0.0, 0.0, 1.0);
await viewer.setCameraPosition(0, 0, 6);
await viewer.addDirectLight(
DirectLight.sun(direction: Vector3(0, 0, -1)..normalize()));
final cube1 = await viewer.createGeometry(GeometryHelper.cube());
var materialInstance = await viewer.getMaterialInstanceAt(cube1, 0);
final cube2 = await viewer.createGeometry(GeometryHelper.cube());
await viewer.setMaterialPropertyFloat4(
cube2, "baseColorFactor", 0, 0, 1, 0, 1);
await viewer.setPosition(cube2, 1.0, 0.0, -1.0);
expect(materialInstance, isNotNull);
// with depth write enabled on both materials, cube2 renders behind the white cube
await testHelper.capture(viewer, "material_instance_depth_write_enabled");
// if we disable depth write on cube1, then cube2 will always appear in front
// (relying on insertion order)
materialInstance!.setDepthWriteEnabled(false);
await testHelper.capture(
viewer, "material_instance_depth_write_disabled");
// set priority for the cube1 cube to 7 (render) last, cube1 renders in front
await viewer.setPriority(cube1, 7);
await testHelper.capture(
viewer, "material_instance_depth_write_disabled_with_priority");
});
});
// test('create instance from glb when keepData is true', () async {
// var model = await viewer.loadGlb("${testHelper.testDir}/cube.glb", keepData: true);
// await viewer.transformToUnitCube(model);
// var instance = await viewer.createInstance(model);
// await viewer.setPosition(instance, 0.5, 0.5, -0.5);
// await viewer.setBackgroundColor(0.0, 0.0, 1.0, 1.0);
// await viewer.setCameraPosition(0, 1, 5);
// await viewer
// .setCameraRotation(Quaternion.axisAngle(Vector3(1, 0, 0), -0.5));
// await viewer.setRendering(true);
// await testHelper.capture(viewer, "glb_create_instance");
// await viewer.setRendering(false);
// });
// test('create instance from glb fails when keepData is false', () async {
// var model = await viewer.loadGlb("${testHelper.testDir}/cube.glb", keepData: false);
// bool thrown = false;
// try {
// await viewer.createInstance(model);
// } catch (err) {
// thrown = true;
// }
// expect(thrown, true);
// });
// });
// group('Skinning & animations', () {
// test('get bone names', () async {
// var model = await viewer.loadGlb("${testHelper.testDir}/assets/shapes.glb");
// var names = await viewer.getBoneNames(model);
// expect(names.first, "Bone");
// });
// test('reset bones', () async {
// var model = await viewer.loadGlb("${testHelper.testDir}/assets/shapes.glb");
// await viewer.resetBones(model);
// });
// test('set from BVH', () async {
// var model = await viewer.loadGlb("${testHelper.testDir}/assets/shapes.glb");
// var animation = BVHParser.parse(
// File("${testHelper.testDir}/assets/animation.bvh").readAsStringSync(),
// boneRegex: RegExp(r"Bone$"));
// await viewer.addBoneAnimation(model, animation);
// });
// test('fade in/out', () async {
// var model = await viewer.loadGlb("${testHelper.testDir}/assets/shapes.glb");
// var animation = BVHParser.parse(
// File("${testHelper.testDir}/assets/animation.bvh").readAsStringSync(),
// boneRegex: RegExp(r"Bone$"));
// await viewer.addBoneAnimation(model, animation,
// fadeInInSecs: 0.5, fadeOutInSecs: 0.5);
// await Future.delayed(Duration(seconds: 1));
// });
group("materials", () {
test('set float4 material property for custom geometry', () async {
var viewer = await testHelper.createViewer();
await viewer.setCameraPosition(0, 0, 6);
await viewer.setBackgroundColor(0.0, 0.0, 1.0, 1.0);
var light = await viewer.addLight(
LightType.SUN, 6500, 1000000, 0, 0, 0, 0, 0, -1);
final cube = await viewer.createGeometry(GeometryHelper.cube());
await testHelper.capture(viewer, "set_material_float4_pre");
await viewer.setMaterialPropertyFloat4(
cube, "baseColorFactor", 0, 0.0, 1.0, 0.0, 1.0);
await testHelper.capture(viewer, "set_material_float4_post");
});
test('set float material property for custom geometry', () async {
var viewer = await testHelper.createViewer();
await viewer.setCameraPosition(0, 0, 6);
await viewer.setBackgroundColor(0.0, 0.0, 1.0, 1.0);
var light = await viewer.addLight(
LightType.SUN, 6500, 1000000, 0, 0, 0, 0, 0, -1);
final cube = await viewer.createGeometry(GeometryHelper.cube());
// this won't actually do anything because the default ubershader doesn't use specular/glossiness
// but we can at least check that the call succeeds
await testHelper.capture(viewer, "set_material_specular_pre");
await viewer.setMaterialPropertyFloat(cube, "specularFactor", 0, 0.0);
await testHelper.capture(viewer, "set_material_specular_post");
});
test('set float material property (roughness) for custom geometry',
() async {
var viewer = await testHelper.createViewer();
await viewer.setCameraPosition(0, 0, 6);
await viewer.setBackgroundColor(0.0, 0.0, 1.0, 1.0);
var light = await viewer.addLight(
LightType.SUN, 6500, 1000000, 0, 0, 0, 0, 0, -1);
final cube = await viewer.createGeometry(GeometryHelper.cube());
// this won't actually do anything because the default ubershader doesn't use specular/glossiness
// but we can at least check that the call succeeds
await testHelper.capture(viewer, "set_material_roughness_pre");
await viewer.setMaterialPropertyFloat(cube, "metallicFactor", 0, 0.0);
await viewer.setMaterialPropertyFloat(cube, "roughnessFactor", 0, 0.0);
await testHelper.capture(viewer, "set_material_roughness_post");
});
});
group("transforms & parenting", () {
test('set multiple transforms simultaneously with setTransforms', () async {
var viewer =
await testHelper.createViewer(bg: kRed, cameraPosition: Vector3(0, 0, 5));
final cube1 = await viewer.createGeometry(GeometryHelper.cube());
final cube2 = await viewer.createGeometry(GeometryHelper.cube());
await viewer.queueTransformUpdates([
cube1,
cube2
], [
Matrix4.translation(Vector3(-1, 0, 0)),
Matrix4.translation(Vector3(1, 0, 0))
]);
await viewer.render(testHelper.swapChain);
await testHelper.capture(viewer, "set_multiple_transforms");
});
test('getParent and getAncestor both return null when entity has no parent',
() async {
var viewer = await testHelper.createViewer();
final cube = await viewer.createGeometry(GeometryHelper.cube());
expect(await viewer.getParent(cube), isNull);
expect(await viewer.getAncestor(cube), isNull);
});
test(
'getParent returns the parent entity after one has been set via setParent',
() async {
var viewer = await testHelper.createViewer();
final cube1 = await viewer.createGeometry(GeometryHelper.cube());
final cube2 = await viewer.createGeometry(GeometryHelper.cube());
await viewer.setParent(cube1, cube2);
final parent = await viewer.getParent(cube1);
expect(parent, cube2);
});
test('getAncestor returns the ultimate parent entity', () async {
var viewer = await testHelper.createViewer();
final grandparent = await viewer.createGeometry(GeometryHelper.cube());
final parent = await viewer.createGeometry(GeometryHelper.cube());
final child = await viewer.createGeometry(GeometryHelper.cube());
await viewer.setParent(child, parent);
await viewer.setParent(parent, grandparent);
expect(await viewer.getAncestor(child), grandparent);
});
test('set position based on screenspace coord', () async {
var viewer = await testHelper.createViewer();
print(await viewer.getCameraFov(true));
await viewer.createIbl(1.0, 1.0, 1.0, 1000);
await viewer.setCameraPosition(0, 0, 6);
await viewer.setBackgroundColor(0.0, 0.0, 1.0, 1.0);
// Create the cube geometry
final cube = await viewer.createGeometry(GeometryHelper.cube());
// await viewer.setPosition(cube, -0.05, 0.04, 5.9);
// await viewer.setPosition(cube, -2.54, 2.54, 0);
await viewer.queuePositionUpdateFromViewportCoords(cube, 0, 0);
// we need an explicit render call here to process the transform queue
await viewer.render(testHelper.swapChain);
await testHelper.capture(viewer, "set_position_from_viewport_coords");
});
});
group("layers & overlays", () {
test('enable grid overlay', () async {
var viewer = await testHelper.createViewer();
await viewer.setBackgroundColor(0, 0, 0, 1);
await viewer
.setCameraRotation(Quaternion.axisAngle(Vector3(1, 0, 0), -pi / 8));
await viewer.setCameraPosition(0, 2, 0);
await testHelper.capture(viewer, "grid_overlay_default");
await viewer.setLayerVisibility(7, true);
await testHelper.capture(viewer, "grid_overlay_enabled");
await viewer.setLayerVisibility(7, false);
await testHelper.capture(viewer, "grid_overlay_disabled");
});
test('load glb from buffer with layer', () async {
var viewer = await testHelper.createViewer();
await viewer.setBackgroundColor(1, 0, 1, 1);
await viewer.setCameraPosition(0, 2, 5);
await viewer
.setCameraRotation(Quaternion.axisAngle(Vector3(1, 0, 0), -0.5));
var buffer = File("${testHelper.testDir}/cube.glb").readAsBytesSync();
var model = await viewer.loadGlbFromBuffer(buffer, layer: 1);
await testHelper.capture(
viewer, "load_glb_from_buffer_with_layer_disabled");
await viewer.setLayerVisibility(1, true);
await testHelper.capture(
viewer, "load_glb_from_buffer_with_layer_enabled");
});
test('change layer visibility at runtime', () async {
var viewer = await testHelper.createViewer();
await viewer.setBackgroundColor(1, 0, 1, 1);
await viewer.setCameraPosition(0, 2, 5);
await viewer
.setCameraRotation(Quaternion.axisAngle(Vector3(1, 0, 0), -0.5));
var cube = await viewer.createGeometry(GeometryHelper.cube());
await testHelper.capture(
viewer, "change_layer_visibility_at_runtime_default");
// all entities set to layer 0 by default, so this should now be invisible
await viewer.setLayerVisibility(0, false);
await testHelper.capture(
viewer, "change_layer_visibility_at_runtime_layer0_invisible");
// now change the visibility layer to 5, should be invisible
await viewer.setVisibilityLayer(cube, 5);
await testHelper.capture(
viewer, "change_layer_visibility_at_runtime_layer5_invisible");
// now toggle layer 5 visibility, cube should now be visible
await viewer.setLayerVisibility(5, true);
await testHelper.capture(
viewer, "change_layer_visibility_at_runtime_layer5_visible");
});
});
// test('point light', () async {
// var model = await viewer.loadGlb("${testHelper.testDir}/cube.glb");
// await viewer.transformToUnitCube(model);
// var light = await viewer.addLight(
// LightType.POINT, 6500, 1000000, 0, 2, 0, 0, -1, 0,
// falloffRadius: 10.0);
// await viewer.setBackgroundColor(0.0, 0.0, 0.0, 1.0);
// await viewer.setCameraPosition(0, 1, 5);
// await viewer
// .setCameraRotation(Quaternion.axisAngle(Vector3(1, 0, 0), -0.5));
// await viewer.setRendering(true);
// await testHelper.capture(viewer, "point_light");
// await viewer.setRendering(false);
// });
// test('set point light position', () async {
// var model = await viewer.loadGlb("${testHelper.testDir}/cube.glb");
// await viewer.transformToUnitCube(model);
// var light = await viewer.addLight(
// LightType.POINT, 6500, 1000000, 0, 2, 0, 0, -1, 0,
// falloffRadius: 10.0);
// await viewer.setLightPosition(light, 0.5, 2, 0);
// await viewer.setBackgroundColor(0.0, 0.0, 0.0, 1.0);
// await viewer.setCameraPosition(0, 1, 5);
// await viewer
// .setCameraRotation(Quaternion.axisAngle(Vector3(1, 0, 0), -0.5));
// await viewer.setRendering(true);
// await testHelper.capture(viewer, "move_point_light");
// await viewer.setRendering(false);
// });
// test('directional light', () async {
// var model = await viewer.loadGlb("${testHelper.testDir}/cube.glb");
// await viewer.transformToUnitCube(model);
// var light = await viewer.addLight(
// LightType.SUN, 6500, 1000000, 0, 0, 0, 0, -1, 0);
// await viewer.setBackgroundColor(0.0, 0.0, 0.0, 1.0);
// await viewer.setCameraPosition(0, 1, 5);
// await viewer
// .setCameraRotation(Quaternion.axisAngle(Vector3(1, 0, 0), -0.5));
// await viewer.setRendering(true);
// await testHelper.capture(viewer, "directional_light");
// await viewer.setRendering(false);
// });
// test('set directional light direction', () async {
// var model = await viewer.loadGlb("${testHelper.testDir}/cube.glb");
// await viewer.transformToUnitCube(model);
// var light = await viewer.addLight(
// LightType.SUN, 6500, 1000000, 0, 0, 0, 0, -1, 0);
// await viewer.setLightDirection(light, Vector3(-1, -1, -1));
// await viewer.setBackgroundColor(0.0, 0.0, 0.0, 1.0);
// await viewer.setCameraPosition(0, 1, 5);
// await viewer
// .setCameraRotation(Quaternion.axisAngle(Vector3(1, 0, 0), -0.5));
// await viewer.setRendering(true);
// await testHelper.capture(viewer, "set_directional_light_direction");
// await viewer.setRendering(false);
// });
group("stencil", () {
test('set stencil highlight for glb', () async {
final viewer = await testHelper.createViewer();
var model = await viewer.loadGlb("${testHelper.testDir}/cube.glb", keepData: true);
await viewer.setPostProcessing(true);
var light = await viewer.addLight(
LightType.SUN, 6500, 1000000, 0, 0, 0, 0, -1, 0);
await viewer.setLightDirection(light, Vector3(0, 1, -1));
await viewer.setBackgroundColor(0.0, 1.0, 0.0, 1.0);
await viewer.setCameraPosition(0, -1, 5);
await viewer
.setCameraRotation(Quaternion.axisAngle(Vector3(1, 0, 0), pi / 8));
await viewer.setStencilHighlight(model);
await testHelper.capture(viewer, "stencil_highlight_glb");
});
test('set stencil highlight for geometry', () async {
var viewer = await testHelper.createViewer();
await viewer.setPostProcessing(true);
await viewer.setBackgroundColor(0.0, 1.0, 0.0, 1.0);
await viewer.setCameraPosition(0, 2, 5);
await viewer
.setCameraRotation(Quaternion.axisAngle(Vector3(1, 0, 0), -0.5));
var cube = await viewer.createGeometry(GeometryHelper.cube());
await viewer.setStencilHighlight(cube);
await testHelper.capture(viewer, "stencil_highlight_geometry");
await viewer.removeStencilHighlight(cube);
await testHelper.capture(viewer, "stencil_highlight_geometry_remove");
});
test('set stencil highlight for gltf asset', () async {
var viewer = await testHelper.createViewer();
await viewer.setPostProcessing(true);
await viewer.setBackgroundColor(0.0, 1.0, 0.0, 1.0);
await viewer.setCameraPosition(0, 1, 5);
await viewer
.setCameraRotation(Quaternion.axisAngle(Vector3(1, 0, 0), -0.5));
var cube1 = await viewer.loadGlb("${testHelper.testDir}/cube.glb", keepData: true);
await viewer.transformToUnitCube(cube1);
await viewer.setStencilHighlight(cube1);
await testHelper.capture(viewer, "stencil_highlight_gltf");
await viewer.removeStencilHighlight(cube1);
await testHelper.capture(viewer, "stencil_highlight_gltf_removed");
});
test('set stencil highlight for multiple geometry ', () async {
var viewer = await testHelper.createViewer();
await viewer.setPostProcessing(true);
await viewer.setBackgroundColor(0.0, 1.0, 0.0, 1.0);
await viewer.setCameraPosition(0, 1, 5);
await viewer
.setCameraRotation(Quaternion.axisAngle(Vector3(1, 0, 0), -0.5));
var cube1 = await viewer.createGeometry(GeometryHelper.cube());
var cube2 = await viewer.createGeometry(GeometryHelper.cube());
await viewer.setPosition(cube2, 0.5, 0.5, 0);
await viewer.setStencilHighlight(cube1);
await viewer.setStencilHighlight(cube2, r: 0.0, g: 0.0, b: 1.0);
await testHelper.capture(viewer, "stencil_highlight_multiple_geometry");
await viewer.removeStencilHighlight(cube1);
await viewer.removeStencilHighlight(cube2);
await testHelper.capture(
viewer, "stencil_highlight_multiple_geometry_removed");
});
test('set stencil highlight for multiple gltf assets ', () async {
var viewer = await testHelper.createViewer();
await viewer.setPostProcessing(true);
await viewer.setBackgroundColor(0.0, 1.0, 0.0, 1.0);
await viewer.setCameraPosition(0, 1, 5);
await viewer
.setCameraRotation(Quaternion.axisAngle(Vector3(1, 0, 0), -0.5));
var cube1 = await viewer.loadGlb("${testHelper.testDir}/cube.glb", keepData: true);
await viewer.transformToUnitCube(cube1);
var cube2 = await viewer.loadGlb("${testHelper.testDir}/cube.glb", keepData: true);
await viewer.transformToUnitCube(cube2);
await viewer.setPosition(cube2, 0.5, 0.5, 0);
await viewer.setStencilHighlight(cube1);
await viewer.setStencilHighlight(cube2, r: 0.0, g: 0.0, b: 1.0);
await testHelper.capture(viewer, "stencil_highlight_multiple_geometry");
await viewer.removeStencilHighlight(cube1);
await viewer.removeStencilHighlight(cube2);
await testHelper.capture(
viewer, "stencil_highlight_multiple_geometry_removed");
});
});
group("texture", () {
test("create/apply/dispose texture", () async {
var viewer = await testHelper.createViewer();
var textureData =
File("${testHelper.testDir}/assets/cube_texture_512x512.png").readAsBytesSync();
var texture = await viewer.createTexture(textureData);
await viewer.setBackgroundColor(0.0, 0.0, 0.0, 1.0);
await viewer.addDirectLight(
DirectLight.sun(direction: Vector3(0, -10, -1)..normalize()));
await viewer.addDirectLight(DirectLight.spot(
intensity: 1000000,
position: Vector3(0, 0, 1.5),
direction: Vector3(0, 0, -1)..normalize(),
falloffRadius: 10,
spotLightConeInner: 1,
spotLightConeOuter: 1));
await viewer.setCameraPosition(0, 2, 6);
await viewer
.setCameraRotation(Quaternion.axisAngle(Vector3(1, 0, 0), -pi / 8));
var materialInstance =
await viewer.createUbershaderMaterialInstance(unlit: true);
var cube = await viewer.createGeometry(GeometryHelper.cube(),
materialInstance: materialInstance);
await viewer.setPostProcessing(true);
await viewer.setToneMapping(ToneMapper.LINEAR);
await viewer.applyTexture(texture, cube,
materialIndex: 0, parameterName: "baseColorMap");
await testHelper.capture(viewer, "texture_applied_to_geometry");
await viewer.removeEntity(cube);
await viewer.destroyTexture(texture);
});
});
// group("unproject", () {
// test("unproject", () async {
// final dimensions = (width: 1280, height: 768);
// var viewer = await testHelper.createViewer(viewportDimensions: dimensions);
// await viewer.setPostProcessing(false);
// // await viewer.setToneMapping(ToneMapper.LINEAR);
// await viewer.setBackgroundColor(1.0, 1.0, 1.0, 1.0);
// // await viewer.createIbl(1.0, 1.0, 1.0, 100000);
// await viewer.addLight(LightType.SUN, 6500, 100000, -2, 0, 0, 1, -1, 0);
// await viewer.addLight(LightType.SPOT, 6500, 500000, 0, 0, 2, 0, 0, -1,
// falloffRadius: 10, spotLightConeInner: 1.0, spotLightConeOuter: 2.0);
// await viewer.setCameraPosition(-3, 4, 6);
// await viewer.setCameraRotation(
// Quaternion.axisAngle(Vector3(0, 1, 0), -pi / 8) *
// Quaternion.axisAngle(Vector3(1, 0, 0), -pi / 6));
// var cube =
// await viewer.createGeometry(GeometryHelper.cube(), keepData: true);
// await viewer.setMaterialPropertyFloat4(
// cube, "baseColorFactor", 0, 1.0, 1.0, 1.0, 1.0);
// var textureData =
// File("${testHelper.testDir}/assets/cube_texture_512x512.png").readAsBytesSync();
// var texture = await viewer.createTexture(textureData);
// await viewer.applyTexture(texture, cube,
// materialIndex: 0, parameterName: "baseColorMap");
// var numFrames = 60;
// // first do the render
// for (int i = 0; i < numFrames; i++) {
// await viewer.setCameraPosition(-3 + (i / numFrames * 2), 4, 6);
// await viewer.setCameraRotation(
// Quaternion.axisAngle(Vector3(0, 1, 0), -pi / 8) *
// Quaternion.axisAngle(
// Vector3(1, 0, 0), -pi / 6 - (i / numFrames * pi / 6)));
// var rendered = await testHelper.capture(viewer, "unproject_render$i");
// var renderPng =
// await pixelsToPng(rendered, dimensions.width, dimensions.height);
// File("${outDir.path}/unproject_render${i}.png")
// .writeAsBytesSync(renderPng);
// }
// // then go off and convert the video
// // now unproject the render back onto the geometry
// final textureSize = (width: 1280, height: 768);
// var pixels = <Uint8List>[];
// // note we skip the first frame
// for (int i = 0; i < numFrames; i++) {
// await viewer.setCameraPosition(-3 + (i / numFrames * 2), 4, 6);
// await viewer.setCameraRotation(
// Quaternion.axisAngle(Vector3(0, 1, 0), -pi / 8) *
// Quaternion.axisAngle(
// Vector3(1, 0, 0), -pi / 6 - (i / numFrames * pi / 6)));
// var input = pngToPixelBuffer(File(
// "${outDir.path}/a8c317af-6081-4848-8a06-f6b69bc57664_${i + 1}.png")
// .readAsBytesSync());
// var pixelBuffer = await (await viewer as ThermionViewerFFI).unproject(
// cube,
// input,
// dimensions.width,
// dimensions.height,
// textureSize.width,
// textureSize.height);
// // var png = await pixelsToPng(Uint8List.fromList(pixelBuffer),
// // dimensions.width, dimensions.height);
// await savePixelBufferToBmp(
// pixelBuffer,
// textureSize.width,
// textureSize.height,
// p.join(outDir.path, "unprojected_texture${i}.bmp"));
// pixels.add(pixelBuffer);
// if (i > 10) {
// break;
// }
// }
// // }
// final aggregatePixelBuffer = medianImages(pixels);
// await savePixelBufferToBmp(aggregatePixelBuffer, textureSize.width,
// textureSize.height, "unproject_texture.bmp");
// var pixelBufferPng = await pixelsToPng(
// Uint8List.fromList(aggregatePixelBuffer),
// dimensions.width,
// dimensions.height);
// File("${outDir.path}/unproject_texture.png")
// .writeAsBytesSync(pixelBufferPng);
// await viewer.setPostProcessing(true);
// await viewer.setToneMapping(ToneMapper.LINEAR);
// final unlit = await viewer.createUnlitMaterialInstance();
// await viewer.removeEntity(cube);
// cube = await viewer.createGeometry(GeometryHelper.cube(),
// materialInstance: unlit);
// var reconstructedTexture = await viewer.createTexture(pixelBufferPng);
// await viewer.applyTexture(reconstructedTexture, cube);
// await viewer.setCameraRotation(
// Quaternion.axisAngle(Vector3(0, 1, 0), -pi / 8) *
// Quaternion.axisAngle(Vector3(1, 0, 0), -pi / 6));
// await testHelper.capture(viewer, "unproject_reconstruct");
// // now re-render
// for (int i = 0; i < numFrames; i++) {
// await viewer.setCameraPosition(-3 + (i / numFrames * 2), 4, 6);
// await viewer.setCameraRotation(
// Quaternion.axisAngle(Vector3(0, 1, 0), -pi / 8) *
// Quaternion.axisAngle(
// Vector3(1, 0, 0), -pi / 6 - (i / numFrames * pi / 6)));
// var rendered = await testHelper.capture(viewer, "unproject_rerender$i");
// var renderPng =
// await pixelsToPng(rendered, dimensions.width, dimensions.height);
// File("${outDir.path}/unproject_rerender${i}.png")
// .writeAsBytesSync(renderPng);
// }
// }, timeout: Timeout(Duration(minutes: 2)));
// });
}

View File

@@ -1,3 +1,4 @@
import 'dart:async';
import 'package:test/test.dart'; import 'package:test/test.dart';
import 'helpers.dart'; import 'helpers.dart';

View File

@@ -1,5 +1,3 @@
// ignore_for_file: unused_local_variable
import 'dart:async'; import 'dart:async';
import 'package:test/test.dart'; import 'package:test/test.dart';
@@ -17,6 +15,7 @@ void main() async {
expect(await view.getCamera(), isNotNull); expect(await view.getCamera(), isNotNull);
await viewer.dispose(); await viewer.dispose();
}); });
test('one swapchain, render view to render target', () async { test('one swapchain, render view to render target', () async {
@@ -38,6 +37,7 @@ void main() async {
"default_swapchain_default_view_render_target"); "default_swapchain_default_view_render_target");
await viewer.dispose(); await viewer.dispose();
}); });
test('create secondary view, default swapchain', () async { test('create secondary view, default swapchain', () async {
@@ -113,29 +113,22 @@ void main() async {
test('pick', () async { test('pick', () async {
var viewer = await testHelper.createViewer( var viewer = await testHelper.createViewer(
bg: kRed, cameraPosition: Vector3(0, 0, 3)); bg: kRed, cameraPosition: Vector3(0, 0, 5));
final view = await viewer.getViewAt(0);
await view.setRenderable(true, testHelper.swapChain);
final cube = await viewer.createGeometry(GeometryHelper.cube()); final cube = await viewer.createGeometry(GeometryHelper.cube());
await testHelper.capture(viewer, "view_pick");
final completer = Completer(); final completer = Completer();
late StreamSubscription listener; late StreamSubscription listener;
listener = viewer.pickResult.listen((result) async { listener = viewer.pickResult.listen((result) async {
completer.complete(result.entity); completer.complete(result.entity);
await listener.cancel(); await listener.cancel();
print("Pick result : ${result.fragX} ${result.fragY} ${result.fragZ}");
}); });
await viewer.pick(250, 250); viewer.pick(250, 250);
for (int i = 0; i < 3; i++) { for (int i = 0; i < 10; i++) {
await viewer.render(); await viewer.requestFrame();
await Future.delayed(Duration(milliseconds: 16)); await Future.delayed(Duration(milliseconds: 100));
} }
expect(completer.isCompleted, true); expect(completer.isCompleted, true);

View File

@@ -0,0 +1,81 @@
import 'dart:ffi';
import 'dart:io';
import 'dart:math';
import 'package:ffi/ffi.dart';
import 'package:test/test.dart';
import 'swift/swift_bindings.g.dart';
import 'package:thermion_dart/src/utils/src/dart_resources.dart';
import 'package:thermion_dart/src/viewer/src/ffi/src/thermion_dart.g.dart';
import 'package:thermion_dart/src/viewer/src/ffi/thermion_viewer_ffi.dart';
import 'package:thermion_dart/thermion_dart.dart';
import 'package:vector_math/vector_math_64.dart';
/// Test files are run in a variety of ways, find this package root in all.
///
/// Test files can be run from source from any working directory. The Dart SDK
/// `tools/test.py` runs them from the root of the SDK for example.
///
/// Test files can be run from dill from the root of package. `package:test`
/// does this.
Uri findPackageRoot(String packageName) {
final script = Platform.script;
final fileName = script.name;
// We're likely running from source.
var directory = script.resolve('.');
while (true) {
final dirName = directory.name;
if (dirName == packageName) {
return directory;
}
final parent = directory.resolve('..');
if (parent == directory) break;
directory = parent;
}
throw StateError("Could not find package root for package '$packageName'. "
'Tried finding the package root via Platform.script '
"'${Platform.script.toFilePath()}' and Directory.current "
"'${Directory.current.uri.toFilePath()}'.");
}
extension on Uri {
String get name => pathSegments.where((e) => e != '').last;
}
late String testDir;
void main() async {
final packageUri = findPackageRoot('thermion_dart');
testDir = Directory("${packageUri.toFilePath()}/test").path;
final lib = ThermionTexture1(DynamicLibrary.open(
'${packageUri.toFilePath()}/native/lib/macos/swift/libthermion_swift.dylib'));
final object = ThermionTexture.new1(lib);
object.initWithWidth_height_(500, 500);
final resourceLoader = calloc<ResourceLoaderWrapper>(1);
var loadToOut = NativeCallable<
Void Function(Pointer<Char>,
Pointer<ResourceBuffer>)>.listener(DartResourceLoader.loadResource);
resourceLoader.ref.loadToOut = loadToOut.nativeFunction;
var freeResource = NativeCallable<Void Function(ResourceBuffer)>.listener(
DartResourceLoader.freeResource);
resourceLoader.ref.freeResource = freeResource.nativeFunction;
var viewer = ThermionViewerFFI(resourceLoader: resourceLoader.cast<Void>());
await viewer.initialized;
await viewer.createSwapChain(500, 500);
await viewer.createRenderTarget(500, 500, object.metalTextureAddress);
await viewer.updateViewportAndCameraProjection(500, 500);
group('viewport', () {
test('viewport', () async {
var entity = await viewer.createGeometry(GeometryHelper.cube());
await viewer.setCameraPosition(0.0, 0.0, 4.0);
await viewer.setCameraRotation(Quaternion.axisAngle(Vector3(0,0,1), pi/2));
await viewer.queueRelativePositionUpdateWorldAxis(
entity, 250.0, 250.0, 1, 0, 0);
});
});
}

View File

@@ -0,0 +1,69 @@
import 'dart:ffi';
import 'dart:io';
import 'package:ffi/ffi.dart';
import 'swift/swift_bindings.g.dart';
import 'package:thermion_dart/src/utils/src/dart_resources.dart';
import 'package:thermion_dart/src/viewer/src/ffi/src/thermion_dart.g.dart';
import 'package:thermion_dart/src/viewer/src/ffi/thermion_viewer_ffi.dart';
/// Test files are run in a variety of ways, find this package root in all.
///
/// Test files can be run from source from any working directory. The Dart SDK
/// `tools/test.py` runs them from the root of the SDK for example.
///
/// Test files can be run from dill from the root of package. `package:test`
/// does this.
Uri findPackageRoot(String packageName) {
final script = Platform.script;
final fileName = script.name;
// We're likely running from source.
var directory = script.resolve('.');
while (true) {
final dirName = directory.name;
if (dirName == packageName) {
return directory;
}
final parent = directory.resolve('..');
if (parent == directory) break;
directory = parent;
}
throw StateError("Could not find package root for package '$packageName'. "
'Tried finding the package root via Platform.script '
"'${Platform.script.toFilePath()}' and Directory.current "
"'${Directory.current.uri.toFilePath()}'.");
}
extension on Uri {
String get name => pathSegments.where((e) => e != '').last;
}
late String testDir;
void main() async {
final packageUri = findPackageRoot('thermion_dart');
testDir = Directory("${packageUri.toFilePath()}/test").path;
final lib = ThermionTexture1(DynamicLibrary.open(
'${packageUri.toFilePath()}/native/lib/macos/swift/libthermion_swift.dylib'));
final object = ThermionTexture.new1(lib);
object.initWithWidth_height_(500, 500);
final resourceLoader = calloc<ResourceLoaderWrapper>(1);
var loadToOut = NativeCallable<
Void Function(Pointer<Char>,
Pointer<ResourceBuffer>)>.listener(DartResourceLoader.loadResource);
resourceLoader.ref.loadToOut = loadToOut.nativeFunction;
var freeResource = NativeCallable<Void Function(ResourceBuffer)>.listener(
DartResourceLoader.freeResource);
resourceLoader.ref.freeResource = freeResource.nativeFunction;
var viewer = ThermionViewerFFI(resourceLoader: resourceLoader.cast<Void>());
await viewer.initialized;
await viewer.createSwapChain(500, 500);
await viewer.createRenderTarget(500, 500, object.metalTextureAddress);
await viewer.updateViewportAndCameraProjection(500, 500);
}

View File

@@ -1,11 +1,3 @@
## 0.2.1-dev.14
- Update a dependency to the latest release.
## 0.2.1-dev.13
- Update a dependency to the latest release.
## 0.2.1-dev.12 ## 0.2.1-dev.12
- **FIX**: (flutter) (windows) remove deleted source file from Windows CMakeLists. - **FIX**: (flutter) (windows) remove deleted source file from Windows CMakeLists.

View File

@@ -1,6 +1,6 @@
name: thermion_flutter name: thermion_flutter
description: Flutter plugin for 3D rendering with the Thermion toolkit. description: Flutter plugin for 3D rendering with the Thermion toolkit.
version: 0.2.1-dev.14 version: 0.2.1-dev.12
homepage: https://thermion.dev homepage: https://thermion.dev
repository: https://github.com/nmfisher/thermion repository: https://github.com/nmfisher/thermion
@@ -17,10 +17,10 @@ dependencies:
plugin_platform_interface: ^2.0.0 plugin_platform_interface: ^2.0.0
ffi: ^2.1.2 ffi: ^2.1.2
animation_tools_dart: ^0.1.0 animation_tools_dart: ^0.1.0
thermion_dart: ^0.2.1-dev.0.0.14 thermion_dart: ^0.2.1-dev.0.0.12
thermion_flutter_platform_interface: ^0.2.1-dev.14 thermion_flutter_platform_interface: ^0.2.1-dev.12
thermion_flutter_ffi: ^0.2.1-dev.14 thermion_flutter_ffi: ^0.2.1-dev.12
thermion_flutter_web: ^0.2.0+5 thermion_flutter_web: ^0.2.0+3
logging: ^1.2.0 logging: ^1.2.0
web: ^1.0.0 web: ^1.0.0

View File

@@ -1,11 +1,3 @@
## 0.2.1-dev.14
- Update a dependency to the latest release.
## 0.2.1-dev.13
- Update a dependency to the latest release.
## 0.2.1-dev.12 ## 0.2.1-dev.12
- Update a dependency to the latest release. - Update a dependency to the latest release.

View File

@@ -1,7 +1,7 @@
name: thermion_flutter_ffi name: thermion_flutter_ffi
description: An FFI implementation for thermion_flutter (i.e. all platforms except web). description: An FFI implementation for thermion_flutter (i.e. all platforms except web).
repository: https://github.com/nmfisher/thermion_flutter/thermion_flutter repository: https://github.com/nmfisher/thermion_flutter/thermion_flutter
version: 0.2.1-dev.14 version: 0.2.1-dev.12
environment: environment:
sdk: ">=3.3.0 <4.0.0" sdk: ">=3.3.0 <4.0.0"
@@ -23,8 +23,8 @@ dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
plugin_platform_interface: ^2.1.0 plugin_platform_interface: ^2.1.0
thermion_flutter_platform_interface: ^0.2.1-dev.14 thermion_flutter_platform_interface: ^0.2.1-dev.12
thermion_dart: ^0.2.1-dev.0.0.14 thermion_dart: ^0.2.1-dev.0.0.12
logging: ^1.2.0 logging: ^1.2.0
dev_dependencies: dev_dependencies:

View File

@@ -1,11 +1,3 @@
## 0.2.1-dev.14
- Update a dependency to the latest release.
## 0.2.1-dev.13
- Update a dependency to the latest release.
## 0.2.1-dev.12 ## 0.2.1-dev.12
- Update a dependency to the latest release. - Update a dependency to the latest release.

View File

@@ -1,7 +1,7 @@
name: thermion_flutter_platform_interface name: thermion_flutter_platform_interface
description: A common platform interface for the thermion_flutter plugin. description: A common platform interface for the thermion_flutter plugin.
repository: https://github.com/nmfisher/thermion_flutter/thermion_flutter repository: https://github.com/nmfisher/thermion_flutter/thermion_flutter
version: 0.2.1-dev.14 version: 0.2.1-dev.12
environment: environment:
sdk: ">=3.3.0 <4.0.0" sdk: ">=3.3.0 <4.0.0"
@@ -11,7 +11,7 @@ dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
plugin_platform_interface: ^2.1.0 plugin_platform_interface: ^2.1.0
thermion_dart: ^0.2.1-dev.0.0.14 thermion_dart: ^0.2.1-dev.0.0.12
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:

View File

@@ -1,11 +1,3 @@
## 0.2.0+5
- Update a dependency to the latest release.
## 0.2.0+4
- Update a dependency to the latest release.
## 0.2.0+3 ## 0.2.0+3
- Update a dependency to the latest release. - Update a dependency to the latest release.

View File

@@ -1,7 +1,7 @@
name: thermion_flutter_web name: thermion_flutter_web
description: A web platform interface for the thermion_flutter plugin. description: A web platform interface for the thermion_flutter plugin.
repository: https://github.com/nmfisher/thermion_flutter/thermion_flutter repository: https://github.com/nmfisher/thermion_flutter/thermion_flutter
version: 0.2.0+5 version: 0.2.0+3
environment: environment:
sdk: ">=3.3.0 <4.0.0" sdk: ">=3.3.0 <4.0.0"
@@ -20,8 +20,8 @@ dependencies:
sdk: flutter sdk: flutter
plugin_platform_interface: ^2.1.0 plugin_platform_interface: ^2.1.0
web: ^1.0.0 web: ^1.0.0
thermion_dart: ^0.2.1-dev.0.0.14 thermion_dart: ^0.2.1-dev.0.0.12
thermion_flutter_platform_interface: ^0.2.1-dev.14 thermion_flutter_platform_interface: ^0.2.1-dev.12
flutter_web_plugins: flutter_web_plugins:
sdk: flutter sdk: flutter