Skip to content

Instantly share code, notes, and snippets.

@Alikberov
Created July 20, 2023 10:11
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Alikberov/0424b72e5c149bce3f8d3f4094648d23 to your computer and use it in GitHub Desktop.
Save Alikberov/0424b72e5c149bce3f8d3f4094648d23 to your computer and use it in GitHub Desktop.
Fantaseur - Quick Comic Generator
<html>
<head>
<meta http-equiv='refresh' content='50'>
<title>Fantaseur</title>
<script>
class FANTASEUR extends HTMLCanvasElement {
constructor() {
super();
var i;
this.fantaSeur = "v0.23.07.20";
this.playing = false;
this.hour = 10;
this.month = 6;
this.bright = 100;
this.windy = -1;
this.actors = [];
this.replics = [];
this.clouds = [];
this.trees = [];
this.firstTime = new Date();
this.ambient = 1;
this.speed = 5;
this.animate();
}
addClouds(n) {
for(var i = 0; i < n; ++ i)
this.clouds.push(this.createCloud(Math.random() * 20, 1));
}
addActors(n) {
for(var i = 0; i < n; ++ i)
this.actors.push(this.createActor());
}
addTrees(n) {
for(var i = 0; i < n; ++ i) {
var tree = this.createCloud(15, 1);
tree.y = this.height * .25;
tree.curls += 5;
this.trees.push(tree);
}
}
animate() {
if(this.playing) {
this.drawSky();
var blobs = [];
for(var cloud of this.clouds)
this.drawCloud(cloud, blobs);
for(var tree of this.trees) {
this.drawTree(tree);
this.drawBranch(this.height/2, 1, 6, tree.x, this.height-.99, tree.mask);
}
this.replics = [];
for(var actor of this.actors)
this.stickMan(actor, this.replics);
for(var cloud of this.replics)
this.drawCloud(cloud, blobs);
this.drawGrass(1);
this.hour = ((new Date() - this.firstTime) / (this.speed * 80)) % 24;
}
window.requestAnimationFrame(this.animate.bind(this));
}
play(speed) {
this.playing = true;
if(speed && isFinite(speed))
this.speed = Number(speed);
}
stop() {
this.playing = false;
}
createCloud(rainy, lighting, text) {
return {
x :Math.random() * this.width,
y :Math.random() * this.height / 10,
z :Math.random() * 0+1,
sailing :1,
leaving :1 / 100,
obese :(Math.random() + 1) * this.width / 12,
curls :Math.floor(Math.random() * 5) + 7,
stormy :rainy,
lighting:lighting,
tender :5,
dimple :10,
bubble :20,
mask :Math.floor(Math.random() * 65536) + 0x5AE9,
text :text || ""//"\tСегодня утром идёт дождь\tи дети прыгают по лужам"
};
};
drawSky() {
const ctx = this.getContext("2d");
const hour = this.hour;
const month = this.month;
const bright = this.bright;
function gradient(from, last, seek) {
return {
r :from.r + (last.r - from.r) * seek,
g :from.g + (last.g - from.g) * seek,
b :from.b + (last.b - from.b) * seek
}
}
var seasons = [
{ sky:{ day:{ r:224, g:240, b:255 }, night:{ r:32, g:48, b:128 }, morning:{ r:176, g:192, b:192 }, evening:{ r:192, g:160, b:128 } } } // winter
, { sky:{ day:{ r:240, g:248, b:248 }, night:{ r:48, g:64, b:128 }, morning:{ r:184, g:184, b:192 }, evening:{ r:160, g:192, b:128 } } } // spring
, { sky:{ day:{ r:255, g:255, b:224 }, night:{ r:64, g:96, b:128 }, morning:{ r:192, g:176, b:160 }, evening:{ r:192, g:160, b:128 } } } // summer
, { sky:{ day:{ r:248, g:255, b:240 }, night:{ r:48, g:64, b:128 }, morning:{ r:184, g:184, b:192 }, evening:{ r:192, g:128, b:96 } } } // autumn
];
var sunrise = Math.sin((month + 2) * Math.PI / 12) * 2 + 4;
var sunset = Math.sin((month + 2) * Math.PI / 12) * 2 + 18;
var quarter = Math.floor((month + 11) % 12 / 3);
var quartnext = Math.floor(month % 12 / 3);
var zenith = (Math.sin((month - 1) * Math.PI / 12) + 1) * (this.height * 3 / 8); // Высота солнца в зените сезона
var sun_x = (Math.sin(hour * Math.PI / 12) + 1) * this.width / 2;
var sun_y = Math.cos(hour * Math.PI / 12) * zenith + this.height * 2 / 3;
var moon_x = (-Math.sin(hour * Math.PI / 12) + 1) * this.width / 2;
var moon_y = (-Math.cos(hour * Math.PI / 12) + 7 / 5) * this.width / 2;
var rgb, morning, day, evening, night, text, width;
var season = seasons[quarter];
var seasonext = seasons[quartnext];
morning = gradient(season.sky.morning, seasonext.sky.morning, (month + 11) % 3 / 3);
day = gradient(season.sky.day, seasonext.sky.day, (month + 11) % 3 / 3);
evening = gradient(season.sky.evening, seasonext.sky.evening, (month + 11) % 3 / 3);
night = gradient(season.sky.night, seasonext.sky.night, (month + 11) % 3 / 3);
if(hour > sunset || hour < sunrise)
rgb = night;
else {
rgb = day;
if(hour < sunrise + 2) // Два часа до утра
if(hour < sunrise + 1) // За час до рассвета
rgb = gradient(night, morning, (hour - sunrise));
else // Утро переходит в день
rgb = gradient(morning, rgb, (hour - sunrise - 1));
if(hour > sunset - 2) // Два часа до вечера
if(hour > sunset - 1) // Вечер переходит в ночь
rgb = gradient(evening, night, (hour - (sunset - 1)));
else // За час до заката
rgb = gradient(rgb, evening, (hour - (sunset - 2)));
}
//bright = 95;
rgb = gradient(night, rgb, bright / 100);
ctx.beginPath();
ctx.fillStyle = "rgba("+[Math.floor(rgb.r), Math.floor(rgb.g), Math.floor(rgb.b)].join()+",100)";
ctx.fillRect(0, 0, this.width, this.height);
this.ambient = Math.max(rgb.r, rgb.g, rgb.b) / 255;
ctx.closePath();
ctx.beginPath();
ctx.fillStyle = "white";
ctx.ellipse(moon_x, moon_y, ctx.canvas.height / 16, ctx.canvas.height / 16, 0, Math.PI * 2, 0, false);
ctx.closePath();
ctx.fill();
ctx.beginPath();
//ctx.strokeStyle = sun_y > 0 ? "rgba(64,64,"+Math.floor(92+bright)+",100)" : "rgba("+[r,g,b].join()+",100)";
ctx.fillStyle = "rgba(" + [Math.floor(255*bright/100),Math.floor(255*bright/100),0*192*bright/100].join() + ",1)";
ctx.ellipse(sun_x, sun_y, zenith / 6, zenith / 6, 0, Math.PI * 2, 0, false);
text = "" + (Math.floor(hour) % 24) + ":" + ("0" + (Math.floor(hour * 60) % 60)).substr(-2);
width = ctx.measureText(text);
ctx.textAlign = "center";
if(hour < 5 || hour > 19)
ctx.strokeText(text, moon_x + width.width * Math.sin(hour * Math.PI / 12), moon_y);
else
ctx.strokeText(text, sun_x - width.width * Math.sin(hour * Math.PI / 12), sun_y + zenith / 5);
ctx.closePath();
ctx.stroke();
ctx.fill();
return this;
}
drawCloud(cloud, blobs) {
var width, height, curls, faraway;
var font, rows, size, tail, text, author;
var tender, dimple, bubble;
var angle, turning, sprain;
var knot, knots = [];
var cx, cy, ascend;
var x1, y1;
var i, n;
var tmp;
var ctx = this.getContext("2d");
faraway = 1;
text = cloud.text.length > 0 ? cloud.text.split(/\t+/) : [];
tail = 0;
ctx.save();
if(text.length > 0) {
author = text.shift().replace(/((?:\s*)[A-ZА-ЯЁ])/g, " $1").trim();
text = text.join("\t");
while(text.match(/(\(.*\))/))
text = text.substr(1, text.length - 1),
tail ++;
text = text.split(/\t/);
font = ctx.font;
width = 0;
for(rows = 0; rows < text.length; ++ rows) {
size = ctx.measureText(text[rows]).width;
if(width < size)
width = size;
}
size = parseInt(font, 10);
height = rows * size;
angle = Math.PI / 4;
width /= Math.sin(angle) * 2;
height /= Math.cos(angle) * 2;
curls = Math.floor(Math.sqrt(width + height));
tender = 5,
dimple = 10,
bubble = 20;
/* if(cloud.x < width / 2)
cloud.x = width / 2,
cloud.sailing = Math.abs(cloud.sailing);
else
if(cloud.x > this.canvas.width - width / 2)
cloud.x = this.canvas.width - width / 2,
cloud.sailing = -Math.abs(cloud.sailing);*/
cx = cloud.x, cy = cloud.y;
if(cloud.x - width < 0)
cx = width,
cloud.sailing = Math.abs(cloud.sailing);
else
if(cloud.x + width > this.width)
cx = this.width - width,
cloud.sailing = -Math.abs(cloud.sailing);
var b, bw = width + bubble, bh = height + bubble;
var dx, dy, dr, dr1, dr2;
if(blobs.length > 0)
for(tmp in blobs) {
b = blobs[tmp];
dx = cx - b.x, dy = cy - b.y;
dr = Math.sqrt(dx * dx + dy * dy);
dr1 = Math.sqrt(b.w * b.w + b.h * b.h);
dr2 = Math.sqrt(bw * bw + bh * bh);
if(dr < (dr1 + dr2))
cy += ((dr1 + dr2) - dr) * 4 / 9 * (Math.abs(dy) / dy);
}
blobs.push({x: cx, y: cy, w: width, h: height});
} else
faraway += Math.log(1 + Math.abs(cloud.z)),
width = cloud.obese / faraway,
height = this.height / 48 / faraway,
curls = cloud.curls,
tender = cloud.tender / faraway,
dimple = cloud.dimple / faraway,
bubble = cloud.bubble / faraway,
cx = cloud.x, cy = cloud.y;
turning = 2 * Math.PI / curls;
sprain = turning / 3;
angle = -turning;
for(i = 0; i < curls; ++ i) {
angle += turning + (Math.random() * 2 - 1) * sprain / curls;
tmp = (angle * 180 / Math.PI + 360 + 60) % 360 > 90 ? 1 : 2 / 3;
knots.push({
x1: Math.sin(angle + sprain) * (width + bubble * tmp + Math.random()),
y1: Math.cos(angle + sprain) * (height + bubble * tmp + Math.random()),
x2: Math.sin(angle + sprain*2) * (width + bubble * tmp + Math.random()),
y2: Math.cos(angle + sprain*2) * (height + bubble * tmp + Math.random()),
x3: Math.sin(angle + sprain*3) * (width + dimple + Math.random()),
y3: Math.cos(angle + sprain*3) * (height + dimple + Math.random())
});
}
ctx.beginPath();
if(text.length > 0 && author != "") {
ctx.fillStyle = "rgba("+[Math.floor(255/(cloud.stormy/5+1)),Math.floor(255/(cloud.stormy/4+1)),255].join(",")+",1)";
ctx.strokeStyle = "blue";
ctx.lineWidth = 3 / faraway;
y1 = cy + knots[knots.length - 1].y2 + (cloud.z - cy) / 2;
x1 = knots[0].x1;
if(tail > 0) {
ascend = (cloud.z - cy + height) / tail,
cy += ascend * (tail - 1);
ctx.moveTo(cx + knots[knots.length - 1].x3 / tail, cy + knots[knots.length - 1].y3 / tail);
} else {
ctx.moveTo(cloud.x, cloud.z);
ascend = 0;
}
n = 1 + tail;
} else {
ctx.fillStyle = "rgba("+[Math.floor(255/(cloud.stormy/5+1)),Math.floor(255/(cloud.stormy/4+1)),255].join(",")+",1)";
ctx.strokeStyle = "blue";
ctx.lineWidth = 3 / faraway;
ctx.moveTo(cx + knots[i - 1].x3, cy + knots[i - 1].y3);
n = 1; ascend = 0;
}
while(n > 0) {
for(i = 0; i < knots.length; ++ i) {
knot = knots[i];
ctx.bezierCurveTo(cx + knot.x1 / n, cy + knot.y1 / n, cx + knot.x2 / n, cy + knot.y2 / n, cx + knot.x3 / n, cy + knot.y3 / n);
}
ctx.closePath();
if(n > 1)
ctx.fillStyle = "rgba(255,255,255," + (2 / n) + ")";
ctx.fill();
if(-- n > 0) {
cy -= ascend;
ctx.beginPath();
ctx.moveTo(cx + knot.x3 / n, cy + knot.y3 / n);
}
}
ctx.stroke();
if(cloud.stormy > 10) { // Rain
ctx.strokeStyle = "rgba(64,160,255," + Math.abs(cloud.stormy/100) + ")";
ctx.lineWidth = 2 / faraway;
for(i = -5; i < 5; ++ i) {
ctx.setLineDash([Math.floor(Math.random() * 10), Math.floor(Math.random() * 10)
// , Math.floor(Math.random() * 10), Math.floor(Math.random() * 10)
// , Math.floor(Math.random() * 10), Math.floor(Math.random() * 10)
, Math.floor(Math.random() * 10), Math.floor(Math.random() * 10)]);
x1 = Math.random() + cx + width * i / 5;
ctx.beginPath();
ctx.moveTo(x1, cy + height + Math.random() * height),
ctx.lineTo(x1 - 5 * cloud.sailing, this.height);
ctx.stroke();
}
} else
if(cloud.stormy < 0) { // Snow
ctx.strokeStyle = "rgba(255,255,255," + Math.abs(cloud.stormy/100) + ")";
ctx.lineWidth = 20 / faraway;
for(i = -5; i < 5; ++ i) {
ctx.setLineDash([Math.floor(Math.random() * 10), 5 + Math.floor(Math.random() * 10)
// , Math.floor(Math.random() * 10), 5 + Math.floor(Math.random() * 10)
// , Math.floor(Math.random() * 10), 5 + Math.floor(Math.random() * 10)
, Math.floor(Math.random() * 10), 5 + Math.floor(Math.random() * 10)]);
x1 = Math.random() + cx + width * i / 5;
ctx.beginPath();
ctx.moveTo(x1, cy + height + Math.random() * height),
ctx.lineTo(x1 - 5 * cloud.sailing, this.height);
ctx.stroke();
}
}
if(text.length > 0) {
if(author)
text.unshift(author),
ctx.font = "bold " + font;
else
ctx.font = "italic " + font;
cy -= (text.length - 1) * size / 2;
for(i = 0; i < text.length; ++ i) {
ctx.fillStyle = !i && author ? "magenta" : "black";
ctx.beginPath();
ctx.fillText(text[i], cx - ctx.measureText(text[i]).width / 2, cy);
cy += size;
if(author)
ctx.font = font;
ctx.stroke();
}
ctx.font = font;
cy = cloud.z > 0 ? size * text.length * 10 : -size * text.length * 5;
if(!author) {
cloud.x += cloud.sailing;
cloud.y += (cy - cloud.y) / (cy > 0 ? 25 : 29);
if(cloud.z > 0 && (cy - cloud.y) < 10)
cloud.z = -cloud.z;
if(cloud.z < 0)
cloud.z ++;
}
} else
cloud.x += cloud.sailing / faraway,
cloud.z += cloud.leaving;
ctx.restore();
}
drawGrass(rows) {
var x;
var ctx = this.getContext("2d");
ctx.save();
ctx.beginPath();
while(rows -- > 0) {
ctx.moveTo(0, this.height - rows * 20);
for(x = 0; x <= this.width; x += 19) {
var x1 = x + 12 - 8 * Math.random();
var x2 = x + 20 - 8 * Math.random();
var x3 = x + 18 - 8 * Math.random();
ctx.bezierCurveTo(x, this.height - rows * 20, x1, this.height - rows * 20, x2, this.height - 24 - rows * 20, x3, this.height - 24 - rows * 20);
ctx.bezierCurveTo(x3, this.height - 24 - rows * 20, x2, this.height - 16 - rows * 20, x1, this.height - rows * 20, x + 9, this.height - rows * 20);
}
ctx.fillStyle = "#8F8";
ctx.strokeStyle = "#4C4";
ctx.fill();
ctx.stroke();
}
ctx.restore();
}
drawBranch(radius, fan, reps, x, y, mask) {
var turn = Math.PI / 3 / fan;
var ctx = this.getContext("2d");
if(reps > 0 && isFinite(x)) {
this.drawBranch.path1 = mask || 65535 * Math.random() *1+0x5AE9;
this.drawBranch.path2 = ~this.drawBranch.path1 | 1;
ctx.save();
ctx.translate(x, y);
ctx.beginPath();
ctx.rotate(Math.PI);
ctx.moveTo(radius*2/3/16, 0);
this.drawBranch(radius*2/3, fan, -reps + 1-2);
ctx.lineTo(-radius*2/3/8, 0);
ctx.stroke();
ctx.fillStyle = "rgba(" + Math.floor(this.ambient * 160) + ",0,0)";
ctx.fill();
ctx.restore();
} else {
var flag;
ctx.save();
if(++ reps < 0) {
ctx.translate(0, radius * (0.69 + Math.random() * 0.006));
flag = this.drawBranch.path1 & 1;
this.drawBranch.path1 >>= 1;
if(flag) {
turn = -Math.PI / 180 * (50 + 2 * Math.random() / reps);
ctx.rotate(turn);
ctx.lineTo(radius / 8, 0);
this.drawBranch(radius / 2, fan, reps);
ctx.rotate(-turn);
}
ctx.translate(0, radius * (0.25 + Math.random() * 0.006));
ctx.rotate(Math.PI / 180 * (50 + 2 * Math.random() / reps));
flag = this.drawBranch.path2 & 1;
this.drawBranch.path2 >>= 1;
if(flag) {
if(reps < -2)
ctx.lineTo(radius / 16, 0);
this.drawBranch(radius / 2, fan, reps);
}
}
ctx.restore();
//if(reps < -1)
ctx.lineTo(-radius / 8, radius / 4);
}
}
drawTree(props) {
var x = props.x + 0 * this.width * (0.5 + Math.cos(Math.PI * props.x / 180));
var width, height, curls, faraway;
var font, rows, size, tail, text, author;
var tender, dimple, bubble;
var angle, turning, sprain;
var knot, knots = [];
var cx, cy, ascend;
var x1, y1;
var i, n;
var tmp;
var ctx = this.getContext("2d");
ctx.save();
faraway = 1;
n = 1;
tail = 0;
//faraway += Math.log(1 + Math.abs(props.z)),
props.obese = 112;
width = props.obese / faraway,
height = props.obese / faraway * 8 / 13,
curls = props.curls,
tender = props.tender / faraway,
dimple = props.dimple / faraway,
bubble = props.bubble / faraway*0+32,
cx = props.x+0*this.width / 2, cy = props.y + 160;
turning = 2 * Math.PI / curls;
sprain = turning / 3;
angle = -turning;
for(i = 0; i < curls; ++ i) {
angle += turning + (Math.random() * 2 - 1) * sprain / curls;
knots.push({
x1: Math.sin(angle + sprain) * (width + bubble + Math.random()),
y1: Math.cos(angle + sprain) * (height + bubble + Math.random()),
x2: Math.sin(angle + sprain*2) * (width + bubble + Math.random()),
y2: Math.cos(angle + sprain*2) * (height + bubble + Math.random()),
x3: Math.sin(angle + sprain*3) * (width + dimple + Math.random()),
y3: Math.cos(angle + sprain*3) * (height + dimple + Math.random())
});
}
ctx.lineWidth = 5;
ctx.beginPath();
while(n > 0) {
ctx.moveTo(cx + knots[0].x1, cy + knots[0].y1);
for(i = 0; i < knots.length; ++ i) {
knot = knots[i];
ctx.bezierCurveTo(cx + knot.x1 / n, cy + knot.y1 / n, cx + knot.x2 / n, cy + knot.y2 / n, cx + knot.x3 / n, cy + knot.y3 / n);
}
ctx.closePath();
if(n > 0)
ctx.fillStyle = "rgba(0," + Math.floor(this.ambient * 160) + ",0," + (2 / n) + ")",
ctx.strokeStyle = "rgba(0,64,0)";
ctx.fill();
if(-- n > 0) {
cy -= ascend;
ctx.beginPath();
ctx.moveTo(cx + knot.x3 / n, cy + knot.y3 / n);
}
}
ctx.stroke();
ctx.restore();
}
drawFace(x, y, width, height, rotation, emoji, eyes) {
var ctx = this.getContext("2d");
var tmp;
ctx.beginPath();
ctx.save(); ctx.translate(x, y); ctx.rotate(rotation); ctx.scale(width, height);
ctx.arc(0, 0, 1, 0, Math.PI * 2, false); ctx.restore();
ctx.stroke(); ctx.fill();
if(emoji) {
tmp = Math.random() * 100 > 95 ? 0.125 : 1;
ctx.save(); ctx.translate(x, y); ctx.rotate(rotation);
ctx.lineWidth = 6 / 5;
ctx.strokeStyle = "white", ctx.fillStyle = eyes;
ctx.beginPath(); // Draw the left eye
ctx.ellipse(-width * 3 / 7, -height / 3, emoji.lRadius * 3 / 2, emoji.lHeight * tmp * 3 / 2, 0, emoji.lStart * Math.PI / 180, emoji.lFinal * Math.PI / 180, true);
ctx.closePath(); ctx.stroke(); ctx.fill(); ctx.beginPath(); // Draw the right eye
ctx.ellipse(+width * 3 / 7, -height / 3, emoji.rRadius * 3 / 2, emoji.rHeight * tmp * 3 / 2, 0, emoji.rStart * Math.PI / 180, emoji.rFinal * Math.PI / 180, true);
ctx.closePath(); ctx.stroke(); ctx.fill(); ctx.beginPath(); // Draw the mouth
ctx.ellipse(0, height * 5 / 9, emoji.mRadius * 1, emoji.mHeight, 0, emoji.mStart * Math.PI / 180, emoji.mFinal * Math.PI / 180, true);
ctx.closePath();
ctx.strokeStyle = "red", ctx.fillStyle = "red";
ctx.stroke();
ctx.restore();
}
}
drawKinematicLine(x1, y1, x3, y3, limit, vx, vy) {
var ctx = this.getContext("2d");
var dx = x3 - x1, dy = y3 - y1;
var len = Math.max(Math.abs(dx), Math.abs(dy)), rad;
if(len >= 2 * limit) {
ctx.lineTo(x1 + 2 * dx / len * limit, y1 + 2 * dy / len * limit);
return {x:x1 + 2 * dx / len * limit, y:y1 + 2 * dy / len * limit};
}
var x2 = x1 + dx / 2, y2 = y1 + dy / 2;
rad = Math.sqrt(dx * dx + dy * dy) / 2;
rad = Math.sqrt(limit * limit - rad * rad);
x2 += vx * dy / len * rad, y2 += vy * dx / len * rad;
ctx.lineTo(x2, y2); ctx.lineTo(x3, y3);
return {x:x3, y:y3};
}
createActor() {
return {
position:{
x :Math.random() * this.width - this.width / 2, // Позиция относительно центра
y :0,
z :Math.random(), // Расстояние от центра
j :125.009+Math.random()*5 // Jumping amplitude
},
members:{
lHandX :-10, // Left Hand X
lHandY :10, // Left Hand Y
rHandX :10,
rHandY :20,
lFootX :0,
lFootY :0,
rFootX :0,
rFootY :0,
},
colours:{
head :"grey",
face :"yellow",
body :"magenta",
eyes :"darkcyan"
},
path:{
points :[],
seek :10
},
chatArea :null,
playing :null,
// is speaking?
"говорит?":function() { return this.text.join("").length > 2; },
// is quiet?
"молчит?":function() { return this.text.join("").length <= 2; },
// is jumping?
"прыгает?":function() { return this.j > 0; },
// is jumped?
"прыгнул?":function() { return this.j > 0 && this.phase < (this.step + (this.step > 180 ? -180 : this.step > -180 ? +180 : +540)) % 360; },
// is steping?
"шагает?":function() { return this.step < -180 || this.step > 180; },
// is stepped?
"шагнул?":function() { return (this.step < -180 || this.step > 180) && this.phase < (this.step + (this.step > 180 ? -180 : this.step > -180 ? +180 : +540)) % 360; },
// let stay
"стоит":function() { return (this.step = 0, this.position.j = 0) && false; },
// let jumping
"прыгает":function() { (this.position.j = 125.0009); return false; },
// let walking
"шагает":function() { return (this.step = 30) && false; },
// let running
"бежит":function() { return (this.step = 60) && false; },
// to back
"обратно":function() { return (this.step = -this.step) && false; },
// let moving
"движется":function() { return (this.step += 180) && false; },
// quickly
"быстрее":function() { return (this.step += this.step > 0 ? 10 : -10) && false; },
// catching
"ловит":function(target) { if(!target) return;
this.members.lHandX = target.position.x;
this.members.lHandY = target.position.y;
this.members.rHandX = target.position.x;
this.members.rHandY = target.position.y;
return false;
},
// overtaking
"догоняет":function(target) { if(!target) return;
var dist = target.position.x - this.position.x;
if(Math.abs(dist) > 64)
this.step = dist < 0 ? -200 : +200;
else
this.step = 0;
},
// escaping
"избегает":function(target) { if(!target) return;
var dist = target.position.x - this.position.x;
if(Math.abs(dist) < 500)
this.step = dist < 0 ? +200 : -200;
else
this.step = 0;
},
// waiting
"ждет":function(target) { return (target.text.length == 0) || (target.text[0] == ""); },
// listing
"слушает":function(target) { return (target.text.length > 1); },
novel :[], // Текущая роль / Current actions
text :[[":-D",":)",":(",":o"][Math.floor(Math.random() * 4)]],//,"StickMan#"+Math.floor(Math.random()*100),(Math.random()*16777216).toString(36)], // Реплика / Remark
age :16, // Возраст персонажа
fat :100.0, // Упитанность
weight :100.0, // Вес
suit :"red", // Цвет костюма
// step :+20,// Right
// step :-20,// Left
// step :+200,// Forward
// step :-200,// Backward
step :1, // Текущая фаза шага: >540 - вперёд, <-540 - назад
phase :0
};
};
//////////////////////////////////////////////////////////////////////////////
//
parse_emotion(emotion, size) {
var face_parts = "lStart lFinal lRadius lHeight rStart rFinal rRadius rHeight mStart mFinal mRadius mHeight".split(/[\s\t]+/);
var emotions = {
":)": "0 360 1/7 1/9 0 360 1/7 1/9 180 360 1/5 1/9"
, ":-D": "0 180 1/7 1/9 0 180 1/7 1/9 180 360 1/5 1/6"
, ":(": "0 360 1/7 1/9 0 360 1/7 1/9 0 180 1/5 1/9"
, ":o": "0 360 1/6 1/8 0 360 1/6 1/8 0 360 1/5 1/6"
};
var members, id, data;
var result = {};
if(emotion in emotions) {
members = emotions[emotion].split(/[\s\t]+/);
for(id in face_parts)
data = members.shift().split(/\//),
result[face_parts[id]] = data.length > 1 ? size * +data[0] / +data[1] : +data[0];
return result;
}
return null;
};
//////////////////////////////////////////////////////////////////////////////
//
stickMan(man, clouds) {
var ctx = this.getContext("2d");
var far = Math.log((man.position.z + 1) / 2*2); // Дальность
var fat = man.fat / 100; // Упитанность
var stature = this.height / (man.age < 16 ? 8 - man.age / 3 : 3) / ((far / 4) + 1); // Рост
var center = this.width / 2 + man.position.x; // Центр оси
var jumphase = man.position.j * Math.PI * 2; // Фаза прыжка
var jump = man.position.j > 0 ?
(Math.abs(Math.sin(jumphase) * 2) - 1) * man.position.j * stature / 600
: man.position.j * stature / 600;
var jumping = stature + jump;
var foot = this.height - man.position.y - far * jumping / 1.875; // Позиция ступ / Foots position
var width = fat * stature / 5; // Ширина головы / Head width
var head_x = center, head_y = foot - jumping * man.fat / man.weight + width / 2; // Позиция головы / Head position
var head_width = width / 2, head_height = width / 2; // Ширина/высота головы / Head width and height
var left_hand = center + fat * width / (man.step >= 180 ? 3 : 3 / 2); // Позиция левой руки / Left hand position
var right_hand = center - fat * width / (man.step < -180 ? 3 : 3 / 2); // Позиция правой руки / Right hand position
var hand_y = (foot - jumping * man.fat / man.weight) + width + jumping / 12; // Позиция плеч / Shoulders position
var body_y = head_y + head_height + jumping / 3; // Позиция тела / Body position
var emotion, offset, tmp, flag;
var a = man.step < -180 ? Math.PI / 9 : man.step > 180 ? -Math.PI / 9 : 0, b, c, dx, dy;
var text = [].concat(man.text);
var sine, cose;
var left, rift;
ctx.save();
for(tmp = 0; tmp < text.length; ++ tmp)
if(emotion = this.parse_emotion(text[tmp], width))
text.splice(tmp, 1);
else
continue;
// Рисуем лицо / Draw the face
ctx.save(); ctx.translate(head_x, head_y); ctx.scale(head_width, head_height);
ctx.beginPath(); ctx.arc(0, 0, 1, 0, Math.PI * 2, false); ctx.closePath(); ctx.restore();
ctx.strokeStyle = man.colours.head; ctx.fillStyle = man.colours.face;
ctx.stroke(); ctx.fill();
this.drawFace(head_x, head_y, head_width, head_height, a, emotion, man.colours.eyes);
// Рисуем левый контур тела / Draw the left side of body
ctx.beginPath(); ctx.lineWidth = (man.step >= 180 ? 1 : 2);
ctx.moveTo(head_x, head_y + head_height); ctx.lineTo(left_hand, hand_y); ctx.lineTo(head_x, body_y);
ctx.stroke();
// Рисуем правый контур тела / Draw the right side of body
ctx.beginPath(); ctx.lineWidth = (man.step < -180 ? 1 : 2);
ctx.moveTo(head_x, head_y + head_height); ctx.lineTo(right_hand, hand_y); ctx.lineTo(head_x, body_y);
ctx.stroke();
// Закрашиваем тело / Painting the body
ctx.beginPath(); ctx.fillStyle = man.colours.body;
ctx.moveTo(head_x, head_y + head_height); ctx.lineTo(left_hand, hand_y);
ctx.lineTo(head_x, body_y); ctx.lineTo(right_hand, hand_y); ctx.closePath(); ctx.fill();
// Рисуем левую руку / Draw the left hand
ctx.beginPath(); ctx.moveTo(left_hand, hand_y);
this.drawKinematicLine(left_hand, hand_y, man.members.lHandX, man.members.lHandY, stature / 8, -1, +1);
ctx.lineWidth = (man.step >= 180 ? 1 : 3); ctx.closePath(); ctx.stroke();
// Рисуем правую руку / Draw the right hand
ctx.beginPath(); ctx.moveTo(right_hand, hand_y);
this.drawKinematicLine(right_hand, hand_y, man.members.rHandX, man.members.rHandY, stature / 8, +1, -1);
ctx.lineWidth = (man.step < -180 ? 1 : 3); ctx.closePath(); ctx.fill(); ctx.stroke();
// Рисуем левую ногу / Draw the left leg
ctx.beginPath(); ctx.moveTo(center, body_y);
if(man.step < -180) { // When walking back
ctx.lineWidth = 3, // Когда шагает назад
offset = -width / 2,
flag = +1;
} else {
if(man.step <= 180) // When just walking
offset = width * 3 / 4, // Когда просто шагает
ctx.lineWidth = 3;
else // When walking forward
offset = width / 2, // Когда шагает вперёд
ctx.lineWidth = 1;
flag = -1;
}
tmp = this.drawKinematicLine(center, body_y, man.members.lFootX + offset, man.members.lFootY, width, -flag, +flag);
ctx.ellipse(tmp.x, tmp.y, width / 3, width / 5, (flag + 1) * Math.PI / 2, 0, Math.PI, flag > 0);
ctx.stroke();
// Рисуем правую ногу / Draw the right leg
ctx.beginPath(); ctx.moveTo(center, body_y);
if(man.step >= 180) { // When walking forward
ctx.lineWidth = 3, // Когда шагает вперёд
offset = width / 2,
flag = -1;
} else {
if(man.step > -180) // When just walking
offset = -width * 3 / 4,// Когда просто шагает
ctx.lineWidth = 3;
else // When walking back
offset = -width / 2, // Когда шагает назад
ctx.lineWidth = 1;
flag = +1;
}
tmp = this.drawKinematicLine(center, body_y, man.members.rFootX + offset, man.members.rFootY, width, -flag, +flag);
ctx.ellipse(tmp.x, tmp.y, width / 3, width / 5, (flag + 1) * Math.PI / 2, 0, Math.PI, flag > 0);
ctx.stroke();
// Двигаем всего персонажа / Moving this personage
if(man.path.seek >= 0 && man.path.seek < man.path.points.length) {
a = Math.floor(man.path.seek);
b = man.path.points[a];
man.position.x = b.x - this.width / 2;
man.position.z = b.y / 72;
if(man.step < -180 || man.step > 180)
do {
a += man.step < -180 ? -1 : +1;
tmp = a >= 0 && a < man.path.points.length;
if(tmp) {
c = man.path.points[Math.floor(a)];
dx = c.x - b.x, dy = c.y - b.y;
c = (man.step < -180 ? man.step + 180 : man.step - 180) % 360;
tmp = Math.sqrt(dx * dx + dy * dy) < c / 8;
}
} while(tmp && false);
man.path.seek = a;
} else {
man.position.x = man.step < -180 ? man.position.x + (man.step + 180) % 360 / 8 : man.step >= 180 ? man.position.x + (man.step - 180) % 360 / 8 : man.position.x;
}
left = man.step >= 540 ? man.phase + 180 : man.phase - 540;
rift = man.step >= 540 ? man.phase + 180 + 180 : man.phase - 540 - 180;
if(man.step > 180)
man.phase = (man.phase + man.step - 180) % 360;
else
if(man.step < -180)
man.phase = (man.phase + man.step + 180) % 360;
else
man.phase = (man.phase + man.step + 360) % 360;
a = (left % 360 + 360) % 360;
sine = a < 180 ? Math.sin(a * Math.PI / 180) : 0;
cose = Math.cos(a * Math.PI / 180);
man.members.lFootX = center - width / 3 * cose, man.members.lFootY = foot - width / 2 * sine;
a = (rift % 360 + 360) % 360;
sine = a < 180 ? Math.sin(a * Math.PI / 180) : 0;
cose = Math.cos(a * Math.PI / 180);
man.members.rFootX = center - width / 3 * cose, man.members.rFootY = foot - width / 2 * sine;
man.members.lFootX = man.step < -180 ? man.members.lFootX + width / 3 : man.step >= 180 ? man.members.lFootX - width / 3 : man.members.lFootX;
man.members.lFootY = man.members.lFootY - width / 6;
man.members.rFootX = man.step < -180 ? man.members.rFootX + width / 3 : man.step >= 180 ? man.members.rFootX - width / 3 : man.members.rFootX;
man.members.rFootY = man.members.rFootY - width / 6;
a = Math.floor(man.position.j);
b = Math.floor(man.position.j * 100);
c = Math.floor(man.position.j * 10000);
//man.position.j = a + Math.floor((man.position.j + man.position.j * 100) * 100) % 100 / 100 + c % 100 / 10000;
man.position.j = a + (b + c) % 100 / 100 + c % 100 / 10000;
if(text.length > 1) {
//this.cloud([],0,0,0, [].concat(text), center, head_y - head_height * 2, 100, blobs);
tmp = this.createCloud(0, 0);
tmp.x = center,
tmp.y = 100,
tmp.z = head_y - head_height * 2,
tmp.text = text.join("\t");
clouds.push(tmp);
}
ctx.restore();
}
}
customElements.define("fanta-seur", FANTASEUR, { extends: "canvas" });
CanvasRenderingContext2D.prototype.sky =
function(hour, month, bright) {
function gradient(from, last, seek) {
return {
r :from.r + (last.r - from.r) * seek,
g :from.g + (last.g - from.g) * seek,
b :from.b + (last.b - from.b) * seek
}
}
var seasons = [
{ sky:{ day:{ r:224, g:240, b:255 }, night:{ r:32, g:48, b:128 }, morning:{ r:176, g:192, b:192 }, evening:{ r:192, g:160, b:128 } } } // winter
, { sky:{ day:{ r:240, g:248, b:248 }, night:{ r:48, g:64, b:128 }, morning:{ r:184, g:184, b:192 }, evening:{ r:160, g:192, b:128 } } } // spring
, { sky:{ day:{ r:255, g:255, b:224 }, night:{ r:64, g:96, b:128 }, morning:{ r:192, g:176, b:160 }, evening:{ r:192, g:160, b:128 } } } // summer
, { sky:{ day:{ r:248, g:255, b:240 }, night:{ r:48, g:64, b:128 }, morning:{ r:184, g:184, b:192 }, evening:{ r:192, g:128, b:96 } } } // autumn
];
var sunrise = Math.sin((month + 2) * Math.PI / 12) * 2 + 4;
var sunset = Math.sin((month + 2) * Math.PI / 12) * 2 + 18;
var quarter = Math.floor((month + 11) % 12 / 3);
var quartnext = Math.floor(month % 12 / 3);
var zenith = (Math.sin((month - 1) * Math.PI / 12) + 1) * (this.canvas.height * 3 / 8); // Высота солнца в зените сезона
var sun_x = (Math.sin(hour * Math.PI / 12) + 1) * this.canvas.width / 2;
var sun_y = Math.cos(hour * Math.PI / 12) * zenith + this.canvas.height * 2 / 3;
var moon_x = (-Math.sin(hour * Math.PI / 12) + 1) * this.canvas.width / 2;
var moon_y = (-Math.cos(hour * Math.PI / 12) + 7 / 5) * this.canvas.width / 2;
var rgb, morning, day, evening, night, text, width;
var season = seasons[quarter];
var seasonext = seasons[quartnext];
morning = gradient(season.sky.morning, seasonext.sky.morning, (month + 11) % 3 / 3);
day = gradient(season.sky.day, seasonext.sky.day, (month + 11) % 3 / 3);
evening = gradient(season.sky.evening, seasonext.sky.evening, (month + 11) % 3 / 3);
night = gradient(season.sky.night, seasonext.sky.night, (month + 11) % 3 / 3);
if(hour > sunset || hour < sunrise)
rgb = night;
else {
rgb = day;
if(hour < sunrise + 2) // Два часа до утра
if(hour < sunrise + 1) // За час до рассвета
rgb = gradient(night, morning, (hour - sunrise));
else // Утро переходит в день
rgb = gradient(morning, rgb, (hour - sunrise - 1));
if(hour > sunset - 2) // Два часа до вечера
if(hour > sunset - 1) // Вечер переходит в ночь
rgb = gradient(evening, night, (hour - (sunset - 1)));
else // За час до заката
rgb = gradient(rgb, evening, (hour - (sunset - 2)));
}
bright = 95;
rgb = gradient(night, rgb, bright / 100);
this.beginPath();
this.fillStyle = "rgba("+[Math.floor(rgb.r), Math.floor(rgb.g), Math.floor(rgb.b)].join()+",100)";
this.fillRect(0, 0, this.canvas.width, this.canvas.height);
this.closePath();
this.beginPath();
//ctx.strokeStyle = sun_y > 0 ? "rgba(64,64,"+Math.floor(92+bright)+",100)" : "rgba("+[r,g,b].join()+",100)";
this.fillStyle = "rgba(" + [Math.floor(255*bright/100),Math.floor(255*bright/100),0*192*bright/100].join() + ",1)";
this.ellipse(sun_x, sun_y, zenith / 6, zenith / 6, 0, Math.PI * 2, 0, false);
text = "" + (Math.floor(hour) % 24) + ":" + ("0" + (Math.floor(hour * 60) % 60)).substr(-2);
width = this.measureText(text);
this.strokeText(text, (sun_x < this.canvas.width / 2 ? sun_x : sun_x - width.width), sun_y + zenith / 5);
this.closePath();
this.stroke();
this.fill();
this.beginPath();
this.fillStyle = "white";
this.ellipse(moon_x, moon_y, this.canvas.height / 16, this.canvas.height / 16, 0, Math.PI * 2, 0, false);
this.closePath();
this.fill();
/* Environment["Солнце"] = {
position :{
x :sun_x,
y :sun_y
}
};
Environment["Луна"] = {
position :{
x :moon_x,
y :moon_y
}
};
//man.members.lHandX = sun_x, man.members.lHandY = sun_y,
//man.members.rHandX = sun_x, man.members.rHandY = sun_y;
/* if(man.position.x -164 > sun_x)
man.step = -180 -20;
else
if(man.position.x +164 < sun_x)
man.step = +180 +20;
else
man.step += man.step > 180 ? -360 : man.step < -180 ? +360 : 0;
*/}
// Рисуем облако: контекст, облако, позиция, ветер, капли
function actor_cloud(cnv, ctx, cloud, x0, windy, d, text, xm, ym, yy) {
var x, y, z, w, h, n, d, r1, r2, r3;
var i, a, x1, y1, x2, y2, x3, y3, r, r0, tmp;
var a1, a2, arr = [];
var tx, ty, tw = 0, th, tl = 0;
if(text) {
th = parseInt(ctx.font, 10);
for(tmp in text) {
w = ctx.measureText(text[tmp]).width;
if(tw < w)
tw = w;
if(tl < text[tmp].length)
tl = text[tmp].length;
}
cloud.x = xm,
cloud.y = yy,
cloud.z = 0;
cloud.w = tw * 0.75,
cloud.h = (th * text.length) / 3;
cloud.r1 = 2,
cloud.r2 = 10,
cloud.r3 = 20;
cloud.n = Math.floor(tl);
cloud.x1 = 0,
cloud.y1 = 0;
}
cloud.x1 += Math.random() * windy / 10 + windy / 5;
cloud.y1 += Math.random() * windy / 10 + windy / 5;
cloud.x1 = cloud.x1 % (windy / 5 + 1), cloud.y1 = cloud.y1 % (windy / 5 + 1);
x = cloud.x + cloud.x1, y = cloud.y + cloud.y1, z = 1 + Math.log(cloud.z + 1);
w = cloud.w / z, h = cloud.h / z, n = cloud.n;
r1 = cloud.r1 / z, r2 = cloud.r2 / z, r3 = cloud.r3 / z;
cloud.x -= windy / z;
x += x0;
n *= 3; a1 = 2 * Math.PI / n / n;
a2 = Math.PI / 3;
for(i = 0; i < n; ++ i) {
a = 2 * Math.PI * i / n + Math.random() * a1 + a2;
r = [r3, r3, r2][i % 3] + Math.random() * r1;
x1 = Math.cos(a) * (w + r), y1 = Math.sin(a) * (h + r);
arr.push({x: x1, y: y1});
}
ctx.beginPath();
ctx.fillStyle = "white";
ctx.strokeStyle = "white";
ctx.closePath();
ctx.stroke();
ctx.beginPath();
tmp = arr.length - 1;
if(text) {
ctx.setLineDash([4,1]);
ctx.moveTo(xm, ym);
} else
ctx.moveTo(x+arr[tmp].x, y+arr[tmp].y);
while(arr.length) {
tmp = arr.shift();
x1 = tmp.x, y1 = tmp.y;
tmp = arr.shift();
x2 = tmp.x, y2 = tmp.y;
tmp = arr.shift();
x3 = tmp.x, y3 = tmp.y;
ctx.bezierCurveTo(x + x1, y + y1, x + x2, y + y2, x + x3, y + y3);
}
ctx.closePath();
ctx.lineWidth = 3 / z;
ctx.fillStyle = "rgba("+[Math.floor(255/(d/5+1)),Math.floor(255/(d/4+1)),255].join(",")+",1)";
ctx.fill();
ctx.strokeStyle = '#0000ff';
ctx.stroke();
ctx.beginPath();
ctx.strokeStyle = "rgba(64,160,255," + (d/100) + ")";
ctx.lineWidth = 2 / z;
if(d > 10) {
for(i = 0; i < 10; ++ i) {
ctx.setLineDash([Math.floor(Math.random() * 10), Math.floor(Math.random() * 10)]);
x1 = Math.random() + x + w * i / 10;
//if(Math.random() * d > i / 2)
ctx.moveTo(x1, y + h + Math.random() * h),
ctx.lineTo(x1 + 1 * windy, cnv.height);
}
}
ctx.stroke();
ctx.setLineDash([]);
if(text) {
ctx.fillStyle = "black";
tx = xm, ty = cloud.y - text.length * th / 2 + th / 2;
tmp = ctx.font;
ctx.font = "bold " + tmp;
for(i = 0; i < text.length; ++ i) {
ctx.fillText(text[i], tx - ctx.measureText(text[i]).width / 2, ty);
ty += th + Math.random() * 2;
tx += 2 - Math.random() * 3;
ctx.font = tmp;
}
ctx.stroke();
return (i + 2.5) * th;
}
}
function main() {
canvas = document.querySelector("canvas");
if("fantaSeur" in canvas) {
var clouds = window.location.href.match(/clouds=(?<clouds>\d+)/i);
var trees = window.location.href.match(/trees=(?<trees>\d+)/i);
var speed = window.location.href.match(/speed=(?<speed>\d+)/i);
var actors = window.location.href.match(/actors=(?<actors>\d+)/i);
if(clouds)
canvas.addClouds(clouds.groups.clouds);
if(trees)
canvas.addTrees(trees.groups.trees);
if(actors)
canvas.addActors(actors.groups.actors);
if(speed)
canvas.play(speed.groups.speed);
else
canvas.play();
}
}
</script>
</head>
<body>
<canvas is='fanta-seur' width=640 height=480></canvas>
<script>setTimeout(main, 10);</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment