chgnhyk 0.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- chgnhyk-0.0.0/PKG-INFO +17 -0
- chgnhyk-0.0.0/README.md +0 -0
- chgnhyk-0.0.0/pyproject.toml +29 -0
- chgnhyk-0.0.0/setup.cfg +4 -0
- chgnhyk-0.0.0/src/chgnhyk/AreaPicker.py +114 -0
- chgnhyk-0.0.0/src/chgnhyk/CCDIK.py +93 -0
- chgnhyk-0.0.0/src/chgnhyk/DFT.py +116 -0
- chgnhyk-0.0.0/src/chgnhyk/FBOParticlePass.py +245 -0
- chgnhyk-0.0.0/src/chgnhyk/FBOPass.py +199 -0
- chgnhyk-0.0.0/src/chgnhyk/FBOTilePass.py +206 -0
- chgnhyk-0.0.0/src/chgnhyk/GeometryFBO.py +500 -0
- chgnhyk-0.0.0/src/chgnhyk/OfflineEngine.py +187 -0
- chgnhyk-0.0.0/src/chgnhyk/OfflineSound.py +166 -0
- chgnhyk-0.0.0/src/chgnhyk/RayMarcher.py +188 -0
- chgnhyk-0.0.0/src/chgnhyk/RealtimeEngine.py +265 -0
- chgnhyk-0.0.0/src/chgnhyk/SDFs.py +141 -0
- chgnhyk-0.0.0/src/chgnhyk/SVGEngine.py +52 -0
- chgnhyk-0.0.0/src/chgnhyk/SVGsampler.py +105 -0
- chgnhyk-0.0.0/src/chgnhyk/__init__.py +15 -0
- chgnhyk-0.0.0/src/chgnhyk/assets/__init__.py +0 -0
- chgnhyk-0.0.0/src/chgnhyk/assets/csound/__init__.py +0 -0
- chgnhyk-0.0.0/src/chgnhyk/assets/shaders/__init__.py +0 -0
- chgnhyk-0.0.0/src/chgnhyk/utils.py +55 -0
- chgnhyk-0.0.0/src/chgnhyk.egg-info/PKG-INFO +17 -0
- chgnhyk-0.0.0/src/chgnhyk.egg-info/SOURCES.txt +26 -0
- chgnhyk-0.0.0/src/chgnhyk.egg-info/dependency_links.txt +1 -0
- chgnhyk-0.0.0/src/chgnhyk.egg-info/requires.txt +11 -0
- chgnhyk-0.0.0/src/chgnhyk.egg-info/top_level.txt +1 -0
chgnhyk-0.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: chgnhyk
|
|
3
|
+
Version: 0.0.0
|
|
4
|
+
Summary: visual and audio engine of chgnhyk
|
|
5
|
+
Requires-Python: >=3.9.0
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
Requires-Dist: moderngl
|
|
8
|
+
Requires-Dist: numpy
|
|
9
|
+
Requires-Dist: pillow
|
|
10
|
+
Requires-Dist: opencv-python
|
|
11
|
+
Requires-Dist: csoundengine
|
|
12
|
+
Requires-Dist: moviepy
|
|
13
|
+
Requires-Dist: svgpathtools
|
|
14
|
+
Requires-Dist: trimesh
|
|
15
|
+
Requires-Dist: pyrr
|
|
16
|
+
Requires-Dist: noise
|
|
17
|
+
Requires-Dist: moderngl_window
|
chgnhyk-0.0.0/README.md
ADDED
|
File without changes
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "chgnhyk"
|
|
7
|
+
version = "0.0.0"
|
|
8
|
+
description = "visual and audio engine of chgnhyk"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9.0"
|
|
11
|
+
dependencies = [
|
|
12
|
+
"moderngl",
|
|
13
|
+
"numpy",
|
|
14
|
+
"pillow",
|
|
15
|
+
"opencv-python",
|
|
16
|
+
"csoundengine",
|
|
17
|
+
"moviepy",
|
|
18
|
+
"svgpathtools",
|
|
19
|
+
"trimesh",
|
|
20
|
+
"pyrr",
|
|
21
|
+
"noise",
|
|
22
|
+
"moderngl_window"
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
[tool.setuptools]
|
|
26
|
+
package-dir = {"" = "src"}
|
|
27
|
+
|
|
28
|
+
[tool.setuptools.packages.find]
|
|
29
|
+
where = ["src"]
|
chgnhyk-0.0.0/setup.cfg
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import math
|
|
2
|
+
import random
|
|
3
|
+
|
|
4
|
+
class AreaCell:
|
|
5
|
+
def __init__(self, x, y, idx):
|
|
6
|
+
self.filled = False
|
|
7
|
+
self.x = x
|
|
8
|
+
self.y = y
|
|
9
|
+
self.idx = idx
|
|
10
|
+
#seed(0)
|
|
11
|
+
pass
|
|
12
|
+
|
|
13
|
+
class AreaPicker:
|
|
14
|
+
def __init__(self, w:int, h:int, seed:int=0):
|
|
15
|
+
self.reset(w, h, seed)
|
|
16
|
+
self.min_w = 0
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def reset(self, w:int, h:int, seed:int=0):
|
|
20
|
+
self.rects = []
|
|
21
|
+
self.depleted = False
|
|
22
|
+
self.seed = seed
|
|
23
|
+
random.seed(self.seed)
|
|
24
|
+
self.setGrid(w, h)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def setGrid(self, w:int, h:int):
|
|
28
|
+
self.gw = w
|
|
29
|
+
self.gh = h
|
|
30
|
+
self.cells = []
|
|
31
|
+
self.pool = []
|
|
32
|
+
for x in range(self.gw):
|
|
33
|
+
for y in range(self.gh):
|
|
34
|
+
idx = x + y * self.gw
|
|
35
|
+
self.cells.append(AreaCell(x, y, idx))
|
|
36
|
+
self.pool.append(idx)
|
|
37
|
+
|
|
38
|
+
def pickArea(self):
|
|
39
|
+
if len(self.pool) == 0:
|
|
40
|
+
self.depleted = True
|
|
41
|
+
return
|
|
42
|
+
|
|
43
|
+
pck_idx = random.randint(0, len(self.pool)-1)
|
|
44
|
+
pck_idx = self.pool[pck_idx]
|
|
45
|
+
|
|
46
|
+
pck_y_idx = math.floor(pck_idx/self.gw)
|
|
47
|
+
pck_x_idx = pck_idx % self.gw
|
|
48
|
+
|
|
49
|
+
x_dir = -1 if random.random() > 0.5 else 1
|
|
50
|
+
y_dir = -1 if random.random() > 0.5 else 1
|
|
51
|
+
avail_w = 0
|
|
52
|
+
avail_h = self.gh
|
|
53
|
+
|
|
54
|
+
x = pck_x_idx
|
|
55
|
+
while 0 <= x < self.gw:
|
|
56
|
+
xidx = x + pck_y_idx * self.gw
|
|
57
|
+
col_avail_h = 0
|
|
58
|
+
y = pck_y_idx
|
|
59
|
+
while 0 <= y < self.gh:
|
|
60
|
+
yidx = x + y * self.gw
|
|
61
|
+
if self.cells[yidx].filled:
|
|
62
|
+
break
|
|
63
|
+
col_avail_h += 1
|
|
64
|
+
y += y_dir
|
|
65
|
+
|
|
66
|
+
avail_h = min(avail_h, col_avail_h)
|
|
67
|
+
if self.cells[xidx].filled:
|
|
68
|
+
break
|
|
69
|
+
avail_w += 1
|
|
70
|
+
x += x_dir
|
|
71
|
+
|
|
72
|
+
# TODO --> need to think about this
|
|
73
|
+
# if avail_w < self.min_w and avail_h < self.min_w:
|
|
74
|
+
# self.cells[pck_idx].filled = True
|
|
75
|
+
# if pck_idx in self.pool:
|
|
76
|
+
# self.pool.remove(pck_idx)
|
|
77
|
+
# return
|
|
78
|
+
|
|
79
|
+
if avail_w > self.min_w:
|
|
80
|
+
rw = random.randint(self.min_w, avail_w-1)
|
|
81
|
+
else:
|
|
82
|
+
rw = max(0, min(self.min_w, avail_w-1))
|
|
83
|
+
|
|
84
|
+
if avail_h > self.min_w:
|
|
85
|
+
rh = random.randint(self.min_w, avail_h-1)
|
|
86
|
+
else:
|
|
87
|
+
rh = max(0, min(self.min_w, avail_h-1))
|
|
88
|
+
|
|
89
|
+
x_strt = pck_x_idx if (x_dir == 1) else pck_x_idx-rw
|
|
90
|
+
x_end = pck_x_idx+rw if (x_dir == 1) else pck_x_idx
|
|
91
|
+
y_strt = pck_y_idx if (y_dir == 1) else pck_y_idx-rh
|
|
92
|
+
y_end = pck_y_idx+rh if (y_dir == 1) else pck_y_idx
|
|
93
|
+
|
|
94
|
+
#print(x_strt, x_end, y_strt, y_end, x_dir, y_dir)
|
|
95
|
+
|
|
96
|
+
for x in range(x_strt, x_end+1, 1):
|
|
97
|
+
for y in range(y_strt, y_end+1, 1):
|
|
98
|
+
idx = x + y * self.gw
|
|
99
|
+
|
|
100
|
+
self.cells[idx].filled = True
|
|
101
|
+
if idx in self.pool:
|
|
102
|
+
self.pool.remove(idx)
|
|
103
|
+
|
|
104
|
+
self.rects.append({
|
|
105
|
+
"x": x_strt,
|
|
106
|
+
"y": y_strt,
|
|
107
|
+
"w": abs(x_end-x_strt)+1,
|
|
108
|
+
"h": abs(y_end-y_strt)+1
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
if len(self.pool) == 0:
|
|
112
|
+
self.depleted = True
|
|
113
|
+
|
|
114
|
+
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import math
|
|
2
|
+
|
|
3
|
+
def clamp(x, min_val, max_val):
|
|
4
|
+
return max(min(x, max_val), min_val)
|
|
5
|
+
|
|
6
|
+
def rotate(x,y,a):
|
|
7
|
+
return (math.cos(a)*x-math.sin(a)*y, math.sin(a)*x+math.cos(a)*y)
|
|
8
|
+
|
|
9
|
+
def fract(x):
|
|
10
|
+
return x-math.floor(x)
|
|
11
|
+
|
|
12
|
+
def smoothstep(mi, mx, v):
|
|
13
|
+
x = max(0, min(1, (v-mi)/(mx-mi)))
|
|
14
|
+
return x*x*(3 - 2*x)
|
|
15
|
+
|
|
16
|
+
def normalize(x, y):
|
|
17
|
+
vl = math.sqrt(x*x+y*y)
|
|
18
|
+
if vl < 1e-6:
|
|
19
|
+
return (0.0, 0.0)
|
|
20
|
+
else:
|
|
21
|
+
return (x/vl, y/vl)
|
|
22
|
+
|
|
23
|
+
def get_dist(x1,y1,x2,y2):
|
|
24
|
+
dx = x2-x1
|
|
25
|
+
dy = y2-y1
|
|
26
|
+
return math.sqrt(dx*dx+dy*dy)
|
|
27
|
+
|
|
28
|
+
def get_angle(v1:tuple,v2:tuple):
|
|
29
|
+
dot = v1[0]*v2[0]+v1[1]*v2[1]
|
|
30
|
+
crss = v1[0]*v2[1]-v1[1]*v2[0]
|
|
31
|
+
|
|
32
|
+
dot = clamp(dot, -1.0, 1.0)
|
|
33
|
+
|
|
34
|
+
if abs(dot - 1.0) < 1e-6 and abs(crss) < 1e-6:
|
|
35
|
+
return 0.0
|
|
36
|
+
else:
|
|
37
|
+
angle = math.atan2(crss,dot)
|
|
38
|
+
return angle
|
|
39
|
+
|
|
40
|
+
class Arm2D:
|
|
41
|
+
def __init__(self, num, l, ease = 1.0, angle = 0.0):
|
|
42
|
+
self.L = l
|
|
43
|
+
self.angles = []
|
|
44
|
+
self.positions = []
|
|
45
|
+
self.sx = 0
|
|
46
|
+
self.sy = 0
|
|
47
|
+
|
|
48
|
+
self.ease = ease
|
|
49
|
+
|
|
50
|
+
self.num = num
|
|
51
|
+
for i in range(self.num):
|
|
52
|
+
if i == 0:
|
|
53
|
+
self.angles.append(math.pi * 0.5 + angle)
|
|
54
|
+
else:
|
|
55
|
+
self.angles.append(0.0)
|
|
56
|
+
self.positions.append((0.0,0.0))
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def get_arm_pos(self):
|
|
60
|
+
data = []
|
|
61
|
+
data = self.positions.copy()
|
|
62
|
+
self.update_arms()
|
|
63
|
+
data.append((self.ex,self.ey))
|
|
64
|
+
# print(len(data))
|
|
65
|
+
return data
|
|
66
|
+
|
|
67
|
+
def update_arms(self):
|
|
68
|
+
_sx = self.sx
|
|
69
|
+
_sy = self.sy
|
|
70
|
+
_a = 0
|
|
71
|
+
for i in range(self.num):
|
|
72
|
+
_a += self.angles[i]
|
|
73
|
+
self.positions[i] = (_sx,_sy)
|
|
74
|
+
_sx = _sx + math.cos(_a) * self.L
|
|
75
|
+
_sy = _sy + math.sin(_a) * self.L
|
|
76
|
+
|
|
77
|
+
self.ex = _sx
|
|
78
|
+
self.ey = _sy
|
|
79
|
+
|
|
80
|
+
def set_target(self, tx, ty):
|
|
81
|
+
self.update_arms()
|
|
82
|
+
|
|
83
|
+
for i in range(self.num-1,-1,-1):
|
|
84
|
+
jx = self.positions[i][0]
|
|
85
|
+
jy = self.positions[i][1]
|
|
86
|
+
|
|
87
|
+
jd = normalize(self.ex-jx, self.ey-jy)
|
|
88
|
+
d = normalize(tx-jx, ty-jy)
|
|
89
|
+
|
|
90
|
+
angle = get_angle(jd, d)
|
|
91
|
+
self.angles[i] += angle * self.ease
|
|
92
|
+
|
|
93
|
+
self.update_arms()
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import math
|
|
2
|
+
|
|
3
|
+
class Cmplx:
|
|
4
|
+
def __init__(self, a, b):
|
|
5
|
+
self.re = a
|
|
6
|
+
self.im = b
|
|
7
|
+
pass
|
|
8
|
+
|
|
9
|
+
def add(self, c):
|
|
10
|
+
self.re += c.re
|
|
11
|
+
self.im += c.im
|
|
12
|
+
|
|
13
|
+
def mult(self, c):
|
|
14
|
+
_re = self.re * c.re - self.im * c.im
|
|
15
|
+
_im = self.re * c.im + self.im * c.re
|
|
16
|
+
return Cmplx(_re, _im)
|
|
17
|
+
|
|
18
|
+
def DFT(x:iter, w, h, compress=0):
|
|
19
|
+
|
|
20
|
+
X = []
|
|
21
|
+
N = len(x)
|
|
22
|
+
for i in range(N):
|
|
23
|
+
x[i] = Cmplx(x[i][0]-w*0.5,x[i][1]-h*0.5)
|
|
24
|
+
|
|
25
|
+
for k in range(N):
|
|
26
|
+
_sum = Cmplx(0, 0)
|
|
27
|
+
for n in range (N):
|
|
28
|
+
phi = (math.pi * 2 * k * n) / N
|
|
29
|
+
c = Cmplx(math.cos(phi), -math.sin(phi))
|
|
30
|
+
_sum.add(x[n].mult(c))
|
|
31
|
+
_sum.re = _sum.re / N
|
|
32
|
+
_sum.im = _sum.im / N
|
|
33
|
+
|
|
34
|
+
#freq = k
|
|
35
|
+
freq = k if k <= N//2 else k - N
|
|
36
|
+
amp = math.sqrt(_sum.re * _sum.re + _sum.im * _sum.im)
|
|
37
|
+
phase = math.atan2(_sum.im, _sum.re)
|
|
38
|
+
|
|
39
|
+
X.append({
|
|
40
|
+
"re": _sum.re,
|
|
41
|
+
"im": _sum.im,
|
|
42
|
+
"freq": freq,
|
|
43
|
+
"amp": amp,
|
|
44
|
+
"phase": phase
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
if compress > 0 and compress < len(x):
|
|
48
|
+
X.sort(key=lambda c: c["amp"], reverse=True)
|
|
49
|
+
X = X[:compress]
|
|
50
|
+
|
|
51
|
+
return X
|
|
52
|
+
|
|
53
|
+
def MixF(X1, X2, t):
|
|
54
|
+
X = []
|
|
55
|
+
for i in range(len(X1)):
|
|
56
|
+
re1 = X1[i]["re"]
|
|
57
|
+
im1 = X1[i]["im"]
|
|
58
|
+
|
|
59
|
+
re2 = X2[i]["re"]
|
|
60
|
+
im2 = X2[i]["im"]
|
|
61
|
+
|
|
62
|
+
re = (1.0-t)*re1 + t*re2
|
|
63
|
+
im = (1.0-t)*im1 + t*im2
|
|
64
|
+
X.append({
|
|
65
|
+
"re": re,
|
|
66
|
+
"im": im,
|
|
67
|
+
"freq": X1[i]["freq"]
|
|
68
|
+
})
|
|
69
|
+
return X
|
|
70
|
+
|
|
71
|
+
def MixSortedF(X1, X2, t):
|
|
72
|
+
X = []
|
|
73
|
+
|
|
74
|
+
# Build lookup tables by frequency
|
|
75
|
+
A = {c["freq"]: c for c in X1}
|
|
76
|
+
B = {c["freq"]: c for c in X2}
|
|
77
|
+
|
|
78
|
+
# Union of all frequencies
|
|
79
|
+
freqs = set(A.keys()) | set(B.keys())
|
|
80
|
+
|
|
81
|
+
for k in freqs:
|
|
82
|
+
c1 = A.get(k)
|
|
83
|
+
c2 = B.get(k)
|
|
84
|
+
|
|
85
|
+
# Zero-fill if missing
|
|
86
|
+
re1 = c1["re"] if c1 else 0.0
|
|
87
|
+
im1 = c1["im"] if c1 else 0.0
|
|
88
|
+
|
|
89
|
+
re2 = c2["re"] if c2 else 0.0
|
|
90
|
+
im2 = c2["im"] if c2 else 0.0
|
|
91
|
+
|
|
92
|
+
# Linear interpolation
|
|
93
|
+
re = (1.0 - t) * re1 + t * re2
|
|
94
|
+
im = (1.0 - t) * im1 + t * im2
|
|
95
|
+
|
|
96
|
+
X.append({
|
|
97
|
+
"re": re,
|
|
98
|
+
"im": im,
|
|
99
|
+
"freq": k
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
return X
|
|
103
|
+
|
|
104
|
+
def epiCycle(x, y, timer, rotation, fourier:iter):
|
|
105
|
+
_x = x
|
|
106
|
+
_y = y
|
|
107
|
+
for i in range(len(fourier)):
|
|
108
|
+
re = fourier[i]["re"]
|
|
109
|
+
im = fourier[i]["im"]
|
|
110
|
+
freq = fourier[i]["freq"] #i if i <= len(fourier)//2 else i - len(fourier)
|
|
111
|
+
radius = math.sqrt(re * re + im * im)
|
|
112
|
+
phase = math.atan2(im, re)
|
|
113
|
+
_x += radius * math.cos(freq * timer + phase + rotation)
|
|
114
|
+
_y += radius * math.sin(freq * timer + phase + rotation)
|
|
115
|
+
|
|
116
|
+
return (_x,_y)
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from PIL import Image
|
|
3
|
+
import moderngl
|
|
4
|
+
from pyrr import Matrix44
|
|
5
|
+
import math
|
|
6
|
+
from typing import Union
|
|
7
|
+
from .utils import FakeUniform, EXT_MAP
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
def particle(i, num):
|
|
11
|
+
|
|
12
|
+
x = np.random.uniform(-0.5, 0.5)
|
|
13
|
+
y = np.random.uniform(-0.5, 0.5)
|
|
14
|
+
z = 1 #np.random.uniform(-1.0, 1.0)
|
|
15
|
+
|
|
16
|
+
r = np.random.uniform(0.0, 1.0)
|
|
17
|
+
g = np.random.uniform(0.0, 1.0)
|
|
18
|
+
b = np.random.uniform(0.0, 1.0)
|
|
19
|
+
return [x, y, z, r, g, b]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class ParticlesFBO:
|
|
24
|
+
|
|
25
|
+
quad_vertex_shader = '''
|
|
26
|
+
#version 330
|
|
27
|
+
in vec2 in_vert;
|
|
28
|
+
in vec2 in_uv;
|
|
29
|
+
out vec2 UV;
|
|
30
|
+
void main() {
|
|
31
|
+
gl_Position = vec4(in_vert, 0.0, 1.0);
|
|
32
|
+
UV = in_uv;
|
|
33
|
+
}
|
|
34
|
+
'''
|
|
35
|
+
copier_fragment_shader = '''
|
|
36
|
+
#version 330
|
|
37
|
+
out vec4 outputColor;
|
|
38
|
+
in vec2 UV;
|
|
39
|
+
|
|
40
|
+
uniform sampler2D copy_target;
|
|
41
|
+
|
|
42
|
+
void main()
|
|
43
|
+
{
|
|
44
|
+
vec4 copy = texture(copy_target,UV);
|
|
45
|
+
outputColor = copy;
|
|
46
|
+
}
|
|
47
|
+
'''
|
|
48
|
+
|
|
49
|
+
def __init__(self, ctx, size, vertex_shader, fragment_shader, transform_shader, enable_backbuffer = False, rest_time = 0.0, num = 10000, particle_func = None):
|
|
50
|
+
self.ctx = ctx
|
|
51
|
+
self.size = size
|
|
52
|
+
self.enable_backbuffer = enable_backbuffer
|
|
53
|
+
|
|
54
|
+
self.texture = self.ctx.texture(self.size, components=4, dtype="f4")
|
|
55
|
+
self.fbo = self.ctx.framebuffer(self.texture)
|
|
56
|
+
self.prog = self.ctx.program(
|
|
57
|
+
vertex_shader=vertex_shader, fragment_shader=fragment_shader)
|
|
58
|
+
|
|
59
|
+
vertex_data = np.array([
|
|
60
|
+
# x, y, z, u, v
|
|
61
|
+
-1.0, -1.0, 0.0, 0.0, 0.0,
|
|
62
|
+
+1.0, -1.0, 0.0, 1.0, 0.0,
|
|
63
|
+
-1.0, +1.0, 0.0, 0.0, 1.0,
|
|
64
|
+
+1.0, +1.0, 0.0, 1.0, 1.0,
|
|
65
|
+
]).astype(np.float32)
|
|
66
|
+
|
|
67
|
+
content = [(
|
|
68
|
+
self.ctx.buffer(vertex_data),
|
|
69
|
+
'3f 2f',
|
|
70
|
+
'in_vert', 'in_uv'
|
|
71
|
+
)]
|
|
72
|
+
|
|
73
|
+
self.transform_prog = self.ctx.program(
|
|
74
|
+
vertex_shader = transform_shader,
|
|
75
|
+
varyings = ['out_pos', 'out_col'])
|
|
76
|
+
|
|
77
|
+
self.particle_number = num
|
|
78
|
+
|
|
79
|
+
self.particle_func = particle_func
|
|
80
|
+
|
|
81
|
+
if callable(self.particle_func):
|
|
82
|
+
particles = np.array([self.particle_func(i, self.particle_number) for i in range(self.particle_number)], dtype='f4')
|
|
83
|
+
else:
|
|
84
|
+
particles = np.array([particle(i, self.particle_number) for i in range(self.particle_number)], dtype='f4')
|
|
85
|
+
|
|
86
|
+
self.vbo1 = self.ctx.buffer(particles.tobytes())
|
|
87
|
+
self.vbo2 = self.ctx.buffer(reserve=self.vbo1.size)
|
|
88
|
+
|
|
89
|
+
self.vao1 = self.ctx.vertex_array(self.transform_prog, [(self.vbo1, '3f 3f', 'in_pos', 'in_col')])
|
|
90
|
+
self.vao2 = self.ctx.vertex_array(self.transform_prog, [(self.vbo2, '3f 3f', 'in_pos', 'in_col')])
|
|
91
|
+
self.render_vao = self.ctx.vertex_array(self.prog, [(self.vbo1, '3f 3f', 'in_vert', 'in_col')])
|
|
92
|
+
|
|
93
|
+
self.aspect_ratio = self.size[0] / self.size[1]
|
|
94
|
+
self.mvp = self.prog.get('Mvp', FakeUniform())
|
|
95
|
+
self.campos = self.transform_prog.get('CAMPOS', FakeUniform())
|
|
96
|
+
self.real_cam = self.prog.get('CAMPOS', FakeUniform())
|
|
97
|
+
self.time = self.prog.get('time', FakeUniform())
|
|
98
|
+
self.resolution = self.prog.get('resolution', FakeUniform())
|
|
99
|
+
|
|
100
|
+
if self.resolution:
|
|
101
|
+
self.set_uniform("resolution", self.size)
|
|
102
|
+
else:
|
|
103
|
+
print(f'unable to set resolution uniform {self.resolution}')
|
|
104
|
+
|
|
105
|
+
if self.enable_backbuffer == True:
|
|
106
|
+
self.bck_texture = self.ctx.texture(
|
|
107
|
+
self.size, components=4, dtype="f4")
|
|
108
|
+
self.bck_fbo = self.ctx.framebuffer(self.bck_texture)
|
|
109
|
+
self.bck_prog = self.ctx.program(
|
|
110
|
+
vertex_shader=self.quad_vertex_shader, fragment_shader=self.copier_fragment_shader)
|
|
111
|
+
self.bck_vao = self.ctx.vertex_array(self.bck_prog, content)
|
|
112
|
+
|
|
113
|
+
self.backbuffer_uniform = self.prog.get(
|
|
114
|
+
'backbuffer', FakeUniform())
|
|
115
|
+
self.copy_target_uniform = self.bck_prog.get(
|
|
116
|
+
'copy_target', FakeUniform())
|
|
117
|
+
else:
|
|
118
|
+
self.bck_fbo = None
|
|
119
|
+
|
|
120
|
+
self.textures = {}
|
|
121
|
+
self.texture_uniforms = {}
|
|
122
|
+
self.textures_ = {}
|
|
123
|
+
self.texture_uniforms_ = {}
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
self.proj = Matrix44.perspective_projection(45.0, self.aspect_ratio, 0.1, 1000.0)
|
|
127
|
+
self.lookat = Matrix44.look_at(
|
|
128
|
+
(0.0, 0.0, 3.0),
|
|
129
|
+
(0.0, 0.0, 0.0),
|
|
130
|
+
(0.0, 1.0, 0.0),
|
|
131
|
+
)
|
|
132
|
+
self.set_cam((0.0, 0.0, 3.0))
|
|
133
|
+
|
|
134
|
+
self.clear_col = (0.0,0.0,0.0)
|
|
135
|
+
|
|
136
|
+
def set_clear_col(self, col:tuple = (0.0,0.0,0.0)):
|
|
137
|
+
self.clear_col = col
|
|
138
|
+
|
|
139
|
+
def release_all(self):
|
|
140
|
+
if hasattr(self, 'bck_texture'):
|
|
141
|
+
self.bck_texture.release()
|
|
142
|
+
for (name, texture) in self.textures.items():
|
|
143
|
+
self.textures[name].release()
|
|
144
|
+
self.textures_[name].release()
|
|
145
|
+
self.texture.release()
|
|
146
|
+
|
|
147
|
+
def set_uniform(self, name, value):
|
|
148
|
+
uniform = self.prog.get(name, FakeUniform())
|
|
149
|
+
uniform.value = value
|
|
150
|
+
|
|
151
|
+
uniform = self.transform_prog.get(name, FakeUniform())
|
|
152
|
+
uniform.value = value
|
|
153
|
+
|
|
154
|
+
def set_texture_uniform_from_path(self, path, name, id):
|
|
155
|
+
im = Image.open(path)
|
|
156
|
+
im_WIDTH, im_HEIGHT, im_DATA = im.size[0], im.size[1], im.convert(
|
|
157
|
+
'RGBA').tobytes("raw", "RGBA", 0, -1)
|
|
158
|
+
texture = self.ctx.texture(
|
|
159
|
+
(im.size[0], im.size[1]), components=4, data=im_DATA)
|
|
160
|
+
self.set_texture_uniform(texture, name, id)
|
|
161
|
+
|
|
162
|
+
def set_texture_uniform(self, texture, name, id):
|
|
163
|
+
if name not in self.textures:
|
|
164
|
+
self.texture_uniforms[name] = self.prog.get(name, FakeUniform())
|
|
165
|
+
self.textures[name] = texture
|
|
166
|
+
|
|
167
|
+
self.texture_uniforms_[name] = self.transform_prog.get(name, FakeUniform())
|
|
168
|
+
self.textures_[name] = texture
|
|
169
|
+
self.texture_uniforms[name].value = id
|
|
170
|
+
self.texture_uniforms_[name].value = id
|
|
171
|
+
|
|
172
|
+
def clear(self):
|
|
173
|
+
self.fbo.clear()
|
|
174
|
+
if self.bck_fbo:
|
|
175
|
+
self.bck_fbo.clear()
|
|
176
|
+
|
|
177
|
+
def extract_img(self, path: Union[str, Path], format='png', quality=100):
|
|
178
|
+
path_format = Path(path).suffix.lower().lstrip(".")
|
|
179
|
+
path_format = EXT_MAP.get(path_format)
|
|
180
|
+
if format != path_format:
|
|
181
|
+
format = path_format
|
|
182
|
+
|
|
183
|
+
if format == 'jpeg':
|
|
184
|
+
image = Image.frombytes('RGB', self.size, self.fbo.read(components=3))
|
|
185
|
+
else:
|
|
186
|
+
image = Image.frombytes('RGBA', self.size, self.fbo.read(components=4))
|
|
187
|
+
image = image.transpose(Image.FLIP_TOP_BOTTOM)
|
|
188
|
+
image.save(str(path), format=format, quality=quality)
|
|
189
|
+
|
|
190
|
+
def extract_np_img(self) -> np.ndarray:
|
|
191
|
+
img_buf = self.fbo.read()
|
|
192
|
+
img_width = self.fbo.size[0]
|
|
193
|
+
img_height = self.fbo.size[1]
|
|
194
|
+
img = np.frombuffer(img_buf, np.uint8).reshape(img_height, img_width, 3)[::-1]
|
|
195
|
+
return img
|
|
196
|
+
|
|
197
|
+
# def extract_vao(self, path, format='jpeg'):
|
|
198
|
+
# image = Image.frombytes('LA', (self.particle_number, self.particle_number), self.vbo1.read(size=-1,offset=2))
|
|
199
|
+
# image = image.transpose(Image.FLIP_TOP_BOTTOM)
|
|
200
|
+
# image.save(path, format=format)
|
|
201
|
+
|
|
202
|
+
def set_cam(self, _campos, _lookat = (0,0,0), _up = (0,1,0)):
|
|
203
|
+
self.campos.value = _campos
|
|
204
|
+
self.real_cam.value = _campos
|
|
205
|
+
self.lookat = Matrix44.look_at(
|
|
206
|
+
_campos,
|
|
207
|
+
_lookat,
|
|
208
|
+
_up,
|
|
209
|
+
)
|
|
210
|
+
self.mvp.write((self.proj * self.lookat).astype('f4'))
|
|
211
|
+
|
|
212
|
+
def render(self):
|
|
213
|
+
self.fbo.use()
|
|
214
|
+
|
|
215
|
+
self.fbo.clear(*self.clear_col)
|
|
216
|
+
self.ctx.enable(moderngl.PROGRAM_POINT_SIZE)
|
|
217
|
+
self.ctx.enable(moderngl.BLEND)
|
|
218
|
+
#self.ctx.enable(moderngl.DEPTH_TEST)
|
|
219
|
+
self.ctx.blend_equation = moderngl.FUNC_ADD, moderngl.MAX
|
|
220
|
+
|
|
221
|
+
for (name, texture) in self.textures.items():
|
|
222
|
+
# print(name, texture, self.texture_uniforms[name].value)
|
|
223
|
+
textureid = self.texture_uniforms[name].value
|
|
224
|
+
self.textures[name].use(textureid)
|
|
225
|
+
self.texture_uniforms[name].value = textureid
|
|
226
|
+
|
|
227
|
+
textureid_ = self.texture_uniforms_[name].value
|
|
228
|
+
self.textures_[name].use(textureid_)
|
|
229
|
+
self.texture_uniforms_[name].value = textureid_
|
|
230
|
+
|
|
231
|
+
if self.enable_backbuffer == True:
|
|
232
|
+
self.bck_texture.use(1)
|
|
233
|
+
self.backbuffer_uniform.value = 1
|
|
234
|
+
|
|
235
|
+
self.vao1.transform(self.vbo2, moderngl.POINTS, self.particle_number)
|
|
236
|
+
self.ctx.copy_buffer(self.vbo1, self.vbo2)
|
|
237
|
+
self.render_vao.render(moderngl.POINTS, self.particle_number)
|
|
238
|
+
|
|
239
|
+
if self.enable_backbuffer == True:
|
|
240
|
+
self.bck_fbo.use()
|
|
241
|
+
|
|
242
|
+
self.texture.use(0)
|
|
243
|
+
self.copy_target_uniform.value = 0
|
|
244
|
+
|
|
245
|
+
self.bck_vao.render(moderngl.TRIANGLE_STRIP)
|