Добавление в Unreal Engine поддержки dxf формата

image

Здравствуйте меня зовут Дмитрий. Я занимаюсь созданием компьютерных игр на Unreal Engine в качестве хобби. Сегодня расскажу как добавить поддержку dxf файлов в Unreal Engine. (Исходники как всегда в конце статьи).

DXF — это открытый формат векторной графики, разработанный компанией Autodesk. В силу своей открытости этот формат поддерживается огромным количеством редакторов векторной графики.

Итак начнем с создания класса который будет содержать информацию об импортированном файле.

UCLASS(BlueprintType)

class DXFPLUGINRUNTIME_API UDXFSketch : public UObject
{
        GENERATED_BODY()
public:

#if WITH_EDITORONLY_DATA
        UPROPERTY(VisibleAnywhere, Instanced, Category = ImportSettings)
        class UAssetImportData* AssetImportData;

        virtual void PostInitProperties() override;

#endif // WITH_EDITORONLY_DATA
        
        UPROPERTY(VisibleAnywhere, BlueprintReadOnly)
        TArray Layers;

        UPROPERTY()
        float DXFBackGroundSize;
        
}; 


Здесь надо заметить что мы добавили в класс объект UAssetImportData этот объект будет содержать информацию о файле исходнике, и нужен для реимпорта ассета. В методе PostInitProperties () создается экземпляр данного класса. Массив объектов FDXFLayer собственно и содержит в себе всю информацию из dxf файла.

Ассет создан теперь нужно для него создать factory класс. Подробней о создании нового ассета можно почитать в этой статье.

UCLASS()
class  UDXFSketchFactory : public UFactory, public FReimportHandler
{
        GENERATED_UCLASS_BODY()

        // UFactory interface
        virtual UObject* FactoryCreateBinary(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, const TCHAR* Type, const uint8*& Buffer, const uint8* BufferEnd, FFeedbackContext* Warn) override;
        virtual bool CanCreateNew() const override;
        // End of UFactory interface

        // Begin FReimportHandler interface
        virtual bool CanReimport(UObject* Obj, TArray& OutFilenames) override;
        virtual void SetReimportPaths(UObject* Obj, const TArray& NewReimportPaths) override;
        virtual EReimportResult::Type Reimport(UObject* Obj) override;
        virtual int32 GetPriority() const override;
        // End FReimportHandler interface

        bool LoadFile(UDXFSketch* Sketch, const uint8*& Buffer, const uint8* BufferEnd);
};


Основным отличием factory класса для импортированных ассетов от обычных, заключается в использовании метода FactoryCreateBinary вместо FactoryCreateNew. Этому методу кроме прочих параметров предается ссылка на массив байт (которые явлются импортированным файлом) и указатель на конец этого массива.

Чтобы файл можно было ре-импортировать необходимо также в базовые классы добавить класс FReimportHandler который добавит метод Reimport.

Назначение метода LoadFile понятно и так. Чтобы не изобретать велосипед для парсинга файла я использовал библиотеку dxflib от ribbonsoft.

Ассет теперь импортируется. Но чтобы понять что находится в импортированном файле не плохо бы создать редактор для ассета. (Подробней про создание редактора для ассета можно почитать здесь). Полностью пересказывать предыдущую статью я не стану просто скажу, что нам нужно создать производный объект от FAssetEditorToolkit в котором мы создадим необходимые для нас вкладки (В данном случае это вкладка вьюпорта, в котором мы будем отрисовывать данные, и вкладка панели свойств). Создание панели свойст уже было рассмотрено. Поэтому поговорим о вьюпорте.

Во вкладке вьюпорт мы создадим объект SDXFEditorViewport

class SDXFEditorViewport : public SCompoundWidget
{
public:

        SLATE_BEGIN_ARGS(SDXFEditorViewport) { }
                SLATE_ARGUMENT(TWeakPtr, CustomEditor)
        SLATE_END_ARGS()

public:
        void Construct( const FArguments& InArgs);
        TSharedPtr GetViewport( ) const;
        TSharedPtr GetViewportWidget( ) const;
        TSharedPtr GetVerticalScrollBar( ) const;
        TSharedPtr GetHorizontalScrollBar( ) const;

        void UpdateScreen();

protected:
        
        TSharedRef GenerateViewOptionsMenu() const;

private:

        // Callback for clicking the View Options menu button.
        FReply HandleViewOptionsMenuButtonClicked();
        // Callback for the horizontal scroll bar.
        void HandleHorizontalScrollBarScrolled( float InScrollOffsetFraction );
        // Callback for getting the visibility of the horizontal scroll bar.
        EVisibility HandleHorizontalScrollBarVisibility( ) const;
        // Callback for the vertical scroll bar.
        void HandleVerticalScrollBarScrolled( float InScrollOffsetFraction );
        // Callback for getting the visibility of the horizontal scroll bar.
        EVisibility HandleVerticalScrollBarVisibility( ) const;
        // Callback for clicking an item in the 'Zoom' menu.
        void HandleZoomMenuEntryClicked( double ZoomValue );
        // Callback for getting the zoom percentage text.
        FText HandleZoomPercentageText( ) const;
        // Callback for changes in the zoom slider.
        void HandleZoomSliderChanged( float NewValue );
        // Callback for getting the zoom slider's value.
        float HandleZoomSliderValue( ) const;

        void HandleLayerActive(int Num);
        void HandleAllLayersActive();

private:

        // Pointer back to the Asset editor tool that owns us.
        TWeakPtr AssetEditor;
        // Level viewport client.
        TSharedPtr ViewportClient;
        // Slate viewport for rendering and IO.
        TSharedPtr Viewport;
        // Viewport widget.
        TSharedPtr ViewportWidget;
        // Vertical scrollbar.
        TSharedPtr TextureViewportVerticalScrollBar;
        // Horizontal scrollbar.
        TSharedPtr TextureViewportHorizontalScrollBar;
        // Holds the anchor for the view options menu.
        TSharedPtr ViewOptionsMenuAnchor;
};


Это объект интерфейса он создает все элементы интерфейса (меню ползунки и т.д) кроме того он создает объект FDXFEditorViewportClient в котором собственно и будет происходить отрисовка примитивов загруженных из файла.

class FDXFEditorViewportClient: public FViewportClient 
{
public:
        /** Constructor */
        FDXFEditorViewportClient(TWeakPtr InTextureEditor, TWeakPtr InTextureEditorViewport);

        /** FViewportClient interface */
        virtual void Draw(FViewport* Viewport, FCanvas* Canvas) override;
        virtual bool InputKey(FViewport* Viewport, int32 ControllerId, FKey Key, EInputEvent Event, float AmountDepressed = 1.0f, bool bGamepad = false) override;
        virtual UWorld* GetWorld() const override { return nullptr; }
        /** Returns the ratio of the size of the Texture texture to the size of the viewport */
        float GetViewportVerticalScrollBarRatio() const;
        float GetViewportHorizontalScrollBarRatio() const;

        void SetZoom(double ZoomValue);
        void ZoomIn();
        void ZoomOut();
        double GetZoom() const;
        
        DrawVar Vars; //variables for drawning viewport
private:
        /** Updates the states of the scrollbars */
        void UpdateScrollBars();

        /** Returns the positions of the scrollbars relative to the Texture textures */
        FVector2D GetViewportScrollBarPositions() const;

private:
        /** Pointer back to the Texture editor tool that owns us */
        TWeakPtr AssetEditor;

        /** Pointer back to the Texture viewport control that owns us */
        TWeakPtr AssetEditorViewport;
};

Собственно редактор создан, но есть ещё одна мелочь. В Unreal Engine есть такое понятие как Thumbnail это такая маленькая картинка которая отображается в контент-браузере в место значка ассета. Чтобы создать этот Thumbnail нужно создать обект производный от UThumbnailRenderer.

UCLASS()
class UDXFThumbnailRenderer : public UThumbnailRenderer
{
        GENERATED_BODY()

        // Begin UThumbnailRenderer Object
        virtual void GetThumbnailSize(UObject* Object, float Zoom, uint32& OutWidth, uint32& OutHeight) const override;
        virtual void Draw(UObject* Object, int32 X, int32 Y, uint32 Width, uint32 Height, FRenderTarget* Viewport, FCanvas* Canvas) override;
        // End UThumbnailRenderer Object
};


В этом объекте имеется метод Draw который собственно и нарисует Thumbnail. Конечноже после создания этого объекта его нужно зарегистрировать.

void FDXFPluginEditor::StartupModule()
{
        // Register DXFSketch AssetActions
        TSharedRef Action = MakeShareable(new FDXFSketchAssetActions);
        IAssetTools& AssetTools = FModuleManager::LoadModuleChecked("AssetTools").Get();
        AssetTools.RegisterAssetTypeActions(Action);
        CreatedAssetTypeActions.Add(Action);

        //Registrate ToolBarCommand for costom graph
        FDXFToolBarCommandsCommands::Register();

        //Registrate Thumbnail render
        UThumbnailManager::Get().RegisterCustomRenderer(UDXFSketch::StaticClass(), UDXFThumbnailRenderer::StaticClass());
}


Теперь откуда же запускать рендер Thumbnail? Я в качестве такова места выбрал метод HandleReimportManagerPostReimport обекта FDXFAssetEditor этот метод выполняется после импорта файла:

void FDXFAssetEditor::HandleReimportManagerPostReimport(UObject* InObject, bool bSuccess)
{
        TArray SelectedObjects;
        SelectedObjects.Add(InObject);
        AssetData = CastChecked(InObject);
        if (bSuccess)
        {
                PropertyEditor->SetObjects(SelectedObjects);
        }
        DXFViewport->UpdateScreen();

        FThumbnailRenderingInfo* RenderInfo = GUnrealEd->GetThumbnailManager()->GetRenderingInfo(AssetData);
        if (RenderInfo != NULL)
        {
                RenderInfo->Renderer; //Render Thumbnail
        }
}


image

Но как можно использовать импортированный ассет? К сожалению наложить dxf файл в качестве текстуры не получится. Но можно например загрузить точки и использовать их координаты для расстановки объектов. Или создать так называемую SplineMesh и вытинуть её вдоль какой-то линии. Пока что плагин распознает линии, замкнутые контуры и точки (которые получаются если в adobe illustrator кисточкой ткнуть в холст, эти точки представляют из себя сплайны состоящие из 10 точек).

Собственно на этом все. Проект я сделал в виде плагина поэтому чтобы добавить поддержку dxf в ваш проект достаточно создать в его директории папку Plugins и закинуть туда папку DXFPlugin, чтобы увидеть исходники плагина в VS нужно удалить старый файл VS- проекта и сгенерировать новый. (Подробней про плагины можно почитать здесь)

Проект с исходниками здесь

© Habrahabr.ru