Processing(p5.js)で雪片を書いて、雪の模様を描いてみた。

本記事は Processing Advent Calendar 2019 の6日目の記事です。めっちゃ遅れましたすいません。

見てみると技術Tipsが多いですが、特に私はProcessingで技術記事を書くつもりではなく、またただ作品を上げるだけでもなく、作品制作してどのように作品をつくっていったか?を記事にしようかと。

なに作ろうかと考えたとき、冬をテーマにしようと思って、じゃあ、雪だし、雪の模様を書こうと思いました。結果はこんな感じです。

https://www.openprocessing.org/sketch/805611

個人的にキレイーって感じでできたんじゃないか?と。

まぁ、これで終わってもいいですが、簡単に解説を。

雪片でなにかプログラミングと言えばコッホ曲線を思い出すでしょうが、それはたぶんみんなやっててつまらないと思うので、そうじゃなくて、やっぱり手で書くような雪片を書こうと最初思いました。

雪片の結晶といえば、六角形の対角線の方向に放射していくような形でしょう。とすれば、私はまず中心から一方向に放射する何かしら形を書き、それをPI/3回転させるようにしていきます。

簡単にコード例を示すと以下の通りです。6回ループ回して、中心から上のびる枝を書いている感じです。

	push();
	translate(width/2, height/2);
	for(var i=0; i<6;++i){
		rotate(PI/3);
		line(0,0,0,200);
	}
	pop();


コメント 2019-12-07 021656

これだと簡単すぎるので、細かい枝をはやしてみましょう。それだけで雪の結晶ぽくなります

function draw() {
	stroke(200);
	push();
	translate(width/2, height/2);
	for(var i=0; i<6;++i){
		rotate(PI/3);
		piece();
	}
	pop();
}
function piece(){
	push();
	line(0,0,0,100);
	translate(0,10);
	for(var i=0;i<10;++i){
		translate(0,20);
		push();
		rotate(PI/3);
		line(0,0,0,60);
		rotate(-PI*2/3);
		line(0,0,0,60);
		pop();
	}
	pop();
}


あとはこれを沢山ふやしてみましょう。その際さっくりクラスにしてしまって、後で扱いやすくしましょう。

snowflakeクラスを示します。ここでradiusやx、yを使ってpieceを書き換えたりしていますが、省略します。

class Snowflake{
	constructor(){
		this.x = 0;
		this.y = 0;
                this.mainColor = color(20,20,155,100);
		this.radius = 100;
	}
	
	move(x,y){
		this.x = x;
		this.y = y;
		return this;
	}
	
	size(s){
		this.radius = s/2;
		return this;
	}
	
	color(c){
		this.mainColor = c;
		return this;
	}
	
	piece(){
            /* 上に枝が伸びるコード */
	}
	
	draw(){
		push();
		translate(width/2, height/2);
		translate(this.x, this.y);
		for(var i=0; i<6;++i){
			rotate(PI/3);
			this.piece();
		}
		pop();
	}
}

適当にコンストラクタを書き、なんとなくメソッドチェーンで、雪片の位置や大きさ、色を変更できるようにしました。

さて、それをArrayに突っ込んで、まとめて描画すればいいだけです。

var snowflakes = [];
var snowflakeCount = 100;

function setup() {
	createCanvas(windowWidth, windowHeight);
	background(10,20,30);
	blendMode(ADD);
	noLoop();
	for(var i = 0; i < snowflakeCount; i++){
		snowflakes.push(
			new Snowflake()
			.move(random(-width/2, width/2),random(-height/2, height/2))
			.size(random(10,200))
			.color(color(random(70,100),random(70,160),random(60,230),random(100,200)))
			);
	}
}

function draw(){
	for(var snowflake of snowflakes){
		snowflake.draw();
	}
}

たくさんかくこんな感じになります。

コメント 2019-12-07 025543

ただこれだと問題があります。雪片と雪片が重なってしまいます。なんか気持ち悪いなと思ったので、当たり判定を導入して重なってしまわないようにします。

当たり判定を導入するために、まず距離を計算して、そのあと当たり判定を導入しました。

class Snowflake{
 /* その他のコード */
    distance(otherSnow){
	return sqrt(pow(this.x - otherSnow.x, 2) + pow(this.y-otherSnow.y,2));
    }
	
    isCollision(otherSnow){
	return this.distance(otherSnow) < (otherSnow.radius + this.radius);
    }
 /* その他のコード */
}

そのあとそれをつかって追加のためのコードを書きました。

function tryToAdd(newSnowflake){
	if( snowflakes.every(snowflake=>!snowflake.isCollision(newSnowflake)) ){
		snowflakes.push(newSnowflake);
		return true;
	}else{
		return false;
	}
}

tryToAddは衝突判定をチェックして当たってなかったら追加してtrueを返して追加成功だと伝えます。そうじゃなかったらfalseを返します。

while(!tryToAdd(snowflake)){
    snowflake.move(random(-width/2,width/2), random(-height/2, height/2))
             .size(random(1,200));
}

このように追加できるまで、位置と大きさを変更し続けるコードを書いてます。

そうすると重なりがなくなりうまく雪を描いてくれます。1万個雪を書いてみました。

コメント 2019-12-07 0255423

ちなみに、snowflakesの初期化や、それにまつわるコードをクラス化しています。

さてあとはいろんな雪片を書きます。雪片の種類を増やすのは簡単でクラスをextendして、piece()をオーバーライドすればいいだけです。

class Snowflake2 extends Snowflake{
	piece(){
	// 別の形
	}
}

class Snowflake2 extends Snowflake{
	piece(){
	// 別の形をかく
	}
}

いろんこ単にランダムに雪片の形をつくるにはこのようにします。

	createSnowflake(){
		return new (random([Snowflake, 
					Snowflake2, 
					Snowflake3]));
	}

あと、他にも単に背景色をぬるだけだとなんか雰囲気出ないので、gradient入れたりしています。

function gradient(){
	const count = width/2;
	noFill();
	push();
	translate(width/2, height * 2.5/4);
	for(var i = 0; i<count; i++){
		var r = i;
		var a = 255* sqrt(1- i/count);
		stroke(20,125,150,a);
		ellipse(0, 0, r, r);
	}
	pop();
}

さてなんだかんだこんな感じになります。雪一万個のときの一部切り取り

コメント 2019-12-07 034503

もしこれみてなんかキレイに見えたらいいですね。

これらをアニメーションしたり、よりキラキラさせたりするのはまた今度。

この記事が気に入ったらサポートをしてみませんか?