1// Copyright (c) 2013 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include <assert.h>
6#include <math.h>
7#include <ppapi/c/pp_point.h>
8#include <ppapi/c/ppb_input_event.h>
9#include <ppapi/cpp/completion_callback.h>
10#include <ppapi/cpp/graphics_2d.h>
11#include <ppapi/cpp/image_data.h>
12#include <ppapi/cpp/input_event.h>
13#include <ppapi/cpp/instance.h>
14#include <ppapi/cpp/module.h>
15#include <ppapi/cpp/rect.h>
16#include <ppapi/cpp/size.h>
17#include <ppapi/cpp/var.h>
18#include <ppapi/cpp/var_array.h>
19#include <ppapi/cpp/var_array_buffer.h>
20#include <ppapi/cpp/var_dictionary.h>
21#include <pthread.h>
22#include <stdio.h>
23#include <stdlib.h>
24#include <string.h>
25#include <unistd.h>
26
27#include <algorithm>
28#include <string>
29
30#include "common/fps.h"
31#include "sdk_util/macros.h"
32#include "sdk_util/thread_pool.h"
33
34// Chromium presubmit prevents checking in changes with calls to printf to
35// prevent spammy output. We'll work around that for this example.
36#define logf printf
37
38using namespace sdk_util;  // For sdk_util::ThreadPool
39
40// Global properties used to setup Earth demo.
41namespace {
42const float kPI = M_PI;
43const float kTwoPI = kPI * 2.0f;
44const float kOneOverPI = 1.0f / kPI;
45const float kOneOver2PI = 1.0f / kTwoPI;
46const float kOneOver255 = 1.0f / 255.0f;
47const int kArcCosineTableSize = 4096;
48const int kFramesToBenchmark = 100;
49const float kZoomMin = 1.0f;
50const float kZoomMax = 50.0f;
51const float kWheelSpeed = 2.0f;
52const float kLightMin = 0.0f;
53const float kLightMax = 2.0f;
54
55// RGBA helper functions.
56inline float ExtractR(uint32_t c) {
57  return static_cast<float>(c & 0xFF) * kOneOver255;
58}
59
60inline float ExtractG(uint32_t c) {
61  return static_cast<float>((c & 0xFF00) >> 8) * kOneOver255;
62}
63
64inline float ExtractB(uint32_t c) {
65  return static_cast<float>((c & 0xFF0000) >> 16) * kOneOver255;
66}
67
68inline uint32_t MakeRGBA(uint32_t r, uint32_t g, uint32_t b, uint32_t a) {
69  return (((a) << 24) | ((r) << 16) | ((g) << 8) | (b));
70}
71
72// simple container for earth texture
73struct Texture {
74  int width, height;
75  uint32_t* pixels;
76  Texture(int w, int h) : width(w), height(h) {
77    pixels = new uint32_t[w * h];
78    memset(pixels, 0, sizeof(uint32_t) * w * h);
79  }
80  explicit Texture(int w, int h, uint32_t* p) : width(w), height(h) {
81    pixels = new uint32_t[w * h];
82    memcpy(pixels, p, sizeof(uint32_t) * w * h);
83  }
84  ~Texture() { delete[] pixels; }
85
86  DISALLOW_COPY_AND_ASSIGN(Texture);
87};
88
89
90
91struct ArcCosine {
92  // slightly larger table so we can interpolate beyond table size
93  float table[kArcCosineTableSize + 2];
94  float TableLerp(float x);
95  ArcCosine();
96};
97
98ArcCosine::ArcCosine() {
99  // build a slightly larger table to allow for numeric imprecision
100  for (int i = 0; i < (kArcCosineTableSize + 2); ++i) {
101    float f = static_cast<float>(i) / kArcCosineTableSize;
102    f = f * 2.0f - 1.0f;
103    table[i] = acos(f);
104  }
105}
106
107// looks up acos(f) using a table and lerping between entries
108// (it is expected that input f is between -1 and 1)
109float ArcCosine::TableLerp(float f) {
110  float x = (f + 1.0f) * 0.5f;
111  x = x * kArcCosineTableSize;
112  int ix = static_cast<int>(x);
113  float fx = static_cast<float>(ix);
114  float dx = x - fx;
115  float af = table[ix];
116  float af2 = table[ix + 1];
117  return af + (af2 - af) * dx;
118}
119
120// Helper functions for quick but approximate sqrt.
121union Convert {
122  float f;
123  int i;
124  Convert(int x) { i = x; }
125  Convert(float x) { f = x; }
126  int AsInt() { return i; }
127  float AsFloat() { return f; }
128};
129
130inline const int AsInteger(const float f) {
131  Convert u(f);
132  return u.AsInt();
133}
134
135inline const float AsFloat(const int i) {
136  Convert u(i);
137  return u.AsFloat();
138}
139
140const long int kOneAsInteger = AsInteger(1.0f);
141
142inline float inline_quick_sqrt(float x) {
143  int i;
144  i = (AsInteger(x) >> 1) + (kOneAsInteger >> 1);
145  return AsFloat(i);
146}
147
148inline float inline_sqrt(float x) {
149  float y;
150  y = inline_quick_sqrt(x);
151  y = (y * y + x) / (2.0f * y);
152  y = (y * y + x) / (2.0f * y);
153  return y;
154}
155
156// takes a -0..1+ color, clamps it to 0..1 and maps it to 0..255 integer
157inline uint32_t Clamp255(float x) {
158  if (x < 0.0f) {
159    x = 0.0f;
160  } else if (x > 1.0f) {
161    x = 1.0f;
162  }
163  return static_cast<uint32_t>(x * 255.0f);
164}
165}  // namespace
166
167
168// The main object that runs the Earth demo.
169class Planet : public pp::Instance {
170 public:
171  explicit Planet(PP_Instance instance);
172  virtual ~Planet();
173
174  virtual bool Init(uint32_t argc, const char* argn[], const char* argv[]);
175
176  virtual void DidChangeView(const pp::View& view);
177
178  // Catch events.
179  virtual bool HandleInputEvent(const pp::InputEvent& event);
180
181  // Catch messages posted from Javascript.
182  virtual void HandleMessage(const pp::Var& message);
183
184 private:
185  // Methods prefixed with 'w' are run on worker threads.
186  uint32_t* wGetAddr(int x, int y);
187  void wRenderPixelSpan(int x0, int x1, int y);
188  void wMakeRect(int r, int *x, int *y, int *w, int *h);
189  void wRenderRect(int x0, int y0, int x1, int y1);
190  void wRenderRegion(int region);
191  static void wRenderRegionEntry(int region, void *thiz);
192
193  // These methods are only called by the main thread.
194  void CacheCalcs();
195  void SetPlanetXYZR(float x, float y, float z, float r);
196  void SetPlanetPole(float x, float y, float z);
197  void SetPlanetEquator(float x, float y, float z);
198  void SetPlanetSpin(float x, float y);
199  void SetEyeXYZ(float x, float y, float z);
200  void SetLightXYZ(float x, float y, float z);
201  void SetAmbientRGB(float r, float g, float b);
202  void SetDiffuseRGB(float r, float g, float b);
203  void SetZoom(float zoom);
204  void SetLight(float zoom);
205  void SetTexture(const std::string& name, int width, int height,
206      uint32_t* pixels);
207
208  void Reset();
209  void PostInit(int32_t result);
210  void UpdateSim();
211  void Render();
212  void Draw();
213  void StartBenchmark();
214  void EndBenchmark();
215
216  // Runs a tick of the simulations, updating all buffers.  Flushes the
217  // contents of |image_data_| to the 2D graphics context.
218  void Update();
219
220  // Post a small key-value message to update JS.
221  void PostUpdateMessage(const char* message_name, double value);
222  // Create and initialize the 2D context used for drawing.
223  void CreateContext(const pp::Size& size);
224  // Destroy the 2D drawing context.
225  void DestroyContext();
226  // Push the pixels to the browser, then attempt to flush the 2D context.
227  void FlushPixelBuffer();
228  static void FlushCallback(void* data, int32_t result);
229
230  // User Interface settings.  These settings are controlled via html
231  // controls or via user input.
232  float ui_light_;
233  float ui_zoom_;
234  float ui_spin_x_;
235  float ui_spin_y_;
236
237  // Various settings for position & orientation of planet.  Do not change
238  // these variables, instead use SetPlanet*() functions.
239  float planet_radius_;
240  float planet_spin_x_;
241  float planet_spin_y_;
242  float planet_x_, planet_y_, planet_z_;
243  float planet_pole_x_, planet_pole_y_, planet_pole_z_;
244  float planet_equator_x_, planet_equator_y_, planet_equator_z_;
245
246  // Observer's eye.  Do not change these variables, instead use SetEyeXYZ().
247  float eye_x_, eye_y_, eye_z_;
248
249  // Light position, ambient and diffuse settings.  Do not change these
250  // variables, instead use SetLightXYZ(), SetAmbientRGB() and SetDiffuseRGB().
251  float light_x_, light_y_, light_z_;
252  float diffuse_r_, diffuse_g_, diffuse_b_;
253  float ambient_r_, ambient_g_, ambient_b_;
254
255  // Cached calculations.  Do not change these variables - they are updated by
256  // CacheCalcs() function.
257  float planet_xyz_;
258  float planet_pole_x_equator_x_;
259  float planet_pole_x_equator_y_;
260  float planet_pole_x_equator_z_;
261  float planet_radius2_;
262  float planet_one_over_radius_;
263  float eye_xyz_;
264
265  // Source texture (earth map).
266  Texture* base_tex_;
267  Texture* night_tex_;
268  int width_for_tex_;
269  int height_for_tex_;
270  std::string name_for_tex_;
271
272  // Quick ArcCos helper.
273  ArcCosine acos_;
274
275  // Misc.
276  pp::Graphics2D* graphics_2d_context_;
277  pp::ImageData* image_data_;
278  bool initial_did_change_view_;
279  int num_threads_;
280  int num_regions_;
281  ThreadPool* workers_;
282  int width_;
283  int height_;
284  bool hidden_;
285  PP_Point last_mouse_pos_;
286  uint32_t stride_in_pixels_;
287  uint32_t* pixel_buffer_;
288  int benchmark_frame_counter_;
289  bool benchmarking_;
290  double benchmark_start_time_;
291  double benchmark_end_time_;
292  FpsState fps_state_;
293};
294
295
296bool Planet::Init(uint32_t argc, const char* argn[], const char* argv[]) {
297  // Request PPAPI input events for mouse & keyboard.
298  RequestInputEvents(PP_INPUTEVENT_CLASS_MOUSE);
299  RequestInputEvents(PP_INPUTEVENT_CLASS_WHEEL);
300  RequestInputEvents(PP_INPUTEVENT_CLASS_KEYBOARD);
301  // Request a set of images from JS.  After images are loaded by JS, a
302  // message from JS -> NaCl will arrive containing the pixel data.  See
303  // HandleMessage() method in this file.
304  pp::VarDictionary message;
305  message.Set("message", "request_textures");
306  pp::VarArray names;
307  names.Set(0, "earth.jpg");
308  names.Set(1, "earthnight.jpg");
309  message.Set("names", names);
310  PostMessage(message);
311  return true;
312}
313
314void Planet::Reset() {
315  // Reset has to first fill in all variables with valid floats, so
316  // CacheCalcs() doesn't potentially propagate NaNs when calling Set*()
317  // functions further below.
318  planet_radius_ = 1.0f;
319  planet_spin_x_ = 0.0f;
320  planet_spin_y_ = 0.0f;
321  planet_x_ = 0.0f;
322  planet_y_ = 0.0f;
323  planet_z_ = 0.0f;
324  planet_pole_x_ = 0.0f;
325  planet_pole_y_ = 0.0f;
326  planet_pole_z_ = 0.0f;
327  planet_equator_x_ = 0.0f;
328  planet_equator_y_ = 0.0f;
329  planet_equator_z_ = 0.0f;
330  eye_x_ = 0.0f;
331  eye_y_ = 0.0f;
332  eye_z_ = 0.0f;
333  light_x_ = 0.0f;
334  light_y_ = 0.0f;
335  light_z_ = 0.0f;
336  diffuse_r_ = 0.0f;
337  diffuse_g_ = 0.0f;
338  diffuse_b_ = 0.0f;
339  ambient_r_ = 0.0f;
340  ambient_g_ = 0.0f;
341  ambient_b_ = 0.0f;
342  planet_xyz_ = 0.0f;
343  planet_pole_x_equator_x_ = 0.0f;
344  planet_pole_x_equator_y_ = 0.0f;
345  planet_pole_x_equator_z_ = 0.0f;
346  planet_radius2_ = 0.0f;
347  planet_one_over_radius_ = 0.0f;
348  eye_xyz_ = 0.0f;
349  ui_zoom_ = 14.0f;
350  ui_light_ = 1.0f;
351  ui_spin_x_ = 0.01f;
352  ui_spin_y_ = 0.0f;
353
354  // Set up reasonable default values.
355  SetPlanetXYZR(0.0f, 0.0f, 48.0f, 4.0f);
356  SetEyeXYZ(0.0f, 0.0f, -ui_zoom_);
357  SetLightXYZ(-60.0f, -30.0f, 0.0f);
358  SetAmbientRGB(0.05f, 0.05f, 0.05f);
359  SetDiffuseRGB(0.8f, 0.8f, 0.8f);
360  SetPlanetPole(0.0f, 1.0f, 0.0f);
361  SetPlanetEquator(1.0f, 0.0f, 0.0f);
362  SetPlanetSpin(kPI / 2.0f, kPI / 2.0f);
363  SetZoom(ui_zoom_);
364  SetLight(ui_light_);
365
366  // Send UI values to JS to reset html sliders.
367  PostUpdateMessage("set_zoom", ui_zoom_);
368  PostUpdateMessage("set_light", ui_light_);
369}
370
371
372Planet::Planet(PP_Instance instance) : pp::Instance(instance),
373                                       graphics_2d_context_(NULL),
374                                       image_data_(NULL),
375                                       initial_did_change_view_(true),
376                                       num_regions_(256) {
377  width_ = 0;
378  height_ = 0;
379  hidden_ = false;
380  stride_in_pixels_ = 0;
381  pixel_buffer_ = NULL;
382  benchmark_frame_counter_ = 0;
383  benchmarking_ = false;
384  base_tex_ = NULL;
385  night_tex_ = NULL;
386  name_for_tex_ = "";
387  last_mouse_pos_ = PP_MakePoint(0, 0);
388  FpsInit(&fps_state_);
389
390  Reset();
391
392  // By default, render from the dispatch thread.
393  num_threads_ = 0;
394  workers_ = new ThreadPool(num_threads_);
395}
396
397Planet::~Planet() {
398  delete workers_;
399  DestroyContext();
400}
401
402// Given a region r, derive a rectangle.
403// This rectangle shouldn't overlap with work being done by other workers.
404// If multithreading, this function is only called by the worker threads.
405void Planet::wMakeRect(int r, int *x, int *y, int *w, int *h) {
406  int dy = height_ / num_regions_;
407  *x = 0;
408  *w = width_;
409  *y = r * dy;
410  *h = dy;
411}
412
413
414inline uint32_t* Planet::wGetAddr(int x, int y) {
415  assert(pixel_buffer_);
416  return (pixel_buffer_ + y * stride_in_pixels_) + x;
417}
418
419// This is the meat of the ray tracer.  Given a pixel span (x0, x1) on
420// scanline y, shoot rays into the scene and render what they hit.  Use
421// scanline coherence to do a few optimizations
422void Planet::wRenderPixelSpan(int x0, int x1, int y) {
423  if (!base_tex_ || !night_tex_)
424    return;
425  const int kColorBlack = MakeRGBA(0, 0, 0, 0xFF);
426  float min_dim = width_ < height_ ? width_ : height_;
427  float offset_x = width_ < height_ ? 0 : (width_ - min_dim) * 0.5f;
428  float offset_y = width_ < height_ ? (height_ - min_dim) * 0.5f : 0;
429  float y0 = eye_y_;
430  float z0 = eye_z_;
431  float y1 = (static_cast<float>(y - offset_y) / min_dim) * 2.0f - 1.0f;
432  float z1 = 0.0f;
433  float dy = (y1 - y0);
434  float dz = (z1 - z0);
435  float dy_dy_dz_dz = dy * dy + dz * dz;
436  float two_dy_y0_y_two_dz_z0_z = 2.0f * dy * (y0 - planet_y_) +
437                                  2.0f * dz * (z0 - planet_z_);
438  float planet_xyz_eye_xyz = planet_xyz_ + eye_xyz_;
439  float y_y0_z_z0 = planet_y_ * y0 + planet_z_ * z0;
440  float oowidth = 1.0f / min_dim;
441  uint32_t* pixels = this->wGetAddr(x0, y);
442  for (int x = x0; x <= x1; ++x) {
443    // scan normalized screen -1..1
444    float x1 = (static_cast<float>(x - offset_x) * oowidth) * 2.0f - 1.0f;
445    // eye
446    float x0 = eye_x_;
447    // delta from screen to eye
448    float dx = (x1 - x0);
449    // build a, b, c
450    float a = dx * dx + dy_dy_dz_dz;
451    float b = 2.0f * dx * (x0 - planet_x_) + two_dy_y0_y_two_dz_z0_z;
452    float c = planet_xyz_eye_xyz +
453              -2.0f * (planet_x_ * x0 + y_y0_z_z0) - (planet_radius2_);
454    // calculate discriminant
455    float disc = b * b - 4.0f * a * c;
456
457    // Did ray hit the sphere?
458    if (disc < 0.0f) {
459      *pixels = kColorBlack;
460      ++pixels;
461      continue;
462    }
463
464    // calc parametric t value
465    float t = (-b - inline_sqrt(disc)) / (2.0f * a);
466    float px = x0 + t * dx;
467    float py = y0 + t * dy;
468    float pz = z0 + t * dz;
469    float nx = (px - planet_x_) * planet_one_over_radius_;
470    float ny = (py - planet_y_) * planet_one_over_radius_;
471    float nz = (pz - planet_z_) * planet_one_over_radius_;
472
473    // Misc raytrace calculations.
474    float Lx = (light_x_ - px);
475    float Ly = (light_y_ - py);
476    float Lz = (light_z_ - pz);
477    float Lq = 1.0f / inline_quick_sqrt(Lx * Lx + Ly * Ly + Lz * Lz);
478    Lx *= Lq;
479    Ly *= Lq;
480    Lz *= Lq;
481    float d = (Lx * nx + Ly * ny + Lz * nz);
482    float pr = (diffuse_r_ * d) + ambient_r_;
483    float pg = (diffuse_g_ * d) + ambient_g_;
484    float pb = (diffuse_b_ * d) + ambient_b_;
485    float ds = -(nx * planet_pole_x_ +
486                 ny * planet_pole_y_ +
487                 nz * planet_pole_z_);
488    float ang = acos_.TableLerp(ds);
489    float v = ang * kOneOverPI;
490    float dp = planet_equator_x_ * nx +
491               planet_equator_y_ * ny +
492               planet_equator_z_ * nz;
493    float w = dp / sin(ang);
494    if (w > 1.0f) w = 1.0f;
495    if (w < -1.0f) w = -1.0f;
496    float th = acos_.TableLerp(w) * kOneOver2PI;
497    float dps = planet_pole_x_equator_x_ * nx +
498                planet_pole_x_equator_y_ * ny +
499                planet_pole_x_equator_z_ * nz;
500    float u;
501    if (dps < 0.0f)
502      u = th;
503    else
504      u = 1.0f - th;
505
506    // Look up daylight texel.
507    int tx = static_cast<int>(u * base_tex_->width);
508    int ty = static_cast<int>(v * base_tex_->height);
509    int offset = tx + ty * base_tex_->width;
510    uint32_t base_texel = base_tex_->pixels[offset];
511    float tr = ExtractR(base_texel);
512    float tg = ExtractG(base_texel);
513    float tb = ExtractB(base_texel);
514
515    float ipr = 1.0f - pr;
516    if (ipr < 0.0f) ipr = 0.0f;
517    float ipg = 1.0f - pg;
518    if (ipg < 0.0f) ipg = 0.0f;
519    float ipb = 1.0f - pb;
520    if (ipb < 0.0f) ipb = 0.0f;
521
522    // Look up night texel.
523    int nix = static_cast<int>(u * night_tex_->width);
524    int niy = static_cast<int>(v * night_tex_->height);
525    int noffset = nix + niy * night_tex_->width;
526    uint32_t night_texel = night_tex_->pixels[noffset];
527    float nr = ExtractR(night_texel);
528    float ng = ExtractG(night_texel);
529    float nb = ExtractB(night_texel);
530
531    // Final color value is lerp between day and night texels.
532    unsigned int ir = Clamp255(pr * tr + nr * ipr);
533    unsigned int ig = Clamp255(pg * tg + ng * ipg);
534    unsigned int ib = Clamp255(pb * tb + nb * ipb);
535
536    unsigned int color = MakeRGBA(ir, ig, ib, 0xFF);
537
538    *pixels = color;
539    ++pixels;
540  }
541}
542
543// Renders a rectangular area of the screen, scan line at a time
544void Planet::wRenderRect(int x, int y, int w, int h) {
545  for (int j = y; j < (y + h); ++j) {
546    this->wRenderPixelSpan(x, x + w - 1, j);
547  }
548}
549
550// If multithreading, this function is only called by the worker threads.
551void Planet::wRenderRegion(int region) {
552  // convert region # into x0, y0, x1, y1 rectangle
553  int x, y, w, h;
554  wMakeRect(region, &x, &y, &w, &h);
555  // render this rectangle
556  wRenderRect(x, y, w, h);
557}
558
559// Entry point for worker thread.  Can't pass a member function around, so we
560// have to do this little round-about.
561void Planet::wRenderRegionEntry(int region, void* thiz) {
562  static_cast<Planet*>(thiz)->wRenderRegion(region);
563}
564
565// Renders the planet, dispatching the work to multiple threads.
566// Note: This Dispatch() is from the main PPAPI thread, so care must be taken
567// not to attempt PPAPI calls from the worker threads, since Dispatch() will
568// block here until all work is complete.  The worker threads are compute only
569// and do not make any PPAPI calls.
570void Planet::Render() {
571  workers_->Dispatch(num_regions_, wRenderRegionEntry, this);
572}
573
574// Pre-calculations to make inner loops faster.
575void Planet::CacheCalcs() {
576  planet_xyz_ = planet_x_ * planet_x_ +
577                planet_y_ * planet_y_ +
578                planet_z_ * planet_z_;
579  planet_radius2_ = planet_radius_ * planet_radius_;
580  planet_one_over_radius_ = 1.0f / planet_radius_;
581  eye_xyz_ = eye_x_ * eye_x_ + eye_y_ * eye_y_ + eye_z_ * eye_z_;
582  // spin vector from center->equator
583  planet_equator_x_ = cos(planet_spin_x_);
584  planet_equator_y_ = 0.0f;
585  planet_equator_z_ = sin(planet_spin_x_);
586
587  // cache cross product of pole & equator
588  planet_pole_x_equator_x_ = planet_pole_y_ * planet_equator_z_ -
589                             planet_pole_z_ * planet_equator_y_;
590  planet_pole_x_equator_y_ = planet_pole_z_ * planet_equator_x_ -
591                             planet_pole_x_ * planet_equator_z_;
592  planet_pole_x_equator_z_ = planet_pole_x_ * planet_equator_y_ -
593                             planet_pole_y_ * planet_equator_x_;
594}
595
596void Planet::SetPlanetXYZR(float x, float y, float z, float r) {
597  planet_x_ = x;
598  planet_y_ = y;
599  planet_z_ = z;
600  planet_radius_ = r;
601  CacheCalcs();
602}
603
604void Planet::SetEyeXYZ(float x, float y, float z) {
605  eye_x_ = x;
606  eye_y_ = y;
607  eye_z_ = z;
608  CacheCalcs();
609}
610
611void Planet::SetLightXYZ(float x, float y, float z) {
612  light_x_ = x;
613  light_y_ = y;
614  light_z_ = z;
615  CacheCalcs();
616}
617
618void Planet::SetAmbientRGB(float r, float g, float b) {
619  ambient_r_ = r;
620  ambient_g_ = g;
621  ambient_b_ = b;
622  CacheCalcs();
623}
624
625void Planet::SetDiffuseRGB(float r, float g, float b) {
626  diffuse_r_ = r;
627  diffuse_g_ = g;
628  diffuse_b_ = b;
629  CacheCalcs();
630}
631
632void Planet::SetPlanetPole(float x, float y, float z) {
633  planet_pole_x_ = x;
634  planet_pole_y_ = y;
635  planet_pole_z_ = z;
636  CacheCalcs();
637}
638
639void Planet::SetPlanetEquator(float x, float y, float z) {
640  // This is really over-ridden by spin at the momenent.
641  planet_equator_x_ = x;
642  planet_equator_y_ = y;
643  planet_equator_z_ = z;
644  CacheCalcs();
645}
646
647void Planet::SetPlanetSpin(float x, float y) {
648  planet_spin_x_ = x;
649  planet_spin_y_ = y;
650  CacheCalcs();
651}
652
653// Run a simple sim to spin the planet.  Update loop is run once per frame.
654// Called from the main thread only and only when the worker threads are idle.
655void Planet::UpdateSim() {
656  float x = planet_spin_x_ + ui_spin_x_;
657  float y = planet_spin_y_ + ui_spin_y_;
658  // keep in nice range
659  if (x > (kPI * 2.0f))
660    x = x - kPI * 2.0f;
661  else if (x < (-kPI * 2.0f))
662    x = x + kPI * 2.0f;
663  if (y > (kPI * 2.0f))
664    y = y - kPI * 2.0f;
665  else if (y < (-kPI * 2.0f))
666    y = y + kPI * 2.0f;
667  SetPlanetSpin(x, y);
668}
669
670void Planet::DidChangeView(const pp::View& view) {
671  pp::Rect position = view.GetRect();
672  // Update hidden_ state
673  hidden_ = !view.IsVisible();
674  if (position.size().width() == width_ &&
675      position.size().height() == height_)
676    return;  // Size didn't change, no need to update anything.
677  // Create a new device context with the new size.
678  DestroyContext();
679  CreateContext(position.size());
680
681  if (initial_did_change_view_) {
682    initial_did_change_view_ = false;
683    Update();
684  }
685}
686
687void Planet::StartBenchmark() {
688  // For more consistent benchmark numbers, reset to default state.
689  Reset();
690  logf("Benchmark started...\n");
691  benchmark_frame_counter_ = kFramesToBenchmark;
692  benchmarking_ = true;
693  benchmark_start_time_ = getseconds();
694}
695
696void Planet::EndBenchmark() {
697  benchmark_end_time_ = getseconds();
698  logf("Benchmark ended... time: %2.5f\n",
699      benchmark_end_time_ - benchmark_start_time_);
700  benchmarking_ = false;
701  benchmark_frame_counter_ = 0;
702  double total_time = benchmark_end_time_ - benchmark_start_time_;
703  // Send benchmark result to JS.
704  PostUpdateMessage("benchmark_result", total_time);
705}
706
707void Planet::SetZoom(float zoom) {
708  ui_zoom_ = std::min(kZoomMax, std::max(kZoomMin, zoom));
709  SetEyeXYZ(0.0f, 0.0f, -ui_zoom_);
710}
711
712void Planet::SetLight(float light) {
713  ui_light_ = std::min(kLightMax, std::max(kLightMin, light));
714  SetDiffuseRGB(0.8f * ui_light_, 0.8f * ui_light_, 0.8f * ui_light_);
715  SetAmbientRGB(0.4f * ui_light_, 0.4f * ui_light_, 0.4f * ui_light_);
716}
717
718void Planet::SetTexture(const std::string& name, int width, int height,
719                        uint32_t* pixels) {
720  if (pixels) {
721    if (name == "earth.jpg") {
722      delete base_tex_;
723      base_tex_ = new Texture(width, height, pixels);
724    } else if (name == "earthnight.jpg") {
725      delete night_tex_;
726      night_tex_ = new Texture(width, height, pixels);
727    }
728  }
729}
730
731// Handle input events from the user.
732bool Planet::HandleInputEvent(const pp::InputEvent& event) {
733  switch (event.GetType()) {
734    case PP_INPUTEVENT_TYPE_KEYDOWN: {
735      pp::KeyboardInputEvent key(event);
736      uint32_t key_code = key.GetKeyCode();
737      if (key_code == 84)  // 't' key
738        if (!benchmarking_)
739          StartBenchmark();
740      break;
741    }
742    case PP_INPUTEVENT_TYPE_MOUSEDOWN: {
743      pp::MouseInputEvent mouse = pp::MouseInputEvent(event);
744      last_mouse_pos_ = mouse.GetPosition();
745      ui_spin_x_ = 0.0f;
746      ui_spin_y_ = 0.0f;
747      break;
748    }
749    case PP_INPUTEVENT_TYPE_MOUSEMOVE: {
750      pp::MouseInputEvent mouse = pp::MouseInputEvent(event);
751      if (mouse.GetModifiers() & PP_INPUTEVENT_MODIFIER_LEFTBUTTONDOWN) {
752        PP_Point mouse_pos = mouse.GetPosition();
753        float delta_x = static_cast<float>(mouse_pos.x - last_mouse_pos_.x);
754        float delta_y = static_cast<float>(mouse_pos.y - last_mouse_pos_.y);
755        float spin_x = std::min(10.0f, std::max(-10.0f, delta_x * 0.5f));
756        float spin_y = std::min(10.0f, std::max(-10.0f, delta_y * 0.5f));
757        ui_spin_x_ = spin_x / 100.0f;
758        ui_spin_y_ = spin_y / 100.0f;
759        last_mouse_pos_ = mouse_pos;
760      }
761      break;
762    }
763    case PP_INPUTEVENT_TYPE_WHEEL: {
764      pp::WheelInputEvent wheel = pp::WheelInputEvent(event);
765      PP_FloatPoint ticks = wheel.GetTicks();
766      SetZoom(ui_zoom_ + (ticks.x + ticks.y) * kWheelSpeed);
767      // Update html slider by sending update message to JS.
768      PostUpdateMessage("set_zoom", ui_zoom_);
769      break;
770    }
771    default:
772      return false;
773  }
774  return true;
775}
776
777// PostUpdateMessage() helper function for sending small messages to JS.
778void Planet::PostUpdateMessage(const char* message_name, double value) {
779  pp::VarDictionary message;
780  message.Set("message", message_name);
781  message.Set("value", value);
782  PostMessage(message);
783}
784
785// Handle message sent from Javascript.
786void Planet::HandleMessage(const pp::Var& var) {
787  if (var.is_dictionary()) {
788    pp::VarDictionary dictionary(var);
789    std::string message = dictionary.Get("message").AsString();
790    if (message == "run benchmark" && !benchmarking_) {
791      StartBenchmark();
792    } else if (message == "set_light") {
793      SetLight(static_cast<float>(dictionary.Get("value").AsDouble()));
794    } else if (message == "set_zoom") {
795      SetZoom(static_cast<float>(dictionary.Get("value").AsDouble()));
796    } else if (message == "set_threads") {
797      int threads = dictionary.Get("value").AsInt();
798      delete workers_;
799      workers_ = new ThreadPool(threads);
800    } else if (message == "texture") {
801      std::string name = dictionary.Get("name").AsString();
802      int width = dictionary.Get("width").AsInt();
803      int height = dictionary.Get("height").AsInt();
804      pp::VarArrayBuffer array_buffer(dictionary.Get("data"));
805      if (!name.empty() && width > 0 && height > 0 && !array_buffer.is_null()) {
806        uint32_t* pixels = static_cast<uint32_t*>(array_buffer.Map());
807        SetTexture(name, width, height, pixels);
808        array_buffer.Unmap();
809      }
810    }
811  } else {
812    logf("Handle message unknown type: %s\n", var.DebugString().c_str());
813  }
814}
815
816void Planet::FlushCallback(void* thiz, int32_t result) {
817  static_cast<Planet*>(thiz)->Update();
818}
819
820// Update the 2d region and flush to make it visible on the page.
821void Planet::FlushPixelBuffer() {
822  graphics_2d_context_->PaintImageData(*image_data_, pp::Point(0, 0));
823  graphics_2d_context_->Flush(pp::CompletionCallback(&FlushCallback, this));
824}
825
826void Planet::Update() {
827  // If view is hidden, don't render, but periodically (every 33ms) chain w/
828  // CallOnMainThread().
829  if (hidden_) {
830    pp::Module::Get()->core()->CallOnMainThread(33,
831        pp::CompletionCallback(&FlushCallback, this));
832    return;
833  }
834  // Don't call FlushPixelBuffer() when benchmarking - vsync is enabled by
835  // default, and will throttle the benchmark results.
836  do {
837    UpdateSim();
838    Render();
839    if (!benchmarking_) break;
840    --benchmark_frame_counter_;
841  } while (benchmark_frame_counter_ > 0);
842  if (benchmarking_)
843    EndBenchmark();
844
845  FlushPixelBuffer();
846
847  double fps;
848  if (FpsStep(&fps_state_, &fps))
849    PostUpdateMessage("fps", fps);
850}
851
852void Planet::CreateContext(const pp::Size& size) {
853  graphics_2d_context_ = new pp::Graphics2D(this, size, false);
854  if (graphics_2d_context_->is_null())
855    logf("Failed to create a 2D resource!\n");
856  if (!BindGraphics(*graphics_2d_context_))
857    logf("Couldn't bind the device context\n");
858  image_data_ = new pp::ImageData(this,
859                                  PP_IMAGEDATAFORMAT_BGRA_PREMUL,
860                                  size,
861                                  false);
862  width_ = image_data_->size().width();
863  height_ = image_data_->size().height();
864  stride_in_pixels_ = static_cast<uint32_t>(image_data_->stride() / 4);
865  pixel_buffer_ = static_cast<uint32_t*>(image_data_->data());
866  num_regions_ = height_;
867}
868
869void Planet::DestroyContext() {
870  delete graphics_2d_context_;
871  delete image_data_;
872  graphics_2d_context_ = NULL;
873  image_data_ = NULL;
874  width_ = 0;
875  height_ = 0;
876  stride_in_pixels_ = 0;
877  pixel_buffer_ = NULL;
878}
879
880class PlanetModule : public pp::Module {
881 public:
882  PlanetModule() : pp::Module() {}
883  virtual ~PlanetModule() {}
884
885  // Create and return a Planet instance.
886  virtual pp::Instance* CreateInstance(PP_Instance instance) {
887    return new Planet(instance);
888  }
889};
890
891namespace pp {
892Module* CreateModule() {
893  return new PlanetModule();
894}
895}  // namespace pp
896
897