【UE4】GAMES101 图形学作业4:贝塞尔曲线

总览

  • Bézier 曲线是一种用于计算机图形学的参数曲线。

    image

  • 在本次作业中,你需要实现de Casteljau 算法来绘制由4 个控制点表示的Bézier 曲线(当你正确实现该算法时,你可以支持绘制由更多点来控制的Bézier 曲线)。

  • 你需要修改的函数在提供的main.cpp 文件中。

    • bezier:该函数实现绘制Bézier 曲线的功能。
      它使用一个控制点序列和一个OpenCV::Mat 对象作为输入,没有返回值。它会使t 在0 到1 的范围内进行迭代,并在每次迭代中使t 增加一个微小值。对于每个需要计算的t,将调用另一个函数recursive_bezier,然后该函数将返回在Bézier 曲线上t处的点。最后,将返回的点绘制在OpenCV ::Mat 对象上。
    • recursive_bezier:该函数使用一个控制点序列和一个浮点数t 作为输入,实现de Casteljau 算法来返回Bézier 曲线上对应点的坐标。

实现

  • 版本 4.26.2

  • 原文地址

  • naive_bezier

    • 数学公式

      image

    • 代码

      void AActor_BezierCuve::naive_bezier()
      {
      	FVector& p_0 = m_points[0];
      	FVector& p_1 = m_points[1];
      	FVector& p_2 = m_points[2];
      	FVector& p_3 = m_points[3];
      	FVector& p_4 = m_points[4];
      	for (double t = 0.0; t <= 1.0; t += 0.001)
      	{
      		auto point = std::pow(1 - t, 4) * p_0 + 4 * t * std::pow(1 - t, 3) * p_1 +
      			6 * std::pow(t, 2) * std::pow((1 - t), 2) * p_2 + 4 * std::pow(t, 3) * (1 - t) * p_3 + std::pow(t, 4) * p_4;
      		DrawDebugPoint(GetWorld(), point, 2.0f, FColor::Green,true,5.0f);
      		//UKismetSystemLibrary::PrintString(GetWorld(), point.ToString());
      	}
      	
      }
      
  • recursive_bezier

    • De Casteljau 算法说明如下:

      1. 考虑一个p0, p1, ... pn 为控制点序列的Bézier 曲线。首先,将相邻的点连接起来以形成线段。
      2. 用t : (1 − t) 的比例细分每个线段,并找到该分割点。
      3. 得到的分割点作为新的控制点序列,新序列的长度会减少一。
      4. 如果序列只包含一个点,则返回该点并终止。否则,使用新的控制点序列并转到步骤1。使用[0,1] 中的多个不同的t 来执行上述算法,你就能得到相应的Bézier 曲线。
    • 代码

      void AActor_BezierCuve::bezier()
      {
      	for (double t = 0.0; t <= 1.0; t += 0.001)
      	{
      		FVector point = recursive_bezier(m_points, t);
      		DrawDebugPoint(GetWorld(), point, 2.0f, FColor(10,214,255,255),true,5.0f);
      	}
      }
      
      // De Casteljau 算法,递归
      FVector AActor_BezierCuve::recursive_bezier(TArray<FVector>& points, float t)
      {
      	if (points.Num() < 3) {
      		return (1 - t) * points[0] + t * points[1];
      	}
      
      	TArray<FVector> newPoint;
      	for (int i = 0; i < points.Num() - 1; i++) {
      		newPoint.Add((1 - t) * points[i] + t * points[i + 1]);
      	}
      	return recursive_bezier(newPoint, t);
      }
      
  • 最终效果
    image

附录

所有代码

  • Actor_BezierCuve.h

    点击查看代码
      ```cpp
      UCLASS()
      class GAMES101_API AActor_BezierCuve : public AActor
      {
      	GENERATED_BODY()
    
      public:	
      	// Sets default values for this actor's properties
      	AActor_BezierCuve();
    
      protected:
      	// Called when the game starts or when spawned
      	virtual void BeginPlay() override;
    
      public:	
      	// Called every frame
      	virtual void Tick(float DeltaTime) override;
    
      	UFUNCTION(BlueprintCallable)
      	void naive_bezier();
    
      	UFUNCTION(BlueprintCallable)
      		void bezier();
    
      	UFUNCTION(BlueprintCallable)
      		FVector recursive_bezier(TArray<FVector>& points,float t);
    
      public:
      	UPROPERTY(VisibleAnywhere)
      		USceneComponent* root;
      	UPROPERTY(VisibleAnywhere)
      		UStaticMeshComponent* point0;
      	UPROPERTY(VisibleAnywhere)
      		UStaticMeshComponent* point1;
      	UPROPERTY(VisibleAnywhere)
      		UStaticMeshComponent* point2;
      	UPROPERTY(VisibleAnywhere)
      		UStaticMeshComponent* point3;
      	UPROPERTY(VisibleAnywhere)
      		UStaticMeshComponent* point4;
    
      	UPROPERTY();
      	TArray<FVector> m_points;
    
      	UPROPERTY(EditAnywhere);
      	bool m_bUseRecursiveBezier;
    
      };
      ```
    
  • AActor_BezierCuve.cpp

    点击查看代码
    #include "Actor_BezierCuve.h"
    #include "DrawDebugHelpers.h"
    #include <cmath>
    #include "Kismet/KismetSystemLibrary.h"
    
    // Sets default values
    AActor_BezierCuve::AActor_BezierCuve()
    {
     	// Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
    	PrimaryActorTick.bCanEverTick = true;
    	root = CreateDefaultSubobject<USceneComponent>(TEXT("root"));
    	SetRootComponent(root);
    
    	point0 = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("point0"));
    	point1 = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("point1"));
    	point2 = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("point2"));
    	point3 = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("point3"));
    	point4 = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("point4"));
    
    	point0->SetupAttachment(root);
    	point1->SetupAttachment(root);
    	point2->SetupAttachment(root);
    	point3->SetupAttachment(root);
    	point4->SetupAttachment(root);
    	m_points.Init(FVector::ZeroVector, 5);
    
    	m_bUseRecursiveBezier = false;
    }
    
    // Called when the game starts or when spawned
    void AActor_BezierCuve::BeginPlay()
    {
    	Super::BeginPlay();
    	m_points[0] = point0->GetComponentLocation();
    	m_points[1] = point1->GetComponentLocation();
    	m_points[2] = point2->GetComponentLocation();
    	m_points[3] = point3->GetComponentLocation();
    	m_points[4] = point4->GetComponentLocation();
    
    	if (!m_bUseRecursiveBezier)
    		naive_bezier();
    	else
    		bezier();
    }
    
    // Called every frame
    void AActor_BezierCuve::Tick(float DeltaTime)
    {
    	Super::Tick(DeltaTime);
    
    	
    }
    
    // 多项式 
    void AActor_BezierCuve::naive_bezier()
    {
    	FVector& p_0 = m_points[0];
    	FVector& p_1 = m_points[1];
    	FVector& p_2 = m_points[2];
    	FVector& p_3 = m_points[3];
    	FVector& p_4 = m_points[4];
    	for (double t = 0.0; t <= 1.0; t += 0.001)
    	{
    		auto point = std::pow(1 - t, 4) * p_0 + 4 * t * std::pow(1 - t, 3) * p_1 +
    			6 * std::pow(t, 2) * std::pow((1 - t), 2) * p_2 + 4 * std::pow(t, 3) * (1 - t) * p_3 + std::pow(t, 4) * p_4;
    		DrawDebugPoint(GetWorld(), point, 2.0f, FColor::Green,true,5.0f);
    		//UKismetSystemLibrary::PrintString(GetWorld(), point.ToString());
    	}
    	
    }
    
    void AActor_BezierCuve::bezier()
    {
    	for (double t = 0.0; t <= 1.0; t += 0.001)
    	{
    		FVector point = recursive_bezier(m_points, t);
    		DrawDebugPoint(GetWorld(), point, 2.0f, FColor(10,214,255,255),true,5.0f);
    	}
    }
    
    // De Casteljau 算法,递归
    FVector AActor_BezierCuve::recursive_bezier(TArray<FVector>& points, float t)
    {
    	if (points.Num() < 3) {
    		return (1 - t) * points[0] + t * points[1];
    	}
    
    	TArray<FVector> newPoint;
    	for (int i = 0; i < points.Num() - 1; i++) {
    		newPoint.Add((1 - t) * points[i] + t * points[i + 1]);
    	}
    	return recursive_bezier(newPoint, t);
    }
    
posted @ 2021-10-23 13:21  砥才人  阅读(1214)  评论(0编辑  收藏  举报