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