diff --git a/thermion_dart/lib/thermion_dart/compatibility/web/interop/thermion_viewer_dart_bridge.dart b/thermion_dart/lib/thermion_dart/compatibility/web/interop/thermion_viewer_dart_bridge.dart index 7d294280..120cdade 100644 --- a/thermion_dart/lib/thermion_dart/compatibility/web/interop/thermion_viewer_dart_bridge.dart +++ b/thermion_dart/lib/thermion_dart/compatibility/web/interop/thermion_viewer_dart_bridge.dart @@ -48,6 +48,11 @@ class ThermionViewerJSDartBridge { @JSExport() JSPromise render() => viewer.render().toJS; + @JSExport() + JSPromise capture() { + return viewer.capture().then((captured) => captured.toJS).toJS; + } + @JSExport() JSPromise setFrameRate(int framerate) => viewer.setFrameRate(framerate).toJS; @@ -342,15 +347,12 @@ class ThermionViewerJSDartBridge { double crossfade = 0.0, double startOffset = 0.0}) => viewer - .playAnimation( - entity, - index, - loop: loop, - reverse: reverse, - replaceActive: replaceActive, - crossfade: crossfade, - startOffset: startOffset - ) + .playAnimation(entity, index, + loop: loop, + reverse: reverse, + replaceActive: replaceActive, + crossfade: crossfade, + startOffset: startOffset) .toJS; @JSExport() diff --git a/thermion_dart/lib/thermion_dart/compatibility/web/interop/thermion_viewer_js.dart b/thermion_dart/lib/thermion_dart/compatibility/web/interop/thermion_viewer_js.dart index 11f5269f..2872e312 100644 --- a/thermion_dart/lib/thermion_dart/compatibility/web/interop/thermion_viewer_js.dart +++ b/thermion_dart/lib/thermion_dart/compatibility/web/interop/thermion_viewer_js.dart @@ -1,6 +1,7 @@ import 'dart:js_interop'; import 'dart:js_interop_unsafe'; import 'dart:math'; +import 'dart:typed_data'; import 'package:animation_tools_dart/animation_tools_dart.dart'; import 'package:logging/logging.dart'; @@ -371,7 +372,8 @@ class ThermionViewerJS implements ThermionViewer { double crossfade = 0.0, double startOffset = 0.0}) async { await _shim - .playAnimation(entity, index, loop, reverse, replaceActive, crossfade, startOffset) + .playAnimation( + entity, index, loop, reverse, replaceActive, crossfade, startOffset) .toDart; } @@ -849,4 +851,10 @@ class ThermionViewerJS implements ThermionViewer { Future setSoftShadowOptions(double penumbraScale, double penumbraRatioScale) { return _shim.setSoftShadowOptions(penumbraScale, penumbraRatioScale).toDart; } + + @override + Future capture() async { + final captured = await _shim.capture().toDart; + return captured.toDart; + } } diff --git a/thermion_dart/lib/thermion_dart/compatibility/web/interop/thermion_viewer_js_shim.dart b/thermion_dart/lib/thermion_dart/compatibility/web/interop/thermion_viewer_js_shim.dart index 50f0e44e..d4cf5dd2 100644 --- a/thermion_dart/lib/thermion_dart/compatibility/web/interop/thermion_viewer_js_shim.dart +++ b/thermion_dart/lib/thermion_dart/compatibility/web/interop/thermion_viewer_js_shim.dart @@ -22,6 +22,9 @@ extension type ThermionViewerJSShim(JSObject _) implements JSObject { @JS('render') external JSPromise render(); + @JS('capture') + external JSPromise capture(); + @JS('setFrameRate') external JSPromise setFrameRate(int framerate); diff --git a/thermion_dart/lib/thermion_dart/compatibility/web/interop/thermion_viewer_wasm.dart b/thermion_dart/lib/thermion_dart/compatibility/web/interop/thermion_viewer_wasm.dart index b0591521..429536ff 100644 --- a/thermion_dart/lib/thermion_dart/compatibility/web/interop/thermion_viewer_wasm.dart +++ b/thermion_dart/lib/thermion_dart/compatibility/web/interop/thermion_viewer_wasm.dart @@ -31,6 +31,9 @@ extension type _EmscriptenModule(JSObject _) implements JSObject { external void stringToUTF8( JSString str, JSNumber ptr, JSNumber maxBytesToWrite); external void writeArrayToMemory(JSUint8Array data, JSNumber ptr); + + external JSNumber addFunction(JSFunction f, String signature); + external void removeFunction(JSNumber f); external JSAny get ALLOC_STACK; external JSAny get HEAPU32; external JSAny get HEAP32; @@ -52,6 +55,8 @@ class ThermionViewerWasm implements ThermionViewer { String? assetPathPrefix; + late (double, double) viewportDimensions; + /// /// Construct an instance of this class by explicitly passing the /// module instance via the [module] property, or by specifying [moduleName], @@ -81,6 +86,8 @@ class ThermionViewerWasm implements ThermionViewer { JSNumber? _viewer; JSNumber? _sceneManager; + int _width = 0; + int _height = 0; Future initialize(int width, int height, {String? uberArchivePath}) async { if (!_initialized) { @@ -103,7 +110,7 @@ class ThermionViewerWasm implements ThermionViewer { "create_filament_viewer", "void*", ["void*".toJS, "void*".toJS, "void*".toJS, "string".toJS].toJS, - [context, loader, null, uberArchivePath?.toJS].toJS, + [context!, loader, null, uberArchivePath?.toJS].toJS, null) as JSNumber; await createSwapChain(width, height); updateViewportAndCameraProjection(width, height, 1.0); @@ -143,6 +150,9 @@ class ThermionViewerWasm implements ThermionViewer { void updateViewportAndCameraProjection( int width, int height, double scaleFactor) { + _width = width; + _height = height; + viewportDimensions = (width.toDouble(), height.toDouble()); _module!.ccall( "update_viewport_and_camera_projection", "void", @@ -772,6 +782,54 @@ class ThermionViewerWasm implements ThermionViewer { null); } + Future capture() async { + bool wasRendering = rendering; + await setRendering(false); + final pixelBuffer = _module!._malloc(_width * _height * 4) as JSNumber; + final completer = Completer(); + final callback = () { + print("Callback invoked!"); + completer.complete(); + }; + final callbackPtr = _module!.addFunction(callback.toJS, "v"); + + print("Aded functrion ${callbackPtr}, calling capture..."); + _module!.ccall( + "capture", + "void", + ["void*".toJS, "uint8_t*".toJS, "void*".toJS].toJS, + [_viewer!, pixelBuffer, callbackPtr].toJS, + null); + print("Waiting for completer..."); + int iter = 0; + while (true) { + await Future.delayed(Duration(milliseconds: 5)); + await render(); + if (completer.isCompleted) { + break; + } + iter++; + if (iter > 1000) { + _module!._free(pixelBuffer); + throw Exception("Failed to complete capture"); + } + } + + // Create a Uint8ClampedList to store the pixel data + var data = Uint8List(_width * _height * 4); + for (int i = 0; i < data.length; i++) { + data[i] = (_module!.getValue(((pixelBuffer.toDartInt) + i).toJS, "i8") + as JSNumber) + .toDartInt; + } + _module!._free(pixelBuffer); + await setRendering(wasRendering); + print("Captured to ${data.length} pixel buffer"); + _module!.removeFunction(callbackPtr); + + return data; + } + @override Scene get scene => throw UnimplementedError(); @@ -1330,21 +1388,21 @@ class ThermionViewerWasm implements ThermionViewer { } @override - Future panEnd() { - // TODO: implement panEnd - throw UnimplementedError(); + Future panEnd() async { + _module!.ccall("grab_end", "void", + ["void*".toJS].toJS, [_viewer!].toJS, null); } @override - Future panStart(double x, double y) { - // TODO: implement panStart - throw UnimplementedError(); + Future panStart(double x, double y) async { + _module!.ccall("grab_begin", "void", + ["void*".toJS, "float".toJS, "float".toJS, "bool".toJS].toJS, [_viewer!, x.toJS, y.toJS, true.toJS].toJS, null); } @override - Future panUpdate(double x, double y) { - // TODO: implement panUpdate - throw UnimplementedError(); + Future panUpdate(double x, double y) async { + _module!.ccall("grab_update", "void", + ["void*".toJS, "float".toJS, "float".toJS].toJS, [_viewer!, x.toJS, y.toJS].toJS, null); } @override @@ -1525,12 +1583,6 @@ class ThermionViewerWasm implements ThermionViewer { } } - @override - Future rotateEnd() { - // TODO: implement rotateEnd - throw UnimplementedError(); - } - @override Future rotateIbl(Matrix3 rotation) async { final ptr = _module!._malloc(9 * 4) as JSNumber; @@ -1544,15 +1596,21 @@ class ThermionViewerWasm implements ThermionViewer { } @override - Future rotateStart(double x, double y) { - // TODO: implement rotateStart - throw UnimplementedError(); + Future rotateStart(double x, double y) async { + _module!.ccall("grab_begin", "void", + ["void*".toJS, "float".toJS, "float".toJS, "bool".toJS].toJS, [_viewer!, x.toJS, y.toJS, false.toJS].toJS, null); } @override - Future rotateUpdate(double x, double y) { - // TODO: implement rotateUpdate - throw UnimplementedError(); + Future rotateUpdate(double x, double y) async { + _module!.ccall("grab_update", "void", + ["void*".toJS, "float".toJS, "float".toJS].toJS, [_viewer!, x.toJS, y.toJS].toJS, null); + } + + @override + Future rotateEnd() async { + _module!.ccall("grab_end", "void", + ["void*".toJS].toJS, [_viewer!].toJS, null); } @override @@ -1936,19 +1994,19 @@ class ThermionViewerWasm implements ThermionViewer { @override Future zoomBegin() async { _module!.ccall( - "zoom_begin", "void", ["void*".toJS].toJS, [_viewer!].toJS, null); + "scroll_begin", "void", ["void*".toJS].toJS, [_viewer!].toJS, null); } @override Future zoomEnd() async { _module! - .ccall("zoom_end", "void", ["void*".toJS].toJS, [_viewer!].toJS, null); + .ccall("scroll_end", "void", ["void*".toJS].toJS, [_viewer!].toJS, null); } @override Future zoomUpdate(double x, double y, double z) async { _module!.ccall( - "zoom_update", + "scroll_update", "void", ["void*".toJS, "float".toJS, "float".toJS, "float".toJS].toJS, [_viewer!, x.toJS, y.toJS, z.toJS].toJS,