Добавление в Unreal Engine поддержки dxf формата
Здравствуйте меня зовут Дмитрий. Я занимаюсь созданием компьютерных игр на 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
}
}
Но как можно использовать импортированный ассет? К сожалению наложить dxf файл в качестве текстуры не получится. Но можно например загрузить точки и использовать их координаты для расстановки объектов. Или создать так называемую SplineMesh и вытинуть её вдоль какой-то линии. Пока что плагин распознает линии, замкнутые контуры и точки (которые получаются если в adobe illustrator кисточкой ткнуть в холст, эти точки представляют из себя сплайны состоящие из 10 точек).
Собственно на этом все. Проект я сделал в виде плагина поэтому чтобы добавить поддержку dxf в ваш проект достаточно создать в его директории папку Plugins и закинуть туда папку DXFPlugin, чтобы увидеть исходники плагина в VS нужно удалить старый файл VS- проекта и сгенерировать новый. (Подробней про плагины можно почитать здесь)
Проект с исходниками здесь