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

в 13:51, , рубрики: c++, dxf, Unreal Engine, UnrealEgine, Программирование, разработка игр, метки: ,

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<FDXFLayer> 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<FString>& OutFilenames) override;
	virtual void SetReimportPaths(UObject* Obj, const TArray<FString>& 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<FDXFAssetEditor>, CustomEditor)
	SLATE_END_ARGS()

public:
	void Construct( const FArguments& InArgs);
	TSharedPtr<FSceneViewport> GetViewport( ) const;
	TSharedPtr<SViewport> GetViewportWidget( ) const;
	TSharedPtr<SScrollBar> GetVerticalScrollBar( ) const;
	TSharedPtr<SScrollBar> GetHorizontalScrollBar( ) const;

	void UpdateScreen();

protected:
	
	TSharedRef<SWidget> 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<FDXFAssetEditor> AssetEditor;
	// Level viewport client.
	TSharedPtr<class FDXFEditorViewportClient> ViewportClient;
	// Slate viewport for rendering and IO.
	TSharedPtr<FSceneViewport> Viewport;
	// Viewport widget.
	TSharedPtr<SViewport> ViewportWidget;
	// Vertical scrollbar.
	TSharedPtr<SScrollBar> TextureViewportVerticalScrollBar;
	// Horizontal scrollbar.
	TSharedPtr<SScrollBar> TextureViewportHorizontalScrollBar;
	// Holds the anchor for the view options menu.
	TSharedPtr<SMenuAnchor> ViewOptionsMenuAnchor;
};

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

class FDXFEditorViewportClient: public FViewportClient 
{
public:
	/** Constructor */
	FDXFEditorViewportClient(TWeakPtr<FDXFAssetEditor> InTextureEditor, TWeakPtr<SDXFEditorViewport> 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<FDXFAssetEditor> AssetEditor;

	/** Pointer back to the Texture viewport control that owns us */
	TWeakPtr<SDXFEditorViewport> 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<IAssetTypeActions> Action = MakeShareable(new FDXFSketchAssetActions);
	IAssetTools& AssetTools = FModuleManager::LoadModuleChecked<FAssetToolsModule>("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<UObject*> SelectedObjects;
	SelectedObjects.Add(InObject);
	AssetData = CastChecked<UDXFSketch>(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- проекта и сгенерировать новый. (Подробней про плагины можно почитать здесь)

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

Автор: deema35

Источник

Поделиться новостью

* - обязательные к заполнению поля