文系エンジニア悪戦苦闘記

広告代理店の営業を辞め、未経験でフロントエンジニアになった僕の日常です。

Web上で動くスライム的な流体物を実装する

終わっちゃいましたね、GW。

 

僕は2日間外出して残りの3日間家に篭ってたので、

出来心でUnityをいじってました。

 

 

今までなんとなく敬遠してたんですUnity。

 

簡単にそれっぽいものが出来ると聞いていたので、

基礎もできていない僕がそんなものを触ってしまったら

プログラミングの勉強しなくなっちゃうんじゃないかって。

 

あれです。科学忍具的な。

便利すぎてそれに頼ってると強くなれないよ的な。

http://soz.matchupp.com/wp-content/uploads/2016/06/9.jpg

 

でも僕には木の葉の里守る使命とかないんだった

と気づいたので、初めて手を出してみました。

んでこれがもうびっくり。

  

噂通りというか、めちゃ簡単なんですね。

こんな感じの謎のゲームつくって遊んでました。

f:id:kurovetica:20170508021941p:plain

 

科学忍具万歳。

 

 

 

それはさておき、

Unityって物理エンジンの側面が強いんですね。

いじってる間に僕はなんだか"自然的な物"をつくりたくなりました。

 

プログラミングで自然的な物とか有機的な物とかを生み出すのって

魅力的じゃないですか。

ちょっとした神様気分味わえますし。

 

 

有機物の最たるものは何かと言えば誰がなんと言おうが「流体」な訳で。

 

そんなこんなでGW最終日は

流体物を書いてみることにしました。

 

 

 (「待てよ...手軽に流体がwebで実装出来るライブラリとかあったら楽だな...」

とかつくる前に邪なことを考えて「web fluid」とかでググッてたら、

「liquidfun」というjavascriptで利用出来る

2Dの流体演算エンジンを見っけました。

 

http://google.github.io/liquidfun/

 

openFrameworksでもお馴染みのBox2D(C++)が基になって作られてるみたいですね。

emscriptenしてjavascriptでも使えるよー的なものらしいです。

 

でもなんだかなー。

 

動きは確かに流体シミュレーションのそれなんだけど、

パーティクルの集合感がなんとも...。

 

サンプルを改造すればもっといい感じの流体にできるのかもですけど、

コード読み解いてから色々パラメーターいじったりするの結局面倒臭いし、

もっとこう愛でたくなる流体物を欲していたので

やっぱり自分で書くことにしました。)

 

 

せっかくフロントエンジニアという肩書きもいただいてるのでWeb上で軽量に動く

2DのものをProcessing.jsで書くことにしました。

コーディングにあたってはクリエイティブコーディディングのよろず屋こと

田所さんのサイトを参考にしています。本当有難い泣。

http://yoppa.org/

 

本当は、画面全体が流体!って感じのがやりたいのですが、

ベクトル場に10万ほどのパーティクルをぶち込む方法を取るとすると

いくらシェーダーを使えどWebだと限界があるし、

(実際openFrameworksでつくった粒子法の流体シミュレーションをemscriptenして試したのですがfps落ちってレベルじゃねーぞ状態でした泣)、

他にやり方も分からなかったんで、丸っこい流体物をつくります。

スライムのもっとごにょごにょしたやつみたいなの。可愛いの。

液体物言った方が正しいかもです。

 

http://livedoor.blogimg.jp/onecall_dazeee/imgs/1/3/13aa671b.png

 

 

 

ただごにょごにょ形が変わる円だと流体感が出ないので、

パーティクル同士をバネで数珠繋ぎにし、

それを円状にしていきます。

 

まず円の頂点となるparticleクラス。

class particle{
PVector position;
PVector velocity;
PVector force;
float friction;
float radius;
boolean bFixed;
float mass;

particle(){
radius = 5.0;
friction = 0.01;
mass = 1.0;
bFixed = false;
}

void setup(float positionX, float positionY, float velocityX, float velocityY){
position = new PVector(positionX,positionY);
velocity = new PVector(velocityX,velocityY);
}
void resetForce(){
force = new PVector(0,0);
}

void addForce(float forceX, float forceY){
force.x += forceX;
force.y += forceY;
}
void updateForce(){
force.x -= velocity.x * friction;
force.y -= velocity.y * friction;

}

void updatePos(){
if(!bFixed){
velocity.x += force.x;
velocity.y += force.y;
position.x += velocity.x;
position.y += velocity.y;
}
}
void update(){
updateForce();
updatePos();
}

void bounceOffWalls(){
boolean bDampedOnCollision = false;
boolean bDidICollide = false;

float minx = 0;
float miny = 0;
float maxx = width;
float maxy = height;

if (position.x > maxx){
position.x = maxx;
velocity.x *= -1;
bDidICollide = true;
} else if (position.x < minx){
position.x = minx;
velocity.x *= -1;
bDidICollide = true;
}

if (position.y > maxy){
position.y = maxy;
velocity.y *= -1;
bDidICollide = true;
} else if (position.y < miny){
position.y = miny;
velocity.y *= -1;
bDidICollide = true;
}

if (bDidICollide == true && bDampedOnCollision == true){
velocity.x *= 0.3;
velocity.y *= 0.3;
}
}
void draw(){
fill(255);
noStroke();
ellipse(position.x,position.y,radius,radius);
}
void addRepulsionForce(float x, float y, float radius, float scale){
PVector posOfForce;
posOfForce = new PVector(x,y);
PVector diff = new PVector(position.x - posOfForce.x, position.y - posOfForce.y);
float length = dist(position.x,position.y,posOfForce.x,posOfForce.y);
boolean bAmCloseEnough = true;
if (radius > 0){
if (length > radius){
bAmCloseEnough = false;
}
}
if (bAmCloseEnough == true){
float pct = 1 - (length / radius);
diff.normalize();
force.x = force.x + diff.x * scale * pct;
force.y = force.y + diff.y * scale * pct;
}
}
void addRepulsionForce(particle p, float radius, float scale){
p = new particle();
PVector posOfForce;
posOfForce = new PVector(p.position.x, p.position.y);
PVector diff = new PVector(position.x - posOfForce.x, position.y - posOfForce.y);
float length = dist(position.x,position.y,posOfForce.x,posOfForce.y);
boolean bAmCloseEnough = true;
if (radius > 0){
if (length > radius){
bAmCloseEnough = false;
}
}
if (bAmCloseEnough == true){
float pct = 1 - (length / radius);
diff.normalize();
force.x = force.x + diff.x * scale * pct;
force.y = force.y + diff.y * scale * pct;
p.force.x = p.force.x - diff.x * scale * pct;
p.force.y = p.force.y - diff.y * scale * pct;
}
}

void addAttractionForce(float x, float y, float radius, float scale){
PVector posOfForce;
posOfForce = new PVector(x,y);
PVector diff = new PVector(position.x - posOfForce.x, position.y - posOfForce.y);
float length = dist(position.x,position.y,posOfForce.x,posOfForce.y);
boolean bAmCloseEnough = true;
if (radius > 0){
if (length > radius){
bAmCloseEnough = false;
}
}
if (bAmCloseEnough == true){
float pct = 1 - (length / radius);
diff.normalize();
force.x = force.x - diff.x * scale * pct;
force.y = force.y - diff.y * scale * pct;
}
}

}

 

PVectorでポジションやら速度やら摩擦やら基本的な部分を定義して、

さらにパーティクル同士が反発しあう斥力を書いてやります。

 

これをしないとバネで繋いだ時にぺちゃんこになっちゃうのです。

 

続いてパーティクル同士をつなぐspringクラス。

 

class spring{
particle particleA = new particle();
particle particleB = new particle();


float distance;
float springiness;

spring(){
particleA = null;
particleB = null;
}

void update(){

PVector pta = new PVector(particleA.position.x, particleA.position.y);
PVector ptb = new PVector(particleB.position.x, particleB.position.y);

float thirDistance = dist(pta.x,pta.y,ptb.x,ptb.y);
float springForce = (springiness * (distance - thirDistance));
PVector frcToAddVec = new PVector(pta.x-ptb.x, pta.y-ptb.y);
frcToAddVec.normalize();
PVector frcToAdd = new PVector(frcToAddVec.x * springForce,frcToAddVec.y * springForce);


particleA.addForce(frcToAdd.x,frcToAdd.y);
particleB.addForce(-frcToAdd.x,-frcToAdd.y);
}

void draw(){

noFill();
stroke(255);

line(particleA.position.x,particleA.position.y,particleB.position.x,particleB.position.y);
}
}


particle myParticle = new particle[100];
spring mySpring = new spring[100];

void setup(){


size(innerWidth,innerHeight);

for(int i = 0; i<100; i++){
myParticle[i] = new particle();
myParticle[i].friction = 0.04;
myParticle[i].radius = 2;
float x = width/2 + 100 * cos ( (i / 200.0) * TWO_PI);
float y = height/2 + 100 * sin ( (i / 200.0) * TWO_PI);
myParticle[i].setup(x,y,0,0);
}

for (int i = 0; i < 100; i++){
mySpring[i] = new spring();
mySpring[i].distance = 0;
mySpring[i].springiness = 0.6;
mySpring[i].particleA = myParticle[i];
mySpring[i].particleB = myParticle[(i+1)0];
}
}

 

バネの両端を取得するためのparticleクラスのインスタンスparticleAとparticleBとを用意して、

それぞれをつなぐバネをつくっていきます。

springForceという変数んとこがバネの式です。

 

バネの伸び(このコード上ではparticleAからparticleBまでの距離)がxのとき、

それによって生じる力をFとすると、

F= -kx

という式になります。

kがバネの定数です。

 

フックの法則です。懐かしいですね。

 

 

んでメインの関数はこんな感じです。

 

particle myParticle = new particle[100];
spring mySpring = new spring[100];

void setup(){


size(innerWidth,innerHeight);

for(int i = 0; i<100; i++){
myParticle[i] = new particle();
myParticle[i].friction = 0.04;
myParticle[i].radius = 2;
float x = width/2 + 100 * cos ( (i / 200.0) * TWO_PI);
float y = height/2 + 100 * sin ( (i / 200.0) * TWO_PI);
myParticle[i].setup(x,y,0,0);
}

for (int i = 0; i < 100; i++){
mySpring[i] = new spring();
mySpring[i].distance = 0;
mySpring[i].springiness = 0.6;
mySpring[i].particleA = myParticle[i];
mySpring[i].particleB = myParticle[(i+1)0];
}
}

void draw(){
fill(200,200,255);
rect(0,0,width,height);
for (int i = 0; i < 100; i++){
myParticle[i].resetForce();
}


for (int i = 0; i < 100; i++){
if(mousePressed){

myParticle[i].addAttractionForce(mouseX, mouseY, 200, 4.0);
} else {

myParticle[i].addRepulsionForce(mouseX, mouseY, 200, 4.0);
}

for (int j = 0; j < 100; j++){
myParticle[i].addRepulsionForce(myParticle[j].position.x,myParticle[j].position.y, 100, 1.0);
}
}


for (int i = 0; i < 100; i++){
mySpring[i].update();
}


for (int i = 0; i < 100; i++){
myParticle[i].updateForce();
myParticle[i].update();
myParticle[i].bounceOffWalls();
}

fill(170,170,255);
noStroke();
beginShape();
for(int i = 0; i<100;i++){
vertex(myParticle[i].position.x,myParticle[i].position.y);
}
endShape();

}

 particleクラスとspringクラスを100個用意してそれを円状に繋いでいます。

 

ちょっと変わってるのが描画の方法です。

 

普通にparticleとspringのdraw()を呼び出すのではなく、

頂点となっているparticleに囲まれた図形を

beginShape()とendShape()で塗りつぶしています。

 

 

 

これで流体物改めスライムができました。

完成したものがこちらです。

マウスで撫でてやったりドラッグしてやったりしてください。

 

http://eikikurokawa.com/fluid/fluidWeb01/index.html

 

うん、可愛いですね。

 

さっき定数きたバネ定数(springiness)など、

ここで各種パラメータをいじるといろんな動きを見せてくれます。

 

こういう流体的なポヨポヨの動き、

意外と応用が効きそうな気がしてるのは僕だけでしょうか。

そうでもないかな。

 

それはともかく有機物ちっくなものつくるの、楽しいですね。

こういうのどんどん生み出して魔王になりたいです。

 

 

ーーーおまけーーー

 

今回のコーディングしてるうちに偶然できた、

キモ美しいジェネラティブアートです。

マウスに絶妙に反応します。

 

http://eikikurokawa.com/gene/gene01/index.html

プログラミングに触れたこともない法学部卒文系畑出身の僕がエンジニアとして転職するまで

 

この時期になると毎年のように新卒の早期退職者が取り沙汰され、

ワイドショーが彼らを血祭りに上げます。

 

 

まぁ、当然と言えば当然ですよね。

 

多大な労力と金を費やしてやっと採用した人材に利益を上げてもらう間もなく早々と辞められてしまっては、

企業にとっては恩を仇で返されたようなものですから。

 

かく言う僕も彼らには批判的な目を向けています。

 

せっかく苦労して入った会社をちょっと辛かったり怒られたりしただけで辞めるなんてどんだけ根性なしなんだよ。てかなんの経験も積まないで辞めてその先どうするつもりなんだろう根性なしの上に想像力もないのかな。てかああいうとこに座ってるコメンテーターと言う名のおっさんおばさんは普段なにやってる人なんだろう。なんか態度だけは偉そうだからきっと偉い人なんだろうな

 

と、テレビを見ながら同じような考えを巡らすのも、これまた毎年のことです。

ですが、今年は少し見方が変わっていました。

 

なにせ僕自身が、

その話題の矛先に立っているからです。

 

 

昨年、アブラゼミが仕事をしに土から出てきた頃、

僕は新卒で入った広告代理店を辞めました。

 

新卒で入社して半年も経たない内の早期退職でした。

 

 

確かに辛かったし怒られたりしたけれど、決してそれが理由ではありません。

別に仕事が嫌だった訳でもありません。

 

なんなら学生の時からコピーライター講座とか通っちゃうくらいにはコテコテの広告志望だったし、

入社した広告代理店も日本で5本の指には入る大手ということもあり、

充実感に溢れていたくらいなのです。

 

 

じゃあなんで辞めたんだよ訳わかんねぇ、って感じですが

その経緯諸々は長くなりそうなので別のエントリに書くことにします。

 

 

兎角僕はタイトルにもある通り、

この春からとあるデジタルプロダクションのフロントエンドエンジニアとして働かせて頂くことになりました。

 

本記事はその記念エントリ(?)です。

 

 

タイトルにもある通り、

一点の曇りもない全くの未経験です。

 

ウェブ、デジタルバージンです。

 

これまでプログラミングやコーディングなんかには

触れたことすらなかったし、

昨年まではhtmlとcssの違いすらよく分かってない始末でした。

 

 

そんなど素人がデジタルクリエイティブの最前線に飛び込む訳ですから、

女子高上がりの一女が舞い上がって訳もわからずサークルの先輩に抱かれる感覚なのです。

 

うん、ちょっと違うかな。

 

www.youtube.com

 

そんな"ウェイウェイ大学生"な僕の、

いわば初体験までのふわっとした経緯は以下の通り。

 

...

 

退職理由の一つに自分の手でつくれる人になりたい!ということもあったので

会社をやめてからすぐにデジタルハリウッドに入学しました。

 

平日夜と週末に1回ずつ、計週に2回講義のある、

他業種でバリバリ働いている社会人用の1年コースです。

 

CGのコースが比較的有名だけれど、

僕が入ったのはデザインや映像、プログラミングについて広く学べるコースでした。

 

ちなみに"社会人用"とは言いながらも、

僕はバリバリのニートでした。

 

 

ニートの強みとは何かと問われれば、

その答えは膨大な時間を持て余していること以外にないでしょう。

 

僕は学校に通いながら、独習で様々な言語に手を出しました。

 

とは言え根っからの文系坊やが初心者から独学でプログラミングを習得することは易くなく、

早々と挫折しかけました。

 

 

あーやっぱ普通に働いてりゃ良かったわー...

 

 

とか思い始めていたそんな最中、

perfumeのライブ演出でも有名なライゾマティクスが使っているという

openFrameworksというC++のライブラリをいじってみました。

 

言わずもがな、当時それがC++という言語であることすら知らずにではありますが。

 

www.youtube.com

 

 

これが衝撃的でした。

プログラミング初心者の僕でも簡単に、

それっぽいメディアアートが作れるのです。

 

3Dのオブジェクトを作ったり、それを周囲の音に反応させたり、

人の動きと連動させたりといった関数があらかじめ用意されていて、

それを呼び出して組み合わせていく。

 

比較的直感的にプログラムが組める大変便利なツールです。今更僕が言うまでもないですが。

 

その魅力に取り憑かれた僕は、年が明けてからしばらくの間、

openFrameworksを使った小さな作品をたくさん作りました。

 

 

思い返せばその甲斐あって

いつの間にかプログラミングの基礎や考え方が身についていたように思います。

 

 

そんなこんなで3月になろうとしていました。

 

ある程度の手応えを感じていた僕は、次第にそれを実際の現場で試したくなっていました。

 

 

善は急げと昔の偉い人が言っていたので 

元々デジハリの卒業月である今年の9月からやろうと思っていた再就職活動を始め、

今の会社になんとか採用していただく運びとなりました。

 

デジタルを生かしてあらゆるコンテンツやWebサイト、広告など制作するクリエイティブプロダクションで

僕が卒業後の第一志望としても考えていた会社です。

 

 

僕は一生分の運をここで使い果たしてしまったように思います。

 

...

 

現在入社して数週間。

 

全くの未経験でエンジニアの世界に飛び込んで先ず始めに思ったことは、

僕の数ヶ月を費やして培ったプログラミングスキルなど

現場では蟻程の力も貸さないということでした。

 

http://livedoor.blogimg.jp/bequeen/imgs/8/4/8431e63f.jpg

http://livedoor.blogimg.jp/bequeen/imgs/8/4/8431e63f.jpg

 

 

当たり前のこと過ぎて気がつきませんでした。

いや、薄々気づいてはいたのだがノリと勢いでなんとかしようとしていました。

 

 

そんな身の程知らずにも会社の先輩方はとても優しく指導してくれるし、

僕の作ったしょーもない作品を発表する場まで与えてくれて、涙がチョチョ切れます。

 

あいつを採って正解だったと思わせなければなりませんが、

果たして期待に答えられるでしょうか。

 

 

デジタルバージンがデジタルビッチになるまでの道のりは、長く険しいみたいです。