project sulphur

1 / 2
2 / 2
1 / 4

        #pragma once
#include "tools/builder/base/flags_base.h"
#include <foundation/utils/template_util.h>
#include <foundation/containers/vector.h>

namespace sulphur 
{
  namespace builder 
  {
    class ModelPipeline;
    class MaterialPipeline;
    class MeshPipeline;
    class ShaderPipeline;
    class TexturePipeline;
    class SkeletonPipeline;
    class AnimationPipeline;
    class ScriptPipeline;
    class AudioPipeline;
    class SceneLoader;

    /**
    *@struct sulphur::builder::Syntax
    *@brief structure describing the syntax of a command
    *@author Stan Pepels
    */
    struct Syntax
    {
      const char* key; //!< key value with which to access the command
      foundation::Vector<Flag*> valid_flags; //!< flags that can be used with this command
    };

    /**
    *@class sulphur::builder::CommandInput
    *@brief helper class for queryng the command input
    *@author Stan Pepels
    */
    class CommandInput
    {
      using Flags = foundation::Vector<Flag*>; //!< flags container
      using FlagArgs = foundation::Vector<foundation::String>; //!< arguments used with each flag

    public:
      /**
      *@brief constructor
      */
      CommandInput();

      /**
      *@brief construct input class with flags and arguments
      *@param[in] flags (sulphur::builder::CommandInput::Flags) flags parsed from a command line
      *@param[in] flag_args (sulphur::builder::CommandInput::FlagArgs) arguments belonging to the flag. 
      *@remark if no argument is found the entry will be empty
      */
      CommandInput(Flags flags, FlagArgs flag_args);

      /**
      *@brief destructor
      */
      ~CommandInput();

      /**
      *@brief get argument that goes with flag of type T. use this to query a flag that can be used multiple times
      *@param[in] index (unsigned int) index specifying what the flag to query
      */
      template<typename T>
      const char* GetFlagArg(unsigned int index) const;

      /**
      *@brief get argument that goes with flag of type T. use this to query if a flag can only occur once
      */
      template<typename T>
      const char* GetFlagArg() const;

      /**
      *@brief check if flag of type T was used. use this to query a flag that can be used multiple times
      *@param[out] num_occurrences (unsigned int) number of times the flag was used. 
      */
      template<typename T>
      bool HasFlag(unsigned int& num_occurrences) const;

      /**
      *@brief check if flag of type T was used. use this to query if a flag can only occur once
      */
      template<typename T>
      bool HasFlag() const;

      /**
       * @brief Returns the number of flags of a certain type.
       * @tparam T Flag class to get the count from.
       * @return (unsigned int) The number of flags of type T.
       */
      template<typename T>
      unsigned int FlagCount() const;

    private:
      Flags flags_; //!< flags parsed from the command line used to call a command
      FlagArgs flag_args_;//!< arguments belonging to flags parsed from the command line used to call a command
    };

    /**
    *@class sulphur::builder::ICommand
    *@brief base class for commands. a command registered by the command system must be derived from this class
    *@author Stan Pepels
    */
    class ICommand
    {
    public:
      /**
      *@brief constructor
      *@param[in] key used to access this command
      */
      ICommand(const char* key);

      /**
      *@brief destructor
      */
      ~ICommand();

      /**
      *@brief get the command syntax
      */
      Syntax syntax();

      /**
      *@brief get the key passed in the constructor 
      */
      const char* key();

      /**
      *@brief executes the command.
      *@param[in] input (const sulphur::builder::CommandInput&) container holding input from the parsed command line.
      */
      virtual void Run(const CommandInput& input) = 0;

      /**
      *@brief check if the command line first input matches the command key value
      *@param[in] command_line (const char*) command recieved from console or command prompt
      */
      bool operator== (const char* command_line) const;

      /**
      *@brief get the description of the command
      *@remark if not implemented a default description will be returned
      */
      virtual const char* GetDescription() const;

      /**
      *@brief sets the pipelines this convert command has to use.
      *@param[in] model_pipeline (sulphur::builder::ModelPipeline*) a model pipeline
      *@param[in] mesh_pipeline (sulphur::builder::MeshPipeline*) a mesh pipeline
      *@param[in] material_pipeline (sulphur::builder::MaterialPipeline*) a material pipeline
      *@param[in] texture_pipeline (sulphur::builder::TexturePipeline*) a texture pipeline
      *@param[in] shader_pipeline (sulphur::builder::ShaderPipeline*) a shader pipeline
      *@param[in] skeleton_pipeline (sulphur::builder::SkeletonPipeline*) a skeleton pipeline
      *@param[in] animation_pipeline (sulphur::builder::AnimationPipeline*) an animation pipeline
      *@param[in] script_pipeline (sulphur::builder::ScriptPipeline*) a script pipeline
      *@param[in] audio_pipeline (sulphur::builder::AudioPipeline*) an audio pipeline
      *@param[in] scene_loader (sulphur::builder::SceneLoader*) a scene loader
      */
      void SetPipelines(ModelPipeline* model_pipeline,
        MeshPipeline* mesh_pipeline, MaterialPipeline* material_pipeline,
        TexturePipeline* texture_pipeline, ShaderPipeline* shader_pipeline,
        SkeletonPipeline* skeleton_pipeline, AnimationPipeline* animation_pipeline,
        ScriptPipeline* script_pipeline, AudioPipeline* audio_pipeline, 
        SceneLoader* scene_loader);

    protected:
      /**
      *@brief specify what flags are valid for use with this command. each type in Args... will be valid.
      */
      template<typename... Args>
      bool SetValidFlags();

      /**
      *@brief set a flag to be allowed multiple times when using this command
      *@param[in] value (bool) value to set. By default flags can only occur once in a command
      */
      template<typename T>
      void AllowMultipleOccurances(bool value);

      /**
      *@brief set a flag to have a paramter passed with it
      *@param[in] value (bool) value to set. By default flags do not take parameters
      */
      template<typename T>
      void HasParameter(bool value);

      /**
       * @brief Set if the flag is optional.
       * @tparam T The type of the flag.
       * @param[in] value (bool) If the flag is optional.
       */
      template<typename T>
      void IsOptional(bool value);
    protected:
      ModelPipeline* model_pipeline_;         //!< a model pipeline passed by the constructor
      MeshPipeline* mesh_pipeline_;           //!< a mesh pipeline passed by the constructor
      MaterialPipeline* material_pipeline_;   //!< a material pipeline passed by the constructor
      TexturePipeline* texture_pipeline_;     //!< a texture pipeline passed by the constructor
      ShaderPipeline* shader_pipeline_;       //!< a shader pipeline passed by the constructor
      SkeletonPipeline* skeleton_pipeline_;   //!< a skeleton pipeline passed by the constructor
      AnimationPipeline* animation_pipeline_; //!< an animation pipeline passed by the constructor
      ScriptPipeline* script_pipeline_;       //!< a script pipeline passed by the constructor
      AudioPipeline* audio_pipeline_;         //!< an audio pipeline passed by the constructor
      SceneLoader* scene_loader_;             //!< a scene loader passed by the constructor

    private:
      template<typename... Args>
      using  derived_from_flag = foundation::and_cond<eastl::is_base_of<Flag, Args>...>; //!< condition to check if all types in Args... are derived from Flag
      Syntax syntax_; //!< syntax of the command as specified in the constructor
    };

    template<typename ...Args>
    inline bool ICommand::SetValidFlags()
    {
      static_assert(derived_from_flag<Args...>::value,
        "invalid flag(s) usind in template parameter pack "
        "SetValidFlags<Args...>(int position). flags must derive from Flag class");
      while (syntax_.valid_flags.empty() == false)
      {
        delete syntax_.valid_flags.back();
        syntax_.valid_flags.pop_back();
      }
      syntax_.valid_flags = { new Args()... };
      return true;
    }

    template<typename T>
    inline void ICommand::AllowMultipleOccurances(bool value)
    {
      static_assert(std::is_base_of<Flag, T>::value,
        "invalid flag query, all flags must be derived from 'Flag'");
      T tmp = T();
      for (Flag* flag : syntax_.valid_flags)
      {
        if (tmp == flag)
        {
          flag->set_allow_multiple(value);
          return;
        }
      }
      // throw an error
    }

    template<typename T>
    inline void ICommand::HasParameter(bool value)
    {
      static_assert(std::is_base_of<Flag, T>::value,
        "invalid flag query, all flags must be derived from 'Flag'");
      T tmp = T();
      for (Flag* flag : syntax_.valid_flags)
      {
        if (*flag == tmp.GetKey())
        {
          flag->set_has_argument(value);
          return;
        }
      }
      // throw an error
    }

    template<typename T>
    inline void ICommand::IsOptional(bool value)
    {
      static_assert(std::is_base_of<Flag, T>::value,
        "invalid flag query, all flags must be derived from 'Flag'");
      T tmp = T();
      for (Flag* flag : syntax_.valid_flags)
      {
        if (*flag == tmp.GetKey())
        {
          flag->set_optional(value);
          return;
        }
      }
      // throw an error
    }

    template<typename T>
    inline const char* CommandInput::GetFlagArg(unsigned int index) const
    {
      unsigned int count = 0; 
      unsigned int curr_index = 0;
      T tmp = T();
      for (Flag* flag : flags_)
      {
        if (*flag == tmp.GetKey())
        {
          if (count == index)
          {
            return flag_args_[curr_index].c_str();
          }
          ++count;
        }
        ++curr_index;
      }
      return "";
    }

    template<typename T>
    inline const char* CommandInput::GetFlagArg() const
    {
      unsigned int curr_index = 0;
      T tmp = T();
      for (Flag* flag : flags_)
      {
        if (*flag == tmp.GetKey())
        {
          return flag_args_[curr_index].c_str();
        }
        ++curr_index;
      }
      return "";
    }

    template<typename T>
    inline bool CommandInput::HasFlag(unsigned int& num_occurrences) const
    {
      static_assert(std::is_base_of<Flag, T>::value,
        "invalid flag query, all flags must be derived from 'Flag'");

      num_occurrences = FlagCount<T>();
      return num_occurrences != 0;
    }

    template<typename T>
    unsigned int CommandInput::FlagCount() const
    {
      static_assert(std::is_base_of<Flag, T>::value,
        "invalid flag query, all flags must be derived from 'Flag'");
      unsigned int count = 0;
      T tmp = T();
      for (Flag* flag : flags_)
      {
        if (*flag == tmp.GetKey())
        {
          ++count;
        }
      }
      return count;
    }

    template<typename T>
    inline bool CommandInput::HasFlag() const
    {
      static_assert(std::is_base_of<Flag, T>::value,
        "invalid flag query, all flags must be derived from 'Flag'");

      T tmp = T();
      for (Flag* flag : flags_)
      {
        if (*flag == tmp.GetKey())
        {
          return true;
        }
      }
      return false;
    }
  }
}
       
Classes used to create a command for the sulphur builder CLI.
2 / 4

        #include "rotate_gizmo.h"
#include "engine\systems\components\camera_system.h"
#include "engine\systems\components\transform_system.h"
#include "engine\graphics\debug_render_system.h"
#include "engine\assets\asset_system.h"
#include "foundation\math\ray.h"

#include "engine\networking\editor\editor_messaging_system.h"
#include "engine\networking\editor\editor_message_payloads.h"

namespace sulphur
{
  namespace engine
  {

    //------------------------------------------------------------------------------------------------------
    RotateGizmo::RotateGizmo() :
      accumulated_rotation_(glm::quat(1,0,0,0)),
      kRadius_(1.0f),
      kSelectionThreshold_(0.1f),
      rotation_(glm::quat(1, 0, 0, 0)),
      rotation_x_axis_(glm::vec3(1, 0, 0)),
      rotation_y_axis_(glm::vec3(0, 1, 0)),
      rotation_z_axis_(glm::vec3(0, 0, 1)),
      display_mode_(DisplayMode::kUnused)
    {
    }

    //------------------------------------------------------------------------------------------------------
    void RotateGizmo::Initialize()
    {
      Mesh* rotation_mesh = foundation::Memory::Construct<Mesh>();
      circle_mesh_.AttachMesh(
        Mesh::CreateLineCircle(),
        glm::vec3(),
        glm::quat(1, 0, 0, 0),
        glm::vec3(kRadius_, kRadius_, 1.0f));

      gizmo_mesh_ = AssetSystem::Instance().AddAsset(rotation_mesh, "rotate_gizmo");
      gizmo_mesh_->SetTopologyType(graphics::TopologyType::kLine);
      gizmo_mesh_->SetAlwaysOnTop(true);
    }

    //------------------------------------------------------------------------------------------------------
    void RotateGizmo::Release()
    {
      gizmo_mesh_ = MeshHandle();
    }

    //------------------------------------------------------------------------------------------------------
    void RotateGizmo::ManipulationStart(const foundation::Ray& camera_ray, foundation::Vector<TransformComponent>&)
    {
      float t_min;
      float t_max;
      if (camera_ray.IntersectSphere(position(), kRadius_ * scale(), t_min, t_max) == false)
      {
        return;
      }
      glm::vec3 intersect = camera_ray.origin + camera_ray.direction * t_min;
      glm::vec3 v = intersect - position();
      switch (rotate_mode_)
      {
      case sulphur::engine::RotateGizmo::RotateMode::kAxis:
      {
        float dot = glm::dot(rotation_axis_, v);
        glm::vec3 p = intersect - dot * rotation_axis_;
        previous_intersect_ = p - position();
        break;
      }
      case sulphur::engine::RotateGizmo::RotateMode::kFree:
        previous_intersect_ = glm::vec3(0, 0, 0);
        break;
      }
      set_is_manipulating(true);
    }

    //------------------------------------------------------------------------------------------------------
    void RotateGizmo::ManipulateTransforms(
      foundation::Vector<TransformComponent>&,
      const foundation::Ray& camera_ray,
      TransformComponent camera_transform,
      editor::EditorMessagingSystem&)
    {
      rotation_x_axis_ = RotateVector(glm::vec3(1, 0, 0), rotation_);
      rotation_y_axis_ = RotateVector(glm::vec3(0, 1, 0), rotation_);
      rotation_z_axis_ = RotateVector(glm::vec3(0, 0, 1), rotation_);
      glm::quat applied_rotation = glm::quat(1,0,0,0);

      switch (rotate_mode_)
      {
      case sulphur::engine::RotateGizmo::RotateMode::kAxis:
      {
        float t_min;
        float t_max;
        if (camera_ray.IntersectSphere(position(), kRadius_ * scale(), t_min, t_max) == false)
        {
          return;
        }
        glm::vec3 intersect = camera_ray.origin + camera_ray.direction * t_min;
        glm::vec3 normal = intersect - position();
        glm::vec3 current_intersect = previous_intersect_;
        float dot;

        dot = glm::dot(rotation_axis_, normal);
        glm::vec3 point = intersect - dot * rotation_axis_;
        current_intersect = point - position();
        dot = glm::dot(current_intersect, previous_intersect_);
        float sqrt = glm::sqrt(glm::length2(current_intersect) * glm::length2(previous_intersect_));
        float angle = glm::acos(dot / sqrt);
        glm::vec3 cross = glm::cross(current_intersect, previous_intersect_);
        dot = glm::dot(cross, rotation_axis_);

        if (glm::isnan(angle) == true)
        {
          return;
        }
        if (dot > 0.0f)
        {
          angle *= -1;
        }
        glm::quat rotation = glm::angleAxis(angle, rotation_axis_);
        glm::quat temp_rot = rotation_;
        applied_rotation = glm::inverse(temp_rot) * rotation * temp_rot;
        previous_intersect_ = current_intersect;
        break;
      }
      case sulphur::engine::RotateGizmo::RotateMode::kFree:
        glm::vec3 camera_y_axis = camera_transform.GetLocalUp();
        glm::vec3 camera_x_axis = camera_transform.GetLocalRight();
        float t;
        camera_ray.IntersectPlane(-camera_transform.GetLocalForward(), camera_ray.origin, t);
        glm::vec3 intersect = camera_ray.origin + camera_ray.direction + t;
        //positions on the near z plane
        float x = -(glm::dot(camera_x_axis, intersect - camera_ray.origin));
        float y = glm::dot(camera_y_axis, intersect - camera_ray.origin);
        if (glm::length(previous_intersect_) == 0)
        {
          previous_intersect_ = { x,y,0 };
          applied_rotation = glm::quat(1, 0, 0, 0);
          break;
        }

        float x_angle = y - previous_intersect_.y;
        float y_angle = x - previous_intersect_.x;

        glm::quat rotation = glm::angleAxis(x_angle, camera_x_axis);
        glm::quat temp_rot = rotation_;
        applied_rotation = glm::inverse(temp_rot) * rotation * temp_rot;

        rotation = glm::angleAxis(y_angle, camera_y_axis);
        temp_rot = rotation_;
        applied_rotation *= glm::inverse(temp_rot) * rotation * temp_rot;

        previous_intersect_ = { x,y, 0.0f };
      {
        break;
      }
      }
      accumulated_rotation_ = applied_rotation * accumulated_rotation_;

      // rotation_ *= applied_rotation;
      // attached[0].SetWorldRotation(rotation_);
    }

    void RotateGizmo::RequestChange(editor::EditorMessagingSystem& system, foundation::Vector<TransformComponent>& attached)
    {
      glm::vec3 vec{ accumulated_rotation_.x, accumulated_rotation_.y, accumulated_rotation_.z };
      if (glm::length2(vec) == 0.0f)
      {
        return;
      }

      editor::EntityRotatePayload payload;
      payload.entity_index = attached[0].GetHierarchyIndex();
      payload.w = accumulated_rotation_.w;
      payload.x = accumulated_rotation_.x;
      payload.y = accumulated_rotation_.y;
      payload.z = accumulated_rotation_.z;
      system.SendToEditor(editor::EditorMessageID::kEntityRotated, payload);
      accumulated_rotation_ = {1,0,0,0 };
    }

    //------------------------------------------------------------------------------------------------------
    void RotateGizmo::ManipulationEnd()
    {
      display_mode_ = DisplayMode::kUnused;
      set_is_manipulating(false);
    }

    //------------------------------------------------------------------------------------------------------
    void RotateGizmo::Draw(foundation::Vector<TransformComponent>& attached_entities)
    {
      if (attached_entities.size() == 0)
      {
        return;
      }

      ConstructMesh();
      set_position(attached_entities[0].GetWorldPosition());
      rotation_ = attached_entities[0].GetWorldRotation();
      DebugRenderSystem::DrawMesh(gizmo_mesh_, position(), rotation_, glm::vec3(scale()));
    }

    //------------------------------------------------------------------------------------------------------
    bool RotateGizmo::Select(const foundation::Ray& camera_ray)
    {
      float t_min;
      float t_max;

      if (camera_ray.IntersectSphere(position(), kRadius_ * scale(), t_min, t_max) == false)
      {
        return false;
      }

      rotate_mode_ = RotateMode::kAxis;
      glm::vec3 intersection = camera_ray.origin + camera_ray.direction * t_min;
      glm::vec3 normal = glm::normalize(intersection - position());
      rotation_axis_ = rotation_x_axis_;
      float dot = glm::dot(normal, rotation_axis_);
      if (dot > -kSelectionThreshold_ && dot < kSelectionThreshold_)
      {
        display_mode_ = DisplayMode::kXAxisSelected;
        return true;
      }

      rotation_axis_ = rotation_z_axis_;
      dot = glm::dot(normal, rotation_axis_);
      if (dot > -kSelectionThreshold_ && dot < kSelectionThreshold_)
      {
        display_mode_ = DisplayMode::kZAxisSelected;
        return true;
      }

      rotation_axis_ = rotation_y_axis_;
      dot = glm::dot(normal, rotation_axis_);
      if (dot > -kSelectionThreshold_ && dot < kSelectionThreshold_)
      {
        display_mode_ = DisplayMode::kYAxisSelected;
        return true;
      }

      rotate_mode_ = RotateMode::kFree;
      return true;
    }

    //------------------------------------------------------------------------------------------------------
    void RotateGizmo::ConstructMesh()
    {
      gizmo_mesh_->Clear(true);
      foundation::Color mesh_colors_[] = {
        foundation::Color::kBlue,
        foundation::Color::kGreen,
        foundation::Color::kRed
      };

      if (display_mode_ != DisplayMode::kUnused)
      {
        mesh_colors_[static_cast<size_t>(display_mode_)] = foundation::Color::kYellow;
      }

      circle_mesh_.SetColor(mesh_colors_[static_cast<size_t>(DisplayMode::kZAxisSelected)]);
      gizmo_mesh_->AttachMesh(circle_mesh_);

      circle_mesh_.SetColor(mesh_colors_[static_cast<size_t>(DisplayMode::kYAxisSelected)]);
      gizmo_mesh_->AttachMesh(circle_mesh_, glm::vec3(), glm::inverse(kRot90X));

      circle_mesh_.SetColor(mesh_colors_[static_cast<size_t>(DisplayMode::kXAxisSelected)]);
      gizmo_mesh_->AttachMesh(circle_mesh_, glm::vec3(),kRot90Y);
    }

    //------------------------------------------------------------------------------------------------------
    glm::vec3 RotateGizmo::RotateVector(const glm::vec3& v, const glm::quat& q)
    {
      glm::quat uint_q = glm::normalize(q);
      glm::quat pure_q(0.0f, v.x, v.y, v.z);
      glm::quat result = uint_q * pure_q * glm::conjugate(uint_q);
      return glm::vec3(result.x, result.y, result.z);
    }
  }
}
       
engine side implementation of the rotate gizmo
3 / 4

        using System;
using System.Runtime.InteropServices;
using System.Reflection;

namespace sulphur
{
  namespace editor
  {
    /**
       * @interface sulphur.editor.IMessageContainer
       * @brief Interface defined so we can store all types of message containers in a single array.
       * @author Stan Pepels
       */
    public interface IMessageContainer
    {
      /**
       * @brief Function used to create an object out of byte data.
       * @param[in] data (byte[]) Byte data to be converted.
       * @return (object) Object created from the byte array.
       */
      object Create(byte[] data);
    }

    /**
     * @class sulphur.editor.MessageContainer<T> : sulphur.editor.IMessageContainer
     * @brief class used to message structs from byte arays.
     * @author Stan Pepels
     */
    class MessageCreator<T> : IMessageContainer where T : struct
    {
      /**
       * @see sulphur.editor.IMessageContainer.Create.
       * @remark Object returned is of type T.
       */
      public object Create(byte[] data)
      {
        return Utils.BytesToStruct<T>(data);
      }
    }

    /**
     * @class sulphur.editor.AssociateEnumToStruct : Attribute
     * @brief Allows a struct type to be associated with an enum.
     * @author Stan Pepels
     */
    [AttributeUsage(AttributeTargets.Field, AllowMultiple = false, Inherited = true)]
    public class AssociateEnumToStruct : Attribute
    {
      /**
       * @brief constructor that creates an object of the type passed.
       * @param[in] type (Type) The type to associate with the field this attribute is used on.
       * @remark The type passed must implement the sulphur.editor.I\IMessageContainer interface
       */
      public AssociateEnumToStruct(Type type)
      {
        Type[] interfaces = type.GetInterfaces();
        Type interface_type = typeof(IMessageContainer);
        foreach (Type i in interfaces)
        {
          if (i == interface_type)
          {
            obj = Activator.CreateInstance(type) as IMessageContainer;
            return;
          }
        }
        obj = null;
      }

      /**
       * @brief object that can be used to convert a byte array to a message struct.
       */
      public IMessageContainer obj { get; private set; }
    }

    namespace native
    {
      namespace messages
      {
        /**
         *@struct sulphur.editor.native.messages.StringMessage
         *@brief message for sending / retrieving a string message from / to the engine
         *@author Stan Pepels  
         */
        [StructLayout(LayoutKind.Sequential)]
        struct StringMessage
        {
          public byte[] bytes; //!< byte array containg the string data in 1byte char values
        }

        /**
         *@struct sulphur.editor.native.messages.AssetInstantiatedPayload
         *@brief message for sending an instantiate message to the engine 
         *@see sulphur.editor.native.NetworkMessages.kAssetInstantiated
         *@author Stan Pepels
         */
        [StructLayout(LayoutKind.Sequential)]
        struct AssetInstantiatedPayload
        {
          public UInt64 asset_id; //!< id of the asset to be instantiated
        }

        /**
         *@struct sulphur.editor.native.messages.WindowHandleMessage
         *@brief message for providing a waiting engine with a window handle to render to
         *@see sulphur.editor.native.NetworkMessages.kWindowhandle
         *@author Maarten ten Velden
         */
        [StructLayout(LayoutKind.Sequential)]
        struct WindowHandleMessage
        {
          public Int64 handle; //!< The window handle to render to
        }

        /**
         *@struct sulphur.editor.native.messages.EntityCreatedMessage
         *@brief message for creating a new entity in the engine with an optional parent
         *@see sulphur.editor.native.NetworkMessages.kEntityCreated
         *@author Maarten ten Velden
         */
        [StructLayout(LayoutKind.Sequential)]
        struct EntityCreatedMessage
        {
          public UInt64 entity_index; //!< The index in the hierarchy of the new entity
          public UInt64 sibling_index; //!< The sibling index of the new entity
          public UInt64 parent_index; //!< The index in the hierarchy of the new entity's parent (or UInt64.MaxValue to indicate no parent)
          [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
          public float[] position; //!< Position to instantiate the object at.

          [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
          public float[] rotation; //!< Rotation to instantiate the object at.

          [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
          public float[] scale; //!< Scale to instantiate the object at.

        }

        /**
         *@struct sulphur.editor.native.messages.EntityDestroyedMessage
         *@brief message for deleting an existing entity from the engine
         *@see sulphur.editor.native.NetworkMessages.kEntityDestroyed
         *@author Maarten ten Velden
         */
        [StructLayout(LayoutKind.Sequential)]
        struct EntityDestroyedMessage
        {
          public UInt64 entity_index; //!< The index in the hierarchy of the entity to be destroyed
        }
        /**
         *@struct sulphur.editor.native.messages.EntityReparentedMessage
         *@brief message for modifying an existing entity's parent in the engine
         *@see sulphur.editor.native.NetworkMessages.kEntityReparented
         *@author Maarten ten Velden
         */
        [StructLayout(LayoutKind.Sequential)]
        struct EntityReparentedMessage
        {
          public UInt64 entity_old_index; //!< The index before reparenting in the hierarchy of the entity
          public UInt64 new_parent_old_index; //!< The index before reparenting in the hierarchy of the entity's new parent (or UInt64.MaxValue to indicate no parent)
          public UInt64 new_sibling_index; //!< The index after reparenting in the hierarchy of the entity
        }

        /**
        *@struct sulphur.editor.native.messages.EntitySelectedMessage
        *@brief message used to indicate that an entity was selected in the editor.
        *@see sulphur.editor.native.NetworkMessages.kEntitySelected
        *@author Stan Pepels
        */
        [StructLayout(LayoutKind.Sequential)]
        struct EntitySelectedMessage
        {
          public UInt64 entity_index; //!< index of the entity in the world hierarchy.
        }

        /**
        *@struct sulphur.editor.native.messages.EntityMovedMessage
        *@brief message used to indicate that an entity was moved in the editor.
        *@see sulphur.editor.native.NetworkMessages.kEntityMoved
        *@author Stan Pepels
        */
        [StructLayout(LayoutKind.Sequential)]
        struct EntityMovedMessage
        {
          public UInt64 entity_index; //!< index of the entity in the world hierarchy.
          public float x; //!< The amount of units to move the entity along the world x axis.
          public float y; //!< The amount of units to move the entity along the world y axis.
          public float z; //!< The amount of units to move the entity along the world z axis.
        }

        /**
        *@struct sulphur.editor.native.messages.SetTransformGizmoMessage
        *@brief message used to indicate a different transform gizmo was selected.
        *@see sulphur.editor.native.NetworkMessages.kSetTransformGizmo
        *@author Stan Pepels
        */
        [StructLayout(LayoutKind.Sequential)]
        struct SetTransformGizmoMessage
        {
          /**
           * @brief Enum of gizmo types.
           */
          public enum Type
          {
            kTranslate, //!< Gizmo used to move an entity in the scen view.
            kRotate, //!< Gizmo used to rotate an entity in the scene view.
            kScale //!< Gizmo used to scale an entity in the scene view.
          }

          public Int32 type; //!< The type of transform gizmo to use.
        }

        /**
        *@struct sulphur.editor.native.messages.EntityScaleMessage
        *@brief message used to indicate that an entity was scaled in the editor.
        *@see sulphur.editor.native.NetworkMessages.kEntityScaled
        *@author Stan Pepels
        */
        [StructLayout(LayoutKind.Sequential)]
        struct EntityScaleMessage
        {
          public UInt64 entity_index; //!< index of the entity in the world hierarchy.
          public float x; //!< The scaling to be applied in the x direction.
          public float y; //!< The scaling to be applied in the y direction.
          public float z; //!< The scaling to be applied in the z direction.
        };

        /**
        *@struct sulphur.editor.native.messages.EntityRotateMessage
        *@brief message used to indicate that an entity was rotated in the editor.
        *@see sulphur.editor.native.NetworkMessages.kEntityRotated
        *@author Stan Pepels
        */
        [StructLayout(LayoutKind.Sequential)]
        struct EntityRotateMessage
        {
          public UInt64 entity_index; //!< index of the entity in the world hierarchy.
          public float w; //!< The w component of the quaternion with which to rotate the entity.
          public float x; //!< The x component of the quaternion with which to rotate the entity.
          public float y; //!< The y component of the quaternion with which to rotate the entity.
          public float z; //!< The z component of the quaternion with which to rotate the entity.
        };

        /**
        *@struct sulphur.editor.native.messages.LoadProjectMessage
        *@brief message that a different project was loaded.
        *@see sulphur.editor.native.NetworkMessages.kLoadProject
        *@author Stan Pepels
        */
        [StructLayout(LayoutKind.Sequential)]
        struct LoadProjectMessage
        {
          /**
           * @brief Constructor used to put the string into a fixed size byte array.
           * @param[in] path (string) The value to contstruct the byte array from.
           */
          public LoadProjectMessage(string path)
          {
            directory_path = new byte[Networking.kMaxPayLoadSize - 1];
            byte[] str_bytes = System.Text.Encoding.ASCII.GetBytes(path);

            Buffer.BlockCopy(str_bytes, 0, directory_path, 0, str_bytes.Length);
          }
          [MarshalAs(UnmanagedType.ByValArray, SizeConst = (int)Networking.kMaxPayLoadSize - 1)]
          byte[] directory_path; //!< Byte array with data containning the converted path string.
        };
      }

      /**
       *@see sulphur::engine::MessageID 
       */
      public enum NetworkMessages : uint
      {
        [AssociateEnumToStruct(typeof(MessageCreator<messages.WindowHandleMessage>))]       kWindowHandle = 0,
        [AssociateEnumToStruct(typeof(MessageCreator<messages.EntityCreatedMessage>))]      kEntityCreated, //!< The creation of a new entity
        [AssociateEnumToStruct(typeof(MessageCreator<messages.EntityDestroyedMessage>))]    kEntityDestroyed, //!< The destruction of an existing entity
        [AssociateEnumToStruct(typeof(MessageCreator<messages.EntityReparentedMessage>))]   kEntityReparented, //!< The change of an existing entity's parent
        [AssociateEnumToStruct(typeof(MessageCreator<messages.AssetInstantiatedPayload>))]  kAssetInstantiated, //!< The instantiation of an asset in the world
        kComponentAdded, //!< The creation of a new component on an entity
        kComponentRemoved, //!< The destruction of an existing component on an entity
        kCacheChanged, //!< Notification that the asset-cache must be reloaded
        kFastBackward, //!< A request from the editor to start rewinding
        kPreviousFrame,  //!< A request from the editor to rewind one frame
        kStartedPlaying, //!< A request from the editor to start playing the game
        kStoppedPlaying, //!< A request from the editor to stop playing the game
        kPause,
        kContinuePlaying,
        kNextFrame,  //!< A request from the editor to forward one frame
        kFastForward,  //!< A request from the editor to fast forward
        kStartRewinding, //!< A request from the editor to start rewinding
        kStopRewinding, //!< A request from the editor to stop rewinding
        [AssociateEnumToStruct(typeof(MessageCreator<messages.EntitySelectedMessage>))]     kObjectSelected, //!< An object got selected via a control in the editor.
        [AssociateEnumToStruct(typeof(MessageCreator<messages.SetTransformGizmoMessage>))]  kSetTransformGizmo, //!< Sets the current type of transform gizmo to use.
        [AssociateEnumToStruct(typeof(MessageCreator<messages.EntitySelectedMessage>))]     kEntitySelected, //!< A notification from the editor that acretain entity was selected.
        [AssociateEnumToStruct(typeof(MessageCreator<messages.EntityMovedMessage>))]        kEntityMoved, //!< A Notification from the editor that an entity was moved.
        [AssociateEnumToStruct(typeof(MessageCreator<messages.EntityRotateMessage>))]       kEntityRotated, //!< A Notification from the editor that an entity was rotated.
        [AssociateEnumToStruct(typeof(MessageCreator<messages.EntityScaleMessage>))]        kEntityScaled, //!< A Notification from the editor that an entity was scaled.
        [AssociateEnumToStruct(typeof(MessageCreator<messages.LoadProjectMessage>))]        kLoadProject, //!< A Notification from the editor that a different project was loaded.
        kNumMessages, //!< The amount of unique messages that can be received
      }

      /**
       *@class sulphur.editor.native.Networking
       *@brief class with static functions to leveradge the sulphur-networking
       *@author Stan Pepels   
       */
      class Networking
      {
        public const uint kPacketSize = 512; //!< max size of a package in bytes
        public const uint kMaxPayLoadSize = kPacketSize - sizeof(UInt32); //!< max payload size of a package

        /**
         *@struct sulphur.editor.native.Networking.Packet
         *@brief struct for retrieving a packet from the sukphur-networking.dll
         *@author Stan Pepels   
         */
        public struct Packet
        {
          public byte[] data; //!< data that came with the packet
        }
        /**
         *@see sulphur::networking::editor::ErrorMessage 
         */
        public enum ErrorMessage : uint
        {
          kOk, //!< ok
          kEnetInitFailed, //!< Enet failed to initialise
          kHostCreateFailed, //!< Creating a host failed
          kResolveIpFailed, //!< Resolving the IP address failed
          kConnectFailed, //!< Connecting to IP failed
          kPacketCreationFailed, //!< Failed to create a packet
          kNoPeerConnected, //!< No peer connected
          kPacketSendFailed //!< Failed to send a packet
        };

        /**
         *@see sulphur::networking::editor::SNetInitEditor 
         */
        [DllImport("sulphur-networking.dll")]
        public static extern ErrorMessage SNetInitEditor();

        /**
         *@see sulphur::networking::editor::SNetEditorConnect 
         */
        [DllImport("sulphur-networking.dll")]
        public static extern ErrorMessage SNetEditorConnect(string ip, UInt32 port);

        /**
         *@see sulphur::networking::editor::SNetRetrievePacket 
         */
        [DllImport("sulphur-networking.dll")]
        public static extern bool_ SNetRetrievePacket(ref UInt32 id, [Out]byte[] buf, uint size = kMaxPayLoadSize);

        /**
         *@see sulphur::networking::editor::SNetSendData 
         */
        [DllImport("sulphur-networking.dll")]
        public static extern ErrorMessage SNetSendData(UInt32 id, byte[] buf, uint size = kMaxPayLoadSize);

        /**
         *@see sulphur::networking::editor::SNetDestroy 
         */
        [DllImport("sulphur-networking.dll")]
        public static extern void SNetDestroy();

        /**
         *@see sulphur::networking::editor::SNetIsConnected 
         */
        [DllImport("sulphur-networking.dll")]
        public static extern bool_ SNetIsConnected();

        /**
         *@see sulphur::networking::editor::SNetFlushPackets 
         */
        [DllImport("sulphur-networking.dll")]
        public static extern void SNetFlushPackets();
      }
    } // native
  } // editor
} // sulphur

       
editor side classes used to communicate with the editor via a neworked connection.
4 / 4

        using System.Xml;
using System.IO;

namespace sulphur.editor
{
  /**
   * @class sulphur.editor.Project
   * @brief Class responsible for doing operation such as loading projects and creating new projects.
   * @author Stan Pepels
   */
  class Project
  {
    /**
     * @brief Absolute directory path to the directory the *.spr file used to load the current project is located in. 
     */
    public static string directory_path { get; private set; } = "";

    /**
     * @brief Absolute file path to the *.spr file used to load the current project.
     */
    public static string path_ { get; private set; } = "";

    /**
     * @brief Name of the currently loaded project.
     */
    public static string name_ { get; private set; } = "";

    /**
     * @brief Project relative path to the current world being edited.
     */
    public static string current_world_ { get; set; } = "";

    /**
     * @brief Loads a new project at a given filepath
     * @param[in] path (string) Absolute or relative filepath to a *.spr file.
     * @return (bool) True if the project was loaded succesfully, false otherwise.
     * @remark This function reinitializes the asset pipelines.
     */
    public static bool Load(string path)
    {
      path_ = path;
      directory_path = path.Substring(0, path.LastIndexOf("\\"));

      XmlReaderSettings settings = new XmlReaderSettings();
      XmlReader reader = XmlReader.Create(path, settings);
      using (reader)
      {
        while (reader.Read())
        {
          if (reader.IsStartElement() && reader.Name == "ProjectSettings")
          {
            ReadSettings(reader);
          }
        }
      }
      native.AssetProcessor.SetProjectDirectory(directory_path);
      native.AssetProcessor.SetOutputPath(directory_path);
      native.AssetProcessor.SetPackageOutputPath("resources");
      return true;
    }

    /**
     * @brief Creates a new project at a given path.
     * @param[in] path (string) Location of the *.spr.
     * @return (bool) True if the project was created succesfully, false otherwise.
     * @remarks This will create an "assets" and "resources" folder in the project directory. 
     * @remarks Calling this function will reinitialize the asset pipelines.
     * @remarks Calling this function will overwrite any project allready located in the target directory.
     */
    public static bool CreateNew(string path)
    {
      path_ = path;
      directory_path = path.Substring(0, path.LastIndexOf("\\"));
      // create the folders required
      if (Directory.Exists(directory_path + "\\resources") == false)
      {
        Directory.CreateDirectory(directory_path + "\\resources");
      }

      if (Directory.Exists(directory_path + "\\assets") == false)
      {
        Directory.CreateDirectory(directory_path + "\\assets");
      }
      
      current_world_ = "assets\\main.swo";

      // write the settings to a new project file
      XmlWriterSettings settings = new XmlWriterSettings();
      settings.Indent = true;
      settings.IndentChars = "\t";
      XmlWriter xml_writer = XmlWriter.Create(path, settings);
      using (xml_writer)
      {
        xml_writer.WriteStartElement("ProjectSettings");
        xml_writer.WriteAttributeString("Name", path.Substring(path.LastIndexOf("\\") + 1));
        xml_writer.WriteElementString("World", current_world_);
        xml_writer.WriteEndElement();
        xml_writer.Close();
      }
      native.AssetProcessor.SetProjectDirectory(directory_path);
      native.AssetProcessor.SetOutputPath(directory_path);
      native.AssetProcessor.SetPackageOutputPath("resources");

      if(CreateEmptyWorld() == false)
      {
        Log.Write(Log.Verbosity.kError, "Unable to create or register world");
        return false;
      }

      if (CreateEmptyMainScript() == false)
      {
        Log.Write(Log.Verbosity.kError, "Unable to create or register empty main script");
        return false;
      }
      return true;
    }

    /**
     * @brief Reads the settings from a *.spr file.
     * @param[in] reader (XmlReader) Reader that reads the *.spr file with its read location set to the 'ProjectSettings' element.
     */
    private static void ReadSettings(XmlReader reader)
    {
      reader.MoveToFirstAttribute();
      name_ = reader[0].Remove(reader[0].IndexOf("."));
      reader.MoveToElement();

      while (reader.Read())
      {
        switch (reader.Name)
        {
          case "World":
            current_world_ = reader.ReadElementContentAsString();
            break;
        }

        if (reader.Name == "ProjectSettings" && reader.NodeType == XmlNodeType.EndElement)
        {
          break;
        }
      }
    }

    private static bool CreateEmptyMainScript()
    {
      StreamWriter file_writer = new StreamWriter(directory_path + "\\assets\\main.lua");
      file_writer.WriteLine("function OnInitialize()");
      file_writer.WriteLine("end");
      file_writer.WriteLine();
      file_writer.WriteLine("function Update(dt)");
      file_writer.WriteLine("end");
      file_writer.WriteLine();
      file_writer.WriteLine("function FixedUpdate()");
      file_writer.WriteLine("end");
      file_writer.Close();

      ulong id = 0;
      native.AssetProcessor.RegisterWorld(current_world_, ref id);
      return native.AssetProcessor.ImportScript("assets\\main.lua", ref id);
    }

    private static bool CreateEmptyWorld()
    {
      File.CreateText(directory_path + "\\assets\\main.swo").Close();
      File.CreateText(directory_path + "\\resources\\main.sbw").Close();
      ulong id = 0;
      return native.AssetProcessor.RegisterWorld(current_world_, ref id);
    }
  }
}

       
Implementation of how projects are created in the editor.

Summary

Project sulphur is a cross-platform engine project by NHTV students. The engine is build in C++ with an editor build in .Net WPF framework. The engine has a special feature we call the 'sulphur rewinder'. This feature allows you to rewind the game in the editor and inspect the data from previous frames. Besides this the editor is completely detached from the engine meaning that if the engine crashes there is no progress lost and the editor can recover.
Go to Project Site

Contributions

  • Sulphur builder: interface
  • Sulphur builder: shader pipeline
  • Sulphur builder: world pipeline
  • Sulphur editor: gizmos
  • Sulphur editor: world view
  • Sulphur editor: logger
  • Sulphur editor: playback controls
  • Sulphur editor: project setup
  • Sulphur editor/engine network communication

Tools Used

visual studio
Doxygen
wpf
Jira

Team

Designers: 1
Programmers: 14

Project length

32 weeks

Details

Project sulphur can be subdivided into 3 catagories: engine, builder and editor.

  • The engine: is the library with all the functionality required to run the game such as rendering, physics, networking etc… This is completely written in C++.
  • The builder: is a standalone command line tool that is used to package raw assets into game-ready formats used by the engine. Like the engine, this is written in C++.
  • The editor: is the environment where the features of the engine are exposed in a user-friendly interface. This is where you can create your game projects, levels etc… The editor is mainly written in C#/xaml but uses some DLLs written in C++.

Engine work: My main work on the engine is on the interfaces that allow the engine to communicate with the engine. The engine is connected to the editor via the network. This means that the engine you see in the editor is its own separate process. Another benefit of this connection is that we could, in theory, launch a second or even a third engine in editor mode and have all engines connect to a single editor instance. These other versions could run on other machines such as a PS4 devkit. This, in turn, would then allow for simultaneous testing and developing for multiple platforms without having to switch around platforms or create a build before testing.

Converting multiple assets types using the sulphur builder.
Builder work: With the builder I mainly worked on the command line interfaces. These interfaces allow other programmers to easily add commands with flags and arguments to the builder. I was also responsible for doing the shader pipeline. The shader pipeline takes in a shader written in hlsl with some custom definitions. This single file is then converted into a shader package that can be loaded by the engine and used to render with Directx12, Vulkan, and GNM(PS4).

Assets can also be imported in the editor and instantiated in the world.

Editor work: Most of my contribution in this project was with the editor. I wrote the interface and utility classes that allow the editor to receive, process and send messages to connected engines. I also wrote an interface that allows controls and systems to be developed completely separate whilst still being able to act upon actions or event that happen in different systems. Besides this, I was responsible for creating the world-view, asset browser, logger, and menu bar. The editor also has a project pipeline allowing you to set up multiple game projects. Each project can be shared with other people since all paths are relative.

Under the hood, the editor uses a DLL version of the sulphur builder. I worked on exposing the sulphur builder(written in C++) functionality to C# so it can be used by the editor.

Download