summaryrefslogtreecommitdiffstats
path: root/o3d/samples/juggler.html
diff options
context:
space:
mode:
Diffstat (limited to 'o3d/samples/juggler.html')
-rw-r--r--o3d/samples/juggler.html431
1 files changed, 431 insertions, 0 deletions
diff --git a/o3d/samples/juggler.html b/o3d/samples/juggler.html
new file mode 100644
index 0000000..3dc01f1
--- /dev/null
+++ b/o3d/samples/juggler.html
@@ -0,0 +1,431 @@
+<!--
+Copyright 2009, Google Inc.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+ * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+-->
+
+<!--
+O3D Juggler
+-->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+<html style="width: 100%; height: 100%;">
+<head>
+<meta http-equiv="content-type" content="text/html; charset=UTF-8">
+<title>
+ Juggler Shader
+</title>
+
+<script type="text/javascript" src="o3djs/base.js"></script>
+
+<script type="text/javascript">
+o3djs.require('o3djs.util');
+o3djs.require('o3djs.math');
+o3djs.require('o3djs.rendergraph');
+o3djs.require('o3djs.primitives');
+
+// Events
+// Run the init() function once the page has finished loading.
+// unload() when the page is unloaded.
+window.onload = init;
+window.onunload = unload;
+// global variables
+var g_o3d;
+var g_math;
+var g_client;
+var g_o3dElement;
+var g_viewInfo;
+var g_pack;
+var g_o3dWidth = -1;
+var g_o3dHeight = -1;
+var g_transform;
+var g_clock = 0.0;
+var g_timeMult = 1; // amount to multiply elapsed time by.
+ // Used to make the animation run faster or slower.
+var g_finished = false; // for selenium testing
+var g_thetaParam;
+var g_numParam;
+var g_numBalls; // Must be either 3, 5, 7, or 9 for now.
+var g_speedScale; // Used to make higher numbers of balls animate faster.
+
+/**
+ * Creates the client area.
+ */
+function init() {
+ o3djs.util.makeClients(initStep2);
+}
+
+/**
+ * Initializes O3D, loads the effect, and creates the square.
+ * @param {Array} clientElements Array of o3d object elements.
+ */
+function initStep2(clientElements) {
+ // Initialize global variables and libraries.
+ g_o3dElement = clientElements[0];
+ g_o3d = g_o3dElement.o3d;
+ g_math = o3djs.math;
+ g_client = g_o3dElement.client;
+
+ // Create a g_pack to manage our resources/assets
+ g_pack = g_client.createPack();
+
+ // Create the render graph for a view.
+ g_viewInfo = o3djs.rendergraph.createBasicView(
+ g_pack,
+ g_client.root,
+ g_client.renderGraphRoot,
+ [0, 0, 0, 1]);
+
+ var effect = g_pack.createObject('Effect');
+ effect.loadFromFXString(document.getElementById('shader').value);
+
+ // Create a Material for the effect.
+ var myMaterial = g_pack.createObject('Material');
+
+ // Apply our effect to this material.
+ myMaterial.effect = effect;
+
+ // Set the material's drawList for opaque objects.
+ myMaterial.drawList = g_viewInfo.performanceDrawList;
+
+ // Create the params the effect needs on the material.
+ effect.createUniformParameters(myMaterial);
+
+ // Create a square.
+ var myShape = o3djs.primitives.createPlane(g_pack, myMaterial,
+ 1, 1, 1, 1);
+
+ // Set up the individual parameters in our effect file.
+ g_thetaParam = myMaterial.getParam('theta');
+ g_thetaParam.value = 0;
+ g_numParam = myMaterial.getParam('num');
+ updateNum();
+
+ // Set the position of the camera.
+ g_viewInfo.drawContext.view = g_math.matrix4.lookAt(
+ [0, 1, 0], //eye
+ [0, 0, 0], //target
+ [0, 0, -1]); //up
+
+ // Generate the projection matrix based
+ // on the g_o3d plugin size by calling resize().
+ resize();
+
+ // Now attach the square to the root of the transform graph.
+ g_client.root.addShape(myShape);
+
+ toggleRenderCallback();
+
+ g_finished = true; // for selenium testing.
+}
+
+function updateNum() {
+ var group = document.the_form.radio_group;
+ for (var i = 0; i < group.length; ++i) {
+ if (group[i].checked) {
+ setNumBalls(parseInt(group[i].value));
+ }
+ }
+}
+
+function toggleRenderCallback() {
+ var box = document.the_form.check_box;
+ if (box.checked) {
+ g_client.setRenderCallback(onrender);
+ } else {
+ g_client.clearRenderCallback();
+ }
+}
+
+function setNumBalls(num) {
+ g_numBalls = num;
+ g_numParam.value = g_numBalls;
+ g_speedScale = Math.sqrt(g_numBalls) * 5;
+}
+
+function onrender(render_event) {
+ g_clock += render_event.elapsedTime * g_timeMult;
+ g_thetaParam.value = g_clock * g_speedScale;
+
+ // If we don't check the size of the client area every frame we don't get a
+ // chance to adjust the perspective matrix fast enough to keep up with the
+ // browser resizing us, so onrender must call resize.
+ resize();
+}
+
+
+// Generates the projection matrix based on the size of the g_o3d plugin
+// and calculates the view-projection matrix.
+function resize() {
+ var newWidth = g_client.width;
+ var newHeight = g_client.height;
+
+ if (newWidth != g_o3dWidth || newHeight != g_o3dHeight) {
+ g_o3dWidth = newWidth;
+ g_o3dHeight = newHeight;
+
+ // Determine what the size of the rendered square within the client should
+ // be in pixels.
+ var side = g_o3dWidth < g_o3dHeight ?
+ g_o3dWidth : g_o3dHeight;
+
+ // Convert to the region of world space that must be enclosed by the
+ // orthographic projection.
+ var worldSize = g_math.divVectorScalar([g_o3dWidth, g_o3dHeight], side);
+
+ // Find a projection matrix to transform from world space to screen space.
+ g_viewInfo.drawContext.projection = o3djs.math.matrix4.orthographic(
+ -0.5 * worldSize[0], 0.5 * worldSize[0],
+ -0.5 * worldSize[1], 0.5 * worldSize[1],
+ 0.5, 1.5);
+ }
+}
+
+/**
+ * Removes any callbacks so they don't get called after the page has unloaded.
+ */
+function unload() {
+ if (g_client) {
+ g_client.cleanup();
+ }
+}
+</script>
+</head>
+<body style="width: 100%; height: 100%;">
+<table style="width: 100%; height: 100%;">
+ <tr>
+ <td>
+ <h1>Juggler</h1>
+ <p>
+ This sample displays a juggling pattern computed entirely in a shader.
+ <form name="the_form">
+ <input type="radio" name="radio_group" value="3"
+ onclick=updateNum()>3 Balls
+ <input type="radio" name="radio_group" value="5" checked
+ onclick=updateNum()>5 Balls
+ <input type="radio" name="radio_group" value="7"
+ onclick=updateNum()>7 Balls
+ <input type="radio" name="radio_group" value="9"
+ onclick=updateNum()>9 Balls
+ <input type="checkbox" name="check_box" checked
+ onclick=toggleRenderCallback()>Animate
+ </form>
+ </p>
+ <table id="container" style="width: 100%; height: 80%;">
+ <tr>
+ <td height="100%">
+ <!-- Start of g_o3d plugin -->
+ <div id="o3d" style="width: 100%; height: 100%;"></div>
+ <!-- End of g_o3d plugin -->
+ </td>
+ </tr>
+ </table>
+ <!-- a simple way to get a multiline string -->
+ <textarea id="shader" name="shader" cols="80" rows="20"
+ style="display: none;">
+// The 4x4 world view projection matrix.
+float4x4 worldViewProjection : WORLDVIEWPROJECTION;
+
+float theta;
+float num;
+
+// input parameters for our vertex shader
+struct VertexShaderInput {
+ float4 position : POSITION;
+ float2 texCoord : TEXCOORD0;
+};
+
+// input parameters for our pixel shader
+// also the output parameters for our vertex shader
+struct PixelShaderInput {
+ float4 position : POSITION;
+ float2 texCoord : TEXCOORD0;
+ float4 color : COLOR;
+};
+
+/**
+ * vertexShaderMain - our vertex shader for the juggling texture
+ */
+PixelShaderInput vertexShaderMain(VertexShaderInput input) {
+ PixelShaderInput output;
+
+ output.position = mul(input.position, worldViewProjection);
+ output.texCoord = 4.0 * (input.texCoord - float2(0.5, 0.5));
+ output.color = float4(1, 1, 1, 1);
+
+ return output;
+}
+
+float length_2(in float2 v) {
+ return dot(v, v);
+}
+
+// Draw the balls in a single arc.
+float drawBallsInArc(in float pi,
+ in float4 offset,
+ in float2 source_hand,
+ in float2 dest_hand,
+ in float height_factor,
+ in float baseline,
+ in float ball_radius_2,
+ in float hand_throw_offset,
+ in float2 Z,
+ in float threshold) {
+ // Map theta from its current range of [0, 2 * num * pi) onto [0, (num - 1))
+ // by scaling, adding offset, and modding, then map that to [0, 1) by scaling.
+ // The first mapping tells us where in the repeating cycle we are, and the
+ // second mapping simplifies the calculation of the parabola.
+
+ // The vector offset is used to distinguish between balls in the same arc, but
+ // out of phase. At the beginning of this function, all the operations are
+ // vectorized to save instructions; we get to calculate 4 ball positions for
+ // the price of 1.
+
+ // The reason for the (num - 1) in the expression below is that with num
+ // balls, each ball spends (num - 1) beats in the air, then one in the hand.
+ // So (num - 1) is the length of time a parabola takes.
+
+ float4 time = fmod(theta / pi + offset, (num - 1)) / (num - 1);
+ float dx = dest_hand.x - source_hand.x;
+ float4 x = time * dx + source_hand.x - hand_throw_offset;
+ float4 y = time * (1 - time);
+ y = y * height_factor + baseline;
+ float4 ZX = Z.x;
+ float4 ZY = Z.y;
+ float4 len_2 = (ZX - x) * (ZX - x) + (ZY - y) * (ZY - y);
+ float4 temp = clamp((len_2 - ball_radius_2) / threshold, 0, 1);
+
+ // One minus the product of all entries in temp.
+ temp.xy = temp.xy * temp.zw;
+ return 1 - temp.x * temp.y;
+}
+
+float drawAirborneBalls(in float pi,
+ in float4 offset,
+ in float2 right_hand,
+ in float2 left_hand,
+ in float height_factor,
+ in float baseline,
+ in float ball_radius_2,
+ in float hand_swing_radius,
+ in float2 Z,
+ in float threshold) {
+ // The expression below is one minus a product. The first factor in the
+ // product is responsible for balls moving from right to left, and the second
+ // factor is responsible for balls moving from left to right.
+ return 1 -
+ (1 - drawBallsInArc(pi, offset, right_hand, left_hand, height_factor,
+ baseline, ball_radius_2, hand_swing_radius, Z, threshold)) *
+ (1 - drawBallsInArc(pi, offset + 1, left_hand, right_hand, height_factor,
+ baseline, ball_radius_2, -hand_swing_radius, Z, threshold));
+}
+
+/**
+ * pixelShaderMain - pixel shader
+ */
+
+float4 pixelShaderMain(PixelShaderInput input) : COLOR {
+ const float pi = 3.14159265;
+ const float baseline = -1.2;
+ const float2 right_hand = float2(0.8, baseline);
+ const float2 left_hand = float2(-0.8, baseline);
+ const float hand_swing_radius = 0.3;
+ const float hand_radius_2 = 0.15 * 0.15;
+ const float ball_radius_2 = 0.1 * 0.1;
+
+ const float4 ball_color = float4(1, 1, 1, 1);
+ const float4 right_hand_color = float4(1, 0, 0, 1);
+ const float4 left_hand_color = float4(0, 0, 1, 1);
+ const float4 background_color = float4(0, 0, 0, 0);
+
+ const float threshold = 0.002; // Used in clamp for antialiasing.
+
+ float height_factor = num;
+
+ float2 Z = input.texCoord;
+
+ // Coerce to the range [0, 2 * Pi * num].
+ float2 r_h = hand_swing_radius * float2(-cos(theta), sin(theta)) + right_hand;
+ float2 l_h = hand_swing_radius * float2(-cos(theta), -sin(theta)) + left_hand;
+
+ // Initialize color of pixel to background_color. Background color has an
+ // alpha of 0. Color of objects each have alpha 1, so multiplying by
+ // (1-alpha) before adding the color ensures that nothing gets overdrawn.
+ // It's kind of like a rudimentary z-buffer.
+ float4 result = background_color;
+
+ // Draw the hands.
+ result += (1 - result.a) * (
+ clamp((hand_radius_2 - length_2(Z - r_h)) / threshold, 0, 1) *
+ (Z.y < r_h.y) * right_hand_color +
+ clamp((hand_radius_2 - length_2(Z - l_h)) / threshold, 0, 1) *
+ (Z.y < l_h.y) * left_hand_color);
+
+ // Draw the ball in the hand. There is always a ball in exactly one hand, and
+ // which hand that is alternates.
+ float2 hand;
+ if (fmod(floor(theta / pi), 2) > 0.5) {
+ hand = r_h;
+ } else {
+ hand = l_h;
+ }
+ result += (1 - result.a) * ball_color *
+ clamp((ball_radius_2 - length_2(Z - hand)) / threshold, 0, 1);
+
+ float4 offset = float4(0, 2, 4, 6);
+
+ // For each of up-to-4 pairs of balls you want to add, increment offsets by
+ // (8, 8, 8, 8) and call drawAirborneBalls again.
+
+ // Draw airborne balls.
+ result += (1 - result.a) * drawAirborneBalls(pi,
+ offset,
+ right_hand,
+ left_hand,
+ height_factor,
+ baseline,
+ ball_radius_2,
+ hand_swing_radius,
+ Z,
+ threshold);
+
+ return result;
+}
+
+// Here we tell our effect file *which* functions are
+// our vertex and pixel shaders.
+
+// #o3d VertexShaderEntryPoint vertexShaderMain
+// #o3d PixelShaderEntryPoint pixelShaderMain
+// #o3d MatrixLoadOrder RowMajor
+ </textarea>
+ </td>
+ </tr>
+</table>
+</body>
+</html>