root/native_client_sdk/src/gonacl_appengine/src/earth/earth.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. ExtractR
  2. ExtractG
  3. ExtractB
  4. MakeRGBA
  5. TableLerp
  6. AsInteger
  7. AsFloat
  8. inline_quick_sqrt
  9. inline_sqrt
  10. Clamp255
  11. Init
  12. Reset
  13. num_regions_
  14. wMakeRect
  15. wGetAddr
  16. wRenderPixelSpan
  17. wRenderRect
  18. wRenderRegion
  19. wRenderRegionEntry
  20. Render
  21. CacheCalcs
  22. SetPlanetXYZR
  23. SetEyeXYZ
  24. SetLightXYZ
  25. SetAmbientRGB
  26. SetDiffuseRGB
  27. SetPlanetPole
  28. SetPlanetEquator
  29. SetPlanetSpin
  30. UpdateSim
  31. DidChangeView
  32. StartBenchmark
  33. EndBenchmark
  34. SetZoom
  35. SetLight
  36. SetTexture
  37. HandleInputEvent
  38. PostUpdateMessage
  39. HandleMessage
  40. FlushCallback
  41. FlushPixelBuffer
  42. Update
  43. CreateContext
  44. DestroyContext
  45. CreateInstance
  46. CreateModule

// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <assert.h>
#include <math.h>
#include <ppapi/c/pp_point.h>
#include <ppapi/c/ppb_input_event.h>
#include <ppapi/cpp/completion_callback.h>
#include <ppapi/cpp/graphics_2d.h>
#include <ppapi/cpp/image_data.h>
#include <ppapi/cpp/input_event.h>
#include <ppapi/cpp/instance.h>
#include <ppapi/cpp/module.h>
#include <ppapi/cpp/rect.h>
#include <ppapi/cpp/size.h>
#include <ppapi/cpp/var.h>
#include <ppapi/cpp/var_array.h>
#include <ppapi/cpp/var_array_buffer.h>
#include <ppapi/cpp/var_dictionary.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include <algorithm>
#include <string>

#include "common/fps.h"
#include "sdk_util/macros.h"
#include "sdk_util/thread_pool.h"

// Chromium presubmit prevents checking in changes with calls to printf to
// prevent spammy output. We'll work around that for this example.
#define logf printf

using namespace sdk_util;  // For sdk_util::ThreadPool

// Global properties used to setup Earth demo.
namespace {
const float kPI = M_PI;
const float kTwoPI = kPI * 2.0f;
const float kOneOverPI = 1.0f / kPI;
const float kOneOver2PI = 1.0f / kTwoPI;
const float kOneOver255 = 1.0f / 255.0f;
const int kArcCosineTableSize = 4096;
const int kFramesToBenchmark = 100;
const float kZoomMin = 1.0f;
const float kZoomMax = 50.0f;
const float kWheelSpeed = 2.0f;
const float kLightMin = 0.0f;
const float kLightMax = 2.0f;

// RGBA helper functions.
inline float ExtractR(uint32_t c) {
  return static_cast<float>(c & 0xFF) * kOneOver255;
}

inline float ExtractG(uint32_t c) {
  return static_cast<float>((c & 0xFF00) >> 8) * kOneOver255;
}

inline float ExtractB(uint32_t c) {
  return static_cast<float>((c & 0xFF0000) >> 16) * kOneOver255;
}

inline uint32_t MakeRGBA(uint32_t r, uint32_t g, uint32_t b, uint32_t a) {
  return (((a) << 24) | ((r) << 16) | ((g) << 8) | (b));
}

// simple container for earth texture
struct Texture {
  int width, height;
  uint32_t* pixels;
  Texture(int w, int h) : width(w), height(h) {
    pixels = new uint32_t[w * h];
    memset(pixels, 0, sizeof(uint32_t) * w * h);
  }
  explicit Texture(int w, int h, uint32_t* p) : width(w), height(h) {
    pixels = new uint32_t[w * h];
    memcpy(pixels, p, sizeof(uint32_t) * w * h);
  }
  ~Texture() { delete[] pixels; }

  DISALLOW_COPY_AND_ASSIGN(Texture);
};



struct ArcCosine {
  // slightly larger table so we can interpolate beyond table size
  float table[kArcCosineTableSize + 2];
  float TableLerp(float x);
  ArcCosine();
};

ArcCosine::ArcCosine() {
  // build a slightly larger table to allow for numeric imprecision
  for (int i = 0; i < (kArcCosineTableSize + 2); ++i) {
    float f = static_cast<float>(i) / kArcCosineTableSize;
    f = f * 2.0f - 1.0f;
    table[i] = acos(f);
  }
}

// looks up acos(f) using a table and lerping between entries
// (it is expected that input f is between -1 and 1)
float ArcCosine::TableLerp(float f) {
  float x = (f + 1.0f) * 0.5f;
  x = x * kArcCosineTableSize;
  int ix = static_cast<int>(x);
  float fx = static_cast<float>(ix);
  float dx = x - fx;
  float af = table[ix];
  float af2 = table[ix + 1];
  return af + (af2 - af) * dx;
}

// Helper functions for quick but approximate sqrt.
union Convert {
  float f;
  int i;
  Convert(int x) { i = x; }
  Convert(float x) { f = x; }
  int AsInt() { return i; }
  float AsFloat() { return f; }
};

inline const int AsInteger(const float f) {
  Convert u(f);
  return u.AsInt();
}

inline const float AsFloat(const int i) {
  Convert u(i);
  return u.AsFloat();
}

const long int kOneAsInteger = AsInteger(1.0f);

inline float inline_quick_sqrt(float x) {
  int i;
  i = (AsInteger(x) >> 1) + (kOneAsInteger >> 1);
  return AsFloat(i);
}

inline float inline_sqrt(float x) {
  float y;
  y = inline_quick_sqrt(x);
  y = (y * y + x) / (2.0f * y);
  y = (y * y + x) / (2.0f * y);
  return y;
}

// takes a -0..1+ color, clamps it to 0..1 and maps it to 0..255 integer
inline uint32_t Clamp255(float x) {
  if (x < 0.0f) {
    x = 0.0f;
  } else if (x > 1.0f) {
    x = 1.0f;
  }
  return static_cast<uint32_t>(x * 255.0f);
}
}  // namespace


// The main object that runs the Earth demo.
class Planet : public pp::Instance {
 public:
  explicit Planet(PP_Instance instance);
  virtual ~Planet();

  virtual bool Init(uint32_t argc, const char* argn[], const char* argv[]);

  virtual void DidChangeView(const pp::View& view);

  // Catch events.
  virtual bool HandleInputEvent(const pp::InputEvent& event);

  // Catch messages posted from Javascript.
  virtual void HandleMessage(const pp::Var& message);

 private:
  // Methods prefixed with 'w' are run on worker threads.
  uint32_t* wGetAddr(int x, int y);
  void wRenderPixelSpan(int x0, int x1, int y);
  void wMakeRect(int r, int *x, int *y, int *w, int *h);
  void wRenderRect(int x0, int y0, int x1, int y1);
  void wRenderRegion(int region);
  static void wRenderRegionEntry(int region, void *thiz);

  // These methods are only called by the main thread.
  void CacheCalcs();
  void SetPlanetXYZR(float x, float y, float z, float r);
  void SetPlanetPole(float x, float y, float z);
  void SetPlanetEquator(float x, float y, float z);
  void SetPlanetSpin(float x, float y);
  void SetEyeXYZ(float x, float y, float z);
  void SetLightXYZ(float x, float y, float z);
  void SetAmbientRGB(float r, float g, float b);
  void SetDiffuseRGB(float r, float g, float b);
  void SetZoom(float zoom);
  void SetLight(float zoom);
  void SetTexture(const std::string& name, int width, int height,
      uint32_t* pixels);

  void Reset();
  void PostInit(int32_t result);
  void UpdateSim();
  void Render();
  void Draw();
  void StartBenchmark();
  void EndBenchmark();

  // Runs a tick of the simulations, updating all buffers.  Flushes the
  // contents of |image_data_| to the 2D graphics context.
  void Update();

  // Post a small key-value message to update JS.
  void PostUpdateMessage(const char* message_name, double value);
  // Create and initialize the 2D context used for drawing.
  void CreateContext(const pp::Size& size);
  // Destroy the 2D drawing context.
  void DestroyContext();
  // Push the pixels to the browser, then attempt to flush the 2D context.
  void FlushPixelBuffer();
  static void FlushCallback(void* data, int32_t result);

  // User Interface settings.  These settings are controlled via html
  // controls or via user input.
  float ui_light_;
  float ui_zoom_;
  float ui_spin_x_;
  float ui_spin_y_;

  // Various settings for position & orientation of planet.  Do not change
  // these variables, instead use SetPlanet*() functions.
  float planet_radius_;
  float planet_spin_x_;
  float planet_spin_y_;
  float planet_x_, planet_y_, planet_z_;
  float planet_pole_x_, planet_pole_y_, planet_pole_z_;
  float planet_equator_x_, planet_equator_y_, planet_equator_z_;

  // Observer's eye.  Do not change these variables, instead use SetEyeXYZ().
  float eye_x_, eye_y_, eye_z_;

  // Light position, ambient and diffuse settings.  Do not change these
  // variables, instead use SetLightXYZ(), SetAmbientRGB() and SetDiffuseRGB().
  float light_x_, light_y_, light_z_;
  float diffuse_r_, diffuse_g_, diffuse_b_;
  float ambient_r_, ambient_g_, ambient_b_;

  // Cached calculations.  Do not change these variables - they are updated by
  // CacheCalcs() function.
  float planet_xyz_;
  float planet_pole_x_equator_x_;
  float planet_pole_x_equator_y_;
  float planet_pole_x_equator_z_;
  float planet_radius2_;
  float planet_one_over_radius_;
  float eye_xyz_;

  // Source texture (earth map).
  Texture* base_tex_;
  Texture* night_tex_;
  int width_for_tex_;
  int height_for_tex_;
  std::string name_for_tex_;

  // Quick ArcCos helper.
  ArcCosine acos_;

  // Misc.
  pp::Graphics2D* graphics_2d_context_;
  pp::ImageData* image_data_;
  bool initial_did_change_view_;
  int num_threads_;
  int num_regions_;
  ThreadPool* workers_;
  int width_;
  int height_;
  bool hidden_;
  PP_Point last_mouse_pos_;
  uint32_t stride_in_pixels_;
  uint32_t* pixel_buffer_;
  int benchmark_frame_counter_;
  bool benchmarking_;
  double benchmark_start_time_;
  double benchmark_end_time_;
  FpsState fps_state_;
};


bool Planet::Init(uint32_t argc, const char* argn[], const char* argv[]) {
  // Request PPAPI input events for mouse & keyboard.
  RequestInputEvents(PP_INPUTEVENT_CLASS_MOUSE);
  RequestInputEvents(PP_INPUTEVENT_CLASS_WHEEL);
  RequestInputEvents(PP_INPUTEVENT_CLASS_KEYBOARD);
  // Request a set of images from JS.  After images are loaded by JS, a
  // message from JS -> NaCl will arrive containing the pixel data.  See
  // HandleMessage() method in this file.
  pp::VarDictionary message;
  message.Set("message", "request_textures");
  pp::VarArray names;
  names.Set(0, "earth.jpg");
  names.Set(1, "earthnight.jpg");
  message.Set("names", names);
  PostMessage(message);
  return true;
}

void Planet::Reset() {
  // Reset has to first fill in all variables with valid floats, so
  // CacheCalcs() doesn't potentially propagate NaNs when calling Set*()
  // functions further below.
  planet_radius_ = 1.0f;
  planet_spin_x_ = 0.0f;
  planet_spin_y_ = 0.0f;
  planet_x_ = 0.0f;
  planet_y_ = 0.0f;
  planet_z_ = 0.0f;
  planet_pole_x_ = 0.0f;
  planet_pole_y_ = 0.0f;
  planet_pole_z_ = 0.0f;
  planet_equator_x_ = 0.0f;
  planet_equator_y_ = 0.0f;
  planet_equator_z_ = 0.0f;
  eye_x_ = 0.0f;
  eye_y_ = 0.0f;
  eye_z_ = 0.0f;
  light_x_ = 0.0f;
  light_y_ = 0.0f;
  light_z_ = 0.0f;
  diffuse_r_ = 0.0f;
  diffuse_g_ = 0.0f;
  diffuse_b_ = 0.0f;
  ambient_r_ = 0.0f;
  ambient_g_ = 0.0f;
  ambient_b_ = 0.0f;
  planet_xyz_ = 0.0f;
  planet_pole_x_equator_x_ = 0.0f;
  planet_pole_x_equator_y_ = 0.0f;
  planet_pole_x_equator_z_ = 0.0f;
  planet_radius2_ = 0.0f;
  planet_one_over_radius_ = 0.0f;
  eye_xyz_ = 0.0f;
  ui_zoom_ = 14.0f;
  ui_light_ = 1.0f;
  ui_spin_x_ = 0.01f;
  ui_spin_y_ = 0.0f;

  // Set up reasonable default values.
  SetPlanetXYZR(0.0f, 0.0f, 48.0f, 4.0f);
  SetEyeXYZ(0.0f, 0.0f, -ui_zoom_);
  SetLightXYZ(-60.0f, -30.0f, 0.0f);
  SetAmbientRGB(0.05f, 0.05f, 0.05f);
  SetDiffuseRGB(0.8f, 0.8f, 0.8f);
  SetPlanetPole(0.0f, 1.0f, 0.0f);
  SetPlanetEquator(1.0f, 0.0f, 0.0f);
  SetPlanetSpin(kPI / 2.0f, kPI / 2.0f);
  SetZoom(ui_zoom_);
  SetLight(ui_light_);

  // Send UI values to JS to reset html sliders.
  PostUpdateMessage("set_zoom", ui_zoom_);
  PostUpdateMessage("set_light", ui_light_);
}


Planet::Planet(PP_Instance instance) : pp::Instance(instance),
                                       graphics_2d_context_(NULL),
                                       image_data_(NULL),
                                       initial_did_change_view_(true),
                                       num_regions_(256) {
  width_ = 0;
  height_ = 0;
  hidden_ = false;
  stride_in_pixels_ = 0;
  pixel_buffer_ = NULL;
  benchmark_frame_counter_ = 0;
  benchmarking_ = false;
  base_tex_ = NULL;
  night_tex_ = NULL;
  name_for_tex_ = "";
  last_mouse_pos_ = PP_MakePoint(0, 0);
  FpsInit(&fps_state_);

  Reset();

  // By default, render from the dispatch thread.
  num_threads_ = 0;
  workers_ = new ThreadPool(num_threads_);
}

Planet::~Planet() {
  delete workers_;
  DestroyContext();
}

// Given a region r, derive a rectangle.
// This rectangle shouldn't overlap with work being done by other workers.
// If multithreading, this function is only called by the worker threads.
void Planet::wMakeRect(int r, int *x, int *y, int *w, int *h) {
  int dy = height_ / num_regions_;
  *x = 0;
  *w = width_;
  *y = r * dy;
  *h = dy;
}


inline uint32_t* Planet::wGetAddr(int x, int y) {
  assert(pixel_buffer_);
  return (pixel_buffer_ + y * stride_in_pixels_) + x;
}

// This is the meat of the ray tracer.  Given a pixel span (x0, x1) on
// scanline y, shoot rays into the scene and render what they hit.  Use
// scanline coherence to do a few optimizations
void Planet::wRenderPixelSpan(int x0, int x1, int y) {
  if (!base_tex_ || !night_tex_)
    return;
  const int kColorBlack = MakeRGBA(0, 0, 0, 0xFF);
  float min_dim = width_ < height_ ? width_ : height_;
  float offset_x = width_ < height_ ? 0 : (width_ - min_dim) * 0.5f;
  float offset_y = width_ < height_ ? (height_ - min_dim) * 0.5f : 0;
  float y0 = eye_y_;
  float z0 = eye_z_;
  float y1 = (static_cast<float>(y - offset_y) / min_dim) * 2.0f - 1.0f;
  float z1 = 0.0f;
  float dy = (y1 - y0);
  float dz = (z1 - z0);
  float dy_dy_dz_dz = dy * dy + dz * dz;
  float two_dy_y0_y_two_dz_z0_z = 2.0f * dy * (y0 - planet_y_) +
                                  2.0f * dz * (z0 - planet_z_);
  float planet_xyz_eye_xyz = planet_xyz_ + eye_xyz_;
  float y_y0_z_z0 = planet_y_ * y0 + planet_z_ * z0;
  float oowidth = 1.0f / min_dim;
  uint32_t* pixels = this->wGetAddr(x0, y);
  for (int x = x0; x <= x1; ++x) {
    // scan normalized screen -1..1
    float x1 = (static_cast<float>(x - offset_x) * oowidth) * 2.0f - 1.0f;
    // eye
    float x0 = eye_x_;
    // delta from screen to eye
    float dx = (x1 - x0);
    // build a, b, c
    float a = dx * dx + dy_dy_dz_dz;
    float b = 2.0f * dx * (x0 - planet_x_) + two_dy_y0_y_two_dz_z0_z;
    float c = planet_xyz_eye_xyz +
              -2.0f * (planet_x_ * x0 + y_y0_z_z0) - (planet_radius2_);
    // calculate discriminant
    float disc = b * b - 4.0f * a * c;

    // Did ray hit the sphere?
    if (disc < 0.0f) {
      *pixels = kColorBlack;
      ++pixels;
      continue;
    }

    // calc parametric t value
    float t = (-b - inline_sqrt(disc)) / (2.0f * a);
    float px = x0 + t * dx;
    float py = y0 + t * dy;
    float pz = z0 + t * dz;
    float nx = (px - planet_x_) * planet_one_over_radius_;
    float ny = (py - planet_y_) * planet_one_over_radius_;
    float nz = (pz - planet_z_) * planet_one_over_radius_;

    // Misc raytrace calculations.
    float Lx = (light_x_ - px);
    float Ly = (light_y_ - py);
    float Lz = (light_z_ - pz);
    float Lq = 1.0f / inline_quick_sqrt(Lx * Lx + Ly * Ly + Lz * Lz);
    Lx *= Lq;
    Ly *= Lq;
    Lz *= Lq;
    float d = (Lx * nx + Ly * ny + Lz * nz);
    float pr = (diffuse_r_ * d) + ambient_r_;
    float pg = (diffuse_g_ * d) + ambient_g_;
    float pb = (diffuse_b_ * d) + ambient_b_;
    float ds = -(nx * planet_pole_x_ +
                 ny * planet_pole_y_ +
                 nz * planet_pole_z_);
    float ang = acos_.TableLerp(ds);
    float v = ang * kOneOverPI;
    float dp = planet_equator_x_ * nx +
               planet_equator_y_ * ny +
               planet_equator_z_ * nz;
    float w = dp / sin(ang);
    if (w > 1.0f) w = 1.0f;
    if (w < -1.0f) w = -1.0f;
    float th = acos_.TableLerp(w) * kOneOver2PI;
    float dps = planet_pole_x_equator_x_ * nx +
                planet_pole_x_equator_y_ * ny +
                planet_pole_x_equator_z_ * nz;
    float u;
    if (dps < 0.0f)
      u = th;
    else
      u = 1.0f - th;

    // Look up daylight texel.
    int tx = static_cast<int>(u * base_tex_->width);
    int ty = static_cast<int>(v * base_tex_->height);
    int offset = tx + ty * base_tex_->width;
    uint32_t base_texel = base_tex_->pixels[offset];
    float tr = ExtractR(base_texel);
    float tg = ExtractG(base_texel);
    float tb = ExtractB(base_texel);

    float ipr = 1.0f - pr;
    if (ipr < 0.0f) ipr = 0.0f;
    float ipg = 1.0f - pg;
    if (ipg < 0.0f) ipg = 0.0f;
    float ipb = 1.0f - pb;
    if (ipb < 0.0f) ipb = 0.0f;

    // Look up night texel.
    int nix = static_cast<int>(u * night_tex_->width);
    int niy = static_cast<int>(v * night_tex_->height);
    int noffset = nix + niy * night_tex_->width;
    uint32_t night_texel = night_tex_->pixels[noffset];
    float nr = ExtractR(night_texel);
    float ng = ExtractG(night_texel);
    float nb = ExtractB(night_texel);

    // Final color value is lerp between day and night texels.
    unsigned int ir = Clamp255(pr * tr + nr * ipr);
    unsigned int ig = Clamp255(pg * tg + ng * ipg);
    unsigned int ib = Clamp255(pb * tb + nb * ipb);

    unsigned int color = MakeRGBA(ir, ig, ib, 0xFF);

    *pixels = color;
    ++pixels;
  }
}

// Renders a rectangular area of the screen, scan line at a time
void Planet::wRenderRect(int x, int y, int w, int h) {
  for (int j = y; j < (y + h); ++j) {
    this->wRenderPixelSpan(x, x + w - 1, j);
  }
}

// If multithreading, this function is only called by the worker threads.
void Planet::wRenderRegion(int region) {
  // convert region # into x0, y0, x1, y1 rectangle
  int x, y, w, h;
  wMakeRect(region, &x, &y, &w, &h);
  // render this rectangle
  wRenderRect(x, y, w, h);
}

// Entry point for worker thread.  Can't pass a member function around, so we
// have to do this little round-about.
void Planet::wRenderRegionEntry(int region, void* thiz) {
  static_cast<Planet*>(thiz)->wRenderRegion(region);
}

// Renders the planet, dispatching the work to multiple threads.
// Note: This Dispatch() is from the main PPAPI thread, so care must be taken
// not to attempt PPAPI calls from the worker threads, since Dispatch() will
// block here until all work is complete.  The worker threads are compute only
// and do not make any PPAPI calls.
void Planet::Render() {
  workers_->Dispatch(num_regions_, wRenderRegionEntry, this);
}

// Pre-calculations to make inner loops faster.
void Planet::CacheCalcs() {
  planet_xyz_ = planet_x_ * planet_x_ +
                planet_y_ * planet_y_ +
                planet_z_ * planet_z_;
  planet_radius2_ = planet_radius_ * planet_radius_;
  planet_one_over_radius_ = 1.0f / planet_radius_;
  eye_xyz_ = eye_x_ * eye_x_ + eye_y_ * eye_y_ + eye_z_ * eye_z_;
  // spin vector from center->equator
  planet_equator_x_ = cos(planet_spin_x_);
  planet_equator_y_ = 0.0f;
  planet_equator_z_ = sin(planet_spin_x_);

  // cache cross product of pole & equator
  planet_pole_x_equator_x_ = planet_pole_y_ * planet_equator_z_ -
                             planet_pole_z_ * planet_equator_y_;
  planet_pole_x_equator_y_ = planet_pole_z_ * planet_equator_x_ -
                             planet_pole_x_ * planet_equator_z_;
  planet_pole_x_equator_z_ = planet_pole_x_ * planet_equator_y_ -
                             planet_pole_y_ * planet_equator_x_;
}

void Planet::SetPlanetXYZR(float x, float y, float z, float r) {
  planet_x_ = x;
  planet_y_ = y;
  planet_z_ = z;
  planet_radius_ = r;
  CacheCalcs();
}

void Planet::SetEyeXYZ(float x, float y, float z) {
  eye_x_ = x;
  eye_y_ = y;
  eye_z_ = z;
  CacheCalcs();
}

void Planet::SetLightXYZ(float x, float y, float z) {
  light_x_ = x;
  light_y_ = y;
  light_z_ = z;
  CacheCalcs();
}

void Planet::SetAmbientRGB(float r, float g, float b) {
  ambient_r_ = r;
  ambient_g_ = g;
  ambient_b_ = b;
  CacheCalcs();
}

void Planet::SetDiffuseRGB(float r, float g, float b) {
  diffuse_r_ = r;
  diffuse_g_ = g;
  diffuse_b_ = b;
  CacheCalcs();
}

void Planet::SetPlanetPole(float x, float y, float z) {
  planet_pole_x_ = x;
  planet_pole_y_ = y;
  planet_pole_z_ = z;
  CacheCalcs();
}

void Planet::SetPlanetEquator(float x, float y, float z) {
  // This is really over-ridden by spin at the momenent.
  planet_equator_x_ = x;
  planet_equator_y_ = y;
  planet_equator_z_ = z;
  CacheCalcs();
}

void Planet::SetPlanetSpin(float x, float y) {
  planet_spin_x_ = x;
  planet_spin_y_ = y;
  CacheCalcs();
}

// Run a simple sim to spin the planet.  Update loop is run once per frame.
// Called from the main thread only and only when the worker threads are idle.
void Planet::UpdateSim() {
  float x = planet_spin_x_ + ui_spin_x_;
  float y = planet_spin_y_ + ui_spin_y_;
  // keep in nice range
  if (x > (kPI * 2.0f))
    x = x - kPI * 2.0f;
  else if (x < (-kPI * 2.0f))
    x = x + kPI * 2.0f;
  if (y > (kPI * 2.0f))
    y = y - kPI * 2.0f;
  else if (y < (-kPI * 2.0f))
    y = y + kPI * 2.0f;
  SetPlanetSpin(x, y);
}

void Planet::DidChangeView(const pp::View& view) {
  pp::Rect position = view.GetRect();
  // Update hidden_ state
  hidden_ = !view.IsVisible();
  if (position.size().width() == width_ &&
      position.size().height() == height_)
    return;  // Size didn't change, no need to update anything.
  // Create a new device context with the new size.
  DestroyContext();
  CreateContext(position.size());

  if (initial_did_change_view_) {
    initial_did_change_view_ = false;
    Update();
  }
}

void Planet::StartBenchmark() {
  // For more consistent benchmark numbers, reset to default state.
  Reset();
  logf("Benchmark started...\n");
  benchmark_frame_counter_ = kFramesToBenchmark;
  benchmarking_ = true;
  benchmark_start_time_ = getseconds();
}

void Planet::EndBenchmark() {
  benchmark_end_time_ = getseconds();
  logf("Benchmark ended... time: %2.5f\n",
      benchmark_end_time_ - benchmark_start_time_);
  benchmarking_ = false;
  benchmark_frame_counter_ = 0;
  double total_time = benchmark_end_time_ - benchmark_start_time_;
  // Send benchmark result to JS.
  PostUpdateMessage("benchmark_result", total_time);
}

void Planet::SetZoom(float zoom) {
  ui_zoom_ = std::min(kZoomMax, std::max(kZoomMin, zoom));
  SetEyeXYZ(0.0f, 0.0f, -ui_zoom_);
}

void Planet::SetLight(float light) {
  ui_light_ = std::min(kLightMax, std::max(kLightMin, light));
  SetDiffuseRGB(0.8f * ui_light_, 0.8f * ui_light_, 0.8f * ui_light_);
  SetAmbientRGB(0.4f * ui_light_, 0.4f * ui_light_, 0.4f * ui_light_);
}

void Planet::SetTexture(const std::string& name, int width, int height,
                        uint32_t* pixels) {
  if (pixels) {
    if (name == "earth.jpg") {
      delete base_tex_;
      base_tex_ = new Texture(width, height, pixels);
    } else if (name == "earthnight.jpg") {
      delete night_tex_;
      night_tex_ = new Texture(width, height, pixels);
    }
  }
}

// Handle input events from the user.
bool Planet::HandleInputEvent(const pp::InputEvent& event) {
  switch (event.GetType()) {
    case PP_INPUTEVENT_TYPE_KEYDOWN: {
      pp::KeyboardInputEvent key(event);
      uint32_t key_code = key.GetKeyCode();
      if (key_code == 84)  // 't' key
        if (!benchmarking_)
          StartBenchmark();
      break;
    }
    case PP_INPUTEVENT_TYPE_MOUSEDOWN: {
      pp::MouseInputEvent mouse = pp::MouseInputEvent(event);
      last_mouse_pos_ = mouse.GetPosition();
      ui_spin_x_ = 0.0f;
      ui_spin_y_ = 0.0f;
      break;
    }
    case PP_INPUTEVENT_TYPE_MOUSEMOVE: {
      pp::MouseInputEvent mouse = pp::MouseInputEvent(event);
      if (mouse.GetModifiers() & PP_INPUTEVENT_MODIFIER_LEFTBUTTONDOWN) {
        PP_Point mouse_pos = mouse.GetPosition();
        float delta_x = static_cast<float>(mouse_pos.x - last_mouse_pos_.x);
        float delta_y = static_cast<float>(mouse_pos.y - last_mouse_pos_.y);
        float spin_x = std::min(10.0f, std::max(-10.0f, delta_x * 0.5f));
        float spin_y = std::min(10.0f, std::max(-10.0f, delta_y * 0.5f));
        ui_spin_x_ = spin_x / 100.0f;
        ui_spin_y_ = spin_y / 100.0f;
        last_mouse_pos_ = mouse_pos;
      }
      break;
    }
    case PP_INPUTEVENT_TYPE_WHEEL: {
      pp::WheelInputEvent wheel = pp::WheelInputEvent(event);
      PP_FloatPoint ticks = wheel.GetTicks();
      SetZoom(ui_zoom_ + (ticks.x + ticks.y) * kWheelSpeed);
      // Update html slider by sending update message to JS.
      PostUpdateMessage("set_zoom", ui_zoom_);
      break;
    }
    default:
      return false;
  }
  return true;
}

// PostUpdateMessage() helper function for sending small messages to JS.
void Planet::PostUpdateMessage(const char* message_name, double value) {
  pp::VarDictionary message;
  message.Set("message", message_name);
  message.Set("value", value);
  PostMessage(message);
}

// Handle message sent from Javascript.
void Planet::HandleMessage(const pp::Var& var) {
  if (var.is_dictionary()) {
    pp::VarDictionary dictionary(var);
    std::string message = dictionary.Get("message").AsString();
    if (message == "run benchmark" && !benchmarking_) {
      StartBenchmark();
    } else if (message == "set_light") {
      SetLight(static_cast<float>(dictionary.Get("value").AsDouble()));
    } else if (message == "set_zoom") {
      SetZoom(static_cast<float>(dictionary.Get("value").AsDouble()));
    } else if (message == "set_threads") {
      int threads = dictionary.Get("value").AsInt();
      delete workers_;
      workers_ = new ThreadPool(threads);
    } else if (message == "texture") {
      std::string name = dictionary.Get("name").AsString();
      int width = dictionary.Get("width").AsInt();
      int height = dictionary.Get("height").AsInt();
      pp::VarArrayBuffer array_buffer(dictionary.Get("data"));
      if (!name.empty() && width > 0 && height > 0 && !array_buffer.is_null()) {
        uint32_t* pixels = static_cast<uint32_t*>(array_buffer.Map());
        SetTexture(name, width, height, pixels);
        array_buffer.Unmap();
      }
    }
  } else {
    logf("Handle message unknown type: %s\n", var.DebugString().c_str());
  }
}

void Planet::FlushCallback(void* thiz, int32_t result) {
  static_cast<Planet*>(thiz)->Update();
}

// Update the 2d region and flush to make it visible on the page.
void Planet::FlushPixelBuffer() {
  graphics_2d_context_->PaintImageData(*image_data_, pp::Point(0, 0));
  graphics_2d_context_->Flush(pp::CompletionCallback(&FlushCallback, this));
}

void Planet::Update() {
  // If view is hidden, don't render, but periodically (every 33ms) chain w/
  // CallOnMainThread().
  if (hidden_) {
    pp::Module::Get()->core()->CallOnMainThread(33,
        pp::CompletionCallback(&FlushCallback, this));
    return;
  }
  // Don't call FlushPixelBuffer() when benchmarking - vsync is enabled by
  // default, and will throttle the benchmark results.
  do {
    UpdateSim();
    Render();
    if (!benchmarking_) break;
    --benchmark_frame_counter_;
  } while (benchmark_frame_counter_ > 0);
  if (benchmarking_)
    EndBenchmark();

  FlushPixelBuffer();

  double fps;
  if (FpsStep(&fps_state_, &fps))
    PostUpdateMessage("fps", fps);
}

void Planet::CreateContext(const pp::Size& size) {
  graphics_2d_context_ = new pp::Graphics2D(this, size, false);
  if (graphics_2d_context_->is_null())
    logf("Failed to create a 2D resource!\n");
  if (!BindGraphics(*graphics_2d_context_))
    logf("Couldn't bind the device context\n");
  image_data_ = new pp::ImageData(this,
                                  PP_IMAGEDATAFORMAT_BGRA_PREMUL,
                                  size,
                                  false);
  width_ = image_data_->size().width();
  height_ = image_data_->size().height();
  stride_in_pixels_ = static_cast<uint32_t>(image_data_->stride() / 4);
  pixel_buffer_ = static_cast<uint32_t*>(image_data_->data());
  num_regions_ = height_;
}

void Planet::DestroyContext() {
  delete graphics_2d_context_;
  delete image_data_;
  graphics_2d_context_ = NULL;
  image_data_ = NULL;
  width_ = 0;
  height_ = 0;
  stride_in_pixels_ = 0;
  pixel_buffer_ = NULL;
}

class PlanetModule : public pp::Module {
 public:
  PlanetModule() : pp::Module() {}
  virtual ~PlanetModule() {}

  // Create and return a Planet instance.
  virtual pp::Instance* CreateInstance(PP_Instance instance) {
    return new Planet(instance);
  }
};

namespace pp {
Module* CreateModule() {
  return new PlanetModule();
}
}  // namespace pp


/* [<][>][^][v][top][bottom][index][help] */