August 2013

Volume 25 Number 08

DirectX - Echtzeit, realistische Seite Eisstockschießen mit DirectX, C++ und XAML

Eric Brumer | August 2013

Bei der Entwicklung von Windows 8 und Visual Studio 2012 erstellt das Microsoft C++-Team einige open-Source-Anwendungen zu präsentieren, die verschiedenen C++-Technologien, Softwareentwicklern zur Verfügung.Eine dieser Apps ist Projekt "Austin", geschrieben in C++ unter Verwendung von DirectX und XAML auf der Windows-Runtime (WinRT) eine digitale Notizen-app.

Ein Benutzer kann in diesem app erstellen eine Notebook und notieren einige Notizen oder kritzeln Diagramme.Es gibt Unterstützung für hinzufügen und Löschen von Seiten, verschiedene Druckfarben und Hinzufügen von Image-Dateien von einem PC oder von SkyDrive.Abbildung 1 zeigt einige Screenshots der app in Aktion.

Projekt“Austin”
Abbildung 1-Projekt "Austin"

Benutzer können ihre Notebooks in drei verschiedene Arten anzeigen: eine einzelne Zeile der Seiten (wie in Abbildung 1), ein Raster von Seiten oder als ob die Seiten übereinander gestapelt wurden.In dieser gestapelte Ansicht kann der Benutzer Seiten durchblättern von swiping seine Finger über die Seite, als ob er durch die Seiten in ein richtiges Buch spiegeln wurden.Die digitalen Seiten sind gelockt, in Echtzeit, basierend auf der Position des Benutzers Finger, wie er die Seite kippt.Abbildung 2 zeigt die Seite Eisstockschießen in Aktion.

Seite Eisstockschießen
Abbildung 2 Seite Eisstockschießen

Die Seite-Curling-Funktion übernimmt auch die Seite uncurling.Wenn der Benutzer können einer Seite beim Eisstockschießen gehen, verhält sich die Seite wie ein echtes Stück Papier: Wenn die Seite unter einem bestimmten Schwellenwert ist, uncurls es wieder zu einer Lay-Flat-Position; Wenn die Seite über der Schwelle ist, uncurls es aber Oberflächen drehen.

Dieser Artikel beschreibt ausführlich die Geometrie, die Technologien und die Echtzeit-Seite curling und uncurling ausgeführt.

Die Geometrie der Seite Eisstockschießen

Bevor Sie sich das Gesamtdesign, erhalte ich die Geometrie und Mathematik aus dem Weg.Diese Information stammt weitgehend aus meinem MSDN-Blog-Post "Projekt-Austin-Teil 2 von 6: Page Curling"(bit.ly/THF40f).

2006-Papier, "Turning Pages 3D elektronische Bücher" (L.Hong, s.k.Karte und J.Chen), beschreibt, wie Seite Curling simuliert werden kann durch Verformung des Papiers um eine imaginäre Kegel, siehe Abbildung 3.Durch eine Änderung der Form und Position des Kegels können Sie mehr (oder weniger) Eisstockschießen simulieren.

Flat Papier (schwarz) Curling um einen Kegel (grün) zu gewelltes Papier (rot)
Abbildung 3 Flat Papier (schwarz) Curling um einen Kegel (grün) zu gewelltes Papier (rot)

In ähnlicher Weise Seite Eisstockschießen kann auch simuliert werden durch Verformung des Papiers auf einer imaginären Zylinder siehe Abbildung 4.

Flat Papier (schwarz) Curling auf einen Zylinder (grün) zu gewelltes Papier (rot)
Abbildung 4 Flat Papier (schwarz) Curling auf einen Zylinder (grün) zu gewelltes Papier (rot)

Meine Methode für Eisstockschießen Seite lautet wie folgt:

  • Wenn der Benutzer aus der oben rechts auf der Seite Eisstockschießen, deformieren Sie die Seite eines Kegels mit Winkel θ und Apex an den Koordinaten (0, Ay, 0).
  • Wenn der Benutzer aus der Mitte-Rechts der Seite Eisstockschießen, verformen der Seite auf einen Zylinder mit Radius R.
  • Wenn der Benutzer aus der unten rechts auf der Seite Eisstockschießen, deformieren Sie die Seite um ein umgekehrter Kegel.
  • Wenn der Benutzer überall dazwischen Eisstockschießen ist, deformieren Sie die Seite rund um die Linearkombination von Kegel und Zylinder, basierend auf die y-Koordinate der Eingabe.
  • Drehen Sie nach verformen das Papier um die y-Achse.

Hier sind die Details, eine Seite auf einen Zylinder zu verwandeln.(Hong-Artikel beschreibt ähnliche Geometrie um eine Seite um einen Kegel zu verwandeln.) Angesichts der Pflat Punkt mit Koordinaten {X 1, y1, z1 = 0} einer flachen Seite, Ziel ist es, sie in Pcurl mit Koordinaten {X 2, y2, z2}, der Punkt auf einem Zylinder mit Radius R umzuwandeln, das auf das "Rückgrat" des Buches liegt.Jetzt schauen Sie sich Abbildung 5, aus denen hervorgeht des Endedes des Zylinders.Sie können sehen, dass das x und Z-Achsen (die y Achse läuft innerhalb und außerhalb der Seite).Beachten Sie, dass ich vertrete das flache Papier und der Zylinder mit die gleichen Farben wie in den vorherigen zahlen.

Umwandlung Pflat, Pcurl
Abbildung 5 Umwandlung Pflat, Pcurl

Die wichtigste Einsicht ist, dass die Entfernung vom Ursprung zum Pflat (X 1) ist das gleiche wie die Entfernung der Bogen vom Ursprung zum Pcurl entlang des Zylinders.Also, von der einfachen Geometrie, ich kann sagen, dass der Winkel β = X 1 / R.Nun, um Pcurl zu erhalten, nehme ich den Ursprung verschieben es um nach unten r auf der Z-Achse, drehen um β, dann verschieben sie von r auf der Z-Achse.Die CurlPage-Methode in Abbildung 6 zeigt den Code, den Vertexpuffer für eine Seite zu deformieren.Die Vertex Buffer und Seite Koordinateninformationen werden entfernt abstrahiert.

Abbildung 6 Verformung des Vertexpuffers

void page_curl::curlPage(curl_parameters curlParams)
{
  float theta = curlParams.theta;
  float Ay = curlParams.ay;
  float alpha = curlParams.alpha;
  float conicContribution = curlParams.conicContribution;
  // As the user grabs toward the middle-right of the page, curl the
  // paper by deforming it on to a cylinder.
The cylinder radius is taken
  // as the endpoint of the cone parameters: for example,
  // cylRadius = R*sin(theta) distance to where R is the the rightmost
  // point on the page, all the way up.
float cylR = sqrt(  _vertexCountX * _vertexCountX
                    + (_vertexCountY /2 - Ay)*( _vertexCountY /2 - Ay));
  float cylRadius = cylR * sin(theta);
  // Flipping from top corner or bottom corner?
float posNegOne;
  if (conicContribution > 0)
  {
    // Top corner
    posNegOne = 1.0f;
  }
  else
  {
    // Bottom corner
    posNegOne = -1.0f;
    Ay = -Ay + _vertexCountY;
  }
  conicContribution = abs(conicContribution);
  for (int j = 0; j < _vertexCountY; j++)
  {
    for (int i = 0; i < _vertexCountX; i++)
    {
      float x = (float)i;
      float y = (float)j;
      float z = 0;
      float coneX = x;
      float coneY = y;
      float coneZ = z;
      {
        // Compute conical parameters and deform
        float R = sqrt(x * x + (y - Ay)*(y - Ay));
        float r = R * sin(theta);
        float beta  = asin(x / R) / sin(theta);
        coneX = r * sin(beta);
        coneY = R + posNegOne * Ay - r * (1 - cos(beta)) * sin(theta);
        coneZ = r * (1 - cos(beta)) * cos(theta);
        // Then rotate by alpha about the y axis
        coneX = coneX * cos(alpha) - coneZ * sin(alpha);
        coneZ = coneX * sin(alpha) + coneZ * cos(alpha);
      }
      float cylX = x;
      float cylY = y;
      float cylZ = z;
      {
        float beta = cylX / cylRadius;
        // Rotate (0,0,0) by beta around line given by x = 0, z = cylRadius
        // aka Rotate (0,0,-cylRadius) by beta, then add cylRadius back
        // to z coordinate
        cylZ = -cylRadius;
        cylX = -cylZ * sin(beta);
        cylZ = cylZ * cos(beta);
        cylZ += cylRadius;
        // Then rotate by alpha about the y axis
        cylX = cylX * cos(alpha) - cylZ * sin(alpha);
        cylZ = cylX * sin(alpha) + cylZ * cos(alpha);
      }
      // Combine cone & cylinder results
      x = conicContribution * coneX + (1-conicContribution) * cylX;
      y = conicContribution * coneY + (1-conicContribution) * cylY;
      z = conicContribution * coneZ + (1-conicContribution) * cylZ;
      _vertexBuffer[j * _vertexCountX + i].position.x = x;
      _vertexBuffer[j * _vertexCountX + i].position.y = y;
      _vertexBuffer[j * _vertexCountX + i].position.z = z;
    }
  }
}

Die Variable ConicContribution von-1 bis + 1, erfasst die Position auf der y-Achse, dass der Benutzer berührt hat. Ein Wert von-1 stellt des Benutzers, die unten auf der Seite zu berühren und + 1 dar oben auf der Seite.

Der vollständige Satz der Verformung Parameter werden im Curl_parameters erfasst:

struct curl_parameters
{
  curl_parameters() {}
  curl_parameters(float t, float a, float ang, float c) :
    theta(t), ay(a), angle(ang), conicContribution(c) {}
  float theta;  // Angle of right-cone
  float ay;     // Location on y axis of cone apex
  float alpha;  // Rotation about y axis
  float conicContribution;  // South tip cone == -1, cylinder == 0,
    north tip cone == 1
};

Beachten Sie, dass der Zylinder Radius diese Struktur fehlt; Ich nehme eine Verknüpfung von Rechner-es basierend auf den Kegel-Parametern, wie in Abbildung 6.

Architektur

Mit der Geometrie aus dem Weg kann ich auf das Design und die Architektur der Seite-Curling konzentrieren.Das Ziel des Entwurfs ist es, realistische Seite curling und uncurling ohne Flüssigkeit zu ermöglichen.Beispielsweise sollte der Benutzer möglicherweise teilweise eine Seite zu locken, loslassen, damit die Seite ein wenig uncurls, dann weiter Eisstockschießen der Seite, während die Animation flüssig und realistisch bleibt.

Die Projekt-Austin-Seite-Curling-Architektur ist implementiert, in die Page_curl-Klasse, in Abbildung 7.

Abbildung 7 Page_curl Klasse

class page_curl
{
public:
  void attachPage(const std::shared_ptr<paper_sheet_node> &pageNode);
  void startUserCurl(float x, float y);
  void startAutoCurl();
  void onRender();
private:
  struct curl_parameters
  {
    curl_parameters() {}
    curl_parameters(float t, float a, float ang, float c) :
      theta(t), ay(a), angle(ang), conicContribution(c) {}
    float theta;  // Angle of right cone
    float ay;     // Location on y axis of cone apex
    float alpha;  // Rotation about y axis
    float conicContribution;  
    // South tip cone == -1, cylinder == 0, north tip cone == 1
  };
  void continueAutoCurl();
  page_curl::curl_parameters computeCurlParameters(float x, float y);
  void page_curl::curlPage(page_curl::curl_parameters curlParams);
  ...
Other helpers that will be discussed later ...
std::shared_ptr<paper_sheet_node> _pageNode; // Page abstraction
  bool _userCurl;           // True if the user is curling the page
  bool _autoCurl;           // True if the page is uncurling
  float _autoCurlStartTime; // The time the user let go to start uncurling
  // Allows for smooth animations
  curl_parameters _currentCone;
  curl_parameters _nextCone;
};

Hier sind die Methoden, die Materie:

void page_curl::attachPage (const std::shared_ptr <paper_sheet_node> & PageNode) von der Projekt-Austin-Code aufgerufen wird, wenn eine Seite gelockt ist.Die Paper_sheet_node-Datenstruktur erfasst die relevante Informationen über die Seite-Koordinatensystem sowie die DirectX-Vertexpuffer verwendet, um diese Seite zu rendern.Die Umsetzung ist nicht in diesem Artikel beschrieben.

void page_curl::startUserCurl (Float x, Float y) wird von der Projekt-Austin Benutzereingaben Handlercode an der Benutzer hat seine Finger gedrückt und Eisstockschießen an der Position (X, y) wird aufgerufen.Dieser Code führt folgende Schritte aus:

  • setzt das _userCurl-Status-Bit um anzugeben, dass der Benutzer die Seite Eisstockschießen ist
  • hebt diese auf das _autoCurl Zustand Bit zu stoppen jede uncurling, wenn es ausgeführt wird
  • legt _nextCurlParams auf die Verformung-Parameter anhand des Benutzers Position (X, y)

void page_curl::startAutoCurl wird von der Projekt-Austin Benutzer input Handler um anzugeben, dass der Benutzer loslassen hat den Bildschirm aufgerufen.Dieser Code führt folgende Schritte aus:

  • hebt diese auf das _userCurl-Status-Bit um anzugeben, dass der Benutzer nicht mehr auf die Seite Eisstockschießen ist
  • legt ist das _autoCurl Zustand Bit an eine Uncurl im Gange ist, mit einem Zeitstempel, wann die Uncurl begonnen

void page_curl::onRender wird von der Projekt-Austin-Render-Schleife für jeden Frame aufgerufen.Beachten Sie, dass dies die einzige Funktion ist, die eigentlich den Vertexpuffer verformt.Dieser Code funktioniert wie folgt:

  • Wenn _userCurl oder _autoCurl festgelegt ist, verformt der Code den Vertexpuffer den Parametern berechnet aus _nextCurlParams und _currentCurlParams.Mit den beiden gewährleistet reibungslose Eisstockschießen, wie weiter unten in diesem Artikel erläutert.
  • Wenn _autoCurl festgelegt ist, ruft der Code continueAutoCurl
  • Setzt _currentCurlParams auf _nextCurlParams

void page_curl::continueAutoCurl wird von page_curl::onRender aufgerufen, wenn die Seite uncurling ist.Dieser Code:

  • berechnet _nextCurlParams basierend auf der Uncurl zu Beginn
  • hebt diese auf _autoCurl, wenn die Seite Curling abgeschlossen ist

page_curl::curl_parameters page_curl::computeCurlParameters (Float x, Float y) berechnet die Curl-Parameter (Theta, Ay, alpha, ConicContribution) basierend auf die Benutzereingabe.

Nun, da Sie die Gesamtarchitektur gesehen haben, werde ich in jedem öffentlichen und privaten Methoden füllen.Ich wählte das Design erleichtert die flüssige Animation zu gewährleisten.Der Schlüssel ist, StartUserCurl und OnRender aufzuteilen und den Status zwischen den beiden beizubehalten.

Nun erörtert einige dieser Methoden die Motivation oder im Hintergrund über die Design-Entscheidungen.

Flüssige Animation

Die zuvor beschriebenen Funktionen gegeben, scheint es für StartUserCurl, einfach die Position der Finger des Benutzers zu lesen, sowie für OnRender auf das Zeichenblatt, um diese Parameter einfach zu verformen.

Leider kann diese besondere Animation hässlich wenn der Benutzer den Finger sehr schnell bewegt.Wenn OnRender deformierte Seite mit 60 Frames pro Sekunde (fps) zieht, ist es möglich, dass zwischen zwei Frames der Benutzer den Finger auf halbem Weg über den Bildschirm bewegt.Auf ein Bild wird die Seite in einen beinahe flachen Zustand verformt.Wenn die Seite auf den nächsten Frame zu einem vollständig gelockt Zustand deformiert, die Fließfähigkeit der Animation geht verloren und...Nun sieht es hässlich.

Um dies zu umgehen, verfolgen der ich nicht nur _nextCurlParams (die gewünschte Position, wohin die Rotation sollte, basierend auf Benutzereingaben oder Formeln für uncurling), sondern auch den aktuellen Zustand der curl in _currentCurlParams.Wenn die gewünschten locken-Location zu weit von der bestehenden Lage locken, dann ich stattdessen zum Eisstockschießen werden sollte ist ein Zwischenwert, die die Animation sorgt für reibungslose.

Der Begriff ist "zu weit" offen für Interpretation.Da gibt es vier Elemente in der Cone_parameters-Struktur, und jeder eine Gleitkommazahl ist, behandle ich _currentCurlParams und _nextCurlParams als Punkte in den vierdimensionalen Raum.Der Abstand zwischen den zwei locken-Parametern ist dann nur der Abstand zwischen zwei Punkten.

Ebenso ist der Begriff "Zwischenwert" auch offen für Interpretation.Wenn die _nextCurlParams weit von _currentCurlParams ist, wähle ich einen Zwischenpunkt, die näher an _nextCurlParams proportional zur Distanz zwischen den beiden Punkten.Also, wenn der Benutzer mit einer flachen Seite beginnt und es extrem schnell die Seite erscheint zunächst locken um schnell vorwärts zu Frühling, aber je näher dann verlangsamt wird es an die gewünschte Position.Da in diesem bei 60 fps Fall die Gesamtwirkung ist sehr gering, aber aus Usability-Sicht die Ergebnisse sehen toll aus.

Abbildung 8 zeigt den vollständigen Wiedergabe-Code.

Abbildung 8 Renderingcode

void page_curl::onRender()
{
  // Read state under a lock
  curl_parameters nextCurlParams;
  curl_parameters currentCurlParams;
  bool userCurl;
  bool autoCurl;
  LOCK(_mutex)
  {
    nextCurlParams = _nextCurlParams;
    currentCurlParams = _currentCurlParams;
    userCurl = _userCurl;
    autoCurl = _autoCurl;
  }
  // Smooth going from currentCurlParams to nextCurlParams
  curl_parameters curl;
  float dt = nextCurlParams.theta - currentCurlParams.theta;
  float da = nextCurlParams.ay    - currentCurlParams.ay;
  float dr = nextCurlParams.alpha - currentCurlParams.alpha;
  float dc = nextCurlParams.conicContribution -
    currentCurlParams.conicContribution;
  float distance = sqrt(dt * dt + da * da + dr * dr + dc * dc);
  if (distance < constants::general::maxCurlDistance())
  {
    curl = nextCurlParams;
  }
  else
  {
    float scale = maxDistance / distance;
    curl.theta = currentCurlParams.theta + scale * dt;
    curl.ay =  currentCurlParams.ay  + scale * da;
    curl.alpha = currentCurlParams.alpha + scale * dr;
    curl.conicContribution = 
      currentCurlParams.conicContribution + scale * dc;
  }
  // Deform the vertex buffer
  if (userCurl || autoCurl)
  {
    LOCK(_mutex)
    {
      _currentCurlParams = curl;
    }
    this->curlPage(curl);
  }
  // Continue (or stop) uncurling
  if (autoCurl)
  {
    this->continueAutoCurl();
  }
}

Behandlung von Eingaben des Benutzers (und deren Fehlen)

Projekt Austin, seiend ein C++ DirectX XAML-app nutzt die WinRT-APIs.Gestenerkennung erfolgt durch das OS — insbesondere durch die Windows::UI::Input::GestureRecognizer.

Anschließen das OnManipulationUpdated-Ereignis aufrufen, StartUserCurl (X, y) Wenn der Benutzer gelockt wird, ist die Seite ist einfach.Dann ist der Code für StartUserCurl:

// x is scaled to (0, 1) and y is scaled to (-1, 1)
void page_curl::startUserCurl(float x, float y)
{
  curl_parameters curl = this->computeCurlParameters(x, y);
  LOCK(_mutex)
  {
    // Set curl state, to be consumed by onRender()
    _nextCurlParams = curl;
    _userCurl = true;
    _autoCurl = false;
  }
}

Ebenso ist es einfach, das OnManipulationCompleted-Ereignis StartAutoCurl aufrufen, wenn der Benutzer der Seite gehen wir Haken. Abbildung 9 zeigt den Code für StartAutoCurl.

Abbildung 9 die StartAutoCurl Methode

void page_curl::startAutoCurl()
{
  LOCK(_mutex)
  {
    // It's possible the user let go, but the page is
    // already fully curled or uncurled
    bool shouldAutoCurl = !this->doneAutoCurling(curl);
    _userCurl = false;
    _autoCurl = shouldAutoCurl;
    if (shouldAutoCurl)
    {
      _autoCurlStartTime = constants::general::currentTime();
    }
  }
}

Mehr interessante Code ist für die Verarbeitung Auto uncurling, wenn der Benutzer der Seite gehen; die Seite wird weiterhin uncurl, bis es vollkommen flach ist oder es komplett nach vorne gelockt hat. Ich stütze diese Transformation auf die aktuelle Curl-Parameter und die verstrichene Zeit (Quadrat), seitdem uncurling. Auf diese Weise die Seite beginnt, langsam uncurl aber wie die Zeit vergeht es uncurls schneller und schneller. Dies ist eine billige Art und Weise zu versuchen, die Schwerkraft zu simulieren. Abbildung 10 zeigt den Code.

Abbildung 10 Umgang mit Auto-Uncurling

void page_curl::continueAutoCurl()
{
  LOCK(_mutex)
  {
    if (this->doneAutoCurling(curl))
    {
      _autoCurl = false;
    }
    else
    {
      float time = constants::general::currentTime() - 
        _autoCurlStartTime;
      _nextCurlParams = this->nextAutoCurlParams(
        _currentCurlParams, 
        time * time);
    }
  }
}

Optimierung der Curling für Realismus

Abwesend von der vorherigen Abschnitte ist der Code für ComputeCurl­Parameter, DoneAutoCurling und NextAutoCurlParams. Dies sind einstellbare Funktionen, bei denen Konstanten, Formeln und Heuristiken, die das Ergebnis von Stunden mühsamen Versuch und Irrtum.

Beispielsweise verbrachte ich viele, viele Stunden versucht vernünftige Ergebnisse für ComputeCurlParameters zu erreichen. Abbildung 11 zeigt zwei Versionen des Codes — derjenige, der das Eisstockschießen ein dickes Stück Papier (die Seiten in einem Buch) simuliert, und eine zweite, die eine Kunststoff-Abdeckung (Cover von einem Taschenbuch, z. B.) simuliert.

Abbildung 11 Eisstockschießen zwei Arten von Seiten

// Helper macro for a straight line F(x) that passes through {x1, y1} and {x2, y2}.
// This can't be a template function (C++ doesn't let you have float literals
// as template parameters).
#define STRAIGHT_LINE(x1, y1, x2, y2, x) 
    (((y2 - y1) / (x2 - x1)) * (x - x1) + y1)
page_curl::curl_parameters page_curl::paperParams(float x, float y)
{
  float theta, ay, alpha;
  if (x > 0.95f)
  {
    theta = STRAIGHT_LINE(1.0f,  90.0f, 0.95f, 60.0f, x);
    ay    = STRAIGHT_LINE(1.0f, -20.0f, 0.95f, -5.0f, x);
    alpha = 0.0;
  }
  else if (x > 0.8333f)
  {
    theta = STRAIGHT_LINE(0.95f,  60.0f, 0.8333f, 55.0f, x);
    ay    = STRAIGHT_LINE(0.95f, -5.0f,  0.8333f, -4.0f, x);
    alpha = STRAIGHT_LINE(0.95f,  0.0f,  0.8333f, 13.0f, x);
  }
  else if (x > 0.3333f)
  {
    theta = STRAIGHT_LINE(0.8333f, 55.0f, 0.3333f,  45.0f, x);
    ay    = STRAIGHT_LINE(0.8333f, -4.0f, 0.3333f, -10.0f, x);
    alpha = STRAIGHT_LINE(0.8333f, 13.0f, 0.3333f,  35.0f, x);
  }
  else if (x > 0.1666f)
  {
    theta = STRAIGHT_LINE(0.3333f,  45.0f, 0.1666f,  25.0f, x);
    ay    = STRAIGHT_LINE(0.3333f, -10.0f, 0.1666f, -30.0f, x);
    alpha = STRAIGHT_LINE(0.3333f,  35.0f, 0.1666f,  60.0f, x);
  }
  else
  {
    theta = STRAIGHT_LINE(0.1666f,  25.0f, 0.0f,  20.0f, x);
    ay    = STRAIGHT_LINE(0.1666f, -30.0f, 0.0f, -40.0f, x);
    alpha = STRAIGHT_LINE(0.1666f,  60.0f, 0.0f,  95.0f, x);
  }
  page_curl::curl_parameters cp(theta, ay, alpha, y);
  return cp;
}
page_curl::curl_parameters page_curl::plasticParams(float x, float y)
{
  float theta, ay, alpha;
  if (x > 0.95f)
  {
    theta = STRAIGHT_LINE(1.0f,  90.0f, 0.9f,  40.0f, x);
    ay    = STRAIGHT_LINE(1.0f, -30.0f, 0.9f, -20.0f, x);
    alpha = 0.0;
  }
  else
  {
    theta = STRAIGHT_LINE(0.95f,  40.0f, 0.0f,  35.0f, x);
    ay    = STRAIGHT_LINE(0.95f, -20.0f, 0.0f, -25.0f, x);
    alpha = STRAIGHT_LINE(0.95f,   0.0f, 0.0f,  95.0f, x);
  }
  page_curl::curl_parameters cp(theta, ay, angle, y);
  return cp;
}

Der Code zu wissen beim Eisstockschießen lediglich abgeschlossen ist umfasst überprüfen, ob die Seite komplett flach ist oder vollständig gelockt:

bool page_curl::doneAutoCurling(curl_parameters curl)
{
  bool doneCurlBackwards =  (curl.theta > 89.999f)
                         && (curl.ay < -69.999f)
                         && (curl.alpha < 0.001f)
                         && (abs(curl.conicContribution) > 0.999f);
  bool doneCurlForwards = (curl.alpha > 99.999f);
  return doneCurlBackwards || doneCurlForwards;
}

Und zuletzt, meine Version von Auto-Curl, gezeigt Abbildung 12, stützt sich auf die aktuelle Curl-Position und den Platz der verstrichenen Zeit seit dem aufrollen. Statt uncurling einer Seite auf die gleiche Weise wie, die es gelockt ist, habe ich einfach die Curl-Parameter linear nähern sich die Parameter für eine flache Seite, aber lassen Sie die Seite rückwärts fallen (wenn der Benutzer loslassen, wenn die Seite nur leicht gelockt wurde) oder (wenn der Benutzer loslassen, wenn die Seite vor allem gelockt war) zu übermitteln. Mit dieser Technik und dem Platz der verstrichenen Zeit, hat die Seite eine schöne Erholung darauf, wenn Sie loslassen. Ich mag, wie es aussieht.

Abbildung 12 meine Version von Auto-Curling

page_curl::curl_parameters page_curl::nextAutoCurlParams(
  curl_parameters curl, float time)
{
  curl_parameters nextCurl;
  if (curl.alpha > 40)
  {
    nextCurl.theta = min(curl.theta + time/800000.0f,  50.0f);
    nextCurl.ay    = curl.ay;
    nextCurl.alpha = min(curl.alpha + time/200000.0f, 100.0f);
  }
  else
  {
    nextCurl.theta = min(curl.theta + time/100000.0f, 90.0f);
    nextCurl.ay    = max(curl.ay - time/200000.0f,   -70.0f);
    nextCurl.alpha = max(curl.alpha - time/300000.0f,  0.0f);
  }
  if (curl.conicContribution > 0.0)
  {
    nextCurl.conicContribution =
      min(curl.conicContribution + time/100000.0f, 1.0f);
  }
  else
  {
    nextCurl.conicContribution =
      max(curl.conicContribution - time/100000.0f, -1.0f);
  }
  return nextCurl;
}

Eine Sache, die ich wünschte, ich umgesetzt ist Seite Trägheit, wenn uncurling.Benutzer sollten die Seite werfen können.Wenn sie loslassen, sollte die Seite Eisstockschießen in die gleiche Richtung weiter, die es geschleudert wurde, bis Widerstandskräfte zu stoppen und Laien wieder flach.Dies könnte durch Hinzufügen von Geschichte zu OnRender, die Verfolgung von die letzten wenigen Positionen der Finger des Benutzers und die Nutzung dieser in den Formeln in NextAutoCurlParams implementiert werden.

Leistung

Die CurlPage-Methode hat dazu einiges an Mathe zu eine einzelne Seite zu locken.Nach meiner Zählung gibt es neun Aufrufe an die sin-Funktion, acht auf cos, man Arcsin, man Sqrt und rund zwei Dutzend multipliziert, sowie Ergänzungen und Subtraktionen — für jeden Vertex in Papier — für jeden Frame, die ausgegeben wird!

Projekt Austin zielt auf 60 fps; Somit kann die Verarbeitung jeder Frame nicht mehr als 15 ms dauern, damit die app fühlen sich träge.

Die erforderliche Leistung wird erreicht, indem sichergestellt wird, dass die innerste Schleife vektorisiert ist, wo der Visual Studio C++-Compiler generiert Streaming SIMD Extensions 2 (SSE2) Anweisungen CPU Vektor Einheiten nutzen.Der Compiler ist in der Lage, alle transzendenten Funktionen in der Headerdatei math.h vektorisieren.

In diesem Fall berechnet die innere Schleife die gelockt Position für vier Eckpunkte in einer Zeit.Die Leistungssteigerung entlastet die CPU für andere Rendering-Aufgaben, wie das Anwenden von Schatten auf die Seite gelockt.

Lesen Sie mehr über die Auto-Vectorizer auf MSDN und die parallele Programmierung in systemeigenem Code Blog (bit.ly/bWfC5Y).

Zusammenfassung

Ich möchte das große Volk zu danken, die am Projekt Austin, insbesondere Jorge Pereira, George Mileka und Alan Chan arbeitete.Durch die Zeit, die ich anfing zu arbeiten an dem Projekt hatten bereits eine große app und ich fühle mich glücklich, einige Zeit ein wenig Realismus hinzufügen verbracht haben.Es half mir zu verstehen Schönheit in Einfachheit und wie schwer es sein kann, Dinge einfach zu machen!

Hier finden Sie weitere Informationen über die app, einschließlich einige Videos auf der MSDN-Blogs mit der Suche nach Projekt Austin.Und finden Sie den Code unter austin.codeplex.com.

Eric Brumer ist ein Software Development Engineer bei Microsoft und arbeitet auf der Visual C++-Compiler-Optimierer-Team. Sie erreichen ihn am ericbr@microsoft.com.

Dank den folgenden technischen Experten für die Überprüfung dieses Artikels: George Mileka (Microsoft)
George Mileka ist ein Software Development Engineer bei Microsoft und arbeitet im Visual C++-Bibliotheken-Team.Sie erreichen ihn am gmileka@microsoft.com.