Einzelnen Beitrag anzeigen

CHackbart

Registriert seit: 22. Okt 2012
267 Beiträge
 
#1

Augmented Reality Toolkit for Delphi

  Alt 16. Dez 2013, 12:24
Hallo,

ich habe die Tage etwas mit Augmented Reality gespielt und für ein Softwareprojekt einlesen. Im Netz gibt es mehrere gute Quellen die auch recht anschaulich das Problem erklären.
Nachdem ich folgendes Tutorial gelesen habe, entstand in knapp einer Stunde unten liegender Code (auf Basis der dort erklärten Bibliotheken):
http://www.raywenderlich.com/42266/a...location-based

Ich bin mir sicher das der Code zu UpdateLocations nicht ganz korrekt ist (da im Original mit UIViews gearbeitet wird und ich versucht habe das ganze so abstrakt wie möglich zu halten).
Ebenso würde mich wundern, wenn mein Transformationscode korrekt ist. Der entstand "On the fly" und naja die letzte Mathevorlesung ist schon *hüstel* 12 Jahre her, auch ist Objective C nicht meine Stärke

Eventuell juckt es ja den ein oder anderen damit etwas sinnvolles zu erstellen bzw. mit mir die Fehler zu beheben? Zumindest würde es mich freuen.

Delphi-Quellcode:
(*
Augmented Reality Toolkit for Delphi

based on the iPhone Augmented Reality Toolkit
Copyright (c) 2013 Agilite Software

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

*)

unit UARKit;

interface uses Classes, SysUtils;

const
  ADJUST_BY = 30;
  SCALE_FACTOR = 1.0;
  DEGREE_TO_UPDATE = 1;

  HEADING_NOT_SET = -1.0;

type
  TARCameraOrientation = (
    coLandscapeLeft, coLandscapeRight,
    coPortrait, coPortraitUpsideDown);

  TARList = TList;

  TARPoint = record x, y: single; end;
  TARRect = record Left, Top, Right, Bottom: single; end;
  TARLocation = record Latitude, Longitude, Altitude: single; end;
  TARVector = record x, y, z: single; end;

  TARMatrix = record
    m11, m12, m13, m14: single;
    m21, m22, m23, m24: single;
    m31, m32, m33, m34: single;
    m41, m42, m43, m44: single;
  end;

const
  ARIdentityMatrix: TARMatrix = (
    m11: 1; m12: 0; m13: 0; m14: 0;
    m21: 0; m22: 1; m23: 0; m24: 0;
    m31: 0; m32: 0; m33: 1; m34: 0;
    m41: 0; m42: 0; m43: 0; m44: 1);

type
  TARCoordinate = class
  private
    FTitle: string;
    FSubtitle: string;

    FAzimuth: single;
    FInclination: single;
    FRadialDistance: single;
  public
    constructor Create(const ARadialDistance, AInclination, AAzimuth: single);

    property Azimuth: single read FAzimuth write FAzimuth;
    property Inclination: single read FInclination write FInclination;
    property RadialDistance: single read FRadialDistance write FRadialDistance;

    property Title: string read FTitle write FTitle;
    property Subtitle: string read FSubtitle write FSubtitle;
  end;

  TARGeoCoordinate = class(TARCoordinate)
  private
    FDisplayView: TARRect;
    FGeoLocation: TARLocation;
    FDistanceFromOrigin: single;

    FWidth: integer;
    FHeight: integer;

    function AngleFromCoordinate(const first, second: TARLocation): single;
  public
    constructor Create(const ALocation: TARLocation; const ATitle: string); overload;
    constructor Create(const AOrigin: TARGeoCoordinate); overload;

    procedure CalibrateUsingOrigin(const AOrigin: TARLocation);

    property Width: integer read FWidth write FWidth;
    property Height: integer read FHeight write FHeight;
    property DisplayView: TARRect read FDisplayView write FDisplayView;

    property GeoLocation: TARLocation read FGeoLocation;
    property DistanceFromOrigin: Single read FDistanceFromOrigin;
  end;

  TARController = class
  private
    FLatestHeading: single;
    FDegreeRange: single;
    FViewAngle: single;
    FPrevHeading: single;

    FDisplayView: TARRect;
    FCoordinates: TARList;

    FCameraOrientation: TARCameraOrientation;
    FCenterCoordinate: TARCoordinate;
    FCenterLocation: TARLocation;

    FMaximumScaleDistance: single;
    FMinimumScaleFactor: single;
    FMaximumRotationAngle: single;

    FScaleViewsBasedOnDistance: Boolean;
    FRotateViewsBasedOnPerspective: Boolean;

    procedure UpdateLocations();

    function FindDeltaOfRadianCenter(centerAzimuth, pointAzimuth: single; var isBetweenNorth: Boolean): single;
    function PointForCoordinate(const coord: TARcoordinate): TARPoint;
    function ShouldDisplayCoordinate(const coord: TARCoordinate): Boolean;

    procedure SetCenterLocation(const ALocation: TARLocation);
    procedure SetDisplayView(const ADisplayView: TARRect);

    procedure SetScaleData(const index: integer; const AValue: Single);
    procedure SetViewData(const index: integer; const AValue: Boolean);
  public

    constructor Create(const ADisplayView: TARRect);
    destructor Destroy; override;

    procedure Clear;
    procedure Add(const coordinate: TARCoordinate);
    procedure Remove(const coordinate: TARCoordinate);

    procedure UpdateAccelerometer(const Ax, Ay, Az: single);
    procedure UpdateHeading(const AHeading: single);

    property CenterLocation: TARLocation read FCenterLocation write SetCenterLocation;
    property DisplayView: TARRect read FDisplayView write SetDisplayView;

    property MaximumScaleDistance: single index 0 read FMaximumScaleDistance write SetScaleData;
    property MinimumScaleFactor: single index 1 read FMinimumScaleFactor write SetScaleData;
    property MaximumRotationAngle: single index 2 read FMaximumRotationAngle write SetScaleData;

    property ScaleViewsBasedOnDistance: Boolean index 0 read FScaleViewsBasedOnDistance write SetViewData;
    property RotateViewsBasedOnPerspective: Boolean index 1 read FRotateViewsBasedOnPerspective write SetViewData;
  end;


implementation uses Math;

function ToDeg(const x: single): single;
begin
  result := PI * x / 180.0;
end;

function toRad(const x: single): single;
begin
  result := x * 180.0 / PI;
end;

function ARTransform3DScale(const Matrix: TARMatrix; const ScaleX, ScaleY, ScaleZ: single): TARMatrix;
begin
  result := Matrix;
  result.m11 := ScaleX;
  result.m22 := ScaleY;
  result.m33 := ScaleZ;
  result.m44 := 1;
end;

function ARRectWidth(const R: TARRect): Single;
begin
  result := R.Right - R.Left;
end;

function ARRectHeight(const R: TARRect): Single;
begin
  result := R.Bottom - R.Top;
end;

function ARVector(const x, y, z: single): TARVector;
begin
  result.x := x;
  result.y := y;
  result.z := z;
end;

function ARSetTransform(const src: TARVector; const m: TARMatrix): TARVector;
var w: single;
begin
  result.x := src.x * m.m11 + src.y * m.m21 + src.z * m.m31 + m.m41;
  result.y := src.x * m.m12 + src.y * m.m22 + src.z * m.m31 + m.m41;
  result.z := src.x * m.m13 + src.y * m.m23 + src.z * m.m32 + m.m41;
  w := src.x * m.m14 + src.y * m.m24 + src.z * m.m34 + m.m44;
  if (w <> 1) and (w <> 0) then
  begin
    result.x := result.x / w;
    result.y := result.y / w;
    result.z := result.z / w;
  end;
end;

procedure SetTransform(var Rect: TARRect; const Matrix: TARMatrix);
var v1, v2: TARVector;
begin
  v1 := ARSetTransform(ArVector(Rect.Left, Rect.Top, 0), Matrix);
  v2 := ARSetTransform(ArVector(Rect.Right, Rect.Bottom, 0), Matrix);

  Rect.Left := v1.x;
  Rect.Top := v1.y;

  Rect.Right := v2.x;
  Rect.Bottom := v2.y;
end;

function DistanceFromLocation(const Location1, Location2: TARLocation): single;
// http://www.movable-type.co.uk/scripts/latlong.html
var lat1, lat2, lon1, lon2: single;
  Haversine, dLat, dLon: single;
begin
  lat1 := ToDeg(Location1.Latitude);
  lon1 := ToDeg(Location1.Longitude);
  lat2 := ToDeg(Location2.Latitude);
  lon2 := ToDeg(Location2.Longitude);

  dLat := (lat2 - lat1);
  dLon := (lon2 - lon1);

  Haversine := sin(dLat / 2) * sin(dLat / 2) +
    sin(dLon / 2) * sin(dLon / 2) * cos(lat1) * cos(lat2);

  result := 6371 * (2 * arctan2(sqrt(Haversine), sqrt(1 - Haversine)));
end;

{ TARCoordinate }

constructor TARCoordinate.create(const ARadialDistance, AInclination, AAzimuth: single);
begin
  FRadialDistance := ARadialDistance;
  FInclination := AInclination;
  FAzimuth := AAzimuth;
  FTitle := '';
  FSubtitle := '';
end;

{ TARGeoCoordinate }

function TARGeoCoordinate.angleFromCoordinate(const first, second: TARLocation): single;
var longitudinalDifference, latitudinalDifference: single;
  possibleAzimuth: single;
begin

  longitudinalDifference := second.longitude - first.longitude;
  latitudinalDifference := second.latitude - first.latitude;
  possibleAzimuth := (PI * 0.5) - arctan(latitudinalDifference / longitudinalDifference);

  if (longitudinalDifference > 0) then
    result := possibleAzimuth
  else if (longitudinalDifference < 0) then
    result := possibleAzimuth + PI
  else if (latitudinalDifference < 0) then
    result := PI else
    result := 0;
end;

constructor TARGeoCoordinate.create(const ALocation: TARLocation; const ATitle: string);
begin
  inherited create(0, 0, 0);
  FGeoLocation := ALocation;
  FTitle := Title;
end;

constructor TARGeoCoordinate.create(const AOrigin: TARGeoCoordinate);
begin
  inherited create(0, 0, 0);
  calibrateUsingOrigin(AOrigin.GeoLocation);
  FTitle := '';
end;

procedure TARGeoCoordinate.calibrateUsingOrigin(const AOrigin: TARLocation);
var angle: single;
begin
  FDistanceFromOrigin := DistanceFromLocation(GeoLocation, AOrigin);
  RadialDistance := sqrt(power(AOrigin.Altitude - geoLocation.altitude, 2) + power(distanceFromOrigin, 2));

  angle := sin(ABS(Aorigin.altitude - geoLocation.altitude) / radialDistance);

  if (Aorigin.altitude > geoLocation.altitude) then
    angle := -angle;

  Inclination := angle;
  Azimuth := angleFromCoordinate(AOrigin, geoLocation);
end;

{ TARController }

constructor TARController.create(const ADisplayView: TARRect);
begin
  FCenterCoordinate := TARCoordinate.Create(1.0, 0, 0);
  FCoordinates := TARList.create;

  FLatestHeading := HEADING_NOT_SET;
  FPrevHeading := HEADING_NOT_SET;

  FMaximumScaleDistance := 0.0;
  FMinimumScaleFactor := SCALE_FACTOR;
  FScaleViewsBasedOnDistance := false;
  FRotateViewsBasedOnPerspective := false;
  FMaximumRotationAngle := PI / 6.0;

  FDisplayView := ADisplayView;
  FDegreeRange := ARRectWidth(ADisplayView) / ADJUST_BY;

  FCenterLocation.Latitude := 37.41711;
  FCenterLocation.longitude := -122.02528;
end;

destructor TARController.Destroy;
begin
  Clear;
  FCoordinates.Free;
  FCenterCoordinate.free;
  inherited;
end;

procedure TARController.Clear;
var i: integer;
  obj: TObject;
begin
  for i := 0 to FCoordinates.count - 1 do
  begin
    obj := FCoordinates[i];
    FreeAndNil(obj);
  end;
  FCoordinates.clear;
end;

procedure TARController.Add(const coordinate: TARCoordinate);
begin
  FCoordinates.Add(Coordinate);
end;

procedure TARController.Remove(const coordinate: TARCoordinate);
begin
  FCoordinates.Remove(coordinate)
end;

function TARController.findDeltaOfRadianCenter(centerAzimuth: single; pointAzimuth: single; var isBetweenNorth: Boolean): single;
begin
  if centerAzimuth < 0.0 then
    centerAzimuth := 2 * PI + centerAzimuth;

  if centerAzimuth > 2 * PI then
    centerAzimuth := centerAzimuth - 2 * PI;

  result := abs(pointAzimuth - centerAzimuth);
  isBetweenNorth := false;

 // If values are on either side of the Azimuth of North we need to adjust it. Only check the degree range
  if (centerAzimuth < ToDeg(FDegreeRange)) and (pointAzimuth > ToDeg(360 - FDegreeRange)) then
  begin
    result := centerAzimuth + (2 * PI - pointAzimuth);
    isBetweenNorth := true;
  end
  else if (pointAzimuth < ToDeg(FDegreeRange)) and (centerAzimuth > ToDeg(360 - FDegreeRange)) then
  begin
    result := (pointAzimuth + (2 * PI - centerAzimuth));
    isBetweenNorth := true;
  end;
end;

function TARController.shouldDisplayCoordinate(const coord: TARCoordinate): Boolean;
var
  isBetweenNorth: Boolean;
  deltaAzimuth: single;
begin
  deltaAzimuth := findDeltaOfRadianCenter(FcenterCoordinate.azimuth, coord.azimuth, isBetweenNorth);
  result := (deltaAzimuth <= ToDeg(FDegreeRange));
end;

function TARController.pointForCoordinate(const coord: TARCoordinate): TARPoint;
var
  isBetweenNorth: boolean;
  deltaAzimith: single;
begin
  isBetweenNorth := false;
  deltaAzimith := findDeltaOfRadianCenter(FcenterCoordinate.azimuth, coord.azimuth, isBetweenNorth);

  if (((coord.azimuth > FcenterCoordinate.azimuth) and (not isBetweenNorth)) or
    ((FcenterCoordinate.azimuth > ToDeg(360 - FDegreeRange))
    and (coord.azimuth < ToDeg(FDegreeRange)))) then
    result.x := (ARRectWidth(FDisplayView) / 2) + ((deltaAzimith / ToDeg(1)) * ADJUST_BY) // Right side of Azimuth
  else
    result.x := (ARRectWidth(FDisplayView) / 2) - ((deltaAzimith / ToDeg(1)) * ADJUST_BY); // Left side of Azimuth

  result.y := (ARRectHeight(FDisplayView) / 2) + (toRad(PI / 2.0 + FViewAngle) * 2.0);
end;

procedure TARController.SetCenterLocation(const ALocation: TARLocation);
var i: integer;
  geoLocation: TARCoordinate;
begin
  FcenterLocation := ALocation;
  for i := 0 to FCoordinates.Count - 1 do
  begin
    geoLocation := FCoordinates[i];
    if geoLocation is TARGeoCoordinate then
    begin
      (geoLocation as TARGeoCoordinate).calibrateUsingOrigin(FCenterLocation);
      if (geoLocation.radialDistance > FMaximumScaleDistance) then
        FMaximumScaleDistance := geoLocation.radialDistance;
    end;
  end;
end;

procedure TARController.UpdateAccelerometer(const Ax, Ay, Az: single);
begin
  case FCameraOrientation of
    coLandscapeLeft:
      FViewAngle := arctan2(Ax, Az);
    coLandscapeRight:
      FViewAngle := arctan2(-Ax, Az);
    coPortrait:
      FViewAngle := arctan2(Ay, Az);
  else
    FViewAngle := arctan2(-Ay, Az);
  end;
end;

procedure TARController.UpdateHeading(const AHeading: single);
var adjustment: single;
begin
  FLatestHeading := ToDeg(AHeading);

  //Let's only update the Center Coordinate when we have adjusted by more than X degrees
  if (abs(FLatestHeading - FPrevHeading) >= ToDeg(DEGREE_TO_UPDATE)) or
    (FPrevHeading = HEADING_NOT_SET) then
  begin
    FPrevHeading := FLatestHeading;

    case FCameraOrientation of
      coLandscapeLeft: adjustment := ToDeg(270);
      coLandscapeRight: adjustment := ToDeg(90);
      coPortraitUpsideDown: adjustment := ToDeg(180);
    else adjustment := 0;
    end;

    FCenterCoordinate.Azimuth := FLatestHeading - adjustment;
    UpdateLocations();
  end;
end;

procedure TARController.SetViewData(const index: integer; const AValue: Boolean);
begin
  case index of
    0: FScaleViewsBasedOnDistance := AValue;
    1: FRotateViewsBasedOnPerspective := AValue;
  end;
  UpdateLocations;
end;

procedure TARController.SetScaleData(const index: integer; const AValue: Single);
begin
  case index of
    0: FMaximumScaleDistance := AValue;
    1: FMinimumScaleFactor := AValue;
    2: FMaximumRotationAngle := AValue;
  end;
  UpdateLocations;
end;

procedure TARController.SetDisplayView(const ADisplayView: TARRect);
begin
  FDisplayView := ADisplayView;
  UpdateLocations;
end;

procedure TARController.UpdateLocations;
var i: integer;
  item: TARGeoCoordinate;
  loc: TARPoint;
  scaleFactor: single;
  transform: TARMatrix;
  R: TARRect;
  width, height: single;
begin
  for i := 0 to FCoordinates.Count - 1 do
  begin
    item := FCoordinates[i];

    if shouldDisplayCoordinate(item) then
    begin
      loc := pointForCoordinate(item);
      scaleFactor := SCALE_FACTOR;

      if (FScaleViewsBasedOnDistance) then
        scaleFactor := scaleFactor - (FMinimumScaleFactor * item.radialDistance) / FMaximumScaleDistance;

      width := item.width * scaleFactor;
      height := item.height * scaleFactor;

      R.Left := loc.x - width / 2.0;
      R.Top := loc.y;
      R.Right := R.Left + width;
      R.Bottom := R.Top + height;

      transform := ARIdentityMatrix;

   // Set the scale if it needs it. Scale the perspective transform if we have one.
      if FScaleViewsBasedOnDistance then
        transform := ARTransform3DScale(transform, scaleFactor, scaleFactor, scaleFactor);

      if FRotateViewsBasedOnPerspective then
        transform.m34 := 1.0 / 300.0;

      SetTransform(R, transform);

      item.DisplayView := R;
    end;
  end;
end;

end.

Geändert von CHackbart (16. Dez 2013 um 12:49 Uhr)
  Mit Zitat antworten Zitat