Cleanup: quiet warnings with descriptions ending with a '.'
[blender-addons.git] / sun_position / hdr.py
bloba80b0e7613674c2c3a98e8dd1f3c952460573dbd
1 # SPDX-License-Identifier: GPL-2.0-or-later
3 # -*- coding: utf-8 -*-
5 import bpy
6 from bpy.props import FloatProperty, FloatVectorProperty
7 import gpu
8 from gpu_extras.batch import batch_for_shader
9 from mathutils import Vector
10 from math import sqrt, pi, atan2, asin
13 vertex_shader = '''
14 uniform mat4 ModelViewProjectionMatrix;
16 /* Keep in sync with intern/opencolorio/gpu_shader_display_transform_vertex.glsl */
17 in vec2 texCoord;
18 in vec2 pos;
19 out vec2 texCoord_interp;
21 void main()
23 gl_Position = ModelViewProjectionMatrix * vec4(pos.xy, 0.0f, 1.0f);
24 gl_Position.z = 1.0f;
25 texCoord_interp = texCoord;
26 }'''
28 fragment_shader = '''
29 in vec2 texCoord_interp;
30 out vec4 fragColor;
32 uniform sampler2D image;
33 uniform float exposure;
35 void main()
37 fragColor = texture(image, texCoord_interp) * vec4(exposure, exposure, exposure, 1.0f);
38 }'''
40 # shader = gpu.types.GPUShader(vertex_shader, fragment_shader)
43 def draw_callback_px(self, context):
44 nt = context.scene.world.node_tree.nodes
45 env_tex_node = nt.get(context.scene.sun_pos_properties.hdr_texture)
46 image = env_tex_node.image
47 texture = gpu.texture.from_image(image)
49 if self.area != context.area:
50 return
52 if image.gl_load():
53 raise Exception()
55 bottom = 0
56 top = context.area.height
57 right = context.area.width
59 position = Vector((right, top)) / 2 + self.offset
60 scale = Vector((context.area.width, context.area.width / 2)) * self.scale
62 shader = gpu.types.GPUShader(vertex_shader, fragment_shader)
64 coords = ((-0.5, -0.5), (0.5, -0.5), (0.5, 0.5), (-0.5, 0.5))
65 uv_coords = ((0, 0), (1, 0), (1, 1), (0, 1))
66 batch = batch_for_shader(shader, 'TRI_FAN',
67 {"pos" : coords,
68 "texCoord" : uv_coords})
70 with gpu.matrix.push_pop():
71 gpu.matrix.translate(position)
72 gpu.matrix.scale(scale)
74 shader.bind()
75 shader.uniform_sampler("image", texture)
76 shader.uniform_float("exposure", self.exposure)
77 batch.draw(shader)
79 # Crosshair
80 # vertical
81 coords = ((self.mouse_position[0], bottom), (self.mouse_position[0], top))
82 colors = ((1,)*4,)*2
83 shader = gpu.shader.from_builtin('2D_FLAT_COLOR')
84 batch = batch_for_shader(shader, 'LINES',
85 {"pos": coords, "color": colors})
86 shader.bind()
87 batch.draw(shader)
89 # horizontal
90 if bottom <= self.mouse_position[1] <= top:
91 coords = ((0, self.mouse_position[1]), (context.area.width, self.mouse_position[1]))
92 batch = batch_for_shader(shader, 'LINES',
93 {"pos": coords, "color": colors})
94 shader.bind()
95 batch.draw(shader)
98 class SUNPOS_OT_ShowHdr(bpy.types.Operator):
99 """Tooltip"""
100 bl_idname = "world.sunpos_show_hdr"
101 bl_label = "Sync Sun to Texture"
103 exposure: FloatProperty(name="Exposure", default=1.0)
104 scale: FloatProperty(name="Scale", default=1.0)
105 offset: FloatVectorProperty(name="Offset", default=(0.0, 0.0), size=2, subtype='COORDINATES')
107 @classmethod
108 def poll(self, context):
109 sun_props = context.scene.sun_pos_properties
110 return sun_props.hdr_texture and sun_props.sun_object is not None
112 def update(self, context, event):
113 sun_props = context.scene.sun_pos_properties
114 mouse_position_abs = Vector((event.mouse_x, event.mouse_y))
116 # Get current area
117 for area in context.screen.areas:
118 # Compare absolute mouse position to area bounds
119 if (area.x < mouse_position_abs.x < area.x + area.width
120 and area.y < mouse_position_abs.y < area.y + area.height):
121 self.area = area
122 if area.type == 'VIEW_3D':
123 # Redraw all areas
124 area.tag_redraw()
126 if self.area.type == 'VIEW_3D':
127 self.top = self.area.height
128 self.right = self.area.width
130 nt = context.scene.world.node_tree.nodes
131 env_tex = nt.get(sun_props.hdr_texture)
133 # Mouse position relative to window
134 self.mouse_position = Vector((mouse_position_abs.x - self.area.x,
135 mouse_position_abs.y - self.area.y))
137 self.selected_point = (self.mouse_position - self.offset - Vector((self.right, self.top))/2) / self.scale
138 u = self.selected_point.x / self.area.width + 0.5
139 v = (self.selected_point.y) / (self.area.width / 2) + 0.5
141 # Set elevation and azimuth from selected point
142 if env_tex.projection == 'EQUIRECTANGULAR':
143 el = v * pi - pi/2
144 az = u * pi*2 - pi/2 + env_tex.texture_mapping.rotation.z
146 # Clamp elevation
147 el = max(el, -pi/2)
148 el = min(el, pi/2)
150 sun_props.hdr_elevation = el
151 sun_props.hdr_azimuth = az
152 elif env_tex.projection == 'MIRROR_BALL':
153 # Formula from intern/cycles/kernel/kernel_projection.h
154 # Point on sphere
155 dir = Vector()
157 # Normalize to -1, 1
158 dir.x = 2.0 * u - 1.0
159 dir.z = 2.0 * v - 1.0
161 # Outside bounds
162 if (dir.x * dir.x + dir.z * dir.z > 1.0):
163 dir = Vector()
165 else:
166 dir.y = -sqrt(max(1.0 - dir.x * dir.x - dir.z * dir.z, 0.0))
168 # Reflection
169 i = Vector((0.0, -1.0, 0.0))
171 dir = 2.0 * dir.dot(i) * dir - i
173 # Convert vector to euler
174 el = asin(dir.z)
175 az = atan2(dir.x, dir.y) + env_tex.texture_mapping.rotation.z
176 sun_props.hdr_elevation = el
177 sun_props.hdr_azimuth = az
179 else:
180 self.report({'ERROR'}, 'Unknown projection')
181 return {'CANCELLED'}
183 def pan(self, context, event):
184 self.offset += Vector((event.mouse_region_x - self.mouse_prev_x,
185 event.mouse_region_y - self.mouse_prev_y))
186 self.mouse_prev_x, self.mouse_prev_y = event.mouse_region_x, event.mouse_region_y
188 def modal(self, context, event):
189 self.area.tag_redraw()
190 if event.type == 'MOUSEMOVE':
191 if self.is_panning:
192 self.pan(context, event)
193 self.update(context, event)
195 # Confirm
196 elif event.type in {'LEFTMOUSE', 'RET'}:
197 bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
198 for area in context.screen.areas:
199 area.tag_redraw()
200 # Bind the environment texture to the sun
201 context.scene.sun_pos_properties.bind_to_sun = True
202 context.workspace.status_text_set(None)
203 return {'FINISHED'}
205 # Cancel
206 elif event.type in {'RIGHTMOUSE', 'ESC'}:
207 bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
208 for area in context.screen.areas:
209 area.tag_redraw()
210 # Reset previous values
211 context.scene.sun_pos_properties.hdr_elevation = self.initial_elevation
212 context.scene.sun_pos_properties.hdr_azimuth = self.initial_azimuth
213 context.workspace.status_text_set(None)
214 return {'CANCELLED'}
216 # Set exposure or zoom
217 elif event.type == 'WHEELUPMOUSE':
218 # Exposure
219 if event.ctrl:
220 self.exposure *= 1.1
221 # Zoom
222 else:
223 self.scale *= 1.1
224 self.offset -= (self.mouse_position - (Vector((self.right, self.top)) / 2 + self.offset)) / 10.0
225 self.update(context, event)
226 elif event.type == 'WHEELDOWNMOUSE':
227 # Exposure
228 if event.ctrl:
229 self.exposure /= 1.1
230 # Zoom
231 else:
232 self.scale /= 1.1
233 self.offset += (self.mouse_position - (Vector((self.right, self.top)) / 2 + self.offset)) / 11.0
234 self.update(context, event)
236 # Toggle pan
237 elif event.type == 'MIDDLEMOUSE':
238 if event.value == 'PRESS':
239 self.mouse_prev_x, self.mouse_prev_y = event.mouse_region_x, event.mouse_region_y
240 self.is_panning = True
241 elif event.value == 'RELEASE':
242 self.is_panning = False
244 else:
245 return {'PASS_THROUGH'}
247 return {'RUNNING_MODAL'}
249 def invoke(self, context, event):
250 self.is_panning = False
251 self.mouse_prev_x = 0.0
252 self.mouse_prev_y = 0.0
254 # Get at least one 3D View
255 area_3d = None
256 for a in context.screen.areas:
257 if a.type == 'VIEW_3D':
258 area_3d = a
259 break
261 if area_3d is None:
262 self.report({'ERROR'}, 'Could not find 3D View')
263 return {'CANCELLED'}
265 nt = context.scene.world.node_tree.nodes
266 env_tex_node = nt.get(context.scene.sun_pos_properties.hdr_texture)
267 if env_tex_node.type != "TEX_ENVIRONMENT":
268 self.report({'ERROR'}, 'Please select an Environment Texture node')
269 return {'CANCELLED'}
271 self.area = context.area
273 self.mouse_position = event.mouse_region_x, event.mouse_region_y
275 self.initial_elevation = context.scene.sun_pos_properties.hdr_elevation
276 self.initial_azimuth = context.scene.sun_pos_properties.hdr_azimuth
278 context.workspace.status_text_set("Enter/LMB: confirm, Esc/RMB: cancel, MMB: pan, mouse wheel: zoom, Ctrl + mouse wheel: set exposure")
280 self._handle = bpy.types.SpaceView3D.draw_handler_add(draw_callback_px,
281 (self, context), 'WINDOW', 'POST_PIXEL')
282 context.window_manager.modal_handler_add(self)
284 return {'RUNNING_MODAL'}