Created
January 27, 2015 00:07
-
-
Save ya-s-u/1fe439233e95e00d3131 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
o3djs.base.o3d = o3d; | |
o3djs.require('o3djs.webgl'); | |
o3djs.require('o3djs.util'); | |
o3djs.require('o3djs.math'); | |
o3djs.require('o3djs.rendergraph'); | |
o3djs.require('o3djs.primitives'); | |
o3djs.require('o3djs.quaternions'); | |
o3djs.require('o3djs.effect'); | |
o3djs.require('o3djs.event'); | |
var SHADOWPOV = false; // Shows the rendertarget where the shadows get drawn. | |
// global variables | |
var g_o3dElement; | |
var g_client; | |
var g_o3d; | |
var g_math; | |
var g_quat; | |
var g_pack; | |
var g_viewInfo; | |
var g_clock = 0; | |
var g_shadowPassViewInfo; | |
var g_ballTransforms = []; | |
var g_centers = []; | |
var g_ballTextures = []; | |
var g_ballTextureSamplers = []; | |
var g_ballTextureSamplerParams = []; | |
var g_shadowOnParams = []; | |
var g_shadowSampler; | |
var g_shadowTexture; | |
var g_tableRoot; | |
var g_shadowRoot; | |
var g_hudRoot; | |
var g_barRoot; | |
var g_physics; | |
var g_target = [0, 0, 0]; | |
var g_light = [0, 0, 50]; | |
var g_materials; | |
var g_solidMaterial; | |
var RENDER_TARGET_WIDTH = 512; | |
var RENDER_TARGET_HEIGHT = 512; | |
var g_queueClock = 0; | |
var g_rolling = false; | |
var g_shooting = false; | |
var g_table = null; | |
g_queue = []; | |
// g_queue is a list of commands to run at various time intervals, | |
// it is supposed to be used one day to implement an opponent. For now, | |
// comment this in to allow the AI to play. | |
/* | |
var g_queue = [ | |
{condition: '!(g_shooting || g_rolling)', | |
action: 'cueNewShot(.9);'} | |
]; | |
*/ | |
// Audio stuff | |
var context = 0; | |
var compressor = 0; | |
var pingBuffer = 0; | |
var info = 0; | |
var ballBallLight = 0; | |
var ballBallMedium = 0; | |
var ballBallHard = 0; | |
var stickCueLight = 0; | |
var stickCueMedium = 0; | |
var stickCueHard = 0; | |
var ballPocket = 0; | |
var mediumEdge = 0; | |
var hardEdge = 0; | |
var softCount = 0; | |
var mediumCount = 0; | |
var hardCount = 0; | |
// Temporary patch until all browsers support unprefixed context. | |
if (window.hasOwnProperty('AudioContext') && !window.hasOwnProperty('webkitAudioContext')) | |
window.webkitAudioContext = AudioContext; | |
function updateInfo() { | |
var info = document.getElementById('info'); | |
var s = "soft = " + softCount + " : medium = " + mediumCount + " : hard = " + hardCount; | |
info.innerHTML = s; | |
} | |
function loadStickCueLight(url) { | |
// Load asynchronously | |
var request = new XMLHttpRequest(); | |
request.open("GET", url, true); | |
request.responseType = "arraybuffer"; | |
request.onload = function() { | |
context.decodeAudioData(request.response, function(buffer) { stickCueLight = buffer; }); | |
} | |
request.send(); | |
} | |
function loadStickCueMedium(url) { | |
// Load asynchronously | |
var request = new XMLHttpRequest(); | |
request.open("GET", url, true); | |
request.responseType = "arraybuffer"; | |
request.onload = function() { | |
context.decodeAudioData(request.response, function(buffer) { stickCueMedium = buffer; }); | |
} | |
request.send(); | |
} | |
function loadStickCueHard(url) { | |
// Load asynchronously | |
var request = new XMLHttpRequest(); | |
request.open("GET", url, true); | |
request.responseType = "arraybuffer"; | |
request.onload = function() { | |
context.decodeAudioData(request.response, function(buffer) { stickCueHard = buffer; }); | |
} | |
request.send(); | |
} | |
function loadBallPocket(url) { | |
// Load asynchronously | |
var request = new XMLHttpRequest(); | |
request.open("GET", url, true); | |
request.responseType = "arraybuffer"; | |
request.onload = function() { | |
context.decodeAudioData(request.response, function(buffer) { ballPocket = buffer; }); | |
} | |
request.send(); | |
} | |
function loadBallBallLight(url) { | |
// Load asynchronously | |
var request = new XMLHttpRequest(); | |
request.open("GET", url, true); | |
request.responseType = "arraybuffer"; | |
request.onload = function() { | |
context.decodeAudioData(request.response, function(buffer) { ballBallLight = buffer; }); | |
} | |
request.send(); | |
} | |
function loadBallBallMedium(url) { | |
// Load asynchronously | |
var request = new XMLHttpRequest(); | |
request.open("GET", url, true); | |
request.responseType = "arraybuffer"; | |
request.onload = function() { | |
context.decodeAudioData(request.response, function(buffer) { ballBallMedium = buffer; }); | |
} | |
request.send(); | |
} | |
function loadBallBallHard(url) { | |
// Load asynchronously | |
var request = new XMLHttpRequest(); | |
request.open("GET", url, true); | |
request.responseType = "arraybuffer"; | |
request.onload = function() { | |
context.decodeAudioData(request.response, function(buffer) { ballBallHard = buffer; }); | |
} | |
request.send(); | |
} | |
function loadMediumEdge(url) { | |
// Load asynchronously | |
var request = new XMLHttpRequest(); | |
request.open("GET", url, true); | |
request.responseType = "arraybuffer"; | |
request.onload = function() { | |
context.decodeAudioData(request.response, function(buffer) { mediumEdge = buffer; }); | |
} | |
request.send(); | |
} | |
function loadHardEdge(url) { | |
// Load asynchronously | |
var request = new XMLHttpRequest(); | |
request.open("GET", url, true); | |
request.responseType = "arraybuffer"; | |
request.onload = function() { | |
context.decodeAudioData(request.response, function(buffer) { hardEdge = buffer; }); | |
} | |
request.send(); | |
} | |
function loadImpulseResponse(url) { | |
// Load impulse response asynchronously | |
var request = new XMLHttpRequest(); | |
request.open("GET", url, true); | |
request.responseType = "arraybuffer"; | |
request.onload = function() { | |
context.decodeAudioData(request.response, function(buffer) { convolver.buffer = buffer; }); | |
} | |
request.send(); | |
} | |
function playSound(impactType, velocity, x, y) { | |
var buffer = 0; | |
var gainScale = 1.0; | |
var sendScale = 1.0; | |
if (impactType == 0) { | |
if (velocity < 0.36) { | |
buffer = ballBallLight; | |
// softCount++; | |
// updateInfo(); | |
} else if (velocity < 0.66) { | |
buffer = ballBallMedium; | |
// mediumCount++; | |
// updateInfo(); | |
} else { | |
buffer = ballBallHard; | |
// hardCount++; | |
// updateInfo(); | |
} | |
} else if (impactType == 1) { | |
if (velocity < 0.66666) { | |
buffer = mediumEdge; | |
} else { | |
buffer = hardEdge; | |
} | |
gainScale = 0.2; | |
sendScale = 0.01; | |
} else if (impactType == 2) { | |
if (velocity < 0.36) { | |
buffer = stickCueLight; | |
} else if (velocity < 0.66) { | |
buffer = stickCueMedium; | |
} else { | |
buffer = stickCueHard; | |
} | |
gainScale = 1; | |
sendScale = 1.0; | |
} else if (impactType == 3) { | |
buffer = ballPocket; | |
gainScale = 0.5; | |
sendScale = 2.0; | |
} | |
var voice = context.createBufferSource(); | |
if (voice) { | |
voice.buffer = buffer; | |
// base volume on velocity | |
var xx = velocity; | |
if (xx > 1.0) xx = 1.0; | |
if (xx < 0.0) xx = 0.0; | |
var s = Math.sin(0.5 * xx * Math.PI); | |
s = s*s; | |
var gain = gainScale * s; | |
var isQuiet = (gain < 0.5); | |
// Setup send and main gains | |
var sendGain = context.createGain(); | |
var mainGain = context.createGain(); | |
// Use biquad filter API if available. | |
var filter = context.createBiquadFilter(); | |
sendGain.gain.value = sendScale; | |
mainGain.gain.value = gain; | |
voice.connect(filter); | |
filter.connect(sendGain); | |
filter.connect(mainGain); | |
sendGain.connect(convolver); | |
mainGain.connect(compressor); | |
// // Randomize pitch | |
var r = Math.random(); | |
var cents = 600.0 * (r - 0.5); | |
var rate = Math.pow(2.0, cents / 1200.0); | |
voice.playbackRate.value = rate; // really rate (not pitch) | |
// | |
// Adjust filter | |
var value = 0.5 + 0.5 * xx; | |
var noctaves = Math.log(22050.0 / 40.0) / Math.LN2; | |
var v2 = Math.pow(2.0, noctaves * (value - 1.0)); | |
var sampleRate = 44100.0; | |
var nyquist = sampleRate * 0.5; | |
filter.type = "lowpass"; | |
filter.frequency.value = v2 * nyquist; | |
filter.Q.value = 0.0; // this is actually resonance in dB | |
var azimuth = 0.5*Math.PI * (x - 200.0 /*250.0*/) / 150.0; | |
if (azimuth < -0.5*Math.PI) azimuth = -0.5*Math.PI; | |
if (azimuth > 0.5*Math.PI) azimuth = 0.5*Math.PI; | |
var posX = 10.0 * Math.sin(azimuth); | |
var posZ = 10.0 * Math.cos(azimuth); | |
var elevation = -0.5*Math.PI * (y - 250.0) / 150.0; | |
if (elevation < -0.5*Math.PI) elevation = -0.5*Math.PI; | |
if (elevation > 0.5*Math.PI) elevation = 0.5*Math.PI; | |
var scaleY = Math.sin(elevation); | |
var scaleXZ = Math.cos(elevation); | |
posX *= scaleXZ; | |
posZ *= scaleXZ; | |
var posY = scaleY * 10.0; | |
// FIXME: add panner back in... | |
// voice.setPosition(posX, posY, isQuiet ? +posZ : -posZ); | |
if (impactType == 3) { | |
voice.start(context.currentTime + 0.125); | |
} else { | |
voice.start(0); | |
} | |
} | |
} | |
var pool = {}; | |
function myMod(n, m) { | |
return ((n % m) + m) % m; | |
} | |
pool.Ball = function() { | |
this.mass = 1.0; | |
this.angularInertia = 0.4; | |
this.center = [0, 0, 0]; | |
this.velocity = [0, 0, 0]; | |
this.verticalAcceleration = 0; | |
this.orientation = [0, 0, 0, 1]; | |
this.angularVelocity = [0, 0, 0]; | |
this.active = true; | |
this.sunkInPocket = -1; | |
}; | |
pool.Physics = function() { | |
this.record = []; | |
this.speedFactor = 0; | |
this.maxSpeed = 1; | |
this.balls = []; | |
for (var i = 0; i < 16; ++i) { | |
this.balls.push(new pool.Ball()); | |
} | |
// The cue ball is slightly heavier | |
// than the rest of the balls. | |
// 6 ounces versus 5.5. | |
this.balls[0].mass *= 6.0 / 5.5; | |
this.balls[0].rotationalInertia *= 6.0 / 5.5; | |
this.walls = []; | |
this.collisions = []; | |
this.wallCollisions = []; | |
this.placeBalls = function() { | |
for (var i = 0; i < 16; ++i) { | |
var ball = this.balls[i]; | |
if (!ball.active) { | |
g_shadowOnParams[i].value = 0; | |
continue; | |
} | |
var p = ball.center; | |
placeBall(i, p[0], p[1], p[2], ball.orientation); | |
} | |
}; | |
this.step = function() { | |
for (var i = 0; i < 5; ++i) { | |
this.ballsLoseEnergy(); | |
this.ballsImpactFloor(); | |
this.move(1); | |
while (this.collide()) { | |
this.move(-1); | |
this.handleCollisions(); | |
this.move(1); | |
} | |
} | |
this.sink(); | |
this.handleFalling(); | |
this.placeBalls(); | |
}; | |
this.move = function(timeStep) { | |
for (var i = 0; i < 16; ++i) { | |
var ball = this.balls[i]; | |
if (!ball.active) | |
continue; | |
var p = ball.center; | |
var v = ball.velocity; | |
p[0] += timeStep * v[0]; | |
p[1] += timeStep * v[1]; | |
p[2] += timeStep * v[2]; | |
ball.orientation = this.quat.normalize(this.quat.mul( | |
vectorToQuaternion(this.math.mulScalarVector( | |
timeStep, ball.angularVelocity)), ball.orientation)); | |
v[2] += ball.verticalAcceleration; | |
} | |
}; | |
this.impartSpeed = function(i, direction) { | |
var ball = this.balls[i]; | |
ball.velocity[0] += direction[0] * this.maxSpeed * this.speedFactor; | |
ball.velocity[1] += direction[1] * this.maxSpeed * this.speedFactor; | |
}; | |
this.stopAllBalls = function() { | |
for (var i = 0; i < 16; ++i) { | |
var ball = this.balls[i]; | |
var v = ball.velocity; | |
var w = ball.angularVelocity; | |
v[0] = 0; | |
v[1] = 0; | |
v[2] = 0; | |
w[0] = 0; | |
w[1] = 0; | |
w[2] = 0; | |
} | |
}; | |
this.stopSlowBalls = function() { | |
var epsilon = 0.0001; | |
for (var i = 0; i < 16; ++i) { | |
var ball = this.balls[i]; | |
if (!ball.active) | |
continue; | |
var v = ball.velocity; | |
var w = ball.angularVelocity; | |
if (this.math.length(v) < epsilon) { | |
v[0] = 0; | |
v[1] = 0; | |
v[2] = 0; | |
} | |
if (this.math.length(w) < epsilon) { | |
w[0] = 0; | |
w[1] = 0; | |
w[2] = 0; | |
} | |
} | |
}; | |
this.someBallsMoving = function() { | |
for (var i = 0; i < 16; ++i) { | |
var ball = this.balls[i]; | |
if (!ball.active) | |
continue; | |
var v = ball.velocity; | |
var w = ball.angularVelocity; | |
if (v[0] != 0 || v[1] != 0 || v[2] != 0 || | |
w[0] != 0 || w[1] != 0 || w[2] != 0) | |
return true; | |
} | |
return false; | |
}; | |
this.sink = function() { | |
for (var i = 0; i < 16; ++i) { | |
var ball = this.balls[i]; | |
if (!ball.active) | |
continue; | |
var p = this.balls[i].center; | |
for (var j = 0; j < this.pocketCenters.length; ++j) { | |
var pocketCenter = this.pocketCenters[j]; | |
var dx = p[0] - pocketCenter[0]; | |
var dy = p[1] - pocketCenter[1]; | |
if (dx * dx + dy * dy < | |
this.pocketRadius * this.pocketRadius) { | |
ball.verticalAcceleration = -0.005; | |
ball.sunkInPocket = j; | |
} | |
} | |
} | |
}; | |
this.handleFalling = function() { | |
for (var i = 0; i < 16; ++i) { | |
var ball = this.balls[i]; | |
if (!ball.active) | |
continue; | |
if (ball.sunkInPocket >= 0) { | |
var p = ball.center; | |
var z = p[2]; | |
var pocketCenter = this.pocketCenters[ball.sunkInPocket]; | |
var dx = p[0] - pocketCenter[0]; | |
var dy = p[1] - pocketCenter[1]; | |
// Once the ball is sunk, it must not escape the pocket. | |
var norm = Math.sqrt(dx * dx + dy * dy); | |
var maxNorm = | |
this.pocketRadius - Math.sqrt(Math.max(0, 1 - (z + 1) * (z + 1))); | |
if (norm > maxNorm) { | |
p[0] = pocketCenter[0] + dx * maxNorm / norm; | |
p[1] = pocketCenter[1] + dy * maxNorm / norm; | |
} | |
} | |
if (ball.center[2] < -3) { | |
var v = ball.velocity; | |
var w = ball.angularVelocity; | |
v[0] = 0; | |
v[1] = 0; | |
v[2] = 0; | |
w[0] = 0; | |
w[1] = 0; | |
w[2] = 0; | |
ball.verticalAcceleration = 0; | |
ball.active = false; | |
ballOff(i); | |
playSound(3, 0.75, 0,0); | |
} | |
} | |
}; | |
this.boundCueBall = function() { | |
var c = this.balls[0].center; | |
if (c[0] < this.left) | |
c[0] = this.left; | |
if (c[0] > this.right) | |
c[0] = this.right; | |
if (c[1] < this.bottom) | |
c[1] = this.bottom; | |
if (c[1] > this.top) | |
c[1] = this.top; | |
this.pushOut(); | |
this.placeBalls(); | |
}; | |
this.collide = function() { | |
this.collideBalls(); | |
this.collideWithWalls(); | |
return this.collisions.length != 0 || this.wallCollisions.length != 0; | |
}; | |
this.pushOut = function() { | |
while (this.collide()) { | |
this.pushCollisions(); | |
} | |
} | |
this.collideBalls = function() { | |
this.collisions = []; | |
for (var i = 0; i < 16; ++i) { | |
if (!this.balls[i].active) | |
continue; | |
var p1 = this.balls[i].center; | |
for (var j = 0; j < i; ++j) { | |
if (!this.balls[j].active) | |
continue; | |
var p2 = this.balls[j].center; | |
var dx = p1[0] - p2[0]; | |
var dy = p1[1] - p2[1]; | |
var normSquared = dx * dx + dy * dy; | |
if (normSquared < 3.99) { | |
var norm = Math.sqrt(normSquared) | |
this.collisions.push({i: i, j: j, ammt: 2 - norm}); | |
} | |
} | |
} | |
}; | |
this.initWalls = function() { | |
var r = this.pocketRadius; | |
var w = this.tableWidth; | |
// Three walls connecting the points in this list get put around each | |
// cushion. | |
var path = [[0, -w / 2 + r, 0], | |
[r, -w / 2 + 2 * r, 0], | |
[r, w / 2 - 2 * r, 0], | |
[0, w / 2 - r, 0]]; | |
var angles = [0, Math.PI/2, Math.PI, Math.PI, 3 * Math.PI / 2, 0]; | |
var translations = this.math.mulMatrixMatrix( | |
[[-1, -1, 0], [0, -2, 0], [1, -1, 0], [1, 1, 0], [0, 2, 0], [-1, 1, 0]], | |
[[w / 2, 0, 0], [0, w / 2, 0], [0, 0, 1]]); | |
for (var i = 0; i < 6; ++i) { | |
var newPath = []; | |
for (var j = 0; j < path.length; ++j) { | |
newPath.push( | |
this.math.matrix4.transformPoint(this.math.matrix4.composition( | |
this.math.matrix4.translation(translations[i]), | |
this.math.matrix4.rotationZ(angles[i])), path[j])); | |
} | |
for (var j = 0; j < newPath.length - 1; ++j) { | |
this.walls.push({p: [newPath[j][0], newPath[j][1]], | |
q: [newPath[j + 1][0], newPath[j + 1][1]]}); | |
} | |
} | |
this.computeWallNormals(this.walls); | |
}; | |
this.computeWallNormals = function(walls) { | |
for (var i = 0; i < walls.length; ++i) { | |
var wall = walls[i]; | |
var tangent = this.math.normalize(this.math.subVector(wall.q, wall.p)); | |
wall.nx = tangent[1]; | |
wall.ny = -tangent[0]; | |
wall.k = wall.nx * wall.p[0] + wall.ny * wall.p[1]; | |
wall.a = wall.p[1] * wall.nx - wall.p[0] * wall.ny; | |
wall.b = wall.q[1] * wall.nx - wall.q[0] * wall.ny; | |
} | |
} | |
this.collideWithWalls = | |
function(opt_wallList, opt_collisionList, opt_radius) { | |
var radius = opt_radius || 1.0; | |
var walls = opt_wallList || this.walls; | |
var wallCollisions = opt_collisionList || this.wallCollisions; | |
while(wallCollisions.length) { | |
wallCollisions.pop(); | |
} | |
for (var i = 0; i < 16; ++i) { | |
var ball = this.balls[i]; | |
if (!ball.active) | |
continue; | |
var p = ball.center; | |
var x = p[0]; | |
var y = p[1]; | |
if (!opt_wallList && | |
x > this.left && | |
x < this.right && | |
y > this.bottom && | |
y < this.top) { | |
continue; | |
} | |
for (var j = 0; j < walls.length; ++j) { | |
var wall = walls[j]; | |
var norm = Math.abs(x * wall.nx + y * wall.ny - wall.k); | |
if (norm < radius) { | |
var t = y * wall.nx - x * wall.ny; | |
if (t > wall.a && t < wall.b) { | |
wallCollisions.push({i: i, x: wall.nx, y: wall.ny, ammt: 1 - norm}); | |
break; | |
} else { | |
var dx = x - wall.p[0]; | |
var dy = y - wall.p[1]; | |
var normSquared = dx * dx + dy * dy; | |
if (normSquared < radius*radius) { | |
var norm = Math.sqrt(normSquared); | |
wallCollisions.push( | |
{i: i, x: dx / norm, y: dy / norm, ammt: 1 - norm}); | |
break; | |
} | |
var dx = x - wall.q[0]; | |
var dy = y - wall.q[1]; | |
var normSquared = dx * dx + dy * dy; | |
if (normSquared < radius*radius) { | |
var norm = Math.sqrt(normSquared); | |
wallCollisions.push( | |
{i: i, x: dx / norm, y: dy / norm, n: 1 - norm}); | |
break; | |
} | |
} | |
} | |
} | |
} | |
}; | |
this.pushCollisions = function() { | |
var l = this.wallCollisions.length; | |
for (var i = 0; i < l; ++i) { | |
var c = this.wallCollisions[i]; | |
var p = this.balls[c.i].center; | |
p[0] += c.ammt * c.x; | |
p[1] += c.ammt * c.y; | |
} | |
var l = this.collisions.length; | |
for (var i = 0; i < l; ++i) { | |
var c = this.collisions[i]; | |
var pi = this.balls[c.i].center; | |
var pj = this.balls[c.j].center; | |
var dx = pj[0] - pi[0]; | |
var dy = pj[1] - pi[1]; | |
var norm = Math.sqrt(dx * dx + dy * dy); | |
var r = [c.ammt * dx / norm / 2, c.ammt * dy / norm / 2]; | |
pi[0] -= r[0]; | |
pi[1] -= r[1]; | |
pj[0] += r[0]; | |
pj[1] += r[1]; | |
} | |
} | |
this.handleCollisions = function() { | |
var l = this.wallCollisions.length; | |
for (var i = 0; i < l; ++i) { | |
var c = this.wallCollisions[i]; | |
var ball = this.balls[c.i]; | |
var v = ball.velocity; | |
var w = ball.angularVelocity; | |
var r1 = [-c.x, -c.y, 0]; | |
var r2 = [c.x, c.y, 0]; | |
var impulse = this.impulse( | |
v, w, ball.mass, ball.angularInertia, r1, | |
[0, 0, 0], [0, 0, 0], 1e100, 1e100, r2, | |
r1, 0.99, 1, 1); | |
this.applyImpulse(c.i, impulse, r1); | |
var vMag = Math.sqrt(v[0]*v[0] + v[1]*v[1] + v[2]*v[2]); | |
var soundVelocity = 5.0 * vMag; | |
if (soundVelocity > 1.0) soundVelocity = 1.0; | |
playSound(1 /* ball-wall */, soundVelocity, 0, 0); | |
} | |
var l = this.collisions.length; | |
for (var i = 0; i < l; ++i) { | |
var c = this.collisions[i]; | |
var bi = this.balls[c.i]; | |
var bj = this.balls[c.j]; | |
var vi = bi.velocity; | |
var wi = bi.angularVelocity; | |
var vj = bj.velocity; | |
var wj = bj.angularVelocity; | |
var ri = this.math.normalize(this.math.subVector(bj.center, bi.center)); | |
var rj = this.math.negativeVector(ri); | |
var impulse = this.impulse( | |
vi, wi, bi.mass, bi.angularInertia, ri, | |
vj, wj, bj.mass, bj.angularInertia, rj, | |
ri, 0.99, .2, .1); | |
this.applyImpulse(c.i, impulse, ri); | |
this.applyImpulse(c.j, this.math.negativeVector(impulse), rj); | |
var v1 = Math.sqrt(vi[0]*vi[0] + vi[1]*vi[1] + vi[2]*vi[2]); | |
var v2 = Math.sqrt(vj[0]*vj[0] + vj[1]*vj[1] + vj[2]*vj[2]); | |
var soundVelocity = 5.0 * (v1 + v2); | |
if (soundVelocity > 1.0) soundVelocity = 1.0; | |
playSound(0 /* ball-ball */, soundVelocity, 0, 0); | |
} | |
}; | |
this.randomOrientations = function() { | |
for (var i = 0; i < 16; ++i) { | |
this.balls[i].orientation = | |
this.math.normalize([Math.random() - 0.5, | |
Math.random() - 0.5, | |
Math.random() - 0.5, | |
Math.random() - 0.5]); | |
} | |
}; | |
this.impulse = function(v1, w1, m1, I1, r1, | |
v2, w2, m2, I2, r2, | |
N, e, u_s, u_d) { | |
// Just to be safe, make N unit-length. | |
N = this.math.normalize(N); | |
// Vr is the relative velocity at the point of impact. | |
// Vrn and Vrt are the normal and tangential parts of Vr. | |
var Vr = | |
this.math.subVector( | |
this.math.addVector(this.math.cross(w2, r2), v2), | |
this.math.addVector(this.math.cross(w1, r1), v1)); | |
var Vrn = this.math.mulScalarVector(this.math.dot(Vr, N), N); | |
var Vrt = this.math.subVector(Vr, Vrn); | |
var K = this.math.addMatrix( | |
this.intertialTensor(m1, I1, r1), this.intertialTensor(m2, I2, r2)); | |
var Kinverse = this.math.inverse(K); | |
// Compute the impulse assuming 0 tangential velocity. | |
var j0 = this.math.mulMatrixVector(Kinverse, | |
this.math.subVector(Vr, this.math.mulScalarVector(-e, Vrn))); | |
// If j0 is in the static friction cone, we return that. | |
// If the length of Vrt is 0, then we cannot normalize it, | |
// so we return j0 in that case, too. | |
var j0n = this.math.mulScalarVector(this.math.dot(j0, N), N); | |
var j0t = this.math.subVector(j0, j0n); | |
if (this.math.lengthSquared(j0t) <= | |
u_s * u_s * this.math.lengthSquared(j0n) || | |
this.math.lengthSquared(Vrt) == 0.0) { | |
return j0; | |
} | |
// Get a unit-length tangent vector by normalizing the tangent velocity. | |
// The friction impulse acts in the opposite direction. | |
var T = this.math.normalize(Vrt); | |
// Compute the current impulse in the normal direction. | |
var jn = this.math.dot(this.math.mulMatrixVector(Kinverse, Vr), N); | |
// Compute the impulse assuming no friction. | |
var js = this.math.mulMatrixVector(Kinverse, | |
this.math.mulScalarVector(1 + e, Vrn)); | |
// Return the frictionless impulse plus the impulse due to friction. | |
return this.math.addVector(js, this.math.mulScalarVector(-u_d * jn, T)); | |
}; | |
this.intertialTensor = function(m, I, r) { | |
var a = r[0]; | |
var b = r[1]; | |
var c = r[2]; | |
return [[1 / m + (b * b + c * c) / I, (-a * b) / I, (-a * c) / I], | |
[(-a * b) / I, 1 / m + (a * a + c * c) / I, (-b * c) / I], | |
[(-a * c) / I, (-b * c) / I, 1 / m + (a * a + b * b) / I]]; | |
}; | |
this.applyImpulse = function(i, impulse, r) { | |
var ball = this.balls[i]; | |
var v = ball.velocity; | |
var w = ball.angularVelocity; | |
// v += impulse / mass | |
v[0] += impulse[0] / ball.mass; | |
v[1] += impulse[1] / ball.mass; | |
// w += r x impulse / angularInertia | |
w[0] += (-r[2] * impulse[1]) / ball.angularInertia; | |
w[1] += (impulse[0] * r[2]) / ball.angularInertia; | |
w[2] += (r[0] * impulse[1] - r[1] * impulse[0]) / ball.angularInertia; | |
}; | |
this.ballsImpactFloor = function() { | |
for (var i = 0; i < 16; ++i) { | |
var ball = this.balls[i]; | |
if (!ball.active) | |
continue; | |
var v = ball.velocity; | |
v = [v[0], v[1], -0.1]; | |
var w = ball.angularVelocity; | |
var impulse = this.impulse( | |
v, w, ball.mass, ball.angularInertia, [0, 0, -1], | |
[0, 0, 0], [0, 0, 0], 1e100, 1e100, [0, 0, 1], | |
[0, 0, -1], 0.1, 0.1, 0.02); | |
this.applyImpulse(i, impulse, [0, 0, -1]); | |
} | |
}; | |
this.ballsLoseEnergy = function() { | |
for (var i = 0; i < 16; ++i) { | |
var ball = this.balls[i]; | |
if (!ball.active) | |
continue; | |
var v = ball.velocity; | |
var w = ball.angularVelocity; | |
this.loseEnergy(v, 0.00004); | |
this.loseEnergy(w, 0.00006); | |
} | |
}; | |
this.loseEnergy = function(v, epsilon) { | |
var vLength = this.math.length(v); | |
if (vLength < epsilon) { | |
v[0] = 0; | |
v[1] = 0; | |
v[2] = 0; | |
} else { | |
var t = epsilon / vLength; | |
v[0] -= t * v[0]; | |
v[1] -= t * v[1]; | |
v[2] -= t * v[2]; | |
} | |
}; | |
}; | |
function vectorToQuaternion(r) { | |
var theta = g_math.length(r); | |
var stot = (theta < 1.0e-6)?1:(Math.sin(theta/2) / theta); | |
return [stot * r[0], stot * r[1], stot * r[2], Math.cos(theta)]; | |
} | |
CameraPosition = function() { | |
this.center = [0, 0, 0]; | |
this.theta = 0; | |
this.phi = 0; | |
this.radius = 1; | |
}; | |
CameraInfo = function() { | |
this.lastX = 0; | |
this.lastY = 0; | |
this.position = new CameraPosition(); | |
this.targetPosition = new CameraPosition(); | |
this.vector_ = [0, 0, 0]; | |
this.lerpCoefficient = 1; | |
this.startingTime = 0; | |
this.begin = function(x, y) { | |
this.lastX = x; | |
this.lastY = y; | |
}; | |
this.update = function(x, y) { | |
this.targetPosition.theta -= (x - this.lastX) / 200; | |
this.targetPosition.phi += (y - this.lastY) / 200; | |
this.bound(); | |
this.lastX = x; | |
this.lastY = y; | |
}; | |
this.bound = function() { | |
if (this.position.phi < 0.01) this.position.phi = 0.01; | |
if (this.position.phi > Math.PI / 2 - 0.01) | |
this.position.phi = Math.PI / 2 - 0.01; | |
if (this.targetPosition.phi < 0.01) this.targetPosition.phi = 0.01; | |
if (this.targetPosition.phi > Math.PI / 2 - 0.01) | |
this.targetPosition.phi = Math.PI / 2 - 0.01; | |
}; | |
this.getCurrentPosition = function() { | |
var t = this.lerpCoefficient; | |
t = 3 * t * t - 2 * t * t * t; | |
var a = this.position; | |
var b = this.targetPosition; | |
return {center: [(1 - t) * a.center[0] + t * b.center[0], | |
(1 - t) * a.center[1] + t * b.center[1], | |
(1 - t) * a.center[2] + t * b.center[2]], | |
radius: (1 - t) * a.radius + t * b.radius, | |
theta: (1 - t) * a.theta + t * b.theta, | |
phi: (1 - t) * a.phi + t * b.phi}; | |
} | |
this.getEyeAndTarget = function(eye, target) { | |
var p = this.getCurrentPosition(); | |
var cosPhi = Math.cos(p.phi); | |
target[0] = p.center[0]; | |
target[1] = p.center[1]; | |
target[2] = p.center[2]; | |
eye[0] = target[0] + p.radius * cosPhi * Math.cos(p.theta); | |
eye[1] = target[1] + p.radius * cosPhi * Math.sin(p.theta); | |
eye[2] = target[2] + p.radius * Math.sin(p.phi); | |
}; | |
this.goTo = function(center, theta, phi, radius) { | |
if (!center) { | |
center = this.targetPosition.center; | |
} | |
if (!theta) { | |
theta = this.targetPosition.theta; | |
} | |
if (!phi) { | |
phi = this.targetPosition.phi; | |
} | |
if (!radius) { | |
radius = this.targetPosition.radius; | |
} | |
var p = this.getCurrentPosition(); | |
this.position.center[0] = p.center[0]; | |
this.position.center[1] = p.center[1]; | |
this.position.center[2] = p.center[2]; | |
this.position.theta = p.theta; | |
this.position.phi = p.phi; | |
this.position.radius = p.radius; | |
this.targetPosition.center = center; | |
this.targetPosition.theta = theta; | |
this.targetPosition.phi = phi; | |
this.targetPosition.radius = radius; | |
this.lerpCoefficient = 0; | |
this.startingTime = g_clock; | |
var k = 3 * Math.PI / 2; | |
this.position.theta = | |
myMod(this.position.theta + k, 2.0 * Math.PI) - k; | |
this.targetPosition.theta = | |
myMod(this.targetPosition.theta + k, 2.0 * Math.PI) - k; | |
}; | |
this.backUp = function() { | |
var c = this.targetPosition.center; | |
this.goTo([c[0], c[1], c[2]], | |
null, | |
Math.PI / 6, | |
100); | |
}; | |
this.zoomToPoint = function(center) { | |
this.goTo(center, | |
this.targetPosition.theta, | |
Math.PI / 20, | |
20); | |
}; | |
this.updateClock = function() { | |
this.lerpCoefficient = Math.min(1, g_clock - this.startingTime); | |
if (this.lerpCoefficient == 1) { | |
this.position.center[0] = this.targetPosition.center[0]; | |
this.position.center[1] = this.targetPosition.center[1]; | |
this.position.center[2] = this.targetPosition.center[2]; | |
this.position.theta = this.targetPosition.theta; | |
this.position.phi = this.targetPosition.phi; | |
this.position.radius = this.targetPosition.radius; | |
} | |
}; | |
this.lookingAt = function(center) { | |
return this.targetPosition.center == center; | |
} | |
this.goTo([0, 0, 0], | |
-Math.PI / 2, | |
Math.PI / 6, | |
140); | |
}; | |
var g_cameraInfo = new CameraInfo(); | |
var g_dragging = false; | |
function startDragging(e) { | |
g_cameraInfo.begin(e.x, e.y); | |
g_dragging = true; | |
} | |
function drag(e) { | |
if (g_dragging) { | |
g_cameraInfo.update(e.x, e.y); | |
updateContext(); | |
} | |
} | |
function stopDragging(e) { | |
if (g_dragging) { | |
g_cameraInfo.update(e.x, e.y); | |
updateContext(); | |
} | |
g_dragging = false; | |
} | |
/** | |
* Initializes global variables, positions eye, draws shapes. | |
* @param {Array} clientElements Array of o3d object elements. | |
*/ | |
function main(clientElements) { | |
initPhysics(); | |
initGlobals(clientElements); | |
initRenderGraph(); | |
updateContext(); | |
initMaterials(); | |
initShadowPlane(); | |
initTable(); | |
initHud(); | |
rack(8); | |
setRenderCallback(); | |
registerEventCallbacks(); | |
} | |
/** | |
* Registers event handlers. | |
*/ | |
function registerEventCallbacks() { | |
o3djs.event.addEventListener(g_o3dElement, 'mousedown', startDragging); | |
o3djs.event.addEventListener(g_o3dElement, 'mousemove', drag); | |
o3djs.event.addEventListener(g_o3dElement, 'mouseup', stopDragging); | |
o3djs.event.addEventListener(g_o3dElement, 'keypress', keyPressed); | |
o3djs.event.addEventListener(g_o3dElement, 'keyup', keyUp); | |
o3djs.event.addEventListener(g_o3dElement, 'keydown', keyDown); | |
} | |
/** | |
* Creates the client area. | |
*/ | |
function initClient() { | |
o3djs.webgl.makeClients(main); | |
} | |
function initAudio() { | |
// Audio init | |
context = new webkitAudioContext(); | |
// Create compressor to sweeten overall mix | |
if (context.createDynamicsCompressor) { | |
compressor = context.createDynamicsCompressor(); | |
compressor.connect(context.destination); | |
} else { | |
// we don't really have a compressor, just connect to destination... | |
compressor = context.destination; | |
} | |
convolver = context.createConvolver(); | |
convolver.connect(compressor); | |
info = document.getElementById('info'); | |
// loadPing("sounds/hyper-reality/filter-noise-3.wav"); | |
loadBallBallLight("../sounds/pool-table/ball-ball-light2.wav"); | |
loadBallBallMedium("../sounds/pool-table/ball-ball-medium3.wav"); | |
loadBallBallHard("../sounds/pool-table/ball-ball-hard2.wav"); | |
// loadMediumEdge("sounds/hyper-reality/filter-noise-1.wav"); | |
loadMediumEdge("../sounds/pool-table/ball-edge-medium2.wav"); | |
loadHardEdge("../sounds/pool-table/ball-edge-hard1.wav"); | |
loadStickCueLight("../sounds/pool-table/stick-cue-light2.wav"); | |
loadStickCueMedium("../sounds/pool-table/stick-cue-medium5.wav"); | |
loadStickCueHard("../sounds/pool-table/stick-cue-medium2.wav"); | |
loadBallPocket("../sounds/pool-table/ball-pocket-balls.wav"); | |
// loadImpulseResponse('../impulse-responses/house-impulses/dining-room.wav'); | |
// loadImpulseResponse('../impulse-responses/house-impulses/dining-room.wav'); | |
// loadImpulseResponse('../impulse-responses/house-impulses/cinema-hallway.wav'); | |
loadImpulseResponse('../impulse-responses/house-impulses/kitchen-true-stereo.wav'); | |
// loadImpulseResponse('../impulse-responses/spatialized4.wav'); | |
} | |
/** | |
* Initializes global variables and libraries. | |
*/ | |
function initGlobals(clientElements) { | |
g_o3dElement = clientElements[0]; | |
window.g_client = g_client = g_o3dElement.client; | |
g_o3d = g_o3dElement.o3d; | |
g_math = o3djs.math; | |
g_quat = o3djs.quaternions; | |
// Create a pack to manage the objects created. | |
g_pack = g_client.createPack(); | |
initAudio(); | |
} | |
/** | |
* Initalizes the render graph. | |
*/ | |
function initRenderGraph() { | |
// Need separate roots for the table, shadow and heads-up display. | |
g_tableRoot = g_pack.createObject('Transform'); | |
g_tableRoot.parent = g_client.root; | |
g_shadowRoot = g_pack.createObject('Transform'); | |
g_shadowRoot.parent = g_client.root; | |
g_hudRoot = g_pack.createObject('Transform'); | |
g_hudRoot.parent = g_client.root; | |
// Create the render graph for a view. | |
var viewRoot = g_pack.createObject('RenderNode'); | |
viewRoot.priority = 1; | |
if (!SHADOWPOV) | |
viewRoot.parent = g_client.renderGraphRoot; | |
var shadowPassRoot = g_pack.createObject('RenderNode'); | |
shadowPassRoot.priority = 0; | |
shadowPassRoot.parent = g_client.renderGraphRoot; | |
g_viewInfo = o3djs.rendergraph.createBasicView( | |
g_pack, | |
g_tableRoot, | |
viewRoot, | |
[0, 0, 0, 1]); | |
var hudRenderRoot = g_client.renderGraphRoot; | |
if (SHADOWPOV) | |
hudRenderRoot = null; | |
g_hudViewInfo = o3djs.rendergraph.createBasicView( | |
g_pack, | |
g_hudRoot, | |
hudRenderRoot); | |
// Make sure the hud gets drawn after the 3d scene. | |
g_hudViewInfo.root.priority = g_viewInfo.root.priority + 1; | |
// Turn off clearing the color for the hud. | |
g_hudViewInfo.clearBuffer.clearColorFlag = false; | |
// Set culling to none so we can flip images using rotation or negative scale. | |
g_hudViewInfo.zOrderedState.getStateParam('CullMode').value = | |
g_o3d.State.CULL_NONE; | |
g_hudViewInfo.zOrderedState.getStateParam('ZWriteEnable').value = false; | |
// Create an orthographic matrix for 2d stuff in the HUD. | |
g_hudViewInfo.drawContext.projection = g_math.matrix4.orthographic( | |
0, 1, 0, 1, -10, 10); | |
g_hudViewInfo.drawContext.view = g_math.matrix4.lookAt( | |
[0, 0, 1], // eye | |
[0, 0, 0], // target | |
[0, 1, 0]); // up | |
// Create the texture required for the render-target. | |
g_shadowTexture = g_pack.createTexture2D(RENDER_TARGET_WIDTH, | |
RENDER_TARGET_HEIGHT, | |
g_o3d.Texture.XRGB8, 1, true); | |
var renderSurface = g_shadowTexture.getRenderSurface(0); | |
var depthSurface = g_pack.createDepthStencilSurface(RENDER_TARGET_WIDTH, | |
RENDER_TARGET_HEIGHT); | |
var renderSurfaceSet = g_pack.createObject('RenderSurfaceSet'); | |
renderSurfaceSet.renderSurface = renderSurface; | |
renderSurfaceSet.renderDepthStencilSurface = depthSurface; | |
renderSurfaceSet.parent = shadowPassRoot; | |
var shadowPassParent = renderSurfaceSet; | |
if (SHADOWPOV) | |
shadowPassParent = g_client.renderGraphRoot; | |
g_shadowPassViewInfo = o3djs.rendergraph.createBasicView( | |
g_pack, | |
g_shadowRoot, | |
shadowPassParent, | |
[0, 0, 0, 1]); | |
g_shadowPassViewInfo.zOrderedState. | |
getStateParam('ZComparisonFunction').value = | |
o3djs.base.o3d.State.CMP_ALWAYS; | |
} | |
function handleResizeEvent(event) { | |
updateContext(); | |
} | |
/** | |
* Sets up reasonable view and projection matrices. | |
*/ | |
function updateContext() { | |
// Set up a perspective transformation for the projection. | |
g_shadowPassViewInfo.drawContext.projection = | |
g_viewInfo.drawContext.projection = g_math.matrix4.perspective( | |
g_math.degToRad(30), // 30 degree frustum. | |
g_client.width / g_client.height, // Aspect ratio. | |
1, // Near plane. | |
5000); // Far plane. | |
// Set the view transformation. | |
var eye = [0, 0, 0]; | |
var target = [0, 0, 0]; | |
g_cameraInfo.getEyeAndTarget(eye, target); | |
g_shadowPassViewInfo.drawContext.view = | |
g_viewInfo.drawContext.view = g_math.matrix4.lookAt(eye, target, [0, 0, 1]); | |
updateMaterials(); | |
} | |
function setOptionalParam(material, name, value) { | |
var param = material.getParam(name); | |
if (param) { | |
param.value = value; | |
} | |
} | |
function initMaterials() { | |
g_materials = { | |
'solid':{}, | |
'felt':{}, | |
'wood':{}, | |
'cushion':{}, | |
'billiard':{}, | |
'ball':{}, | |
'shadowPlane':{}}; | |
var vertexShaderString = document.getElementById('vshader').value; | |
var pixelShaderString = document.getElementById('pshader').value; | |
for (name in g_materials) { | |
var material = g_pack.createObject('Material'); | |
g_materials[name] = material; | |
var effect = g_pack.createObject('Effect'); | |
var mainString = | |
'void main() {' + | |
' gl_FragColor = ' + name + 'PixelShader();' + | |
'}'; | |
effect.loadVertexShaderFromString(vertexShaderString); | |
effect.loadPixelShaderFromString(pixelShaderString + mainString); | |
material.effect = effect; | |
effect.createUniformParameters(material); | |
material.drawList = g_viewInfo.performanceDrawList; | |
var eye = [0, 0, 0]; | |
var target = [0, 0, 0]; | |
g_cameraInfo.getEyeAndTarget(eye, target); | |
setOptionalParam(material, 'factor', 2 / g_tableWidth); | |
setOptionalParam(material, 'lightWorldPosition', g_light); | |
setOptionalParam(material, 'eyeWorldPosition', eye); | |
} | |
g_solidMaterial = g_materials['solid']; | |
g_solidMaterial.drawList = g_hudViewInfo.zOrderedDrawList; | |
g_materials['shadowPlane'].drawList = g_shadowPassViewInfo.zOrderedDrawList; | |
g_shadowSampler = g_pack.createObject('Sampler'); | |
g_shadowSampler.texture = g_shadowTexture; | |
g_materials['felt'].getParam('textureSampler').value = g_shadowSampler; | |
o3djs.io.loadBitmaps(g_pack, | |
o3djs.util.getAbsoluteURI('../assets/poolballs.png'), | |
finishLoadingBitmaps); | |
} | |
function updateMaterials() { | |
for (name in g_materials) { | |
var eye = [0, 0, 0]; | |
var target = [0, 0, 0]; | |
g_cameraInfo.getEyeAndTarget(eye, target); | |
setOptionalParam(g_materials[name], 'eyeWorldPosition', eye); | |
} | |
} | |
/** | |
* Gets called back when the bitmap has loaded. | |
*/ | |
function finishLoadingBitmaps(bitmaps, exception) { | |
var bitmap = bitmaps[0]; | |
bitmap.flipVertically(); | |
var width = bitmaps[0].width / 4; | |
var height = bitmaps[0].height / 4; | |
var levels = o3djs.texture.computeNumLevels(width, height); | |
for (var i = 0; i < 16; ++i) { | |
g_ballTextures[i] = g_pack.createTexture2D( | |
width, height, g_o3d.Texture.XRGB8, 0, false); | |
g_ballTextureSamplers[i] = g_pack.createObject('Sampler'); | |
g_ballTextureSamplers[i].texture = g_ballTextures[i]; | |
} | |
for (var i = 0; i < 16; ++i) { | |
var u = i % 4; | |
var v = Math.floor(i / 4); | |
g_ballTextures[i].drawImage(bitmap, | |
0, u * width, v * height, width, height, | |
0, 0, 0, width, height); | |
g_ballTextures[i].generateMips(0, levels - 1); | |
} | |
for (var i = 0; i < 16; ++i) { | |
g_ballTextureSamplerParams[i].value = g_ballTextureSamplers[i]; | |
} | |
} | |
function flatMesh(material, vertexPositions, faceIndices) { | |
var vertexInfo = o3djs.primitives.createVertexInfo(); | |
var positionStream = vertexInfo.addStream( | |
3, o3djs.base.o3d.Stream.POSITION); | |
var normalStream = vertexInfo.addStream( | |
3, o3djs.base.o3d.Stream.NORMAL); | |
var vertexCount = 0; | |
for (var i = 0; i < faceIndices.length; ++i) { | |
var face = faceIndices[i]; | |
var n = g_math.normalize(g_math.cross( | |
g_math.subVector(vertexPositions[face[1]], | |
vertexPositions[face[0]]), | |
g_math.subVector(vertexPositions[face[2]], | |
vertexPositions[face[0]]))); | |
var faceFirstIndex = vertexCount; | |
for (var j = 0; j < face.length; ++j) { | |
var v = vertexPositions[face[j]]; | |
positionStream.addElement(v[0], v[1], v[2]); | |
normalStream.addElement(n[0], n[1], n[2]); | |
++vertexCount; | |
} | |
for (var j = 1; j < face.length - 1; ++j) | |
vertexInfo.addTriangle(faceFirstIndex, | |
faceFirstIndex + j, | |
faceFirstIndex + j + 1); | |
} | |
return vertexInfo.createShape(g_pack, material); | |
} | |
function arc(center, radius, start, end, steps) { | |
var r = []; | |
for (var i = 0; i <= steps; ++i) { | |
var theta = start + i * (end - start) / steps; | |
r.push([center[0] + radius * Math.cos(theta), | |
center[1] + radius * Math.sin(theta)]); | |
} | |
return r; | |
} | |
function myreverse(l) { | |
var r = [l[0]]; | |
var n = l.length; | |
for (var i = 0; i < n - 1; ++i) { | |
r.push(l[n - i - 1]); | |
} | |
return r; | |
} | |
function flip(a, b) { | |
r = []; | |
for (var i = 0; i < a.length; ++i) | |
r.push([b[0] * a[i][0], b[1] * a[i][1]]); | |
if (b[0] * b[1] < 0) | |
return myreverse(r); | |
return r; | |
} | |
var g_pocketRadius = 2.3; | |
var g_woodBreadth = 3.2; | |
var g_tableThickness = 5; | |
var g_tableWidth = 45; | |
var g_woodHeight = 1.1; | |
function initTable() { | |
var feltMaterial = g_materials.felt; | |
var woodMaterial = g_materials.wood; | |
var cushionMaterial = g_materials.cushion; | |
var billiardMaterial = g_materials.billiard; | |
var shapes = []; | |
var root = g_pack.createObject('Transform'); | |
root.parent = g_tableRoot; | |
var tableRoot = g_pack.createObject('Transform'); | |
tableRoot.translate(0, 0, -g_tableThickness / 2 - 1); | |
var cushionRoot = g_pack.createObject('Transform'); | |
var ballRoot = g_pack.createObject('Transform'); | |
tableRoot.parent = root; | |
cushionRoot.parent = tableRoot; | |
ballRoot.parent = root; | |
var root2 = Math.sqrt(2); | |
var scaledPocketRadius = 2 * g_pocketRadius / g_tableWidth; | |
var scaledWoodBreadth = 2 * g_woodBreadth / g_tableWidth; | |
var felt_polygon_A = | |
[[0, -2], [0, (1 + .5 * root2) * scaledPocketRadius - 2]].concat( | |
arc([.5 * root2 * scaledPocketRadius - 1, | |
.5 * root2 * scaledPocketRadius - 2], | |
scaledPocketRadius, Math.PI / 2, -.25 * Math.PI, 15)); | |
var felt_polygon_B = | |
[[-1, (1 + .5 * root2) * scaledPocketRadius - 2]].concat( | |
arc([.5 * root2 * scaledPocketRadius - 1, | |
.5 * root2 * scaledPocketRadius - 2], | |
scaledPocketRadius, .75 * Math.PI, .5 * Math.PI, 15)); | |
var felt_polygon_C = | |
[[0, (1 + .5 * root2) * scaledPocketRadius - 2], [0, 0]].concat( | |
arc([-1, 0], scaledPocketRadius, 0, -.5 * Math.PI, 15)).concat( | |
[[-1, (1 + .5 * root2) * scaledPocketRadius - 2]]); | |
var wood_polygon = | |
[[-scaledWoodBreadth - 1, -scaledWoodBreadth - 2], | |
[0, -scaledWoodBreadth - 2], | |
[0, -2]].concat( | |
arc([.5 * root2 * scaledPocketRadius - 1, | |
.5 * root2 * scaledPocketRadius - 2], | |
scaledPocketRadius, | |
-.25 * Math.PI, | |
-1.25 * Math.PI, | |
15)).concat( | |
arc([-1, 0], | |
scaledPocketRadius, | |
1.5 * Math.PI, | |
Math.PI, | |
15)).concat([[-scaledWoodBreadth - 1, 0]]); | |
var m = g_math.mulScalarMatrix(g_tableWidth / 2, g_math.identity(2)); | |
felt_polygon_A = g_math.mulMatrixMatrix(felt_polygon_A, m); | |
felt_polygon_B = g_math.mulMatrixMatrix(felt_polygon_B, m); | |
felt_polygon_C = g_math.mulMatrixMatrix(felt_polygon_C, m); | |
wood_polygon = g_math.mulMatrixMatrix(wood_polygon, m); | |
var felt_polygons = []; | |
var wood_polygons = []; | |
for (var i = -1; i < 2; i+=2) { | |
for (var j = -1; j < 2; j+=2) { | |
felt_polygons.push(flip(felt_polygon_A, [i, j]), | |
flip(felt_polygon_B, [i, j]), | |
flip(felt_polygon_C, [i, j])); | |
wood_polygons.push(flip(wood_polygon, [i, j])); | |
} | |
} | |
for (var i = 0; i < felt_polygons.length; ++i) { | |
shapes.push(o3djs.primitives.createPrism( | |
g_pack, | |
feltMaterial, | |
felt_polygons[i], g_tableThickness)); | |
} | |
for (var i = 0; i < wood_polygons.length; ++i) { | |
shapes.push(o3djs.primitives.createPrism( | |
g_pack, | |
woodMaterial, | |
wood_polygons[i], g_tableThickness + 2 * g_woodHeight)); | |
} | |
for (var i = 0; i < 1; i++) { | |
var t = g_pack.createObject('Transform'); | |
t.parent = tableRoot; | |
for (var j = 0; j < shapes.length; ++j) { | |
t.addShape(shapes[j]); | |
} | |
} | |
var cushionHeight = 1.1 * g_woodHeight; | |
var cushionUp = g_tableThickness / 2; | |
var cushionProp = .9 * g_woodHeight; | |
var cushionDepth = g_tableWidth; | |
var cushionBreadth = g_pocketRadius; | |
var cushionSwoop = g_pocketRadius; | |
var angles = [0, Math.PI/2, Math.PI, Math.PI, 3 * Math.PI / 2, 0]; | |
var translations = g_math.mulMatrixMatrix( | |
[[-1, -1, 0], [0, -2, 0], [1, -1, 0], [1, 1, 0], [0, 2, 0], [-1, 1, 0]], | |
[[g_tableWidth / 2, 0, 0], [0, g_tableWidth / 2, 0], [0, 0, 1]]); | |
var shortenings = g_math.mulScalarMatrix(g_pocketRadius, | |
[[1, root2], [root2, root2], [root2, 1]]) | |
var billiardThickness = 0.1; | |
var billiardBreadth = 1; | |
var billiardDepth = .309; | |
var billiardOut = -g_woodBreadth / 2; | |
var billiardSpacing = g_tableWidth / 4; | |
var billiards = []; | |
for (var i = -1; i < 2; ++i) { | |
billiards.push(o3djs.primitives.createPrism( | |
g_pack, | |
billiardMaterial, | |
[[billiardOut + billiardBreadth / 2, i * billiardSpacing], | |
[billiardOut, billiardDepth + i * billiardSpacing], | |
[billiardOut - billiardBreadth / 2, i * billiardSpacing], | |
[billiardOut, -billiardDepth + i * billiardSpacing]], | |
g_tableThickness + 2 * g_woodHeight + billiardThickness)); | |
} | |
for (var i = 0; i < 6; ++i) { | |
var backShortening = shortenings[i % 3][1]; | |
var frontShortening = shortenings[i % 3][0]; | |
var vertexPositions = [ | |
[0, -cushionDepth / 2 + backShortening, cushionUp], | |
[cushionBreadth, -cushionDepth / 2 + cushionSwoop + backShortening, | |
cushionUp + cushionProp], | |
[cushionBreadth, -cushionDepth / 2 + cushionSwoop + backShortening, | |
cushionUp + cushionHeight], | |
[0, -cushionDepth / 2 + backShortening, cushionUp + cushionHeight], | |
[0, cushionDepth / 2 - frontShortening, cushionUp], | |
[cushionBreadth, cushionDepth / 2 - cushionSwoop - frontShortening, | |
cushionUp + cushionProp], | |
[cushionBreadth, cushionDepth / 2 - cushionSwoop - frontShortening, | |
cushionUp + cushionHeight], | |
[0, cushionDepth / 2 - frontShortening, cushionUp + cushionHeight] | |
]; | |
var faceIndices = [ | |
[0, 1, 2, 3], // front | |
[7, 6, 5, 4], // back | |
[1, 0, 4, 5], // bottom | |
[2, 1, 5, 6], // right | |
[3, 2, 6, 7], // top | |
[0, 3, 7, 4] // left | |
]; | |
var cushion = flatMesh(cushionMaterial, vertexPositions, faceIndices); | |
shapes.push(cushion); | |
var t = g_pack.createObject('Transform'); | |
t.localMatrix = g_math.mulMatrixMatrix( | |
g_math.matrix4.rotationZ(angles[i]), | |
g_math.matrix4.translation(translations[i])); | |
t.parent = cushionRoot; | |
t.addShape(cushion); | |
for (var j = 0; j < billiards.length; ++j) | |
t.addShape(billiards[j]); | |
} | |
for (var j = 0; j < billiards.length; ++j) | |
shapes.push(billiards[j]); | |
var ball = | |
o3djs.primitives.createSphere(g_pack, g_materials.ball, 1, 50, 70); | |
shapes.push(ball); | |
for(var i = 0; i < 16; ++i) { | |
var transform = g_pack.createObject('Transform'); | |
g_ballTextureSamplerParams[i] = | |
transform.createParam('textureSampler', 'ParamSampler'); | |
transform.parent = ballRoot; | |
g_ballTransforms[i] = transform; | |
transform.addShape(ball); | |
} | |
} | |
function initHud() { | |
var barT1 = g_pack.createObject('Transform'); | |
g_barScaling = g_pack.createObject('Transform'); | |
var barT2 = g_pack.createObject('Transform'); | |
var backT2 = g_pack.createObject('Transform'); | |
g_barRoot = barT1; | |
barT1.parent = g_hudRoot; | |
g_barScaling.parent = barT1; | |
barT2.parent = g_barScaling; | |
backT2.parent = barT1; | |
var plane = o3djs.primitives.createPlane( | |
g_pack, g_solidMaterial, 1, 1, 1, 1, | |
[[1, 0, 0, 0], | |
[0, 0, 1, 0], | |
[0,-1, 0, 0], | |
[0, 0, 0, 1]]); | |
var backPlane = o3djs.primitives.createPlane( | |
g_pack, g_solidMaterial, 1, 1, 1, 1, | |
[[1, 0, 0, 0], | |
[0, 0, 1, 0], | |
[0,-1, 0, 0], | |
[0, 0, 0, 1]]); | |
barT2.addShape(plane); | |
//backT2.addShape(backPlane); | |
barT1.translate([0.05, 0.05, 0]); | |
barT1.scale([0.05, 0.9, 1]); | |
g_barScaling.localMatrix = g_math.matrix4.scaling([1, 0.0, 1]); | |
barT2.translate([.5, .5, 0]); | |
backT2.translate([.5, .5, 0.1]); | |
} | |
function setBarScale(t) { | |
g_barScaling.localMatrix = g_math.matrix4.scaling([1, t, 1]); | |
} | |
if ( !window.requestAnimationFrame ) { | |
window.requestAnimationFrame = ( function() { | |
return window.webkitRequestAnimationFrame || | |
window.mozRequestAnimationFrame || // comment out if FF4 is slow (it caps framerate at ~30fps: https://bugzilla.mozilla.org/show_bug.cgi?id=630127) | |
window.oRequestAnimationFrame || | |
window.msRequestAnimationFrame || | |
function( /* function FrameRequestCallback */ callback, /* DOMElement Element */ element ) { | |
window.setTimeout( callback, 1000 / 60 ); | |
}; | |
} )(); | |
} | |
function onrender(event) { | |
g_clock += event.elapsedTime; | |
g_queueClock += event.elapsedTime; | |
var clock = g_queueClock; | |
if (g_queue.length) { | |
if (eval(g_queue[0].condition)) { | |
var action = g_queue[0].action; | |
for (var i = 0; i < g_queue.length - 1; ++i) { | |
g_queue[i] = g_queue[i + 1]; | |
} | |
g_queue.pop(); | |
eval(action); | |
g_queueClock = 0; | |
} | |
} | |
if (g_cameraInfo) { | |
g_cameraInfo.updateClock(); | |
} | |
if (g_physics) { | |
if (g_physics.someBallsMoving()) { | |
g_physics.step(); | |
g_physics.stopSlowBalls(); | |
} else { | |
if (g_rolling) { | |
g_rolling = false; | |
var cueBall = g_physics.balls[0]; | |
if (g_cameraInfo.lookingAt(cueBall.center)) | |
g_barRoot.visible = true; | |
if (!cueBall.active) { | |
ballOn(0); | |
cueBall.center[0] = 0; | |
cueBall.center[1] = 0; | |
cueBall.center[2] = 0; | |
g_physics.boundCueBall(); | |
} | |
} | |
} | |
} | |
updateContext(); | |
} | |
function doAnimation() { | |
g_client.render(); | |
requestAnimationFrame(doAnimation); | |
} | |
function setRenderCallback() { | |
g_client.setRenderCallback(onrender); | |
doAnimation(); | |
} | |
function initPhysics() { | |
g_physics = new pool.Physics(); | |
g_physics.math = o3djs.math; | |
g_physics.quat = o3djs.quaternions; | |
g_physics.left = -g_tableWidth / 2 + g_pocketRadius + 1; | |
g_physics.right = g_tableWidth / 2 - g_pocketRadius - 1; | |
g_physics.top = g_tableWidth - g_pocketRadius - 1; | |
g_physics.bottom = - g_tableWidth + g_pocketRadius + 1; | |
var w = g_tableWidth / 2; | |
var r = g_pocketRadius; | |
var root2 = Math.sqrt(2); | |
var x = .5 * root2 * r - w; | |
var y = .5 * root2 * r - 2 * w; | |
g_physics.pocketCenters = [ | |
[w, 0], [-w, 0], [x, y], [-x, y], [x, -y], [-x, -y]]; | |
g_physics.pocketRadius = g_pocketRadius; | |
g_physics.tableWidth = g_tableWidth; | |
g_physics.initWalls(); | |
} | |
function rack(game, yOffset, cueYOffset) { | |
var root3 = Math.sqrt(3); | |
if (!yOffset) | |
yOffset = 6.0 * g_tableWidth / 12.0; | |
if (!cueYOffset) | |
cueYOffset = -g_tableWidth / 2; | |
for (var i = 0; i < 16; ++i) | |
ballOn(i); | |
g_physics.stopAllBalls(); | |
switch(game) { | |
case 8: | |
placeBall(1, 0, 0 + yOffset); | |
placeBall(9, -1, root3 + yOffset); | |
placeBall(2, 1, root3 + yOffset); | |
placeBall(10, 2, 2 * root3 + yOffset); | |
placeBall(8, 0, 2 * root3 + yOffset); | |
placeBall(3, -2, 2 * root3 + yOffset); | |
placeBall(11, -3, 3 * root3 + yOffset); | |
placeBall(4, -1, 3 * root3 + yOffset); | |
placeBall(12, 1, 3 * root3 + yOffset); | |
placeBall(5, 3, 3 * root3 + yOffset); | |
placeBall(13, 4, 4 * root3 + yOffset); | |
placeBall(6, 2, 4 * root3 + yOffset); | |
placeBall(14, 0, 4 * root3 + yOffset); | |
placeBall(15, -2, 4 * root3 + yOffset); | |
placeBall(7, -4, 4 * root3 + yOffset); | |
placeBall(0, 0, cueYOffset); | |
break; | |
case 9: | |
placeBall(1, 0, 0 + yOffset); | |
placeBall(2, 1, root3 + yOffset); | |
placeBall(3, -1, root3 + yOffset); | |
placeBall(9, 0, 2 * root3 + yOffset); | |
placeBall(4, 2, 2 * root3 + yOffset); | |
placeBall(5, -2, 2 * root3 + yOffset); | |
placeBall(6, 1, 3 * root3 + yOffset); | |
placeBall(7, -1, 3 * root3 + yOffset); | |
placeBall(8, 0, 4 * root3 + yOffset); | |
for (var i = 10; i < 16; ++i) { | |
placeBall(i, 0, 0, -5); | |
ballOff(i); | |
} | |
placeBall(0, 0, cueYOffset); | |
break; | |
case 0: | |
for (var i = 1; i < 16; ++i) { | |
placeBall(i, 0, 0, -5); | |
ballOff(i); | |
} | |
placeBall(0, 0, cueYOffset); | |
break; | |
case 1: | |
for (var i = 1; i < 16; ++i) { | |
placeBall(i, 0, 0, -5); | |
ballOff(i); | |
} | |
placeBall(0, 0, cueYOffset); | |
placeBall(1, -g_tableWidth/4, cueYOffset/2); | |
placeBall(2, -3*g_tableWidth/8, cueYOffset/4); | |
placeBall(3, g_tableWidth/4, 0); | |
ballOn(0); | |
ballOn(1); | |
ballOn(2); | |
ballOn(3); | |
break; | |
} | |
g_physics.randomOrientations(); | |
g_physics.placeBalls(); | |
g_cameraInfo.goTo([0, 0, 0], -Math.PI / 2, Math.PI / 6, 140); | |
} | |
function ballOn(i) { | |
g_physics.balls[i].active = true; | |
g_physics.balls[i].sunkInPocket = -1; | |
g_ballTransforms[i].visible = true; | |
g_shadowOnParams[i].value = 1; | |
} | |
function ballOff(i) { | |
g_physics.balls[i].active = false; | |
g_ballTransforms[i].visible = false; | |
g_shadowOnParams[i].value = 0; | |
} | |
function placeBall(i, x, y, z, q) { | |
if (!q) { | |
q = [0, 0, 0, 1]; | |
} | |
if (!z) { | |
z = 0; | |
} | |
g_physics.balls[i].center[0] = x; | |
g_physics.balls[i].center[1] = y; | |
g_physics.balls[i].center[2] = z; | |
g_ballTransforms[i].localMatrix = g_math.matrix4.translation([x, y, z]); | |
g_ballTransforms[i].quaternionRotate(q); | |
g_centers[i].value = [x, y]; | |
} | |
function initShadowPlane() { | |
var root = g_pack.createObject('Transform'); | |
root.parent = g_shadowRoot; | |
var plane = o3djs.primitives.createPlane(g_pack, | |
g_materials.shadowPlane, | |
g_tableWidth, | |
g_tableWidth * 2, | |
1, | |
1); | |
root.translate([0, 0, -1]); | |
root.rotateX(Math.PI / 2); | |
for (var i = 0; i < 16; ++i) { | |
var transform = g_pack.createObject('Transform'); | |
transform.parent = root; | |
g_centers.push(transform.createParam('ballCenter', 'ParamFloat2')); | |
g_shadowOnParams[i] = | |
transform.createParam('shadowOn', 'ParamFloat'); | |
g_shadowOnParams[i].value = 1; | |
transform.addShape(plane); | |
} | |
} | |
// To avoid problem where user just taps space bar instead of holding for | |
// more thrust: If the user doesn't hold the button down for a few ticks | |
// showing 'seriousness' the stroke doesn't take. | |
var g_seriousness = 0; | |
var g_shooting_timers = []; | |
function computeShot(i, j, cueCenter, objectCenter, pocketCenter) { | |
// The vector from the object ball to the pocket, I'm calling "second". | |
// The vector from the cue ball to the "ghost ball" behind the object | |
// ball I'm calling "first" | |
var secondX = pocketCenter[0] - objectCenter[0]; | |
var secondY = pocketCenter[1] - objectCenter[1]; | |
var secondDistance = Math.sqrt(secondX * secondX + secondY * secondY); | |
var toPocket = [secondX / secondDistance, secondY / secondDistance]; | |
var toObject = | |
[objectCenter[0] - cueCenter[0], objectCenter[0] - cueCenter[0]]; | |
var d = Math.sqrt(toObject[0] * toObject[0] + toObject[1] * toObject[1]); | |
toObject = [toObject[0] / d, toObject[1] / d]; | |
// Cut correction. | |
var cc = (toObject[0] * toPocket[0] + toObject[1] * toPocket[1]); | |
cc = cc > 0.8 ? .4 : 0 ; | |
var cut = [(2.0 + cc) * toPocket[0], (2.0 + cc) * toPocket[1]]; | |
var target = [objectCenter[0] - cut[0], objectCenter[1] - cut[1]]; | |
var firstX = target[0] - cueCenter[0]; | |
var firstY = target[1] - cueCenter[1]; | |
var firstDistance = Math.sqrt(firstX * firstX + firstY * firstY); | |
var cutAmmount = 1.0 - (firstX * secondX + firstY * secondY) / | |
(firstDistance * secondDistance); | |
var power = 0.12 * (firstDistance + secondDistance / (1.01-cutAmmount)) / | |
g_tableWidth; | |
var difficulty = cutAmmount * cutAmmount - 0.5 / (1 + secondDistance/2); | |
if (difficulty < 1) { | |
// Determine if the shot is occluded. | |
var walls = [ | |
{p:cueCenter, q:target}, | |
{p:objectCenter, q:pocketCenter} | |
]; | |
var collisions = []; | |
g_physics.computeWallNormals(walls); | |
g_physics.collideWithWalls(walls, collisions, 1.99); | |
if (collisions.length > 2) | |
difficulty += 10; | |
} | |
return {target: target, | |
power: Math.min(1, Math.max(0.1, power)), | |
difficulty: difficulty}; | |
} | |
function cueNewShot(opt_power) { | |
g_queue.push( | |
{condition: 'clock > 1', | |
action: 'g_cameraInfo.zoomToPoint(g_physics.balls[0].center);'}); | |
var cue = g_physics.balls[0]; | |
var current = null; | |
var objectBalls = []; | |
for (var i = 1; i < 8; ++i) { | |
var ball = g_physics.balls[i]; | |
if (ball.active) { | |
objectBalls.push(ball); | |
} | |
} | |
var eight = g_physics.balls[i]; | |
if (objectBalls.length == 0 && eight.active) { | |
objectBalls.push(eight); | |
} | |
for (var i = 0; i < objectBalls.length; ++i) { | |
var ball = objectBalls[i]; | |
for (var j = 0; j < g_physics.pocketCenters.length; ++j) { | |
var pocketCenter = g_physics.pocketCenters[j]; | |
pocketCenter = [pocketCenter[0], pocketCenter[1]]; | |
var k = g_pocketRadius; | |
if (pocketCenter[0] > 1) | |
pocketCenter[0] -= k; | |
if (pocketCenter[0] < -1) | |
pocketCenter[0] += k; | |
if (pocketCenter[1] > 1) | |
pocketCenter[1] -= k; | |
if (pocketCenter[1] < -1) | |
pocketCenter[1] += k; | |
var shot = computeShot(i, j, cue.center, ball.center, pocketCenter); | |
if (!current || shot.difficulty < current.difficulty) | |
current = shot; | |
} | |
} | |
if (current) { | |
var theta = Math.atan2(cue.center[1] - current.target[1], | |
cue.center[0] - current.target[0]); | |
var power = current.power; | |
if (opt_power) | |
power = opt_power; | |
g_queue.push( | |
{condition: 'true', | |
action: 'g_cameraInfo.goTo(null, ' + theta + ', null, 0);'}); | |
g_queue.push( | |
{condition: 'clock > 1.5', | |
action: 'startShooting();'}); | |
g_queue.push( | |
{condition: 'g_physics.speedFactor >= ' + power, | |
action: 'g_physics.speedFactor = ' + power + | |
'; finishShooting();'}); | |
g_queue.push( | |
{condition: '!(g_shooting || g_rolling)', | |
action: 'cueNewShot();'}); | |
} | |
} | |
var g_phi = 0.0; | |
function cueNewTestShot() { | |
var cue = g_physics.balls[0]; | |
placeBall(0, 0, -20); | |
placeBall(1, 0, 0); | |
ballOn(0); | |
ballOn(1); | |
var current = {target: [0, 0], power: 0.1}; | |
var phi = g_phi; | |
g_phi += 0.1; | |
current.target[0] = - 2.0 * Math.cos(phi); | |
current.target[1] = - 2.0 * Math.sin(phi); | |
if (current) { | |
var theta = Math.atan2(cue.center[1] - current.target[1], | |
cue.center[0] - current.target[0]); | |
var power = current.power; | |
g_queue.push( | |
{condition: 'true', | |
action: 'g_cameraInfo.goTo(null, ' + theta + ', null, 0);'}); | |
g_queue.push( | |
{condition: 'clock > 1.5', | |
action: 'startShooting();'}); | |
g_queue.push( | |
{condition: 'g_physics.speedFactor >= ' + power, | |
action: 'g_physics.speedFactor = ' + power + | |
'; finishShooting();'}); | |
g_queue.push( | |
{condition: '!(g_shooting || g_rolling)', | |
action: 'printResult(' + phi + '); cueNewTestShot();'}); | |
} | |
} | |
function startShooting() { | |
g_shooting = true; | |
g_shooting_timers.push( | |
setInterval('increaseFactor()', 1000.0 / 60.0)); | |
} | |
function increaseFactor() { | |
g_physics.speedFactor += 0.01; | |
setBarScale(g_physics.speedFactor); | |
if (g_physics.speedFactor > 1) | |
g_physics.speedFactor = 1; | |
} | |
function finishShooting() { | |
while (g_shooting_timers.length > 0) | |
clearTimeout(g_shooting_timers.pop()) | |
if (g_physics.speedFactor > 0.0) { | |
playSound(2, g_physics.speedFactor, 0,0); | |
var eye = [0, 0, 0]; | |
var target = [0, 0, 0]; | |
g_cameraInfo.getEyeAndTarget(eye, target); | |
var dx = target[0] - eye[0]; | |
var dy = target[1] - eye[1]; | |
var norm = Math.sqrt(dx * dx + dy * dy); | |
g_physics.impartSpeed(0, [dx / norm, dy / norm]); | |
g_cameraInfo.backUp(); | |
g_rolling = true; | |
g_barRoot.visible = false; | |
} | |
g_physics.speedFactor = 0; | |
g_seriousness = 0; | |
setBarScale(g_physics.speedFactor); | |
g_shooting = false; | |
} | |
function keyUp(event) { | |
switch (event.keyCode) { | |
case 32: | |
finishShooting(); | |
break; | |
} | |
} | |
function keyDown(event) { | |
switch (event.keyCode) { | |
} | |
} | |
function keyPressed(event) { | |
var keyChar = String.fromCharCode(o3djs.event.getEventKeyChar(event)); | |
keyChar = keyChar.toLowerCase(); | |
var identifier = o3djs.event.getKeyIdentifier(event.charCode, event.keyCode); | |
var spotDelta = 1; | |
var cueBall = g_physics.balls[0]; | |
var x = cueBall.center[0]; | |
var y = cueBall.center[1]; | |
switch(keyChar) { | |
case '*': | |
rack(8); | |
break; | |
case '(': | |
rack(9); | |
break; | |
case ')': | |
rack(0); | |
break; | |
case 'd': | |
ballOn(0); | |
placeBall(0, x + spotDelta, y); | |
g_physics.boundCueBall(); | |
break; | |
case 'a': | |
ballOn(0); | |
placeBall(0, x - spotDelta, y); | |
g_physics.boundCueBall(); | |
break; | |
case 's': | |
ballOn(0); | |
placeBall(0, x, y - spotDelta); | |
g_physics.boundCueBall(); | |
break; | |
case 'w': | |
ballOn(0); | |
placeBall(0, x, y + spotDelta); | |
g_physics.boundCueBall(); | |
break; | |
case 'c': | |
g_cameraInfo.zoomToPoint(g_physics.balls[0].center); | |
if (!g_rolling) | |
g_barRoot.visible = true; | |
break; | |
case 't': | |
g_cameraInfo.goTo([0, 0, 0], null, null, 100); | |
break; | |
case '=': | |
case '+': | |
g_cameraInfo.targetPosition.radius *= 0.9; | |
break; | |
case '-': | |
case '_': | |
g_cameraInfo.targetPosition.radius /= 0.9; | |
break; | |
case ' ': | |
if (!g_cameraInfo.lookingAt(g_physics.balls[0].center)) { | |
g_cameraInfo.zoomToPoint(g_physics.balls[0].center); | |
if (!g_rolling) | |
g_barRoot.visible = true; | |
} else { | |
if (g_seriousness > 1) { | |
if (!(g_rolling || g_shooting)) { | |
startShooting(); | |
} | |
} | |
g_seriousness++; | |
} | |
break; | |
} | |
updateContext(); | |
} | |
</script> | |
</head> | |
<body onload="initClient()" style="background-color: #111111"> | |
<div id="container"> | |
<!-- Start of O3D client area --> | |
<div id="o3d" style="width: 100%; height:100%;"> </div> | |
<!-- End of O3D plugin --> | |
<div id="footer"> | |
<center> | |
<table width = 800 style="color: gray"> | |
<tr> | |
<td> Click and drag to move the view. </td> | |
<td> spacebar : Hold down to shoot.</td> | |
<td> t : Table view mode.</td> | |
<td> * : Rack for 8-Ball. </td> | |
</tr> | |
<tr> | |
<td> +/- : Zoom in / out. </td> | |
<td> asdw : Position the cue ball.</td> | |
<td> c : Cue ball view mode.</td> | |
<td> ( : Rack for 9-Ball. </td> | |
</tr> | |
</table> | |
</center> | |
</div> | |
</div> | |
<div style="display:none"> | |
<!-- Start of effect --> | |
<textarea id="vshader"> | |
uniform mat4 worldViewProjection; | |
uniform mat4 worldInverseTranspose; | |
uniform mat4 world; | |
attribute vec4 position; | |
attribute vec3 normal; | |
varying vec4 vposition; | |
varying vec4 vobjectPosition; | |
varying vec3 vworldPosition; | |
varying vec4 vscreenPosition; | |
varying vec3 vnormal; | |
void main() { | |
vposition = worldViewProjection * position; | |
vec4 temp = vposition; | |
temp += temp.w * vec4(1.0, 1.0, 0.0, 0.0); | |
temp.xyz /= 2.0; | |
vscreenPosition = temp; | |
vnormal = (worldInverseTranspose * vec4(normal, 0.0)).xyz; | |
vworldPosition = (world * vec4(position.xyz, 1.0)).xyz; | |
vobjectPosition = position; | |
gl_Position = vposition; | |
} | |
</textarea> | |
<textarea id="pshader"> | |
#ifdef GL_ES | |
precision mediump float; | |
#endif | |
uniform vec3 lightWorldPosition; | |
uniform vec3 eyeWorldPosition; | |
uniform float factor; | |
uniform float shadowOn; | |
uniform sampler2D textureSampler; | |
uniform vec2 ballCenter; | |
varying vec4 vposition; | |
varying vec4 vobjectPosition; | |
varying vec3 vworldPosition; | |
varying vec4 vscreenPosition; | |
varying vec3 vnormal; | |
vec4 roomColor(vec3 p, vec3 r) { | |
vec2 c = vec2(1.0 / 15.0, 1.0 / 30.0) * | |
(p.xy + r.xy * (lightWorldPosition.z - p.z) / r.z); | |
float temp = (abs(c.x + c.y) + abs(c.y - c.x)); | |
float t = min(0.15 * max(7.0 - temp, 0.0) + | |
((temp < 5.0) ? 1.0 : 0.0), 1.0); | |
return vec4(t, t, t, 1.0); | |
} | |
vec4 lighting(vec4 pigment, float shininess) { | |
vec3 p = vworldPosition; | |
vec3 l = normalize(lightWorldPosition - p); // Toward light. | |
vec3 n = normalize(vnormal); // Normal. | |
vec3 v = normalize(eyeWorldPosition - p); // Toward eye. | |
vec3 r = normalize(-reflect(v, n)); // Reflection of v across n. | |
return vec4(max(dot(l, n), 0.0) * pigment.xyz + | |
0.2 * pow(max(dot(l, r), 0.0), shininess) * vec3(1, 1, 1), 1.0); | |
} | |
vec4 woodPigment(vec3 p) { | |
vec3 core = normalize( | |
(abs(p.y) > abs(p.x) + 1.0) ? | |
vec3(1.0, 0.2, 0.3) : vec3(0.2, 1.0, 0.3)); | |
float grainThickness = 0.02; | |
float t = | |
mod(length(p - dot(p,core)*core), grainThickness) / grainThickness; | |
return mix(vec4(0.15, 0.05, 0.0, 0.1), vec4(0.1, 0.0, 0.0, 0.1), t); | |
} | |
vec4 feltPigment(vec3 p) { | |
return vec4(0.1, 0.45, 0.15, 1.0); | |
} | |
vec4 environmentColor(vec3 p, vec3 r) { | |
vec4 upColor = 0.1 * roomColor(p, r); | |
vec4 downColor = -r.z * 0.3 * feltPigment(p); | |
float t = smoothstep(0.0, 0.05, r.z); | |
return mix(downColor, upColor, t); | |
} | |
vec4 solidPixelShader() { | |
return vec4(1.0, 1.0, 1.0, 0.2); | |
} | |
vec4 feltPixelShader() { | |
vec2 tex = vscreenPosition.xy / vscreenPosition.w; | |
vec3 p = factor * vworldPosition; | |
vec3 c = factor * eyeWorldPosition.xyz; | |
float width = 0.3; | |
float height = 0.3; | |
float d = | |
1.0 * (smoothstep(1.0 - width, 1.0 + width, abs(p.x)) + | |
smoothstep(2.0 - height, 2.0 + height, abs(p.y))); | |
p = vworldPosition; | |
return (1.0 - texture2D(textureSampler, tex).x - d) * | |
lighting(feltPigment(p), 4.0); | |
} | |
vec4 woodPixelShader() { | |
vec3 p = factor * vworldPosition; | |
return lighting(woodPigment(p), 50.0); | |
} | |
vec4 cushionPixelShader() { | |
vec3 p = factor * vworldPosition; | |
return lighting(feltPigment(p), 4.0); | |
} | |
vec4 billiardPixelShader() { | |
vec3 p = factor * vworldPosition; | |
return lighting(vec4(0.5, 0.5, 0.2, 1), 30.0); | |
} | |
vec4 ballPixelShader() { | |
vec3 p = normalize(vobjectPosition.xyz); | |
vec4 u = 0.5 * vec4(p.x, p.y, p.x, -p.y); | |
u = clamp(u, -0.45, 0.45); | |
u += vec4(0.5, 0.5, 0.5, 0.5); | |
float t = clamp(5.0 * p.z, 0.0, 1.0); | |
p = vworldPosition; | |
vec3 l = normalize(lightWorldPosition - p); // Toward light. | |
vec3 n = normalize(vnormal); // Normal. | |
vec3 v = normalize(eyeWorldPosition - p); // Toward eye. | |
vec3 r = normalize(-reflect(v, n)); // Reflection of v across n. | |
vec4 pigment = | |
mix(texture2D(textureSampler, u.zw), | |
texture2D(textureSampler, u.xy), t); | |
return 0.4 * environmentColor(p, r) + | |
pigment * (0.3 * smoothstep(0.0, 1.1, dot(n, l)) + | |
0.3 * (p.z + 1.0)); | |
} | |
vec4 shadowPlanePixelShader() { | |
vec2 p = vworldPosition.xy - ballCenter; | |
vec2 q = (vworldPosition.xy / lightWorldPosition.z); | |
vec2 offset = (1.0 - 1.0 / (vec2(1.0, 1.0) + abs(q))) * sign(q); | |
float t = mix(smoothstep(0.9, 0.0, length(p - length(p) * offset) / 2.0), | |
smoothstep(1.0, 0.0, length(p) / 10.0), 0.15); | |
return shadowOn * vec4(t, t, t, t); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment