Skip to content

Instantly share code, notes, and snippets.

@halfmanhalfdonut
Last active September 29, 2015 17:17
Show Gist options
  • Save halfmanhalfdonut/1635771 to your computer and use it in GitHub Desktop.
Save halfmanhalfdonut/1635771 to your computer and use it in GitHub Desktop.
Keanu Basketball module
Basketball = (keanu, opts, duration) ->
return if not keanu
@keanu = keanu
@ctx = @keanu.ctx
@origin = opts.origin or []
@originX = @origin[0] or 0
@originY = @origin[1] or 0
@originRadius = @origin[2] or 0
@control = opts.control or []
@controlX = @control[0] or 0
@controlY = @control[1] or 0
@controlRadius = @control[2] or 0
@destination = opts.destination or []
@destinationX = @destination[0] or 0
@destinationY = @destination[1] or 0
@destinationRadius = @destination[2] or 0
@trailStates = []
@keepStates = 15
@step = 3
@animationMiddle = opts.animationMiddle or 2
@shape = opts.shape or keanu.circle
@styles = opts.styles or {}
@zIndex = opts.zIndex or 0
@easeType = opts.easeType or "linear"
@callback = opts.callback
@useTrail = opts.useTrail or true
@duration = duration or 1000
@easing = @keanu.tweens[@easeType]
@originX = if @originX is @controlX then @originX + 1 else @originX
@originY = if @originY is @controlY then @originY + 1 else @originY
@ballX = @trailX = @shadowX = @originX
@ballY = @trailY = @shadowY = @originY
@ballRadius = @trailRadius = @shadowRadius = @originRadius
@xChange = @destinationX - @originX
@yChange = @destinationY - @originY
@rChange1 = @controlRadius - @originRadius
@rChange2 = @destinationRadius - @controlRadius
@intervalTime = @keanu.getIntervalTime()
@tickerStep = @duration / @intervalTime
@frame = 0
@tick = 0
@draw = =>
@drawShadow()
@drawTrail()
@drawShot()
@frame++
@tick += @intervalTime
if @tick >= @duration
console.log "callback"
keanu.unsubscribe "enterFrame", @draw, @zIndex
@trailStates = []
@callback and @callback()
true
@keanu.subscribe "enterFrame", @draw
this
Basketball::drawTrail = ->
len = @trailStates.length
if len > 0
alpha = 0.1
rad = 0.6
i = len - 1
while i >= 0
@drawBall @keanu,
x: @trailStates[i][0] or 0
y: @trailStates[i][1] or 0
r: (@trailStates[i][2] * rad) or 0
styles:
fillStyle: "rgba(12, 131, 218, #{alpha})"
strokeStyle: "rgba(12, 131, 218, #{alpha})"
@keanu.setDimensions
x: @trailStates[i][0] - (@trailStates[i][2] * rad)
y: @trailStates[i][1] - (@trailStates[i][2] * rad)
w: @trailStates[i][0] + (@trailStates[i][2] * rad)
h: @trailStates[i][1] + (@trailStates[i][2] * rad)
alpha += 0.05
rad += 0.02 if rad < 0.98
i--
this
Basketball::drawShadow = ->
@shadowX = @easing @frame, @originX, @xChange, @tickerStep
@shadowY = @easing @frame, @originY, @yChange, @tickerStep
@shadowRadius = 2
@drawBall @keanu,
x: @shadowX
y: @shadowY
r: @shadowRadius
styles:
fillStyle: "#000"
strokeStyle: "#000"
lineWidth: "0"
@keanu.setDimensions
x: @shadowX - @shadowRadius
y: @shadowY - @shadowRadius
w: @shadowX + @shadowRadius
h: @shadowY + @shadowRadius
this
Basketball::handleTrailState = (state) ->
temp = []
loopLen = if @trailStates.length >= @keepStates then @keepStates else @trailStates.length
temp.push state
temp.push @trailStates[i] for i in [0...loopLen]
@trailStates = temp
this
Basketball::drawShot = ->
@ballX = @easing @frame, @originX, @xChange, @tickerStep
@ballY = @easing @frame, @originY, @yChange, @tickerStep
if @frame < @tickerStep / @animationMiddle
@ballRadius = @easing @frame, @originRadius, @rChange1, @tickerStep
else
@ballRadius = @easing @frame, @controlRadius, @rChange2, @tickerStep
if @controlX isnt 0
@ballX = @keanu.tweens.quadraticBezierCurve (@originX - @ballX) / (@originX - @destinationX), @originX, @controlX, @destinationX
if @controlY isnt 0
@ballY = @keanu.tweens.quadraticBezierCurve (@originY - @ballY) / (@originY - @destinationY), @originY, @controlY, @destinationY
@handleTrailState [@ballX, @ballY, @ballRadius] if @frame % @step is 0
@drawBall @keanu,
x: @ballX
y: @ballY
r: @ballRadius
styles:
fillStyle: @styles.fillStyle or false
strokeStyle: @styles.strokeStyle or false
lineWidth: @styles.lineWidth or false
@keanu.setDimensions
x: @ballX - @ballRadius
y: @ballY - @ballRadius
w: @ballX + @ballRadius
h: @ballY + @ballRadius
this
Basketball::drawBall = (keanu, data) ->
data = data or {}
data.styles = data.styles or {}
x = data.x or 0
y = data.y or 0
r = data.r or 0
isTrail = data.isTrail or false
f = data.styles.fillStyle or "#E08428"
w = data.styles.lineWidth or 0
s = data.styles.strokeStyle or "#D07317"
keanu.ctx.fillStyle = f
keanu.ctx.strokeStyle = s
keanu.ctx.lineWidth = w
keanu.ctx.beginPath()
keanu.ctx.arc x, y, r, 0, Math.PI * 2, true
keanu.ctx.closePath()
keanu.ctx.fill()
keanu.ctx.stroke()
this
window.Keanu.modules.Basketball = Basketball
var Basketball;
Basketball = function(keanu, opts, duration) {
var _this = this;
if (!keanu) {
return;
}
this.keanu = keanu;
this.ctx = this.keanu.ctx;
this.origin = opts.origin || [];
this.originX = this.origin[0] || 0;
this.originY = this.origin[1] || 0;
this.originRadius = this.origin[2] || 0;
this.control = opts.control || [];
this.controlX = this.control[0] || 0;
this.controlY = this.control[1] || 0;
this.controlRadius = this.control[2] || 0;
this.destination = opts.destination || [];
this.destinationX = this.destination[0] || 0;
this.destinationY = this.destination[1] || 0;
this.destinationRadius = this.destination[2] || 0;
this.trailStates = [];
this.keepStates = 15;
this.step = 3;
this.animationMiddle = opts.animationMiddle || 2;
this.shape = opts.shape || keanu.circle;
this.styles = opts.styles || {};
this.zIndex = opts.zIndex || 0;
this.easeType = opts.easeType || "linear";
this.callback = opts.callback;
this.useTrail = opts.useTrail || true;
this.duration = duration || 1000;
this.easing = this.keanu.tweens[this.easeType];
this.originX = this.originX === this.controlX ? this.originX + 1 : this.originX;
this.originY = this.originY === this.controlY ? this.originY + 1 : this.originY;
this.ballX = this.trailX = this.shadowX = this.originX;
this.ballY = this.trailY = this.shadowY = this.originY;
this.ballRadius = this.trailRadius = this.shadowRadius = this.originRadius;
this.xChange = this.destinationX - this.originX;
this.yChange = this.destinationY - this.originY;
this.rChange1 = this.controlRadius - this.originRadius;
this.rChange2 = this.destinationRadius - this.controlRadius;
this.intervalTime = this.keanu.getIntervalTime();
this.tickerStep = this.duration / this.intervalTime;
this.frame = 0;
this.tick = 0;
this.draw = function() {
_this.drawShadow();
_this.drawTrail();
_this.drawShot();
_this.frame++;
_this.tick += _this.intervalTime;
if (_this.tick >= _this.duration) {
console.log("callback");
keanu.unsubscribe("enterFrame", _this.draw, _this.zIndex);
_this.trailStates = [];
_this.callback && _this.callback();
return true;
}
};
this.keanu.subscribe("enterFrame", this.draw);
return this;
};
Basketball.prototype.drawTrail = function() {
var alpha, i, len, rad;
len = this.trailStates.length;
if (len > 0) {
alpha = 0.1;
rad = 0.6;
i = len - 1;
while (i >= 0) {
this.drawBall(this.keanu, {
x: this.trailStates[i][0] || 0,
y: this.trailStates[i][1] || 0,
r: (this.trailStates[i][2] * rad) || 0,
styles: {
fillStyle: "rgba(12, 131, 218, " + alpha + ")",
strokeStyle: "rgba(12, 131, 218, " + alpha + ")"
}
});
this.keanu.setDimensions({
x: this.trailStates[i][0] - (this.trailStates[i][2] * rad),
y: this.trailStates[i][1] - (this.trailStates[i][2] * rad),
w: this.trailStates[i][0] + (this.trailStates[i][2] * rad),
h: this.trailStates[i][1] + (this.trailStates[i][2] * rad)
});
alpha += 0.05;
if (rad < 0.98) {
rad += 0.02;
}
i--;
}
}
return this;
};
Basketball.prototype.drawShadow = function() {
this.shadowX = this.easing(this.frame, this.originX, this.xChange, this.tickerStep);
this.shadowY = this.easing(this.frame, this.originY, this.yChange, this.tickerStep);
this.shadowRadius = 2;
this.drawBall(this.keanu, {
x: this.shadowX,
y: this.shadowY,
r: this.shadowRadius,
styles: {
fillStyle: "#000",
strokeStyle: "#000",
lineWidth: "0"
}
});
this.keanu.setDimensions({
x: this.shadowX - this.shadowRadius,
y: this.shadowY - this.shadowRadius,
w: this.shadowX + this.shadowRadius,
h: this.shadowY + this.shadowRadius
});
return this;
};
Basketball.prototype.handleTrailState = function(state) {
var i, loopLen, temp, _i;
temp = [];
loopLen = this.trailStates.length >= this.keepStates ? this.keepStates : this.trailStates.length;
temp.push(state);
for (i = _i = 0; 0 <= loopLen ? _i < loopLen : _i > loopLen; i = 0 <= loopLen ? ++_i : --_i) {
temp.push(this.trailStates[i]);
}
this.trailStates = temp;
return this;
};
Basketball.prototype.drawShot = function() {
this.ballX = this.easing(this.frame, this.originX, this.xChange, this.tickerStep);
this.ballY = this.easing(this.frame, this.originY, this.yChange, this.tickerStep);
if (this.frame < this.tickerStep / this.animationMiddle) {
this.ballRadius = this.easing(this.frame, this.originRadius, this.rChange1, this.tickerStep);
} else {
this.ballRadius = this.easing(this.frame, this.controlRadius, this.rChange2, this.tickerStep);
}
if (this.controlX !== 0) {
this.ballX = this.keanu.tweens.quadraticBezierCurve((this.originX - this.ballX) / (this.originX - this.destinationX), this.originX, this.controlX, this.destinationX);
}
if (this.controlY !== 0) {
this.ballY = this.keanu.tweens.quadraticBezierCurve((this.originY - this.ballY) / (this.originY - this.destinationY), this.originY, this.controlY, this.destinationY);
}
if (this.frame % this.step === 0) {
this.handleTrailState([this.ballX, this.ballY, this.ballRadius]);
}
this.drawBall(this.keanu, {
x: this.ballX,
y: this.ballY,
r: this.ballRadius,
styles: {
fillStyle: this.styles.fillStyle || false,
strokeStyle: this.styles.strokeStyle || false,
lineWidth: this.styles.lineWidth || false
}
});
this.keanu.setDimensions({
x: this.ballX - this.ballRadius,
y: this.ballY - this.ballRadius,
w: this.ballX + this.ballRadius,
h: this.ballY + this.ballRadius
});
return this;
};
Basketball.prototype.drawBall = function(keanu, data) {
var f, isTrail, r, s, w, x, y;
data = data || {};
data.styles = data.styles || {};
x = data.x || 0;
y = data.y || 0;
r = data.r || 0;
isTrail = data.isTrail || false;
f = data.styles.fillStyle || "#E08428";
w = data.styles.lineWidth || 0;
s = data.styles.strokeStyle || "#D07317";
keanu.ctx.fillStyle = f;
keanu.ctx.strokeStyle = s;
keanu.ctx.lineWidth = w;
keanu.ctx.beginPath();
keanu.ctx.arc(x, y, r, 0, Math.PI * 2, true);
keanu.ctx.closePath();
keanu.ctx.fill();
keanu.ctx.stroke();
return this;
};
window.Keanu.modules.Basketball = Basketball;
class Basketball
constructor: (keanu, opts, duration) ->
return if not keanu
@keanu = keanu
@ctx = @keanu.ctx
@origin = opts.origin or []
@originX = @origin[0] or 0
@originY = @origin[1] or 0
@originRadius = @origin[2] or 0
@control = opts.control or []
@controlX = @control[0] or 0
@controlY = @control[1] or 0
@controlRadius = @control[2] or 0
@destination = opts.destination or []
@destinationX = @destination[0] or 0
@destinationY = @destination[1] or 0
@destinationRadius = @destination[2] or 0
@trailStates = []
@keepStates = 15
@step = 3
@animationMiddle = opts.animationMiddle or 2
@shape = opts.shape or keanu.circle
@styles = opts.styles or {}
@zIndex = opts.zIndex or 0
@easeType = opts.easeType or "linear"
@callback = opts.callback
@useTrail = opts.useTrail or true
@duration = duration or 1000
@easing = @keanu.tweens[@easeType]
@originX = if @originX is @controlX then @originX + 1 else @originX
@originY = if @originY is @controlY then @originY + 1 else @originY
@ballX = @trailX = @shadowX = @originX
@ballY = @trailY = @shadowY = @originY
@ballRadius = @trailRadius = @shadowRadius = @originRadius
@xChange = @destinationX - @originX
@yChange = @destinationY - @originY
@rChange1 = @controlRadius - @originRadius
@rChange2 = @destinationRadius - @controlRadius
@intervalTime = @keanu.getIntervalTime()
@tickerStep = @duration / @intervalTime
@frame = 0
@tick = 0
@draw = =>
@drawShadow()
@drawTrail()
@drawShot()
@frame++
@tick += @intervalTime
if @tick >= @duration
console.log "callback"
keanu.unsubscribe "enterFrame", @draw, @zIndex
@trailStates = []
@callback and @callback()
true
@keanu.subscribe "enterFrame", @draw
this
drawTrail: ->
len = @trailStates.length
if len > 0
alpha = 0.1
rad = 0.6
i = len - 1
while i >= 0
@drawBall @keanu,
x: @trailStates[i][0] or 0
y: @trailStates[i][1] or 0
r: (@trailStates[i][2] * rad) or 0
styles:
fillStyle: "rgba(12, 131, 218, #{alpha})"
strokeStyle: "rgba(12, 131, 218, #{alpha})"
@keanu.setDimensions
x: @trailStates[i][0] - (@trailStates[i][2] * rad)
y: @trailStates[i][1] - (@trailStates[i][2] * rad)
w: @trailStates[i][0] + (@trailStates[i][2] * rad)
h: @trailStates[i][1] + (@trailStates[i][2] * rad)
alpha += 0.05
rad += 0.02 if rad < 0.98
i--
this
drawShadow: ->
@shadowX = @easing @frame, @originX, @xChange, @tickerStep
@shadowY = @easing @frame, @originY, @yChange, @tickerStep
@shadowRadius = 2
@drawBall @keanu,
x: @shadowX
y: @shadowY
r: @shadowRadius
styles:
fillStyle: "#000"
strokeStyle: "#000"
lineWidth: "0"
@keanu.setDimensions
x: @shadowX - @shadowRadius
y: @shadowY - @shadowRadius
w: @shadowX + @shadowRadius
h: @shadowY + @shadowRadius
this
handleTrailState: (state) ->
temp = []
loopLen = if @trailStates.length >= @keepStates then @keepStates else @trailStates.length
temp.push state
temp.push @trailStates[i] for i in [0...loopLen]
@trailStates = temp
this
drawShot: ->
@ballX = @easing @frame, @originX, @xChange, @tickerStep
@ballY = @easing @frame, @originY, @yChange, @tickerStep
if @frame < @tickerStep / @animationMiddle
@ballRadius = @easing @frame, @originRadius, @rChange1, @tickerStep
else
@ballRadius = @easing @frame, @controlRadius, @rChange2, @tickerStep
if @controlX isnt 0
@ballX = @keanu.tweens.quadraticBezierCurve (@originX - @ballX) / (@originX - @destinationX), @originX, @controlX, @destinationX
if @controlY isnt 0
@ballY = @keanu.tweens.quadraticBezierCurve (@originY - @ballY) / (@originY - @destinationY), @originY, @controlY, @destinationY
@handleTrailState [@ballX, @ballY, @ballRadius] if @frame % @step is 0
@drawBall @keanu,
x: @ballX
y: @ballY
r: @ballRadius
styles:
fillStyle: @styles.fillStyle or false
strokeStyle: @styles.strokeStyle or false
lineWidth: @styles.lineWidth or false
@keanu.setDimensions
x: @ballX - @ballRadius
y: @ballY - @ballRadius
w: @ballX + @ballRadius
h: @ballY + @ballRadius
this
drawBall: (keanu, data) ->
data = data or {}
data.styles = data.styles or {}
x = data.x or 0
y = data.y or 0
r = data.r or 0
isTrail = data.isTrail or false
f = data.styles.fillStyle or "#E08428"
w = data.styles.lineWidth or 0
s = data.styles.strokeStyle or "#D07317"
keanu.ctx.fillStyle = f
keanu.ctx.strokeStyle = s
keanu.ctx.lineWidth = w
keanu.ctx.beginPath()
keanu.ctx.arc x, y, r, 0, Math.PI * 2, true
keanu.ctx.closePath()
keanu.ctx.fill()
keanu.ctx.stroke()
this
version: "0.2-coffee"
window.Keanu.modules.Basketball = Basketball
(function() {
var Basketball;
Basketball = (function() {
function Basketball(keanu, opts, duration) {
var _this = this;
if (!keanu) return;
this.keanu = keanu;
this.ctx = this.keanu.ctx;
this.origin = opts.origin || [];
this.originX = this.origin[0] || 0;
this.originY = this.origin[1] || 0;
this.originRadius = this.origin[2] || 0;
this.control = opts.control || [];
this.controlX = this.control[0] || 0;
this.controlY = this.control[1] || 0;
this.controlRadius = this.control[2] || 0;
this.destination = opts.destination || [];
this.destinationX = this.destination[0] || 0;
this.destinationY = this.destination[1] || 0;
this.destinationRadius = this.destination[2] || 0;
this.trailStates = [];
this.keepStates = 15;
this.step = 3;
this.animationMiddle = opts.animationMiddle || 2;
this.shape = opts.shape || keanu.circle;
this.styles = opts.styles || {};
this.zIndex = opts.zIndex || 0;
this.easeType = opts.easeType || "linear";
this.callback = opts.callback;
this.useTrail = opts.useTrail || true;
this.duration = duration || 1000;
this.easing = this.keanu.tweens[this.easeType];
this.originX = this.originX === this.controlX ? this.originX + 1 : this.originX;
this.originY = this.originY === this.controlY ? this.originY + 1 : this.originY;
this.ballX = this.trailX = this.shadowX = this.originX;
this.ballY = this.trailY = this.shadowY = this.originY;
this.ballRadius = this.trailRadius = this.shadowRadius = this.originRadius;
this.xChange = this.destinationX - this.originX;
this.yChange = this.destinationY - this.originY;
this.rChange1 = this.controlRadius - this.originRadius;
this.rChange2 = this.destinationRadius - this.controlRadius;
this.intervalTime = this.keanu.getIntervalTime();
this.tickerStep = this.duration / this.intervalTime;
this.frame = 0;
this.tick = 0;
this.draw = function() {
_this.drawShadow();
_this.drawTrail();
_this.drawShot();
_this.frame++;
_this.tick += _this.intervalTime;
if (_this.tick >= _this.duration) {
console.log("callback");
keanu.unsubscribe("enterFrame", _this.draw, _this.zIndex);
_this.trailStates = [];
_this.callback && _this.callback();
return true;
}
};
this.keanu.subscribe("enterFrame", this.draw);
this;
}
Basketball.prototype.drawTrail = function() {
var alpha, i, len, rad;
len = this.trailStates.length;
if (len > 0) {
alpha = 0.1;
rad = 0.6;
i = len - 1;
while (i >= 0) {
this.drawBall(this.keanu, {
x: this.trailStates[i][0] || 0,
y: this.trailStates[i][1] || 0,
r: (this.trailStates[i][2] * rad) || 0,
styles: {
fillStyle: "rgba(12, 131, 218, " + alpha + ")",
strokeStyle: "rgba(12, 131, 218, " + alpha + ")"
}
});
this.keanu.setDimensions({
x: this.trailStates[i][0] - (this.trailStates[i][2] * rad),
y: this.trailStates[i][1] - (this.trailStates[i][2] * rad),
w: this.trailStates[i][0] + (this.trailStates[i][2] * rad),
h: this.trailStates[i][1] + (this.trailStates[i][2] * rad)
});
alpha += 0.05;
if (rad < 0.98) rad += 0.02;
i--;
}
}
return this;
};
Basketball.prototype.drawShadow = function() {
this.shadowX = this.easing(this.frame, this.originX, this.xChange, this.tickerStep);
this.shadowY = this.easing(this.frame, this.originY, this.yChange, this.tickerStep);
this.shadowRadius = 2;
this.drawBall(this.keanu, {
x: this.shadowX,
y: this.shadowY,
r: this.shadowRadius,
styles: {
fillStyle: "#000",
strokeStyle: "#000",
lineWidth: "0"
}
});
this.keanu.setDimensions({
x: this.shadowX - this.shadowRadius,
y: this.shadowY - this.shadowRadius,
w: this.shadowX + this.shadowRadius,
h: this.shadowY + this.shadowRadius
});
return this;
};
Basketball.prototype.handleTrailState = function(state) {
var i, loopLen, temp;
temp = [];
loopLen = this.trailStates.length >= this.keepStates ? this.keepStates : this.trailStates.length;
temp.push(state);
for (i = 0; 0 <= loopLen ? i < loopLen : i > loopLen; 0 <= loopLen ? i++ : i--) {
temp.push(this.trailStates[i]);
}
this.trailStates = temp;
return this;
};
Basketball.prototype.drawShot = function() {
this.ballX = this.easing(this.frame, this.originX, this.xChange, this.tickerStep);
this.ballY = this.easing(this.frame, this.originY, this.yChange, this.tickerStep);
if (this.frame < this.tickerStep / this.animationMiddle) {
this.ballRadius = this.easing(this.frame, this.originRadius, this.rChange1, this.tickerStep);
} else {
this.ballRadius = this.easing(this.frame, this.controlRadius, this.rChange2, this.tickerStep);
}
if (this.controlX !== 0) {
this.ballX = this.keanu.tweens.quadraticBezierCurve((this.originX - this.ballX) / (this.originX - this.destinationX), this.originX, this.controlX, this.destinationX);
}
if (this.controlY !== 0) {
this.ballY = this.keanu.tweens.quadraticBezierCurve((this.originY - this.ballY) / (this.originY - this.destinationY), this.originY, this.controlY, this.destinationY);
}
if (this.frame % this.step === 0) {
this.handleTrailState([this.ballX, this.ballY, this.ballRadius]);
}
this.drawBall(this.keanu, {
x: this.ballX,
y: this.ballY,
r: this.ballRadius,
styles: {
fillStyle: this.styles.fillStyle || false,
strokeStyle: this.styles.strokeStyle || false,
lineWidth: this.styles.lineWidth || false
}
});
this.keanu.setDimensions({
x: this.ballX - this.ballRadius,
y: this.ballY - this.ballRadius,
w: this.ballX + this.ballRadius,
h: this.ballY + this.ballRadius
});
return this;
};
Basketball.prototype.drawBall = function(keanu, data) {
var f, isTrail, r, s, w, x, y;
data = data || {};
data.styles = data.styles || {};
x = data.x || 0;
y = data.y || 0;
r = data.r || 0;
isTrail = data.isTrail || false;
f = data.styles.fillStyle || "#E08428";
w = data.styles.lineWidth || 0;
s = data.styles.strokeStyle || "#D07317";
keanu.ctx.fillStyle = f;
keanu.ctx.strokeStyle = s;
keanu.ctx.lineWidth = w;
keanu.ctx.beginPath();
keanu.ctx.arc(x, y, r, 0, Math.PI * 2, true);
keanu.ctx.closePath();
keanu.ctx.fill();
keanu.ctx.stroke();
return this;
};
Basketball.prototype.version = "0.2-coffee";
return Basketball;
})();
window.Keanu.modules.Basketball = Basketball;
}).call(this);
// So our basketball needs to take some options -- namely if it's using ball trails or not. Yes? Yes.
// We also need it to keep track of its own bounding box. That is, the minimum and maximum X/Y values
// We can probably reduce mouse trail load by making them one shape rather than multiple circles.
(function() {
// The only thing I smoke are fools like you on the b-ball court
var Basketball = function(keanu, opts, duration) {
if (!keanu) return; // Well forget YOU, then. No where to draw this bad boy!
var self = this; // let's keep a reference to ourself, shall we?
// Add in all of our options here
this.keanu = keanu;
this.ctx = this.keanu.ctx;
// origin -- [x, y, radius]
this.origin = opts.origin || [];
this.originX = this.origin[0] || 0;
this.originY = this.origin[1] || 0;
this.originRadius = this.origin[2] || 0;
// control points, middle of the curve from origin to destination
this.control = opts.control || [];
this.controlX = this.control[0] || 0;
this.controlY = this.control[1] || 0;
this.controlRadius = this.control[2] || 0;
// destination -- [x, y, radius]
this.destination = opts.destination || [];
this.destinationX = this.destination[0] || 0;
this.destinationY = this.destination[1] || 0;
this.destinationRadius = this.destination[2] || 0;
// Our ball trail will just be a collection of previous states
// We'll slap lower alpha value on them as they get further away
this.trailStates = [];
this.keepStates = 15; // How many states do we want to keep?
this.step = 3; // How often do we want to save the ball state?
this.animationMiddle = opts.animationMiddle || 2; // What are we dividing the length by for a "midpoint?"
this.shape = opts.shape || keanu.circle; // default to a circle -- it IS a ball after all
this.styles = opts.styles || {}; // Hash of properties, completely unnecessary though
this.zIndex = opts.zIndex || 0; // default to the lowest z-index
this.easeType = opts.easeType || "linear"; // default to none (linear)
this.callback = opts.callback; // When the animation completes, fire it up! Or nothing, whichever
this.useTrail = opts.useTrail || true; // This option is for low-end browsers
this.duration = duration || 1000; // time in milliseconds
// Need to set up some values for this animation, eh?
this.easing = this.keanu.tweens[this.easeType];
// The origin and control X/Y values cannot be identical or the curve will wonk out
this.originX = this.originX !== this.controlX ? this.originX : this.originX + 1;
this.originY = this.originY !== this.controlY ? this.originY : this.originY + 1;
// We need to initialize the ball, trail and shadow info.
this.ballX = this.trailX = this.shadowX = this.originX;
this.ballY = this.trailY = this.shadowY = this.originY;
this.ballRadius = this.trailRadius = this.shadowRadius = this.originRadius;
// Set up our animation change values - the distance between various points
this.xChange = this.destinationX - this.originX;
this.yChange = this.destinationY - this.originY;
this.rChange1 = this.controlRadius - this.originRadius;
this.rChange2 = this.destinationRadius - this.controlRadius;
// Set up the timing bits
this.intervalTime = this.keanu.getIntervalTime();
this.tickerStep = this.duration / this.intervalTime;
this.frame = 0;
this.tick = 0;
// this is the Keanu enterFrame callback -- it provides the draw state for our basketball at each frame
this.draw = function() {
self.drawShadow();
self.drawTrail();
self.drawShot();
self.frame++;
self.tick += self.intervalTime;
if (self.tick >= self.duration) {
keanu.unsubscribe("enterFrame", self.draw, self.zIndex);
this.trailStates = [];
self.callback && self.callback();
}
};
// Without this step, Keanu has no idea of our existence..
this.keanu.subscribe("enterFrame", this.draw);
};
// This kind of brings the ball to life, no?
Basketball.prototype.drawTrail = function() {
var len = this.trailStates.length,
i;
// If we have any previous states, draw them
if (len > 0) {
var alpha = 0.1,
rad = 0.6;
// Draw these in reverse order
for (i = len - 1; i >= 0; i--) {
// Draw this state on the canvas
this.drawBall(this.keanu, {
x: this.trailStates[i][0] || 0, // x
y: this.trailStates[i][1] || 0, // y
r: (this.trailStates[i][2] * rad) || 0, // radius
styles: {
fillStyle: "rgba(12, 131, 218, "+ alpha +")",
strokeStyle: "rgba(12, 131, 218, "+ alpha +")"
}
});
// Give Keanu our bounding box for a given ball
this.keanu.setDimensions({
x: this.trailStates[i][0] - (this.trailStates[i][2] * rad),
y: this.trailStates[i][1] - (this.trailStates[i][2] * rad),
w: this.trailStates[i][0] + (this.trailStates[i][2] * rad),
h: this.trailStates[i][1] + (this.trailStates[i][2] * rad)
});
// as we get closer to the ball, make it more opaque
alpha += 0.05;
if (rad < 0.98) rad += 0.02;
}
}
};
// Without this, the ball is nothing. NOTHING. I mean, this just gives it an added dimension. Ask wolfmother.
Basketball.prototype.drawShadow = function() {
this.shadowX = this.easing(this.frame, this.originX, this.xChange, this.tickerStep);
this.shadowY = this.easing(this.frame, this.originY, this.yChange, this.tickerStep);
this.shadowRadius = 2;
// Draw this state!
this.drawBall(this.keanu, {
x: this.shadowX,
y: this.shadowY,
r: this.shadowRadius,
styles: {
fillStyle: "#000",
strokeStyle: "#000",
lineWidth: "0"
}
});
// Let keanu know about the shadow's bounding box
this.keanu.setDimensions({
x: this.shadowX - this.shadowRadius,
y: this.shadowY - this.shadowRadius,
w: this.shadowX + this.shadowRadius,
h: this.shadowY + this.shadowRadius
});
};
// Handles the trail states for each frame of the animation
Basketball.prototype.handleTrailState = function(state) {
var temp = [],
loopLen = this.trailStates.length >= this.keepStates ? this.keepStates : this.trailStates.length; // use only as many iterations as possible
temp.push(state); // add the first state to the top
// add the remaining 9 (or less) states
for (var i = 0; i < loopLen; i++) {
temp.push(this.trailStates[i]);
}
// Set the trail states to the temp
this.trailStates = temp;
};
// I suppose if you're not drawing the ball itself, the animations lose their purpose. It's an existential crisis waiting to happen.
Basketball.prototype.drawShot = function() {
this.ballX = this.easing(this.frame, this.originX, this.xChange, this.tickerStep);
this.ballY = this.easing(this.frame, this.originY, this.yChange, this.tickerStep);
// If we're just starting out, we need to animate the radius larger
if (this.frame < this.tickerStep / this.animationMiddle) {
this.ballRadius = this.easing(this.frame, this.originRadius, this.rChange1, this.tickerStep);
// otherwise it needs to animate the radius smaller
} else {
this.ballRadius = this.easing(this.frame, this.controlRadius, this.rChange2, this.tickerStep);
}
// As long as this has control points, animate towards them
if (this.controlX != 0) {
this.ballX = this.keanu.tweens.quadraticBezierCurve((this.originX - this.ballX) / (this.originX - this.destinationX), this.originX, this.controlX, this.destinationX);
}
if (this.controlY != 0) {
this.ballY = this.keanu.tweens.quadraticBezierCurve((this.originY - this.ballY) / (this.originY - this.destinationY), this.originY, this.controlY, this.destinationY);
}
// Every third frame, keep the ball's state. This is preference. Keeping every state makes it look more like a line.
// Think of this like a "step" in a drawing program
if (this.frame % this.step == 0) this.handleTrailState([this.ballX, this.ballY, this.ballRadius]);
// Draw it on the canvas
this.drawBall(this.keanu, {
x: this.ballX,
y: this.ballY,
r: this.ballRadius,
styles: {
fillStyle: this.styles.fillStyle || false,
strokeStyle: this.styles.strokeStyle || false,
lineWidth: this.styles.lineWidth || false
}
});
// Let keanu know about our bounding box
this.keanu.setDimensions({
x: this.ballX - this.ballRadius,
y: this.ballY - this.ballRadius,
w: this.ballX + this.ballRadius,
h: this.ballY + this.ballRadius
});
};
// A basketball! Ok so it's just an orange circle (by default..)
Basketball.prototype.drawBall = function(keanu, data) {
data = data || {};
data.styles = data.styles || {};
var x = data.x || 0,
y = data.y || 0,
r = data.r || 0,
isTrail = data.isTrail || false,
f = data.styles.fillStyle || "#E08428",
w = data.styles.lineWidth || 0,
s = data.styles.strokeStyle || "#D07317";
keanu.ctx.fillStyle = f;
keanu.ctx.strokeStyle = s;
keanu.ctx.lineWidth = w;
keanu.ctx.beginPath();
keanu.ctx.arc(x, y, r, 0, Math.PI * 2, true);
keanu.ctx.closePath();
keanu.ctx.fill();
keanu.ctx.stroke();
};
// This means nothing, but I felt like giving it a version number.
Basketball.prototype.version = "0.0.0.0.0.0.1-theta-confirmed";
// Add the Basketball module to Keanu. Yeehaw.
Keanu.modules.Basketball = Basketball;
}());
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment