phg-vis 1.2.1__tar.gz → 1.3.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.
- {phg_vis-1.2.1/phg_vis.egg-info → phg_vis-1.3.0}/PKG-INFO +1 -1
- phg_vis-1.3.0/phg/__init__.py +82 -0
- phg_vis-1.3.0/phg/phg_to_shader.py +433 -0
- phg_vis-1.3.0/phg/shader_render.py +425 -0
- phg_vis-1.3.0/phg/vis/GCU.dll +0 -0
- {phg_vis-1.2.1 → phg_vis-1.3.0}/phg/vis/vis.exe +0 -0
- {phg_vis-1.2.1 → phg_vis-1.3.0/phg_vis.egg-info}/PKG-INFO +1 -1
- {phg_vis-1.2.1 → phg_vis-1.3.0}/phg_vis.egg-info/SOURCES.txt +2 -0
- {phg_vis-1.2.1 → phg_vis-1.3.0}/setup.py +1 -1
- phg_vis-1.2.1/phg/__init__.py +0 -4
- phg_vis-1.2.1/phg/vis/GCU.dll +0 -0
- {phg_vis-1.2.1 → phg_vis-1.3.0}/LICENSE +0 -0
- {phg_vis-1.2.1 → phg_vis-1.3.0}/MANIFEST.in +0 -0
- {phg_vis-1.2.1 → phg_vis-1.3.0}/README.md +0 -0
- {phg_vis-1.2.1 → phg_vis-1.3.0}/phg/vis/dragpad.exe +0 -0
- {phg_vis-1.2.1 → phg_vis-1.3.0}/phg/vis/dragpad_config.ini +0 -0
- {phg_vis-1.2.1 → phg_vis-1.3.0}/phg/vis/freeglut.dll +0 -0
- {phg_vis-1.2.1 → phg_vis-1.3.0}/phg/vis/imgui/main.lua +0 -0
- {phg_vis-1.2.1 → phg_vis-1.3.0}/phg/vis/lua.dll +0 -0
- {phg_vis-1.2.1 → phg_vis-1.3.0}/phg/vis/script.phg +0 -0
- {phg_vis-1.2.1 → phg_vis-1.3.0}/phg/vis/sqlite3.dll +0 -0
- {phg_vis-1.2.1 → phg_vis-1.3.0}/phg/vis/vis.ini +0 -0
- {phg_vis-1.2.1 → phg_vis-1.3.0}/phg/vis/zlib1.dll +0 -0
- {phg_vis-1.2.1 → phg_vis-1.3.0}/phg/visphg.py +0 -0
- {phg_vis-1.2.1 → phg_vis-1.3.0}/phg_vis.egg-info/dependency_links.txt +0 -0
- {phg_vis-1.2.1 → phg_vis-1.3.0}/phg_vis.egg-info/top_level.txt +0 -0
- {phg_vis-1.2.1 → phg_vis-1.3.0}/setup.cfg +0 -0
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"""
|
|
2
|
+
__init__.py
|
|
3
|
+
PHG主库 - 集成PHG到Shader转换功能
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from .visphg import vis, image
|
|
7
|
+
from .shader_render import ShaderRenderer, render_shader
|
|
8
|
+
from .phg_to_shader import PHGToShaderConverter, phg_to_shader
|
|
9
|
+
|
|
10
|
+
# 扩展ShaderRenderer类以支持PHG直接渲染
|
|
11
|
+
class PHGShaderRenderer(ShaderRenderer):
|
|
12
|
+
"""支持PHG直接渲染的Shader渲染器"""
|
|
13
|
+
|
|
14
|
+
def render_phg(self, phg_script: str, duration=0, interactive=True):
|
|
15
|
+
"""
|
|
16
|
+
渲染PHG脚本
|
|
17
|
+
|
|
18
|
+
参数:
|
|
19
|
+
phg_script: PHG脚本代码
|
|
20
|
+
duration: 渲染持续时间
|
|
21
|
+
interactive: 是否允许交互
|
|
22
|
+
|
|
23
|
+
返回:
|
|
24
|
+
success: 是否成功渲染
|
|
25
|
+
"""
|
|
26
|
+
converter = PHGToShaderConverter()
|
|
27
|
+
shader_code = converter.generate_shader_code(phg_script)
|
|
28
|
+
|
|
29
|
+
if not shader_code:
|
|
30
|
+
print("错误: PHG转换失败")
|
|
31
|
+
return False
|
|
32
|
+
|
|
33
|
+
return self.render_shader(shader_code, duration, interactive)
|
|
34
|
+
|
|
35
|
+
# 便捷函数
|
|
36
|
+
def render_phg(phg_script: str, width=800, height=600, duration=0, title="PHG Renderer"):
|
|
37
|
+
"""
|
|
38
|
+
直接渲染PHG脚本
|
|
39
|
+
|
|
40
|
+
参数:
|
|
41
|
+
phg_script: PHG脚本代码
|
|
42
|
+
width: 窗口宽度
|
|
43
|
+
height: 窗口高度
|
|
44
|
+
duration: 渲染持续时间
|
|
45
|
+
title: 窗口标题
|
|
46
|
+
"""
|
|
47
|
+
renderer = PHGShaderRenderer(width, height, title)
|
|
48
|
+
success = renderer.render_phg(phg_script, duration)
|
|
49
|
+
renderer.close()
|
|
50
|
+
return success
|
|
51
|
+
|
|
52
|
+
def convert_phg_to_shader(phg_script: str) -> str:
|
|
53
|
+
"""
|
|
54
|
+
将PHG脚本转换为GLSL着色器代码
|
|
55
|
+
|
|
56
|
+
参数:
|
|
57
|
+
phg_script: PHG脚本
|
|
58
|
+
|
|
59
|
+
返回:
|
|
60
|
+
shader_code: GLSL着色器代码
|
|
61
|
+
"""
|
|
62
|
+
return phg_to_shader(phg_script)
|
|
63
|
+
|
|
64
|
+
__all__ = [
|
|
65
|
+
'vis',
|
|
66
|
+
'image',
|
|
67
|
+
'ShaderRenderer',
|
|
68
|
+
'render_shader',
|
|
69
|
+
'PHGToShaderConverter',
|
|
70
|
+
'PHGShaderRenderer',
|
|
71
|
+
'render_phg',
|
|
72
|
+
'convert_phg_to_shader',
|
|
73
|
+
'phg_to_shader'
|
|
74
|
+
]
|
|
75
|
+
|
|
76
|
+
__version__ = "0.2.0"
|
|
77
|
+
__author__ = "PHG Development Team"
|
|
78
|
+
__description__ = "Python Graphics Library with PHG to Shader conversion"
|
|
79
|
+
|
|
80
|
+
# 包初始化信息
|
|
81
|
+
print(f"PHG {__version__} - {__description__}")
|
|
82
|
+
print("PHG to Shader转换器已加载")
|
|
@@ -0,0 +1,433 @@
|
|
|
1
|
+
"""
|
|
2
|
+
phg_to_shader.py
|
|
3
|
+
PHG to Shader Converter Core Library
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import re
|
|
7
|
+
import math
|
|
8
|
+
from typing import Dict, List, Any, Tuple
|
|
9
|
+
|
|
10
|
+
class PHGToShaderConverter:
|
|
11
|
+
"""PHG to Shader converter"""
|
|
12
|
+
|
|
13
|
+
def __init__(self):
|
|
14
|
+
self.nodes = {}
|
|
15
|
+
self.geometry_types = {
|
|
16
|
+
'cylinder': 'CYLINDER',
|
|
17
|
+
'sphere': 'SPHERE',
|
|
18
|
+
'cube': 'BOX',
|
|
19
|
+
'box': 'BOX',
|
|
20
|
+
'cone': 'CONE',
|
|
21
|
+
'torus': 'TORUS',
|
|
22
|
+
'plane': 'PLANE'
|
|
23
|
+
}
|
|
24
|
+
self.primitive_count = 0
|
|
25
|
+
|
|
26
|
+
def parse_phg(self, phg_script: str) -> Dict[str, Any]:
|
|
27
|
+
"""Parse PHG script and extract node information"""
|
|
28
|
+
# Remove comments
|
|
29
|
+
phg_clean = re.sub(r'#.*$', '', phg_script, flags=re.MULTILINE)
|
|
30
|
+
|
|
31
|
+
# Extract node definitions
|
|
32
|
+
node_pattern = r'(\w+)\s*\{([^}]*)\}'
|
|
33
|
+
nodes = {}
|
|
34
|
+
|
|
35
|
+
for match in re.finditer(node_pattern, phg_clean):
|
|
36
|
+
node_name = match.group(1)
|
|
37
|
+
node_content = match.group(2)
|
|
38
|
+
|
|
39
|
+
# Parse node properties
|
|
40
|
+
properties = self._parse_node_properties(node_content)
|
|
41
|
+
nodes[node_name] = properties
|
|
42
|
+
|
|
43
|
+
return nodes
|
|
44
|
+
|
|
45
|
+
def _parse_node_properties(self, content: str) -> Dict[str, Any]:
|
|
46
|
+
"""Parse node properties"""
|
|
47
|
+
properties = {}
|
|
48
|
+
|
|
49
|
+
# Property pattern: property:value1,value2;
|
|
50
|
+
prop_pattern = r'(\w+):([^;]+);'
|
|
51
|
+
|
|
52
|
+
for match in re.finditer(prop_pattern, content):
|
|
53
|
+
prop_name = match.group(1)
|
|
54
|
+
prop_value = match.group(2).strip()
|
|
55
|
+
|
|
56
|
+
# Handle different property types
|
|
57
|
+
if prop_name == 'md':
|
|
58
|
+
# Geometry definition
|
|
59
|
+
properties['geometry'] = self._parse_geometry(prop_value)
|
|
60
|
+
elif prop_name in ['xyz', 'sxyz', 'hpr', 'rot']:
|
|
61
|
+
# Vector values
|
|
62
|
+
properties[prop_name] = self._parse_vector(prop_value)
|
|
63
|
+
elif prop_name in ['x', 'y', 'z', 'rx', 'ry', 'rz', 's']:
|
|
64
|
+
# Scalar values
|
|
65
|
+
properties[prop_name] = float(prop_value)
|
|
66
|
+
elif prop_name == 'rgb':
|
|
67
|
+
# Color values
|
|
68
|
+
properties['color'] = self._parse_color(prop_value)
|
|
69
|
+
|
|
70
|
+
return properties
|
|
71
|
+
|
|
72
|
+
def _parse_geometry(self, geom_str: str) -> Dict[str, Any]:
|
|
73
|
+
"""Parse geometry definition"""
|
|
74
|
+
parts = geom_str.strip().split()
|
|
75
|
+
if not parts:
|
|
76
|
+
return {}
|
|
77
|
+
|
|
78
|
+
geom_type = parts[0]
|
|
79
|
+
|
|
80
|
+
geometry = {'type': geom_type}
|
|
81
|
+
|
|
82
|
+
try:
|
|
83
|
+
if geom_type == 'cylinder':
|
|
84
|
+
geometry['radius'] = float(parts[1])
|
|
85
|
+
geometry['height'] = float(parts[2])
|
|
86
|
+
elif geom_type == 'sphere':
|
|
87
|
+
geometry['radius'] = float(parts[1])
|
|
88
|
+
elif geom_type == 'cube':
|
|
89
|
+
geometry['size'] = [float(parts[1])] * 3
|
|
90
|
+
elif geom_type == 'box':
|
|
91
|
+
if len(parts) >= 4:
|
|
92
|
+
geometry['size'] = [float(parts[1]), float(parts[2]), float(parts[3])]
|
|
93
|
+
else:
|
|
94
|
+
geometry['size'] = [1.0, 1.0, 1.0]
|
|
95
|
+
elif geom_type == 'cone':
|
|
96
|
+
geometry['radius1'] = float(parts[1])
|
|
97
|
+
geometry['radius2'] = float(parts[2]) if len(parts) > 2 else 0.0
|
|
98
|
+
geometry['height'] = float(parts[3]) if len(parts) > 3 else 1.0
|
|
99
|
+
elif geom_type == 'torus':
|
|
100
|
+
geometry['radius1'] = float(parts[1])
|
|
101
|
+
geometry['radius2'] = float(parts[2]) if len(parts) > 2 else 0.3
|
|
102
|
+
elif geom_type == 'plane':
|
|
103
|
+
geometry['normal'] = [float(parts[1]), float(parts[2]), float(parts[3])] if len(parts) > 3 else [0, 1, 0]
|
|
104
|
+
except (IndexError, ValueError):
|
|
105
|
+
print(f"Warning: Geometry parameter parsing error: {geom_str}")
|
|
106
|
+
|
|
107
|
+
return geometry
|
|
108
|
+
|
|
109
|
+
def _parse_vector(self, vec_str: str) -> List[float]:
|
|
110
|
+
"""Parse vector values"""
|
|
111
|
+
try:
|
|
112
|
+
return [float(x.strip()) for x in vec_str.split(',')]
|
|
113
|
+
except ValueError:
|
|
114
|
+
return [0.0, 0.0, 0.0]
|
|
115
|
+
|
|
116
|
+
def _parse_color(self, color_str: str) -> List[float]:
|
|
117
|
+
"""Parse color values and normalize to 0-1"""
|
|
118
|
+
try:
|
|
119
|
+
rgb = [float(x.strip()) for x in color_str.split(',')]
|
|
120
|
+
return [c / 255.0 for c in rgb]
|
|
121
|
+
except ValueError:
|
|
122
|
+
return [0.7, 0.7, 0.7]
|
|
123
|
+
|
|
124
|
+
def geometry_to_sdf_params(self, geometry: Dict[str, Any], transform: Dict[str, Any]) -> List[str]:
|
|
125
|
+
"""Convert geometry to SDF parameter array"""
|
|
126
|
+
geom_type = geometry.get('type', 'sphere')
|
|
127
|
+
color = transform.get('color', [0.7, 0.7, 0.7])
|
|
128
|
+
position = transform.get('xyz', [0, 0, 0])
|
|
129
|
+
|
|
130
|
+
# Determine SDF type
|
|
131
|
+
sdf_type = self.geometry_types.get(geom_type, 'SPHERE')
|
|
132
|
+
|
|
133
|
+
params = []
|
|
134
|
+
|
|
135
|
+
# params[0]: position + type
|
|
136
|
+
params.append(f"vec4({position[0]:.3f}, {position[1]:.3f}, {position[2]:.3f}, float({sdf_type}))")
|
|
137
|
+
|
|
138
|
+
# params[1]: size parameters
|
|
139
|
+
if geom_type == 'cylinder':
|
|
140
|
+
radius = geometry.get('radius', 1.0)
|
|
141
|
+
height = geometry.get('height', 2.0)
|
|
142
|
+
params.append(f"vec4({radius:.3f}, {height:.3f}, 0.0, 0.0)")
|
|
143
|
+
elif geom_type == 'sphere':
|
|
144
|
+
radius = geometry.get('radius', 1.0)
|
|
145
|
+
params.append(f"vec4({radius:.3f}, 0.0, 0.0, 0.0)")
|
|
146
|
+
elif geom_type in ['cube', 'box']:
|
|
147
|
+
size = geometry.get('size', [1.0, 1.0, 1.0])
|
|
148
|
+
params.append(f"vec4({size[0]:.3f}, {size[1]:.3f}, {size[2]:.3f}, 0.0)")
|
|
149
|
+
elif geom_type == 'cone':
|
|
150
|
+
r1 = geometry.get('radius1', 1.0)
|
|
151
|
+
r2 = geometry.get('radius2', 0.0)
|
|
152
|
+
h = geometry.get('height', 2.0)
|
|
153
|
+
params.append(f"vec4({r1:.3f}, {h:.3f}, {r2:.3f}, 0.0)")
|
|
154
|
+
elif geom_type == 'torus':
|
|
155
|
+
r1 = geometry.get('radius1', 1.0)
|
|
156
|
+
r2 = geometry.get('radius2', 0.3)
|
|
157
|
+
params.append(f"vec4({r1:.3f}, {r2:.3f}, 0.0, 0.0)")
|
|
158
|
+
elif geom_type == 'plane':
|
|
159
|
+
normal = geometry.get('normal', [0, 1, 0])
|
|
160
|
+
params.append(f"vec4({normal[0]:.1f}, {normal[1]:.1f}, {normal[2]:.1f}, 0.0)")
|
|
161
|
+
else:
|
|
162
|
+
params.append("vec4(1.0, 0.0, 0.0, 0.0)")
|
|
163
|
+
|
|
164
|
+
# params[2]: rotation/direction
|
|
165
|
+
rotation = transform.get('hpr', transform.get('rot', [0, 0, 0]))
|
|
166
|
+
params.append(f"vec4({rotation[0]:.1f}, {rotation[1]:.1f}, {rotation[2]:.1f}, 0.0)")
|
|
167
|
+
|
|
168
|
+
# params[3]: color
|
|
169
|
+
params.append(f"vec4({color[0]:.3f}, {color[1]:.3f}, {color[2]:.3f}, 0.0)")
|
|
170
|
+
|
|
171
|
+
return params
|
|
172
|
+
|
|
173
|
+
def generate_shader_code(self, phg_script: str) -> str:
|
|
174
|
+
"""Generate complete GLSL shader code"""
|
|
175
|
+
self.nodes = self.parse_phg(phg_script)
|
|
176
|
+
|
|
177
|
+
# Collect all geometries
|
|
178
|
+
primitives = []
|
|
179
|
+
for node_name, properties in self.nodes.items():
|
|
180
|
+
if 'geometry' in properties:
|
|
181
|
+
primitives.append((node_name, properties))
|
|
182
|
+
|
|
183
|
+
self.primitive_count = len(primitives)
|
|
184
|
+
|
|
185
|
+
if self.primitive_count == 0:
|
|
186
|
+
return self._create_empty_shader()
|
|
187
|
+
|
|
188
|
+
# Generate shader code
|
|
189
|
+
shader_code = self._create_shader_template()
|
|
190
|
+
shader_code = self._insert_primitives(shader_code, primitives)
|
|
191
|
+
|
|
192
|
+
return shader_code
|
|
193
|
+
|
|
194
|
+
def _create_empty_shader(self) -> str:
|
|
195
|
+
"""Create shader for empty scene"""
|
|
196
|
+
return """
|
|
197
|
+
#version 330 core
|
|
198
|
+
out vec4 fragColor;
|
|
199
|
+
uniform float time;
|
|
200
|
+
uniform vec2 resolution;
|
|
201
|
+
uniform vec2 mouse;
|
|
202
|
+
uniform vec3 cameraPosition;
|
|
203
|
+
uniform vec3 cameraTarget;
|
|
204
|
+
uniform vec3 cameraUp;
|
|
205
|
+
uniform float cameraFov;
|
|
206
|
+
|
|
207
|
+
void main() {
|
|
208
|
+
vec2 uv = gl_FragCoord.xy / resolution.xy;
|
|
209
|
+
vec3 color = vec3(0.1, 0.2, 0.4);
|
|
210
|
+
fragColor = vec4(color, 1.0);
|
|
211
|
+
}
|
|
212
|
+
"""
|
|
213
|
+
|
|
214
|
+
def _create_shader_template(self) -> str:
|
|
215
|
+
"""Create shader template with camera support"""
|
|
216
|
+
return """
|
|
217
|
+
#version 330 core
|
|
218
|
+
out vec4 fragColor;
|
|
219
|
+
|
|
220
|
+
uniform float time;
|
|
221
|
+
uniform vec2 resolution;
|
|
222
|
+
uniform vec2 mouse;
|
|
223
|
+
uniform vec3 cameraPosition;
|
|
224
|
+
uniform vec3 cameraTarget;
|
|
225
|
+
uniform vec3 cameraUp;
|
|
226
|
+
uniform float cameraFov;
|
|
227
|
+
|
|
228
|
+
#define MAX_STEPS 100
|
|
229
|
+
#define MAX_DIST 200.0
|
|
230
|
+
#define SURF_DIST 0.001
|
|
231
|
+
|
|
232
|
+
// Primitive type enum
|
|
233
|
+
#define SPHERE 0
|
|
234
|
+
#define BOX 1
|
|
235
|
+
#define CYLINDER 2
|
|
236
|
+
#define TORUS 3
|
|
237
|
+
#define PLANE 4
|
|
238
|
+
#define CONE 5
|
|
239
|
+
|
|
240
|
+
// Primitive parameter definitions
|
|
241
|
+
{primitive_definitions}
|
|
242
|
+
|
|
243
|
+
// SDF function definitions
|
|
244
|
+
float sdfSphere(vec3 p, vec4 params) {
|
|
245
|
+
return length(p) - params.x;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
float sdfBox(vec3 p, vec4 params) {
|
|
249
|
+
vec3 b = params.xyz;
|
|
250
|
+
vec3 q = abs(p) - b;
|
|
251
|
+
return length(max(q, 0.0)) + min(max(q.x, max(q.y, q.z)), 0.0);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
float sdfCylinder(vec3 p, vec4 params) {
|
|
255
|
+
vec2 d = abs(vec2(length(p.xz), p.y)) - vec2(params.x, params.y);
|
|
256
|
+
return min(max(d.x, d.y), 0.0) + length(max(d, 0.0));
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
float sdfTorus(vec3 p, vec4 params) {
|
|
260
|
+
vec2 q = vec2(length(p.xz) - params.x, p.y);
|
|
261
|
+
return length(q) - params.y;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
float sdfPlane(vec3 p, vec4 params) {
|
|
265
|
+
return dot(p, normalize(params.xyz));
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
float sdfCone(vec3 p, vec4 params) {
|
|
269
|
+
// params.x = base radius, params.y = height, params.z = top radius
|
|
270
|
+
vec2 q = vec2(length(p.xz), -p.y);
|
|
271
|
+
float d = length(q);
|
|
272
|
+
float angle = atan(params.z - params.x, params.y);
|
|
273
|
+
float c = cos(angle);
|
|
274
|
+
float s = sin(angle);
|
|
275
|
+
return max(dot(q, vec2(c, s)), -p.y - params.y);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Unified SDF dispatcher function
|
|
279
|
+
float mapPrimitive(vec3 p, vec4 params[4]) {
|
|
280
|
+
int type = int(params[0].w);
|
|
281
|
+
|
|
282
|
+
if (type == SPHERE) return sdfSphere(p - params[0].xyz, params[1]);
|
|
283
|
+
if (type == BOX) return sdfBox(p - params[0].xyz, params[1]);
|
|
284
|
+
if (type == CYLINDER) return sdfCylinder(p - params[0].xyz, params[1]);
|
|
285
|
+
if (type == TORUS) return sdfTorus(p - params[0].xyz, params[1]);
|
|
286
|
+
if (type == PLANE) return sdfPlane(p - params[0].xyz, params[1]);
|
|
287
|
+
if (type == CONE) return sdfCone(p - params[0].xyz, params[1]);
|
|
288
|
+
|
|
289
|
+
return 1000.0;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Scene SDF
|
|
293
|
+
float mapScene(vec3 p) {
|
|
294
|
+
float minDist = 1000.0;
|
|
295
|
+
|
|
296
|
+
{primitive_calls}
|
|
297
|
+
|
|
298
|
+
return minDist;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Normal calculation
|
|
302
|
+
vec3 calculateNormal(vec3 p) {
|
|
303
|
+
vec2 e = vec2(0.001, 0.0);
|
|
304
|
+
return normalize(vec3(
|
|
305
|
+
mapScene(p + e.xyy) - mapScene(p - e.xyy),
|
|
306
|
+
mapScene(p + e.yxy) - mapScene(p - e.yxy),
|
|
307
|
+
mapScene(p + e.yyx) - mapScene(p - e.yyx)
|
|
308
|
+
));
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Ray marching
|
|
312
|
+
float rayMarch(vec3 ro, vec3 rd) {
|
|
313
|
+
float depth = 0.0;
|
|
314
|
+
|
|
315
|
+
for (int i = 0; i < MAX_STEPS; i++) {
|
|
316
|
+
vec3 p = ro + depth * rd;
|
|
317
|
+
float dist = mapScene(p);
|
|
318
|
+
depth += dist;
|
|
319
|
+
|
|
320
|
+
if (dist < SURF_DIST || depth > MAX_DIST) break;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
return depth;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// Get hit object color
|
|
327
|
+
vec3 getObjectColor(vec3 p) {
|
|
328
|
+
float minDist = 1000.0;
|
|
329
|
+
vec3 color = vec3(0.5);
|
|
330
|
+
|
|
331
|
+
{color_calls}
|
|
332
|
+
|
|
333
|
+
return color;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// Calculate camera ray direction using camera uniforms
|
|
337
|
+
vec3 getCameraRay(vec2 uv, vec3 camPos, vec3 camTarget, vec3 camUp, float fov) {
|
|
338
|
+
vec3 forward = normalize(camTarget - camPos);
|
|
339
|
+
vec3 right = normalize(cross(forward, camUp));
|
|
340
|
+
vec3 up = normalize(cross(right, forward));
|
|
341
|
+
|
|
342
|
+
float tanFov = tan(radians(fov) * 0.5);
|
|
343
|
+
vec3 rayDir = normalize(forward + right * uv.x * tanFov * (resolution.x / resolution.y) + up * uv.y * tanFov);
|
|
344
|
+
|
|
345
|
+
return rayDir;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
void main() {
|
|
349
|
+
vec2 uv = (gl_FragCoord.xy - 0.5 * resolution.xy) / resolution.y;
|
|
350
|
+
|
|
351
|
+
// Use camera uniforms from Python for ray direction
|
|
352
|
+
vec3 ro = cameraPosition;
|
|
353
|
+
vec3 rd = getCameraRay(uv, cameraPosition, cameraTarget, cameraUp, cameraFov);
|
|
354
|
+
|
|
355
|
+
// Ray marching
|
|
356
|
+
float dist = rayMarch(ro, rd);
|
|
357
|
+
|
|
358
|
+
// Shading
|
|
359
|
+
vec3 color = vec3(0.1, 0.2, 0.4);
|
|
360
|
+
|
|
361
|
+
if (dist < MAX_DIST) {
|
|
362
|
+
vec3 p = ro + rd * dist;
|
|
363
|
+
vec3 normal = calculateNormal(p);
|
|
364
|
+
vec3 objColor = getObjectColor(p);
|
|
365
|
+
|
|
366
|
+
// Simple lighting
|
|
367
|
+
vec3 lightDir = normalize(vec3(1.0, 3.0, 2.0));
|
|
368
|
+
float diff = max(dot(normal, lightDir), 0.2);
|
|
369
|
+
|
|
370
|
+
color = objColor * diff;
|
|
371
|
+
|
|
372
|
+
// Add some ambient occlusion
|
|
373
|
+
float ao = 1.0 - (1.0 / (1.0 + dist * 0.1));
|
|
374
|
+
color *= ao;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
fragColor = vec4(color, 1.0);
|
|
378
|
+
}
|
|
379
|
+
"""
|
|
380
|
+
|
|
381
|
+
def _insert_primitives(self, template: str, primitives: List[Tuple[str, Dict]]) -> str:
|
|
382
|
+
"""Insert primitive definitions and call code"""
|
|
383
|
+
primitive_definitions = []
|
|
384
|
+
primitive_calls = []
|
|
385
|
+
color_calls = []
|
|
386
|
+
|
|
387
|
+
for i, (node_name, properties) in enumerate(primitives):
|
|
388
|
+
geometry = properties['geometry']
|
|
389
|
+
params = self.geometry_to_sdf_params(geometry, properties)
|
|
390
|
+
|
|
391
|
+
# Generate primitive parameter definitions
|
|
392
|
+
primitive_definitions.append(f"// {node_name}")
|
|
393
|
+
for j, param in enumerate(params):
|
|
394
|
+
primitive_definitions.append(f"vec4 prim_{i}_{j} = {param};")
|
|
395
|
+
primitive_definitions.append("")
|
|
396
|
+
|
|
397
|
+
# Generate SDF calls
|
|
398
|
+
primitive_calls.append(f" // {node_name}")
|
|
399
|
+
primitive_calls.append(f" vec4 prim_{i}[4] = vec4[4](prim_{i}_0, prim_{i}_1, prim_{i}_2, prim_{i}_3);")
|
|
400
|
+
primitive_calls.append(f" float dist_{i} = mapPrimitive(p, prim_{i});")
|
|
401
|
+
primitive_calls.append(f" minDist = min(minDist, dist_{i});")
|
|
402
|
+
primitive_calls.append("")
|
|
403
|
+
|
|
404
|
+
# Generate color calls
|
|
405
|
+
color_calls.append(f" // {node_name}")
|
|
406
|
+
color_calls.append(f" vec4 color_prim_{i}[4] = vec4[4](prim_{i}_0, prim_{i}_1, prim_{i}_2, prim_{i}_3);")
|
|
407
|
+
color_calls.append(f" float color_dist_{i} = mapPrimitive(p, color_prim_{i});")
|
|
408
|
+
color_calls.append(f" if (color_dist_{i} < SURF_DIST && color_dist_{i} < minDist) {{")
|
|
409
|
+
color_calls.append(f" minDist = color_dist_{i};")
|
|
410
|
+
color_calls.append(f" color = prim_{i}_3.xyz;")
|
|
411
|
+
color_calls.append(f" }}")
|
|
412
|
+
color_calls.append("")
|
|
413
|
+
|
|
414
|
+
# Replace placeholders in template
|
|
415
|
+
shader_code = template.replace("{primitive_definitions}", "\n".join(primitive_definitions))
|
|
416
|
+
shader_code = shader_code.replace("{primitive_calls}", "\n".join(primitive_calls))
|
|
417
|
+
shader_code = shader_code.replace("{color_calls}", "\n".join(color_calls))
|
|
418
|
+
|
|
419
|
+
return shader_code
|
|
420
|
+
|
|
421
|
+
# Convenient conversion function
|
|
422
|
+
def phg_to_shader(phg_script: str) -> str:
|
|
423
|
+
"""
|
|
424
|
+
Convert PHG script to GLSL shader code
|
|
425
|
+
|
|
426
|
+
Args:
|
|
427
|
+
phg_script: PHG script string
|
|
428
|
+
|
|
429
|
+
Returns:
|
|
430
|
+
shader_code: GLSL shader code
|
|
431
|
+
"""
|
|
432
|
+
converter = PHGToShaderConverter()
|
|
433
|
+
return converter.generate_shader_code(phg_script)
|
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
import pygame
|
|
2
|
+
import numpy as np
|
|
3
|
+
from OpenGL.GL import *
|
|
4
|
+
from OpenGL.GL.shaders import compileProgram, compileShader
|
|
5
|
+
import sys
|
|
6
|
+
import time
|
|
7
|
+
|
|
8
|
+
class Camera:
|
|
9
|
+
"""Camera class with mouse interaction controls"""
|
|
10
|
+
|
|
11
|
+
def __init__(self):
|
|
12
|
+
# Start further back to see the whole scene
|
|
13
|
+
self.position = np.array([0.0, 3.0, 12.0], dtype=np.float32) # Camera position - further back
|
|
14
|
+
self.target = np.array([0.0, 0.0, 0.0], dtype=np.float32) # Look at target
|
|
15
|
+
self.up = np.array([0.0, 1.0, 0.0], dtype=np.float32) # Up direction
|
|
16
|
+
|
|
17
|
+
# Camera parameters
|
|
18
|
+
self.fov = 45.0 # Field of view
|
|
19
|
+
self.zoom_speed = 0.5 # Increased zoom speed
|
|
20
|
+
self.rotate_speed = 0.01 # Increased rotate speed
|
|
21
|
+
self.pan_speed = 0.02 # Increased pan speed
|
|
22
|
+
|
|
23
|
+
# Mouse state
|
|
24
|
+
self.last_mouse_pos = None
|
|
25
|
+
self.is_rotating = False
|
|
26
|
+
self.is_panning = False
|
|
27
|
+
|
|
28
|
+
def zoom(self, delta):
|
|
29
|
+
"""Zoom control"""
|
|
30
|
+
direction = self.target - self.position
|
|
31
|
+
distance = np.linalg.norm(direction)
|
|
32
|
+
|
|
33
|
+
# Limit min and max distance
|
|
34
|
+
min_distance = 1.0
|
|
35
|
+
max_distance = 100.0
|
|
36
|
+
|
|
37
|
+
new_distance = distance * (1.0 - delta * self.zoom_speed)
|
|
38
|
+
new_distance = np.clip(new_distance, min_distance, max_distance)
|
|
39
|
+
|
|
40
|
+
self.position = self.target - direction * (new_distance / distance)
|
|
41
|
+
print(f"Zoom: position={self.position}, distance={new_distance}")
|
|
42
|
+
|
|
43
|
+
def rotate(self, delta_x, delta_y):
|
|
44
|
+
"""Rotation control"""
|
|
45
|
+
# Calculate vector from target to camera (inverse of camera to target)
|
|
46
|
+
direction = self.position - self.target
|
|
47
|
+
distance = np.linalg.norm(direction)
|
|
48
|
+
|
|
49
|
+
if distance < 0.001:
|
|
50
|
+
return
|
|
51
|
+
|
|
52
|
+
# Spherical coordinates rotation
|
|
53
|
+
# Convert to spherical coordinates
|
|
54
|
+
theta = np.arctan2(direction[0], direction[2]) # Horizontal angle (around Y axis)
|
|
55
|
+
phi = np.arctan2(direction[1], np.sqrt(direction[0]**2 + direction[2]**2)) # Vertical angle
|
|
56
|
+
|
|
57
|
+
# Apply rotation (inverted for more intuitive control)
|
|
58
|
+
theta -= delta_x * self.rotate_speed
|
|
59
|
+
phi += delta_y * self.rotate_speed # Inverted Y for more natural control
|
|
60
|
+
|
|
61
|
+
# Limit vertical angle (avoid flipping)
|
|
62
|
+
phi = np.clip(phi, -np.pi/2 + 0.1, np.pi/2 - 0.1)
|
|
63
|
+
|
|
64
|
+
# Convert back to Cartesian coordinates
|
|
65
|
+
new_x = distance * np.sin(theta) * np.cos(phi)
|
|
66
|
+
new_y = distance * np.sin(phi)
|
|
67
|
+
new_z = distance * np.cos(theta) * np.cos(phi)
|
|
68
|
+
|
|
69
|
+
self.position = self.target + np.array([new_x, new_y, new_z])
|
|
70
|
+
|
|
71
|
+
print(f"Rotate: position={self.position}")
|
|
72
|
+
|
|
73
|
+
def pan(self, delta_x, delta_y):
|
|
74
|
+
"""Pan control"""
|
|
75
|
+
# Calculate camera coordinate system
|
|
76
|
+
forward = normalize(self.target - self.position)
|
|
77
|
+
right = normalize(np.cross(forward, self.up))
|
|
78
|
+
up = normalize(np.cross(right, forward))
|
|
79
|
+
|
|
80
|
+
# Apply pan (inverted for more intuitive control)
|
|
81
|
+
pan_distance = 0.1
|
|
82
|
+
pan_vector = -delta_x * right * self.pan_speed + delta_y * up * self.pan_speed
|
|
83
|
+
|
|
84
|
+
self.position += pan_vector
|
|
85
|
+
self.target += pan_vector
|
|
86
|
+
|
|
87
|
+
print(f"Pan: position={self.position}, target={self.target}")
|
|
88
|
+
|
|
89
|
+
def normalize(v):
|
|
90
|
+
"""Normalize a vector"""
|
|
91
|
+
norm = np.linalg.norm(v)
|
|
92
|
+
if norm == 0:
|
|
93
|
+
return v
|
|
94
|
+
return v / norm
|
|
95
|
+
|
|
96
|
+
class ShaderRenderer:
|
|
97
|
+
"""
|
|
98
|
+
Shader renderer class for visualizing GLSL shaders
|
|
99
|
+
|
|
100
|
+
Usage:
|
|
101
|
+
renderer = ShaderRenderer()
|
|
102
|
+
renderer.render_shader(fragment_shader_code)
|
|
103
|
+
"""
|
|
104
|
+
|
|
105
|
+
def __init__(self, width=800, height=600, title="Shader Renderer"):
|
|
106
|
+
"""
|
|
107
|
+
Initialize Shader renderer
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
width: Window width
|
|
111
|
+
height: Window height
|
|
112
|
+
title: Window title
|
|
113
|
+
"""
|
|
114
|
+
self.width = width
|
|
115
|
+
self.height = height
|
|
116
|
+
self.title = title
|
|
117
|
+
self.is_running = False
|
|
118
|
+
self.shader_program = None
|
|
119
|
+
self.start_time = 0
|
|
120
|
+
self.camera = Camera()
|
|
121
|
+
self.show_help = True
|
|
122
|
+
|
|
123
|
+
self.vertex_shader_src = """
|
|
124
|
+
#version 330 core
|
|
125
|
+
layout (location = 0) in vec3 aPos;
|
|
126
|
+
layout (location = 1) in vec2 aTexCoord;
|
|
127
|
+
|
|
128
|
+
out vec2 TexCoord;
|
|
129
|
+
|
|
130
|
+
void main()
|
|
131
|
+
{
|
|
132
|
+
gl_Position = vec4(aPos, 1.0);
|
|
133
|
+
TexCoord = aTexCoord;
|
|
134
|
+
}
|
|
135
|
+
"""
|
|
136
|
+
|
|
137
|
+
self._init_pygame()
|
|
138
|
+
self._init_opengl()
|
|
139
|
+
self._create_geometry()
|
|
140
|
+
self._init_font()
|
|
141
|
+
|
|
142
|
+
def _init_pygame(self):
|
|
143
|
+
"""Initialize Pygame and OpenGL context"""
|
|
144
|
+
pygame.init()
|
|
145
|
+
self.screen = pygame.display.set_mode((self.width, self.height), pygame.OPENGL | pygame.DOUBLEBUF)
|
|
146
|
+
pygame.display.set_caption(self.title)
|
|
147
|
+
|
|
148
|
+
def _init_opengl(self):
|
|
149
|
+
"""Initialize OpenGL settings"""
|
|
150
|
+
glClearColor(0.0, 0.0, 0.0, 1.0)
|
|
151
|
+
glEnable(GL_BLEND)
|
|
152
|
+
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
|
|
153
|
+
|
|
154
|
+
def _init_font(self):
|
|
155
|
+
"""Initialize font for text rendering"""
|
|
156
|
+
self.font = pygame.font.Font(None, 24)
|
|
157
|
+
self.small_font = pygame.font.Font(None, 18)
|
|
158
|
+
|
|
159
|
+
def _create_geometry(self):
|
|
160
|
+
"""Create geometry required for rendering"""
|
|
161
|
+
# Vertex data: position + texture coordinates
|
|
162
|
+
self.vertices = np.array([
|
|
163
|
+
# Position # Texture coordinates
|
|
164
|
+
-1.0, -1.0, 0.0, 0.0, 0.0,
|
|
165
|
+
1.0, -1.0, 0.0, 1.0, 0.0,
|
|
166
|
+
1.0, 1.0, 0.0, 1.0, 1.0,
|
|
167
|
+
-1.0, 1.0, 0.0, 0.0, 1.0
|
|
168
|
+
], dtype=np.float32)
|
|
169
|
+
|
|
170
|
+
self.indices = np.array([
|
|
171
|
+
0, 1, 2,
|
|
172
|
+
2, 3, 0
|
|
173
|
+
], dtype=np.uint32)
|
|
174
|
+
|
|
175
|
+
# Create VAO, VBO, EBO
|
|
176
|
+
self.VAO = glGenVertexArrays(1)
|
|
177
|
+
self.VBO = glGenBuffers(1)
|
|
178
|
+
self.EBO = glGenBuffers(1)
|
|
179
|
+
|
|
180
|
+
glBindVertexArray(self.VAO)
|
|
181
|
+
|
|
182
|
+
glBindBuffer(GL_ARRAY_BUFFER, self.VBO)
|
|
183
|
+
glBufferData(GL_ARRAY_BUFFER, self.vertices.nbytes, self.vertices, GL_STATIC_DRAW)
|
|
184
|
+
|
|
185
|
+
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, self.EBO)
|
|
186
|
+
glBufferData(GL_ELEMENT_ARRAY_BUFFER, self.indices.nbytes, self.indices, GL_STATIC_DRAW)
|
|
187
|
+
|
|
188
|
+
# Position attribute
|
|
189
|
+
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * self.vertices.itemsize, ctypes.c_void_p(0))
|
|
190
|
+
glEnableVertexAttribArray(0)
|
|
191
|
+
|
|
192
|
+
# Texture coordinate attribute
|
|
193
|
+
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * self.vertices.itemsize, ctypes.c_void_p(3 * self.vertices.itemsize))
|
|
194
|
+
glEnableVertexAttribArray(1)
|
|
195
|
+
|
|
196
|
+
glBindBuffer(GL_ARRAY_BUFFER, 0)
|
|
197
|
+
glBindVertexArray(0)
|
|
198
|
+
|
|
199
|
+
def _compile_shader(self, fragment_src):
|
|
200
|
+
"""
|
|
201
|
+
Compile shader program
|
|
202
|
+
|
|
203
|
+
Args:
|
|
204
|
+
fragment_src: Fragment shader code
|
|
205
|
+
|
|
206
|
+
Returns:
|
|
207
|
+
shader_program: Compiled shader program, None if failed
|
|
208
|
+
"""
|
|
209
|
+
try:
|
|
210
|
+
vertex_shader = compileShader(self.vertex_shader_src, GL_VERTEX_SHADER)
|
|
211
|
+
fragment_shader = compileShader(fragment_src, GL_FRAGMENT_SHADER)
|
|
212
|
+
shader_program = compileProgram(vertex_shader, fragment_shader)
|
|
213
|
+
return shader_program
|
|
214
|
+
except Exception as e:
|
|
215
|
+
print(f"Shader compilation error: {e}")
|
|
216
|
+
return None
|
|
217
|
+
|
|
218
|
+
def _render_help_text(self):
|
|
219
|
+
"""Render help text on screen"""
|
|
220
|
+
# Switch to 2D rendering for text
|
|
221
|
+
glDisable(GL_DEPTH_TEST)
|
|
222
|
+
|
|
223
|
+
# Create a semi-transparent background for better readability
|
|
224
|
+
help_lines = [
|
|
225
|
+
"=== CAMERA CONTROLS ===",
|
|
226
|
+
"LEFT CLICK + DRAG: Rotate Camera",
|
|
227
|
+
"MIDDLE CLICK + DRAG: Pan Camera",
|
|
228
|
+
"MOUSE WHEEL: Zoom In/Out",
|
|
229
|
+
"R: Reset Camera Position",
|
|
230
|
+
"H: Toggle This Help",
|
|
231
|
+
"ESC: Exit Renderer",
|
|
232
|
+
"",
|
|
233
|
+
"Camera debugging enabled - check console"
|
|
234
|
+
]
|
|
235
|
+
|
|
236
|
+
y_offset = 20
|
|
237
|
+
for i, line in enumerate(help_lines):
|
|
238
|
+
if i == 0: # Header
|
|
239
|
+
text_surface = self.font.render(line, True, (255, 255, 0))
|
|
240
|
+
else:
|
|
241
|
+
text_surface = self.small_font.render(line, True, (255, 255, 255))
|
|
242
|
+
self.screen.blit(text_surface, (20, y_offset))
|
|
243
|
+
y_offset += 22 if i == 0 else 18
|
|
244
|
+
|
|
245
|
+
# Camera position info
|
|
246
|
+
cam_info = [
|
|
247
|
+
f"Camera Pos: ({self.camera.position[0]:.1f}, {self.camera.position[1]:.1f}, {self.camera.position[2]:.1f})",
|
|
248
|
+
f"Camera Target: ({self.camera.target[0]:.1f}, {self.camera.target[1]:.1f}, {self.camera.target[2]:.1f})"
|
|
249
|
+
]
|
|
250
|
+
|
|
251
|
+
y_offset += 10
|
|
252
|
+
for info in cam_info:
|
|
253
|
+
text_surface = self.small_font.render(info, True, (0, 255, 255))
|
|
254
|
+
self.screen.blit(text_surface, (20, y_offset))
|
|
255
|
+
y_offset += 18
|
|
256
|
+
|
|
257
|
+
glEnable(GL_DEPTH_TEST)
|
|
258
|
+
|
|
259
|
+
def render_shader(self, fragment_shader, duration=0, interactive=True):
|
|
260
|
+
"""
|
|
261
|
+
Render specified shader
|
|
262
|
+
|
|
263
|
+
Args:
|
|
264
|
+
fragment_shader: Fragment shader code string
|
|
265
|
+
duration: Render duration in seconds, 0 for infinite
|
|
266
|
+
interactive: Allow interaction (ESC to exit)
|
|
267
|
+
|
|
268
|
+
Returns:
|
|
269
|
+
success: Whether rendering was successful
|
|
270
|
+
"""
|
|
271
|
+
# Compile shader
|
|
272
|
+
self.shader_program = self._compile_shader(fragment_shader)
|
|
273
|
+
if not self.shader_program:
|
|
274
|
+
return False
|
|
275
|
+
|
|
276
|
+
# Get uniform locations
|
|
277
|
+
time_loc = glGetUniformLocation(self.shader_program, "time")
|
|
278
|
+
resolution_loc = glGetUniformLocation(self.shader_program, "resolution")
|
|
279
|
+
mouse_loc = glGetUniformLocation(self.shader_program, "mouse")
|
|
280
|
+
camera_pos_loc = glGetUniformLocation(self.shader_program, "cameraPosition")
|
|
281
|
+
camera_target_loc = glGetUniformLocation(self.shader_program, "cameraTarget")
|
|
282
|
+
camera_up_loc = glGetUniformLocation(self.shader_program, "cameraUp")
|
|
283
|
+
camera_fov_loc = glGetUniformLocation(self.shader_program, "cameraFov")
|
|
284
|
+
|
|
285
|
+
self.is_running = True
|
|
286
|
+
self.start_time = time.time()
|
|
287
|
+
clock = pygame.time.Clock()
|
|
288
|
+
|
|
289
|
+
mouse_pos = [0.0, 0.0]
|
|
290
|
+
|
|
291
|
+
print("Renderer started! Camera controls:")
|
|
292
|
+
print("- Left mouse button + drag: Rotate")
|
|
293
|
+
print("- Middle mouse button + drag: Pan")
|
|
294
|
+
print("- Mouse wheel: Zoom")
|
|
295
|
+
print("- R: Reset camera")
|
|
296
|
+
print("- H: Toggle help")
|
|
297
|
+
|
|
298
|
+
while self.is_running:
|
|
299
|
+
current_time = time.time() - self.start_time
|
|
300
|
+
|
|
301
|
+
# Handle events
|
|
302
|
+
for event in pygame.event.get():
|
|
303
|
+
if event.type == pygame.QUIT:
|
|
304
|
+
self.is_running = False
|
|
305
|
+
elif event.type == pygame.KEYDOWN:
|
|
306
|
+
if event.key == pygame.K_ESCAPE and interactive:
|
|
307
|
+
self.is_running = False
|
|
308
|
+
elif event.key == pygame.K_r: # R key to reset camera
|
|
309
|
+
self.camera = Camera()
|
|
310
|
+
print("Camera reset to default position")
|
|
311
|
+
elif event.key == pygame.K_h: # H key to toggle help
|
|
312
|
+
self.show_help = not self.show_help
|
|
313
|
+
print(f"Help display: {'ON' if self.show_help else 'OFF'}")
|
|
314
|
+
|
|
315
|
+
# Handle mouse events for camera control
|
|
316
|
+
if interactive:
|
|
317
|
+
if event.type == pygame.MOUSEBUTTONDOWN:
|
|
318
|
+
if event.button == 1: # Left button - rotate
|
|
319
|
+
self.camera.is_rotating = True
|
|
320
|
+
self.camera.last_mouse_pos = event.pos
|
|
321
|
+
print("Rotation started")
|
|
322
|
+
elif event.button == 2: # Middle button - pan
|
|
323
|
+
self.camera.is_panning = True
|
|
324
|
+
self.camera.last_mouse_pos = event.pos
|
|
325
|
+
print("Panning started")
|
|
326
|
+
elif event.button == 4: # Wheel up - zoom in
|
|
327
|
+
self.camera.zoom(1.0)
|
|
328
|
+
elif event.button == 5: # Wheel down - zoom out
|
|
329
|
+
self.camera.zoom(-1.0)
|
|
330
|
+
|
|
331
|
+
elif event.type == pygame.MOUSEBUTTONUP:
|
|
332
|
+
if event.button == 1: # Left button release
|
|
333
|
+
self.camera.is_rotating = False
|
|
334
|
+
print("Rotation ended")
|
|
335
|
+
elif event.button == 2: # Middle button release
|
|
336
|
+
self.camera.is_panning = False
|
|
337
|
+
print("Panning ended")
|
|
338
|
+
self.camera.last_mouse_pos = None
|
|
339
|
+
|
|
340
|
+
elif event.type == pygame.MOUSEMOTION:
|
|
341
|
+
# Update mouse position for shader
|
|
342
|
+
mouse_pos[0] = event.pos[0] / self.width
|
|
343
|
+
mouse_pos[1] = 1.0 - event.pos[1] / self.height
|
|
344
|
+
|
|
345
|
+
# Handle camera movement
|
|
346
|
+
if self.camera.last_mouse_pos is not None:
|
|
347
|
+
delta_x = event.pos[0] - self.camera.last_mouse_pos[0]
|
|
348
|
+
delta_y = event.pos[1] - self.camera.last_mouse_pos[1]
|
|
349
|
+
|
|
350
|
+
if self.camera.is_rotating:
|
|
351
|
+
self.camera.rotate(delta_x, delta_y)
|
|
352
|
+
elif self.camera.is_panning:
|
|
353
|
+
self.camera.pan(delta_x, delta_y)
|
|
354
|
+
|
|
355
|
+
self.camera.last_mouse_pos = event.pos
|
|
356
|
+
|
|
357
|
+
# Check duration
|
|
358
|
+
if duration > 0 and current_time >= duration:
|
|
359
|
+
break
|
|
360
|
+
|
|
361
|
+
# Render
|
|
362
|
+
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
|
|
363
|
+
|
|
364
|
+
glUseProgram(self.shader_program)
|
|
365
|
+
|
|
366
|
+
# Set uniforms
|
|
367
|
+
if time_loc != -1:
|
|
368
|
+
glUniform1f(time_loc, current_time)
|
|
369
|
+
if resolution_loc != -1:
|
|
370
|
+
glUniform2f(resolution_loc, self.width, self.height)
|
|
371
|
+
if mouse_loc != -1:
|
|
372
|
+
glUniform2f(mouse_loc, mouse_pos[0], mouse_pos[1])
|
|
373
|
+
if camera_pos_loc != -1:
|
|
374
|
+
glUniform3f(camera_pos_loc,
|
|
375
|
+
self.camera.position[0],
|
|
376
|
+
self.camera.position[1],
|
|
377
|
+
self.camera.position[2])
|
|
378
|
+
if camera_target_loc != -1:
|
|
379
|
+
glUniform3f(camera_target_loc,
|
|
380
|
+
self.camera.target[0],
|
|
381
|
+
self.camera.target[1],
|
|
382
|
+
self.camera.target[2])
|
|
383
|
+
if camera_up_loc != -1:
|
|
384
|
+
glUniform3f(camera_up_loc,
|
|
385
|
+
self.camera.up[0],
|
|
386
|
+
self.camera.up[1],
|
|
387
|
+
self.camera.up[2])
|
|
388
|
+
if camera_fov_loc != -1:
|
|
389
|
+
glUniform1f(camera_fov_loc, self.camera.fov)
|
|
390
|
+
|
|
391
|
+
glBindVertexArray(self.VAO)
|
|
392
|
+
glDrawElements(GL_TRIANGLES, len(self.indices), GL_UNSIGNED_INT, None)
|
|
393
|
+
glBindVertexArray(0)
|
|
394
|
+
|
|
395
|
+
# Render help text if enabled
|
|
396
|
+
if self.show_help:
|
|
397
|
+
self._render_help_text()
|
|
398
|
+
|
|
399
|
+
pygame.display.flip()
|
|
400
|
+
clock.tick(60)
|
|
401
|
+
|
|
402
|
+
return True
|
|
403
|
+
|
|
404
|
+
def close(self):
|
|
405
|
+
"""Clean up resources"""
|
|
406
|
+
if self.shader_program:
|
|
407
|
+
glDeleteProgram(self.shader_program)
|
|
408
|
+
pygame.quit()
|
|
409
|
+
|
|
410
|
+
# Convenience function
|
|
411
|
+
def render_shader(fragment_shader, width=800, height=600, duration=0, title="Shader Renderer"):
|
|
412
|
+
"""
|
|
413
|
+
Convenience function: Directly render shader
|
|
414
|
+
|
|
415
|
+
Args:
|
|
416
|
+
fragment_shader: Fragment shader code
|
|
417
|
+
width: Window width
|
|
418
|
+
height: Window height
|
|
419
|
+
duration: Render duration in seconds
|
|
420
|
+
title: Window title
|
|
421
|
+
"""
|
|
422
|
+
renderer = ShaderRenderer(width, height, title)
|
|
423
|
+
success = renderer.render_shader(fragment_shader, duration)
|
|
424
|
+
renderer.close()
|
|
425
|
+
return success
|
|
Binary file
|
|
Binary file
|
phg_vis-1.2.1/phg/__init__.py
DELETED
phg_vis-1.2.1/phg/vis/GCU.dll
DELETED
|
Binary file
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|