304 lines
10 KiB
Python
304 lines
10 KiB
Python
### BEGIN GPL LICENSE BLOCK #####
|
|
#
|
|
# This program is free software; you can redistribute it and/or
|
|
# modify it under the terms of the GNU General Public License
|
|
# as published by the Free Software Foundation; either version 2
|
|
# of the License, or (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program; if not, write to the Free Software Foundation,
|
|
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
#
|
|
# ##### END GPL LICENSE BLOCK #####
|
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
import bpy
|
|
import gpu
|
|
import bgl
|
|
from gpu_extras.batch import batch_for_shader
|
|
from mathutils import Vector
|
|
from math import sqrt, pi, atan2, asin
|
|
|
|
|
|
vertex_shader = '''
|
|
uniform mat4 ModelViewProjectionMatrix;
|
|
|
|
/* Keep in sync with intern/opencolorio/gpu_shader_display_transform_vertex.glsl */
|
|
in vec2 texCoord;
|
|
in vec2 pos;
|
|
out vec2 texCoord_interp;
|
|
|
|
void main()
|
|
{
|
|
gl_Position = ModelViewProjectionMatrix * vec4(pos.xy, 0.0f, 1.0f);
|
|
gl_Position.z = 1.0f;
|
|
texCoord_interp = texCoord;
|
|
}'''
|
|
|
|
fragment_shader = '''
|
|
in vec2 texCoord_interp;
|
|
out vec4 fragColor;
|
|
|
|
uniform sampler2D image;
|
|
uniform float exposure;
|
|
|
|
void main()
|
|
{
|
|
fragColor = texture(image, texCoord_interp) * vec4(exposure, exposure, exposure, 1.0f);
|
|
}'''
|
|
|
|
# shader = gpu.types.GPUShader(vertex_shader, fragment_shader)
|
|
|
|
|
|
def draw_callback_px(self, context):
|
|
nt = context.scene.world.node_tree.nodes
|
|
env_tex_node = nt.get(context.scene.sun_pos_properties.hdr_texture)
|
|
image = env_tex_node.image
|
|
|
|
if self.area != context.area:
|
|
return
|
|
|
|
if image.gl_load():
|
|
raise Exception()
|
|
|
|
bottom = 0
|
|
top = context.area.height
|
|
right = context.area.width
|
|
|
|
position = Vector((right, top)) / 2 + self.offset
|
|
scale = Vector((context.area.width, context.area.width / 2)) * self.scale
|
|
|
|
shader = gpu.types.GPUShader(vertex_shader, fragment_shader)
|
|
|
|
coords = ((-0.5, -0.5), (0.5, -0.5), (0.5, 0.5), (-0.5, 0.5))
|
|
uv_coords = ((0, 0), (1, 0), (1, 1), (0, 1))
|
|
batch = batch_for_shader(shader, 'TRI_FAN',
|
|
{"pos" : coords,
|
|
"texCoord" : uv_coords})
|
|
|
|
bgl.glActiveTexture(bgl.GL_TEXTURE0)
|
|
bgl.glBindTexture(bgl.GL_TEXTURE_2D, image.bindcode)
|
|
|
|
|
|
with gpu.matrix.push_pop():
|
|
gpu.matrix.translate(position)
|
|
gpu.matrix.scale(scale)
|
|
|
|
shader.bind()
|
|
shader.uniform_int("image", 0)
|
|
shader.uniform_float("exposure", self.exposure)
|
|
batch.draw(shader)
|
|
|
|
# Crosshair
|
|
# vertical
|
|
coords = ((self.mouse_position[0], bottom), (self.mouse_position[0], top))
|
|
colors = ((1,)*4,)*2
|
|
shader = gpu.shader.from_builtin('2D_FLAT_COLOR')
|
|
batch = batch_for_shader(shader, 'LINES',
|
|
{"pos": coords, "color": colors})
|
|
shader.bind()
|
|
batch.draw(shader)
|
|
|
|
# horizontal
|
|
if bottom <= self.mouse_position[1] <= top:
|
|
coords = ((0, self.mouse_position[1]), (context.area.width, self.mouse_position[1]))
|
|
batch = batch_for_shader(shader, 'LINES',
|
|
{"pos": coords, "color": colors})
|
|
shader.bind()
|
|
batch.draw(shader)
|
|
|
|
|
|
class SUNPOS_OT_ShowHdr(bpy.types.Operator):
|
|
"""Tooltip"""
|
|
bl_idname = "world.sunpos_show_hdr"
|
|
bl_label = "Sync Sun to Texture"
|
|
|
|
exposure = 1.0
|
|
|
|
@classmethod
|
|
def poll(self, context):
|
|
sun_props = context.scene.sun_pos_properties
|
|
return sun_props.hdr_texture and sun_props.sun_object is not None
|
|
|
|
def update(self, context, event):
|
|
sun_props = context.scene.sun_pos_properties
|
|
mouse_position_abs = Vector((event.mouse_x, event.mouse_y))
|
|
|
|
# Get current area
|
|
for area in context.screen.areas:
|
|
# Compare absolute mouse position to area bounds
|
|
if (area.x < mouse_position_abs.x < area.x + area.width
|
|
and area.y < mouse_position_abs.y < area.y + area.height):
|
|
self.area = area
|
|
if area.type == 'VIEW_3D':
|
|
# Redraw all areas
|
|
area.tag_redraw()
|
|
|
|
if self.area.type == 'VIEW_3D':
|
|
self.top = self.area.height
|
|
self.right = self.area.width
|
|
|
|
nt = context.scene.world.node_tree.nodes
|
|
env_tex = nt.get(sun_props.hdr_texture)
|
|
|
|
# Mouse position relative to window
|
|
self.mouse_position = Vector((mouse_position_abs.x - self.area.x,
|
|
mouse_position_abs.y - self.area.y))
|
|
|
|
self.selected_point = (self.mouse_position - self.offset - Vector((self.right, self.top))/2) / self.scale
|
|
u = self.selected_point.x / self.area.width + 0.5
|
|
v = (self.selected_point.y) / (self.area.width / 2) + 0.5
|
|
|
|
# Set elevation and azimuth from selected point
|
|
if env_tex.projection == 'EQUIRECTANGULAR':
|
|
el = v * pi - pi/2
|
|
az = u * pi*2 - pi/2 + env_tex.texture_mapping.rotation.z
|
|
|
|
# Clamp elevation
|
|
el = max(el, -pi/2)
|
|
el = min(el, pi/2)
|
|
|
|
sun_props.hdr_elevation = el
|
|
sun_props.hdr_azimuth = az
|
|
elif env_tex.projection == 'MIRROR_BALL':
|
|
# Formula from intern/cycles/kernel/kernel_projection.h
|
|
# Point on sphere
|
|
dir = Vector()
|
|
|
|
# Normalize to -1, 1
|
|
dir.x = 2.0 * u - 1.0
|
|
dir.z = 2.0 * v - 1.0
|
|
|
|
# Outside bounds
|
|
if (dir.x * dir.x + dir.z * dir.z > 1.0):
|
|
dir = Vector()
|
|
|
|
else:
|
|
dir.y = -sqrt(max(1.0 - dir.x * dir.x - dir.z * dir.z, 0.0))
|
|
|
|
# Reflection
|
|
i = Vector((0.0, -1.0, 0.0))
|
|
|
|
dir = 2.0 * dir.dot(i) * dir - i
|
|
|
|
# Convert vector to euler
|
|
el = asin(dir.z)
|
|
az = atan2(dir.x, dir.y) + env_tex.texture_mapping.rotation.z
|
|
sun_props.hdr_elevation = el
|
|
sun_props.hdr_azimuth = az
|
|
|
|
else:
|
|
self.report({'ERROR'}, 'Unknown projection')
|
|
return {'CANCELLED'}
|
|
|
|
def pan(self, context, event):
|
|
self.offset += Vector((event.mouse_region_x - self.mouse_prev_x,
|
|
event.mouse_region_y - self.mouse_prev_y))
|
|
self.mouse_prev_x, self.mouse_prev_y = event.mouse_region_x, event.mouse_region_y
|
|
|
|
def modal(self, context, event):
|
|
self.area.tag_redraw()
|
|
if event.type == 'MOUSEMOVE':
|
|
if self.is_panning:
|
|
self.pan(context, event)
|
|
self.update(context, event)
|
|
|
|
# Confirm
|
|
elif event.type in {'LEFTMOUSE', 'RET'}:
|
|
bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
|
|
for area in context.screen.areas:
|
|
area.tag_redraw()
|
|
# Bind the environment texture to the sun
|
|
context.scene.sun_pos_properties.bind_to_sun = True
|
|
context.workspace.status_text_set(None)
|
|
return {'FINISHED'}
|
|
|
|
# Cancel
|
|
elif event.type in {'RIGHTMOUSE', 'ESC'}:
|
|
bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
|
|
for area in context.screen.areas:
|
|
area.tag_redraw()
|
|
# Reset previous values
|
|
context.scene.sun_pos_properties.hdr_elevation = self.initial_elevation
|
|
context.scene.sun_pos_properties.hdr_azimuth = self.initial_azimuth
|
|
context.workspace.status_text_set(None)
|
|
return {'CANCELLED'}
|
|
|
|
# Set exposure or zoom
|
|
elif event.type == 'WHEELUPMOUSE':
|
|
# Exposure
|
|
if event.ctrl:
|
|
self.exposure *= 1.1
|
|
# Zoom
|
|
else:
|
|
self.scale *= 1.1
|
|
self.offset -= (self.mouse_position - (Vector((self.right, self.top)) / 2 + self.offset)) / 10.0
|
|
self.update(context, event)
|
|
elif event.type == 'WHEELDOWNMOUSE':
|
|
# Exposure
|
|
if event.ctrl:
|
|
self.exposure /= 1.1
|
|
# Zoom
|
|
else:
|
|
self.scale /= 1.1
|
|
self.offset += (self.mouse_position - (Vector((self.right, self.top)) / 2 + self.offset)) / 11.0
|
|
self.update(context, event)
|
|
|
|
# Toggle pan
|
|
elif event.type == 'MIDDLEMOUSE':
|
|
if event.value == 'PRESS':
|
|
self.mouse_prev_x, self.mouse_prev_y = event.mouse_region_x, event.mouse_region_y
|
|
self.is_panning = True
|
|
elif event.value == 'RELEASE':
|
|
self.is_panning = False
|
|
|
|
else:
|
|
return {'PASS_THROUGH'}
|
|
|
|
return {'RUNNING_MODAL'}
|
|
|
|
def invoke(self, context, event):
|
|
self.is_panning = False
|
|
self.mouse_prev_x = 0.0
|
|
self.mouse_prev_y = 0.0
|
|
self.offset = Vector((0.0, 0.0))
|
|
self.scale = 1.0
|
|
|
|
# Get at least one 3D View
|
|
area_3d = None
|
|
for a in context.screen.areas:
|
|
if a.type == 'VIEW_3D':
|
|
area_3d = a
|
|
break
|
|
|
|
if area_3d is None:
|
|
self.report({'ERROR'}, 'Could not find 3D View')
|
|
return {'CANCELLED'}
|
|
|
|
nt = context.scene.world.node_tree.nodes
|
|
env_tex_node = nt.get(context.scene.sun_pos_properties.hdr_texture)
|
|
if env_tex_node.type != "TEX_ENVIRONMENT":
|
|
self.report({'ERROR'}, 'Please select an Environment Texture node')
|
|
return {'CANCELLED'}
|
|
|
|
self.area = context.area
|
|
|
|
self.mouse_position = event.mouse_region_x, event.mouse_region_y
|
|
|
|
self.initial_elevation = context.scene.sun_pos_properties.hdr_elevation
|
|
self.initial_azimuth = context.scene.sun_pos_properties.hdr_azimuth
|
|
|
|
context.workspace.status_text_set("Enter/LMB: confirm, Esc/RMB: cancel, MMB: pan, mouse wheel: zoom, Ctrl + mouse wheel: set exposure")
|
|
|
|
self._handle = bpy.types.SpaceView3D.draw_handler_add(draw_callback_px,
|
|
(self, context), 'WINDOW', 'POST_PIXEL')
|
|
context.window_manager.modal_handler_add(self)
|
|
|
|
return {'RUNNING_MODAL'}
|