1// Copyright 2014 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/ppb_input_event.h>
8#include <ppapi/cpp/input_event.h>
9#include <ppapi/cpp/var.h>
10#include <ppapi/cpp/var_array.h>
11#include <ppapi/cpp/var_array_buffer.h>
12#include <ppapi/cpp/var_dictionary.h>
13#include <pthread.h>
14#include <stdio.h>
15#include <stdlib.h>
16#include <string.h>
17#include <sys/time.h>
18#include <unistd.h>
19
20#include <algorithm>
21#include <string>
22
23#include "ppapi_simple/ps.h"
24#include "ppapi_simple/ps_context_2d.h"
25#include "ppapi_simple/ps_event.h"
26#include "ppapi_simple/ps_interface.h"
27#include "ppapi_simple/ps_main.h"
28#include "sdk_util/macros.h"
29#include "sdk_util/thread_pool.h"
30
31using namespace sdk_util;  // For sdk_util::ThreadPool
32
33#define INLINE inline __attribute__((always_inline))
34
35// 128 bit SIMD vector types
36typedef uint8_t u8x16_t __attribute__ ((vector_size (16)));
37typedef int32_t i32x4_t __attribute__ ((vector_size (16)));
38typedef uint32_t u32x4_t __attribute__ ((vector_size (16)));
39typedef float f32x4_t __attribute__ ((vector_size (16)));
40
41// Global properties used to setup Earth demo.
42namespace {
43const float kPI = M_PI;
44const float kTwoPI = kPI * 2.0f;
45const float kOneOverPI = 1.0f / kPI;
46const float kOneOver2PI = 1.0f / kTwoPI;
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// Timer helper for benchmarking.  Returns seconds elapsed since program start,
56// as a double.
57timeval start_tv;
58int start_tv_retv = gettimeofday(&start_tv, NULL);
59
60inline double getseconds() {
61  const double usec_to_sec = 0.000001;
62  timeval tv;
63  if ((0 == start_tv_retv) && (0 == gettimeofday(&tv, NULL)))
64    return (tv.tv_sec - start_tv.tv_sec) + tv.tv_usec * usec_to_sec;
65  return 0.0;
66}
67
68// SIMD Vector helper functions.
69//
70// Note that a compare between two vectors will return a signed integer vector
71// with the same number of elements, where each element will be all bits set
72// for true (-1), and all bits clear for false (0) This integer vector can be
73// useful as a mask.
74//
75// Also note that c-style casts do not mutate the bits of a vector - only the
76// type.  Boolean operators can't operate on float vectors, but it is possible
77// to cast them temporarily to integer vector, perform the mask, and cast
78// them back to float.
79//
80// To convert a float vector to an integer vector using trunction, or to
81// convert an integer vector to a float vector, use __builtin_convertvector().
82
83INLINE f32x4_t min(f32x4_t a, f32x4_t b) {
84  i32x4_t m = a < b;
85  return (f32x4_t)(((i32x4_t)a & m) | ((i32x4_t)b & ~m));
86}
87
88INLINE f32x4_t max(f32x4_t a, f32x4_t b) {
89  i32x4_t m = a > b;
90  return (f32x4_t)(((i32x4_t)a & m) | ((i32x4_t)b & ~m));
91}
92
93INLINE float dot3(f32x4_t a, f32x4_t b) {
94  f32x4_t c = a * b;
95  return c[0] + c[1] + c[2];
96}
97
98INLINE f32x4_t broadcast(float x) {
99  f32x4_t r = {x, x, x, x};
100  return r;
101}
102
103// SIMD RGBA helper functions, used for extracting color from RGBA source image.
104INLINE f32x4_t ExtractRGBA(uint32_t c) {
105  const f32x4_t kOneOver255 = broadcast(1.0f / 255.0f);
106  const i32x4_t kZero = {0, 0, 0, 0};
107  i32x4_t v = {c, c, c, c};
108  // zero extend packed color into 32x4 integer vector
109  v = (i32x4_t)__builtin_shufflevector((u8x16_t)v, (u8x16_t)kZero,
110      0, 16, 16, 16, 1, 16, 16, 16, 2, 16, 16, 16, 3, 16, 16, 16);
111  // convert color values to float, range 0..1
112  f32x4_t f = __builtin_convertvector(v, f32x4_t) * kOneOver255;
113  return f;
114}
115
116// SIMD BGRA helper function, for constructing a pixel for a BGRA buffer.
117INLINE uint32_t PackBGRA(f32x4_t f) {
118  const f32x4_t kZero = broadcast(0.0f);
119  const f32x4_t kHalf = broadcast(0.5f);
120  const f32x4_t k255 = broadcast(255.0f);
121  f = max(f, kZero);
122  // Add 0.5 to perform rounding instead of truncation.
123  f = f * k255 + kHalf;
124  f = min(f, k255);
125  i32x4_t i = __builtin_convertvector(f, i32x4_t);
126  u32x4_t p = (u32x4_t)__builtin_shufflevector((u8x16_t)i, (u8x16_t)i,
127      8, 4, 0, 12, 8, 4, 0, 12, 8, 4, 0, 12, 8, 4, 0, 12);
128  return p[0];
129}
130
131// BGRA helper function, for constructing a pixel for a BGRA buffer.
132INLINE uint32_t MakeBGRA(uint32_t b, uint32_t g, uint32_t r, uint32_t a) {
133  return (((a) << 24) | ((r) << 16) | ((g) << 8) | (b));
134}
135
136// simple container for earth texture
137struct Texture {
138  int width, height;
139  uint32_t* pixels;
140  Texture(int w, int h) : width(w), height(h) {
141    pixels = new uint32_t[w * h];
142    memset(pixels, 0, sizeof(uint32_t) * w * h);
143  }
144  explicit Texture(int w, int h, uint32_t* p) : width(w), height(h) {
145    pixels = new uint32_t[w * h];
146    memcpy(pixels, p, sizeof(uint32_t) * w * h);
147  }
148  ~Texture() { delete[] pixels; }
149
150  DISALLOW_COPY_AND_ASSIGN(Texture);
151};
152
153
154
155struct ArcCosine {
156  // slightly larger table so we can interpolate beyond table size
157  float table[kArcCosineTableSize + 2];
158  float TableLerp(float x);
159  ArcCosine();
160};
161
162ArcCosine::ArcCosine() {
163  // build a slightly larger table to allow for numeric imprecision
164  for (int i = 0; i < (kArcCosineTableSize + 2); ++i) {
165    float f = static_cast<float>(i) / kArcCosineTableSize;
166    f = f * 2.0f - 1.0f;
167    table[i] = acos(f);
168  }
169}
170
171// looks up acos(f) using a table and lerping between entries
172// (it is expected that input f is between -1 and 1)
173INLINE float ArcCosine::TableLerp(float f) {
174  float x = (f + 1.0f) * 0.5f;
175  x = x * kArcCosineTableSize;
176  int ix = static_cast<int>(x);
177  float fx = static_cast<float>(ix);
178  float dx = x - fx;
179  float af = table[ix];
180  float af2 = table[ix + 1];
181  return af + (af2 - af) * dx;
182}
183
184// Helper functions for quick but approximate sqrt.
185union Convert {
186  float f;
187  int i;
188  Convert(int x) { i = x; }
189  Convert(float x) { f = x; }
190  int AsInt() { return i; }
191  float AsFloat() { return f; }
192};
193
194INLINE const int AsInteger(const float f) {
195  Convert u(f);
196  return u.AsInt();
197}
198
199INLINE const float AsFloat(const int i) {
200  Convert u(i);
201  return u.AsFloat();
202}
203
204const long int kOneAsInteger = AsInteger(1.0f);
205
206INLINE float inline_quick_sqrt(float x) {
207  int i;
208  i = (AsInteger(x) >> 1) + (kOneAsInteger >> 1);
209  return AsFloat(i);
210}
211
212INLINE float inline_sqrt(float x) {
213  float y;
214  y = inline_quick_sqrt(x);
215  y = (y * y + x) / (2.0f * y);
216  y = (y * y + x) / (2.0f * y);
217  return y;
218}
219
220}  // namespace
221
222
223// The main object that runs the Earth demo.
224class Planet {
225 public:
226  Planet();
227  virtual ~Planet();
228  // Runs a tick of the simulations, update 2D output.
229  void Update();
230  // Handle event from user, or message from JS.
231  void HandleEvent(PSEvent* ps_event);
232
233 private:
234  // Methods prefixed with 'w' are run on worker threads.
235  uint32_t* wGetAddr(int x, int y);
236  void wRenderPixelSpan(int x0, int x1, int y);
237  void wMakeRect(int r, int *x, int *y, int *w, int *h);
238  void wRenderRect(int x0, int y0, int x1, int y1);
239  void wRenderRegion(int region);
240  static void wRenderRegionEntry(int region, void *thiz);
241
242  // These methods are only called by the main thread.
243  void CacheCalcs();
244  void SetPlanetXYZR(float x, float y, float z, float r);
245  void SetPlanetPole(float x, float y, float z);
246  void SetPlanetEquator(float x, float y, float z);
247  void SetPlanetSpin(float x, float y);
248  void SetEyeXYZ(float x, float y, float z);
249  void SetLightXYZ(float x, float y, float z);
250  void SetAmbientRGB(float r, float g, float b);
251  void SetDiffuseRGB(float r, float g, float b);
252  void SetZoom(float zoom);
253  void SetLight(float zoom);
254  void SetTexture(const std::string& name, int width, int height,
255      uint32_t* pixels);
256  void SpinPlanet(pp::Point new_point, pp::Point last_point);
257
258  void Reset();
259  void RequestTextures();
260  void UpdateSim();
261  void Render();
262  void Draw();
263  void StartBenchmark();
264  void EndBenchmark();
265  // Post a small key-value message to update JS.
266  void PostUpdateMessage(const char* message_name, double value);
267
268  // User Interface settings.  These settings are controlled via html
269  // controls or via user input.
270  float ui_light_;
271  float ui_zoom_;
272  float ui_spin_x_;
273  float ui_spin_y_;
274  pp::Point ui_last_point_;
275
276  // Various settings for position & orientation of planet.  Do not change
277  // these variables, instead use SetPlanet*() functions.
278  float planet_radius_;
279  float planet_spin_x_;
280  float planet_spin_y_;
281  float planet_x_, planet_y_, planet_z_;
282  float planet_pole_x_, planet_pole_y_, planet_pole_z_;
283  float planet_equator_x_, planet_equator_y_, planet_equator_z_;
284
285  // Observer's eye.  Do not change these variables, instead use SetEyeXYZ().
286  float eye_x_, eye_y_, eye_z_;
287
288  // Light position, ambient and diffuse settings.  Do not change these
289  // variables, instead use SetLightXYZ(), SetAmbientRGB() and SetDiffuseRGB().
290  float light_x_, light_y_, light_z_;
291  float diffuse_r_, diffuse_g_, diffuse_b_;
292  float ambient_r_, ambient_g_, ambient_b_;
293
294  // Cached calculations.  Do not change these variables - they are updated by
295  // CacheCalcs() function.
296  float planet_xyz_;
297  float planet_pole_x_equator_x_;
298  float planet_pole_x_equator_y_;
299  float planet_pole_x_equator_z_;
300  float planet_radius2_;
301  float planet_one_over_radius_;
302  float eye_xyz_;
303
304  // Source texture (earth map).
305  Texture* base_tex_;
306  Texture* night_tex_;
307  int width_for_tex_;
308  int height_for_tex_;
309
310  // Quick ArcCos helper.
311  ArcCosine acos_;
312
313  // Misc.
314  PSContext2D_t* ps_context_;
315  int num_threads_;
316  ThreadPool* workers_;
317  bool benchmarking_;
318  int benchmark_frame_counter_;
319  double benchmark_start_time_;
320  double benchmark_end_time_;
321};
322
323
324void Planet::RequestTextures() {
325  // Request a set of images from JS.  After images are loaded by JS, a
326  // message from JS -> NaCl will arrive containing the pixel data.  See
327  // HandleMessage() method in this file.
328  pp::VarDictionary message;
329  message.Set("message", "request_textures");
330  pp::VarArray names;
331  names.Set(0, "earth.jpg");
332  names.Set(1, "earthnight.jpg");
333  message.Set("names", names);
334  PSInterfaceMessaging()->PostMessage(PSGetInstanceId(), message.pp_var());
335}
336
337void Planet::Reset() {
338  // Reset has to first fill in all variables with valid floats, so
339  // CacheCalcs() doesn't potentially propagate NaNs when calling Set*()
340  // functions further below.
341  planet_radius_ = 1.0f;
342  planet_spin_x_ = 0.0f;
343  planet_spin_y_ = 0.0f;
344  planet_x_ = 0.0f;
345  planet_y_ = 0.0f;
346  planet_z_ = 0.0f;
347  planet_pole_x_ = 0.0f;
348  planet_pole_y_ = 0.0f;
349  planet_pole_z_ = 0.0f;
350  planet_equator_x_ = 0.0f;
351  planet_equator_y_ = 0.0f;
352  planet_equator_z_ = 0.0f;
353  eye_x_ = 0.0f;
354  eye_y_ = 0.0f;
355  eye_z_ = 0.0f;
356  light_x_ = 0.0f;
357  light_y_ = 0.0f;
358  light_z_ = 0.0f;
359  diffuse_r_ = 0.0f;
360  diffuse_g_ = 0.0f;
361  diffuse_b_ = 0.0f;
362  ambient_r_ = 0.0f;
363  ambient_g_ = 0.0f;
364  ambient_b_ = 0.0f;
365  planet_xyz_ = 0.0f;
366  planet_pole_x_equator_x_ = 0.0f;
367  planet_pole_x_equator_y_ = 0.0f;
368  planet_pole_x_equator_z_ = 0.0f;
369  planet_radius2_ = 0.0f;
370  planet_one_over_radius_ = 0.0f;
371  eye_xyz_ = 0.0f;
372  ui_zoom_ = 14.0f;
373  ui_light_ = 1.0f;
374  ui_spin_x_ = 0.01f;
375  ui_spin_y_ = 0.0f;
376  ui_last_point_ = pp::Point(0, 0);
377
378  // Set up reasonable default values.
379  SetPlanetXYZR(0.0f, 0.0f, 48.0f, 4.0f);
380  SetEyeXYZ(0.0f, 0.0f, -ui_zoom_);
381  SetLightXYZ(-60.0f, -30.0f, 0.0f);
382  SetAmbientRGB(0.05f, 0.05f, 0.05f);
383  SetDiffuseRGB(0.8f, 0.8f, 0.8f);
384  SetPlanetPole(0.0f, 1.0f, 0.0f);
385  SetPlanetEquator(1.0f, 0.0f, 0.0f);
386  SetPlanetSpin(kPI / 2.0f, kPI / 2.0f);
387  SetZoom(ui_zoom_);
388  SetLight(ui_light_);
389
390  // Send UI values to JS to reset html sliders.
391  PostUpdateMessage("set_zoom", ui_zoom_);
392  PostUpdateMessage("set_light", ui_light_);
393}
394
395
396Planet::Planet() : base_tex_(NULL), night_tex_(NULL), num_threads_(0),
397    benchmarking_(false), benchmark_frame_counter_(0) {
398
399  Reset();
400  RequestTextures();
401  // By default, render from the dispatch thread.
402  workers_ = new ThreadPool(num_threads_);
403  PSEventSetFilter(PSE_ALL);
404  ps_context_ = PSContext2DAllocate(PP_IMAGEDATAFORMAT_BGRA_PREMUL);
405}
406
407Planet::~Planet() {
408  delete workers_;
409  PSContext2DFree(ps_context_);
410}
411
412// Given a region r, derive a rectangle.
413// This rectangle shouldn't overlap with work being done by other workers.
414// If multithreading, this function is only called by the worker threads.
415void Planet::wMakeRect(int r, int *x, int *y, int *w, int *h) {
416  *x = 0;
417  *w = ps_context_->width;
418  *y = r;
419  *h = 1;
420}
421
422
423inline uint32_t* Planet::wGetAddr(int x, int y) {
424  return ps_context_->data + x + y * ps_context_->stride / sizeof(uint32_t);
425}
426
427// This is the inner loop of the ray tracer.  Given a pixel span (x0, x1) on
428// scanline y, shoot rays into the scene and render what they hit.  Use
429// scanline coherence to do a few optimizations.
430// This version uses portable SIMD 4 element single precision floating point
431// vectors to perform many of the calculations, and builds only on PNaCl.
432void Planet::wRenderPixelSpan(int x0, int x1, int y) {
433  if (!base_tex_ || !night_tex_)
434    return;
435  const uint32_t kColorBlack = MakeBGRA(0, 0, 0, 0xFF);
436  const uint32_t kSolidAlpha = MakeBGRA(0, 0, 0, 0xFF);
437  const f32x4_t kOne = {1.0f, 1.0f, 1.0f, 1.0f};
438  const f32x4_t diffuse = {diffuse_r_, diffuse_g_, diffuse_b_, 0.0f};
439  const f32x4_t ambient = {ambient_r_, ambient_g_, ambient_b_, 0.0f};
440  const f32x4_t light_pos = {light_x_, light_y_, light_z_, 1.0f};
441  const f32x4_t planet_pos = {planet_x_, planet_y_, planet_z_, 1.0f};
442  const f32x4_t planet_one_over_radius = broadcast(planet_one_over_radius_);
443  const f32x4_t planet_equator = {
444      planet_equator_x_, planet_equator_y_, planet_equator_z_, 0.0f};
445  const f32x4_t planet_pole = {
446      planet_pole_x_, planet_pole_y_, planet_pole_z_, 1.0f};
447  const f32x4_t planet_pole_x_equator = {
448      planet_pole_x_equator_x_, planet_pole_x_equator_y_,
449      planet_pole_x_equator_z_, 0.0f};
450
451  float width = ps_context_->width;
452  float height = ps_context_->height;
453  float min_dim = width < height ? width : height;
454  float offset_x = width < height ? 0 : (width - min_dim) * 0.5f;
455  float offset_y = width < height ? (height - min_dim) * 0.5f : 0;
456  float y0 = eye_y_;
457  float z0 = eye_z_;
458  float y1 = (static_cast<float>(y - offset_y) / min_dim) * 2.0f - 1.0f;
459  float z1 = 0.0f;
460  float dy = (y1 - y0);
461  float dz = (z1 - z0);
462  float dy_dy_dz_dz = dy * dy + dz * dz;
463  float two_dy_y0_y_two_dz_z0_z = 2.0f * dy * (y0 - planet_y_) +
464                                  2.0f * dz * (z0 - planet_z_);
465  float planet_xyz_eye_xyz = planet_xyz_ + eye_xyz_;
466  float y_y0_z_z0 = planet_y_ * y0 + planet_z_ * z0;
467  float oowidth = 1.0f / min_dim;
468  uint32_t* pixels = this->wGetAddr(x0, y);
469  for (int x = x0; x <= x1; ++x) {
470    // scan normalized screen -1..1
471    float x1 = (static_cast<float>(x - offset_x) * oowidth) * 2.0f - 1.0f;
472    // eye
473    float x0 = eye_x_;
474    // delta from screen to eye
475    float dx = (x1 - x0);
476    // build a, b, c
477    float a = dx * dx + dy_dy_dz_dz;
478    float b = 2.0f * dx * (x0 - planet_x_) + two_dy_y0_y_two_dz_z0_z;
479    float c = planet_xyz_eye_xyz +
480              -2.0f * (planet_x_ * x0 + y_y0_z_z0) - (planet_radius2_);
481    // calculate discriminant
482    float disc = b * b - 4.0f * a * c;
483
484    // Did ray hit the sphere?
485    if (disc < 0.0f) {
486      *pixels = kColorBlack;
487      ++pixels;
488      continue;
489    }
490
491    f32x4_t delta = {dx, dy, dz, 1.0f};
492    f32x4_t base = {x0, y0, z0, 1.0f};
493
494    // Calc parametric t value.
495    float t = (-b - inline_sqrt(disc)) / (2.0f * a);
496
497    f32x4_t pos = base + broadcast(t) * delta;
498    f32x4_t normal = (pos - planet_pos) * planet_one_over_radius;
499
500    // Misc raytrace calculations.
501    f32x4_t L = light_pos - pos;
502    float Lq = 1.0f / inline_quick_sqrt(dot3(L, L));
503    L = L * broadcast(Lq);
504    float d = dot3(L, normal);
505    f32x4_t p = diffuse * broadcast(d) + ambient;
506    float ds = -dot3(normal, planet_pole);
507    float ang = acos_.TableLerp(ds);
508    float v = ang * kOneOverPI;
509    float dp = dot3(planet_equator, normal);
510    float w = dp / sinf(ang);
511    if (w > 1.0f) w = 1.0f;
512    if (w < -1.0f) w = -1.0f;
513    float th = acos_.TableLerp(w) * kOneOver2PI;
514    float dps = dot3(planet_pole_x_equator, normal);
515    float u;
516    if (dps < 0.0f)
517      u = th;
518    else
519      u = 1.0f - th;
520
521    // Look up daylight texel.
522    int tx = static_cast<int>(u * base_tex_->width);
523    int ty = static_cast<int>(v * base_tex_->height);
524    int offset = tx + ty * base_tex_->width;
525    uint32_t base_texel = base_tex_->pixels[offset];
526    f32x4_t dc = ExtractRGBA(base_texel);
527
528    // Look up night texel.
529    int nix = static_cast<int>(u * night_tex_->width);
530    int niy = static_cast<int>(v * night_tex_->height);
531    int noffset = nix + niy * night_tex_->width;
532    uint32_t night_texel = night_tex_->pixels[noffset];
533    f32x4_t nc = ExtractRGBA(night_texel);
534
535    // Blend between daylight (dc) and nighttime (nc) color.
536    f32x4_t pc = min(p, kOne);
537    f32x4_t fc = dc * p + nc * (kOne - pc);
538    uint32_t color = PackBGRA(fc);
539
540    *pixels = color | kSolidAlpha;
541    ++pixels;
542  }
543}
544
545// Renders a rectangular area of the screen, scan line at a time
546void Planet::wRenderRect(int x, int y, int w, int h) {
547  for (int j = y; j < (y + h); ++j) {
548    this->wRenderPixelSpan(x, x + w - 1, j);
549  }
550}
551
552// If multithreading, this function is only called by the worker threads.
553void Planet::wRenderRegion(int region) {
554  // convert region # into x0, y0, x1, y1 rectangle
555  int x, y, w, h;
556  wMakeRect(region, &x, &y, &w, &h);
557  // render this rectangle
558  wRenderRect(x, y, w, h);
559}
560
561// Entry point for worker thread.  Can't pass a member function around, so we
562// have to do this little round-about.
563void Planet::wRenderRegionEntry(int region, void* thiz) {
564  static_cast<Planet*>(thiz)->wRenderRegion(region);
565}
566
567// Renders the planet, dispatching the work to multiple threads.
568void Planet::Render() {
569  workers_->Dispatch(ps_context_->height, wRenderRegionEntry, this);
570}
571
572// Pre-calculations to make inner loops faster.
573void Planet::CacheCalcs() {
574  planet_xyz_ = planet_x_ * planet_x_ +
575                planet_y_ * planet_y_ +
576                planet_z_ * planet_z_;
577  planet_radius2_ = planet_radius_ * planet_radius_;
578  planet_one_over_radius_ = 1.0f / planet_radius_;
579  eye_xyz_ = eye_x_ * eye_x_ + eye_y_ * eye_y_ + eye_z_ * eye_z_;
580  // spin vector from center->equator
581  planet_equator_x_ = cos(planet_spin_x_);
582  planet_equator_y_ = 0.0f;
583  planet_equator_z_ = sin(planet_spin_x_);
584
585  // cache cross product of pole & equator
586  planet_pole_x_equator_x_ = planet_pole_y_ * planet_equator_z_ -
587                             planet_pole_z_ * planet_equator_y_;
588  planet_pole_x_equator_y_ = planet_pole_z_ * planet_equator_x_ -
589                             planet_pole_x_ * planet_equator_z_;
590  planet_pole_x_equator_z_ = planet_pole_x_ * planet_equator_y_ -
591                             planet_pole_y_ * planet_equator_x_;
592}
593
594void Planet::SetPlanetXYZR(float x, float y, float z, float r) {
595  planet_x_ = x;
596  planet_y_ = y;
597  planet_z_ = z;
598  planet_radius_ = r;
599  CacheCalcs();
600}
601
602void Planet::SetEyeXYZ(float x, float y, float z) {
603  eye_x_ = x;
604  eye_y_ = y;
605  eye_z_ = z;
606  CacheCalcs();
607}
608
609void Planet::SetLightXYZ(float x, float y, float z) {
610  light_x_ = x;
611  light_y_ = y;
612  light_z_ = z;
613  CacheCalcs();
614}
615
616void Planet::SetAmbientRGB(float r, float g, float b) {
617  ambient_r_ = r;
618  ambient_g_ = g;
619  ambient_b_ = b;
620  CacheCalcs();
621}
622
623void Planet::SetDiffuseRGB(float r, float g, float b) {
624  diffuse_r_ = r;
625  diffuse_g_ = g;
626  diffuse_b_ = b;
627  CacheCalcs();
628}
629
630void Planet::SetPlanetPole(float x, float y, float z) {
631  planet_pole_x_ = x;
632  planet_pole_y_ = y;
633  planet_pole_z_ = z;
634  CacheCalcs();
635}
636
637void Planet::SetPlanetEquator(float x, float y, float z) {
638  // This is really over-ridden by spin at the momenent.
639  planet_equator_x_ = x;
640  planet_equator_y_ = y;
641  planet_equator_z_ = z;
642  CacheCalcs();
643}
644
645void Planet::SetPlanetSpin(float x, float y) {
646  planet_spin_x_ = x;
647  planet_spin_y_ = y;
648  CacheCalcs();
649}
650
651// Run a simple sim to spin the planet.  Update loop is run once per frame.
652// Called from the main thread only and only when the worker threads are idle.
653void Planet::UpdateSim() {
654  float x = planet_spin_x_ + ui_spin_x_;
655  float y = planet_spin_y_ + ui_spin_y_;
656  // keep in nice range
657  if (x > (kPI * 2.0f))
658    x = x - kPI * 2.0f;
659  else if (x < (-kPI * 2.0f))
660    x = x + kPI * 2.0f;
661  if (y > (kPI * 2.0f))
662    y = y - kPI * 2.0f;
663  else if (y < (-kPI * 2.0f))
664    y = y + kPI * 2.0f;
665  SetPlanetSpin(x, y);
666}
667
668void Planet::StartBenchmark() {
669  // For more consistent benchmark numbers, reset to default state.
670  Reset();
671  printf("Benchmark started...\n");
672  benchmark_frame_counter_ = kFramesToBenchmark;
673  benchmarking_ = true;
674  benchmark_start_time_ = getseconds();
675}
676
677void Planet::EndBenchmark() {
678  benchmark_end_time_ = getseconds();
679  printf("Benchmark ended... time: %2.5f\n",
680      benchmark_end_time_ - benchmark_start_time_);
681  benchmarking_ = false;
682  benchmark_frame_counter_ = 0;
683  double total_time = benchmark_end_time_ - benchmark_start_time_;
684  // Send benchmark result to JS.
685  PostUpdateMessage("benchmark_result", total_time);
686}
687
688void Planet::SetZoom(float zoom) {
689  ui_zoom_ = std::min(kZoomMax, std::max(kZoomMin, zoom));
690  SetEyeXYZ(0.0f, 0.0f, -ui_zoom_);
691}
692
693void Planet::SetLight(float light) {
694  ui_light_ = std::min(kLightMax, std::max(kLightMin, light));
695  SetDiffuseRGB(0.8f * ui_light_, 0.8f * ui_light_, 0.8f * ui_light_);
696  SetAmbientRGB(0.4f * ui_light_, 0.4f * ui_light_, 0.4f * ui_light_);
697}
698
699void Planet::SetTexture(const std::string& name, int width, int height,
700                        uint32_t* pixels) {
701  if (pixels) {
702    if (name == "earth.jpg") {
703      delete base_tex_;
704      base_tex_ = new Texture(width, height, pixels);
705    } else if (name == "earthnight.jpg") {
706      delete night_tex_;
707      night_tex_ = new Texture(width, height, pixels);
708    }
709  }
710}
711
712void Planet::SpinPlanet(pp::Point new_point, pp::Point last_point) {
713  float delta_x = static_cast<float>(new_point.x() - last_point.x());
714  float delta_y = static_cast<float>(new_point.y() - last_point.y());
715  float spin_x = std::min(10.0f, std::max(-10.0f, delta_x * 0.5f));
716  float spin_y = std::min(10.0f, std::max(-10.0f, delta_y * 0.5f));
717  ui_spin_x_ = spin_x / 100.0f;
718  ui_spin_y_ = spin_y / 100.0f;
719  ui_last_point_ = new_point;
720}
721
722// Handle input events from the user and messages from JS.
723void Planet::HandleEvent(PSEvent* ps_event) {
724  // Give the 2D context a chance to process the event.
725  if (0 != PSContext2DHandleEvent(ps_context_, ps_event))
726    return;
727  if (ps_event->type == PSE_INSTANCE_HANDLEINPUT) {
728    // Convert Pepper Simple event to a PPAPI C++ event
729    pp::InputEvent event(ps_event->as_resource);
730    switch (event.GetType()) {
731      case PP_INPUTEVENT_TYPE_KEYDOWN: {
732        pp::KeyboardInputEvent key(event);
733        uint32_t key_code = key.GetKeyCode();
734        if (key_code == 84)  // 't' key
735          if (!benchmarking_)
736            StartBenchmark();
737        break;
738      }
739      case PP_INPUTEVENT_TYPE_MOUSEDOWN:
740      case PP_INPUTEVENT_TYPE_MOUSEMOVE: {
741        pp::MouseInputEvent mouse = pp::MouseInputEvent(event);
742        if (mouse.GetModifiers() & PP_INPUTEVENT_MODIFIER_LEFTBUTTONDOWN) {
743          if (event.GetType() == PP_INPUTEVENT_TYPE_MOUSEDOWN)
744            SpinPlanet(mouse.GetPosition(), mouse.GetPosition());
745          else
746            SpinPlanet(mouse.GetPosition(), ui_last_point_);
747        }
748        break;
749      }
750      case PP_INPUTEVENT_TYPE_WHEEL: {
751        pp::WheelInputEvent wheel = pp::WheelInputEvent(event);
752        PP_FloatPoint ticks = wheel.GetTicks();
753        SetZoom(ui_zoom_ + (ticks.x + ticks.y) * kWheelSpeed);
754        // Update html slider by sending update message to JS.
755        PostUpdateMessage("set_zoom", ui_zoom_);
756        break;
757      }
758      case PP_INPUTEVENT_TYPE_TOUCHSTART:
759      case PP_INPUTEVENT_TYPE_TOUCHMOVE: {
760        pp::TouchInputEvent touches = pp::TouchInputEvent(event);
761        uint32_t count = touches.GetTouchCount(PP_TOUCHLIST_TYPE_TOUCHES);
762        if (count > 0) {
763          // Use first touch point to spin planet.
764          pp::TouchPoint touch =
765              touches.GetTouchByIndex(PP_TOUCHLIST_TYPE_TOUCHES, 0);
766          pp::Point screen_point(touch.position().x(),
767                                 touch.position().y());
768          if (event.GetType() == PP_INPUTEVENT_TYPE_TOUCHSTART)
769            SpinPlanet(screen_point, screen_point);
770          else
771            SpinPlanet(screen_point, ui_last_point_);
772        }
773        break;
774      }
775      default:
776        break;
777    }
778  } else if (ps_event->type == PSE_INSTANCE_HANDLEMESSAGE) {
779    // Convert Pepper Simple message to PPAPI C++ vars
780    pp::Var var(ps_event->as_var);
781    if (var.is_dictionary()) {
782      pp::VarDictionary dictionary(var);
783      std::string message = dictionary.Get("message").AsString();
784      if (message == "run benchmark" && !benchmarking_) {
785        StartBenchmark();
786      } else if (message == "set_light") {
787        SetLight(static_cast<float>(dictionary.Get("value").AsDouble()));
788      } else if (message == "set_zoom") {
789        SetZoom(static_cast<float>(dictionary.Get("value").AsDouble()));
790      } else if (message == "set_threads") {
791        int threads = dictionary.Get("value").AsInt();
792        delete workers_;
793        workers_ = new ThreadPool(threads);
794      } else if (message == "texture") {
795        std::string name = dictionary.Get("name").AsString();
796        int width = dictionary.Get("width").AsInt();
797        int height = dictionary.Get("height").AsInt();
798        pp::VarArrayBuffer array_buffer(dictionary.Get("data"));
799        if (!name.empty() && !array_buffer.is_null()) {
800          if (width > 0 && height > 0) {
801            uint32_t* pixels = static_cast<uint32_t*>(array_buffer.Map());
802            SetTexture(name, width, height, pixels);
803            array_buffer.Unmap();
804          }
805        }
806      }
807    } else {
808      printf("Handle message unknown type: %s\n", var.DebugString().c_str());
809    }
810  }
811}
812
813// PostUpdateMessage() helper function for sending small messages to JS.
814void Planet::PostUpdateMessage(const char* message_name, double value) {
815  pp::VarDictionary message;
816  message.Set("message", message_name);
817  message.Set("value", value);
818  PSInterfaceMessaging()->PostMessage(PSGetInstanceId(), message.pp_var());
819}
820
821void Planet::Update() {
822  // When benchmarking is running, don't update display via
823  // PSContext2DSwapBuffer() - vsync is enabled by default, and will throttle
824  // the benchmark results.
825  PSContext2DGetBuffer(ps_context_);
826  if (NULL == ps_context_->data)
827    return;
828
829  do {
830    UpdateSim();
831    Render();
832    if (!benchmarking_) break;
833    --benchmark_frame_counter_;
834  } while (benchmark_frame_counter_ > 0);
835  if (benchmarking_)
836    EndBenchmark();
837
838  PSContext2DSwapBuffer(ps_context_);
839}
840
841
842// Starting point for the module.  We do not use main since it would
843// collide with main in libppapi_cpp.
844int example_main(int argc, char* argv[]) {
845  Planet earth;
846  while (true) {
847    PSEvent* ps_event;
848    // Consume all available events
849    while ((ps_event = PSEventTryAcquire()) != NULL) {
850      earth.HandleEvent(ps_event);
851      PSEventRelease(ps_event);
852    }
853    // Do simulation, render and present.
854    earth.Update();
855  }
856
857  return 0;
858}
859
860// Register the function to call once the Instance Object is initialized.
861// see: pappi_simple/ps_main.h
862PPAPI_SIMPLE_REGISTER_MAIN(example_main);
863