You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
428 lines
11 KiB
428 lines
11 KiB
5 years ago
|
"""
|
||
|
This module defines functions that need to be overwritten
|
||
|
in order for OpenGL to work with the main window. This
|
||
|
module is named the same as the actual widget in order
|
||
|
to make namespacing consistent.
|
||
|
|
||
|
To be clear, the actual widget is defined in the UI
|
||
|
generated code - `voronoiview_ui.py`. The functions
|
||
|
here are imported as overrides to the OpenGL functions of
|
||
|
that widget.
|
||
|
|
||
|
It should be split up into a few more separate files eventually...
|
||
|
Probably even into it's own module folder.
|
||
|
"""
|
||
|
|
||
|
import math
|
||
|
from typing import List
|
||
|
|
||
|
from OpenGL.GL import (glBegin, glClearColor, glColor3f, glColor4f,
|
||
|
glEnable, glEnd, GL_LINES, GL_LINE_LOOP, GL_LINE_SMOOTH,
|
||
|
GL_POINTS, glPointSize, glVertex3f,
|
||
|
glViewport)
|
||
|
|
||
|
from voronoiview.colors import Color, COLOR_TO_RGBA
|
||
|
from voronoiview.exceptions import (handle_exceptions,
|
||
|
InvalidStateError)
|
||
|
from voronoiview.mode import Mode
|
||
|
from voronoiview.point_manager import PointManager
|
||
|
|
||
|
# Constants set based on the size of the window.
|
||
|
__BOTTOM_LEFT = (0, 0)
|
||
|
__WIDTH = None
|
||
|
__HEIGHT = None
|
||
|
|
||
|
# State variables for a move selection bounding box.
|
||
|
# There are always reset to None after a selection has been made.
|
||
|
__move_bb_top_left = None
|
||
|
__move_bb_bottom_right = None
|
||
|
|
||
|
# Module-global state variables for our drawing
|
||
|
# state machine.
|
||
|
#
|
||
|
# Below functions have to mark these as `global` so
|
||
|
# the interpreter knows that the variables are not
|
||
|
# function local.
|
||
|
__current_context = None
|
||
|
__current_event = None
|
||
|
|
||
|
|
||
|
# TODO: This should live inside of a class as static methods with the
|
||
|
# globals moved into the static scope to make this nicer...once you
|
||
|
# get it running before doing kmeans make this modification.
|
||
|
|
||
|
def set_drawing_context(ctx):
|
||
|
"""
|
||
|
Sets the drawing context so that drawing functions can properly
|
||
|
interact with the widget.
|
||
|
"""
|
||
|
global __current_context
|
||
|
|
||
|
__current_context = ctx
|
||
|
|
||
|
|
||
|
def set_drawing_event(event):
|
||
|
"""
|
||
|
State machine event management function.
|
||
|
|
||
|
@param event The event.
|
||
|
"""
|
||
|
global __current_context
|
||
|
global __current_event
|
||
|
|
||
|
if __current_context is None:
|
||
|
raise InvalidStateError('Drawing context must be set before setting ' +
|
||
|
'drawing mode')
|
||
|
|
||
|
if event is not None:
|
||
|
__current_event = event
|
||
|
|
||
|
|
||
|
def mouse_leave(ctx, event):
|
||
|
"""
|
||
|
The leave event for the OpenGL widget to properly reset the mouse
|
||
|
position label.
|
||
|
|
||
|
@param ctx The context.
|
||
|
@param event The event.
|
||
|
"""
|
||
|
ctx.mouse_position_label.setText('')
|
||
|
|
||
|
|
||
|
def set_move_bb_top_left(x, y):
|
||
|
"""
|
||
|
Called to set the move bounding box's top left corner.
|
||
|
|
||
|
@param x The x-coordinate.
|
||
|
@param y The y-coordinate.
|
||
|
"""
|
||
|
global __move_bb_top_left
|
||
|
|
||
|
__move_bb_top_left = (x, y)
|
||
|
|
||
|
|
||
|
def set_move_bb_bottom_right(x, y):
|
||
|
"""
|
||
|
Called to set the move bounding box's bottom right corner.
|
||
|
|
||
|
@param x The x-coordinate.
|
||
|
@param y The y-coordinate.
|
||
|
"""
|
||
|
global __move_bb_bottom_right
|
||
|
|
||
|
__move_bb_bottom_right = (x, y)
|
||
|
|
||
|
|
||
|
def get_bb_top_left():
|
||
|
return __move_bb_top_left
|
||
|
|
||
|
|
||
|
def get_bb_bottom_right():
|
||
|
return __move_bb_bottom_right
|
||
|
|
||
|
|
||
|
def reset_move_bbs():
|
||
|
global __move_bb_top_left
|
||
|
global __move_bb_bottom_right
|
||
|
|
||
|
__move_bb_top_left = None
|
||
|
__move_bb_bottom_right = None
|
||
|
|
||
|
|
||
|
def initialize_gl():
|
||
|
"""
|
||
|
Initializes the OpenGL context on the Window.
|
||
|
"""
|
||
|
|
||
|
# Set white background
|
||
|
glClearColor(255, 255, 255, 0)
|
||
|
|
||
|
|
||
|
def resize_gl(w, h):
|
||
|
"""
|
||
|
OpenGL resize handler used to get the current viewport size.
|
||
|
|
||
|
@param w The new width.
|
||
|
@param h The new height.
|
||
|
"""
|
||
|
global __WIDTH
|
||
|
global __HEIGHT
|
||
|
|
||
|
__WIDTH = __current_context.opengl_widget.width()
|
||
|
__HEIGHT = __current_context.opengl_widget.height()
|
||
|
|
||
|
|
||
|
def viewport_width():
|
||
|
return __WIDTH
|
||
|
|
||
|
|
||
|
def viewport_height():
|
||
|
return __HEIGHT
|
||
|
|
||
|
|
||
|
@handle_exceptions
|
||
|
def paint_gl():
|
||
|
"""
|
||
|
Stock PaintGL function from OpenGL that switches
|
||
|
on the current mode to determine what action to
|
||
|
perform on the current event.
|
||
|
"""
|
||
|
if(__current_context.mode is Mode.OFF and
|
||
|
not PointManager.point_set.empty()):
|
||
|
|
||
|
# We want to redraw on any change to Mode.OFF so points are preserved -
|
||
|
# without this, any switch to Mode.OFF will cause a blank screen to
|
||
|
# render.
|
||
|
draw_points(PointManager.point_set)
|
||
|
|
||
|
if (__current_context.mode in [Mode.ADD, Mode.EDIT,
|
||
|
Mode.MOVE, Mode.DELETE] and
|
||
|
__current_event is None and PointManager.point_set.empty()):
|
||
|
return
|
||
|
|
||
|
if (__current_context.mode in [Mode.ADD, Mode.EDIT, Mode.DELETE] and
|
||
|
PointManager.point_set.empty()):
|
||
|
return
|
||
|
|
||
|
if (__current_context.mode is Mode.ADD or
|
||
|
__current_context.mode is Mode.DELETE or
|
||
|
__current_context.mode is Mode.EDIT or
|
||
|
__current_context.mode is Mode.LOADED or
|
||
|
__current_context.mode is Mode.VORONOI):
|
||
|
|
||
|
draw_points(PointManager.point_set)
|
||
|
|
||
|
if (__current_context.mode is Mode.VORONOI and
|
||
|
__current_context.voronoi_solved):
|
||
|
|
||
|
draw_voronoi_diagram()
|
||
|
|
||
|
elif __current_context.mode is Mode.MOVE:
|
||
|
# We have to repeatedly draw the points while we are showing the
|
||
|
# move box.
|
||
|
if not PointManager.point_set.empty():
|
||
|
draw_points(PointManager.point_set)
|
||
|
|
||
|
draw_selection_box(Color.BLACK)
|
||
|
|
||
|
if (__move_bb_top_left is not None and
|
||
|
__move_bb_bottom_right is not None):
|
||
|
|
||
|
# Mark points that are selected in the bounding box
|
||
|
# and draw them using the normal function
|
||
|
highlight_selection()
|
||
|
draw_points(PointManager.point_set)
|
||
|
|
||
|
|
||
|
def __clamp_x(x):
|
||
|
"""
|
||
|
X-coordinate clamping function that goes from mouse coordinates to
|
||
|
OpenGL coordinates.
|
||
|
|
||
|
@param x The x-coordinate to clamp.
|
||
|
@returns The clamped x coordinate.
|
||
|
"""
|
||
|
x_w = (x / (__WIDTH / 2.0) - 1.0)
|
||
|
return x_w
|
||
|
|
||
|
|
||
|
def __clamp_y(y):
|
||
|
"""
|
||
|
Y-coordinate clamping function that goes from mouse coordinates to
|
||
|
OpenGL coordinates.
|
||
|
|
||
|
@param y The y-coordinate to clamp.
|
||
|
@returns The clamped y coordinate.
|
||
|
"""
|
||
|
y_w = -1.0 * (y / (__HEIGHT / 2.0) - 1.0)
|
||
|
return y_w
|
||
|
|
||
|
|
||
|
def box_hit(tx, ty, x1, y1, x2, y2):
|
||
|
"""
|
||
|
Calculates whether or not a given point collides with the given bounding
|
||
|
box.
|
||
|
|
||
|
@param tx The target x.
|
||
|
@param ty The target y.
|
||
|
@param x1 The top left x.
|
||
|
@param y1 The top left y.
|
||
|
@param x2 The bottom left x.
|
||
|
@param y2 The bottom left y.
|
||
|
"""
|
||
|
|
||
|
# The box in this case is flipped - the user started at the bottom right
|
||
|
# corner. Pixel-wise top left is (0, 0) and bottom right is
|
||
|
# (screen_x, screen_y)
|
||
|
if x1 > x2 and y1 > y2:
|
||
|
return (tx <= x1 and
|
||
|
tx >= x2 and
|
||
|
ty <= y1 and
|
||
|
ty >= y2)
|
||
|
|
||
|
# The box in this case started from the top right
|
||
|
if x1 > x2 and y1 < y2:
|
||
|
return (tx <= x1 and
|
||
|
tx >= x2 and
|
||
|
ty >= y1 and
|
||
|
ty <= y2)
|
||
|
|
||
|
# The box in this case started from the bottom left
|
||
|
if x1 < x2 and y1 > y2:
|
||
|
return (tx >= x1 and
|
||
|
tx <= x2 and
|
||
|
ty <= y1 and
|
||
|
ty >= y2)
|
||
|
|
||
|
# Normal condition: Box starts from the top left
|
||
|
return (tx >= x1 and
|
||
|
tx <= x2 and
|
||
|
ty >= y1 and
|
||
|
ty <= y2)
|
||
|
|
||
|
|
||
|
def highlight_selection():
|
||
|
"""
|
||
|
Given the current move bounding box, highlights any points inside it.
|
||
|
"""
|
||
|
|
||
|
top_left = get_bb_top_left()
|
||
|
bottom_right = get_bb_bottom_right()
|
||
|
|
||
|
for point in PointManager.point_set.points:
|
||
|
if box_hit(point.x, point.y, top_left[0], top_left[1],
|
||
|
bottom_right[0], bottom_right[1]):
|
||
|
|
||
|
point.select()
|
||
|
else:
|
||
|
point.unselect()
|
||
|
|
||
|
|
||
|
def draw_selection_box(color):
|
||
|
"""
|
||
|
When the move bounding box state is populated and the mode is set
|
||
|
to MODE.Move this function will draw the selection bounding box.
|
||
|
|
||
|
@param color The color Enum.
|
||
|
"""
|
||
|
global __current_context
|
||
|
|
||
|
if __current_context is None:
|
||
|
raise InvalidStateError('Drawing context must be set before setting ' +
|
||
|
'drawing mode')
|
||
|
|
||
|
if not isinstance(color, Color):
|
||
|
raise ValueError('Color must exist in the Color enumeration')
|
||
|
|
||
|
if __move_bb_top_left is None or __move_bb_bottom_right is None:
|
||
|
# Nothing to draw.
|
||
|
return
|
||
|
|
||
|
ct = COLOR_TO_RGBA[color]
|
||
|
|
||
|
glViewport(0, 0, __WIDTH, __HEIGHT)
|
||
|
|
||
|
# Top right corner has the same x as the bottom right
|
||
|
# and same y as the top left.
|
||
|
top_right_corner = (__move_bb_bottom_right[0], __move_bb_top_left[1])
|
||
|
|
||
|
# Bottom left corner has the same x as the top left and
|
||
|
# same y as the bottom right.
|
||
|
bottom_left_corner = (__move_bb_top_left[0], __move_bb_bottom_right[1])
|
||
|
|
||
|
glBegin(GL_LINE_LOOP)
|
||
|
glColor3f(ct[0], ct[1], ct[2])
|
||
|
|
||
|
glVertex3f(__clamp_x(__move_bb_top_left[0]),
|
||
|
__clamp_y(__move_bb_top_left[1]),
|
||
|
0.0)
|
||
|
|
||
|
glVertex3f(__clamp_x(top_right_corner[0]),
|
||
|
__clamp_y(top_right_corner[1]),
|
||
|
0.0)
|
||
|
|
||
|
glVertex3f(__clamp_x(__move_bb_bottom_right[0]),
|
||
|
__clamp_y(__move_bb_bottom_right[1]),
|
||
|
0.0)
|
||
|
|
||
|
glVertex3f(__clamp_x(bottom_left_corner[0]),
|
||
|
__clamp_y(bottom_left_corner[1]),
|
||
|
0.0)
|
||
|
|
||
|
glEnd()
|
||
|
|
||
|
|
||
|
def clear_selection():
|
||
|
"""
|
||
|
A helper designed to be called from the main window
|
||
|
in order to clear the selection internal to the graphics
|
||
|
and mode files. This way you dont have to do something
|
||
|
before the selection clears.
|
||
|
"""
|
||
|
if not PointManager.point_set.empty():
|
||
|
PointManager.point_set.clear_selection()
|
||
|
|
||
|
|
||
|
def draw_points(point_set):
|
||
|
"""
|
||
|
Simple point drawing function.
|
||
|
|
||
|
Given a coordinate (x, y), and a Color enum this
|
||
|
function will draw the given point with the given
|
||
|
color.
|
||
|
|
||
|
@param point_set The PointSet to draw.
|
||
|
@param color The Color Enum.
|
||
|
"""
|
||
|
global __current_context
|
||
|
|
||
|
if __current_context is None:
|
||
|
raise InvalidStateError('Drawing context must be set before setting ' +
|
||
|
'drawing mode')
|
||
|
|
||
|
glViewport(0, 0, __WIDTH, __HEIGHT)
|
||
|
glPointSize(PointManager.point_set.point_size)
|
||
|
|
||
|
glBegin(GL_POINTS)
|
||
|
for point in point_set.points:
|
||
|
|
||
|
if point.selected:
|
||
|
blue = COLOR_TO_RGBA[Color.BLUE]
|
||
|
glColor3f(blue[0], blue[1], blue[2])
|
||
|
else:
|
||
|
ct = COLOR_TO_RGBA[point.color]
|
||
|
glColor3f(ct[0], ct[1], ct[2])
|
||
|
|
||
|
glVertex3f(__clamp_x(point.x),
|
||
|
__clamp_y(point.y),
|
||
|
0.0) # Z is currently fixed to 0
|
||
|
glEnd()
|
||
|
|
||
|
|
||
|
def draw_voronoi_diagram():
|
||
|
"""
|
||
|
Draws the voronoi regions to the screen. Uses the global point manager to draw the points.
|
||
|
"""
|
||
|
|
||
|
results = PointManager.voronoi_results
|
||
|
|
||
|
vertices = results.vertices
|
||
|
|
||
|
color = COLOR_TO_RGBA[Color.BLACK]
|
||
|
|
||
|
for region_indices in results.regions:
|
||
|
# The region index is out of bounds
|
||
|
if -1 in region_indices:
|
||
|
continue
|
||
|
|
||
|
glBegin(GL_LINE_LOOP)
|
||
|
for idx in region_indices:
|
||
|
vertex = vertices[idx]
|
||
|
|
||
|
glColor3f(color[0], color[1], color[2])
|
||
|
glVertex3f(__clamp_x(vertex[0]),
|
||
|
__clamp_y(vertex[1]),
|
||
|
0.0) # Z is currently fixed to 0
|
||
|
|
||
|
glEnd()
|