html5 交互作品,HTML5 Canvas 交互式奔跑动画

JavaScript

语言:

JaveScriptBabelCoffeeScript

确定

// ----- utils ----- //

// extends objects

function extend(a, b) {

for (var prop in b) {

a[prop] = b[prop];

}

return a;

}

function modulo(num, div) {

return ((num % div) + div) % div;

}

function normalizeAngle(angle) {

return modulo(angle, Math.PI * 2);

}

function getDegrees(angle) {

return angle * (180 / Math.PI);

}

// -------------------------- -------------------------- //

var TAU = Math.PI * 2;

var PI = Math.PI;

// -------------------------- -------------------------- //

function line(ctx, a, b) {

ctx.beginPath();

ctx.moveTo(a.x, a.y);

ctx.lineTo(b.x, b.y);

ctx.stroke();

ctx.closePath();

}

function dot(ctx, v) {

ctx.beginPath();

ctx.arc(v.x, v.y, 5, 0, Math.PI * 2);

ctx.fill();

ctx.closePath();

}

function fillCircle(ctx, v, radius) {

ctx.beginPath();

ctx.arc(v.x, v.y, radius, 0, TAU);

ctx.fill();

ctx.closePath();

}

// -------------------------- vector -------------------------- //

function Vector(x, y) {

this.x = x || 0;

this.y = y || 0;

}

Vector.prototype.set = function(v) {

this.x = v.x;

this.y = v.y;

};

Vector.prototype.setCoords = function(x, y) {

this.x = x;

this.y = y;

}

Vector.prototype.add = function(v) {

this.x += v.x;

this.y += v.y;

};

Vector.prototype.subtract = function(v) {

this.x -= v.x;

this.y -= v.y;

};

Vector.prototype.scale = function(s) {

this.x *= s;

this.y *= s;

};

Vector.prototype.multiply = function(v) {

this.x *= v.x;

this.y *= v.y;

};

// custom getter whaaaaaaat

Object.defineProperty(Vector.prototype, 'magnitude', {

get: function() {

return Math.sqrt(this.x * this.x + this.y * this.y);

}

});

Vector.prototype.equals = function(v) {

return this.x == v.x && this.y == v.y;

};

Vector.prototype.zero = function() {

this.x = 0;

this.y = 0;

};

Vector.prototype.block = function(size) {

this.x = Math.floor(this.x / size);

this.y = Math.floor(this.y / size);

};

Object.defineProperty(Vector.prototype, 'angle', {

get: function() {

return normalizeAngle(Math.atan2(this.y, this.x));

}

});

// ----- class functions ----- //

// return new vectors

Vector.subtract = function(a, b) {

return new Vector(a.x - b.x, a.y - b.y);

};

Vector.add = function(a, b) {

return new Vector(a.x + b.x, a.y + b.y);

};

Vector.copy = function(v) {

return new Vector(v.x, v.y);

};

Vector.isSame = function(a, b) {

return a.x == b.x && a.y == b.y;

};

Vector.getDistance = function(a, b) {

var dx = a.x - b.x;

var dy = a.y - b.y;

return Math.sqrt(dx * dx + dy * dy);

};

Vector.addDistance = function(vector, distance, angle) {

var x = vector.x + Math.cos(angle) * distance;

var y = vector.y + Math.sin(angle) * distance;

return new Vector(x, y);

};

// -------------------------- -------------------------- //

// -------------------------- Particle -------------------------- //

function Particle(x, y) {

this.position = new Vector(x, y);

this.previousPosition = new Vector(x, y);

}

Particle.prototype.update = function(friction, gravity) {

var velocity = Vector.subtract(this.position, this.previousPosition);

// friction

velocity.scale(friction);

this.previousPosition.set(this.position);

this.position.add(velocity);

this.position.add(gravity);

};

// -------------------------- -------------------------- //

Particle.prototype.render = function(ctx) {

// big circle

ctx.fillStyle = 'hsla(0, 0%, 10%, 0.5)';

circle(ctx, this.position.x, this.position.y, 4);

// dot

// ctx.fillStyle = 'hsla(0, 100%, 50%, 0.5)';

// circle( this.position.x, this.position.y, 5 );

};

function circle(ctx, x, y, radius) {

ctx.beginPath();

ctx.arc(x, y, radius, 0, Math.PI * 2);

ctx.fill();

ctx.closePath();

}

// -------------------------- -------------------------- //

function PinConstraint(particle, position) {

this.particle = particle;

this.position = position;

}

PinConstraint.prototype.update = function() {

this.particle.position.set(this.position);

};

PinConstraint.prototype.render = function() {};

// -------------------------- -------------------------- //

function StickConstraint(particleA, particleB, distance) {

this.particleA = particleA;

this.particleB = particleB;

if (distance) {

this.distance = distance;

} else {

var delta = Vector.subtract(particleA.position, particleB.position);

this.distance = delta.magnitude;

}

this.distanceSqrd = this.distance * this.distance;

}

StickConstraint.prototype.update = function() {

var delta = Vector.subtract(this.particleA.position, this.particleB.position);

var mag = delta.magnitude;

var scale = (this.distance - mag) / mag * 0.5;

delta.scale(scale);

this.particleA.position.add(delta);

this.particleB.position.subtract(delta);

};

StickConstraint.prototype.render = function(ctx) {

ctx.strokeStyle = 'hsla(200, 100%, 50%, 0.5)';

ctx.lineWidth = 2;

line(ctx, this.particleA.position, this.particleB.position);

};

// -------------------------- -------------------------- //

function SpringAngleConstraint(particleA, particleB, strength, angle) {

this.particleA = particleA;

this.particleB = particleB;

this.strength = strength;

if (angle === undefined) {

var delta = Vector.subtract(particleB.position, particleA.position);

this.angle = delta.angle;

} else {

this.angle = angle;

}

}

SpringAngleConstraint.prototype.update = function() {

var positionA = this.particleA.position;

var positionB = this.particleB.position;

var delta = Vector.subtract(positionB, positionA);

var deltaAngle = delta.angle;

var angleDiff = normalizeAngle(this.angle - deltaAngle);

angleDiff = angleDiff > Math.PI ? angleDiff - Math.PI * 2 : angleDiff;

var springAngle = deltaAngle + Math.PI / 2;

var springForce = new Vector(Math.cos(springAngle), Math.sin(springAngle));

springForce.scale(angleDiff * this.strength * Math.PI * 2);

this.particleB.position.add(springForce);

};

SpringAngleConstraint.prototype.render = function(ctx) {

var end = Vector.addDistance(this.particleA.position, 50, this.angle);

ctx.strokeStyle = 'hsla(0, 0%, 50%, 0.5)';

line(ctx, this.particleA.position, end);

};

function ChainLinkConstraint(particleA, particleB, distance, shiftEase) {

this.particleA = particleA;

this.particleB = particleB;

this.distance = distance;

this.distanceSqrd = distance * distance;

this.shiftEase = shiftEase === undefined ? 0.85 : shiftEase;

}

// -------------------------- -------------------------- //

ChainLinkConstraint.prototype.update = function() {

var delta = Vector.subtract(this.particleA.position, this.particleB.position);

var deltaMagSqrd = delta.x * delta.x + delta.y * delta.y;

if (deltaMagSqrd <= this.distanceSqrd) {

return;

}

var newPosition = Vector.addDistance(this.particleA.position, this.distance, delta.angle + Math.PI);

var shift = Vector.subtract(newPosition, this.particleB.position);

shift.scale(this.shiftEase);

this.particleB.previousPosition.add(shift);

this.particleB.position.set(newPosition);

};

ChainLinkConstraint.prototype.render = function(ctx) {

ctx.strokeStyle = 'hsla(200, 100%, 50%, 0.5)';

ctx.lineWidth = 2;

line(ctx, this.particleA.position, this.particleB.position);

};

// -------------------------- -------------------------- //

function Ribbon(props) {

extend(this, props);

// create particles

this.particles = [];

this.constraints = [];

this.controlParticle = new Particle(this.controlPoint.x, this.controlPoint.y);

var pin = new PinConstraint(this.controlParticle, this.controlPoint);

this.constraints.push(pin);

var x = this.controlPoint.x;

for (var i = 0; i < this.sections; i++) {

var y = this.controlPoint.y + this.sectionLength * i;

var particle = new Particle(x, y);

this.particles.push(particle);

// create links

var linkParticle = i === 0 ? this.controlParticle : this.particles[i - 1];

var link = new ChainLinkConstraint(linkParticle, particle, this.sectionLength, this.chainLinkShiftEase);

this.constraints.push(link);

}

}

Ribbon.prototype.update = function() {

var i, len;

for (i = 0, len = this.particles.length; i < len; i++) {

this.particles[i].update(this.friction, this.gravity);

}

for (i = 0, len = this.constraints.length; i < len; i++) {

this.constraints[i].update();

}

for (i = 0, len = this.constraints.length; i < len; i++) {

this.constraints[i].update();

}

};

Ribbon.prototype.addBreeze = function(v) {

for (var i = 0, len = this.particles.length; i < len; i++) {

this.particles[i].position.add(v);

}

};

Ribbon.prototype.render = function(ctx) {

ctx.strokeStyle = '#B19';

ctx.lineWidth = this.width;

ctx.lineCap = 'butt';

ctx.lineJoin = 'round';

ctx.beginPath();

ctx.moveTo(this.controlParticle.x, this.controlParticle.y);

for (var i = 0, len = this.particles.length; i < len; i++) {

var particle = this.particles[i];

ctx.lineTo(particle.position.x, particle.position.y);

}

ctx.stroke();

ctx.closePath();

ctx.lineWidth = 1;

};

// -------------------------- -------------------------- //

// x, y

// angle

// springStrength

// curl

// segmentLength

// friction

// gravity

// movementStrength

function Follicle(props) {

extend(this, props);

delete this.x;

delete this.y;

this.particleA = new Particle(props.x, props.y);

var positionB = Vector.addDistance(this.particleA.position, this.segmentLength, this.angle);

this.particleB = new Particle(positionB.x, positionB.y);

this.stick0 = new StickConstraint(this.particleA, this.particleB);

this.springAngle0 = new SpringAngleConstraint(this.particleA, this.particleB, this.springStrength, this.angle);

var angle1 = this.angle + this.curl;

var positionC = Vector.addDistance(this.particleB.position, this.segmentLength, angle1);

this.particleC = new Particle(positionC.x, positionC.y);

this.stick1 = new StickConstraint(this.particleB, this.particleC);

this.springAngle1 = new SpringAngleConstraint(this.particleB, this.particleC, this.springStrength, angle1);

this.controlPoint = new Vector(props.x, props.y);

this.pin = new PinConstraint(this.particleA, this.controlPoint);

}

Follicle.prototype.update = function() {

this.particleA.update(this.friction, this.gravity);

this.particleB.update(this.friction, this.gravity);

this.particleC.update(this.friction, this.gravity);

this.stick0.update();

this.springAngle0.update();

// update springAngle1's angle

var delta = Vector.subtract(this.particleB.position, this.particleA.position);

this.springAngle1.angle = delta.angle + this.curl;

this.pin.update();

this.stick1.update();

this.springAngle1.update();

};

Follicle.prototype.move = function(movement) {

movement = Vector.copy(movement);

this.controlPoint.add(movement);

movement.x *= this.movementStrength.x;

movement.y *= this.movementStrength.y;

// movement.scale( this.movementStrength );

this.particleB.position.add(movement);

this.particleC.position.add(movement);

this.particleB.previousPosition.add(movement);

this.particleC.previousPosition.add(movement);

};

Follicle.prototype.render = function(ctx) {

ctx.lineWidth = 46;

// ctx.strokeStyle = 'hsla(0, 100%, 50%, 0.4)';

ctx.strokeStyle = '#433';

ctx.lineCap = 'round';

ctx.beginPath();

ctx.moveTo(this.particleA.position.x, this.particleA.position.y);

ctx.quadraticCurveTo(this.particleB.position.x, this.particleB.position.y,

this.particleC.position.x, this.particleC.position.y);

ctx.stroke();

ctx.closePath();

// reset line props

ctx.lineCap = 'butt';

ctx.lineWidth = 1;

};

// -------------------------- -------------------------- //

// everything connects to coccyx

// coccyx

// shoulderLength

// leftShoulderOffset

// rightShoulderOffset

// leftHipOffset

// rightHipOffset

// armLength

// legLength

// footLength

// position

// hipRunAmplitude

// shoulderRunAmplitude

var PI = Math.PI;

var TAU = PI * 2;

function Skeleton(props) {

extend(this, props);

this.coccyx = new Vector();

this.neck = new Vector();

this.resetRunCycle();

this.idleCycleTheta = 0;

this.frame = 0;

this.state = {

// runningRight: 1,

// runningLeft: 1,

idleRight: 1,

// neutral: 1

};

this.animationFrames = {};

}

Skeleton.prototype.update = function() {

this.frame++;

for (var aniName in this.animationFrames) {

this.animationFrames[aniName] ++;

}

// this.runCycleTheta += this.state == 'running' ? 0.07 : 0;

// this.runCycleSpeed = Math.min( 0.15, this.runCycleSpeed + 0.0003 );

this.runCycleTheta += this.runCycleSpeed;

this.idleCycleTheta += 0.05;

this.coccyx.set(this.position);

// get and set correct rig

this.updateTransitionState();

var completeRig = {};

var rigs = {};

for (var rigName in this.state) {

var rig = extend({}, neutralRig);

var rigMethod = 'get' + capitalize(rigName) + 'Rig';

extend(rig, this[rigMethod]());

rigs[rigName] = rig;

}

for (var prop in neutralRig) {

var value = 0;

for (rigName in rigs) {

value += rigs[rigName][prop] * this.state[rigName];

}

completeRig[prop] = value;

}

this.setRig(completeRig);

};

Skeleton.prototype.getRig = function(rigName) {

var rigMethod = 'get' + capitalize(rigName) + 'Rig';

var rig = extend({}, neutralRig);

return extend(rig, this[rigMethod]());

};

function capitalize(str) {

return str.charAt(0).toUpperCase() + str.slice(1);

}

Skeleton.prototype.resetRunCycle = function() {

this.runCycleSpeed = 0.15;

this.runCycleTheta = TAU * 1 / 16;

};

// -------------------------- -------------------------- //

Skeleton.prototype.startAnimation = function(aniName) {

this.animationFrames[aniName] = 0;

};

// -------------------------- -------------------------- //

var zeroPoint = new Vector();

var neutralRig = {

offsetX: 0,

offsetY: 0,

offset: zeroPoint,

headRotateY: 0,

shouldersRotateY: 0,

shouldersY: 0,

hipsRotateY: 0,

hipsY: 0,

leftElbowAngle: 0,

leftWristAngle: 0,

leftKneeAngle: 0,

leftAnkleAngle: 0,

leftToeAngle: 0,

leftFootRotateY: 0,

rightElbowAngle: 0,

rightWristAngle: 0,

rightKneeAngle: 0,

rightAnkleAngle: 0,

rightToeAngle: 0,

rightFootRotateY: 0

};

Skeleton.prototype.getNeutralRig = function() {

return neutralRig;

};

// get positions and angles for rig, not vectors

Skeleton.prototype.getRunningRightRig = function() {

var rig = {};

var quadFactor = 1.5;

var sin = Math.sin(this.runCycleTheta);

var cos = Math.cos(this.runCycleTheta);

var quadSine = quadWave(sin, quadFactor);

var leftQuadSine = cos < 0 ? quadSine : sin;

var rightQuadSine = cos > 0 ? quadSine : sin;

var lift = Math.abs(Math.cos(this.runCycleTheta - 1)) * -40 + 20;

rig.offsetY = lift;

//

rig.headRotateY = 0.2;

// shoulder

rig.shouldersRotateY = (sin + 1) * 0.6;

// elbow

rig.leftElbowAngle = -rightQuadSine * 1.2 + 0.4;

rig.rightElbowAngle = leftQuadSine * 1.2 + 0.4;

// wrist

rig.leftWristAngle = -leftQuadSine * 0.4 - TAU / 4;

rig.rightWristAngle = rightQuadSine * 0.4 - TAU / 4;

// hip

rig.hipsRotateY = (-sin + 1) * 0.6;

// knee

rig.leftKneeAngle = leftQuadSine * 1.1 - 0.07;

rig.rightKneeAngle = -rightQuadSine * 1.1 - 0.07;

var ankleTheta = this.runCycleTheta - TAU / 8;

var normAnkleTheta = normalizeAngle(ankleTheta);

var ankleAngle = Math.max(0, Math.sin(normAnkleTheta * 2 / 3));

rig.leftAnkleAngle = ankleAngle * 1.7;

ankleTheta = this.runCycleTheta - TAU / 8 + TAU / 2;

normAnkleTheta = normalizeAngle(ankleTheta);

ankleAngle = Math.max(0, Math.sin(normAnkleTheta * 2 / 3));

rig.rightAnkleAngle = ankleAngle * 1.7;

rig.leftFootRotateY = TAU / 4;

rig.rightFootRotateY = TAU / 4;

return rig;

};

// get positions and angles for rig, not vectors

// /*

Skeleton.prototype.getRunningLeftRig = function() {

var rig = {};

var quadFactor = 1.5;

var sin = Math.sin(this.runCycleTheta + TAU / 2);

var cos = Math.cos(this.runCycleTheta + TAU / 2);

var quadSine = quadWave(sin, quadFactor);

var quadSineA = cos < 0 ? quadSine : sin;

var quadSineB = cos > 0 ? quadSine : sin;

var lift = Math.abs(Math.cos(this.runCycleTheta - 1)) * -40 + 20;

rig.offsetY = lift;

//

rig.headRotateY = -0.2;

// shoulder

rig.shouldersRotateY = (sin + 1) * -0.6;

// elbow

rig.leftElbowAngle = -quadSineA * 1.2 - 0.4;

rig.rightElbowAngle = quadSineB * 1.2 - 0.4;

// wrist

rig.leftWristAngle = -quadSineA * 0.4 + TAU / 4;

rig.rightWristAngle = quadSineB * 0.4 + TAU / 4;

// hip

rig.hipsRotateY = (-sin + 1) * -0.6;

// knee

rig.leftKneeAngle = quadSineB * 1.1 + 0.07;

rig.rightKneeAngle = -quadSineA * 1.1 + 0.07;

var ankleTheta = this.runCycleTheta - TAU / 8;

var normAnkleTheta = normalizeAngle(ankleTheta);

var ankleAngle = Math.max(0, Math.sin(normAnkleTheta * 2 / 3));

rig.leftAnkleAngle = ankleAngle * -1.7;

ankleTheta = this.runCycleTheta - TAU / 8 + TAU / 2;

normAnkleTheta = normalizeAngle(ankleTheta);

ankleAngle = Math.max(0, Math.sin(normAnkleTheta * 2 / 3));

rig.rightAnkleAngle = ankleAngle * -1.7;

rig.leftFootRotateY = -TAU / 4;

rig.rightFootRotateY = -TAU / 4;

return rig;

};

/**/

// i: sin or cos

// b: square factor

function quadWave(i, b) {

return Math.sqrt((1 + b * b) / (1 + b * b * i * i)) * i;

}

Skeleton.prototype.getIdleRightRig = function() {

var sin = quadWave(Math.sin(this.idleCycleTheta), 1);

var rig = {

headRotateY: 0.2,

shouldersRotateY: sin * -0.2 + 0.4,

hipsRotateY: 0.6,

leftFootRotateY: TAU / 4,

rightFootRotateY: TAU / 4

};

rig.shouldersY = sin * -5 - 5;

var elbowAngle = sin * 0.1 + 0.1;

rig.leftElbowAngle = elbowAngle;

rig.rightElbowAngle = -elbowAngle;

var wristAngle = sin * -0.1 - 0.1;

rig.leftWristAngle = wristAngle;

rig.rightWristAngle = -wristAngle;

return rig;

};

Skeleton.prototype.getIdleLeftRig = function() {

var sin = quadWave(Math.sin(this.idleCycleTheta), 1);

var rig = {

headRotateY: -0.2,

shouldersRotateY: sin * 0.2 - 0.4,

hipsRotateY: -0.6,

leftFootRotateY: -TAU / 4,

rightFootRotateY: -TAU / 4

};

rig.shouldersY = sin * -5 - 5;

var elbowAngle = sin * 0.1 + 0.1;

rig.leftElbowAngle = elbowAngle;

rig.rightElbowAngle = -elbowAngle;

var wristAngle = sin * -0.1 - 0.1;

rig.leftWristAngle = wristAngle;

rig.rightWristAngle = -wristAngle;

return rig;

};

Skeleton.prototype.setRig = function(rig) {

// save rig

this.rig = rig;

// coccyx

this.coccyx.add({

x: rig.offsetX,

y: rig.offsetY

});

this.setRigSide(rig, 'left', -1);

this.setRigSide(rig, 'right', 1);

};

Skeleton.prototype.setRigSide = function(rig, side, direction) {

// shoulder

this[side + 'Shoulder'] = Vector.copy(this.coccyx);

var shoulderCosine = Math.cos(rig.shouldersRotateY) * direction;

this[side + 'Shoulder'] = Vector.add(this.coccyx, {

x: shoulderCosine * this.shoulderLength,

y: -this.torsoLength + rig.shouldersY

});

// neck

this.neck.set(this.coccyx);

this.neck.y = this[side + 'Shoulder'].y;

// chest

this[side + 'Chest'] = Vector.add(this.neck, {

x: this.chestPosition.x * shoulderCosine,

y: this.chestPosition.y

});

this[side + 'Chest'].x += Math.sin(rig.shouldersRotateY) * this.chestPosition.z;

// elbow

var elbowAngle = rig[side + 'ElbowAngle'] + TAU / 4;

this[side + 'Elbow'] = Vector.addDistance(this[side + 'Shoulder'], this.armLength,

elbowAngle);

// wrist

var wristAngle = rig[side + 'WristAngle'] + elbowAngle;

this[side + 'Wrist'] = Vector.addDistance(this[side + 'Elbow'], this.armLength,

wristAngle);

// hip

var hipCosine = Math.cos(rig.hipsRotateY) * direction;

var hipX = this.hipLength * hipCosine;

this[side + 'Hip'] = Vector.add(this.coccyx, {

x: hipX,

y: 0

});

// butt

this[side + 'Butt'] = Vector.add(this.coccyx, {

x: this.buttPosition.x * hipCosine,

y: this.buttPosition.y

});

this[side + 'Butt'].x += Math.sin(rig.hipsRotateY) * this.buttPosition.z;

// knee

var kneeAngle = rig[side + 'KneeAngle'] + TAU / 4;

this[side + 'Knee'] = Vector.addDistance(this[side + 'Hip'], this.legLength,

kneeAngle);

// ankle

var ankleAngle = rig[side + 'AnkleAngle'] + kneeAngle;

this[side + 'Ankle'] = Vector.addDistance(this[side + 'Knee'], this.legLength,

ankleAngle);

// toe

var toeAngle = rig[side + 'ToeAngle'] + ankleAngle - TAU / 4;

var footLength = this.footLength * Math.sin(rig[side + 'FootRotateY']);

this[side + 'Toe'] = Vector.addDistance(this[side + 'Ankle'], footLength,

toeAngle);

};

// -------------------------- -------------------------- //

Skeleton.prototype.transition = function(rigName, frameCount) {

this.transitionFrame = this.frame;

this.transitionFrameCount = frameCount;

this.previousState = extend({}, this.state);

this.transitionEndRig = rigName;

this.isTransitioning = true;

// console.log('start transition', rigName );

};

Skeleton.prototype.updateTransitionState = function() {

if (!this.isTransitioning) {

return;

}

this.state = {};

var i = (this.frame - this.transitionFrame) / this.transitionFrameCount;

if (i >= 1) {

// end transition

this.state[this.transitionEndRig] = 1;

this.isTransitioning = false;

// console.log('end transition', this.transitionEndRig );

return;

}

for (var rigName in this.previousState) {

this.state[rigName] = this.previousState[rigName] * (1 - i);

}

this.state[this.transitionEndRig] = (this.state[this.transitionEndRig] || 0) + i;

// this.state[ this.transitionEndRig ] = i;

};

// -------------------------- render -------------------------- //

Skeleton.prototype.render = function(ctx) {

ctx.fillStyle = 'hsla(200, 100%, 35%, 0.8)';

ctx.strokeStyle = 'hsla(200, 100%, 50%, 0.8)';

ctx.lineWidth = 6;

ctx.lineCap = 'round';

// line( ctx, this.torso, this.head );

line(ctx, this.leftShoulder, this.leftElbow);

line(ctx, this.leftElbow, this.leftWrist);

line(ctx, this.leftHip, this.leftKnee);

line(ctx, this.leftKnee, this.leftAnkle);

line(ctx, this.leftAnkle, this.leftToe);

line(ctx, this.rightShoulder, this.rightElbow);

line(ctx, this.rightElbow, this.rightWrist);

line(ctx, this.rightHip, this.rightKnee);

line(ctx, this.rightKnee, this.rightAnkle);

line(ctx, this.rightAnkle, this.rightToe);

line(ctx, this.leftShoulder, this.rightShoulder);

line(ctx, this.leftShoulder, this.coccyx);

line(ctx, this.rightShoulder, this.coccyx);

line(ctx, this.leftHip, this.rightHip);

dot(ctx, this.leftShoulder);

dot(ctx, this.leftChest);

dot(ctx, this.leftElbow);

dot(ctx, this.leftWrist);

dot(ctx, this.leftHip);

dot(ctx, this.leftButt);

dot(ctx, this.leftKnee);

dot(ctx, this.leftAnkle);

dot(ctx, this.leftToe);

dot(ctx, this.rightShoulder);

dot(ctx, this.rightChest);

dot(ctx, this.rightElbow);

dot(ctx, this.rightWrist);

dot(ctx, this.rightHip);

dot(ctx, this.rightButt);

dot(ctx, this.rightKnee);

dot(ctx, this.rightAnkle);

dot(ctx, this.rightToe);

// dot( ctx, this.torso );

dot(ctx, this.coccyx);

dot(ctx, this.neck);

// dot( ctx, this.head );

};

// -------------------------- -------------------------- //

var Cole = {};

Cole.init = function() {

// ----- skeleton ----- //

this.skeleton = new Skeleton({

position: new Vector(2000, 300),

shoulderLength: 50,

torsoLength: 48,

hipLength: 35,

buttPosition: {

x: 20,

y: -10,

z: -10

}, // relative to coccyx, x is reversed for left

chestPosition: {

x: 25,

y: 5,

z: 8

}, // offset from neck, x is reversed for left

armLength: 32,

legLength: 30,

footLength: 25,

runningShoulderAmplitude: 15,

runningHipAmplitude: 10

});

// ----- hair ----- //

this.follicles = [];

var hairProps = {

friction: 0.85,

gravity: new Vector(0, 0.4),

springStrength: 0.3,

movementStrength: new Vector(0.95, 0.01)

};

// position are relative to skeleton.position

this.follicles.push(new Follicle(extend(hairProps, {

x: 2000 - 28,

y: -180,

segmentLength: 49,

angle: -1.75,

curl: 1.17

})));

this.follicles.push(new Follicle(extend(hairProps, {

x: 2000 + 0,

y: -190,

segmentLength: 58,

angle: -1.33,

curl: 1.15

})));

this.follicles.push(new Follicle(extend(hairProps, {

x: 2000 + 25,

y: -185,

segmentLength: 49,

angle: -1.05,

curl: 1.15

})));

this.follicles.push(new Follicle(extend(hairProps, {

x: 2000 + 38,

y: -177,

segmentLength: 47,

angle: -0.63,

curl: 1.15

})));

this.follicles.push(new Follicle(extend(hairProps, {

x: 2000 + 45,

y: -160,

segmentLength: 41,

angle: -0.29,

curl: 1.15

})));

this.follicles.push(new Follicle(extend(hairProps, {

x: 2000 + 40,

y: -138,

segmentLength: 35,

angle: 0.05,

curl: 1.15

})));

this.follicles.push(new Follicle(extend(hairProps, {

x: 2000 + 20,

y: -124,

segmentLength: 25,

angle: 0.45,

curl: 0.8

})));

// middle bottom

this.follicles.push(new Follicle(extend(hairProps, {

x: 2000 + 5,

y: -124,

segmentLength: 21,

angle: Math.PI / 2,

curl: 0

})));

// compare to 7

this.follicles.push(new Follicle(extend(hairProps, {

x: 2000 + -10,

y: -124,

segmentLength: 25,

angle: 2.7,

curl: -0.8

})));

// compare to 6

this.follicles.push(new Follicle(extend(hairProps, {

x: 2000 + -22,

y: -138,

segmentLength: 41,

angle: 3.20,

curl: -1.15

})));

// compare to 5

this.follicles.push(new Follicle(extend(hairProps, {

x: 2000 + -35,

y: -160,

segmentLength: 41,

angle: -2.8,

curl: -1.15

})));

// compare to 4

this.follicles.push(new Follicle(extend(hairProps, {

x: 2000 + -20,

y: -185,

segmentLength: 47,

angle: -2.5,

curl: -1.15

})));

// ----- ribbons ----- //

this.ribbonA = new Ribbon({

controlPoint: new Vector(2000, -110),

sections: 30,

width: 40,

sectionLength: 9,

friction: 0.95,

gravity: new Vector(0, 0.2),

chainLinkShiftEase: 0.9

});

this.ribbonB = new Ribbon({

controlPoint: new Vector(2000, -110),

sections: 30,

width: 40,

sectionLength: 9,

friction: 0.9,

gravity: new Vector(0, 0.25),

chainLinkShiftEase: 0.9

});

};

var headImg = new Image();

var isHeadImgLoaded;

headImg.onload = function() {

isHeadImgLoaded = true;

};

headImg.src = 'http://i.imgur.com/B92gbFZ.png';

Cole.update = function() {

this.previousNeck = Vector.copy(this.skeleton.neck || new Vector());

this.skeleton.update();

var neckMovement = this.previousNeck.y === 0 ? new Vector() :

Vector.subtract(this.skeleton.neck, this.previousNeck);

this.ribbonA.controlPoint.add(neckMovement);

this.ribbonB.controlPoint.add(neckMovement);

this.ribbonA.update();

this.ribbonB.update();

var i, len;

for (i = 0, len = this.follicles.length; i < len; i++) {

var follicle = this.follicles[i];

follicle.move(neckMovement);

follicle.update();

}

};

// var scale = 1;

var brownSkin = '#963';

var black = '#433';

var magenta = '#B19';

Cole.render = function(ctx) {

ctx.save();

ctx.translate(0, this.skeleton.position.y);

this.ribbonA.render(ctx);

this.ribbonB.render(ctx);

for (var i = 0, len = this.follicles.length; i < len; i++) {

this.follicles[i].render(ctx);

}

ctx.restore();

Cole.renderBody(ctx);

};

Cole.renderBody = function(ctx) {

var skeleton = this.skeleton;

// arm in back

if (Math.sin(this.skeleton.rig.shouldersRotateY) >= 0) {

Cole.renderRightIlloArm(ctx);

} else {

Cole.renderLeftIlloArm(ctx);

}

// legs

ctx.fillStyle = black;

ctx.fillRect(skeleton.leftButt.x, skeleton.leftButt.y,

skeleton.rightButt.x - skeleton.leftButt.x, 42);

if (Math.sin(skeleton.rig.hipsRotateY) >= 0) {

Cole.renderIlloLeg(ctx, 'right');

Cole.renderIlloLeg(ctx, 'left');

} else {

Cole.renderIlloLeg(ctx, 'left');

Cole.renderIlloLeg(ctx, 'right');

}

ctx.fillStyle = black;

// chest

fillCircle(ctx, skeleton.leftChest, 25);

fillCircle(ctx, skeleton.rightChest, 25);

// head

ctx.save();

ctx.translate(skeleton.neck.x - 135 / 2, skeleton.neck.y - 145);

Cole.renderHead(ctx);

ctx.restore();

// arm in front

if (Math.sin(skeleton.rig.shouldersRotateY) >= 0) {

Cole.renderLeftIlloArm(ctx);

} else {

Cole.renderRightIlloArm(ctx);

}

// inspect

// ctx.save();

// ctx.translate( 250, 0 );

// skeleton.render( ctx );

// ctx.restore();

};

Cole.renderLeftIlloArm = function(ctx) {

var skeleton = this.skeleton;

renderIlloArm(ctx, skeleton.leftShoulder, skeleton.leftElbow, skeleton.leftWrist);

};

Cole.renderRightIlloArm = function(ctx) {

var skeleton = this.skeleton;

renderIlloArm(ctx, skeleton.rightShoulder, skeleton.rightElbow, skeleton.rightWrist, true);

};

function renderIlloArm(ctx, shoulder, elbow, wrist, hasBand) {

ctx.strokeStyle = brownSkin;

ctx.lineWidth = 45;

ctx.lineCap = 'round';

ctx.lineJoin = 'round';

line(ctx, shoulder, elbow);

ctx.strokeStyle = hasBand ? magenta : brownSkin;

line(ctx, elbow, wrist);

ctx.fillStyle = brownSkin;

fillCircle(ctx, wrist, 28);

}

Cole.renderIlloLeg = function(ctx, side) {

var hip = this.skeleton[side + 'Hip'];

var butt = this.skeleton[side + 'Butt'];

var knee = this.skeleton[side + 'Knee'];

var ankle = this.skeleton[side + 'Ankle'];

var toe = this.skeleton[side + 'Toe'];

var footRotateY = this.skeleton.rig[side + 'FootRotateY'];

ctx.lineCap = 'round';

ctx.lineJoin = 'round';

ctx.lineWidth = 55;

// boot

ctx.strokeStyle = magenta;

line(ctx, knee, ankle);

line(ctx, ankle, toe);

// thigh

ctx.strokeStyle = black;

line(ctx, hip, knee);

// butt

ctx.fillStyle = black;

fillCircle(ctx, butt, 42);

// toe circle

ctx.fillStyle = magenta;

fillCircle(ctx, toe, Math.max(0, Math.cos(footRotateY)) * 55 / 2);

};

var faceCanvas = document.createElement('canvas');

var faceCtx = faceCanvas.getContext('2d');

faceCanvas.width = 400;

faceCanvas.height = 200;

faceCtx.fillStyle = magenta;

faceCtx.fillRect(0, 0, 400, 200);

faceCtx.fillStyle = brownSkin;

faceCtx.beginPath();

faceCtx.arc(200, 260, 160, TAU / 2, TAU);

faceCtx.fill();

faceCtx.closePath();

var headCanvas = document.createElement('canvas');

var headCtx = headCanvas.getContext('2d');

headCanvas.width = 135;

headCanvas.height = 135;

Cole.renderHead = function(ctx) {

headCtx.clearRect(0, 0, 135, 135);

headCtx.beginPath();

headCtx.arc(135 / 2, 135 / 2, 135 / 2, 0, TAU);

headCtx.fill();

headCtx.closePath();

headCtx.globalCompositeOperation = 'source-in';

var headX = -135 * 4 + (this.skeleton.rig.headRotateY / TAU) * 135 * 3;

headCtx.drawImage(headImg, headX, -40);

headCtx.globalCompositeOperation = 'source-over';

ctx.drawImage(headCanvas, 0, 0);

};

// -------------------------- -------------------------- //

function KeyboardController() {

// hashes of keys that are down or up

this.keysDown = {};

this.onceKeysDown = {};

this.keysUp = {};

// hashes of things to do when key goes down/up/repeat down

this.onceKeyDownActions = {};

this.repeatKeyDownActions = {};

this.keyUpActions = {};

// bind events

document.addEventListener('keydown', this, false);

document.addEventListener('keyup', this, false);

}

// -------------------------- events -------------------------- //

// enable .ontype to trigger from .addEventListener( elem, 'type' )

KeyboardController.prototype.handleEvent = function(event) {

var method = 'on' + event.type;

if (this[method]) {

this[method](event);

}

};

KeyboardController.prototype.onkeydown = function(event) {

var keyCode = event.keyCode;

// dismiss keyboard auto-repeats

if (!this.keysDown[keyCode]) {

// keep track of keys that are down

this.keysDown[keyCode] = true;

this.onceKeysDown[keyCode] = true;

}

if (this.onceKeyDownActions[keyCode] || this.repeatKeyDownActions[keyCode]) {

event.preventDefault();

}

};

KeyboardController.prototype.onkeyup = function(event) {

var keyCode = event.keyCode;

this.keysUp[keyCode] = true;

delete this.keysDown[keyCode];

delete this.onceKeysDown[keyCode];

if (this.keyUpActions[keyCode]) {

event.preventDefault();

}

};

// -------------------------- update -------------------------- //

KeyboardController.prototype.update = function() {

var keyCode;

for (keyCode in this.keysDown) {

this.triggerAction(keyCode, this.repeatKeyDownActions);

if (this.onceKeysDown[keyCode]) {

this.triggerAction(keyCode, this.onceKeyDownActions);

// reset flag, only trigger update once, keydown event might repeat

delete this.onceKeysDown[keyCode];

}

}

for (keyCode in this.keysUp) {

this.triggerAction(keyCode, this.keyUpActions);

}

// reset keyup hash

this.keysUp = {};

};

KeyboardController.prototype.triggerAction = function(keyCode, actions) {

var action = actions[keyCode];

if (action) {

action();

}

};

// -------------------------- -------------------------- //

var canvas = document.querySelector('canvas');

var ctx = canvas.getContext('2d');

var w = canvas.width;

var h = canvas.height;

var TAU = Math.PI * 2;

var logger1 = document.querySelector('.logger1 code');

// var logger2 = document.querySelector('.logger2 code');

// -------------------------- -------------------------- //

Cole.init();

Cole.skeleton.position.x = 2000;

var coleVelocityX = 0;

// -------------------------- -------------------------- //

var keyboard = new KeyboardController();

// left

keyboard.onceKeyDownActions[37] = function() {

Cole.skeleton.transition('runningLeft', 16);

};

// right

keyboard.onceKeyDownActions[39] = function() {

Cole.skeleton.transition('runningRight', 16);

};

// left

keyboard.repeatKeyDownActions[37] = function() {

coleVelocityX -= 4;

};

// right

keyboard.repeatKeyDownActions[39] = function() {

coleVelocityX += 4;

};

// left

keyboard.keyUpActions[37] = function() {

Cole.skeleton.transition('idleLeft', 16);

};

// right

keyboard.keyUpActions[39] = function() {

Cole.skeleton.transition('idleRight', 16);

};

// -------------------------- -------------------------- //

function update() {

coleVelocityX *= 0.85;

Cole.skeleton.position.x += coleVelocityX;

Cole.skeleton.position.x = Math.max(100, Math.min(3900, Cole.skeleton.position.x));

keyboard.update();

Cole.update();

}

function render() {

ctx.clearRect(0, 0, w, h);

ctx.save();

ctx.translate(0, 100);

ctx.scale(0.25, 0.25);

Cole.render(ctx);

ctx.restore();

}

var isAnimating = false;

function animate() {

ctx.clearRect(0, 0, w, h);

update();

render();

if (isAnimating) {

requestAnimationFrame(animate);

}

}

function stop() {

isAnimating = false;

}

function start() {

isAnimating = true;

animate();

}

// -------------------------- -------------------------- //

start();

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值