add (very rough) gizmo, restructure Dart package into library, add EntityListWidget
This commit is contained in:
@@ -6,7 +6,7 @@ import 'dart:html';
|
||||
import 'dart:ui';
|
||||
import 'dart:web_gl';
|
||||
import 'package:wasm_ffi/wasm_ffi.dart';
|
||||
import 'generated_bindings_web.dart';
|
||||
import 'filament/generated_bindings_web.dart';
|
||||
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_web_plugins/flutter_web_plugins.dart';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import 'package:flutter_filament/animations/animation_data.dart';
|
||||
import 'package:flutter_filament/filament/animations/animation_data.dart';
|
||||
import 'package:vector_math/vector_math.dart';
|
||||
|
||||
class AnimationBuilder {
|
||||
@@ -1,7 +1,6 @@
|
||||
import 'dart:io';
|
||||
import 'dart:math';
|
||||
import 'dart:ui';
|
||||
import 'package:flutter_filament/animations/animation_data.dart';
|
||||
|
||||
import 'package:flutter_filament/filament/animations/animation_data.dart';
|
||||
import 'package:vector_math/vector_math_64.dart';
|
||||
|
||||
enum RotationMode { ZYX, XYZ }
|
||||
@@ -1,8 +1,8 @@
|
||||
import 'dart:async';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_filament/filament_controller.dart';
|
||||
import 'package:flutter_filament/filament/filament_controller.dart';
|
||||
import 'package:flutter_filament/filament/utils/hardware_keyboard_listener.dart';
|
||||
import 'package:vector_math/vector_math_64.dart' as v;
|
||||
|
||||
class EntityTransformController {
|
||||
@@ -178,4 +178,26 @@ class EntityTransformController {
|
||||
void mouse2Up() async {}
|
||||
|
||||
void mouse2Down() async {}
|
||||
static HardwareKeyboardListener? _keyboardListener;
|
||||
|
||||
static Future<EntityTransformController> create(
|
||||
FilamentController controller, FilamentEntity entity,
|
||||
{double? translationSpeed, String? forwardAnimation}) async {
|
||||
int? forwardAnimationIndex;
|
||||
if (forwardAnimation != null) {
|
||||
final animationNames = await controller.getAnimationNames(entity);
|
||||
forwardAnimationIndex = animationNames.indexOf(forwardAnimation);
|
||||
}
|
||||
|
||||
if (forwardAnimationIndex == -1) {
|
||||
throw Exception("Invalid animation : $forwardAnimation");
|
||||
}
|
||||
|
||||
_keyboardListener?.dispose();
|
||||
var transformController = EntityTransformController(controller, entity,
|
||||
translationSpeed: translationSpeed ?? 1.0,
|
||||
forwardAnimationIndex: forwardAnimationIndex);
|
||||
_keyboardListener = HardwareKeyboardListener(transformController);
|
||||
return transformController;
|
||||
}
|
||||
}
|
||||
72
lib/filament/entities/gizmo.dart
Normal file
72
lib/filament/entities/gizmo.dart
Normal file
@@ -0,0 +1,72 @@
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:vector_math/vector_math_64.dart';
|
||||
|
||||
import '../filament_controller.dart';
|
||||
|
||||
class Gizmo {
|
||||
final FilamentEntity x;
|
||||
Vector3 _x = Vector3(0.1, 0, 0);
|
||||
final FilamentEntity y;
|
||||
Vector3 _y = Vector3(0.0, 0.1, 0);
|
||||
final FilamentEntity z;
|
||||
Vector3 _z = Vector3(0.0, 0.0, 0.1);
|
||||
|
||||
final FilamentController controller;
|
||||
|
||||
FilamentEntity? _activeAxis;
|
||||
FilamentEntity? _activeEntity;
|
||||
bool get isActive => _activeAxis != null;
|
||||
|
||||
Gizmo(this.x, this.y, this.z, this.controller) {
|
||||
controller.pickResult.listen(_onPickResult);
|
||||
}
|
||||
|
||||
Future _reveal() async {
|
||||
await controller.reveal(x, null);
|
||||
await controller.reveal(y, null);
|
||||
await controller.reveal(z, null);
|
||||
}
|
||||
|
||||
void translate(Offset offset) async {
|
||||
late Vector3 vec;
|
||||
if (_activeAxis == x) {
|
||||
vec = _x;
|
||||
} else if (_activeAxis == y) {
|
||||
vec = _y;
|
||||
} else if (_activeAxis == z) {
|
||||
vec = _z;
|
||||
}
|
||||
await controller.queuePositionUpdate(_activeEntity!, offset.dx * vec.x,
|
||||
-offset.dy * vec.y, -offset.dx * vec.z,
|
||||
relative: true);
|
||||
}
|
||||
|
||||
void reset() {
|
||||
_activeAxis = null;
|
||||
}
|
||||
|
||||
void _onPickResult(FilamentPickResult result) async {
|
||||
if (result.entity == x || result.entity == y || result.entity == z) {
|
||||
_activeAxis = result.entity;
|
||||
} else {
|
||||
attach(result.entity);
|
||||
}
|
||||
}
|
||||
|
||||
void attach(FilamentEntity entity) async {
|
||||
print("Attaching to $entity");
|
||||
_activeAxis = null;
|
||||
_activeEntity = entity;
|
||||
await _reveal();
|
||||
await controller.setParent(x, entity);
|
||||
await controller.setParent(y, entity);
|
||||
await controller.setParent(z, entity);
|
||||
}
|
||||
|
||||
void detach() async {
|
||||
await controller.hide(x, null);
|
||||
await controller.hide(y, null);
|
||||
await controller.hide(z, null);
|
||||
}
|
||||
}
|
||||
@@ -5,17 +5,28 @@ import 'dart:typed_data';
|
||||
import 'dart:ui' as ui;
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
import 'package:flutter_filament/animations/animation_data.dart';
|
||||
import 'package:flutter_filament/entities/entity_transform_controller.dart';
|
||||
import 'package:flutter_filament/generated_bindings.dart';
|
||||
import 'package:flutter_filament/filament/entities/gizmo.dart';
|
||||
import 'package:vector_math/vector_math_64.dart';
|
||||
|
||||
import 'animations/animation_data.dart';
|
||||
|
||||
// a handle that can be safely passed back to the rendering layer to manipulate an Entity
|
||||
typedef FilamentEntity = int;
|
||||
|
||||
// "picking" means clicking/tapping on the viewport, and unprojecting the X/Y coordinate to determine whether any renderable entities were present at those coordinates.
|
||||
typedef FilamentPickResult = ({FilamentEntity entity, double x, double y});
|
||||
|
||||
// copied from filament/backened/DriverEnums.h
|
||||
enum PrimitiveType {
|
||||
// don't change the enums values (made to match GL)
|
||||
POINTS, //!< points
|
||||
LINES, //!< lines
|
||||
UNUSED1,
|
||||
LINE_STRIP, //!< line strip
|
||||
TRIANGLES, //!< triangles
|
||||
TRIANGLE_STRIP, //!< triangle strip
|
||||
}
|
||||
|
||||
enum ToneMapper { ACES, FILMIC, LINEAR }
|
||||
|
||||
// see filament Manipulator.h for more details
|
||||
@@ -33,17 +44,6 @@ class TextureDetails {
|
||||
}
|
||||
|
||||
abstract class FilamentController {
|
||||
///
|
||||
/// A Stream containing every FilamentEntity added to the scene (i.e. via [loadGlb], [loadGltf] or [addLight]).
|
||||
/// This is provided for convenience so you can set listeners in front-end widgets that can respond to entity loads without manually passing around the FilamentEntity returned from those methods.
|
||||
///
|
||||
Stream<FilamentEntity> get onLoad;
|
||||
|
||||
///
|
||||
/// A Stream containing every FilamentEntity removed from the scene (i.e. via [removeEntity], [clearEntities], [removeLight] or [clearLights]).
|
||||
|
||||
Stream<FilamentEntity> get onUnload;
|
||||
|
||||
///
|
||||
/// A [ValueNotifier] to indicate whether a FilamentViewer is currently available.
|
||||
/// (FilamentViewer is a C++ type, hence why it is not referenced) here.
|
||||
@@ -212,14 +212,6 @@ abstract class FilamentController {
|
||||
///
|
||||
Future<FilamentEntity> loadGlb(String path, {int numInstances = 1});
|
||||
|
||||
///
|
||||
/// Load the .glb asset from the specified path (either Flutter asset URI or filepath) and insert into the scene.
|
||||
/// If [cache] is true, the contents of the path will be cached locally and re-used for any future calls to load that asset.
|
||||
/// See also [evictCache].
|
||||
///
|
||||
Future<FilamentEntity> loadGlbFromBuffer(String path,
|
||||
{bool cache = false, int numInstances = 1});
|
||||
|
||||
///
|
||||
/// Create a new instance of [entity].
|
||||
///
|
||||
@@ -235,11 +227,6 @@ abstract class FilamentController {
|
||||
///
|
||||
Future<List<FilamentEntity>> getInstances(FilamentEntity entity);
|
||||
|
||||
///
|
||||
/// Frees all cached resources loaded via [loadGlbFromBuffer].
|
||||
///
|
||||
Future evictCache();
|
||||
|
||||
///
|
||||
/// Load the .gltf asset at the given path and insert into the scene.
|
||||
/// [relativeResourcePath] is the folder path where the glTF resources are stored;
|
||||
@@ -558,7 +545,7 @@ abstract class FilamentController {
|
||||
///
|
||||
/// Reveal the node [meshName] under [entity]. Only applicable if [hide] had previously been called; this is a no-op otherwise.
|
||||
///
|
||||
Future reveal(FilamentEntity entity, String meshName);
|
||||
Future reveal(FilamentEntity entity, String? meshName);
|
||||
|
||||
///
|
||||
/// If [meshName] is provided, hide the node [meshName] under [entity], otherwise hide the root node for [entity].
|
||||
@@ -613,12 +600,6 @@ abstract class FilamentController {
|
||||
///
|
||||
Future setRecordingOutputDirectory(String outputDirectory);
|
||||
|
||||
///
|
||||
/// Attach the keyboard/mouse to [entity].
|
||||
///
|
||||
Future<EntityTransformController> control(FilamentEntity entity,
|
||||
{double? translationSpeed, String? forwardAnimation});
|
||||
|
||||
///
|
||||
/// 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.
|
||||
@@ -642,8 +623,9 @@ abstract class FilamentController {
|
||||
///
|
||||
/// Creates a (renderable) entity with the specified geometry and adds to the scene.
|
||||
///
|
||||
Future createGeometry(
|
||||
List<double> vertices, List<int> indices, String? materialPath);
|
||||
Future createGeometry(List<double> vertices, List<int> indices,
|
||||
{String? materialPath,
|
||||
PrimitiveType primitiveType = PrimitiveType.TRIANGLES});
|
||||
|
||||
///
|
||||
/// Sets the parent transform of [child] to the transform of [parent].
|
||||
@@ -655,4 +637,59 @@ abstract class FilamentController {
|
||||
/// This method returns void; the relevant callback passed to [addCollisionComponent] will be fired if a collision is detected.
|
||||
///
|
||||
Future testCollisions(FilamentEntity entity);
|
||||
|
||||
///
|
||||
/// Sets the draw priority for the given entity. See RenderableManager.h for more details.
|
||||
///
|
||||
Future setPriority(FilamentEntity entityId, int priority);
|
||||
|
||||
///
|
||||
/// The Scene holds the transform gizmo and all loaded entities/lights.
|
||||
///
|
||||
Scene get scene;
|
||||
}
|
||||
|
||||
///
|
||||
/// For now, this class just holds the entities that have been loaded (though not necessarily visible in the Filament Scene).
|
||||
///
|
||||
abstract class Scene {
|
||||
///
|
||||
/// The last entity clicked/tapped in the viewport (internally, the result of calling pick);
|
||||
FilamentEntity? selected;
|
||||
|
||||
///
|
||||
/// A Stream updated whenever an entity is added/removed from the scene.
|
||||
///
|
||||
Stream<bool> get onUpdated;
|
||||
|
||||
///
|
||||
/// A Stream containing every FilamentEntity added to the scene (i.e. via [loadGlb], [loadGltf] or [addLight]).
|
||||
/// This is provided for convenience so you can set listeners in front-end widgets that can respond to entity loads without manually passing around the FilamentEntity returned from those methods.
|
||||
///
|
||||
Stream<FilamentEntity> get onLoad;
|
||||
|
||||
///
|
||||
/// A Stream containing every FilamentEntity removed from the scene (i.e. via [removeEntity], [clearEntities], [removeLight] or [clearLights]).
|
||||
|
||||
Stream<FilamentEntity> get onUnload;
|
||||
|
||||
///
|
||||
/// Lists all light entities currently loaded (not necessarily active in the scene). Does not account for instances.
|
||||
///
|
||||
Iterable<FilamentEntity> listLights();
|
||||
|
||||
///
|
||||
/// Lists all entities currently loaded (not necessarily active in the scene). Does not account for instances.
|
||||
///
|
||||
Iterable<FilamentEntity> listEntities();
|
||||
|
||||
///
|
||||
/// Attach the gizmo to the specified entity.
|
||||
///
|
||||
void select(FilamentEntity entity);
|
||||
|
||||
///
|
||||
/// The transform gizmo.
|
||||
///
|
||||
Gizmo get gizmo;
|
||||
}
|
||||
@@ -1,24 +1,20 @@
|
||||
import 'dart:async';
|
||||
import 'dart:ffi';
|
||||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
import 'dart:ui' as ui;
|
||||
import 'dart:developer' as dev;
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:ffi/ffi.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_filament/entities/entity_transform_controller.dart';
|
||||
|
||||
import 'package:flutter_filament/filament_controller.dart';
|
||||
|
||||
import 'package:flutter_filament/animations/animation_data.dart';
|
||||
import 'package:flutter_filament/generated_bindings.dart';
|
||||
import 'package:flutter_filament/generated_bindings.dart' as gb;
|
||||
import 'package:flutter_filament/hardware/hardware_keyboard_listener.dart';
|
||||
|
||||
import 'package:flutter_filament/rendering_surface.dart';
|
||||
import 'package:flutter_filament/filament/animations/animation_data.dart';
|
||||
import 'package:flutter_filament/filament/entities/gizmo.dart';
|
||||
import 'package:flutter_filament/filament/filament_controller.dart';
|
||||
import 'package:flutter_filament/filament/generated_bindings.dart';
|
||||
import 'package:flutter_filament/filament/generated_bindings.dart' as gb;
|
||||
import 'package:flutter_filament/filament/rendering_surface.dart';
|
||||
import 'package:vector_math/vector_math_64.dart';
|
||||
import 'scene.dart';
|
||||
|
||||
// ignore: constant_identifier_names
|
||||
const FilamentEntity _FILAMENT_ASSET_ERROR = 0;
|
||||
@@ -26,6 +22,9 @@ const FilamentEntity _FILAMENT_ASSET_ERROR = 0;
|
||||
class FilamentControllerFFI extends FilamentController {
|
||||
final _channel = const MethodChannel("app.polyvox.filament/event");
|
||||
|
||||
late SceneImpl _scene;
|
||||
Scene get scene => _scene;
|
||||
|
||||
///
|
||||
/// This will be set on constructor invocation.
|
||||
/// On Windows, this will be set to the value returned by the [usesBackingWindow] method call.
|
||||
@@ -64,22 +63,8 @@ class FilamentControllerFFI extends FilamentController {
|
||||
|
||||
Timer? _resizeTimer;
|
||||
|
||||
final _lights = <FilamentEntity>{};
|
||||
final _entities = <FilamentEntity>{};
|
||||
|
||||
final _onLoadController = StreamController<FilamentEntity>.broadcast();
|
||||
Stream<FilamentEntity> get onLoad => _onLoadController.stream;
|
||||
|
||||
final _onUnloadController = StreamController<FilamentEntity>.broadcast();
|
||||
Stream<FilamentEntity> get onUnload => _onUnloadController.stream;
|
||||
|
||||
final allocator = calloc;
|
||||
|
||||
void _using(Pointer ptr, Future Function(Pointer ptr) function) async {
|
||||
await function.call(ptr);
|
||||
allocator.free(ptr);
|
||||
}
|
||||
|
||||
///
|
||||
/// 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 FlutterFilamentApi.h.
|
||||
@@ -145,7 +130,6 @@ class FilamentControllerFFI extends FilamentController {
|
||||
Future setFrameRate(int framerate) async {
|
||||
final interval = 1000.0 / framerate;
|
||||
set_frame_interval_ffi(interval);
|
||||
print("Set frame interval to $interval");
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -360,7 +344,7 @@ class FilamentControllerFFI extends FilamentController {
|
||||
textureId: renderingSurface.flutterTextureId,
|
||||
width: _rect.value!.width.toInt(),
|
||||
height: _rect.value!.height.toInt());
|
||||
print("texture details ${textureDetails.value}");
|
||||
|
||||
await _withVoidCallback((callback) {
|
||||
update_viewport_and_camera_projection_ffi(
|
||||
_viewer!,
|
||||
@@ -369,6 +353,13 @@ class FilamentControllerFFI extends FilamentController {
|
||||
1.0,
|
||||
callback);
|
||||
});
|
||||
|
||||
final out = allocator<Int32>(3);
|
||||
get_gizmo(_sceneManager!, out);
|
||||
var gizmo = Gizmo(out[0], out[1], out[2], this);
|
||||
allocator.free(out);
|
||||
_scene = SceneImpl(gizmo);
|
||||
|
||||
hasViewer.value = true;
|
||||
_creating = false;
|
||||
}
|
||||
@@ -642,8 +633,8 @@ class FilamentControllerFFI extends FilamentController {
|
||||
dirZ,
|
||||
castShadows,
|
||||
callback));
|
||||
_onLoadController.sink.add(entity);
|
||||
_lights.add(entity);
|
||||
|
||||
_scene.registerLight(entity);
|
||||
return entity;
|
||||
}
|
||||
|
||||
@@ -652,9 +643,8 @@ class FilamentControllerFFI extends FilamentController {
|
||||
if (_viewer == null) {
|
||||
throw Exception("No viewer available, ignoring");
|
||||
}
|
||||
_lights.remove(entity);
|
||||
_scene.unregisterLight(entity);
|
||||
remove_light_ffi(_viewer!, entity);
|
||||
_onUnloadController.add(entity);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -663,48 +653,8 @@ class FilamentControllerFFI extends FilamentController {
|
||||
throw Exception("No viewer available, ignoring");
|
||||
}
|
||||
clear_lights_ffi(_viewer!);
|
||||
for (final entity in _lights) {
|
||||
_onUnloadController.add(entity);
|
||||
}
|
||||
_lights.clear();
|
||||
}
|
||||
|
||||
final _assetCache = <String, (Pointer<Void>, int)>{};
|
||||
|
||||
@override
|
||||
Future<FilamentEntity> loadGlbFromBuffer(String path,
|
||||
{bool cache = false, int numInstances = 1}) async {
|
||||
late (Pointer<Void>, int) data;
|
||||
|
||||
if (cache && _assetCache.containsKey(path)) {
|
||||
data = _assetCache[path]!;
|
||||
} else {
|
||||
late ByteData asset;
|
||||
if (path.startsWith("file://")) {
|
||||
var raw = File(path.replaceAll("file://", "")).readAsBytesSync();
|
||||
asset = raw.buffer.asByteData(raw.offsetInBytes);
|
||||
} else {
|
||||
asset = await rootBundle.load(path.replaceAll("asset://", ""));
|
||||
}
|
||||
|
||||
var ptr = allocator<Char>(asset.lengthInBytes);
|
||||
for (int i = 0; i < asset.lengthInBytes; i++) {
|
||||
ptr[i] = asset.getUint8(i);
|
||||
}
|
||||
|
||||
data = (ptr.cast<Void>(), asset.lengthInBytes);
|
||||
}
|
||||
var entity = await _withIntCallback((callback) => load_glb_from_buffer_ffi(
|
||||
_sceneManager!, data.$1, data.$2, numInstances, callback));
|
||||
if (!cache) {
|
||||
allocator.free(data.$1);
|
||||
} else {
|
||||
_assetCache[path] = data;
|
||||
}
|
||||
if (entity == _FILAMENT_ASSET_ERROR) {
|
||||
throw Exception("Failed to load GLB from path $path");
|
||||
}
|
||||
return entity;
|
||||
_scene.clearLights();
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -735,14 +685,6 @@ class FilamentControllerFFI extends FilamentController {
|
||||
return instances;
|
||||
}
|
||||
|
||||
@override
|
||||
Future evictCache() async {
|
||||
for (final value in _assetCache.values) {
|
||||
allocator.free(value.$1);
|
||||
}
|
||||
_assetCache.clear();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<FilamentEntity> loadGlb(String path,
|
||||
{bool unlit = false, int numInstances = 1}) async {
|
||||
@@ -759,8 +701,8 @@ class FilamentControllerFFI extends FilamentController {
|
||||
if (entity == _FILAMENT_ASSET_ERROR) {
|
||||
throw Exception("An error occurred loading the asset at $path");
|
||||
}
|
||||
_entities.add(entity);
|
||||
_onLoadController.sink.add(entity);
|
||||
_scene.registerEntity(entity);
|
||||
|
||||
return entity;
|
||||
}
|
||||
|
||||
@@ -784,8 +726,8 @@ class FilamentControllerFFI extends FilamentController {
|
||||
if (entity == _FILAMENT_ASSET_ERROR) {
|
||||
throw Exception("An error occurred loading the asset at $path");
|
||||
}
|
||||
_entities.add(entity);
|
||||
_onLoadController.sink.add(entity);
|
||||
_scene.registerEntity(entity);
|
||||
|
||||
return entity;
|
||||
}
|
||||
|
||||
@@ -1021,10 +963,10 @@ class FilamentControllerFFI extends FilamentController {
|
||||
if (_viewer == null) {
|
||||
throw Exception("No viewer available, ignoring");
|
||||
}
|
||||
_entities.remove(entity);
|
||||
_scene.unregisterEntity(entity);
|
||||
|
||||
await _withVoidCallback(
|
||||
(callback) => remove_entity_ffi(_viewer!, entity, callback));
|
||||
_onUnloadController.add(entity);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -1034,11 +976,7 @@ class FilamentControllerFFI extends FilamentController {
|
||||
}
|
||||
await _withVoidCallback(
|
||||
(callback) => clear_entities_ffi(_viewer!, callback));
|
||||
|
||||
for (final entity in _entities) {
|
||||
_onUnloadController.add(entity);
|
||||
}
|
||||
_entities.clear();
|
||||
_scene.clearEntities();
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -1078,6 +1016,14 @@ class FilamentControllerFFI extends FilamentController {
|
||||
_sceneManager!, entity, index, loop, reverse, replaceActive, crossfade);
|
||||
}
|
||||
|
||||
@override
|
||||
Future stopAnimation(FilamentEntity entity, int animationIndex) async {
|
||||
if (_viewer == null) {
|
||||
throw Exception("No viewer available, ignoring");
|
||||
}
|
||||
stop_animation(_sceneManager!, entity, animationIndex);
|
||||
}
|
||||
|
||||
@override
|
||||
Future stopAnimationByName(FilamentEntity entity, String name) async {
|
||||
var animations = await getAnimationNames(entity);
|
||||
@@ -1107,14 +1053,6 @@ class FilamentControllerFFI extends FilamentController {
|
||||
set_animation_frame(_sceneManager!, entity, index, animationFrame);
|
||||
}
|
||||
|
||||
@override
|
||||
Future stopAnimation(FilamentEntity entity, int animationIndex) async {
|
||||
if (_viewer == null) {
|
||||
throw Exception("No viewer available, ignoring");
|
||||
}
|
||||
stop_animation(_sceneManager!, entity, animationIndex);
|
||||
}
|
||||
|
||||
@override
|
||||
Future setMainCamera() async {
|
||||
set_main_camera(_viewer!);
|
||||
@@ -1303,16 +1241,6 @@ class FilamentControllerFFI extends FilamentController {
|
||||
set_position(_sceneManager!, entity, x, y, z);
|
||||
}
|
||||
|
||||
@override
|
||||
Future setRotation(
|
||||
FilamentEntity entity, double rads, double x, double y, double z) async {
|
||||
if (_viewer == null) {
|
||||
throw Exception("No viewer available, ignoring");
|
||||
}
|
||||
var quat = Quaternion.axisAngle(Vector3(x, y, z), rads);
|
||||
await setRotationQuat(entity, quat);
|
||||
}
|
||||
|
||||
@override
|
||||
Future setRotationQuat(FilamentEntity entity, Quaternion rotation,
|
||||
{bool relative = false}) async {
|
||||
@@ -1323,6 +1251,16 @@ class FilamentControllerFFI extends FilamentController {
|
||||
rotation.y, rotation.z, rotation.w);
|
||||
}
|
||||
|
||||
@override
|
||||
Future setRotation(
|
||||
FilamentEntity entity, double rads, double x, double y, double z) async {
|
||||
if (_viewer == null) {
|
||||
throw Exception("No viewer available, ignoring");
|
||||
}
|
||||
var quat = Quaternion.axisAngle(Vector3(x, y, z), rads);
|
||||
await setRotationQuat(entity, quat);
|
||||
}
|
||||
|
||||
@override
|
||||
Future setScale(FilamentEntity entity, double scale) async {
|
||||
if (_viewer == null) {
|
||||
@@ -1331,15 +1269,13 @@ class FilamentControllerFFI extends FilamentController {
|
||||
set_scale(_sceneManager!, entity, scale);
|
||||
}
|
||||
|
||||
@override
|
||||
Future queuePositionUpdate(
|
||||
FilamentEntity entity, double x, double y, double z,
|
||||
Future queueRotationUpdateQuat(FilamentEntity entity, Quaternion rotation,
|
||||
{bool relative = false}) async {
|
||||
if (_viewer == null) {
|
||||
throw Exception("No viewer available, ignoring");
|
||||
}
|
||||
|
||||
queue_position_update(_sceneManager!, entity, x, y, z, relative);
|
||||
queue_rotation_update(_sceneManager!, entity, rotation.radians, rotation.x,
|
||||
rotation.y, rotation.z, rotation.w, relative);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -1353,13 +1289,15 @@ class FilamentControllerFFI extends FilamentController {
|
||||
await queueRotationUpdateQuat(entity, quat, relative: relative);
|
||||
}
|
||||
|
||||
Future queueRotationUpdateQuat(FilamentEntity entity, Quaternion rotation,
|
||||
@override
|
||||
Future queuePositionUpdate(
|
||||
FilamentEntity entity, double x, double y, double z,
|
||||
{bool relative = false}) async {
|
||||
if (_viewer == null) {
|
||||
throw Exception("No viewer available, ignoring");
|
||||
}
|
||||
queue_rotation_update(_sceneManager!, entity, rotation.radians, rotation.x,
|
||||
rotation.y, rotation.z, rotation.w, relative);
|
||||
|
||||
queue_position_update(_sceneManager!, entity, x, y, z, relative);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -1395,14 +1333,13 @@ class FilamentControllerFFI extends FilamentController {
|
||||
return result.cast<Utf8>().toDartString();
|
||||
}
|
||||
|
||||
final _pick = <int, Completer<int>>{};
|
||||
|
||||
void _onPickResult(FilamentEntity entityId, int x, int y) {
|
||||
_pickResultController.add((
|
||||
entity: entityId,
|
||||
x: (x / _pixelRatio).toDouble(),
|
||||
y: (textureDetails.value!.height - y) / _pixelRatio
|
||||
));
|
||||
_scene.registerSelected(entityId);
|
||||
}
|
||||
|
||||
late NativeCallable<Void Function(Int32 entityId, Int x, Int y)>
|
||||
@@ -1414,6 +1351,8 @@ class FilamentControllerFFI extends FilamentController {
|
||||
throw Exception("No viewer available, ignoring");
|
||||
}
|
||||
|
||||
_scene.unregisterSelected();
|
||||
|
||||
gb.pick(
|
||||
_viewer!,
|
||||
(x * _pixelRatio).toInt(),
|
||||
@@ -1592,28 +1531,6 @@ class FilamentControllerFFI extends FilamentController {
|
||||
allocator.free(pathPtr);
|
||||
}
|
||||
|
||||
HardwareKeyboardListener? _keyboardListener;
|
||||
@override
|
||||
Future<EntityTransformController> control(FilamentEntity entity,
|
||||
{double? translationSpeed, String? forwardAnimation}) async {
|
||||
int? forwardAnimationIndex;
|
||||
if (forwardAnimation != null) {
|
||||
final animationNames = await getAnimationNames(entity);
|
||||
forwardAnimationIndex = animationNames.indexOf(forwardAnimation);
|
||||
}
|
||||
|
||||
if (forwardAnimationIndex == -1) {
|
||||
throw Exception("Invalid animation : $forwardAnimation");
|
||||
}
|
||||
|
||||
_keyboardListener?.dispose();
|
||||
var transformController = EntityTransformController(this, entity,
|
||||
translationSpeed: translationSpeed ?? 1.0,
|
||||
forwardAnimationIndex: forwardAnimationIndex);
|
||||
_keyboardListener = HardwareKeyboardListener(transformController);
|
||||
return transformController;
|
||||
}
|
||||
|
||||
final _collisions = <FilamentEntity, NativeCallable>{};
|
||||
|
||||
@override
|
||||
@@ -1649,7 +1566,9 @@ class FilamentControllerFFI extends FilamentController {
|
||||
|
||||
@override
|
||||
Future<FilamentEntity> createGeometry(
|
||||
List<double> vertices, List<int> indices, String? materialPath) async {
|
||||
List<double> vertices, List<int> indices,
|
||||
{String? materialPath,
|
||||
PrimitiveType primitiveType = PrimitiveType.TRIANGLES}) async {
|
||||
if (_viewer == null) {
|
||||
throw Exception("Viewer must not be null");
|
||||
}
|
||||
@@ -1672,14 +1591,14 @@ class FilamentControllerFFI extends FilamentController {
|
||||
vertices.length,
|
||||
indicesPtr,
|
||||
indices.length,
|
||||
primitiveType.index,
|
||||
materialPathPtr.cast<Char>(),
|
||||
callback));
|
||||
if (entity == _FILAMENT_ASSET_ERROR) {
|
||||
throw Exception("Failed to create geometry");
|
||||
}
|
||||
|
||||
_entities.add(entity);
|
||||
_onLoadController.sink.add(entity);
|
||||
_scene.registerEntity(entity);
|
||||
|
||||
allocator.free(materialPathPtr);
|
||||
allocator.free(vertexPtr);
|
||||
@@ -1700,4 +1619,9 @@ class FilamentControllerFFI extends FilamentController {
|
||||
Future testCollisions(FilamentEntity entity) async {
|
||||
test_collisions(_sceneManager!, entity);
|
||||
}
|
||||
|
||||
@override
|
||||
Future setPriority(FilamentEntity entityId, int priority) async {
|
||||
set_priority(_sceneManager!, entityId, priority);
|
||||
}
|
||||
}
|
||||
@@ -955,8 +955,14 @@ external void add_animation_component(
|
||||
);
|
||||
|
||||
@ffi.Native<
|
||||
EntityId Function(ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Float>,
|
||||
ffi.Int, ffi.Pointer<ffi.Uint16>, ffi.Int, ffi.Pointer<ffi.Char>)>(
|
||||
EntityId Function(
|
||||
ffi.Pointer<ffi.Void>,
|
||||
ffi.Pointer<ffi.Float>,
|
||||
ffi.Int,
|
||||
ffi.Pointer<ffi.Uint16>,
|
||||
ffi.Int,
|
||||
ffi.Int,
|
||||
ffi.Pointer<ffi.Char>)>(
|
||||
symbol: 'create_geometry', assetId: 'flutter_filament_plugin')
|
||||
external int create_geometry(
|
||||
ffi.Pointer<ffi.Void> viewer,
|
||||
@@ -964,6 +970,7 @@ external int create_geometry(
|
||||
int numVertices,
|
||||
ffi.Pointer<ffi.Uint16> indices,
|
||||
int numIndices,
|
||||
int primitiveType,
|
||||
ffi.Pointer<ffi.Char> materialPath,
|
||||
);
|
||||
|
||||
@@ -982,6 +989,21 @@ external void test_collisions(
|
||||
int entity,
|
||||
);
|
||||
|
||||
@ffi.Native<ffi.Void Function(ffi.Pointer<ffi.Void>, EntityId, ffi.Int)>(
|
||||
symbol: 'set_priority', assetId: 'flutter_filament_plugin')
|
||||
external void set_priority(
|
||||
ffi.Pointer<ffi.Void> sceneManager,
|
||||
int entityId,
|
||||
int priority,
|
||||
);
|
||||
|
||||
@ffi.Native<ffi.Void Function(ffi.Pointer<ffi.Void>, ffi.Pointer<EntityId>)>(
|
||||
symbol: 'get_gizmo', assetId: 'flutter_filament_plugin')
|
||||
external void get_gizmo(
|
||||
ffi.Pointer<ffi.Void> sceneManager,
|
||||
ffi.Pointer<EntityId> out,
|
||||
);
|
||||
|
||||
@ffi.Native<
|
||||
ffi.Void Function(
|
||||
ffi.Pointer<ffi.Void>,
|
||||
@@ -1510,6 +1532,7 @@ external void ios_dummy_ffi();
|
||||
ffi.Int,
|
||||
ffi.Pointer<ffi.Uint16>,
|
||||
ffi.Int,
|
||||
ffi.Int,
|
||||
ffi.Pointer<ffi.Char>,
|
||||
ffi.Pointer<ffi.NativeFunction<ffi.Void Function(EntityId)>>)>(
|
||||
symbol: 'create_geometry_ffi', assetId: 'flutter_filament_plugin')
|
||||
@@ -1519,6 +1542,7 @@ external void create_geometry_ffi(
|
||||
int numVertices,
|
||||
ffi.Pointer<ffi.Uint16> indices,
|
||||
int numIndices,
|
||||
int primitiveType,
|
||||
ffi.Pointer<ffi.Char> materialPath,
|
||||
ffi.Pointer<ffi.NativeFunction<ffi.Void Function(EntityId)>> callback,
|
||||
);
|
||||
118
lib/filament/scene.dart
Normal file
118
lib/filament/scene.dart
Normal file
@@ -0,0 +1,118 @@
|
||||
import 'dart:async';
|
||||
import 'package:flutter_filament/filament/entities/gizmo.dart';
|
||||
import 'package:flutter_filament/filament/filament_controller.dart';
|
||||
|
||||
///
|
||||
/// For now, this class just holds the entities that have been loaded (though not necessarily visible in the Filament Scene).
|
||||
///
|
||||
class SceneImpl extends Scene {
|
||||
final Gizmo _gizmo;
|
||||
Gizmo get gizmo => _gizmo;
|
||||
|
||||
SceneImpl(this._gizmo);
|
||||
|
||||
@override
|
||||
FilamentEntity? selected;
|
||||
|
||||
final _onUpdatedController = StreamController<bool>.broadcast();
|
||||
@override
|
||||
Stream<bool> get onUpdated => _onUpdatedController.stream;
|
||||
|
||||
final _onLoadController = StreamController<FilamentEntity>.broadcast();
|
||||
@override
|
||||
Stream<FilamentEntity> get onLoad => _onLoadController.stream;
|
||||
|
||||
final _onUnloadController = StreamController<FilamentEntity>.broadcast();
|
||||
@override
|
||||
Stream<FilamentEntity> get onUnload => _onUnloadController.stream;
|
||||
|
||||
final _lights = <FilamentEntity>{};
|
||||
final _entities = <FilamentEntity>{};
|
||||
|
||||
void registerLight(FilamentEntity entity) {
|
||||
_lights.add(entity);
|
||||
_onLoadController.sink.add(entity);
|
||||
_onUpdatedController.add(true);
|
||||
}
|
||||
|
||||
void unregisterLight(FilamentEntity entity) {
|
||||
if (selected == entity) {
|
||||
selected = null;
|
||||
_gizmo.detach();
|
||||
}
|
||||
_lights.remove(entity);
|
||||
_onUnloadController.add(entity);
|
||||
_onUpdatedController.add(true);
|
||||
}
|
||||
|
||||
void unregisterEntity(FilamentEntity entity) {
|
||||
if (selected == entity) {
|
||||
selected = null;
|
||||
_gizmo.detach();
|
||||
}
|
||||
_entities.remove(entity);
|
||||
_onUnloadController.add(entity);
|
||||
_onUpdatedController.add(true);
|
||||
}
|
||||
|
||||
void registerEntity(FilamentEntity entity) {
|
||||
_entities.add(entity);
|
||||
_entities.add(entity);
|
||||
_onLoadController.sink.add(entity);
|
||||
_onUpdatedController.add(true);
|
||||
}
|
||||
|
||||
void clearLights() {
|
||||
for (final light in _lights) {
|
||||
if (selected == light) {
|
||||
selected = null;
|
||||
_gizmo.detach();
|
||||
}
|
||||
_onUnloadController.add(light);
|
||||
}
|
||||
|
||||
_lights.clear();
|
||||
_onUpdatedController.add(true);
|
||||
}
|
||||
|
||||
void clearEntities() {
|
||||
for (final entity in _entities) {
|
||||
if (selected == entity) {
|
||||
selected = null;
|
||||
_gizmo.detach();
|
||||
}
|
||||
_onUnloadController.add(entity);
|
||||
}
|
||||
_entities.clear();
|
||||
_onUpdatedController.add(true);
|
||||
}
|
||||
|
||||
///
|
||||
/// Lists all entities currently loaded (not necessarily active in the scene).
|
||||
///
|
||||
Iterable<FilamentEntity> listLights() {
|
||||
return _lights;
|
||||
}
|
||||
|
||||
@override
|
||||
Iterable<FilamentEntity> listEntities() {
|
||||
return _entities;
|
||||
}
|
||||
|
||||
void registerSelected(FilamentEntity entity) {
|
||||
selected = entity;
|
||||
_onUpdatedController.add(true);
|
||||
}
|
||||
|
||||
void unregisterSelected() {
|
||||
selected = null;
|
||||
_onUpdatedController.add(true);
|
||||
}
|
||||
|
||||
@override
|
||||
void select(FilamentEntity entity) {
|
||||
selected = entity;
|
||||
_gizmo.attach(entity);
|
||||
_onUpdatedController.add(true);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_filament/entities/entity_transform_controller.dart';
|
||||
import 'package:flutter_filament/filament_controller.dart';
|
||||
import 'package:flutter_filament/filament/entities/entity_transform_controller.dart';
|
||||
|
||||
class HardwareKeyboardListener {
|
||||
final EntityTransformController _controller;
|
||||
@@ -1,15 +1,13 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_filament/entities/entity_transform_controller.dart';
|
||||
import 'package:flutter_filament/filament_controller.dart';
|
||||
import 'package:flutter_filament/filament/entities/entity_transform_controller.dart';
|
||||
|
||||
class HardwareKeyboardPoll {
|
||||
final EntityTransformController _controller;
|
||||
late Timer _timer;
|
||||
HardwareKeyboardPoll(this._controller) {
|
||||
_timer = Timer.periodic(const Duration(milliseconds: 16), (_) {
|
||||
print(RawKeyboard.instance.keysPressed);
|
||||
if (RawKeyboard.instance.keysPressed.contains(LogicalKeyboardKey.keyW)) {
|
||||
_controller.forwardPressed();
|
||||
} else {
|
||||
10
lib/filament/utils/using_pointer.dart
Normal file
10
lib/filament/utils/using_pointer.dart
Normal file
@@ -0,0 +1,10 @@
|
||||
import 'dart:ffi';
|
||||
|
||||
import 'package:ffi/ffi.dart';
|
||||
|
||||
final allocator = calloc;
|
||||
|
||||
void using(Pointer ptr, Future Function(Pointer ptr) function) async {
|
||||
await function.call(ptr);
|
||||
allocator.free(ptr);
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_filament/camera/camera_orientation.dart';
|
||||
import 'package:flutter_filament/filament_controller.dart';
|
||||
|
||||
import 'package:flutter_filament/filament/filament_controller.dart';
|
||||
import 'package:flutter_filament/filament/utils/camera_orientation.dart';
|
||||
import 'dart:math';
|
||||
import 'package:vector_math/vector_math_64.dart' as v64;
|
||||
|
||||
147
lib/filament/widgets/debug/entity_list_widget.dart
Normal file
147
lib/filament/widgets/debug/entity_list_widget.dart
Normal file
@@ -0,0 +1,147 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_filament/filament/entities/gizmo.dart';
|
||||
import 'package:flutter_filament/filament/filament_controller.dart';
|
||||
|
||||
class EntityListWidget extends StatefulWidget {
|
||||
final FilamentController? controller;
|
||||
|
||||
const EntityListWidget({super.key, required this.controller});
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => _EntityListWidget();
|
||||
}
|
||||
|
||||
class _EntityListWidget extends State<EntityListWidget> {
|
||||
@override
|
||||
void didUpdateWidget(EntityListWidget oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
}
|
||||
|
||||
Widget _entity(FilamentEntity entity) {
|
||||
return FutureBuilder(
|
||||
future: widget.controller!.getAnimationNames(entity),
|
||||
builder: (_, animations) {
|
||||
if (animations.data == null) {
|
||||
return Container();
|
||||
}
|
||||
final menuController = MenuController();
|
||||
return Row(children: [
|
||||
Expanded(
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
widget.controller!.scene.select(entity);
|
||||
},
|
||||
child: Text(entity.toString(),
|
||||
style: TextStyle(
|
||||
fontWeight:
|
||||
entity == widget.controller!.scene.selected
|
||||
? FontWeight.bold
|
||||
: FontWeight.normal)))),
|
||||
MenuAnchor(
|
||||
controller: menuController,
|
||||
child: Container(
|
||||
color: Colors.transparent,
|
||||
child: IconButton(
|
||||
icon: const Icon(
|
||||
Icons.arrow_drop_down,
|
||||
color: Colors.black,
|
||||
),
|
||||
onPressed: () {
|
||||
menuController.open();
|
||||
},
|
||||
)),
|
||||
menuChildren: [
|
||||
MenuItemButton(
|
||||
child: const Text("Remove"),
|
||||
onPressed: () async {
|
||||
await widget.controller!.removeEntity(entity);
|
||||
}),
|
||||
MenuItemButton(
|
||||
child: const Text("Transform to unit cube"),
|
||||
onPressed: () async {
|
||||
await widget.controller!.transformToUnitCube(entity);
|
||||
}),
|
||||
SubmenuButton(
|
||||
child: const Text("Animations"),
|
||||
menuChildren: animations.data!
|
||||
.map((a) => MenuItemButton(
|
||||
child: Text(a),
|
||||
onPressed: () {
|
||||
widget.controller!.playAnimation(
|
||||
entity, animations.data!.indexOf(a));
|
||||
},
|
||||
))
|
||||
.toList())
|
||||
])
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
Widget _light(FilamentEntity entity) {
|
||||
final controller = MenuController();
|
||||
return Row(children: [
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
widget.controller!.scene.select(entity);
|
||||
},
|
||||
child: Container(
|
||||
color: Colors.transparent,
|
||||
child: Text("Light $entity",
|
||||
style: TextStyle(
|
||||
fontWeight: entity == widget.controller!.scene.selected
|
||||
? FontWeight.bold
|
||||
: FontWeight.normal)))),
|
||||
MenuAnchor(
|
||||
controller: controller,
|
||||
child: Container(
|
||||
color: Colors.transparent,
|
||||
child: IconButton(
|
||||
icon: const Icon(
|
||||
Icons.arrow_drop_down,
|
||||
color: Colors.black,
|
||||
),
|
||||
onPressed: () {
|
||||
controller.open();
|
||||
},
|
||||
)),
|
||||
menuChildren: [
|
||||
MenuItemButton(
|
||||
child: const Text("Remove"),
|
||||
onPressed: () async {
|
||||
await widget.controller!.removeLight(entity);
|
||||
})
|
||||
])
|
||||
]);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (widget.controller == null) {
|
||||
return Container();
|
||||
}
|
||||
return ValueListenableBuilder(
|
||||
valueListenable: widget.controller!.hasViewer,
|
||||
builder: (_, bool hasViewer, __) => !hasViewer
|
||||
? Container()
|
||||
: StreamBuilder(
|
||||
stream: widget.controller!.scene.onUpdated,
|
||||
builder: (_, __) => Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 30, vertical: 10),
|
||||
height: 100,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(30),
|
||||
color: Colors.white.withOpacity(0.25),
|
||||
),
|
||||
child: ListView(
|
||||
reverse: true,
|
||||
children: widget.controller!.scene
|
||||
.listLights()
|
||||
.map(_light)
|
||||
.followedBy(widget.controller!.scene
|
||||
.listEntities()
|
||||
.map(_entity))
|
||||
.cast<Widget>()
|
||||
.toList()))));
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_filament/entities/entity_transform_controller.dart';
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter_filament/filament/entities/entity_transform_controller.dart';
|
||||
|
||||
///
|
||||
/// A widget that translates mouse gestures to zoom/pan/rotate actions.
|
||||
///
|
||||
@@ -2,8 +2,8 @@ import 'dart:io';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_filament/widgets/filament_gesture_detector_desktop.dart';
|
||||
import 'package:flutter_filament/widgets/filament_gesture_detector_mobile.dart';
|
||||
import 'package:flutter_filament/filament/widgets/filament_gesture_detector_desktop.dart';
|
||||
import 'package:flutter_filament/filament/widgets/filament_gesture_detector_mobile.dart';
|
||||
import '../filament_controller.dart';
|
||||
|
||||
enum GestureType { rotateCamera, panCamera, panBackground }
|
||||
@@ -59,26 +59,35 @@ class FilamentGestureDetector extends StatelessWidget {
|
||||
|
||||
@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,
|
||||
enableCamera: enableCamera,
|
||||
enablePicking: enablePicking,
|
||||
);
|
||||
} else {
|
||||
return FilamentGestureDetectorMobile(
|
||||
controller: controller,
|
||||
child: child,
|
||||
showControlOverlay: showControlOverlay,
|
||||
enableCamera: enableCamera,
|
||||
enablePicking: enablePicking,
|
||||
onScaleStart: onScaleStart,
|
||||
onScaleUpdate: onScaleUpdate,
|
||||
onScaleEnd: onScaleEnd);
|
||||
}
|
||||
return ValueListenableBuilder(
|
||||
valueListenable: controller.hasViewer,
|
||||
builder: (_, bool hasViewer, __) {
|
||||
if (!hasViewer) {
|
||||
return Container(child: child);
|
||||
}
|
||||
if (kIsWeb) {
|
||||
throw Exception("TODO");
|
||||
} else if (Platform.isLinux ||
|
||||
Platform.isWindows ||
|
||||
Platform.isMacOS) {
|
||||
return FilamentGestureDetectorDesktop(
|
||||
controller: controller,
|
||||
child: child,
|
||||
showControlOverlay: showControlOverlay,
|
||||
enableCamera: enableCamera,
|
||||
enablePicking: enablePicking,
|
||||
);
|
||||
} else {
|
||||
return FilamentGestureDetectorMobile(
|
||||
controller: controller,
|
||||
child: child,
|
||||
showControlOverlay: showControlOverlay,
|
||||
enableCamera: enableCamera,
|
||||
enablePicking: enablePicking,
|
||||
onScaleStart: onScaleStart,
|
||||
onScaleUpdate: onScaleUpdate,
|
||||
onScaleEnd: onScaleEnd);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import 'dart:async';
|
||||
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_filament/filament/entities/gizmo.dart';
|
||||
import '../filament_controller.dart';
|
||||
|
||||
///
|
||||
@@ -60,6 +61,13 @@ class _FilamentGestureDetectorDesktopState
|
||||
|
||||
bool _pointerMoving = false;
|
||||
|
||||
Gizmo get _gizmo => widget.controller.scene.gizmo;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(FilamentGestureDetectorDesktop oldWidget) {
|
||||
if (widget.showControlOverlay != oldWidget.showControlOverlay ||
|
||||
@@ -91,9 +99,21 @@ class _FilamentGestureDetectorDesktopState
|
||||
});
|
||||
}
|
||||
|
||||
Timer? _pickTimer;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Listener(
|
||||
// onPointerHover: (event) async {
|
||||
// if (_gizmo.isActive) {
|
||||
// return;
|
||||
// }
|
||||
// _pickTimer?.cancel();
|
||||
// _pickTimer = Timer(const Duration(milliseconds: 100), () async {
|
||||
// widget.controller
|
||||
// .pick(event.position.dx.toInt(), event.position.dy.toInt());
|
||||
// });
|
||||
// },
|
||||
onPointerSignal: (PointerSignalEvent pointerSignal) async {
|
||||
if (pointerSignal is PointerScrollEvent) {
|
||||
if (widget.enableCamera) {
|
||||
@@ -106,10 +126,10 @@ class _FilamentGestureDetectorDesktopState
|
||||
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 {
|
||||
if (_gizmo.isActive) {
|
||||
return;
|
||||
}
|
||||
if (d.buttons != kTertiaryButton && widget.enablePicking) {
|
||||
widget.controller
|
||||
.pick(d.localPosition.dx.toInt(), d.localPosition.dy.toInt());
|
||||
@@ -118,6 +138,10 @@ class _FilamentGestureDetectorDesktopState
|
||||
},
|
||||
// holding/moving the left mouse button is interpreted as a pan, middle mouse button as a rotate
|
||||
onPointerMove: (PointerMoveEvent d) async {
|
||||
if (_gizmo.isActive) {
|
||||
_gizmo.translate(d.delta);
|
||||
return;
|
||||
}
|
||||
// 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.enableCamera) {
|
||||
@@ -142,6 +166,11 @@ class _FilamentGestureDetectorDesktopState
|
||||
// 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 (_gizmo.isActive) {
|
||||
_gizmo.reset();
|
||||
return;
|
||||
}
|
||||
|
||||
if (d.buttons == kTertiaryButton && widget.enableCamera) {
|
||||
widget.controller.rotateEnd();
|
||||
} else {
|
||||
@@ -6,7 +6,7 @@ import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
|
||||
import 'package:flutter_filament/filament_controller.dart';
|
||||
import 'package:flutter_filament/filament/filament_controller.dart';
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
@@ -2,7 +2,7 @@ import 'dart:math';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_filament/filament_controller.dart';
|
||||
import 'package:flutter_filament/filament/filament_controller.dart';
|
||||
import 'package:vector_math/vector_math_64.dart' as v;
|
||||
|
||||
class IblRotationSliderWidget extends StatefulWidget {
|
||||
@@ -2,8 +2,8 @@ import 'dart:math';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_filament/filament_controller.dart';
|
||||
import 'package:flutter_filament/lights/light_options.dart';
|
||||
import 'package:flutter_filament/filament/filament_controller.dart';
|
||||
import 'package:flutter_filament/filament/utils/light_options.dart';
|
||||
import 'package:vector_math/vector_math_64.dart' as v;
|
||||
|
||||
class LightSliderWidget extends StatefulWidget {
|
||||
12
lib/flutter_filament.dart
Normal file
12
lib/flutter_filament.dart
Normal file
@@ -0,0 +1,12 @@
|
||||
library flutter_filament;
|
||||
|
||||
export 'filament/filament_controller.dart';
|
||||
export 'filament/filament_controller_ffi.dart';
|
||||
export 'filament/animations/animation_builder.dart';
|
||||
export 'filament/animations/animation_data.dart';
|
||||
export 'filament/widgets/camera_options_widget.dart';
|
||||
export 'filament/widgets/filament_gesture_detector.dart';
|
||||
export 'filament/widgets/filament_widget.dart';
|
||||
export 'filament/widgets/debug/entity_list_widget.dart';
|
||||
export 'filament/entities/entity_transform_controller.dart';
|
||||
export 'filament/widgets/entity_controller_mouse_widget.dart';
|
||||
Reference in New Issue
Block a user