node_presets: add back from addons_contrib
[blender-addons.git] / sun_position / hdr.py
blob257daf5809cf1cabb50bd35f7088eb3108ce2b1a
1 ### BEGIN GPL LICENSE BLOCK #####
3 # This program is free software; you can redistribute it and/or
4 # modify it under the terms of the GNU General Public License
5 # as published by the Free Software Foundation; either version 2
6 # of the License, or (at your option) any later version.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with this program; if not, write to the Free Software Foundation,
15 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 # ##### END GPL LICENSE BLOCK #####
19 # -*- coding: utf-8 -*-
21 import bpy
22 import gpu
23 import bgl
24 from gpu_extras.batch import batch_for_shader
25 from mathutils import Vector
26 from math import sqrt, pi, atan2, asin
29 vertex_shader = '''
30 uniform mat4 ModelViewProjectionMatrix;
32 /* Keep in sync with intern/opencolorio/gpu_shader_display_transform_vertex.glsl */
33 in vec2 texCoord;
34 in vec2 pos;
35 out vec2 texCoord_interp;
37 void main()
39 gl_Position = ModelViewProjectionMatrix * vec4(pos.xy, 0.0f, 1.0f);
40 gl_Position.z = 1.0;
41 texCoord_interp = texCoord;
42 }'''
44 fragment_shader = '''
45 in vec2 texCoord_interp;
46 out vec4 fragColor;
48 uniform sampler2D image;
49 uniform float exposure;
51 void main()
53 fragColor = texture(image, texCoord_interp) * exposure;
54 }'''
56 # shader = gpu.types.GPUShader(vertex_shader, fragment_shader)
59 def draw_callback_px(self, context):
60 nt = context.scene.world.node_tree.nodes
61 env_tex_node = nt.get(context.scene.sun_pos_properties.hdr_texture)
62 image = env_tex_node.image
64 if self.area != context.area:
65 return
67 if image.gl_load():
68 raise Exception()
70 bottom = 0
71 top = context.area.height
72 right = context.area.width
74 position = Vector((right, top)) / 2 + self.offset
75 scale = Vector((context.area.width, context.area.width / 2)) * self.scale
77 shader = gpu.types.GPUShader(vertex_shader, fragment_shader)
79 coords = ((-0.5, -0.5), (0.5, -0.5), (0.5, 0.5), (-0.5, 0.5))
80 uv_coords = ((0, 0), (1, 0), (1, 1), (0, 1))
81 batch = batch_for_shader(shader, 'TRI_FAN',
82 {"pos" : coords,
83 "texCoord" : uv_coords})
85 bgl.glActiveTexture(bgl.GL_TEXTURE0)
86 bgl.glBindTexture(bgl.GL_TEXTURE_2D, image.bindcode)
89 with gpu.matrix.push_pop():
90 gpu.matrix.translate(position)
91 gpu.matrix.scale(scale)
93 shader.bind()
94 shader.uniform_int("image", 0)
95 shader.uniform_float("exposure", self.exposure)
96 batch.draw(shader)
98 # Crosshair
99 # vertical
100 coords = ((self.mouse_position[0], bottom), (self.mouse_position[0], top))
101 colors = ((1,)*4,)*2
102 shader = gpu.shader.from_builtin('2D_FLAT_COLOR')
103 batch = batch_for_shader(shader, 'LINES',
104 {"pos": coords, "color": colors})
105 shader.bind()
106 batch.draw(shader)
108 # horizontal
109 if bottom <= self.mouse_position[1] <= top:
110 coords = ((0, self.mouse_position[1]), (context.area.width, self.mouse_position[1]))
111 batch = batch_for_shader(shader, 'LINES',
112 {"pos": coords, "color": colors})
113 shader.bind()
114 batch.draw(shader)
117 class SUNPOS_OT_ShowHdr(bpy.types.Operator):
118 """Tooltip"""
119 bl_idname = "world.sunpos_show_hdr"
120 bl_label = "Sync Sun to Texture"
122 exposure = 1.0
124 @classmethod
125 def poll(self, context):
126 sun_props = context.scene.sun_pos_properties
127 return sun_props.hdr_texture and sun_props.sun_object is not None
129 def update(self, context, event):
130 sun_props = context.scene.sun_pos_properties
131 mouse_position_abs = Vector((event.mouse_x, event.mouse_y))
133 # Get current area
134 for area in context.screen.areas:
135 # Compare absolute mouse position to area bounds
136 if (area.x < mouse_position_abs.x < area.x + area.width
137 and area.y < mouse_position_abs.y < area.y + area.height):
138 self.area = area
139 if area.type == 'VIEW_3D':
140 # Redraw all areas
141 area.tag_redraw()
143 if self.area.type == 'VIEW_3D':
144 self.top = self.area.height
145 self.right = self.area.width
147 nt = context.scene.world.node_tree.nodes
148 env_tex = nt.get(sun_props.hdr_texture)
150 # Mouse position relative to window
151 self.mouse_position = Vector((mouse_position_abs.x - self.area.x,
152 mouse_position_abs.y - self.area.y))
154 self.selected_point = (self.mouse_position - self.offset - Vector((self.right, self.top))/2) / self.scale
155 u = self.selected_point.x / self.area.width + 0.5
156 v = (self.selected_point.y) / (self.area.width / 2) + 0.5
158 # Set elevation and azimuth from selected point
159 if env_tex.projection == 'EQUIRECTANGULAR':
160 el = v * pi - pi/2
161 az = u * pi*2 - pi/2 + env_tex.texture_mapping.rotation.z
163 # Clamp elevation
164 el = max(el, -pi/2)
165 el = min(el, pi/2)
167 sun_props.hdr_elevation = el
168 sun_props.hdr_azimuth = az
169 elif env_tex.projection == 'MIRROR_BALL':
170 # Formula from intern/cycles/kernel/kernel_projection.h
171 # Point on sphere
172 dir = Vector()
174 # Normalize to -1, 1
175 dir.x = 2.0 * u - 1.0
176 dir.z = 2.0 * v - 1.0
178 # Outside bounds
179 if (dir.x * dir.x + dir.z * dir.z > 1.0):
180 dir = Vector()
182 else:
183 dir.y = -sqrt(max(1.0 - dir.x * dir.x - dir.z * dir.z, 0.0))
185 # Reflection
186 i = Vector((0.0, -1.0, 0.0))
188 dir = 2.0 * dir.dot(i) * dir - i
190 # Convert vector to euler
191 el = asin(dir.z)
192 az = atan2(dir.x, dir.y) + env_tex.texture_mapping.rotation.z
193 sun_props.hdr_elevation = el
194 sun_props.hdr_azimuth = az
196 else:
197 self.report({'ERROR'}, 'Unknown projection')
198 return {'CANCELLED'}
200 def pan(self, context, event):
201 self.offset += Vector((event.mouse_region_x - self.mouse_prev_x,
202 event.mouse_region_y - self.mouse_prev_y))
203 self.mouse_prev_x, self.mouse_prev_y = event.mouse_region_x, event.mouse_region_y
205 def modal(self, context, event):
206 self.area.tag_redraw()
207 if event.type == 'MOUSEMOVE':
208 if self.is_panning:
209 self.pan(context, event)
210 self.update(context, event)
212 # Confirm
213 elif event.type in {'LEFTMOUSE', 'RET'}:
214 bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
215 for area in context.screen.areas:
216 area.tag_redraw()
217 # Bind the environment texture to the sun
218 context.scene.sun_pos_properties.bind_to_sun = True
219 context.workspace.status_text_set(None)
220 return {'FINISHED'}
222 # Cancel
223 elif event.type in {'RIGHTMOUSE', 'ESC'}:
224 bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
225 for area in context.screen.areas:
226 area.tag_redraw()
227 # Reset previous values
228 context.scene.sun_pos_properties.hdr_elevation = self.initial_elevation
229 context.scene.sun_pos_properties.hdr_azimuth = self.initial_azimuth
230 context.workspace.status_text_set(None)
231 return {'CANCELLED'}
233 # Set exposure or zoom
234 elif event.type == 'WHEELUPMOUSE':
235 # Exposure
236 if event.ctrl:
237 self.exposure *= 1.1
238 # Zoom
239 else:
240 self.scale *= 1.1
241 self.offset -= (self.mouse_position - (Vector((self.right, self.top)) / 2 + self.offset)) / 10.0
242 self.update(context, event)
243 elif event.type == 'WHEELDOWNMOUSE':
244 # Exposure
245 if event.ctrl:
246 self.exposure /= 1.1
247 # Zoom
248 else:
249 self.scale /= 1.1
250 self.offset += (self.mouse_position - (Vector((self.right, self.top)) / 2 + self.offset)) / 11.0
251 self.update(context, event)
253 # Toggle pan
254 elif event.type == 'MIDDLEMOUSE':
255 if event.value == 'PRESS':
256 self.mouse_prev_x, self.mouse_prev_y = event.mouse_region_x, event.mouse_region_y
257 self.is_panning = True
258 elif event.value == 'RELEASE':
259 self.is_panning = False
261 else:
262 return {'PASS_THROUGH'}
264 return {'RUNNING_MODAL'}
266 def invoke(self, context, event):
267 self.is_panning = False
268 self.mouse_prev_x = 0.0
269 self.mouse_prev_y = 0.0
270 self.offset = Vector((0.0, 0.0))
271 self.scale = 1.0
273 # Get at least one 3D View
274 area_3d = None
275 for a in context.screen.areas:
276 if a.type == 'VIEW_3D':
277 area_3d = a
278 break
280 if area_3d is None:
281 self.report({'ERROR'}, 'Could not find 3D View')
282 return {'CANCELLED'}
284 nt = context.scene.world.node_tree.nodes
285 env_tex_node = nt.get(context.scene.sun_pos_properties.hdr_texture)
286 if env_tex_node.type != "TEX_ENVIRONMENT":
287 self.report({'ERROR'}, 'Please select an Environment Texture node')
288 return {'CANCELLED'}
290 self.area = context.area
292 self.mouse_position = event.mouse_region_x, event.mouse_region_y
294 self.initial_elevation = context.scene.sun_pos_properties.hdr_elevation
295 self.initial_azimuth = context.scene.sun_pos_properties.hdr_azimuth
297 context.workspace.status_text_set("Enter/LMB: confirm, Esc/RMB: cancel, MMB: pan, mouse wheel: zoom, Ctrl + mouse wheel: set exposure")
299 self._handle = bpy.types.SpaceView3D.draw_handler_add(draw_callback_px,
300 (self, context), 'WINDOW', 'POST_PIXEL')
301 context.window_manager.modal_handler_add(self)
303 return {'RUNNING_MODAL'}