implement picker/getNameForEntity
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -5,11 +6,11 @@ import 'package:flutter/material.dart';
|
||||
import 'package:polyvox_filament/filament_controller.dart';
|
||||
import 'package:polyvox_filament/animations/bone_animation_data.dart';
|
||||
import 'package:polyvox_filament/filament_controller_ffi.dart';
|
||||
import 'package:polyvox_filament/filament_gesture_detector.dart';
|
||||
import 'package:polyvox_filament/filament_widget.dart';
|
||||
import 'package:polyvox_filament/animations/animation_builder.dart';
|
||||
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:polyvox_filament/widgets/filament_gesture_detector.dart';
|
||||
import 'package:polyvox_filament/widgets/filament_widget.dart';
|
||||
|
||||
void main() {
|
||||
runApp(const MyApp());
|
||||
@@ -47,6 +48,9 @@ class _ExampleWidgetState extends State<ExampleWidget> {
|
||||
List<String>? _animations;
|
||||
FilamentEntity? _light;
|
||||
|
||||
late StreamSubscription _pickResultListener;
|
||||
String? picked;
|
||||
|
||||
final weights = List.filled(255, 0.0);
|
||||
|
||||
bool _loop = false;
|
||||
@@ -64,9 +68,16 @@ class _ExampleWidgetState extends State<ExampleWidget> {
|
||||
getApplicationSupportDirectory().then((dir) {
|
||||
print(dir);
|
||||
});
|
||||
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
_pickResultListener.cancel();
|
||||
}
|
||||
|
||||
Widget _item(void Function() onTap, String text) {
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
@@ -80,6 +91,16 @@ class _ExampleWidgetState extends State<ExampleWidget> {
|
||||
child: Text(text)));
|
||||
}
|
||||
|
||||
void _createController({String? uberArchivePath}) {
|
||||
_filamentController =
|
||||
FilamentControllerFFI(uberArchivePath: uberArchivePath);
|
||||
_filamentController!.pickResult.listen((entityId) {
|
||||
setState(() {
|
||||
picked = _filamentController!.getNameForEntity(entityId);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var children = <Widget>[];
|
||||
@@ -87,10 +108,10 @@ class _ExampleWidgetState extends State<ExampleWidget> {
|
||||
if (_filamentController == null) {
|
||||
children.addAll([
|
||||
_item(() {
|
||||
_filamentController = FilamentControllerFFI();
|
||||
_createController();
|
||||
}, "create viewer (default ubershader)"),
|
||||
_item(() {
|
||||
_filamentController = FilamentControllerFFI(
|
||||
_createController(
|
||||
uberArchivePath: Platform.isWindows
|
||||
? "assets/lit_opaque_32.uberz"
|
||||
: "assets/lit_opaque_43.uberz");
|
||||
@@ -318,6 +339,11 @@ class _ExampleWidgetState extends State<ExampleWidget> {
|
||||
controller: _filamentController!,
|
||||
)))
|
||||
: Container(),
|
||||
Positioned(
|
||||
right: 50,
|
||||
top: 50,
|
||||
child: Text(picked ?? "",
|
||||
style: TextStyle(color: Colors.green, fontSize: 24))),
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
|
||||
@@ -70,6 +70,7 @@ namespace polyvox {
|
||||
void setAnimationFrame(EntityId entity, int animationIndex, int animationFrame);
|
||||
bool hide(EntityId entity, const char* meshName);
|
||||
bool reveal(EntityId entity, const char* meshName);
|
||||
const char* getNameForEntity(EntityId entityId);
|
||||
|
||||
private:
|
||||
AssetLoader* _assetLoader = nullptr;
|
||||
|
||||
@@ -107,6 +107,8 @@ namespace polyvox {
|
||||
void removeLight(EntityId entityId);
|
||||
void clearLights();
|
||||
void setPostProcessing(bool enabled);
|
||||
|
||||
void pick(uint32_t x, uint32_t y, EntityId* entityId);
|
||||
|
||||
AssetManager* const getAssetManager() {
|
||||
return (AssetManager* const) _assetManager;
|
||||
|
||||
@@ -151,6 +151,8 @@ FLUTTER_PLUGIN_EXPORT void set_camera_focus_distance(const void* const viewer, f
|
||||
FLUTTER_PLUGIN_EXPORT int hide_mesh(void* assetManager, EntityId asset, const char* meshName);
|
||||
FLUTTER_PLUGIN_EXPORT int reveal_mesh(void* assetManager, EntityId asset, const char* meshName);
|
||||
FLUTTER_PLUGIN_EXPORT void set_post_processing(void* const viewer, bool enabled);
|
||||
FLUTTER_PLUGIN_EXPORT void pick(void* const viewer, int x, int y, EntityId* entityId);
|
||||
FLUTTER_PLUGIN_EXPORT const char* get_name_for_entity(void* const assetManager, const EntityId entityId);
|
||||
FLUTTER_PLUGIN_EXPORT void ios_dummy();
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
@@ -946,6 +946,16 @@ size_t AssetManager::getLightEntityCount(EntityId entity) const noexcept {
|
||||
return asset.mAsset->getLightEntityCount();
|
||||
}
|
||||
|
||||
const char* AssetManager::getNameForEntity(EntityId entityId) {
|
||||
const auto& entity = Entity::import(entityId);
|
||||
auto nameInstance = _ncm->getInstance(entity);
|
||||
if(!nameInstance.isValid()) {
|
||||
Log("Failed to find name instance for entity ID %d", entityId);
|
||||
return nullptr;
|
||||
}
|
||||
return _ncm->getName(nameInstance);
|
||||
}
|
||||
|
||||
|
||||
} // namespace polyvox
|
||||
|
||||
|
||||
@@ -113,6 +113,8 @@ static const uint16_t sFullScreenTriangleIndices[3] = {0, 1, 2};
|
||||
|
||||
FilamentViewer::FilamentViewer(const void* sharedContext, const ResourceLoaderWrapper* const resourceLoaderWrapper, void* const platform, const char* uberArchivePath)
|
||||
: _resourceLoaderWrapper(resourceLoaderWrapper) {
|
||||
|
||||
ASSERT_POSTCONDITION(_resourceLoaderWrapper != nullptr, "Resource loader must be non-null");
|
||||
|
||||
#if TARGET_OS_IPHONE
|
||||
ASSERT_POSTCONDITION(platform == nullptr, "Custom Platform not supported on iOS");
|
||||
@@ -630,7 +632,7 @@ void FilamentViewer::createRenderTarget(intptr_t texture, uint32_t width, uint32
|
||||
// Make a specific viewport just for our render target
|
||||
_view->setRenderTarget(_rt);
|
||||
|
||||
Log("Set render target for glTextureId %u %u x %u", texture, width, height);
|
||||
Log("Set render target for texture id %u to %u x %u", texture, width, height);
|
||||
|
||||
}
|
||||
|
||||
@@ -1051,7 +1053,16 @@ void FilamentViewer::scrollUpdate(float x, float y, float delta) {
|
||||
}
|
||||
|
||||
void FilamentViewer::scrollEnd() {
|
||||
|
||||
// noop
|
||||
}
|
||||
|
||||
void FilamentViewer::pick(uint32_t x, uint32_t y, EntityId* entityId) {
|
||||
Log("Picking at %d,%d", x, y);
|
||||
_view->pick(x, y, [=](filament::View::PickingQueryResult const & result) {
|
||||
|
||||
*entityId = Entity::smuggle(result.renderable);
|
||||
Log("Got result %d", *entityId);
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace polyvox
|
||||
|
||||
@@ -373,15 +373,24 @@ extern "C" {
|
||||
((AssetManager*)assetManager)->stopAnimation(asset, index);
|
||||
}
|
||||
|
||||
int hide_mesh(void* assetManager, EntityId asset, const char* meshName) {
|
||||
FLUTTER_PLUGIN_EXPORT int hide_mesh(void* assetManager, EntityId asset, const char* meshName) {
|
||||
return ((AssetManager*)assetManager)->hide(asset, meshName);
|
||||
}
|
||||
|
||||
int reveal_mesh(void* assetManager, EntityId asset, const char* meshName) {
|
||||
FLUTTER_PLUGIN_EXPORT int reveal_mesh(void* assetManager, EntityId asset, const char* meshName) {
|
||||
return ((AssetManager*)assetManager)->reveal(asset, meshName);
|
||||
}
|
||||
|
||||
FLUTTER_PLUGIN_EXPORT void ios_dummy() {
|
||||
|
||||
FLUTTER_PLUGIN_EXPORT void pick(void* const viewer, int x, int y, EntityId* entityId) {
|
||||
((FilamentViewer*)viewer)->pick(static_cast<uint32_t>(x), static_cast<uint32_t>(y), static_cast<int32_t*>(entityId));
|
||||
}
|
||||
|
||||
FLUTTER_PLUGIN_EXPORT const char* get_name_for_entity(void* const assetManager, const EntityId entityId) {
|
||||
return ((AssetManager*)assetManager)->getNameForEntity(entityId);
|
||||
}
|
||||
|
||||
FLUTTER_PLUGIN_EXPORT void ios_dummy() {
|
||||
Log("Dummy called");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -444,6 +444,16 @@ extern "C"
|
||||
fut.wait();
|
||||
}
|
||||
|
||||
FLUTTER_PLUGIN_EXPORT const char* get_name_for_entity_ffi(void* const assetManager, const EntityId entityId) {
|
||||
std::packaged_task<const char*()> lambda([&] {
|
||||
return get_name_for_entity(assetManager, entityId);
|
||||
});
|
||||
auto fut = _rl->add_task(lambda);
|
||||
fut.wait();
|
||||
return fut.get();
|
||||
}
|
||||
|
||||
|
||||
FLUTTER_PLUGIN_EXPORT void ios_dummy_ffi() {
|
||||
Log("Dummy called");
|
||||
}
|
||||
|
||||
@@ -1,159 +0,0 @@
|
||||
// import 'dart:async';
|
||||
|
||||
// import 'package:flutter/gestures.dart';
|
||||
// import 'package:flutter/material.dart';
|
||||
// import 'filament_controller.dart';
|
||||
// import 'filament_widget.dart';
|
||||
|
||||
// enum GestureType { RotateCamera, PanCamera, PanBackground }
|
||||
|
||||
// class AvatarGestureDetector extends StatefulWidget {
|
||||
// final AvatarInstance controller;
|
||||
// final bool showControls;
|
||||
|
||||
// const AvatarGestureDetector({
|
||||
// Key? key,
|
||||
// required this.controller,
|
||||
// this.showControls = false,
|
||||
// }) : super(key: key);
|
||||
|
||||
// @override
|
||||
// State<StatefulWidget> createState() => _AvatarGestureDetectorState();
|
||||
// }
|
||||
|
||||
// class _AvatarGestureDetectorState extends State<AvatarGestureDetector> {
|
||||
// GestureType gestureType = GestureType.PanCamera;
|
||||
|
||||
// final _icons = {
|
||||
// GestureType.PanBackground: Icons.image,
|
||||
// GestureType.PanCamera: Icons.pan_tool,
|
||||
// GestureType.RotateCamera: Icons.rotate_90_degrees_ccw
|
||||
// };
|
||||
|
||||
// // to avoid duplicating code for pan/rotate (panStart, panUpdate, panEnd, rotateStart, rotateUpdate etc)
|
||||
// // we have only a single function for start/update/end.
|
||||
// // when the gesture type is changed, these properties are updated to point to the correct function.
|
||||
// late Future Function(double x, double y) _functionStart;
|
||||
// late Future Function(double x, double y) _functionUpdate;
|
||||
// late Future Function() _functionEnd;
|
||||
|
||||
// double _lastScale = 0;
|
||||
|
||||
// @override
|
||||
// void initState() {
|
||||
// _setFunction();
|
||||
// super.initState();
|
||||
// }
|
||||
|
||||
// void _setFunction() {
|
||||
// switch (gestureType) {
|
||||
// case GestureType.RotateCamera:
|
||||
// _functionStart = widget.controller.rotateStart;
|
||||
// _functionUpdate = widget.controller.rotateUpdate;
|
||||
// _functionEnd = widget.controller.rotateEnd;
|
||||
// break;
|
||||
// case GestureType.PanCamera:
|
||||
// _functionStart = widget.controller.panStart;
|
||||
// _functionUpdate = widget.controller.panUpdate;
|
||||
// _functionEnd = widget.controller.panEnd;
|
||||
// break;
|
||||
// // TODO
|
||||
// case GestureType.PanBackground:
|
||||
// _functionStart = (x, y) async {};
|
||||
// _functionUpdate = (x, y) async {};
|
||||
// _functionEnd = () async {};
|
||||
// }
|
||||
// }
|
||||
|
||||
// @override
|
||||
// void didUpdateWidget(AvatarGestureDetector oldWidget) {
|
||||
// if (widget.showControls != oldWidget.showControls) {
|
||||
// setState(() {});
|
||||
// }
|
||||
|
||||
// super.didUpdateWidget(oldWidget);
|
||||
// }
|
||||
|
||||
// Timer? _scrollTimer;
|
||||
|
||||
// @override
|
||||
// Widget build(BuildContext context) {
|
||||
// return Stack(children: [
|
||||
// Positioned.fill(
|
||||
// // pinch zoom on mobile
|
||||
// // couldn't find any equivalent for pointerCount in Listener so we use two widgets:
|
||||
// // - outer is a GestureDetector only for pinch zoom
|
||||
// // - inner is a Listener for all other gestures
|
||||
// child: GestureDetector(
|
||||
// // onScaleStart: (d) async {
|
||||
// // if (d.pointerCount == 2) {
|
||||
// // await widget.controller.zoomEnd();
|
||||
// // await widget.controller.zoomBegin();
|
||||
// // }
|
||||
// // },
|
||||
// // onScaleEnd: (d) async {
|
||||
// // if (d.pointerCount == 2) {
|
||||
// // _lastScale = 0;
|
||||
// // await widget.controller.zoomEnd();
|
||||
// // }
|
||||
// // },
|
||||
// // onScaleUpdate: (d) async {
|
||||
// // if (d.pointerCount == 2) {
|
||||
// // if (_lastScale != 0) {
|
||||
// // await widget.controller
|
||||
// // .zoomUpdate(100 * (_lastScale - d.scale));
|
||||
// // }
|
||||
// // }
|
||||
// // _lastScale = d.scale;
|
||||
// // },
|
||||
// child: Listener(
|
||||
// onPointerSignal: (pointerSignal) async {
|
||||
// // scroll-wheel zoom on desktop
|
||||
// if (pointerSignal is PointerScrollEvent) {
|
||||
// _scrollTimer?.cancel();
|
||||
// await widget.controller.zoomBegin();
|
||||
// await widget.controller.zoomUpdate(
|
||||
// pointerSignal.scrollDelta.dy > 0 ? 10 : -10);
|
||||
// _scrollTimer = Timer(Duration(milliseconds: 100), () {
|
||||
// widget.controller.zoomEnd();
|
||||
// _scrollTimer = null;
|
||||
// });
|
||||
// } else {
|
||||
// print(pointerSignal);
|
||||
// }
|
||||
// },
|
||||
// onPointerPanZoomStart: (pzs) {},
|
||||
// onPointerDown: (d) async {
|
||||
// await _functionStart(
|
||||
// d.localPosition.dx, d.localPosition.dy);
|
||||
// },
|
||||
// onPointerMove: (d) async {
|
||||
// await _functionUpdate(
|
||||
// d.localPosition.dx, d.localPosition.dy);
|
||||
// },
|
||||
// onPointerUp: (d) async {
|
||||
// await _functionEnd();
|
||||
// },
|
||||
// child: widget.child))),
|
||||
// widget.showControls
|
||||
// ? Align(
|
||||
// alignment: Alignment.bottomRight,
|
||||
// child: GestureDetector(
|
||||
// onTap: () {
|
||||
// setState(() {
|
||||
// var curIdx = GestureType.values.indexOf(gestureType);
|
||||
// var nextIdx = curIdx == GestureType.values.length - 1
|
||||
// ? 0
|
||||
// : curIdx + 1;
|
||||
// gestureType = GestureType.values[nextIdx];
|
||||
// _setFunction();
|
||||
// });
|
||||
// },
|
||||
// child: Container(
|
||||
// padding: const EdgeInsets.all(50),
|
||||
// child: Icon(_icons[gestureType], color: Colors.green)),
|
||||
// ))
|
||||
// : Container()
|
||||
// ]);
|
||||
// }
|
||||
// }
|
||||
@@ -1,11 +1,8 @@
|
||||
import 'dart:async';
|
||||
import 'dart:ffi';
|
||||
import 'dart:io';
|
||||
import 'dart:ui' as ui;
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:polyvox_filament/animations/bone_animation_data.dart';
|
||||
import 'package:polyvox_filament/animations/morph_animation_data.dart';
|
||||
import 'package:polyvox_filament/generated_bindings.dart';
|
||||
|
||||
typedef FilamentEntity = int;
|
||||
const FilamentEntity FILAMENT_ASSET_ERROR = 0;
|
||||
@@ -18,9 +15,34 @@ abstract class FilamentController {
|
||||
|
||||
Stream<int?> get textureId;
|
||||
Future get isReadyForScene;
|
||||
|
||||
///
|
||||
/// 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<FilamentEntity?> get pickResult;
|
||||
|
||||
///
|
||||
/// Set to true to continuously render the scene at the framerate specified by [setFrameRate] (60 fps by default).
|
||||
///
|
||||
Future setRendering(bool render);
|
||||
|
||||
///
|
||||
/// Render a single frame.
|
||||
///
|
||||
Future render();
|
||||
|
||||
///
|
||||
/// Sets the framerate for continuous rendering when [setRendering] is enabled.
|
||||
///
|
||||
Future setFrameRate(int framerate);
|
||||
|
||||
///
|
||||
/// Called by FilamentGestureDetector to set the pixel ratio (obtained from [MediaQuery]) before creating the texture/viewport.
|
||||
/// You may call this yourself if you want to increase/decrease the pixel density of the viewport, but calling this method won't do anything on its own.
|
||||
/// You will need to manually recreate the texture/viewer afterwards.
|
||||
///
|
||||
void setPixelRatio(double ratio);
|
||||
|
||||
///
|
||||
@@ -80,24 +102,51 @@ abstract class FilamentController {
|
||||
double dirY,
|
||||
double dirZ,
|
||||
bool castShadows);
|
||||
|
||||
Future removeLight(FilamentEntity light);
|
||||
|
||||
///
|
||||
/// Remove all lights (excluding IBL) from the scene.
|
||||
///
|
||||
Future clearLights();
|
||||
|
||||
Future<FilamentEntity> loadGlb(String path, {bool unlit = false});
|
||||
|
||||
Future<FilamentEntity> loadGltf(String path, String relativeResourcePath);
|
||||
|
||||
///
|
||||
/// 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 under node [meshName] in [asset] to [weights].
|
||||
///
|
||||
Future setMorphTargetWeights(
|
||||
FilamentEntity asset, String meshName, List<double> weights);
|
||||
|
||||
@@ -112,6 +161,7 @@ abstract class FilamentController {
|
||||
Future<double> getAnimationDuration(FilamentEntity asset, int animationIndex);
|
||||
|
||||
///
|
||||
/// Create/start a dynamic morph target animation for [asset].
|
||||
/// Animates morph target weights/bone transforms (where each frame requires a duration of [frameLengthInMs].
|
||||
/// [morphWeights] is a list of doubles in frame-major format.
|
||||
/// Each frame is [numWeights] in length, and each entry is the weight to be applied to the morph target located at that index in the mesh primitive at that frame.
|
||||
@@ -126,11 +176,37 @@ abstract class FilamentController {
|
||||
/// for now we only allow animating a single bone (though multiple skinned targets are supported)
|
||||
///
|
||||
Future setBoneAnimation(FilamentEntity asset, BoneAnimationData animation);
|
||||
|
||||
///
|
||||
/// Removes/destroys the specified entity from the scene.
|
||||
/// [asset] will no longer be a valid handle after this method is called; ensure you immediately discard all references once this method is complete.
|
||||
///
|
||||
Future removeAsset(FilamentEntity asset);
|
||||
|
||||
///
|
||||
/// Removes/destroys all renderable entities from the scene (including cameras).
|
||||
/// All [FilamentEntity] 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 clearAssets();
|
||||
|
||||
///
|
||||
/// 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 z);
|
||||
|
||||
///
|
||||
/// Called by `FilamentGestureDetector`. You probably don't want to call this yourself.
|
||||
///
|
||||
Future zoomEnd();
|
||||
|
||||
///
|
||||
/// Schedules the glTF animation at [index] in [asset] to start playing on the next frame.
|
||||
///
|
||||
Future playAnimation(FilamentEntity asset, int index,
|
||||
{bool loop = false,
|
||||
bool reverse = false,
|
||||
@@ -138,13 +214,33 @@ abstract class FilamentController {
|
||||
double crossfade = 0.0});
|
||||
Future setAnimationFrame(FilamentEntity asset, int index, int animationFrame);
|
||||
Future stopAnimation(FilamentEntity asset, int animationIndex);
|
||||
|
||||
///
|
||||
/// Sets the current scene camera to the glTF camera under [name] in [asset].
|
||||
///
|
||||
Future setCamera(FilamentEntity asset, String? name);
|
||||
|
||||
///
|
||||
/// Sets the tone mapping (requires postprocessing).
|
||||
///
|
||||
Future setToneMapping(ToneMapper mapper);
|
||||
|
||||
///
|
||||
/// Sets the strength of the bloom.
|
||||
///
|
||||
Future setBloom(double bloom);
|
||||
Future setCameraFocalLength(double focalLength);
|
||||
Future setCameraFocusDistance(double focusDistance);
|
||||
Future setCameraPosition(double x, double y, double z);
|
||||
|
||||
///
|
||||
/// Repositions the camera to the last vertex of the bounding box of [asset], looking at the penultimate vertex.
|
||||
///
|
||||
Future moveCameraToAsset(FilamentEntity asset);
|
||||
|
||||
///
|
||||
/// Enables/disables frustum culling. Currently we don't expose a method for manipulating the camera projection/culling matrices so this is your only option to deal with unwanted near/far clipping.
|
||||
///
|
||||
Future setViewFrustumCulling(bool enabled);
|
||||
Future setCameraExposure(
|
||||
double aperture, double shutterSpeed, double sensitivity);
|
||||
@@ -153,12 +249,32 @@ abstract class FilamentController {
|
||||
|
||||
Future setMaterialColor(
|
||||
FilamentEntity asset, String meshName, int materialIndex, Color color);
|
||||
|
||||
///
|
||||
/// Scales [asset] up/down so it fits within a unit cube.
|
||||
///
|
||||
Future transformToUnitCube(FilamentEntity asset);
|
||||
|
||||
///
|
||||
/// Sets the world space position for [asset] to the given coordinates.
|
||||
///
|
||||
Future setPosition(FilamentEntity asset, double x, double y, double z);
|
||||
|
||||
///
|
||||
/// Enable/disable postprocessing.
|
||||
///
|
||||
Future setPostProcessing(bool enabled);
|
||||
Future setScale(FilamentEntity asset, double scale);
|
||||
Future setRotation(
|
||||
FilamentEntity asset, double rads, double x, double y, double z);
|
||||
Future hide(FilamentEntity asset, String meshName);
|
||||
Future reveal(FilamentEntity asset, 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 FilamentWidget).
|
||||
///
|
||||
void pick(int x, int y);
|
||||
}
|
||||
|
||||
@@ -32,6 +32,9 @@ class FilamentControllerFFI extends FilamentController {
|
||||
|
||||
final String? uberArchivePath;
|
||||
|
||||
Stream<FilamentEntity> get pickResult => _pickResultController.stream;
|
||||
final _pickResultController = StreamController<FilamentEntity>.broadcast();
|
||||
|
||||
///
|
||||
/// This controller uses platform channels to bridge Dart with the C/C++ code for the Filament API.
|
||||
/// Setting up the context/texture (since this is platform-specific) and the render ticker are platform-specific; all other methods are passed through by the platform channel to the methods specified in PolyvoxFilamentApi.h.
|
||||
@@ -115,6 +118,14 @@ class FilamentControllerFFI extends FilamentController {
|
||||
throw Exception(
|
||||
"Do not call createViewer when a viewer has already been created without calling destroyViewer");
|
||||
}
|
||||
var loader = Pointer<ResourceLoaderWrapper>.fromAddress(
|
||||
await _channel.invokeMethod("getResourceLoaderWrapper"));
|
||||
if (loader == nullptr) {
|
||||
throw Exception("Failed to get resource loader");
|
||||
}
|
||||
|
||||
print("Using loader ${loader.address}");
|
||||
|
||||
size = ui.Size(width * _pixelRatio, height * _pixelRatio);
|
||||
|
||||
print("Creating viewer with size $size");
|
||||
@@ -145,13 +156,12 @@ class FilamentControllerFFI extends FilamentController {
|
||||
|
||||
var sharedContext = await _channel.invokeMethod("getSharedContext");
|
||||
print("Got shared context : $sharedContext");
|
||||
var loader = await _channel.invokeMethod("getResourceLoaderWrapper");
|
||||
|
||||
_viewer = _lib.create_filament_viewer_ffi(
|
||||
Pointer<Void>.fromAddress(sharedContext ?? 0),
|
||||
driver,
|
||||
uberArchivePath?.toNativeUtf8().cast<Char>() ?? nullptr,
|
||||
Pointer<ResourceLoaderWrapper>.fromAddress(loader),
|
||||
loader,
|
||||
renderCallback,
|
||||
renderCallbackOwner);
|
||||
|
||||
@@ -166,6 +176,7 @@ class FilamentControllerFFI extends FilamentController {
|
||||
size.height.toInt());
|
||||
if (nativeTexture != 0) {
|
||||
assert(surfaceAddress == 0);
|
||||
print("Creating render target from native texture $nativeTexture");
|
||||
_lib.create_render_target_ffi(
|
||||
_viewer!, nativeTexture, size.width.toInt(), size.height.toInt());
|
||||
}
|
||||
@@ -338,9 +349,6 @@ class FilamentControllerFFI extends FilamentController {
|
||||
return asset;
|
||||
}
|
||||
|
||||
///
|
||||
/// Called by `FilamentGestureDetector`. You probably don't want to call this yourself.
|
||||
///
|
||||
@override
|
||||
Future panStart(double x, double y) async {
|
||||
if (_viewer == null || _resizing) {
|
||||
@@ -349,9 +357,6 @@ class FilamentControllerFFI extends FilamentController {
|
||||
_lib.grab_begin(_viewer!, x * _pixelRatio, y * _pixelRatio, true);
|
||||
}
|
||||
|
||||
///
|
||||
/// Called by `FilamentGestureDetector`. You probably don't want to call this yourself.
|
||||
///
|
||||
@override
|
||||
Future panUpdate(double x, double y) async {
|
||||
if (_viewer == null || _resizing) {
|
||||
@@ -360,9 +365,6 @@ class FilamentControllerFFI extends FilamentController {
|
||||
_lib.grab_update(_viewer!, x * _pixelRatio, y * _pixelRatio);
|
||||
}
|
||||
|
||||
///
|
||||
/// Called by `FilamentGestureDetector`. You probably don't want to call this yourself.
|
||||
///
|
||||
@override
|
||||
Future panEnd() async {
|
||||
if (_viewer == null || _resizing) {
|
||||
@@ -371,9 +373,6 @@ class FilamentControllerFFI extends FilamentController {
|
||||
_lib.grab_end(_viewer!);
|
||||
}
|
||||
|
||||
///
|
||||
/// Called by `FilamentGestureDetector`. You probably don't want to call this yourself.
|
||||
///
|
||||
@override
|
||||
Future rotateStart(double x, double y) async {
|
||||
if (_viewer == null || _resizing) {
|
||||
@@ -382,9 +381,6 @@ class FilamentControllerFFI extends FilamentController {
|
||||
_lib.grab_begin(_viewer!, x * _pixelRatio, y * _pixelRatio, false);
|
||||
}
|
||||
|
||||
///
|
||||
/// Called by `FilamentGestureDetector`. You probably don't want to call this yourself.
|
||||
///
|
||||
@override
|
||||
Future rotateUpdate(double x, double y) async {
|
||||
if (_viewer == null || _resizing) {
|
||||
@@ -393,9 +389,6 @@ class FilamentControllerFFI extends FilamentController {
|
||||
_lib.grab_update(_viewer!, x * _pixelRatio, y * _pixelRatio);
|
||||
}
|
||||
|
||||
///
|
||||
/// Called by `FilamentGestureDetector`. You probably don't want to call this yourself.
|
||||
///
|
||||
@override
|
||||
Future rotateEnd() async {
|
||||
if (_viewer == null || _resizing) {
|
||||
@@ -404,9 +397,6 @@ class FilamentControllerFFI extends FilamentController {
|
||||
_lib.grab_end(_viewer!);
|
||||
}
|
||||
|
||||
///
|
||||
/// Set the weights for all morph targets under node [meshName] in [asset] to [weights].
|
||||
///
|
||||
@override
|
||||
Future setMorphTargetWeights(
|
||||
FilamentEntity asset, String meshName, List<double> weights) async {
|
||||
@@ -458,9 +448,6 @@ class FilamentControllerFFI extends FilamentController {
|
||||
return names;
|
||||
}
|
||||
|
||||
///
|
||||
/// Returns the length (in seconds) of the animation at the given index.
|
||||
///
|
||||
@override
|
||||
Future<double> getAnimationDuration(
|
||||
FilamentEntity asset, int animationIndex) async {
|
||||
@@ -473,9 +460,6 @@ class FilamentControllerFFI extends FilamentController {
|
||||
return duration;
|
||||
}
|
||||
|
||||
///
|
||||
/// Create/start a dynamic morph target animation for [asset].
|
||||
///
|
||||
@override
|
||||
Future setMorphAnimationData(
|
||||
FilamentEntity asset, MorphAnimationData animation) async {
|
||||
@@ -506,12 +490,6 @@ class FilamentControllerFFI extends FilamentController {
|
||||
calloc.free(idxPtr);
|
||||
}
|
||||
|
||||
///
|
||||
/// Animates morph target weights/bone transforms (where each frame requires a duration of [frameLengthInMs].
|
||||
/// [morphWeights] is a list of doubles in frame-major format.
|
||||
/// Each frame is [numWeights] in length, and each entry is the weight to be applied to the morph target located at that index in the mesh primitive at that frame.
|
||||
/// for now we only allow animating a single bone (though multiple skinned targets are supported)
|
||||
///
|
||||
@override
|
||||
Future setBoneAnimation(
|
||||
FilamentEntity asset, BoneAnimationData animation) async {
|
||||
@@ -550,10 +528,6 @@ class FilamentControllerFFI extends FilamentController {
|
||||
// calloc.free(data);
|
||||
}
|
||||
|
||||
///
|
||||
/// Removes/destroys the specified entity from the scene.
|
||||
/// [asset] will no longer be a valid handle after this method is called; ensure you immediately discard all references once this method is complete.
|
||||
///
|
||||
@override
|
||||
Future removeAsset(FilamentEntity asset) async {
|
||||
if (_viewer == null || _resizing) {
|
||||
@@ -562,10 +536,6 @@ class FilamentControllerFFI extends FilamentController {
|
||||
_lib.remove_asset_ffi(_viewer!, asset);
|
||||
}
|
||||
|
||||
///
|
||||
/// Removes/destroys all renderable entities from the scene (including cameras).
|
||||
/// All [FilamentEntity] 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.
|
||||
///
|
||||
@override
|
||||
Future clearAssets() async {
|
||||
if (_viewer == null || _resizing) {
|
||||
@@ -574,9 +544,6 @@ class FilamentControllerFFI extends FilamentController {
|
||||
_lib.clear_assets_ffi(_viewer!);
|
||||
}
|
||||
|
||||
///
|
||||
/// Called by `FilamentGestureDetector`. You probably don't want to call this yourself.
|
||||
///
|
||||
@override
|
||||
Future zoomBegin() async {
|
||||
if (_viewer == null || _resizing) {
|
||||
@@ -585,9 +552,6 @@ class FilamentControllerFFI extends FilamentController {
|
||||
_lib.scroll_begin(_viewer!);
|
||||
}
|
||||
|
||||
///
|
||||
/// Called by `FilamentGestureDetector`. You probably don't want to call this yourself.
|
||||
///
|
||||
@override
|
||||
Future zoomUpdate(double z) async {
|
||||
if (_viewer == null || _resizing) {
|
||||
@@ -596,9 +560,6 @@ class FilamentControllerFFI extends FilamentController {
|
||||
_lib.scroll_update(_viewer!, 0.0, 0.0, z);
|
||||
}
|
||||
|
||||
///
|
||||
/// Called by `FilamentGestureDetector`. You probably don't want to call this yourself.
|
||||
///
|
||||
@override
|
||||
Future zoomEnd() async {
|
||||
if (_viewer == null || _resizing) {
|
||||
@@ -607,9 +568,6 @@ class FilamentControllerFFI extends FilamentController {
|
||||
_lib.scroll_end(_viewer!);
|
||||
}
|
||||
|
||||
///
|
||||
/// Schedules the glTF animation at [index] in [asset] to start playing on the next frame.
|
||||
///
|
||||
@override
|
||||
Future playAnimation(FilamentEntity asset, int index,
|
||||
{bool loop = false,
|
||||
@@ -638,9 +596,6 @@ class FilamentControllerFFI extends FilamentController {
|
||||
_lib.stop_animation(_assetManager!, asset, animationIndex);
|
||||
}
|
||||
|
||||
///
|
||||
/// Sets the current scene camera to the glTF camera under [name] in [asset].
|
||||
///
|
||||
@override
|
||||
Future setCamera(FilamentEntity asset, String? name) async {
|
||||
if (_viewer == null || _resizing) {
|
||||
@@ -653,9 +608,6 @@ class FilamentControllerFFI extends FilamentController {
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// Sets the tone mapping (requires postprocessing).
|
||||
///
|
||||
@override
|
||||
Future setToneMapping(ToneMapper mapper) async {
|
||||
if (_viewer == null || _resizing) {
|
||||
@@ -665,9 +617,6 @@ class FilamentControllerFFI extends FilamentController {
|
||||
_lib.set_tone_mapping_ffi(_viewer!, mapper.index);
|
||||
}
|
||||
|
||||
///
|
||||
/// Enable/disable postprocessing.
|
||||
///
|
||||
@override
|
||||
Future setPostProcessing(bool enabled) async {
|
||||
if (_viewer == null || _resizing) {
|
||||
@@ -677,9 +626,6 @@ class FilamentControllerFFI extends FilamentController {
|
||||
_lib.set_post_processing_ffi(_viewer!, enabled);
|
||||
}
|
||||
|
||||
///
|
||||
/// Sets the strength of the bloom.
|
||||
///
|
||||
@override
|
||||
Future setBloom(double bloom) async {
|
||||
if (_viewer == null || _resizing) {
|
||||
@@ -716,9 +662,6 @@ class FilamentControllerFFI extends FilamentController {
|
||||
_lib.move_camera_to_asset(_viewer!, asset);
|
||||
}
|
||||
|
||||
///
|
||||
/// Enables/disables frustum culling. Currently we don't expose a method for manipulating the camera projection/culling matrices so this is your only option to deal with unwanted near/far clipping.
|
||||
///
|
||||
@override
|
||||
Future setViewFrustumCulling(bool enabled) async {
|
||||
if (_viewer == null || _resizing) {
|
||||
@@ -822,4 +765,31 @@ class FilamentControllerFFI extends FilamentController {
|
||||
throw Exception("Failed to reveal mesh $meshName");
|
||||
}
|
||||
}
|
||||
|
||||
String? getNameForEntity(FilamentEntity entity) {
|
||||
final result = _lib.get_name_for_entity(_assetManager!, entity);
|
||||
if (result == nullptr) {
|
||||
return null;
|
||||
}
|
||||
return result.cast<Utf8>().toDartString();
|
||||
}
|
||||
|
||||
void pick(int x, int y) async {
|
||||
if (_viewer == null || _resizing) {
|
||||
throw Exception("No viewer available, ignoring");
|
||||
}
|
||||
final outPtr = calloc<EntityId>(1);
|
||||
outPtr.value = 0;
|
||||
print("height ${size.height.toInt()} y $y");
|
||||
|
||||
_lib.pick_ffi(_viewer!, x, size.height.toInt() - y, outPtr);
|
||||
while (outPtr.value == 0) {
|
||||
await Future.delayed(Duration(milliseconds: 100));
|
||||
print("Waiting");
|
||||
}
|
||||
|
||||
var entityId = outPtr.value;
|
||||
_pickResultController.add(entityId);
|
||||
calloc.free(outPtr);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -650,4 +650,13 @@ class FilamentControllerMethodChannel extends FilamentController {
|
||||
// TODO: implement destroy
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
void pick(int x, int y) {
|
||||
// TODO: implement pick
|
||||
}
|
||||
|
||||
@override
|
||||
// TODO: implement pickResult
|
||||
Stream<FilamentEntity?> get pickResult => throw UnimplementedError();
|
||||
}
|
||||
|
||||
@@ -1321,6 +1321,44 @@ class NativeLibrary {
|
||||
late final _set_post_processing = _set_post_processingPtr
|
||||
.asFunction<void Function(ffi.Pointer<ffi.Void>, bool)>();
|
||||
|
||||
void pick(
|
||||
ffi.Pointer<ffi.Void> viewer,
|
||||
int x,
|
||||
int y,
|
||||
ffi.Pointer<EntityId> entityId,
|
||||
) {
|
||||
return _pick(
|
||||
viewer,
|
||||
x,
|
||||
y,
|
||||
entityId,
|
||||
);
|
||||
}
|
||||
|
||||
late final _pickPtr = _lookup<
|
||||
ffi.NativeFunction<
|
||||
ffi.Void Function(ffi.Pointer<ffi.Void>, ffi.Int, ffi.Int,
|
||||
ffi.Pointer<EntityId>)>>('pick');
|
||||
late final _pick = _pickPtr.asFunction<
|
||||
void Function(ffi.Pointer<ffi.Void>, int, int, ffi.Pointer<EntityId>)>();
|
||||
|
||||
ffi.Pointer<ffi.Char> get_name_for_entity(
|
||||
ffi.Pointer<ffi.Void> assetManager,
|
||||
int entityId,
|
||||
) {
|
||||
return _get_name_for_entity(
|
||||
assetManager,
|
||||
entityId,
|
||||
);
|
||||
}
|
||||
|
||||
late final _get_name_for_entityPtr = _lookup<
|
||||
ffi.NativeFunction<
|
||||
ffi.Pointer<ffi.Char> Function(
|
||||
ffi.Pointer<ffi.Void>, EntityId)>>('get_name_for_entity');
|
||||
late final _get_name_for_entity = _get_name_for_entityPtr
|
||||
.asFunction<ffi.Pointer<ffi.Char> Function(ffi.Pointer<ffi.Void>, int)>();
|
||||
|
||||
void ios_dummy() {
|
||||
return _ios_dummy();
|
||||
}
|
||||
@@ -1412,7 +1450,7 @@ class NativeLibrary {
|
||||
|
||||
late final _create_render_target_ffiPtr = _lookup<
|
||||
ffi.NativeFunction<
|
||||
ffi.Void Function(ffi.Pointer<ffi.Void>, ffi.Uint32, ffi.Uint32,
|
||||
ffi.Void Function(ffi.Pointer<ffi.Void>, ffi.IntPtr, ffi.Uint32,
|
||||
ffi.Uint32)>>('create_render_target_ffi');
|
||||
late final _create_render_target_ffi = _create_render_target_ffiPtr
|
||||
.asFunction<void Function(ffi.Pointer<ffi.Void>, int, int, int)>();
|
||||
@@ -2173,6 +2211,27 @@ class NativeLibrary {
|
||||
late final _set_post_processing_ffi = _set_post_processing_ffiPtr
|
||||
.asFunction<void Function(ffi.Pointer<ffi.Void>, bool)>();
|
||||
|
||||
void pick_ffi(
|
||||
ffi.Pointer<ffi.Void> viewer,
|
||||
int x,
|
||||
int y,
|
||||
ffi.Pointer<EntityId> entityId,
|
||||
) {
|
||||
return _pick_ffi(
|
||||
viewer,
|
||||
x,
|
||||
y,
|
||||
entityId,
|
||||
);
|
||||
}
|
||||
|
||||
late final _pick_ffiPtr = _lookup<
|
||||
ffi.NativeFunction<
|
||||
ffi.Void Function(ffi.Pointer<ffi.Void>, ffi.Int, ffi.Int,
|
||||
ffi.Pointer<EntityId>)>>('pick_ffi');
|
||||
late final _pick_ffi = _pick_ffiPtr.asFunction<
|
||||
void Function(ffi.Pointer<ffi.Void>, int, int, ffi.Pointer<EntityId>)>();
|
||||
|
||||
void ios_dummy_ffi() {
|
||||
return _ios_dummy_ffi();
|
||||
}
|
||||
@@ -2306,6 +2365,9 @@ typedef LoadFilamentResourceFromOwner = ffi.Pointer<
|
||||
typedef FreeFilamentResourceFromOwner = ffi.Pointer<
|
||||
ffi
|
||||
.NativeFunction<ffi.Void Function(ResourceBuffer, ffi.Pointer<ffi.Void>)>>;
|
||||
|
||||
/// This header replicates most of the methods in PolyvoxFilamentApi.h, and is only intended to be used to generate client FFI bindings.
|
||||
/// The intention is that calling one of these methods will call its respective method in PolyvoxFilamentApi.h, but wrapped in some kind of thread runner to ensure thread safety.
|
||||
typedef EntityId = ffi.Int32;
|
||||
typedef FilamentRenderCallback = ffi.Pointer<
|
||||
ffi.NativeFunction<ffi.Void Function(ffi.Pointer<ffi.Void> owner)>>;
|
||||
|
||||
72
lib/widgets/filament_gesture_detector.dart
Normal file
72
lib/widgets/filament_gesture_detector.dart
Normal file
@@ -0,0 +1,72 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:polyvox_filament/widgets/filament_gesture_detector_desktop.dart';
|
||||
import 'package:polyvox_filament/widgets/filament_gesture_detector_mobile.dart';
|
||||
import '../filament_controller.dart';
|
||||
|
||||
enum GestureType { RotateCamera, PanCamera, PanBackground }
|
||||
|
||||
///
|
||||
/// A widget that translates finger/mouse gestures to zoom/pan/rotate actions.
|
||||
///
|
||||
class FilamentGestureDetector extends StatelessWidget {
|
||||
///
|
||||
/// The content to display below the gesture detector/listener widget.
|
||||
/// This will usually be a FilamentWidget (so you can navigate by directly interacting with the viewport), but this is not necessary.
|
||||
/// It is equally possible to render the viewport/gesture controls elsewhere in the widget hierarchy. The only requirement is that they share the same [FilamentController].
|
||||
///
|
||||
final Widget? child;
|
||||
|
||||
///
|
||||
/// The [controller] attached to the [FilamentWidget] you wish to control.
|
||||
///
|
||||
final FilamentController controller;
|
||||
|
||||
///
|
||||
/// If true, an overlay will be shown with buttons to toggle whether pointer movements are interpreted as:
|
||||
/// 1) rotate or a pan (mobile only),
|
||||
/// 2) moving the camera or the background image (TODO).
|
||||
///
|
||||
final bool showControlOverlay;
|
||||
|
||||
///
|
||||
/// If false, all gestures will be ignored.
|
||||
///
|
||||
final bool listenerEnabled;
|
||||
|
||||
final double zoomDelta;
|
||||
|
||||
const FilamentGestureDetector(
|
||||
{Key? key,
|
||||
required this.controller,
|
||||
this.child,
|
||||
this.showControlOverlay = false,
|
||||
this.listenerEnabled = true,
|
||||
this.zoomDelta = 1})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (kIsWeb) {
|
||||
throw Exception("TODO");
|
||||
} else if (Platform.isLinux || Platform.isWindows || Platform.isMacOS) {
|
||||
return FilamentGestureDetectorDesktop(
|
||||
controller: controller,
|
||||
child: child,
|
||||
showControlOverlay: showControlOverlay,
|
||||
listenerEnabled: listenerEnabled,
|
||||
);
|
||||
} else {
|
||||
return FilamentGestureDetectorMobile(
|
||||
controller: controller,
|
||||
child: child,
|
||||
showControlOverlay: showControlOverlay,
|
||||
listenerEnabled: listenerEnabled,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
144
lib/widgets/filament_gesture_detector_desktop.dart
Normal file
144
lib/widgets/filament_gesture_detector_desktop.dart
Normal file
@@ -0,0 +1,144 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import '../filament_controller.dart';
|
||||
|
||||
///
|
||||
/// A widget that translates finger/mouse gestures to zoom/pan/rotate actions.
|
||||
///
|
||||
class FilamentGestureDetectorDesktop extends StatefulWidget {
|
||||
///
|
||||
/// The content to display below the gesture detector/listener widget.
|
||||
/// This will usually be a FilamentWidget (so you can navigate by directly interacting with the viewport), but this is not necessary.
|
||||
/// It is equally possible to render the viewport/gesture controls elsewhere in the widget hierarchy. The only requirement is that they share the same [FilamentController].
|
||||
///
|
||||
final Widget? child;
|
||||
|
||||
///
|
||||
/// The [controller] attached to the [FilamentWidget] you wish to control.
|
||||
///
|
||||
final FilamentController controller;
|
||||
|
||||
///
|
||||
/// If true, an overlay will be shown with buttons to toggle whether pointer movements are interpreted as:
|
||||
/// 1) rotate or a pan (mobile only),
|
||||
/// 2) moving the camera or the background image (TODO).
|
||||
///
|
||||
final bool showControlOverlay;
|
||||
|
||||
///
|
||||
/// If false, all gestures will be ignored.
|
||||
///
|
||||
final bool listenerEnabled;
|
||||
|
||||
final double zoomDelta;
|
||||
|
||||
const FilamentGestureDetectorDesktop(
|
||||
{Key? key,
|
||||
required this.controller,
|
||||
this.child,
|
||||
this.showControlOverlay = false,
|
||||
this.listenerEnabled = true,
|
||||
this.zoomDelta = 1})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => _FilamentGestureDetectorDesktopState();
|
||||
}
|
||||
|
||||
class _FilamentGestureDetectorDesktopState
|
||||
extends State<FilamentGestureDetectorDesktop> {
|
||||
///
|
||||
///
|
||||
///
|
||||
bool _scaling = false;
|
||||
|
||||
bool _pointerMoving = false;
|
||||
|
||||
@override
|
||||
void didUpdateWidget(FilamentGestureDetectorDesktop oldWidget) {
|
||||
if (widget.showControlOverlay != oldWidget.showControlOverlay ||
|
||||
widget.listenerEnabled != oldWidget.listenerEnabled) {
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
super.didUpdateWidget(oldWidget);
|
||||
}
|
||||
|
||||
Timer? _scrollTimer;
|
||||
|
||||
///
|
||||
/// Scroll-wheel on desktop, interpreted as zoom
|
||||
///
|
||||
void _zoom(PointerScrollEvent pointerSignal) {
|
||||
_scrollTimer?.cancel();
|
||||
widget.controller.zoomBegin();
|
||||
widget.controller.zoomUpdate(pointerSignal.scrollDelta.dy > 0
|
||||
? widget.zoomDelta
|
||||
: -widget.zoomDelta);
|
||||
_scrollTimer = Timer(const Duration(milliseconds: 100), () {
|
||||
widget.controller.zoomEnd();
|
||||
_scrollTimer = null;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (!widget.listenerEnabled) {
|
||||
return widget.child ?? Container();
|
||||
}
|
||||
return Listener(
|
||||
onPointerSignal: (PointerSignalEvent pointerSignal) async {
|
||||
if (pointerSignal is PointerScrollEvent) {
|
||||
_zoom(pointerSignal);
|
||||
} else {
|
||||
throw Exception("TODO");
|
||||
}
|
||||
},
|
||||
onPointerPanZoomStart: (pzs) {
|
||||
throw Exception("TODO - is this a pinch zoom on laptop trackpad?");
|
||||
},
|
||||
// ignore all pointer down events
|
||||
// so we can wait to see if the pointer will be held/moved (interpreted as rotate/pan),
|
||||
// or if this is a single mousedown event (interpreted as viewport pick)
|
||||
onPointerDown: (d) async {},
|
||||
// holding/moving the left mouse button is interpreted as a pan, middle mouse button as a rotate
|
||||
onPointerMove: (PointerMoveEvent d) async {
|
||||
// if this is the first move event, we need to call rotateStart/panStart to set the first coordinates
|
||||
if (!_pointerMoving) {
|
||||
if (d.buttons == kTertiaryButton) {
|
||||
widget.controller.rotateStart(d.position.dx, d.position.dy);
|
||||
} else {
|
||||
widget.controller
|
||||
.panStart(d.localPosition.dx, d.localPosition.dy);
|
||||
}
|
||||
}
|
||||
// set the _pointerMoving flag so we don't call rotateStart/panStart on future move events
|
||||
_pointerMoving = true;
|
||||
if (d.buttons == kTertiaryButton) {
|
||||
widget.controller.rotateUpdate(d.position.dx, d.position.dy);
|
||||
} else {
|
||||
widget.controller.panUpdate(d.localPosition.dx, d.localPosition.dy);
|
||||
}
|
||||
},
|
||||
// when the left mouse button is released:
|
||||
// 1) if _pointerMoving is true, this completes the pan
|
||||
// 2) if _pointerMoving is false, this is interpreted as a pick
|
||||
// same applies to middle mouse button, but this is ignored as a pick
|
||||
onPointerUp: (PointerUpEvent d) async {
|
||||
if (d.buttons == kTertiaryButton) {
|
||||
widget.controller.rotateEnd();
|
||||
} else {
|
||||
if (_pointerMoving) {
|
||||
widget.controller.panEnd();
|
||||
} else {
|
||||
widget.controller
|
||||
.pick(d.localPosition.dx.toInt(), d.localPosition.dy.toInt());
|
||||
}
|
||||
}
|
||||
_pointerMoving = false;
|
||||
},
|
||||
child: widget.child);
|
||||
}
|
||||
}
|
||||
@@ -1,34 +1,54 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'filament_controller.dart';
|
||||
import '../filament_controller.dart';
|
||||
|
||||
enum GestureType { RotateCamera, PanCamera, PanBackground }
|
||||
|
||||
class FilamentGestureDetector extends StatefulWidget {
|
||||
///
|
||||
/// A widget that translates finger/mouse gestures to zoom/pan/rotate actions.
|
||||
///
|
||||
class FilamentGestureDetectorMobile extends StatefulWidget {
|
||||
///
|
||||
/// The content to display below the gesture detector/listener widget.
|
||||
/// This will usually be a FilamentWidget (so you can navigate by directly interacting with the viewport), but this is not necessary.
|
||||
/// It is equally possible to render the viewport/gesture controls elsewhere in the widget hierarchy. The only requirement is that they share the same [FilamentController].
|
||||
///
|
||||
final Widget? child;
|
||||
|
||||
///
|
||||
/// The [controller] attached to the [FilamentWidget] you wish to control.
|
||||
///
|
||||
final FilamentController controller;
|
||||
|
||||
///
|
||||
/// If true, an overlay will be shown with buttons to toggle whether pointer movements are interpreted as:
|
||||
/// 1) rotate or a pan (mobile only),
|
||||
/// 2) moving the camera or the background image (TODO).
|
||||
///
|
||||
final bool showControlOverlay;
|
||||
final bool enableControls;
|
||||
|
||||
///
|
||||
/// If false, all gestures will be ignored.
|
||||
///
|
||||
final bool listenerEnabled;
|
||||
|
||||
final double zoomDelta;
|
||||
|
||||
const FilamentGestureDetector(
|
||||
const FilamentGestureDetectorMobile(
|
||||
{Key? key,
|
||||
required this.controller,
|
||||
this.child,
|
||||
this.showControlOverlay = false,
|
||||
this.enableControls = true,
|
||||
this.listenerEnabled = true,
|
||||
this.zoomDelta = 1})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => _FilamentGestureDetectorState();
|
||||
State<StatefulWidget> createState() => _FilamentGestureDetectorMobileState();
|
||||
}
|
||||
|
||||
class _FilamentGestureDetectorState extends State<FilamentGestureDetector> {
|
||||
class _FilamentGestureDetectorMobileState
|
||||
extends State<FilamentGestureDetectorMobile> {
|
||||
GestureType gestureType = GestureType.PanCamera;
|
||||
|
||||
final _icons = {
|
||||
@@ -37,7 +57,14 @@ class _FilamentGestureDetectorState extends State<FilamentGestureDetector> {
|
||||
GestureType.RotateCamera: Icons.rotate_90_degrees_ccw
|
||||
};
|
||||
|
||||
bool _rotating = false;
|
||||
// on mobile, we can't differentiate between pointer down events like we do on desktop with primary/secondary/tertiary buttons
|
||||
// we allow the user to toggle between panning and rotating by double-tapping the widget
|
||||
bool _rotateOnPointerMove = false;
|
||||
|
||||
//
|
||||
//
|
||||
//
|
||||
bool _scaling = false;
|
||||
|
||||
// to avoid duplicating code for pan/rotate (panStart, panUpdate, panEnd, rotateStart, rotateUpdate etc)
|
||||
// we have only a single function for start/update/end.
|
||||
@@ -73,9 +100,9 @@ class _FilamentGestureDetectorState extends State<FilamentGestureDetector> {
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(FilamentGestureDetector oldWidget) {
|
||||
void didUpdateWidget(FilamentGestureDetectorMobile oldWidget) {
|
||||
if (widget.showControlOverlay != oldWidget.showControlOverlay ||
|
||||
widget.enableControls != oldWidget.enableControls) {
|
||||
widget.listenerEnabled != oldWidget.listenerEnabled) {
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
@@ -84,79 +111,31 @@ class _FilamentGestureDetectorState extends State<FilamentGestureDetector> {
|
||||
|
||||
Timer? _scrollTimer;
|
||||
|
||||
Widget _desktop() {
|
||||
return Listener(
|
||||
onPointerSignal: !widget.enableControls
|
||||
? null
|
||||
: (pointerSignal) async {
|
||||
// scroll-wheel zoom on desktop
|
||||
if (pointerSignal is PointerScrollEvent) {
|
||||
_scrollTimer?.cancel();
|
||||
widget.controller.zoomBegin();
|
||||
widget.controller.zoomUpdate(pointerSignal.scrollDelta.dy > 0
|
||||
? widget.zoomDelta
|
||||
: -widget.zoomDelta);
|
||||
_scrollTimer = Timer(Duration(milliseconds: 100), () {
|
||||
widget.controller.zoomEnd();
|
||||
_scrollTimer = null;
|
||||
});
|
||||
}
|
||||
},
|
||||
onPointerPanZoomStart: !widget.enableControls ? null : (pzs) {},
|
||||
onPointerDown: !widget.enableControls
|
||||
? null
|
||||
: (d) async {
|
||||
if (d.buttons == kTertiaryButton || _rotating) {
|
||||
widget.controller.rotateStart(
|
||||
d.position.dx * 2.0,
|
||||
d.position.dy *
|
||||
2.0); // multiply by 2.0 to account for pixel density, TODO don't hardcode
|
||||
} else {
|
||||
widget.controller
|
||||
.panStart(d.localPosition.dx, d.localPosition.dy);
|
||||
}
|
||||
},
|
||||
onPointerMove: !widget.enableControls
|
||||
? null
|
||||
: (PointerMoveEvent d) async {
|
||||
if (d.buttons == kTertiaryButton || _rotating) {
|
||||
widget.controller
|
||||
.rotateUpdate(d.position.dx * 2.0, d.position.dy * 2.0);
|
||||
} else {
|
||||
widget.controller
|
||||
.panUpdate(d.localPosition.dx, d.localPosition.dy);
|
||||
}
|
||||
},
|
||||
onPointerUp: !widget.enableControls
|
||||
? null
|
||||
: (d) async {
|
||||
if (d.buttons == kTertiaryButton || _rotating) {
|
||||
widget.controller.rotateEnd();
|
||||
} else {
|
||||
widget.controller.panEnd();
|
||||
}
|
||||
},
|
||||
child: widget.child);
|
||||
}
|
||||
// pinch zoom on mobile
|
||||
// couldn't find any equivalent for pointerCount in Listener so we use two widgets:
|
||||
// - outer is a GestureDetector only for pinch zoom
|
||||
// - inner is a Listener for all other gestures (including scroll zoom on desktop)
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (!widget.listenerEnabled) {
|
||||
return widget.child ?? Container();
|
||||
}
|
||||
|
||||
bool _scaling = false;
|
||||
double _lastScale = 0;
|
||||
DateTime _lastUpdate = DateTime.now();
|
||||
Widget _mobile() {
|
||||
return GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onDoubleTap: () {
|
||||
_rotating = !_rotating;
|
||||
},
|
||||
onScaleStart: !widget.enableControls
|
||||
? null
|
||||
: (d) async {
|
||||
return Stack(children: [
|
||||
Positioned.fill(
|
||||
child: GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onDoubleTap: () {
|
||||
setState(() {
|
||||
_rotateOnPointerMove = !_rotateOnPointerMove;
|
||||
});
|
||||
},
|
||||
onScaleStart: (d) async {
|
||||
if (d.pointerCount == 2) {
|
||||
_lastScale = 0;
|
||||
_scaling = true;
|
||||
widget.controller.zoomBegin();
|
||||
} else if (!_scaling) {
|
||||
if (_rotating) {
|
||||
if (_rotateOnPointerMove) {
|
||||
widget.controller
|
||||
.rotateStart(d.focalPoint.dx, d.focalPoint.dy);
|
||||
} else {
|
||||
@@ -165,32 +144,24 @@ class _FilamentGestureDetectorState extends State<FilamentGestureDetector> {
|
||||
}
|
||||
}
|
||||
},
|
||||
onScaleEnd: !widget.enableControls
|
||||
? null
|
||||
: (d) async {
|
||||
onScaleEnd: (d) async {
|
||||
if (d.pointerCount == 2) {
|
||||
widget.controller.zoomEnd();
|
||||
_lastScale = 0;
|
||||
_scaling = false;
|
||||
} else if (!_scaling) {
|
||||
if (_rotating) {
|
||||
if (_rotateOnPointerMove) {
|
||||
widget.controller.rotateEnd();
|
||||
} else {
|
||||
widget.controller.panEnd();
|
||||
}
|
||||
}
|
||||
},
|
||||
onScaleUpdate: !widget.enableControls
|
||||
? null
|
||||
: (ScaleUpdateDetails d) async {
|
||||
onScaleUpdate: (ScaleUpdateDetails d) async {
|
||||
if (d.pointerCount == 2) {
|
||||
// var scale = d.horizontalScale - _lastScale;
|
||||
// print(scale);
|
||||
widget.controller
|
||||
.zoomUpdate(d.horizontalScale > 1 ? 0.1 : -0.1);
|
||||
_lastScale = d.horizontalScale;
|
||||
} else if (!_scaling) {
|
||||
if (_rotating) {
|
||||
if (_rotateOnPointerMove) {
|
||||
widget.controller
|
||||
.rotateUpdate(d.focalPoint.dx, d.focalPoint.dy);
|
||||
} else {
|
||||
@@ -199,27 +170,7 @@ class _FilamentGestureDetectorState extends State<FilamentGestureDetector> {
|
||||
}
|
||||
}
|
||||
},
|
||||
child: widget.child);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
late Widget controls;
|
||||
if (kIsWeb) {
|
||||
controls = Container();
|
||||
} else if (Platform.isLinux || Platform.isWindows || Platform.isMacOS) {
|
||||
controls = _desktop();
|
||||
} else {
|
||||
controls = _mobile();
|
||||
}
|
||||
|
||||
return Stack(children: [
|
||||
Positioned.fill(
|
||||
// pinch zoom on mobile
|
||||
// couldn't find any equivalent for pointerCount in Listener so we use two widgets:
|
||||
// - outer is a GestureDetector only for pinch zoom
|
||||
// - inner is a Listener for all other gestures (including scroll zoom on desktop)
|
||||
child: controls),
|
||||
child: widget.child)),
|
||||
widget.showControlOverlay
|
||||
? Align(
|
||||
alignment: Alignment.bottomRight,
|
||||
@@ -4,12 +4,10 @@ import 'dart:math';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter/scheduler.dart';
|
||||
|
||||
import 'package:polyvox_filament/filament_controller.dart';
|
||||
|
||||
import 'dart:async';
|
||||
import 'filament_controller_method_channel.dart';
|
||||
|
||||
typedef ResizeCallback = void Function(Size oldSize, Size newSize);
|
||||
|
||||
@@ -18,9 +18,10 @@ namespace polyvox {
|
||||
class AssetManager {
|
||||
public:
|
||||
AssetManager(const ResourceLoaderWrapper* const loader,
|
||||
NameComponentManager *ncm,
|
||||
Engine *engine,
|
||||
Scene *scene);
|
||||
NameComponentManager* ncm,
|
||||
Engine* engine,
|
||||
Scene* scene,
|
||||
const char* uberArchivePath);
|
||||
~AssetManager();
|
||||
EntityId loadGltf(const char* uri, const char* relativeResourcePath);
|
||||
EntityId loadGlb(const char* uri, bool unlit);
|
||||
@@ -69,6 +70,7 @@ namespace polyvox {
|
||||
void setAnimationFrame(EntityId entity, int animationIndex, int animationFrame);
|
||||
bool hide(EntityId entity, const char* meshName);
|
||||
bool reveal(EntityId entity, const char* meshName);
|
||||
const char* getNameForEntity(EntityId entityId);
|
||||
|
||||
private:
|
||||
AssetLoader* _assetLoader = nullptr;
|
||||
|
||||
@@ -49,7 +49,7 @@ namespace polyvox {
|
||||
|
||||
class FilamentViewer {
|
||||
public:
|
||||
FilamentViewer(const void* context, const ResourceLoaderWrapper* const resourceLoaderWrapper, void* const platform=nullptr);
|
||||
FilamentViewer(const void* context, const ResourceLoaderWrapper* const resourceLoaderWrapper, void* const platform=nullptr, const char* uberArchivePath=nullptr);
|
||||
~FilamentViewer();
|
||||
|
||||
void setToneMapping(ToneMapping toneMapping);
|
||||
@@ -107,6 +107,8 @@ namespace polyvox {
|
||||
void removeLight(EntityId entityId);
|
||||
void clearLights();
|
||||
void setPostProcessing(bool enabled);
|
||||
|
||||
void pick(uint32_t x, uint32_t y, EntityId* entityId);
|
||||
|
||||
AssetManager* const getAssetManager() {
|
||||
return (AssetManager* const) _assetManager;
|
||||
|
||||
@@ -53,7 +53,7 @@ extern "C" {
|
||||
#endif
|
||||
typedef int32_t EntityId;
|
||||
|
||||
FLUTTER_PLUGIN_EXPORT const void* create_filament_viewer(const void* const context, const ResourceLoaderWrapper* const loader, void* const platform);
|
||||
FLUTTER_PLUGIN_EXPORT const void* create_filament_viewer(const void* const context, const ResourceLoaderWrapper* const loader, void* const platform, const char* uberArchivePath);
|
||||
FLUTTER_PLUGIN_EXPORT void destroy_filament_viewer(const void* const viewer);
|
||||
FLUTTER_PLUGIN_EXPORT ResourceLoaderWrapper* make_resource_loader(LoadFilamentResourceFromOwner loadFn, FreeFilamentResourceFromOwner freeFn, void* owner);
|
||||
FLUTTER_PLUGIN_EXPORT void* get_asset_manager(const void* const viewer);
|
||||
@@ -151,6 +151,8 @@ FLUTTER_PLUGIN_EXPORT void set_camera_focus_distance(const void* const viewer, f
|
||||
FLUTTER_PLUGIN_EXPORT int hide_mesh(void* assetManager, EntityId asset, const char* meshName);
|
||||
FLUTTER_PLUGIN_EXPORT int reveal_mesh(void* assetManager, EntityId asset, const char* meshName);
|
||||
FLUTTER_PLUGIN_EXPORT void set_post_processing(void* const viewer, bool enabled);
|
||||
FLUTTER_PLUGIN_EXPORT void pick(void* const viewer, int x, int y, EntityId* entityId);
|
||||
FLUTTER_PLUGIN_EXPORT const char* get_name_for_entity(void* const assetManager, const EntityId entityId);
|
||||
FLUTTER_PLUGIN_EXPORT void ios_dummy();
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#ifndef _POLYVOX_FILAMENT_FFI_API_H
|
||||
#define _POLYVOX_FILAMENT_FFI_API_H
|
||||
|
||||
#include "PolyvoxFilamentApi.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
@@ -10,14 +12,12 @@ extern "C" {
|
||||
/// The intention is that calling one of these methods will call its respective method in PolyvoxFilamentApi.h, but wrapped in some kind of thread runner to ensure thread safety.
|
||||
///
|
||||
|
||||
#include "PolyvoxFilamentApi.h"
|
||||
|
||||
typedef int32_t EntityId;
|
||||
typedef void (*FilamentRenderCallback)(void* const owner);
|
||||
|
||||
FLUTTER_PLUGIN_EXPORT void* const create_filament_viewer_ffi(void* const context, void* const platform, const ResourceLoaderWrapper* const loader, void (*renderCallback)(void* const renderCallbackOwner), void* const renderCallbackOwner);
|
||||
FLUTTER_PLUGIN_EXPORT void* const create_filament_viewer_ffi(void* const context, void* const platform, const char* uberArchivePath, const ResourceLoaderWrapper* const loader, void (*renderCallback)(void* const renderCallbackOwner), void* const renderCallbackOwner);
|
||||
FLUTTER_PLUGIN_EXPORT void create_swap_chain_ffi(void* const viewer, void* const surface, uint32_t width, uint32_t height);
|
||||
FLUTTER_PLUGIN_EXPORT void create_render_target_ffi(void* const viewer, uint32_t nativeTextureId, uint32_t width, uint32_t height);
|
||||
FLUTTER_PLUGIN_EXPORT void create_render_target_ffi(void* const viewer, intptr_t nativeTextureId, uint32_t width, uint32_t height);
|
||||
FLUTTER_PLUGIN_EXPORT void destroy_filament_viewer_ffi(void* const viewer);
|
||||
FLUTTER_PLUGIN_EXPORT void render_ffi(void* const viewer);
|
||||
FLUTTER_PLUGIN_EXPORT FilamentRenderCallback make_render_callback_fn_pointer(FilamentRenderCallback);
|
||||
@@ -84,7 +84,7 @@ FLUTTER_PLUGIN_EXPORT void get_animation_name_ffi(void* const assetManager, Enti
|
||||
FLUTTER_PLUGIN_EXPORT void get_morph_target_name_ffi(void* const assetManager, EntityId asset, const char *meshName, char *const outPtr, int index);
|
||||
FLUTTER_PLUGIN_EXPORT int get_morph_target_name_count_ffi(void* const assetManager, EntityId asset, const char *meshName);
|
||||
FLUTTER_PLUGIN_EXPORT void set_post_processing_ffi(void* const viewer, bool enabled);
|
||||
|
||||
FLUTTER_PLUGIN_EXPORT void pick_ffi(void* const viewer, int x, int y, EntityId* entityId);
|
||||
FLUTTER_PLUGIN_EXPORT void ios_dummy_ffi();
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
@@ -38,9 +38,10 @@ using namespace filament;
|
||||
using namespace filament::gltfio;
|
||||
|
||||
AssetManager::AssetManager(const ResourceLoaderWrapper* const resourceLoaderWrapper,
|
||||
NameComponentManager *ncm,
|
||||
Engine *engine,
|
||||
Scene *scene)
|
||||
NameComponentManager* ncm,
|
||||
Engine* engine,
|
||||
Scene* scene,
|
||||
const char* uberArchivePath)
|
||||
: _resourceLoaderWrapper(resourceLoaderWrapper),
|
||||
_ncm(ncm),
|
||||
_engine(engine),
|
||||
@@ -52,15 +53,17 @@ _scene(scene) {
|
||||
_gltfResourceLoader = new ResourceLoader({.engine = _engine,
|
||||
.normalizeSkinningWeights = true });
|
||||
|
||||
// TODO - allow passing uberz archive
|
||||
// e.g. auto uberArchivePath = "packages/polyvox_filament/assets/default.uberz"
|
||||
// auto uberdata = resourceLoaderWrapper->load(uberArchivePath);
|
||||
// if (!uberdata.data) {
|
||||
// Log("Failed to load ubershader material. This is fatal.");
|
||||
// }
|
||||
// _ubershaderProvider = gltfio::createUbershaderProvider(_engine, uberdata.data, uberdata.size);
|
||||
_ubershaderProvider = gltfio::createUbershaderProvider(
|
||||
if(uberArchivePath) {
|
||||
auto uberdata = resourceLoaderWrapper->load(uberArchivePath);
|
||||
if (!uberdata.data) {
|
||||
Log("Failed to load ubershader material. This is fatal.");
|
||||
}
|
||||
_ubershaderProvider = gltfio::createUbershaderProvider(_engine, uberdata.data, uberdata.size);
|
||||
resourceLoaderWrapper->free(uberdata);
|
||||
} else {
|
||||
_ubershaderProvider = gltfio::createUbershaderProvider(
|
||||
_engine, UBERARCHIVE_DEFAULT_DATA, UBERARCHIVE_DEFAULT_SIZE);
|
||||
}
|
||||
Log("Created ubershader provider.");
|
||||
|
||||
EntityManager &em = EntityManager::get();
|
||||
@@ -943,6 +946,16 @@ size_t AssetManager::getLightEntityCount(EntityId entity) const noexcept {
|
||||
return asset.mAsset->getLightEntityCount();
|
||||
}
|
||||
|
||||
const char* AssetManager::getNameForEntity(EntityId entityId) {
|
||||
const auto& entity = Entity::import(entityId);
|
||||
auto nameInstance = _ncm->getInstance(entity);
|
||||
if(!nameInstance.isValid()) {
|
||||
Log("Failed to find name instance for entity ID %d", entityId);
|
||||
return nullptr;
|
||||
}
|
||||
return _ncm->getName(nameInstance);
|
||||
}
|
||||
|
||||
|
||||
} // namespace polyvox
|
||||
|
||||
|
||||
@@ -111,8 +111,10 @@ static constexpr float4 sFullScreenTriangleVertices[3] = {
|
||||
|
||||
static const uint16_t sFullScreenTriangleIndices[3] = {0, 1, 2};
|
||||
|
||||
FilamentViewer::FilamentViewer(const void* sharedContext, const ResourceLoaderWrapper* const resourceLoaderWrapper, void* const platform)
|
||||
FilamentViewer::FilamentViewer(const void* sharedContext, const ResourceLoaderWrapper* const resourceLoaderWrapper, void* const platform, const char* uberArchivePath)
|
||||
: _resourceLoaderWrapper(resourceLoaderWrapper) {
|
||||
|
||||
ASSERT_POSTCONDITION(_resourceLoaderWrapper != nullptr, "Resource loader must be non-null");
|
||||
|
||||
#if TARGET_OS_IPHONE
|
||||
ASSERT_POSTCONDITION(platform == nullptr, "Custom Platform not supported on iOS");
|
||||
@@ -190,7 +192,9 @@ FilamentViewer::FilamentViewer(const void* sharedContext, const ResourceLoaderWr
|
||||
_resourceLoaderWrapper,
|
||||
_ncm,
|
||||
_engine,
|
||||
_scene);
|
||||
_scene,
|
||||
uberArchivePath
|
||||
);
|
||||
|
||||
_imageTexture = Texture::Builder()
|
||||
.width(1)
|
||||
@@ -628,7 +632,7 @@ void FilamentViewer::createRenderTarget(intptr_t texture, uint32_t width, uint32
|
||||
// Make a specific viewport just for our render target
|
||||
_view->setRenderTarget(_rt);
|
||||
|
||||
Log("Set render target for glTextureId %u %u x %u", texture, width, height);
|
||||
Log("Set render target for texture id %u to %u x %u", texture, width, height);
|
||||
|
||||
}
|
||||
|
||||
@@ -1049,7 +1053,16 @@ void FilamentViewer::scrollUpdate(float x, float y, float delta) {
|
||||
}
|
||||
|
||||
void FilamentViewer::scrollEnd() {
|
||||
|
||||
// noop
|
||||
}
|
||||
|
||||
void FilamentViewer::pick(uint32_t x, uint32_t y, EntityId* entityId) {
|
||||
Log("Picking at %d,%d", x, y);
|
||||
_view->pick(x, y, [=](filament::View::PickingQueryResult const & result) {
|
||||
|
||||
*entityId = Entity::smuggle(result.renderable);
|
||||
Log("Got result %d", *entityId);
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace polyvox
|
||||
|
||||
@@ -15,8 +15,8 @@ extern "C" {
|
||||
|
||||
#include "PolyvoxFilamentApi.h"
|
||||
|
||||
FLUTTER_PLUGIN_EXPORT const void* create_filament_viewer(const void* context, const ResourceLoaderWrapper* const loader, void* const platform) {
|
||||
return (const void*) new FilamentViewer(context, loader, platform);
|
||||
FLUTTER_PLUGIN_EXPORT const void* create_filament_viewer(const void* context, const ResourceLoaderWrapper* const loader, void* const platform, const char* uberArchivePath) {
|
||||
return (const void*) new FilamentViewer(context, loader, platform, uberArchivePath);
|
||||
}
|
||||
|
||||
FLUTTER_PLUGIN_EXPORT ResourceLoaderWrapper* make_resource_loader(LoadFilamentResourceFromOwner loadFn, FreeFilamentResourceFromOwner freeFn, void* const owner) {
|
||||
@@ -373,15 +373,24 @@ extern "C" {
|
||||
((AssetManager*)assetManager)->stopAnimation(asset, index);
|
||||
}
|
||||
|
||||
int hide_mesh(void* assetManager, EntityId asset, const char* meshName) {
|
||||
FLUTTER_PLUGIN_EXPORT int hide_mesh(void* assetManager, EntityId asset, const char* meshName) {
|
||||
return ((AssetManager*)assetManager)->hide(asset, meshName);
|
||||
}
|
||||
|
||||
int reveal_mesh(void* assetManager, EntityId asset, const char* meshName) {
|
||||
FLUTTER_PLUGIN_EXPORT int reveal_mesh(void* assetManager, EntityId asset, const char* meshName) {
|
||||
return ((AssetManager*)assetManager)->reveal(asset, meshName);
|
||||
}
|
||||
|
||||
FLUTTER_PLUGIN_EXPORT void ios_dummy() {
|
||||
|
||||
FLUTTER_PLUGIN_EXPORT void pick(void* const viewer, int x, int y, EntityId* entityId) {
|
||||
((FilamentViewer*)viewer)->pick(static_cast<uint32_t>(x), static_cast<uint32_t>(y), static_cast<int32_t*>(entityId));
|
||||
}
|
||||
|
||||
FLUTTER_PLUGIN_EXPORT const char* get_name_for_entity(void* const assetManager, const EntityId entityId) {
|
||||
return ((AssetManager*)assetManager)->getNameForEntity(entityId);
|
||||
}
|
||||
|
||||
FLUTTER_PLUGIN_EXPORT void ios_dummy() {
|
||||
Log("Dummy called");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,6 +44,7 @@ public:
|
||||
void* const createViewer(
|
||||
void* const context,
|
||||
void* const platform,
|
||||
const char* uberArchivePath,
|
||||
const ResourceLoaderWrapper* const loader,
|
||||
void (*renderCallback)(void*), void* const owner
|
||||
) {
|
||||
@@ -51,7 +52,7 @@ public:
|
||||
_renderCallbackOwner = owner;
|
||||
std::packaged_task<FilamentViewer*()> lambda([&]() mutable
|
||||
{
|
||||
return new FilamentViewer(context, loader, platform);
|
||||
return new FilamentViewer(context, loader, platform, uberArchivePath);
|
||||
});
|
||||
auto fut = add_task(lambda);
|
||||
fut.wait();
|
||||
@@ -59,6 +60,16 @@ public:
|
||||
return (void* const)_viewer;
|
||||
}
|
||||
|
||||
void destroyViewer() {
|
||||
std::packaged_task<void()> lambda([&]() mutable {
|
||||
_rendering = false;
|
||||
destroy_filament_viewer(_viewer);
|
||||
_viewer = nullptr;
|
||||
});
|
||||
auto fut = add_task(lambda);
|
||||
fut.wait();
|
||||
}
|
||||
|
||||
void setRendering(bool rendering)
|
||||
{
|
||||
_rendering = rendering;
|
||||
@@ -106,6 +117,7 @@ extern "C"
|
||||
FLUTTER_PLUGIN_EXPORT void* const create_filament_viewer_ffi(
|
||||
void* const context,
|
||||
void* const platform,
|
||||
const char* uberArchivePath,
|
||||
const ResourceLoaderWrapper* const loader,
|
||||
void (*renderCallback)(void* const renderCallbackOwner),
|
||||
void* const renderCallbackOwner) {
|
||||
@@ -113,7 +125,11 @@ extern "C"
|
||||
{
|
||||
_rl = new RenderLoop();
|
||||
}
|
||||
return _rl->createViewer(context, platform, loader, renderCallback, renderCallbackOwner);
|
||||
return _rl->createViewer(context, platform,uberArchivePath, loader, renderCallback, renderCallbackOwner);
|
||||
}
|
||||
|
||||
FLUTTER_PLUGIN_EXPORT void destroy_filament_viewer_ffi(void* const viewer) {
|
||||
_rl->destroyViewer();
|
||||
}
|
||||
|
||||
FLUTTER_PLUGIN_EXPORT void create_swap_chain_ffi(void* const viewer, void* const surface, uint32_t width, uint32_t height)
|
||||
@@ -127,7 +143,7 @@ extern "C"
|
||||
fut.wait();
|
||||
}
|
||||
|
||||
FLUTTER_PLUGIN_EXPORT void create_render_target_ffi(void* const viewer, uint32_t nativeTextureId, uint32_t width, uint32_t height)
|
||||
FLUTTER_PLUGIN_EXPORT void create_render_target_ffi(void* const viewer, intptr_t nativeTextureId, uint32_t width, uint32_t height)
|
||||
{
|
||||
std::packaged_task<void()> lambda([&]() mutable
|
||||
{ create_render_target(viewer, nativeTextureId, width, height); });
|
||||
@@ -267,6 +283,7 @@ extern "C"
|
||||
auto fut = _rl->add_task(lambda);
|
||||
fut.wait();
|
||||
}
|
||||
|
||||
FLUTTER_PLUGIN_EXPORT void remove_ibl_ffi(void* const viewer)
|
||||
{
|
||||
std::packaged_task<void()> lambda([&]
|
||||
@@ -274,6 +291,7 @@ extern "C"
|
||||
auto fut = _rl->add_task(lambda);
|
||||
fut.wait();
|
||||
}
|
||||
|
||||
EntityId add_light_ffi(void* const viewer, uint8_t type, float colour, float intensity, float posX, float posY, float posZ, float dirX, float dirY, float dirZ, bool shadows)
|
||||
{
|
||||
std::packaged_task<EntityId()> lambda([&]
|
||||
@@ -418,6 +436,24 @@ extern "C"
|
||||
fut.wait();
|
||||
}
|
||||
|
||||
FLUTTER_PLUGIN_EXPORT void pick_ffi(void* const viewer, int x, int y, EntityId* entityId) {
|
||||
std::packaged_task<void()> lambda([&] {
|
||||
pick(viewer, x, y, entityId);
|
||||
});
|
||||
auto fut = _rl->add_task(lambda);
|
||||
fut.wait();
|
||||
}
|
||||
|
||||
FLUTTER_PLUGIN_EXPORT const char* get_name_for_entity_ffi(void* const assetManager, const EntityId entityId) {
|
||||
std::packaged_task<const char*()> lambda([&] {
|
||||
return get_name_for_entity(assetManager, entityId);
|
||||
});
|
||||
auto fut = _rl->add_task(lambda);
|
||||
fut.wait();
|
||||
return fut.get();
|
||||
}
|
||||
|
||||
|
||||
FLUTTER_PLUGIN_EXPORT void ios_dummy_ffi() {
|
||||
Log("Dummy called");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user