/* * KNWebGLObjects.js * Keynote HTML Player * * Created by Tungwei Cheng * Copyright (c) 2016-2019 Apple Inc. All rights reserved. */ var kShaderUniformGravity = "Gravity"; var kShaderUniformMaskTexture = "MaskTexture"; var kShaderUniformNoiseAmount = "NoiseAmount"; var kShaderUniformNoiseMax = "NoiseMax"; var kShaderUniformNoiseSeed = "NoiseSeed"; var kShaderUniformParticleBurstTiming = "ParticleBurstTiming"; var kShaderUniformPreviousParticleBurstTiming = "PreviousParticleBurstTiming"; var kShaderUniformPreviousPercent = "PreviousPercent"; var kShaderUniformShouldSparkle = "ShouldSparkle"; var kShaderUniformSparklePeriod = "SparklePeriod"; var kShaderUniformSparkleStartTime = "SparkleStartTime"; var kShaderUniformStartScale = "StartScale"; var kShimmerUniformParticleScalePercent = "ParticleScalePercent"; var kShimmerUniformRotationMatrix = "RotationMatrix"; var KNSparkleMaxParticleLife = 0.667; var KNWebGLRenderer = Class.create({ initialize: function(params) { var canvas = this.canvas = params.canvas; this.canvasId = params.canvasId; this.textureAssets = params.textureAssets; this.durationMax = params.overallEndTime * 1000; this.glPrograms = []; // to be used in request animation frame this.elapsed = 0; // attempt to create webgl context var gl = this.gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl"); // if webgl is not supported then set noGL to true if (!gl) { this.noGL = true; return; } // indicate if the animation has started for this renderer this.animationStarted = false; gl.viewportWidth = canvas.width; gl.viewportHeight = canvas.height; // create default project matrix this.initMVPMatrix(); // initialize core animation wrapper this.coreAnimationWrapper = new KNWebGLCoreAnimationWrapper(gl); }, initMVPMatrix: function() { var gl = this.gl; var w = gl.viewportWidth; var h = gl.viewportHeight; var fovradians = 20 * (Math.PI / 180); var backupDistance = h / (2 * Math.tan(fovradians / 2)); var frontclipping = backupDistance - (w * 1.5); var backclipping = backupDistance + (w * 15.0); // create default ortho and proj matrices this.slideProjectionMatrix = WebGraphics.makePerspectiveMatrix4(20, w / h, Math.max(1, frontclipping), backclipping); var translate = WebGraphics.translateMatrix4(WebGraphics.createMatrix4(), -w / 2, -h / 2, -backupDistance); this.slideProjectionMatrix = WebGraphics.multiplyMatrix4(this.slideProjectionMatrix, translate); this.slideOrthoMatrix = WebGraphics.makeOrthoMatrix4(0, w, 0, h, -1, 1); }, setupTexture: function(effect) { var textures = []; this.textureInfoFromEffect(effect.kpfLayer, effect.name, {"pointX": 0, "pointY": 0}, effect.baseLayer.initialState.opacity, textures); for (var i = 0, length = textures.length; i < length; i++) { var textureId = textures[i].textureId; var image = this.textureAssets[textureId]; textures[i].texture = KNWebGLUtil.createTexture(this.gl, image); var toTextureId = textures[i].toTextureId; if (toTextureId) { var toTextureImage = this.textureAssets[toTextureId]; textures[i].toTexture = KNWebGLUtil.createTexture(this.gl, toTextureImage); } } return textures; }, textureInfoFromEffect: function(kpfLayer, name, offset, parentOpacity, textures) { var textureInfo = {}; textureInfo.offset = { "pointX": offset.pointX + kpfLayer.bounds.offset.pointX, "pointY": offset.pointY + kpfLayer.bounds.offset.pointY }; textureInfo.parentOpacity = parentOpacity * kpfLayer.initialState.opacity; if (kpfLayer.textureId) { textureInfo.textureId = kpfLayer.textureId; textureInfo.width = kpfLayer.bounds.width; textureInfo.height = kpfLayer.bounds.height; textureInfo.initialState = kpfLayer.initialState; textureInfo.hasHighlightedBulletAnimation = kpfLayer.hasHighlightedBulletAnimation; textureInfo.texturedRectangle = kpfLayer.texturedRectangle; // search the animations within group for contents animation var groupAnimations = kpfLayer.animations; if (groupAnimations && groupAnimations.length > 0) { var groupAnimation = groupAnimations[0]; if (groupAnimation.property === "contents") { textureInfo.toTextureId = groupAnimation.to.texture; } else if (!groupAnimation.property) { var animations = groupAnimation.animations; if (animations) { for (var i = 0, length = animations.length; i < length; i++) { var animation = animations[i]; if (animation.property === "contents") { textureInfo.toTextureId = animation.to.texture; break; } } } } } textureInfo.animations = groupAnimations; textureInfo.textureRect = { origin: { x: textureInfo.offset.pointX, y: textureInfo.offset.pointY }, size: { width: textureInfo.width, height: textureInfo.height } }; textures.push(textureInfo); } else { for (var i = 0, length = kpfLayer.layers.length; i < length; i++) { this.textureInfoFromEffect(kpfLayer.layers[i], name, textureInfo.offset, textureInfo.parentOpacity, textures); } } }, draw: function(effect) { var params = { effect: effect, textures: this.setupTexture(effect) }; var effectType = effect.type; var program; if (effectType === "transition") { switch (effect.name) { case "apple:wipe-iris": program = new KNWebGLTransitionIris(this, params); break; case "com.apple.iWork.Keynote.BUKTwist": program = new KNWebGLTransitionTwist(this, params); break; case "com.apple.iWork.Keynote.KLNColorPlanes": program = new KNWebGLTransitionColorPlanes(this, params); break; case "com.apple.iWork.Keynote.BUKFlop": program = new KNWebGLTransitionFlop(this, params); break; case "com.apple.iWork.Keynote.KLNConfetti": program = new KNWebGLTransitionConfetti(this, params); break; case "apple:magic-move-implied-motion-path": program = new KNWebGLTransitionMagicMove(this, params); break; case "apple:ca-text-shimmer": program = new KNWebGLTransitionShimmer(this, params); break; case "apple:ca-text-sparkle": program = new KNWebGLTransitionSparkle(this, params); break; default: // fallback to dissolve program = new KNWebGLDissolve(this, params); break; } } else if (effectType === "buildIn" || effectType === "buildOut") { switch (effect.name) { case "apple:wipe-iris": program = new KNWebGLBuildIris(this, params); break; case "com.apple.iWork.Keynote.BUKAnvil": program = new KNWebGLBuildAnvil(this, params); break; case "com.apple.iWork.Keynote.KLNFlame": program = new KNWebGLBuildFlame(this, params); break; case "com.apple.iWork.Keynote.KNFireworks": program = new KNWebGLBuildFireworks(this, params); break; case "com.apple.iWork.Keynote.KLNConfetti": program = new KNWebGLBuildConfetti(this, params); break; case "com.apple.iWork.Keynote.KLNDiffuse": program = new KNWebGLBuildDiffuse(this, params); break; case "com.apple.iWork.Keynote.KLNShimmer": program = new KNWebGLBuildShimmer(this, params); break; case "com.apple.iWork.Keynote.KLNSparkle": program = new KNWebGLBuildSparkle(this, params); break; default: // fallback to dissolve program = new KNWebGLDissolve(this, params); break; } } else if (effectType === "smartBuild") { switch (effect.name) { case "apple:gallery-dissolve": program = new KNWebGLContents(this, params); break; default: // fallback to dissolve program = new KNWebGLDissolve(this, params); break; } } // remove existing gl program for the same object when new program is rendered such as build in by highlighted paragraph this.removeProgram(effect.objectID); // push new gl program into the array this.glPrograms.push(program); }, animate: function() { // compute time difference var time = new Date(); var difference = 0; if (this.time) { var mseconds = time.getTime(); difference = mseconds - this.time; this.time = mseconds; } else { difference = 0; this.time = time.getTime(); } this.elapsed += difference; var glPrograms = this.glPrograms; var length = glPrograms.length; if (this.elapsed <= this.durationMax) { // set up the frame for the next drawing operation, only if there is time left in the animation this.animationRequest = window.requestAnimFrame(this.animate.bind(this)); } else { // set gl program to isCompleted when there is no overall event time left for (var i = 0; i < length; i++) { var program = glPrograms[i]; program.isCompleted = true; } } // clear the buffers before animation frame var gl = this.gl; gl.clearColor(0, 0, 0, 0); gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); for (var i = 0; i < length; i++) { var program = glPrograms[i]; program.drawFrame(difference, this.elapsed, program.duration); } }, removeProgram: function(objectID) { var glPrograms = this.glPrograms; var glProgramLength = glPrograms.length; // remove gl program for the same objectID from the array while (glProgramLength--) { var glProgram = glPrograms[glProgramLength]; if (glProgram.effect.objectID === objectID) { glPrograms.splice(glProgramLength, 1); } } }, resize: function(viewport) { var gl = this.gl; var viewportWidth = viewport.width; var viewportHeight = viewport.height; if (gl.viewportWidth !== viewportWidth || gl.viewportHeight !== viewportHeight) { gl.viewport(0, 0, viewportWidth, viewportHeight); gl.viewportWidth = viewportWidth; gl.viewportHeight = viewportHeight; } } }); var KNWebGLProgram = Class.create({ initialize: function(renderer, programData) { // reference to the renderer this.renderer = renderer; // reference to gl context this.gl = renderer.gl; // specify textures this.textures = programData.textures; // reference to the effect object var effect = this.effect = programData.effect; // specify the effect type var type = this.type = effect.type; // specific the direction from the effect this.direction = effect.attributes ? effect.attributes.direction : null; // specify the duration from the effect this.duration = effect.duration * 1000; // boolean to indicate if the effect is a build out this.buildOut = type === "buildOut"; // boolean to indicate if the effect is a build in this.buildIn = type === "buildIn"; // create a shader program container object this.program = {}; // indicate if the effect is completed this.isCompleted = false; // setup program data if (programData.programNames) { this.setupProgram(programData); } }, setupProgram: function(programData) { var gl = this.gl; for (var i = 0, length = programData.programNames.length; i < length; i++) { var programName = programData.programNames[i]; this.program[programName] = KNWebGLUtil.setupProgram(gl, programName); } // enable blend function gl.enable(gl.BLEND); gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); } }); var KNWebGLContents = Class.create(KNWebGLProgram, { initialize: function($super, renderer, params) { this.programData = { name: "contents", effect: params.effect, textures: params.textures }; $super(renderer, this.programData); // initialize percent finish based on effect type this.percentfinished = 0; // setup requirements this.animationWillBeginWithContext(); }, animationWillBeginWithContext: function() { var renderer = this.renderer; var gl = this.gl; var textureRect = this.textures[0].textureRect; var vertexRect = CGRectMake(0, 0, textureRect.size.width, textureRect.size.height); var meshSize = CGSizeMake(2, 2); // init contents shader and data buffer var contentsShader = this.contentsShader = new TSDGLShader(gl); contentsShader.initWithContentsShader(); // contents shader set methods contentsShader.setMat4WithTransform3D(renderer.slideProjectionMatrix, kTSDGLShaderUniformMVPMatrix); // outgoing Texture contentsShader.setGLint(0, kTSDGLShaderUniformTexture2); // incoming Texture contentsShader.setGLint(1, kTSDGLShaderUniformTexture); // init contents data buffer var contentsDataBuffer = this.contentsDataBuffer = new TSDGLDataBuffer(gl); contentsDataBuffer.initWithVertexRect(vertexRect, TSDRectUnit, meshSize, false, false); }, drawFrame: function(difference, elapsed, duration) { var renderer = this.renderer; var gl = this.gl; var percentfinished = this.percentfinished; percentfinished += difference / duration; if (percentfinished >= 1) { percentfinished = 1; this.isCompleted = true; } this.percentfinished = percentfinished; // draw contents using glsl mix this.p_drawContents(percentfinished); }, p_drawContents: function(percent) { var gl = this.gl; var textures = this.textures; var incomingTexture = textures[0].texture; var outgoingTexture = textures[1].texture; // calculate the mix factor in ease in and ease out fashion var mixFactor = TSUSineMap(percent); if (percent >= 1) { mixFactor = 1.0; } gl.activeTexture(gl.TEXTURE1); gl.bindTexture(gl.TEXTURE_2D, incomingTexture); gl.activeTexture(gl.TEXTURE0); gl.bindTexture(gl.TEXTURE_2D, outgoingTexture); this.contentsShader.setGLFloat(mixFactor, "mixFactor"); gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA); this.contentsDataBuffer.drawWithShader(this.contentsShader, true); } }); var KNWebGLDrawable = Class.create(KNWebGLProgram, { initialize: function($super, renderer, params) { this.programData = { name: "WebDrawable", programNames:["defaultTextureAndOpacity"], effect: params.effect, textures: params.textures }; $super(renderer, this.programData); this.Opacity = 1.0; // setup web drawable requirements this.animationWillBeginWithContext(); }, animationWillBeginWithContext: function() { var renderer = this.renderer; var gl = this.gl; var program = this.program["defaultTextureAndOpacity"]; var uniforms = program.uniforms; var attribs = program.attribs; var textureInfo = this.textures[0]; gl.useProgram(program.shaderProgram); gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA); // create WebGLBuffer object for texture coordinates var textureCoordinateBuffer = this.textureCoordinateBuffer = gl.createBuffer(); var textureCoordinates = this.textureCoordinates = [ 0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 1.0, 1.0, ]; // bind WebGLBuffer object to gl.ARRAY_BUFFER target gl.bindBuffer(gl.ARRAY_BUFFER, textureCoordinateBuffer); // send vertex data to this bound buffer gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(textureCoordinates), gl.STATIC_DRAW); // create WebGLBuffer object for position coordinates var positionBuffer = this.positionBuffer = gl.createBuffer(); var boxPosition = this.boxPosition = [ 0.0, 0.0, 0.0, 0.0, textureInfo.height, 0.0, textureInfo.width, 0.0, 0.0, textureInfo.width, textureInfo.height, 0.0 ]; // bind WebGLBuffer object to gl.ARRAY_BUFFER target gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer); // send vertex data to this bound buffer gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(boxPosition), gl.STATIC_DRAW); // move the MVPMatrix to appropriate offset this.MVPMatrix = WebGraphics.translateMatrix4(renderer.slideProjectionMatrix, textureInfo.offset.pointX, gl.viewportHeight - textureInfo.offset.pointY - textureInfo.height, 0); }, drawFrame: function() { var renderer = this.renderer; var gl = this.gl; var program = this.program["defaultTextureAndOpacity"]; var uniforms = program.uniforms; var attribs = program.attribs; var textures = this.textures; var texture = textures[0].texture; gl.useProgram(program.shaderProgram); // bind WebGLBuffer object to gl.ARRAY_BUFFER target gl.bindBuffer(gl.ARRAY_BUFFER, this.textureCoordinateBuffer); // assigns the WebGLBuffer object currently bound to the gl.ARRAY_BUFFER target to a vertex attribute index gl.vertexAttribPointer(attribs["TexCoord"], 2, gl.FLOAT, false, 0, 0); // call enableVertexAttribArray, otherwise it won't draw gl.enableVertexAttribArray(attribs["TexCoord"]); // bind WebGLBuffer object to gl.ARRAY_BUFFER target gl.bindBuffer(gl.ARRAY_BUFFER, this.positionBuffer); // assigns the WebGLBuffer object currently bound to the gl.ARRAY_BUFFER target to a vertex attribute index gl.vertexAttribPointer(attribs["Position"], 3, gl.FLOAT, false, 0, 0); // call enableVertexAttribArray, otherwise it won't draw gl.enableVertexAttribArray(attribs["Position"]); // set MVPMatrix gl.uniformMatrix4fv(uniforms["MVPMatrix"], false, this.MVPMatrix); // set Opacity gl.uniform1f(uniforms["Opacity"], this.Opacity); // set sampler2D Texture in fragment shader to have the value 0, so it matches the texture unit gl.TEXTURE0 gl.activeTexture(gl.TEXTURE0); gl.uniform1i(uniforms["Texture"], 0); // bind the texture to texture unit gl.TEXTURE0 gl.bindTexture(gl.TEXTURE_2D, texture); gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); } }); var KNWebGLFramebufferDrawable = Class.create(KNWebGLProgram, { initialize: function($super, renderer, params) { var gl = renderer.gl; var frameRect = this.frameRect = params.frameRect; var texture = this.texture = this.createFramebufferTexture(gl, frameRect); this.buffer = this.createFramebuffer(gl, texture); var textureInfo = { width: frameRect.size.width, height: frameRect.size.height, offset: {pointX: 0, pointY: 0}, texture: texture }; this.programData = { name: "FramebufferDrawable", programNames:["defaultTexture"], effect: params.effect, textures: [textureInfo] }; $super(renderer, this.programData); this.drawableFrame = params.drawableFrame; // setup web drawable requirements this.animationWillBeginWithContext(); }, createFramebufferTexture: function(gl, rect) { var texture = gl.createTexture(); // bind texture gl.bindTexture(gl.TEXTURE_2D, texture); gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, true); gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); // setup texture parameters gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); // specify the texture size for memory allocation gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, rect.size.width, rect.size.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); // unbind texture gl.bindTexture(gl.TEXTURE_2D, null); return texture; }, createFramebuffer: function(gl, texture) { var buffer = gl.createFramebuffer(); //bind framebuffer to texture gl.bindFramebuffer(gl.FRAMEBUFFER, buffer); gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0); return buffer; }, animationWillBeginWithContext: function() { var renderer = this.renderer; var gl = this.gl; var program = this.program["defaultTexture"]; var uniforms = program.uniforms; var attribs = program.attribs; var textureInfo = this.textures[0]; gl.useProgram(program.shaderProgram); gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA); // create WebGLBuffer object for texture coordinates var textureCoordinateBuffer = this.textureCoordinateBuffer = gl.createBuffer(); var textureCoordinates = this.textureCoordinates = [ 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 1.0, 0.0, ]; // bind WebGLBuffer object to gl.ARRAY_BUFFER target gl.bindBuffer(gl.ARRAY_BUFFER, textureCoordinateBuffer); // send vertex data to this bound buffer gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(textureCoordinates), gl.STATIC_DRAW); // create WebGLBuffer object for position coordinates var positionBuffer = this.positionBuffer = gl.createBuffer(); var boxPosition = this.boxPosition = [ 0.0, 0.0, 0.0, 0.0, textureInfo.height, 0.0, textureInfo.width, 0.0, 0.0, textureInfo.width, textureInfo.height, 0.0 ]; // bind WebGLBuffer object to gl.ARRAY_BUFFER target gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer); // send vertex data to this bound buffer gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(boxPosition), gl.STATIC_DRAW); // move the MVPMatrix to appropriate offset this.MVPMatrix = WebGraphics.translateMatrix4(renderer.slideProjectionMatrix, textureInfo.offset.pointX, gl.viewportHeight - textureInfo.offset.pointY - textureInfo.height, 0); }, drawFrame: function() { var renderer = this.renderer; var gl = this.gl; var program = this.program["defaultTexture"]; var uniforms = program.uniforms; var attribs = program.attribs; var textures = this.textures; var texture = textures[0].texture; gl.useProgram(program.shaderProgram); // bind WebGLBuffer object to gl.ARRAY_BUFFER target gl.bindBuffer(gl.ARRAY_BUFFER, this.textureCoordinateBuffer); // assigns the WebGLBuffer object currently bound to the gl.ARRAY_BUFFER target to a vertex attribute index gl.vertexAttribPointer(attribs["TexCoord"], 2, gl.FLOAT, false, 0, 0); // call enableVertexAttribArray, otherwise it won't draw gl.enableVertexAttribArray(attribs["TexCoord"]); // bind WebGLBuffer object to gl.ARRAY_BUFFER target gl.bindBuffer(gl.ARRAY_BUFFER, this.positionBuffer); // assigns the WebGLBuffer object currently bound to the gl.ARRAY_BUFFER target to a vertex attribute index gl.vertexAttribPointer(attribs["Position"], 3, gl.FLOAT, false, 0, 0); // call enableVertexAttribArray, otherwise it won't draw gl.enableVertexAttribArray(attribs["Position"]); // set MVPMatrix gl.uniformMatrix4fv(uniforms["MVPMatrix"], false, this.MVPMatrix); // set sampler2D Texture in fragment shader to have the value 0, so it matches the texture unit gl.TEXTURE0 gl.activeTexture(gl.TEXTURE0); gl.uniform1i(uniforms["Texture"], 0); // bind the texture to texture unit gl.TEXTURE0 gl.bindTexture(gl.TEXTURE_2D, texture); gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); } }); var KNWebGLDissolve = Class.create(KNWebGLProgram, { initialize: function($super, renderer, params) { this.programData = { name: "dissolve", programNames:["defaultTextureAndOpacity"], effect: params.effect, textures: params.textures }; $super(renderer, this.programData); // initialize percent finish based on effect type this.percentfinished = 0; // setup requirements this.animationWillBeginWithContext(); }, animationWillBeginWithContext: function() { var renderer = this.renderer; var gl = this.gl; var program = this.program["defaultTextureAndOpacity"]; var uniforms = program.uniforms; var attribs = program.attribs; var textureInfo = this.textures[0]; gl.useProgram(program.shaderProgram); gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA); // create WebGLBuffer object for texture coordinates var textureCoordinateBuffer = this.textureCoordinateBuffer = gl.createBuffer(); var textureCoordinates = this.textureCoordinates = [ 0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 1.0, 1.0, ]; // bind WebGLBuffer object to gl.ARRAY_BUFFER target gl.bindBuffer(gl.ARRAY_BUFFER, textureCoordinateBuffer); // send vertex data to this bound buffer gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(textureCoordinates), gl.STATIC_DRAW); // create WebGLBuffer object for position coordinates var positionBuffer = this.positionBuffer = gl.createBuffer(); var boxPosition = this.boxPosition = [ 0.0, 0.0, 0.0, 0.0, textureInfo.height, 0.0, textureInfo.width, 0.0, 0.0, textureInfo.width, textureInfo.height, 0.0 ]; // bind WebGLBuffer object to gl.ARRAY_BUFFER target gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer); // send vertex data to this bound buffer gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(boxPosition), gl.STATIC_DRAW); this.MVPMatrix = WebGraphics.translateMatrix4(renderer.slideProjectionMatrix, textureInfo.offset.pointX, gl.viewportHeight - (textureInfo.offset.pointY + textureInfo.height), 0); this.drawFrame(0, 0, 4); }, drawFrame: function(difference, elapsed, duration) { var percentfinished = this.percentfinished; percentfinished += difference / duration; percentfinished > 1 ? percentfinished = 1 : 0; var percentAlpha = TSUSineMap(percentfinished); if (percentfinished === 1) { percentAlpha = 1.0; } if (this.buildOut) { percentAlpha = 1 - percentAlpha; } this.percentfinished = percentfinished; this.percentAlpha = percentAlpha; this.draw(); }, draw: function() { var renderer = this.renderer; var gl = this.gl; var program = this.program["defaultTextureAndOpacity"]; var uniforms = program.uniforms; var attribs = program.attribs; var textures = this.textures; var texture = textures[0].texture; var outgoingTexture; if (textures.length > 1) { outgoingTexture = textures[1].texture; } // use this program gl.useProgram(program.shaderProgram); gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA); // bind WebGLBuffer object to gl.ARRAY_BUFFER target gl.bindBuffer(gl.ARRAY_BUFFER, this.textureCoordinateBuffer); // send vertex data to this bound buffer gl.vertexAttribPointer(attribs["TexCoord"], 2, gl.FLOAT, false, 0, 0); // call enableVertexAttribArray, otherwise it won't draw gl.enableVertexAttribArray(attribs["TexCoord"]); // bind WebGLBuffer object to gl.ARRAY_BUFFER target gl.bindBuffer(gl.ARRAY_BUFFER, this.positionBuffer); // send vertex data to this bound buffer gl.vertexAttribPointer(attribs["Position"], 3, gl.FLOAT, false, 0, 0); // call enableVertexAttribArray, otherwise it won't draw gl.enableVertexAttribArray(attribs["Position"]); // set MVPMatrix gl.uniformMatrix4fv(uniforms["MVPMatrix"], false, this.MVPMatrix); // set sampler2D Texture in fragment shader to have the value 0, so it matches the texture unit gl.TEXTURE0 gl.activeTexture(gl.TEXTURE0); gl.uniform1i(uniforms["Texture"], 0); // bind the texture to texture unit gl.TEXTURE0 if (outgoingTexture) { gl.bindTexture(gl.TEXTURE_2D, outgoingTexture); gl.uniform1f(uniforms["Opacity"], 1.0); gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); } gl.bindTexture(gl.TEXTURE_2D, texture); gl.uniform1f(uniforms["Opacity"], this.percentAlpha); gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); } }); var KNWebGLTransitionIris = Class.create(KNWebGLProgram, { initialize: function($super, renderer, params) { this.programData = { name: "apple:wipe-iris", programNames: ["iris"], effect: params.effect, textures: params.textures }; $super(renderer, this.programData); // determine the type and direction var direction = this.direction; var directionOut = direction === KNDirection.kKNDirectionOut; var buildOut = this.buildOut; if ((buildOut && directionOut) || (!buildOut && !directionOut)) { this.mix = 0.0; this.percentfinished = 1.0; } else { this.mix = 1.0; this.percentfinished = 0.0; } this.percentAlpha = 0.0; // setup requirements this.animationWillBeginWithContext(); }, animationWillBeginWithContext: function() { var renderer = this.renderer; var gl = this.gl; var program = this.program["iris"]; var attribs = program.attribs; var uniforms = program.uniforms; var textureInfo = this.textures[0]; gl.useProgram(program.shaderProgram); gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA); // initial scale uniform this.scale = textureInfo.width/textureInfo.height; // create buffers var textureCoordinatesBuffer = this.textureCoordinatesBuffer = gl.createBuffer(); var textureCoordinates = this.textureCoordinates = [ 0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 1.0, 1.0, ]; gl.bindBuffer(gl.ARRAY_BUFFER, textureCoordinatesBuffer); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(textureCoordinates), gl.STATIC_DRAW); var positionBuffer = this.positionBuffer = gl.createBuffer(); var boxPosition = this.boxPosition = [ 0.0, 0.0, 0.0, 0.0, textureInfo.height, 0.0, textureInfo.width, 0.0, 0.0, textureInfo.width, textureInfo.height, 0.0 ]; gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(boxPosition), gl.STATIC_DRAW); this.MVPMatrix = WebGraphics.translateMatrix4(renderer.slideProjectionMatrix, textureInfo.offset.pointX, gl.viewportHeight - (textureInfo.offset.pointY + textureInfo.height), 0); this.drawFrame(0, 0, 4); }, drawFrame: function(difference, elapsed, duration) { // determine the type and direction var buildOut = this.buildOut; var directionOut = this.direction === KNDirection.kKNDirectionOut; var percentfinished = this.percentfinished; if ((buildOut && directionOut) || (!buildOut && !directionOut)) { percentfinished -= difference / duration; percentfinished < 0 ? percentfinished = 0 : 0; } else { percentfinished += difference / duration; percentfinished > 1 ? percentfinished = 1 : 0; } var percentAlpha = TSUSineMap(percentfinished); if (percentfinished === 1) { percentAlpha = 1.0; } if (buildOut) { percentAlpha = 1 - percentAlpha; } this.percentAlpha = percentAlpha; this.percentfinished = percentfinished; this.draw(); }, draw: function() { var renderer = this.renderer; var gl = this.gl; var program = this.program["iris"]; var attribs = program.attribs; var uniforms = program.uniforms; var textures = this.textures; var texture = textures[0].texture; var textureInfo = textures[0]; var outgoingTexture; var scale = this.scale; if (textures.length > 1) { outgoingTexture = textures[1].texture; } gl.useProgram(program.shaderProgram); gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA); // setup attributes gl.bindBuffer(gl.ARRAY_BUFFER, this.textureCoordinatesBuffer); gl.vertexAttribPointer(attribs["TexCoord"], 2, gl.FLOAT, false, 0, 0); gl.enableVertexAttribArray(attribs["TexCoord"]); gl.bindBuffer(gl.ARRAY_BUFFER, this.positionBuffer); gl.vertexAttribPointer(attribs["Position"], 3, gl.FLOAT, false, 0, 0); gl.enableVertexAttribArray(attribs["Position"]); // setup uniforms and textures gl.uniformMatrix4fv(uniforms["MVPMatrix"], false, this.MVPMatrix); gl.activeTexture(gl.TEXTURE0); gl.uniform1i(uniforms["Texture"], 0); // set Opacity gl.uniform1f(uniforms["Opacity"], 1); // bg texture if (outgoingTexture) { gl.bindTexture(gl.TEXTURE_2D, outgoingTexture); gl.uniform1f(uniforms["PercentForAlpha"], 0.0); gl.uniform1f(uniforms["Scale"], scale); gl.uniform1f(uniforms["Mix"], 0.0); gl.drawArrays(gl.TRIANGLE_STRIP, 0 , 4); } //fg texture gl.bindTexture(gl.TEXTURE_2D, texture); gl.uniform1f(uniforms["PercentForAlpha"], this.percentAlpha); gl.uniform1f(uniforms["Scale"], scale); gl.uniform1f(uniforms["Mix"], this.mix); gl.drawArrays(gl.TRIANGLE_STRIP, 0 , 4); } }); var KNWebGLBuildIris = Class.create(KNWebGLProgram, { initialize: function($super, renderer, params) { var effect = params.effect; this.programData = { name: "apple:wipe-iris", programNames: ["iris"], effect: effect, textures: params.textures }; $super(renderer, this.programData); // determine the type and direction var direction = this.direction; var directionOut = direction === KNDirection.kKNDirectionOut; var buildOut = this.buildOut; if ((buildOut && directionOut) || (!buildOut && !directionOut)) { this.mix = 0.0; this.percentfinished = 1.0; } else { this.mix = 1.0; this.percentfinished = 0.0; } this.percentAlpha = 0.0; // create drawable object for drawing static texture this.drawableObjects = []; for (var i = 0, length = this.textures.length; i < length; i++) { var texture = params.textures[i]; var drawableParams = { effect: effect, textures: [texture] }; var drawableObject = new KNWebGLDrawable(renderer, drawableParams); this.drawableObjects.push(drawableObject); } // set parent opacity from CA baseLayer this.parentOpacity = effect.baseLayer.initialState.opacity; // setup requirements this.animationWillBeginWithContext(); }, animationWillBeginWithContext: function() { var renderer = this.renderer; var gl = this.gl; var program = this.program["iris"]; var attribs = program.attribs; var uniforms = program.uniforms; gl.useProgram(program.shaderProgram); gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA); // setup attributes var textureCoordinatesBuffer = gl.createBuffer(); var textureCoordinates = [ 0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 1.0, 1.0, ]; gl.bindBuffer(gl.ARRAY_BUFFER, textureCoordinatesBuffer); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(textureCoordinates), gl.STATIC_DRAW); var viewportWidth = gl.viewportWidth; var viewportHeight = gl.viewportHeight; this.irisSystems = []; for (var i = 0, length = this.textures.length; i < length; i++) { var textureInfo = this.textures[i]; var width = textureInfo.width; var height = textureInfo.height; // initial scale uniform var scale = textureInfo.width/textureInfo.height; var positionBuffer = gl.createBuffer(); var boxPosition = [ 0.0, 0.0, 0.0, 0.0, textureInfo.height, 0.0, textureInfo.width, 0.0, 0.0, textureInfo.width, textureInfo.height, 0.0 ]; gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(boxPosition), gl.STATIC_DRAW); var MVPMatrix = WebGraphics.translateMatrix4(renderer.slideProjectionMatrix, textureInfo.offset.pointX, gl.viewportHeight - (textureInfo.offset.pointY + textureInfo.height), 0); this.irisSystems[i] = { textureCoordinatesBuffer: textureCoordinatesBuffer, positionBuffer: positionBuffer, MVPMatrix: MVPMatrix, scale: scale }; } }, drawFrame: function(difference, elapsed, duration) { var renderer = this.renderer; var gl = this.gl; // determine the type and direction var buildOut = this.buildOut; var directionOut = this.direction === KNDirection.kKNDirectionOut; var percentfinished = this.percentfinished; if ((buildOut && directionOut) || (!buildOut && !directionOut)) { percentfinished -= difference / duration; if (percentfinished <= 0) { percentfinished = 0; this.isCompleted = true; } } else { percentfinished += difference / duration; if (percentfinished >= 1) { percentfinished = 1; this.isCompleted = true; } } var percentAlpha = TSUSineMap(percentfinished); if (percentfinished === 1) { percentAlpha = 1.0; } if (buildOut) { percentAlpha = 1 - percentAlpha; } this.percentAlpha = percentAlpha; this.percentfinished = percentfinished; gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA); for (var i = 0, length = this.textures.length; i < length; i++) { var textureInfo = this.textures[i]; var initialState = textureInfo.initialState; var animations = textureInfo.animations; if (textureInfo.hasHighlightedBulletAnimation) { if (!initialState.hidden) { var opacity; if (animations.length > 0 && animations[0].property === "opacity") { var opacityFrom = animations[0].from.scalar; var opacityTo = animations[0].to.scalar; var diff = opacityTo - opacityFrom; if (buildOut) { opacity = opacityFrom + diff * (1 - this.percentfinished); } else { opacity = opacityFrom + diff * this.percentfinished; } } else { opacity = textureInfo.initialState.opacity; } this.drawableObjects[i].Opacity = this.parentOpacity * opacity; this.drawableObjects[i].drawFrame(); } } else if (textureInfo.animations.length > 0) { if (this.isCompleted) { if (!buildOut) { // if completed, just draw its texture object for better performance this.drawableObjects[i].Opacity = this.parentOpacity * textureInfo.initialState.opacity; this.drawableObjects[i].drawFrame(); } continue; } var program = this.program["iris"]; var attribs = program.attribs; var uniforms = program.uniforms; var irisSystem = this.irisSystems[i]; var scale = irisSystem.scale; gl.useProgram(program.shaderProgram); var textureCoordinatesBuffer = irisSystem.textureCoordinatesBuffer; gl.bindBuffer(gl.ARRAY_BUFFER, textureCoordinatesBuffer); gl.vertexAttribPointer(attribs["TexCoord"], 2, gl.FLOAT, false, 0, 0); gl.enableVertexAttribArray(attribs["TexCoord"]); var positionBuffer = irisSystem.positionBuffer; gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer); gl.vertexAttribPointer(attribs["Position"], 3, gl.FLOAT, false, 0, 0); gl.enableVertexAttribArray(attribs["Position"]); var MVPMatrix = irisSystem.MVPMatrix; gl.uniformMatrix4fv(uniforms["MVPMatrix"], false, MVPMatrix); gl.activeTexture(gl.TEXTURE0); gl.uniform1i(uniforms["Texture"], 0); // set Opacity gl.uniform1f(uniforms["Opacity"], this.parentOpacity * textureInfo.initialState.opacity); //fg texture gl.bindTexture(gl.TEXTURE_2D, textureInfo.texture); gl.uniform1f(uniforms["PercentForAlpha"], this.percentAlpha); gl.uniform1f(uniforms["Scale"], scale); gl.uniform1f(uniforms["Mix"], this.mix); gl.drawArrays(gl.TRIANGLE_STRIP, 0 , 4); } else { if (!textureInfo.initialState.hidden) { this.drawableObjects[i].Opacity = this.parentOpacity * textureInfo.initialState.opacity; this.drawableObjects[i].drawFrame(); } } } } }); var KNWebGLTransitionTwist = Class.create(KNWebGLProgram, { initialize: function($super, renderer, params) { this.programData = { name: "com.apple.iWork.Keynote.BUKTwist", programNames:["twist"], effect: params.effect, textures: params.textures }; $super(renderer, this.programData); var gl = this.gl; this.direction = this.effect.attributes.direction; this.percentfinished = 0.0; var mNumPoints = this.mNumPoints = 24; var dx = gl.viewportWidth / (mNumPoints - 1); var dy = gl.viewportHeight / (mNumPoints - 1); var fractionOfUnitLength = 1 / (mNumPoints - 1); var x, y; var TexCoords = this.TexCoords = []; var PositionCoords = this.PositionCoords = []; var NormalCoords = this.NormalCoords = []; for (y = 0; y < mNumPoints; y++) { for (x = 0; x < mNumPoints; x++) { var index = y * mNumPoints + x; PositionCoords[index * 3] = x * dx; PositionCoords[index * 3 + 1] = y * dy; PositionCoords[index * 3 + 2] = 0; TexCoords.push(x * fractionOfUnitLength); TexCoords.push(y * fractionOfUnitLength); NormalCoords.push(0); NormalCoords.push(0); NormalCoords.push(-1); } } var index = 0; var elementArray = this.elementArray = []; for (y = 0; y < mNumPoints - 1; y++) { for (x = 0; x < mNumPoints; x++) { elementArray[index++] = (y) * (mNumPoints) + x; elementArray[index++] = (y + 1) * (mNumPoints) + x; } } // setup requirements this.animationWillBeginWithContext(); }, animationWillBeginWithContext: function() { var renderer = this.renderer; var gl = this.gl; var program = this.program["twist"]; var uniforms = program.uniforms; var attribs = program.attribs; gl.enable(gl.CULL_FACE); this.buffers = {}; this.buffers["TexCoord"] = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, this.buffers["TexCoord"]); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(this.TexCoords), gl.STATIC_DRAW); gl.vertexAttribPointer(attribs["TexCoord"], 2, gl.FLOAT, false, 0, 0); gl.enableVertexAttribArray(attribs["TexCoord"]); this.buffers["Position"] = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, this.buffers["Position"]); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(this.PositionCoords), gl.DYNAMIC_DRAW); gl.vertexAttribPointer(attribs["Position"], 3, gl.FLOAT, false, 0, 0); gl.enableVertexAttribArray(attribs["Position"]); this.buffers["Normal"] = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, this.buffers["Normal"]); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(this.NormalCoords), gl.DYNAMIC_DRAW); gl.vertexAttribPointer(attribs["Normal"], 3, gl.FLOAT, false, 0, 0); gl.enableVertexAttribArray(attribs["Normal"]); this.MVPMatrix = renderer.slideProjectionMatrix; gl.uniformMatrix4fv(uniforms["MVPMatrix"], false, this.MVPMatrix); this.AffineTransform = new Matrix3(); this.AffineTransform.affineScale(1.0, -1.0); this.AffineTransform.affineTranslate(0.0, 1.0); this.AffineIdentity = new Matrix3(); this.elementIndicesBuffer = gl.createBuffer(); gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.elementIndicesBuffer); gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(this.elementArray), gl.STATIC_DRAW); gl.activeTexture(gl.TEXTURE0); gl.uniform1i(uniforms["Texture"], 0); this.drawFrame(0, 0, 4); }, drawFrame: function(difference, elapsed, duration) { var gl = this.gl; var program = this.program["twist"]; var attribs = program.attribs; var percentfinished = this.percentfinished; percentfinished += difference / duration; percentfinished > 1 ? percentfinished = 1 : 0; this.specularcolor = TSUSineMap(percentfinished * 2) * 0.5; var y, x; var height = gl.viewportHeight / 2.0; var mNumPoints = this.mNumPoints; var TexCoords = this.TexCoords; var PositionCoords = this.PositionCoords; var NormalCoords = this.NormalCoords; for (y = 0; y < mNumPoints; y++) { for (x = 0; x < mNumPoints; x++) { var index = y * mNumPoints + x; var start = {}; start.x = TexCoords[index * 2]; start.y = TexCoords[index * 2 + 1]; var angle = -Math.PI * TwistFX(this.direction === KNDirection.kKNDirectionLeftToRight ? start.x : (1 - start.x), percentfinished); var result = {}; result.y = (height - (height * (1 - start.y * 2) * Math.cos(angle))); result.z = (height * (1 - start.y * 2) * Math.sin(angle)); PositionCoords[index * 3 + 1] = result.y; PositionCoords[index * 3 + 2] = result.z; } } for (y = 0; y < mNumPoints; y++) { for (x = 0; x < mNumPoints; x++) { var finalNormal = new vector3(); var index = y * mNumPoints + x; for (var q = 0; q < 4; q++) { var q1x = 0, q1y = 0, q2x = 0, q2y = 0; switch (q) { case 0: q1x = 1; q2y = 1; break; case 1: q1y = 1; q2x = -1; break; case 2: q1x = -1; q2y = -1; break; case 3: q1y = -1; q2x = 1; default: break; } if ((x + q1x) < 0 || (x + q2x) < 0 || (y + q1y) < 0 || (y + q2y) < 0 || x + q1x >= mNumPoints || x + q2x >= mNumPoints || y + q1y >= mNumPoints || y + q2y >= mNumPoints) { continue; } var thisV = new vector3([PositionCoords[index * 3], PositionCoords[index * 3 + 1], PositionCoords[index * 3 + 2] ]); var nextV = new vector3([PositionCoords[((y + q1y) * mNumPoints + (x + q1x)) * 3], PositionCoords[((y + q1y) * mNumPoints + (x + q1x)) * 3 + 1], PositionCoords[((y + q1y) * mNumPoints + (x + q1x)) * 3 + 2] ]); var prevV = new vector3([PositionCoords[(((y + q2y) * mNumPoints) + (x + q2x)) * 3], PositionCoords[(((y + q2y) * mNumPoints) + (x + q2x)) * 3 + 1], PositionCoords[(((y + q2y) * mNumPoints) + (x + q2x)) * 3 + 2] ]); nextV.subtract(thisV); prevV.subtract(thisV); nextV.cross(prevV); // cross gives you the normal finalNormal.add(nextV); } finalNormal.normalize(); finalNormal.scale(-1.0); finalNormal = finalNormal.getArray(); NormalCoords[index * 3] = finalNormal[0]; NormalCoords[index * 3 + 1] = finalNormal[1]; NormalCoords[index * 3 + 2] = finalNormal[2]; } } gl.bindBuffer(gl.ARRAY_BUFFER, this.buffers["Position"]); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(PositionCoords), gl.DYNAMIC_DRAW); gl.vertexAttribPointer(attribs["Position"], 3, gl.FLOAT, false, 0, 0); gl.bindBuffer(gl.ARRAY_BUFFER, this.buffers["Normal"]); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(NormalCoords), gl.DYNAMIC_DRAW); gl.vertexAttribPointer(attribs["Normal"], 3, gl.FLOAT, false, 0, 0); this.percentfinished = percentfinished; this.draw(); }, draw: function() { var renderer = this.renderer; var gl = this.gl; var program = this.program["twist"]; var uniforms = program.uniforms; var textures = this.textures; var texture = textures[0].texture; var outgoingTexture = textures[1].texture; var mNumPoints = this.mNumPoints; var specularcolor = this.specularcolor; var AffineTransform = this.AffineTransform.getColumnMajorFloat32Array(); var AffineIdentity = this.AffineIdentity.getColumnMajorFloat32Array(); var elementIndicesBuffer = this.elementIndicesBuffer; if (!specularcolor) { specularcolor = 0; } gl.uniform1f(uniforms["SpecularColor"], specularcolor); if (this.percentfinished < 0.5) { gl.cullFace(gl.BACK); gl.bindTexture(gl.TEXTURE_2D, texture); gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, elementIndicesBuffer); gl.uniformMatrix3fv(uniforms["TextureMatrix"], false, AffineTransform); gl.uniform1f(uniforms["FlipNormals"], 1.0); // draw for (y = 0; y < mNumPoints - 1; y++) { gl.drawElements(gl.TRIANGLE_STRIP, mNumPoints * 2, gl.UNSIGNED_SHORT, y * mNumPoints * 2 * (2)); } // ANIMATE OVERLAY gl.cullFace(gl.FRONT); gl.bindTexture(gl.TEXTURE_2D, outgoingTexture); gl.uniformMatrix3fv(uniforms["TextureMatrix"], false, AffineIdentity); gl.uniform1f(uniforms["FlipNormals"], -1.0); for (y = 0; y < mNumPoints - 1; y++) { gl.drawElements(gl.TRIANGLE_STRIP, mNumPoints * 2, gl.UNSIGNED_SHORT, y * mNumPoints * 2 * (2)); } } else { gl.cullFace(gl.FRONT); gl.bindTexture(gl.TEXTURE_2D, outgoingTexture); gl.uniformMatrix3fv(uniforms["TextureMatrix"], false, AffineIdentity); gl.uniform1f(uniforms["FlipNormals"], -1.0); for (y = 0; y < mNumPoints - 1; y++) { gl.drawElements(gl.TRIANGLE_STRIP, mNumPoints * 2, gl.UNSIGNED_SHORT, y * mNumPoints * 2 * (2)); } gl.cullFace(gl.BACK); gl.bindTexture(gl.TEXTURE_2D, texture); gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, elementIndicesBuffer); gl.uniformMatrix3fv(uniforms["TextureMatrix"], false, AffineTransform); gl.uniform1f(uniforms["SpecularColor"], specularcolor); gl.uniform1f(uniforms["FlipNormals"], 1.0); // draw for (y = 0; y < mNumPoints - 1; y++) { gl.drawElements(gl.TRIANGLE_STRIP, mNumPoints * 2, gl.UNSIGNED_SHORT, y * mNumPoints * 2 * (2)); } } } }); var KNWebGLTransitionColorPlanes = Class.create(KNWebGLProgram, { initialize: function($super, renderer, params) { this.programData = { name: "com.apple.iWork.Keynote.KLNColorPlanes", programNames:["colorPlanes"], effect: params.effect, textures: params.textures }; $super(renderer, this.programData); var direction = this.effect.attributes.direction; if (direction !== KNDirection.kKNDirectionLeftToRight && direction !== KNDirection.kKNDirectionRightToLeft && direction !== KNDirection.kKNDirectionTopToBottom && direction !== KNDirection.kKNDirectionBottomToTop) { // default direction to left to right if not specified direction = KNDirection.kKNDirectionLeftToRight } this.direction = direction; this.mNumColors = 3; this.percentfinished = 0.0; // setup requirements this.animationWillBeginWithContext(); }, animationWillBeginWithContext: function() { var renderer = this.renderer; var gl = this.gl; var program = this.program["colorPlanes"]; var uniforms = program.uniforms; var attribs = program.attribs; var textureInfo = this.textures[0]; gl.disable(gl.CULL_FACE); gl.blendFunc(gl.ONE, gl.ONE); var buffers = this.buffers = {}; buffers["TexCoord"] = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, buffers["TexCoord"]); var TexCoords = this.TexCoords = [ 0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 1.0, 1.0, ]; gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(TexCoords), gl.STATIC_DRAW); gl.vertexAttribPointer(attribs["TexCoord"], 2, gl.FLOAT, false, 0,0); gl.enableVertexAttribArray(attribs["TexCoord"]); buffers["Position"] = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, buffers["Position"]); var PositionCoords = this.PositionCoords = [ 0.0, 0.0, 0.0, 0.0, textureInfo.height, 0.0, textureInfo.width, 0.0, 0.0, textureInfo.width, textureInfo.height, 0.0 ]; gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(PositionCoords), gl.STATIC_DRAW); gl.vertexAttribPointer(attribs["Position"], 3, gl.FLOAT, false, 0,0); gl.enableVertexAttribArray(attribs["Position"]); this.MVPMatrix = renderer.slideProjectionMatrix; gl.uniformMatrix4fv(uniforms["MVPMatrix"], false, this.MVPMatrix); gl.activeTexture(gl.TEXTURE0); gl.uniform1i(uniforms["Texture"], 0); this.drawFrame(0, 0, 4); }, drawFrame: function(difference, elapsed, duration) { var renderer = this.renderer; var gl = this.gl; var program = this.program["colorPlanes"]; var uniforms = program.uniforms; var attribs = program.attribs; var textures = this.textures; var textureInfo = textures[0]; var outgoingTextureInfo = textures[1]; this.percentfinished += difference / duration; this.percentfinished > 1 ? this.percentfinished = 1 : 0; var percent = this.percentfinished; var direction = this.direction; var planeSeparation = 0.25; var cameraPullBack = 1.0; var clockwise = (direction == KNDirection.kKNDirectionRightToLeft || direction == KNDirection.kKNDirectionBottomToTop); var yAxis = (direction == KNDirection.kKNDirectionLeftToRight || direction == KNDirection.kKNDirectionRightToLeft); var percentInvSq = 1-(1-percent)*(1-percent); var cameraAmount = yAxis ? textureInfo.width : textureInfo.height; var uCurve = TSUSineMap(percent * 2.0); var planeOffset = uCurve * cameraAmount * planeSeparation; var zOffset = Math.sin(-percentInvSq*2.*Math.PI); zOffset *= percentInvSq * cameraAmount * cameraPullBack; if (percent < 0.5) { gl.bindTexture(gl.TEXTURE_2D, outgoingTextureInfo.texture); gl.uniform2fv(uniforms["FlipTexCoords"], new Float32Array([0,0])); } else { gl.bindTexture(gl.TEXTURE_2D, textureInfo.texture); if (direction == KNDirection.kKNDirectionTopToBottom || direction == KNDirection.kKNDirectionBottomToTop) { gl.uniform2fv(uniforms["FlipTexCoords"], new Float32Array([0,1])); } else { gl.uniform2fv(uniforms["FlipTexCoords"], new Float32Array([1,0])); } } for (var iHue = 0, mNumColors = this.mNumColors; iHue < mNumColors; iHue++) { var thisHue = iHue/mNumColors; // setup color mask var color = WebGraphics.colorWithHSBA(thisHue, 1, 1, 1/mNumColors); gl.uniform4fv(uniforms["ColorMask"], new Float32Array([color.red, color.green, color.blue, color.alpha])); var angle = (Math.PI/180.0) * (180.0 * (TSUSineMap(percent))); var mvpMatrix = WebGraphics.translateMatrix4(this.MVPMatrix, textureInfo.width/2, textureInfo.height/2, zOffset); mvpMatrix = WebGraphics.rotateMatrix4AboutXYZ(mvpMatrix, angle, (clockwise ? -1 : 1) * (yAxis ? 0 : 1), (clockwise ? -1 : 1) * (yAxis ? 1 : 0), 0); mvpMatrix = WebGraphics.translateMatrix4(mvpMatrix, -textureInfo.width/2, -textureInfo.height/2, planeOffset*(iHue-1)); gl.uniformMatrix4fv(uniforms["MVPMatrix"], false, mvpMatrix); gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); } } }); var KNWebGLTransitionFlop = Class.create(KNWebGLProgram, { initialize: function($super, renderer, params) { this.programData = { name: "com.apple.iWork.Keynote.BUKFlop", programNames:["flop", "defaultTexture"], effect: params.effect, textures: params.textures }; $super(renderer, this.programData); var direction = this.effect.attributes.direction; if (direction !== KNDirection.kKNDirectionLeftToRight && direction !== KNDirection.kKNDirectionRightToLeft && direction !== KNDirection.kKNDirectionTopToBottom && direction !== KNDirection.kKNDirectionBottomToTop) { // default direction to left to right if not specified direction = KNDirection.kKNDirectionLeftToRight } this.direction = direction; this.percentfinished = 0.0; var elementArray = this.elementArray = []; var gl = this.gl; var texWidth = gl.viewportWidth; var texHeight = gl.viewportHeight; var width = texWidth var height = texHeight; if (direction === KNDirection.kKNDirectionTopToBottom || direction === KNDirection.kKNDirectionBottomToTop) { height *= 0.5; } else { width *= 0.5; } var mNumPoints = this.mNumPoints = 8; var index = 0; for (y = 0; y < mNumPoints - 1; y++) { for (x = 0; x < mNumPoints; x++) { elementArray[index++] = (y + 0) * (mNumPoints) + x; elementArray[index++] = (y + 1) * (mNumPoints) + x; } } var dx = width / (mNumPoints - 1); var dy = height / (mNumPoints - 1); var yOffset = (direction == KNDirection.kKNDirectionTopToBottom) ? height : yOffset = 0; var xOffset = (direction == KNDirection.kKNDirectionRightToLeft) ? width : xOffset = 0; var attributeBufferData = this.attributeBufferData = { Position: [], TexCoords: [], Normal: [], ShadowPosition: [], ShadowTexCoord: [], PreviousPosition: [], PreviousTexCoords: [], PreviousNormal: [] }; for (var y = 0; y < mNumPoints; y++) { for (var x = 0; x < mNumPoints; x++) { index = y * mNumPoints + x; KNWebGLUtil.setPoint3DAtIndexForAttribute(WebGraphics.makePoint3D(x * dx + xOffset, y * dy, 0), index, attributeBufferData["Position"]); KNWebGLUtil.setPoint2DAtIndexForAttribute(WebGraphics.makePoint((x * dx + xOffset) / texWidth, (y * dy + yOffset) / texHeight), index, attributeBufferData["TexCoords"]); KNWebGLUtil.setPoint3DAtIndexForAttribute(WebGraphics.makePoint3D(0, 0, 1), index, attributeBufferData["Normal"]); } } // setup requirements this.animationWillBeginWithContext(); }, animationWillBeginWithContext: function() { var renderer = this.renderer; var gl = this.gl; var program = this.program["flop"]; var attribs = program.attribs; var uniforms = program.uniforms; var basicProgram = this.program["defaultTexture"]; var MVPMatrix = this.MVPMatrix = renderer.slideProjectionMatrix; var width = gl.viewportWidth; var height = gl.viewportHeight; var direction = this.direction; if (direction === KNDirection.kKNDirectionTopToBottom || direction === KNDirection.kKNDirectionBottomToTop) { height *= 0.5; } else { width *= 0.5; } var textureCoordinates = [ 0.0, 0.0, 0.0, 0.5, 1.0, 0.0, 1.0, 0.5, ]; var boxPosition = [ 0.0, 0.0, 0.0, 0.0, height, 0.0, width,0.0, 0.0, width, height, 0.0, ]; var textureCoordinates2 = [ 0.0, 0.5, 0.0, 1.0, 1.0, 0.5, 1.0, 1.0, ]; var boxPosition2 = [ 0.0, height, 0.0, 0.0, height*2, 0.0, width, height, 0.0, width, height*2, 0.0, ]; // use this program and enable vertex attrib array KNWebGLUtil.enableAttribs(gl, program); var attributeBufferData = this.attributeBufferData; var buffers = this.buffers = {}; var Coordinates = this.Coordinates = {}; buffers["TexCoord"] = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, buffers["TexCoord"]); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(attributeBufferData["TexCoords"]), gl.DYNAMIC_DRAW); gl.vertexAttribPointer(attribs["TexCoord"], 2, gl.FLOAT, false, 0,0); buffers["Position"] = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, buffers["Position"]); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(attributeBufferData["Position"]), gl.DYNAMIC_DRAW); gl.vertexAttribPointer(attribs["Position"], 3, gl.FLOAT, false, 0,0); buffers["Normal"] = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, buffers["Normal"]); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(attributeBufferData["Normal"]), gl.DYNAMIC_DRAW); gl.vertexAttribPointer(attribs["Normal"], 3, gl.FLOAT, false, 0,0); gl.uniformMatrix4fv(uniforms["MVPMatrix"], false, MVPMatrix); var AffineTransform = this.AffineTransform = new Matrix3(); if (direction === KNDirection.kKNDirectionTopToBottom) { AffineTransform.affineScale(1.0, -1.0); AffineTransform.affineTranslate(0.0,1.0); } else if (direction == KNDirection.kKNDirectionBottomToTop) { AffineTransform.affineScale(1.0, -1.0); AffineTransform.affineTranslate(0.0,1.0); textureCoordinates = [ 0.0, 0.5, 0.0, 1.0, 1.0, 0.5, 1.0, 1.0, ]; textureCoordinates2 = [ 0.0, 0.0, 0.0, 0.5, 1.0, 0.0, 1.0, 0.5, ]; boxPosition = [ 0.0, height, 0.0, 0.0, height*2, 0.0, width,height, 0.0, width, height*2, 0.0, ]; boxPosition2 = [ 0, 0, 0.0, 0, height, 0.0, width, 0, 0.0, width, height, 0.0, ]; } else if (direction == KNDirection.kKNDirectionRightToLeft) { AffineTransform.affineScale(-1.0, 1.0); AffineTransform.affineTranslate(1.0, 0.0); textureCoordinates = [ 0.0, 0.0, 0.0, 1.0, 0.5, 0.0, 0.5, 1.0, ]; textureCoordinates2 = [ 0.5, 0.0, 0.5, 1.0, 1.0, 0.0, 1.0, 1.0, ]; boxPosition2 = [ width, 0, 0.0, width, height, 0.0, width*2, 0, 0.0, width*2, height, 0.0, ]; } else if (direction === KNDirection.kKNDirectionLeftToRight) { AffineTransform.affineScale(-1.0, 1.0); AffineTransform.affineTranslate(1.0, 0.0); boxPosition = [ width, 0, 0.0, width, height, 0.0, width*2,0, 0.0, width*2, height, 0.0, ]; textureCoordinates = [ 0.5, 0.0, 0.5, 1.0, 1.0, 0.0, 1.0, 1.0, ]; textureCoordinates2 = [ 0.0, 0.0, 0.0, 1.0, 0.5, 0.0, 0.5, 1.0, ]; boxPosition2 = [ 0, 0, 0.0, 0, height, 0.0, width, 0, 0.0, width, height, 0.0, ]; } this.AffineIdentity = new Matrix3(); this.elementIndicesBuffer = gl.createBuffer(); gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.elementIndicesBuffer); gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(this.elementArray), gl.STATIC_DRAW); //setup second program Coordinates["DefaultTexture"] = textureCoordinates; Coordinates["DefaultTexture2"] = textureCoordinates2; Coordinates["DefaultPosition"] = boxPosition; Coordinates["DefaultPosition2"] = boxPosition2; // use this program and enable vertex attrib array KNWebGLUtil.enableAttribs(gl, basicProgram); //setup VBO and FTB buffers["TextureCoordinates"] = gl.createBuffer(); buffers["PositionCoordinates"] = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, buffers["TextureCoordinates"]); gl.bindBuffer(gl.ARRAY_BUFFER, buffers["PositionCoordinates"]); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(textureCoordinates), gl.DYNAMIC_DRAW); gl.vertexAttribPointer(basicProgram.attribs["TexCoord"], 2, gl.FLOAT, false, 0, 0); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(boxPosition), gl.DYNAMIC_DRAW); gl.vertexAttribPointer(basicProgram.attribs["Position"], 3, gl.FLOAT, false, 0, 0); gl.uniform1i(basicProgram.uniforms["Texture"], 0); gl.uniformMatrix4fv(basicProgram.uniforms["MVPMatrix"], false, MVPMatrix); // switch back to main program with animation gl.useProgram(program.shaderProgram); gl.activeTexture(gl.TEXTURE0); gl.uniform1i(program.uniforms["Texture"], 0); this.drawFrame(0, 0, 4); }, drawFrame: function(difference, elapsed, duration) { this.percentfinished += difference / duration; this.percentfinished > 1 ? this.percentfinished = 1 : 0; this.updateFlopWithPercent(); this.draw(); }, updateFlopWithPercent: function() { var gl = this.gl; var direction = this.direction; var texWidth = gl.viewportWidth; var texHeight = gl.viewportHeight; var thetaA = this.percentfinished * Math.PI; var thetaB = this.percentfinished * this.percentfinished * this.percentfinished * Math.PI; var height = texHeight / 2.0; var width = texWidth / 2.0; var location = 0.0; var mNumPoints = this.mNumPoints; var attributeBufferData = this.attributeBufferData; for (var y = 0; y < mNumPoints; y++) { for(var x = 0; x < mNumPoints; x++) { var index = y * mNumPoints + x; var start = KNWebGLUtil.getPoint2DForArrayAtIndex(attributeBufferData["TexCoords"], index); start.x *= texWidth; start.y *= texHeight; if (direction === KNDirection.kKNDirectionBottomToTop) { location = start.y / height; } else if (direction === KNDirection.kKNDirectionTopToBottom) { location = (height*2 - start.y) / height; } else if (direction === KNDirection.kKNDirectionLeftToRight) { location = start.x / width; } else { location = (width*2 - start.x) / width; } var angle = location*thetaA + (1-location) * thetaB; if (direction === KNDirection.kKNDirectionLeftToRight || direction === KNDirection.kKNDirectionTopToBottom) { angle *= -1; } var sinAngle = Math.sin(angle); var cosAngle = Math.cos(angle); var startPosition = KNWebGLUtil.getPoint3DForArrayAtIndex(attributeBufferData["Position"], index); var startNormal = KNWebGLUtil.getPoint3DForArrayAtIndex(attributeBufferData["Normal"], index); if (direction === KNDirection.kKNDirectionTopToBottom || direction === KNDirection.kKNDirectionBottomToTop) { var thisPosition = WebGraphics.makePoint3D(startPosition.x, height - (height - start.y) * cosAngle, (height - start.y) * sinAngle); KNWebGLUtil.setPoint3DAtIndexForAttribute(thisPosition, index, attributeBufferData["Position"]); var thisNormal = WebGraphics.makePoint3D(startNormal.x, -sinAngle, cosAngle); KNWebGLUtil.setPoint3DAtIndexForAttribute(thisNormal, index, attributeBufferData["Normal"]); } else { var thisPosition = WebGraphics.makePoint3D(width - (width - start.x) * cosAngle, startPosition.y, -(width - start.x) * sinAngle); KNWebGLUtil.setPoint3DAtIndexForAttribute(thisPosition, index, attributeBufferData["Position"]); var thisNormal = WebGraphics.makePoint3D(-sinAngle, startNormal.y, cosAngle); KNWebGLUtil.setPoint3DAtIndexForAttribute(thisNormal, index, attributeBufferData["Normal"]); } } } }, draw: function() { var renderer = this.renderer; var gl = this.gl; var program = this.program["flop"]; var basicProgram = this.program["defaultTexture"]; var textures = this.textures; var outgoingTexture = textures[1].texture; var incomingTexture = textures[0].texture; gl.useProgram(basicProgram.shaderProgram); gl.disable(gl.CULL_FACE); gl.bindTexture(gl.TEXTURE_2D, outgoingTexture); var mNumPoints = this.mNumPoints; var buffers = this.buffers; var Coordinates = this.Coordinates; var attributeBufferData = this.attributeBufferData; KNWebGLUtil.bindDynamicBufferWithData(gl, basicProgram.attribs["Position"], buffers["PositionCoordinates"], Coordinates["DefaultPosition"], 3); KNWebGLUtil.bindDynamicBufferWithData(gl, basicProgram.attribs["TexCoord"], buffers["TextureCoordinates"], Coordinates["DefaultTexture"], 2); gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); gl.useProgram(basicProgram.shaderProgram); gl.disable(gl.CULL_FACE); gl.bindTexture(gl.TEXTURE_2D, incomingTexture); KNWebGLUtil.bindDynamicBufferWithData(gl, basicProgram.attribs["Position"], buffers["PositionCoordinates"], Coordinates["DefaultPosition2"], 3); KNWebGLUtil.bindDynamicBufferWithData(gl, basicProgram.attribs["TexCoord"], buffers["TextureCoordinates"], Coordinates["DefaultTexture2"], 2); gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); gl.enable(gl.CULL_FACE); //ANIMATE OVERLAY gl.useProgram(program.shaderProgram); gl.bindBuffer(gl.ARRAY_BUFFER, buffers["Position"]); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(attributeBufferData["Position"]), gl.DYNAMIC_DRAW); gl.vertexAttribPointer(program.attribs["Position"], 3, gl.FLOAT, false, 0,0); gl.bindBuffer(gl.ARRAY_BUFFER, buffers["Normal"]); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(attributeBufferData["Normal"]), gl.DYNAMIC_DRAW); gl.vertexAttribPointer(program.attribs["Normal"], 3, gl.FLOAT, false, 0,0); gl.bindBuffer(gl.ARRAY_BUFFER, buffers["TexCoord"]); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(attributeBufferData["TexCoords"]), gl.DYNAMIC_DRAW); gl.vertexAttribPointer(program.attribs["TexCoord"], 2, gl.FLOAT, false, 0,0); gl.cullFace(gl.BACK); gl.bindTexture(gl.TEXTURE_2D, incomingTexture); gl.uniformMatrix3fv(program.uniforms["TextureMatrix"], false, this.AffineTransform.getColumnMajorFloat32Array()); gl.uniform1f(program.uniforms["FlipNormals"], -1.0); for (var y = 0; y< mNumPoints-1; y++) { gl.drawElements(gl.TRIANGLE_STRIP, mNumPoints*2, gl.UNSIGNED_SHORT, y*mNumPoints*2*(2)); } gl.bindTexture(gl.TEXTURE_2D, outgoingTexture); gl.cullFace(gl.FRONT); gl.uniformMatrix3fv(program.uniforms["TextureMatrix"], false, this.AffineIdentity.getColumnMajorFloat32Array()); gl.uniform1f(program.uniforms["FlipNormals"], 1.0); for (var y = 0; y < mNumPoints-1; y++) { gl.drawElements(gl.TRIANGLE_STRIP, mNumPoints*2, gl.UNSIGNED_SHORT, y*mNumPoints*2*(2)); } } }); var KNWebGLBuildAnvil = Class.create(KNWebGLProgram, { initialize: function($super, renderer, params) { var effect = params.effect; this.programData = { name: "com.apple.iWork.Keynote.BUKAnvil", programNames: ["anvilsmoke", "anvilspeck"], effect: effect, textures: params.textures }; $super(renderer, this.programData); var gl = this.gl; // bind required textures from base64 image source this.smokeTexture = KNWebGLUtil.bindTextureWithImage(gl, smokeImage); this.speckTexture = KNWebGLUtil.bindTextureWithImage(gl, speckImage); // initialize percent finish this.percentfinished = 0; // create drawable object for drawing static texture this.drawableObjects = []; for (var i = 0, length = this.textures.length; i < length; i++) { var texture = params.textures[i]; var drawableParams = { effect: effect, textures: [texture] }; var drawableObject = new KNWebGLDrawable(renderer, drawableParams); this.drawableObjects.push(drawableObject); } this.objectY = 1; // set parent opacity from CA baseLayer this.parentOpacity = effect.baseLayer.initialState.opacity; // setup requirements this.animationWillBeginWithContext(); }, animationWillBeginWithContext: function() { var renderer = this.renderer; var gl = this.gl; this.smokeSystems = []; this.speckSystems = []; for (var i = 0, length = this.textures.length; i < length; i++) { var textureInfo = this.textures[i]; var width = textureInfo.width; var height = textureInfo.height; var viewportWidth = gl.viewportWidth; var viewportHeight = gl.viewportHeight; var numParticles = 300; var smokeSystem = new KNWebGLBuildAnvilSmokeSystem( renderer, this.program["anvilsmoke"], {"width": width, "height": height}, {"width": viewportWidth, "height": viewportHeight}, this.duration, {"width": numParticles, "height": 1}, {"width": kParticleSize, "height": kParticleSize}, this.smokeTexture); numParticles = 40; var speckSystem = new KNWebGLBuildAnvilSpeckSystem( renderer, this.program["anvilspeck"], {"width": width, "height": height}, {"width": viewportWidth, "height": viewportHeight}, this.duration, {"width": numParticles, "height": 1}, {"width": kParticleSize, "height": kParticleSize}, this.speckTexture); this.smokeSystems.push(smokeSystem); this.speckSystems.push(speckSystem); } }, drawFrame: function(difference, elapsed, duration) { var renderer = this.renderer; var gl = this.gl; this.percentfinished += difference / duration; if (this.percentfinished >= 1) { this.percentfinished = 1; this.isCompleted = true; } gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA); for (var i = 0, length = this.textures.length; i < length; i++) { var textureInfo = this.textures[i]; var initialState = textureInfo.initialState; var animations = textureInfo.animations; if (textureInfo.hasHighlightedBulletAnimation) { if (!initialState.hidden) { var opacity; if (animations.length > 0 && animations[0].property === "opacity") { var opacityFrom = animations[0].from.scalar; var opacityTo = animations[0].to.scalar; var diff = opacityTo - opacityFrom; opacity = opacityFrom + diff * this.percentfinished; } else { opacity = textureInfo.initialState.opacity; } this.drawableObjects[i].Opacity = this.parentOpacity * opacity; this.drawableObjects[i].drawFrame(); } } else if (textureInfo.animations.length > 0) { if (this.isCompleted) { // if completed, just draw its texture object for better performance this.drawableObjects[i].Opacity = this.parentOpacity * textureInfo.initialState.opacity; this.drawableObjects[i].drawFrame(); continue; } var width = textureInfo.width; var height = textureInfo.height; var offsetX = textureInfo.offset.pointX; var offsetY = textureInfo.offset.pointY; var viewportWidth = gl.viewportWidth; var viewportHeight = gl.viewportHeight; duration /= 1000; var kObjectSmashDuration = Math.min(0.20, duration * 0.4); var kCameraShakeDuration = Math.min(0.25, duration * 0.5); var cameraShakePoints = this.cameraShakePointsWithRandomGenerator(); var cameraShakePercent = (this.percentfinished * duration - kObjectSmashDuration) / kCameraShakeDuration; var shakePoint = WebGraphics.makePoint(0, 0); if (0 < cameraShakePercent && cameraShakePercent < 1) { var minIndex = Math.floor(cameraShakePercent * kNumCameraShakePoints); var maxIndex = Math.ceil(WebGraphics.clamp(cameraShakePercent * kNumCameraShakePoints, 0, cameraShakePoints.length - 1)); var minPoint = cameraShakePoints[minIndex]; var maxPoint = cameraShakePoints[maxIndex]; var cameraLerp = cameraShakePercent * kNumCameraShakePoints - minIndex; shakePoint = WebGraphics.makePoint( WebGraphics.mix(minPoint.x, maxPoint.x, cameraLerp), WebGraphics.mix(minPoint.y, maxPoint.y, cameraLerp)); } var objectSmashPercent = WebGraphics.clamp((this.percentfinished * duration) / kObjectSmashDuration, 0, 1); var smokepercent = WebGraphics.clamp(((this.percentfinished * duration) - kObjectSmashDuration) / (duration - kObjectSmashDuration), 0, 1); var percent = this.percentfinished; // calculations for the camera shake this.objectY = offsetY + height; this.objectY *= (1.0 - objectSmashPercent * objectSmashPercent); // draw the texture this.drawableObjects[i].MVPMatrix = WebGraphics.translateMatrix4( renderer.slideOrthoMatrix, offsetX + (shakePoint.x * viewportWidth), viewportHeight - offsetY - height + this.objectY + (shakePoint.y * viewportHeight), 0); this.drawableObjects[i].Opacity = this.parentOpacity * textureInfo.initialState.opacity; this.drawableObjects[i].drawFrame(); // draw smoke var MVPMatrix = WebGraphics.translateMatrix4(renderer.slideProjectionMatrix, offsetX, viewportHeight - (offsetY + (height + 16)) * (1 - (smokepercent * smokepercent * 0.02)), 0); var smokeSystem = this.smokeSystems[i]; smokeSystem.setMVPMatrix(MVPMatrix); smokeSystem.drawFrame(smokepercent, 1 - (smokepercent * smokepercent)); // draw specks if (smokepercent < 0.50) { MVPMatrix = WebGraphics.translateMatrix4(renderer.slideOrthoMatrix, offsetX, viewportHeight - (offsetY + height + 16), 0); var speckSystem = this.speckSystems[i]; speckSystem.setMVPMatrix(MVPMatrix); speckSystem.drawFrame(smokepercent, WebGraphics.clamp(1 - WebGraphics.sineMap(smokepercent) * 2, 0, 1)); } } else { if (!textureInfo.initialState.hidden) { this.drawableObjects[i].Opacity = this.parentOpacity * textureInfo.initialState.opacity; this.drawableObjects[i].drawFrame(); } } } }, cameraShakePointsWithRandomGenerator: function() { var cameraShakePoints = []; var globalScale = 0.025; for (var i = 0; i < kNumCameraShakePoints; i++) { var scale = 1 - (i / kNumCameraShakePoints); scale *= scale; var thisPoint = WebGraphics.makePoint( WebGraphics.randomBetween(-1, 1) * globalScale * scale * 0.4, Math.pow(-1, i) * globalScale * scale); cameraShakePoints[i] = thisPoint; } return cameraShakePoints; } }); var KNWebGLBuildFlame = Class.create(KNWebGLProgram, { initialize: function($super, renderer, params) { this.programData = { name: "com.apple.iWork.Keynote.KLNFlame", programNames: ["flame"], effect: params.effect, textures: params.textures }; $super(renderer, this.programData); var gl = this.gl; // bind required textures from base64 image source this.flameTexture = KNWebGLUtil.bindTextureWithImage(gl, flameImage); // initialize percent finish this.percentfinished = 0; // create drawable object for drawing static texture this.drawableObjects = []; // create framebuffer drawable object array for drawing flame this.framebufferDrawableObjects = []; this.slideSize = {"width": gl.viewportWidth, "height": gl.viewportHeight}; var effect = this.effect; for (var i = 0, length = this.textures.length; i < length; i++) { var texture = params.textures[i]; var drawableParams = { effect: effect, textures: [texture] }; var drawableObject = new KNWebGLDrawable(renderer, drawableParams); this.drawableObjects.push(drawableObject); var drawableFrame = { "size": { "width": texture.width, "height": texture.height }, "origin": { "x": texture.offset.pointX, "y": texture.offset.pointY } }; var frameRect = this.frameOfEffectWithFrame(drawableFrame); var framebufferParams = { effect: effect, textures: [], drawableFrame: drawableFrame, frameRect: frameRect }; var framebufferDrawable = new KNWebGLFramebufferDrawable(renderer, framebufferParams); // push the framebufferDrawable to the array this.framebufferDrawableObjects.push(framebufferDrawable); } // set parent opacity from CA baseLayer this.parentOpacity = effect.baseLayer.initialState.opacity; // setup requirements this.animationWillBeginWithContext(); }, frameOfEffectWithFrame: function(drawableFrame) { var objSize = drawableFrame.size; var slideSize = this.slideSize; // the larger the object, the less we have to inflate its size var widthAdjust = (1.2 - Math.min(1.0, Math.sqrt(objSize.width / slideSize.width))) + 1.0; var heightAdjust = (1.25 - Math.min(1.0, Math.sqrt(objSize.height / slideSize.height))) + 1.0; var viewSize = { "width": Math.round(objSize.width * widthAdjust), "height": Math.round(objSize.height * heightAdjust) }; if (objSize.width / objSize.height < 1.0) { // for really skinny objects, make sure GL View is more squarish viewSize.width = Math.max(viewSize.width, (objSize.width + objSize.height)); } var rect = { "size": viewSize, "origin": { "x": drawableFrame.origin.x + (objSize.width - viewSize.width) / 2, "y": drawableFrame.origin.y + (objSize.height - viewSize.height) / 2 } }; // Now move the FBO up a bit so only 25% of extra space is on the bottom rect.origin.y -= (rect.size.height - drawableFrame.size.height) * 0.25; var gl = this.gl; var slideRect = { "origin": { "x": 0, "y": 0 }, "size": { "width": gl.viewportWidth, "height": gl.viewportHeight } }; var mFrameRect = CGRectIntersection(rect, slideRect); mFrameRect = CGRectIntegral(mFrameRect); return mFrameRect; }, p_orthoTransformWithScale: function(scale, offset, mFrameRect) { var size = { "width": mFrameRect.size.width * scale, "height": mFrameRect.size.height * scale }; var ortho = WebGraphics.makeOrthoMatrix4(0, size.width, 0, size.height, -1, 1); var result = WebGraphics.translateMatrix4(ortho, offset.x, -offset.y, 0); return result; }, animationWillBeginWithContext: function() { var renderer = this.renderer; var gl = this.gl; var duration = this.duration / 1000; this.flameSystems = []; for (var i = 0, length = this.textures.length; i < length; i++) { var textureInfo = this.textures[i]; var width = textureInfo.width; var height = textureInfo.height; var viewportWidth = gl.viewportWidth; var viewportHeight = gl.viewportHeight; var framebufferDrawable = this.framebufferDrawableObjects[i]; var mFrameRect = framebufferDrawable.frameRect var mDrawableFrame = framebufferDrawable.drawableFrame; var orthoOffset = { "x": textureInfo.offset.pointX - mFrameRect.origin.x, "y": textureInfo.offset.pointY + height - (mFrameRect.origin.y + mFrameRect.size.height) }; var bottomPadding = mDrawableFrame.origin.y - mFrameRect.origin.y; var topPadding = mFrameRect.origin.y + mFrameRect.size.height - (mDrawableFrame.origin.y + mDrawableFrame.size.height); orthoOffset.y += (topPadding - bottomPadding); framebufferDrawable.MVPMatrix = this.p_orthoTransformWithScale(1.0, orthoOffset, mFrameRect); var ratio = width / height; var numParticles = Math.round(ratio * 150); numParticles *= (duration + Math.max(0, 1.0 - duration / 2)); // We updated actualSize, so need to update the max speed in the shader var flameSystem = new KNWebGLBuildFlameSystem( renderer, this.program["flame"], {"width": width, "height": height}, {"width": viewportWidth, "height": viewportHeight}, Math.max(2, this.duration), numParticles, this.flameTexture ); flameSystem.p_setupParticleDataWithTexture(textureInfo); this.flameSystems.push(flameSystem); } }, drawFrame: function(difference, elapsed, duration) { var renderer = this.renderer; var gl = this.gl; var program = this.program["flame"]; var uniforms = program.uniforms; var buildOut = this.buildOut; var percentfinished = this.percentfinished; percentfinished += difference / duration; if (percentfinished >= 1) { percentfinished = 1; this.isCompleted = true; } this.percentfinished = percentfinished; gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA); for (var i = 0, length = this.textures.length; i < length; i++) { var textureInfo = this.textures[i]; var initialState = textureInfo.initialState; var animations = textureInfo.animations; if (textureInfo.hasHighlightedBulletAnimation) { if (!initialState.hidden) { var opacity; if (animations.length > 0 && animations[0].property === "opacity") { var opacityFrom = animations[0].from.scalar; var opacityTo = animations[0].to.scalar; var diff = opacityTo - opacityFrom; opacity = opacityFrom + diff * this.percentfinished; } else { opacity = textureInfo.initialState.opacity; } this.drawableObjects[i].Opacity = this.parentOpacity * opacity; this.drawableObjects[i].drawFrame(); } } else if (textureInfo.animations.length > 0) { if (this.isCompleted) { if (!buildOut) { // if completed, just draw its texture object for better performance this.drawableObjects[i].Opacity = this.parentOpacity * textureInfo.initialState.opacity; this.drawableObjects[i].drawFrame(); } continue; } var width = textureInfo.width; var height = textureInfo.height; var offsetX = textureInfo.offset.pointX; var offsetY = textureInfo.offset.pointY; var viewportWidth = gl.viewportWidth; var viewportHeight = gl.viewportHeight; duration /= 1000; var percent = percentfinished; if (buildOut) { percent = 1.0 - percent; } var minCutoff = buildOut ? 0.25 : 0.5; var cutoff = Math.min(minCutoff, 1.0 / duration); if (percent > cutoff) { var newPercent = (percent - cutoff) / (1 - cutoff); var alpha = TSUSineMap(Math.min(1.0, 2 * newPercent)); alpha *= this.parentOpacity * textureInfo.initialState.opacity; var drawable = this.drawableObjects[i]; drawable.Opacity = alpha; drawable.drawFrame(); } var framebufferDrawable = this.framebufferDrawableObjects[i]; var mDrawableFrame = framebufferDrawable.drawableFrame; var mFrameRect = framebufferDrawable.frameRect; var orthoOffset = { "x": textureInfo.offset.pointX - mFrameRect.origin.x, "y": textureInfo.offset.pointY + height - (mFrameRect.origin.y + mFrameRect.size.height) }; var bottomPadding = mDrawableFrame.origin.y - mFrameRect.origin.y; var topPadding = mFrameRect.origin.y + mFrameRect.size.height - (mDrawableFrame.origin.y + mDrawableFrame.size.height); orthoOffset.y += (topPadding - bottomPadding); // this is slightly different implementation because we do not scale up and down in web var MVPMatrix = this.p_orthoTransformWithScale(1, orthoOffset, mFrameRect); // change viewport to match the frame buffer size gl.viewport(0, 0, mFrameRect.size.width, mFrameRect.size.height); //bind framebuffer gl.bindFramebuffer(gl.FRAMEBUFFER, framebufferDrawable.buffer); //now render the scene gl.clear(gl.COLOR_BUFFER_BIT); gl.enable(gl.BLEND); gl.blendFunc(gl.SRC_ALPHA, gl.ONE); var flameOpacity = (percentfinished == 0.0 || percentfinished == 1.0 ? 0.0 : 1.0); // bind framebuffer texture gl.bindTexture(gl.TEXTURE_2D, framebufferDrawable.texture); var flameSystem = this.flameSystems[i]; flameSystem.setMVPMatrix(MVPMatrix); gl.uniform1f(uniforms["SpeedMax"], flameSystem._speedMax); flameSystem.drawFrame(percentfinished, flameOpacity); // unbind the framebuffer gl.bindFramebuffer(gl.FRAMEBUFFER, null); // unbind the texture gl.bindTexture(gl.TEXTURE_2D, null); // change viewport back to original size gl.viewport(0, 0, gl.viewportWidth, gl.viewportHeight); // send result to framebuffer gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA); framebufferDrawable.MVPMatrix = WebGraphics.translateMatrix4(renderer.slideProjectionMatrix, mFrameRect.origin.x, gl.viewportHeight - (mFrameRect.origin.y + mFrameRect.size.height), 0); framebufferDrawable.drawFrame(); } else { if (!textureInfo.initialState.hidden) { this.drawableObjects[i].Opacity = this.parentOpacity * textureInfo.initialState.opacity; this.drawableObjects[i].drawFrame(); } } } } }); var KNWebGLTransitionConfetti = Class.create(KNWebGLProgram, { initialize: function($super, renderer, params) { this.programData = { name: "com.apple.iWork.Keynote.KLNConfetti", programNames: ["confetti", "defaultTexture"], effect: params.effect, textures: params.textures }; $super(renderer, this.programData); this.useGravity = this.direction === KNDirection.kKNDirectionGravity ? true : false; this.percentfinished = 0.0; this.animationWillBeginWithContext(); }, animationWillBeginWithContext: function(){ var renderer = this.renderer; var gl = this.gl; var textures = this.textures; var textureInfo = textures[0]; var width = textureInfo.width; var height = textureInfo.height; var viewportWidth = gl.viewportWidth; var viewportHeight = gl.viewportHeight; var numParticles = 10000; gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); // create a confetti system this.confettiSystem = new KNWebGLBuildConfettiSystem( renderer, this.program["confetti"], {"width": width, "height": height}, {"width": viewportWidth, "height": viewportHeight}, this.duration, numParticles, textures[1].texture); this.confettiSystem.setMVPMatrix(renderer.slideProjectionMatrix); // use default texture shader program for incoming slide var program = this.program["defaultTexture"]; // enable attribs before binding and set the program to use. KNWebGLUtil.enableAttribs(gl, program); var textureCoordinates = [ 0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 1.0, 1.0, ]; var boxPosition = [ 0.0, 0.0, -1.0, 0.0, viewportHeight, -1.0, viewportWidth, 0.0, -1.0, viewportWidth, viewportHeight, -1.0, ]; // setup VBO and FTB this.textureCoordinatesBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, this.textureCoordinatesBuffer); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(textureCoordinates), gl.STATIC_DRAW); gl.vertexAttribPointer(program.attribs["TexCoord"], 2, gl.FLOAT, false, 0, 0); this.positionBuffer = gl.createBuffer(); KNWebGLUtil.bindDynamicBufferWithData(gl, program.attribs["Position"], this.positionBuffer, boxPosition, 3); gl.uniformMatrix4fv(program.uniforms["MVPMatrix"], false, renderer.slideOrthoMatrix); gl.activeTexture(gl.TEXTURE0); gl.uniform1i(program.uniforms["Texture"], 0); this.drawFrame(0, 0, 4); }, drawFrame: function(difference, elapsed, duration) { var gl = this.gl; var viewportWidth = gl.viewportWidth; var viewportHeight = gl.viewportHeight; var percentfinished = this.percentfinished; percentfinished += difference / duration; if (percentfinished > 1) { percentfinished = 1; this.isCompleted = true; } var percent = this.percentfinished = percentfinished; var revPercent = 1 - percent; var myPercent = 1 - revPercent*revPercent*revPercent; myPercent = myPercent*(1-percent*percent) + (1-revPercent*revPercent)*(percent*percent) + percent; myPercent *= 0.5; myPercent*= myPercent; var scale= 0.75 + (1 - Math.pow(revPercent,4)) * 0.25; var quadShaderMVPMatrix = WebGraphics.translateMatrix4(this.renderer.slideProjectionMatrix, viewportWidth / 2, viewportHeight / 2, 0); quadShaderMVPMatrix = WebGraphics.scaleMatrix4(quadShaderMVPMatrix, scale, scale, 1); quadShaderMVPMatrix = WebGraphics.translateMatrix4(quadShaderMVPMatrix, -viewportWidth / 2, -viewportHeight / 2, 0); // draw the incoming slide var program = this.program["defaultTexture"]; gl.useProgram(program.shaderProgram); gl.uniformMatrix4fv(program.uniforms["MVPMatrix"], false, quadShaderMVPMatrix); this.draw(); //draw the confetti system frame var finalPercent = 1 - percent; finalPercent = WebGraphics.clamp(finalPercent, 0, 1); myPercent = WebGraphics.clamp(myPercent, 0, 1); if (this.useGravity) { var ratio = 1; var MVPMatrix = this.renderer.slideProjectionMatrix; MVPMatrix = WebGraphics.translateMatrix4(MVPMatrix, 0, -viewportHeight * 2 * percent * percent * (1.0 - ratio * 0.5), 0); this.confettiSystem.setMVPMatrix(MVPMatrix); } this.confettiSystem.drawFrame(myPercent, finalPercent); }, draw: function() { var gl = this.gl; var program = this.program["defaultTexture"]; var attribs = program.attribs; var viewportWidth = gl.viewportWidth; var viewportHeight = gl.viewportHeight; gl.useProgram(program.shaderProgram); var textureCoordinates = [ 0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 1.0, 1.0, ]; var boxPosition = [ 0.0, 0.0, -1.0, 0.0, viewportHeight, -1.0, viewportWidth, 0.0, -1.0, viewportWidth, viewportHeight, -1.0, ]; gl.bindBuffer(gl.ARRAY_BUFFER, this.textureCoordinatesBuffer); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(textureCoordinates), gl.STATIC_DRAW); gl.vertexAttribPointer(attribs["TexCoord"], 2, gl.FLOAT, false, 0, 0); KNWebGLUtil.bindDynamicBufferWithData(gl, attribs["Position"], this.positionBuffer, boxPosition, 3); // bind incoming texture gl.bindTexture(gl.TEXTURE_2D, this.textures[0].texture); gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); } }); var KNWebGLBuildConfetti = Class.create(KNWebGLProgram, { initialize: function($super, renderer, params) { var effect = params.effect; this.programData = { name: "com.apple.iWork.Keynote.KLNConfetti", programNames: ["confetti"], effect: effect, textures: params.textures }; $super(renderer, this.programData); this.useGravity = this.direction === KNDirection.kKNDirectionGravity ? true : false; this.percentfinished = 0.0; // create drawable object for drawing static texture this.drawableObjects = []; for (var i = 0, length = this.textures.length; i < length; i++) { var texture = params.textures[i]; var drawableParams = { effect: effect, textures: [texture] }; var drawableObject = new KNWebGLDrawable(renderer, drawableParams); this.drawableObjects.push(drawableObject); } // set parent opacity from CA baseLayer this.parentOpacity = effect.baseLayer.initialState.opacity; // setup requirements this.animationWillBeginWithContext(); }, animationWillBeginWithContext: function() { var renderer = this.renderer; var gl = this.gl; var viewportWidth = gl.viewportWidth; var viewportHeight = gl.viewportHeight; this.confettiSystems = []; for (var i = 0, length = this.textures.length; i < length; i++) { var textureInfo = this.textures[i]; var width = textureInfo.width; var height = textureInfo.height; var ratio = (height / viewportHeight * width / viewportWidth); ratio = Math.sqrt(Math.sqrt(ratio)); var numParticles = Math.round(ratio * 10000); // create a confetti system var confettiSystem = new KNWebGLBuildConfettiSystem( renderer, this.program["confetti"], {"width": width, "height": height}, {"width": viewportWidth, "height": viewportHeight}, this.duration, numParticles, textureInfo.texture); // set ratio so we don't need to recalculate during draw frame confettiSystem.ratio = ratio; this.confettiSystems.push(confettiSystem); } }, drawFrame: function(difference, elapsed, duration) { var renderer = this.renderer; var gl = this.gl; var viewportWidth = gl.viewportWidth; var viewportHeight = gl.viewportHeight; // determine the type and direction var buildIn = this.buildIn; var buildOut = this.buildOut; var percentfinished = this.percentfinished; percentfinished += difference / duration; if (percentfinished > 1) { percentfinished = 1; this.isCompleted = true; } this.percentfinished = percentfinished; for (var i = 0, length = this.textures.length; i < length; i++) { var textureInfo = this.textures[i]; var initialState = textureInfo.initialState; var animations = textureInfo.animations; if (textureInfo.hasHighlightedBulletAnimation) { if (!initialState.hidden) { var opacity; if (animations.length > 0 && animations[0].property === "opacity") { var opacityFrom = animations[0].from.scalar; var opacityTo = animations[0].to.scalar; var diff = opacityTo - opacityFrom; opacity = opacityFrom + diff * percentfinished; } else { opacity = textureInfo.initialState.opacity; } gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA); this.drawableObjects[i].Opacity = this.parentOpacity * opacity; this.drawableObjects[i].drawFrame(); } } else if (textureInfo.animations.length > 0) { if (this.isCompleted) { if (buildIn) { // if completed, just draw its texture object for better performance this.drawableObjects[i].Opacity = this.parentOpacity * textureInfo.initialState.opacity; this.drawableObjects[i].drawFrame(); } continue; } var width = textureInfo.width; var height = textureInfo.height; var percent = buildIn ? 1 - percentfinished : percentfinished; var revPercent = 1 - percent; var myPercent = 1 - revPercent * revPercent * revPercent; myPercent = myPercent * (1 - percent * percent) + (1 - revPercent * revPercent) * (percent * percent) + percent; myPercent *= 0.5; if (buildIn) { myPercent *= myPercent; } //draw the confetti system frame var confettiSystem = this.confettiSystems[i]; var MVPMatrix = WebGraphics.translateMatrix4(renderer.slideProjectionMatrix, textureInfo.offset.pointX, viewportHeight - (textureInfo.offset.pointY + height), 0); var finalPercent = 1 - percent; finalPercent = WebGraphics.clamp(finalPercent, 0, 1); myPercent = WebGraphics.clamp(myPercent, 0, 1); if (this.useGravity) { var ratio = confettiSystem.ratio; MVPMatrix = WebGraphics.translateMatrix4(MVPMatrix, 0, -viewportHeight * 2 * percent * percent * (1.0 - ratio * 0.5), 0); } gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); confettiSystem.setMVPMatrix(MVPMatrix); confettiSystem.drawFrame(myPercent, finalPercent); } else { if (!textureInfo.initialState.hidden) { gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA); this.drawableObjects[i].Opacity = this.parentOpacity * textureInfo.initialState.opacity; this.drawableObjects[i].drawFrame(); } } } } }); var KNWebGLBuildDiffuse = Class.create(KNWebGLProgram, { initialize: function($super, renderer, params) { var effect = params.effect; this.programData = { name: "com.apple.iWork.Keynote.KLNDiffuse", programNames: ["diffuse"], effect: effect, textures: params.textures }; $super(renderer, this.programData); this.percentfinished = 0.0; // create drawable object for drawing static texture this.drawableObjects = []; for (var i = 0, length = this.textures.length; i < length; i++) { var texture = params.textures[i]; var drawableParams = { effect: effect, textures: [texture] }; var drawableObject = new KNWebGLDrawable(renderer, drawableParams); this.drawableObjects.push(drawableObject); } // set parent opacity from CA baseLayer this.parentOpacity = effect.baseLayer.initialState.opacity; // setup requirements this.animationWillBeginWithContext(); }, animationWillBeginWithContext: function() { var renderer = this.renderer; var gl = this.gl; var viewportWidth = gl.viewportWidth; var viewportHeight = gl.viewportHeight; this.diffuseSystems = []; for (var i = 0, length = this.textures.length; i < length; i++) { var textureInfo = this.textures[i]; var width = textureInfo.width; var height = textureInfo.height; var ratio = (height / viewportHeight * width / viewportWidth); ratio = Math.sqrt(Math.sqrt(ratio)); var numParticles = Math.round(ratio * 4000); // create a confetti system var diffuseSystem = new KNWebGLBuildDiffuseSystem( renderer, this.program["diffuse"], {"width": width, "height": height}, {"width": viewportWidth, "height": viewportHeight}, this.duration, numParticles, textureInfo.texture, this.direction === KNDirection.kKNDirectionRightToLeft); this.diffuseSystems.push(diffuseSystem); } }, drawFrame: function(difference, elapsed, duration) { var renderer = this.renderer; var gl = this.gl; var viewportWidth = gl.viewportWidth; var viewportHeight = gl.viewportHeight; var percentfinished = this.percentfinished; percentfinished += difference / duration; if (percentfinished > 1) { percentfinished = 1; this.isCompleted = true; } this.percentfinished = percentfinished; gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA); for (var i = 0, length = this.textures.length; i < length; i++) { var textureInfo = this.textures[i]; var initialState = textureInfo.initialState; var animations = textureInfo.animations; if (textureInfo.hasHighlightedBulletAnimation) { if (!initialState.hidden) { var opacity; if (animations.length > 0 && animations[0].property === "opacity") { var opacityFrom = animations[0].from.scalar; var opacityTo = animations[0].to.scalar; var diff = opacityTo - opacityFrom; opacity = opacityFrom + diff * percentfinished; } else { opacity = textureInfo.initialState.opacity; } this.drawableObjects[i].Opacity = this.parentOpacity * opacity; this.drawableObjects[i].drawFrame(); } } else if (textureInfo.animations.length > 0) { var width = textureInfo.width; var height = textureInfo.height; var offsetX = textureInfo.offset.pointX; var offsetY = textureInfo.offset.pointY; //draw the diffuse system frame var diffuseSystem = this.diffuseSystems[i]; var MVPMatrix = WebGraphics.translateMatrix4(renderer.slideProjectionMatrix, offsetX, viewportHeight - (offsetY + height), 0); diffuseSystem.setMVPMatrix(MVPMatrix); diffuseSystem.drawFrame(this.percentfinished, 1.0); } else { if (!textureInfo.initialState.hidden) { this.drawableObjects[i].Opacity = this.parentOpacity * textureInfo.initialState.opacity; this.drawableObjects[i].drawFrame(); } } } } }); var KNWebGLBuildFireworks = Class.create(KNWebGLProgram, { initialize: function($super, renderer, params) { this.programData = { name: "com.apple.iWork.Keynote.KNFireworks", programNames: ["fireworks"], effect: params.effect, textures: params.textures }; $super(renderer, this.programData); var gl = this.gl; // animation parameter group this.animParameterGroup = new KNAnimParameterGroup("Fireworks"); // bind required textures from base64 image source this.fireworksTexture = KNWebGLUtil.bindTextureWithImage(gl, fireworksImage); this.fireworksCenterBurstTexture = KNWebGLUtil.bindTextureWithImage(gl, fireworksCenterBurstImage); // initialize percent finish this.percentfinished = 0; this.prevpercentfinished = 0; // create drawable object for drawing static texture this.drawableObjects = []; // frame rect for all firework systems this.frameRect = this.frameOfEffectWithFrame(); this.slideSize = {"width": gl.viewportWidth, "height": gl.viewportHeight}; var effect = this.effect; for (var i = 0, length = this.textures.length; i < length; i++) { var texture = params.textures[i]; var drawableParams = { effect: effect, textures: [texture] }; var drawableObject = new KNWebGLDrawable(renderer, drawableParams); // push drawable object to drawableObjects array this.drawableObjects.push(drawableObject); } // set parent opacity from CA baseLayer this.parentOpacity = effect.baseLayer.initialState.opacity; // setup requirements this.animationWillBeginWithContext(); }, frameOfEffectWithFrame: function() { var gl = this.gl; var slideRect = { "origin": { "x": 0, "y": 0 }, "size": { "width": gl.viewportWidth, "height": gl.viewportHeight } }; return slideRect; }, p_orthoTransformWithScale: function(scale, offset, mFrameRect) { var size = { "width": mFrameRect.size.width * scale, "height": mFrameRect.size.height * scale }; var ortho = WebGraphics.makeOrthoMatrix4(0, size.width, 0, size.height, -1, 1); var result = WebGraphics.translateMatrix4(ortho, offset.x, -offset.y, 0); return result; }, p_setupFBOWithSize: function(size) { this.framebuffer = new TSDGLFrameBuffer(this.gl, size, 2); }, p_fireworksSystemsForTR: function(textureInfo) { var renderer = this.renderer; var gl = this.gl; var viewportWidth = gl.viewportWidth; var viewportHeight = gl.viewportHeight; var duration = this.duration / 1000; var parameterGroup = this.animParameterGroup; var numFireworks = duration * parameterGroup.doubleForKey("FireworksCount"); // At least 2 fireworks! numFireworks = Math.max(2, numFireworks); var systems = []; var startOnLeftIndex = 0; var startOnRightIndex = 1; var startImmediatelyIndex = parseInt(WebGraphics.randomBetween(0, numFireworks - 1)); for (var i = 0; i < numFireworks; i++) { var numParticles = parameterGroup.doubleForKey("ParticleCount"); var minSlideSide = Math.min(viewportWidth, viewportHeight); var fireworkSpan = minSlideSide * WebGraphics.doubleBetween(parameterGroup.doubleForKey("FireworkSizeMin"), parameterGroup.doubleForKey("FireworkSizeMax")); var particleSystem = new KNWebGLBuildFireworksSystem( renderer, this.program["fireworks"], {"width": textureInfo.width, "height": textureInfo.height}, {"width": viewportWidth, "height": viewportHeight}, this.duration, {"width": numParticles, "height": 1}, {"width": 1, "height": 1}, this.fireworksTexture ); var randomSize = WebGraphics.makeSize(parameterGroup.doubleForKey("ParticleSizeMin"), parameterGroup.doubleForKey("ParticleSizeMax")); randomSize.width = randomSize.width * minSlideSide / 100; randomSize.height = randomSize.height * minSlideSide / 100; particleSystem.randomParticleSizeMinMax = randomSize; particleSystem.maxDistance = fireworkSpan; particleSystem.colorRandomness = parameterGroup.doubleForKey("ParticleColorRandomness"); particleSystem.lifeSpanMinDuration = parameterGroup.doubleForKey("ParticleLifeSpanMinDuration"); particleSystem.randomParticleSpeedMinMax = WebGraphics.makePoint(parameterGroup.doubleForKey("FireworkSpeedMin"), parameterGroup.doubleForKey("FireworkSpeedMax")); if (i % 2 === 0) { // 1/2 of particles start in left half particleSystem.fireworkStartingPositionX = WebGraphics.randomBetween(0, 0.5); } else if (i % 2 === 1) { // 1/2 of particles start in right half particleSystem.fireworkStartingPositionX = WebGraphics.randomBetween(0.5, 1); } if (i === startOnLeftIndex) { // Make sure at least one burst is all the way on the left side particleSystem.fireworkStartingPositionX = 0; } if (i === startOnRightIndex) { // Make sure at least one burst is all the way on the right side particleSystem.fireworkStartingPositionX = 1; } // Lifespan/duration of firework var randomDuration = WebGraphics.randomBetween(parameterGroup.doubleForKey("FireworkDurationMin"), parameterGroup.doubleForKey("FireworkDurationMax")); randomDuration /= duration; var startTime = WebGraphics.randomBetween(0, 1.0 - randomDuration); if (i === startImmediatelyIndex) { // Make sure ONE of the fireworks starts right away! startTime = 0; } startTime = Math.max(startTime, 0.001); particleSystem.lifeSpan = { "start": startTime, "duration": randomDuration }; particleSystem.setupWithTexture(textureInfo); systems.push(particleSystem); } return systems; }, animationWillBeginWithContext: function() { var renderer = this.renderer; var gl = this.gl; var parameterGroup = this.animParameterGroup; var centerBurstVertexRect = CGRectMake(0, 0, 512, 512); var vertexRect = CGRectMake(0, 0, this.slideSize.width, this.slideSize.height); var textureRect = CGRectMake(0, 0, 1, 1); var meshSize = CGSizeMake(2, 2); var mFrameRect = this.frameRect; this.fireworksSystems = []; for (var i = 0, length = this.textures.length; i < length; i++) { var texture = this.textures[i]; var orthoOffset = { "x": texture.offset.pointX - mFrameRect.origin.x, "y": texture.offset.pointY + texture.height - (mFrameRect.origin.y + mFrameRect.size.height) }; var baseOrthoTransform = WebGraphics.makeOrthoMatrix4(0, mFrameRect.size.width, 0, mFrameRect.size.height, -1, 1); var baseTransform = WebGraphics.translateMatrix4(baseOrthoTransform, orthoOffset.x, -orthoOffset.y, 0); // init object shader and data buffer var objectShader = new TSDGLShader(gl); objectShader.initWithDefaultTextureAndOpacityShader(); // object shader set methods objectShader.setMat4WithTransform3D(baseTransform, kTSDGLShaderUniformMVPMatrix); objectShader.setGLint(0, kTSDGLShaderUniformTexture); // init object data buffer var objectTextureRect = texture.textureRect; var objectVertexRect = CGRectMake(0, 0, objectTextureRect.size.width, objectTextureRect.size.height); var objectDataBuffer = new TSDGLDataBuffer(gl); objectDataBuffer.initWithVertexRect(objectVertexRect, TSDRectUnit, meshSize, false, false); // Set up shaders for particle systems var fireworksMVP = renderer.slideProjectionMatrix; fireworksMVP = WebGraphics.translateMatrix4(fireworksMVP, orthoOffset.x, -orthoOffset.y, 0); var fireworksSystems = this.p_fireworksSystemsForTR(texture); // set up FBO this.p_setupFBOWithSize(mFrameRect.size); var fboShader = this.fboShader = new TSDGLShader(gl); fboShader.initWithShaderFileNames("fireworkstrails", "fireworkstrails"); fboShader.setMat4WithTransform3D(baseOrthoTransform, kTSDGLShaderUniformMVPMatrix); fboShader.setGLint(0, kTSDGLShaderUniformTexture); var fboDataBuffer = this.fboDataBuffer = new TSDGLDataBuffer(gl); fboDataBuffer.initWithVertexRect(CGRectMake(0, 0, mFrameRect.size.width, mFrameRect.size.height), TSDRectUnit, meshSize, false, false); var centerBurstShader = this.centerBurstShader = new TSDGLShader(gl); centerBurstShader.initWithDefaultTextureAndOpacityShader(); centerBurstShader.setGLFloat(1.0, kTSDGLShaderUniformOpacity); var centerBurstDataBuffer = this.centerBurstDataBuffer = new TSDGLDataBuffer(gl); centerBurstDataBuffer.initWithVertexRect(centerBurstVertexRect, TSDRectUnit, meshSize, false, false); var _bloomEffect = this._bloomEffect = new TSDGLBloomEffect(gl); _bloomEffect.initWithEffectSize(mFrameRect.size, parameterGroup.doubleForKey("BloomBlurScale")); var fireworksSystem = { "_baseOrthoTransform": baseOrthoTransform, "_baseTransform": baseTransform, "objectShader": objectShader, "objectDataBuffer": objectDataBuffer, "fireworksMVP": fireworksMVP, "systems": fireworksSystems }; this.fireworksSystems.push(fireworksSystem); gl.clearColor(0.0, 0.0, 0.0, 0.0); gl.enable(gl.BLEND); gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA); gl.disable(gl.DEPTH_TEST); } }, drawFrame: function(difference, elapsed, duration) { var renderer = this.renderer; var gl = this.gl; var program = this.program["fireworks"]; var uniforms = program.uniforms; var buildOut = this.buildOut; var percentfinished = this.percentfinished; var parameterGroup = this.animParameterGroup; var noiseAmount = parameterGroup.doubleForKey("ParticleTrailsDitherAmount"); var noiseMax = parameterGroup.doubleForKey("ParticleTrailsDitherMax"); var bloomAmount = parameterGroup.doubleForKey("BloomPower"); percentfinished += difference / duration; if (percentfinished >= 1) { percentfinished = 1; this.isCompleted = true; } this.percentfinished = percentfinished; gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA); for (var i = 0, length = this.textures.length; i < length; i++) { var textureInfo = this.textures[i]; var initialState = textureInfo.initialState; var animations = textureInfo.animations; if (textureInfo.hasHighlightedBulletAnimation) { if (!initialState.hidden) { var opacity; if (animations.length > 0 && animations[0].property === "opacity") { var opacityFrom = animations[0].from.scalar; var opacityTo = animations[0].to.scalar; var diff = opacityTo - opacityFrom; opacity = opacityFrom + diff * this.percentfinished; } else { opacity = textureInfo.initialState.opacity; } this.drawableObjects[i].Opacity = this.parentOpacity * opacity; this.drawableObjects[i].drawFrame(); } } else if (textureInfo.animations.length > 0) { if (this.isCompleted) { if (!buildOut) { // if completed, just draw its texture object for better performance this.drawableObjects[i].Opacity = this.parentOpacity * textureInfo.initialState.opacity; this.drawableObjects[i].drawFrame(); } continue; } var width = textureInfo.width; var height = textureInfo.height; var offsetX = textureInfo.offset.pointX; var offsetY = textureInfo.offset.pointY; var viewportWidth = gl.viewportWidth; var viewportHeight = gl.viewportHeight; duration /= 1000; var percent = percentfinished; var currentGLFramebuffer = TSDGLFrameBuffer.currentGLFramebuffer(gl); var fireworksSystem = this.fireworksSystems[i]; var objectShader = fireworksSystem.objectShader; var objectDataBuffer = fireworksSystem.objectDataBuffer; // Draw the actual object this.p_drawObject(percent, textureInfo, objectShader, objectDataBuffer); // Draw particles into FBO to save trails var framebuffer = this.framebuffer; var fboShader = this.fboShader; var fboDataBuffer = this.fboDataBuffer; var previousFBOTexture = framebuffer.currentGLTexture(); framebuffer.setCurrentTextureToNext(); framebuffer.bindFramebuffer(); // clear current framebuffer texture gl.clear(gl.COLOR_BUFFER_BIT); // change viewport to match the frame buffer size gl.viewport(0, 0, framebuffer.size.width, framebuffer.size.height); // First, draw existing trails, but faded out a bit // bind previous framebuffer texture so we can take the content and draw into current one gl.bindTexture(gl.TEXTURE_2D, previousFBOTexture); var minDuration = parameterGroup.doubleForKey("FireworkDurationMin") / duration; minDuration = Math.min(minDuration / 2.0, 1.0); var trailsFadePercent = WebGraphics.clamp((percentfinished - minDuration) / (1.0 - minDuration), 0, 1); var trailsFadeOut = 1.0 - WebGraphics.mix(parameterGroup.doubleForKey("TrailsFadeOutMin"), parameterGroup.doubleForKey("TrailsFadeOutMax"), Math.pow(trailsFadePercent, 2)); fboShader.setGLFloat(trailsFadeOut, kTSDGLShaderUniformOpacity); fboShader.setGLFloat(noiseAmount, kShaderUniformNoiseAmount); fboShader.setGLFloat(noiseMax, kShaderUniformNoiseMax); var noiseSeed = WebGraphics.makePoint(WebGraphics.randomBetween(0, 1), WebGraphics.randomBetween(0, 1)); fboShader.setPoint2D(noiseSeed, kShaderUniformNoiseSeed); fboDataBuffer.drawWithShader(this.fboShader, true); // Draw center burst gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA); // need to use fireworks program before drawing particle system gl.useProgram(program.shaderProgram); var gravity = parameterGroup.doubleForKey("Gravity"); gravity *= Math.min(viewportWidth, viewportHeight) * 0.001; gravity *= duration; // acceleration is per second! gl.uniform1f(uniforms["Gravity"], gravity); var minSlideSide = Math.min(viewportWidth, viewportHeight); var startScale = minSlideSide * parameterGroup.doubleForKey("ParticleSizeStart") / 100; gl.uniform1f(uniforms["StartScale"], startScale); gl.uniform1f(uniforms["SparklePeriod"], parameterGroup.doubleForKey("SparklePeriod")); // draw particle system with percent this.drawParticleSystemsWithPercent(percentfinished, false, 1.0, fireworksSystem); // change viewport back to original gl.viewport(0, 0, gl.viewportWidth, gl.viewportHeight); // done drawing particle into FBO so unbind the framebuffer framebuffer.unbindFramebufferAndBindGLFramebuffer(currentGLFramebuffer); // Draw particle trails var maxDuration = parameterGroup.doubleForKey("FireworkDurationMax"); maxDuration = Math.min(maxDuration, 0.999); var particleOpacityPercent = WebGraphics.clamp((percentfinished - maxDuration) / (1.0 - maxDuration), 0, 1); var particleSystemOpacity = 1.0 - parameterGroup.doubleForAnimationCurve("ParticleTransparency", particleOpacityPercent); // apply bloom effect this._bloomEffect.bindFramebuffer(); gl.clear(gl.COLOR_BUFFER_BIT); // draw to bloom effect's _colorFramebuffer fboShader.setGLFloat(particleSystemOpacity, kTSDGLShaderUniformOpacity); fboShader.setGLFloat(0, kShaderUniformNoiseAmount); gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); // bind to current framebuffer texture gl.bindTexture(gl.TEXTURE_2D, framebuffer.currentGLTexture()); // draw trails FBO to bloom effect FBO fboDataBuffer.drawWithShader(fboShader, true); // draw new sparkles into bloom effect FBO gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA); // need to use the program before drawing fireworks particle system gl.useProgram(program.shaderProgram); this.drawParticleSystemsWithPercent(percentfinished, true, particleSystemOpacity, fireworksSystem); // unbind bloom effect framebuffer and bind to default drawing buffer this._bloomEffect.unbindFramebufferAndBindGLFramebuffer(currentGLFramebuffer); // additive blend mode gl.blendFunc(gl.ONE, gl.ONE); this._bloomEffect.drawBloomEffectWithMVPMatrix(fireworksSystem._baseOrthoTransform, bloomAmount, currentGLFramebuffer); gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA); } else { if (!textureInfo.initialState.hidden) { this.drawableObjects[i].Opacity = this.parentOpacity * textureInfo.initialState.opacity; this.drawableObjects[i].drawFrame(); } } } this.prevpercentfinished = this.percentfinished; }, p_drawObject: function(percent, textureInfo, objectShader, objectDataBuffer) { var gl = this.gl; var parameterGroup = this.animParameterGroup; var beginTime = parameterGroup.doubleForKey("TextOpacityBeginTime"); var endTime = parameterGroup.doubleForKey("TextOpacityEndTime"); percent = WebGraphics.clamp((percent - beginTime) / (endTime - beginTime), 0, 1); var opacity = this.parentOpacity * textureInfo.initialState.opacity; opacity *= parameterGroup.doubleForAnimationCurve("TextOpacityTiming", percent); objectShader.setGLFloat(opacity, kTSDGLShaderUniformOpacity); gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA); gl.bindTexture(gl.TEXTURE_2D, textureInfo.texture); objectDataBuffer.drawWithShader(objectShader, true); }, drawParticleSystemsWithPercent: function(percent, shouldDrawSparkles, particleSystemOpacity, fireworksSystem) { var renderer = this.renderer; var gl = this.gl; var program = this.program["fireworks"]; var uniforms = program.uniforms; var parameterGroup = this.animParameterGroup; var systems = fireworksSystem.systems; var baseTransform = fireworksSystem._baseTransform; var MVPMatrix = fireworksSystem.fireworksMVP; // need to use fireworks program before drawing particle system gl.useProgram(program.shaderProgram); gl.uniform1f(uniforms["ShouldSparkle"], shouldDrawSparkles ? 1 : 0); for (var i = 0, length = systems.length; i < length; i++) { var particleSystem = systems[i]; var lifeSpan = particleSystem.lifeSpan; var systemPercent = (percent - lifeSpan.start) / lifeSpan.duration; if (systemPercent <= 0 || systemPercent >= 1) { continue; } var systemPercent = WebGraphics.clamp(systemPercent, 0, 1); var prevSystemPercent = (this.prevpercentfinished - lifeSpan.start) / lifeSpan.duration; prevSystemPercent = WebGraphics.clamp(prevSystemPercent, systemPercent / 2, 1); var opacity = particleSystemOpacity; if (shouldDrawSparkles) { opacity = 1.0 - parameterGroup.doubleForAnimationCurve("ParticleTransparency", systemPercent); } // Also send in previous particle burst timing so we can blur in direction of burst velocity and avoid strobing var prevParticleBurstTiming = parameterGroup.doubleForAnimationCurve("ParticleBurstTiming", prevSystemPercent); var particleBurstTiming = parameterGroup.doubleForAnimationCurve("ParticleBurstTiming", systemPercent); gl.uniform1f(uniforms["ParticleBurstTiming"], particleBurstTiming); gl.uniform1f(uniforms["PreviousParticleBurstTiming"], prevParticleBurstTiming); gl.uniform1f(uniforms["PreviousPercent"], prevSystemPercent); if (!shouldDrawSparkles) { // Draw big center burst once at very first frame of Firework... the FBO fading will handle persisting it for a bit if (!particleSystem.didDrawCenterBurst) { gl.bindTexture(gl.TEXTURE_2D, this.fireworksCenterBurstTexture); // Scale is percent of slide size var scale = gl.viewportHeight / 512; scale *= WebGraphics.randomBetween(parameterGroup.doubleForKey("CenterBurstScaleMin"), parameterGroup.doubleForKey("CenterBurstScaleMax")); var center = particleSystem._startingPoint; var t = WebGraphics.translateMatrix4(baseTransform, center.x, center.y, 0); var centerAdjust = WebGraphics.makePoint(-(512 / 2.0 * scale), -(512 / 2.0 * scale)); t = WebGraphics.translateMatrix4(t, centerAdjust.x, centerAdjust.y, 0); t = WebGraphics.scaleMatrix4(t, scale, scale, 1); this.centerBurstShader.setGLFloat(parameterGroup.doubleForKey("CenterBurstOpacity"), kTSDGLShaderUniformOpacity); this.centerBurstShader.setMat4WithTransform3D(t, kTSDGLShaderUniformMVPMatrix); gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); this.centerBurstDataBuffer.drawWithShader(this.centerBurstShader, true); gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA); particleSystem.didDrawCenterBurst = true; } } // need to use fireworks program before drawing particle system gl.useProgram(program.shaderProgram); particleSystem.setMVPMatrix(MVPMatrix); particleSystem.drawFrame(systemPercent, opacity); } } }); var KNWebGLBuildShimmer = Class.create(KNWebGLProgram, { initialize: function($super, renderer, params) { var effect = params.effect; this.programData = { name: "com.apple.iWork.Keynote.KLNShimmer", programNames: ["shimmerObject", "shimmerParticle"], effect: effect, textures: params.textures }; $super(renderer, this.programData); var gl = this.gl; this.percentfinished = 0.0; // create drawable object for drawing static texture this.drawableObjects = []; this.slideOrigin = {"x": 0, "y": 0}; this.slideSize = {"width": gl.viewportWidth, "height": gl.viewportHeight}; this.slideRect = { "origin": this.slideOrigin, "size": this.slideSize }; for (var i = 0, length = this.textures.length; i < length; i++) { var texture = params.textures[i]; var drawableFrame = texture.textureRect; var drawableParams = { effect: effect, textures: [texture] }; var frameRect = this.frameOfEffectWithFrame(drawableFrame); var drawableObject = new KNWebGLDrawable(renderer, drawableParams); drawableObject.frameRect = frameRect; this.drawableObjects.push(drawableObject); } // set parent opacity from CA baseLayer this.parentOpacity = effect.baseLayer.initialState.opacity; // setup requirements this.animationWillBeginWithContext(); }, frameOfEffectWithFrame: function(drawableFrame) { var gl = this.gl; var minPt = { "x": CGRectGetMinX(drawableFrame), "y": CGRectGetMinY(drawableFrame) }; var maxPt = { "x": CGRectGetMaxX(drawableFrame), "y": CGRectGetMaxY(drawableFrame) }; var extraPadding = Math.max(drawableFrame.size.width, drawableFrame.size.height); extraPadding = Math.max(extraPadding, this.slideSize.height / 3.0); minPt.y -= extraPadding; maxPt.y += extraPadding; minPt.x -= extraPadding; maxPt.x += extraPadding; var frameRect = TSDRectWithPoints(minPt, maxPt); frameRect = CGRectIntersection(frameRect, this.slideRect); frameRect = CGRectIntegral(frameRect); return frameRect; }, animationWillBeginWithContext: function() { var renderer = this.renderer; // initialize a shimmer effect object for each texture rectangle this.shimmerEffects = []; var program = this.program; var slideRect = this.slideRect; var duration = this.duration; var direction = this.direction; var type = this.type; var parentOpacity = this.parentOpacity; for (var i = 0, length = this.textures.length; i < length; i++) { var texture = this.textures[i]; var tr = this.textures[i].textureRect; var frameRect = this.drawableObjects[i].frameRect; var orthoOffset = { "x": texture.offset.pointX - frameRect.origin.x, "y": texture.offset.pointY + texture.height - (frameRect.origin.y + frameRect.size.height) }; var baseOrthoTransform = WebGraphics.makeOrthoMatrix4(0, frameRect.size.width, 0, frameRect.size.height, -1, 1); var baseTransform = WebGraphics.translateMatrix4(baseOrthoTransform, orthoOffset.x, -orthoOffset.y, 0); var shimmerEffect = new KNWebGLBuildShimmerEffect( renderer, program, slideRect, texture, frameRect, baseTransform, duration, direction, type, parentOpacity ); this.shimmerEffects.push(shimmerEffect); } }, drawFrame: function(difference, elapsed, duration) { var renderer = this.renderer; var gl = this.gl; var viewportWidth = gl.viewportWidth; var viewportHeight = gl.viewportHeight; var percentfinished = this.percentfinished; percentfinished += difference / duration; if (percentfinished > 1) { percentfinished = 1; this.isCompleted = true; } this.percentfinished = percentfinished; gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA); for (var i = 0, length = this.textures.length; i < length; i++) { var textureInfo = this.textures[i]; var initialState = textureInfo.initialState; var animations = textureInfo.animations; if (textureInfo.hasHighlightedBulletAnimation) { if (!initialState.hidden) { var opacity; if (animations.length > 0 && animations[0].property === "opacity") { var opacityFrom = animations[0].from.scalar; var opacityTo = animations[0].to.scalar; var diff = opacityTo - opacityFrom; opacity = opacityFrom + diff * percentfinished; } else { opacity = textureInfo.initialState.opacity; } this.drawableObjects[i].Opacity = this.parentOpacity * opacity; this.drawableObjects[i].drawFrame(); } } else if (textureInfo.animations.length > 0) { if (this.isCompleted) { if (this.buildIn) { // if completed, just draw its texture object for better performance this.drawableObjects[i].Opacity = this.parentOpacity * textureInfo.initialState.opacity; this.drawableObjects[i].drawFrame(); } continue; } var width = textureInfo.width; var height = textureInfo.height; var offsetX = textureInfo.offset.pointX; var offsetY = textureInfo.offset.pointY; //draw shimmer effect var shimmerEffect = this.shimmerEffects[i]; shimmerEffect.renderEffectAtPercent(this.percentfinished); } else { if (!textureInfo.initialState.hidden) { this.drawableObjects[i].Opacity = this.parentOpacity * textureInfo.initialState.opacity; this.drawableObjects[i].drawFrame(); } } } } }); var KNWebGLBuildShimmerEffect = Class.create({ initialize: function(renderer, program, slideRect, texture, destinationRect, translate, duration, direction, buildType, parentOpacity) { this.renderer = renderer; this.gl = renderer.gl; this.program = program; this._slideRect = slideRect; this._texture = texture; this._destinationRect = destinationRect; this._translate = translate; this._duration = duration; this._direction = direction; this._buildType = buildType; this._baseTransform = new Float32Array(16); this._isSetup = false; this.parentOpacity = parentOpacity; // bind shimmer texture this.shimmerTexture = KNWebGLUtil.bindTextureWithImage(this.gl, shimmerImage); this.setupEffectIfNecessary(); }, setupEffectIfNecessary: function() { if (this._isSetup) { return; } var gl = this.gl; var texture = this._texture; var meshSize = CGSizeMake(2, 2); var mFrameRect = { "origin": { "x": 0, "y": 0 }, "size": { "width": gl.viewportWidth, "height": gl.viewportHeight } }; var orthoOffset = { "x": texture.offset.pointX - mFrameRect.origin.x, "y": texture.offset.pointY + texture.height - (mFrameRect.origin.y + mFrameRect.size.height) }; var baseOrthoTransform = WebGraphics.makeOrthoMatrix4(0, mFrameRect.size.width, 0, mFrameRect.size.height, -1, 1); var baseTransform = this.baseTransform = WebGraphics.translateMatrix4(baseOrthoTransform, orthoOffset.x, -orthoOffset.y, 0); this._objectSystem = this.objectSystemForTR(this._texture, this._slideRect, this._duration); this._objectSystem.setMVPMatrix(this.baseTransform); // Set up particle particle system if (this._objectSystem.shouldDraw) { // Only set up the particles if we will actually draw this particle system! this._particleSystem = this.particleSystemForTR(this._texture, this._slideRect, this._duration); this._particleSystem.setMVPMatrix(this.baseTransform); } this._isSetup = true; }, p_numberOfParticlesForTR: function(tr, slideRect, duration) { var destRect = this._destinationRect; var slideSize = slideRect.size; var slideRatio = (destRect.size.width / slideSize.width * destRect.size.height / slideSize.height); var texRatio = (tr.size.width / destRect.size.width * tr.size.height / destRect.size.height); // create as many particles as possible without hitting our vertex limit var numParticles = parseInt(Math.min((slideRatio * texRatio * 2000), 3276)); return numParticles; }, objectSystemForTR: function(texture, slideRect, duration) { var tr = texture.textureRect; var numParticles = this.p_numberOfParticlesForTR(tr, slideRect, duration); var particleSystem = new KNWebGLBuildShimmerObjectSystem( this.renderer, this.program["shimmerObject"], {"width": tr.size.width, "height": tr.size.height}, {"width": slideRect.size.width, "height": slideRect.size.height}, duration, numParticles, texture.texture, this._direction ); return particleSystem; }, particleSystemForTR: function(texture, slideRect, duration) { var tr = texture.textureRect; // Extra sparkles at end var extraParticles = this.p_numberOfParticlesForTR(tr, slideRect, duration); extraParticles = Math.max(2, extraParticles / 40); // Add in sparkles to match object's particles var objectSystemParticleCount = this._objectSystem.particleCount; var numParticles = objectSystemParticleCount; numParticles += extraParticles; numParticles = Math.min(numParticles, 3276); var particleSystem = new KNWebGLBuildShimmerParticleSystem( this.renderer, this.program["shimmerParticle"], {"width": tr.size.width, "height": tr.size.height}, {"width": slideRect.size.width, "height": slideRect.size.height}, duration, CGSizeMake(numParticles, 1), this._objectSystem.particleSize, this._objectSystem, this.shimmerTexture, this._direction ); return particleSystem; }, p_drawObject: function(percent, textureInfo, objectShader, objectDataBuffer) { var gl = this.gl; var opacity = this.parentOpacity * textureInfo.initialState.opacity; opacity = opacity * TSUSineMap(percent); objectShader.setGLFloat(opacity, kTSDGLShaderUniformOpacity); gl.bindTexture(gl.TEXTURE_2D, textureInfo.texture); gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA); objectDataBuffer.drawWithShader(objectShader, true); }, renderEffectAtPercent: function(percent) { var gl = this.gl; var texture = this._texture; if (this._buildType === "buildOut") { percent = 1.0 - percent; } var accelPercent = (1 - percent) * (1 - percent); var isClockwise = this._buildType === "buildIn"; var rotation = (TSUReverseSquare(percent) * this._duration/1000 + percent) * Math.PI/2; if (!isClockwise) { rotation *= -1.0; } // Draw main object as pieces var objectOpacitySpan = WebGraphics.makePoint(0.2, 0.4); var objectOpacity = (percent - objectOpacitySpan.x) / objectOpacitySpan.y; objectOpacity = WebGraphics.clamp(objectOpacity, 0.0, 1.0); objectOpacity = TSUSineMap(objectOpacity); var opacity = this.parentOpacity * texture.initialState.opacity; objectOpacity *= opacity; gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA); // need to use the program before drawing the particle system gl.useProgram(this.program["shimmerObject"].shaderProgram); // set MVP Matrix for object system this._objectSystem.setMVPMatrix(this.baseTransform); this._objectSystem.drawGLSLWithPercent(accelPercent, objectOpacity, rotation, isClockwise, texture.texture); // Draw shimmers // need to use the program before drawing the particle system gl.useProgram(this.program["shimmerParticle"].shaderProgram); this._particleSystem.setMVPMatrix(this.baseTransform); this._particleSystem.drawGLSLWithPercent(accelPercent, opacity * 0.5, rotation, isClockwise, this.shimmerTexture); } }); var KNWebGLBuildSparkle = Class.create(KNWebGLProgram, { initialize: function($super, renderer, params) { var effect = params.effect; this.programData = { name: "com.apple.iWork.Keynote.KLNSparkle", programNames: ["sparkle"], effect: effect, textures: params.textures }; $super(renderer, this.programData); var gl = this.gl; this.percentfinished = 0.0; // create drawable object for drawing static texture this.drawableObjects = []; this.slideOrigin = {"x": 0, "y": 0}; this.slideSize = {"width": gl.viewportWidth, "height": gl.viewportHeight}; this.slideRect = { "origin": this.slideOrigin, "size": this.slideSize }; for (var i = 0, length = this.textures.length; i < length; i++) { var texture = params.textures[i]; var drawableFrame = texture.textureRect; var drawableParams = { effect: effect, textures: [texture] }; var frameRect = this.frameOfEffectWithFrame(drawableFrame); var drawableObject = new KNWebGLDrawable(renderer, drawableParams); drawableObject.frameRect = frameRect; this.drawableObjects.push(drawableObject); } // set parent opacity from CA baseLayer this.parentOpacity = effect.baseLayer.initialState.opacity; // setup requirements this.animationWillBeginWithContext(); }, frameOfEffectWithFrame: function(drawableFrame) { var minPt = WebGraphics.makePoint(CGRectGetMinX(drawableFrame), CGRectGetMinY(drawableFrame)); var maxPt = WebGraphics.makePoint(CGRectGetMaxX(drawableFrame), CGRectGetMaxY(drawableFrame)); var extraPadding = Math.max(drawableFrame.size.width, drawableFrame.size.height); // Make sure the width is large enough to deal with floating point precision errors in proj matrix // (Otherwise very small text will look blurry) extraPadding = Math.max(extraPadding, 128); minPt.y = Math.max(CGRectGetMinY(this.slideRect), minPt.y - extraPadding); maxPt.y = Math.min(CGRectGetMaxY(this.slideRect), maxPt.y + extraPadding); minPt.x = Math.max(CGRectGetMinX(this.slideRect), minPt.x - extraPadding); maxPt.x = Math.min(CGRectGetMaxX(this.slideRect), maxPt.x + extraPadding); var frameRect = TSDRectWithPoints(minPt, maxPt); frameRect = CGRectIntegral(frameRect); return frameRect; }, animationWillBeginWithContext: function() { var renderer = this.renderer; // initialize a shimmer effect object for each texture rectangle this.sparkleEffects = []; var program = this.program; var slideRect = this.slideRect; var duration = this.duration; var direction = this.direction; var type = this.type; var parentOpacity = this.parentOpacity; for (var i = 0, length = this.textures.length; i < length; i++) { var texture = this.textures[i]; var direction = this.direction; var tr = this.textures[i].textureRect; var frameRect = this.drawableObjects[i].frameRect; var orthoOffset = { "x": texture.offset.pointX - frameRect.origin.x, "y": texture.offset.pointY + texture.height - (frameRect.origin.y + frameRect.size.height) }; var baseOrthoTransform = WebGraphics.makeOrthoMatrix4(0, frameRect.size.width, 0, frameRect.size.height, -1, 1); var baseTransform = WebGraphics.translateMatrix4(baseOrthoTransform, orthoOffset.x, -orthoOffset.y, 0); var sparkleEffect = new KNWebGLBuildSparkleEffect( renderer, program, slideRect, texture, frameRect, baseTransform, duration, direction, type, parentOpacity ); this.sparkleEffects.push(sparkleEffect); } }, drawFrame: function(difference, elapsed, duration) { var renderer = this.renderer; var gl = this.gl; var viewportWidth = gl.viewportWidth; var viewportHeight = gl.viewportHeight; // determine the type and direction var buildIn = this.buildIn; var buildOut = this.buildOut; var percentfinished = this.percentfinished; percentfinished += difference / duration; if (percentfinished > 1) { percentfinished = 1; this.isCompleted = true; } this.percentfinished = percentfinished; gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA); for (var i = 0, length = this.textures.length; i < length; i++) { var textureInfo = this.textures[i]; var initialState = textureInfo.initialState; var animations = textureInfo.animations; if (textureInfo.hasHighlightedBulletAnimation) { if (!initialState.hidden) { var opacity; if (animations.length > 0 && animations[0].property === "opacity") { var opacityFrom = animations[0].from.scalar; var opacityTo = animations[0].to.scalar; var diff = opacityTo - opacityFrom; opacity = opacityFrom + diff * percentfinished; } else { opacity = textureInfo.initialState.opacity; } this.drawableObjects[i].Opacity = this.parentOpacity * opacity; this.drawableObjects[i].drawFrame(); } } else if (textureInfo.animations.length > 0) { if (this.isCompleted) { if (buildIn) { // if completed, just draw its texture object for better performance this.drawableObjects[i].Opacity = this.parentOpacity * textureInfo.initialState.opacity; this.drawableObjects[i].drawFrame(); } continue; } //draw shimmer effect var sparkleEffect = this.sparkleEffects[i]; sparkleEffect.renderEffectAtPercent(this.percentfinished); } else { if (!textureInfo.initialState.hidden) { this.drawableObjects[i].Opacity = this.parentOpacity * textureInfo.initialState.opacity; this.drawableObjects[i].drawFrame(); } } } } }); var KNWebGLBuildSparkleEffect = Class.create({ initialize: function(renderer, program, slideRect, texture, destinationRect, translate, duration, direction, buildType, parentOpacity) { this.renderer = renderer; this.gl = renderer.gl; this.program = program; this._slideRect = slideRect; this._texture = texture; this._destinationRect = destinationRect; this._translate = translate; this._duration = duration; this._direction = direction; this._buildType = buildType; this._baseTransform = new Float32Array(16); this._isSetup = false; this.parentOpacity = parentOpacity; // bind shimmer texture this.sparkleTexture = KNWebGLUtil.bindTextureWithImage(this.gl, sparkleImage); this.setupEffectIfNecessary(); }, setupEffectIfNecessary: function() { if (this._isSetup) { return; } var gl = this.gl; var texture = this._texture; var meshSize = CGSizeMake(2, 2); var mFrameRect = { "origin": { "x": 0, "y": 0 }, "size": { "width": gl.viewportWidth, "height": gl.viewportHeight } }; var orthoOffset = { "x": texture.offset.pointX - mFrameRect.origin.x, "y": texture.offset.pointY + texture.height - (mFrameRect.origin.y + mFrameRect.size.height) }; var baseOrthoTransform = WebGraphics.makeOrthoMatrix4(0, mFrameRect.size.width, 0, mFrameRect.size.height, -1, 1); var baseTransform = this.baseTransform = WebGraphics.translateMatrix4(baseOrthoTransform, orthoOffset.x, -orthoOffset.y, 0); // init object shader and data buffer var objectShader = this._objectShader = new TSDGLShader(gl); objectShader.initWithDefaultTextureAndOpacityShader(); // object shader set methods objectShader.setMat4WithTransform3D(baseTransform, kTSDGLShaderUniformMVPMatrix); objectShader.setGLint(0, kTSDGLShaderUniformTexture); // new data buffer attributes var objectPositionAttribute = new TSDGLDataBufferAttribute(kTSDGLShaderAttributePosition, GL_STREAM_DRAW, GL_FLOAT, false, 2); var objectTexCoordAttribute = new TSDGLDataBufferAttribute(kTSDGLShaderAttributeTexCoord, GL_STREAM_DRAW, GL_FLOAT, false, 2); // init object data buffer var objectDataBuffer = this._objectDataBuffer = new TSDGLDataBuffer(gl); objectDataBuffer.newDataBufferWithVertexAttributes([objectPositionAttribute, objectTexCoordAttribute] , meshSize, true); // Set up sparkle particle system this.sparkleSystem = this.sparkleSystemForTR(this._texture, this._slideRect, this._duration); this.sparkleSystem.setMVPMatrix(baseTransform); this.sparkleSystem.setColor(new Float32Array([1, 1, 1, 1])); this._isSetup = true; }, p_numberOfParticlesForTR: function(tr, slideRect, duration) { var destRect = this._destinationRect; var slideSize = slideRect.size; var slideRatio = (destRect.size.width / slideSize.width * destRect.size.height / slideSize.height); var texRatio = (tr.size.width / destRect.size.width * tr.size.height / destRect.size.height); // create as many particles as possible without hitting our vertex limit var numParticles = parseInt(Math.min((slideRatio * texRatio * 2000), 3276)); return numParticles; }, sparkleSystemForTR: function(texture, slideRect, duration) { var tr = texture.textureRect; var slideSize = this._slideRect.size; var boundingRect = this._destinationRect; var slideRatio = Math.min(boundingRect.size.width, slideSize.width) / slideSize.width * Math.min(boundingRect.size.height, slideSize.height) / slideSize.height; var numParticles = parseInt(((2 - Math.sqrt(slideRatio)) / 2) * 1500 * this._duration / 1000); var sparkleSystem = new KNWebGLBuildSparkleSystem( this.renderer, this.program["sparkle"], {"width": tr.size.width, "height": tr.size.height}, {"width": slideRect.size.width, "height": slideRect.size.height}, duration, CGSizeMake(numParticles, 1), {"width": 128, "height": 128}, this.sparkleTexture, this._direction ); return sparkleSystem; }, p_drawObject: function(percent, textureInfo, objectShader, objectDataBuffer) { var gl = this.gl; var opacity = this.parentOpacity * textureInfo.initialState.opacity; opacity = opacity * TSUSineMap(percent); objectShader.setGLFloat(opacity, kTSDGLShaderUniformOpacity); gl.bindTexture(gl.TEXTURE_2D, textureInfo.texture); gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA); objectDataBuffer.drawWithShader(objectShader, true); }, renderEffectAtPercent: function(percent) { var gl = this.gl; var texture = this._texture; var direction = this._direction; var tr = texture.textureRect; var isReverse = (direction == KNDirection.kKNDirectionRightToLeft || direction == KNDirection.kKNDirectionTopToBottom); var isHorizontal = (direction == KNDirection.kKNDirectionRightToLeft || direction == KNDirection.kKNDirectionLeftToRight); var mvpMatrix = this._translate; var alpha = this.parentOpacity * texture.initialState.opacity; // CONSTANTS var duration = this._duration / 1000; var blurWidth = 0.2 / duration; var width = tr.size.width; var height = tr.size.height; // ========= // FIRST, we draw the original image fading out var particleTiming = KNSparkleMaxParticleLife / Math.max(0.75, duration); var opaqueWidth = percent / (1. - particleTiming); var xStart = 0, yStart = 0, xEnd = 0, yEnd = 0, xTexStart = 0, yTexStart = 0, xTexEnd = 0, yTexEnd = 0; if (this._buildType == "buildOut") { opaqueWidth -= blurWidth; xStart = (isHorizontal) ? ((isReverse) ? 0 : width) : 0; yStart = (isHorizontal) ? 0 : ((isReverse) ? 0 : height); xEnd = (isHorizontal) ? ((isReverse) ? width - (width * WebGraphics.clamp(opaqueWidth, 0, 1)) : width * WebGraphics.clamp(opaqueWidth, 0, 1)) : width; yEnd = (isHorizontal) ? height : ((isReverse) ? height - (height * WebGraphics.clamp(opaqueWidth, 0, 1)) : (height * WebGraphics.clamp(opaqueWidth, 0, 1))); xTexStart = (isHorizontal) ? ((isReverse) ? 0 : 1) : 0; yTexStart = (isHorizontal) ? 0 : ((isReverse) ? 0 : 1); xTexEnd = (isHorizontal) ? ((isReverse) ? 1 - (1 * WebGraphics.clamp(opaqueWidth, 0, 1)) : (1 * WebGraphics.clamp(opaqueWidth, 0, 1))) : 1; yTexEnd = (isHorizontal) ? 1 : ((isReverse) ? 1 - (1 * WebGraphics.clamp(opaqueWidth, 0, 1)) : (1 * WebGraphics.clamp(opaqueWidth, 0, 1))); } else { opaqueWidth -= blurWidth; xStart = (isHorizontal) ? ((isReverse) ? width : 0) : 0; yStart = (isHorizontal) ? 0 : ((isReverse) ? height : 0); xEnd = (isHorizontal) ? ((isReverse) ? width - (width * WebGraphics.clamp(opaqueWidth, 0, 1)) : width * WebGraphics.clamp(opaqueWidth, 0, 1)) : width; yEnd = (isHorizontal) ? height : ((isReverse) ? height - (height * WebGraphics.clamp(opaqueWidth, 0, 1)) : height * WebGraphics.clamp(opaqueWidth, 0, 1)); xTexStart = (isHorizontal) ? ((isReverse) ? 1 : 0) : 0; yTexStart = (isHorizontal) ? 0 : ((isReverse) ? 1 : 0); xTexEnd = (isHorizontal) ? ((isReverse) ? 1 - (1 * WebGraphics.clamp(opaqueWidth, 0, 1)) : 1 * WebGraphics.clamp(opaqueWidth, 0, 1)) : 1; yTexEnd = (isHorizontal) ? 1 : ((isReverse) ? 1 - (1 * WebGraphics.clamp(opaqueWidth, 0, 1)) : 1 * WebGraphics.clamp(opaqueWidth, 0, 1)); } gl.bindTexture(gl.TEXTURE_2D, texture.texture); this._objectShader.setGLFloat(alpha, kTSDGLShaderUniformOpacity); // update data buffer position and text coord var objectDataBuffer = this._objectDataBuffer; var objectPositionAttribute = objectDataBuffer.vertexAttributeNamed(kTSDGLShaderAttributePosition); var objectTexCoordAttribute = objectDataBuffer.vertexAttributeNamed(kTSDGLShaderAttributeTexCoord); objectDataBuffer.setGLPoint2D(WebGraphics.makePoint(xStart, yStart), objectPositionAttribute, 0); objectDataBuffer.setGLPoint2D(WebGraphics.makePoint(xEnd, yStart), objectPositionAttribute, 1); objectDataBuffer.setGLPoint2D(WebGraphics.makePoint(xStart, yEnd), objectPositionAttribute, 2); objectDataBuffer.setGLPoint2D(WebGraphics.makePoint(xEnd, yEnd), objectPositionAttribute, 3); objectDataBuffer.setGLPoint2D(WebGraphics.makePoint(xTexStart, yTexStart), objectTexCoordAttribute, 0); objectDataBuffer.setGLPoint2D(WebGraphics.makePoint(xTexEnd, yTexStart), objectTexCoordAttribute, 1); objectDataBuffer.setGLPoint2D(WebGraphics.makePoint(xTexStart, yTexEnd), objectTexCoordAttribute, 2); objectDataBuffer.setGLPoint2D(WebGraphics.makePoint(xTexEnd, yTexEnd), objectTexCoordAttribute, 3); objectDataBuffer.drawWithShader(this._objectShader, true); gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA); // need to use the program before drawing the particle system gl.useProgram(this.program["sparkle"].shaderProgram); this.sparkleSystem.setMVPMatrix(this.baseTransform); this.sparkleSystem.drawFrame(percent, 1.0); } }); var KNWebGLTransitionMagicMove = Class.create(KNWebGLProgram, { initialize: function($super, renderer, params) { // initialize default program data for core animation wrapper program this.coreAnimationWrapperProgram = new KNWebGLCoreAnimationWrapperProgram(params); // create WebGL program using core animation wrapper program data $super(renderer, this.coreAnimationWrapperProgram.data); var gl = this.gl; this.percentfinished = 0.0; // create drawable object for drawing the texture this.drawableObjects = []; this.slideOrigin = {"x": 0, "y": 0}; this.slideSize = {"width": gl.viewportWidth, "height": gl.viewportHeight}; this.slideRect = { "origin": this.slideOrigin, "size": this.slideSize }; this.frameRect = this.slideRect; var effect = params.effect; // set parent opacity from CA baseLayer this.parentOpacity = effect.baseLayer.initialState.opacity; // setup web drawable requirements this.animationWillBeginWithContext(); }, animationWillBeginWithContext: function() { var renderer = this.renderer; // initialize a core animation wrapper based effect object for each texture rectangle this.coreAnimationWrapperBasedEffects = []; var program = this.program; var slideRect = this.slideRect; var duration = this.duration; var direction = this.direction; var buildType = this.type; var parentOpacity = this.parentOpacity; var parameterGroupName = this.parameterGroupName; for (var i = 0, length = this.textures.length; i < length; i++) { var texture = this.textures[i]; var direction = this.direction; var tr = this.textures[i].textureRect; var frameRect = this.frameRect; var orthoOffset = { "x": texture.offset.pointX - frameRect.origin.x, "y": texture.offset.pointY + texture.height - (frameRect.origin.y + frameRect.size.height) }; var baseOrthoTransform = WebGraphics.makeOrthoMatrix4(0, frameRect.size.width, 0, frameRect.size.height, -1, 1); var baseTransform = WebGraphics.translateMatrix4(baseOrthoTransform, orthoOffset.x, -orthoOffset.y, 0); var coreAnimationWrapperBasedEffect = new KNWebGLCoreAnimationWrapperBasedEffect( renderer, program, slideRect, texture, frameRect, baseTransform, duration, direction, buildType, parentOpacity ); // push each effect into effect dictionary this.coreAnimationWrapperBasedEffects.push(coreAnimationWrapperBasedEffect); } }, drawFrame: function(difference, elapsed, duration) { var coreAnimationWrapperBasedEffects = this.coreAnimationWrapperBasedEffects; for (var i = 0, length = coreAnimationWrapperBasedEffects.length; i < length; i++) { coreAnimationWrapperBasedEffects[i].drawFrame(difference, elapsed, duration); } } }); var KNWebGLTransitionContentAware = Class.create(KNWebGLProgram, { initialize: function($super, renderer, params) { // initialize default program data for core animation wrapper program this.coreAnimationWrapperProgram = new KNWebGLCoreAnimationWrapperProgram(params); this.params = params; // create WebGL program using core animation wrapper program data $super(renderer, this.coreAnimationWrapperProgram.data); var gl = this.gl; this.percentfinished = 0.0; this.slideOrigin = {"x": 0, "y": 0}; this.slideSize = {"width": gl.viewportWidth, "height": gl.viewportHeight}; this.slideRect = { "origin": this.slideOrigin, "size": this.slideSize }; // set frameRect to slideRect for content aware transition this.frameRect = this.slideRect; var effect = params.effect; // set parent opacity from CA baseLayer this.parentOpacity = effect.baseLayer.initialState.opacity; // setup web drawable requirements this.animationWillBeginWithContext(); }, animationWillBeginWithContext: function() { var renderer = this.renderer; // effect array object to include both text effects and CA wrapper based objects this.contentAwareEffects = []; var program = this.program; var slideRect = this.slideRect; var duration = this.duration; var direction = this.direction; var buildType = this.type; var parentOpacity = this.parentOpacity; var parameterGroupName = this.parameterGroupName; for (var i = 0, length = this.textures.length; i < length; i++) { var texture = this.textures[i]; var direction = this.direction; var tr = this.textures[i].textureRect; var frameRect = this.frameRect; var orthoOffset = { "x": texture.offset.pointX - frameRect.origin.x, "y": texture.offset.pointY + texture.height - (frameRect.origin.y + frameRect.size.height) }; var baseOrthoTransform = WebGraphics.makeOrthoMatrix4(0, frameRect.size.width, 0, frameRect.size.height, -1, 1); var baseTransform = WebGraphics.translateMatrix4(baseOrthoTransform, orthoOffset.x, -orthoOffset.y, 0); // make sure the effect only work on text type or shape object var texturedRectangle = texture.texturedRectangle; var textureType = texturedRectangle.textureType; var isShapeObject = (textureType === TSDTextureType.Object && texturedRectangle.shapePath) ? true : false; if (textureType === TSDTextureType.Text || isShapeObject) { var params = this.params; var effect = params.effect; // set this texture for text effect params.textures = [texture]; // use hidden animations to find out the correct build type var groupAnimations = texture.animations; var program; if (groupAnimations && groupAnimations.length > 0) { var animations = groupAnimations[0].animations; for (var j = 0, animationLength = animations.length; j < animationLength; j++) { var animation = animations[j]; if (animation.property === "hidden") { effect.type = animation.to.scalar ? "buildOut" : "buildIn"; break; } } } switch (effect.name) { case "apple:ca-text-shimmer": program = new KNWebGLBuildShimmer(renderer, params); break; case "apple:ca-text-sparkle": program = new KNWebGLBuildSparkle(renderer, params); break; default: program = new KNWebGLDissolve(renderer, params); break; } // push each text effect into effect dictionary this.contentAwareEffects.push(program); } else { var coreAnimationWrapperBasedEffect = new KNWebGLCoreAnimationWrapperBasedEffect( renderer, program, slideRect, texture, frameRect, baseTransform, duration, direction, buildType, parentOpacity ); // push each CA effect into effect dictionary this.contentAwareEffects.push(coreAnimationWrapperBasedEffect); } } }, drawFrame: function(difference, elapsed, duration) { var contentAwareEffects = this.contentAwareEffects; for (var i = 0, length = contentAwareEffects.length; i < length; i++) { var contentAwareEffect = contentAwareEffects[i]; contentAwareEffect.drawFrame(difference, elapsed, duration); } } }); var KNWebGLTransitionShimmer = Class.create(KNWebGLTransitionContentAware, { initialize: function($super, renderer, params) { // Set up Shimmer as content aware transition $super(renderer, params); } }); var KNWebGLTransitionSparkle = Class.create(KNWebGLTransitionContentAware, { initialize: function($super, renderer, params) { // Set up Sparkle as content aware transition $super(renderer, params); } });