mirror of
https://github.com/FoggedLens/deflock-app.git
synced 2026-07-10 06:28:38 +02:00
Support FOV range notation: 0-360, 90-270, 10-45;90-125
This commit is contained in:
@@ -6,6 +6,7 @@ import 'package:latlong2/latlong.dart';
|
||||
import '../../app_state.dart';
|
||||
import '../../dev_config.dart';
|
||||
import '../../models/osm_node.dart';
|
||||
import '../../models/direction_fov.dart';
|
||||
|
||||
/// Helper class to build direction cone polygons for cameras
|
||||
class DirectionConesBuilder {
|
||||
@@ -20,10 +21,13 @@ class DirectionConesBuilder {
|
||||
|
||||
// Add session cones if in add-camera mode and profile requires direction
|
||||
if (session != null && session.target != null && session.profile?.requiresDirection == true) {
|
||||
final sessionFov = session.profile?.fov ?? (kDirectionConeHalfAngle * 2);
|
||||
|
||||
// Add current working direction (full opacity)
|
||||
overlays.add(_buildCone(
|
||||
overlays.add(_buildConeWithFov(
|
||||
session.target!,
|
||||
session.directionDegrees,
|
||||
sessionFov,
|
||||
zoom,
|
||||
context: context,
|
||||
isSession: true,
|
||||
@@ -33,9 +37,10 @@ class DirectionConesBuilder {
|
||||
// Add other directions (reduced opacity)
|
||||
for (int i = 0; i < session.directions.length; i++) {
|
||||
if (i != session.currentDirectionIndex) {
|
||||
overlays.add(_buildCone(
|
||||
overlays.add(_buildConeWithFov(
|
||||
session.target!,
|
||||
session.directions[i],
|
||||
sessionFov,
|
||||
zoom,
|
||||
context: context,
|
||||
isSession: true,
|
||||
@@ -47,10 +52,13 @@ class DirectionConesBuilder {
|
||||
|
||||
// Add edit session cones if in edit-camera mode and profile requires direction
|
||||
if (editSession != null && editSession.profile?.requiresDirection == true) {
|
||||
final sessionFov = editSession.profile?.fov ?? (kDirectionConeHalfAngle * 2);
|
||||
|
||||
// Add current working direction (full opacity)
|
||||
overlays.add(_buildCone(
|
||||
overlays.add(_buildConeWithFov(
|
||||
editSession.target,
|
||||
editSession.directionDegrees,
|
||||
sessionFov,
|
||||
zoom,
|
||||
context: context,
|
||||
isSession: true,
|
||||
@@ -60,9 +68,10 @@ class DirectionConesBuilder {
|
||||
// Add other directions (reduced opacity)
|
||||
for (int i = 0; i < editSession.directions.length; i++) {
|
||||
if (i != editSession.currentDirectionIndex) {
|
||||
overlays.add(_buildCone(
|
||||
overlays.add(_buildConeWithFov(
|
||||
editSession.target,
|
||||
editSession.directions[i],
|
||||
sessionFov,
|
||||
zoom,
|
||||
context: context,
|
||||
isSession: true,
|
||||
@@ -76,11 +85,12 @@ class DirectionConesBuilder {
|
||||
for (final node in cameras) {
|
||||
if (_isValidCameraWithDirection(node) &&
|
||||
(editSession == null || node.id != editSession.originalNode.id)) {
|
||||
// Build a cone for each direction
|
||||
for (final direction in node.directionDeg) {
|
||||
overlays.add(_buildCone(
|
||||
// Build a cone for each direction+fov pair
|
||||
for (final directionFov in node.directionFovPairs) {
|
||||
overlays.add(_buildConeWithFov(
|
||||
node.coord,
|
||||
direction,
|
||||
directionFov.centerDegrees,
|
||||
directionFov.fovDegrees,
|
||||
zoom,
|
||||
context: context,
|
||||
));
|
||||
@@ -103,6 +113,30 @@ class DirectionConesBuilder {
|
||||
node.tags['_pending_upload'] == 'true';
|
||||
}
|
||||
|
||||
/// Build cone with variable FOV width - new method for range notation support
|
||||
static Polygon _buildConeWithFov(
|
||||
LatLng origin,
|
||||
double bearingDeg,
|
||||
double fovDegrees,
|
||||
double zoom, {
|
||||
required BuildContext context,
|
||||
bool isPending = false,
|
||||
bool isSession = false,
|
||||
bool isActiveDirection = true,
|
||||
}) {
|
||||
return _buildConeInternal(
|
||||
origin: origin,
|
||||
bearingDeg: bearingDeg,
|
||||
halfAngleDeg: fovDegrees / 2,
|
||||
zoom: zoom,
|
||||
context: context,
|
||||
isPending: isPending,
|
||||
isSession: isSession,
|
||||
isActiveDirection: isActiveDirection,
|
||||
);
|
||||
}
|
||||
|
||||
/// Legacy method for backward compatibility - uses dev_config FOV
|
||||
static Polygon _buildCone(
|
||||
LatLng origin,
|
||||
double bearingDeg,
|
||||
@@ -112,7 +146,39 @@ class DirectionConesBuilder {
|
||||
bool isSession = false,
|
||||
bool isActiveDirection = true,
|
||||
}) {
|
||||
final halfAngle = kDirectionConeHalfAngle;
|
||||
return _buildConeInternal(
|
||||
origin: origin,
|
||||
bearingDeg: bearingDeg,
|
||||
halfAngleDeg: kDirectionConeHalfAngle,
|
||||
zoom: zoom,
|
||||
context: context,
|
||||
isPending: isPending,
|
||||
isSession: isSession,
|
||||
isActiveDirection: isActiveDirection,
|
||||
);
|
||||
}
|
||||
|
||||
/// Internal cone building method that handles the actual rendering
|
||||
static Polygon _buildConeInternal({
|
||||
required LatLng origin,
|
||||
required double bearingDeg,
|
||||
required double halfAngleDeg,
|
||||
required double zoom,
|
||||
required BuildContext context,
|
||||
bool isPending = false,
|
||||
bool isSession = false,
|
||||
bool isActiveDirection = true,
|
||||
}) {
|
||||
// Handle full circle case (360-degree FOV)
|
||||
if (halfAngleDeg >= 180) {
|
||||
return _buildFullCircle(
|
||||
origin: origin,
|
||||
zoom: zoom,
|
||||
context: context,
|
||||
isSession: isSession,
|
||||
isActiveDirection: isActiveDirection,
|
||||
);
|
||||
}
|
||||
|
||||
// Calculate pixel-based radii
|
||||
final outerRadiusPx = kNodeIconDiameter + (kNodeIconDiameter * kDirectionConeBaseLength);
|
||||
@@ -124,7 +190,9 @@ class DirectionConesBuilder {
|
||||
final innerRadius = innerRadiusPx * pixelToCoordinate;
|
||||
|
||||
// Number of points for the outer arc (within our directional range)
|
||||
const int arcPoints = 12;
|
||||
// Scale arc points based on FOV width for better rendering
|
||||
final baseArcPoints = 12;
|
||||
final arcPoints = math.max(6, (baseArcPoints * halfAngleDeg / 45).round());
|
||||
|
||||
LatLng project(double deg, double distance) {
|
||||
final rad = deg * math.pi / 180;
|
||||
@@ -139,13 +207,13 @@ class DirectionConesBuilder {
|
||||
|
||||
// Add outer arc points from left to right (counterclockwise for proper polygon winding)
|
||||
for (int i = 0; i <= arcPoints; i++) {
|
||||
final angle = bearingDeg - halfAngle + (i * 2 * halfAngle / arcPoints);
|
||||
final angle = bearingDeg - halfAngleDeg + (i * 2 * halfAngleDeg / arcPoints);
|
||||
points.add(project(angle, outerRadius));
|
||||
}
|
||||
|
||||
// Add inner arc points from right to left (to close the donut shape)
|
||||
for (int i = arcPoints; i >= 0; i--) {
|
||||
final angle = bearingDeg - halfAngle + (i * 2 * halfAngle / arcPoints);
|
||||
final angle = bearingDeg - halfAngleDeg + (i * 2 * halfAngleDeg / arcPoints);
|
||||
points.add(project(angle, innerRadius));
|
||||
}
|
||||
|
||||
@@ -162,4 +230,59 @@ class DirectionConesBuilder {
|
||||
borderStrokeWidth: getDirectionConeBorderWidth(context),
|
||||
);
|
||||
}
|
||||
|
||||
/// Build a full circle for 360-degree FOV cases
|
||||
static Polygon _buildFullCircle({
|
||||
required LatLng origin,
|
||||
required double zoom,
|
||||
required BuildContext context,
|
||||
bool isSession = false,
|
||||
bool isActiveDirection = true,
|
||||
}) {
|
||||
// Calculate pixel-based radii
|
||||
final outerRadiusPx = kNodeIconDiameter + (kNodeIconDiameter * kDirectionConeBaseLength);
|
||||
final innerRadiusPx = kNodeIconDiameter + (2 * getNodeRingThickness(context));
|
||||
|
||||
// Convert pixels to coordinate distances with zoom scaling
|
||||
final pixelToCoordinate = 0.00001 * math.pow(2, 15 - zoom);
|
||||
final outerRadius = outerRadiusPx * pixelToCoordinate;
|
||||
final innerRadius = innerRadiusPx * pixelToCoordinate;
|
||||
|
||||
// Create full circle with many points for smooth rendering
|
||||
const int circlePoints = 36;
|
||||
final points = <LatLng>[];
|
||||
|
||||
LatLng project(double deg, double distance) {
|
||||
final rad = deg * math.pi / 180;
|
||||
final dLat = distance * math.cos(rad);
|
||||
final dLon =
|
||||
distance * math.sin(rad) / math.cos(origin.latitude * math.pi / 180);
|
||||
return LatLng(origin.latitude + dLat, origin.longitude + dLon);
|
||||
}
|
||||
|
||||
// Add outer circle points
|
||||
for (int i = 0; i < circlePoints; i++) {
|
||||
final angle = i * 360.0 / circlePoints;
|
||||
points.add(project(angle, outerRadius));
|
||||
}
|
||||
|
||||
// Add inner circle points in reverse order to create donut
|
||||
for (int i = circlePoints - 1; i >= 0; i--) {
|
||||
final angle = i * 360.0 / circlePoints;
|
||||
points.add(project(angle, innerRadius));
|
||||
}
|
||||
|
||||
// Adjust opacity based on direction state
|
||||
double opacity = kDirectionConeOpacity;
|
||||
if (isSession && !isActiveDirection) {
|
||||
opacity = kDirectionConeOpacity * 0.4;
|
||||
}
|
||||
|
||||
return Polygon(
|
||||
points: points,
|
||||
color: kDirectionConeColor.withOpacity(opacity),
|
||||
borderColor: kDirectionConeColor,
|
||||
borderStrokeWidth: getDirectionConeBorderWidth(context),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user