From 791473c5c5e0a34d917e0972673668724b7c245b Mon Sep 17 00:00:00 2001 From: Mark Hu Date: Wed, 23 Dec 2020 16:11:55 +0800 Subject: [PATCH] add hotspots for placing widgets in the panorama --- lib/panorama.dart | 96 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 94 insertions(+), 2 deletions(-) diff --git a/lib/panorama.dart b/lib/panorama.dart index 614e352..292c123 100644 --- a/lib/panorama.dart +++ b/lib/panorama.dart @@ -39,6 +39,7 @@ class Panorama extends StatefulWidget { this.sensorControl = SensorControl.None, this.onViewChanged, this.child, + this.hotspots, }) : super(key: key); /// The initial latitude, in degrees, between -90 and 90. default to 0 (the vertical center of the image). @@ -95,6 +96,9 @@ class Panorama extends StatefulWidget { /// Specify an Image(equirectangular image) widget to the panorama. final Image child; + /// Place widgets in the panorama. + final List hotspots; + @override _PanoramaState createState() => _PanoramaState(); } @@ -116,6 +120,8 @@ class _PanoramaState extends State with SingleTickerProviderStateMixin Vector3 orientation = Vector3(0, radians(90), 0); StreamSubscription _orientationSubscription; StreamSubscription _screenOrientSubscription; + StreamController _streamController; + Stream _stream; void _handleScaleStart(ScaleStartDetails details) { _lastFocalPoint = details.localFocalPoint; @@ -252,11 +258,52 @@ class _PanoramaState extends State with SingleTickerProviderStateMixin } } + Vector3 positionFromLatLon(double lat, double lon) { + if (scene == null) return Vector3.all(double.maxFinite); + // generate a transform matrix + final Matrix4 model = Matrix4.identity(); + model.rotateY(radians(90.0 - lon)); + model.rotateX(radians(lat)); + final Matrix4 m = scene.camera.projectionMatrix * scene.camera.lookAtMatrix * model; + // apply transform + final Vector4 v = Vector4(0.0, 0.0, -_radius, 1.0); + v.applyMatrix4(m); + return (v.z < 0.0) + // make it invisible if the hotspot is behind the camera + ? Vector3.all(double.maxFinite) + // transform homogeneous coordinates to NDC and remaps to the viewport + : Vector3( + (1.0 + v.x / v.w) * scene.camera.viewportWidth / 2, + (1.0 - v.y / v.w) * scene.camera.viewportHeight / 2, + v.z, + ); + } + + Widget buildHotspotWidgets(List hotspots) { + final List widgets = List(); + if (hotspots != null) { + for (Hotspot hotspot in hotspots) { + final Vector3 pos = positionFromLatLon(hotspot.latitude, hotspot.longitude); + final Widget child = Positioned( + left: pos.x - hotspot.width * hotspot.orgin.dx, + top: pos.y - hotspot.height * hotspot.orgin.dy, + width: hotspot.width, + height: hotspot.height, + child: hotspot.widget, + ); + widgets.add(child); + } + } + return Stack(children: widgets); + } + @override void initState() { super.initState(); latitude = degrees(widget.latitude); longitude = degrees(widget.longitude); + _streamController = StreamController.broadcast(); + _stream = _streamController.stream; _updateSensorControl(); @@ -269,6 +316,7 @@ class _PanoramaState extends State with SingleTickerProviderStateMixin _orientationSubscription?.cancel(); _screenOrientSubscription?.cancel(); _controller.dispose(); + _streamController.close(); super.dispose(); } @@ -294,16 +342,60 @@ class _PanoramaState extends State with SingleTickerProviderStateMixin @override Widget build(BuildContext context) { + Widget pano = Stack( + children: [ + Cube(interactive: false, onSceneCreated: _onSceneCreated), + StreamBuilder( + stream: _stream, + builder: (BuildContext context, AsyncSnapshot snapshot) { + return buildHotspotWidgets(widget.hotspots); + }, + ), + ], + ); + return widget.interactive ? GestureDetector( onScaleStart: _handleScaleStart, onScaleUpdate: _handleScaleUpdate, - child: Cube(interactive: false, onSceneCreated: _onSceneCreated), + child: pano, ) - : Cube(interactive: false, onSceneCreated: _onSceneCreated); + : pano; } } +class Hotspot { + Hotspot({ + this.name, + this.latitude, + this.longitude, + this.orgin = const Offset(0.5, 0.5), + this.width = 32.0, + this.height = 32.0, + this.widget, + }); + + /// The name of this hotspot. + String name; + + /// The initial latitude, in degrees, between -90 and 90. + final double latitude; + + /// The initial longitude, in degrees, between -180 and 180. + final double longitude; + + /// The local orgin of this hotspot. Default is Offset(0.5, 0.5). + final Offset orgin; + + // The width of widget. Default is 32.0 + double width; + + // The height of widget. Default is 32.0 + double height; + + Widget widget; +} + Mesh generateSphereMesh({num radius = 1.0, int latSegments = 16, int lonSegments = 16, ui.Image texture}) { int count = (latSegments + 1) * (lonSegments + 1); List vertices = List(count);