Pull to refresh

Генератор случайных котов за 8 шагов

Reading time 8 min
Views 76K

Доброго времени, Хабр!
Я всегда очень любил котов, и любил их рисовать, особенно морды кошачьи. Чуть изменишь форму, линию — и совершенно другое выражение, другое настроение. У меня листы А4 были изрисованы под предел. И тут мне недавно стукнуло в голову — а что если сделать генератор морд котов? Чтобы нажал на кнопочку и тебе выкинется случайная морда кота. Как можно более случайная и интересная. Давайте же посмотрим, как же сделать такую штуку.
Прошу под кат, любители котов.

Всё будем делать на JS и Canvas'е, и я не привожу код инициализации, настройки и подобного. Этого в интернетах полно, а нам же интересно как рисовать котов, правда?

Приведу лишь несколько предопределенных функций, просто для дальнейшего удобства, вот они:

function add(func, scale){

	Graphics.ctx.scale(scale.x, scale.y);
	func(Graphics.ctx);	
	Graphics.ctx.scale(1, 1);

}

Меняем размер канваса, что-то рисуем, возвращаем назад.

function drawСircle(ctx, pos, radius, fillColor, strokeColor, lineWidth)
{
    ctx.beginPath();
    ctx.arc(center.x - pos.x, center.y + pos.y, radius, 0, 2*Math.PI, false);
    ctx.fillStyle = fillColor;
    ctx.fill();
    ctx.lineWidth = lineWidth;
    ctx.strokeStyle = strokeColor;
    ctx.stroke();
}


А это рисование окружностей\кругов. Тоже ничего особенного, всё это можно прочитать миллионы раз и на хабре и где угодно, давайте скорее к котам!

Шаг 1 — Голова


Начнем мы с головы. Шо есмь голова? Окружность. Которую можно немного сжать, или расширить.
Ещё можно задать толщину обводки, это тоже сделаем. И есть маленькая деталь, смотрим в коде комментарии.
Код весь прокомментирован, думаю проблем с понимаем не возникнет.

//Генерим случайный радиус
var radius = Math.sRandom(60, 70);
//Изменяем размеры
var scaleCircle = {x:Math.sRandom(1,1.1),y:Math.sRandom(0.9,1.0)};
//Задаем цвета
var whiteColor = "#fff";
var blackColor = "#000";
//Рисуем
add(function(ctx){
        // (Math.sRandom(0, 100) < 95 ? blackColor : whiteColor) - эта строчка, иногда, редко, делает контур белым, т.е. голова оказывается без контура. Уверяю, это получаеться здорово.
	drawСircle(ctx, {x:0,y:0}, radius, whiteColor, (Math.sRandom(0, 100) < 95 ? blackColor : whiteColor), Math.sRandom(2,5));
}, scaleCircle);


Воть и голова появилась.



Идем дальше!

Шаг 2 — Уши


Уши у котов — одно из самых выразительных элементов морды. Поэтому чем более уши будут разные, по разному повернуты, разной ширины, тем больше будет разнообразия выражений.
Делаем.

	//Вектор, который будем поворачивать, в поисках точек ушей
	var dir = {x:0, y:radius};
	//Первый угол
    var angleOne = Math.PI + Math.PI/Math.sRandom(2, 5);
    //Поворачиваем вектор и получаем первую точку
    var pointR1 = VectorRot(dir, angleOne);
    //Делаем вторую точку, через поворт вектора на первый угол минус отклонение
    var pointR2 = VectorRot(dir, angleOne - Math.PI/Math.sRandom(4, 7));
    //Считаем верхнюю точку уха
    var topPointR = {x:((pointR1.x + pointR2.x) / 2)+Math.sRandom(-10, 10), y:pointR2.y - 30 + Math.sRandom(0, 5)};
    //Рисуем
	add(function(ctx){
		//Рисуем правое ухо
	    ctx.beginPath();
        ctx.strokeStyle = strokeColor;
        ctx.fillStyle = fillColor;
        ctx.lineWidth = Math.sRandom(2, 4);
        topPointR = {x:topPointR.x+Math.sRandom(-1,5), y:topPointR.y+Math.sRandom(-5,5)};
		ctx.moveTo(center.x + pointR1.x,center.y + pointR1.y);
		ctx.lineTo(center.x + topPointR.x,center.y + topPointR.y);
		ctx.lineTo(center.x + pointR2.x,center.y +pointR2.y);
		ctx.fill();
		ctx.stroke();
		//Рисуем левое ухо
    	ctx.beginPath();
        ctx.strokeStyle = strokeColor;
        ctx.fillStyle = fillColor;
        ctx.lineWidth = Math.sRandom(2, 4);
        var topPointL = VectorXInvert({x:topPointR.x+Math.sRandom(-5,5), y:topPointR.y+Math.sRandom(-5,5)});
        var pointL1 = VectorXInvert(pointR1);
        var pointL2 = VectorXInvert(pointR2);
		ctx.moveTo(center.x + pointL1.x,center.y + pointL1.y);
		ctx.lineTo(center.x + topPointL.x,center.y + topPointL.y);
		ctx.lineTo(center.x + pointL2.x,center.y +pointL2.y);
		ctx.fill();
		ctx.stroke();


	}, {x:1,y:1});


Иииии вот что у нас получилось:



Уже напоминает кота, правда?

Шаг 3 — Усы


Второй во выразительности, после ушей, элемент морды кота. Сделать его не сложно, но сделать так чтобы они выглядели более правдоподобно сложнее. Одним рандомом тут не обойдёшься, приходится крутить цикл, и по счетчику высчитывать точки, для более линейных результатов.

	//Расчитываем точки начала усов
	//путем выбора вектора, и поворота вектора на почти случайное значение
	//Результат - точка начала уса
	var pointsR = [];
	//Выбираем число усов
	var count = Math.floor(Math.sRandom(3, 5));
	for (var i = 0; i < count; i++) {
		//Делаем вектор, с длиной из радиуса круга / на случайное значение
		var dir = {x:0, y:radius/Math.sRandom(1.6,1.9)};
		//Выбираем угол, плюс некоторые действия, для красоты
		var angleOne = Math.PI/(2 + ((i+1)/4));
		//Пвоворачиваем вектор
		var pointR1 = VectorRot(dir, angleOne);
		//Выбираем y конечной точки, так чтобы первую половину count усы отлонялись в одну сторону
		//а во второй половине в другую
		var y = pointR1.y+(i < count / 2 ? -Math.sRandom(8, 25) : Math.sRandom(7, 15) );
		//Записываем точки начала и конца в массив
		pointsR.push({begin:pointR1, end:{x:pointR1.x - Math.sRandom(60, 100),y:y}});
	}
	//Случайное значение ширины линии
	var lineWidth = Math.sRandom(0.5, 2);
	//Рисуем
	add(function(ctx){
		//Правая сторона
		for (var i = 0; i < pointsR.length; i++) {
			ctx.beginPath();
	        ctx.strokeStyle = strokeColor;
	        ctx.fillStyle = fillColor;
	        ctx.lineWidth = lineWidth;
			ctx.moveTo(center.x - pointsR[i].begin.x,center.y + pointsR[i].begin.y);
			ctx.lineTo(center.x - pointsR[i].end.x,center.y + pointsR[i].end.y);
			ctx.stroke();
		}
		//Левая сторона
	    for (var i = 0; i < pointsR.length; i++) {
			
			ctx.beginPath();
	        ctx.strokeStyle = strokeColor;
	        ctx.fillStyle = fillColor;
	        ctx.lineWidth = lineWidth;
	        var pointLBegin = VectorXInvert(pointsR[i].begin);
	        var pointLEnd = VectorXInvert(pointsR[i].end);
			ctx.moveTo(center.x - pointLBegin.x,center.y + pointLBegin.y);
			ctx.lineTo(center.x - pointLEnd.x,center.y + pointLEnd.y);
			ctx.stroke();
		}

	}, {x:1,y:1});


Функция VectorXInvert пусть вас не смущает, просто приходилось часто отражать по x различные элементы, поэтому сделана простая функция, которая делает x = -x;

Смотрим как прорисовывается наш кот:



Шаг 4 — Рот


Это довольно сложно сделать реалистично, поэтому сделаем просто линию рта, схематично, но для нашего графического рисунка вполне пойдет.
Пишем код, рисующий линию рта. Сделано на кривых Безье.

	//---Рот
	//Рот строем с помощью кривых Безье. Задаем четыре точки + 2 точки для отражения
	//      P0
	//  P3  |   iP3
	//  |   |    |
	//  P2--P1--iP2
	var P0 = {x:center.x, y:center.y};
	var P1 = {x:center.x, y:center.y + Math.sRandom(40, 65)};
	var P2 = {x:center.x - Math.sRandom(29, 36),y: center.y + 40};
	var P3 = {x:center.x - Math.sRandom(20, 40), y:center.y + Math.sRandom(23, 28)};
	var iP2 = {x:center.x + Math.sRandom(29, 36),y: center.y + 40};
	var iP3 = {x:center.x + Math.sRandom(20, 40), y:center.y + Math.sRandom(23, 28)};
	//Рисуем
	add(function(ctx){
	    ctx.beginPath();
        ctx.strokeStyle = strokeColor;
        ctx.fillStyle = fillColor ;
        ctx.lineWidth = Math.sRandom(1,3);
		ctx.moveTo(P0.x,P0.y );
		ctx.bezierCurveTo(P1.x, P1.y, P2.x, P2.y, P3.x, P3.y);
		ctx.stroke();

	    ctx.beginPath();
        ctx.strokeStyle = strokeColor;
        ctx.fillStyle = fillColor ;
        ctx.lineWidth = Math.sRandom(1,3);
		ctx.moveTo(P0.x,P0.y );
		
		ctx.bezierCurveTo(P1.x, P1.y, iP2.x, iP2.y, iP3.x, iP3.y);
		ctx.stroke();


	}, {x:1,y:1});


Глядим на то, что получилось. Терпение, ещё чуть-чуть осталось!


Шаг 5 — Нос


Нос есть нос. Сделаем его двух вариантов — маленький круг, и треугольничек. Круг будет появляться намного реже. Ну так, для интереса.
Разуметься примешиваем рандом, в том числе в цвета, чтобы получались разные носы — закрашенные, и просто контур. Нам ведь нужно много котов?

	//Два типа носа - либо треугольник, либо круг
	var chance = Math.sRandom(0, 100);
	if(chance < 98)
	{
		//Коефициент размера
		var scale = {x:Math.sRandom(0.9,1.3), y:Math.sRandom(0.9,1.3)};
		//Правая точка
		var pointR ={x:Math.sRandom(4,5)*scale.x,y:Math.sRandom(-5,-4)*scale.y};
		//Левая точка
		var pointL ={x:Math.sRandom(-4,-5)*scale.x,y:Math.sRandom(-5,-4)*scale.y};
		//Нижняя точка
		var bottomPoint ={x:0,y:Math.sRandom(5,6)*scale.y};
		//Рисуем
		add(function(ctx){
		    ctx.beginPath();
	        ctx.strokeStyle = strokeColor;
	        ctx.fillStyle = (Math.sRandom(0, 100) > 50 ? fillColor : strokeColor);
	        ctx.lineWidth = Math.sRandom(1,3);
			ctx.moveTo(center.x + pointR.x,center.y + 5 + pointR.y);
			ctx.lineTo(center.x + bottomPoint.x,center.y + 5 + bottomPoint.y);
			ctx.lineTo(center.x + pointL.x,center.y + 5 + pointL.y);
			ctx.closePath();
			ctx.fill();
			ctx.stroke();

		}, {x:1,y:1});
	}
	else
	{
		//Рисуем кот
		add(function(ctx){
			drawСircle(ctx, {x:0,y:0}, Math.sRandom(7, 10), strokeColor, strokeColor, 1);
		}, {x:1,y:1});
	}


Наш кот постепенно превращается в кота. Это не может не радовать.



Шаг 6 — Глаза


Глаза штука сложная. Нет, нарисовать не сложно — просто две кривых Безье. Но надо ещё добавить закрывающееся глаза, котик же может спать, а может и один глаз приоткрыть. И это надо сделать. В этот раз просто кот, без кода. Ибо большой объем, не хочется утомлять читателя кучей текста. В конце будут приведены исходники, кто захочет поглядит. А мы просто посмотрим на то что получилось.



Кот уже есть, но добавим ещё некоторый декор.

Шаг 7 — Точки усов на щеках


Знаете, бывают такие. Вероятность появления естественно, не 100%. Вот код, потом результат.
Код простой — просто раскидываем точки на некотором расстояниие от носа.

	if(Math.sRandom(0, 100) > 60)
	{
		add(function(ctx){

			for (var i = 0; i < Math.sRandom(5, 7); i++) {
				var P = {x:Math.sRandom(20, 40),y:Math.sRandom(0, 30)};
				drawСircle(ctx, P, 1, strokeColor, strokeColor, 1);
			}
		    for (var i = 0; i < Math.sRandom(5, 7); i++) {
				var P = {x:-Math.sRandom(20, 40),y:Math.sRandom(0, 30)};
				drawСircle(ctx, P, 1, strokeColor, strokeColor, 1);
			}

		}, {x:1,y:1});
	}


Результат:


Шаг 8 — «Челка»


Просто иногда рисуем парочку линий сверху. На словах тяжело описать, смотрим код, смотрим что получается.


	if(Math.sRandom(0, 100) > 75)
	{
		add(function(ctx){

			
			for (var i = 0; i < Math.sRandom(3, 5); i++) {
				//Берем вектор равный по длине радиусу
				var radiusVector = {x:0,y:-radius};
				//Поворачиваем вектор на некоторое отлонение
				//Нужно чтобы волосы выходили ровно из окружности
				radiusVector = VectorRot(radiusVector, Math.sRandom(-0.01, 0.01))
				//Строим нижнии точки
				var P0 = {x:Math.sRandom(-25, 25), y: radiusVector.y};
				var P1 = {x:Math.sRandom(-25, 25), y: Math.sRandom(-50, -40)};
				//Ширина волоса
				var lineWidth = Math.sRandom(0.5, 1.5);
				//Рисуем
				drawLine(ctx, P0, P1, strokeColor, strokeColor, lineWidth);
			}

		}, {x:1,y:1});
	}


Здорово, правда?



Шаг 8 — Колокольчик или бантик


Ну а это уже просто для интереса, я думал чтобы ещё добавить, и решил добавить бантик и колокольчик. Не судите строго, я развлекаюсь :)
Как всегда код, потом красивые картинки с котиками.

	//Декорации
	//Либо бабочка либо колокольчик
	var chanceBottom = Math.sRandom(0, 100);
	if(chanceBottom > 50)
	{

		var chance = Math.sRandom(0, 100);
		if(chance > 90)
		{
			//Бабочка - два треугольника + круг, с точками в случайном дипазаоне
			var P = {x:0,y:radius};
			var P0 = {x:Math.sRandom(20, 45), y:radius- Math.sRandom(13, 22)};
			var P1 = {x:Math.sRandom(20, 45), y:radius+ Math.sRandom(13, 22)};
			add(function(ctx){
					
				var color = (Math.sRandom(0, 100) > 50 ? fillColor : strokeColor);
			    ctx.beginPath();
		    	ctx.strokeStyle = strokeColor;
		    	ctx.fillStyle = color;
		    	ctx.lineWidth = Math.sRandom(2, 5);
				ctx.moveTo(center.x + P.x,center.y + P.y);
				ctx.lineTo(center.x + P0.x,center.y + P0.y);
				ctx.lineTo(center.x + P1.x,center.y + P1.y);
				ctx.closePath();
				ctx.stroke();
				ctx.fill();

				ctx.beginPath();
		    	ctx.strokeStyle = strokeColor;
		    	ctx.fillStyle = color;
		    	ctx.lineWidth = Math.sRandom(2, 5);
				ctx.moveTo(center.x - P.x,center.y + P.y);
				ctx.lineTo(center.x - P0.x,center.y + P0.y);
				ctx.lineTo(center.x - P1.x,center.y + P1.y);
				ctx.closePath();
				ctx.stroke();
				ctx.fill();

				drawСircle(ctx, P, Math.sRandom(6, 12), (Math.sRandom(0, 100) > 50 ? fillColor : strokeColor), strokeColor, Math.sRandom(1, 3));

			}, {x:1,y:1});
		}
	}
	else
	{
		//Колокольчик
		var chance = Math.sRandom(0, 100);
		if(chance > 90)
		{
			//Колокольчик - треугольник, внизу с кругом. Строится по трём точкам, всё аналогично, как и выше
			var P = {x:0,y:radius};
			var P0 = {x:Math.sRandom(8, 15), y:radius+ Math.sRandom(23, 29)};
			var P1 = {x:-P0.x, y:P0.y};
			var P3 = {x:0,y:P0.y + Math.sRandom(0, 7)};
			add(function(ctx){

				drawСircle(ctx, P3, Math.sRandom(2, 6), (Math.sRandom(0, 100) > 50 ? fillColor : strokeColor), strokeColor, Math.sRandom(1, 3));

				var color = (Math.sRandom(0, 100) > 50 ? fillColor : strokeColor);
			    ctx.beginPath();
		    	ctx.strokeStyle = strokeColor;
		    	ctx.fillStyle = color;
		    	ctx.lineWidth = Math.sRandom(2, 5);
				ctx.moveTo(center.x + P.x,center.y + P.y);
				ctx.lineTo(center.x + P0.x,center.y + P0.y);
				ctx.lineTo(center.x + P1.x,center.y + P1.y);
				ctx.closePath();
				ctx.stroke();
				ctx.fill();

			}, {x:1,y:1});
		}
	}


Как видите ничего сложного, просто геометрические фигуры.
Код был бы скучен, если бы не было котов, да будет кот с бантиком!



Заключение


Ну вот и закончил я свое повествование о котах.
Привожу код на гитхабе: github.com/MagistrAVSH/random-cat
А вот результат всех трудов, можете пощелкать: magistravsh.github.io/random-cat
А теперь брысь от монитора, и погладь кота! :)
Tags:
Hubs:
+162
Comments 69
Comments Comments 69

Articles