Export_3ds: Improved distance cue nodes setup
[blender-addons.git] / sun_position / hdr.py
blob08175777afc895fb423eb5a9f57e3bfe5c4a9fa9
1 # SPDX-FileCopyrightText: 2019-2023 Blender Foundation
3 # SPDX-License-Identifier: GPL-2.0-or-later
5 # -*- coding: utf-8 -*-
7 import bpy
8 from bpy.props import FloatProperty, FloatVectorProperty
9 from bpy.app.translations import pgettext_iface as iface_
10 import gpu
11 from gpu_extras.batch import batch_for_shader
12 from mathutils import Vector
13 from math import sqrt, pi, atan2, asin
16 if not bpy.app.background: # ignore drawing in background mode
17 image_shader = gpu.shader.from_builtin('IMAGE_COLOR')
18 line_shader = gpu.shader.from_builtin('FLAT_COLOR')
21 def draw_callback_px(self, context):
22 nt = context.scene.world.node_tree.nodes
23 env_tex_node = nt.get(context.scene.sun_pos_properties.hdr_texture)
24 image = env_tex_node.image
25 texture = gpu.texture.from_image(image)
27 if self.area != context.area:
28 return
30 bottom = 0
31 top = context.area.height
32 right = context.area.width
34 position = Vector((right, top)) / 2 + self.offset
35 scale = Vector((context.area.width, context.area.width / 2)) * self.scale
37 coords = ((-0.5, -0.5), (0.5, -0.5), (0.5, 0.5), (-0.5, 0.5))
38 uv_coords = ((0, 0), (1, 0), (1, 1), (0, 1))
39 batch = batch_for_shader(image_shader, 'TRI_FAN',
40 {"pos": coords, "texCoord": uv_coords})
42 with gpu.matrix.push_pop():
43 gpu.matrix.translate(position)
44 gpu.matrix.scale(scale)
46 image_shader.bind()
47 image_shader.uniform_sampler("image", texture)
48 image_shader.uniform_float("color", (self.exposure, self.exposure, self.exposure, 1.0))
49 batch.draw(image_shader)
51 # Crosshair
52 # vertical
53 coords = ((self.mouse_position[0], bottom), (self.mouse_position[0], top))
54 colors = ((1,) * 4,) * 2
55 batch = batch_for_shader(line_shader, 'LINES',
56 {"pos": coords, "color": colors})
57 line_shader.bind()
58 batch.draw(line_shader)
60 # horizontal
61 if bottom <= self.mouse_position[1] <= top:
62 coords = ((0, self.mouse_position[1]), (context.area.width, self.mouse_position[1]))
63 batch = batch_for_shader(line_shader, 'LINES',
64 {"pos": coords, "color": colors})
65 line_shader.bind()
66 batch.draw(line_shader)
69 class SUNPOS_OT_ShowHdr(bpy.types.Operator):
70 """Select the location of the Sun in any 3D viewport and keep it in sync with the environment"""
71 bl_idname = "world.sunpos_show_hdr"
72 bl_label = "Pick Sun in Viewport"
74 exposure: FloatProperty(name="Exposure", default=1.0)
75 scale: FloatProperty(name="Scale", default=1.0)
76 offset: FloatVectorProperty(name="Offset", default=(0.0, 0.0), size=2, subtype='COORDINATES')
78 @classmethod
79 def poll(self, context):
80 sun_props = context.scene.sun_pos_properties
81 if sun_props.sun_object is None:
82 self.poll_message_set("Please select a Sun object")
83 return False
84 if not sun_props.hdr_texture:
85 self.poll_message_set("Please select an Environment Texture node")
86 return False
87 if sun_props.bind_to_sun:
88 self.poll_message_set("The environment texture is already bound to the Sun object")
89 return False
91 nt = context.scene.world.node_tree.nodes
92 env_tex_node = nt.get(context.scene.sun_pos_properties.hdr_texture)
93 if (env_tex_node is None
94 or env_tex_node.type != "TEX_ENVIRONMENT"
95 or env_tex_node.image is None):
96 self.poll_message_set("Please select a valid Environment Texture node")
97 return False
98 return True
100 def update(self, context, event):
101 sun_props = context.scene.sun_pos_properties
102 mouse_position_abs = Vector((event.mouse_x, event.mouse_y))
104 # Get current area
105 for area in context.screen.areas:
106 # Compare absolute mouse position to area bounds
107 if (area.x < mouse_position_abs.x < area.x + area.width
108 and area.y < mouse_position_abs.y < area.y + area.height):
109 self.area = area
110 if area.type == 'VIEW_3D':
111 # Redraw all areas
112 area.tag_redraw()
114 if self.area.type == 'VIEW_3D':
115 self.top = self.area.height
116 self.right = self.area.width
118 nt = context.scene.world.node_tree.nodes
119 env_tex = nt.get(sun_props.hdr_texture)
121 # Mouse position relative to window
122 self.mouse_position = Vector((mouse_position_abs.x - self.area.x,
123 mouse_position_abs.y - self.area.y))
125 self.selected_point = (self.mouse_position
126 - self.offset
127 - Vector((self.right, self.top)) / 2) / self.scale
128 u = self.selected_point.x / self.area.width + 0.5
129 v = (self.selected_point.y) / (self.area.width / 2) + 0.5
131 # Set elevation and azimuth from selected point
132 if env_tex.projection == 'EQUIRECTANGULAR':
133 el = v * pi - pi/2
134 az = u * pi*2 - pi/2 + env_tex.texture_mapping.rotation.z
136 # Clamp elevation
137 el = max(el, -pi/2)
138 el = min(el, pi/2)
140 sun_props.hdr_elevation = el
141 sun_props.hdr_azimuth = az
142 elif env_tex.projection == 'MIRROR_BALL':
143 # Formula from intern/cycles/kernel/kernel_projection.h
144 # Point on sphere
145 dir = Vector()
147 # Normalize to -1, 1
148 dir.x = 2.0 * u - 1.0
149 dir.z = 2.0 * v - 1.0
151 # Outside bounds
152 if (dir.x * dir.x + dir.z * dir.z > 1.0):
153 dir = Vector()
155 else:
156 dir.y = -sqrt(max(1.0 - dir.x * dir.x - dir.z * dir.z, 0.0))
158 # Reflection
159 i = Vector((0.0, -1.0, 0.0))
161 dir = 2.0 * dir.dot(i) * dir - i
163 # Convert vector to euler
164 el = asin(dir.z)
165 az = atan2(dir.x, dir.y) + env_tex.texture_mapping.rotation.z
166 sun_props.hdr_elevation = el
167 sun_props.hdr_azimuth = az
169 else:
170 self.report({'ERROR'}, 'Unknown projection')
171 return {'CANCELLED'}
173 def pan(self, context, event):
174 self.offset += Vector((event.mouse_region_x - self.mouse_prev_x,
175 event.mouse_region_y - self.mouse_prev_y))
176 self.mouse_prev_x, self.mouse_prev_y = event.mouse_region_x, event.mouse_region_y
178 def modal(self, context, event):
179 self.area.tag_redraw()
180 if event.type == 'MOUSEMOVE':
181 if self.is_panning:
182 self.pan(context, event)
183 self.update(context, event)
185 # Confirm
186 elif event.type in {'LEFTMOUSE', 'RET'}:
187 bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
188 for area in context.screen.areas:
189 area.tag_redraw()
190 # Bind the environment texture to the sun
191 context.scene.sun_pos_properties.bind_to_sun = True
192 context.workspace.status_text_set(None)
193 return {'FINISHED'}
195 # Cancel
196 elif event.type in {'RIGHTMOUSE', 'ESC'}:
197 bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
198 for area in context.screen.areas:
199 area.tag_redraw()
200 # Reset previous values
201 context.scene.sun_pos_properties.hdr_elevation = self.initial_elevation
202 context.scene.sun_pos_properties.hdr_azimuth = self.initial_azimuth
203 context.workspace.status_text_set(None)
204 return {'CANCELLED'}
206 # Set exposure or zoom
207 elif event.type == 'WHEELUPMOUSE':
208 # Exposure
209 if event.ctrl:
210 self.exposure *= 1.1
211 # Zoom
212 else:
213 self.scale *= 1.1
214 self.offset -= (self.mouse_position - (Vector((self.right, self.top)) / 2 + self.offset)) / 10.0
215 self.update(context, event)
216 elif event.type == 'WHEELDOWNMOUSE':
217 # Exposure
218 if event.ctrl:
219 self.exposure /= 1.1
220 # Zoom
221 else:
222 self.scale /= 1.1
223 self.offset += (self.mouse_position - (Vector((self.right, self.top)) / 2 + self.offset)) / 11.0
224 self.update(context, event)
226 # Toggle pan
227 elif event.type == 'MIDDLEMOUSE':
228 if event.value == 'PRESS':
229 self.mouse_prev_x, self.mouse_prev_y = event.mouse_region_x, event.mouse_region_y
230 self.is_panning = True
231 elif event.value == 'RELEASE':
232 self.is_panning = False
234 else:
235 return {'PASS_THROUGH'}
237 return {'RUNNING_MODAL'}
239 def invoke(self, context, event):
240 self.is_panning = False
241 self.mouse_prev_x = 0.0
242 self.mouse_prev_y = 0.0
244 # Get at least one 3D View
245 area_3d = None
246 for a in context.screen.areas:
247 if a.type == 'VIEW_3D':
248 area_3d = a
249 break
251 if area_3d is None:
252 self.report({'ERROR'}, 'Could not find 3D View')
253 return {'CANCELLED'}
255 nt = context.scene.world.node_tree.nodes
256 env_tex_node = nt.get(context.scene.sun_pos_properties.hdr_texture)
257 if env_tex_node is None or env_tex_node.type != "TEX_ENVIRONMENT":
258 self.report({'ERROR'}, 'Please select an Environment Texture node')
259 return {'CANCELLED'}
261 self.area = context.area
263 self.mouse_position = event.mouse_region_x, event.mouse_region_y
265 self.initial_elevation = context.scene.sun_pos_properties.hdr_elevation
266 self.initial_azimuth = context.scene.sun_pos_properties.hdr_azimuth
268 context.workspace.status_text_set(
269 iface_("Enter/LMB: confirm, Esc/RMB: cancel, MMB: pan, "
270 "mouse wheel: zoom, Ctrl + mouse wheel: set exposure"))
272 self._handle = bpy.types.SpaceView3D.draw_handler_add(
273 draw_callback_px, (self, context), 'WINDOW', 'POST_PIXEL'
275 context.window_manager.modal_handler_add(self)
277 return {'RUNNING_MODAL'}