diff --git a/Demos/DX11ClothDemo/premake4.lua b/Demos/DX11ClothDemo/premake4.lua new file mode 100644 index 000000000..bb21f6a7a --- /dev/null +++ b/Demos/DX11ClothDemo/premake4.lua @@ -0,0 +1,41 @@ + + hasDX11 = findDirectX11() + + if (hasDX11) then + + project "App_DX11ClothDemo" + + initDirectX11() + + language "C++" + + defines { "UNICODE","_UNICODE"} + + kind "WindowedApp" + flags { "WinMain" } + + targetdir "../.." + includedirs { + "../../src", + "DXUT/Core", + "DXUT/Optional" + } + + links { + "LinearMath","BulletCollision","BulletDynamics", "BulletSoftBody", "BulletSoftBodyDX11Solvers" + } + files { + "DXUT/Core/DXUT.cpp", + "DXUT/Optional/DXUTcamera.cpp", + "DXUT/Core/DXUTDevice11.cpp", + "DXUT/Core/DXUTDevice9.cpp", + "DXUT/Optional/DXUTgui.cpp", + "DXUT/Core/DXUTmisc.cpp", + "DXUT/Optional/DXUTres.cpp", + "DXUT/Optional/DXUTsettingsdlg.cpp", + "DXUT/Optional/SDKmesh.cpp", + "DXUT/Optional/SDKmisc.cpp", + "cloth_renderer.cpp" + } + + end \ No newline at end of file diff --git a/Demos/NativeClient/bin_html/bind.js b/Demos/NativeClient/bin_html/bind.js new file mode 100644 index 000000000..92fbbd218 --- /dev/null +++ b/Demos/NativeClient/bin_html/bind.js @@ -0,0 +1,22 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * @fileoverview This class implements an extension to Function object that + * lets you bind a scope for |this| to a function. + */ + +/** + * Bind a scope to a function. Used to bind an object to |this| for event + * handlers. + * @param {!Object} scope The scope in which the function executes. |scope| + * becomes |this| during function execution. + * @return {function} the bound version of the original function. + */ +Function.prototype.bind = function(scope) { + var boundContext = this; + return function() { + return boundContext.apply(scope, arguments); + } +} diff --git a/Demos/NativeClient/bin_html/dragger.js b/Demos/NativeClient/bin_html/dragger.js new file mode 100644 index 000000000..232d8b5fb --- /dev/null +++ b/Demos/NativeClient/bin_html/dragger.js @@ -0,0 +1,134 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * @fileoverview This class implements a mouse-drag event. It registers for + * mousedown events, and when it sees one, starts capturing mousemove events + * until it gets a mousup event. It manufactures three drag events: the + * DRAG_START, DRAG and DRAG_END. + */ + +// Requires bind + +/** + * Constructor for the Dragger. Register for mousedown events that happen on + * |opt_target|. If |opt_target| is null or undefined, then this object + * observes mousedown on the whole document. + * @param {?Element} opt_target The event target. Defaults to the whole + * document. + * @constructor + */ +tumbler.Dragger = function(opt_target) { + /** + * The event target. + * @type {Element} + * @private + */ + this.target_ = opt_target || document; + + /** + * The array of objects that get notified of drag events. Each object in + * this array get sent a handleStartDrag(), handleDrag() and handleEndDrag() + * message. + * @type {Array.} + * @private + */ + this.listeners_ = []; + + /** + * Flag to indicate whether the object is in a drag sequence or not. + * @type {boolean} + * @private + */ + this.isDragging_ = false; + + /** + * The function objects that get attached as event handlers. These are + * cached so that they can be removed on mouse up. + * @type {function} + * @private + */ + this.boundMouseMove_ = null; + this.boundMouseUp_ = null; + + this.target_.addEventListener('mousedown', + this.onMouseDown.bind(this), + false); +} + +/** + * The ids used for drag event types. + * @enum {string} + */ +tumbler.Dragger.DragEvents = { + DRAG_START: 'dragstart', // Start a drag sequence + DRAG: 'drag', // Mouse moved during a drag sequence. + DRAG_END: 'dragend' // End a drag sewquence. +}; + +/** + * Add a drag listener. Each listener should respond to thhree methods: + * handleStartDrag(), handleDrag() and handleEndDrag(). This method assumes + * that |listener| does not already exist in the array of listeners. + * @param {!Object} listener The object that will listen to drag events. + */ +tumbler.Dragger.prototype.addDragListener = function(listener) { + this.listeners_.push(listener); +} + +/** + * Handle a mousedown event: register for mousemove and mouseup, then tell + * the target that is has a DRAG_START event. + * @param {Event} event The mousedown event that triggered this method. + */ +tumbler.Dragger.prototype.onMouseDown = function(event) { + this.boundMouseMove_ = this.onMouseMove.bind(this); + this.boundMouseUp_ = this.onMouseUp.bind(this); + this.target_.addEventListener('mousemove', this.boundMouseMove_); + this.target_.addEventListener('mouseup', this.boundMouseUp_); + this.isDragging_ = true; + var dragStartEvent = { type: tumbler.Dragger.DragEvents.DRAG_START, + clientX: event.offsetX, + clientY: event.offsetY }; + var i; + for (i = 0; i < this.listeners_.length; ++i) { + this.listeners_[i].handleStartDrag(this.target_, dragStartEvent); + } +} + +/** + * Handle a mousemove event: tell the target that is has a DRAG event. + * @param {Event} event The mousemove event that triggered this method. + */ +tumbler.Dragger.prototype.onMouseMove = function(event) { + if (!this.isDragging_) + return; + var dragEvent = { type: tumbler.Dragger.DragEvents.DRAG, + clientX: event.offsetX, + clientY: event.offsetY}; + var i; + for (i = 0; i < this.listeners_.length; ++i) { + this.listeners_[i].handleDrag(this.target_, dragEvent); + } +} + +/** + * Handle a mouseup event: un-register for mousemove and mouseup, then tell + * the target that is has a DRAG_END event. + * @param {Event} event The mouseup event that triggered this method. + */ +tumbler.Dragger.prototype.onMouseUp = function(event) { + this.target_.removeEventListener('mouseup', this.boundMouseUp_, false); + this.target_.removeEventListener('mousemove', this.boundMouseMove_, false); + this.boundMouseUp_ = null; + this.boundMouseMove_ = null; + this.isDragging_ = false; + var dragEndEvent = { type: tumbler.Dragger.DragEvents.DRAG_END, + clientX: event.offsetX, + clientY: event.offsetY}; + var i; + for (i = 0; i < this.listeners_.length; ++i) { + this.listeners_[i].handleEndDrag(this.target_, dragEndEvent); + } +} diff --git a/Demos/NativeClient/bin_html/httpd.cmd b/Demos/NativeClient/bin_html/httpd.cmd new file mode 100644 index 000000000..5a31ae723 --- /dev/null +++ b/Demos/NativeClient/bin_html/httpd.cmd @@ -0,0 +1,9 @@ +@echo off +setlocal + +REM Relative path of CygWin +set CYGWIN=%~dp0%..\third_party\cygwin\bin + +PATH=%CYGWIN%;%PATH% + +python httpd.py diff --git a/Demos/NativeClient/bin_html/httpd.py b/Demos/NativeClient/bin_html/httpd.py new file mode 100644 index 000000000..65434a875 --- /dev/null +++ b/Demos/NativeClient/bin_html/httpd.py @@ -0,0 +1,114 @@ +#!/usr/bin/python +# +# Copyright (c) 2011, The Native Client Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +# + +"""A tiny web server. + +This is intended to be used for testing, and only run from within the examples +directory. +""" + +import BaseHTTPServer +import logging +import os +import SimpleHTTPServer +import SocketServer +import sys +import urlparse + +logging.getLogger().setLevel(logging.INFO) + +# Using 'localhost' means that we only accept connections +# via the loop back interface. +SERVER_PORT = 5103 +SERVER_HOST = '' + +# We only run from the examples directory (the one that contains scons-out), so +# that not too much is exposed via this HTTP server. Everything in the +# directory is served, so there should never be anything potentially sensitive +# in the serving directory, especially if the machine might be a +# multi-user machine and not all users are trusted. We only serve via +# the loopback interface. + +SAFE_DIR_COMPONENTS = ['bin_html'] +SAFE_DIR_SUFFIX = apply(os.path.join, SAFE_DIR_COMPONENTS) + +def SanityCheckDirectory(): + if os.getcwd().endswith(SAFE_DIR_SUFFIX): + return + logging.error('httpd.py should only be run from the %s', SAFE_DIR_SUFFIX) + logging.error('directory for testing purposes.') + logging.error('We are currently in %s', os.getcwd()) + sys.exit(1) + + +# An HTTP server that will quit when |is_running| is set to False. We also use +# SocketServer.ThreadingMixIn in order to handle requests asynchronously for +# faster responses. +class QuittableHTTPServer(SocketServer.ThreadingMixIn, + BaseHTTPServer.HTTPServer): + def serve_forever(self, timeout=0.5): + self.is_running = True + self.timeout = timeout + while self.is_running: + self.handle_request() + + def shutdown(self): + self.is_running = False + return 1 + + +# "Safely" split a string at |sep| into a [key, value] pair. If |sep| does not +# exist in |str|, then the entire |str| is the key and the value is set to an +# empty string. +def KeyValuePair(str, sep='='): + if sep in str: + return str.split(sep) + else: + return [str, ''] + + +# A small handler that looks for '?quit=1' query in the path and shuts itself +# down if it finds that parameter. +class QuittableHTTPHandler(SimpleHTTPServer.SimpleHTTPRequestHandler): + def do_GET(self): + (_, _, _, query, _) = urlparse.urlsplit(self.path) + url_params = dict([KeyValuePair(key_value) + for key_value in query.split('&')]) + if 'quit' in url_params and '1' in url_params['quit']: + self.send_response(200, 'OK') + self.send_header('Content-type', 'text/html') + self.send_header('Content-length', '0') + self.end_headers() + self.server.shutdown() + return + + SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self) + + +def Run(server_address, + server_class=QuittableHTTPServer, + handler_class=QuittableHTTPHandler): + httpd = server_class(server_address, handler_class) + logging.info("Starting local server on port %d", server_address[1]) + logging.info("To shut down send http://localhost:%d?quit=1", + server_address[1]) + try: + httpd.serve_forever() + except KeyboardInterrupt: + logging.info("Received keyboard interrupt.") + httpd.server_close() + + logging.info("Shutting down local server on port %d", server_address[1]) + + +if __name__ == '__main__': + SanityCheckDirectory() + if len(sys.argv) > 1: + Run((SERVER_HOST, int(sys.argv[1]))) + else: + Run((SERVER_HOST, SERVER_PORT)) + sys.exit(0) diff --git a/Demos/NativeClient/bin_html/index.html b/Demos/NativeClient/bin_html/index.html new file mode 100644 index 000000000..a3002da5e --- /dev/null +++ b/Demos/NativeClient/bin_html/index.html @@ -0,0 +1,33 @@ + + + + + Interactive Cube Example + + + + + + + + +

Interactive Cube Example

+

+ The Native Client module executed in this page draws a 3D cube + and allows you to rotate it using a virtual trackball method. +

+
+ + + diff --git a/Demos/NativeClient/bin_html/trackball.js b/Demos/NativeClient/bin_html/trackball.js new file mode 100644 index 000000000..88b9a62df --- /dev/null +++ b/Demos/NativeClient/bin_html/trackball.js @@ -0,0 +1,296 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * @fileoverview Implement a virtual trackball in the tumbler.Trackball + * class. This class maps 2D mouse events to 3D rotations by simulating a + * trackball that you roll by dragging the mouse. There are two principle + * methods in the class: startAtPointInFrame which you use to begin a trackball + * simulation and rollToPoint, which you use while dragging the mouse. The + * rollToPoint method returns a rotation expressed as a quaternion. + */ + + +// Requires tumbler.Application +// Requires tumbler.DragEvent +// Requires tumbler.Vector3 + +/** + * Constructor for the Trackball object. This class maps 2D mouse drag events + * into 3D rotations by simulating a trackball. The idea is to simulate + * clicking on the trackball, and then rolling it as you drag the mouse. + * The math behind the trackball is simple: start with a vector from the first + * mouse-click on the ball to the center of the 3D view. At the same time, set + * the radius of the ball to be the smaller dimension of the 3D view. As you + * drag the mouse around in the 3D view, a second vector is computed from the + * surface of the ball to the center. The axis of rotation is the cross + * product of these two vectors, and the angle of rotation is the angle between + * the two vectors. + * @constructor + */ +tumbler.Trackball = function() { + /** + * The square of the trackball's radius. The math never looks at the radius, + * but looks at the radius squared. + * @type {number} + * @private + */ + this.sqrRadius_ = 0; + + /** + * The 3D vector representing the point on the trackball where the mouse + * was clicked. Default is pointing stright through the center of the ball. + * @type {Object} + * @private + */ + this.rollStart_ = new tumbler.Vector3(0, 0, 1); + + /** + * The 2D center of the frame that encloses the trackball. + * @type {!Object} + * @private + */ + this.center_ = { x: 0, y: 0 }; + + /** + * Cached camera orientation. When a drag START event happens this is set to + * the current orientation in the calling view's plugin. The default is the + * identity quaternion. + * @type {Array.} + * @private + */ + this.cameraOrientation_ = [0, 0, 0, 1]; +}; + +/** + * Compute the dimensions of the virtual trackball to fit inside |frameSize|. + * The radius of the trackball is set to be 1/2 of the smaller of the two frame + * dimensions, the center point is at the midpoint of each side. + * @param {!goog.math.Size} frameSize 2D-point representing the size of the + * element that encloses the virtual trackball. + * @private + */ +tumbler.Trackball.prototype.initInFrame_ = function(frameSize) { + // Compute the radius of the virtual trackball. This is 1/2 of the smaller + // of the frame's width and height. + var halfFrameSize = 0.5 * Math.min(frameSize.width, frameSize.height); + // Cache the square of the trackball's radius. + this.sqrRadius_ = halfFrameSize * halfFrameSize; + // Figure the center of the view. + this.center_.x = frameSize.width * 0.5; + this.center_.y = frameSize.height * 0.5; +}; + +/** + * Method to convert (by translation) a 2D client point from a coordinate space + * with origin in the lower-left corner of the client view to a space with + * origin in the center of the client view. Use this method before mapping the + * 2D point to he 3D tackball point (see also the projectOnTrackball_() method). + * Call the startAtPointInFrame before calling this method so that the + * |center_| property is correctly initialized. + * @param {!Object} clientPoint map this point to the coordinate space with + * origin in thecenter of the client view. + * @return {Object} the converted point. + * @private + */ +tumbler.Trackball.prototype.convertClientPoint_ = function(clientPoint) { + var difference = { x: clientPoint.x - this.center_.x, + y: clientPoint.y - this.center_.y } + return difference; +}; + +/** + * Method to map a 2D point to a 3D point on the virtual trackball that was set + * up using the startAtPointInFrame method. If the point lies outside of the + * radius of the virtual trackball, then the z-coordinate of the 3D point + * is set to 0. + * @param {!Object.} point 2D-point in the coordinate space with origin + * in the center of the client view. + * @return {tumbler.Vector3} the 3D point on the virtual trackball. + * @private + */ +tumbler.Trackball.prototype.projectOnTrackball_ = function(point) { + var sqrRadius2D = point.x * point.x + point.y * point.y; + var zValue; + if (sqrRadius2D > this.sqrRadius_) { + // |point| lies outside the virtual trackball's sphere, so use a virtual + // z-value of 0. This is equivalent to clicking on the horizontal equator + // of the trackball. + zValue = 0; + } else { + // A sphere can be defined as: r^2 = x^2 + y^2 + z^2, so z = + // sqrt(r^2 - (x^2 + y^2)). + zValue = Math.sqrt(this.sqrRadius_ - sqrRadius2D); + } + var trackballPoint = new tumbler.Vector3(point.x, point.y, zValue); + return trackballPoint; +}; + +/** + * Method to start up the trackball. The trackball works by pretending that a + * ball encloses the 3D view. You roll this pretend ball with the mouse. For + * example, if you click on the center of the ball and move the mouse straight + * to the right, you roll the ball around its Y-axis. This produces a Y-axis + * rotation. You can click on the "edge" of the ball and roll it around + * in a circle to get a Z-axis rotation. + * @param {!Object.} startPoint 2D-point, usually the mouse-down + * point. + * @param {!Object.} frameSize 2D-point representing the size of + * the element that encloses the virtual trackball. + */ +tumbler.Trackball.prototype.startAtPointInFrame = + function(startPoint, frameSize) { + this.initInFrame_(frameSize); + // Compute the starting vector from the surface of the ball to its center. + this.rollStart_ = this.projectOnTrackball_( + this.convertClientPoint_(startPoint)); +}; + +/** + * Method to roll the virtual trackball; call this in response to a mouseDrag + * event. Takes |dragPoint| and projects it from 2D mouse coordinates onto the + * virtual track ball that was set up in startAtPointInFrame method. + * Returns a quaternion that represents the rotation from |rollStart_| to + * |rollEnd_|. + * @param {!Object.} dragPoint 2D-point representing the + * destination mouse point. + * @return {Array.} a quaternion that represents the rotation from + * the point wnere the mouse was clicked on the trackball to this point. + * The quaternion looks like this: [[v], cos(angle/2)], where [v] is the + * imaginary part of the quaternion and is computed as [x, y, z] * + * sin(angle/2). + */ +tumbler.Trackball.prototype.rollToPoint = function(dragPoint) { + var rollTo = this.convertClientPoint_(dragPoint); + if ((Math.abs(this.rollStart_.x - rollTo.x) < + tumbler.Trackball.DOUBLE_EPSILON) && + (Math.abs(this.rollStart_.y, rollTo.y) < + tumbler.Trackball.DOUBLE_EPSILON)) { + // Not enough change in the vectors to roll the ball, return the identity + // quaternion. + return [0, 0, 0, 1]; + } + + // Compute the ending vector from the surface of the ball to its center. + var rollEnd = this.projectOnTrackball_(rollTo); + + // Take the cross product of the two vectors. r = s X e + var rollVector = this.rollStart_.cross(rollEnd); + var invStartMag = 1.0 / this.rollStart_.magnitude(); + var invEndMag = 1.0 / rollEnd.magnitude(); + + // cos(a) = (s . e) / (||s|| ||e||) + var cosAng = this.rollStart_.dot(rollEnd) * invStartMag * invEndMag; + // sin(a) = ||(s X e)|| / (||s|| ||e||) + var sinAng = rollVector.magnitude() * invStartMag * invEndMag; + // Build a quaternion that represents the rotation about |rollVector|. + // Use atan2 for a better angle. If you use only cos or sin, you only get + // half the possible angles, and you can end up with rotations that flip + // around near the poles. + var rollHalfAngle = Math.atan2(sinAng, cosAng) * 0.5; + rollVector.normalize(); + // The quaternion looks like this: [[v], cos(angle/2)], where [v] is the + // imaginary part of the quaternion and is computed as [x, y, z] * + // sin(angle/2). + rollVector.scale(Math.sin(rollHalfAngle)); + var ballQuaternion = [rollVector.x, + rollVector.y, + rollVector.z, + Math.cos(rollHalfAngle)]; + return ballQuaternion; +}; + +/** + * Handle the drag START event: grab the current camera orientation from the + * sending view and set up the virtual trackball. + * @param {!tumbler.Application} view The view controller that called this + * method. + * @param {!tumbler.DragEvent} dragStartEvent The DRAG_START event that + * triggered this handler. + */ +tumbler.Trackball.prototype.handleStartDrag = + function(controller, dragStartEvent) { + // Cache the camera orientation. The orientations from the trackball as it + // rolls are concatenated to this orientation and pushed back into the + // plugin on the other side of the JavaScript bridge. + controller.setCameraOrientation(this.cameraOrientation_); + // Invert the y-coordinate for the trackball computations. + var frameSize = { width: controller.offsetWidth, + height: controller.offsetHeight }; + var flippedY = { x: dragStartEvent.clientX, + y: frameSize.height - dragStartEvent.clientY }; + this.startAtPointInFrame(flippedY, frameSize); +}; + +/** + * Handle the drag DRAG event: concatenate the current orientation to the + * cached orientation. Send this final value through to the GSPlugin via the + * setValueForKey() method. + * @param {!tumbler.Application} view The view controller that called this + * method. + * @param {!tumbler.DragEvent} dragEvent The DRAG event that triggered this + * handler. + */ +tumbler.Trackball.prototype.handleDrag = + function(controller, dragEvent) { + // Flip the y-coordinate so that the 2D origin is in the lower-left corner. + var frameSize = { width: controller.offsetWidth, + height: controller.offsetHeight }; + var flippedY = { x: dragEvent.clientX, + y: frameSize.height - dragEvent.clientY }; + controller.setCameraOrientation( + tumbler.multQuaternions(this.rollToPoint(flippedY), + this.cameraOrientation_)); +}; + +/** + * Handle the drag END event: get the final orientation and concatenate it to + * the cached orientation. + * @param {!tumbler.Application} view The view controller that called this + * method. + * @param {!tumbler.DragEvent} dragEndEvent The DRAG_END event that triggered + * this handler. + */ +tumbler.Trackball.prototype.handleEndDrag = + function(controller, dragEndEvent) { + // Flip the y-coordinate so that the 2D origin is in the lower-left corner. + var frameSize = { width: controller.offsetWidth, + height: controller.offsetHeight }; + var flippedY = { x: dragEndEvent.clientX, + y: frameSize.height - dragEndEvent.clientY }; + this.cameraOrientation_ = tumbler.multQuaternions(this.rollToPoint(flippedY), + this.cameraOrientation_); + controller.setCameraOrientation(this.cameraOrientation_); +}; + +/** + * A utility function to multiply two quaterions. Returns the product q0 * q1. + * This is effectively the same thing as concatenating the two rotations + * represented in each quaternion together. Note that quaternion multiplication + * is NOT commutative: q0 * q1 != q1 * q0. + * @param {!Array.} q0 A 4-element array representing the first + * quaternion. + * @param {!Array.} q1 A 4-element array representing the second + * quaternion. + * @return {Array.} A 4-element array representing the product q0 * q1. + */ +tumbler.multQuaternions = function(q0, q1) { + // Return q0 * q1 (note the order). + var qMult = [ + q0[3] * q1[0] + q0[0] * q1[3] + q0[1] * q1[2] - q0[2] * q1[1], + q0[3] * q1[1] - q0[0] * q1[2] + q0[1] * q1[3] + q0[2] * q1[0], + q0[3] * q1[2] + q0[0] * q1[1] - q0[1] * q1[0] + q0[2] * q1[3], + q0[3] * q1[3] - q0[0] * q1[0] - q0[1] * q1[1] - q0[2] * q1[2] + ]; + return qMult; +}; + +/** + * Real numbers that are less than this distance apart are considered + * equivalent. + * TODO(dspringer): It seems as though there should be a const like this + * in Closure somewhere (goog.math?). + * @type {number} + */ +tumbler.Trackball.DOUBLE_EPSILON = 1.0e-16; diff --git a/Demos/NativeClient/bin_html/tumbler.js b/Demos/NativeClient/bin_html/tumbler.js new file mode 100644 index 000000000..e8e42ebdc --- /dev/null +++ b/Demos/NativeClient/bin_html/tumbler.js @@ -0,0 +1,133 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * @fileoverview The tumbler Application object. This object instantiates a + * Trackball object and connects it to the element named |tumbler_content|. + * It also conditionally embeds a debuggable module or a release module into + * the |tumbler_content| element. + */ + +// Requires tumbler +// Requires tumbler.Dragger +// Requires tumbler.Trackball + +/** + * Constructor for the Application class. Use the run() method to populate + * the object with controllers and wire up the events. + * @constructor + */ +tumbler.Application = function() { + /** + * The native module for the application. This refers to the module loaded + * via the tag. + * @type {Element} + * @private + */ + this.module_ = null; + + /** + * The trackball object. + * @type {tumbler.Trackball} + * @private + */ + this.trackball_ = null; + + /** + * The mouse-drag event object. + * @type {tumbler.Dragger} + * @private + */ + this.dragger_ = null; + + /** + * The function objects that get attached as event handlers. These are + * cached so that they can be removed when they are no longer needed. + * @type {function} + * @private + */ + this.boundModuleDidLoad_ = null; +} + +/** + * The ids used for elements in the DOM. The Tumlber Application expects these + * elements to exist. + * @enum {string} + * @private + */ +tumbler.Application.DomIds_ = { + MODULE: 'tumbler', // The element representing the NaCl module + VIEW: 'tumbler_view' // The
containing the NaCl element. +} + +/** + * Called by the module loading function once the module has been loaded. + * @param {?Element} nativeModule The instance of the native module. + */ +tumbler.Application.prototype.moduleDidLoad = function() { + this.module_ = document.getElementById(tumbler.Application.DomIds_.MODULE); + // Unbind the load function. + this.boundModuleDidLoad_ = null; + + /** + * Set the camera orientation property on the NaCl module. + * @param {Array.} orientation A 4-element array representing the + * camera orientation as a quaternion. + */ + this.module_.setCameraOrientation = function(orientation) { + var methodString = 'setCameraOrientation ' + + 'orientation:' + + JSON.stringify(orientation); + this.postMessage(methodString); + } + + this.trackball_ = new tumbler.Trackball(); + this.dragger_ = new tumbler.Dragger(this.module_); + this.dragger_.addDragListener(this.trackball_); +} + +/** + * Asserts that cond is true; issues an alert and throws an Error otherwise. + * @param {bool} cond The condition. + * @param {String} message The error message issued if cond is false. + */ +tumbler.Application.prototype.assert = function(cond, message) { + if (!cond) { + message = "Assertion failed: " + message; + alert(message); + throw new Error(message); + } +} + +/** + * The run() method starts and 'runs' the application. The trackball object + * is allocated and all the events get wired up. + * @param {?String} opt_contentDivName The id of a DOM element in which to + * embed the Native Client module. If unspecified, defaults to + * VIEW. The DOM element must exist. + */ +tumbler.Application.prototype.run = function(opt_contentDivName) { + contentDivName = opt_contentDivName || tumbler.Application.DomIds_.VIEW; + var contentDiv = document.getElementById(contentDivName); + this.assert(contentDiv, "Missing DOM element '" + contentDivName + "'"); + + // Note that the element is wrapped inside a
, which has a 'load' + // event listener attached. This method is used instead of attaching the + // 'load' event listener directly to the element to ensure that the + // listener is active before the NaCl module 'load' event fires. + this.boundModuleDidLoad_ = this.moduleDidLoad.bind(this); + contentDiv.addEventListener('load', this.boundModuleDidLoad_, true); + + // Load the published .nexe. This includes the 'nacl' attribute which + // shows how to load multi-architecture modules. Each entry in the "nexes" + // object in the .nmf manifest file is a key-value pair: the key is the + // runtime ('x86-32', 'x86-64', etc.); the value is a URL for the desired + // NaCl module. To load the debug versions of your .nexes, set the 'nacl' + // attribute to the _dbg.nmf version of the manifest file. + contentDiv.innerHTML = '' +} diff --git a/Demos/NativeClient/bin_html/tumbler.nmf b/Demos/NativeClient/bin_html/tumbler.nmf new file mode 100644 index 000000000..1c5b49558 --- /dev/null +++ b/Demos/NativeClient/bin_html/tumbler.nmf @@ -0,0 +1,6 @@ +{ + "program": { + "x86-64": {"url": "NativeClientTumbler_x64.exe"}, + "x86-32": {"url": "NativeClientTumbler.exe"} + } +} diff --git a/Demos/NativeClient/bin_html/vector3.js b/Demos/NativeClient/bin_html/vector3.js new file mode 100644 index 000000000..a79f78173 --- /dev/null +++ b/Demos/NativeClient/bin_html/vector3.js @@ -0,0 +1,91 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * @fileoverview A 3D vector class. Proviudes some utility functions on + * 3-dimentional vectors. + */ + +// Requires tumbler + +/** + * Constructor for the Vector3 object. This class contains a 3-tuple that + * represents a vector in 3D space. + * @param {?number} opt_x The x-coordinate for this vector. If null or + * undefined, the x-coordinate value is set to 0. + * @param {?number} opt_y The y-coordinate for this vector. If null or + * undefined, the y-coordinate value is set to 0. + * @param {?number} opt_z The z-coordinate for this vector. If null or + * undefined, the z-coordinate value is set to 0. + * @constructor + */ +tumbler.Vector3 = function(opt_x, opt_y, opt_z) { + /** + * The vector's 3-tuple. + * @type {number} + */ + this.x = opt_x || 0; + this.y = opt_y || 0; + this.z = opt_z || 0; +} + +/** + * Method to return the magnitude of a Vector3. + * @return {number} the magnitude of the vector. + */ +tumbler.Vector3.prototype.magnitude = function() { + return Math.sqrt(this.dot(this)); +} + +/** + * Normalize the vector in-place. + * @return {number} the magnitude of the vector. + */ +tumbler.Vector3.prototype.normalize = function() { + var mag = this.magnitude(); + if (mag < tumbler.Vector3.DOUBLE_EPSILON) + return 0.0; // |this| is equivalent to the 0-vector, don't normalize. + this.scale(1.0 / mag); + return mag; +} + +/** + * Scale the vector in-place by |s|. + * @param {!number} s The scale factor. + */ +tumbler.Vector3.prototype.scale = function(s) { + this.x *= s; + this.y *= s; + this.z *= s; +} + +/** + * Compute the dot product: |this| . v. + * @param {!tumbler.Vector3} v The vector to dot. + * @return {number} the result of |this| . v. + */ +tumbler.Vector3.prototype.dot = function(v) { + return this.x * v.x + this.y * v.y + this.z * v.z; +} + +/** + * Compute the cross product: |this| X v. + * @param {!tumbler.Vector3} v The vector to cross with. + * @return {tumbler.Vector3} the result of |this| X v. + */ +tumbler.Vector3.prototype.cross = function(v) { + var vCross = new tumbler.Vector3(this.y * v.z - this.z * v.y, + this.z * v.x - this.x * v.z, + this.x * v.y - this.y * v.x); + return vCross; +} + +/** + * Real numbers that are less than this distance apart are considered + * equivalent. + * TODO(dspringer): It seems as though there should be a const like this + * in generally available somewhere. + * @type {number} + */ +tumbler.Vector3.DOUBLE_EPSILON = 1.0e-16; diff --git a/Demos/NativeClient/callback.h b/Demos/NativeClient/callback.h new file mode 100644 index 000000000..4d67262ad --- /dev/null +++ b/Demos/NativeClient/callback.h @@ -0,0 +1,92 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef EXAMPLES_TUMBLER_CALLBACK_H_ +#define EXAMPLES_TUMBLER_CALLBACK_H_ + +#include +#include +#include + +namespace tumbler { + +class ScriptingBridge; + +// Templates used to support method call-backs when a method or property is +// accessed from the browser code. + +// Class suite used to publish a method name to Javascript. Typical use is +// like this: +// photo::MethodCallback* calculate_callback_; +// calculate_callback_ = +// new scripting::MethodCallback(this, +// &Calculator::Calculate); +// bridge->AddMethodNamed("calculate", calculate_callback_); +// ... +// delete calculate_callback_; +// +// The caller must delete the callback. + +// Methods get parameters as a dictionary that maps parameter names to values. +typedef std::map MethodParameter; + +// Pure virtual class used in STL containers. +class MethodCallbackExecutor { + public: + virtual ~MethodCallbackExecutor() {} + virtual void Execute( + const ScriptingBridge& bridge, + const MethodParameter& parameters) = 0; +}; + +template +class MethodCallback : public MethodCallbackExecutor { + public: + typedef void (T::*Method)( + const ScriptingBridge& bridge, + const MethodParameter& parameters); + + MethodCallback(T* instance, Method method) + : instance_(instance), method_(method) {} + virtual ~MethodCallback() {} + virtual void Execute( + const ScriptingBridge& bridge, + const MethodParameter& parameters) { + // Use "this->" to force C++ to look inside our templatized base class; see + // Effective C++, 3rd Ed, item 43, p210 for details. + ((this->instance_)->*(this->method_))(bridge, parameters); + } + + private: + T* instance_; + Method method_; +}; + +template +class ConstMethodCallback : public MethodCallbackExecutor { + public: + typedef void (T::*ConstMethod)( + const ScriptingBridge& bridge, + const MethodParameter& parameters) const; + + ConstMethodCallback(const T* instance, ConstMethod method) + : instance_(instance), const_method_(method) {} + virtual ~ConstMethodCallback() {} + virtual void Execute( + const ScriptingBridge& bridge, + const MethodParameter& parameters) { + // Use "this->" to force C++ to look inside our templatized base class; see + // Effective C++, 3rd Ed, item 43, p210 for details. + ((this->instance_)->*(this->const_method_))(bridge, parameters); + } + + private: + const T* instance_; + ConstMethod const_method_; +}; + +} // namespace tumbler + +#endif // EXAMPLES_TUMBLER_CALLBACK_H_ + diff --git a/Demos/NativeClient/cube.cc b/Demos/NativeClient/cube.cc new file mode 100644 index 000000000..777e6ebdc --- /dev/null +++ b/Demos/NativeClient/cube.cc @@ -0,0 +1,267 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "cube.h" + +#include + +#include "shader_util.h" +#include "transforms.h" + +namespace tumbler { + +static const size_t kVertexCount = 24; +static const int kIndexCount = 36; + +Cube::Cube(SharedOpenGLContext opengl_context) + : opengl_context_(opengl_context), + width_(1), + height_(1) { + eye_[0] = eye_[1] = 0.0f; + eye_[2] = 2.0f; + orientation_[0] = 0.0f; + orientation_[1] = 0.0f; + orientation_[2] = 0.0f; + orientation_[3] = 1.0f; +} + +Cube::~Cube() { + glDeleteBuffers(3, cube_vbos_); + glDeleteProgram(shader_program_object_); +} + +void Cube::PrepareOpenGL() { + CreateShaders(); + CreateCube(); + glClearColor(1.0f, 1.0f, 1.0f, 1.0f); + glEnable(GL_DEPTH_TEST); +} + +void Cube::Resize(int width, int height) { + width_ = std::max(width, 1); + height_ = std::max(height, 1); + // Set the viewport + glViewport(0, 0, width_, height_); + // Compute the perspective projection matrix with a 60 degree FOV. + GLfloat aspect = static_cast(width_) / static_cast(height_); + transform_4x4::LoadIdentity(perspective_proj_); + transform_4x4::Perspective(perspective_proj_, 60.0f, aspect, 1.0f, 20.0f); +} + +void Cube::Draw() { + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + // Compute a new model-view matrix, then use that to make the composite + // model-view-projection matrix: MVP = MV . P. + GLfloat model_view[16]; + ComputeModelViewTransform(model_view); + transform_4x4::Multiply(mvp_matrix_, model_view, perspective_proj_); + + glBindBuffer(GL_ARRAY_BUFFER, cube_vbos_[0]); + glUseProgram(shader_program_object_); + glEnableVertexAttribArray(position_location_); + glVertexAttribPointer(position_location_, + 3, + GL_FLOAT, + GL_FALSE, + 3 * sizeof(GLfloat), + NULL); + glEnableVertexAttribArray(color_location_); + glBindBuffer(GL_ARRAY_BUFFER, cube_vbos_[1]); + glVertexAttribPointer(color_location_, + 3, + GL_FLOAT, + GL_FALSE, + 3 * sizeof(GLfloat), + NULL); + glUniformMatrix4fv(mvp_location_, 1, GL_FALSE, mvp_matrix_); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, cube_vbos_[2]); + glDrawElements(GL_TRIANGLES, kIndexCount, GL_UNSIGNED_SHORT, 0); + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); +} + +bool Cube::CreateShaders() { + const char vertex_shader_src[] = + "uniform mat4 u_mvpMatrix; \n" + "attribute vec4 a_position; \n" + "attribute vec3 a_color; \n" + "varying lowp vec4 v_color; \n" + "void main() \n" + "{ \n" + " v_color.xyz = a_color; \n" + " v_color.w = 1.0; \n" + " gl_Position = u_mvpMatrix * a_position; \n" + "} \n"; + + const char fragment_shader_src[] = + "varying lowp vec4 v_color; \n" + "void main() \n" + "{ \n" + " gl_FragColor = v_color; \n" + "} \n"; + + // Load the shaders and get a linked program object + shader_program_object_ = + shader_util::CreateProgramFromVertexAndFragmentShaders( + vertex_shader_src, fragment_shader_src); + if (shader_program_object_ == 0) + return false; + position_location_ = glGetAttribLocation(shader_program_object_, + "a_position"); + color_location_ = glGetAttribLocation(shader_program_object_, "a_color"); + mvp_location_ = glGetUniformLocation(shader_program_object_, "u_mvpMatrix"); + return true; +} + +void Cube::CreateCube() { + static const GLfloat cube_vertices[] = { + // Vertex coordinates interleaved with color values + // Bottom + -0.5f, -0.5f, -0.5f, + -0.5f, -0.5f, 0.5f, + 0.5f, -0.5f, 0.5f, + 0.5f, -0.5f, -0.5f, + // Top + -0.5f, 0.5f, -0.5f, + -0.5f, 0.5f, 0.5f, + 0.5f, 0.5f, 0.5f, + 0.5f, 0.5f, -0.5f, + // Back + -0.5f, -0.5f, -0.5f, + -0.5f, 0.5f, -0.5f, + 0.5f, 0.5f, -0.5f, + 0.5f, -0.5f, -0.5f, + // Front + -0.5f, -0.5f, 0.5f, + -0.5f, 0.5f, 0.5f, + 0.5f, 0.5f, 0.5f, + 0.5f, -0.5f, 0.5f, + // Left + -0.5f, -0.5f, -0.5f, + -0.5f, -0.5f, 0.5f, + -0.5f, 0.5f, 0.5f, + -0.5f, 0.5f, -0.5f, + // Right + 0.5f, -0.5f, -0.5f, + 0.5f, -0.5f, 0.5f, + 0.5f, 0.5f, 0.5f, + 0.5f, 0.5f, -0.5f + }; + + static const GLfloat cube_colors[] = { + // Vertex coordinates interleaved with color values + // Bottom + 1.0, 0.0, 0.0, + 1.0, 0.0, 0.0, + 1.0, 0.0, 0.0, + 1.0, 0.0, 0.0, + // Top + 0.0, 1.0, 0.0, + 0.0, 1.0, 0.0, + 0.0, 1.0, 0.0, + 0.0, 1.0, 0.0, + // Back + 0.0, 0.0, 1.0, + 0.0, 0.0, 1.0, + 0.0, 0.0, 1.0, + 0.0, 0.0, 1.0, + // Front + 1.0, 0.0, 1.0, + 1.0, 0.0, 1.0, + 1.0, 0.0, 1.0, + 1.0, 0.0, 1.0, + // Left + 1.0, 1.0, 0.0, + 1.0, 1.0, 0.0, + 1.0, 1.0, 0.0, + 1.0, 1.0, 0.0, + // Right + 0.0, 1.0, 1.0, + 0.0, 1.0, 1.0, + 0.0, 1.0, 1.0, + 0.0, 1.0, 1.0 + }; + + static const GLushort cube_indices[] = { + // Bottom + 0, 2, 1, + 0, 3, 2, + // Top + 4, 5, 6, + 4, 6, 7, + // Back + 8, 9, 10, + 8, 10, 11, + // Front + 12, 15, 14, + 12, 14, 13, + // Left + 16, 17, 18, + 16, 18, 19, + // Right + 20, 23, 22, + 20, 22, 21 + }; + + // Generate the VBOs and upload them to the graphics context. + glGenBuffers(3, cube_vbos_); + glBindBuffer(GL_ARRAY_BUFFER, cube_vbos_[0]); + glBufferData(GL_ARRAY_BUFFER, + kVertexCount * sizeof(GLfloat) * 3, + cube_vertices, + GL_STATIC_DRAW); + glBindBuffer(GL_ARRAY_BUFFER, cube_vbos_[1]); + glBufferData(GL_ARRAY_BUFFER, + kVertexCount * sizeof(GLfloat) * 3, + cube_colors, + GL_STATIC_DRAW); + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, cube_vbos_[2]); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, + kIndexCount * sizeof(GL_UNSIGNED_SHORT), + cube_indices, + GL_STATIC_DRAW); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); +} + +void Cube::ComputeModelViewTransform(GLfloat* model_view) { + // This method takes into account the possiblity that |orientation_| + // might not be normalized. + double sqrx = orientation_[0] * orientation_[0]; + double sqry = orientation_[1] * orientation_[1]; + double sqrz = orientation_[2] * orientation_[2]; + double sqrw = orientation_[3] * orientation_[3]; + double sqrLength = 1.0 / (sqrx + sqry + sqrz + sqrw); + + transform_4x4::LoadIdentity(model_view); + model_view[0] = (sqrx - sqry - sqrz + sqrw) * sqrLength; + model_view[5] = (-sqrx + sqry - sqrz + sqrw) * sqrLength; + model_view[10] = (-sqrx - sqry + sqrz + sqrw) * sqrLength; + + double temp1 = orientation_[0] * orientation_[1]; + double temp2 = orientation_[2] * orientation_[3]; + model_view[1] = 2.0 * (temp1 + temp2) * sqrLength; + model_view[4] = 2.0 * (temp1 - temp2) * sqrLength; + + temp1 = orientation_[0] * orientation_[2]; + temp2 = orientation_[1] * orientation_[3]; + model_view[2] = 2.0 * (temp1 - temp2) * sqrLength; + model_view[8] = 2.0 * (temp1 + temp2) * sqrLength; + temp1 = orientation_[1] * orientation_[2]; + temp2 = orientation_[0] * orientation_[3]; + model_view[6] = 2.0 * (temp1 + temp2) * sqrLength; + model_view[9] = 2.0 * (temp1 - temp2) * sqrLength; + model_view[3] = 0.0; + model_view[7] = 0.0; + model_view[11] = 0.0; + + // Concatenate the translation to the eye point. + model_view[12] = -eye_[0]; + model_view[13] = -eye_[1]; + model_view[14] = -eye_[2]; + model_view[15] = 1.0; +} + +} // namespace tumbler diff --git a/Demos/NativeClient/cube.h b/Demos/NativeClient/cube.h new file mode 100644 index 000000000..1c3b62baa --- /dev/null +++ b/Demos/NativeClient/cube.h @@ -0,0 +1,97 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef EXAMPLES_TUMBLER_CUBE_H_ +#define EXAMPLES_TUMBLER_CUBE_H_ + +#include +#include +#include "opengl_context.h" +#include "opengl_context_ptrs.h" + +namespace tumbler { + +// The Cube class provides a place to implement 3D rendering. It has a +// frame that it occupies in a browser window. +class Cube { + public: + explicit Cube(SharedOpenGLContext opengl_context); + ~Cube(); + + // Called once when a new RenderContext is first bound to the view. The + // bound context is guaranteed to be current and valid before calling this + // method. + void PrepareOpenGL(); + + // Called whenever the size of the browser view changes. This method is + // called at least once when the view is first made visible. Clamps the + // sizes to 1. + void Resize(int width, int height); + + // Called every time the view need to be drawn. The bound context is + // guaranteed to be current and valid before this method is called. The + // visible portion of the context is flushed to the browser after this + // method returns. + void Draw(); + + // Accessor for width and height. To change these, call Resize. + const int width() const { + return width_; + } + + const int height() const { + return height_; + } + + // Accessor/mutator for the camera orientation. + void GetOrientation(std::vector* orientation) const { + if (!orientation) + return; + (*orientation)[0] = static_cast(orientation_[0]); + (*orientation)[1] = static_cast(orientation_[1]); + (*orientation)[2] = static_cast(orientation_[2]); + (*orientation)[3] = static_cast(orientation_[3]); + } + void SetOrientation(const std::vector& orientation) { + orientation_[0] = static_cast(orientation[0]); + orientation_[1] = static_cast(orientation[1]); + orientation_[2] = static_cast(orientation[2]); + orientation_[3] = static_cast(orientation[3]); + } + + private: + // Create the shaders used to draw the cube, and link them into a program. + // Initializes |shader_progam_object_|, |position_loction_| and + // |mvp_location_|. + bool CreateShaders(); + + // Generates a cube as a series of GL_TRIANGLE_STRIPs, and initializes + // |index_count_| to the number of indices in the index list used as a VBO. + // Creates the |vbo_ids_| required for the vertex and index data and uploads + // the the VBO data. + void CreateCube(); + + // Build up the model-view transform from the eye and orienation properties. + // Assumes that |model_view| is a 4x4 matrix. + void ComputeModelViewTransform(GLfloat* model_view); + + SharedOpenGLContext opengl_context_; + int width_; + int height_; + GLuint shader_program_object_; // The compiled shaders. + GLint position_location_; // The position attribute location. + GLint color_location_; // The color attribute location. + GLint mvp_location_; // The Model-View-Projection composite matrix. + GLuint cube_vbos_[3]; + GLfloat eye_[3]; // The eye point of the virtual camera. + // The orientation of the virtual camera stored as a quaternion. The + // quaternion is laid out as {{x, y, z}, w}. + GLfloat orientation_[4]; + GLfloat perspective_proj_[16]; + GLfloat mvp_matrix_[16]; +}; + +} // namespace tumbler + +#endif // EXAMPLES_TUMBLER_CUBE_H_ diff --git a/Demos/NativeClient/opengl_context.cc b/Demos/NativeClient/opengl_context.cc new file mode 100644 index 000000000..1c0afa711 --- /dev/null +++ b/Demos/NativeClient/opengl_context.cc @@ -0,0 +1,72 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "opengl_context.h" + +#include +#include "ppapi/gles2/gl2ext_ppapi.h" + +namespace { +// This is called by the brower when the 3D context has been flushed to the +// browser window. +void FlushCallback(void* data, int32_t result) { + static_cast(data)->set_flush_pending(false); +} +} // namespace + +namespace tumbler { + +OpenGLContext::OpenGLContext(pp::Instance* instance) + : pp::Graphics3DClient_Dev(instance), + flush_pending_(false) { + pp::Module* module = pp::Module::Get(); + assert(module); + gles2_interface_ = static_cast( + module->GetBrowserInterface(PPB_OPENGLES2_DEV_INTERFACE)); + assert(gles2_interface_); +} + +OpenGLContext::~OpenGLContext() { + glSetCurrentContextPPAPI(0); +} + +bool OpenGLContext::MakeContextCurrent(pp::Instance* instance) { + if (instance == NULL) { + glSetCurrentContextPPAPI(0); + return false; + } + // Lazily create the Pepper context. + if (context_.is_null()) { + context_ = pp::Context3D_Dev(*instance, 0, pp::Context3D_Dev(), NULL); + if (context_.is_null()) { + glSetCurrentContextPPAPI(0); + return false; + } + surface_ = pp::Surface3D_Dev(*instance, 0, NULL); + context_.BindSurfaces(surface_, surface_); + instance->BindGraphics(surface_); + } + glSetCurrentContextPPAPI(context_.pp_resource()); + return true; +} + +void OpenGLContext::InvalidateContext(pp::Instance* instance) { + if (instance == NULL) + return; + // Unbind the existing surface and re-bind to null surfaces. + instance->BindGraphics(pp::Surface3D_Dev()); + context_.BindSurfaces(pp::Surface3D_Dev(), pp::Surface3D_Dev()); + glSetCurrentContextPPAPI(0); +} + +void OpenGLContext::FlushContext() { + if (flush_pending()) { + // A flush is pending so do nothing; just drop this flush on the floor. + return; + } + set_flush_pending(true); + surface_.SwapBuffers(pp::CompletionCallback(&FlushCallback, this)); +} +} // namespace tumbler + diff --git a/Demos/NativeClient/opengl_context.h b/Demos/NativeClient/opengl_context.h new file mode 100644 index 000000000..851b9dbfc --- /dev/null +++ b/Demos/NativeClient/opengl_context.h @@ -0,0 +1,90 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef EXAMPLES_TUMBLER_OPENGL_CONTEXT_H_ +#define EXAMPLES_TUMBLER_OPENGL_CONTEXT_H_ + +/// +/// @file +/// OpenGLContext manages the OpenGL context in the browser that is associated +/// with a @a pp::Instance instance. +/// + +#include + +#include +#include + +#include "opengl_context_ptrs.h" +#include "ppapi/c/dev/ppb_opengles_dev.h" +#include "ppapi/cpp/dev/context_3d_dev.h" +#include "ppapi/cpp/dev/graphics_3d_client_dev.h" +#include "ppapi/cpp/dev/graphics_3d_dev.h" +#include "ppapi/cpp/dev/surface_3d_dev.h" +#include "ppapi/cpp/instance.h" + +namespace tumbler { + +/// OpenGLContext manages an OpenGL rendering context in the browser. +/// +class OpenGLContext : public pp::Graphics3DClient_Dev { + public: + explicit OpenGLContext(pp::Instance* instance); + + /// Release all the in-browser resources used by this context, and make this + /// context invalid. + virtual ~OpenGLContext(); + + /// The Graphics3DClient interfcace. + virtual void Graphics3DContextLost() { + assert(!"Unexpectedly lost graphics context"); + } + + /// Make @a this the current 3D context in @a instance. + /// @param instance The instance of the NaCl module that will receive the + /// the current 3D context. + /// @return success. + bool MakeContextCurrent(pp::Instance* instance); + + /// Flush the contents of this context to the browser's 3D device. + void FlushContext(); + + /// Make the underlying 3D device invalid, so that any subsequent rendering + /// commands will have no effect. The next call to MakeContextCurrent() will + /// cause the underlying 3D device to get rebound and start receiving + /// receiving rendering commands again. Use InvalidateContext(), for + /// example, when resizing the context's viewing area. + void InvalidateContext(pp::Instance* instance); + + /// The OpenGL ES 2.0 interface. + const struct PPB_OpenGLES2_Dev* gles2() const { + return gles2_interface_; + } + + /// The PP_Resource needed to make GLES2 calls through the Pepper interface. + const PP_Resource gl_context() const { + return context_.pp_resource(); + } + + /// Indicate whether a flush is pending. This can only be called from the + /// main thread; it is not thread safe. + bool flush_pending() const { + return flush_pending_; + } + void set_flush_pending(bool flag) { + flush_pending_ = flag; + } + + private: + pp::Context3D_Dev context_; + pp::Surface3D_Dev surface_; + bool flush_pending_; + + const struct PPB_OpenGLES2_Dev* gles2_interface_; +}; + +} // namespace tumbler + +#endif // EXAMPLES_TUMBLER_OPENGL_CONTEXT_H_ + diff --git a/Demos/NativeClient/opengl_context_ptrs.h b/Demos/NativeClient/opengl_context_ptrs.h new file mode 100644 index 000000000..347852134 --- /dev/null +++ b/Demos/NativeClient/opengl_context_ptrs.h @@ -0,0 +1,22 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef EXAMPLES_TUMBLER_OPENGL_CONTEXT_PTRS_H_ +#define EXAMPLES_TUMBLER_OPENGL_CONTEXT_PTRS_H_ + +// A convenience wrapper for a shared OpenGLContext pointer type. As other +// smart pointer types are needed, add them here. + +#include + +namespace tumbler { + +class OpenGLContext; + +typedef std::tr1::shared_ptr SharedOpenGLContext; + +} // namespace tumbler + +#endif // EXAMPLES_TUMBLER_OPENGL_CONTEXT_PTRS_H_ + diff --git a/Demos/NativeClient/premake4.lua b/Demos/NativeClient/premake4.lua new file mode 100644 index 000000000..209059715 --- /dev/null +++ b/Demos/NativeClient/premake4.lua @@ -0,0 +1,27 @@ + project "NativeClientTumbler" + + kind "ConsoleApp" + + targetdir "bin_html" + + includedirs { "." } + + --libdirs {} + + links { + "ppapi_gles2", + "ppapi", + "ppapi_cpp", + "ppruntime" + } + + + files { + "cube.cc", + "opengl_context.cc", + "scripting_bridge.cc", + "shader_util.cc", + "transforms.cc", + "tumbler.cc", + "tumbler_module.cc" + } \ No newline at end of file diff --git a/Demos/NativeClient/scripting_bridge.cc b/Demos/NativeClient/scripting_bridge.cc new file mode 100644 index 000000000..abc67ca2e --- /dev/null +++ b/Demos/NativeClient/scripting_bridge.cc @@ -0,0 +1,95 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "scripting_bridge.h" + +namespace { +const char* const kWhiteSpaceCharacters = " \t"; + +// Helper function to pull out the next token in |token_string|. A token is +// delimited by whitespace. Scanning begins at |*pos|, if pos goes beyond the +// end of |token_string|, it is set to std::string::npos and an empty string +// is returned. On return, |*pos| will point to the beginning of the next +// token. |pos| must not be NULL. +const std::string ScanToken(const std::string& token_string, size_t* pos) { + std::string token; + if (*pos == std::string::npos) { + return token; + } + size_t token_start_pos = token_string.find_first_not_of(kWhiteSpaceCharacters, + *pos); + size_t token_end_pos = token_string.find_first_of(kWhiteSpaceCharacters, + token_start_pos); + if (token_start_pos != std::string::npos) { + token = token_string.substr(token_start_pos, token_end_pos); + } + *pos = token_end_pos; + return token; +} + +// Take a string of the form 'name:value' and split it into two strings, one +// containing 'name' and the other 'value'. If the ':' separator is missing, +// or is the last character in |parameter|, |parameter| is copied to +// |param_name|, |param_value| is left unchanged and false is returned. +bool ParseParameter(const std::string& parameter, + std::string* param_name, + std::string* param_value) { + bool success = false; + size_t sep_pos = parameter.find_first_of(':'); + if (sep_pos != std::string::npos) { + *param_name = parameter.substr(0, sep_pos); + if (sep_pos < parameter.length() - 1) { + *param_value = parameter.substr(sep_pos + 1); + success = true; + } else { + success = false; + } + } else { + *param_name = parameter; + success = false; + } + return success; +} +} // namespace + +namespace tumbler { + +bool ScriptingBridge::AddMethodNamed(const std::string& method_name, + SharedMethodCallbackExecutor method) { + if (method_name.size() == 0 || method == NULL) + return false; + method_dictionary_.insert( + std::pair(method_name, + method)); + return true; +} + +bool ScriptingBridge::InvokeMethod(const std::string& method) { + size_t current_pos = 0; + const std::string method_name = ScanToken(method, ¤t_pos); + MethodDictionary::iterator method_iter; + method_iter = method_dictionary_.find(method_name); + if (method_iter != method_dictionary_.end()) { + // Pull out the method parameters and build a dictionary that maps + // parameter names to values. + std::map param_dict; + while (current_pos != std::string::npos) { + const std::string parameter = ScanToken(method, ¤t_pos); + if (parameter.length()) { + std::string param_name; + std::string param_value; + if (ParseParameter(parameter, ¶m_name, ¶m_value)) { + // Note that duplicate parameter names will override each other. The + // last one in the method string will be used. + param_dict[param_name] = param_value; + } + } + } + (*method_iter->second).Execute(*this, param_dict); + return true; + } + return false; +} + +} // namespace tumbler diff --git a/Demos/NativeClient/scripting_bridge.h b/Demos/NativeClient/scripting_bridge.h new file mode 100644 index 000000000..f6a366c65 --- /dev/null +++ b/Demos/NativeClient/scripting_bridge.h @@ -0,0 +1,52 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef EXAMPLES_TUMBLER_SCRIPTING_BRIDGE_H_ +#define EXAMPLES_TUMBLER_SCRIPTING_BRIDGE_H_ + +#include +#include +#include +#include + +#include "callback.h" +#include "ppapi/cpp/var.h" + +namespace tumbler { + +class MethodCallbackExecutor; + +// This class handles the interface between the browser and the NaCl module. +// There is a single point of entry from the browser: postMessage(). The +// string passed to postMessage() has this format: +// 'function_name arg_name0:arg_0 arg_name1:arg1 ...' +// The arguments have undetermined type; they are placed in a map of argument +// names and values. Values are all strings, it is up to the target code to +// do any type coercion. +// Methods called by the scripting bridge must have a signature like this: +// void Method(const ScriptingBridge& bridge, +// const ParameterDictionary&); +class ScriptingBridge { + public: + // Shared pointer type used in the method map. + typedef std::tr1::shared_ptr + SharedMethodCallbackExecutor; + + virtual ~ScriptingBridge() {} + + // Causes |method_name| to be published as a method that can be called via + // postMessage() from the browser. Associates this method with |method|. + bool AddMethodNamed(const std::string& method_name, + SharedMethodCallbackExecutor method); + + bool InvokeMethod(const std::string& method); + + private: + typedef std::map MethodDictionary; + + MethodDictionary method_dictionary_; +}; + +} // namespace tumbler +#endif // EXAMPLES_TUMBLER_SCRIPTING_BRIDGE_H_ diff --git a/Demos/NativeClient/shader_util.cc b/Demos/NativeClient/shader_util.cc new file mode 100644 index 000000000..802955b66 --- /dev/null +++ b/Demos/NativeClient/shader_util.cc @@ -0,0 +1,95 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "shader_util.h" + +#include +#include + +namespace shader_util { + +GLuint CreateShaderOfType(GLenum type, const char *shader_src) { + GLuint shader; + GLint compiled; + + // Create the shader object + shader = glCreateShader(type); + + if (shader == 0) + return 0; + + // Load and compile the shader source + glShaderSource(shader, 1, &shader_src, NULL); + glCompileShader(shader); + + // Check the compile status + glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled); + if (compiled == 0) { + GLint info_len = 0; + glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &info_len); + if (info_len > 1) { + char* info_log = reinterpret_cast(malloc(sizeof(char) * info_len)); + glGetShaderInfoLog(shader, info_len, NULL, info_log); + // TODO(dspringer): We could really use a logging API. + printf("Error compiling shader:\n%s\n", info_log); + free(info_log); + } + glDeleteShader(shader); + return 0; + } + + return shader; +} + +GLuint CreateProgramFromVertexAndFragmentShaders( + const char *vertex_shader_src, const char *fragment_shader_src) { + GLuint vertex_shader; + GLuint fragment_shader; + GLuint program_object; + GLint linked; + + // Load the vertex/fragment shaders + vertex_shader = CreateShaderOfType(GL_VERTEX_SHADER, vertex_shader_src); + if (vertex_shader == 0) + return 0; + fragment_shader = CreateShaderOfType(GL_FRAGMENT_SHADER, fragment_shader_src); + if (fragment_shader == 0) { + glDeleteShader(vertex_shader); + return 0; + } + + // Create the program object and attach the shaders. + program_object = glCreateProgram(); + if (program_object == 0) + return 0; + glAttachShader(program_object, vertex_shader); + glAttachShader(program_object, fragment_shader); + + // Link the program + glLinkProgram(program_object); + + // Check the link status + glGetProgramiv(program_object, GL_LINK_STATUS, &linked); + if (linked == 0) { + GLint info_len = 0; + glGetProgramiv(program_object, GL_INFO_LOG_LENGTH, &info_len); + if (info_len > 1) { + char* info_log = reinterpret_cast(malloc(info_len)); + glGetProgramInfoLog(program_object, info_len, NULL, info_log); + // TODO(dspringer): We could really use a logging API. + printf("Error linking program:\n%s\n", info_log); + free(info_log); + } + glDeleteProgram(program_object); + return 0; + } + + // Delete these here because they are attached to the program object. + glDeleteShader(vertex_shader); + glDeleteShader(fragment_shader); + + return program_object; +} + +} // namespace shader_util diff --git a/Demos/NativeClient/shader_util.h b/Demos/NativeClient/shader_util.h new file mode 100644 index 000000000..635b16b9f --- /dev/null +++ b/Demos/NativeClient/shader_util.h @@ -0,0 +1,29 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Some simple helper functions that load shaders and create program objects. + +#ifndef EXAMPLES_TUMBLER_SHADER_UTIL_H_ +#define EXAMPLES_TUMBLER_SHADER_UTIL_H_ + +#include + +namespace shader_util { + +// Load and compile a shader. |type| can be one of GL_VERTEX_SHADER or +// GL_FRAGMENT_SHADER. Returns a non-0 value representing the compiled +// shader on success, 0 on failure. The caller is responsible for deleting +// the returned shader using glDeleteShader(). +GLuint CreateShaderOfType(GLenum type, const char *shader_src); + +// Load and compile the vertex and fragment shaders, then link these together +// into a complete program. Returns a non-0 value representing the program on, +// success or 0 on failure. The caller is responsible for deleting the +// returned program using glDeleteProgram(). +GLuint CreateProgramFromVertexAndFragmentShaders( + const char *vertex_shader_src, const char *fragment_shader_src); + +} // namespace shader_util + +#endif // EXAMPLES_TUMBLER_SHADER_UTIL_H_ diff --git a/Demos/NativeClient/transforms.cc b/Demos/NativeClient/transforms.cc new file mode 100644 index 000000000..10ab16c94 --- /dev/null +++ b/Demos/NativeClient/transforms.cc @@ -0,0 +1,116 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "transforms.h" + +#include +#include +#include + +namespace transform_4x4 { + +static const GLfloat kPI = 3.1415926535897932384626433832795f; + +void Translate(GLfloat* m, GLfloat tx, GLfloat ty, GLfloat tz) { + m[12] += (m[0] * tx + m[4] * ty + m[8] * tz); + m[13] += (m[1] * tx + m[5] * ty + m[9] * tz); + m[14] += (m[2] * tx + m[6] * ty + m[10] * tz); + m[15] += (m[3] * tx + m[7] * ty + m[11] * tz); +} + +void Frustum(GLfloat* m, + GLfloat left, + GLfloat right, + GLfloat bottom, + GLfloat top, + GLfloat near_z, + GLfloat far_z) { + GLfloat delta_x = right - left; + GLfloat delta_y = top - bottom; + GLfloat delta_z = far_z - near_z; + GLfloat frustum[16]; + + if ((near_z <= 0.0f) || (far_z <= 0.0f) || + (delta_x <= 0.0f) || (delta_y <= 0.0f) || (delta_z <= 0.0f)) + return; + + frustum[0] = 2.0f * near_z / delta_x; + frustum[1] = frustum[2] = frustum[3] = 0.0f; + + frustum[5] = 2.0f * near_z / delta_y; + frustum[4] = frustum[6] = frustum[7] = 0.0f; + + frustum[8] = (right + left) / delta_x; + frustum[9] = (top + bottom) / delta_y; + frustum[10] = -(near_z + far_z) / delta_z; + frustum[11] = -1.0f; + + frustum[14] = -2.0f * near_z * far_z / delta_z; + frustum[12] = frustum[13] = frustum[15] = 0.0f; + + transform_4x4::Multiply(m, frustum, m); +} + + +void Perspective(GLfloat* m, + GLfloat fovy, + GLfloat aspect, + GLfloat near_z, + GLfloat far_z) { + GLfloat frustum_w, frustum_h; + + frustum_h = tanf((fovy * 0.5f) / 180.0f * kPI) * near_z; + frustum_w = frustum_h * aspect; + transform_4x4::Frustum(m, -frustum_w, frustum_w, -frustum_h, frustum_h, + near_z, far_z); +} + +void Multiply(GLfloat *m, GLfloat *a, GLfloat* b) { + GLfloat tmp[16]; + // tmp = a . b + GLfloat a0, a1, a2, a3; + a0 = a[0]; + a1 = a[1]; + a2 = a[2]; + a3 = a[3]; + tmp[0] = a0 * b[0] + a1 * b[4] + a2 * b[8] + a3 * b[12]; + tmp[1] = a0 * b[1] + a1 * b[5] + a2 * b[9] + a3 * b[13]; + tmp[2] = a0 * b[2] + a1 * b[6] + a2 * b[10] + a3 * b[14]; + tmp[3] = a0 * b[3] + a1 * b[7] + a2 * b[11] + a3 * b[15]; + + a0 = a[4]; + a1 = a[5]; + a2 = a[6]; + a3 = a[7]; + tmp[4] = a0 * b[0] + a1 * b[4] + a2 * b[8] + a3 * b[12]; + tmp[5] = a0 * b[1] + a1 * b[5] + a2 * b[9] + a3 * b[13]; + tmp[6] = a0 * b[2] + a1 * b[6] + a2 * b[10] + a3 * b[14]; + tmp[7] = a0 * b[3] + a1 * b[7] + a2 * b[11] + a3 * b[15]; + + a0 = a[8]; + a1 = a[9]; + a2 = a[10]; + a3 = a[11]; + tmp[8] = a0 * b[0] + a1 * b[4] + a2 * b[8] + a3 * b[12]; + tmp[9] = a0 * b[1] + a1 * b[5] + a2 * b[9] + a3 * b[13]; + tmp[10] = a0 * b[2] + a1 * b[6] + a2 * b[10] + a3 * b[14]; + tmp[11] = a0 * b[3] + a1 * b[7] + a2 * b[11] + a3 * b[15]; + + a0 = a[12]; + a1 = a[13]; + a2 = a[14]; + a3 = a[15]; + tmp[12] = a0 * b[0] + a1 * b[4] + a2 * b[8] + a3 * b[12]; + tmp[13] = a0 * b[1] + a1 * b[5] + a2 * b[9] + a3 * b[13]; + tmp[14] = a0 * b[2] + a1 * b[6] + a2 * b[10] + a3 * b[14]; + tmp[15] = a0 * b[3] + a1 * b[7] + a2 * b[11] + a3 * b[15]; + memcpy(m, tmp, sizeof(GLfloat) * 4 * 4); +} + +void LoadIdentity(GLfloat* m) { + memset(m, 0, sizeof(GLfloat) * 4 * 4); + m[0] = m[5] = m[10] = m[15] = 1.0f; +} + +} // namespace transform_4x4 diff --git a/Demos/NativeClient/transforms.h b/Demos/NativeClient/transforms.h new file mode 100644 index 000000000..5ac3d6e52 --- /dev/null +++ b/Demos/NativeClient/transforms.h @@ -0,0 +1,45 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef EXAMPLES_TUMBLER_TRANSFORMS_H_ +#define EXAMPLES_TUMBLER_TRANSFORMS_H_ + +#include + +// A very simple set of 4x4 matrix routines. In all these routines, the input +// matrix is assumed to be a 4x4 of GLfloats. + +namespace transform_4x4 { + +// Pre-multply |m| with a projection transformation 4x4 matrix from a +// truncated pyramid viewing frustum. +void Frustum(GLfloat* m, + GLfloat left, + GLfloat right, + GLfloat bottom, + GLfloat top, + GLfloat near_z, + GLfloat far_z); + +// Replace |m| with the 4x4 identity matrix. +void LoadIdentity(GLfloat* m); + +// |m| <- |a| . |b|. |m| can point at the same memory as either |a| or |b|. +void Multiply(GLfloat *m, GLfloat *a, GLfloat* b); + +// Pre-multiply |m| with a single-point perspective matrix based on the viewing +// frustum whose view angle is |fovy|. +void Perspective(GLfloat* m, + GLfloat fovy, + GLfloat aspect, + GLfloat near_z, + GLfloat far_z); + +// Pre-multiply |m| with a matrix that represents a translation by |tx|, |ty|, +// |tz|. +void Translate(GLfloat* m, GLfloat tx, GLfloat ty, GLfloat tz); +} // namespace transform_4x4 + +#endif // EXAMPLES_TUMBLER_TRANSFORMS_H_ + diff --git a/Demos/NativeClient/tumbler.cc b/Demos/NativeClient/tumbler.cc new file mode 100644 index 000000000..1beecd39f --- /dev/null +++ b/Demos/NativeClient/tumbler.cc @@ -0,0 +1,136 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "tumbler.h" + +#include +#include +#include +#include + +#include "cube.h" +#include "opengl_context.h" +#include "scripting_bridge.h" +#include "ppapi/cpp/rect.h" +#include "ppapi/cpp/size.h" +#include "ppapi/cpp/var.h" + +namespace { +const size_t kQuaternionElementCount = 4; +const char* const kArrayStartCharacter = "["; +const char* const kArrayEndCharacter = "]"; +const char* const kArrayDelimiter = ","; + +// Return the value of parameter named |param_name| from |parameters|. If +// |param_name| doesn't exist, then return an empty string. +std::string GetParameterNamed( + const std::string& param_name, + const tumbler::MethodParameter& parameters) { + tumbler::MethodParameter::const_iterator i = + parameters.find(param_name); + if (i == parameters.end()) { + return ""; + } + return i->second; +} + +// Convert the JSON string |array| into a vector of floats. |array| is +// expected to be a string bounded by '[' and ']', and a comma-delimited list +// of numbers. Any errors result in the return of an empty array. +std::vector CreateArrayFromJSON(const std::string& json_array) { + std::vector float_array; + size_t array_start_pos = json_array.find_first_of(kArrayStartCharacter); + size_t array_end_pos = json_array.find_last_of(kArrayEndCharacter); + if (array_start_pos == std::string::npos || + array_end_pos == std::string::npos) + return float_array; // Malformed JSON: missing '[' or ']'. + // Pull out the array elements. + size_t token_pos = array_start_pos + 1; + while (token_pos < array_end_pos) { + float_array.push_back(strtof(json_array.data() + token_pos, NULL)); + size_t delim_pos = json_array.find_first_of(kArrayDelimiter, token_pos); + if (delim_pos == std::string::npos) + break; + token_pos = delim_pos + 1; + } + return float_array; +} +} // namespace + +namespace tumbler { + +Tumbler::Tumbler(PP_Instance instance) + : pp::Instance(instance), + cube_(NULL) { +} + +Tumbler::~Tumbler() { + // Destroy the cube view while GL context is current. + opengl_context_->MakeContextCurrent(this); + delete cube_; +} + +bool Tumbler::Init(uint32_t /* argc */, + const char* /* argn */[], + const char* /* argv */[]) { + // Add all the methods to the scripting bridge. + ScriptingBridge::SharedMethodCallbackExecutor set_orientation_method( + new tumbler::MethodCallback( + this, &Tumbler::SetCameraOrientation)); + scripting_bridge_.AddMethodNamed("setCameraOrientation", + set_orientation_method); + return true; +} + +void Tumbler::HandleMessage(const pp::Var& message) { + if (!message.is_string()) + return; + scripting_bridge_.InvokeMethod(message.AsString()); +} + +void Tumbler::DidChangeView(const pp::Rect& position, const pp::Rect& clip) { + int cube_width = cube_ ? cube_->width() : 0; + int cube_height = cube_ ? cube_->height() : 0; + if (position.size().width() == cube_width && + position.size().height() == cube_height) + return; // Size didn't change, no need to update anything. + + if (opengl_context_ == NULL) + opengl_context_.reset(new OpenGLContext(this)); + opengl_context_->InvalidateContext(this); + if (!opengl_context_->MakeContextCurrent(this)) + return; + if (cube_ == NULL) { + cube_ = new Cube(opengl_context_); + cube_->PrepareOpenGL(); + } + cube_->Resize(position.size().width(), position.size().height()); + DrawSelf(); +} + +void Tumbler::DrawSelf() { + if (cube_ == NULL || opengl_context_ == NULL) + return; + opengl_context_->MakeContextCurrent(this); + cube_->Draw(); + opengl_context_->FlushContext(); +} + +void Tumbler::SetCameraOrientation( + const tumbler::ScriptingBridge& bridge, + const tumbler::MethodParameter& parameters) { + // |parameters| is expected to contain one object named "orientation", whose + // value is a JSON string that represents an array of four floats. + if (parameters.size() != 1 || cube_ == NULL) + return; + std::string orientation_desc = GetParameterNamed("orientation", parameters); + std::vector orientation = CreateArrayFromJSON(orientation_desc); + if (orientation.size() != kQuaternionElementCount) { + return; + } + cube_->SetOrientation(orientation); + DrawSelf(); +} +} // namespace tumbler + diff --git a/Demos/NativeClient/tumbler.h b/Demos/NativeClient/tumbler.h new file mode 100644 index 000000000..e29d3535d --- /dev/null +++ b/Demos/NativeClient/tumbler.h @@ -0,0 +1,64 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef EXAMPLES_TUMBLER_TUMBLER_H_ +#define EXAMPLES_TUMBLER_TUMBLER_H_ + +#include +#include +#include + +#include "cube.h" +#include "opengl_context.h" +#include "opengl_context_ptrs.h" +#include "scripting_bridge.h" +#include "ppapi/cpp/instance.h" + +namespace tumbler { + +class Tumbler : public pp::Instance { + public: + explicit Tumbler(PP_Instance instance); + + // The dtor makes the 3D context current before deleting the cube view, then + // destroys the 3D context both in the module and in the browser. + virtual ~Tumbler(); + + // Called by the browser when the NaCl module is loaded and all ready to go. + virtual bool Init(uint32_t argc, const char* argn[], const char* argv[]); + + // Called whenever the in-browser window changes size. + virtual void DidChangeView(const pp::Rect& position, const pp::Rect& clip); + + // Called by the browser to handle the postMessage() call in Javascript. + virtual void HandleMessage(const pp::Var& message); + + // Bind and publish the module's methods to JavaScript. + void InitializeMethods(ScriptingBridge* bridge); + + // Set the camera orientation to the quaternion in |args[0]|. |args| must + // have length at least 1; the first element is expeted to be an Array + // object containing 4 floating point number elements (the quaternion). + // This method is bound to the JavaScript "setCameraOrientation" method and + // is called like this: + // module.setCameraOrientation([0.0, 1.0, 0.0, 0.0]); + void SetCameraOrientation( + const tumbler::ScriptingBridge& bridge, + const tumbler::MethodParameter& parameters); + + // Called to draw the contents of the module's browser area. + void DrawSelf(); + + private: + // Browser connectivity and scripting support. + ScriptingBridge scripting_bridge_; + + SharedOpenGLContext opengl_context_; + // Wouldn't it be awesome if we had boost::scoped_ptr<>? + Cube* cube_; +}; + +} // namespace tumbler + +#endif // EXAMPLES_TUMBLER_TUMBLER_H_ diff --git a/Demos/NativeClient/tumbler_module.cc b/Demos/NativeClient/tumbler_module.cc new file mode 100644 index 000000000..e58d73e46 --- /dev/null +++ b/Demos/NativeClient/tumbler_module.cc @@ -0,0 +1,45 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "tumbler.h" +#include "ppapi/cpp/instance.h" +#include "ppapi/cpp/module.h" +#include "ppapi/gles2/gl2ext_ppapi.h" + +/// The Module class. The browser calls the CreateInstance() method to create +/// an instance of your NaCl module on the web page. The browser creates a new +/// instance for each tag with type="application/x-nacl". +class TumberModule : public pp::Module { + public: + TumberModule() : pp::Module() {} + virtual ~TumberModule() { + glTerminatePPAPI(); + } + + /// Called by the browser when the module is first loaded and ready to run. + /// This is called once per module, not once per instance of the module on + /// the page. + virtual bool Init() { + return glInitializePPAPI(get_browser_interface()) == GL_TRUE; + } + + /// Create and return a Tumbler instance object. + /// @param[in] instance The browser-side instance. + /// @return the plugin-side instance. + virtual pp::Instance* CreateInstance(PP_Instance instance) { + return new tumbler::Tumbler(instance); + } +}; + +namespace pp { +/// Factory function called by the browser when the module is first loaded. +/// The browser keeps a singleton of this module. It calls the +/// CreateInstance() method on the object you return to make instances. There +/// is one instance per tag on the page. This is the main binding +/// point for your NaCl module with the browser. +Module* CreateModule() { + return new TumberModule(); +} +} // namespace pp + diff --git a/msvc/nacl.sh b/msvc/nacl.sh new file mode 100644 index 000000000..f319bf777 --- /dev/null +++ b/msvc/nacl.sh @@ -0,0 +1,15 @@ + +echo Only tested using a Cygwin Bash Shell with make.exe available + +export AR=/cygdrive/f/sdks/native_client_sdk_0_5_984/toolchain/win_x86/bin/nacl-ar.exe +export CC=/cygdrive/f/sdks/native_client_sdk_0_5_984/toolchain/win_x86/bin/nacl-gcc.exe +export CXX=/cygdrive/f/sdks/native_client_sdk_0_5_984/toolchain/win_x86/bin/nacl-g++.exe + +./premake4 --with-nacl gmake +cd gmake +export config=release32 +make + +export config=release64 +make + diff --git a/msvc/premake4.lua b/msvc/premake4.lua index 1d7099367..a4ff2b956 100644 --- a/msvc/premake4.lua +++ b/msvc/premake4.lua @@ -63,15 +63,18 @@ end include "../src/BulletMultiThreaded/GpuSoftBodySolvers/DX11" end + + if not _OPTIONS["with-dx11"] and not _OPTIONS["with-nacl"] then include "../Demos" include "../Extras" end - include "../src/LinearMath" - include "../src/BulletCollision" - include "../src/BulletDynamics" - include "../src/BulletSoftBody" - - - + if _OPTIONS["with-nacl"] then + include "../Demos/NativeClient" + else + include "../src/LinearMath" + include "../src/BulletCollision" + include "../src/BulletDynamics" + include "../src/BulletSoftBody" + end