restructure viewer/types/helper folders, remove old WASM/web FFI interop, add SceneUpdated stream
This commit is contained in:
@@ -1 +0,0 @@
|
||||
export 'native/compatibility.dart';
|
||||
@@ -1,238 +0,0 @@
|
||||
import 'dart:ffi';
|
||||
export "allocator.dart";
|
||||
export "thermion_dart.g.dart";
|
||||
|
||||
import 'dart:convert';
|
||||
import 'dart:ffi' as ffi hide Uint8Pointer, FloatPointer;
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:thermion_dart/thermion_dart/compatibility/web/ffi/thermion_dart.g.dart';
|
||||
|
||||
import 'package:ffi/ffi.dart';
|
||||
export 'package:ffi/ffi.dart' hide StringUtf8Pointer, Utf8Pointer;
|
||||
export 'dart:ffi'
|
||||
hide
|
||||
Uint8Pointer,
|
||||
FloatPointer,
|
||||
DoublePointer,
|
||||
Int32Pointer,
|
||||
Int64Pointer,
|
||||
PointerPointer,
|
||||
Allocator;
|
||||
|
||||
class Allocator implements ffi.Allocator {
|
||||
const Allocator();
|
||||
@override
|
||||
ffi.Pointer<T> allocate<T extends ffi.NativeType>(int byteCount,
|
||||
{int? alignment}) {
|
||||
return thermion_flutter_web_allocate(byteCount).cast<T>();
|
||||
}
|
||||
|
||||
@override
|
||||
void free(ffi.Pointer<ffi.NativeType> pointer) {
|
||||
thermion_flutter_web_free(pointer.cast<ffi.Void>());
|
||||
}
|
||||
}
|
||||
|
||||
extension CharPointer on ffi.Pointer<ffi.Char> {
|
||||
int get value {
|
||||
return thermion_flutter_web_get(this, 0);
|
||||
}
|
||||
|
||||
set value(int value) {
|
||||
thermion_flutter_web_set(this, 0, value);
|
||||
}
|
||||
|
||||
void operator []=(int index, int value) {
|
||||
this.elementAt(index).value = value;
|
||||
}
|
||||
|
||||
ffi.Pointer<ffi.Char> elementAt(int index) =>
|
||||
ffi.Pointer.fromAddress(address + ffi.sizeOf<ffi.Char>() * index);
|
||||
}
|
||||
|
||||
extension IntPointer on ffi.Pointer<ffi.Int> {
|
||||
int get value {
|
||||
return thermion_flutter_web_get_int32(this.cast<ffi.Int32>(), 0);
|
||||
}
|
||||
|
||||
set value(int value) {
|
||||
thermion_flutter_web_set_int32(this.cast<ffi.Int32>(), 0, value);
|
||||
}
|
||||
|
||||
void operator []=(int index, int value) {
|
||||
this.elementAt(index).value = value;
|
||||
}
|
||||
|
||||
int operator [](int index) {
|
||||
return this.elementAt(index).value;
|
||||
}
|
||||
|
||||
ffi.Pointer<ffi.Int> elementAt(int index) =>
|
||||
ffi.Pointer.fromAddress(address + ffi.sizeOf<ffi.Int>() * index);
|
||||
}
|
||||
|
||||
extension Int32Pointer on ffi.Pointer<ffi.Int32> {
|
||||
int get value {
|
||||
return thermion_flutter_web_get_int32(this, 0);
|
||||
}
|
||||
|
||||
set value(int value) {
|
||||
thermion_flutter_web_set_int32(this, 0, value);
|
||||
}
|
||||
|
||||
void operator []=(int index, int value) {
|
||||
this.elementAt(index).value = value;
|
||||
}
|
||||
|
||||
int operator [](int index) {
|
||||
return this.elementAt(index).value;
|
||||
}
|
||||
|
||||
ffi.Pointer<ffi.Int32> elementAt(int index) =>
|
||||
ffi.Pointer.fromAddress(address + ffi.sizeOf<ffi.Int32>() * index);
|
||||
}
|
||||
|
||||
extension UInt8Pointer on ffi.Pointer<ffi.Uint8> {
|
||||
int get value {
|
||||
return thermion_flutter_web_get(this.cast<ffi.Char>(), 0);
|
||||
}
|
||||
|
||||
set value(int value) {
|
||||
thermion_flutter_web_set(this.cast<ffi.Char>(), 0, value);
|
||||
}
|
||||
|
||||
void operator []=(int index, int value) {
|
||||
this.elementAt(index).value = value;
|
||||
}
|
||||
|
||||
int operator [](int index) {
|
||||
return this.elementAt(index).value;
|
||||
}
|
||||
|
||||
ffi.Pointer<ffi.Uint8> elementAt(int index) =>
|
||||
ffi.Pointer.fromAddress(address + ffi.sizeOf<ffi.Uint8>() * index);
|
||||
}
|
||||
|
||||
extension PointerPointer<T extends ffi.NativeType>
|
||||
on ffi.Pointer<ffi.Pointer<T>> {
|
||||
ffi.Pointer<T> get value {
|
||||
return thermion_flutter_web_get_pointer(cast<ffi.Pointer<ffi.Void>>(), 0)
|
||||
.cast<T>();
|
||||
}
|
||||
|
||||
set value(ffi.Pointer<T> value) {
|
||||
thermion_flutter_web_set_pointer(
|
||||
cast<ffi.Pointer<ffi.Void>>(), 0, value.cast<ffi.Void>());
|
||||
}
|
||||
|
||||
|
||||
ffi.Pointer<T> operator [](int index) {
|
||||
return this.elementAt(index).value;
|
||||
}
|
||||
|
||||
void operator []=(int index, ffi.Pointer<T> value) {
|
||||
this.elementAt(index).value = value;
|
||||
}
|
||||
|
||||
ffi.Pointer<ffi.Pointer<T>> elementAt(int index) =>
|
||||
ffi.Pointer.fromAddress(address + ffi.sizeOf<ffi.Pointer>() * index);
|
||||
}
|
||||
|
||||
extension FloatPointer on ffi.Pointer<ffi.Float> {
|
||||
double get value {
|
||||
return thermion_flutter_web_get_float(this, 0);
|
||||
}
|
||||
|
||||
set value(double value) {
|
||||
thermion_flutter_web_set_float(this, 0, value);
|
||||
}
|
||||
|
||||
double operator [](int index) {
|
||||
return this.elementAt(index).value;
|
||||
}
|
||||
|
||||
void operator []=(int index, double value) {
|
||||
this.elementAt(index).value = value;
|
||||
}
|
||||
|
||||
ffi.Pointer<ffi.Float> elementAt(int index) =>
|
||||
ffi.Pointer.fromAddress(address + ffi.sizeOf<ffi.Float>() * index);
|
||||
|
||||
Float32List asTypedList(int length) {
|
||||
var list = Float32List(length);
|
||||
|
||||
for (int i = 0; i < length; i++) {
|
||||
list[i] = this[i];
|
||||
}
|
||||
return list;
|
||||
}
|
||||
}
|
||||
|
||||
extension StringConversion on String {
|
||||
ffi.Pointer<Utf8> toNativeUtf8({ffi.Allocator? allocator}) {
|
||||
final units = utf8.encode(this);
|
||||
final ffi.Pointer<ffi.Uint8> result =
|
||||
allocator!<ffi.Uint8>(units.length + 1);
|
||||
for (int i = 0; i < units.length; i++) {
|
||||
result.elementAt(i).value = units[i];
|
||||
}
|
||||
result.elementAt(units.length).value = 0;
|
||||
return result.cast();
|
||||
}
|
||||
}
|
||||
|
||||
extension StringUtf8Pointer on ffi.Pointer<Utf8> {
|
||||
static int _length(ffi.Pointer<ffi.Uint8> codeUnits) {
|
||||
var length = 0;
|
||||
while (codeUnits[length] != 0) {
|
||||
length++;
|
||||
}
|
||||
return length;
|
||||
}
|
||||
|
||||
String toDartString({int? length}) {
|
||||
final codeUnits = this.cast<ffi.Uint8>();
|
||||
final list = <int>[];
|
||||
|
||||
if (length != null) {
|
||||
RangeError.checkNotNegative(length, 'length');
|
||||
} else {
|
||||
length = _length(codeUnits);
|
||||
}
|
||||
for (int i = 0; i < length; i++) {
|
||||
list.add(codeUnits.elementAt(i).value);
|
||||
}
|
||||
return utf8.decode(list);
|
||||
}
|
||||
}
|
||||
|
||||
extension DoublePointer on ffi.Pointer<ffi.Double> {
|
||||
double get value {
|
||||
return thermion_flutter_web_get_double(this, 0);
|
||||
}
|
||||
|
||||
set value(double value) {
|
||||
return thermion_flutter_web_set_double(this, 0, value);
|
||||
}
|
||||
|
||||
Float64List asTypedList(int length) {
|
||||
var list = Float64List(length);
|
||||
|
||||
for (int i = 0; i < length; i++) {
|
||||
list[i] = elementAt(i).value;
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
double operator [](int index) {
|
||||
return elementAt(index).value;
|
||||
}
|
||||
|
||||
void operator []=(int index, double value) {
|
||||
elementAt(index).value = value;
|
||||
}
|
||||
|
||||
ffi.Pointer<ffi.Double> elementAt(int index) =>
|
||||
ffi.Pointer.fromAddress(address + ffi.sizeOf<ffi.Double>() * index);
|
||||
}
|
||||
@@ -1,118 +0,0 @@
|
||||
import 'dart:async';
|
||||
import 'dart:js_interop';
|
||||
import 'package:thermion_dart/thermion_dart/compatibility/web/ffi/interop.dart';
|
||||
|
||||
import "allocator.dart";
|
||||
|
||||
export "allocator.dart";
|
||||
export "thermion_dart.g.dart";
|
||||
|
||||
export 'package:ffi/ffi.dart' hide StringUtf8Pointer, Utf8Pointer;
|
||||
export 'dart:ffi'
|
||||
hide
|
||||
Uint8Pointer,
|
||||
FloatPointer,
|
||||
DoublePointer,
|
||||
Int32Pointer,
|
||||
Int64Pointer,
|
||||
PointerPointer,
|
||||
Allocator;
|
||||
|
||||
const allocator = Allocator();
|
||||
|
||||
@AbiSpecificIntegerMapping({
|
||||
Abi.androidArm: Uint8(),
|
||||
Abi.androidArm64: Uint8(),
|
||||
Abi.androidIA32: Int8(),
|
||||
Abi.androidX64: Int8(),
|
||||
Abi.androidRiscv64: Uint8(),
|
||||
Abi.fuchsiaArm64: Uint8(),
|
||||
Abi.fuchsiaX64: Int8(),
|
||||
Abi.fuchsiaRiscv64: Uint8(),
|
||||
Abi.iosArm: Int8(),
|
||||
Abi.iosArm64: Int8(),
|
||||
Abi.iosX64: Int8(),
|
||||
Abi.linuxArm: Uint8(),
|
||||
Abi.linuxArm64: Uint8(),
|
||||
Abi.linuxIA32: Int8(),
|
||||
Abi.linuxX64: Int8(),
|
||||
Abi.linuxRiscv32: Uint8(),
|
||||
Abi.linuxRiscv64: Uint8(),
|
||||
Abi.macosArm64: Int8(),
|
||||
Abi.macosX64: Int8(),
|
||||
Abi.windowsArm64: Int8(),
|
||||
Abi.windowsIA32: Int8(),
|
||||
Abi.windowsX64: Int8(),
|
||||
})
|
||||
final class FooChar extends AbiSpecificInteger {
|
||||
const FooChar();
|
||||
}
|
||||
|
||||
class Compatibility {
|
||||
final _foo = FooChar();
|
||||
}
|
||||
|
||||
Future<void> withVoidCallback(
|
||||
Function(Pointer<NativeFunction<Void Function()>>) func) async {
|
||||
JSArray retVal = createVoidCallback();
|
||||
var promise = retVal.toDart[0] as JSPromise<JSNumber>;
|
||||
var fnPtrAddress = retVal.toDart[1] as JSNumber;
|
||||
var fnPtr = Pointer<NativeFunction<Void Function()>>.fromAddress(
|
||||
fnPtrAddress.toDartInt);
|
||||
func(fnPtr);
|
||||
await promise.toDart;
|
||||
}
|
||||
|
||||
Future<int> withVoidPointerCallback(
|
||||
void Function(Pointer<NativeFunction<Void Function(Pointer<Void>)>>)
|
||||
func) async {
|
||||
JSArray retVal = createVoidPointerCallback();
|
||||
var promise = retVal.toDart[0] as JSPromise<JSNumber>;
|
||||
|
||||
var fnPtrAddress = retVal.toDart[1] as JSNumber;
|
||||
var fnPtr = Pointer<NativeFunction<Void Function(Pointer<Void>)>>.fromAddress(
|
||||
fnPtrAddress.toDartInt);
|
||||
func(fnPtr);
|
||||
final addr = await promise.toDart;
|
||||
return addr.toDartInt;
|
||||
}
|
||||
|
||||
Future<bool> withBoolCallback(
|
||||
Function(Pointer<NativeFunction<Void Function(Bool)>>) func) async {
|
||||
JSArray retVal = createBoolCallback();
|
||||
var promise = retVal.toDart[0] as JSPromise<JSBoolean>;
|
||||
|
||||
var fnPtrAddress = retVal.toDart[1] as JSNumber;
|
||||
var fnPtr = Pointer<NativeFunction<Void Function(Bool)>>.fromAddress(
|
||||
fnPtrAddress.toDartInt);
|
||||
func(fnPtr);
|
||||
final addr = await promise.toDart;
|
||||
return addr.toDart;
|
||||
}
|
||||
|
||||
Future<int> withIntCallback(
|
||||
Function(Pointer<NativeFunction<Void Function(Int32)>>) func) async {
|
||||
JSArray retVal = createBoolCallback();
|
||||
var promise = retVal.toDart[0] as JSPromise<JSNumber>;
|
||||
|
||||
var fnPtrAddress = retVal.toDart[1] as JSNumber;
|
||||
var fnPtr = Pointer<NativeFunction<Void Function(Int32)>>.fromAddress(
|
||||
fnPtrAddress.toDartInt);
|
||||
func(fnPtr);
|
||||
final addr = await promise.toDart;
|
||||
return addr.toDartInt;
|
||||
}
|
||||
|
||||
Future<String> withCharPtrCallback(
|
||||
Function(Pointer<NativeFunction<Void Function(Pointer<Char>)>>)
|
||||
func) async {
|
||||
JSArray retVal = createVoidPointerCallback();
|
||||
var promise = retVal.toDart[0] as JSPromise<JSNumber>;
|
||||
|
||||
var fnPtrAddress = retVal.toDart[1] as JSNumber;
|
||||
var fnPtr = Pointer<NativeFunction<Void Function(Pointer<Char>)>>.fromAddress(
|
||||
fnPtrAddress.toDartInt);
|
||||
func(fnPtr);
|
||||
final addr = await promise.toDart;
|
||||
return Pointer<Utf8>.fromAddress(addr.toDartInt).toDartString();
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
import 'dart:js_interop';
|
||||
|
||||
@JS()
|
||||
external JSArray createIntCallback();
|
||||
|
||||
@JS()
|
||||
external JSArray createBoolCallback();
|
||||
|
||||
@JS()
|
||||
external JSArray createVoidPointerCallback();
|
||||
|
||||
@JS()
|
||||
external JSArray createVoidCallback();
|
||||
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -86,7 +86,6 @@ class Gizmo extends AbstractGizmo {
|
||||
}
|
||||
|
||||
Future attach(ThermionEntity entity) async {
|
||||
print("Attaching");
|
||||
_activeAxis = null;
|
||||
if (entity == _activeEntity) {
|
||||
return;
|
||||
@@ -102,7 +101,7 @@ class Gizmo extends AbstractGizmo {
|
||||
}
|
||||
_activeEntity = entity;
|
||||
await _viewer.setGizmoVisibility(true);
|
||||
await _viewer.setParent(center, entity, preserveScaling: true);
|
||||
await _viewer.setParent(center, entity, preserveScaling: false);
|
||||
_boundingBoxController.sink.add(await _viewer.getViewportBoundingBox(x));
|
||||
|
||||
}
|
||||
|
||||
@@ -1,48 +1,233 @@
|
||||
import 'dart:convert';
|
||||
import 'package:thermion_dart/thermion_dart/thermion_viewer.dart';
|
||||
import 'dart:async';
|
||||
import 'package:vector_math/vector_math_64.dart';
|
||||
|
||||
///
|
||||
/// For now, this class just holds the entities that have been loaded (though not necessarily visible in the Filament Scene).
|
||||
///
|
||||
abstract class Scene {
|
||||
///
|
||||
/// The last entity clicked/tapped in the viewport (internally, the result of calling pick);
|
||||
ThermionEntity? selected;
|
||||
class SceneV2 {
|
||||
final Map<String, AssetInfo> assets;
|
||||
final List<LightInfo> lights;
|
||||
List<CameraInfo> cameras;
|
||||
final List<EntityInfo> entities;
|
||||
EnvironmentInfo? environment;
|
||||
|
||||
///
|
||||
/// A Stream updated whenever an entity is added/removed from the scene.
|
||||
///
|
||||
Stream<bool> get onUpdated;
|
||||
SceneV2({
|
||||
Map<String, AssetInfo>? assets,
|
||||
List<LightInfo>? lights,
|
||||
List<CameraInfo>? cameras,
|
||||
List<EntityInfo>? entities,
|
||||
this.environment,
|
||||
}) : assets = assets ?? {},
|
||||
lights = lights ?? [],
|
||||
cameras = cameras ?? [],
|
||||
entities = entities ?? [];
|
||||
|
||||
///
|
||||
/// A Stream containing every ThermionEntity added to the scene (i.e. via [loadGlb], [loadGltf] or [addLight]).
|
||||
/// This is provided for convenience so you can set listeners in front-end widgets that can respond to entity loads without manually passing around the ThermionEntity returned from those methods.
|
||||
///
|
||||
Stream<ThermionEntity> get onLoad;
|
||||
void addAsset(String uri, AssetType type) {
|
||||
assets[uri] = AssetInfo(uri: uri, type: type);
|
||||
}
|
||||
|
||||
///
|
||||
/// A Stream containing every ThermionEntity removed from the scene (i.e. via [removeEntity], [clearEntities], [removeLight] or [clearLights]).
|
||||
void addLight(LightInfo light) {
|
||||
lights.add(light);
|
||||
}
|
||||
|
||||
Stream<ThermionEntity> get onUnload;
|
||||
void clearAssets() {
|
||||
assets.clear();
|
||||
}
|
||||
|
||||
///
|
||||
/// Lists all light entities currently loaded (not necessarily active in the scene). Does not account for instances.
|
||||
///
|
||||
Iterable<ThermionEntity> listLights();
|
||||
void clearLights() {
|
||||
lights.clear();
|
||||
}
|
||||
|
||||
///
|
||||
/// Lists all entities currently loaded (not necessarily active in the scene). Does not account for instances.
|
||||
///
|
||||
Iterable<ThermionEntity> listEntities();
|
||||
void setCamera(Matrix4 modelMatrix, Matrix4 projectionMatrix) {
|
||||
var camera = cameras.firstWhere((cam) => cam.isActive);
|
||||
camera.modelMatrix = modelMatrix;
|
||||
camera.projectionMatrix = projectionMatrix;
|
||||
}
|
||||
|
||||
///
|
||||
/// Attach the gizmo to the specified entity.
|
||||
///
|
||||
void select(ThermionEntity entity);
|
||||
void addEntity(String assetUri, Matrix4 transform) {
|
||||
if (assets.containsKey(assetUri)) {
|
||||
entities.add(EntityInfo(assetUri: assetUri, transform: transform));
|
||||
} else {
|
||||
throw Exception('Asset not found: $assetUri');
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
///
|
||||
///
|
||||
void registerEntity(ThermionEntity entity);
|
||||
void setEnvironment(String? skyboxUri, String? iblUri) {
|
||||
environment = EnvironmentInfo(skyboxUri: skyboxUri, iblUri: iblUri);
|
||||
}
|
||||
|
||||
}
|
||||
Map<String, dynamic> toJson() => {
|
||||
'assets': assets.map((key, value) => MapEntry(key, value.toJson())),
|
||||
'lights': lights.map((light) => light.toJson()).toList(),
|
||||
'cameras': cameras.map((camera) => camera.toJson()),
|
||||
'entities': entities.map((entity) => entity.toJson()).toList(),
|
||||
'environment': environment?.toJson(),
|
||||
};
|
||||
|
||||
String toJsonString() => jsonEncode(toJson());
|
||||
|
||||
static SceneV2 fromJson(Map<String, dynamic> json) {
|
||||
return SceneV2(
|
||||
assets: (json['assets'] as Map<String, dynamic>).map(
|
||||
(key, value) => MapEntry(key, AssetInfo.fromJson(value)),
|
||||
),
|
||||
lights: (json['lights'] as List)
|
||||
.map((light) => LightInfo.fromJson(light))
|
||||
.toList(),
|
||||
cameras: json['cameras'].map((camera) => CameraInfo.fromJson),
|
||||
entities: (json['entities'] as List)
|
||||
.map((entity) => EntityInfo.fromJson(entity))
|
||||
.toList(),
|
||||
environment: json['environment'] != null
|
||||
? EnvironmentInfo.fromJson(json['environment'])
|
||||
: null,
|
||||
);
|
||||
}
|
||||
|
||||
static SceneV2 fromJsonString(String jsonString) =>
|
||||
fromJson(jsonDecode(jsonString));
|
||||
}
|
||||
|
||||
class AssetInfo {
|
||||
final String uri;
|
||||
final AssetType type;
|
||||
|
||||
AssetInfo({required this.uri, required this.type});
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'uri': uri,
|
||||
'type': type.toString().split('.').last,
|
||||
};
|
||||
|
||||
static AssetInfo fromJson(Map<String, dynamic> json) {
|
||||
return AssetInfo(
|
||||
uri: json['uri'],
|
||||
type: AssetType.values.firstWhere(
|
||||
(e) => e.toString().split('.').last == json['type'],
|
||||
orElse: () => AssetType.glb),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
enum AssetType { glb, gltf, geometryPrimitive }
|
||||
|
||||
class LightInfo {
|
||||
final LightType type;
|
||||
final Vector3 position;
|
||||
final Vector3 direction;
|
||||
final Color color;
|
||||
final double intensity;
|
||||
|
||||
LightInfo({
|
||||
required this.type,
|
||||
required this.position,
|
||||
required this.direction,
|
||||
required this.color,
|
||||
required this.intensity,
|
||||
});
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'type': type.toString().split('.').last,
|
||||
'position': [position.x, position.y, position.z],
|
||||
'direction': [direction.x, direction.y, direction.z],
|
||||
'color': color.toJson(),
|
||||
'intensity': intensity,
|
||||
};
|
||||
|
||||
static LightInfo fromJson(Map<String, dynamic> json) {
|
||||
return LightInfo(
|
||||
type: LightType.values.firstWhere((e) => e.name == json['type']),
|
||||
position: Vector3.array(json['position'].cast<double>()),
|
||||
direction: Vector3.array(json['direction'].cast<double>()),
|
||||
color: Color.fromJson(json['color']),
|
||||
intensity: json['intensity'],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class CameraInfo {
|
||||
final bool isActive;
|
||||
Matrix4 modelMatrix;
|
||||
Matrix4 projectionMatrix;
|
||||
|
||||
CameraInfo(
|
||||
{required this.isActive,
|
||||
required this.modelMatrix,
|
||||
required this.projectionMatrix});
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'modelMatrix': modelMatrix.storage,
|
||||
'projectionMatrix': projectionMatrix.storage,
|
||||
'isActive': isActive,
|
||||
};
|
||||
|
||||
static CameraInfo fromJson(Map<String, dynamic> json) {
|
||||
return CameraInfo(
|
||||
modelMatrix:
|
||||
Matrix4.fromFloat64List(json['modelMatrix'].cast<double>()),
|
||||
projectionMatrix:
|
||||
Matrix4.fromFloat64List(json['modelMatrix'].cast<double>()),
|
||||
isActive: json["isActive"]);
|
||||
}
|
||||
}
|
||||
|
||||
class EntityInfo {
|
||||
final String assetUri;
|
||||
final Matrix4 transform;
|
||||
|
||||
EntityInfo({required this.assetUri, required this.transform});
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'assetUri': assetUri,
|
||||
'transform': transform.storage,
|
||||
};
|
||||
|
||||
static EntityInfo fromJson(Map<String, dynamic> json) {
|
||||
return EntityInfo(
|
||||
assetUri: json['assetUri'],
|
||||
transform: Matrix4.fromList(List<double>.from(json['transform'])),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class EnvironmentInfo {
|
||||
final String? skyboxUri;
|
||||
final String? iblUri;
|
||||
|
||||
EnvironmentInfo({this.skyboxUri, this.iblUri});
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'skyboxUri': skyboxUri,
|
||||
'iblUri': iblUri,
|
||||
};
|
||||
|
||||
static EnvironmentInfo fromJson(Map<String, dynamic> json) {
|
||||
return EnvironmentInfo(
|
||||
skyboxUri: json['skyboxUri'],
|
||||
iblUri: json['iblUri'],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class Color {
|
||||
final double r;
|
||||
final double g;
|
||||
final double b;
|
||||
final double a;
|
||||
|
||||
Color({required this.r, required this.g, required this.b, this.a = 1.0});
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'r': r,
|
||||
'g': g,
|
||||
'b': b,
|
||||
'a': a,
|
||||
};
|
||||
|
||||
static Color fromJson(Map<String, dynamic> json) {
|
||||
return Color(
|
||||
r: json['r'],
|
||||
g: json['g'],
|
||||
b: json['b'],
|
||||
a: json['a'],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,125 +0,0 @@
|
||||
import 'dart:async';
|
||||
import 'package:thermion_dart/thermion_dart/scene.dart';
|
||||
import 'thermion_viewer.dart';
|
||||
|
||||
///
|
||||
/// For now, this class just holds the entities that have been loaded (though not necessarily visible in the Filament Scene).
|
||||
///
|
||||
class SceneImpl extends Scene {
|
||||
ThermionViewer controller;
|
||||
|
||||
SceneImpl(this.controller);
|
||||
|
||||
@override
|
||||
ThermionEntity? selected;
|
||||
|
||||
final _onUpdatedController = StreamController<bool>.broadcast();
|
||||
@override
|
||||
Stream<bool> get onUpdated => _onUpdatedController.stream;
|
||||
|
||||
final _onLoadController = StreamController<ThermionEntity>.broadcast();
|
||||
@override
|
||||
Stream<ThermionEntity> get onLoad => _onLoadController.stream;
|
||||
|
||||
final _onUnloadController = StreamController<ThermionEntity>.broadcast();
|
||||
@override
|
||||
Stream<ThermionEntity> get onUnload => _onUnloadController.stream;
|
||||
|
||||
final _lights = <ThermionEntity>{};
|
||||
final _entities = <ThermionEntity>{};
|
||||
|
||||
void registerLight(ThermionEntity entity) {
|
||||
_lights.add(entity);
|
||||
_onLoadController.sink.add(entity);
|
||||
_onUpdatedController.add(true);
|
||||
}
|
||||
|
||||
void unregisterLight(ThermionEntity entity) async {
|
||||
var children = await controller.getChildEntities(entity, true);
|
||||
if (selected == entity || children.contains(selected)) {
|
||||
selected = null;
|
||||
controller.gizmo?.detach();
|
||||
}
|
||||
_lights.remove(entity);
|
||||
_onUnloadController.add(entity);
|
||||
_onUpdatedController.add(true);
|
||||
}
|
||||
|
||||
void unregisterEntity(ThermionEntity entity) async {
|
||||
var children = await controller.getChildEntities(entity, true);
|
||||
if (selected == entity || children.contains(selected)) {
|
||||
selected = null;
|
||||
|
||||
controller.gizmo?.detach();
|
||||
}
|
||||
_entities.remove(entity);
|
||||
_onUnloadController.add(entity);
|
||||
_onUpdatedController.add(true);
|
||||
}
|
||||
|
||||
void registerEntity(ThermionEntity entity) {
|
||||
_entities.add(entity);
|
||||
_onLoadController.sink.add(entity);
|
||||
_onUpdatedController.add(true);
|
||||
}
|
||||
|
||||
void clearLights() {
|
||||
for (final light in _lights) {
|
||||
if (selected == light) {
|
||||
selected = null;
|
||||
controller.gizmo?.detach();
|
||||
}
|
||||
_onUnloadController.add(light);
|
||||
}
|
||||
|
||||
_lights.clear();
|
||||
_onUpdatedController.add(true);
|
||||
}
|
||||
|
||||
void clearEntities() {
|
||||
for (final entity in _entities) {
|
||||
if (selected == entity) {
|
||||
selected = null;
|
||||
controller.gizmo?.detach();
|
||||
}
|
||||
_onUnloadController.add(entity);
|
||||
}
|
||||
_entities.clear();
|
||||
_onUpdatedController.add(true);
|
||||
}
|
||||
|
||||
///
|
||||
/// Lists all entities currently loaded (not necessarily active in the scene).
|
||||
///
|
||||
Iterable<ThermionEntity> listLights() {
|
||||
return _lights;
|
||||
}
|
||||
|
||||
@override
|
||||
Iterable<ThermionEntity> listEntities() {
|
||||
return _entities;
|
||||
}
|
||||
|
||||
void registerSelected(ThermionEntity entity) {
|
||||
selected = entity;
|
||||
_onUpdatedController.add(true);
|
||||
}
|
||||
|
||||
void unregisterSelected() {
|
||||
selected = null;
|
||||
_onUpdatedController.add(true);
|
||||
}
|
||||
|
||||
@override
|
||||
void select(ThermionEntity entity) {
|
||||
selected = entity;
|
||||
controller.gizmo?.attach(entity);
|
||||
_onUpdatedController.add(true);
|
||||
}
|
||||
|
||||
Future dispose() async {
|
||||
await _onLoadController.close();
|
||||
await _onUnloadController.close();
|
||||
await _onUpdatedController.close();
|
||||
}
|
||||
}
|
||||
@@ -1,885 +1,6 @@
|
||||
import 'dart:math';
|
||||
import 'dart:typed_data';
|
||||
library thermion_viewer;
|
||||
|
||||
import 'package:thermion_dart/thermion_dart/entities/abstract_gizmo.dart';
|
||||
import 'package:thermion_dart/thermion_dart/scene.dart';
|
||||
import 'package:vector_math/vector_math_64.dart';
|
||||
import 'dart:async';
|
||||
import 'package:animation_tools_dart/animation_tools_dart.dart';
|
||||
|
||||
// a handle that can be safely passed back to the rendering layer to manipulate an Entity
|
||||
typedef ThermionEntity = int;
|
||||
|
||||
// "picking" means clicking/tapping on the viewport, and unprojecting the X/Y coordinate to determine whether any renderable entities were present at those coordinates.
|
||||
typedef FilamentPickResult = ({ThermionEntity entity, double x, double y});
|
||||
|
||||
enum LightType {
|
||||
SUN, //!< Directional light that also draws a sun's disk in the sky.
|
||||
DIRECTIONAL, //!< Directional light, emits light in a given direction.
|
||||
POINT, //!< Point light, emits light from a position, in all directions.
|
||||
FOCUSED_SPOT, //!< Physically correct spot light.
|
||||
SPOT,
|
||||
}
|
||||
|
||||
enum ShadowType {
|
||||
PCF, //!< percentage-closer filtered shadows (default)
|
||||
VSM, //!< variance shadows
|
||||
DPCF, //!< PCF with contact hardening simulation
|
||||
PCSS, //!< PCF with soft shadows and contact hardening
|
||||
}
|
||||
|
||||
// copied from filament/backened/DriverEnums.h
|
||||
enum PrimitiveType {
|
||||
// don't change the enums values (made to match GL)
|
||||
POINTS, //!< points
|
||||
LINES, //!< lines
|
||||
UNUSED1,
|
||||
LINE_STRIP, //!< line strip
|
||||
TRIANGLES, //!< triangles
|
||||
TRIANGLE_STRIP, //!< triangle strip
|
||||
}
|
||||
|
||||
enum ToneMapper { ACES, FILMIC, LINEAR }
|
||||
|
||||
// see filament Manipulator.h for more details
|
||||
enum ManipulatorMode { ORBIT, MAP, FREE_FLIGHT }
|
||||
|
||||
class TextureDetails {
|
||||
final int textureId;
|
||||
|
||||
// both width and height are in physical, not logical pixels
|
||||
final int width;
|
||||
final int height;
|
||||
|
||||
TextureDetails(
|
||||
{required this.textureId, required this.width, required this.height});
|
||||
}
|
||||
|
||||
abstract class ThermionViewer {
|
||||
Future<bool> get initialized;
|
||||
|
||||
///
|
||||
/// The current dimensions of the viewport (in physical pixels).
|
||||
///
|
||||
var viewportDimensions = (0.0,0.0);
|
||||
|
||||
///
|
||||
/// The current ratio of logical to physical pixels.
|
||||
///
|
||||
late double pixelRatio;
|
||||
|
||||
///
|
||||
/// The result(s) of calling [pick] (see below).
|
||||
/// This may be a broadcast stream, so you should ensure you have subscribed to this stream before calling [pick].
|
||||
/// If [pick] is called without an active subscription to this stream, the results will be silently discarded.
|
||||
///
|
||||
Stream<FilamentPickResult> get pickResult;
|
||||
|
||||
///
|
||||
/// The result(s) of calling [pickGizmo] (see below).
|
||||
///
|
||||
Stream<FilamentPickResult> get gizmoPickResult;
|
||||
|
||||
///
|
||||
/// Whether the controller is currently rendering at [framerate].
|
||||
///
|
||||
bool get rendering;
|
||||
|
||||
///
|
||||
/// Set to true to continuously render the scene at the framerate specified by [setFrameRate] (60 fps by default).
|
||||
///
|
||||
Future setRendering(bool render);
|
||||
|
||||
///
|
||||
/// Render a single frame.
|
||||
///
|
||||
Future render();
|
||||
|
||||
///
|
||||
/// Render a single frame to the viewport and copy the pixel buffer to [out].
|
||||
///
|
||||
Future<Uint8List> capture();
|
||||
|
||||
///
|
||||
/// Sets the framerate for continuous rendering when [setRendering] is enabled.
|
||||
///
|
||||
Future setFrameRate(int framerate);
|
||||
|
||||
///
|
||||
/// Destroys/disposes the viewer (including the entire scene). You cannot use the viewer after calling this method.
|
||||
///
|
||||
Future dispose();
|
||||
|
||||
///
|
||||
/// Set the background image to [path] (which should have a file extension .png, .jpg, or .ktx).
|
||||
/// This will be rendered at the maximum depth (i.e. behind all other objects including the skybox).
|
||||
/// If [fillHeight] is false, the image will be rendered at its original size. Note this may cause issues with pixel density so be sure to specify the correct resolution
|
||||
/// If [fillHeight] is true, the image will be stretched/compressed to fit the height of the viewport.
|
||||
///
|
||||
Future setBackgroundImage(String path, {bool fillHeight = false});
|
||||
|
||||
///
|
||||
/// Moves the background image to the relative offset from the origin (bottom-left) specified by [x] and [y].
|
||||
/// If [clamp] is true, the image cannot be positioned outside the bounds of the viewport.
|
||||
///
|
||||
Future setBackgroundImagePosition(double x, double y, {bool clamp = false});
|
||||
|
||||
///
|
||||
/// Removes the background image.
|
||||
///
|
||||
Future clearBackgroundImage();
|
||||
|
||||
///
|
||||
/// Sets the color for the background plane (positioned at the maximum depth, i.e. behind all other objects including the skybox).
|
||||
///
|
||||
Future setBackgroundColor(double r, double g, double b, double alpha);
|
||||
|
||||
///
|
||||
/// Load a skybox from [skyboxPath] (which must be a .ktx file)
|
||||
///
|
||||
Future loadSkybox(String skyboxPath);
|
||||
|
||||
///
|
||||
/// Removes the skybox from the scene.
|
||||
///
|
||||
Future removeSkybox();
|
||||
|
||||
///
|
||||
/// Creates an indirect light by loading the reflections/irradiance from the KTX file.
|
||||
/// Only one indirect light can be active at any given time; if an indirect light has already been loaded, it will be replaced.
|
||||
///
|
||||
Future loadIbl(String lightingPath, {double intensity = 30000});
|
||||
|
||||
///
|
||||
/// Creates a indirect light with the given color.
|
||||
/// Only one indirect light can be active at any given time; if an indirect light has already been loaded, it will be replaced.
|
||||
///
|
||||
Future createIbl(double r, double g, double b, double intensity);
|
||||
|
||||
///
|
||||
/// Rotates the IBL & skybox.
|
||||
///
|
||||
Future rotateIbl(Matrix3 rotation);
|
||||
|
||||
///
|
||||
/// Removes the image-based light from the scene.
|
||||
///
|
||||
Future removeIbl();
|
||||
|
||||
///
|
||||
/// Add a light to the scene.
|
||||
/// See LightManager.h for details
|
||||
/// Note that [sunAngularRadius] is in degrees,
|
||||
/// whereas [spotLightConeInner] and [spotLightConeOuter] are in radians
|
||||
///
|
||||
Future<ThermionEntity> addLight(
|
||||
LightType type,
|
||||
double colour,
|
||||
double intensity,
|
||||
double posX,
|
||||
double posY,
|
||||
double posZ,
|
||||
double dirX,
|
||||
double dirY,
|
||||
double dirZ,
|
||||
{double falloffRadius = 1.0,
|
||||
double spotLightConeInner = pi / 8,
|
||||
double spotLightConeOuter = pi / 4,
|
||||
double sunAngularRadius = 0.545,
|
||||
double sunHaloSize = 10.0,
|
||||
double sunHaloFallof = 80.0,
|
||||
bool castShadows = true});
|
||||
|
||||
Future removeLight(ThermionEntity light);
|
||||
|
||||
///
|
||||
/// Remove all lights (excluding IBL) from the scene.
|
||||
///
|
||||
Future clearLights();
|
||||
|
||||
///
|
||||
/// Load the .glb asset at the given path and insert into the scene.
|
||||
/// Specify [numInstances] to create multiple instances (this is more efficient than dynamically instantating at a later time). You can then retrieve the created instances with [getInstances].
|
||||
/// If you want to be able to call [createInstance] at a later time, you must pass true for [keepData].
|
||||
/// If [keepData] is false, the source glTF data will be released and [createInstance] will throw an exception.
|
||||
///
|
||||
Future<ThermionEntity> loadGlb(String path,
|
||||
{int numInstances = 1, bool keepData = false});
|
||||
|
||||
///
|
||||
/// Load the .glb asset from the specified buffer and insert into the scene.
|
||||
/// Specify [numInstances] to create multiple instances (this is more efficient than dynamically instantating at a later time). You can then retrieve the created instances with [getInstances].
|
||||
/// If you want to be able to call [createInstance] at a later time, you must pass true for [keepData].
|
||||
/// If [keepData] is false, the source glTF data will be released and [createInstance] will throw an exception.
|
||||
///
|
||||
Future<ThermionEntity> loadGlbFromBuffer(Uint8List data,
|
||||
{int numInstances = 1, bool keepData = false});
|
||||
|
||||
///
|
||||
/// Create a new instance of [entity].
|
||||
///
|
||||
Future<ThermionEntity> createInstance(ThermionEntity entity);
|
||||
|
||||
///
|
||||
/// Returns the number of instances of the asset associated with [entity].
|
||||
///
|
||||
Future<int> getInstanceCount(ThermionEntity entity);
|
||||
|
||||
///
|
||||
/// Returns all instances of [entity].
|
||||
///
|
||||
Future<List<ThermionEntity>> getInstances(ThermionEntity entity);
|
||||
|
||||
///
|
||||
/// Load the .gltf asset at the given path and insert into the scene.
|
||||
/// [relativeResourcePath] is the folder path where the glTF resources are stored;
|
||||
/// this is usually the parent directory of the .gltf file itself.
|
||||
///
|
||||
/// See [loadGlb] for an explanation of [keepData].
|
||||
///
|
||||
Future<ThermionEntity> loadGltf(String path, String relativeResourcePath,
|
||||
{bool keepData = false});
|
||||
|
||||
///
|
||||
/// Called by `FilamentGestureDetector`. You probably don't want to call this yourself.
|
||||
///
|
||||
Future panStart(double x, double y);
|
||||
|
||||
///
|
||||
/// Called by `FilamentGestureDetector`. You probably don't want to call this yourself.
|
||||
///
|
||||
Future panUpdate(double x, double y);
|
||||
|
||||
///
|
||||
/// Called by `FilamentGestureDetector`. You probably don't want to call this yourself.
|
||||
///
|
||||
Future panEnd();
|
||||
|
||||
///
|
||||
/// Called by `FilamentGestureDetector`. You probably don't want to call this yourself.
|
||||
///
|
||||
Future rotateStart(double x, double y);
|
||||
|
||||
///
|
||||
/// Called by `FilamentGestureDetector`. You probably don't want to call this yourself.
|
||||
///
|
||||
Future rotateUpdate(double x, double y);
|
||||
|
||||
///
|
||||
/// Called by `FilamentGestureDetector`. You probably don't want to call this yourself.
|
||||
///
|
||||
Future rotateEnd();
|
||||
|
||||
///
|
||||
/// Set the weights for all morph targets in [entity] to [weights].
|
||||
/// Note that [weights] must contain values for ALL morph targets, but no exception will be thrown if you don't do so (you'll just get incorrect results).
|
||||
/// If you only want to set one value, set all others to zero (check [getMorphTargetNames] if you need the get a list of all morph targets).
|
||||
/// IMPORTANT - this accepts the actual ThermionEntity with the relevant morph targets (unlike [getMorphTargetNames], which uses the parent entity and the child mesh name).
|
||||
/// Use [getChildEntityByName] if you are setting the weights for a child mesh.
|
||||
///
|
||||
Future setMorphTargetWeights(ThermionEntity entity, List<double> weights);
|
||||
|
||||
///
|
||||
/// Gets the names of all morph targets for the child renderable [childEntity] under [entity].
|
||||
///
|
||||
Future<List<String>> getMorphTargetNames(
|
||||
ThermionEntity entity, ThermionEntity childEntity);
|
||||
|
||||
///
|
||||
/// Gets the names of all bones for the armature at [skinIndex] under the specified [entity].
|
||||
///
|
||||
Future<List<String>> getBoneNames(ThermionEntity entity, {int skinIndex = 0});
|
||||
|
||||
///
|
||||
/// Gets the names of all glTF animations embedded in the specified entity.
|
||||
///
|
||||
Future<List<String>> getAnimationNames(ThermionEntity entity);
|
||||
|
||||
///
|
||||
/// Returns the length (in seconds) of the animation at the given index.
|
||||
///
|
||||
Future<double> getAnimationDuration(
|
||||
ThermionEntity entity, int animationIndex);
|
||||
|
||||
///
|
||||
/// Animate the morph targets in [entity]. See [MorphTargetAnimation] for an explanation as to how to construct the animation frame data.
|
||||
/// This method will check the morph target names specified in [animation] against the morph target names that actually exist exist under [meshName] in [entity],
|
||||
/// throwing an exception if any cannot be found.
|
||||
/// It is permissible for [animation] to omit any targets that do exist under [meshName]; these simply won't be animated.
|
||||
///
|
||||
Future setMorphAnimationData(
|
||||
ThermionEntity entity, MorphAnimationData animation,
|
||||
{List<String>? targetMeshNames});
|
||||
|
||||
///
|
||||
/// Clear all current morph animations for [entity].
|
||||
///
|
||||
Future clearMorphAnimationData(ThermionEntity entity);
|
||||
|
||||
///
|
||||
/// Resets all bones in the given entity to their rest pose.
|
||||
/// This should be done before every call to addBoneAnimation.
|
||||
///
|
||||
Future resetBones(ThermionEntity entity);
|
||||
|
||||
///
|
||||
/// Enqueues and plays the [animation] for the specified bone(s).
|
||||
/// By default, frame data is interpreted as being in *parent* bone space;
|
||||
/// a 45 degree around Y means the bone will rotate 45 degrees around the
|
||||
/// Y axis of the parent bone *in its current orientation*.
|
||||
/// (i.e NOT the parent bone's rest position!).
|
||||
/// Currently, only [Space.ParentBone] and [Space.Model] are supported; if you want
|
||||
/// to transform to another space, you will need to do so manually.
|
||||
///
|
||||
/// [fadeInInSecs]/[fadeOutInSecs]/[maxDelta] are used to cross-fade between
|
||||
/// the current active glTF animation ("animation1") and the animation you
|
||||
/// set via this method ("animation2"). The bone orientations will be
|
||||
/// linearly interpolated between animation1 and animation2; at time 0,
|
||||
/// the orientation will be 100% animation1, at time [fadeInInSecs], the
|
||||
/// animation will be ((1 - maxDelta) * animation1) + (maxDelta * animation2).
|
||||
/// This will be applied in reverse after [fadeOutInSecs].
|
||||
///
|
||||
///
|
||||
Future addBoneAnimation(ThermionEntity entity, BoneAnimationData animation,
|
||||
{int skinIndex = 0,
|
||||
double fadeInInSecs = 0.0,
|
||||
double fadeOutInSecs = 0.0,
|
||||
double maxDelta = 1.0});
|
||||
|
||||
///
|
||||
/// Gets the entity representing the bone at [boneIndex]/[skinIndex].
|
||||
/// The returned entity is only intended for use with [getWorldTransform].
|
||||
///
|
||||
Future<ThermionEntity> getBone(ThermionEntity parent, int boneIndex,
|
||||
{int skinIndex = 0});
|
||||
|
||||
///
|
||||
/// Gets the local (relative to parent) transform for [entity].
|
||||
///
|
||||
Future<Matrix4> getLocalTransform(ThermionEntity entity);
|
||||
|
||||
///
|
||||
/// Gets the world transform for [entity].
|
||||
///
|
||||
Future<Matrix4> getWorldTransform(ThermionEntity entity);
|
||||
|
||||
///
|
||||
/// Gets the inverse bind (pose) matrix for the bone.
|
||||
/// Note that [parent] must be the ThermionEntity returned by [loadGlb/loadGltf], not any other method ([getChildEntity] etc).
|
||||
/// This is because all joint information is internally stored with the parent entity.
|
||||
///
|
||||
Future<Matrix4> getInverseBindMatrix(ThermionEntity parent, int boneIndex,
|
||||
{int skinIndex = 0});
|
||||
|
||||
///
|
||||
/// Sets the transform (relative to its parent) for [entity].
|
||||
///
|
||||
Future setTransform(ThermionEntity entity, Matrix4 transform);
|
||||
|
||||
///
|
||||
/// Updates the bone matrices for [entity] (which must be the ThermionEntity
|
||||
/// returned by [loadGlb/loadGltf]).
|
||||
/// Under the hood, this just calls [updateBoneMatrices] on the Animator
|
||||
/// instance of the relevant FilamentInstance (which uses the local
|
||||
/// bone transform and the inverse bind matrix to set the bone matrix).
|
||||
///
|
||||
Future updateBoneMatrices(ThermionEntity entity);
|
||||
|
||||
///
|
||||
/// Directly set the bone matrix for the bone at the given index.
|
||||
/// Don't call this manually unless you know what you're doing.
|
||||
///
|
||||
Future setBoneTransform(
|
||||
ThermionEntity entity, int boneIndex, Matrix4 transform,
|
||||
{int skinIndex = 0});
|
||||
|
||||
///
|
||||
/// Removes/destroys the specified entity from the scene.
|
||||
/// [entity] will no longer be a valid handle after this method is called; ensure you immediately discard all references once this method is complete.
|
||||
///
|
||||
Future removeEntity(ThermionEntity entity);
|
||||
|
||||
///
|
||||
/// Removes/destroys all renderable entities from the scene (including cameras).
|
||||
/// All [ThermionEntity] handles will no longer be valid after this method is called; ensure you immediately discard all references to all entities once this method is complete.
|
||||
///
|
||||
Future clearEntities();
|
||||
|
||||
///
|
||||
/// Called by `FilamentGestureDetector`. You probably don't want to call this yourself.
|
||||
///
|
||||
Future zoomBegin();
|
||||
|
||||
///
|
||||
/// Called by `FilamentGestureDetector`. You probably don't want to call this yourself.
|
||||
///
|
||||
Future zoomUpdate(double x, double y, double z);
|
||||
|
||||
///
|
||||
/// Called by `FilamentGestureDetector`. You probably don't want to call this yourself.
|
||||
///
|
||||
Future zoomEnd();
|
||||
|
||||
///
|
||||
/// Schedules the glTF animation at [index] in [entity] to start playing on the next frame.
|
||||
///
|
||||
Future playAnimation(ThermionEntity entity, int index,
|
||||
{bool loop = false,
|
||||
bool reverse = false,
|
||||
bool replaceActive = true,
|
||||
double crossfade = 0.0,
|
||||
double startOffset = 0.0});
|
||||
|
||||
///
|
||||
/// Schedules the glTF animation at [index] in [entity] to start playing on the next frame.
|
||||
///
|
||||
Future playAnimationByName(ThermionEntity entity, String name,
|
||||
{bool loop = false,
|
||||
bool reverse = false,
|
||||
bool replaceActive = true,
|
||||
double crossfade = 0.0});
|
||||
|
||||
Future setAnimationFrame(
|
||||
ThermionEntity entity, int index, int animationFrame);
|
||||
|
||||
Future stopAnimation(ThermionEntity entity, int animationIndex);
|
||||
Future stopAnimationByName(ThermionEntity entity, String name);
|
||||
|
||||
///
|
||||
/// Sets the current scene camera to the glTF camera under [name] in [entity].
|
||||
///
|
||||
Future setCamera(ThermionEntity entity, String? name);
|
||||
|
||||
///
|
||||
/// Sets the current scene camera to the main camera (which is always available and added to every scene by default).
|
||||
///
|
||||
Future setMainCamera();
|
||||
|
||||
///
|
||||
/// Returns the entity associated with the main camera.
|
||||
///
|
||||
Future<ThermionEntity> getMainCamera();
|
||||
|
||||
///
|
||||
/// Sets the horizontal field of view (if [horizontal] is true) or vertical field of view for the currently active camera to [degrees].
|
||||
/// The aspect ratio of the current viewport is used.
|
||||
///
|
||||
Future setCameraFov(double degrees, {bool horizontal = true});
|
||||
|
||||
///
|
||||
/// Gets the field of view (in degrees).
|
||||
///
|
||||
Future<double> getCameraFov(bool horizontal);
|
||||
|
||||
///
|
||||
/// Sets the tone mapping (requires postprocessing).
|
||||
///
|
||||
Future setToneMapping(ToneMapper mapper);
|
||||
|
||||
///
|
||||
/// Sets the strength of the bloom.
|
||||
///
|
||||
Future setBloom(double bloom);
|
||||
|
||||
///
|
||||
/// Sets the focal length of the camera. Default value is 28.0.
|
||||
///
|
||||
Future setCameraFocalLength(double focalLength);
|
||||
|
||||
///
|
||||
/// Sets the distance (in world units) to the near/far planes for the active camera. Default values are 0.05/1000.0. See Camera.h for details.
|
||||
///
|
||||
Future setCameraCulling(double near, double far);
|
||||
|
||||
///
|
||||
/// Get the distance (in world units) to the near plane for the active camera.
|
||||
///
|
||||
@Deprecated("Use getCameraNear")
|
||||
Future<double> getCameraCullingNear();
|
||||
|
||||
///
|
||||
/// Get the distance (in world units) to the near plane for the active camera.
|
||||
///
|
||||
Future<double> getCameraNear();
|
||||
|
||||
///
|
||||
/// Get the distance (in world units) to the far culling plane for the active camera.
|
||||
///
|
||||
Future<double> getCameraCullingFar();
|
||||
|
||||
///
|
||||
///
|
||||
///
|
||||
Future setCameraLensProjection(double near, double far, double aspect, double focalLength);
|
||||
|
||||
///
|
||||
/// Sets the focus distance for the camera.
|
||||
///
|
||||
Future setCameraFocusDistance(double focusDistance);
|
||||
|
||||
///
|
||||
/// Get the camera position in world space.
|
||||
///
|
||||
Future<Vector3> getCameraPosition();
|
||||
|
||||
///
|
||||
/// Get the camera's model matrix.
|
||||
///
|
||||
Future<Matrix4> getCameraModelMatrix();
|
||||
|
||||
///
|
||||
/// Get the camera's view matrix. See Camera.h for more details.
|
||||
///
|
||||
Future<Matrix4> getCameraViewMatrix();
|
||||
|
||||
///
|
||||
/// Get the camera's projection matrix. See Camera.h for more details.
|
||||
///
|
||||
Future<Matrix4> getCameraProjectionMatrix();
|
||||
|
||||
///
|
||||
/// Get the camera's culling projection matrix. See Camera.h for more details.
|
||||
///
|
||||
Future<Matrix4> getCameraCullingProjectionMatrix();
|
||||
|
||||
///
|
||||
/// Get the camera's culling frustum in world space. Returns a (vector_math) [Frustum] instance where plane0-plane6 define the left, right, bottom, top, far and near planes respectively.
|
||||
/// See Camera.h and (filament) Frustum.h for more details.
|
||||
///
|
||||
Future<Frustum> getCameraFrustum();
|
||||
|
||||
///
|
||||
/// Set the camera position in world space. Note this is not persistent - any viewport navigation will reset the camera transform.
|
||||
///
|
||||
Future setCameraPosition(double x, double y, double z);
|
||||
|
||||
///
|
||||
/// Get the camera rotation matrix.
|
||||
///
|
||||
Future<Matrix3> getCameraRotation();
|
||||
|
||||
///
|
||||
/// Repositions the camera to the last vertex of the bounding box of [entity], looking at the penultimate vertex.
|
||||
///
|
||||
Future moveCameraToAsset(ThermionEntity entity);
|
||||
|
||||
///
|
||||
/// Enables/disables frustum culling. Currently we don't expose a method for manipulating the camera projection/culling matrices so this is your only option to deal with unwanted near/far clipping.
|
||||
///
|
||||
Future setViewFrustumCulling(bool enabled);
|
||||
|
||||
///
|
||||
/// Sets the camera exposure.
|
||||
///
|
||||
Future setCameraExposure(
|
||||
double aperture, double shutterSpeed, double sensitivity);
|
||||
|
||||
///
|
||||
/// Rotate the camera by [rads] around the given axis.
|
||||
///
|
||||
Future setCameraRotation(Quaternion quaternion);
|
||||
|
||||
///
|
||||
/// Sets the camera model matrix.
|
||||
///
|
||||
@Deprecated("Will be superseded by setCameraModelMatrix4")
|
||||
Future setCameraModelMatrix(List<double> matrix);
|
||||
|
||||
///
|
||||
/// Sets the camera model matrix.
|
||||
///
|
||||
Future setCameraModelMatrix4(Matrix4 matrix);
|
||||
|
||||
///
|
||||
/// Sets the `baseColorFactor` property for the material at index [materialIndex] in [entity] under node [meshName] to [color].
|
||||
///
|
||||
@Deprecated("Use setMaterialPropertyFloat4 instead")
|
||||
Future setMaterialColor(ThermionEntity entity, String meshName,
|
||||
int materialIndex, double r, double g, double b, double a);
|
||||
|
||||
///
|
||||
/// Sets the material property [propertyName] under material [materialIndex] for [entity] to [value].
|
||||
/// [entity] must have a Renderable attached.
|
||||
///
|
||||
Future setMaterialPropertyFloat4(ThermionEntity entity, String propertyName,
|
||||
int materialIndex, double f1, double f2, double f3, double f4);
|
||||
|
||||
///
|
||||
/// Sets the material property [propertyName] under material [materialIndex] for [entity] to [value].
|
||||
/// [entity] must have a Renderable attached.
|
||||
///
|
||||
Future setMaterialPropertyFloat(ThermionEntity entity, String propertyName, int materialIndex, double value);
|
||||
|
||||
///
|
||||
/// Scale [entity] to fit within the unit cube.
|
||||
///
|
||||
Future transformToUnitCube(ThermionEntity entity);
|
||||
|
||||
///
|
||||
/// Directly sets the world space position for [entity] to the given coordinates.
|
||||
///
|
||||
Future setPosition(ThermionEntity entity, double x, double y, double z);
|
||||
|
||||
///
|
||||
/// Set the world space position for [lightEntity] to the given coordinates.
|
||||
///
|
||||
Future setLightPosition(
|
||||
ThermionEntity lightEntity, double x, double y, double z);
|
||||
|
||||
///
|
||||
/// Sets the world space direction for [lightEntity] to the given vector.
|
||||
///
|
||||
Future setLightDirection(ThermionEntity lightEntity, Vector3 direction);
|
||||
|
||||
///
|
||||
/// Directly sets the scale for [entity], skipping all collision detection.
|
||||
///
|
||||
Future setScale(ThermionEntity entity, double scale);
|
||||
|
||||
///
|
||||
/// Directly sets the rotation for [entity] to [rads] around the axis {x,y,z}, skipping all collision detection.
|
||||
///
|
||||
Future setRotation(
|
||||
ThermionEntity entity, double rads, double x, double y, double z);
|
||||
|
||||
///
|
||||
/// Queues an update to the worldspace position for [entity] to {x,y,z}.
|
||||
/// The actual update will occur on the next frame, and will be subject to collision detection.
|
||||
///
|
||||
Future queuePositionUpdate(
|
||||
ThermionEntity entity, double x, double y, double z,
|
||||
{bool relative = false});
|
||||
|
||||
///
|
||||
/// TODO
|
||||
///
|
||||
Future queuePositionUpdateFromViewportCoords(
|
||||
ThermionEntity entity, double x, double y);
|
||||
|
||||
///
|
||||
/// TODO
|
||||
///
|
||||
Future queueRelativePositionUpdateWorldAxis(ThermionEntity entity,
|
||||
double viewportX, double viewportY, double x, double y, double z);
|
||||
|
||||
///
|
||||
/// Queues an update to the worldspace rotation for [entity].
|
||||
/// The actual update will occur on the next frame, and will be subject to collision detection.
|
||||
///
|
||||
Future queueRotationUpdate(
|
||||
ThermionEntity entity, double rads, double x, double y, double z,
|
||||
{bool relative = false});
|
||||
|
||||
///
|
||||
/// Same as [queueRotationUpdate].
|
||||
///
|
||||
Future queueRotationUpdateQuat(ThermionEntity entity, Quaternion quat,
|
||||
{bool relative = false});
|
||||
|
||||
///
|
||||
/// Enable/disable postprocessing (disabled by default).
|
||||
///
|
||||
Future setPostProcessing(bool enabled);
|
||||
|
||||
///
|
||||
/// Enable/disable shadows (disabled by default).
|
||||
///
|
||||
Future setShadowsEnabled(bool enabled);
|
||||
|
||||
///
|
||||
/// Set shadow type.
|
||||
///
|
||||
Future setShadowType(ShadowType shadowType);
|
||||
|
||||
///
|
||||
/// Set soft shadow options (ShadowType DPCF and PCSS)
|
||||
///
|
||||
Future setSoftShadowOptions(double penumbraScale, double penumbraRatioScale);
|
||||
|
||||
///
|
||||
/// Set antialiasing options.
|
||||
///
|
||||
Future setAntiAliasing(bool msaa, bool fxaa, bool taa);
|
||||
|
||||
///
|
||||
/// Sets the rotation for [entity] to the specified quaternion.
|
||||
///
|
||||
Future setRotationQuat(ThermionEntity entity, Quaternion rotation);
|
||||
|
||||
///
|
||||
/// Reveal the node [meshName] under [entity]. Only applicable if [hide] had previously been called; this is a no-op otherwise.
|
||||
///
|
||||
Future reveal(ThermionEntity entity, String? meshName);
|
||||
|
||||
///
|
||||
/// If [meshName] is provided, hide the node [meshName] under [entity], otherwise hide the root node for [entity].
|
||||
/// The entity still exists in memory, but is no longer being rendered into the scene. Call [reveal] to re-commence rendering.
|
||||
///
|
||||
Future hide(ThermionEntity entity, String? meshName);
|
||||
|
||||
///
|
||||
/// Used to select the entity in the scene at the given viewport coordinates.
|
||||
/// Called by `FilamentGestureDetector` on a mouse/finger down event. You probably don't want to call this yourself.
|
||||
/// 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).
|
||||
///
|
||||
void pick(int x, int y);
|
||||
|
||||
///
|
||||
/// Used to test whether a Gizmo is at the given viewport coordinates.
|
||||
/// Called by `FilamentGestureDetector` on a mouse/finger down event. You probably don't want to call this yourself.
|
||||
/// This is asynchronous and will require 2-3 frames to complete - subscribe to the [gizmoPickResult] 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).
|
||||
///
|
||||
void pickGizmo(int x, int y);
|
||||
|
||||
///
|
||||
/// Retrieves the name assigned to the given ThermionEntity (usually corresponds to the glTF mesh name).
|
||||
///
|
||||
String? getNameForEntity(ThermionEntity entity);
|
||||
|
||||
///
|
||||
/// Sets the options for manipulating the camera via the viewport.
|
||||
/// ManipulatorMode.FREE_FLIGHT and ManipulatorMode.MAP are currently unsupported and will throw an exception.
|
||||
///
|
||||
@Deprecated("Use ThermionGestureHandler instead")
|
||||
Future setCameraManipulatorOptions(
|
||||
{ManipulatorMode mode = ManipulatorMode.ORBIT,
|
||||
double orbitSpeedX = 0.01,
|
||||
double orbitSpeedY = 0.01,
|
||||
double zoomSpeed = 0.01});
|
||||
|
||||
///
|
||||
/// Returns all child entities under [parent].
|
||||
///
|
||||
Future<List<ThermionEntity>> getChildEntities(
|
||||
ThermionEntity parent, bool renderableOnly);
|
||||
|
||||
///
|
||||
/// Finds the child entity named [childName] associated with the given parent.
|
||||
/// Usually, [parent] will be the return value from [loadGlb]/[loadGltf] and [childName] will be the name of a node/mesh.
|
||||
///
|
||||
Future<ThermionEntity> getChildEntity(
|
||||
ThermionEntity parent, String childName);
|
||||
|
||||
///
|
||||
/// List the name of all child entities under the given entity.
|
||||
///
|
||||
Future<List<String>> getChildEntityNames(ThermionEntity entity,
|
||||
{bool renderableOnly = true});
|
||||
|
||||
///
|
||||
/// If [recording] is set to true, each frame the framebuffer/texture will be written to /tmp/output_*.png.
|
||||
/// This will impact performance; handle with care.
|
||||
///
|
||||
Future setRecording(bool recording);
|
||||
|
||||
///
|
||||
/// Sets the output directory where recorded PNGs will be placed.
|
||||
///
|
||||
Future setRecordingOutputDirectory(String outputDirectory);
|
||||
|
||||
///
|
||||
/// An [entity] will only be animatable after an animation component is attached.
|
||||
/// Any calls to [playAnimation]/[setBoneAnimation]/[setMorphAnimation] will have no visual effect until [addAnimationComponent] has been called on the instance.
|
||||
///
|
||||
Future addAnimationComponent(ThermionEntity entity);
|
||||
|
||||
///
|
||||
/// Removes an animation component from [entity].
|
||||
///
|
||||
Future removeAnimationComponent(ThermionEntity entity);
|
||||
|
||||
///
|
||||
/// Makes [entity] collidable.
|
||||
/// This allows you to call [testCollisions] with any other entity ("entity B") to see if [entity] has collided with entity B. The callback will be invoked if so.
|
||||
/// Alternatively, if [affectsTransform] is true and this entity collides with another entity, any queued position updates to the latter entity will be ignored.
|
||||
///
|
||||
Future addCollisionComponent(ThermionEntity entity,
|
||||
{void Function(int entityId1, int entityId2)? callback,
|
||||
bool affectsTransform = false});
|
||||
|
||||
///
|
||||
/// Removes the collision component from [entity], meaning this will no longer be tested when [testCollisions] or [queuePositionUpdate] is called with another entity.
|
||||
///
|
||||
Future removeCollisionComponent(ThermionEntity entity);
|
||||
|
||||
///
|
||||
/// Creates a (renderable) entity with the specified geometry and adds to the scene.
|
||||
///
|
||||
Future createGeometry(List<double> vertices, List<int> indices,
|
||||
{String? materialPath,
|
||||
List<double>? normals,
|
||||
PrimitiveType primitiveType = PrimitiveType.TRIANGLES});
|
||||
|
||||
///
|
||||
/// Gets the parent entity of [entity]. Returns null if the entity has no parent.
|
||||
///
|
||||
Future<ThermionEntity?> getParent(ThermionEntity entity);
|
||||
|
||||
///
|
||||
/// Gets the ancestor (ultimate parent) entity of [entity]. Returns null if the entity has no parent.
|
||||
///
|
||||
Future<ThermionEntity?> getAncestor(ThermionEntity entity);
|
||||
|
||||
///
|
||||
/// Sets the parent transform of [child] to [parent].
|
||||
///
|
||||
Future setParent(ThermionEntity child, ThermionEntity parent,
|
||||
{bool preserveScaling});
|
||||
|
||||
///
|
||||
/// Test all collidable entities against this entity to see if any have collided.
|
||||
/// This method returns void; the relevant callback passed to [addCollisionComponent] will be fired if a collision is detected.
|
||||
///
|
||||
Future testCollisions(ThermionEntity entity);
|
||||
|
||||
///
|
||||
/// Sets the draw priority for the given entity. See RenderableManager.h for more details.
|
||||
///
|
||||
Future setPriority(ThermionEntity entityId, int priority);
|
||||
|
||||
///
|
||||
/// The Scene holds all loaded entities/lights.
|
||||
///
|
||||
Scene get scene;
|
||||
|
||||
///
|
||||
/// The gizmo for translating/rotating objects. Only one gizmo is present in the scene.
|
||||
///
|
||||
AbstractGizmo? get gizmo;
|
||||
|
||||
///
|
||||
/// Register a callback to be invoked when this viewer is disposed.
|
||||
///
|
||||
void onDispose(Future Function() callback);
|
||||
|
||||
///
|
||||
/// Gets the 2D bounding box (in viewport coordinates) for the given entity.
|
||||
///
|
||||
Future<Aabb2> getViewportBoundingBox(ThermionEntity entity);
|
||||
|
||||
///
|
||||
/// Filament assigns renderables to a numeric layer.
|
||||
/// We place all scene assets in layer 0 (enabled by default), gizmos in layer 1 (enabled by default), world grid in layer 2 (disabled by default).
|
||||
/// Use this method to toggle visibility of the respective layer.
|
||||
///
|
||||
Future setLayerEnabled(int layer, bool enabled);
|
||||
|
||||
///
|
||||
/// Show/hide the translation gizmo.
|
||||
///
|
||||
Future setGizmoVisibility(bool visible);
|
||||
|
||||
///
|
||||
/// Renders an outline around [entity] with the given color.
|
||||
///
|
||||
Future setStencilHighlight(ThermionEntity entity,
|
||||
{double r = 1.0, double g = 0.0, double b = 0.0});
|
||||
|
||||
///
|
||||
/// Removes the outline around [entity]. Noop if there was no highlight.
|
||||
///
|
||||
Future removeStencilHighlight(ThermionEntity entity);
|
||||
|
||||
}
|
||||
export 'viewer/thermion_viewer_base.dart';
|
||||
export 'viewer/thermion_viewer_stub.dart'
|
||||
if (dart.library.io) 'viewer/ffi/thermion_viewer_ffi.dart'
|
||||
if (dart.library.js_interop) 'viewer/web/thermion_viewer_wasm.dart';
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import 'dart:ffi';
|
||||
import 'dart:io';
|
||||
import '../compatibility/compatibility.dart';
|
||||
|
||||
import 'package:ffi/ffi.dart';
|
||||
import 'package:thermion_dart/thermion_dart/viewer/ffi/thermion_dart.g.dart';
|
||||
|
||||
class DartResourceLoader {
|
||||
static final _assets = <int, Pointer>{};
|
||||
|
||||
@@ -1,26 +1,14 @@
|
||||
import 'dart:math';
|
||||
|
||||
class Geometry {
|
||||
final List<double> vertices;
|
||||
final List<int> indices;
|
||||
final List<double>? normals;
|
||||
|
||||
Geometry(this.vertices, this.indices, this.normals);
|
||||
|
||||
void scale(double factor) {
|
||||
for (int i = 0; i < vertices.length; i++) {
|
||||
vertices[i] = vertices[i] * factor;
|
||||
}
|
||||
}
|
||||
}
|
||||
import 'package:thermion_dart/thermion_dart/viewer/shared_types/geometry.dart';
|
||||
|
||||
class GeometryHelper {
|
||||
static Geometry sphere() {
|
||||
static Geometry sphere({bool normals = true, bool uvs = true}) {
|
||||
int latitudeBands = 20;
|
||||
int longitudeBands = 20;
|
||||
|
||||
List<double> vertices = [];
|
||||
List<double> normals = [];
|
||||
List<double> _normals = [];
|
||||
List<int> indices = [];
|
||||
|
||||
for (int latNumber = 0; latNumber <= latitudeBands; latNumber++) {
|
||||
@@ -38,7 +26,7 @@ static Geometry sphere() {
|
||||
double z = sinPhi * sinTheta;
|
||||
|
||||
vertices.addAll([x, y, z]);
|
||||
normals.addAll([
|
||||
_normals.addAll([
|
||||
x,
|
||||
y,
|
||||
z
|
||||
@@ -56,10 +44,10 @@ static Geometry sphere() {
|
||||
}
|
||||
}
|
||||
|
||||
return Geometry(vertices, indices, normals);
|
||||
return Geometry(vertices, indices, normals: normals ? _normals : null);
|
||||
}
|
||||
|
||||
static Geometry cube() {
|
||||
static Geometry cube({bool normals = true, bool uvs = true}) {
|
||||
final vertices = <double>[
|
||||
// Front face
|
||||
-1, -1, 1,
|
||||
@@ -98,7 +86,7 @@ static Geometry sphere() {
|
||||
-1, 1, -1,
|
||||
];
|
||||
|
||||
final normals = <double>[
|
||||
final _normals = <double>[
|
||||
// Front face
|
||||
0, 0, 1,
|
||||
0, 0, 1,
|
||||
@@ -136,7 +124,7 @@ static Geometry sphere() {
|
||||
-1, 0, 0,
|
||||
];
|
||||
|
||||
final indices = [
|
||||
final indices = [
|
||||
// Front face
|
||||
0, 1, 2, 0, 2, 3,
|
||||
// Back face
|
||||
@@ -150,13 +138,13 @@ static Geometry sphere() {
|
||||
// Left face
|
||||
20, 21, 22, 20, 22, 23
|
||||
];
|
||||
return Geometry(vertices, indices, normals);
|
||||
return Geometry(vertices, indices, normals: normals ? _normals : null);
|
||||
}
|
||||
|
||||
static Geometry cylinder({double radius = 1.0, double length = 1.0}) {
|
||||
static Geometry cylinder({double radius = 1.0, double length = 1.0, bool normals = true, bool uvs = true }) {
|
||||
int segments = 32;
|
||||
List<double> vertices = [];
|
||||
List<double> normals = [];
|
||||
List<double> _normals = [];
|
||||
List<int> indices = [];
|
||||
|
||||
// Create vertices and normals
|
||||
@@ -167,11 +155,11 @@ static Geometry sphere() {
|
||||
|
||||
// Top circle
|
||||
vertices.addAll([x, length / 2, z]);
|
||||
normals.addAll([x / radius, 0, z / radius]);
|
||||
_normals.addAll([x / radius, 0, z / radius]);
|
||||
|
||||
// Bottom circle
|
||||
vertices.addAll([x, -length / 2, z]);
|
||||
normals.addAll([x / radius, 0, z / radius]);
|
||||
_normals.addAll([x / radius, 0, z / radius]);
|
||||
}
|
||||
|
||||
// Create indices
|
||||
@@ -192,23 +180,23 @@ static Geometry sphere() {
|
||||
|
||||
// Add center vertices and normals for top and bottom faces
|
||||
vertices.addAll([0, length / 2, 0]); // Top center
|
||||
normals.addAll([0, 1, 0]);
|
||||
_normals.addAll([0, 1, 0]);
|
||||
vertices.addAll([0, -length / 2, 0]); // Bottom center
|
||||
normals.addAll([0, -1, 0]);
|
||||
_normals.addAll([0, -1, 0]);
|
||||
|
||||
// Add top and bottom face normals
|
||||
for (int i = 0; i <= segments; i++) {
|
||||
normals.addAll([0, 1, 0]); // Top face normal
|
||||
normals.addAll([0, -1, 0]); // Bottom face normal
|
||||
_normals.addAll([0, 1, 0]); // Top face normal
|
||||
_normals.addAll([0, -1, 0]); // Bottom face normal
|
||||
}
|
||||
|
||||
return Geometry(vertices, indices, normals);
|
||||
return Geometry(vertices, indices, normals: normals ? _normals : null);
|
||||
}
|
||||
|
||||
static Geometry conic({double radius = 1.0, double length = 1.0}) {
|
||||
static Geometry conic({double radius = 1.0, double length = 1.0, bool normals = true, bool uvs = true}) {
|
||||
int segments = 32;
|
||||
List<double> vertices = [];
|
||||
List<double> normals = [];
|
||||
List<double> _normals = [];
|
||||
List<int> indices = [];
|
||||
|
||||
// Create vertices and normals
|
||||
@@ -219,53 +207,73 @@ static Geometry sphere() {
|
||||
|
||||
// Base circle
|
||||
vertices.addAll([x, 0, z]);
|
||||
|
||||
|
||||
// Calculate normal for the side
|
||||
double nx = x / sqrt(x * x + length * length);
|
||||
double nz = z / sqrt(z * z + length * length);
|
||||
double ny = radius / sqrt(radius * radius + length * length);
|
||||
normals.addAll([nx, ny, nz]);
|
||||
_normals.addAll([nx, ny, nz]);
|
||||
}
|
||||
// Apex
|
||||
vertices.addAll([0, length, 0]);
|
||||
normals.addAll([0, 1, 0]); // Normal at apex points straight up
|
||||
_normals.addAll([0, 1, 0]); // Normal at apex points straight up
|
||||
|
||||
// Create indices
|
||||
for (int i = 0; i < segments; i++) {
|
||||
// Base face
|
||||
indices.addAll([i, i + 1, segments + 1]);
|
||||
// Side faces
|
||||
// Base face (fixed to counterclockwise)
|
||||
indices.addAll([segments + 1, i + 1, i]);
|
||||
// Side faces (already correct)
|
||||
indices.addAll([i, segments, i + 1]);
|
||||
}
|
||||
|
||||
// Add base face normals
|
||||
for (int i = 0; i <= segments; i++) {
|
||||
normals.addAll([0, -1, 0]); // Base face normal
|
||||
_normals.addAll([0, -1, 0]); // Base face normal
|
||||
}
|
||||
|
||||
return Geometry(vertices, indices, normals);
|
||||
return Geometry(vertices, indices, normals: normals ? _normals : null);
|
||||
}
|
||||
|
||||
static Geometry plane({double width = 1.0, double height = 1.0}) {
|
||||
static Geometry plane({double width = 1.0, double height = 1.0, bool normals = true, bool uvs = true}) {
|
||||
List<double> vertices = [
|
||||
-width / 2, 0, -height / 2,
|
||||
width / 2, 0, -height / 2,
|
||||
width / 2, 0, height / 2,
|
||||
-width / 2, 0, height / 2,
|
||||
-width / 2,
|
||||
0,
|
||||
-height / 2,
|
||||
width / 2,
|
||||
0,
|
||||
-height / 2,
|
||||
width / 2,
|
||||
0,
|
||||
height / 2,
|
||||
-width / 2,
|
||||
0,
|
||||
height / 2,
|
||||
];
|
||||
|
||||
List<double> normals = [
|
||||
0, 1, 0,
|
||||
0, 1, 0,
|
||||
0, 1, 0,
|
||||
0, 1, 0,
|
||||
List<double> _normals = [
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
];
|
||||
|
||||
List<int> indices = [
|
||||
0, 1, 2,
|
||||
0, 2, 3,
|
||||
0,
|
||||
2,
|
||||
1,
|
||||
0,
|
||||
3,
|
||||
2,
|
||||
];
|
||||
|
||||
return Geometry(vertices, indices, normals);
|
||||
return Geometry(vertices, indices, normals: normals ? _normals : null);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
import 'package:vector_math/vector_math_64.dart' as v;
|
||||
|
||||
class LightOptions {
|
||||
String? iblPath;
|
||||
double iblIntensity;
|
||||
int directionalType;
|
||||
double directionalColor;
|
||||
double directionalIntensity;
|
||||
bool directionalCastShadows;
|
||||
late v.Vector3 directionalPosition;
|
||||
late v.Vector3 directionalDirection;
|
||||
|
||||
LightOptions(
|
||||
{required this.iblPath,
|
||||
required this.iblIntensity,
|
||||
required this.directionalType,
|
||||
required this.directionalColor,
|
||||
required this.directionalIntensity,
|
||||
required this.directionalCastShadows,
|
||||
v.Vector3? directionalDirection,
|
||||
v.Vector3? directionalPosition}) {
|
||||
this.directionalDirection = directionalDirection == null
|
||||
? v.Vector3(0, -1, 0)
|
||||
: directionalDirection;
|
||||
this.directionalPosition = directionalPosition == null
|
||||
? v.Vector3(0, 100, 0)
|
||||
: directionalPosition;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
// Helper function to convert double4x4 to Matrix4
|
||||
import 'package:thermion_dart/thermion_dart/compatibility/native/thermion_dart.g.dart';
|
||||
import 'package:thermion_dart/thermion_dart/viewer/ffi/thermion_dart.g.dart';
|
||||
import 'package:vector_math/vector_math_64.dart';
|
||||
import 'dart:ffi';
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
import 'dart:ffi';
|
||||
|
||||
import 'package:ffi/ffi.dart';
|
||||
|
||||
final allocator = calloc;
|
||||
|
||||
void using(Pointer ptr, Future Function(Pointer ptr) function) async {
|
||||
await function.call(ptr);
|
||||
allocator.free(ptr);
|
||||
}
|
||||
86
thermion_dart/lib/thermion_dart/viewer/events.dart
Normal file
86
thermion_dart/lib/thermion_dart/viewer/events.dart
Normal file
@@ -0,0 +1,86 @@
|
||||
import 'package:thermion_dart/thermion_dart/viewer/shared_types/entities.dart';
|
||||
import 'shared_types/shared_types.dart';
|
||||
|
||||
///
|
||||
/// To ensure we can easily store/recreate a particular, [ThermionViewer] will raise an event whenever an
|
||||
/// entity is added/removed.
|
||||
///
|
||||
enum EventType { EntityAdded, EntityRemoved, EntityHidden, EntityRevealed, ClearLights }
|
||||
|
||||
///
|
||||
/// An "entity added" event must provide sufficient detail to enable that asset to be reloaded in future.
|
||||
/// This requires a bit more legwork because entities may be lights (direct/indirect), geometry or gltf.
|
||||
///
|
||||
enum EntityType { Geometry, Gltf, DirectLight, IBL }
|
||||
|
||||
class SceneUpdateEvent {
|
||||
late final ThermionEntity? entity;
|
||||
late final EventType eventType;
|
||||
|
||||
EntityType get addedEntityType {
|
||||
if (_directLight != null) {
|
||||
return EntityType.DirectLight;
|
||||
} else if (_ibl != null) {
|
||||
return EntityType.IBL;
|
||||
} else if (_gltf != null) {
|
||||
return EntityType.Gltf;
|
||||
} else if (_geometry != null) {
|
||||
return EntityType.Geometry;
|
||||
} else {
|
||||
throw Exception("Unknown entity type");
|
||||
}
|
||||
}
|
||||
|
||||
DirectLight? _directLight;
|
||||
IBL? _ibl;
|
||||
GLTF? _gltf;
|
||||
Geometry? _geometry;
|
||||
|
||||
SceneUpdateEvent.remove(this.entity) {
|
||||
this.eventType = EventType.EntityRemoved;
|
||||
}
|
||||
|
||||
SceneUpdateEvent.reveal(this.entity) {
|
||||
this.eventType = EventType.EntityRevealed;
|
||||
}
|
||||
|
||||
SceneUpdateEvent.hide(this.entity) {
|
||||
this.eventType = EventType.EntityHidden;
|
||||
}
|
||||
|
||||
SceneUpdateEvent.addDirectLight(this.entity, this._directLight) {
|
||||
this.eventType = EventType.EntityAdded;
|
||||
}
|
||||
|
||||
SceneUpdateEvent.addIbl(this.entity, this._ibl) {
|
||||
this.eventType = EventType.EntityAdded;
|
||||
}
|
||||
|
||||
SceneUpdateEvent.addGltf(this.entity, this._gltf) {
|
||||
this.eventType = EventType.EntityAdded;
|
||||
}
|
||||
|
||||
SceneUpdateEvent.addGeometry(this.entity, this._geometry) {
|
||||
this.eventType = EventType.EntityAdded;
|
||||
}
|
||||
|
||||
SceneUpdateEvent.clearLights() {
|
||||
this.eventType = EventType.ClearLights;
|
||||
}
|
||||
|
||||
DirectLight getDirectLight() {
|
||||
return _directLight!;
|
||||
}
|
||||
|
||||
IBL getAsIBL() {
|
||||
return _ibl!;
|
||||
}
|
||||
|
||||
GLTF getAsGLTF() {
|
||||
return _gltf!;
|
||||
}
|
||||
|
||||
Geometry getAsGeometry() {
|
||||
return _geometry!;
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,11 @@ export 'thermion_dart.g.dart';
|
||||
|
||||
final allocator = calloc;
|
||||
|
||||
void using(Pointer ptr, Future Function(Pointer ptr) function) async {
|
||||
await function.call(ptr);
|
||||
allocator.free(ptr);
|
||||
}
|
||||
|
||||
Future<void> withVoidCallback(
|
||||
Function(Pointer<NativeFunction<Void Function()>>) func) async {
|
||||
final completer = Completer();
|
||||
@@ -82,4 +87,3 @@ Future<String> withCharPtrCallback(
|
||||
return completer.future;
|
||||
}
|
||||
|
||||
class Compatibility {}
|
||||
@@ -1,19 +1,18 @@
|
||||
import 'dart:async';
|
||||
import 'dart:ffi';
|
||||
import 'dart:math';
|
||||
import 'dart:typed_data';
|
||||
import 'package:animation_tools_dart/animation_tools_dart.dart';
|
||||
import 'package:thermion_dart/thermion_dart/compatibility/compatibility.dart';
|
||||
import 'package:thermion_dart/thermion_dart/entities/gizmo.dart';
|
||||
import 'package:thermion_dart/thermion_dart/matrix_helper.dart';
|
||||
import 'package:thermion_dart/thermion_dart/scene.dart';
|
||||
|
||||
import 'package:thermion_dart/thermion_dart/utils/matrix.dart';
|
||||
import 'package:thermion_dart/thermion_dart/viewer/events.dart';
|
||||
import 'package:thermion_dart/thermion_dart/viewer/ffi/callbacks.dart';
|
||||
import 'package:vector_math/vector_math_64.dart';
|
||||
import 'package:vector_math/vector_math_64.dart' as v64;
|
||||
import 'thermion_viewer.dart';
|
||||
import 'scene_impl.dart';
|
||||
import '../thermion_viewer_base.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
|
||||
typedef ThermionViewerImpl = ThermionViewerFFI;
|
||||
|
||||
// ignore: constant_identifier_names
|
||||
const ThermionEntity _FILAMENT_ASSET_ERROR = 0;
|
||||
|
||||
@@ -26,9 +25,6 @@ double kFocalLength = 28.0;
|
||||
class ThermionViewerFFI extends ThermionViewer {
|
||||
final _logger = Logger("ThermionViewerFFI");
|
||||
|
||||
SceneImpl? _scene;
|
||||
Scene get scene => _scene!;
|
||||
|
||||
double pixelRatio = 1.0;
|
||||
|
||||
Pointer<Void>? _sceneManager;
|
||||
@@ -48,12 +44,22 @@ class ThermionViewerFFI extends ThermionViewer {
|
||||
final _pickResultController =
|
||||
StreamController<FilamentPickResult>.broadcast();
|
||||
|
||||
///
|
||||
///
|
||||
///
|
||||
@override
|
||||
Stream<FilamentPickResult> get gizmoPickResult =>
|
||||
_gizmoPickResultController.stream;
|
||||
final _gizmoPickResultController =
|
||||
StreamController<FilamentPickResult>.broadcast();
|
||||
|
||||
///
|
||||
///
|
||||
///
|
||||
Stream<SceneUpdateEvent> get sceneUpdated =>
|
||||
_sceneUpdateEventController.stream;
|
||||
final _sceneUpdateEventController = StreamController<SceneUpdateEvent>.broadcast();
|
||||
|
||||
final Pointer<Void> resourceLoader;
|
||||
|
||||
var _driver = nullptr.cast<Void>();
|
||||
@@ -150,7 +156,6 @@ class ThermionViewerFFI extends ThermionViewer {
|
||||
}
|
||||
|
||||
_sceneManager = get_scene_manager(_viewer!);
|
||||
_scene = SceneImpl(this);
|
||||
|
||||
await setCameraManipulatorOptions(zoomSpeed: 1.0);
|
||||
|
||||
@@ -229,13 +234,14 @@ class ThermionViewerFFI extends ThermionViewer {
|
||||
await setRendering(false);
|
||||
await clearEntities();
|
||||
await clearLights();
|
||||
await _scene!.dispose();
|
||||
_scene = null;
|
||||
|
||||
destroy_filament_viewer_ffi(_viewer!);
|
||||
|
||||
_sceneManager = null;
|
||||
_viewer = null;
|
||||
await _pickResultController.close();
|
||||
await _gizmoPickResultController.close();
|
||||
await _sceneUpdateEventController.close();
|
||||
|
||||
for (final callback in _onDispose) {
|
||||
await callback.call();
|
||||
@@ -393,7 +399,38 @@ class ThermionViewerFFI extends ThermionViewer {
|
||||
throw Exception("Failed to add light to scene");
|
||||
}
|
||||
|
||||
_scene!.registerLight(entity);
|
||||
return entity;
|
||||
}
|
||||
|
||||
///
|
||||
///
|
||||
///
|
||||
@override
|
||||
Future<ThermionEntity> addDirectLight(DirectLight directLight) async {
|
||||
var entity = await withIntCallback((callback) => add_light_ffi(
|
||||
_viewer!,
|
||||
directLight.type.index,
|
||||
directLight.color,
|
||||
directLight.intensity,
|
||||
directLight.position.x,
|
||||
directLight.position.y,
|
||||
directLight.position.z,
|
||||
directLight.direction.x,
|
||||
directLight.direction.y,
|
||||
directLight.direction.z,
|
||||
directLight.falloffRadius,
|
||||
directLight.spotLightConeInner,
|
||||
directLight.spotLightConeOuter,
|
||||
directLight.sunAngularRadius,
|
||||
directLight.sunHaloSize,
|
||||
directLight.sunHaloFallof,
|
||||
directLight.castShadows,
|
||||
callback));
|
||||
if (entity == _FILAMENT_ASSET_ERROR) {
|
||||
throw Exception("Failed to add light to scene");
|
||||
}
|
||||
_sceneUpdateEventController
|
||||
.add(SceneUpdateEvent.addDirectLight(entity, directLight));
|
||||
return entity;
|
||||
}
|
||||
|
||||
@@ -402,8 +439,8 @@ class ThermionViewerFFI extends ThermionViewer {
|
||||
///
|
||||
@override
|
||||
Future removeLight(ThermionEntity entity) async {
|
||||
_scene!.unregisterLight(entity);
|
||||
remove_light_ffi(_viewer!, entity);
|
||||
_sceneUpdateEventController.add(SceneUpdateEvent.remove(entity));
|
||||
}
|
||||
|
||||
///
|
||||
@@ -413,7 +450,7 @@ class ThermionViewerFFI extends ThermionViewer {
|
||||
Future clearLights() async {
|
||||
clear_lights_ffi(_viewer!);
|
||||
|
||||
_scene!.clearLights();
|
||||
_sceneUpdateEventController.add(SceneUpdateEvent.clearLights());
|
||||
}
|
||||
|
||||
///
|
||||
@@ -468,7 +505,9 @@ class ThermionViewerFFI extends ThermionViewer {
|
||||
if (entity == _FILAMENT_ASSET_ERROR) {
|
||||
throw Exception("An error occurred loading the asset at $path");
|
||||
}
|
||||
_scene!.registerEntity(entity);
|
||||
|
||||
_sceneUpdateEventController
|
||||
.add(SceneUpdateEvent.addGltf(entity, GLTF(path, numInstances)));
|
||||
|
||||
return entity;
|
||||
}
|
||||
@@ -494,7 +533,6 @@ class ThermionViewerFFI extends ThermionViewer {
|
||||
if (entity == _FILAMENT_ASSET_ERROR) {
|
||||
throw Exception("An error occurred loading GLB from buffer");
|
||||
}
|
||||
_scene!.registerEntity(entity);
|
||||
return entity;
|
||||
}
|
||||
|
||||
@@ -514,7 +552,6 @@ class ThermionViewerFFI extends ThermionViewer {
|
||||
if (entity == _FILAMENT_ASSET_ERROR) {
|
||||
throw Exception("An error occurred loading the asset at $path");
|
||||
}
|
||||
_scene!.registerEntity(entity);
|
||||
|
||||
return entity;
|
||||
}
|
||||
@@ -995,10 +1032,9 @@ class ThermionViewerFFI extends ThermionViewer {
|
||||
///
|
||||
@override
|
||||
Future removeEntity(ThermionEntity entity) async {
|
||||
_scene!.unregisterEntity(entity);
|
||||
|
||||
await withVoidCallback(
|
||||
(callback) => remove_entity_ffi(_viewer!, entity, callback));
|
||||
_sceneUpdateEventController.add(SceneUpdateEvent.remove(entity));
|
||||
}
|
||||
|
||||
///
|
||||
@@ -1012,7 +1048,6 @@ class ThermionViewerFFI extends ThermionViewer {
|
||||
await withVoidCallback((callback) {
|
||||
clear_entities_ffi(_viewer!, callback);
|
||||
});
|
||||
_scene!.clearEntities();
|
||||
}
|
||||
|
||||
///
|
||||
@@ -1498,7 +1533,6 @@ class ThermionViewerFFI extends ThermionViewer {
|
||||
x: (x / pixelRatio).toDouble(),
|
||||
y: (viewportDimensions.$2 - y) / pixelRatio
|
||||
));
|
||||
_scene!.registerSelected(entityId);
|
||||
}
|
||||
|
||||
void _onGizmoPickResult(ThermionEntity entityId, int x, int y) {
|
||||
@@ -1519,8 +1553,6 @@ class ThermionViewerFFI extends ThermionViewer {
|
||||
///
|
||||
@override
|
||||
void pick(int x, int y) async {
|
||||
_scene!.unregisterSelected();
|
||||
|
||||
x = (x * pixelRatio).ceil();
|
||||
y = (viewportDimensions.$2 - (y * pixelRatio)).ceil();
|
||||
|
||||
@@ -1787,33 +1819,29 @@ class ThermionViewerFFI extends ThermionViewer {
|
||||
///
|
||||
///
|
||||
@override
|
||||
Future<ThermionEntity> createGeometry(
|
||||
List<double> vertices, List<int> indices,
|
||||
{String? materialPath,
|
||||
List<double>? normals,
|
||||
bool keepData = false,
|
||||
PrimitiveType primitiveType = PrimitiveType.TRIANGLES}) async {
|
||||
Future<ThermionEntity> createGeometry(Geometry geometry,
|
||||
{bool keepData = false}) async {
|
||||
if (_viewer == null) {
|
||||
throw Exception("Viewer must not be null");
|
||||
}
|
||||
|
||||
final materialPathPtr =
|
||||
materialPath?.toNativeUtf8(allocator: allocator) ?? nullptr;
|
||||
final vertexPtr = allocator<Float>(vertices.length);
|
||||
final indicesPtr = allocator<Uint16>(indices.length);
|
||||
for (int i = 0; i < vertices.length; i++) {
|
||||
vertexPtr[i] = vertices[i];
|
||||
geometry.materialPath?.toNativeUtf8(allocator: allocator) ?? nullptr;
|
||||
final vertexPtr = allocator<Float>(geometry.vertices.length);
|
||||
final indicesPtr = allocator<Uint16>(geometry.indices.length);
|
||||
for (int i = 0; i < geometry.vertices.length; i++) {
|
||||
vertexPtr[i] = geometry.vertices[i];
|
||||
}
|
||||
|
||||
for (int i = 0; i < indices.length; i++) {
|
||||
(indicesPtr + i).value = indices[i];
|
||||
for (int i = 0; i < geometry.indices.length; i++) {
|
||||
(indicesPtr + i).value = geometry.indices[i];
|
||||
}
|
||||
|
||||
var normalsPtr = nullptr.cast<Float>();
|
||||
if (normals != null) {
|
||||
normalsPtr = allocator<Float>(normals.length);
|
||||
for (int i = 0; i < normals.length; i++) {
|
||||
normalsPtr[i] = normals[i];
|
||||
if (geometry.normals != null) {
|
||||
normalsPtr = allocator<Float>(geometry.normals!.length);
|
||||
for (int i = 0; i < geometry.normals!.length; i++) {
|
||||
normalsPtr[i] = geometry.normals![i];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1821,12 +1849,12 @@ class ThermionViewerFFI extends ThermionViewer {
|
||||
create_geometry_with_normals_ffi(
|
||||
_sceneManager!,
|
||||
vertexPtr,
|
||||
vertices.length,
|
||||
geometry.vertices.length,
|
||||
normalsPtr,
|
||||
normals?.length ?? 0,
|
||||
geometry.normals?.length ?? 0,
|
||||
indicesPtr,
|
||||
indices.length,
|
||||
primitiveType.index,
|
||||
geometry.indices.length,
|
||||
geometry.primitiveType.index,
|
||||
materialPathPtr.cast<Char>(),
|
||||
keepData,
|
||||
callback));
|
||||
@@ -1834,16 +1862,17 @@ class ThermionViewerFFI extends ThermionViewer {
|
||||
throw Exception("Failed to create geometry");
|
||||
}
|
||||
|
||||
_scene!.registerEntity(entity);
|
||||
|
||||
allocator.free(materialPathPtr);
|
||||
allocator.free(vertexPtr);
|
||||
allocator.free(indicesPtr);
|
||||
|
||||
if (normals != null) {
|
||||
if (geometry.normals != null) {
|
||||
allocator.free(normalsPtr);
|
||||
}
|
||||
|
||||
_sceneUpdateEventController
|
||||
.add(SceneUpdateEvent.addGeometry(entity, geometry));
|
||||
|
||||
return entity;
|
||||
}
|
||||
|
||||
@@ -1968,8 +1997,8 @@ class ThermionViewerFFI extends ThermionViewer {
|
||||
struct.y = f2;
|
||||
struct.z = f3;
|
||||
struct.w = f3;
|
||||
set_material_property_float4(_sceneManager!, entity, materialIndex,
|
||||
ptr.cast<Char>(), struct);
|
||||
set_material_property_float4(
|
||||
_sceneManager!, entity, materialIndex, ptr.cast<Char>(), struct);
|
||||
allocator.free(ptr);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
library;
|
||||
|
||||
export 'geometry.dart';
|
||||
export 'gltf.dart';
|
||||
|
||||
export 'light_options.dart';
|
||||
|
||||
// a handle that can be safely passed back to the rendering layer to manipulate an Entity
|
||||
typedef ThermionEntity = int;
|
||||
@@ -0,0 +1,19 @@
|
||||
import 'package:thermion_dart/thermion_dart/viewer/thermion_viewer_base.dart';
|
||||
|
||||
class Geometry {
|
||||
final List<double> vertices;
|
||||
final List<int> indices;
|
||||
final List<double>? normals;
|
||||
final List<(double, double)>? uvs;
|
||||
final PrimitiveType primitiveType;
|
||||
final String? materialPath;
|
||||
|
||||
Geometry(this.vertices, this.indices, { this.normals=null, this.uvs=null,
|
||||
this.primitiveType = PrimitiveType.TRIANGLES, this.materialPath = null});
|
||||
|
||||
void scale(double factor) {
|
||||
for (int i = 0; i < vertices.length; i++) {
|
||||
vertices[i] = vertices[i] * factor;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
class GLTF {
|
||||
final String uri;
|
||||
final int numInstances;
|
||||
|
||||
GLTF(this.uri, this.numInstances);
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
enum LightType {
|
||||
SUN, //!< Directional light that also draws a sun's disk in the sky.
|
||||
DIRECTIONAL, //!< Directional light, emits light in a given direction.
|
||||
POINT, //!< Point light, emits light from a position, in all directions.
|
||||
FOCUSED_SPOT, //!< Physically correct spot light.
|
||||
SPOT,
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
import 'dart:math';
|
||||
import 'package:vector_math/vector_math_64.dart' as v;
|
||||
import 'package:vector_math/vector_math_64.dart';
|
||||
import 'light.dart';
|
||||
|
||||
class IBL {
|
||||
String? iblPath;
|
||||
final double iblIntensity;
|
||||
|
||||
IBL(this.iblIntensity);
|
||||
}
|
||||
|
||||
class DirectLight {
|
||||
final LightType type;
|
||||
final double color;
|
||||
final double intensity;
|
||||
final bool castShadows;
|
||||
late final v.Vector3 position;
|
||||
late final v.Vector3 direction;
|
||||
final double falloffRadius;
|
||||
final double spotLightConeInner;
|
||||
final double spotLightConeOuter;
|
||||
final double sunAngularRadius;
|
||||
final double sunHaloSize;
|
||||
final double sunHaloFallof;
|
||||
|
||||
DirectLight({
|
||||
required this.type,
|
||||
required this.color,
|
||||
required this.intensity,
|
||||
this.castShadows = false,
|
||||
required this.direction,
|
||||
required this.position,
|
||||
this.falloffRadius = 1.0,
|
||||
this.spotLightConeInner = pi / 8,
|
||||
this.spotLightConeOuter = pi / 4,
|
||||
this.sunAngularRadius = 0.545,
|
||||
this.sunHaloSize = 10.0,
|
||||
this.sunHaloFallof = 80.0,
|
||||
});
|
||||
DirectLight.point({
|
||||
double color = 6500,
|
||||
double intensity = 100000,
|
||||
bool castShadows = false,
|
||||
Vector3? position,
|
||||
double falloffRadius = 1.0,
|
||||
}) : this(
|
||||
type: LightType.POINT,
|
||||
color: color,
|
||||
intensity: intensity,
|
||||
castShadows: castShadows,
|
||||
position: position ?? Vector3(0, 1, 0),
|
||||
direction: Vector3.zero(),
|
||||
falloffRadius: falloffRadius,
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
// see filament Manipulator.h for more details
|
||||
@Deprecated(
|
||||
"This is used the native pointer manipulator Prefer ThermionGestureHandler instead")
|
||||
enum ManipulatorMode { ORBIT, MAP, FREE_FLIGHT }
|
||||
@@ -0,0 +1,4 @@
|
||||
// "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 'package:thermion_dart/thermion_dart/viewer/shared_types/shared_types.dart';
|
||||
|
||||
typedef FilamentPickResult = ({ThermionEntity entity, double x, double y});
|
||||
@@ -0,0 +1,10 @@
|
||||
// copied from filament/backened/DriverEnums.h
|
||||
enum PrimitiveType {
|
||||
// don't change the enums values (made to match GL)
|
||||
POINTS, //!< points
|
||||
LINES, //!< lines
|
||||
UNUSED1,
|
||||
LINE_STRIP, //!< line strip
|
||||
TRIANGLES, //!< triangles
|
||||
TRIANGLE_STRIP, //!< triangle strip
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
enum ShadowType {
|
||||
PCF, //!< percentage-closer filtered shadows (default)
|
||||
VSM, //!< variance shadows
|
||||
DPCF, //!< PCF with contact hardening simulation
|
||||
PCSS, //!< PCF with soft shadows and contact hardening
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
library shared_types;
|
||||
|
||||
export 'entities.dart';
|
||||
export 'light.dart';
|
||||
export 'shadow.dart';
|
||||
export 'manipulator.dart';
|
||||
export 'pick_result.dart';
|
||||
export 'primitive.dart';
|
||||
export 'texture_details.dart';
|
||||
export 'tone_mapper.dart';
|
||||
@@ -0,0 +1,14 @@
|
||||
///
|
||||
/// This represents the backing "surface" that we render into.
|
||||
/// "Texture" here is a misnomer as it is only a render target texture on certain platforms.
|
||||
///
|
||||
class TextureDetails {
|
||||
final int textureId;
|
||||
|
||||
// both width and height are in physical, not logical pixels
|
||||
final int width;
|
||||
final int height;
|
||||
|
||||
TextureDetails(
|
||||
{required this.textureId, required this.width, required this.height});
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
enum ToneMapper { ACES, FILMIC, LINEAR }
|
||||
856
thermion_dart/lib/thermion_dart/viewer/thermion_viewer_base.dart
Normal file
856
thermion_dart/lib/thermion_dart/viewer/thermion_viewer_base.dart
Normal file
@@ -0,0 +1,856 @@
|
||||
import 'package:thermion_dart/thermion_dart/viewer/events.dart';
|
||||
|
||||
import 'shared_types/shared_types.dart';
|
||||
export 'shared_types/shared_types.dart';
|
||||
|
||||
import 'dart:math';
|
||||
import 'dart:typed_data';
|
||||
import 'package:thermion_dart/thermion_dart/entities/abstract_gizmo.dart';
|
||||
import 'package:vector_math/vector_math_64.dart';
|
||||
import 'dart:async';
|
||||
import 'package:animation_tools_dart/animation_tools_dart.dart';
|
||||
|
||||
abstract class ThermionViewer {
|
||||
|
||||
///
|
||||
/// A Future that resolves when the underlying rendering context has been successfully created.
|
||||
///
|
||||
Future<bool> get initialized;
|
||||
|
||||
///
|
||||
/// The current dimensions of the viewport (in physical pixels).
|
||||
///
|
||||
var viewportDimensions = (0.0, 0.0);
|
||||
|
||||
///
|
||||
/// The current ratio of logical to physical pixels.
|
||||
///
|
||||
late double pixelRatio;
|
||||
|
||||
///
|
||||
/// The result(s) of calling [pick] (see below).
|
||||
/// This may be a broadcast stream, so you should ensure you have subscribed to this stream before calling [pick].
|
||||
/// If [pick] is called without an active subscription to this stream, the results will be silently discarded.
|
||||
///
|
||||
Stream<FilamentPickResult> get pickResult;
|
||||
|
||||
///
|
||||
/// The result(s) of calling [pickGizmo] (see below).
|
||||
///
|
||||
Stream<FilamentPickResult> get gizmoPickResult;
|
||||
|
||||
///
|
||||
/// A Stream containing entities added/removed to/from to the scene.
|
||||
///
|
||||
Stream<SceneUpdateEvent> get sceneUpdated;
|
||||
|
||||
///
|
||||
/// Whether the controller is currently rendering at [framerate].
|
||||
///
|
||||
bool get rendering;
|
||||
|
||||
///
|
||||
/// Set to true to continuously render the scene at the framerate specified by [setFrameRate] (60 fps by default).
|
||||
///
|
||||
Future setRendering(bool render);
|
||||
|
||||
///
|
||||
/// Render a single frame.
|
||||
///
|
||||
Future render();
|
||||
|
||||
///
|
||||
/// Render a single frame to the viewport and copy the pixel buffer to [out].
|
||||
///
|
||||
Future<Uint8List> capture();
|
||||
|
||||
///
|
||||
/// Sets the framerate for continuous rendering when [setRendering] is enabled.
|
||||
///
|
||||
Future setFrameRate(int framerate);
|
||||
|
||||
///
|
||||
/// Destroys/disposes the viewer (including the entire scene). You cannot use the viewer after calling this method.
|
||||
///
|
||||
Future dispose();
|
||||
|
||||
///
|
||||
/// Set the background image to [path] (which should have a file extension .png, .jpg, or .ktx).
|
||||
/// This will be rendered at the maximum depth (i.e. behind all other objects including the skybox).
|
||||
/// If [fillHeight] is false, the image will be rendered at its original size. Note this may cause issues with pixel density so be sure to specify the correct resolution
|
||||
/// If [fillHeight] is true, the image will be stretched/compressed to fit the height of the viewport.
|
||||
///
|
||||
Future setBackgroundImage(String path, {bool fillHeight = false});
|
||||
|
||||
///
|
||||
/// Moves the background image to the relative offset from the origin (bottom-left) specified by [x] and [y].
|
||||
/// If [clamp] is true, the image cannot be positioned outside the bounds of the viewport.
|
||||
///
|
||||
Future setBackgroundImagePosition(double x, double y, {bool clamp = false});
|
||||
|
||||
///
|
||||
/// Removes the background image.
|
||||
///
|
||||
Future clearBackgroundImage();
|
||||
|
||||
///
|
||||
/// Sets the color for the background plane (positioned at the maximum depth, i.e. behind all other objects including the skybox).
|
||||
///
|
||||
Future setBackgroundColor(double r, double g, double b, double alpha);
|
||||
|
||||
///
|
||||
/// Load a skybox from [skyboxPath] (which must be a .ktx file)
|
||||
///
|
||||
Future loadSkybox(String skyboxPath);
|
||||
|
||||
///
|
||||
/// Removes the skybox from the scene.
|
||||
///
|
||||
Future removeSkybox();
|
||||
|
||||
///
|
||||
/// Creates an indirect light by loading the reflections/irradiance from the KTX file.
|
||||
/// Only one indirect light can be active at any given time; if an indirect light has already been loaded, it will be replaced.
|
||||
///
|
||||
Future loadIbl(String lightingPath, {double intensity = 30000});
|
||||
|
||||
///
|
||||
/// Creates a indirect light with the given color.
|
||||
/// Only one indirect light can be active at any given time; if an indirect light has already been loaded, it will be replaced.
|
||||
///
|
||||
Future createIbl(double r, double g, double b, double intensity);
|
||||
|
||||
///
|
||||
/// Rotates the IBL & skybox.
|
||||
///
|
||||
Future rotateIbl(Matrix3 rotation);
|
||||
|
||||
///
|
||||
/// Removes the image-based light from the scene.
|
||||
///
|
||||
Future removeIbl();
|
||||
|
||||
///
|
||||
/// Add a light to the scene.
|
||||
/// See LightManager.h for details
|
||||
/// Note that [sunAngularRadius] is in degrees,
|
||||
/// whereas [spotLightConeInner] and [spotLightConeOuter] are in radians
|
||||
///
|
||||
@Deprecated("This will be removed in future versions. Use addDirectLight instead.")
|
||||
Future<ThermionEntity> addLight(
|
||||
LightType type,
|
||||
double colour,
|
||||
double intensity,
|
||||
double posX,
|
||||
double posY,
|
||||
double posZ,
|
||||
double dirX,
|
||||
double dirY,
|
||||
double dirZ,
|
||||
{double falloffRadius = 1.0,
|
||||
double spotLightConeInner = pi / 8,
|
||||
double spotLightConeOuter = pi / 4,
|
||||
double sunAngularRadius = 0.545,
|
||||
double sunHaloSize = 10.0,
|
||||
double sunHaloFallof = 80.0,
|
||||
bool castShadows = true});
|
||||
|
||||
///
|
||||
/// Adds a direct light to the scene.
|
||||
/// See LightManager.h for details
|
||||
/// Note that [sunAngularRadius] is in degrees,
|
||||
/// whereas [spotLightConeInner] and [spotLightConeOuter] are in radians
|
||||
///
|
||||
Future<ThermionEntity> addDirectLight(
|
||||
DirectLight light);
|
||||
|
||||
///
|
||||
/// Remove a light from the scene.
|
||||
///
|
||||
Future removeLight(ThermionEntity light);
|
||||
|
||||
///
|
||||
/// Remove all lights (excluding IBL) from the scene.
|
||||
///
|
||||
Future clearLights();
|
||||
|
||||
///
|
||||
/// Load the .glb asset at the given path and insert into the scene.
|
||||
/// Specify [numInstances] to create multiple instances (this is more efficient than dynamically instantating at a later time). You can then retrieve the created instances with [getInstances].
|
||||
/// If you want to be able to call [createInstance] at a later time, you must pass true for [keepData].
|
||||
/// If [keepData] is false, the source glTF data will be released and [createInstance] will throw an exception.
|
||||
///
|
||||
Future<ThermionEntity> loadGlb(String path,
|
||||
{int numInstances = 1, bool keepData = false});
|
||||
|
||||
///
|
||||
/// Load the .glb asset from the specified buffer and insert into the scene.
|
||||
/// Specify [numInstances] to create multiple instances (this is more efficient than dynamically instantating at a later time). You can then retrieve the created instances with [getInstances].
|
||||
/// If you want to be able to call [createInstance] at a later time, you must pass true for [keepData].
|
||||
/// If [keepData] is false, the source glTF data will be released and [createInstance] will throw an exception.
|
||||
///
|
||||
Future<ThermionEntity> loadGlbFromBuffer(Uint8List data,
|
||||
{int numInstances = 1, bool keepData = false});
|
||||
|
||||
///
|
||||
/// Create a new instance of [entity].
|
||||
///
|
||||
Future<ThermionEntity> createInstance(ThermionEntity entity);
|
||||
|
||||
///
|
||||
/// Returns the number of instances of the asset associated with [entity].
|
||||
///
|
||||
Future<int> getInstanceCount(ThermionEntity entity);
|
||||
|
||||
///
|
||||
/// Returns all instances of [entity].
|
||||
///
|
||||
Future<List<ThermionEntity>> getInstances(ThermionEntity entity);
|
||||
|
||||
///
|
||||
/// Load the .gltf asset at the given path and insert into the scene.
|
||||
/// [relativeResourcePath] is the folder path where the glTF resources are stored;
|
||||
/// this is usually the parent directory of the .gltf file itself.
|
||||
///
|
||||
/// See [loadGlb] for an explanation of [keepData].
|
||||
///
|
||||
Future<ThermionEntity> loadGltf(String path, String relativeResourcePath,
|
||||
{bool keepData = false});
|
||||
|
||||
///
|
||||
/// Called by `FilamentGestureDetector`. You probably don't want to call this yourself.
|
||||
///
|
||||
Future panStart(double x, double y);
|
||||
|
||||
///
|
||||
/// Called by `FilamentGestureDetector`. You probably don't want to call this yourself.
|
||||
///
|
||||
Future panUpdate(double x, double y);
|
||||
|
||||
///
|
||||
/// Called by `FilamentGestureDetector`. You probably don't want to call this yourself.
|
||||
///
|
||||
Future panEnd();
|
||||
|
||||
///
|
||||
/// Called by `FilamentGestureDetector`. You probably don't want to call this yourself.
|
||||
///
|
||||
Future rotateStart(double x, double y);
|
||||
|
||||
///
|
||||
/// Called by `FilamentGestureDetector`. You probably don't want to call this yourself.
|
||||
///
|
||||
Future rotateUpdate(double x, double y);
|
||||
|
||||
///
|
||||
/// Called by `FilamentGestureDetector`. You probably don't want to call this yourself.
|
||||
///
|
||||
Future rotateEnd();
|
||||
|
||||
///
|
||||
/// Set the weights for all morph targets in [entity] to [weights].
|
||||
/// Note that [weights] must contain values for ALL morph targets, but no exception will be thrown if you don't do so (you'll just get incorrect results).
|
||||
/// If you only want to set one value, set all others to zero (check [getMorphTargetNames] if you need the get a list of all morph targets).
|
||||
/// IMPORTANT - this accepts the actual ThermionEntity with the relevant morph targets (unlike [getMorphTargetNames], which uses the parent entity and the child mesh name).
|
||||
/// Use [getChildEntityByName] if you are setting the weights for a child mesh.
|
||||
///
|
||||
Future setMorphTargetWeights(ThermionEntity entity, List<double> weights);
|
||||
|
||||
///
|
||||
/// Gets the names of all morph targets for the child renderable [childEntity] under [entity].
|
||||
///
|
||||
Future<List<String>> getMorphTargetNames(
|
||||
ThermionEntity entity, ThermionEntity childEntity);
|
||||
|
||||
///
|
||||
/// Gets the names of all bones for the armature at [skinIndex] under the specified [entity].
|
||||
///
|
||||
Future<List<String>> getBoneNames(ThermionEntity entity, {int skinIndex = 0});
|
||||
|
||||
///
|
||||
/// Gets the names of all glTF animations embedded in the specified entity.
|
||||
///
|
||||
Future<List<String>> getAnimationNames(ThermionEntity entity);
|
||||
|
||||
///
|
||||
/// Returns the length (in seconds) of the animation at the given index.
|
||||
///
|
||||
Future<double> getAnimationDuration(
|
||||
ThermionEntity entity, int animationIndex);
|
||||
|
||||
///
|
||||
/// Animate the morph targets in [entity]. See [MorphTargetAnimation] for an explanation as to how to construct the animation frame data.
|
||||
/// This method will check the morph target names specified in [animation] against the morph target names that actually exist exist under [meshName] in [entity],
|
||||
/// throwing an exception if any cannot be found.
|
||||
/// It is permissible for [animation] to omit any targets that do exist under [meshName]; these simply won't be animated.
|
||||
///
|
||||
Future setMorphAnimationData(
|
||||
ThermionEntity entity, MorphAnimationData animation,
|
||||
{List<String>? targetMeshNames});
|
||||
|
||||
///
|
||||
/// Clear all current morph animations for [entity].
|
||||
///
|
||||
Future clearMorphAnimationData(ThermionEntity entity);
|
||||
|
||||
///
|
||||
/// Resets all bones in the given entity to their rest pose.
|
||||
/// This should be done before every call to addBoneAnimation.
|
||||
///
|
||||
Future resetBones(ThermionEntity entity);
|
||||
|
||||
///
|
||||
/// Enqueues and plays the [animation] for the specified bone(s).
|
||||
/// By default, frame data is interpreted as being in *parent* bone space;
|
||||
/// a 45 degree around Y means the bone will rotate 45 degrees around the
|
||||
/// Y axis of the parent bone *in its current orientation*.
|
||||
/// (i.e NOT the parent bone's rest position!).
|
||||
/// Currently, only [Space.ParentBone] and [Space.Model] are supported; if you want
|
||||
/// to transform to another space, you will need to do so manually.
|
||||
///
|
||||
/// [fadeInInSecs]/[fadeOutInSecs]/[maxDelta] are used to cross-fade between
|
||||
/// the current active glTF animation ("animation1") and the animation you
|
||||
/// set via this method ("animation2"). The bone orientations will be
|
||||
/// linearly interpolated between animation1 and animation2; at time 0,
|
||||
/// the orientation will be 100% animation1, at time [fadeInInSecs], the
|
||||
/// animation will be ((1 - maxDelta) * animation1) + (maxDelta * animation2).
|
||||
/// This will be applied in reverse after [fadeOutInSecs].
|
||||
///
|
||||
///
|
||||
Future addBoneAnimation(ThermionEntity entity, BoneAnimationData animation,
|
||||
{int skinIndex = 0,
|
||||
double fadeInInSecs = 0.0,
|
||||
double fadeOutInSecs = 0.0,
|
||||
double maxDelta = 1.0});
|
||||
|
||||
///
|
||||
/// Gets the entity representing the bone at [boneIndex]/[skinIndex].
|
||||
/// The returned entity is only intended for use with [getWorldTransform].
|
||||
///
|
||||
Future<ThermionEntity> getBone(ThermionEntity parent, int boneIndex,
|
||||
{int skinIndex = 0});
|
||||
|
||||
///
|
||||
/// Gets the local (relative to parent) transform for [entity].
|
||||
///
|
||||
Future<Matrix4> getLocalTransform(ThermionEntity entity);
|
||||
|
||||
///
|
||||
/// Gets the world transform for [entity].
|
||||
///
|
||||
Future<Matrix4> getWorldTransform(ThermionEntity entity);
|
||||
|
||||
///
|
||||
/// Gets the inverse bind (pose) matrix for the bone.
|
||||
/// Note that [parent] must be the ThermionEntity returned by [loadGlb/loadGltf], not any other method ([getChildEntity] etc).
|
||||
/// This is because all joint information is internally stored with the parent entity.
|
||||
///
|
||||
Future<Matrix4> getInverseBindMatrix(ThermionEntity parent, int boneIndex,
|
||||
{int skinIndex = 0});
|
||||
|
||||
///
|
||||
/// Sets the transform (relative to its parent) for [entity].
|
||||
///
|
||||
Future setTransform(ThermionEntity entity, Matrix4 transform);
|
||||
|
||||
///
|
||||
/// Updates the bone matrices for [entity] (which must be the ThermionEntity
|
||||
/// returned by [loadGlb/loadGltf]).
|
||||
/// Under the hood, this just calls [updateBoneMatrices] on the Animator
|
||||
/// instance of the relevant FilamentInstance (which uses the local
|
||||
/// bone transform and the inverse bind matrix to set the bone matrix).
|
||||
///
|
||||
Future updateBoneMatrices(ThermionEntity entity);
|
||||
|
||||
///
|
||||
/// Directly set the bone matrix for the bone at the given index.
|
||||
/// Don't call this manually unless you know what you're doing.
|
||||
///
|
||||
Future setBoneTransform(
|
||||
ThermionEntity entity, int boneIndex, Matrix4 transform,
|
||||
{int skinIndex = 0});
|
||||
|
||||
///
|
||||
/// Removes/destroys the specified entity from the scene.
|
||||
/// [entity] will no longer be a valid handle after this method is called; ensure you immediately discard all references once this method is complete.
|
||||
///
|
||||
Future removeEntity(ThermionEntity entity);
|
||||
|
||||
///
|
||||
/// Removes/destroys all renderable entities from the scene (including cameras).
|
||||
/// All [ThermionEntity] handles will no longer be valid after this method is called; ensure you immediately discard all references to all entities once this method is complete.
|
||||
///
|
||||
Future clearEntities();
|
||||
|
||||
///
|
||||
/// Called by `FilamentGestureDetector`. You probably don't want to call this yourself.
|
||||
///
|
||||
Future zoomBegin();
|
||||
|
||||
///
|
||||
/// Called by `FilamentGestureDetector`. You probably don't want to call this yourself.
|
||||
///
|
||||
Future zoomUpdate(double x, double y, double z);
|
||||
|
||||
///
|
||||
/// Called by `FilamentGestureDetector`. You probably don't want to call this yourself.
|
||||
///
|
||||
Future zoomEnd();
|
||||
|
||||
///
|
||||
/// Schedules the glTF animation at [index] in [entity] to start playing on the next frame.
|
||||
///
|
||||
Future playAnimation(ThermionEntity entity, int index,
|
||||
{bool loop = false,
|
||||
bool reverse = false,
|
||||
bool replaceActive = true,
|
||||
double crossfade = 0.0,
|
||||
double startOffset = 0.0});
|
||||
|
||||
///
|
||||
/// Schedules the glTF animation at [index] in [entity] to start playing on the next frame.
|
||||
///
|
||||
Future playAnimationByName(ThermionEntity entity, String name,
|
||||
{bool loop = false,
|
||||
bool reverse = false,
|
||||
bool replaceActive = true,
|
||||
double crossfade = 0.0});
|
||||
|
||||
Future setAnimationFrame(
|
||||
ThermionEntity entity, int index, int animationFrame);
|
||||
|
||||
Future stopAnimation(ThermionEntity entity, int animationIndex);
|
||||
Future stopAnimationByName(ThermionEntity entity, String name);
|
||||
|
||||
///
|
||||
/// Sets the current scene camera to the glTF camera under [name] in [entity].
|
||||
///
|
||||
Future setCamera(ThermionEntity entity, String? name);
|
||||
|
||||
///
|
||||
/// Sets the current scene camera to the main camera (which is always available and added to every scene by default).
|
||||
///
|
||||
Future setMainCamera();
|
||||
|
||||
///
|
||||
/// Returns the entity associated with the main camera.
|
||||
///
|
||||
Future<ThermionEntity> getMainCamera();
|
||||
|
||||
///
|
||||
/// Sets the horizontal field of view (if [horizontal] is true) or vertical field of view for the currently active camera to [degrees].
|
||||
/// The aspect ratio of the current viewport is used.
|
||||
///
|
||||
Future setCameraFov(double degrees, {bool horizontal = true});
|
||||
|
||||
///
|
||||
/// Gets the field of view (in degrees).
|
||||
///
|
||||
Future<double> getCameraFov(bool horizontal);
|
||||
|
||||
///
|
||||
/// Sets the tone mapping (requires postprocessing).
|
||||
///
|
||||
Future setToneMapping(ToneMapper mapper);
|
||||
|
||||
///
|
||||
/// Sets the strength of the bloom.
|
||||
///
|
||||
Future setBloom(double bloom);
|
||||
|
||||
///
|
||||
/// Sets the focal length of the camera. Default value is 28.0.
|
||||
///
|
||||
Future setCameraFocalLength(double focalLength);
|
||||
|
||||
///
|
||||
/// Sets the distance (in world units) to the near/far planes for the active camera. Default values are 0.05/1000.0. See Camera.h for details.
|
||||
///
|
||||
Future setCameraCulling(double near, double far);
|
||||
|
||||
///
|
||||
/// Get the distance (in world units) to the near plane for the active camera.
|
||||
///
|
||||
@Deprecated("Use getCameraNear")
|
||||
Future<double> getCameraCullingNear();
|
||||
|
||||
///
|
||||
/// Get the distance (in world units) to the near plane for the active camera.
|
||||
///
|
||||
Future<double> getCameraNear();
|
||||
|
||||
///
|
||||
/// Get the distance (in world units) to the far culling plane for the active camera.
|
||||
///
|
||||
Future<double> getCameraCullingFar();
|
||||
|
||||
///
|
||||
///
|
||||
///
|
||||
Future setCameraLensProjection(
|
||||
double near, double far, double aspect, double focalLength);
|
||||
|
||||
///
|
||||
/// Sets the focus distance for the camera.
|
||||
///
|
||||
Future setCameraFocusDistance(double focusDistance);
|
||||
|
||||
///
|
||||
/// Get the camera position in world space.
|
||||
///
|
||||
Future<Vector3> getCameraPosition();
|
||||
|
||||
///
|
||||
/// Get the camera's model matrix.
|
||||
///
|
||||
Future<Matrix4> getCameraModelMatrix();
|
||||
|
||||
///
|
||||
/// Get the camera's view matrix. See Camera.h for more details.
|
||||
///
|
||||
Future<Matrix4> getCameraViewMatrix();
|
||||
|
||||
///
|
||||
/// Get the camera's projection matrix. See Camera.h for more details.
|
||||
///
|
||||
Future<Matrix4> getCameraProjectionMatrix();
|
||||
|
||||
///
|
||||
/// Get the camera's culling projection matrix. See Camera.h for more details.
|
||||
///
|
||||
Future<Matrix4> getCameraCullingProjectionMatrix();
|
||||
|
||||
///
|
||||
/// Get the camera's culling frustum in world space. Returns a (vector_math) [Frustum] instance where plane0-plane6 define the left, right, bottom, top, far and near planes respectively.
|
||||
/// See Camera.h and (filament) Frustum.h for more details.
|
||||
///
|
||||
Future<Frustum> getCameraFrustum();
|
||||
|
||||
///
|
||||
/// Set the camera position in world space. Note this is not persistent - any viewport navigation will reset the camera transform.
|
||||
///
|
||||
Future setCameraPosition(double x, double y, double z);
|
||||
|
||||
///
|
||||
/// Get the camera rotation matrix.
|
||||
///
|
||||
Future<Matrix3> getCameraRotation();
|
||||
|
||||
///
|
||||
/// Repositions the camera to the last vertex of the bounding box of [entity], looking at the penultimate vertex.
|
||||
///
|
||||
Future moveCameraToAsset(ThermionEntity entity);
|
||||
|
||||
///
|
||||
/// Enables/disables frustum culling. Currently we don't expose a method for manipulating the camera projection/culling matrices so this is your only option to deal with unwanted near/far clipping.
|
||||
///
|
||||
Future setViewFrustumCulling(bool enabled);
|
||||
|
||||
///
|
||||
/// Sets the camera exposure.
|
||||
///
|
||||
Future setCameraExposure(
|
||||
double aperture, double shutterSpeed, double sensitivity);
|
||||
|
||||
///
|
||||
/// Rotate the camera by [rads] around the given axis.
|
||||
///
|
||||
Future setCameraRotation(Quaternion quaternion);
|
||||
|
||||
///
|
||||
/// Sets the camera model matrix.
|
||||
///
|
||||
@Deprecated("Will be superseded by setCameraModelMatrix4")
|
||||
Future setCameraModelMatrix(List<double> matrix);
|
||||
|
||||
///
|
||||
/// Sets the camera model matrix.
|
||||
///
|
||||
Future setCameraModelMatrix4(Matrix4 matrix);
|
||||
|
||||
///
|
||||
/// Sets the `baseColorFactor` property for the material at index [materialIndex] in [entity] under node [meshName] to [color].
|
||||
///
|
||||
@Deprecated("Use setMaterialPropertyFloat4 instead")
|
||||
Future setMaterialColor(ThermionEntity entity, String meshName,
|
||||
int materialIndex, double r, double g, double b, double a);
|
||||
|
||||
///
|
||||
/// Sets the material property [propertyName] under material [materialIndex] for [entity] to [value].
|
||||
/// [entity] must have a Renderable attached.
|
||||
///
|
||||
Future setMaterialPropertyFloat4(ThermionEntity entity, String propertyName,
|
||||
int materialIndex, double f1, double f2, double f3, double f4);
|
||||
|
||||
///
|
||||
/// Sets the material property [propertyName] under material [materialIndex] for [entity] to [value].
|
||||
/// [entity] must have a Renderable attached.
|
||||
///
|
||||
Future setMaterialPropertyFloat(ThermionEntity entity, String propertyName,
|
||||
int materialIndex, double value);
|
||||
|
||||
///
|
||||
/// Scale [entity] to fit within the unit cube.
|
||||
///
|
||||
Future transformToUnitCube(ThermionEntity entity);
|
||||
|
||||
///
|
||||
/// Directly sets the world space position for [entity] to the given coordinates.
|
||||
///
|
||||
Future setPosition(ThermionEntity entity, double x, double y, double z);
|
||||
|
||||
///
|
||||
/// Set the world space position for [lightEntity] to the given coordinates.
|
||||
///
|
||||
Future setLightPosition(
|
||||
ThermionEntity lightEntity, double x, double y, double z);
|
||||
|
||||
///
|
||||
/// Sets the world space direction for [lightEntity] to the given vector.
|
||||
///
|
||||
Future setLightDirection(ThermionEntity lightEntity, Vector3 direction);
|
||||
|
||||
///
|
||||
/// Directly sets the scale for [entity], skipping all collision detection.
|
||||
///
|
||||
Future setScale(ThermionEntity entity, double scale);
|
||||
|
||||
///
|
||||
/// Directly sets the rotation for [entity] to [rads] around the axis {x,y,z}, skipping all collision detection.
|
||||
///
|
||||
Future setRotation(
|
||||
ThermionEntity entity, double rads, double x, double y, double z);
|
||||
|
||||
///
|
||||
/// Queues an update to the worldspace position for [entity] to {x,y,z}.
|
||||
/// The actual update will occur on the next frame, and will be subject to collision detection.
|
||||
///
|
||||
Future queuePositionUpdate(
|
||||
ThermionEntity entity, double x, double y, double z,
|
||||
{bool relative = false});
|
||||
|
||||
///
|
||||
/// TODO
|
||||
///
|
||||
Future queuePositionUpdateFromViewportCoords(
|
||||
ThermionEntity entity, double x, double y);
|
||||
|
||||
///
|
||||
/// TODO
|
||||
///
|
||||
Future queueRelativePositionUpdateWorldAxis(ThermionEntity entity,
|
||||
double viewportX, double viewportY, double x, double y, double z);
|
||||
|
||||
///
|
||||
/// Queues an update to the worldspace rotation for [entity].
|
||||
/// The actual update will occur on the next frame, and will be subject to collision detection.
|
||||
///
|
||||
Future queueRotationUpdate(
|
||||
ThermionEntity entity, double rads, double x, double y, double z,
|
||||
{bool relative = false});
|
||||
|
||||
///
|
||||
/// Same as [queueRotationUpdate].
|
||||
///
|
||||
Future queueRotationUpdateQuat(ThermionEntity entity, Quaternion quat,
|
||||
{bool relative = false});
|
||||
|
||||
///
|
||||
/// Enable/disable postprocessing (disabled by default).
|
||||
///
|
||||
Future setPostProcessing(bool enabled);
|
||||
|
||||
///
|
||||
/// Enable/disable shadows (disabled by default).
|
||||
///
|
||||
Future setShadowsEnabled(bool enabled);
|
||||
|
||||
///
|
||||
/// Set shadow type.
|
||||
///
|
||||
Future setShadowType(ShadowType shadowType);
|
||||
|
||||
///
|
||||
/// Set soft shadow options (ShadowType DPCF and PCSS)
|
||||
///
|
||||
Future setSoftShadowOptions(double penumbraScale, double penumbraRatioScale);
|
||||
|
||||
///
|
||||
/// Set antialiasing options.
|
||||
///
|
||||
Future setAntiAliasing(bool msaa, bool fxaa, bool taa);
|
||||
|
||||
///
|
||||
/// Sets the rotation for [entity] to the specified quaternion.
|
||||
///
|
||||
Future setRotationQuat(ThermionEntity entity, Quaternion rotation);
|
||||
|
||||
///
|
||||
/// Reveal the node [meshName] under [entity]. Only applicable if [hide] had previously been called; this is a no-op otherwise.
|
||||
///
|
||||
Future reveal(ThermionEntity entity, String? meshName);
|
||||
|
||||
///
|
||||
/// If [meshName] is provided, hide the node [meshName] under [entity], otherwise hide the root node for [entity].
|
||||
/// The entity still exists in memory, but is no longer being rendered into the scene. Call [reveal] to re-commence rendering.
|
||||
///
|
||||
Future hide(ThermionEntity entity, String? meshName);
|
||||
|
||||
///
|
||||
/// Used to select the entity in the scene at the given viewport coordinates.
|
||||
/// Called by `FilamentGestureDetector` on a mouse/finger down event. You probably don't want to call this yourself.
|
||||
/// 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).
|
||||
///
|
||||
void pick(int x, int y);
|
||||
|
||||
///
|
||||
/// Used to test whether a Gizmo is at the given viewport coordinates.
|
||||
/// Called by `FilamentGestureDetector` on a mouse/finger down event. You probably don't want to call this yourself.
|
||||
/// This is asynchronous and will require 2-3 frames to complete - subscribe to the [gizmoPickResult] 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).
|
||||
///
|
||||
void pickGizmo(int x, int y);
|
||||
|
||||
///
|
||||
/// Retrieves the name assigned to the given ThermionEntity (usually corresponds to the glTF mesh name).
|
||||
///
|
||||
String? getNameForEntity(ThermionEntity entity);
|
||||
|
||||
///
|
||||
/// Sets the options for manipulating the camera via the viewport.
|
||||
/// ManipulatorMode.FREE_FLIGHT and ManipulatorMode.MAP are currently unsupported and will throw an exception.
|
||||
///
|
||||
@Deprecated("Use ThermionGestureHandler instead")
|
||||
Future setCameraManipulatorOptions(
|
||||
{ManipulatorMode mode = ManipulatorMode.ORBIT,
|
||||
double orbitSpeedX = 0.01,
|
||||
double orbitSpeedY = 0.01,
|
||||
double zoomSpeed = 0.01});
|
||||
|
||||
///
|
||||
/// Returns all child entities under [parent].
|
||||
///
|
||||
Future<List<ThermionEntity>> getChildEntities(
|
||||
ThermionEntity parent, bool renderableOnly);
|
||||
|
||||
///
|
||||
/// Finds the child entity named [childName] associated with the given parent.
|
||||
/// Usually, [parent] will be the return value from [loadGlb]/[loadGltf] and [childName] will be the name of a node/mesh.
|
||||
///
|
||||
Future<ThermionEntity> getChildEntity(
|
||||
ThermionEntity parent, String childName);
|
||||
|
||||
///
|
||||
/// List the name of all child entities under the given entity.
|
||||
///
|
||||
Future<List<String>> getChildEntityNames(ThermionEntity entity,
|
||||
{bool renderableOnly = true});
|
||||
|
||||
///
|
||||
/// If [recording] is set to true, each frame the framebuffer/texture will be written to /tmp/output_*.png.
|
||||
/// This will impact performance; handle with care.
|
||||
///
|
||||
Future setRecording(bool recording);
|
||||
|
||||
///
|
||||
/// Sets the output directory where recorded PNGs will be placed.
|
||||
///
|
||||
Future setRecordingOutputDirectory(String outputDirectory);
|
||||
|
||||
///
|
||||
/// An [entity] will only be animatable after an animation component is attached.
|
||||
/// Any calls to [playAnimation]/[setBoneAnimation]/[setMorphAnimation] will have no visual effect until [addAnimationComponent] has been called on the instance.
|
||||
///
|
||||
Future addAnimationComponent(ThermionEntity entity);
|
||||
|
||||
///
|
||||
/// Removes an animation component from [entity].
|
||||
///
|
||||
Future removeAnimationComponent(ThermionEntity entity);
|
||||
|
||||
///
|
||||
/// Makes [entity] collidable.
|
||||
/// This allows you to call [testCollisions] with any other entity ("entity B") to see if [entity] has collided with entity B. The callback will be invoked if so.
|
||||
/// Alternatively, if [affectsTransform] is true and this entity collides with another entity, any queued position updates to the latter entity will be ignored.
|
||||
///
|
||||
Future addCollisionComponent(ThermionEntity entity,
|
||||
{void Function(int entityId1, int entityId2)? callback,
|
||||
bool affectsTransform = false});
|
||||
|
||||
///
|
||||
/// Removes the collision component from [entity], meaning this will no longer be tested when [testCollisions] or [queuePositionUpdate] is called with another entity.
|
||||
///
|
||||
Future removeCollisionComponent(ThermionEntity entity);
|
||||
|
||||
///
|
||||
/// Creates a (renderable) entity with the specified geometry and adds to the scene.
|
||||
/// If [keepData] is true, the source data will not be released.
|
||||
///
|
||||
Future createGeometry(Geometry geometry, { bool keepData= false});
|
||||
|
||||
///
|
||||
/// Gets the parent entity of [entity]. Returns null if the entity has no parent.
|
||||
///
|
||||
Future<ThermionEntity?> getParent(ThermionEntity entity);
|
||||
|
||||
///
|
||||
/// Gets the ancestor (ultimate parent) entity of [entity]. Returns null if the entity has no parent.
|
||||
///
|
||||
Future<ThermionEntity?> getAncestor(ThermionEntity entity);
|
||||
|
||||
///
|
||||
/// Sets the parent transform of [child] to [parent].
|
||||
///
|
||||
Future setParent(ThermionEntity child, ThermionEntity parent,
|
||||
{bool preserveScaling});
|
||||
|
||||
///
|
||||
/// Test all collidable entities against this entity to see if any have collided.
|
||||
/// This method returns void; the relevant callback passed to [addCollisionComponent] will be fired if a collision is detected.
|
||||
///
|
||||
Future testCollisions(ThermionEntity entity);
|
||||
|
||||
///
|
||||
/// Sets the draw priority for the given entity. See RenderableManager.h for more details.
|
||||
///
|
||||
Future setPriority(ThermionEntity entityId, int priority);
|
||||
|
||||
///
|
||||
/// The gizmo for translating/rotating objects. Only one gizmo is present in the scene.
|
||||
///
|
||||
AbstractGizmo? get gizmo;
|
||||
|
||||
///
|
||||
/// Register a callback to be invoked when this viewer is disposed.
|
||||
///
|
||||
void onDispose(Future Function() callback);
|
||||
|
||||
///
|
||||
/// Gets the 2D bounding box (in viewport coordinates) for the given entity.
|
||||
///
|
||||
Future<Aabb2> getViewportBoundingBox(ThermionEntity entity);
|
||||
|
||||
///
|
||||
/// Filament assigns renderables to a numeric layer.
|
||||
/// We place all scene assets in layer 0 (enabled by default), gizmos in layer 1 (enabled by default), world grid in layer 2 (disabled by default).
|
||||
/// Use this method to toggle visibility of the respective layer.
|
||||
///
|
||||
Future setLayerEnabled(int layer, bool enabled);
|
||||
|
||||
///
|
||||
/// Show/hide the translation gizmo.
|
||||
///
|
||||
Future setGizmoVisibility(bool visible);
|
||||
|
||||
///
|
||||
/// Renders an outline around [entity] with the given color.
|
||||
///
|
||||
Future setStencilHighlight(ThermionEntity entity,
|
||||
{double r = 1.0, double g = 0.0, double b = 0.0});
|
||||
|
||||
///
|
||||
/// Removes the outline around [entity]. Noop if there was no highlight.
|
||||
///
|
||||
Future removeStencilHighlight(ThermionEntity entity);
|
||||
}
|
||||
@@ -2,14 +2,13 @@ import 'dart:math';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:thermion_dart/thermion_dart/entities/abstract_gizmo.dart';
|
||||
import 'package:thermion_dart/thermion_dart/scene.dart';
|
||||
import 'package:thermion_dart/thermion_dart/thermion_viewer.dart';
|
||||
import 'package:thermion_dart/thermion_dart/viewer/events.dart';
|
||||
import 'package:thermion_dart/thermion_dart/viewer/thermion_viewer_base.dart';
|
||||
import 'package:vector_math/vector_math_64.dart';
|
||||
import 'dart:async';
|
||||
import 'package:animation_tools_dart/animation_tools_dart.dart';
|
||||
|
||||
typedef ThermionViewerImpl = ThermionViewerStub;
|
||||
|
||||
class ThermionViewerStub extends ThermionViewer {
|
||||
@override
|
||||
Future addAnimationComponent(ThermionEntity entity) {
|
||||
@@ -436,10 +435,6 @@ class ThermionViewerStub extends ThermionViewer {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
// TODO: implement scene
|
||||
Scene get scene => throw UnimplementedError();
|
||||
|
||||
@override
|
||||
Future setAnimationFrame(
|
||||
ThermionEntity entity, int index, int animationFrame) {
|
||||
@@ -850,12 +845,7 @@ class ThermionViewerStub extends ThermionViewer {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
Future createGeometry(List<double> vertices, List<int> indices, {String? materialPath, List<double>? normals, PrimitiveType primitiveType = PrimitiveType.TRIANGLES}) {
|
||||
// TODO: implement createGeometry
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
Future<ThermionEntity> loadGlb(String path, {int numInstances = 1, bool keepData = false}) {
|
||||
// TODO: implement loadGlb
|
||||
@@ -867,4 +857,34 @@ class ThermionViewerStub extends ThermionViewer {
|
||||
// TODO: implement loadGltf
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
Future setMaterialPropertyFloat(ThermionEntity entity, String propertyName, int materialIndex, double value) {
|
||||
// TODO: implement setMaterialPropertyFloat
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
Future setMaterialPropertyFloat4(ThermionEntity entity, String propertyName, int materialIndex, double f1, double f2, double f3, double f4) {
|
||||
// TODO: implement setMaterialPropertyFloat4
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
Future createGeometry(Geometry geometry, {bool keepData=false}) {
|
||||
// TODO: implement createGeometry
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
// TODO: implement sceneUpdated
|
||||
Stream<SceneUpdateEvent> get sceneUpdated => throw UnimplementedError();
|
||||
|
||||
@override
|
||||
Future<ThermionEntity> addDirectLight(DirectLight light) {
|
||||
// TODO: implement addDirectLight
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -3,7 +3,7 @@ library thermion_flutter_js;
|
||||
|
||||
import 'dart:js_interop';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:thermion_dart/thermion_dart/compatibility/web/interop/thermion_viewer_js_shim.dart';
|
||||
import 'package:thermion_dart/thermion_dart/viewer/web/thermion_viewer_js_shim.dart';
|
||||
|
||||
import 'package:vector_math/vector_math_64.dart' as v64;
|
||||
import 'package:animation_tools_dart/animation_tools_dart.dart';
|
||||
@@ -166,9 +166,9 @@ class ThermionViewerJSDartBridge {
|
||||
|
||||
@JSExport()
|
||||
JSPromise<JSNumber> loadGltf(String path, String relativeResourcePath,
|
||||
{bool force = false}) {
|
||||
{bool keepData = false}) {
|
||||
return viewer
|
||||
.loadGltf(path, relativeResourcePath, force: force)
|
||||
.loadGltf(path, relativeResourcePath, keepData: keepData)
|
||||
.then((entity) => entity.toJS)
|
||||
.toJS;
|
||||
}
|
||||
@@ -9,7 +9,6 @@ import 'package:thermion_dart/thermion_dart/entities/abstract_gizmo.dart';
|
||||
import 'package:thermion_dart/thermion_dart/scene.dart';
|
||||
import 'package:thermion_dart/thermion_dart/thermion_viewer.dart';
|
||||
|
||||
import 'package:thermion_dart/thermion_dart/scene_impl.dart';
|
||||
import 'package:vector_math/vector_math_64.dart';
|
||||
import 'thermion_viewer_js_shim.dart';
|
||||
|
||||
@@ -911,4 +910,98 @@ class ThermionViewerJS implements ThermionViewer {
|
||||
// TODO: implement setLayerEnabled
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
// TODO: implement entitiesAdded
|
||||
Stream<ThermionEntity> get entitiesAdded => throw UnimplementedError();
|
||||
|
||||
@override
|
||||
// TODO: implement entitiesRemoved
|
||||
Stream<ThermionEntity> get entitiesRemoved => throw UnimplementedError();
|
||||
|
||||
@override
|
||||
Future<ThermionEntity?> getAncestor(ThermionEntity entity) {
|
||||
// TODO: implement getAncestor
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<double> getCameraNear() {
|
||||
// TODO: implement getCameraNear
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Aabb2> getViewportBoundingBox(ThermionEntity entity) {
|
||||
// TODO: implement getViewportBoundingBox
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
// TODO: implement lightsAdded
|
||||
Stream<ThermionEntity> get lightsAdded => throw UnimplementedError();
|
||||
|
||||
@override
|
||||
// TODO: implement lightsRemoved
|
||||
Stream<ThermionEntity> get lightsRemoved => throw UnimplementedError();
|
||||
|
||||
@override
|
||||
Future<ThermionEntity> loadGlbFromBuffer(Uint8List data, {int numInstances = 1, bool keepData = false}) {
|
||||
// TODO: implement loadGlbFromBuffer
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
Future queuePositionUpdateFromViewportCoords(ThermionEntity entity, double x, double y) {
|
||||
// TODO: implement queuePositionUpdateFromViewportCoords
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
Future removeStencilHighlight(ThermionEntity entity) {
|
||||
// TODO: implement removeStencilHighlight
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
Future setCameraLensProjection(double near, double far, double aspect, double focalLength) {
|
||||
// TODO: implement setCameraLensProjection
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
Future setCameraModelMatrix4(Matrix4 matrix) {
|
||||
// TODO: implement setCameraModelMatrix4
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
Future setLightDirection(ThermionEntity lightEntity, Vector3 direction) {
|
||||
// TODO: implement setLightDirection
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
Future setLightPosition(ThermionEntity lightEntity, double x, double y, double z) {
|
||||
// TODO: implement setLightPosition
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
Future setMaterialPropertyFloat(ThermionEntity entity, String propertyName, int materialIndex, double value) {
|
||||
// TODO: implement setMaterialPropertyFloat
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
Future setMaterialPropertyFloat4(ThermionEntity entity, String propertyName, int materialIndex, double f1, double f2, double f3, double f4) {
|
||||
// TODO: implement setMaterialPropertyFloat4
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
Future setStencilHighlight(ThermionEntity entity, {double r = 1.0, double g = 0.0, double b = 0.0}) {
|
||||
// TODO: implement setStencilHighlight
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
@@ -185,7 +185,6 @@ class ThermionViewerWasm implements ThermionViewer {
|
||||
_width = (width * pixelRatio).ceil();
|
||||
_height = (height * pixelRatio).ceil();
|
||||
viewportDimensions = (_width.toDouble(), _height.toDouble());
|
||||
print("Update viewport camera projection : $_width $height");
|
||||
_module!.ccall(
|
||||
"update_viewport_and_camera_projection",
|
||||
"void",
|
||||
@@ -718,7 +717,7 @@ class ThermionViewerWasm implements ThermionViewer {
|
||||
}
|
||||
|
||||
@override
|
||||
Future<ThermionEntity> loadGlb(String path, {int numInstances = 1}) async {
|
||||
Future<ThermionEntity> loadGlb(String path, {int numInstances = 1, bool keepData = false}) async {
|
||||
final promise = _module!.ccall(
|
||||
"load_glb",
|
||||
"int",
|
||||
@@ -733,8 +732,7 @@ class ThermionViewerWasm implements ThermionViewer {
|
||||
}
|
||||
|
||||
@override
|
||||
Future<ThermionEntity> loadGltf(String path, String relativeResourcePath,
|
||||
{bool force = false}) async {
|
||||
Future<ThermionEntity> loadGltf(String path, String relativeResourcePath, { bool keepData = false}) async {
|
||||
final promise = _module!.ccall(
|
||||
"load_gltf",
|
||||
"int",
|
||||
@@ -2280,4 +2278,80 @@ class ThermionViewerWasm implements ThermionViewer {
|
||||
.toJS,
|
||||
null);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<ThermionEntity?> getAncestor(ThermionEntity entity) {
|
||||
// TODO: implement getAncestor
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
Future queuePositionUpdateFromViewportCoords(ThermionEntity entity, double x, double y) {
|
||||
// TODO: implement queuePositionUpdateFromViewportCoords
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
Future removeStencilHighlight(ThermionEntity entity) {
|
||||
// TODO: implement removeStencilHighlight
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
Future setStencilHighlight(ThermionEntity entity, {double r = 1.0, double g = 0.0, double b = 0.0}) {
|
||||
// TODO: implement setStencilHighlight
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
// TODO: implement entitiesAdded
|
||||
Stream<ThermionEntity> get entitiesAdded => throw UnimplementedError();
|
||||
|
||||
@override
|
||||
// TODO: implement entitiesRemoved
|
||||
Stream<ThermionEntity> get entitiesRemoved => throw UnimplementedError();
|
||||
|
||||
@override
|
||||
Future<double> getCameraNear() {
|
||||
// TODO: implement getCameraNear
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Aabb2> getViewportBoundingBox(ThermionEntity entity) {
|
||||
// TODO: implement getViewportBoundingBox
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
// TODO: implement lightsAdded
|
||||
Stream<ThermionEntity> get lightsAdded => throw UnimplementedError();
|
||||
|
||||
@override
|
||||
// TODO: implement lightsRemoved
|
||||
Stream<ThermionEntity> get lightsRemoved => throw UnimplementedError();
|
||||
|
||||
@override
|
||||
Future setCameraLensProjection(double near, double far, double aspect, double focalLength) {
|
||||
// TODO: implement setCameraLensProjection
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
Future setCameraModelMatrix4(Matrix4 matrix) {
|
||||
// TODO: implement setCameraModelMatrix4
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
Future setMaterialPropertyFloat(ThermionEntity entity, String propertyName, int materialIndex, double value) {
|
||||
// TODO: implement setMaterialPropertyFloat
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
Future setMaterialPropertyFloat4(ThermionEntity entity, String propertyName, int materialIndex, double f1, double f2, double f3, double f4) {
|
||||
// TODO: implement setMaterialPropertyFloat4
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user