kinet-test / kinetix /editor.py
tree3po's picture
Update kinetix/editor.py
01831e9 verified
import os
os.system(f"cd {os.getcwd()}/Kinetix")
import itertools
import time
from timeit import default_timer as tmr
import optax
from PIL import Image
from flax.serialization import to_state_dict
from flax.training.train_state import TrainState
from matplotlib import pyplot as plt
from kinetix.environment.ued.distributions import sample_kinetix_level
from kinetix.models import make_network_from_config
from kinetix.models.actor_critic import ScannedRNN
from kinetix.render.renderer_symbolic_entity import make_render_entities
ss = tmr()
import jax
jax.config.update("jax_compilation_cache_dir", ".cache-location")
import hydra
from omegaconf import OmegaConf
from kinetix.environment.ued.mutators import (
make_mutate_change_shape_rotation,
mutate_add_connected_shape,
mutate_add_shape,
make_mutate_change_shape_size,
mutate_change_shape_location,
mutate_swap_role,
mutate_remove_shape,
mutate_remove_joint,
mutate_toggle_fixture,
mutate_add_thruster,
mutate_remove_thruster,
)
from kinetix.environment.ued.ued import make_mutate_env, ALL_MUTATION_FNS
from kinetix.environment.ued.ued_state import UEDParams
from kinetix.environment.ued.util import rectangle_vertices
from kinetix.util.config import generate_params_from_config, normalise_config
import argparse
import os
import sys
sys.path.append("editor")
import tkinter
import tkinter.filedialog
from enum import Enum
from timeit import default_timer as tmr
import jax.numpy as jnp
import numpy as np
import pygame
import pygame_widgets
from pygame_widgets.slider import Slider
from pygame_widgets.textbox import TextBox
from pygame_widgets.toggle import Toggle
from jax2d.engine import (
calc_inverse_inertia_circle,
calc_inverse_inertia_polygon,
calc_inverse_mass_circle,
calc_inverse_mass_polygon,
calculate_collision_matrix,
recalculate_mass_and_inertia,
recompute_global_joint_positions,
select_shape,
)
from jax2d.maths import rmat
from jax2d.sim_state import RigidBody
from kinetix.environment.env import (
create_empty_env,
make_kinetix_env_from_name,
)
from kinetix.environment.env_state import EnvParams, EnvState, StaticEnvParams
from kinetix.environment.utils import permute_pcg_state
from kinetix.environment.wrappers import AutoResetWrapper
from kinetix.pcg.pcg import env_state_to_pcg_state, sample_pcg_state
from kinetix.pcg.pcg_state import PCGState
from kinetix.render.renderer_pixels import make_render_pixels
from kinetix.render.textures import (
CIRCLE_TEXTURE_RGBA,
EDIT_TEXTURE_RGBA,
PLAY_TEXTURE_RGBA,
RECT_TEXTURE_RGBA,
RJOINT_TEXTURE_RGBA,
SELECT_TEXTURE_RGBA,
THRUSTER_TEXTURE_RGBA,
TRIANGLE_TEXTURE_RGBA,
)
from kinetix.util.saving import (
expand_pcg_state,
export_env_state_to_json,
get_pcg_state_from_json,
load_from_json_file,
load_pcg_state_pickle,
load_world_state_pickle,
save_pickle,
load_params_from_wandb_artifact_path,
load_train_state_from_wandb_artifact_path,
)
from kinetix.util.timing import time_function
from tkinter import Tk
#root = Tk()
#root.destroy()
ee = tmr()
print(f"Imported in {ee - ss} seconds")
editor = None
outer_timer = tmr()
EMPTY_ENV = False
class ObjectType(Enum):
POLYGON = 0
CIRCLE = 1
JOINT = 2
THRUSTER = 3
class EditMode(Enum):
ADD_CIRCLE = 0
ADD_RECTANGLE = 1
ADD_JOINT = 2
SELECT = 3
ADD_TRIANGLE = 4
ADD_THRUSTER = 5
TOTAL_DUMMY_STEPS_TO_SNAP = 0
SNAPPING_DIST = 0.1
def select_object(state: EnvState, type: int, index: int):
if type is None:
type = ObjectType.POLYGON
li = {0: state.polygon, 1: state.circle, 2: state.joint, 3: state.thruster}[type.value]
return jax.tree.map(lambda x: x[index], li)
def snap_to_center(shape: RigidBody, position: jnp.ndarray):
if jnp.linalg.norm(shape.position - position) < SNAPPING_DIST:
return shape.position
return position
def snap_to_polygon_center_line(polygon: RigidBody, position: jnp.ndarray):
# Snap to the center line
r = rmat(polygon.rotation)
x = jnp.matmul(r, position - polygon.position)
if jnp.abs(x[0]) < SNAPPING_DIST:
x = x.at[0].set(0.0)
if jnp.abs(x[1]) < SNAPPING_DIST:
x = x.at[1].set(0.0)
x = jnp.matmul(r.transpose(), x)
return x + polygon.position
def snap_to_circle_center_line(circle: RigidBody, position: jnp.ndarray):
# Snap to the center line, i.e. on the edge of the circle, if the position is close enough to directly below the circle, etc., snap the position to that
x = position - circle.position
if jnp.linalg.norm(x) < SNAPPING_DIST:
return circle.position
angle = (jnp.arctan2(x[1], x[0]) + 2 * jnp.pi) % (2 * jnp.pi)
for i in range(0, 8):
if jnp.abs(angle - i * jnp.pi / 4) < jnp.radians(25): # 25 degrees
angle = i * jnp.pi / 4
break
x = jnp.array([jnp.cos(angle), jnp.sin(angle)]) * circle.radius
return x + circle.position
def prompt_file(save=False):
dir = os.path.join(os.path.dirname(os.path.dirname(__file__)), "worlds")
"""Create a Tk file dialog and cleanup when finished"""
top = tkinter.Tk()
top.withdraw() # hide window
if save:
file_name = tkinter.filedialog.asksaveasfilename(parent=top, initialdir=dir)
else:
file_name = tkinter.filedialog.askopenfilename(parent=top, initialdir=dir)
top.destroy()
return file_name
def get_numeric_key_pressed(pygame_events, is_mod=False):
for event in pygame_events:
if not is_mod:
if event.type == pygame.KEYDOWN and event.unicode.isdigit():
return int(event.unicode)
else:
if event.type == pygame.KEYDOWN:
pass
if event.type == pygame.KEYDOWN and event.key in [pygame.K_0 + i for i in range(10)]:
return int(event.key - pygame.K_0)
return None
def new_env(static_env_params):
return create_empty_env(static_env_params)
myrng = jax.random.PRNGKey(0)
def make_reset_function(static_env_params):
def reset(rng):
return env_state_to_pcg_state(create_empty_env(static_env_params))
return reset
def new_pcg_env(static_env_params):
global myrng
if EMPTY_ENV:
env_state = create_empty_env(static_env_params)
else:
return get_pcg_state_from_json("worlds/l/h0_angrybirds.json")
return env_state_to_pcg_state(env_state)
class Editor:
def __init__(self, env, env_params, config, upscale=1):
self.env = env
self.upscale = upscale
self.env_params = env_params
self.static_env_params = env.static_env_params
self.ued_params = UEDParams()
self.side_panel_width = env.static_env_params.screen_dim[0] // 3
self.rng = jax.random.PRNGKey(0)
self.config = config
self.pcg_state = new_pcg_env(self.static_env_params)
self.rng, _rng = jax.random.split(self.rng)
self.play_state = sample_pcg_state(_rng, self.pcg_state, self.env_params, self.static_env_params)
self.last_played_level = None
self.pygame_events = []
self.mutate_world = make_mutate_env(env.static_env_params, env_params, self.ued_params)
self.num_triangle_clicks = 0
self.triangle_order = jnp.array([0, 1, 2])
# Init rendering
pygame.init()
pygame.key.set_repeat(250, 75)
self.screen_surface = pygame.display.set_mode(
tuple(
(t + extra) * self.upscale
for t, extra in zip(self.static_env_params.screen_dim, (self.side_panel_width, 0))
),
display=0,
)
self.all_widgets = {}
self._setup_side_panel()
self.has_done_action = False
self._setup_rendering(self.static_env_params, env_params)
self._render_edit_overlay_fn = jax.jit(self._render_edit_overlay)
self._step_fn = jax.jit(env.step)
# Agent
if self.config["agent_taking_actions"]:
self._entity_renderer = jax.jit(make_render_entities(env_params, self.env.static_env_params))
self.network = make_network_from_config(env, env_params, config)
rng = jax.random.PRNGKey(0)
dones = jnp.zeros((config["num_train_envs"]), dtype=jnp.bool_)
rng, _rng = jax.random.split(rng)
init_hstate = ScannedRNN.initialize_carry(config["num_train_envs"])
obsv = self._entity_renderer(self.play_state)
obsv = jax.tree.map(lambda x: jnp.repeat(x[None, ...], repeats=config["num_train_envs"], axis=0), obsv)
init_x = jax.tree.map(lambda x: x[None, ...], (obsv, dones))
network_params = self.network.init(_rng, init_hstate, init_x)
tx = optax.chain(
optax.clip_by_global_norm(config["max_grad_norm"]),
optax.adam(config["lr"], eps=1e-5),
)
self.train_state = TrainState.create(
apply_fn=self.network.apply,
params=network_params,
tx=tx,
)
self.train_state = load_train_state_from_wandb_artifact_path(self.train_state, config["agent_wandb_path"])
self.apply_fn = jax.jit(self.network.apply)
# JIT Compile
def _jit_step():
rng = jax.random.PRNGKey(0)
ans = self._step_fn(
rng,
self.env.reset_env_to_level(rng, self.play_state, self.env_params)[1],
jnp.zeros(
env.static_env_params.num_motor_bindings + env.static_env_params.num_thruster_bindings, dtype=int
),
self.env_params,
)
def _jit_render():
self._render_fn(self.play_state)
self._render_fn_edit(self.play_state)
time_function(_jit_step, "_jit_step")
time_function(_jit_render, "_jit_render")
# self._step_fn(rng, self.play_state, 0, self.env_params)
# Editing
self.is_editing = True
self.edit_shape_mode = EditMode.ADD_CIRCLE
self.creating_shape = False
self.create_shape_position = jnp.array([0.0, 0.0])
self.creating_shape_index = 0
self.selected_shape_index = -1
self.selected_shape_type = ObjectType.POLYGON
self.all_selected_shapes = []
self.rng = jax.random.PRNGKey(0)
time_function(self._jit, "self._jit")
self._put_state_values_into_gui(self.pcg_state)
self.mutate_change_shape_size = make_mutate_change_shape_size(self.env_params, self.static_env_params)
self.mutate_change_shape_rotation = make_mutate_change_shape_rotation(self.env_params, self.static_env_params)
def _setup_rendering(self, static_env_params: StaticEnvParams, env_params: EnvParams):
def _make_render(should_do_edit_additions=False):
def _render(env_state):
side_panel = self._render_side_panel()
render_pixels = make_render_pixels(params=env_params, static_params=static_env_params)
pixels = render_pixels(
env_state,
)
pixels = jnp.concatenate([side_panel, pixels], axis=0)
pixels = jnp.repeat(pixels, repeats=static_env_params.downscale * self.upscale, axis=0)
pixels = jnp.repeat(pixels, repeats=static_env_params.downscale * self.upscale, axis=1)
return pixels[:, ::-1, :]
return _render
def _make_screenshot_render():
def _render(env_state):
px_upscale = 4
static_params = static_env_params.replace(screen_dim=(500 * px_upscale, 500 * px_upscale))
ss_env_params = env_params.replace(
pixels_per_unit=100 * px_upscale,
)
render_pixels = make_render_pixels(
params=ss_env_params,
static_params=static_params,
render_rjoint_sectors=False,
pixel_upscale=2 * px_upscale,
)
pixels = render_pixels(
env_state,
)
return pixels[:, ::-1, :]
return _render
self._render_fn_edit = jax.jit(_make_render(True))
self._render_fn = jax.jit(_make_render(False))
self._render_fn_screenshot = jax.jit(_make_screenshot_render())
def _jit(self):
self._get_circles_on_mouse(self.pcg_state.env_state)
self._get_polygons_on_mouse(self.pcg_state.env_state)
self._get_revolute_joints_on_mouse(self.pcg_state.env_state)
self._get_thrusters_on_mouse(self.pcg_state.env_state)
self.pygame_events = list(pygame.event.get())
self._handle_events(do_dummy=True)
state = self.play_state
for mutation_fn in ALL_MUTATION_FNS:
mutation_fn(jax.random.PRNGKey(0), state, self.env_params, self.static_env_params, self.ued_params)
def update(self, rng):
# Update pygame events
self.pygame_events = list(pygame.event.get())
for event in self.pygame_events:
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_SPACE:
self.has_done_action = False
self.is_editing = not self.is_editing
if not self.is_editing:
self.pcg_state = self._discard_shape_being_created(self.pcg_state)
self.pcg_state = self._reset_select_shape(self.pcg_state)
self.pcg_state = self.pcg_state.replace(
env_state=self.pcg_state.env_state.replace(
collision_matrix=calculate_collision_matrix(
self.static_env_params, self.pcg_state.env_state.joint
),
),
env_state_pcg_mask=self.pcg_state.env_state_pcg_mask.replace(
collision_matrix=jnp.zeros_like(self.pcg_state.env_state_pcg_mask.collision_matrix)
),
)
self.rng, _rng = jax.random.split(self.rng)
self.play_state = sample_pcg_state(
_rng, self.pcg_state, self.env_params, self.static_env_params
)
self.last_played_level = self.play_state
elif event.key == pygame.K_s and not self.is_editing:
self.take_screenshot()
if self.is_editing:
self.pcg_state = self.edit()
else:
rng, _rng = jax.random.split(rng)
# action = []
action = jnp.zeros(
self.env.static_env_params.num_motor_bindings + self.env.static_env_params.num_thruster_bindings,
dtype=jnp.int32,
)
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT]:
action = action.at[0].set(1)
if keys[pygame.K_RIGHT]:
action = action.at[0].set(2)
if keys[pygame.K_UP]:
action = action.at[1].set(1)
if keys[pygame.K_DOWN]:
action = action.at[1].set(2)
if keys[pygame.K_1]:
action = action.at[0 + self.env.static_env_params.num_motor_bindings].set(1)
if keys[pygame.K_2]:
action = action.at[1 + self.env.static_env_params.num_motor_bindings].set(1)
if keys[pygame.K_3]:
action = action.at[2 + self.env.static_env_params.num_motor_bindings].set(1)
if keys[pygame.K_4]:
action = action.at[3 + self.env.static_env_params.num_motor_bindings].set(1)
# if self.has_done_action: action = action * 0
self.has_done_action = self.has_done_action | (action != 0).any()
if self.config["agent_taking_actions"]:
obs = self._entity_renderer(self.play_state)
obs = jax.tree.map(lambda x: x[None, ...], obs)
last_done = jnp.zeros((1, 1), dtype=bool)
ac_in = (jax.tree.map(lambda x: x[np.newaxis, :], obs), last_done[np.newaxis, :])
hstate = ScannedRNN.initialize_carry(1)
hstate, pi, value = self.apply_fn(self.train_state.params, hstate, ac_in)
rng, _rng = jax.random.split(rng)
action = pi.sample(seed=_rng)
action = action[0, 0]
_rng, __rng = jax.random.split(_rng)
obs, self.play_state, reward, done, info = self._step_fn(
_rng, self.env.reset_to_level(__rng, self.play_state, self.env_params)[1], action, self.env_params
)
if done:
self.rng, _rng = jax.random.split(self.rng)
self.play_state = sample_pcg_state(_rng, self.pcg_state, self.env_params, self.static_env_params)
state_to_render = self.pcg_state.env_state if self.is_editing else self.play_state
self.render(state_to_render)
self._handle_events()
# Update screen
pygame.display.flip()
def take_screenshot(self):
print("screenshot!")
pixels = self._render_fn_screenshot(self.play_state)
mtime = round(time.time() * 1000)
pixels = pixels.transpose((1, 0, 2))
# Black border
border_thickness = 5
pixels = pixels.at[:, :border_thickness].set(0.0)
pixels = pixels.at[:, -border_thickness:].set(0.0)
pixels = pixels.at[:border_thickness, :].set(0.0)
pixels = pixels.at[-border_thickness:, :].set(0.0)
im = Image.fromarray(np.array(pixels.astype(jnp.uint8)))
im.save(f"results/screenshot_{str(mtime)}.png")
def _get_selected_shape_global_indices(self):
def _idx(idx, type):
if type == ObjectType.CIRCLE:
return idx + self.static_env_params.num_polygons
return idx
indices_to_use = jnp.array([_idx(idx, type) for idx, type in self.all_selected_shapes])
return indices_to_use
# flag1
def _handle_events(self, do_dummy=False):
pygame_widgets.update(self.pygame_events)
if do_dummy or self.selected_shape_index < 0:
gravity_main = self.all_widgets[None]["sldGravity"].getValue()
gravity_max = self.all_widgets[None]["sldMaxGravity"].getValue()
gravity_main, gravity_max = min(gravity_main, gravity_max), max(gravity_main, gravity_max)
gravity_pcg_mask = self.all_widgets[None]["pcgTglGravity"].getValue()
def _set_single_global(state, gravity):
return state.replace(
gravity=state.gravity.at[1].set(gravity),
)
env_state = _set_single_global(self.pcg_state.env_state, gravity_main)
env_state_max = _set_single_global(self.pcg_state.env_state_max, gravity_max)
env_state_pcg_mask = _set_single_global(self.pcg_state.env_state_pcg_mask, gravity_pcg_mask)
if not do_dummy:
self.pcg_state = self.pcg_state.replace(
env_state=env_state,
env_state_max=env_state_max,
env_state_pcg_mask=env_state_pcg_mask,
)
if self.edit_shape_mode == EditMode.SELECT or do_dummy: # is on the hand.
if do_dummy or len(self.all_selected_shapes) > 1:
# this processes the tying together.
indices_to_use = self._get_selected_shape_global_indices()
if len(indices_to_use) > 1:
toggle_val = self.all_widgets["TIE_TOGETHER"]["tglTieTogether"].getValue()
idxs = itertools.product(indices_to_use, indices_to_use)
idxs_a = []
idxs_b = []
for i, j in idxs:
idxs_a.append(i)
idxs_b.append(j)
idxs = jnp.array(idxs_a), jnp.array(idxs_b)
if toggle_val:
self.pcg_state = self.pcg_state.replace(
tied_together=self.pcg_state.tied_together.at[idxs[0], idxs[1]].set(True)
)
else:
self.pcg_state = self.pcg_state.replace(
tied_together=self.pcg_state.tied_together.at[idxs[0], idxs[1]].set(False)
)
if self.selected_shape_index < 0 and not do_dummy:
return
if do_dummy or self.selected_shape_type in [ObjectType.POLYGON, ObjectType.CIRCLE]: # rigidbody
shape_main = select_object(
self.pcg_state.env_state, self.selected_shape_type, self.selected_shape_index
)
shape_max = select_object(
self.pcg_state.env_state_max, self.selected_shape_type, self.selected_shape_index
)
parent_container_main = (
self.pcg_state.env_state.circle
if self.selected_shape_type == ObjectType.CIRCLE
else self.pcg_state.env_state.polygon
)
parent_container_max = (
self.pcg_state.env_state_max.circle
if self.selected_shape_type == ObjectType.CIRCLE
else self.pcg_state.env_state_max.polygon
)
parent_container_pcg_mask = (
self.pcg_state.env_state_pcg_mask.circle
if self.selected_shape_type == ObjectType.CIRCLE
else self.pcg_state.env_state_pcg_mask.polygon
)
new_density_main = self.all_widgets[self.selected_shape_type]["sldDensity"].getValue()
new_density_max = self.all_widgets[self.selected_shape_type]["sldMaxDensity"].getValue()
density_pcg_mask = self.all_widgets[self.selected_shape_type]["pcgTglDensity"].getValue()
if density_pcg_mask:
new_density_main, new_density_max = min(new_density_main, new_density_max), max(
new_density_main, new_density_max
)
fixated = self.all_widgets[self.selected_shape_type]["tglFixate"].getValue()
fix_val = 0.0 if fixated else 1.0
def _density_calcs(base, new_density):
inv_mass = jax.lax.select(
self.selected_shape_type == ObjectType.CIRCLE,
calc_inverse_mass_circle(base.radius, new_density),
calc_inverse_mass_polygon(base.vertices, base.n_vertices, self.static_env_params, new_density)[
0
],
)
inv_inertia = jax.lax.select(
self.selected_shape_type == ObjectType.CIRCLE,
calc_inverse_inertia_circle(base.radius, new_density),
calc_inverse_inertia_polygon(
base.vertices, base.n_vertices, self.static_env_params, new_density
),
)
return inv_mass, inv_inertia
inv_mass_main, inv_inertia_main = _density_calcs(shape_main, new_density_main)
inv_mass_max, inv_inertia_max = _density_calcs(shape_max, new_density_max)
friction_main = self.all_widgets[self.selected_shape_type]["sldFriction"].getValue()
friction_max = self.all_widgets[self.selected_shape_type]["sldMaxFriction"].getValue()
friction_pcg_mask = self.all_widgets[self.selected_shape_type]["pcgTglFriction"].getValue()
if friction_pcg_mask:
friction_main, friction_max = min(friction_main, friction_max), max(friction_main, friction_max)
restitution = self.all_widgets[self.selected_shape_type]["sldRestitution"].getValue()
position_main = jnp.array(
[
self.all_widgets[self.selected_shape_type]["sldPosition_X"].getValue(),
self.all_widgets[self.selected_shape_type]["sldPosition_Y"].getValue(),
]
)
position_max = jnp.array(
[
self.all_widgets[self.selected_shape_type]["sldMaxPosition_X"].getValue(),
self.all_widgets[self.selected_shape_type]["sldMaxPosition_Y"].getValue(),
]
)
position_pcg_mask = self.all_widgets[self.selected_shape_type]["pcgTglPosition_X"].getValue()
if position_pcg_mask:
position_main, position_max = jnp.minimum(position_main, position_max), jnp.maximum(
position_main, position_max
)
rotation_main = self.all_widgets[self.selected_shape_type]["sldRotation"].getValue()
rotation_max = self.all_widgets[self.selected_shape_type]["sldMaxRotation"].getValue()
rotation_pcg_mask = self.all_widgets[self.selected_shape_type]["pcgTglRotation"].getValue()
if rotation_pcg_mask:
rotation_main, rotation_max = min(rotation_main, rotation_max), max(rotation_main, rotation_max)
velocity_main = jnp.array(
[
self.all_widgets[self.selected_shape_type]["sldVelocity_X"].getValue(),
self.all_widgets[self.selected_shape_type]["sldVelocity_Y"].getValue(),
]
)
velocity_max = jnp.array(
[
self.all_widgets[self.selected_shape_type]["sldMaxVelocity_X"].getValue(),
self.all_widgets[self.selected_shape_type]["sldMaxVelocity_Y"].getValue(),
]
)
velocity_main, velocity_max = jnp.minimum(velocity_main, velocity_max), jnp.maximum(
velocity_main, velocity_max
)
velocity_pcg_mask = self.all_widgets[self.selected_shape_type]["pcgTglVelocity_X"].getValue()
angular_velocity_main = self.all_widgets[self.selected_shape_type]["sldAngular_Velocity"].getValue()
angular_velocity_max = self.all_widgets[self.selected_shape_type]["sldMaxAngular_Velocity"].getValue()
angular_velocity_pcg_mask = self.all_widgets[self.selected_shape_type][
"pcgTglAngular_Velocity"
].getValue()
if angular_velocity_pcg_mask:
angular_velocity_main, angular_velocity_max = min(angular_velocity_main, angular_velocity_max), max(
angular_velocity_main, angular_velocity_max
)
# Circle stuff
radius_main, radius_max, radius_pcg_mask = None, None, None
if self.selected_shape_type == ObjectType.CIRCLE:
radius_main = self.all_widgets[self.selected_shape_type]["sldRadius"].getValue()
radius_max = self.all_widgets[self.selected_shape_type]["sldMaxRadius"].getValue()
radius_pcg_mask = self.all_widgets[self.selected_shape_type]["pcgTglRadius"].getValue()
# Poly stuff
vertices_main, vertices_max, vertices_pcg_mask = None, None, None
if self.selected_shape_type == ObjectType.POLYGON:
# Triangle
new_size_main = self.all_widgets[self.selected_shape_type]["sldSize"].getValue()
new_size_max = self.all_widgets[self.selected_shape_type]["sldMaxSize"].getValue()
current_size = jnp.abs(self.pcg_state.env_state.polygon.vertices[self.selected_shape_index]).max()
scale_main = new_size_main / current_size
scale_max = new_size_max / current_size
vertices_main = self.pcg_state.env_state.polygon.vertices[self.selected_shape_index] * scale_main
vertices_max = self.pcg_state.env_state.polygon.vertices[self.selected_shape_index] * scale_max
vertices_pcg_mask = (
jnp.ones_like(vertices_main, dtype=bool)
* self.all_widgets[self.selected_shape_type]["pcgTglSize"].getValue()
)
def _set_single_state_rbody(
state,
parent_container,
density,
inv_mass,
inv_inertia,
friction,
position,
radius,
rotation,
velocity,
angular_velocity,
vertices,
):
new = {
"friction": parent_container.friction.at[self.selected_shape_index].set(friction),
"collision_mode": parent_container.collision_mode.at[self.selected_shape_index].set(
int(self.all_widgets[self.selected_shape_type]["sldCollidability"].getValue())
),
"inverse_mass": parent_container.inverse_mass.at[self.selected_shape_index].set(
inv_mass * fix_val
),
"inverse_inertia": parent_container.inverse_inertia.at[self.selected_shape_index].set(
inv_inertia * fix_val
),
"position": parent_container.position.at[self.selected_shape_index].set(position),
"rotation": parent_container.rotation.at[self.selected_shape_index].set(rotation),
"velocity": parent_container.velocity.at[self.selected_shape_index].set(velocity),
"angular_velocity": parent_container.angular_velocity.at[self.selected_shape_index].set(
angular_velocity
),
"restitution": parent_container.restitution.at[self.selected_shape_index].set(restitution),
}
if self.selected_shape_type == ObjectType.CIRCLE:
state = state.replace(
circle=state.circle.replace(
**new,
radius=parent_container.radius.at[self.selected_shape_index].set(radius),
),
circle_shape_roles=state.circle_shape_roles.at[self.selected_shape_index].set(
int(self.all_widgets[self.selected_shape_type]["sldRole"].getValue())
),
circle_densities=state.circle_densities.at[self.selected_shape_index].set(density),
)
else:
state = state.replace(
polygon=state.polygon.replace(
**new,
vertices=parent_container.vertices.at[self.selected_shape_index].set(vertices),
),
polygon_shape_roles=state.polygon_shape_roles.at[self.selected_shape_index].set(
int(self.all_widgets[self.selected_shape_type]["sldRole"].getValue())
),
polygon_densities=state.polygon_densities.at[self.selected_shape_index].set(density),
)
return state
position_delta = position_main - shape_main.position
env_state = _set_single_state_rbody(
self.pcg_state.env_state,
parent_container_main,
new_density_main,
inv_mass_main,
inv_inertia_main,
friction_main,
position_main,
radius_main,
rotation_main,
velocity_main,
angular_velocity_main,
vertices_main,
)
env_state_max = _set_single_state_rbody(
self.pcg_state.env_state_max,
parent_container_max,
new_density_max,
inv_mass_max,
inv_inertia_max,
friction_max,
position_max,
radius_max,
rotation_max,
velocity_max,
angular_velocity_max,
vertices_max,
)
env_state_pcg_mask = _set_single_state_rbody(
self.pcg_state.env_state_pcg_mask,
parent_container_pcg_mask,
density_pcg_mask,
density_pcg_mask,
density_pcg_mask,
friction_pcg_mask,
position_pcg_mask,
radius_pcg_mask,
rotation_pcg_mask,
velocity_pcg_mask,
angular_velocity_pcg_mask,
vertices_pcg_mask,
)
# Now, we must also set all of the tied shape's positions
correct_index = self.selected_shape_index + (
self.static_env_params.num_polygons if self.selected_shape_type == ObjectType.CIRCLE else 0
)
nonzero_indices = set(
jnp.nonzero(self.pcg_state.tied_together[correct_index].at[correct_index].set(False))[0].tolist()
)
for idx in nonzero_indices:
if idx < self.static_env_params.num_polygons:
env_state = env_state.replace(
polygon=env_state.polygon.replace(
position=env_state.polygon.position.at[idx].set(
env_state.polygon.position[idx] + position_delta
)
)
)
else:
idx = idx - self.static_env_params.num_polygons
env_state = env_state.replace(
circle=env_state.circle.replace(
position=env_state.circle.position.at[idx].set(
env_state.circle.position[idx] + position_delta
)
)
)
if not do_dummy:
self.pcg_state = PCGState(
env_state=env_state,
env_state_max=env_state_max,
env_state_pcg_mask=env_state_pcg_mask,
tied_together=self.pcg_state.tied_together,
)
if do_dummy or self.selected_shape_type == ObjectType.JOINT: # rjoint
speed_main = self.all_widgets[ObjectType.JOINT]["sldSpeed"].getValue()
speed_max = self.all_widgets[ObjectType.JOINT]["sldMaxSpeed"].getValue()
speed_pcg_mask = self.all_widgets[ObjectType.JOINT]["pcgTglSpeed"].getValue()
if speed_pcg_mask:
speed_main, speed_max = min(speed_main, speed_max), max(speed_main, speed_max)
power_main = self.all_widgets[ObjectType.JOINT]["sldPower"].getValue()
power_max = self.all_widgets[ObjectType.JOINT]["sldMaxPower"].getValue()
power_pcg_mask = self.all_widgets[ObjectType.JOINT]["pcgTglPower"].getValue()
if power_pcg_mask:
power_main, power_max = min(power_main, power_max), max(power_main, power_max)
motor_binding_val_min = int(self.all_widgets[ObjectType.JOINT]["sldColour"].getValue())
motor_binding_val_max = int(self.all_widgets[ObjectType.JOINT]["sldMaxColour"].getValue())
colour_pcg_mask = self.all_widgets[ObjectType.JOINT]["pcgTglColour"].getValue()
if colour_pcg_mask:
motor_binding_val_min, motor_binding_val_max = min(
motor_binding_val_min, motor_binding_val_max
), max(motor_binding_val_min, motor_binding_val_max)
auto_motor_val = self.all_widgets[ObjectType.JOINT]["tglAutoMotor"].getValue()
joint_limits_val = self.all_widgets[ObjectType.JOINT]["tglJointLimits"].getValue()
is_fixed_val = self.all_widgets[ObjectType.JOINT]["tglIsFixedJoint"].getValue()
is_motor_on = self.all_widgets[ObjectType.JOINT]["tglIsMotorOn"].getValue()
min_rot_val = jnp.deg2rad(self.all_widgets[ObjectType.JOINT]["sldMin_Rotation"].getValue())
max_rot_val = jnp.deg2rad(self.all_widgets[ObjectType.JOINT]["sldMax_Rotation"].getValue())
# ensure the min is less than the max
min_rot_val, max_rot_val = min(min_rot_val, max_rot_val), max(min_rot_val, max_rot_val)
def _set_single_state_joint(state, speed, power, colour):
state = state.replace(
joint=state.joint.replace(
motor_speed=state.joint.motor_speed.at[self.selected_shape_index].set(speed),
motor_power=state.joint.motor_power.at[self.selected_shape_index].set(power),
motor_has_joint_limits=state.joint.motor_has_joint_limits.at[self.selected_shape_index].set(
joint_limits_val
),
min_rotation=state.joint.min_rotation.at[self.selected_shape_index].set(min_rot_val),
max_rotation=state.joint.max_rotation.at[self.selected_shape_index].set(max_rot_val),
is_fixed_joint=state.joint.is_fixed_joint.at[self.selected_shape_index].set(is_fixed_val),
motor_on=state.joint.motor_on.at[self.selected_shape_index].set(is_motor_on),
),
motor_bindings=state.motor_bindings.at[self.selected_shape_index].set(colour),
motor_auto=state.motor_auto.at[self.selected_shape_index].set(auto_motor_val),
)
return state
env_state = _set_single_state_joint(
self.pcg_state.env_state,
speed_main,
power_main,
motor_binding_val_min,
)
env_state_max = _set_single_state_joint(
self.pcg_state.env_state_max,
speed_max,
power_max,
motor_binding_val_max,
)
env_state_pcg_mask = _set_single_state_joint(
self.pcg_state.env_state_pcg_mask, speed_pcg_mask, power_pcg_mask, colour_pcg_mask
)
if not do_dummy:
self.pcg_state = self.pcg_state.replace(
env_state=env_state,
env_state_max=env_state_max,
env_state_pcg_mask=env_state_pcg_mask,
)
if do_dummy or self.selected_shape_type == ObjectType.THRUSTER: # thruster
power_main = self.all_widgets[ObjectType.THRUSTER]["sldPower"].getValue()
power_max = self.all_widgets[ObjectType.THRUSTER]["sldMaxPower"].getValue()
power_pcg_mask = self.all_widgets[ObjectType.THRUSTER]["pcgTglPower"].getValue()
if power_pcg_mask:
power_main, power_max = min(power_main, power_max), max(power_main, power_max)
def _set_single_state_thruster(state, power):
return state.replace(
thruster=state.thruster.replace(
power=state.thruster.power.at[self.selected_shape_index].set(power),
),
thruster_bindings=state.thruster_bindings.at[self.selected_shape_index].set(
int(self.all_widgets[ObjectType.THRUSTER]["sldColour"].getValue())
),
)
env_state = _set_single_state_thruster(self.pcg_state.env_state, power_main)
env_state_max = _set_single_state_thruster(self.pcg_state.env_state_max, power_max)
env_state_pcg_mask = _set_single_state_thruster(self.pcg_state.env_state_pcg_mask, power_pcg_mask)
if not do_dummy:
self.pcg_state = self.pcg_state.replace(
env_state=env_state,
env_state_max=env_state_max,
env_state_pcg_mask=env_state_pcg_mask,
)
# flag2
def _put_state_values_into_gui(self, pcg_state=None):
def _set_toggle_val(toggle, val):
if toggle.getValue() != val:
toggle.toggle()
def _enable_slider(slider):
slider.enable()
slider.colour = (200, 200, 200)
slider.handleColour = (0, 0, 0)
def _disable_slider(slider):
slider.disable()
slider.colour = (255, 0, 0)
slider.handleColour = (255, 0, 0)
if pcg_state is None:
# state = self.edit_state
raise ValueError
def pcg_text(main_val, max_val, pcg_mask):
if pcg_mask:
return f"{main_val:.2f} - {max_val:.2f}"
else:
return f"{main_val:.2f}"
# global ones
gravity_pcg_mask = pcg_state.env_state_pcg_mask.gravity[1]
self.all_widgets[None]["lblGravity"].setText(
f"Gravity: {pcg_text(pcg_state.env_state.gravity[1], pcg_state.env_state_max.gravity[1], pcg_state.env_state_pcg_mask.gravity[1])}"
)
self.all_widgets[None]["sldGravity"].setValue(pcg_state.env_state.gravity[1])
self.all_widgets[None]["sldMaxGravity"].setValue(pcg_state.env_state_max.gravity[1])
if not gravity_pcg_mask:
_disable_slider(self.all_widgets[None]["sldMaxGravity"])
else:
_enable_slider(self.all_widgets[None]["sldMaxGravity"])
if self.edit_shape_mode != EditMode.SELECT or self.selected_shape_index < 0:
return
obj_main = select_object(pcg_state.env_state, self.selected_shape_type, self.selected_shape_index)
obj_max = select_object(pcg_state.env_state_max, self.selected_shape_type, self.selected_shape_index)
obj_pcg_mask = select_object(pcg_state.env_state_pcg_mask, self.selected_shape_type, self.selected_shape_index)
if len(self.all_selected_shapes) > 1:
indices_to_use = self._get_selected_shape_global_indices()
are_all_tied = pcg_state.tied_together[indices_to_use.min(), indices_to_use].all()
_set_toggle_val(self.all_widgets["TIE_TOGETHER"]["tglTieTogether"], are_all_tied)
if self.selected_shape_type == ObjectType.JOINT:
self.all_widgets[ObjectType.JOINT]["lblSpeed"].setText(
f"Speed: {pcg_text(obj_main.motor_speed, obj_max.motor_speed, obj_pcg_mask.motor_speed)}"
)
self.all_widgets[ObjectType.JOINT]["sldSpeed"].setValue(obj_main.motor_speed)
self.all_widgets[ObjectType.JOINT]["sldMaxSpeed"].setValue(obj_max.motor_speed)
_set_toggle_val(self.all_widgets[ObjectType.JOINT]["pcgTglSpeed"], obj_pcg_mask.motor_speed)
if obj_pcg_mask.motor_speed:
_enable_slider(self.all_widgets[ObjectType.JOINT]["sldMaxSpeed"])
else:
_disable_slider(self.all_widgets[ObjectType.JOINT]["sldMaxSpeed"])
self.all_widgets[ObjectType.JOINT]["lblPower"].setText(
f"Power: {pcg_text(obj_main.motor_power, obj_max.motor_power, obj_pcg_mask.motor_power)}"
)
self.all_widgets[ObjectType.JOINT]["sldPower"].setValue(obj_main.motor_power)
self.all_widgets[ObjectType.JOINT]["sldMaxPower"].setValue(obj_max.motor_power)
_set_toggle_val(self.all_widgets[ObjectType.JOINT]["pcgTglPower"], obj_pcg_mask.motor_power)
if obj_pcg_mask.motor_power:
_enable_slider(self.all_widgets[ObjectType.JOINT]["sldMaxPower"])
else:
_disable_slider(self.all_widgets[ObjectType.JOINT]["sldMaxPower"])
self.all_widgets[ObjectType.JOINT]["lblColour"].setText(
f"Colour: {pcg_state.env_state.motor_bindings[self.selected_shape_index]}"
)
self.all_widgets[ObjectType.JOINT]["sldColour"].setValue(
pcg_state.env_state.motor_bindings[self.selected_shape_index]
)
self.all_widgets[ObjectType.JOINT]["sldMaxColour"].setValue(
pcg_state.env_state_max.motor_bindings[self.selected_shape_index]
)
colour_pcg_mask = pcg_state.env_state_pcg_mask.motor_bindings[self.selected_shape_index]
if not colour_pcg_mask:
_disable_slider(self.all_widgets[ObjectType.JOINT]["sldMaxColour"])
else:
_enable_slider(self.all_widgets[ObjectType.JOINT]["sldMaxColour"])
self.all_widgets[ObjectType.JOINT]["lblJointLimits"].setText(
f"Joint Limits: {obj_main.motor_has_joint_limits}"
)
widget_motor_has_joint_limits = self.all_widgets[ObjectType.JOINT]["tglJointLimits"].getValue()
if obj_main.motor_has_joint_limits != widget_motor_has_joint_limits: # Update the toggle
self.all_widgets[ObjectType.JOINT]["tglJointLimits"].toggle()
mini, maxi = jnp.rad2deg(obj_main.min_rotation), jnp.rad2deg(obj_main.max_rotation)
self.all_widgets[ObjectType.JOINT]["lblMin_Rotation"].setText(f"Min Rot: {int(mini)}")
self.all_widgets[ObjectType.JOINT]["sldMin_Rotation"].setValue(mini)
self.all_widgets[ObjectType.JOINT]["lblMax_Rotation"].setText(f"Max Rot: {int(maxi)}")
self.all_widgets[ObjectType.JOINT]["sldMax_Rotation"].setValue(maxi)
if not obj_main.motor_has_joint_limits:
for k in ["min_rotation", "max_rotation"]:
self.all_widgets[self.selected_shape_type][f"sld{k.title()}"].disable()
self.all_widgets[self.selected_shape_type][f"sld{k.title()}"].colour = (255, 0, 0)
self.all_widgets[self.selected_shape_type][f"sld{k.title()}"].handleColour = (255, 0, 0)
else:
for k in ["min_rotation", "max_rotation"]:
self.all_widgets[self.selected_shape_type][f"sld{k.title()}"].enable()
self.all_widgets[self.selected_shape_type][f"sld{k.title()}"].colour = (200, 200, 200)
self.all_widgets[self.selected_shape_type][f"sld{k.title()}"].handleColour = (0, 0, 0)
self.all_widgets[ObjectType.JOINT]["lblAutoMotor"].setText(
f"Auto: {pcg_state.env_state.motor_auto[self.selected_shape_index]}"
)
widget_is_auto_motor = self.all_widgets[ObjectType.JOINT]["tglAutoMotor"].getValue()
if pcg_state.env_state.motor_auto[self.selected_shape_index] != widget_is_auto_motor: # Update the toggle
self.all_widgets[ObjectType.JOINT]["tglAutoMotor"].toggle()
self.all_widgets[ObjectType.JOINT]["lblIsFixedJoint"].setText(f"Fixed: {obj_main.is_fixed_joint}")
widget_is_motor_on = self.all_widgets[ObjectType.JOINT]["tglIsFixedJoint"].getValue()
if obj_main.is_fixed_joint != widget_is_motor_on: # Update the toggle
self.all_widgets[ObjectType.JOINT]["tglIsFixedJoint"].toggle()
self.all_widgets[ObjectType.JOINT]["lblIsMotorOn"].setText(f"Motor On: {obj_main.motor_on}")
widget_is_motor_on = self.all_widgets[ObjectType.JOINT]["tglIsMotorOn"].getValue()
if obj_main.motor_on != widget_is_motor_on: # Update the toggle
self.all_widgets[ObjectType.JOINT]["tglIsMotorOn"].toggle()
elif self.selected_shape_type == ObjectType.THRUSTER:
# thruster
self.all_widgets[ObjectType.THRUSTER]["lblPower"].setText(
f"Power: {pcg_text(obj_main.power, obj_max.power, obj_pcg_mask.power)}"
)
self.all_widgets[ObjectType.THRUSTER]["sldPower"].setValue(obj_main.power)
self.all_widgets[ObjectType.THRUSTER]["sldMaxPower"].setValue(obj_max.power)
_set_toggle_val(self.all_widgets[ObjectType.THRUSTER]["pcgTglPower"], obj_pcg_mask.power)
if obj_pcg_mask.power:
_enable_slider(self.all_widgets[ObjectType.THRUSTER]["sldMaxPower"])
else:
_disable_slider(self.all_widgets[ObjectType.THRUSTER]["sldMaxPower"])
self.all_widgets[ObjectType.THRUSTER]["sldColour"].setValue(
pcg_state.env_state.thruster_bindings[self.selected_shape_index]
)
self.all_widgets[ObjectType.THRUSTER]["lblColour"].setText(
f"Colour: {pcg_state.env_state.thruster_bindings[self.selected_shape_index]}"
)
elif self.selected_shape_type in [ObjectType.POLYGON, ObjectType.CIRCLE]:
# rigidbody
# Position
# We use the mask for position_x for the entire position vector
self.all_widgets[self.selected_shape_type]["lblPosition_X"].setText(
f"Position X: {pcg_text(obj_main.position[0], obj_max.position[0], obj_pcg_mask.position[0])}"
)
self.all_widgets[self.selected_shape_type]["sldPosition_X"].setValue(obj_main.position[0])
self.all_widgets[self.selected_shape_type]["sldMaxPosition_X"].setValue(obj_max.position[0])
_set_toggle_val(self.all_widgets[self.selected_shape_type]["pcgTglPosition_X"], obj_pcg_mask.position[0])
self.all_widgets[self.selected_shape_type]["lblPosition_Y"].setText(
f"Position Y: {pcg_text(obj_main.position[1], obj_max.position[1], obj_pcg_mask.position[0])}"
)
self.all_widgets[self.selected_shape_type]["sldPosition_Y"].setValue(obj_main.position[1])
self.all_widgets[self.selected_shape_type]["sldMaxPosition_Y"].setValue(obj_max.position[1])
if obj_pcg_mask.position[0]:
_enable_slider(self.all_widgets[self.selected_shape_type]["sldMaxPosition_X"])
_enable_slider(self.all_widgets[self.selected_shape_type]["sldMaxPosition_Y"])
else:
_disable_slider(self.all_widgets[self.selected_shape_type]["sldMaxPosition_X"])
_disable_slider(self.all_widgets[self.selected_shape_type]["sldMaxPosition_Y"])
# Velocity
# We use the mask for velocity_x for the entire velocity vector
self.all_widgets[self.selected_shape_type]["lblVelocity_X"].setText(
f"Velocity X: {pcg_text(obj_main.velocity[0], obj_max.velocity[0], obj_pcg_mask.velocity[0])}"
)
self.all_widgets[self.selected_shape_type]["sldVelocity_X"].setValue(obj_main.velocity[0])
self.all_widgets[self.selected_shape_type]["sldMaxVelocity_X"].setValue(obj_max.velocity[0])
_set_toggle_val(self.all_widgets[self.selected_shape_type]["pcgTglVelocity_X"], obj_pcg_mask.velocity[0])
self.all_widgets[self.selected_shape_type]["lblVelocity_Y"].setText(
f"Velocity Y: {pcg_text(obj_main.velocity[1], obj_max.velocity[1], obj_pcg_mask.velocity[0])}"
)
self.all_widgets[self.selected_shape_type]["sldVelocity_Y"].setValue(obj_main.velocity[1])
self.all_widgets[self.selected_shape_type]["sldMaxVelocity_Y"].setValue(obj_max.velocity[1])
if obj_pcg_mask.velocity[0]:
_enable_slider(self.all_widgets[self.selected_shape_type]["sldMaxVelocity_X"])
_enable_slider(self.all_widgets[self.selected_shape_type]["sldMaxVelocity_Y"])
else:
_disable_slider(self.all_widgets[self.selected_shape_type]["sldMaxVelocity_X"])
_disable_slider(self.all_widgets[self.selected_shape_type]["sldMaxVelocity_Y"])
# Density
is_fixated = obj_main.inverse_mass == 0
def _calc_density(state):
if self.selected_shape_type == ObjectType.POLYGON:
return state.polygon_densities[self.selected_shape_index]
elif self.selected_shape_type == ObjectType.CIRCLE:
return state.circle_densities[self.selected_shape_index]
else:
raise ValueError
density_main = _calc_density(pcg_state.env_state)
density_max = _calc_density(pcg_state.env_state_max)
density_pcg_mask = obj_pcg_mask.inverse_mass
self.all_widgets[self.selected_shape_type]["lblDensity"].setText(
f"Density: {pcg_text(density_main, density_max, density_pcg_mask)}"
)
self.all_widgets[self.selected_shape_type]["sldDensity"].setValue(density_main)
self.all_widgets[self.selected_shape_type]["sldMaxDensity"].setValue(density_max)
_set_toggle_val(self.all_widgets[self.selected_shape_type]["pcgTglDensity"], density_pcg_mask)
if is_fixated:
_disable_slider(self.all_widgets[self.selected_shape_type]["sldDensity"])
else:
_enable_slider(self.all_widgets[self.selected_shape_type]["sldDensity"])
if is_fixated or (not density_pcg_mask):
_disable_slider(self.all_widgets[self.selected_shape_type]["sldMaxDensity"])
else:
_enable_slider(self.all_widgets[self.selected_shape_type]["sldMaxDensity"])
# Friction
self.all_widgets[self.selected_shape_type]["lblFriction"].setText(
f"Friction: {pcg_text(obj_main.friction, obj_max.friction, obj_pcg_mask.friction)}"
)
self.all_widgets[self.selected_shape_type]["sldFriction"].setValue(obj_main.friction)
self.all_widgets[self.selected_shape_type]["sldMaxFriction"].setValue(obj_max.friction)
_set_toggle_val(self.all_widgets[self.selected_shape_type]["pcgTglFriction"], obj_pcg_mask.friction)
if not obj_pcg_mask.friction:
_disable_slider(self.all_widgets[self.selected_shape_type]["sldMaxFriction"])
else:
_enable_slider(self.all_widgets[self.selected_shape_type]["sldMaxFriction"])
# Restitution
self.all_widgets[self.selected_shape_type]["lblRestitution"].setText(
f"Restitution: {obj_main.restitution:.2f}"
)
self.all_widgets[self.selected_shape_type]["sldRestitution"].setValue(obj_main.restitution)
# Rotation
self.all_widgets[self.selected_shape_type]["lblRotation"].setText(
f"Rotation: {pcg_text(obj_main.rotation, obj_max.rotation, obj_pcg_mask.rotation)}"
)
self.all_widgets[self.selected_shape_type]["sldRotation"].setValue(obj_main.rotation)
self.all_widgets[self.selected_shape_type]["sldMaxRotation"].setValue(obj_max.rotation)
_set_toggle_val(self.all_widgets[self.selected_shape_type]["pcgTglRotation"], obj_pcg_mask.rotation)
if not obj_pcg_mask.rotation:
_disable_slider(self.all_widgets[self.selected_shape_type]["sldMaxRotation"])
else:
_enable_slider(self.all_widgets[self.selected_shape_type]["sldMaxRotation"])
# Angular_Velocity
self.all_widgets[self.selected_shape_type]["lblAngular_Velocity"].setText(
f"Angular_Velocity: {pcg_text(obj_main.angular_velocity, obj_max.angular_velocity, obj_pcg_mask.angular_velocity)}"
)
self.all_widgets[self.selected_shape_type]["sldAngular_Velocity"].setValue(obj_main.angular_velocity)
self.all_widgets[self.selected_shape_type]["sldMaxAngular_Velocity"].setValue(obj_max.angular_velocity)
_set_toggle_val(
self.all_widgets[self.selected_shape_type]["pcgTglAngular_Velocity"], obj_pcg_mask.angular_velocity
)
if not obj_pcg_mask.angular_velocity:
_disable_slider(self.all_widgets[self.selected_shape_type]["sldMaxAngular_Velocity"])
else:
_enable_slider(self.all_widgets[self.selected_shape_type]["sldMaxAngular_Velocity"])
# Collision mode
self.all_widgets[self.selected_shape_type]["lblCollidability"].setText(
f"Collidability: {obj_main.collision_mode}"
)
self.all_widgets[self.selected_shape_type]["sldCollidability"].setValue(obj_main.collision_mode)
# Shape role
if self.selected_shape_type == ObjectType.POLYGON:
shape_role = pcg_state.env_state.polygon_shape_roles[self.selected_shape_index]
else:
shape_role = pcg_state.env_state.circle_shape_roles[self.selected_shape_index]
self.all_widgets[self.selected_shape_type]["sldRole"].setValue(shape_role)
self.all_widgets[self.selected_shape_type]["lblRole"].setText(f"Role: {shape_role}")
# Fixate
self.all_widgets[self.selected_shape_type]["lblFixate"].setText(f"Fixate: {is_fixated}")
widget_is_fixed = self.all_widgets[self.selected_shape_type]["tglFixate"].getValue()
if is_fixated != widget_is_fixed: # Update the toggle
self.all_widgets[self.selected_shape_type]["tglFixate"].toggle()
# Radius
if self.selected_shape_type == ObjectType.CIRCLE:
self.all_widgets[self.selected_shape_type]["lblRadius"].setText(
f"Radius: {pcg_text(obj_main.radius, obj_max.radius, obj_pcg_mask.radius)}"
)
self.all_widgets[self.selected_shape_type]["sldRadius"].setValue(obj_main.radius)
self.all_widgets[self.selected_shape_type]["sldMaxRadius"].setValue(obj_max.radius)
_set_toggle_val(self.all_widgets[self.selected_shape_type]["pcgTglRadius"], obj_pcg_mask.radius)
if not obj_pcg_mask.radius:
_disable_slider(self.all_widgets[self.selected_shape_type]["sldMaxRadius"])
else:
_enable_slider(self.all_widgets[self.selected_shape_type]["sldMaxRadius"])
elif self.selected_shape_type == ObjectType.POLYGON:
size_main = jnp.abs(obj_main.vertices).max()
size_max = jnp.abs(obj_max.vertices).max()
size_pcg_mask = obj_pcg_mask.vertices.any()
self.all_widgets[self.selected_shape_type]["lblSize"].setText(
f"Size: {pcg_text(size_main, size_max, size_pcg_mask)}"
)
self.all_widgets[self.selected_shape_type]["sldSize"].setValue(size_main)
self.all_widgets[self.selected_shape_type]["sldMaxSize"].setValue(size_max)
_set_toggle_val(self.all_widgets[self.selected_shape_type]["pcgTglSize"], size_pcg_mask)
if not size_pcg_mask:
_disable_slider(self.all_widgets[self.selected_shape_type]["sldMaxSize"])
else:
_enable_slider(self.all_widgets[self.selected_shape_type]["sldMaxSize"])
def _render_side_panel(self):
arr = jnp.ones((self.side_panel_width, self.static_env_params.screen_dim[1], 3)) * (
jnp.array([135.0, 206.0, 235.0])[None, None] + 20
)
return arr
def make_label_and_slider(
self,
start_y,
label_text,
slider_min=0.0,
slider_max=1.0,
slider_step=0.05,
font_size=18,
is_toggle=False,
is_pcg=False,
add_pcg_toggle=True,
):
Wl = round(self.W * 0.7)
pcg_lbl = None
pcg_toggle = None
widget_max = None
label = TextBox(
self.screen_surface,
self.MARGIN,
start_y,
Wl,
20,
fontSize=font_size,
margin=0,
placeholderText=label_text,
font=pygame.font.SysFont("sans-serif", font_size),
)
label.disable() # Act as label instead of textbox
if is_toggle:
widget = Toggle(self.screen_surface, self.W // 2 - 20, start_y + 23, 20, 13)
else:
widget = Slider(
self.screen_surface,
self.MARGIN,
start_y + 23,
Wl,
13,
min=slider_min,
max=slider_max,
step=slider_step,
)
if is_pcg:
widget_max = Slider(
self.screen_surface,
self.MARGIN,
start_y + 50,
Wl,
13,
min=slider_min,
max=slider_max,
step=slider_step,
)
pcg_lbl = TextBox(
self.screen_surface,
self.MARGIN + Wl + 30,
start_y,
60,
20,
fontSize=font_size,
margin=0,
placeholderText="PCG",
font=pygame.font.SysFont("sans-serif", font_size),
)
if add_pcg_toggle:
pcg_toggle = Toggle(self.screen_surface, Wl + 80, start_y + 23, 20, 13)
return label, widget, (pcg_toggle, pcg_lbl, widget_max)
def _setup_side_panel(self):
W = self.W = int(self.side_panel_width * self.upscale * 0.8)
MARGIN = self.MARGIN = (self.side_panel_width * self.upscale - W) // 2
# global values
G = {}
gravity_label, gravity_slider, (pcg_toggle, pcg_lbl, slider_max) = self.make_label_and_slider(
150, "Gravity", -20.0, 0.0, 0.2, is_pcg=True
)
G["lblGravity"] = gravity_label
G["sldGravity"] = gravity_slider
G["sldMaxGravity"] = slider_max
G["pcgLblGravity"] = pcg_lbl
G["pcgTglGravity"] = pcg_toggle
# thruster values
T = {}
thruster_power_label, thruster_power_slider, (pcg_toggle, pcg_lbl, slider_max) = self.make_label_and_slider(
150, "Power", slider_max=3.0, is_pcg=True
)
T["lblPower"] = thruster_power_label
T["sldPower"] = thruster_power_slider
T["sldMaxPower"] = slider_max
T["pcgLblPower"] = pcg_lbl
T["pcgTglPower"] = pcg_toggle
thruster_colour_label, thruster_colour_slider, _ = self.make_label_and_slider(
250, "Colour", 0, self.static_env_params.num_thruster_bindings - 1, 1
)
T["lblColour"] = thruster_colour_label
T["sldColour"] = thruster_colour_slider
# joints
D = {}
for i, (name, (mini, maxi, step), is_pcg) in enumerate(
zip(
["speed", "power", "colour", "min_rotation", "max_rotation"],
[
(-3, 3, 0.05),
(0, 3, 0.05),
(0, self.static_env_params.num_motor_bindings - 1, 1),
(-180, 180, 5),
(-180, 180, 5),
],
[True, True, True, False, False],
)
):
label, slider, (pcg_toggle, pcg_lbl, slider_max) = self.make_label_and_slider(
150 + 80 * i,
f"Motor {name.title()}",
slider_min=mini,
slider_max=maxi,
slider_step=step,
is_pcg=is_pcg,
)
D["lbl" + name.title()] = label
D["sld" + name.title()] = slider
if is_pcg:
D["sldMax" + name.title()] = slider_max
D["pcgLbl" + name.title()] = pcg_lbl
D["pcgTgl" + name.title()] = pcg_toggle
label, toggle, _ = self.make_label_and_slider(150 + 80 * 5, "Joint Limits", is_toggle=True)
D["lblJointLimits"] = label
D["tglJointLimits"] = toggle
label, toggle, _ = self.make_label_and_slider(150 + 80 * 6, "Auto", is_toggle=True)
D["lblAutoMotor"] = label
D["tglAutoMotor"] = toggle
label, toggle, _ = self.make_label_and_slider(150 + 80 * 7, "Fixed", is_toggle=True)
D["lblIsFixedJoint"] = label
D["tglIsFixedJoint"] = toggle
label, toggle, _ = self.make_label_and_slider(150 + 80 * 8, "Motor On", is_toggle=True)
D["lblIsMotorOn"] = label
D["tglIsMotorOn"] = toggle
def _create_rigid_body_base_gui():
D_rigid = {}
# rigidbodies
total_toggles = 0
total_non_toggles = 0
for i, (name, bounds, is_pcg, add_pcg_toggle) in enumerate(
zip(
[
"position_x",
"position_y",
"rotation",
"velocity_x",
"velocity_y",
"angular_velocity",
"density",
"friction",
"restitution",
"collidability",
"role",
],
[
(0, 5.0, 0.01),
(0, 5.0, 0.01),
(-2 * jnp.pi, 2 * jnp.pi, 0.01),
(-10.0, 10.0, 0.1),
(-10.0, 10.0, 0.1),
(-6, 6.0, 0.01),
(0.1, 5.0, 0.1),
(0.02, 1.0, 0.02),
(0.0, 0.8, 0.02),
(0, 2, 1),
(0, 3, 1),
],
[True, True, True, True, True, True, True, True, False, False, False],
[True, False, True, True, False, True, True, True, False, False, False],
)
):
location = 50 + 80 * total_toggles + 40 * total_non_toggles
label, slider, (pcg_toggle, pcg_lbl, slider_max) = self.make_label_and_slider(
location, name.title(), *bounds, is_pcg=is_pcg, add_pcg_toggle=add_pcg_toggle
)
total_toggles += is_pcg
total_non_toggles += not is_pcg
D_rigid["lbl" + name.title()] = label
D_rigid["sld" + name.title()] = slider
if is_pcg:
D_rigid["sldMax" + name.title()] = slider_max
if add_pcg_toggle:
D_rigid["pcgLbl" + name.title()] = pcg_lbl
D_rigid["pcgTgl" + name.title()] = pcg_toggle
location = 50 + 80 * total_toggles + 40 * total_non_toggles
# toggles:
label, toggle, _ = self.make_label_and_slider(location, "Fixate", is_toggle=True)
D_rigid["lblFixate"] = label
D_rigid["tglFixate"] = toggle
return D_rigid, location
# Circle extras
D_circle, location = _create_rigid_body_base_gui()
label, slider, (pcg_toggle, pcg_lbl, slider_max) = self.make_label_and_slider(
location + 40, "Radius", slider_min=0.1, slider_max=1.0, slider_step=0.02, is_pcg=True, add_pcg_toggle=True
)
D_circle["lblRadius"] = label
D_circle["sldRadius"] = slider
D_circle["sldMaxRadius"] = slider_max
D_circle["pcgLblRadius"] = pcg_lbl
D_circle["pcgTglRadius"] = pcg_toggle
# Polygon extras
D_poly, location = _create_rigid_body_base_gui()
label, slider, (pcg_toggle, pcg_lbl, slider_max) = self.make_label_and_slider(
location + 40, "Size", slider_min=0.1, slider_max=2.0, slider_step=0.02, is_pcg=True, add_pcg_toggle=True
)
D_poly["lblSize"] = label
D_poly["sldSize"] = slider
D_poly["sldMaxSize"] = slider_max
D_poly["pcgLblSize"] = pcg_lbl
D_poly["pcgTglSize"] = pcg_toggle
label, toggle, _ = self.make_label_and_slider(150 + 80 * 5, "Tie Positions Together", is_toggle=True)
TIE_TOGETHER = {
"lblTieTogether": label,
"tglTieTogether": toggle,
}
self.all_widgets = {
ObjectType.THRUSTER: T,
ObjectType.JOINT: D,
"GENERAL": {
"lblGeneral": TextBox(
self.screen_surface,
MARGIN,
10,
W,
30,
fontSize=20,
margin=0,
placeholderText="General",
font=pygame.font.SysFont("sans-serif", 35),
),
},
ObjectType.POLYGON: D_poly,
ObjectType.CIRCLE: D_circle,
None: G,
"TIE_TOGETHER": TIE_TOGETHER,
}
self._hide_all_widgets()
def _render_edit_overlay(self, pixels, is_editing, edit_shape_mode):
is_editing_texture = jax.lax.select(is_editing, EDIT_TEXTURE_RGBA, PLAY_TEXTURE_RGBA)
is_editing_texture = jnp.repeat(jnp.repeat(is_editing_texture, self.upscale, axis=0), self.upscale, axis=1)
offset = self.side_panel_width * self.upscale
w = 64 * self.upscale
offset2 = int(w * 1.25)
offset_y = 16 * self.upscale
play_tex_with_background = (1 - is_editing_texture[:, :, 3:]) * pixels[
offset + 0 : offset + w, 0:w
] + is_editing_texture[:, :, 3:] * is_editing_texture[:, :, :3]
pixels = pixels.at[offset : offset + w, 0:w].set(play_tex_with_background)
edit_shape_texture = jax.lax.switch(
edit_shape_mode,
[
lambda: CIRCLE_TEXTURE_RGBA,
lambda: RECT_TEXTURE_RGBA,
lambda: RJOINT_TEXTURE_RGBA,
lambda: SELECT_TEXTURE_RGBA,
lambda: TRIANGLE_TEXTURE_RGBA,
lambda: THRUSTER_TEXTURE_RGBA,
],
)
edit_shape_texture = jnp.repeat(jnp.repeat(edit_shape_texture, self.upscale, axis=0), self.upscale, axis=1)
edit_shape_texture_alpha = edit_shape_texture[:, :, 3:] * is_editing
w = 32 * self.upscale
edit_shape_texture_with_background = (1 - edit_shape_texture_alpha) * pixels[
offset + offset2 : offset + offset2 + w, offset_y : offset_y + w
] + edit_shape_texture_alpha * edit_shape_texture[:, :, :3]
pixels = pixels.at[offset + offset2 : offset + offset2 + w, offset_y : offset_y + w].set(
edit_shape_texture_with_background
)
return pixels
def edit(self):
self.rng, _rng = jax.random.split(self.rng)
pcg_state = self.pcg_state
left_click = False
right_click = False
keys = []
keys_up_this_frame = set()
for event in self.pygame_events:
if event.type == pygame.KEYDOWN:
if (
event.key == pygame.K_s
and (pygame.key.get_mods() & pygame.KMOD_CTRL)
and (pygame.key.get_mods() & pygame.KMOD_SHIFT)
):
filename = prompt_file(save=True)
if filename:
filename += ".level.pkl"
save_pickle(filename, self.last_played_level)
print(f"Saved last sampled level to {filename}")
elif event.key == pygame.K_s and (pygame.key.get_mods() & pygame.KMOD_CTRL):
pcg_state = self._reset_select_shape(pcg_state)
filename = prompt_file(save=True)
if filename:
if filename.endswith(".json"):
export_env_state_to_json(
filename, pcg_state.env_state, self.static_env_params, self.env_params
)
elif not filename.endswith(".pcg.pkl"):
filename += ".pcg.pkl"
save_pickle(filename, pcg_state)
print(f"Saved PCG state to {filename}")
elif event.key == pygame.K_o and (pygame.key.get_mods() & pygame.KMOD_CTRL):
filename = prompt_file(save=False)
if filename:
self._reset_select_shape(pcg_state)
if filename.endswith(".pcg.pkl"):
pcg_state = load_pcg_state_pickle(filename)
pcg_state = expand_pcg_state(pcg_state, self.static_env_params)
print(f"Loaded PCG state from {filename}")
elif filename.endswith(".level.pkl"):
env_state = load_world_state_pickle(filename)
pcg_state = env_state_to_pcg_state(env_state)
print(f"Converted level state to PCG state from {filename}")
elif filename.endswith(".json"):
env_state, new_static_env_params, new_env_params = load_from_json_file(filename)
self._update_params(new_static_env_params, new_env_params)
pcg_state = env_state_to_pcg_state(env_state)
self._reset_triangles()
elif event.key == pygame.K_n and (pygame.key.get_mods() & pygame.KMOD_CTRL):
self._reset_select_shape(pcg_state)
pcg_state = new_pcg_env(self.static_env_params)
self._reset_triangles()
else:
keys.append(event.key)
elif event.type == pygame.KEYUP:
keys_up_this_frame.add(event.key)
if event.type == pygame.MOUSEBUTTONDOWN and self._get_mouse_position_world_space()[0] >= 0:
if event.button == 1:
left_click = True
if event.button == 3:
right_click = True
if event.type == pygame.MOUSEWHEEL:
pcg_state = self._handle_scroll_wheel(pcg_state, event.y)
if self.selected_shape_index == -1:
num = get_numeric_key_pressed(self.pygame_events)
if num is not None:
self.edit_shape_mode = EditMode(num % len(EditMode))
# We have to do these checks outside the loop, otherwise they get triggered multiple times per key press.
if pygame.key.get_mods() & pygame.KMOD_SHIFT:
state = pcg_state.env_state
if pygame.K_m in keys_up_this_frame:
state = self.mutate_world(_rng, state, 1)
if pygame.K_c in keys_up_this_frame:
state, _ = mutate_add_connected_shape(
_rng, state, self.env_params, self.static_env_params, self.ued_params
)
elif pygame.K_s in keys_up_this_frame:
state = mutate_add_shape(_rng, state, self.env_params, self.static_env_params, self.ued_params)
elif pygame.K_p in keys_up_this_frame:
state = mutate_swap_role(_rng, state, self.env_params, self.static_env_params, self.ued_params)
elif pygame.K_r in keys_up_this_frame:
state = mutate_remove_shape(_rng, state, self.env_params, self.static_env_params, self.ued_params)
elif pygame.K_j in keys_up_this_frame:
state = mutate_remove_joint(_rng, state, self.env_params, self.static_env_params, self.ued_params)
elif pygame.K_t in keys_up_this_frame:
state = mutate_toggle_fixture(_rng, state, self.env_params, self.static_env_params, self.ued_params)
elif pygame.K_g in keys_up_this_frame:
state = mutate_add_thruster(_rng, state, self.env_params, self.static_env_params, self.ued_params)
elif pygame.K_l in keys_up_this_frame:
state = mutate_remove_thruster(_rng, state, self.env_params, self.static_env_params, self.ued_params)
elif pygame.K_b in keys_up_this_frame:
state = self.mutate_change_shape_size(
_rng, state, self.env_params, self.static_env_params, self.ued_params
)
elif pygame.K_x in keys_up_this_frame:
state = mutate_change_shape_location(
_rng, state, self.env_params, self.static_env_params, self.ued_params
)
elif pygame.K_k in keys_up_this_frame:
state = self.mutate_change_shape_rotation(
_rng, state, self.env_params, self.static_env_params, self.ued_params
)
pcg_state = pcg_state.replace(env_state=state)
if pygame.K_p in keys_up_this_frame:
global myrng
myrng, _rng = jax.random.split(myrng)
# use the same rng
pcg_state = permute_pcg_state(_rng, pcg_state, self.static_env_params)
if self.edit_shape_mode == EditMode.SELECT: # select a shape
pcg_state = self._edit_select_shape(pcg_state, left_click, right_click, keys)
self._put_state_values_into_gui(pcg_state)
pcg_state = self._select_shape_keyboard_shortcuts(pcg_state, left_click, keys)
else:
pcg_state = self._reset_select_shape(pcg_state) # don't highlight
self._put_state_values_into_gui(pcg_state)
self._show_correct_widgets(None)
if self.edit_shape_mode != EditMode.ADD_TRIANGLE or not self.creating_shape:
self.num_triangle_clicks = 0
if self.edit_shape_mode == EditMode.ADD_CIRCLE:
pcg_state = self._edit_circle(pcg_state, left_click, right_click)
elif self.edit_shape_mode == EditMode.ADD_RECTANGLE:
pcg_state = self._edit_rect(pcg_state, left_click, right_click)
elif self.edit_shape_mode == EditMode.ADD_JOINT:
pcg_state = self._edit_joint(pcg_state, left_click, right_click)
elif self.edit_shape_mode == EditMode.ADD_TRIANGLE:
pcg_state = self._edit_triangle(pcg_state, left_click, right_click)
elif self.edit_shape_mode == EditMode.ADD_THRUSTER:
pcg_state = self._edit_thruster(pcg_state, left_click, right_click)
pcg_state = pcg_state.replace(
env_state=recompute_global_joint_positions(
pcg_state.env_state.replace(
collision_matrix=calculate_collision_matrix(self.static_env_params, pcg_state.env_state.joint),
),
self.static_env_params,
),
env_state_pcg_mask=pcg_state.env_state_pcg_mask.replace(
collision_matrix=jnp.zeros_like(pcg_state.env_state_pcg_mask.collision_matrix)
),
)
return pcg_state
def _update_params(self, new_static_env_params: StaticEnvParams, new_env_params: EnvParams):
self.static_env_params = new_static_env_params.replace(
frame_skip=self.config["frame_skip"], downscale=self.config["downscale"]
)
self.env_params = new_env_params
env = make_kinetix_env_from_name("Kinetix-Entity-MultiDiscrete-v1", static_env_params=self.static_env_params)
self.env = AutoResetWrapper(env, make_reset_function(self.static_env_params))
self._setup_rendering(self.static_env_params, self.env_params)
def _discard_shape_being_created(self, pcg_state):
env_state = pcg_state.env_state
if self.creating_shape:
if self.edit_shape_mode == EditMode.ADD_CIRCLE:
env_state = env_state.replace(
circle=env_state.circle.replace(
active=env_state.circle.active.at[self.creating_shape_index].set(False)
)
)
elif self.edit_shape_mode == EditMode.ADD_RECTANGLE:
env_state = env_state.replace(
polygon=env_state.polygon.replace(
active=env_state.polygon.active.at[self.creating_shape_index].set(False)
)
)
self.creating_shape = False
return pcg_state.replace(env_state=env_state)
def _handle_scroll_wheel(self, pcg_state, y):
if y == 0:
return pcg_state
state = self._discard_shape_being_created(pcg_state)
self.edit_shape_mode = EditMode((self.edit_shape_mode.value + y) % len(EditMode))
return state
def _get_mouse_position_world_space(self):
mouse_pos = pygame.mouse.get_pos()
return (
jnp.array(
[
mouse_pos[0] / self.upscale - self.side_panel_width,
self.static_env_params.screen_dim[1] - mouse_pos[1] / self.upscale,
]
)
/ self.env_params.pixels_per_unit
)
def _get_circles_on_mouse(self, state):
mouse_pos = self._get_mouse_position_world_space()
cis = []
for ci in jnp.arange(self.static_env_params.num_circles)[::-1]:
circle = jax.tree.map(lambda x: x[ci], state.circle)
if not circle.active:
continue
dist = jnp.linalg.norm(mouse_pos - circle.position)
if dist <= circle.radius:
cis.append(ci)
return cis
def _get_revolute_joints_on_mouse(self, state: EnvState):
mouse_pos = self._get_mouse_position_world_space()
ris = []
for ri in jnp.arange(self.static_env_params.num_joints)[::-1]:
joint = jax.tree.map(lambda x: x[ri], state.joint)
if not joint.active:
continue
dist = jnp.linalg.norm(mouse_pos - joint.global_position)
if dist <= 10 / 100: # arbitrary
ris.append(ri)
return ris
def _get_thrusters_on_mouse(self, state: EnvState):
mouse_pos = self._get_mouse_position_world_space()
ris = []
for ri in jnp.arange(self.static_env_params.num_thrusters)[::-1]:
thruster = jax.tree.map(lambda x: x[ri], state.thruster)
if not thruster.active:
continue
dist = jnp.linalg.norm(mouse_pos - thruster.global_position)
if dist <= 16 / 100: # arbitrary
ris.append(ri)
return ris
def _get_joints_attached_to_shape(self, state, shape_index):
r_a = jnp.arange(self.static_env_params.num_joints)[state.joint.a_index == shape_index]
r_b = jnp.arange(self.static_env_params.num_joints)[state.joint.b_index == shape_index]
t = jnp.arange(self.static_env_params.num_thrusters)[state.thruster.object_index == shape_index]
return jnp.concatenate([r_a, r_b], axis=0), t
def _edit_thruster(self, pcg_state: PCGState, left_click: bool, right_click: bool):
if not self.creating_shape and (1 - pcg_state.env_state.thruster.active.astype(int)).sum() == 0:
if not right_click:
return pcg_state
thruster_pos = self._get_mouse_position_world_space()
idx = -1
for ri in self._get_polygons_on_mouse(pcg_state.env_state):
r = jax.tree.map(lambda x: x[ri], pcg_state.env_state.polygon)
thruster_pos = snap_to_polygon_center_line(r, thruster_pos)
relative_pos = jnp.matmul(rmat(r.rotation).transpose((1, 0)), thruster_pos - r.position)
idx = ri
break
if idx == -1:
for ci in self._get_circles_on_mouse(pcg_state.env_state):
c = jax.tree.map(lambda x: x[ci], pcg_state.env_state.circle)
thruster_pos = snap_to_center(c, thruster_pos)
thruster_pos = snap_to_circle_center_line(c, thruster_pos)
relative_pos = thruster_pos - c.position
idx = ci + self.static_env_params.num_polygons
break
if left_click:
if self.creating_shape:
self.creating_shape = False
else:
if idx >= 0:
self.creating_shape = True
self.creating_shape_position = thruster_pos
self.creating_shape_index = jnp.argmin(pcg_state.env_state.thruster.active)
shape = select_shape(pcg_state.env_state, idx, self.static_env_params)
def _add_thruster_to_state(state):
state = state.replace(
thruster=state.thruster.replace(
object_index=state.thruster.object_index.at[self.creating_shape_index].set(idx),
relative_position=state.thruster.relative_position.at[self.creating_shape_index].set(
relative_pos
),
power=state.thruster.power.at[self.creating_shape_index].set(
1.0 / jax.lax.select(shape.inverse_mass == 0, 1.0, shape.inverse_mass)
),
active=state.thruster.active.at[self.creating_shape_index].set(True),
global_position=state.thruster.global_position.at[self.creating_shape_index].set(
thruster_pos
),
rotation=state.thruster.rotation.at[self.creating_shape_index].set(0.0),
),
thruster_bindings=state.thruster_bindings.at[self.creating_shape_index].set(0),
)
return state
pcg_state = pcg_state.replace(
env_state=_add_thruster_to_state(pcg_state.env_state),
env_state_max=_add_thruster_to_state(pcg_state.env_state_max),
)
elif right_click:
for ti in self._get_thrusters_on_mouse(pcg_state.env_state):
def _remove_thruster_from_state(state):
return state.replace(
thruster=state.thruster.replace(active=state.thruster.active.at[ti].set(False))
)
return pcg_state.replace(
env_state=_remove_thruster_from_state(pcg_state.env_state),
env_state_max=_remove_thruster_from_state(pcg_state.env_state_max),
)
else:
if self.creating_shape:
curr_pos = self._get_mouse_position_world_space()
normal = pcg_state.env_state.thruster.relative_position[self.creating_shape_index]
angle = jnp.arctan2(normal[1], normal[0])
relative_pos = curr_pos - self.creating_shape_position
# rotation = jnp.arctan2(relative_pos[1], relative_pos[0])
rotation = jnp.pi + jnp.arctan2(relative_pos[1], relative_pos[0]) + angle
angle_round = jnp.round(rotation / (jnp.pi / 2))
angle_norm = rotation / (jnp.pi / 2)
if jnp.abs(angle_round - angle_norm) < 0.3:
rotation = angle_round * (jnp.pi / 2)
def _update_thruster_rotation(state):
return state.replace(
thruster=state.thruster.replace(
rotation=state.thruster.rotation.at[self.creating_shape_index].set(rotation - angle),
)
)
pcg_state = pcg_state.replace(
env_state=_update_thruster_rotation(pcg_state.env_state),
env_state_max=_update_thruster_rotation(pcg_state.env_state_max),
)
else:
pass
return pcg_state
def _edit_circle(self, pcg_state: PCGState, left_click: bool, right_click: bool):
if right_click:
for ci in self._get_circles_on_mouse(pcg_state.env_state):
attached_j, attached_t = self._get_joints_attached_to_shape(
pcg_state.env_state, ci + self.static_env_params.num_polygons
)
def _remove_circle_from_state(state):
return state.replace(
circle=state.circle.replace(active=state.circle.active.at[ci].set(False)),
joint=state.joint.replace(active=state.joint.active.at[attached_j].set(False)),
thruster=state.thruster.replace(active=state.thruster.active.at[attached_t].set(False)),
)
env_state = _remove_circle_from_state(pcg_state.env_state)
env_state_pcg_mask = _remove_circle_from_state(pcg_state.env_state_pcg_mask)
env_state_max = _remove_circle_from_state(pcg_state.env_state_max)
env_state = env_state.replace(
collision_matrix=calculate_collision_matrix(self.static_env_params, env_state.joint)
)
return PCGState(
env_state=env_state,
env_state_pcg_mask=env_state_pcg_mask,
env_state_max=env_state_max,
tied_together=pcg_state.tied_together,
)
if not self.creating_shape and (1 - pcg_state.env_state.circle.active.astype(int)).sum() == 0:
return pcg_state
radius = jnp.linalg.norm(self._get_mouse_position_world_space() - self.create_shape_position)
radius = jnp.clip(radius, 5.0 / self.env_params.pixels_per_unit, self.static_env_params.max_shape_size / 2)
def _add_circle(state, highlight):
state = state.replace(
circle=state.circle.replace(
position=state.circle.position.at[self.creating_shape_index].set(self.create_shape_position),
velocity=state.circle.velocity.at[self.creating_shape_index].set(jnp.array([0.0, 0.0])),
radius=state.circle.radius.at[self.creating_shape_index].set(radius),
inverse_mass=state.circle.inverse_mass.at[self.creating_shape_index].set(1.0),
inverse_inertia=state.circle.inverse_inertia.at[self.creating_shape_index].set(1.0),
active=state.circle.active.at[self.creating_shape_index].set(True),
collision_mode=state.circle.collision_mode.at[self.creating_shape_index].set(1),
),
circle_shape_roles=state.circle_shape_roles.at[self.creating_shape_index].set(0),
circle_highlighted=state.circle_highlighted.at[self.creating_shape_index].set(highlight),
circle_densities=state.circle_densities.at[self.creating_shape_index].set(1.0),
)
return state
if left_click:
if self.creating_shape:
env_state = _add_circle(pcg_state.env_state, False)
env_state_max = _add_circle(pcg_state.env_state_max, False)
env_state = recalculate_mass_and_inertia(
env_state,
self.static_env_params,
env_state.polygon_densities,
env_state.circle_densities,
)
env_state_max = recalculate_mass_and_inertia(
env_state_max,
self.static_env_params,
env_state.polygon_densities,
env_state.circle_densities,
)
pcg_state = pcg_state.replace(env_state=env_state, env_state_max=env_state_max)
self.creating_shape = False
else:
self.creating_shape_index = jnp.argmin(pcg_state.env_state.circle.active)
self.create_shape_position = self._get_mouse_position_world_space()
self.creating_shape = True
else:
if self.creating_shape:
env_state = _add_circle(pcg_state.env_state, True)
env_state_max = _add_circle(pcg_state.env_state_max, True)
pcg_state = pcg_state.replace(env_state=env_state, env_state_max=env_state_max)
else:
pass
return pcg_state
def _get_polygons_on_mouse(self, state, n_vertices=None):
# n_vertices=None selects both triangles and quads
ris = []
mouse_pos = self._get_mouse_position_world_space()
for ri in jnp.arange(self.static_env_params.num_polygons)[::-1]:
polygon = jax.tree.map(lambda x: x[ri], state.polygon)
if (not polygon.active) or ((n_vertices is not None) and polygon.n_vertices != n_vertices):
continue
mpos = rmat(-polygon.rotation) @ (mouse_pos - polygon.position)
def _signed_line_distance(a, b, c):
return (b[0] - a[0]) * (c[1] - a[1]) - (b[1] - a[1]) * (c[0] - a[0])
inside = True
for fi in range(polygon.n_vertices):
v1 = polygon.vertices[fi]
v2 = polygon.vertices[(fi + 1) % polygon.n_vertices]
if _signed_line_distance(mpos, v1, v2) > 0:
inside = False
if inside:
ris.append(ri)
return ris
def _edit_rect(self, pcg_state: PCGState, left_click: bool, right_click: bool):
if right_click:
for ri in self._get_polygons_on_mouse(pcg_state.env_state, n_vertices=4):
attached_j, attached_t = self._get_joints_attached_to_shape(pcg_state.env_state, ri)
def _remove_rect_from_state(state):
state = state.replace(
polygon=state.polygon.replace(
active=state.polygon.active.at[ri].set(False),
rotation=state.polygon.rotation.at[ri].set(0.0),
),
joint=state.joint.replace(active=state.joint.active.at[attached_j].set(False)),
thruster=state.thruster.replace(active=state.thruster.active.at[attached_t].set(False)),
)
return state
env_state = _remove_rect_from_state(pcg_state.env_state)
env_state_max = _remove_rect_from_state(pcg_state.env_state_max)
env_state_pcg_mask = _remove_rect_from_state(pcg_state.env_state_pcg_mask)
env_state = env_state.replace(
collision_matrix=calculate_collision_matrix(self.static_env_params, env_state.joint)
)
return PCGState(
env_state=env_state,
env_state_max=env_state_max,
env_state_pcg_mask=env_state_pcg_mask,
tied_together=pcg_state.tied_together,
)
if not self.creating_shape and (1 - pcg_state.env_state.polygon.active.astype(int)).sum() == 0:
return pcg_state
diff = (self._get_mouse_position_world_space() - self.create_shape_position) / 2
diff = jnp.clip(
diff,
-(self.static_env_params.max_shape_size / 2) / jnp.sqrt(2),
(self.static_env_params.max_shape_size / 2) / jnp.sqrt(2),
)
half_dim = jnp.abs(diff)
half_dim = jnp.clip(half_dim, a_min=5.0 / self.env_params.pixels_per_unit)
vertices = rectangle_vertices(half_dim)
def _add_rect_to_state(state, highlight):
state = state.replace(
polygon=state.polygon.replace(
position=state.polygon.position.at[self.creating_shape_index].set(
self.create_shape_position + diff
),
velocity=state.polygon.velocity.at[self.creating_shape_index].set(jnp.array([0.0, 0.0])),
vertices=state.polygon.vertices.at[self.creating_shape_index].set(vertices),
inverse_mass=state.polygon.inverse_mass.at[self.creating_shape_index].set(1.0),
inverse_inertia=state.polygon.inverse_inertia.at[self.creating_shape_index].set(1.0),
active=state.polygon.active.at[self.creating_shape_index].set(True),
collision_mode=state.polygon.collision_mode.at[self.creating_shape_index].set(1),
n_vertices=state.polygon.n_vertices.at[self.creating_shape_index].set(4),
),
polygon_shape_roles=state.polygon_shape_roles.at[self.creating_shape_index].set(0),
polygon_highlighted=state.polygon_highlighted.at[self.creating_shape_index].set(highlight),
polygon_densities=state.polygon_densities.at[self.creating_shape_index].set(1.0),
)
return state
if left_click:
if self.creating_shape:
env_state = _add_rect_to_state(pcg_state.env_state, False)
env_state_max = _add_rect_to_state(pcg_state.env_state_max, False)
env_state = recalculate_mass_and_inertia(
env_state,
self.static_env_params,
env_state.polygon_densities,
env_state.circle_densities,
)
env_state_max = recalculate_mass_and_inertia(
env_state_max,
self.static_env_params,
env_state.polygon_densities,
env_state.circle_densities,
)
pcg_state = pcg_state.replace(env_state=env_state, env_state_max=env_state_max)
self.creating_shape = False
else:
self.creating_shape_index = jnp.argmin(pcg_state.env_state.polygon.active)
self.create_shape_position = self._get_mouse_position_world_space()
self.creating_shape = True
else:
if self.creating_shape:
env_state = _add_rect_to_state(pcg_state.env_state, True)
env_state_max = _add_rect_to_state(pcg_state.env_state_max, True)
pcg_state = pcg_state.replace(env_state=env_state, env_state_max=env_state_max)
else:
pass
return pcg_state
def _reset_triangles(self):
self.triangle_order = jnp.array([0, 1, 2])
self.num_triangle_clicks = 0
self.creating_shape = False
def _edit_triangle(self, pcg_state: PCGState, left_click: bool, right_click: bool):
if right_click:
self.num_triangle_clicks = 0
for ri in self._get_polygons_on_mouse(pcg_state.env_state, n_vertices=3):
attached_r, attached_f = self._get_joints_attached_to_shape(pcg_state.env_state, ri)
def _remove_triangle_from_state(state):
state = state.replace(
polygon=state.polygon.replace(
active=state.polygon.active.at[ri].set(False),
rotation=state.polygon.rotation.at[ri].set(0.0),
),
joint=state.joint.replace(active=state.joint.active.at[attached_r].set(False)),
)
return state
env_state = _remove_triangle_from_state(pcg_state.env_state)
env_state_max = _remove_triangle_from_state(pcg_state.env_state_max)
env_state_pcg_mask = _remove_triangle_from_state(pcg_state.env_state_pcg_mask)
env_state = env_state.replace(
collision_matrix=calculate_collision_matrix(self.static_env_params, env_state.joint)
)
pcg_state = PCGState(
env_state=env_state,
env_state_max=env_state_max,
env_state_pcg_mask=env_state_pcg_mask,
tied_together=pcg_state.tied_together,
)
return pcg_state
if not self.creating_shape and (1 - pcg_state.env_state.polygon.active.astype(int)).sum() == 0:
return pcg_state
def get_correct_center_two_verts(verts):
return (jnp.max(verts, axis=0) + jnp.min(verts, axis=0)) / 2
def order_clockwise(verts, loose_ordering=False):
# verts has shape (3, 2), order them clockwise.
# https://stackoverflow.com/questions/51074984/sort-vertices-in-clockwise-order
# Calculate centroid
centroid = jnp.mean(verts, axis=0)
# Calculate angles
angles = jnp.round(jnp.arctan2(verts[:, 1] - centroid[1], verts[:, 0] - centroid[0]), 2)
# Order vertices
order = jnp.argsort(-angles, stable=True)
if loose_ordering:
order = jnp.arange(len(order))
ans = verts[order]
# order is of shape (2, ) or (3, ). I want it to always be of shape 3
if len(order) < 3:
order = jnp.concatenate([order, jnp.array([2])])
return ans, order
def do_triangle_n_click(pcg_state, how_many_clicks, is_on_a_click=False):
n = how_many_clicks
# if we must keep them clockwise all the time, then the one we edit / move around may have varying indices.
current_index_to_change = self.triangle_order[n]
sign = 1
idxs = jnp.arange(n + 1)
idxs_to_allow = idxs[~(idxs == current_index_to_change)]
# Get the new vertex and clip its position
new_tentative_vert = (
self._get_mouse_position_world_space() - pcg_state.env_state.polygon.position[self.creating_shape_index]
)
new_tentative_vert = jnp.clip(
new_tentative_vert,
jnp.max(pcg_state.env_state.polygon.vertices[self.creating_shape_index, idxs_to_allow], axis=0)
- self.static_env_params.max_shape_size * 0.8,
jnp.min(pcg_state.env_state.polygon.vertices[self.creating_shape_index, idxs_to_allow], axis=0)
+ self.static_env_params.max_shape_size * 0.8,
)
new_verts = pcg_state.env_state.polygon.vertices.at[self.creating_shape_index, current_index_to_change].set(
new_tentative_vert
)
new_center_two = get_correct_center_two_verts(new_verts[self.creating_shape_index, : n + 1])
_, new_center_three = calc_inverse_mass_polygon(
new_verts[self.creating_shape_index],
3,
self.static_env_params,
1.0,
)
new_center = jax.lax.select(n == 1, new_center_two, new_center_three)
new_verts = new_verts.at[self.creating_shape_index].add(-sign * new_center)
vvs = new_verts[self.creating_shape_index, : n + 1]
ordered_vertices, new_permutation = order_clockwise(vvs, loose_ordering=not is_on_a_click)
self.triangle_order = self.triangle_order[new_permutation]
new_verts = new_verts.at[self.creating_shape_index, : n + 1].set(ordered_vertices)
env_state = pcg_state.env_state.replace(
polygon=pcg_state.env_state.polygon.replace(
vertices=new_verts,
position=pcg_state.env_state.polygon.position.at[self.creating_shape_index].add(sign * new_center),
n_vertices=pcg_state.env_state.polygon.n_vertices.at[self.creating_shape_index].set(n + 1),
),
)
env_state_max = pcg_state.env_state_max.replace(
polygon=pcg_state.env_state_max.polygon.replace(
vertices=new_verts,
position=pcg_state.env_state_max.polygon.position.at[self.creating_shape_index].add(
sign * new_center
),
n_vertices=pcg_state.env_state_max.polygon.n_vertices.at[self.creating_shape_index].set(n + 1),
),
)
pcg_state = pcg_state.replace(env_state=env_state, env_state_max=env_state_max)
return pcg_state
if left_click:
if self.creating_shape:
assert 3 > self.num_triangle_clicks > 0
if self.num_triangle_clicks == 1:
pcg_state = do_triangle_n_click(pcg_state, 1, is_on_a_click=True)
self.num_triangle_clicks += 1
else: # this finishes it
pcg_state = do_triangle_n_click(pcg_state, 2, is_on_a_click=True)
self.creating_shape = False
self.num_triangle_clicks = 0
pcg_state = pcg_state.replace(
env_state=recalculate_mass_and_inertia(
pcg_state.env_state,
self.static_env_params,
pcg_state.env_state.polygon_densities,
pcg_state.env_state.circle_densities,
)
)
else:
self.triangle_order = jnp.array([0, 1, 2])
self.creating_shape_index = jnp.argmin(pcg_state.env_state.polygon.active)
self.create_shape_position = self._get_mouse_position_world_space()
self.creating_shape = True
self.num_triangle_clicks = 1
vertices = jnp.zeros((self.static_env_params.max_polygon_vertices, 2), dtype=jnp.float32)
def _add_triangle_to_state(state):
state = state.replace(
polygon=state.polygon.replace(
position=state.polygon.position.at[self.creating_shape_index].set(
self.create_shape_position
),
velocity=state.polygon.velocity.at[self.creating_shape_index].set(jnp.array([0.0, 0.0])),
vertices=state.polygon.vertices.at[self.creating_shape_index].set(vertices),
inverse_mass=state.polygon.inverse_mass.at[self.creating_shape_index].set(1.0),
inverse_inertia=state.polygon.inverse_inertia.at[self.creating_shape_index].set(1.0),
active=state.polygon.active.at[self.creating_shape_index].set(True),
n_vertices=state.polygon.n_vertices.at[self.creating_shape_index].set(1),
),
polygon_shape_roles=state.polygon_shape_roles.at[self.creating_shape_index].set(0),
polygon_highlighted=state.polygon_highlighted.at[self.creating_shape_index].set(False),
polygon_densities=state.polygon_densities.at[self.creating_shape_index].set(1.0),
)
return state
pcg_state = pcg_state.replace(
env_state=_add_triangle_to_state(pcg_state.env_state),
env_state_max=_add_triangle_to_state(pcg_state.env_state_max),
)
elif self.creating_shape:
assert 1 <= self.num_triangle_clicks <= 2
pcg_state = do_triangle_n_click(
pcg_state, self.num_triangle_clicks, is_on_a_click=self.num_triangle_clicks == 1
)
return pcg_state
def _edit_joint(self, pcg_state: PCGState, left_click: bool, right_click: bool):
if left_click and pcg_state.env_state.joint.active.all():
return pcg_state
if left_click:
joint_index = jnp.argmin(pcg_state.env_state.joint.active)
joint_position = self._get_mouse_position_world_space()
# reverse them so that the joint order and rendering order remains the same.
# We want the first shape to have a lower index than the second shape, with circles always having higher indices compared to rectangles.
circles = self._get_circles_on_mouse(pcg_state.env_state)[::-1]
rects = self._get_polygons_on_mouse(pcg_state.env_state)[::-1]
if len(rects) + len(circles) >= 2:
r1 = len(rects) >= 1
r2 = len(rects) >= 2
a_index = rects[0] if r1 else circles[0] # + self.static_env_params.num_polygons
b_index = rects[r1 * 1] if r2 else circles[1 - 1 * r1] # + self.static_env_params.num_polygons
a_shape = pcg_state.env_state.polygon if r1 else pcg_state.env_state.circle
b_shape = pcg_state.env_state.polygon if r2 else pcg_state.env_state.circle
a = jax.tree.map(lambda x: x[a_index], a_shape)
b = jax.tree.map(lambda x: x[b_index], b_shape)
a_index += (not r1) * self.static_env_params.num_polygons
b_index += (not r2) * self.static_env_params.num_polygons
joint_position = snap_to_center(a, joint_position)
joint_position = snap_to_center(b, joint_position)
a_relative_pos = jnp.matmul(rmat(a.rotation).transpose((1, 0)), joint_position - a.position)
b_relative_pos = jnp.matmul(rmat(b.rotation).transpose((1, 0)), joint_position - b.position)
def _add_joint_to_state(state):
state = state.replace(
joint=state.joint.replace(
a_index=state.joint.a_index.at[joint_index].set(a_index),
b_index=state.joint.b_index.at[joint_index].set(b_index),
a_relative_pos=state.joint.a_relative_pos.at[joint_index].set(a_relative_pos),
b_relative_pos=state.joint.b_relative_pos.at[joint_index].set(b_relative_pos),
active=state.joint.active.at[joint_index].set(True),
global_position=state.joint.global_position.at[joint_index].set(joint_position),
motor_on=state.joint.motor_on.at[joint_index].set(True),
motor_speed=state.joint.motor_speed.at[joint_index].set(1.0),
motor_power=state.joint.motor_power.at[joint_index].set(1.0),
rotation=state.joint.rotation.at[joint_index].set(b.rotation - a.rotation),
)
)
return state
env_state = _add_joint_to_state(pcg_state.env_state)
env_state_max = _add_joint_to_state(pcg_state.env_state_max)
env_state = env_state.replace(
collision_matrix=calculate_collision_matrix(self.static_env_params, env_state.joint)
)
pcg_state = pcg_state.replace(env_state=env_state, env_state_max=env_state_max)
return pcg_state
def _reset_select_shape(self, pcg_state):
pcg_state = pcg_state.replace(
env_state=pcg_state.env_state.replace(
polygon_highlighted=jnp.zeros_like(pcg_state.env_state.polygon_highlighted),
circle_highlighted=jnp.zeros_like(pcg_state.env_state.circle_highlighted),
)
)
self.selected_shape_index = -1
self.selected_shape_type = ObjectType.POLYGON
self._hide_all_widgets()
return pcg_state
def _hide_all_widgets(self):
for widget in self.all_widgets.values():
for w in widget.values():
w.hide()
def _show_correct_widgets(self, type: ObjectType | None, do_tie_ui: bool = False):
for widget in self.all_widgets["GENERAL"].values():
widget.show()
if do_tie_ui:
for widget in self.all_widgets["TIE_TOGETHER"].values():
widget.show()
n = len(self.all_selected_shapes)
# {[int(i) for (i, t) in self.all_selected_shapes]}
self.all_widgets["GENERAL"]["lblGeneral"].setText(f"Selected {n} Objects")
return
for widget in self.all_widgets[type].values():
widget.show()
if type is None:
self.all_widgets["GENERAL"]["lblGeneral"].setText(f"Global")
else:
self.all_widgets["GENERAL"]["lblGeneral"].setText(f"{type.name} (idx {self.selected_shape_index})")
def _select_shape_keyboard_shortcuts(self, pcg_state: PCGState, left_click: bool, keys: list[int]):
if left_click:
return pcg_state
if len(keys) != 0 and self.selected_shape_index != -1:
s = 1.0
ang_s = 0.1
vel = jnp.array([0.0, 0.0])
angular_vel = 0.0
should_toggle_fixed = False
should_toggle_collidable = False
change_angle = 0
def add_step(widget_name, direction, speed=10, overwrite_amount=None):
widget = self.all_widgets[self.selected_shape_type][widget_name]
val = widget.getValue()
step = widget.step
amount_to_add = overwrite_amount or step * direction * speed
widget.setValue(jnp.clip(val + amount_to_add, widget.min, widget.max))
if pygame.K_w in keys:
add_step("sldPosition_Y", 1)
if pygame.K_s in keys:
add_step("sldPosition_Y", -1)
if pygame.K_a in keys:
add_step("sldPosition_X", -1)
if pygame.K_d in keys:
add_step("sldPosition_X", 1)
if pygame.K_q in keys:
add_step("sldRotation", 1)
if pygame.K_e in keys:
add_step("sldRotation", -1)
if pygame.K_f in keys:
self.all_widgets[self.selected_shape_type]["tglFixate"].toggle()
if pygame.K_c in keys and not (pygame.key.get_mods() & pygame.KMOD_CTRL):
widget = self.all_widgets[self.selected_shape_type]["sldCollidability"]
curr_val = int(widget.getValue())
widget.setValue((curr_val + 1) % (widget.max + 1))
if pygame.K_r in keys and not (pygame.key.get_mods() & pygame.KMOD_CTRL):
widget = self.all_widgets[self.selected_shape_type]["sldRole"]
curr_val = int(widget.getValue())
widget.setValue((curr_val + 1) % (widget.max + 1))
if pygame.K_LEFTBRACKET in keys:
add_step("sldRotation", 1, 10, jnp.pi / 4)
if pygame.K_RIGHTBRACKET in keys:
add_step("sldRotation", -1, 10, -jnp.pi / 4)
if pygame.K_c in keys and (pygame.key.get_mods() & pygame.KMOD_CTRL):
# copy
if self.selected_shape_type == ObjectType.POLYGON: # rect
if not self.pcg_state.env_state.polygon.active.all():
where_to_add = jnp.argmin(pcg_state.env_state.polygon.active)
if where_to_add < self.static_env_params.num_polygons:
def _copy_polygon(state, shift):
state = state.replace(
polygon=jax.tree.map(
lambda x: x.at[where_to_add].set(x[self.selected_shape_index]), state.polygon
)
)
if shift:
state = state.replace(
polygon=state.polygon.replace(
position=state.polygon.position.at[where_to_add].add(0.1),
),
polygon_highlighted=state.polygon_highlighted.at[where_to_add].set(False),
)
return state
pcg_state = pcg_state.replace(
env_state=_copy_polygon(pcg_state.env_state, shift=True),
env_state_max=_copy_polygon(pcg_state.env_state_max, shift=True),
env_state_pcg_mask=_copy_polygon(pcg_state.env_state_pcg_mask, shift=False),
)
elif self.selected_shape_type == ObjectType.CIRCLE: # circle
if not self.pcg_state.env_state.circle.active.all():
where_to_add = jnp.argmin(pcg_state.env_state.circle.active)
if where_to_add < self.static_env_params.num_circles:
def _copy_circle(state, shift=True):
state = state.replace(
circle=jax.tree.map(
lambda x: x.at[where_to_add].set(x[self.selected_shape_index]), state.circle
)
)
if shift:
state = state.replace(
circle=state.circle.replace(
position=state.circle.position.at[where_to_add].add(0.1),
),
circle_highlighted=state.circle_highlighted.at[where_to_add].set(False),
)
return state
pcg_state = pcg_state.replace(
env_state=_copy_circle(pcg_state.env_state),
env_state_max=_copy_circle(pcg_state.env_state_max),
env_state_pcg_mask=_copy_circle(pcg_state.env_state_pcg_mask, shift=False),
)
if self.selected_shape_index >= 0:
num = get_numeric_key_pressed(self.pygame_events)
if num is not None:
if self.selected_shape_type in [ObjectType.CIRCLE, ObjectType.POLYGON]:
self.all_widgets[self.selected_shape_type]["sldRole"].setValue(num % 4)
elif self.selected_shape_type == ObjectType.JOINT:
self.all_widgets[self.selected_shape_type]["sldColour"].setValue(
num % self.static_env_params.num_motor_bindings
)
elif self.selected_shape_type == ObjectType.THRUSTER:
self.all_widgets[self.selected_shape_type]["sldColour"].setValue(
num % self.static_env_params.num_thruster_bindings
)
return pcg_state
def _edit_select_shape(self, pcg_state: PCGState, left_click: bool, right_click: bool, keys: list[int]):
def _find_shape(pcg_state):
found_shape = False
selected_shape_index, selected_shape_type = -1, ObjectType.POLYGON
for ri in self._get_revolute_joints_on_mouse(pcg_state.env_state):
selected_shape_index = ri
selected_shape_type = ObjectType.JOINT
found_shape = True
break
if not found_shape:
for ti in self._get_thrusters_on_mouse(pcg_state.env_state):
selected_shape_index = ti
selected_shape_type = ObjectType.THRUSTER
found_shape = True
break
if not found_shape:
for ri in self._get_polygons_on_mouse(pcg_state.env_state):
pcg_state = pcg_state.replace(
env_state=pcg_state.env_state.replace(
polygon_highlighted=pcg_state.env_state.polygon_highlighted.at[ri].set(True),
)
)
selected_shape_index = ri
selected_shape_type = ObjectType.POLYGON
found_shape = True
break
if not found_shape:
for ci in self._get_circles_on_mouse(pcg_state.env_state):
pcg_state = pcg_state.replace(
env_state=pcg_state.env_state.replace(
circle_highlighted=pcg_state.env_state.circle_highlighted.at[ci].set(True),
)
)
selected_shape_index = ci
selected_shape_type = ObjectType.CIRCLE
found_shape = True
break
return selected_shape_index, selected_shape_type, found_shape, pcg_state
if found_shape and self.selected_shape_type in self.all_widgets:
self._show_correct_widgets(self.selected_shape_type)
# if left and shift
if left_click and (pygame.key.get_mods() & pygame.KMOD_SHIFT):
# This is trying to select multiple things.
idx, type, found, pcg_state = _find_shape(pcg_state)
if found:
t = (idx, type)
if t in self.all_selected_shapes:
self.all_selected_shapes.remove(t)
else:
self.all_selected_shapes.append(t)
self._hide_all_widgets()
self._show_correct_widgets(None, do_tie_ui=True)
elif left_click:
self.all_selected_shapes = []
self._hide_all_widgets()
pcg_state = self._reset_select_shape(pcg_state)
self.selected_shape_index, self.selected_shape_type, found_shape, pcg_state = _find_shape(pcg_state)
if found_shape:
self.all_selected_shapes = [(self.selected_shape_index, self.selected_shape_type)]
if self.selected_shape_type in self.all_widgets:
self._show_correct_widgets(self.selected_shape_type)
if self.selected_shape_index < 0:
self._show_correct_widgets(None)
return pcg_state
def render(self, env_state):
# Clear
self.screen_surface.fill((0, 0, 0))
if self.is_editing:
pixels = self._render_fn_edit(env_state)
else:
pixels = self._render_fn(env_state)
pixels = self._render_edit_overlay_fn(pixels, self.is_editing, self.edit_shape_mode.value)
surface = pygame.surfarray.make_surface(np.array(pixels))
self.screen_surface.blit(surface, (0, 0))
def is_quit_requested(self):
for event in self.pygame_events:
if event.type == pygame.QUIT:
return True
return False
@hydra.main(version_base=None, config_path="../configs", config_name="editor")
def main(config):
config = normalise_config(OmegaConf.to_container(config), "EDITOR", editor_config=True)
env_params, static_env_params = generate_params_from_config(config)
static_env_params = static_env_params.replace(frame_skip=config["frame_skip"], downscale=config["downscale"])
config["env_params"] = to_state_dict(env_params)
config["static_env_params"] = to_state_dict(static_env_params)
env = make_kinetix_env_from_name("Kinetix-Entity-MultiDiscrete-v1", static_env_params=static_env_params)
env = AutoResetWrapper(env, make_reset_function(static_env_params))
seed = config["seed"]
print("seed", seed)
rng = jax.random.PRNGKey(seed)
outer_timer = tmr()
editor = Editor(env, env_params, config, upscale=config["upscale"])
time_e = tmr()
print("Took {:2f}s to create editor".format(time_e - outer_timer))
clock = pygame.time.Clock()
while not editor.is_quit_requested():
rng, _rng = jax.random.split(rng)
editor.update(_rng)
clock.tick(config["fps"])
if __name__ == "__main__":
main()