class CollisionActor(StaticActor):
    def __init__(self,  name, **object_data):
        StaticActor.__init__(self,  name, **object_data)
        self.visible = object_data.get('visible', False) class StaticActor:
    def __init__(self, name, **object_data):
        self.name = name
        self.selected = False
        self.model = None
        self.has_mesh = False
        self.visible = object_data.get('visible', True)
        self.object_id = object_data.get('object_id', 0)
        self.object_color = object_data.get('object_color', Float3(1.0, 1.0, 1.0))

        # transform
        self.bound_box = BoundBox()
        self.geometry_bound_boxes = []
        self.transform = TransformObject()
        self.transform.set_pos(object_data.get('pos', [0, 0, 0]))
        self.transform.set_rotation(object_data.get('rot', [0, 0, 0]))
        self.transform.set_scale(object_data.get('scale', [1, 1, 1]))

        self.set_model(object_data.get('model'))

        self.instance_pos = RangeVariable(**object_data.get('instance_pos',
                                                            dict(min_value=Float3(-10.0, 0.0, -10.0),
                                                                 max_value=Float3(10.0, 0.0, 10.0))))
        self.instance_rot = RangeVariable(**object_data.get('instance_rot', dict(min_value=FLOAT3_ZERO)))
        self.instance_scale = RangeVariable(**object_data.get('instance_scale', dict(min_value=1.0)))
        self.instance_pos_list = object_data.get('instance_pos_list', [])
        self.instance_rot_list = object_data.get('instance_rot_list', [])
        self.instance_scale_list = object_data.get('instance_scale_list', [])
        self.instance_count = object_data.get('instance_count', 1)
        self.instance_render_count = object_data.get('instance_render_count', None)
        self.instance_matrix = None
        self.bound_box_scale = Float3()
        self.bound_box_offset = Float3()

        self.set_instance_count(self.instance_count)

        self.attributes = Attributes()

    def delete(self):
        pass

    def is_skeletal_actor(self):
        return False

    def get_center(self):
        return self.bound_box.bound_center

    def get_pos(self):
        return self.transform.get_pos()

    def set_pos(self, pos):
        self.transform.set_pos(pos)

    def get_object_id(self):
        return self.object_id

    def set_object_id(self, object_id):
        self.object_id = object_id

    def get_object_color(self):
        return self.object_color

    def set_object_color(self, object_color):
        self.object_color[...] = object_color

    def set_model(self, model):
        self.model = model
        self.has_mesh = model is not None and model.mesh is not None

        self.geometry_bound_boxes.clear()
        if self.has_mesh:
            self.bound_box.clone(self.model.mesh.bound_box)
            for i, geometry in enumerate(self.model.mesh.geometries):
                self.geometry_bound_boxes.append(BoundBox())
                self.geometry_bound_boxes[i].clone(geometry.bound_box)

    def get_save_data(self):
        save_data = dict(
            name=self.name,
            visible=self.visible,
            model=self.model.name if self.model else '',
            pos=self.transform.pos.tolist(),
            rot=self.transform.rot.tolist(),
            scale=self.transform.scale.tolist(),
            instance_count=self.instance_count,
            instance_pos=self.instance_pos.get_save_data(),
            instance_rot=self.instance_rot.get_save_data(),
            instance_scale=self.instance_scale.get_save_data(),
            instance_pos_list=self.instance_pos_list,
            instance_rot_list=self.instance_rot_list,
            instance_scale_list=self.instance_scale_list,
        )
        return save_data

    def is_instancing(self):
        return 1 < self.instance_count

    def get_instance_render_count(self):
        if self.instance_render_count is None:
            return self.instance_count
        return self.instance_render_count

    def set_instance_render_count(self, count):
        self.instance_render_count = min(count, self.instance_count)

    def set_instance_count(self, count):
        if not self.has_mesh:
            return

        mesh = self.model.mesh

        self.instance_count = count

        if self.instance_render_count is not None and count < self.instance_render_count:
            self.set_instance_render_count(count)

        if 1 < count:
            self.instance_pos_list = [self.instance_pos.get_uniform() for i in range(count)]
            self.instance_rot_list = [self.instance_rot.get_uniform() for i in range(count)]
            self.instance_scale_list = [self.instance_scale.get_uniform() for i in range(count)]
            self.instance_matrix = np.zeros(count, (np.float32, (4, 4)))

            bound_min = Float3(FLOAT32_MAX, FLOAT32_MAX, FLOAT32_MAX)
            bound_max = Float3(FLOAT32_MIN, FLOAT32_MIN, FLOAT32_MIN)
            bound_box = BoundBox()

            for i in range(count):
                self.instance_matrix[i][...] = MATRIX4_IDENTITY
                uniform_scale = self.instance_scale_list[i]
                matrix_scale(self.instance_matrix[i], uniform_scale, uniform_scale, uniform_scale)
                matrix_rotate(self.instance_matrix[i], *self.instance_rot_list[i])
                matrix_translate(self.instance_matrix[i], *self.instance_pos_list[i])

                bound_box.update_with_matrix(mesh.bound_box, self.instance_matrix[i])
                bound_min = np.minimum(bound_min, bound_box.bound_min)
                bound_max = np.maximum(bound_max, bound_box.bound_max)
            # update bound box
            self.bound_box_scale[...] = abs((bound_max - bound_min) / (mesh.bound_box.bound_max - mesh.bound_box.bound_min))
            self.bound_box_offset[...] = bound_min - mesh.bound_box.bound_min
        else:
            self.instance_matrix = None
        self.update_bound_box()

    def get_attribute(self):
        self.attributes.set_attribute('name', self.name)
        self.attributes.set_attribute('visible', self.visible)
        self.attributes.set_attribute('pos', self.transform.pos)
        self.attributes.set_attribute('rot', self.transform.rot)
        self.attributes.set_attribute('scale', self.transform.scale)
        self.attributes.set_attribute('model', self.model.name if self.model else '')
        self.attributes.set_attribute('instance_count', self.instance_count)
        self.attributes.set_attribute('instance_pos', self.instance_pos.get_save_data())
        self.attributes.set_attribute('instance_rot', self.instance_rot.get_save_data())
        self.attributes.set_attribute('instance_scale', self.instance_scale.get_save_data())
        return self.attributes

    def set_attribute(self, attribute_name, attribute_value, item_info_history, attribute_index):
        if attribute_name == 'pos':
            self.transform.set_pos(attribute_value)
        elif attribute_name == 'rot':
            self.transform.set_rotation(attribute_value)
        elif attribute_name == 'scale':
            self.transform.set_scale(attribute_value)
        elif attribute_name == 'instance_count':
            self.set_instance_count(attribute_value)
        elif hasattr(self, attribute_name):
            setattr(self, attribute_name, attribute_value)
        elif 1 < len(item_info_history) or 'instance_scale' == item_info_history[0].attribute_name:
            if hasattr(self, item_info_history[0].attribute_name):
                attribute = getattr(self, item_info_history[0].attribute_name)
                if isinstance(attribute, RangeVariable):
                    if 'min_value' == attribute_name:
                        attribute.set_range(attribute_value, attribute.value[1])
                    elif 'max_value' == attribute_name:
                        attribute.set_range(attribute.value[0], attribute_value)
                    self.set_instance_count(self.instance_count)

    def get_mesh(self):
        return self.model.mesh if self.has_mesh else None

    def get_geometry_count(self):
        return len(self.model.mesh.geometries)

    def get_geometry(self, index):
        return self.model.mesh.geometries[index] if self.model else None

    def get_geometry_data(self, index):
        return self.model.mesh.get_geometry_data(index) if self.model else None

    def get_gl_call_list(self, index):
        return self.model.mesh.get_gl_call_list(index) if self.model else None

    def get_geometries(self):
        return self.model.mesh.geometries if self.has_mesh else []

    def get_geometry_bound_box(self, index):
        return self.geometry_bound_boxes[index] if self.model else None

    def get_geometry_bound_boxes(self):
        return self.geometry_bound_boxes

    def get_material_instance(self, index):
        return self.model.material_instances[index] if self.model else None

    def is_selected(self):
        return self.selected

    def set_selected(self, selected):
        self.selected = selected

    def update_bound_box(self):
        if self.has_mesh:
            if 1 < self.instance_count:
                def apply_instance_scale_offset(bound_box):
                    bound_box.bound_max[...] = bound_box.bound_min + (bound_box.bound_max - bound_box.bound_min) * self.bound_box_scale
                    bound_box.bound_max[...] = bound_box.bound_max + self.bound_box_offset
                    bound_box.bound_min[...] = bound_box.bound_min + self.bound_box_offset
                    bound_box.update()
                self.bound_box.clone(self.model.mesh.bound_box)
                apply_instance_scale_offset(self.bound_box)
                for i, geometry in enumerate(self.model.mesh.geometries):
                    self.geometry_bound_boxes[i].clone(geometry.bound_box)
                    apply_instance_scale_offset(self.geometry_bound_boxes[i])
                    self.geometry_bound_boxes[i].update_with_matrix(self.geometry_bound_boxes[i], self.transform.matrix)
            else:
                self.bound_box.update_with_matrix(self.model.mesh.bound_box, self.transform.matrix)
                for i, geometry in enumerate(self.model.mesh.geometries):
                    self.geometry_bound_boxes[i].update_with_matrix(geometry.bound_box, self.transform.matrix)

    def update(self, dt):
        if self.transform.update_transform():
            self.update_bound_box() class SkeletonActor(StaticActor):
    def __init__(self, name, **object_data):
        StaticActor.__init__(self, name, **object_data)

        self.last_animation_frame = 0.0
        self.animation_loop = True
        self.animation_blend_time = 0.5
        self.animation_elapsed_time = 0.0
        self.animation_speed = 1.0
        self.animation_frame = 0.0
        self.animation_play_time = 0.0
        self.animation_end_time = None
        self.is_animation_end = False
        self.animation_buffers = []
        self.prev_animation_buffers = []
        self.blend_animation_buffers = []
        self.animation_count = 0
        self.animation_mesh = None

        if self.has_mesh:
            for animation in self.model.mesh.animations:
                if animation:
                    animation_buffer = animation.get_animation_transforms(0.0)
                    # just initialize
                    self.prev_animation_buffers.append(animation_buffer.copy())
                    self.animation_buffers.append(animation_buffer.copy())
                    self.blend_animation_buffers.append(animation_buffer.copy())
                else:
                    self.prev_animation_buffers.append(None)
                    self.animation_buffers.append(None)
                    self.blend_animation_buffers.append(None)
            self.animation_mesh = self.model.mesh

    def is_skeletal_actor(self):
        return True

    def set_animation(self, mesh, speed=1.0, loop=True, start_time=0.0, end_time=None, blend_time=0.5, force=False, reset=True):
        if mesh != self.animation_mesh or force:
            self.animation_mesh = mesh
            self.animation_speed = speed
            self.animation_loop = loop
            self.animation_blend_time = blend_time
            self.animation_end_time = end_time
            if reset:
                self.animation_elapsed_time = 0.0
                self.animation_play_time = start_time
                self.animation_frame = 0.0
                self.is_animation_end = False
            # swap
            self.animation_buffers, self.blend_animation_buffers = self.blend_animation_buffers, self.animation_buffers

    def get_prev_animation_buffer(self, index):
        return self.prev_animation_buffers[index]

    def get_animation_buffer(self, index):
        return self.animation_buffers[index]

    def update(self, dt):
        StaticActor.update(self, dt)

        # update animation
        animation_end = self.is_animation_end
        blend_ratio = 1.0
        update_animation_frame = True
        for i, animation in enumerate(self.animation_mesh.animations):
            if animation is not None:
                # update animation frame only first animation
                if update_animation_frame:
                    update_animation_frame = False
                    frame_count = animation.frame_count
                    if frame_count > 1:
                        self.animation_play_time += dt * self.animation_speed

                        animation_end_time = animation.animation_length

                        if self.animation_end_time is not None and self.animation_end_time < animation_end_time:
                            animation_end_time = self.animation_end_time

                        if self.animation_loop:
                            if animation_end_time < self.animation_play_time:
                                self.animation_play_time = math.fmod(self.animation_play_time, animation_end_time)
                        else:
                            self.animation_play_time = min(animation_end_time, self.animation_play_time)
                            if animation_end_time == self.animation_play_time:
                                animation_end = True
                        self.animation_frame = animation.get_time_to_frame(self.animation_frame, self.animation_play_time)
                    else:
                        self.animation_frame = 0.0
                    if self.animation_elapsed_time < self.animation_blend_time:
                        blend_ratio = self.animation_elapsed_time / self.animation_blend_time
                    self.animation_elapsed_time += dt

                # update animation buffers
                self.prev_animation_buffers[i][...] = self.animation_buffers[i]

                if self.last_animation_frame != self.animation_frame:
                    self.last_animation_frame = self.animation_frame
                    animation_buffer = animation.get_animation_transforms(self.animation_frame)

                    if blend_ratio < 1.0:
                        self.animation_buffers[i][...] = self.blend_animation_buffers[i] * (1.0 - blend_ratio) + animation_buffer * blend_ratio
                    else:
                        self.animation_buffers[i][...] = animation_buffer
        self.is_animation_end = animation_end class AxisGizmo(StaticActor):
    counter = count_generator(0)
    ID_NONE = 0
    ID_START = 1
    ID_POSITION_Z = ID_START + next(counter)
    ID_ROTATION_YAW = ID_START + next(counter)
    ID_POSITION_XZ = ID_START + next(counter)
    ID_ROTATION_PITCH = ID_START + next(counter)
    ID_POSITION_YZ = ID_START + next(counter)
    ID_ROTATION_ROLL = ID_START + next(counter)
    ID_POSITION_XY = ID_START + next(counter)
    ID_POSITION_Y = ID_START + next(counter)
    ID_POSITION_X = ID_START + next(counter)
    ID_SCALE_Z = ID_START + next(counter)
    ID_SCALE_X = ID_START + next(counter)
    ID_SCALE_Y = ID_START + next(counter)
    ID_COUNT = ID_START + next(counter)

    def __init__(self, name, **object_data):
        StaticActor.__init__(self, name, **object_data)

        axis_gizmo_geometry_count = self.get_geometry_count()
        assert (axis_gizmo_geometry_count == (AxisGizmo.ID_COUNT - AxisGizmo.ID_START))

        self.axis_gizmo_colors = [Float4(1.0, 1.0, 1.0, 1.0) for i in range(AxisGizmo.ID_COUNT)]
        self.axis_gizmo_colors[AxisGizmo.ID_POSITION_X][0:3] = Float3(1.0, 0.0, 0.0)
        self.axis_gizmo_colors[AxisGizmo.ID_POSITION_Y][0:3] = Float3(0.0, 1.0, 0.0)
        self.axis_gizmo_colors[AxisGizmo.ID_POSITION_Z][0:3] = Float3(0.0, 0.0, 1.0)
        self.axis_gizmo_colors[AxisGizmo.ID_POSITION_XY][0:3] = Float3(1.0, 1.0, 0.0)
        self.axis_gizmo_colors[AxisGizmo.ID_POSITION_XZ][0:3] = Float3(1.0, 0.0, 1.0)
        self.axis_gizmo_colors[AxisGizmo.ID_POSITION_YZ][0:3] = Float3(0.0, 1.0, 1.0)
        self.axis_gizmo_colors[AxisGizmo.ID_ROTATION_PITCH][0:3] = Float3(1.0, 0.0, 0.0)
        self.axis_gizmo_colors[AxisGizmo.ID_ROTATION_YAW][0:3] = Float3(0.0, 1.0, 0.0)
        self.axis_gizmo_colors[AxisGizmo.ID_ROTATION_ROLL][0:3] = Float3(0.0, 0.0, 1.0)
        self.axis_gizmo_colors[AxisGizmo.ID_SCALE_X][0:3] = Float3(1.0, 0.0, 0.0)
        self.axis_gizmo_colors[AxisGizmo.ID_SCALE_Y][0:3] = Float3(0.0, 1.0, 0.0)
        self.axis_gizmo_colors[AxisGizmo.ID_SCALE_Z][0:3] = Float3(0.0, 0.0, 1.0)

    def get_object_color(self, index):
        return self.axis_gizmo_colors[self.get_object_id(index)]

    def get_object_id(self, index):
        return AxisGizmo.ID_START + index class Camera(StaticActor):
    def __init__(self, name, scene_manager, **object_data):
        if 'pos' not in object_data:
            object_data['pos'] = [0, 1.0, 0]

        StaticActor.__init__(self, name, **object_data)

        self.scene_manager = scene_manager
        self.postprocess = self.scene_manager.renderer.postprocess

        self.meter_per_unit = object_data.get('meter_per_unit', 1.0)
        self.aspect = object_data.get('aspect', 0.0)
        self.fov = object_data.get('fov', 0.0)
        self.near = object_data.get('near', 0.0)
        self.far = object_data.get('far', 0.0)
        self.move_speed = object_data.get('move_speed', 0.0)
        self.pan_speed = object_data.get('pan_speed', 0.0)
        self.rotation_speed = object_data.get('rotation_speed', 0.005)

        self.half_cone = 0.0  # view frustum cone half radian
        self.frustum_vectors = np.zeros(12, dtype=np.float32).reshape(4, 3)

        self.projection = Matrix4()
        self.projection_jitter = Matrix4()
        self.inv_projection = Matrix4()
        self.inv_projection_jitter = Matrix4()
        self.projection_offset = Float2()

        self.view = Matrix4()
        self.inv_view = Matrix4()
        self.view_origin = Matrix4()
        self.inv_view_origin = Matrix4()
        self.view_projection = Matrix4()
        self.view_projection_jitter = Matrix4()
        self.view_origin_projection = Matrix4()
        self.view_origin_projection_jitter = Matrix4()
        self.inv_view_origin_projection = Matrix4()

        self.prev_view = Matrix4()
        self.inv_prev_view = Matrix4()
        self.prev_view_origin = Matrix4()
        self.inv_prev_view_origin = Matrix4()
        self.prev_view_projection = Matrix4()
        self.prev_view_projection_jitter = Matrix4()
        self.prev_view_origin_projection = Matrix4()
        self.prev_view_origin_projection_jitter = Matrix4()

    def initialize(self):
        config = CoreManager.instance().project_manager.config
        # get properties
        self.fov = config.Camera.fov
        self.near = config.Camera.near
        self.far = config.Camera.far
        self.move_speed = config.Camera.move_speed
        self.pan_speed = config.Camera.pan_speed
        self.rotation_speed = config.Camera.rotation_speed

    def get_save_data(self):
        save_data = StaticActor.get_save_data(self)
        save_data['meter_per_unit'] = 1.0
        save_data['aspect'] = 0.0
        save_data['fov'] = 0.0
        save_data['near'] = 0.0
        save_data['far'] = 0.0
        save_data['move_speed'] = 0.0
        save_data['pan_speed'] = 0.0
        save_data['rotation_speed'] = 0.0
        return save_data

    def write_to_config(self, config):
        config.setValue("Camera", "meter_per_unit", self.meter_per_unit)
        config.setValue("Camera", "aspect", self.aspect)
        config.setValue("Camera", "fov", self.fov)
        config.setValue("Camera", "near", self.near)
        config.setValue("Camera", "far", self.far)
        config.setValue("Camera", "move_speed", self.move_speed)
        config.setValue("Camera", "pan_speed", self.pan_speed)
        config.setValue("Camera", "rotation_speed", self.rotation_speed)

    def get_attribute(self):
        StaticActor.get_attribute(self)
        self.attributes.set_attribute('fov', self.fov)
        self.attributes.set_attribute('near', self.near)
        self.attributes.set_attribute('far', self.far)
        self.attributes.set_attribute('move_speed', self.move_speed)
        self.attributes.set_attribute('pan_speed', self.pan_speed)
        self.attributes.set_attribute('rotation_speed', self.rotation_speed)
        return self.attributes

    def set_attribute(self, attribute_name, attribute_value, item_info_history, attribute_index):
        StaticActor.set_attribute(self, attribute_name, attribute_value, item_info_history, attribute_index)
        if hasattr(self, attribute_name):
            setattr(self, attribute_name, attribute_value)
            if "fov" == attribute_name:
                self.update_projection(force_update=True)
            else:
                self.scene_manager.renderer.reset_renderer()

    def update_projection(self, fov=0.0, aspect=0.0, force_update=False):
        need_to_update = False
        if 0.0 < fov != self.fov:
            self.fov = fov
            need_to_update = True

        if 0.0 < aspect != self.aspect:
            self.aspect = aspect
            need_to_update = True

        if force_update or need_to_update:
            self.projection[...] = perspective(self.fov, self.aspect, self.near, self.far)
            self.projection_jitter[...] = self.projection
            self.projection_jitter[2][0] = -self.postprocess.jitter[0]
            self.projection_jitter[2][1] = -self.postprocess.jitter[1]

            self.inv_projection[...] = np.linalg.inv(self.projection)
            self.inv_projection_jitter[...] = np.linalg.inv(self.projection_jitter)

    def update(self, force_update=False):
        updated = self.transform.update_transform(update_inverse_matrix=True, force_update=force_update)

        if updated or force_update:
            self.prev_view = self.transform.prev_inverse_matrix
            self.inv_prev_view = self.transform.prev_matrix
            self.prev_view_origin[...] = self.view_origin
            self.inv_prev_view_origin[...] = self.inv_view_origin

            self.view = self.transform.inverse_matrix
            self.inv_view = self.transform.matrix
            self.view_origin[...] = self.view
            self.view_origin[3, 0:3] = [0.0, 0.0, 0.0]
            self.inv_view_origin[...] = np.transpose(self.view_origin)

        self.prev_view_projection[...] = self.view_projection
        self.prev_view_projection_jitter[...] = self.view_projection_jitter
        self.prev_view_origin_projection[...] = self.view_origin_projection
        self.prev_view_origin_projection_jitter[...] = self.view_origin_projection_jitter

        # Update projection jitter.
        # This part is very important because the w value of the projection matrix 3rd row is ​​-1.0.
        self.projection_jitter[2][0] = -self.postprocess.jitter[0]
        self.projection_jitter[2][1] = -self.postprocess.jitter[1]
        self.inv_projection_jitter[...] = np.linalg.inv(self.projection_jitter)

        self.view_projection[...] = np.dot(self.view, self.projection)
        self.view_projection_jitter[...] = np.dot(self.view, self.projection_jitter)
        self.view_origin_projection[...] = np.dot(self.view_origin, self.projection)
        self.view_origin_projection_jitter[...] = np.dot(self.view_origin, self.projection_jitter)

        self.inv_view_origin_projection[...] = np.dot(self.inv_projection, self.inv_view_origin)

        # update frustum planes
        if updated or force_update:
            frustum_vectors = self.frustum_vectors
            view_projection = self.view_origin_projection

            # Left
            frustum_vectors[0][0] = view_projection[0][3] + view_projection[0][0]
            frustum_vectors[0][1] = view_projection[1][3] + view_projection[1][0]
            frustum_vectors[0][2] = view_projection[2][3] + view_projection[2][0]

            # Right
            frustum_vectors[1][0] = view_projection[0][3] - view_projection[0][0]
            frustum_vectors[1][1] = view_projection[1][3] - view_projection[1][0]
            frustum_vectors[1][2] = view_projection[2][3] - view_projection[2][0]

            # Top
            frustum_vectors[2][0] = view_projection[0][3] - view_projection[0][1]
            frustum_vectors[2][1] = view_projection[1][3] - view_projection[1][1]
            frustum_vectors[2][2] = view_projection[2][3] - view_projection[2][1]

            # Bottom
            frustum_vectors[3][0] = view_projection[0][3] + view_projection[0][1]
            frustum_vectors[3][1] = view_projection[1][3] + view_projection[1][1]
            frustum_vectors[3][2] = view_projection[2][3] + view_projection[2][1]

            for i in range(4):
                frustum_vectors[i][...] = normalize(frustum_vectors[i])

            frustum_vectors[0][...] = -np.cross(self.transform.up, frustum_vectors[0])
            frustum_vectors[1][...] = np.cross(self.transform.up, frustum_vectors[1])
            frustum_vectors[2][...] = np.cross(-self.transform.left, frustum_vectors[2])
            frustum_vectors[3][...] = -np.cross(-self.transform.left, frustum_vectors[3]) class LightProbe(StaticActor):
    def __init__(self, name, **object_data):
        StaticActor.__init__(self, name, **object_data)

        self.isRendered = False

        if CoreManager.instance().is_basic_mode:
            self.texture_probe = None
        else:
            self.texture_probe = self.generate_texture_probe(self.name)

    def get_save_data(self):
        save_data = StaticActor.get_save_data(self)
        return save_data

    def delete(self):
        if self.texture_probe is not None:
            self.texture_probe.delete()
            self.texture_probe = None

    def replace_texture_probe(self, texture_probe):
        self.delete()
        self.texture_probe = texture_probe

    @staticmethod
    def generate_texture_probe(name):
        texture_datas = RenderTargets.LIGHT_PROBE_ATMOSPHERE.get_texture_info()
        return CreateTexture(name=name, **texture_datas) class MainLight(StaticActor):
    def __init__(self, name, **object_data):
        StaticActor.__init__(self, name, **object_data)
        self.light_color = Float4(*object_data.get('light_color', (1.0, 1.0, 1.0, 1.0)))

        self.transform.set_rotation(object_data.get('rot', [-1.0, 0, 0]))

        self.last_shadow_camera = None
        self.last_shadow_position = FLOAT3_ZERO.copy()

        self.shadow_samples = object_data.get('shadow_samples', SHADOW_SAMPLES)
        self.shadow_exp = object_data.get('shadow_exp', SHADOW_EXP)
        self.shadow_bias = object_data.get('shadow_bias', SHADOW_BIAS)
        self.shadow_width = object_data.get('shadow_width', SHADOW_DISTANCE)
        self.shadow_height = object_data.get('shadow_height', SHADOW_DISTANCE)
        self.shadow_depth = object_data.get('shadow_depth', SHADOW_DISTANCE)
        self.shadow_orthogonal = Matrix4()
        self.shadow_view_projection = Matrix4()
        self.changed = False

        self.update_shadow_orthogonal()

    def reset_changed(self):
        self.changed = False

    def update_shadow_orthogonal(self):
        ortho(self.shadow_orthogonal,
              -self.shadow_width, self.shadow_width,
              -self.shadow_height, self.shadow_height,
              -self.shadow_depth, self.shadow_depth)
        self.changed = True

    def get_attribute(self):
        super().get_attribute()
        self.attributes.set_attribute('light_color', self.light_color)
        self.attributes.set_attribute('shadow_width', self.shadow_width)
        self.attributes.set_attribute('shadow_height', self.shadow_height)
        self.attributes.set_attribute('shadow_depth', self.shadow_depth)
        self.attributes.set_attribute('shadow_exp', self.shadow_exp)
        self.attributes.set_attribute('shadow_bias', self.shadow_bias)
        self.attributes.set_attribute('shadow_samples', self.shadow_samples)
        return self.attributes

    def set_attribute(self, attribute_name, attribute_value, item_info_history, attribute_index):
        if 'light_color' == attribute_name:
            self.light_color[...] = attribute_value
            self.changed = True
        elif attribute_name in ('shadow_width', 'shadow_height', 'shadow_depth'):
            setattr(self, attribute_name, attribute_value)
            self.update_shadow_orthogonal()
        else:
            super().set_attribute(attribute_name, attribute_value, item_info_history, attribute_index)

    def get_save_data(self):
        save_data = StaticActor.get_save_data(self)
        save_data['light_color'] = self.light_color.tolist()
        save_data['shadow_width'] = self.shadow_width
        save_data['shadow_height'] = self.shadow_height
        save_data['shadow_depth'] = self.shadow_depth
        save_data['shadow_exp'] = self.shadow_exp
        save_data['shadow_bias'] = self.shadow_bias
        save_data['shadow_samples'] = self.shadow_samples
        return save_data

    def update(self, current_camera):
        changed = self.transform.update_transform(update_inverse_matrix=True)
        self.changed = self.changed or changed

        if current_camera is not None:
            camera_pos = current_camera.transform.get_pos()

            self.last_shadow_camera = current_camera
            self.last_shadow_position[...] = camera_pos
            set_translate_matrix(self.shadow_view_projection, *(-camera_pos))
            self.shadow_view_projection[...] = np.dot(np.dot(self.shadow_view_projection, self.transform.inverse_matrix), self.shadow_orthogonal) class PointLight(StaticActor):
    def __init__(self, name, **object_data):
        StaticActor.__init__(self, name, **object_data)
        self.light_color = Float3(*object_data.get('light_color', (1.0, 1.0, 1.0)))
        self.light_radius = object_data.get('light_radius', 10.0)

    def get_attribute(self):
        super().get_attribute()
        self.attributes.set_attribute('light_color', self.light_color)
        self.attributes.set_attribute('light_radius', self.light_radius)
        return self.attributes

    def set_attribute(self, attribute_name, attribute_value, item_info_history, attribute_index):
        super().set_attribute(attribute_name, attribute_value, item_info_history, attribute_index)
        if attribute_name == 'light_color':
            self.light_color[:] = attribute_value[:]
        elif hasattr(self, attribute_name):
            setattr(self, attribute_name, attribute_value)

    def get_save_data(self):
        save_data = StaticActor.get_save_data(self)
        save_data['light_color'] = self.light_color.tolist()
        save_data['light_radius'] = self.light_radius
        return save_data

    def update(self):
        self.transform.update_transform() def gather_render_infos(culling_func, camera, light, actor_list, solid_render_infos, translucent_render_infos):
    for actor in actor_list:
        for i in range(actor.get_geometry_count()):
            if not actor.visible:
                continue

            if culling_func(camera, light, actor, actor.get_geometry_bound_box(i)):
                continue

            material_instance = actor.get_material_instance(i)
            render_info = RenderInfo()
            render_info.actor = actor
            render_info.geometry = actor.get_geometry(i)
            render_info.geometry_data = actor.get_geometry_data(i)
            render_info.gl_call_list = actor.get_gl_call_list(i)
            render_info.material = material_instance.material if material_instance else None
            render_info.material_instance = material_instance
            if render_info.material_instance is not None and render_info.material_instance.is_translucent():
                if translucent_render_infos is not None:
                    translucent_render_infos.append(render_info)
            elif solid_render_infos is not None:
                solid_render_infos.append(render_info) def view_frustum_culling_geometry(camera, light, actor, geometry_bound_box):
    to_geometry = geometry_bound_box.bound_center - camera.transform.pos
    for i in range(4):
        d = np.dot(camera.frustum_vectors[i], to_geometry)
        if geometry_bound_box.radius < d:
            return True
    return False def always_pass(*args):
    return False def shadow_culling(camera, light, actor, geometry_bound_box):
    bound_min = np.dot(np.array([geometry_bound_box.bound_min[0], geometry_bound_box.bound_min[1], geometry_bound_box.bound_min[2], 1.0], dtype=np.float32), light.shadow_view_projection)[: 3]
    bound_max = np.dot(np.array([geometry_bound_box.bound_max[0], geometry_bound_box.bound_max[1], geometry_bound_box.bound_max[2], 1.0], dtype=np.float32), light.shadow_view_projection)[: 3]
    minimum = np.minimum(bound_min, bound_max)
    maximum = np.maximum(bound_min, bound_max)
    if any(x < -1.0 for x in maximum) or any(1.0 < x for x in minimum):
        return True
    return False class Terrain:
    def __init__(self, **object_data):
        self.renderer = CoreManager.instance().renderer
        self.scene_manager = CoreManager.instance().scene_manager
        self.resource_manager = CoreManager.instance().resource_manager

        self.name = object_data.get('name', 'terrain')
        self.is_render_terrain = object_data.get('is_render_terrain', True)

        self.transform = TransformObject()
        self.transform.set_pos(object_data.get('pos', [0, 0, 0]))
        self.transform.set_rotation(object_data.get('rot', [0, 0, 0]))
        self.transform.set_scale(object_data.get('scale', [1, 1, 1]))

        self.width = object_data.get('width', 10)
        self.height = object_data.get('height', 10)
        self.subdivide_level = object_data.get('subdivide_level', 100)
        self.height_map_size = np.array(object_data.get('height_map_size', [10.0, 10.0]), dtype=np.float32)

        self.instance_offset = None
        self.instance_buffer = None

        self.terrain_grid = None

        self.texture_height_map_name = object_data.get('texture_height_map', "common.noise")
        self.texture_height_map = None
        self.terrain_render = None
        self.terrain_shadow = None

        self.attributes = Attributes()

    def initialize(self):
        self.set_instance_offset(self.width, self.height)
        self.instance_buffer = InstanceBuffer(name="terrain_instance_buffer", location_offset=5, element_datas=[FLOAT4_ZERO, ])

        self.generate_terrain(self.subdivide_level)

        self.texture_height_map = self.resource_manager.get_texture(self.texture_height_map_name)
        self.terrain_render = self.resource_manager.get_material_instance('terrain.terrain_render_ps')
        self.terrain_shadow = self.resource_manager.get_material_instance('terrain.terrain_shadow')

    def get_save_data(self):
        save_data = dict(
            is_render_terrain=self.is_render_terrain,
            pos=self.transform.pos,
            rot=self.transform.rot,
            scale=self.transform.scale,
            height_map_size=self.height_map_size,
            width=self.width,
            height=self.height,
            subdivide_level=self.subdivide_level,
            texture_height_map=self.texture_height_map.name if self.texture_height_map is not None else self.texture_height_map_name,
        )
        return save_data

    def get_attribute(self):
        save_data = self.get_save_data()
        for key in save_data:
            self.attributes.set_attribute(key, save_data[key])
        return self.attributes

    def set_attribute(self, attribute_name, attribute_value, item_info_history, attribute_index):
        if attribute_name == 'pos':
            self.transform.set_pos(attribute_value)
        elif attribute_name == 'rot':
            self.transform.set_rotation(attribute_value)
        elif attribute_name == 'scale':
            self.transform.set_scale(attribute_value)
        elif attribute_name == 'texture_height_map':
            self.texture_height_map = self.resource_manager.get_texture(attribute_value)
        elif hasattr(self, attribute_name):
            setattr(self, attribute_name, attribute_value)
            if attribute_name in ('width', 'height'):
                self.set_instance_offset(self.width, self.height)
            elif attribute_name in 'subdivide_level':
                self.generate_terrain(self.subdivide_level)
        return self.attributes

    def generate_terrain(self, subdivide_level):
        self.terrain_grid = Plane("Terrain_Grid", mode=GL_QUADS, width=subdivide_level, height=subdivide_level, xz_plane=True)

    def set_instance_offset(self, width, height):
        count = width * height
        self.instance_offset = np.zeros(count, (np.float32, 4))
        i = 0
        for y in range(height):
            for x in range(width):
                self.instance_offset[i][0] = x
                self.instance_offset[i][1] = y
                i += 1

    def update(self, delta):
        self.transform.update_transform()

    def render_terrain(self, render_mode):
        if RenderMode.GBUFFER == render_mode:
            material_instance = self.terrain_render
        elif RenderMode.SHADOW == render_mode:
            material_instance = self.terrain_shadow
        else:
            raise BaseException("Unkown terrain render mode %s" % render_mode)
        material_instance.use_program()
        material_instance.bind_material_instance()
        material_instance.bind_uniform_data('height_map_size', self.height_map_size)
        material_instance.bind_uniform_data('model', self.transform.matrix)
        material_instance.bind_uniform_data('texture_height_map', self.texture_height_map)
        material_instance.bind_uniform_data('scale', self.transform.scale)
        material_instance.bind_uniform_data('subdivide_level', self.subdivide_level)
        self.terrain_grid.get_geometry().draw_elements_instanced(len(self.instance_offset), self.instance_buffer, [self.instance_offset, ]) class Atmosphere:
    def __init__(self, **object_data):
        self.name = object_data.get('name', 'atmosphere')
        self.attributes = Attributes()
        self.is_render_atmosphere = object_data.get('is_render_atmosphere', True)
        self.use_constant_solar_spectrum = False
        self.use_ozone = True
        self.use_combined_textures = True
        self.luminance_type = Luminance.NONE
        self.num_precomputed_wavelengths = 15 if Luminance.PRECOMPUTED == self.luminance_type else 3
        self.do_white_balance = False
        self.show_help = True
        self.view_distance_meters = 9000.0
        self.view_zenith_angle_radians = 1.47
        self.view_azimuth_angle_radians = -0.1
        self.sun_zenith_angle_radians = 1.3
        self.sun_azimuth_angle_radians = 2.9
        self.sun_direction = Float3()

        self.white_point = Float3()
        self.earth_center = Float3(0.0, -kBottomRadius / kLengthUnitInMeters, 0.0)
        self.sun_size = Float2(math.tan(kSunAngularRadius), math.cos(kSunAngularRadius))

        self.kSky = Float3(1.0, 1.0, 1.0)
        self.kSun = Float3(1.0, 1.0, 1.0)

        self.atmosphere_exposure = 0.0001

        # cloud constants
        self.cloud_exposure = 0.1
        self.cloud_altitude = 100.0
        self.cloud_height = 500.0
        self.cloud_speed = 0.01
        self.cloud_absorption = 0.15

        self.cloud_contrast = 2.0
        self.cloud_coverage = 0.8
        self.cloud_tiling = 0.0004

        self.inscatter_power = 0.25

        self.noise_contrast = 1.0
        self.noise_coverage = 1.0
        self.noise_tiling = 0.0003

        self.model = None
        self.atmosphere_material_instance = None

        self.transmittance_texture = None
        self.scattering_texture = None
        self.irradiance_texture = None
        self.optional_single_mie_scattering_texture = None

        self.cloud_texture = None
        self.noise_texture = None

        self.quad = None

        self.load_data(object_data)

    def get_attribute(self):
        save_data = self.get_save_data()
        attribute_names = list(save_data.keys())
        attribute_names.sort()

        for attribute_name in attribute_names:
            self.attributes.set_attribute(attribute_name, save_data[attribute_name])

        return self.attributes

    def set_attribute(self, attribute_name, attribute_value, item_info_history, attribute_index):
        if hasattr(self, attribute_name):
            setattr(self, attribute_name, attribute_value)

    def load_data(self, object_data):
        for key, value in object_data.items():
            if hasattr(self, key):
                setattr(self, key, value)

    def get_save_data(self):
        save_data = dict(
            is_render_atmosphere=self.is_render_atmosphere,
            atmosphere_exposure=self.atmosphere_exposure,
            cloud_exposure=self.cloud_exposure,
            cloud_altitude=self.cloud_altitude,
            cloud_height=self.cloud_height,
            cloud_tiling=self.cloud_tiling,
            cloud_speed=self.cloud_speed,
            cloud_contrast=self.cloud_contrast,
            cloud_coverage=self.cloud_coverage,
            cloud_absorption=self.cloud_absorption,
            inscatter_power=self.inscatter_power,
            noise_tiling=self.noise_tiling,
            noise_contrast=self.noise_contrast,
            noise_coverage=self.noise_coverage,
            sun_size=self.sun_size,
        )
        return save_data

    def initialize(self):
        resource_manager = CoreManager.instance().resource_manager

        self.quad = ScreenQuad.get_vertex_array_buffer()

        self.atmosphere_material_instance = resource_manager.get_material_instance(
            'precomputed_atmosphere.atmosphere',
            macros={
                'USE_LUMINANCE': 1 if self.luminance_type else 0,
                'COMBINED_SCATTERING_TEXTURES': 1 if self.use_combined_textures else 0
            }
        )

        # precompute constants
        max_sun_zenith_angle = 120.0 / 180.0 * kPi

        rayleigh_layer = DensityProfileLayer(0.0, 1.0, -1.0 / kRayleighScaleHeight, 0.0, 0.0)
        mie_layer = DensityProfileLayer(0.0, 1.0, -1.0 / kMieScaleHeight, 0.0, 0.0)

        ozone_density = [DensityProfileLayer(25000.0, 0.0, 0.0, 1.0 / 15000.0, -2.0 / 3.0),
                         DensityProfileLayer(0.0, 0.0, 0.0, -1.0 / 15000.0, 8.0 / 3.0)]

        wavelengths = []
        solar_irradiance = []
        rayleigh_scattering = []
        mie_scattering = []
        mie_extinction = []
        absorption_extinction = []
        ground_albedo = []

        for i in range(kLambdaMin, kLambdaMax + 1, 10):
            L = float(i) * 1e-3  # micro-meters
            mie = kMieAngstromBeta / kMieScaleHeight * math.pow(L, -kMieAngstromAlpha)
            wavelengths.append(i)
            if self.use_constant_solar_spectrum:
                solar_irradiance.append(kConstantSolarIrradiance)
            else:
                solar_irradiance.append(kSolarIrradiance[int((i - kLambdaMin) / 10)])
            rayleigh_scattering.append(kRayleigh * math.pow(L, -4))
            mie_scattering.append(mie * kMieSingleScatteringAlbedo)
            mie_extinction.append(mie)
            if self.use_ozone:
                absorption_extinction.append(kMaxOzoneNumberDensity * kOzoneCrossSection[int((i - kLambdaMin) / 10)])
            else:
                absorption_extinction.append(0.0)
            ground_albedo.append(kGroundAlbedo)

        rayleigh_density = [rayleigh_layer, ]
        mie_density = [mie_layer, ]

        if Luminance.PRECOMPUTED == self.luminance_type:
            self.kSky[...] = [MAX_LUMINOUS_EFFICACY, MAX_LUMINOUS_EFFICACY, MAX_LUMINOUS_EFFICACY]
        else:
            self.kSky[...] = ComputeSpectralRadianceToLuminanceFactors(wavelengths, solar_irradiance, -3)
        self.kSun[...] = ComputeSpectralRadianceToLuminanceFactors(wavelengths, solar_irradiance, 0)

        # generate precomputed textures
        if not resource_manager.texture_loader.hasResource('precomputed_atmosphere.transmittance') or \
                not resource_manager.texture_loader.hasResource('precomputed_atmosphere.scattering') or \
                not resource_manager.texture_loader.hasResource('precomputed_atmosphere.irradiance') or \
                not resource_manager.texture_loader.hasResource(
                    'precomputed_atmosphere.optional_single_mie_scattering') and not self.use_combined_textures:
            model = Model(wavelengths,
                          solar_irradiance,
                          kSunAngularRadius,
                          kBottomRadius,
                          kTopRadius,
                          rayleigh_density,
                          rayleigh_scattering,
                          mie_density,
                          mie_scattering,
                          mie_extinction,
                          kMiePhaseFunctionG,
                          ozone_density,
                          absorption_extinction,
                          ground_albedo,
                          max_sun_zenith_angle,
                          kLengthUnitInMeters,
                          self.num_precomputed_wavelengths,
                          Luminance.PRECOMPUTED == self.luminance_type,
                          self.use_combined_textures)
            model.generate()

        self.transmittance_texture = resource_manager.get_texture('precomputed_atmosphere.transmittance')
        self.scattering_texture = resource_manager.get_texture('precomputed_atmosphere.scattering')
        self.irradiance_texture = resource_manager.get_texture('precomputed_atmosphere.irradiance')

        if not self.use_combined_textures:
            self.optional_single_mie_scattering_texture = resource_manager.get_texture(
                'precomputed_atmosphere.optional_single_mie_scattering')

        self.cloud_texture = resource_manager.get_texture('precomputed_atmosphere.cloud_3d')
        self.noise_texture = resource_manager.get_texture('precomputed_atmosphere.noise_3d')

    def update(self, main_light):
        if not self.is_render_atmosphere:
            return

        self.sun_direction[...] = main_light.transform.front

    def bind_precomputed_atmosphere(self, material_instance):
        material_instance.bind_uniform_data("transmittance_texture", self.transmittance_texture)
        material_instance.bind_uniform_data("scattering_texture", self.scattering_texture)
        material_instance.bind_uniform_data("irradiance_texture", self.irradiance_texture)

        if not self.use_combined_textures:
            material_instance.bind_uniform_data(
                "single_mie_scattering_texture", self.optional_single_mie_scattering_texture)

        material_instance.bind_uniform_data("SKY_RADIANCE_TO_LUMINANCE", self.kSky * self.atmosphere_exposure)
        material_instance.bind_uniform_data("SUN_RADIANCE_TO_LUMINANCE", self.kSun * self.atmosphere_exposure)

        material_instance.bind_uniform_data("atmosphere_exposure", self.atmosphere_exposure)
        material_instance.bind_uniform_data("earth_center", self.earth_center)

    def bind_cloud(self, material_instance):
        material_instance.bind_uniform_data("texture_cloud", self.cloud_texture)
        material_instance.bind_uniform_data("texture_noise", self.noise_texture)

        material_instance.bind_uniform_data('cloud_exposure', self.cloud_exposure)
        material_instance.bind_uniform_data('cloud_altitude', self.cloud_altitude)
        material_instance.bind_uniform_data('cloud_height', self.cloud_height)
        material_instance.bind_uniform_data('cloud_speed', self.cloud_speed)
        material_instance.bind_uniform_data('cloud_absorption', self.cloud_absorption)

        material_instance.bind_uniform_data('cloud_tiling', self.cloud_tiling)
        material_instance.bind_uniform_data('cloud_contrast', self.cloud_contrast)
        material_instance.bind_uniform_data('cloud_coverage', self.cloud_coverage)

        material_instance.bind_uniform_data('noise_tiling', self.noise_tiling)
        material_instance.bind_uniform_data('noise_contrast', self.noise_contrast)
        material_instance.bind_uniform_data('noise_coverage', self.noise_coverage)

    def render_precomputed_atmosphere(self, texture_linear_depth, texture_shadow, render_light_probe_mode):
        if not self.is_render_atmosphere:
            return

        self.atmosphere_material_instance.use_program()
        self.atmosphere_material_instance.bind_material_instance()
        self.atmosphere_material_instance.bind_uniform_data("texture_linear_depth", texture_linear_depth)
        self.atmosphere_material_instance.bind_uniform_data("texture_shadow", texture_shadow)
        self.atmosphere_material_instance.bind_uniform_data("sun_size", self.sun_size)
        self.atmosphere_material_instance.bind_uniform_data("render_light_probe_mode", render_light_probe_mode)
        self.bind_precomputed_atmosphere(self.atmosphere_material_instance)
        self.bind_cloud(self.atmosphere_material_instance)
        self.quad.draw_elements() class Ocean:
    DEFAULT_FFT_SEED = 1234

    def __init__(self, **object_data):
        self.name = object_data.get('name', 'ocean')
        self.height = object_data.get('height', 0.0)
        self.wind = object_data.get('wind', WIND)
        self.omega = object_data.get('omega', OMEGA)
        self.amplitude = object_data.get('amplitude', AMPLITUDE)

        self.simulation_wind = object_data.get('simulation_wind', 1.0)
        self.simulation_amplitude = object_data.get('simulation_amplitude', 3.0)
        self.simulation_scale = object_data.get('simulation_scale', 1.0)

        self.is_render_ocean = object_data.get('is_render_ocean', True)
        self.attributes = Attributes()

        self.acc_time = 0.0
        self.fft_seed = Ocean.DEFAULT_FFT_SEED
        self.simulation_size = GRID_SIZES * self.simulation_scale

        self.renderer = CoreManager.instance().renderer
        self.scene_manager = CoreManager.instance().scene_manager
        self.resource_manager = CoreManager.instance().resource_manager

        self.fft_init = None
        self.fft_x = None
        self.fft_y = None
        self.fft_render = None
        self.fft_variance = None

        self.texture_spectrum_1_2 = None
        self.texture_spectrum_3_4 = None
        self.texture_slope_variance = None
        self.texture_butterfly = None

        self.quad = None
        self.fft_grid = None

        self.caustic_index = 0
        self.texture_caustics = []

        self.texture_foam = None
        self.texture_noise = None

    def initialize(self):
        self.fft_seed = Ocean.DEFAULT_FFT_SEED
        self.fft_init = self.resource_manager.get_material_instance('fft_ocean.init')
        self.fft_x = self.resource_manager.get_material_instance('fft_ocean.fft_x')
        self.fft_y = self.resource_manager.get_material_instance('fft_ocean.fft_y')
        self.fft_render = self.resource_manager.get_material_instance('fft_ocean.render')
        self.fft_variance = self.resource_manager.get_material_instance('fft_ocean.fft_variance')

        self.texture_spectrum_1_2 = self.resource_manager.get_texture("fft_ocean.spectrum_1_2", default_texture=False)
        self.texture_spectrum_3_4 = self.resource_manager.get_texture("fft_ocean.spectrum_3_4", default_texture=False)
        self.texture_slope_variance = self.resource_manager.get_texture("fft_ocean.slope_variance", default_texture=False)
        self.texture_butterfly = self.resource_manager.get_texture("fft_ocean.butterfly", default_texture=False)

        self.quad = ScreenQuad.get_vertex_array_buffer()
        self.fft_grid = Plane("FFT_Grid", mode=GL_QUADS, width=GRID_VERTEX_COUNT, height=GRID_VERTEX_COUNT, xz_plane=False)

        if None in (self.texture_spectrum_1_2, self.texture_spectrum_3_4, self.texture_slope_variance, self.texture_butterfly):
            self.generate_texture()

        self.caustic_index = 0
        self.texture_caustics = []
        i = 0
        while True:
            resource_name = "common.water_caustic_%02d" % i
            if self.resource_manager.texture_loader.hasResource(resource_name):
                self.texture_caustics.append(self.resource_manager.get_texture(resource_name))
                i += 1
                continue
            break

        self.texture_foam = self.resource_manager.get_texture("common.water_foam")
        self.texture_noise = self.resource_manager.get_texture("common.noise")

    def get_attribute(self):
        self.attributes.set_attribute('is_render_ocean', self.is_render_ocean)
        self.attributes.set_attribute('height', self.height)
        self.attributes.set_attribute('wind', self.wind)
        self.attributes.set_attribute('omega', self.omega)
        self.attributes.set_attribute('amplitude', self.amplitude)
        self.attributes.set_attribute('simulation_wind', self.simulation_wind)
        self.attributes.set_attribute('simulation_amplitude', self.simulation_amplitude)
        self.attributes.set_attribute('simulation_scale', self.simulation_scale)
        return self.attributes

    def set_attribute(self, attribute_name, attribute_value, item_info_history, attribute_index):
        if hasattr(self, attribute_name):
            setattr(self, attribute_name, attribute_value)
            # recreate resources
            if attribute_name in ('amplitude', 'wind', 'omega'):
                self.generate_texture()
            elif attribute_name == 'simulation_scale':
                self.simulation_size = GRID_SIZES * self.simulation_scale
        return self.attributes

    def get_save_data(self):
        save_data = dict(
            is_render_ocean=self.is_render_ocean,
            texture_type=self.__class__.__name__,
            height=self.height,
            wind=self.wind,
            omega=self.omega,
            amplitude=self.amplitude,
        )
        return save_data

    def getSlopeVariance(self, kx, ky, spectrumSample0, spectrumSample1):
        kSquare = kx * kx + ky * ky
        real = spectrumSample0
        img = spectrumSample1
        hSquare = real * real + img * img
        return kSquare * hSquare * 2.0

    def spectrum(self, kx, ky, omnispectrum=False):
        U10 = max(0.001, self.wind)
        Omega = self.omega
        Amp = self.amplitude

        k = sqrt(kx * kx + ky * ky)
        c = omega(k) / k

        # spectral peak
        kp = 9.81 * sqr(Omega / U10)
        cp = omega(kp) / kp

        # friction velocity
        z0 = 3.7e-5 * sqr(U10) / 9.81 * pow(U10 / cp, 0.9)
        u_star = 0.41 * U10 / log(10.0 / z0)

        Lpm = exp(- 5.0 / 4.0 * sqr(kp / k))
        gamma = 1.7 if Omega < 1.0 else 1.7 + 6.0 * log(Omega)
        sigma = 0.08 * (1.0 + 4.0 / pow(Omega, 3.0))
        Gamma = exp(-1.0 / (2.0 * sqr(sigma)) * sqr(sqrt(k / kp) - 1.0))
        Jp = pow(gamma, Gamma)
        Fp = Lpm * Jp * exp(- Omega / sqrt(10.0) * (sqrt(k / kp) - 1.0))
        alphap = 0.006 * sqrt(Omega)
        Bl = 0.5 * alphap * cp / c * Fp

        alpham = 0.01
        if u_star < cm:
            alpham *= (1.0 + log(u_star / cm))
        else:
            alpham *= (1.0 + 3.0 * log(u_star / cm))
        Fm = exp(-0.25 * sqr(k / km - 1.0))
        Bh = 0.5 * alpham * cm / c * Fm * Lpm

        if omnispectrum:
            return Amp * (Bl + Bh) / (k * sqr(k))

        a0 = log(2.0) / 4.0
        ap = 4.0
        am = 0.13 * u_star / cm
        Delta = tanh(a0 + ap * pow(c / cp, 2.5) + am * pow(cm / c, 2.5))
        phi = atan2(ky, kx)

        if kx < 0.0:
            return 0.0
        else:
            Bl *= 2.0
            Bh *= 2.0
        return Amp * (Bl + Bh) * (1.0 + Delta * cos(2.0 * phi)) / (2.0 * pi * sqr(sqr(k)))

    def getSpectrumSample(self, i, j, lengthScale, kMin):
        dk = 2.0 * pi / lengthScale
        kx = i * dk
        ky = j * dk
        if abs(kx) < kMin and abs(ky) < kMin:
            return 0.0, 0.0
        else:
            S = self.spectrum(kx, ky)
            h = sqrt(S / 2.0) * dk
            self.fft_seed = (self.fft_seed * 1103515245 + 12345) & 0x7FFFFFFF
            phi = frandom(self.fft_seed) * 2.0 * pi
            return h * cos(phi), h * sin(phi)

    def computeButterflyLookupTexture(self, butterfly_data):
        for i in range(PASSES):
            nBlocks = int(pow(2.0, float(PASSES - 1 - i)))
            nHInputs = int(pow(2.0, float(i)))
            for j in range(nBlocks):
                for k in range(nHInputs):
                    i1, i2, j1, j2 = 0, 0, 0, 0
                    if i == 0:
                        i1 = j * nHInputs * 2 + k
                        i2 = j * nHInputs * 2 + nHInputs + k
                        j1 = bitReverse(i1, FFT_SIZE)
                        j2 = bitReverse(i2, FFT_SIZE)
                    else:
                        i1 = j * nHInputs * 2 + k
                        i2 = j * nHInputs * 2 + nHInputs + k
                        j1 = i1
                        j2 = i2

                    wr, wi = computeWeight(FFT_SIZE, k * nBlocks)

                    offset1 = 4 * (i1 + i * FFT_SIZE)
                    butterfly_data[offset1 + 0] = (j1 + 0.5) / FFT_SIZE
                    butterfly_data[offset1 + 1] = (j2 + 0.5) / FFT_SIZE
                    butterfly_data[offset1 + 2] = wr
                    butterfly_data[offset1 + 3] = wi

                    offset2 = 4 * (i2 + i * FFT_SIZE)
                    butterfly_data[offset2 + 0] = (j1 + 0.5) / FFT_SIZE
                    butterfly_data[offset2 + 1] = (j2 + 0.5) / FFT_SIZE
                    butterfly_data[offset2 + 2] = -wr
                    butterfly_data[offset2 + 3] = -wi

    def generateWavesSpectrum(self, spectrum12_data, spectrum34_data):
        for y in range(FFT_SIZE):
            for x in range(FFT_SIZE):
                offset = 4 * (x + y * FFT_SIZE)
                i = (x - FFT_SIZE) if (x >= FFT_SIZE / 2) else x
                j = (y - FFT_SIZE) if (y >= FFT_SIZE / 2) else y
                s12_0, s12_1 = self.getSpectrumSample(i, j, GRID1_SIZE, pi / GRID1_SIZE)
                s12_2, s12_3 = self.getSpectrumSample(i, j, GRID2_SIZE, pi * FFT_SIZE / GRID1_SIZE)
                s34_0, s34_1 = self.getSpectrumSample(i, j, GRID3_SIZE, pi * FFT_SIZE / GRID2_SIZE)
                s34_2, s34_3 = self.getSpectrumSample(i, j, GRID4_SIZE, pi * FFT_SIZE / GRID3_SIZE)
                spectrum12_data[offset: offset+4] = s12_0, s12_1, s12_2, s12_3
                spectrum34_data[offset: offset+4] = s34_0, s34_1, s34_2, s34_3

    def computeSlopeVarianceTex(self, spectrum12_data, spectrum34_data):
        theoreticSlopeVariance = 0.0
        k = 5e-3
        while k < 1e3:
            nextK = k * 1.001
            theoreticSlopeVariance += k * k * self.spectrum(k, 0, True) * (nextK - k)
            k = nextK

        totalSlopeVariance = 0.0
        for y in range(FFT_SIZE):
            for x in range(FFT_SIZE):
                offset = 4 * (x + y * FFT_SIZE)
                i = 2.0 * pi * ((x - FFT_SIZE) if (x >= FFT_SIZE / 2) else x)
                j = 2.0 * pi * ((y - FFT_SIZE) if (y >= FFT_SIZE / 2) else y)
                s12_0, s12_1, s12_2, s12_3 = spectrum12_data[offset: offset + 4]
                s34_0, s34_1, s34_2, s34_3 = spectrum34_data[offset: offset + 4]
                totalSlopeVariance += self.getSlopeVariance(i/GRID1_SIZE, j/GRID1_SIZE, s12_0, s12_1)
                totalSlopeVariance += self.getSlopeVariance(i/GRID2_SIZE, j/GRID2_SIZE, s12_2, s12_3)
                totalSlopeVariance += self.getSlopeVariance(i/GRID3_SIZE, j/GRID3_SIZE, s34_0, s34_1)
                totalSlopeVariance += self.getSlopeVariance(i/GRID4_SIZE, j/GRID4_SIZE, s34_2, s34_3)

        self.fft_variance.use_program()
        self.fft_variance.bind_uniform_data("GRID_SIZES", GRID_SIZES)
        self.fft_variance.bind_uniform_data("slopeVarianceDelta", (theoreticSlopeVariance - totalSlopeVariance) * 0.5)
        self.fft_variance.bind_uniform_data("N_SLOPE_VARIANCE", N_SLOPE_VARIANCE)
        self.fft_variance.bind_uniform_data("spectrum_1_2_Sampler", self.texture_spectrum_1_2)
        self.fft_variance.bind_uniform_data("spectrum_3_4_Sampler", self.texture_spectrum_3_4)
        self.fft_variance.bind_uniform_data("FFT_SIZE", FFT_SIZE)

        for layer in range(N_SLOPE_VARIANCE):
            self.renderer.framebuffer_manager.bind_framebuffer(self.texture_slope_variance, target_layer=layer)
            self.fft_variance.bind_uniform_data("c", layer)
            self.quad.draw_elements()

    def save_texture(self, texture):
        resource = self.resource_manager.texture_loader.get_resource(texture.name)
        if resource is None:
            resource = self.resource_manager.texture_loader.create_resource(texture.name, texture)
            self.resource_manager.texture_loader.save_resource(resource.name)
        else:
            old_texture = resource.get_data()
            old_texture.delete()
            resource.set_data(texture)

    def generate_texture(self):
        glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)
        glDepthFunc(GL_LEQUAL)
        glEnable(GL_CULL_FACE)
        glFrontFace(GL_CCW)
        glEnable(GL_DEPTH_TEST)
        glDepthMask(True)
        glDisable(GL_BLEND)
        glClearColor(0.0, 0.0, 0.0, 1.0)
        glClearDepth(1.0)

        spectrum12_data = np.zeros(FFT_SIZE * FFT_SIZE * 4, dtype=np.float32)
        spectrum34_data = np.zeros(FFT_SIZE * FFT_SIZE * 4, dtype=np.float32)
        butterfly_data = np.zeros(FFT_SIZE * PASSES * 4, dtype=np.float32)

        self.generateWavesSpectrum(spectrum12_data, spectrum34_data)
        self.computeButterflyLookupTexture(butterfly_data)

        # create render targets
        self.texture_spectrum_1_2 = CreateTexture(
            name='fft_ocean.spectrum_1_2',
            texture_type=Texture2D,
            image_mode='RGBA',
            width=FFT_SIZE,
            height=FFT_SIZE,
            internal_format=GL_RGBA16F,
            texture_format=GL_RGBA,
            min_filter=GL_NEAREST,
            mag_filter=GL_NEAREST,
            data_type=GL_FLOAT,
            wrap=GL_REPEAT,
            data=spectrum12_data,
        )

        self.texture_spectrum_3_4 = CreateTexture(
            name='fft_ocean.spectrum_3_4',
            texture_type=Texture2D,
            image_mode='RGBA',
            width=FFT_SIZE,
            height=FFT_SIZE,
            internal_format=GL_RGBA16F,
            texture_format=GL_RGBA,
            min_filter=GL_NEAREST,
            mag_filter=GL_NEAREST,
            data_type=GL_FLOAT,
            wrap=GL_REPEAT,
            data=spectrum34_data,
        )

        self.texture_slope_variance = CreateTexture(
            name='fft_ocean.slope_variance',
            texture_type=Texture3D,
            image_mode='RGBA',
            width=N_SLOPE_VARIANCE,
            height=N_SLOPE_VARIANCE,
            depth=N_SLOPE_VARIANCE,
            internal_format=GL_RGBA16F,
            texture_format=GL_RGBA,
            min_filter=GL_LINEAR,
            mag_filter=GL_LINEAR,
            wrap=GL_CLAMP_TO_EDGE,
            data_type=GL_FLOAT,
        )

        self.texture_butterfly = CreateTexture(
            name='fft_ocean.butterfly',
            texture_type=Texture2D,
            image_mode='RGBA',
            width=FFT_SIZE,
            height=PASSES,
            internal_format=GL_RGBA16F,
            texture_format=GL_RGBA,
            min_filter=GL_NEAREST,
            mag_filter=GL_NEAREST,
            wrap=GL_CLAMP_TO_EDGE,
            data_type=GL_FLOAT,
            data=butterfly_data,
        )

        self.computeSlopeVarianceTex(spectrum12_data, spectrum34_data)

        self.save_texture(self.texture_spectrum_1_2)
        self.save_texture(self.texture_spectrum_3_4)
        self.save_texture(self.texture_slope_variance)
        self.save_texture(self.texture_butterfly)

    def update(self, delta):
        self.acc_time += delta
        self.caustic_index = int((self.acc_time * 20.0) % len(self.texture_caustics))

    def simulateFFTWaves(self):
        framebuffer_manager = CoreManager.instance().renderer.framebuffer_manager
        RenderTargets = RenderTarget.RenderTargets

        fft_a_framebuffer = framebuffer_manager.get_framebuffer(RenderTargets.FFT_A,
                                                                RenderTargets.FFT_A,
                                                                RenderTargets.FFT_A,
                                                                RenderTargets.FFT_A,
                                                                RenderTargets.FFT_A)

        fft_b_framebuffer = framebuffer_manager.get_framebuffer(RenderTargets.FFT_B,
                                                                RenderTargets.FFT_B,
                                                                RenderTargets.FFT_B,
                                                                RenderTargets.FFT_B,
                                                                RenderTargets.FFT_B)

        # initialize
        fft_a_framebuffer.bind_framebuffer()
        glClear(GL_COLOR_BUFFER_BIT)

        self.fft_init.use_program()
        self.fft_init.bind_uniform_data("FFT_SIZE", FFT_SIZE)
        self.fft_init.bind_uniform_data("INVERSE_GRID_SIZES", INVERSE_GRID_SIZES)
        self.fft_init.bind_uniform_data("spectrum_1_2_Sampler", self.texture_spectrum_1_2)
        self.fft_init.bind_uniform_data("spectrum_3_4_Sampler", self.texture_spectrum_3_4)
        self.fft_init.bind_uniform_data("t", self.acc_time * self.simulation_wind)

        self.quad.draw_elements()

        # # fft passes
        self.fft_x.use_program()
        self.fft_x.bind_uniform_data("butterflySampler", self.texture_butterfly)
        for i in range(PASSES):
            self.fft_x.bind_uniform_data("pass", float(i + 0.5) / PASSES)
            if i % 2 == 0:
                self.fft_x.bind_uniform_data("imgSampler", RenderTargets.FFT_A)
                fft_b_framebuffer.bind_framebuffer()
            else:
                self.fft_x.bind_uniform_data("imgSampler", RenderTargets.FFT_B)
                fft_a_framebuffer.bind_framebuffer()
            self.quad.draw_elements()

        self.fft_y.use_program()
        self.fft_y.bind_uniform_data("butterflySampler", self.texture_butterfly)
        for i in range(PASSES, PASSES * 2, 1):
            self.fft_y.bind_uniform_data("pass", float(i - PASSES + 0.5) / PASSES)
            if i % 2 == 0:
                self.fft_y.bind_uniform_data("imgSampler", RenderTargets.FFT_A)
                fft_b_framebuffer.bind_framebuffer()
            else:
                self.fft_y.bind_uniform_data("imgSampler", RenderTargets.FFT_B)
                fft_a_framebuffer.bind_framebuffer()
            self.quad.draw_elements()

        RenderTargets.FFT_A.generate_mipmap()

    def render_ocean(self, atmosphere, texture_scene, texture_linear_depth, texture_probe, texture_shadow):
        self.fft_render.use_program()
        self.fft_render.bind_material_instance()
        self.fft_render.bind_uniform_data("height", self.height)
        self.fft_render.bind_uniform_data("simulation_wind", self.simulation_wind)
        self.fft_render.bind_uniform_data("simulation_amplitude", self.simulation_amplitude)
        self.fft_render.bind_uniform_data("simulation_size", self.simulation_size)
        self.fft_render.bind_uniform_data("cell_size", GRID_CELL_SIZE)
        self.fft_render.bind_uniform_data("t", self.acc_time * self.simulation_wind)

        self.fft_render.bind_uniform_data("fftWavesSampler", RenderTarget.RenderTargets.FFT_A)
        self.fft_render.bind_uniform_data("slopeVarianceSampler", self.texture_slope_variance)

        self.fft_render.bind_uniform_data('texture_scene', texture_scene)
        self.fft_render.bind_uniform_data('texture_linear_depth', texture_linear_depth)
        self.fft_render.bind_uniform_data('texture_probe', texture_probe)
        self.fft_render.bind_uniform_data('texture_shadow', texture_shadow)

        self.fft_render.bind_uniform_data('texture_noise', self.texture_noise)
        self.fft_render.bind_uniform_data('texture_caustic', self.texture_caustics[self.caustic_index])
        self.fft_render.bind_uniform_data('texture_foam', self.texture_foam)

        # Bind Atmosphere
        atmosphere.bind_precomputed_atmosphere(self.fft_render)

        self.fft_grid.get_geometry().draw_elements() class Effect:
    def __init__(self, **effect_data):
        self.name = effect_data.get('name', 'effect')
        self.effect_info = effect_data.get('effect_info')
        self.object_id = effect_data.get('object_id', 0)

        self.transform = TransformObject()
        self.transform.set_pos(effect_data.get('pos', (0.0, 0.0, 0.0)))
        self.transform.set_rotation(effect_data.get('rot', (0.0, 0.0, 0.0)))
        self.transform.set_scale(effect_data.get('scale', (1.0, 1.0, 1.0)))

        self.alive = False
        self.emitters = []
        self.attributes = Attributes()

    def get_save_data(self):
        save_data = dict(
            name=self.name,
            pos=self.transform.pos.tolist(),
            rot=self.transform.rot.tolist(),
            scale=self.transform.scale.tolist(),
            effect_info=self.effect_info.name if self.effect_info is not None else ''
        )
        return save_data

    def get_attribute(self):
        self.attributes.set_attribute('name', self.name)
        self.attributes.set_attribute('pos', self.transform.pos)
        self.attributes.set_attribute('rot', self.transform.rot)
        self.attributes.set_attribute('scale', self.transform.scale)
        self.attributes.set_attribute('effect_info', self.effect_info.name if self.effect_info is not None else '')
        return self.attributes

    def set_attribute(self, attribute_name, attribute_value, item_info_history, attribute_index):
        if attribute_name == 'pos':
            self.transform.set_pos(attribute_value)
        elif attribute_name == 'rot':
            self.transform.set_rotation(attribute_value)
        elif attribute_name == 'scale':
            self.transform.set_scale(attribute_value)

    def clear_effect(self):
        for emitter in self.emitters:
            emitter.destroy()

        self.emitters = []

    def play(self):
        if self.effect_info is None:
            return

        self.clear_effect()

        self.alive = True

        for particle_info in self.effect_info.particle_infos:
            emitter = Emitter(self, particle_info)
            self.emitters.append(emitter)

        for emitter in self.emitters:
            emitter.play()

    def destroy(self):
        self.alive = False
        self.clear_effect()

    def update(self, dt):
        if not self.alive:
            return

        self.transform.update_transform(update_inverse_matrix=True)

        is_alive = False
        alive_particle_count = 0

        for emitter in self.emitters:
            alive_particle_count += emitter.update(dt)
            is_alive = is_alive or emitter.alive

        if not is_alive:
            self.destroy()

        return alive_particle_count class Spline3D:
    def __init__(self, **spline_data):
        self.name = spline_data.get('name', 'spline')
        self.object_id = spline_data.get('object_id', 0)
        self.transform = TransformObject()
        self.transform.set_pos(spline_data.get('pos', [0, 0, 0]))
        self.transform.set_rotation(spline_data.get('rot', [0, 0, 0]))
        self.transform.set_scale(spline_data.get('scale', [1, 1, 1]))
        self.spline_data = spline_data.get('spline_data')
        self.depth_test = spline_data.get('depth_test', True)
        self.color = Float4(*spline_data.get('color', [1.0, 1.0, 1.0, 1.0]))
        self.width = spline_data.get('width', 1.0)
        self.selected = False
        self.attributes = Attributes()

    def get_object_id(self):
        return self.object_id

    def set_object_id(self, object_id):
        self.object_id = object_id

    def is_selected(self):
        return self.selected

    def set_selected(self, selected):
        self.selected = selected

    def get_attribute(self):
        save_data = self.get_save_data()
        attribute_names = list(save_data.keys())
        attribute_names.sort()

        for attribute_name in attribute_names:
            self.attributes.set_attribute(attribute_name, save_data[attribute_name])
        return self.attributes

    def set_attribute(self, attribute_name, attribute_value, item_info_history, attribute_index):
        if 'spline_data' == attribute_name:
            spline_data = CoreManager.instance().resource_manager.get_spline(attribute_value)
            if spline_data is not None:
                self.spline_data = spline_data
        elif 'color' == attribute_name:
            self.color = Float4(*attribute_value)
        elif attribute_name == 'pos':
            self.transform.set_pos(attribute_value)
        elif attribute_name == 'rot':
            self.transform.set_rotation(attribute_value)
        elif attribute_name == 'scale':
            self.transform.set_scale(attribute_value)
        elif hasattr(self, attribute_name):
            setattr(self, attribute_name, attribute_value)

    def get_save_data(self):
        save_data = dict(
            name=self.name,
            spline_data=self.spline_data.name if self.spline_data is not None else '',
            pos=self.transform.pos.tolist(),
            rot=self.transform.rot.tolist(),
            scale=self.transform.scale.tolist(),
            color=self.color.tolist(),
            width=self.width,
            depth_test=self.depth_test
        )
        return save_data

    def get_resampling_position(self, ratio):
        pos = self.spline_data.get_resampling_position(ratio)
        return np.dot(Float4(*pos, 1.0), self.transform.matrix)[:3]

    def update(self, dt):
        self.transform.update_transform(update_inverse_matrix=True) class RenderOption:
    RENDER_LIGHT_PROBE = False
    RENDER_ONLY_ATMOSPHERE = False
    RENDER_FONT = True
    RENDER_STATIC_ACTOR = True
    RENDER_SKELETON_ACTOR = True
    RENDER_ATMOSPHERE = True
    RENDER_OCEAN = True
    RENDER_EFFECT = True
    RENDER_COLLISION = True
    RENDER_DEBUG_LINE = True
    RENDER_GIZMO = True
    RENDER_OBJECT_ID = True class RenderTargets:
    SCREENBUFFER = None
    BACKBUFFER = None
    DEPTH = None
    DEPTH_STENCIL = None
    OBJECT_ID = None
    OBJECT_ID_DEPTH = None
    HDR = None
    HDR_TEMP = None
    HDR_BACKUP = None
    BLOOM_0 = None
    BLOOM_1 = None
    BLOOM_2 = None
    BLOOM_3 = None
    BLOOM_4 = None
    LIGHT_SHAFT = None
    LIGHT_PROBE_ATMOSPHERE = None
    ATMOSPHERE = None
    ATMOSPHERE_INSCATTER = None
    TAA_RESOLVE = None
    DIFFUSE = None
    MATERIAL = None
    WORLD_NORMAL = None
    STATIC_SHADOWMAP = None
    DYNAMIC_SHADOWMAP = None
    COMPOSITE_SHADOWMAP = None
    LINEAR_DEPTH = None
    FOCUS_DISTANCE = None
    SCREEN_SPACE_REFLECTION = None
    SCREEN_SPACE_REFLECTION_RESOLVED_PREV = None
    SCREEN_SPACE_REFLECTION_RESOLVED = None
    SSAO = None
    VELOCITY = None
    FFT_A = None
    FFT_B = None
    TEMP_RENDER_BUFFER_MULTISAMPLE = None
    TEMP_RGBA8 = None
    TEMP_2D_ARRAY = None
    TEMP_MULTISAMPLE_X4 = None
    TEMP_HEIGHT_MAP = None
import copy
import os
import glob
import math
import numpy as np
from collections import OrderedDict
from PyEngine3D.Common import logger
from PyEngine3D.Common.Constants import *
from PyEngine3D.Render import CollisionActor, StaticActor, SkeletonActor, AxisGizmo
from PyEngine3D.Render import Camera, MainLight, PointLight, LightProbe
from PyEngine3D.Render import gather_render_infos, always_pass, view_frustum_culling_geometry, shadow_culling
from PyEngine3D.Render import Atmosphere, Ocean, Terrain
from PyEngine3D.Render import Effect
from PyEngine3D.Render import Spline3D
from PyEngine3D.Render.RenderOptions import RenderOption
from PyEngine3D.Render.RenderTarget import RenderTargets
from PyEngine3D.Utilities import *
        for object_data in scene_data.get('static_actors', []):
            self.add_object(**object_data)

        for object_data in scene_data.get('skeleton_actors', []):
            self.add_object(**object_data)

        for effect_data in scene_data.get('effects', []):
            self.add_effect(**effect_data)

        self.end_open_scene()

    def save_scene(self):
        if self.__current_scene_name == "":
            self.set_current_scene_name(self.resource_manager.scene_loader.get_new_resource_name("new_scene"))
        self.resource_manager.scene_loader.save_resource(self.__current_scene_name)

    def get_save_data(self):
        scene_data = dict(
            cameras=[camera.get_save_data() for camera in self.cameras],
            main_light=self.main_light.get_save_data() if self.main_light is not None else dict(),
            lights=[light.get_save_data() for light in self.point_lights],
            light_probes=[light_probe.get_save_data() for light_probe in self.light_probes],
            splines=[spline.get_save_data() for spline in self.splines],
            atmosphere=self.atmosphere.get_save_data(),
            ocean=self.ocean.get_save_data(),
            terrain=self.terrain.get_save_data(),
            collision_actors=[collision_actor.get_save_data() for collision_actor in self.collision_actors],
            static_actors=[static_actor.get_save_data() for static_actor in self.static_actors],
            skeleton_actors=[skeleton_actor.get_save_data() for skeleton_actor in self.skeleton_actors],
            effects=self.effect_manager.get_save_data()
        )
        return scene_data

    def generate_object_name(self, currName):
        index = 0
        if currName in self.objectMap:
            while True:
                newName = "%s_%d" % (currName, index)
                if newName not in self.objectMap:
                    return newName
                index += 1
        return currName

    def get_object_list(self, object_type):
        if Camera == object_type:
            return self.cameras
        elif PointLight == object_type:
            return self.point_lights
        elif LightProbe == object_type:
            return self.light_probes
        elif CollisionActor == object_type:
            return self.collision_actors
        elif StaticActor == object_type:
            return self.static_actors
        elif SkeletonActor == object_type:
            return self.skeleton_actors
        elif Spline3D == object_type:
            return self.splines
        return None

    def generate_object_id(self):
        object_id = self.objectIDEntry[self.objectIDCounter]
        self.objectIDCounter += 1
        return object_id

    def restore_object_id(self, object_id):
        if 0 < self.objectIDCounter:
            self.objectIDCounter -= 1
            self.objectIDEntry[self.objectIDCounter] = object_id

    def regist_object(self, obj):
        if obj is not None and obj.name not in self.objectMap:
            object_type = type(obj)
            object_list = self.get_object_list(object_type)
            if object_list is not None:
                object_list.append(obj)
            elif object_type is Effect:
                self.effect_manager.add_effect(obj)
            if hasattr(obj, 'set_object_id'):
                object_id = self.generate_object_id()
                obj.set_object_id(object_id)
                self.objectIDMap[object_id] = obj
            self.objectMap[obj.name] = obj
            self.core_manager.send_object_info(obj)
        else:
            logger.error("SceneManager::regist_object error. %s" % obj.name if obj else 'None')

    def unregist_resource(self, obj):
        if obj is not None and obj.name in self.objectMap:
            object_type = type(obj)
            object_list = self.get_object_list(object_type)
            if object_list is not None:
                object_list.remove(obj)
            elif object_type is Effect:
                self.effect_manager.delete_effect(obj)

            self.objectMap.pop(obj.name)

            if hasattr(obj, 'get_object_id'):
                object_id = obj.get_object_id()
                self.restore_object_id(object_id)
                if AxisGizmo.ID_COUNT <= object_id:
                    self.objectIDMap.pop(object_id)

            self.core_manager.notify_delete_object(obj.name)

            if self.selected_object is obj and hasattr(self.selected_object, "set_selected"):
                obj.set_selected(False)
                self.selected_object = None

            if hasattr(obj, 'delete'):
                obj.delete()
        else:
            logger.error("SceneManager::unregist_resource error. %s" % obj.name if obj else 'None')

    def add_camera(self, **camera_data):
        name = self.generate_object_name(camera_data.get('name', 'camera'))
        camera_data['name'] = name
        camera_data['model'] = self.resource_manager.get_model('Cube')
        logger.info("add Camera : %s" % name)

        camera = Camera(scene_manager=self, **camera_data)