chore: rearrange library/export structure

This commit is contained in:
Nick Fisher
2024-09-26 16:35:33 +08:00
parent f023810859
commit 3dffaddfe8
74 changed files with 119 additions and 810 deletions

View File

@@ -0,0 +1,21 @@
import 'package:vector_math/vector_math_64.dart';
import '../../thermion_dart.dart';
abstract class AbstractGizmo {
bool get isVisible;
bool get isHovered;
Future translate(double transX, double transY);
void reset();
Future attach(ThermionEntity entity);
Future detach();
Stream<Aabb2> get boundingBox;
void checkHover(double x, double y);
}

View File

@@ -0,0 +1,118 @@
import 'dart:async';
import 'package:thermion_dart/src/entities/abstract_gizmo.dart';
import 'package:vector_math/vector_math_64.dart';
import '../viewer/viewer.dart';
class Gizmo extends AbstractGizmo {
final ThermionEntity x;
final ThermionEntity y;
final ThermionEntity z;
final ThermionEntity center;
final ThermionViewer _viewer;
ThermionEntity? _activeAxis;
ThermionEntity? _activeEntity;
bool _visible = false;
bool get isVisible => _visible;
bool _isHovered = false;
bool get isHovered => _isHovered;
final Set<ThermionEntity> ignore;
Stream<Aabb2> get boundingBox => _boundingBoxController.stream;
final _boundingBoxController = StreamController<Aabb2>.broadcast();
Gizmo(this.x, this.y, this.z, this.center, this._viewer,
{this.ignore = const <ThermionEntity>{}}) {
_viewer.gizmoPickResult.listen(_onGizmoPickResult);
_viewer.pickResult.listen(_onPickResult);
}
final _stopwatch = Stopwatch();
double _transX = 0.0;
double _transY = 0.0;
Future translate(double transX, double transY) async {
if (!_stopwatch.isRunning) {
_stopwatch.start();
}
_transX += transX;
_transY += transY;
if (_stopwatch.elapsedMilliseconds < 16) {
return;
}
final axis = Vector3(_activeAxis == x ? 1.0 : 0.0,
_activeAxis == y ? 1.0 : 0.0, _activeAxis == z ? 1.0 : 0.0);
await _viewer.queueRelativePositionUpdateWorldAxis(
_activeEntity!,
_transX * _viewer.pixelRatio,
-_transY *
_viewer
.pixelRatio, // flip the sign because "up" in NDC Y axis is positive, but negative in Flutter
axis.x,
axis.y,
axis.z);
_transX = 0;
_transY = 0;
_stopwatch.reset();
}
void reset() {
_activeAxis = null;
}
void _onPickResult(FilamentPickResult result) async {
await attach(result.entity);
}
void _onGizmoPickResult(FilamentPickResult result) async {
if (result.entity == x || result.entity == y || result.entity == z) {
_activeAxis = result.entity;
_isHovered = true;
} else if (result.entity == 0) {
_activeAxis = null;
_isHovered = false;
} else {
throw Exception("Unexpected gizmo pick result");
}
}
Future attach(ThermionEntity entity) async {
_activeAxis = null;
if (entity == _activeEntity) {
return;
}
if (entity == center) {
_activeEntity = null;
return;
}
_visible = true;
if (_activeEntity != null) {
await _viewer.removeStencilHighlight(_activeEntity!);
}
_activeEntity = entity;
await _viewer.setGizmoVisibility(true);
await _viewer.setParent(center, entity, preserveScaling: false);
_boundingBoxController.sink.add(await _viewer.getViewportBoundingBox(x));
}
Future detach() async {
await _viewer.setGizmoVisibility(false);
}
@override
void checkHover(double x, double y) {
_viewer.pickGizmo(x.toInt(), y.toInt());
}
}

View File

@@ -0,0 +1,5 @@
library;
export 'src/input_handler.dart';
export 'src/delegates.dart';
export 'src/delegate_gesture_handler.dart';

View File

@@ -0,0 +1,235 @@
import 'dart:async';
import 'package:logging/logging.dart';
import 'package:thermion_dart/thermion_dart.dart';
import 'package:vector_math/vector_math_64.dart';
import 'implementations/fixed_orbit_camera_rotation_delegate.dart';
import 'implementations/free_flight_camera_delegate.dart';
class DelegateInputHandler implements InputHandler {
final ThermionViewer viewer;
final _logger = Logger("DelegateInputHandler");
InputHandlerDelegate? transformDelegate;
PickDelegate? pickDelegate;
final Set<PhysicalKey> _pressedKeys = {};
final _inputDeltas = <InputType, Vector3>{};
Map<InputType, InputAction> _actions = {
InputType.LMB_HOLD_AND_MOVE: InputAction.TRANSLATE,
InputType.MMB_HOLD_AND_MOVE: InputAction.ROTATE,
InputType.SCROLLWHEEL: InputAction.TRANSLATE,
InputType.POINTER_MOVE: InputAction.NONE,
InputType.KEYDOWN_W: InputAction.TRANSLATE,
InputType.KEYDOWN_S: InputAction.TRANSLATE,
InputType.KEYDOWN_A: InputAction.TRANSLATE,
InputType.KEYDOWN_D: InputAction.TRANSLATE,
};
final _axes = <InputType, Matrix3>{};
void setTransformForAction(InputType inputType, Matrix3 transform) {
_axes[inputType] = transform;
}
DelegateInputHandler({
required this.viewer,
required this.transformDelegate,
this.pickDelegate,
Map<InputType, InputAction>? actions,
}) {
if (actions != null) {
_actions = actions;
}
for (var gestureType in InputType.values) {
_inputDeltas[gestureType] = Vector3.zero();
}
viewer.registerRequestFrameHook(process);
}
factory DelegateInputHandler.fixedOrbit(ThermionViewer viewer,
{double minimumDistance = 10.0,
double? Function(Vector3)? getDistanceToTarget,
ThermionEntity? entity,
PickDelegate? pickDelegate}) =>
DelegateInputHandler(
viewer: viewer,
pickDelegate: pickDelegate,
transformDelegate: FixedOrbitRotateInputHandlerDelegate(viewer,
getDistanceToTarget: getDistanceToTarget,
minimumDistance: minimumDistance),
actions: {
InputType.MMB_HOLD_AND_MOVE: InputAction.ROTATE,
InputType.SCROLLWHEEL: InputAction.TRANSLATE
});
factory DelegateInputHandler.flight(ThermionViewer viewer,
{PickDelegate? pickDelegate, bool freeLook=false}) =>
DelegateInputHandler(
viewer: viewer,
pickDelegate: pickDelegate,
transformDelegate: FreeFlightInputHandlerDelegate(viewer),
actions: {
InputType.MMB_HOLD_AND_MOVE: InputAction.ROTATE,
InputType.SCROLLWHEEL: InputAction.TRANSLATE,
InputType.LMB_HOLD_AND_MOVE: InputAction.TRANSLATE,
InputType.KEYDOWN_A: InputAction.TRANSLATE,
InputType.KEYDOWN_W: InputAction.TRANSLATE,
InputType.KEYDOWN_S: InputAction.TRANSLATE,
InputType.KEYDOWN_D: InputAction.TRANSLATE,
if(freeLook)
InputType.POINTER_MOVE: InputAction.ROTATE,
});
bool _processing = false;
Future<void> process() async {
_processing = true;
for (var gestureType in _inputDeltas.keys) {
var vector = _inputDeltas[gestureType]!;
var action = _actions[gestureType];
if (action == null) {
continue;
}
final transform = _axes[gestureType];
if (transform != null) {
vector = transform * vector;
}
await transformDelegate?.queue(action, vector);
}
for (final key in _pressedKeys) {
InputAction? keyAction;
Vector3? vector;
switch (key) {
case PhysicalKey.W:
keyAction = _actions[InputType.KEYDOWN_W];
vector = Vector3(0, 0, -1);
break;
case PhysicalKey.A:
keyAction = _actions[InputType.KEYDOWN_A];
vector = Vector3(-1, 0, 0);
break;
case PhysicalKey.S:
keyAction = _actions[InputType.KEYDOWN_S];
vector = Vector3(0, 0, 1);
break;
case PhysicalKey.D:
keyAction = _actions[InputType.KEYDOWN_D];
vector = Vector3(1, 0, 0);
break;
}
if (keyAction != null) {
var transform = _axes[keyAction];
if (transform != null) {
vector = transform * vector;
}
transformDelegate?.queue(keyAction, vector!);
}
}
await transformDelegate?.execute();
_inputDeltas.clear();
_processing = false;
}
@override
Future<void> onPointerDown(Vector2 localPosition, bool isMiddle) async {
if (!isMiddle) {
final action = _actions[InputType.LMB_DOWN];
switch (action) {
case InputAction.PICK:
pickDelegate?.pick(localPosition);
default:
// noop
}
}
}
@override
Future<void> onPointerMove(
Vector2 localPosition, Vector2 delta, bool isMiddle) async {
if (_processing) {
return;
}
if (isMiddle) {
_inputDeltas[InputType.MMB_HOLD_AND_MOVE] =
(_inputDeltas[InputType.MMB_HOLD_AND_MOVE] ?? Vector3.zero()) + Vector3(delta.x, delta.y, 0.0);
} else {
_inputDeltas[InputType.LMB_HOLD_AND_MOVE] =
(_inputDeltas[InputType.LMB_HOLD_AND_MOVE] ?? Vector3.zero()) + Vector3(delta.x, delta.y, 0.0);
}
// else {
// _inputDeltas[InputType.POINTER_MOVE] =
// (_inputDeltas[InputType.POINTER_MOVE] ?? Vector3.zero()) + delta;
// }
}
@override
Future<void> onPointerUp(bool isMiddle) async {}
@override
Future<void> onPointerHover(Vector2 localPosition, Vector2 delta) async {
if (_processing) {
return;
}
_inputDeltas[InputType.POINTER_MOVE] =
(_inputDeltas[InputType.POINTER_MOVE] ?? Vector3.zero()) + Vector3(delta.x, delta.y, 0.0);
}
@override
Future<void> onPointerScroll(
Vector2 localPosition, double scrollDelta) async {
if (_processing) {
return;
}
try {
_inputDeltas[InputType.SCROLLWHEEL] =
(_inputDeltas[InputType.SCROLLWHEEL] ?? Vector3.zero())
+ Vector3(0,0, scrollDelta > 0 ? 1 : -1);
} catch (e) {
_logger.warning("Error during scroll accumulation: $e");
}
}
@override
Future dispose() async {
viewer.unregisterRequestFrameHook(process);
}
@override
Future<bool> get initialized => viewer.initialized;
@override
Future<void> onScaleEnd() async {}
@override
Future<void> onScaleStart() async {}
@override
Future<void> onScaleUpdate() async {}
@override
void setActionForType(InputType gestureType, InputAction gestureAction) {
_actions[gestureType] = gestureAction;
}
@override
InputAction? getActionForType(InputType gestureType) {
return _actions[gestureType];
}
void keyDown(PhysicalKey key) {
_pressedKeys.add(key);
}
void keyUp(PhysicalKey key) {
_pressedKeys.remove(key);
}
}

View File

@@ -0,0 +1,27 @@
import 'package:vector_math/vector_math_64.dart';
import 'input_handler.dart';
abstract class InputHandlerDelegate {
Future queue(InputAction action, Vector3? delta);
Future execute();
}
abstract class VelocityDelegate {
Vector2? get velocity;
void updateVelocity(Vector2 delta);
void startDeceleration();
void stopDeceleration();
void dispose() {
stopDeceleration();
}
}
abstract class PickDelegate {
const PickDelegate();
void pick(Vector2 location);
}

View File

@@ -0,0 +1,87 @@
// import 'dart:async';
// import 'dart:ui';
// import 'package:flutter/services.dart';
// import 'package:thermion_dart/thermion_dart/thermion_viewer.dart';
// import 'package:thermion_dart/thermion_dart/input/delegates.dart';
// import 'package:vector_math/vector_math_64.dart';
// class DefaultKeyboardCameraFlightDelegate
// {
// final ThermionViewer viewer;
// static const double _panSensitivity = 0.005;
// static const double _keyMoveSensitivity = 0.1;
// final Map<PhysicalKeyboardKey, bool> _pressedKeys = {};
// Timer? _moveTimer;
// DefaultKeyboardCameraFlightDelegate(this.viewer) {
// _startMoveLoop();
// }
// @override
// Future<void> panCamera(Offset delta, Vector2? velocity) async {
// double deltaX = delta.dx;
// double deltaY = delta.dy;
// deltaX *= _panSensitivity * viewer.pixelRatio;
// deltaY *= _panSensitivity * viewer.pixelRatio;
// await _moveCamera(deltaX, deltaY, 0);
// }
// @override
// Future<void> onKeypress(PhysicalKeyboardKey key) async {
// _pressedKeys[key] = true;
// }
// // New method to handle key release
// Future<void> onKeyRelease(PhysicalKeyboardKey key) async {
// _pressedKeys.remove(key);
// }
// void _startMoveLoop() {
// _moveTimer = Timer.periodic(
// Duration(milliseconds: 16), (_) => _processKeyboardInput());
// }
// Future<void> _processKeyboardInput() async {
// double dx = 0, dy = 0, dz = 0;
// if (_pressedKeys[PhysicalKeyboardKey.keyW] == true)
// dz += _keyMoveSensitivity;
// if (_pressedKeys[PhysicalKeyboardKey.keyS] == true)
// dz -= _keyMoveSensitivity;
// if (_pressedKeys[PhysicalKeyboardKey.keyA] == true)
// dx -= _keyMoveSensitivity;
// if (_pressedKeys[PhysicalKeyboardKey.keyD] == true)
// dx += _keyMoveSensitivity;
// if (dx != 0 || dy != 0 || dz != 0) {
// await _moveCamera(dx, dy, dz);
// }
// // Removed _pressedKeys.clear(); from here
// }
// Future<void> _moveCamera(double dx, double dy, double dz) async {
// Matrix4 currentModelMatrix = await viewer.getCameraModelMatrix();
// Vector3 currentPosition = currentModelMatrix.getTranslation();
// Quaternion currentRotation =
// Quaternion.fromRotation(currentModelMatrix.getRotation());
// Vector3 forward = Vector3(0, 0, -1)..applyQuaternion(currentRotation);
// Vector3 right = Vector3(1, 0, 0)..applyQuaternion(currentRotation);
// Vector3 up = Vector3(0, 1, 0)..applyQuaternion(currentRotation);
// Vector3 moveOffset = right * dx + up * dy + forward * dz;
// Vector3 newPosition = currentPosition + moveOffset;
// Matrix4 newModelMatrix =
// Matrix4.compose(newPosition, currentRotation, Vector3(1, 1, 1));
// await viewer.setCameraModelMatrix4(newModelMatrix);
// }
// void dispose() {
// _moveTimer?.cancel();
// }
// }

View File

@@ -0,0 +1,35 @@
// import 'dart:ui';
// import 'package:thermion_dart/thermion_dart/thermion_viewer.dart';
// import 'package:thermion_flutter/thermion/widgets/camera/gestures/v2/delegates.dart';
// import 'package:vector_math/vector_math_64.dart';
// class DefaultPanInputHandlerDelegate implements PanInputHandlerDelegate {
// final ThermionViewer viewer;
// static const double _panSensitivity = 0.005;
// DefaultPanInputHandlerDelegate(this.viewer);
// static const double _panSensitivity = 0.005;
// @override
// Future<void> panCamera(Offset delta, Vector2? velocity) async {
// double deltaX = delta.dx;
// double deltaY = delta.dy;
// deltaX *= _panSensitivity * viewer.pixelRatio;
// deltaY *= _panSensitivity * viewer.pixelRatio;
// Matrix4 currentModelMatrix = await viewer.getCameraModelMatrix();
// Vector3 currentPosition = currentModelMatrix.getTranslation();
// Quaternion currentRotation = Quaternion.fromRotation(currentModelMatrix.getRotation());
// Vector3 right = Vector3(1, 0, 0)..applyQuaternion(currentRotation);
// Vector3 up = Vector3(0, 1, 0)..applyQuaternion(currentRotation);
// Vector3 panOffset = right * -deltaX + up * deltaY;
// Vector3 newPosition = currentPosition + panOffset;
// Matrix4 newModelMatrix = Matrix4.compose(newPosition, currentRotation, Vector3(1, 1, 1));
// await viewer.setCameraModelMatrix4(newModelMatrix);
// }
// }

View File

@@ -0,0 +1,131 @@
import 'dart:async';
import 'package:vector_math/vector_math_64.dart';
import '../../../viewer/src/shared_types/camera.dart';
import '../../../viewer/viewer.dart';
import '../../input.dart';
import '../input_handler.dart';
class FixedOrbitRotateInputHandlerDelegate implements InputHandlerDelegate {
final ThermionViewer viewer;
late Future<Camera> _camera;
final double minimumDistance;
double? Function(Vector3)? getDistanceToTarget;
Vector2 _queuedRotationDelta = Vector2.zero();
double _queuedZoomDelta = 0.0;
static final _up = Vector3(0, 1, 0);
Timer? _updateTimer;
FixedOrbitRotateInputHandlerDelegate(
this.viewer, {
this.getDistanceToTarget,
this.minimumDistance = 10.0,
}) {
_camera = viewer.getMainCamera();
}
void dispose() {
_updateTimer?.cancel();
}
@override
Future<void> queue(InputAction action, Vector3? delta) async {
if (delta == null) return;
switch (action) {
case InputAction.ROTATE:
_queuedRotationDelta += Vector2(delta.x, delta.y);
break;
case InputAction.TRANSLATE:
_queuedZoomDelta += delta!.z;
break;
case InputAction.PICK:
// Assuming PICK is used for zoom in this context
_queuedZoomDelta += delta.z;
break;
case InputAction.NONE:
// Do nothing
break;
}
}
@override
Future<void> execute() async {
if (_queuedRotationDelta.length2 == 0.0 && _queuedZoomDelta == 0.0) {
return;
}
var viewMatrix = await viewer.getCameraViewMatrix();
var modelMatrix = await viewer.getCameraModelMatrix();
var projectionMatrix = await viewer.getCameraProjectionMatrix();
var inverseProjectionMatrix = projectionMatrix.clone()..invert();
Vector3 currentPosition = modelMatrix.getTranslation();
Vector3 forward = -currentPosition.normalized();
Vector3 right = _up.cross(forward).normalized();
Vector3 up = forward.cross(right);
// Calculate intersection point and depth
double radius = getDistanceToTarget?.call(currentPosition) ?? 1.0;
if (radius != 1.0) {
radius = currentPosition.length - radius;
}
Vector3 intersection = (-forward).scaled(radius);
final intersectionInViewSpace = viewMatrix *
Vector4(intersection.x, intersection.y, intersection.z, 1.0);
final intersectionInClipSpace = projectionMatrix * intersectionInViewSpace;
final intersectionInNdcSpace =
intersectionInClipSpace / intersectionInClipSpace.w;
// Calculate new camera position based on rotation
final ndcX = 2 *
((-_queuedRotationDelta.x * viewer.pixelRatio) /
viewer.viewportDimensions.$1);
final ndcY = 2 *
((_queuedRotationDelta.y * viewer.pixelRatio) /
viewer.viewportDimensions.$2);
final ndc = Vector4(ndcX, ndcY, intersectionInNdcSpace.z, 1.0);
var clipSpace = Vector4(
ndc.x * intersectionInClipSpace.w,
ndcY * intersectionInClipSpace.w,
ndc.z * intersectionInClipSpace.w,
intersectionInClipSpace.w);
Vector4 cameraSpace = inverseProjectionMatrix * clipSpace;
Vector4 worldSpace = modelMatrix * cameraSpace;
var worldSpace3 = worldSpace.xyz.normalized() * currentPosition.length;
currentPosition = worldSpace3;
// Apply zoom
if (_queuedZoomDelta != 0.0) {
Vector3 toSurface = currentPosition - intersection;
currentPosition =
currentPosition + toSurface.scaled(_queuedZoomDelta * 0.1);
}
// Ensure minimum distance
if (currentPosition.length < radius + minimumDistance) {
currentPosition =
(currentPosition.normalized() * (radius + minimumDistance));
}
// Calculate view matrix
forward = -currentPosition.normalized();
right = _up.cross(forward).normalized();
up = forward.cross(right);
Matrix4 newViewMatrix = makeViewMatrix(currentPosition, Vector3.zero(), up);
newViewMatrix.invert();
// Set the camera model matrix
var camera = await _camera;
await camera.setModelMatrix(newViewMatrix);
// Reset queued deltas
_queuedRotationDelta = Vector2.zero();
_queuedZoomDelta = 0.0;
}
}

View File

@@ -0,0 +1,156 @@
import 'dart:async';
import 'package:vector_math/vector_math_64.dart';
import '../../../viewer/viewer.dart';
import '../delegates.dart';
import '../input_handler.dart';
class FreeFlightInputHandlerDelegate implements InputHandlerDelegate {
final ThermionViewer viewer;
final Vector3? minBounds;
final Vector3? maxBounds;
final double rotationSensitivity;
final double movementSensitivity;
final double zoomSensitivity;
final double panSensitivity;
static final _up = Vector3(0, 1, 0);
static final _forward = Vector3(0, 0, -1);
static final Vector3 _right = Vector3(1, 0, 0);
Vector2 _queuedRotationDelta = Vector2.zero();
Vector2 _queuedPanDelta = Vector2.zero();
double _queuedZoomDelta = 0.0;
Vector3 _queuedMoveDelta = Vector3.zero();
FreeFlightInputHandlerDelegate(
this.viewer, {
this.minBounds,
this.maxBounds,
this.rotationSensitivity = 0.001,
this.movementSensitivity = 0.1,
this.zoomSensitivity = 0.1,
this.panSensitivity = 0.1,
});
@override
Future<void> queue(InputAction action, Vector3? delta) async {
if (delta == null) return;
switch (action) {
case InputAction.ROTATE:
_queuedRotationDelta += Vector2(delta.x, delta.y);
break;
case InputAction.TRANSLATE:
_queuedPanDelta += Vector2(delta.x, delta.y);
_queuedZoomDelta += delta.z;
break;
case InputAction.PICK:
// Assuming PICK is used for zoom in this context
_queuedZoomDelta += delta.z;
break;
case InputAction.NONE:
// Do nothing
break;
}
}
bool _executing = false;
@override
Future<void> execute() async {
if (_executing) {
return;
}
_executing = true;
if (_queuedRotationDelta.length2 == 0.0 &&
_queuedPanDelta.length2 == 0.0 &&
_queuedZoomDelta == 0.0 &&
_queuedMoveDelta.length2 == 0.0) {
_executing = false;
return;
}
Matrix4 currentModelMatrix = await viewer.getCameraModelMatrix();
Vector3 currentPosition = currentModelMatrix.getTranslation();
Quaternion currentRotation =
Quaternion.fromRotation(currentModelMatrix.getRotation());
// Apply rotation
if (_queuedRotationDelta.length2 > 0.0) {
double deltaX =
_queuedRotationDelta.x * rotationSensitivity * viewer.pixelRatio;
double deltaY =
_queuedRotationDelta.y * rotationSensitivity * viewer.pixelRatio;
Quaternion yawRotation = Quaternion.axisAngle(_up, -deltaX);
Quaternion pitchRotation = Quaternion.axisAngle(_right, -deltaY);
currentRotation = currentRotation * pitchRotation * yawRotation;
currentRotation.normalize();
_queuedRotationDelta = Vector2.zero();
}
// Apply pan
if (_queuedPanDelta.length2 > 0.0) {
Vector3 right = _right.clone()..applyQuaternion(currentRotation);
Vector3 up = _up.clone()..applyQuaternion(currentRotation);
double deltaX = _queuedPanDelta.x * panSensitivity * viewer.pixelRatio;
double deltaY = _queuedPanDelta.y * panSensitivity * viewer.pixelRatio;
Vector3 panOffset = right * deltaX + up * deltaY;
currentPosition += panOffset;
_queuedPanDelta = Vector2.zero();
}
// Apply zoom
if (_queuedZoomDelta != 0.0) {
Vector3 forward = _forward.clone()..applyQuaternion(currentRotation);
currentPosition += forward * -_queuedZoomDelta * zoomSensitivity;
_queuedZoomDelta = 0.0;
}
// Apply queued movement
if (_queuedMoveDelta.length2 > 0.0) {
Vector3 forward = _forward.clone()..applyQuaternion(currentRotation);
Vector3 right = _right.clone()..applyQuaternion(currentRotation);
Vector3 up = _up.clone()..applyQuaternion(currentRotation);
Vector3 moveOffset = right * _queuedMoveDelta.x +
up * _queuedMoveDelta.y +
forward * _queuedMoveDelta.z;
currentPosition += moveOffset;
_queuedMoveDelta = Vector3.zero();
}
// Constrain position
currentPosition = _constrainPosition(currentPosition);
// Update camera
Matrix4 newModelMatrix =
Matrix4.compose(currentPosition, currentRotation, Vector3(1, 1, 1));
await viewer.setCameraModelMatrix4(newModelMatrix);
_executing = false;
}
Vector3 _constrainPosition(Vector3 position) {
if (minBounds != null) {
position.x = position.x.clamp(minBounds!.x, double.infinity);
position.y = position.y.clamp(minBounds!.y, double.infinity);
position.z = position.z.clamp(minBounds!.z, double.infinity);
}
if (maxBounds != null) {
position.x = position.x.clamp(double.negativeInfinity, maxBounds!.x);
position.y = position.y.clamp(double.negativeInfinity, maxBounds!.y);
position.z = position.z.clamp(double.negativeInfinity, maxBounds!.z);
}
return position;
}
}

View File

@@ -0,0 +1,230 @@
// import 'dart:async';
// import 'package:flutter/gestures.dart';
// import 'package:flutter/services.dart';
// import 'package:logging/logging.dart';
// import 'package:thermion_dart/thermion_dart/entities/abstract_gizmo.dart';
// import 'package:thermion_dart/thermion_dart/thermion_viewer.dart';
// import 'dart:ui';
// import 'package:thermion_dart/thermion_dart/input/input_handler.dart';
// // Renamed implementation
// class PickingCameraGestureHandler implements InputHandler {
// final ThermionViewer viewer;
// final bool enableCamera;
// final bool enablePicking;
// final Logger _logger = Logger("PickingCameraGestureHandler");
// AbstractGizmo? _gizmo;
// Timer? _scrollTimer;
// ThermionEntity? _highlightedEntity;
// StreamSubscription<FilamentPickResult>? _pickResultSubscription;
// bool _gizmoAttached = false;
// PickingCameraGestureHandler({
// required this.viewer,
// this.enableCamera = true,
// this.enablePicking = true,
// }) {
// try {
// _gizmo = viewer.gizmo;
// } catch (err) {
// _logger.warning(
// "Failed to get gizmo. If you are running on WASM, this is expected");
// }
// _pickResultSubscription = viewer.pickResult.listen(_onPickResult);
// // Add keyboard listener
// RawKeyboard.instance.addListener(_handleKeyEvent);
// }
// @override
// ThermionGestureState get currentState => _currentState;
// void _handleKeyEvent(RawKeyEvent event) {
// if (event is RawKeyDownEvent &&
// event.logicalKey == LogicalKeyboardKey.escape) {
// _resetToNullState();
// }
// }
// void _resetToNullState() async {
// _currentState = ThermionGestureState.NULL;
// if (_highlightedEntity != null) {
// await viewer.removeStencilHighlight(_highlightedEntity!);
// _highlightedEntity = null;
// }
// }
// void _onPickResult(FilamentPickResult result) async {
// var targetEntity = await viewer.getAncestor(result.entity) ?? result.entity;
// if (_highlightedEntity != targetEntity) {
// if (_highlightedEntity != null) {
// await viewer.removeStencilHighlight(_highlightedEntity!);
// }
// _highlightedEntity = targetEntity;
// if (_highlightedEntity != null) {
// await viewer.setStencilHighlight(_highlightedEntity!);
// _gizmo?.attach(_highlightedEntity!);
// }
// }
// }
// @override
// Future<void> onPointerHover(Offset localPosition, Offset delta) async {
// if (_gizmoAttached) {
// _gizmo?.checkHover(localPosition.dx, localPosition.dy);
// }
// if (_highlightedEntity != null) {
// await viewer.queuePositionUpdateFromViewportCoords(
// _highlightedEntity!,
// localPosition.dx,
// localPosition.dy,
// );
// }
// }
// @override
// Future<void> onPointerScroll(Offset localPosition, double scrollDelta) async {
// if (!enableCamera) {
// return;
// }
// if (_currentState == ThermionGestureState.NULL ||
// _currentState == ThermionGestureState.ZOOMING) {
// await _zoom(localPosition, scrollDelta);
// }
// }
// @override
// Future<void> onPointerDown(Offset localPosition, int buttons) async {
// if (_highlightedEntity != null) {
// _resetToNullState();
// return;
// }
// if (enablePicking && buttons != kMiddleMouseButton) {
// viewer.pick(localPosition.dx.toInt(), localPosition.dy.toInt());
// }
// if (buttons == kMiddleMouseButton && enableCamera) {
// await viewer.rotateStart(localPosition.dx, localPosition.dy);
// _currentState = ThermionGestureState.ROTATING;
// } else if (buttons == kPrimaryMouseButton && enableCamera) {
// await viewer.panStart(localPosition.dx, localPosition.dy);
// _currentState = ThermionGestureState.PANNING;
// }
// }
// @override
// Future<void> onPointerMove(
// Offset localPosition, Offset delta, int buttons) async {
// if (_highlightedEntity != null) {
// await _handleEntityHighlightedMove(localPosition);
// return;
// }
// switch (_currentState) {
// case ThermionGestureState.NULL:
// break;
// case ThermionGestureState.ROTATING:
// if (enableCamera) {
// await viewer.rotateUpdate(localPosition.dx, localPosition.dy);
// }
// break;
// case ThermionGestureState.PANNING:
// if (enableCamera) {
// await viewer.panUpdate(localPosition.dx, localPosition.dy);
// }
// break;
// case ThermionGestureState.ZOOMING:
// // ignore
// break;
// }
// }
// @override
// Future<void> onPointerUp(int buttons) async {
// switch (_currentState) {
// case ThermionGestureState.ROTATING:
// await viewer.rotateEnd();
// _currentState = ThermionGestureState.NULL;
// break;
// case ThermionGestureState.PANNING:
// await viewer.panEnd();
// _currentState = ThermionGestureState.NULL;
// break;
// default:
// break;
// }
// }
// Future<void> _handleEntityHighlightedMove(Offset localPosition) async {
// if (_highlightedEntity != null) {
// await viewer.queuePositionUpdateFromViewportCoords(
// _highlightedEntity!,
// localPosition.dx,
// localPosition.dy,
// );
// }
// }
// Future<void> _zoom(Offset localPosition, double scrollDelta) async {
// _scrollTimer?.cancel();
// _currentState = ThermionGestureState.ZOOMING;
// await viewer.zoomBegin();
// await viewer.zoomUpdate(
// localPosition.dx, localPosition.dy, scrollDelta > 0 ? 1 : -1);
// _scrollTimer = Timer(const Duration(milliseconds: 100), () async {
// await viewer.zoomEnd();
// _currentState = ThermionGestureState.NULL;
// });
// }
// @override
// void dispose() {
// _pickResultSubscription?.cancel();
// if (_highlightedEntity != null) {
// viewer.removeStencilHighlight(_highlightedEntity!);
// }
// RawKeyboard.instance.removeListener(_handleKeyEvent);
// }
// @override
// Future<bool> get initialized => viewer.initialized;
// @override
// InputAction getActionForType(InputType type) {
// // TODO: implement getActionForType
// throw UnimplementedError();
// }
// @override
// Future<void> onScaleEnd() {
// // TODO: implement onScaleEnd
// throw UnimplementedError();
// }
// @override
// Future<void> onScaleStart() {
// // TODO: implement onScaleStart
// throw UnimplementedError();
// }
// @override
// Future<void> onScaleUpdate() {
// // TODO: implement onScaleUpdate
// throw UnimplementedError();
// }
// @override
// void setActionForType(InputType type, InputAction action) {
// // TODO: implement setActionForType
// }
// }

View File

@@ -0,0 +1,47 @@
import 'dart:async';
import 'package:vector_math/vector_math_64.dart';
enum InputType {
LMB_DOWN,
LMB_HOLD_AND_MOVE,
LMB_UP,
LMB_HOVER,
MMB_DOWN,
MMB_HOLD_AND_MOVE,
MMB_UP,
MMB_HOVER,
SCALE1,
SCALE2,
SCROLLWHEEL,
POINTER_MOVE,
KEYDOWN_W,
KEYDOWN_A,
KEYDOWN_S,
KEYDOWN_D,
}
enum PhysicalKey { W, A, S, D }
enum InputAction { TRANSLATE, ROTATE, PICK, NONE }
abstract class InputHandler {
Future<void> onPointerHover(Vector2 localPosition, Vector2 delta);
Future<void> onPointerScroll(Vector2 localPosition, double scrollDelta);
Future<void> onPointerDown(Vector2 localPosition, bool isMiddle);
Future<void> onPointerMove(Vector2 localPosition, Vector2 delta, bool isMiddle);
Future<void> onPointerUp(bool isMiddle);
Future<void> onScaleStart();
Future<void> onScaleUpdate();
Future<void> onScaleEnd();
Future<bool> get initialized;
Future dispose();
void setActionForType(InputType gestureType, InputAction gestureAction);
InputAction? getActionForType(InputType gestureType);
void keyDown(PhysicalKey key);
void keyUp(PhysicalKey key);
}

View File

@@ -0,0 +1,234 @@
import 'dart:convert';
import 'package:vector_math/vector_math_64.dart';
import '../thermion_dart.dart';
class SceneV2 {
final Map<String, AssetInfo> assets;
final List<LightInfo> lights;
List<CameraInfo> cameras;
final List<EntityInfo> entities;
EnvironmentInfo? environment;
SceneV2({
Map<String, AssetInfo>? assets,
List<LightInfo>? lights,
List<CameraInfo>? cameras,
List<EntityInfo>? entities,
this.environment,
}) : assets = assets ?? {},
lights = lights ?? [],
cameras = cameras ?? [],
entities = entities ?? [];
void addAsset(String uri, AssetType type) {
assets[uri] = AssetInfo(uri: uri, type: type);
}
void addLight(LightInfo light) {
lights.add(light);
}
void clearAssets() {
assets.clear();
}
void clearLights() {
lights.clear();
}
void setCamera(Matrix4 modelMatrix, Matrix4 projectionMatrix) {
var camera = cameras.firstWhere((cam) => cam.isActive);
camera.modelMatrix = modelMatrix;
camera.projectionMatrix = projectionMatrix;
}
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 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'],
);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,15 @@
import 'package:vector_math/vector_math_64.dart' as v;
class CameraOrientation {
v.Vector3 position = v.Vector3(0, 0, 0);
var rotationX = 0.0;
var rotationY = 0.0;
var rotationZ = 0.0;
v.Quaternion compose() {
return v.Quaternion.axisAngle(v.Vector3(0, 0, 1), rotationZ) *
v.Quaternion.axisAngle(v.Vector3(0, 1, 0), rotationY) *
v.Quaternion.axisAngle(v.Vector3(1, 0, 0), rotationX);
}
}

View File

@@ -0,0 +1,30 @@
import 'dart:ffi';
import 'dart:io';
import 'package:ffi/ffi.dart';
import '../viewer/src/ffi/src/thermion_dart.g.dart';
class DartResourceLoader {
static final _assets = <int, Pointer>{};
static void loadResource(Pointer<Char> uri, Pointer<ResourceBuffer> out) {
try {
var data = File(uri.cast<Utf8>().toDartString().replaceAll("file://", ""))
.readAsBytesSync();
var ptr = calloc<Uint8>(data.lengthInBytes);
ptr.asTypedList(data.lengthInBytes).setRange(0, data.lengthInBytes, data);
out.ref.data = ptr.cast<Void>();
out.ref.size = data.lengthInBytes;
out.ref.id = _assets.length;
_assets[out.ref.id] = ptr;
} catch (err) {
print(err);
out.ref.size = -1;
}
}
static void freeResource(ResourceBuffer rb) {
calloc.free(_assets[rb.id]!);
}
}

View File

@@ -0,0 +1,334 @@
import 'dart:math';
import 'dart:typed_data';
import '../../thermion_dart.dart';
class GeometryHelper {
static Geometry sphere({bool normals = true, bool uvs = true}) {
int latitudeBands = 20;
int longitudeBands = 20;
List<double> verticesList = [];
List<double> normalsList = [];
List<double> uvsList = [];
List<int> indices = [];
for (int latNumber = 0; latNumber <= latitudeBands; latNumber++) {
double theta = latNumber * pi / latitudeBands;
double sinTheta = sin(theta);
double cosTheta = cos(theta);
for (int longNumber = 0; longNumber <= longitudeBands; longNumber++) {
double phi = longNumber * 2 * pi / longitudeBands;
double sinPhi = sin(phi);
double cosPhi = cos(phi);
double x = cosPhi * sinTheta;
double y = cosTheta;
double z = sinPhi * sinTheta;
verticesList.addAll([x, y, z]);
normalsList.addAll([x, y, z]);
uvsList.addAll([longNumber / longitudeBands, latNumber / latitudeBands]);
}
}
for (int latNumber = 0; latNumber < latitudeBands; latNumber++) {
for (int longNumber = 0; longNumber < longitudeBands; longNumber++) {
int first = (latNumber * (longitudeBands + 1)) + longNumber;
int second = first + longitudeBands + 1;
indices.addAll([first, second, first + 1, second, second + 1, first + 1]);
}
}
Float32List vertices = Float32List.fromList(verticesList);
Float32List? _normals = normals ? Float32List.fromList(normalsList) : null;
Float32List? _uvs = uvs ? Float32List.fromList(uvsList) : null;
return Geometry(vertices, indices, normals: _normals, uvs: _uvs);
}
static Geometry cube({bool normals = true, bool uvs = true}) {
final vertices = Float32List.fromList([
// Front face
-1, -1, 1,
1, -1, 1,
1, 1, 1,
-1, 1, 1,
// Back face
-1, -1, -1,
-1, 1, -1,
1, 1, -1,
1, -1, -1,
// Top face
-1, 1, -1,
-1, 1, 1,
1, 1, 1,
1, 1, -1,
// Bottom face
-1, -1, -1,
1, -1, -1,
1, -1, 1,
-1, -1, 1,
// Right face
1, -1, -1,
1, 1, -1,
1, 1, 1,
1, -1, 1,
// Left face
-1, -1, -1,
-1, -1, 1,
-1, 1, 1,
-1, 1, -1,
]);
final _normals = normals ? Float32List.fromList([
// Front face
0, 0, 1,
0, 0, 1,
0, 0, 1,
0, 0, 1,
// Back face
0, 0, -1,
0, 0, -1,
0, 0, -1,
0, 0, -1,
// Top face
0, 1, 0,
0, 1, 0,
0, 1, 0,
0, 1, 0,
// Bottom face
0, -1, 0,
0, -1, 0,
0, -1, 0,
0, -1, 0,
// Right face
1, 0, 0,
1, 0, 0,
1, 0, 0,
1, 0, 0,
// Left face
-1, 0, 0,
-1, 0, 0,
-1, 0, 0,
-1, 0, 0,
]) : null;
final _uvs = uvs ? Float32List.fromList([
// Front face
1/3, 1/3,
2/3, 1/3,
2/3, 2/3,
1/3, 2/3,
// Back face
2/3, 2/3,
2/3, 1,
1, 1,
1, 2/3,
// Top face
1/3, 0,
1/3, 1/3,
2/3, 1/3,
2/3, 0,
// Bottom face
1/3, 2/3,
2/3, 2/3,
2/3, 1,
1/3, 1,
// Right face
2/3, 1/3,
2/3, 2/3,
1, 2/3,
1, 1/3,
// Left face
0, 1/3,
1/3, 1/3,
1/3, 2/3,
0, 2/3,
]) : null;
final indices = [
// Front face
0, 1, 2, 0, 2, 3,
// Back face
4, 5, 6, 4, 6, 7,
// Top face
8, 9, 10, 8, 10, 11,
// Bottom face
12, 13, 14, 12, 14, 15,
// Right face
16, 17, 18, 16, 18, 19,
// Left face
20, 21, 22, 20, 22, 23
];
return Geometry(vertices, indices, normals: _normals, uvs: _uvs);
}
static Geometry cylinder({double radius = 1.0, double length = 1.0, bool normals = true, bool uvs = true }) {
int segments = 32;
List<double> verticesList = [];
List<double> normalsList = [];
List<double> uvsList = [];
List<int> indices = [];
// Create vertices, normals, and UVs
for (int i = 0; i <= segments; i++) {
double theta = i * 2 * pi / segments;
double x = radius * cos(theta);
double z = radius * sin(theta);
// Top circle
verticesList.addAll([x, length / 2, z]);
normalsList.addAll([x / radius, 0, z / radius]);
uvsList.addAll([i / segments, 1]);
// Bottom circle
verticesList.addAll([x, -length / 2, z]);
normalsList.addAll([x / radius, 0, z / radius]);
uvsList.addAll([i / segments, 0]);
}
// Create indices
for (int i = 0; i < segments; i++) {
int topFirst = i * 2;
int topSecond = (i + 1) * 2;
int bottomFirst = topFirst + 1;
int bottomSecond = topSecond + 1;
// Top face (counter-clockwise)
indices.addAll([segments * 2, topSecond, topFirst]);
// Bottom face (counter-clockwise when viewed from below)
indices.addAll([segments * 2 + 1, bottomFirst, bottomSecond]);
// Side faces (counter-clockwise)
indices.addAll([topFirst, bottomFirst, topSecond]);
indices.addAll([bottomFirst, bottomSecond, topSecond]);
}
// Add center vertices, normals, and UVs for top and bottom faces
verticesList.addAll([0, length / 2, 0]); // Top center
normalsList.addAll([0, 1, 0]);
uvsList.addAll([0.5, 0.5]); // Center of top face
verticesList.addAll([0, -length / 2, 0]); // Bottom center
normalsList.addAll([0, -1, 0]);
uvsList.addAll([0.5, 0.5]); // Center of bottom face
// Add top and bottom face normals and UVs
for (int i = 0; i <= segments; i++) {
normalsList.addAll([0, 1, 0]); // Top face normal
normalsList.addAll([0, -1, 0]); // Bottom face normal
double u = 0.5 + 0.5 * cos(i * 2 * pi / segments);
double v = 0.5 + 0.5 * sin(i * 2 * pi / segments);
uvsList.addAll([u, v]); // Top face UV
uvsList.addAll([u, v]); // Bottom face UV
}
Float32List vertices = Float32List.fromList(verticesList);
Float32List? _normals = normals ? Float32List.fromList(normalsList) : null;
Float32List? _uvs = uvs ? Float32List.fromList(uvsList) : null;
return Geometry(vertices, indices, normals: _normals, uvs: _uvs);
}
static Geometry conic({double radius = 1.0, double length = 1.0, bool normals = true, bool uvs = true}) {
int segments = 32;
List<double> verticesList = [];
List<double> normalsList = [];
List<double> uvsList = [];
List<int> indices = [];
// Create vertices, normals, and UVs
for (int i = 0; i <= segments; i++) {
double theta = i * 2 * pi / segments;
double x = radius * cos(theta);
double z = radius * sin(theta);
// Base circle
verticesList.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);
normalsList.addAll([nx, ny, nz]);
// UV coordinates
uvsList.addAll([i / segments, 0]);
}
// Apex
verticesList.addAll([0, length, 0]);
normalsList.addAll([0, 1, 0]); // Normal at apex points straight up
uvsList.addAll([0.5, 1]); // UV for apex
// Create indices
for (int i = 0; i < segments; i++) {
// 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 and UVs
for (int i = 0; i <= segments; i++) {
normalsList.addAll([0, -1, 0]); // Base face normal
double u = 0.5 + 0.5 * cos(i * 2 * pi / segments);
double v = 0.5 + 0.5 * sin(i * 2 * pi / segments);
uvsList.addAll([u, v]); // Base face UV
}
Float32List vertices = Float32List.fromList(verticesList);
Float32List? _normals = normals ? Float32List.fromList(normalsList) : null;
Float32List? _uvs = uvs ? Float32List.fromList(uvsList) : null;
return Geometry(vertices, indices, normals: _normals, uvs: _uvs);
}
static Geometry plane({double width = 1.0, double height = 1.0, bool normals = true, bool uvs = true}) {
Float32List vertices = Float32List.fromList([
-width / 2, 0, -height / 2,
width / 2, 0, -height / 2,
width / 2, 0, height / 2,
-width / 2, 0, height / 2,
]);
Float32List? _normals = normals ? Float32List.fromList([
0, 1, 0,
0, 1, 0,
0, 1, 0,
0, 1, 0,
]) : null;
Float32List? _uvs = uvs ? Float32List.fromList([
0, 0,
1, 0,
1, 1,
0, 1,
]) : null;
List<int> indices = [
0, 2, 1,
0, 3, 2,
];
return Geometry(vertices, indices, normals: _normals, uvs: _uvs);
}
}

View File

@@ -0,0 +1,38 @@
// Helper function to convert double4x4 to Matrix4
import 'package:thermion_dart/src/viewer/src/ffi/src/thermion_dart.g.dart';
import 'package:vector_math/vector_math_64.dart';
import 'dart:ffi';
Matrix4 double4x4ToMatrix4(double4x4 mat) {
return Matrix4.fromList([
mat.col1[0],
mat.col1[1],
mat.col1[2],
mat.col1[3],
mat.col2[0],
mat.col2[1],
mat.col2[2],
mat.col2[3],
mat.col3[0],
mat.col3[1],
mat.col3[2],
mat.col3[3],
mat.col4[0],
mat.col4[1],
mat.col4[2],
mat.col4[3],
]);
}
double4x4 matrix4ToDouble4x4(Matrix4 mat) {
final out = Struct.create<double4x4>();
for (int i = 0; i < 4; i++) {
out.col1[i] = mat.storage[i];
out.col2[i] = mat.storage[i + 4];
out.col3[i] = mat.storage[i + 8];
out.col4[i] = mat.storage[i + 12];
}
return out;
}

View File

@@ -0,0 +1,85 @@
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!;
}
}

View File

@@ -0,0 +1,89 @@
import 'dart:async';
import 'dart:ffi';
import 'package:ffi/ffi.dart';
export 'package:ffi/ffi.dart';
export 'dart:ffi';
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();
// ignore: prefer_function_declarations_over_variables
void Function() callback = () {
completer.complete();
};
final nativeCallable = NativeCallable<Void Function()>.listener(callback);
func.call(nativeCallable.nativeFunction);
await completer.future;
nativeCallable.close();
}
Future<int> withPointerCallback<T extends NativeType>(
Function(Pointer<NativeFunction<Void Function(Pointer<T>)>>)
func) async {
final completer = Completer<Pointer<T>>();
// ignore: prefer_function_declarations_over_variables
void Function(Pointer<NativeType>) callback = (Pointer<NativeType> ptr) {
completer.complete(ptr.cast<T>());
};
final nativeCallable =
NativeCallable<Void Function(Pointer<NativeType>)>.listener(callback);
func.call(nativeCallable.nativeFunction);
var ptr = await completer.future;
nativeCallable.close();
return ptr.address;
}
Future<bool> withBoolCallback(
Function(Pointer<NativeFunction<Void Function(Bool)>>) func) async {
final completer = Completer<bool>();
// ignore: prefer_function_declarations_over_variables
void Function(bool) callback = (bool result) {
completer.complete(result);
};
final nativeCallable = NativeCallable<Void Function(Bool)>.listener(callback);
func.call(nativeCallable.nativeFunction);
await completer.future;
nativeCallable.close();
return completer.future;
}
Future<int> withIntCallback(
Function(Pointer<NativeFunction<Void Function(Int32)>>) func) async {
final completer = Completer<int>();
// ignore: prefer_function_declarations_over_variables
void Function(int) callback = (int result) {
completer.complete(result);
};
final nativeCallable =
NativeCallable<Void Function(Int32)>.listener(callback);
func.call(nativeCallable.nativeFunction);
await completer.future;
nativeCallable.close();
return completer.future;
}
Future<String> withCharPtrCallback(
Function(Pointer<NativeFunction<Void Function(Pointer<Char>)>>)
func) async {
final completer = Completer<String>();
// ignore: prefer_function_declarations_over_variables
void Function(Pointer<Char>) callback = (Pointer<Char> result) {
completer.complete(result.cast<Utf8>().toDartString());
};
final nativeCallable =
NativeCallable<Void Function(Pointer<Char>)>.listener(callback);
func.call(nativeCallable.nativeFunction);
await completer.future;
nativeCallable.close();
return completer.future;
}

View File

@@ -0,0 +1,54 @@
import 'dart:ffi';
import 'package:vector_math/vector_math_64.dart';
import '../../../../utils/matrix.dart';
import '../../shared_types/camera.dart';
import '../../thermion_viewer_base.dart';
import 'thermion_dart.g.dart';
class ThermionFFICamera extends Camera {
final Pointer<TCamera> camera;
final Pointer<TEngine> engine;
late ThermionEntity _entity;
ThermionFFICamera(this.camera, this.engine) {
_entity = Camera_getEntity(camera);
}
@override
Future setProjectionMatrixWithCulling(
Matrix4 projectionMatrix, double near, double far) async {
Camera_setCustomProjectionWithCulling(
camera, matrix4ToDouble4x4(projectionMatrix), near, far);
}
Future<Matrix4> getModelMatrix() async {
return double4x4ToMatrix4(Camera_getModelMatrix(camera));
}
@override
Future setTransform(Matrix4 transform) async {
var entity = Camera_getEntity(camera);
Engine_setTransform(engine, entity, matrix4ToDouble4x4(transform));
}
@override
Future setLensProjection(
{double near = kNear,
double far = kFar,
double aspect = 1.0,
double focalLength = kFocalLength}) async {
Camera_setLensProjection(camera, near, far, aspect, focalLength);
}
@override
ThermionEntity getEntity() {
return _entity;
}
@override
Future setModelMatrix(Matrix4 matrix) async {
Camera_setModelMatrix(camera, matrix4ToDouble4x4(matrix));
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,4 @@
library;
export 'src/thermion_viewer_ffi.dart' show ThermionViewerFFI;
export 'src/camera_ffi.dart';

View File

@@ -0,0 +1,21 @@
import 'package:vector_math/vector_math_64.dart';
import '../thermion_viewer_base.dart';
abstract class Camera {
Future setProjectionMatrixWithCulling(
Matrix4 projectionMatrix, double near, double far);
Future setLensProjection(
{double near = kNear,
double far = kFar,
double aspect = 1.0,
double focalLength = kFocalLength});
Future<Matrix4> getModelMatrix();
Future setModelMatrix(Matrix4 matrix);
ThermionEntity getEntity();
Future setTransform(Matrix4 transform);
}

View File

@@ -0,0 +1,11 @@
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;

View File

@@ -0,0 +1,32 @@
import 'dart:typed_data';
import '../../viewer.dart';
class Geometry {
final Float32List vertices;
final Uint16List indices;
final Float32List normals;
final Float32List uvs;
final PrimitiveType primitiveType;
Geometry(
this.vertices,
List<int> indices, {
Float32List? normals,
Float32List? uvs,
this.primitiveType = PrimitiveType.TRIANGLES,
}) : indices = Uint16List.fromList(indices),
normals = normals ?? Float32List(0),
uvs = uvs ?? Float32List(0) {
assert(this.uvs.length == 0 || this.uvs.length == (vertices.length ~/ 3) * 2);
}
void scale(double factor) {
for (int i = 0; i < vertices.length; i++) {
vertices[i] = vertices[i] * factor;
}
}
bool get hasNormals => normals.isNotEmpty;
bool get hasUVs => uvs.isNotEmpty;
}

View File

@@ -0,0 +1,6 @@
class GLTF {
final String uri;
final int numInstances;
GLTF(this.uri, this.numInstances);
}

View File

@@ -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,
}

View File

@@ -0,0 +1,98 @@
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,
);
DirectLight.sun({
double color = 6500,
double intensity = 100000,
bool castShadows = true,
Vector3? direction,
double sunAngularRadius = 0.545,
double sunHaloSize = 10.0,
double sunHaloFalloff = 80.0,
}) : this(
type: LightType.DIRECTIONAL,
color: color,
intensity: intensity,
castShadows: castShadows,
position: Vector3(0, 0, 0),
direction: direction ?? Vector3(0, -1, 0),
sunAngularRadius: sunAngularRadius,
sunHaloSize: sunHaloSize,
sunHaloFallof: sunHaloFalloff,
);
DirectLight.spot({
double color = 6500,
double intensity = 100000,
bool castShadows = true,
Vector3? position,
Vector3? direction,
double falloffRadius = 1.0,
double spotLightConeInner = pi / 8,
double spotLightConeOuter = pi / 4,
}) : this(
type: LightType.SPOT,
color: color,
intensity: intensity,
castShadows: castShadows,
position: position ?? Vector3(0, 1, 0),
direction: direction ?? Vector3(0, -1, 0),
falloffRadius: falloffRadius,
spotLightConeInner: spotLightConeInner,
spotLightConeOuter: spotLightConeOuter,
);
}

View File

@@ -0,0 +1,4 @@
// see filament Manipulator.h for more details
@Deprecated(
"This is used the native pointer manipulator Prefer InputHandler instead")
enum ManipulatorMode { ORBIT, MAP, FREE_FLIGHT }

View File

@@ -0,0 +1,6 @@
abstract class MaterialInstance {
Future setDepthWriteEnabled(bool enabled);
Future setDepthCullingEnabled(bool enabled);
}
enum AlphaMode { OPAQUE, MASK, BLEND }

View File

@@ -0,0 +1,5 @@
// "picking" means clicking/tapping on the viewport, and unprojecting the X/Y coordinate to determine whether any renderable entities were present at those coordinates.
import '../../viewer.dart';
typedef FilamentPickResult = ({ThermionEntity entity, double x, double y});
typedef ThermionPickResult = FilamentPickResult;

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -0,0 +1,13 @@
library shared_types;
export 'camera.dart';
export 'material.dart';
export 'texture.dart';
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';

View File

@@ -0,0 +1,3 @@
abstract class ThermionTexture {
}

View File

@@ -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});
}

View File

@@ -0,0 +1 @@
enum ToneMapper { ACES, FILMIC, LINEAR }

View File

@@ -0,0 +1,982 @@
import 'package:thermion_dart/src/viewer/src/events.dart';
import '../../entities/abstract_gizmo.dart';
import 'shared_types/camera.dart';
import 'shared_types/shared_types.dart';
export 'shared_types/shared_types.dart';
import 'dart:math';
import 'dart:typed_data';
import 'package:vector_math/vector_math_64.dart';
import 'dart:async';
import 'package:animation_tools_dart/animation_tools_dart.dart';
const double kNear = 0.05;
const double kFar = 1000.0;
const double kFocalLength = 28.0;
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 immediately.
///
Future render();
///
/// Requests a single frame to be rendered. This is only intended to be used internally.
///
Future requestFrame();
///
/// Render a single frame 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,
int priority = 4,
int layer = 0});
///
/// 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. You probably never need this; use getMainCamera instead.
///
Future<ThermionEntity> getMainCameraEntity();
///
/// Returns the entity associated with the main camera. You probably never need this; use getMainCamera instead.
///
Future<Camera> 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 = kNear,
double far = kFar,
double? aspect,
double focalLength = kFocalLength});
///
/// 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.
///
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);
///
/// Sets the material property [propertyName] under material [materialIndex] for [entity] to [value].
/// [entity] must have a Renderable attached.
///
Future setMaterialPropertyInt(
ThermionEntity entity, String propertyName, int materialIndex, int 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 InputHandler 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,
{MaterialInstance? materialInstance, 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 setLayerVisibility(int layer, bool visible);
///
/// Assigns [entity] to visibility layer [layer].
///
Future setVisibilityLayer(ThermionEntity entity, int layer);
///
/// 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);
///
/// Decodes the specified image data and creates a texture.
///
Future<ThermionTexture> createTexture(Uint8List data);
///
///
///
Future applyTexture(covariant ThermionTexture texture, ThermionEntity entity,
{int materialIndex = 0, String parameterName = "baseColorMap"});
///
///
///
Future destroyTexture(covariant ThermionTexture texture);
///
///
///
Future<MaterialInstance> createUbershaderMaterialInstance({
bool doubleSided = false,
bool unlit = false,
bool hasVertexColors = false,
bool hasBaseColorTexture = false,
bool hasNormalTexture = false,
bool hasOcclusionTexture = false,
bool hasEmissiveTexture = false,
bool useSpecularGlossiness = false,
AlphaMode alphaMode = AlphaMode.OPAQUE,
bool enableDiagnostics = false,
bool hasMetallicRoughnessTexture = false,
int metallicRoughnessUV = 0,
int baseColorUV = 0,
bool hasClearCoatTexture = false,
int clearCoatUV = 0,
bool hasClearCoatRoughnessTexture = false,
int clearCoatRoughnessUV = 0,
bool hasClearCoatNormalTexture = false,
int clearCoatNormalUV = 0,
bool hasClearCoat = false,
bool hasTransmission = false,
bool hasTextureTransforms = false,
int emissiveUV = 0,
int aoUV = 0,
int normalUV = 0,
bool hasTransmissionTexture = false,
int transmissionUV = 0,
bool hasSheenColorTexture = false,
int sheenColorUV = 0,
bool hasSheenRoughnessTexture = false,
int sheenRoughnessUV = 0,
bool hasVolumeThicknessTexture = false,
int volumeThicknessUV = 0,
bool hasSheen = false,
bool hasIOR = false,
bool hasVolume = false,
});
///
///
///
Future destroyMaterialInstance(covariant MaterialInstance materialInstance);
///
///
///
Future<MaterialInstance> createUnlitMaterialInstance();
///
///
///
Future<MaterialInstance?> getMaterialInstanceAt(
ThermionEntity entity, int index);
///
///
///
Future<Camera> createCamera();
///
///
///
Future setActiveCamera(covariant Camera camera);
///
///
///
Future registerRequestFrameHook(Future Function() hook);
///
///
///
Future unregisterRequestFrameHook(Future Function() hook);
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,759 @@
@JS()
library thermion_flutter_js;
import 'dart:js_interop';
import 'package:logging/logging.dart';
import 'package:vector_math/vector_math_64.dart' as v64;
import 'package:animation_tools_dart/animation_tools_dart.dart';
import 'dart:js_interop_unsafe';
import 'package:vector_math/vector_math_64.dart';
import '../../../viewer.dart';
import 'thermion_viewer_js_shim.dart';
///
/// A (Dart) class that wraps a (Dart) instance of [ThermionViewer],
/// but exported to JS by binding to a global property.
/// This is effectively an implementation of [ThermionViewerJSShim];
/// allowing users to interact with an instance of [ThermionViewer]
/// (presumably compiled to WASM) from any Javascript context (including
/// the browser console).
///
@JSExport()
class ThermionViewerJSDartBridge {
final _logger = Logger("ThermionViewerJSDartBridge");
final ThermionViewer viewer;
ThermionViewerJSDartBridge(this.viewer);
void bind({String globalPropertyName = "thermionViewer"}) {
var wrapper = createJSInteropWrapper<ThermionViewerJSDartBridge>(this)
as ThermionViewerJSShim;
globalContext.setProperty(globalPropertyName.toJS, wrapper);
}
JSPromise<JSBoolean> get initialized {
return viewer.initialized.then((v) => v.toJS).toJS;
}
@JSExport()
JSBoolean get rendering => viewer.rendering.toJS;
@JSExport()
JSPromise setRendering(bool render) {
return viewer.setRendering(render).toJS;
}
@JSExport()
JSPromise render() => viewer.render().toJS;
@JSExport()
JSPromise<JSUint8Array> capture() {
return viewer.capture().then((captured) => captured.toJS).toJS;
}
@JSExport()
JSPromise setFrameRate(int framerate) => viewer.setFrameRate(framerate).toJS;
@JSExport()
JSPromise dispose() => viewer.dispose().toJS;
@JSExport()
JSPromise setBackgroundImage(String path, {bool fillHeight = false}) =>
viewer.setBackgroundImage(path, fillHeight: fillHeight).toJS;
@JSExport()
JSPromise setBackgroundImagePosition(double x, double y,
{bool clamp = false}) =>
viewer.setBackgroundImagePosition(x, y, clamp: clamp).toJS;
@JSExport()
JSPromise clearBackgroundImage() => viewer.clearBackgroundImage().toJS;
@JSExport()
JSPromise setBackgroundColor(double r, double g, double b, double alpha) =>
viewer.setBackgroundColor(r, g, b, alpha).toJS;
@JSExport()
JSPromise loadSkybox(String skyboxPath) => viewer.loadSkybox(skyboxPath).toJS;
@JSExport()
JSPromise removeSkybox() => viewer.removeSkybox().toJS;
@JSExport()
JSPromise loadIbl(String lightingPath, double intensity) {
_logger.info("Loading IBL from $lightingPath with intensity $intensity");
return viewer.loadIbl(lightingPath, intensity: intensity).toJS;
}
@JSExport()
JSPromise rotateIbl(JSArray<JSNumber> rotation) {
var matrix =
Matrix3.fromList(rotation.toDart.map((v) => v.toDartDouble).toList());
return viewer.rotateIbl(matrix).toJS;
}
@JSExport()
JSPromise removeIbl() => viewer.removeIbl().toJS;
@JSExport()
JSPromise<JSNumber> addLight(
int type,
double colour,
double intensity,
double posX,
double posY,
double posZ,
double dirX,
double dirY,
double dirZ,
double falloffRadius,
double spotLightConeInner,
double spotLightConeOuter,
double sunAngularRadius,
double sunHaloSize,
double sunHaloFallof,
bool castShadows) {
return viewer
.addLight(LightType.values[type], colour, intensity, posX, posY, posZ,
dirX, dirY, dirZ,
falloffRadius: falloffRadius,
spotLightConeInner: spotLightConeInner,
spotLightConeOuter: spotLightConeOuter,
sunAngularRadius: sunAngularRadius,
sunHaloSize: sunHaloSize,
sunHaloFallof: sunHaloFallof,
castShadows: castShadows)
.then((entity) => entity.toJS)
.toJS;
}
@JSExport()
JSPromise removeLight(ThermionEntity light) => viewer.removeLight(light).toJS;
@JSExport()
JSPromise clearLights() => viewer.clearLights().toJS;
@JSExport()
JSPromise<JSNumber> loadGlb(String path, {int numInstances = 1}) {
_logger.info("Loading GLB from path $path with numInstances $numInstances");
return viewer
.loadGlb(path, numInstances: numInstances)
.then((entity) => entity.toJS)
.catchError((err) {
_logger.info("Error: $err");
}).toJS;
}
@JSExport()
JSPromise<JSNumber> createInstance(ThermionEntity entity) {
return viewer.createInstance(entity).then((instance) => instance.toJS).toJS;
}
@JSExport()
JSPromise<JSNumber> getInstanceCount(ThermionEntity entity) =>
viewer.getInstanceCount(entity).then((v) => v.toJS).toJS;
@JSExport()
JSPromise<JSArray<JSNumber>> getInstances(ThermionEntity entity) {
return viewer
.getInstances(entity)
.then((instances) =>
instances.map((instance) => instance.toJS).toList().toJS)
.toJS;
}
@JSExport()
JSPromise<JSNumber> loadGltf(String path, String relativeResourcePath,
{bool keepData = false}) {
return viewer
.loadGltf(path, relativeResourcePath, keepData: keepData)
.then((entity) => entity.toJS)
.toJS;
}
@JSExport()
JSPromise panStart(double x, double y) => viewer.panStart(x, y).toJS;
@JSExport()
JSPromise panUpdate(double x, double y) => viewer.panUpdate(x, y).toJS;
@JSExport()
JSPromise panEnd() => viewer.panEnd().toJS;
@JSExport()
JSPromise rotateStart(double x, double y) => viewer.rotateStart(x, y).toJS;
@JSExport()
JSPromise rotateUpdate(double x, double y) => viewer.rotateUpdate(x, y).toJS;
@JSExport()
JSPromise rotateEnd() => viewer.rotateEnd().toJS;
@JSExport()
JSPromise setMorphTargetWeights(
ThermionEntity entity, JSArray<JSNumber> weights) {
var dartWeights = weights.toDart.map((w) => w.toDartDouble).toList();
return viewer.setMorphTargetWeights(entity, dartWeights).toJS;
}
@JSExport()
JSPromise<JSArray<JSString>> getMorphTargetNames(
ThermionEntity entity, ThermionEntity childEntity) {
var morphTargetNames = viewer
.getMorphTargetNames(entity, childEntity)
.then((v) => v.map((s) => s.toJS).toList().toJS);
return morphTargetNames.toJS;
}
@JSExport()
JSPromise<JSArray<JSString>> getBoneNames(
ThermionEntity entity, int skinIndex) {
return viewer
.getBoneNames(entity, skinIndex: skinIndex)
.then((v) => v.map((s) => s.toJS).toList().toJS)
.toJS;
}
@JSExport()
JSPromise<JSArray<JSString>> getAnimationNames(ThermionEntity entity) =>
viewer
.getAnimationNames(entity)
.then((v) => v.map((s) => s.toJS).toList().toJS)
.toJS;
@JSExport()
JSPromise<JSNumber> getAnimationDuration(
ThermionEntity entity, int animationIndex) =>
viewer
.getAnimationDuration(entity, animationIndex)
.then((v) => v.toJS)
.toJS;
@JSExport()
void clearMorphAnimationData(ThermionEntity entity) {
viewer.clearMorphAnimationData(entity);
}
@JSExport()
JSPromise setMorphAnimationData(
ThermionEntity entity,
JSArray<JSArray<JSNumber>> animation,
JSArray<JSString> morphTargets,
JSArray<JSString>? targetMeshNames,
double frameLengthInMs) {
try {
var morphTargetsDart = morphTargets.toDart.map((m) => m.toDart).toList();
var animationDataDart = animation.toDart
.map((x) => x.toDart.map((y) => y.toDartDouble).toList())
.toList();
var morphAnimationData = MorphAnimationData(
animationDataDart, morphTargetsDart,
frameLengthInMs: frameLengthInMs);
var targetMeshNamesDart =
targetMeshNames?.toDart.map((x) => x.toDart).toList();
if (animationDataDart.first.length != morphTargetsDart.length) {
throw Exception(
"Length mismatch between morph targets and animation data");
}
var result = viewer
.setMorphAnimationData(
entity,
morphAnimationData,
targetMeshNames: targetMeshNamesDart,
)
.onError((err, st) {
_logger.severe("ERROR SETTING MORPH ANIMATION DATA : $err\n$st");
return null;
});
return result.toJS;
} catch (err, st) {
_logger.severe(err);
_logger.severe(st);
rethrow;
}
}
@JSExport()
JSPromise resetBones(ThermionEntity entity) => viewer.resetBones(entity).toJS;
@JSExport()
JSPromise addBoneAnimation(
ThermionEntity entity,
JSArray<JSString> bones,
JSArray<JSArray<JSArray<JSNumber>>> frameData,
JSNumber frameLengthInMs,
JSNumber spaceEnum,
JSNumber skinIndex,
JSNumber fadeInInSecs,
JSNumber fadeOutInSecs,
JSNumber maxDelta) {
var frameDataDart = frameData.toDart
.map((frame) => frame.toDart
.map((v) {
var values = v.toDart;
var trans = v64.Vector3(values[0].toDartDouble,
values[1].toDartDouble, values[2].toDartDouble);
var rot = v64.Quaternion(
values[3].toDartDouble,
values[4].toDartDouble,
values[5].toDartDouble,
values[6].toDartDouble);
return (rotation: rot, translation: trans);
})
.cast<BoneAnimationFrame>()
.toList())
.toList();
var data = BoneAnimationData(
bones.toDart.map((n) => n.toDart).toList(), frameDataDart,
frameLengthInMs: frameLengthInMs.toDartDouble,
space: Space.values[spaceEnum.toDartInt]);
return viewer
.addBoneAnimation(entity, data,
skinIndex: skinIndex.toDartInt,
fadeInInSecs: fadeInInSecs.toDartDouble,
fadeOutInSecs: fadeOutInSecs.toDartDouble)
.toJS;
}
@JSExport()
JSPromise removeEntity(ThermionEntity entity) =>
viewer.removeEntity(entity).toJS;
@JSExport()
JSPromise clearEntities() {
return viewer.clearEntities().toJS;
}
@JSExport()
JSPromise zoomBegin() => viewer.zoomBegin().toJS;
@JSExport()
JSPromise zoomUpdate(double x, double y, double z) =>
viewer.zoomUpdate(x, y, z).toJS;
@JSExport()
JSPromise zoomEnd() => viewer.zoomEnd().toJS;
@JSExport()
JSPromise playAnimation(ThermionEntity entity, int index,
{bool loop = false,
bool reverse = false,
bool replaceActive = true,
double crossfade = 0.0,
double startOffset = 0.0}) =>
viewer
.playAnimation(entity, index,
loop: loop,
reverse: reverse,
replaceActive: replaceActive,
crossfade: crossfade,
startOffset: startOffset)
.toJS;
@JSExport()
JSPromise playAnimationByName(ThermionEntity entity, String name,
{bool loop = false,
bool reverse = false,
bool replaceActive = true,
double crossfade = 0.0}) =>
viewer
.playAnimationByName(
entity,
name,
loop: loop,
reverse: reverse,
replaceActive: replaceActive,
crossfade: crossfade,
)
.toJS;
@JSExport()
JSPromise setAnimationFrame(
ThermionEntity entity, int index, int animationFrame) =>
viewer
.setAnimationFrame(
entity,
index,
animationFrame,
)
.toJS;
@JSExport()
JSPromise stopAnimation(ThermionEntity entity, int animationIndex) =>
viewer.stopAnimation(entity, animationIndex).toJS;
@JSExport()
JSPromise stopAnimationByName(ThermionEntity entity, String name) =>
viewer.stopAnimationByName(entity, name).toJS;
@JSExport()
JSPromise setCamera(ThermionEntity entity, String? name) =>
viewer.setCamera(entity, name).toJS;
@JSExport()
JSPromise setMainCamera() => viewer.setMainCamera().toJS;
@JSExport()
JSPromise<JSNumber> getMainCamera() {
throw UnimplementedError("TODO");
// return viewer.getMainCamera().then((camera) => camera.toJS).toJS;
}
@JSExport()
JSPromise setParent(
ThermionEntity child, ThermionEntity parent, bool preserveScaling) {
return viewer
.setParent(child, parent, preserveScaling: preserveScaling)
.toJS;
}
@JSExport()
JSPromise setCameraFov(double degrees, bool horizontal) =>
viewer.setCameraFov(degrees, horizontal: horizontal).toJS;
@JSExport()
JSPromise setToneMapping(int mapper) =>
viewer.setToneMapping(ToneMapper.values[mapper]).toJS;
@JSExport()
JSPromise setBloom(double bloom) => viewer.setBloom(bloom).toJS;
@JSExport()
JSPromise setCameraFocalLength(double focalLength) =>
viewer.setCameraFocalLength(focalLength).toJS;
@JSExport()
JSPromise setCameraCulling(double near, double far) =>
viewer.setCameraCulling(near, far).toJS;
@JSExport()
JSPromise<JSNumber> getCameraCullingNear() =>
viewer.getCameraCullingNear().then((v) => v.toJS).toJS;
@JSExport()
JSPromise<JSNumber> getCameraCullingFar() =>
viewer.getCameraCullingFar().then((v) => v.toJS).toJS;
@JSExport()
JSPromise setCameraFocusDistance(double focusDistance) =>
viewer.setCameraFocusDistance(focusDistance).toJS;
@JSExport()
JSPromise<JSArray<JSNumber>> getCameraPosition() {
throw UnimplementedError();
// return viewer.getCameraPosition().then((position) => position.toJS).toJS;
}
@JSExport()
JSPromise<JSArray<JSNumber>> getCameraModelMatrix() {
throw UnimplementedError();
// return viewer.getCameraModelMatrix().then((matrix) => matrix.toJSArray<JSNumber>()).toJS;
}
@JSExport()
JSPromise<JSArray<JSNumber>> getCameraViewMatrix() {
throw UnimplementedError();
// return viewer.getCameraViewMatrix().then((matrix) => matrix.toJSArray<JSNumber>()).toJS;
}
@JSExport()
JSPromise<JSArray<JSNumber>> getCameraProjectionMatrix() {
throw UnimplementedError();
// return viewer.getCameraProjectionMatrix().then((matrix) => matrix.toJSArray<JSNumber>()).toJS;
}
@JSExport()
JSPromise<JSArray<JSNumber>> getCameraCullingProjectionMatrix() {
throw UnimplementedError();
// return viewer.getCameraCullingProjectionMatrix().then((matrix) => matrix.toJSArray<JSNumber>()).toJS;
}
@JSExport()
JSPromise<JSNumber> getCameraFrustum() {
throw UnimplementedError();
// return viewer.getCameraFrustum().then((frustum) => frustum.toJS).toJS;
}
@JSExport()
JSPromise setCameraPosition(double x, double y, double z) =>
viewer.setCameraPosition(x, y, z).toJS;
@JSExport()
JSPromise<JSArray<JSNumber>> getCameraRotation() {
return viewer
.getCameraRotation()
.then((rotation) => rotation.storage.map((v) => v.toJS).toList().toJS)
.toJS;
}
@JSExport()
JSPromise moveCameraToAsset(ThermionEntity entity) =>
throw UnimplementedError();
// viewer.moveCameraToAsset(entity)).toJS;
@JSExport()
JSPromise setViewFrustumCulling(JSBoolean enabled) =>
throw UnimplementedError();
// viewer.setViewFrustumCulling(enabled).toJS;
@JSExport()
JSPromise setCameraExposure(
double aperture, double shutterSpeed, double sensitivity) =>
viewer.setCameraExposure(aperture, shutterSpeed, sensitivity).toJS;
@JSExport()
JSPromise setCameraRotation(JSArray<JSNumber> quaternion) {
var dartVals = quaternion.toDart;
return viewer
.setCameraRotation(v64.Quaternion(
dartVals[0].toDartDouble,
dartVals[1].toDartDouble,
dartVals[2].toDartDouble,
dartVals[3].toDartDouble))
.toJS;
}
@JSExport()
JSPromise setCameraModelMatrix(JSArray<JSNumber> matrix) {
throw UnimplementedError();
// viewer.setCameraModelMatrix(matrix).toJS;
}
@JSExport()
JSPromise setMaterialColor(ThermionEntity entity, String meshName,
int materialIndex, double r, double g, double b, double a) =>
throw UnimplementedError();
// viewer.setMaterialColor(
// entity),
// meshName,
// materialIndex,
// r,
// g,
// b,
// a,
// ).toJS;
@JSExport()
JSPromise transformToUnitCube(ThermionEntity entity) =>
viewer.transformToUnitCube(entity).toJS;
@JSExport()
JSPromise setPosition(ThermionEntity entity, double x, double y, double z) =>
viewer.setPosition(entity, x, y, z).toJS;
@JSExport()
JSPromise setScale(ThermionEntity entity, double scale) =>
viewer.setScale(entity, scale).toJS;
@JSExport()
JSPromise setRotation(
ThermionEntity entity, double rads, double x, double y, double z) =>
viewer.setRotation(entity, rads, x, y, z).toJS;
@JSExport()
JSPromise queuePositionUpdate(
ThermionEntity entity, double x, double y, double z, bool relative) =>
viewer
.queuePositionUpdate(
entity,
x,
y,
z,
relative: relative,
)
.toJS;
@JSExport()
JSPromise queueRotationUpdate(ThermionEntity entity, double rads, double x,
double y, double z, bool relative) =>
viewer
.queueRotationUpdate(
entity,
rads,
x,
y,
z,
relative: relative,
)
.toJS;
@JSExport()
JSPromise queueRotationUpdateQuat(
ThermionEntity entity, JSArray<JSNumber> quat, JSBoolean relative) =>
throw UnimplementedError();
// viewer.queueRotationUpdateQuat(
// entity,
// quat.toDartQuaternion(),
// relative: relative,
// ).toJS;
@JSExport()
JSPromise setPostProcessing(bool enabled) =>
viewer.setPostProcessing(enabled).toJS;
@JSExport()
JSPromise setAntiAliasing(bool msaa, bool fxaa, bool taa) =>
viewer.setAntiAliasing(msaa, fxaa, taa).toJS;
@JSExport()
JSPromise setRotationQuat(
ThermionEntity entity, JSArray<JSNumber> rotation) =>
throw UnimplementedError();
@JSExport()
JSPromise reveal(ThermionEntity entity, String? meshName) =>
viewer.reveal(entity, meshName).toJS;
@JSExport()
JSPromise hide(ThermionEntity entity, String? meshName) =>
viewer.hide(entity, meshName).toJS;
@JSExport()
void pick(int x, int y) => viewer.pick(x, y);
@JSExport()
String? getNameForEntity(ThermionEntity entity) =>
viewer.getNameForEntity(entity);
@JSExport()
JSPromise setCameraManipulatorOptions({
int mode = 0,
double orbitSpeedX = 0.01,
double orbitSpeedY = 0.01,
double zoomSpeed = 0.01,
}) =>
viewer
.setCameraManipulatorOptions(
mode: ManipulatorMode.values[mode],
orbitSpeedX: orbitSpeedX,
orbitSpeedY: orbitSpeedY,
zoomSpeed: zoomSpeed,
)
.toJS;
@JSExport()
JSPromise<JSArray<JSNumber>> getChildEntities(
ThermionEntity parent, bool renderableOnly) {
return viewer
.getChildEntities(
parent,
renderableOnly,
)
.then((entities) => entities.map((entity) => entity.toJS).toList().toJS)
.onError((e, st) async {
_logger.severe("Error : $e\n$st");
return <JSNumber>[].toJS;
}).toJS;
}
@JSExport()
JSPromise<JSNumber> getChildEntity(ThermionEntity parent, String childName) {
return viewer
.getChildEntity(
parent,
childName,
)
.then((entity) => entity.toJS)
.onError((e, st) async {
_logger.severe("Error getChildEntity : $e\n$st");
return 0.toJS;
}).toJS;
}
@JSExport()
JSPromise<JSArray<JSString>> getChildEntityNames(
ThermionEntity entity, bool renderableOnly) =>
viewer
.getChildEntityNames(
entity,
renderableOnly: renderableOnly,
)
.then((v) => v.map((s) => s.toJS).toList().toJS)
.toJS;
@JSExport()
JSPromise setRecording(bool recording) => viewer.setRecording(recording).toJS;
@JSExport()
JSPromise setRecordingOutputDirectory(String outputDirectory) =>
viewer.setRecordingOutputDirectory(outputDirectory).toJS;
@JSExport()
JSPromise addAnimationComponent(ThermionEntity entity) =>
viewer.addAnimationComponent(entity).toJS;
@JSExport()
JSPromise removeAnimationComponent(ThermionEntity entity) =>
viewer.removeAnimationComponent(entity).toJS;
@JSExport()
JSPromise getParent(ThermionEntity entity) =>
viewer.removeAnimationComponent(entity).toJS;
@JSExport()
JSPromise getBone(ThermionEntity entity, int boneIndex, int skinIndex) =>
viewer.getBone(entity, boneIndex, skinIndex: skinIndex).toJS;
@JSExport()
JSPromise<JSArray<JSNumber>> getLocalTransform(ThermionEntity entity) {
return viewer
.getLocalTransform(entity)
.then((t) => t.storage.map((v) => v.toJS).toList().toJS)
.toJS;
}
@JSExport()
JSPromise<JSArray<JSNumber>> getWorldTransform(ThermionEntity entity) {
return viewer
.getWorldTransform(entity)
.then((t) => t.storage.map((v) => v.toJS).toList().toJS)
.toJS;
}
@JSExport()
JSPromise setTransform(ThermionEntity entity, JSArray<JSNumber> transform) {
return viewer
.setTransform(
entity,
Matrix4.fromList(
transform.toDart.map((v) => v.toDartDouble).toList()))
.toJS;
}
@JSExport()
JSPromise updateBoneMatrices(ThermionEntity entity) {
return viewer.updateBoneMatrices(entity).toJS;
}
@JSExport()
JSPromise setBoneTransform(ThermionEntity entity, int boneIndex,
JSArray<JSNumber> transform, int skinIndex) {
return viewer
.setBoneTransform(
entity,
boneIndex,
Matrix4.fromList(
transform.toDart.map((v) => v.toDartDouble).toList()),
skinIndex: skinIndex)
.toJS;
}
@JSExport()
JSPromise addCollisionComponent(ThermionEntity entity,
{JSFunction? callback, bool affectsTransform = false}) {
throw UnimplementedError();
}
@JSExport()
JSPromise setShadowsEnabled(bool enabled) {
return viewer.setShadowsEnabled(enabled).toJS;
}
@JSExport()
JSPromise setShadowType(int shadowType) {
return viewer.setShadowType(ShadowType.values[shadowType]).toJS;
}
@JSExport()
JSPromise setSoftShadowOptions(
double penumbraScale, double penumbraRatioScale) {
return viewer.setSoftShadowOptions(penumbraScale, penumbraRatioScale).toJS;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,424 @@
@JS()
library thermion_flutter_js;
import 'dart:js_interop';
import '../../shared_types/shared_types.dart';
///
/// An extension type on [JSObject] that represents a
/// Javascript shim implementation of the [ThermionViewer] interface.
///
extension type ThermionViewerJSShim(JSObject _) implements JSObject {
@JS('initialized')
external JSPromise<JSBoolean> get initialized;
@JS('rendering')
external bool get rendering;
@JS('setRendering')
external JSPromise setRendering(bool render);
@JS('render')
external JSPromise render();
@JS('capture')
external JSPromise<JSUint8Array> capture();
@JS('setFrameRate')
external JSPromise setFrameRate(int framerate);
@JS('dispose')
external JSPromise dispose();
@JS('setBackgroundImage')
external JSPromise setBackgroundImage(String path, bool fillHeight);
@JS('setBackgroundImagePosition')
external JSPromise setBackgroundImagePosition(double x, double y, bool clamp);
@JS('clearBackgroundImage')
external JSPromise clearBackgroundImage();
@JS('setBackgroundColor')
external JSPromise setBackgroundColor(
double r, double g, double b, double alpha);
@JS('loadSkybox')
external JSPromise loadSkybox(String skyboxPath);
@JS('removeSkybox')
external JSPromise removeSkybox();
@JS('loadIbl')
external JSPromise loadIbl(String lightingPath, double intensity);
@JS('rotateIbl')
external JSPromise rotateIbl(JSArray<JSNumber> rotationMatrix);
@JS('removeIbl')
external JSPromise removeIbl();
@JS('addLight')
external JSPromise<JSNumber> addLight(
int type,
double colour,
double intensity,
double posX,
double posY,
double posZ,
double dirX,
double dirY,
double dirZ,
double falloffRadius,
double spotLightConeInner,
double spotLightConeOuter,
double sunAngularRadius,
double sunHaloSize,
double sunHaloFallof,
bool castShadows);
@JS('removeLight')
external JSPromise removeLight(ThermionEntity light);
@JS('clearLights')
external JSPromise clearLights();
@JS('loadGlb')
external JSPromise<JSNumber> loadGlb(String path, int numInstances);
@JS('createInstance')
external JSPromise<JSNumber> createInstance(ThermionEntity entity);
@JS('getInstanceCount')
external JSPromise<JSNumber> getInstanceCount(ThermionEntity entity);
@JS('getInstances')
external JSPromise<JSArray<JSNumber>> getInstances(ThermionEntity entity);
@JS('loadGltf')
external JSPromise<JSNumber> loadGltf(
String path, String relativeResourcePath);
@JS('panStart')
external JSPromise panStart(double x, double y);
@JS('panUpdate')
external JSPromise panUpdate(double x, double y);
@JS('panEnd')
external JSPromise panEnd();
@JS('rotateStart')
external JSPromise rotateStart(double x, double y);
@JS('rotateUpdate')
external JSPromise rotateUpdate(double x, double y);
@JS('rotateEnd')
external JSPromise rotateEnd();
@JS('setMorphTargetWeights')
external JSPromise setMorphTargetWeights(
ThermionEntity entity, JSArray<JSNumber> weights);
@JS('getMorphTargetNames')
external JSPromise<JSArray<JSString>> getMorphTargetNames(
ThermionEntity entity, ThermionEntity childEntity);
@JS('getBoneNames')
external JSPromise<JSArray<JSString>> getBoneNames(
ThermionEntity entity, int skinIndex);
@JS('getAnimationNames')
external JSPromise<JSArray<JSString>> getAnimationNames(
ThermionEntity entity);
@JS('getAnimationDuration')
external JSPromise<JSNumber> getAnimationDuration(
ThermionEntity entity, int animationIndex);
@JS('clearMorphAnimationData')
external void clearMorphAnimationData(ThermionEntity entity);
@JS('setMorphAnimationData')
external JSPromise setMorphAnimationData(
ThermionEntity entity,
JSArray<JSArray<JSNumber>> animation,
JSArray<JSString> morphTargets,
JSArray<JSString>? targetMeshNames,
double frameLengthInMs);
@JS('resetBones')
external JSPromise resetBones(ThermionEntity entity);
@JS('addBoneAnimation')
external JSPromise addBoneAnimation(
ThermionEntity entity,
JSArray<JSString> bones,
JSArray<JSArray<JSArray<JSNumber>>> frameData,
JSNumber frameLengthInMs,
JSNumber spaceEnum,
JSNumber skinIndex,
JSNumber fadeInInSecs,
JSNumber fadeOutInSecs,
JSNumber maxDelta);
@JS('removeEntity')
external JSPromise removeEntity(ThermionEntity entity);
@JS('clearEntities')
external JSPromise clearEntities();
@JS('zoomBegin')
external JSPromise zoomBegin();
@JS('zoomUpdate')
external JSPromise zoomUpdate(double x, double y, double z);
@JS('zoomEnd')
external JSPromise zoomEnd();
@JS('playAnimation')
external JSPromise playAnimation(
ThermionEntity entity,
int index,
bool loop,
bool reverse,
bool replaceActive,
double crossfade,
double startOffset,
);
@JS('playAnimationByName')
external JSPromise playAnimationByName(
ThermionEntity entity,
String name,
bool loop,
bool reverse,
bool replaceActive,
double crossfade,
);
@JS('setAnimationFrame')
external JSPromise setAnimationFrame(
ThermionEntity entity, int index, int animationFrame);
@JS('stopAnimation')
external JSPromise stopAnimation(ThermionEntity entity, int animationIndex);
@JS('stopAnimationByName')
external JSPromise stopAnimationByName(ThermionEntity entity, String name);
@JS('setCamera')
external JSPromise setCamera(ThermionEntity entity, String? name);
@JS('setMainCamera')
external JSPromise setMainCamera();
@JS('getMainCamera')
external JSPromise<JSNumber> getMainCamera();
@JS('setCameraFov')
external JSPromise setCameraFov(double degrees, bool horizontal);
@JS('setToneMapping')
external JSPromise setToneMapping(int mapper);
@JS('setBloom')
external JSPromise setBloom(double bloom);
@JS('setCameraFocalLength')
external JSPromise setCameraFocalLength(double focalLength);
@JS('setCameraCulling')
external JSPromise setCameraCulling(double near, double far);
@JS('getCameraCullingNear')
external JSPromise<JSNumber> getCameraCullingNear();
@JS('getCameraCullingFar')
external JSPromise<JSNumber> getCameraCullingFar();
@JS('setCameraFocusDistance')
external JSPromise setCameraFocusDistance(double focusDistance);
@JS('getCameraPosition')
external JSPromise<JSArray<JSNumber>> getCameraPosition();
@JS('getCameraModelMatrix')
external JSPromise<JSArray<JSNumber>> getCameraModelMatrix();
@JS('getCameraViewMatrix')
external JSPromise<JSArray<JSNumber>> getCameraViewMatrix();
@JS('getCameraProjectionMatrix')
external JSPromise<JSArray<JSNumber>> getCameraProjectionMatrix();
@JS('getCameraCullingProjectionMatrix')
external JSPromise<JSArray<JSNumber>> getCameraCullingProjectionMatrix();
@JS('getCameraFrustum')
external JSPromise<JSObject> getCameraFrustum();
@JS('setCameraPosition')
external JSPromise setCameraPosition(double x, double y, double z);
@JS('getCameraRotation')
external JSPromise<JSArray<JSNumber>> getCameraRotation();
@JS('moveCameraToAsset')
external JSPromise moveCameraToAsset(ThermionEntity entity);
@JS('setViewFrustumCulling')
external JSPromise setViewFrustumCulling(JSBoolean enabled);
@JS('setCameraExposure')
external JSPromise setCameraExposure(
double aperture, double shutterSpeed, double sensitivity);
@JS('setCameraRotation')
external JSPromise setCameraRotation(JSArray<JSNumber> quaternion);
@JS('setCameraModelMatrix')
external JSPromise setCameraModelMatrix(JSArray<JSNumber> matrix);
@JS('setMaterialColor')
external JSPromise setMaterialColor(ThermionEntity entity, String meshName,
int materialIndex, double r, double g, double b, double a);
@JS('transformToUnitCube')
external JSPromise transformToUnitCube(ThermionEntity entity);
@JS('setPosition')
external JSPromise setPosition(
ThermionEntity entity, double x, double y, double z);
@JS('setScale')
external JSPromise setScale(ThermionEntity entity, double scale);
@JS('setRotation')
external JSPromise setRotation(
ThermionEntity entity, double rads, double x, double y, double z);
@JS('queuePositionUpdate')
external JSPromise queuePositionUpdate(
ThermionEntity entity, double x, double y, double z, bool relative);
@JS('queueRotationUpdate')
external JSPromise queueRotationUpdate(ThermionEntity entity, double rads,
double x, double y, double z, bool relative);
@JS('queueRotationUpdateQuat')
external JSPromise queueRotationUpdateQuat(
ThermionEntity entity, JSArray<JSNumber> quat, bool relative);
@JS('setPostProcessing')
external JSPromise setPostProcessing(bool enabled);
@JS('setAntiAliasing')
external JSPromise setAntiAliasing(bool msaa, bool fxaa, bool taa);
@JS('setRotationQuat')
external JSPromise setRotationQuat(
ThermionEntity entity, JSArray<JSNumber> rotation);
@JS('reveal')
external JSPromise reveal(ThermionEntity entity, String? meshName);
@JS('hide')
external JSPromise hide(ThermionEntity entity, String? meshName);
@JS('pick')
external void pick(int x, int y);
@JS('getNameForEntity')
external String? getNameForEntity(ThermionEntity entity);
@JS('setCameraManipulatorOptions')
external JSPromise setCameraManipulatorOptions(
int mode,
double orbitSpeedX,
double orbitSpeedY,
double zoomSpeed,
);
@JS('getChildEntities')
external JSPromise<JSArray<JSNumber>> getChildEntities(
ThermionEntity parent, bool renderableOnly);
@JS('getChildEntity')
external JSPromise<JSNumber> getChildEntity(
ThermionEntity parent, String childName);
@JS('getChildEntityNames')
external JSPromise<JSArray<JSString>> getChildEntityNames(
ThermionEntity entity, bool renderableOnly);
@JS('setRecording')
external JSPromise setRecording(JSBoolean recording);
@JS('setRecordingOutputDirectory')
external JSPromise setRecordingOutputDirectory(String outputDirectory);
@JS('addAnimationComponent')
external JSPromise addAnimationComponent(ThermionEntity entity);
@JS('removeAnimationComponent')
external JSPromise removeAnimationComponent(ThermionEntity entity);
@JS('addCollisionComponent')
external JSPromise addCollisionComponent(ThermionEntity entity);
@JS('removeCollisionComponent')
external JSPromise removeCollisionComponent(ThermionEntity entity);
@JS('createGeometry')
external JSPromise<JSNumber> createGeometry(JSArray<JSNumber> vertices,
JSArray<JSNumber> indices, String? materialPath, int primitiveType);
@JS('setParent')
external JSPromise setParent(ThermionEntity child, ThermionEntity parent, bool preserveScaling);
@JS('getParent')
external JSPromise<JSNumber> getParent(ThermionEntity child);
@JS('getParent')
external JSPromise<JSNumber> getBone(
ThermionEntity child, int boneIndex, int skinIndex);
@JS('testCollisions')
external JSPromise testCollisions(ThermionEntity entity);
@JS('setPriority')
external JSPromise setPriority(ThermionEntity entityId, int priority);
@JS('getLocalTransform')
external JSPromise<JSArray<JSNumber>> getLocalTransform(
ThermionEntity entity);
@JS('getWorldTransform')
external JSPromise<JSArray<JSNumber>> getWorldTransform(
ThermionEntity entity);
@JS('updateBoneMatrices')
external JSPromise updateBoneMatrices(ThermionEntity entity);
@JS('setTransform')
external JSPromise setTransform(
ThermionEntity entity, JSArray<JSNumber> transform);
@JS('setBoneTransform')
external JSPromise setBoneTransform(ThermionEntity entity, int boneIndex,
JSArray<JSNumber> transform, int skinIndex);
@JS('setShadowsEnabled')
external JSPromise setShadowsEnabled(bool enabled);
@JS('setShadowType')
external JSPromise setShadowType(int shadowType);
@JS('setSoftShadowOptions')
external JSPromise setSoftShadowOptions(
double penumbraScale, double penumbraRatioScale);
}

View File

@@ -0,0 +1,5 @@
library;
export 'src/thermion_viewer_dart_bridge.dart';
export 'src/thermion_viewer_js_shim.dart';
export 'src/thermion_viewer_js.dart';

View File

@@ -0,0 +1,48 @@
import 'package:vector_math/vector_math_64.dart';
import '../../shared_types/camera.dart';
import '../../thermion_viewer_base.dart';
class ThermionWasmCamera extends Camera {
final int pointer;
ThermionWasmCamera(this.pointer);
@override
Future setProjectionMatrixWithCulling(
Matrix4 projectionMatrix, double near, double far) {
// TODO: implement setProjectionMatrixWithCulling
throw UnimplementedError();
}
@override
Future<Matrix4> getModelMatrix() {
// TODO: implement getModelMatrix
throw UnimplementedError();
}
@override
Future setLensProjection({double near = kNear, double far = kFar, double aspect = 1.0, double focalLength = kFocalLength}) {
// TODO: implement setLensProjection
throw UnimplementedError();
}
@override
Future setTransform(Matrix4 transform) {
// TODO: implement setTransform
throw UnimplementedError();
}
@override
ThermionEntity getEntity() {
// TODO: implement getEntity
throw UnimplementedError();
}
@override
Future setModelMatrix(Matrix4 matrix) {
// TODO: implement setModelMatrix
throw UnimplementedError();
}
}

View File

@@ -0,0 +1,18 @@
import '../../../viewer.dart';
class ThermionWasmMaterialInstance extends MaterialInstance {
final int pointer;
ThermionWasmMaterialInstance(this.pointer);
@override
Future setDepthCullingEnabled(bool enabled) {
// TODO: implement setDepthCullingEnabled
throw UnimplementedError();
}
@override
Future setDepthWriteEnabled(bool enabled) {
// TODO: implement setDepthWriteEnabled
throw UnimplementedError();
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,3 @@
library;
export 'src/thermion_viewer_wasm.dart' show ThermionViewerWasm;

View File

@@ -0,0 +1,7 @@
library thermion_viewer;
export 'src/shared_types/shared_types.dart';
export 'src/thermion_viewer_base.dart';
export 'src/thermion_viewer_stub.dart'
if (dart.library.io) 'src/ffi/thermion_viewer_ffi.dart'
if (dart.library.js_interop) 'src/web_wasm/thermion_viewer_web_wasm.dart';