/*
* KNWebGLWrapper.js
* Keynote HTML Player
*
* Created by Tungwei Cheng
* Copyright (c) 2019 Apple Inc. All rights reserved.
*/
var KNWebGLCoreAnimationWrapperProjectionTransformType = {
Invalid: 0,
Orthographic: 1,
Perspective: 2,
Custom: 3
};
var KNWebGLCoreAnimationWrapperTextureDrawOptions = Class.create({
initialize: function(textureInfo, effectDuration, baseTransform) {
// is hidden
this.hidden = false;
// culling backface
this.wantsBackFaceCulling = false;
// is background texture
this.isBackground = false;
// is foreground texture
this.isForeground = true;
// if this object isn't moving at all, we can optimize around that
this.isMoving = true;
// is blending between two textures
this.isBlending = false;
// texture opacity from animation
this.opacity = 1;
// all things related to the texture
this.textureInfo = textureInfo;
// duration of current effect in seconds
this.effectDuration = effectDuration;
// base transform matrix for the object
this.baseTransform = baseTransform;
}
});
var KNWebGLCoreAnimationWrapperProgram = Class.create({
initialize: function(params) {
this.name = "CoreAnimationWrapperBasedEffect";
this.effect = params.effect;
this.textures = params.textures;
this.data = {
name: this.name,
programNames: [],
effect: this.effect,
textures: this.textures
};
}
});
var KNWebGLCoreAnimationWrapper = Class.create({
initialize: function(gl) {
// initialize wrapper class to be used in core animation wrapper based effects
this.gl = gl;
this.setupWithContext();
},
setupWithContext: function() {
// setup animation parameter group for timing function
this.animParameterGroup = new KNAnimParameterGroup("timingFunction");
},
renderFrameWithContext: function(objectShader, objectDataBuffer, textureDrawOptions) {
var gl = this.gl;
var parameterGroup = this.animParameterGroup;
// texture draw options
var textureInfo = textureDrawOptions.textureInfo;
var textureRect = textureInfo.textureRect;
var initialState = textureInfo.initialState;
// overall duration for the effect
var overallDuration = textureDrawOptions.effectDuration;
// base transform matrix for the object
var baseTransform = textureDrawOptions.baseTransform;
// effect percent
var percent = textureDrawOptions.percent;
// is the effect blending two textures
var isBlending = textureDrawOptions.isBlending;
// default transfrom origin
var transformPoint = {
x: textureRect.size.width / 2,
y: textureRect.size.height / 2
};
// postion
var fromPositionX = 0;
var fromPositionY = 0;
var toPositionX = 0;
var toPositionY = 0;
var offsetX = 0;
var offsetY = 0;
// scale
var fromScaleX = 1;
var fromScaleY = 1;
var toScaleX = 1;
var toScaleY = 1;
// rotate
var hasRotationZ = false;
var fromRotationZ = 0;
var toRotationZ = 0;
// opacity
var fromOpacity = 1;
var toOpacity = 1;
var opacityPercent = 0;
// contents
var outgoingTexture = textureInfo.texture;
var incomingTexture;
// search the animations within group
var groupAnimations = textureInfo.animations;
var animations = groupAnimations[0].animations;
for (var i = 0, length = animations.length; i < length; i++) {
var animation = animations[i];
var key = animation.property;
var fromValue = animation.from;
var toValue = animation.to;
var newPercent = percent;
var beginTime = animation.beginTime * 1000;
var duration = animation.duration * 1000;
// apply timing function for the animation curve if it is not linear
if (animation.timingFunction && animation.timingFunction !== "Linear") {
newPercent = parameterGroup.doubleForAnimationCurve(animation.timingFunction, percent);
}
switch (key) {
case "transform.translation":
fromPositionX = fromValue.pointX;
fromPositionY = fromValue.pointY;
toPositionX = toValue.pointX;
toPositionY = toValue.pointY;
offsetX = (toValue.pointX - fromValue.pointX) * newPercent;
offsetY = (toValue.pointY - fromValue.pointY) * newPercent;
break;
case "transform.rotation.z":
hasRotationZ = true;
fromRotationZ = fromValue.scalar;
toRotationZ = toValue.scalar;
break;
case "transform.scale.x":
fromScaleX = fromValue.scalar;
toScaleX = toValue.scalar;
break;
case "transform.scale.y":
fromScaleY = fromValue.scalar;
toScaleY = toValue.scalar;
break;
case "opacity":
fromOpacity = fromValue.scalar;
toOpacity = toValue.scalar;
if (overallDuration !== duration) {
var timeAtPercent = percent * overallDuration;
if (timeAtPercent < beginTime) {
opacityPercent = 0;
} else if (timeAtPercent > beginTime + duration) {
opacityPercent = 1;
} else {
opacityPercent = (timeAtPercent - beginTime) / duration;
}
if (animation.timingFunction && animation.timingFunction !== "Linear") {
opacityPercent = parameterGroup.doubleForAnimationCurve(animation.timingFunction, opacityPercent);
}
} else {
opacityPercent = newPercent;
}
break;
case "contents":
incomingTexture = textureInfo.toTexture;
break;
default:
break;
}
}
// Opacity animation
var opacity = initialState.hidden ? 0 : textureInfo.parentOpacity * initialState.opacity;
if (fromOpacity !== toOpacity) {
opacity = fromOpacity + (toOpacity - fromOpacity) * opacityPercent;
}
objectShader.setGLFloat(opacity, kTSDGLShaderUniformOpacity);
var mvpMatrix = WebGraphics.translateMatrix4(baseTransform, fromPositionX, -fromPositionY, 0);
// Affine Transform Translation
mvpMatrix = WebGraphics.translateMatrix4(mvpMatrix, offsetX, -offsetY, 0);
// Affine Transform Rotation and Scale
// set transform origin by translating to the transform center
// find out if the anchorPoint is different from the default and apply the offset
var anchorPoint = initialState.anchorPoint;
if (anchorPoint.pointX !== 0.5 || anchorPoint.pointY !== 0.5) {
// set the new transform point
transformPoint.x = anchorPoint.pointX * textureRect.size.width;
transformPoint.y = (1 - anchorPoint.pointY) * textureRect.size.height;
}
mvpMatrix = WebGraphics.translateMatrix4(mvpMatrix, transformPoint.x, transformPoint.y, 0);
// set rotation amount to initial state rotation
var rotatedRadian = initialState.rotation;
if (hasRotationZ) {
// if the from value is different from initial state then use the from value
if (fromRotationZ !== rotatedRadian) {
rotatedRadian = fromRotationZ;
}
// add up the rotatedRadian for each percentage update
rotatedRadian = rotatedRadian + (toRotationZ - fromRotationZ) * newPercent;
}
// apply the rotation
if (rotatedRadian !== 0) {
mvpMatrix = WebGraphics.rotateMatrix4AboutXYZ(mvpMatrix, -rotatedRadian, 0, 0, 1);
}
// apply initial state scale
var initialStateScale = initialState.scale;
if (initialStateScale !== 1) {
mvpMatrix = WebGraphics.scaleMatrix4(mvpMatrix, initialStateScale, initialStateScale, 1);
}
// apply transform Scale if there is scale animation
if (fromScaleX !== toScaleX || fromScaleY !== toScaleY) {
mvpMatrix = WebGraphics.scaleMatrix4(mvpMatrix, (toScaleX - fromScaleX) * newPercent + fromScaleX, (toScaleY - fromScaleY) * newPercent + fromScaleY, 1);
}
// untranslate the translation to the transform center
mvpMatrix = WebGraphics.translateMatrix4(mvpMatrix, -transformPoint.x, -transformPoint.y, 0);
objectShader.setMat4WithTransform3D(mvpMatrix, kTSDGLShaderUniformMVPMatrix);
// set up default blend mode
gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
// if there is an incoming texture then it is contents animations
if (isBlending) {
gl.activeTexture(gl.TEXTURE1);
gl.bindTexture(gl.TEXTURE_2D, incomingTexture);
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, outgoingTexture);
objectShader.setGLFloat(newPercent, "mixFactor");
} else {
gl.bindTexture(gl.TEXTURE_2D, outgoingTexture);
}
objectDataBuffer.drawWithShader(objectShader, true);
}
});
var KNWebGLCoreAnimationWrapperBasedEffect = Class.create({
initialize: function(renderer, program, slideRect, texture, frameRect, baseTransform, duration, direction, buildType, parentOpacity) {
this.renderer = renderer;
this.gl = renderer.gl;
this.program = program;
this.slideRect = slideRect;
this.texture = texture;
this.frameRect = frameRect;
this.baseTransform = baseTransform;
this.duration = duration;
this.direction = direction;
this.buildType = buildType;
this.parentOpacity = parentOpacity;
// animation parameter group
this.animParameterGroup = new KNAnimParameterGroup("timingFunction");
this.percentfinished = 0;
this.prepareAnimationWithContext();
this.animationWillBeginWithContext();
},
isOrthographicProjection: function() {
return true;
},
prepareAnimationWithContext: function() {
// prepare core animation wrapper from the gl renderer
this.coreAnimationWrapper = this.renderer.coreAnimationWrapper;
// default texture draw options
var textureDrawOptions = this.textureDrawOptions = new KNWebGLCoreAnimationWrapperTextureDrawOptions(this.texture, this.duration, this.baseTransform);
// is blending between two textures
textureDrawOptions.isBlending = this.texture.toTexture ? true : false;
},
animationWillBeginWithContext: function() {
var renderer = this.renderer;
var gl = this.gl;
var frameRect = this.frameRect;
var meshSize = CGSizeMake(2, 2);
var texture = this.texture;
// init object shader and data buffer
var objectShader = this.objectShader = new TSDGLShader(gl);
objectShader.initWithContentsAndOpacityShader();
// object shader set methods
objectShader.setMat4WithTransform3D(this.baseTransform, kTSDGLShaderUniformMVPMatrix);
// outgoing texture
objectShader.setGLint(0, kTSDGLShaderUniformTexture2);
// incoming Texture
objectShader.setGLint(1, kTSDGLShaderUniformTexture);
// init object data buffer
var objectTextureRect = this.texture.textureRect;
var objectVertexRect = CGRectMake(0, 0, objectTextureRect.size.width, objectTextureRect.size.height);
var objectDataBuffer = this.objectDataBuffer = new TSDGLDataBuffer(gl);
objectDataBuffer.initWithVertexRect(objectVertexRect, TSDRectUnit, meshSize, false, false);
},
drawFrame: function(difference, elapsed, duration) {
var renderer = this.renderer;
var gl = this.gl;
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);
var textureInfo = this.texture;
var initialState = textureInfo.initialState;
var animations = textureInfo.animations;
var objectShader = this.objectShader;
var objectDataBuffer = this.objectDataBuffer;
if (textureInfo.animations.length > 0) {
var percent = percentfinished;
// update texture draw options
var textureDrawOptions = this.textureDrawOptions;
textureDrawOptions.percent = percent;
// render the effect in core animation wrapper
this.coreAnimationWrapper.renderFrameWithContext(objectShader, objectDataBuffer, textureDrawOptions);
} else {
var opacity = textureInfo.initialState.hidden ? 0 : this.parentOpacity * textureInfo.initialState.opacity;
// set texture opacity
objectShader.setGLFloat(opacity, kTSDGLShaderUniformOpacity);
// draw static object
gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
gl.bindTexture(gl.TEXTURE_2D, textureInfo.texture);
objectDataBuffer.drawWithShader(objectShader, true);
}
}
});