2次元ディスタンスフィールド線まとめ

# はじめに
* [2次元ディスタンスフィールドまとめ](https://qiita.com/7CIT/items/fe33b9b341b9918b6c3d)ページが肥大化したので別ページとして切り出し詳細にまとめ直しました。
* 直線・線分のディスタンスフィールドは特殊で円のディスタンスフィールドに比べると複雑になっています。
* 最大の特徴としては、円や正多角形のディスタンスフィールドは原点が”点”でしたが、原点が"線"や”線分”になっています。
* 一般的に使用する線とは2点間を結ぶ『線分』のことを指しますが、『線分』のディスタンスフィールドを理解するには、まずは、永久に続く線『直線』のディスタンスフィールドを理解する必要がある為、『線分』のディスタンスフィールドから解説します。

## 直線
ディスタンスフィールドに(永久に続く)直線はあるが、基本的に線分(2点で区切られた直線)はない。
ので、結論としては、直線をif文で条件分けして実現するのが一番てっとり早い。
他には、テッセレーションで直線を線分に分割したり、四角形の対向する2辺を極限まで小さくする方法があるが、if文で分岐するのが一番楽でしょう。

線分の仕方もいくつあるが、直線の描画方法にもいくつかの種類がある。


### 横軸直線

![xline.png](https://qiita-image-store.s3.amazonaws.com/0/135381/b2265e44-0a1b-3a4d-93f8-25e52dc692e7.png)
https://goo.gl/6qC4pN

```glsl
	l=abs(p.y);
```

```glsl
	p.x=0.;
```

```glsl
	p*=vec2(0.,1.);
```

```glsl
	p*=mat2(0.,0.,0.,1.);
```

### 縦軸直線

![yline.png](https://qiita-image-store.s3.amazonaws.com/0/135381/01ce953c-8c4f-4b8f-0745-a19edd72c682.png)
https://goo.gl/C4y5To

```glsl
	l=abs(p.x);
```

```glsl
	p.y=0.;
```

```glsl
	p*=vec2(1.,0.);
```

```glsl
	p*=mat2(1.,0.,0.,0.);
```

### 任意角度直線

![linerotate.png](https://qiita-image-store.s3.amazonaws.com/0/135381/8d2a2002-bbaf-ac08-91f2-2a289c15dd52.png)
https://goo.gl/2vbVh3

```glsl
	p=vec2(p.x*cos(t)+p.y*sin(t));
```

### 任意ベクトル方向直線

![vectline.png](https://qiita-image-store.s3.amazonaws.com/0/135381/4f1604ad-e54d-9eda-eaaf-ef7247c3cfbd.png)
https://goo.gl/zvTdVP

```glsl
precision mediump float;
uniform vec2  m;       // mouse
uniform float t;       // time
uniform vec2  r;       // resolution
uniform sampler2D smp; // prev scene

float cross(vec2 v1, vec2 v2) {return v1.x*v2.y - v1.y*v2.x;}

void main(void){
	vec2 p = (gl_FragCoord.xy * 2.0 - r) / min(r.x, r.y);
	float l;
	l=abs(cross(p,normalize(vec2(3.,4.))));
	gl_FragColor = vec4(vec3(l), 1.0);
}
```

## 片側直線

![absline.png](https://qiita-image-store.s3.amazonaws.com/0/135381/dd54eda3-8ad6-0146-61d4-9696d2c24504.png)

### 任意ベクトル

片方しか要らない場合はabsを外す

https://goo.gl/zuHgCG

```glsl
	l=cross(p,normalize(vec2(3.,4.)));
```

dotにすると90度回転、このまま使う場合もある

```glsl
	l=dot(p,normalize(vec2(3.,4.)));
```

実際に使うときはマイナスの時は NaN,Infinite or 1として使う

* NaN

```glsl
l=l<0.?sqrt(l):l;
```

* 1

```glsl
l=l<0.?1.:l;
```

* 1(三項演算子ではなく、stepを使ったバージョン、どっちが早い？)

```glsl
l=step(l,0.) + step(0.,l)*l;
```

* Infinite

```glsl
l=l<0.?1./0.:l;
```

### 縦
```glsl
	l=p.y;
```

### 横
```glsl
	l=p.x;
```

### 任意方向
```glsl
l=p.x*cos(t)+p.y*sin(t);
```

## 線分
ディスタンスフィールドは、極座標系なので、基本的に線分は存在しない。
ので、以下の3つの方法のどれかを選択します。
イラストレーターの[線の先端に相当](https://helpx.adobe.com/jp/illustrator/kb/5708.html)

実装が簡単なので、glslでは丸形先端が使われることが多いですが、線分と線分の接合部分がおかしくなりがちなので、止めた方がいいです。

* 90度反転した直線のディスタンスフィールドでマスク(バット先端)
* 細長いカプセルで、太さを最小としたもの(丸形先端)
* 細長い長方形で、太さを最小としたもの(突出先端)

### バット先端

![lineseg.png](https://qiita-image-store.s3.amazonaws.com/0/135381/3d0bfb4e-4e3c-ad6d-c7de-0c659a314dcf.png)

https://goo.gl/oFpkgj

* 90度回転させた直線のディタンスフィールドをもう1個使ってマスクする方法

90度回転させた直線のディタンスフィールドは外積の代わりに内積を使えばいい

```glsl
precision mediump float;
uniform vec2  m;       // mouse
uniform float t;       // time
uniform vec2  r;       // resolution
uniform sampler2D smp; // prev scene

float cross(vec2 v1, vec2 v2) {return dot(v1,vec2(v2.y,-v2.x));}

float lLine(vec2 p,vec2 v1,vec2 v2){
  vec2 v = v2 - v1;
  vec2 d = normalize(v); //direction
  p-=v1;
  return 1.-(1.-abs(cross(p,d)))*step(0.,length(v)*.5-abs(dot(p-v*.5,d)));
}

void main(void){
	vec2 p = (gl_FragCoord.xy * 2.0 - r) / min(r.x, r.y);
	float l;
	l=lLine(p,vec2(-.8,-0.8),vec2(.8,.8));
	gl_FragColor = vec4(vec3(l), 1.0);
}
```

* dotだけで作りif文で分岐するバージョン

```glsl
precision mediump float;
uniform vec2  m;       // mouse
uniform float t;       // time
uniform vec2  r;       // resolution
uniform sampler2D smp; // prev scene

float cross(vec2 v1, vec2 v2) {return dot(v1,vec2(v2.y,-v2.x));}

float lLine( vec2 p,vec2 v1, vec2 v2){
    vec2 v     = v1 - v2;
    vec2 p2p   = p - v2;
    vec2 p1p   = p - v1;
    vec2 pd    = normalize(vec2(v.y, -v.x));
    float proj = dot(pd, -p1p);
    float pr1  = dot( v, p2p);
    float pr2  = dot(-v, p1p);
    return (pr1 > 0.0 && pr2 > 0.0) ? abs(proj): 0.;
}

void main(void){
	vec2 p = (gl_FragCoord.xy * 2.0 - r) / min(r.x, r.y);
	float l;
	l=lLine(p,vec2(-.8),vec2(.8));
	gl_FragColor = vec4(vec3(l), 1.0);
}
```

### 丸形先端

![liner.png](https://qiita-image-store.s3.amazonaws.com/0/135381/845d2584-1468-9cb8-96b9-d466f86c77c7.png)

https://goo.gl/QdMY9m

```glsl
precision mediump float;
uniform vec2  m;       // mouse
uniform float t;       // time
uniform vec2  r;       // resolution
uniform sampler2D smp; // prev scene

const float PI  = acos(-1.);
const float TAU = PI * 2.;

float cross(vec2 v1, vec2 v2) {return dot(v1,vec2(v2.y,-v2.x));}
float sumabs(vec2 p){return abs(p.x)+abs(p.y);}
float maxabs(vec2 p){return max(abs(p.x), abs(p.y));}
mat2 rot(float a){float c=cos(a),s=sin(a);return mat2(c,-s,s,c);}
float line(vec2 p, vec2 v1, vec2 v2,int cap) {
    vec2 p1 = p  - v1;
    vec2 v  = v2 - v1;
    float t = dot(p1, normalize(v));
    vec2 pt = (p-((t<0.)?v1:v2))*rot(atan(v.x,v.y));
    return (t<0.||t>length(v))
     ? ((cap==1)?length(pt):(cap==2)?maxabs(pt):(cap==3)?sumabs(pt):1e10)
     : abs(cross(p1, normalize(v)));
}
float line(vec2 p, vec2 v1, vec2 v2) {return line(p,v1,v2,0);}

void main(void){
    vec2 p = (gl_FragCoord.xy * 2.0 - r) / min(r.x, r.y);
    float l;
    l=line(p,vec2(-.1,-.4),vec2(.4),1);
    gl_FragColor = vec4(vec3(l), 1.0);
}
```
先端の形状

* cap=0 バット先端
* cap=1 丸型先端
* cap=2 突出先端
* cap=3 矢印

### 参考
* https://thebookofshaders.com/edit.php#07/line.frag
* [クリエイティブコーディングをしよう！【その１】GLSLで遊ぶ](https://trap.jp/post/142/)