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