Commit 20e0b6c2 authored by Karol Actun's avatar Karol Actun
Browse files

added animation with automatic speed adjustment according to the set rounds per second value

parent 986882d3
......@@ -59,7 +59,7 @@ grid_size = 100
;particle_model_file = 3d_particle.obj
;tile_model_file = 3d_ccp_tile.obj
;location_model_file = 3d_location.obj
;grid_size = 10
;grid_size = 0
# end of ccp grid configs
......@@ -113,6 +113,14 @@ show_border = True
# color of the border lines
border_color = (1.0, 0.0, 0.0, 1.0)
# animation flag
animation = True
# automatic speed adjustment for the animation
auto_animation = True
# speed for manual adjustment of the animation (if auto_animation is false)
manual_animation_speed = 50
[World]
## False = Unlimited world size
......
......@@ -2,17 +2,25 @@ from PyQt5.QtGui import QColor, QIntValidator
from PyQt5.QtWidgets import (QVBoxLayout, QPushButton, QColorDialog, QRadioButton, QLabel, QTabWidget,
QSlider, QHBoxLayout, QCheckBox, QTabBar, QLineEdit, QGroupBox, QComboBox, QStyle)
from PyQt5.QtCore import Qt
from lib.vis3d import Visualization
from lib.visualization.utils import show_msg
from lib.world import World
# global variables for all te functions..
world: World
vis: Visualization
simulation_tab: QTabBar
start_stop_button: QPushButton
def create_gui(w, v):
global world, vis, simtab
def create_gui(w: World, v: Visualization):
global world, vis, simulation_tab
world = w
vis = v
tabbar = QTabWidget()
tabbar.setMinimumWidth(200)
simtab = sim_tab()
tabbar.addTab(simtab, "Simulation")
simulation_tab = sim_tab()
tabbar.addTab(simulation_tab, "Simulation")
tabbar.addTab(vis_tab(), "Visualization")
tabbar.addTab(grid_tab(), "Grid")
tabbar.addTab(matter_tab(), "Matter")
......@@ -21,8 +29,8 @@ def create_gui(w, v):
def set_disable_sim(disable_flag):
global simtab
simtab.setDisabled(disable_flag)
global simulation_tab
simulation_tab.setDisabled(disable_flag)
def key_handler(key, w, v):
......@@ -294,6 +302,13 @@ def vis_tab():
sens_slider_box.setLayout(vbox)
layout.addWidget(sens_slider_box)
anim_box = QGroupBox("animation")
vbox = QVBoxLayout()
vbox.addWidget(get_animation_checkbox())
vbox.addLayout(get_auto_animation())
anim_box.setLayout(vbox)
layout.addWidget(anim_box)
misc_box = QGroupBox("miscellaneous")
vbox = QVBoxLayout()
vbox.addLayout(get_antialiasing_combobox())
......@@ -303,11 +318,49 @@ def vis_tab():
vbox.addWidget(reset_position_button)
misc_box.setLayout(vbox)
layout.addWidget(misc_box)
layout.addStretch(0)
tab.setLayout(layout)
return tab
def get_animation_checkbox():
chkbx = QCheckBox("enable")
chkbx.setChecked(vis.get_animation())
def chkbx_click():
vis.set_animation(chkbx.isChecked())
chkbx.clicked.connect(chkbx_click)
return chkbx
def get_auto_animation():
mss_label = QLabel("speed (%d steps per round):" % vis.get_manual_animation_speed())
def mss_change(value):
vis.set_manual_animation_speed(1000-value)
mss_label.setText("speed (%d step%s):" % (vis.get_manual_animation_speed(), '' if 1000-value == 1 else 's'))
mss = create_slider(1, 100, 999, 0, 1000-vis.get_manual_animation_speed(), mss_change, Qt.Horizontal)
mss.setDisabled(vis.get_auto_animation())
mss_label.setDisabled(vis.get_auto_animation())
chkbx = QCheckBox("automatic speed adjustment")
chkbx.setChecked(vis.get_auto_animation())
def chkbx_click():
mss.setDisabled(chkbx.isChecked())
mss_label.setDisabled(chkbx.isChecked())
vis.set_auto_animation(chkbx.isChecked())
chkbx.clicked.connect(chkbx_click)
vbox = QVBoxLayout()
vbox.addWidget(chkbx)
vbox.addWidget(mss_label)
vbox.addWidget(mss)
return vbox
def grid_tab():
tab = QTabBar()
layout = QVBoxLayout()
......@@ -501,7 +554,7 @@ def get_color_picker():
def bg():
qcd = QColorDialog()
qcd.setCurrentColor(QColor.fromRgbF(*vis.get_background_color()))
cd.setWindowFlags(Qt.WindowStaysOnTopHint)
qcd.setWindowFlags(Qt.WindowStaysOnTopHint)
qcd.exec()
if qcd.result() == 1:
vis.set_background_color((qcd.selectedColor().getRgbF()[:3]))
......
......@@ -72,6 +72,10 @@ class ConfigData:
self.show_border = config.getboolean("Visualization", "show_border")
self.border_color = make_tuple(config.get("Visualization", "border_color"))
self.animation = config.getboolean("Visualization", "animation")
self.auto_animation = config.getboolean("Visualization", "auto_animation")
self.manual_animation_speed = config.getint("Visualization", "manual_animation_speed")
self.size_x = config.getfloat("World", "size_x")
self.size_y = config.getfloat("World", "size_y")
self.size_z = config.getfloat("World", "size_z")
......
......@@ -3,6 +3,7 @@ import os
from threading import Thread
import cv2
from OpenGL import GL
from PyQt5.QtWidgets import QApplication, QSplitter, QWidget, QFileDialog
from lib.visualization.recorder import Recorder
......@@ -34,6 +35,9 @@ class Visualization:
self._gui = None
self._splitter = None
self._recording = False
self._animation = world.config_data.animation
self._auto_animation = world.config_data.auto_animation
self._manual_animation_speed = world.config_data.manual_animation_speed
self.light_rotation = False
self.grid_size = world.grid.size
......@@ -41,7 +45,7 @@ class Visualization:
self._app = QApplication([])
# create camera for the visualization
# if grid is 2D, set to ortho (ortho is better for 2D)
# if grid is 2D, set to orthographic projection (it is better for 2D)
if self._world.grid.get_dimension_count() == 2:
self._camera = Camera(self._world.config_data.window_size_x, self._world.config_data.window_size_y,
self._world.config_data.look_at, self._world.config_data.phi,
......@@ -97,6 +101,7 @@ class Visualization:
self._splitter.setSizes(
[self._world.config_data.window_size_x * 0.25, self._world.config_data.window_size_x * 0.75])
else:
# noinspection PyUnresolvedReferences
show_msg("The create_gui(world, vis) function in gui module didn't return a QWidget." +
"Expected a QWidget or a subclass, but got %s."
% self._gui.__class__.__name__, 1)
......@@ -193,6 +198,48 @@ class Visualization:
self.rotate_light()
self._app.processEvents()
def animate(self, round_start_time, speed):
"""
loop for animating the movement of particles and carried tiles
:param round_start_time: the start of the round
:param speed: speed of the animation in 1/steps. less or equal zero = automatic mode
"""
if speed < 0:
# measure the drawing of one frame
start = time.perf_counter()
self._viewer.set_animation_percentage(0)
self._viewer.glDraw()
GL.glFinish()
frametime = time.perf_counter() - start
# using the rounds_per_second and the
# timestamp of the start of the round, calculate the time left for animation
timeleft = (1/self._rounds_per_second) - (time.perf_counter() - round_start_time)
# calculate the amount of animation steps we can do in the time left
steps = int(timeleft / frametime / 1.5)
else:
steps = speed
# animate
for i in range(1, steps):
self._viewer.set_animation_percentage(float(i/steps))
self._app.processEvents()
self._viewer.glDraw()
# draw the last frame. its outside of the loop, so that if steps is equal or less then one,
# the particles will still be drawn at the correct locations.
self._viewer.set_animation_percentage(1.0)
self._viewer.glDraw()
# reset the previous position after animation.
# not reseting it causes a small visual bug if the particle didn't move.
for particle in self._viewer.particle_offset_data:
current_data = self._viewer.particle_offset_data[particle]
self._viewer.particle_offset_data[particle] = (current_data[0], current_data[1], particle.coordinates,
current_data[3])
self._viewer.particle_update_flag = True
def run(self, round_start_timestamp):
"""
main function for running the simulation with the visualization.
......@@ -200,9 +247,13 @@ class Visualization:
:param round_start_timestamp: timestamp of the start of the round.
:return:
"""
# update and draw scene
# update and draw/animate scene
self._viewer.update_data()
self._viewer.glDraw()
if self._animation:
self.animate(round_start_timestamp, -1 if self._auto_animation else self._manual_animation_speed)
else:
self._viewer.glDraw()
# waiting until simulation starts
self._wait_while_not_running()
if self._recording:
......@@ -242,7 +293,10 @@ class Visualization:
:return:
"""
self._viewer.particle_update_flag = True
self._viewer.particle_offset_data[particle] = (particle.coordinates, particle.color,
prev_pos = particle.coordinates
if particle in self._viewer.particle_offset_data:
prev_pos = self._viewer.particle_offset_data[particle][0]
self._viewer.particle_offset_data[particle] = (particle.coordinates, particle.color, prev_pos,
1.0 if particle.get_carried_status() else 0.0)
def remove_tile(self, tile):
......@@ -262,7 +316,11 @@ class Visualization:
:return:
"""
self._viewer.tile_update_flag = True
self._viewer.tile_offset_data[tile] = (tile.coordinates, tile.color, 1.0 if tile.get_tile_status() else 0.0)
prev_pos = tile.coordinates
if tile in self._viewer.tile_offset_data:
prev_pos = self._viewer.particle_offset_data[tile][0]
self._viewer.tile_offset_data[tile] = (tile.coordinates, tile.color, prev_pos,
1.0 if tile.get_tile_status() else 0.0)
def remove_location(self, location):
"""
......@@ -582,5 +640,22 @@ class Visualization:
else:
show_msg("Not implemented yet.\nWorks only with Triangular Grid for now!\nSorry!", 2)
def set_animation(self, animation):
if not animation:
self._viewer.set_animation_percentage(1)
self._animation = animation
def get_animation(self):
return self._animation
def set_auto_animation(self, auto_animation):
self._auto_animation = auto_animation
def get_auto_animation(self):
return self._auto_animation
def set_manual_animation_speed(self, manual_animation_speed):
self._manual_animation_speed = manual_animation_speed
def get_manual_animation_speed(self):
return self._manual_animation_speed
\ No newline at end of file
......@@ -104,11 +104,13 @@ class OGLWidget(QtOpenGL.QGLWidget):
if len(tmp) == 0:
self.programs["particle"].update_offsets([])
self.programs["particle"].update_colors([])
self.programs["particle"].update_previous_positions([])
self.programs["particle"].update_carried([])
else:
self.programs["particle"].update_offsets(tmp[0].tolist())
self.programs["particle"].update_colors(tmp[1].tolist())
self.programs["particle"].update_carried(tmp[2].tolist())
self.programs["particle"].update_previous_positions(tmp[2].tolist())
self.programs["particle"].update_carried(tmp[3].tolist())
if self.tile_update_flag:
self.tile_update_flag = False
......@@ -116,12 +118,14 @@ class OGLWidget(QtOpenGL.QGLWidget):
if len(tmp) == 0:
self.programs["tile"].update_offsets([])
self.programs["tile"].update_colors([])
self.programs["tile"].update_previous_positions([])
self.programs["tile"].update_carried([])
else:
self.programs["tile"].update_offsets(tmp[0].tolist())
self.programs["tile"].update_colors(tmp[1].tolist())
self.programs["tile"].update_carried(tmp[2].tolist())
self.programs["tile"].update_previous_positions(tmp[2].tolist())
self.programs["tile"].update_carried(tmp[3].tolist())
if self.location_update_flag:
self.location_update_flag = False
......@@ -154,10 +158,12 @@ class OGLWidget(QtOpenGL.QGLWidget):
self.programs["particle"] = OffsetColorCarryProgram(self.world.config_data.particle_model_file)
self.programs["particle"].set_world_scaling(self.world.grid.get_scaling())
self.programs["particle"].set_model_scaling(self.world.config_data.particle_scaling)
self.programs["particle"].set_animation(True)
self.programs["tile"] = OffsetColorCarryProgram(self.world.config_data.tile_model_file)
self.programs["tile"].set_world_scaling(self.world.grid.get_scaling())
self.programs["tile"].set_model_scaling(self.world.config_data.tile_scaling)
self.programs["tile"].set_animation(True)
self.programs["location"] = OffsetColorProgram(self.world.config_data.location_model_file)
self.programs["location"].set_world_scaling(self.world.grid.get_scaling())
......@@ -564,4 +570,8 @@ class OGLWidget(QtOpenGL.QGLWidget):
def enable_aa(self, value):
GL.glEnable(GL.GL_MULTISAMPLE)
self.fmt.setSamples(value)
\ No newline at end of file
self.fmt.setSamples(value)
def set_animation_percentage(self, animation_percentage):
self.programs["particle"].set_animation_percentage(animation_percentage)
self.programs["tile"].set_animation_percentage(animation_percentage)
\ No newline at end of file
import OpenGL.GL as GL
from lib.visualization.programs.offset_color_program import OffsetColorProgram
import ctypes
import numpy as np
from lib.visualization.programs.offset_color_program import OffsetColorProgram
from lib.visualization.utils import show_msg
class OffsetColorCarryProgram(OffsetColorProgram):
......@@ -23,22 +24,81 @@ class OffsetColorCarryProgram(OffsetColorProgram):
super()._init_buffers(v, n, _)
self.vbos.append(GL.glGenBuffers(1))
# init VBO 2 - dynamic color data
# init VBO 3 - dynamic previous position data
GL.glBindBuffer(GL.GL_ARRAY_BUFFER, self.vbos[3])
loc = self.get_attribute_location("prev_pos")
GL.glEnableVertexAttribArray(loc)
GL.glVertexAttribPointer(loc, 3, GL.GL_FLOAT, GL.GL_FALSE, 0, ctypes.c_void_p(0))
GL.glVertexAttribDivisor(loc, 1)
GL.glBufferData(GL.GL_ARRAY_BUFFER, 0, np.array([], dtype=np.float32), GL.GL_DYNAMIC_DRAW)
GL.glBindBuffer(GL.GL_ARRAY_BUFFER, 0)
self.vbos.append(GL.glGenBuffers(1))
# init VBO 4 - dynamic carried flag data
GL.glBindBuffer(GL.GL_ARRAY_BUFFER, self.vbos[4])
loc = self.get_attribute_location("carried")
GL.glEnableVertexAttribArray(loc)
GL.glVertexAttribPointer(loc, 1, GL.GL_FLOAT, GL.GL_FALSE, 0, ctypes.c_void_p(0))
GL.glVertexAttribDivisor(loc, 1)
GL.glBufferData(GL.GL_ARRAY_BUFFER, 0, np.array([], dtype=np.float32), GL.GL_DYNAMIC_DRAW)
def _init_uniforms(self):
super(OffsetColorCarryProgram, self)._init_uniforms()
self.set_animation(False)
def update_carried(self, data):
"""
updates the carry flag data (VBO3)
:param data: list/array of floats (1.0 = True, 0.0 = False).
:return:
"""
self.use()
gpu_data = np.array(data, dtype=np.float32)
gpu_data = np.array(data, dtype=np.float32).flatten()
GL.glBindBuffer(GL.GL_ARRAY_BUFFER, self.vbos[4])
GL.glBufferData(GL.GL_ARRAY_BUFFER, gpu_data.nbytes, gpu_data, GL.GL_DYNAMIC_DRAW)
def update_previous_positions(self, data):
"""
updates the previous positions data (VBO 3)
:param data: array of 3d positions
"""
self.use()
gpu_data = np.array(data, dtype=np.float32).flatten()
GL.glBindBuffer(GL.GL_ARRAY_BUFFER, self.vbos[3])
GL.glBufferData(GL.GL_ARRAY_BUFFER, gpu_data.nbytes, gpu_data, GL.GL_DYNAMIC_DRAW)
if len(gpu_data) % 3.0 != 0.0:
show_msg("Invalid previous positions data! Amount of coordinate "
"components not dividable by 3 (not in xyz format?)!", 2)
def set_animation(self, animation: bool):
"""
sets the animation flag
:param animation: bool, if true the animation will be running
"""
self.use()
loc = self.get_uniform_location("animation")
GL.glUniform1i(loc, animation)
def get_animation(self):
"""
reads the animation flag from the vertex shader
:return: bool
"""
self.use()
return self.get_uniform("animation", 1)
def set_animation_percentage(self, animation_percentage: float):
"""
sets the animation flag
:param animation_percentage: bool, if true the animation will be running
"""
self.use()
loc = self.get_uniform_location("animation_percentage")
GL.glUniform1f(loc, animation_percentage)
def get_animation_percentage(self):
"""
reads the animation_percentage value from the vertex shader
:return: float
"""
self.use()
return self.get_uniform("animation_percentage", 1)
......@@ -10,10 +10,11 @@ in vec3 normal;
in vec3 offset;
// VBO 2 - per instance - color of the model
in vec4 color;
// VBO 3 - per instance - is carried
// VBO 3 - per instance - previous position, needed for animation
in vec3 prev_pos;
// VBO 4 - per instance - is carried
in float carried;
// uniforms
// projection matrix
uniform mat4 projection;
......@@ -31,6 +32,10 @@ uniform float ambient_light;
uniform vec3 light_direction;
// color of the parallel lightsource
uniform vec4 light_color;
// percentage of animation position between prev_pos and offset
uniform float animation_percentage;
// flag for using animation
uniform bool animation;
// varying color for fragment shader
out vec4 v_color;
......@@ -43,7 +48,13 @@ void main(void)
* max(max(ambient_light, dot(nn, light_direction)), dot(-nn, light_direction));
mat4 use_world = world;
use_world[3] += vec4(offset * world_scaling, 0);
vec3 real_offset;
if(animation){
real_offset = prev_pos+((offset-prev_pos)*animation_percentage);
}else{
real_offset = offset;
}
use_world[3] += vec4(real_offset * world_scaling, 0);
float alpha = color[3];
if(carried > 0.5){
use_world[0][0] = 0.5;
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment