1 # SPDX-License-Identifier: GPL-2.0-or-later
3 # -*- coding: utf-8 -*-
6 from bpy
.props
import FloatProperty
, FloatVectorProperty
8 from gpu_extras
.batch
import batch_for_shader
9 from mathutils
import Vector
10 from math
import sqrt
, pi
, atan2
, asin
14 uniform mat4 ModelViewProjectionMatrix;
16 /* Keep in sync with intern/opencolorio/gpu_shader_display_transform_vertex.glsl */
19 out vec2 texCoord_interp;
23 gl_Position = ModelViewProjectionMatrix * vec4(pos.xy, 0.0f, 1.0f);
25 texCoord_interp = texCoord;
29 in vec2 texCoord_interp;
32 uniform sampler2D image;
33 uniform float exposure;
37 fragColor = texture(image, texCoord_interp) * vec4(exposure, exposure, exposure, 1.0f);
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
:
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',
68 "texCoord" : uv_coords
})
70 with gpu
.matrix
.push_pop():
71 gpu
.matrix
.translate(position
)
72 gpu
.matrix
.scale(scale
)
75 shader
.uniform_sampler("image", texture
)
76 shader
.uniform_float("exposure", self
.exposure
)
81 coords
= ((self
.mouse_position
[0], bottom
), (self
.mouse_position
[0], top
))
83 shader
= gpu
.shader
.from_builtin('2D_FLAT_COLOR')
84 batch
= batch_for_shader(shader
, 'LINES',
85 {"pos": coords
, "color": colors
})
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
})
98 class SUNPOS_OT_ShowHdr(bpy
.types
.Operator
):
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')
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
))
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
):
122 if area
.type == 'VIEW_3D':
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':
144 az
= u
* pi
*2 - pi
/2 + env_tex
.texture_mapping
.rotation
.z
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
158 dir.x
= 2.0 * u
- 1.0
159 dir.z
= 2.0 * v
- 1.0
162 if (dir.x
* dir.x
+ dir.z
* dir.z
> 1.0):
166 dir.y
= -sqrt(max(1.0 - dir.x
* dir.x
- dir.z
* dir.z
, 0.0))
169 i
= Vector((0.0, -1.0, 0.0))
171 dir = 2.0 * dir.dot(i
) * dir - i
173 # Convert vector to euler
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
180 self
.report({'ERROR'}, 'Unknown projection')
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':
192 self
.pan(context
, event
)
193 self
.update(context
, event
)
196 elif event
.type in {'LEFTMOUSE', 'RET'}:
197 bpy
.types
.SpaceView3D
.draw_handler_remove(self
._handle
, 'WINDOW')
198 for area
in context
.screen
.areas
:
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)
206 elif event
.type in {'RIGHTMOUSE', 'ESC'}:
207 bpy
.types
.SpaceView3D
.draw_handler_remove(self
._handle
, 'WINDOW')
208 for area
in context
.screen
.areas
:
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)
216 # Set exposure or zoom
217 elif event
.type == 'WHEELUPMOUSE':
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':
233 self
.offset
+= (self
.mouse_position
- (Vector((self
.right
, self
.top
)) / 2 + self
.offset
)) / 11.0
234 self
.update(context
, event
)
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
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
256 for a
in context
.screen
.areas
:
257 if a
.type == 'VIEW_3D':
262 self
.report({'ERROR'}, 'Could not find 3D View')
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')
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'}