Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix position initial when go to second hotspot #48

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
168 changes: 124 additions & 44 deletions lib/panorama.dart
Original file line number Diff line number Diff line change
Expand Up @@ -114,14 +114,17 @@ class Panorama extends StatefulWidget {
final Function(double longitude, double latitude, double tilt)? onTap;

/// This event will be called when the user has started a long press, it contains latitude and longitude about where the user pressed.
final Function(double longitude, double latitude, double tilt)? onLongPressStart;
final Function(double longitude, double latitude, double tilt)?
onLongPressStart;

/// This event will be called when the user has drag-moved after a long press, it contains latitude and longitude about where the user pressed.
final Function(double longitude, double latitude, double tilt)? onLongPressMoveUpdate;
final Function(double longitude, double latitude, double tilt)?
onLongPressMoveUpdate;

/// This event will be called when the user has stopped a long presses, it contains latitude and longitude about where the user pressed.
final Function(double longitude, double latitude, double tilt)? onLongPressEnd;

final Function(double longitude, double latitude, double tilt)?
onLongPressEnd;

/// This event will be called when provided image is loaded on texture.
final Function()? onImageLoad;

Expand All @@ -135,7 +138,8 @@ class Panorama extends StatefulWidget {
_PanoramaState createState() => _PanoramaState();
}

class _PanoramaState extends State<Panorama> with SingleTickerProviderStateMixin {
class _PanoramaState extends State<Panorama>
with SingleTickerProviderStateMixin {
Scene? scene;
Object? surface;
late double latitude;
Expand All @@ -158,22 +162,26 @@ class _PanoramaState extends State<Panorama> with SingleTickerProviderStateMixin
ImageStream? _imageStream;

void _handleTapUp(TapUpDetails details) {
final Vector3 o = positionToLatLon(details.localPosition.dx, details.localPosition.dy);
final Vector3 o =
positionToLatLon(details.localPosition.dx, details.localPosition.dy);
widget.onTap!(degrees(o.x), degrees(-o.y), degrees(o.z));
}

void _handleLongPressStart(LongPressStartDetails details) {
final Vector3 o = positionToLatLon(details.localPosition.dx, details.localPosition.dy);
final Vector3 o =
positionToLatLon(details.localPosition.dx, details.localPosition.dy);
widget.onLongPressStart!(degrees(o.x), degrees(-o.y), degrees(o.z));
}

void _handleLongPressMoveUpdate(LongPressMoveUpdateDetails details) {
final Vector3 o = positionToLatLon(details.localPosition.dx, details.localPosition.dy);
final Vector3 o =
positionToLatLon(details.localPosition.dx, details.localPosition.dy);
widget.onLongPressMoveUpdate!(degrees(o.x), degrees(-o.y), degrees(o.z));
}

void _handleLongPressEnd(LongPressEndDetails details) {
final Vector3 o = positionToLatLon(details.localPosition.dx, details.localPosition.dy);
final Vector3 o =
positionToLatLon(details.localPosition.dx, details.localPosition.dy);
widget.onLongPressEnd!(degrees(o.x), degrees(-o.y), degrees(o.z));
}

Expand All @@ -185,13 +193,23 @@ class _PanoramaState extends State<Panorama> with SingleTickerProviderStateMixin
void _handleScaleUpdate(ScaleUpdateDetails details) {
final offset = details.localFocalPoint - _lastFocalPoint;
_lastFocalPoint = details.localFocalPoint;
latitudeDelta += widget.sensitivity * 0.5 * math.pi * offset.dy / scene!.camera.viewportHeight;
longitudeDelta -= widget.sensitivity * _animateDirection * 0.5 * math.pi * offset.dx / scene!.camera.viewportHeight;
latitudeDelta += widget.sensitivity *
0.5 *
math.pi *
offset.dy /
scene!.camera.viewportHeight;
longitudeDelta -= widget.sensitivity *
_animateDirection *
0.5 *
math.pi *
offset.dx /
scene!.camera.viewportHeight;
if (_lastZoom == null) {
_lastZoom = scene!.camera.zoom;
}
zoomDelta += _lastZoom! * details.scale - (scene!.camera.zoom + zoomDelta);
if (widget.sensorControl == SensorControl.None && !_controller.isAnimating) {
if (widget.sensorControl == SensorControl.None &&
!_controller.isAnimating) {
_controller.reset();
if (widget.animSpeed != 0) {
_controller.repeat();
Expand All @@ -208,7 +226,10 @@ class _PanoramaState extends State<Panorama> with SingleTickerProviderStateMixin
latitude += latitudeDelta * _dampingFactor * widget.sensitivity;
latitudeDelta *= 1 - _dampingFactor * widget.sensitivity;
// animate horizontal rotating
longitude += _animateDirection * longitudeDelta * _dampingFactor * widget.sensitivity;
longitude += _animateDirection *
longitudeDelta *
_dampingFactor *
widget.sensitivity;
longitudeDelta *= 1 - _dampingFactor * widget.sensitivity;
// animate zomming
final double zoom = scene!.camera.zoom + zoomDelta * _dampingFactor;
Expand Down Expand Up @@ -263,7 +284,8 @@ class _PanoramaState extends State<Panorama> with SingleTickerProviderStateMixin
// rotate around the local X axis
q = Quaternion.axisAngle(Vector3(1, 0, 0), -latitude) * q;

o = quaternionToOrientation(q * Quaternion.axisAngle(Vector3(0, 1, 0), math.pi * 0.5));
o = quaternionToOrientation(
q * Quaternion.axisAngle(Vector3(0, 1, 0), math.pi * 0.5));
widget.onViewChanged?.call(degrees(o.x), degrees(-o.y), degrees(o.z));

q.rotate(scene!.camera.target..setFrom(Vector3(0, 0, -_radius)));
Expand All @@ -276,14 +298,18 @@ class _PanoramaState extends State<Panorama> with SingleTickerProviderStateMixin
_orientationSubscription?.cancel();
switch (widget.sensorControl) {
case SensorControl.Orientation:
motionSensors.orientationUpdateInterval = Duration.microsecondsPerSecond ~/ 60;
_orientationSubscription = motionSensors.orientation.listen((OrientationEvent event) {
motionSensors.orientationUpdateInterval =
Duration.microsecondsPerSecond ~/ 60;
_orientationSubscription =
motionSensors.orientation.listen((OrientationEvent event) {
orientation.setValues(event.yaw, event.pitch, event.roll);
});
break;
case SensorControl.AbsoluteOrientation:
motionSensors.absoluteOrientationUpdateInterval = Duration.microsecondsPerSecond ~/ 60;
_orientationSubscription = motionSensors.absoluteOrientation.listen((AbsoluteOrientationEvent event) {
motionSensors.absoluteOrientationUpdateInterval =
Duration.microsecondsPerSecond ~/ 60;
_orientationSubscription = motionSensors.absoluteOrientation
.listen((AbsoluteOrientationEvent event) {
orientation.setValues(event.yaw, event.pitch, event.roll);
});
break;
Expand All @@ -292,15 +318,17 @@ class _PanoramaState extends State<Panorama> with SingleTickerProviderStateMixin

_screenOrientSubscription?.cancel();
if (widget.sensorControl != SensorControl.None) {
_screenOrientSubscription = motionSensors.screenOrientation.listen((ScreenOrientationEvent event) {
_screenOrientSubscription = motionSensors.screenOrientation
.listen((ScreenOrientationEvent event) {
screenOrientation = radians(event.angle!);
});
}
}

void _updateTexture(ImageInfo imageInfo, bool synchronousCall) {
surface?.mesh.texture = imageInfo.image;
surface?.mesh.textureRect = Rect.fromLTWH(0, 0, imageInfo.image.width.toDouble(), imageInfo.image.height.toDouble());
surface?.mesh.textureRect = Rect.fromLTWH(0, 0,
imageInfo.image.width.toDouble(), imageInfo.image.height.toDouble());
scene!.texture = imageInfo.image;
scene!.update();
widget.onImageLoad?.call();
Expand All @@ -312,6 +340,9 @@ class _PanoramaState extends State<Panorama> with SingleTickerProviderStateMixin
_imageStream = provider.resolve(ImageConfiguration());
ImageStreamListener listener = ImageStreamListener(_updateTexture);
_imageStream!.addListener(listener);

latitude = degrees(widget.latitude); // Fix Position
longitude = degrees(widget.longitude); // Fix Position
}

void _onSceneCreated(Scene scene) {
Expand All @@ -322,7 +353,13 @@ class _PanoramaState extends State<Panorama> with SingleTickerProviderStateMixin
scene.camera.zoom = widget.zoom;
scene.camera.position.setFrom(Vector3(0, 0, 0.1));
if (widget.child != null) {
final Mesh mesh = generateSphereMesh(radius: _radius, latSegments: widget.latSegments, lonSegments: widget.lonSegments, croppedArea: widget.croppedArea, croppedFullWidth: widget.croppedFullWidth, croppedFullHeight: widget.croppedFullHeight);
final Mesh mesh = generateSphereMesh(
radius: _radius,
latSegments: widget.latSegments,
lonSegments: widget.lonSegments,
croppedArea: widget.croppedArea,
croppedFullWidth: widget.croppedFullWidth,
croppedFullHeight: widget.croppedFullHeight);
surface = Object(name: 'surface', mesh: mesh, backfaceCulling: false);
_loadTexture(widget.child!.image);
scene.world.add(surface!);
Expand All @@ -336,23 +373,29 @@ class _PanoramaState extends State<Panorama> with SingleTickerProviderStateMixin

Vector3 positionToLatLon(double x, double y) {
// transform viewport coordinate to NDC, values between -1 and 1
final Vector4 v = Vector4(2.0 * x / scene!.camera.viewportWidth - 1.0, 1.0 - 2.0 * y / scene!.camera.viewportHeight, 1.0, 1.0);
final Vector4 v = Vector4(2.0 * x / scene!.camera.viewportWidth - 1.0,
1.0 - 2.0 * y / scene!.camera.viewportHeight, 1.0, 1.0);
// create projection matrix
final Matrix4 m = scene!.camera.projectionMatrix * scene!.camera.lookAtMatrix;
final Matrix4 m =
scene!.camera.projectionMatrix * scene!.camera.lookAtMatrix;
// apply inversed projection matrix
m.invert();
v.applyMatrix4(m);
// apply perspective division
v.scale(1 / v.w);
// get rotation from two vectors
final Quaternion q = Quaternion.fromTwoVectors(v.xyz, Vector3(0.0, 0.0, -_radius));
final Quaternion q =
Quaternion.fromTwoVectors(v.xyz, Vector3(0.0, 0.0, -_radius));
// get euler angles from rotation
return quaternionToOrientation(q * Quaternion.axisAngle(Vector3(0, 1, 0), math.pi * 0.5));
return quaternionToOrientation(
q * Quaternion.axisAngle(Vector3(0, 1, 0), math.pi * 0.5));
}

Vector3 positionFromLatLon(double lat, double lon) {
// create projection matrix
final Matrix4 m = scene!.camera.projectionMatrix * scene!.camera.lookAtMatrix * matrixFromLatLon(lat, lon);
final Matrix4 m = scene!.camera.projectionMatrix *
scene!.camera.lookAtMatrix *
matrixFromLatLon(lat, lon);
// apply projection matrix
final Vector4 v = Vector4(0.0, 0.0, -_radius, 1.0)..applyMatrix4(m);
// apply perspective division and transform NDC to the viewport coordinate
Expand All @@ -367,9 +410,12 @@ class _PanoramaState extends State<Panorama> with SingleTickerProviderStateMixin
final List<Widget> widgets = <Widget>[];
if (hotspots != null && scene != null) {
for (Hotspot hotspot in hotspots) {
final Vector3 pos = positionFromLatLon(hotspot.latitude, hotspot.longitude);
final Offset orgin = Offset(hotspot.width * hotspot.orgin.dx, hotspot.height * hotspot.orgin.dy);
final Matrix4 transform = scene!.camera.lookAtMatrix * matrixFromLatLon(hotspot.latitude, hotspot.longitude);
final Vector3 pos =
positionFromLatLon(hotspot.latitude, hotspot.longitude);
final Offset orgin = Offset(hotspot.width * hotspot.orgin.dx,
hotspot.height * hotspot.orgin.dy);
final Matrix4 transform = scene!.camera.lookAtMatrix *
matrixFromLatLon(hotspot.latitude, hotspot.longitude);
final Widget child = Positioned(
left: pos.x - orgin.dx,
top: pos.y - orgin.dy,
Expand Down Expand Up @@ -400,8 +446,11 @@ class _PanoramaState extends State<Panorama> with SingleTickerProviderStateMixin

_updateSensorControl();

_controller = AnimationController(duration: Duration(milliseconds: 60000), vsync: this)..addListener(_updateView);
if (widget.sensorControl != SensorControl.None || widget.animSpeed != 0) _controller.repeat();
_controller = AnimationController(
duration: Duration(milliseconds: 60000), vsync: this)
..addListener(_updateView);
if (widget.sensorControl != SensorControl.None || widget.animSpeed != 0)
_controller.repeat();
}

@override
Expand All @@ -418,8 +467,18 @@ class _PanoramaState extends State<Panorama> with SingleTickerProviderStateMixin
void didUpdateWidget(Panorama oldWidget) {
super.didUpdateWidget(oldWidget);
if (surface == null) return;
if (widget.latSegments != oldWidget.latSegments || widget.lonSegments != oldWidget.lonSegments || widget.croppedArea != oldWidget.croppedArea || widget.croppedFullWidth != oldWidget.croppedFullWidth || widget.croppedFullHeight != oldWidget.croppedFullHeight) {
surface!.mesh = generateSphereMesh(radius: _radius, latSegments: widget.latSegments, lonSegments: widget.lonSegments, croppedArea: widget.croppedArea, croppedFullWidth: widget.croppedFullWidth, croppedFullHeight: widget.croppedFullHeight);
if (widget.latSegments != oldWidget.latSegments ||
widget.lonSegments != oldWidget.lonSegments ||
widget.croppedArea != oldWidget.croppedArea ||
widget.croppedFullWidth != oldWidget.croppedFullWidth ||
widget.croppedFullHeight != oldWidget.croppedFullHeight) {
surface!.mesh = generateSphereMesh(
radius: _radius,
latSegments: widget.latSegments,
lonSegments: widget.lonSegments,
croppedArea: widget.croppedArea,
croppedFullWidth: widget.croppedFullWidth,
croppedFullHeight: widget.croppedFullHeight);
}
if (widget.child?.image != oldWidget.child?.image) {
_loadTexture(widget.child?.image);
Expand Down Expand Up @@ -448,9 +507,13 @@ class _PanoramaState extends State<Panorama> with SingleTickerProviderStateMixin
onScaleStart: _handleScaleStart,
onScaleUpdate: _handleScaleUpdate,
onTapUp: widget.onTap == null ? null : _handleTapUp,
onLongPressStart: widget.onLongPressStart == null ? null : _handleLongPressStart,
onLongPressMoveUpdate: widget.onLongPressMoveUpdate == null ? null : _handleLongPressMoveUpdate,
onLongPressEnd: widget.onLongPressEnd == null ? null : _handleLongPressEnd,
onLongPressStart:
widget.onLongPressStart == null ? null : _handleLongPressStart,
onLongPressMoveUpdate: widget.onLongPressMoveUpdate == null
? null
: _handleLongPressMoveUpdate,
onLongPressEnd:
widget.onLongPressEnd == null ? null : _handleLongPressEnd,
child: pano,
)
: pano;
Expand Down Expand Up @@ -489,22 +552,33 @@ class Hotspot {
Widget? widget;
}

Mesh generateSphereMesh({num radius = 1.0, int latSegments = 16, int lonSegments = 16, ui.Image? texture, Rect croppedArea = const Rect.fromLTWH(0.0, 0.0, 1.0, 1.0), double croppedFullWidth = 1.0, double croppedFullHeight = 1.0}) {
Mesh generateSphereMesh(
{num radius = 1.0,
int latSegments = 16,
int lonSegments = 16,
ui.Image? texture,
Rect croppedArea = const Rect.fromLTWH(0.0, 0.0, 1.0, 1.0),
double croppedFullWidth = 1.0,
double croppedFullHeight = 1.0}) {
int count = (latSegments + 1) * (lonSegments + 1);
List<Vector3> vertices = List<Vector3>.filled(count, Vector3.zero());
List<Offset> texcoords = List<Offset>.filled(count, Offset.zero);
List<Polygon> indices = List<Polygon>.filled(latSegments * lonSegments * 2, Polygon(0, 0, 0));
List<Polygon> indices =
List<Polygon>.filled(latSegments * lonSegments * 2, Polygon(0, 0, 0));

int i = 0;
for (int y = 0; y <= latSegments; ++y) {
final double tv = y / latSegments;
final double v = (croppedArea.top + croppedArea.height * tv) / croppedFullHeight;
final double v =
(croppedArea.top + croppedArea.height * tv) / croppedFullHeight;
final double sv = math.sin(v * math.pi);
final double cv = math.cos(v * math.pi);
for (int x = 0; x <= lonSegments; ++x) {
final double tu = x / lonSegments;
final double u = (croppedArea.left + croppedArea.width * tu) / croppedFullWidth;
vertices[i] = Vector3(radius * math.cos(u * math.pi * 2.0) * sv, radius * cv, radius * math.sin(u * math.pi * 2.0) * sv);
final double u =
(croppedArea.left + croppedArea.width * tu) / croppedFullWidth;
vertices[i] = Vector3(radius * math.cos(u * math.pi * 2.0) * sv,
radius * cv, radius * math.sin(u * math.pi * 2.0) * sv);
texcoords[i] = Offset(tu, 1.0 - tv);
i++;
}
Expand All @@ -520,7 +594,11 @@ Mesh generateSphereMesh({num radius = 1.0, int latSegments = 16, int lonSegments
}
}

final Mesh mesh = Mesh(vertices: vertices, texcoords: texcoords, indices: indices, texture: texture);
final Mesh mesh = Mesh(
vertices: vertices,
texcoords: texcoords,
indices: indices,
texture: texture);
return mesh;
}

Expand All @@ -533,9 +611,11 @@ Vector3 quaternionToOrientation(Quaternion q) {
final double y = storage[1];
final double z = storage[2];
final double w = storage[3];
final double roll = math.atan2(-2 * (x * y - w * z), 1.0 - 2 * (x * x + z * z));
final double roll =
math.atan2(-2 * (x * y - w * z), 1.0 - 2 * (x * x + z * z));
final double pitch = math.asin(2 * (y * z + w * x));
final double yaw = math.atan2(-2 * (x * z - w * y), 1.0 - 2 * (x * x + y * y));
final double yaw =
math.atan2(-2 * (x * z - w * y), 1.0 - 2 * (x * x + y * y));
return Vector3(yaw, pitch, roll);
}

Expand Down