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