随机生成植物生长及舞动算法

本文介绍了使用分形和放样建模技术生成随机树木的算法,通过重构递归函数实现非递归结构,模拟树木生长、随风舞动的动态效果。作者探讨了树木形态的数学规律,包括分形、达芬奇公式、斐波那契数列和黄金分割,并提供了可调整的参数来控制树枝形态。算法涉及枝条计算、骨骼动画模型生成,同时提到了生长动画和风力动画的实现方法。此外,还分享了数据结构和类的设计,如BranchType和BranchMeshType。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

        几年前写过一套随机树木的生成算法,其中使用了分形和放样建模。那时候还不知道有speedtree这款软件,写的比较粗糙,最近看了speedtree的演示把原算法改进了一下,算是一个speedtree的简化版本。
        重构主要是把原先使用递归函数实现的分形过程改为了非递归的,这样结构更清晰一些,尽量把树木生成分拆为若干独立步骤,减少每个步骤之间的耦合。使用骨骼动画模拟树木生长及随风舞动。

搜集的一些关于树木形态的数学规律:
分形:每条分枝就像一颗经过旋转的独立树。
达芬奇公式:当树木分岔时,树干的粗度等于同一高度树枝的总粗度,工程学的角度发现这样的结构最能抵抗强风。
一般树冠分为六种形态:长型、球型、椭圆型、瓶型,金字塔型和垂枝型。这跟顶端优势是否明显有关系,如果顶端优势明显,向两边生长就会抑制,形成高大的树,如松树;如果顶端优势不明显,树就会倾向于横向生长,如橡树。树冠的形态和生长地域息息相关,寒冷地区的针叶树,树冠比温带地区要小,而且呈金字塔状,这样的结构可以防止在下雪的时候侧枝积累过多的重量而折断。
分叉很多时候符合斐波纳契数列(Fibonacci Sequence),形如[1、1、2、3、5、8、13、21],F0=0,F1=1,Fn=F(n-1)+F(n-2)。越往后面,相邻两个斐波纳契数的比(1/2, 2/3, 3/5, 5/8, 8/13)越近似等于0.618黄金分割。在发育成树枝的叶腋的排列中,记任意一个点为0然后往上数,直到另一个相同位置出现叶腋,其中的周数/叶腋数(1/2, 1/3, 2/5, 3/8, 8/13)很多都是斐波那契数的相邻两个。这种排列方式可以保证在空间上错落开,充分利用阳光。分叉黄金角为137.51度。

    可调整分支参数:
        BranchType     branchType;  //分支类型
        BranchMeshType meshType; //模型类型
        RectF texRect          ;  //纹理
        int   branchNum        ;  //当前层枝条数
        float length           ;  //枝干长度
        float radiusBottom     ;  //枝干底部半径
        float radiusTop        ;  //枝干顶部半径
        float sizeVariation    ;  // 大小异性
        float bend             ;  //枝干弯曲度
        float gravityW         ;  //全局重力(world) 
        float gravityL         ;  //局部重力(local 平行父枝干方向)
        float gravityC         ;  //局部向心力(center垂直指向父枝干方向) 适用于花瓣内收
        int   pathStepNum      ;  //长度段数
        int   shapeStepNum     ;  //切面段数
        bool  axisXAlign       ;  //是否将x轴放到父枝的垂直面上,树叶(花瓣)的放样才正确
        float pitchAng         ;  //当前层枝条与父枝的夹角 pitch
        float pitchAngVariation;  //pitchAng差异范围 
        float rotAng           ;  //当前层枝条与父枝的旋转递增夹角 rot 一般为黄金角137.51,君子兰对称为180 十字花90
        //float rotAngVariation  ; 

        float minOffset        ;  //当前层枝条在父枝上分叉点高度偏移 
        float maxOffset        ;  //.......最大
        float offsetRelax      ;  //分叉均匀度,0度时-分叉点集中到每节的起始处

        //叶子参数
        float    leafConvex    ; //叶子横向凹凸 (放样形状)
        String   leafMesh      ;

        //风力动画
        float windWeight       ; //风动系数

        //生长动画
        float matureTime       ; //成熟时间[0~1],成熟后大小不变,成熟前线性长大并且被父枝的生长大小叠加缩放
        //生长期纹理融合 in treedef

参数调整技巧:
垂柳:  设置全局重力参数gravityW
花    :    花瓣可以使用多层树叶代替,顶层和底层花瓣向心力不同、局部重力不同,模拟出花瓣的不同弧度。
球形树:设置分枝pitchAng=0、pitchAngVariation=180,在分枝的末端添加树叶pitchAng=90、pitchAngVariation=0。 
草丛:  可以使用一个微小的假主干,或者将主干数目设置为大于1。
树叶不一定是末端,也可以再分出次级树枝、树叶或花。

 算法大部分代码集中在分枝路径的计算和骨骼动画模型的生成,大体的步骤:
1、计算枝条个数(因为不想用动态数组,所以先计算个数分配空间)
2、构造枝条结构
3、计算骨骼个数
4、构造骨骼结构
5、计算枝条曲线、放样形状。这一步代码较多,路径受弯曲度影响分形叠加曲线,路径受重力、局部重力、向心力等影响。放样形状可以是闭合曲线(枝条)或弯曲线(面片,十字面片)。
6、计算骨骼矩阵
7、生成骨骼动画驱动的树
其中只有枝条计算和骨骼计算有一些穿插耦合,因为枝条曲线的计算需要用到父骨骼的矩阵,骨骼矩阵又需要根据自身枝条曲线计算,不好完全独立开。

数据结构:


enum BranchType
{
	BT_Trunk , //树干
	BT_Branch, //树枝
	BT_Leaves, //树叶
	BT_Root  , //树根
	BT_Flower, //花、果实
};
const char* BranchTypeToString(int enumeration);
bool StringToBranchType(const char* str,int& type);

enum BranchMeshType   //模型类型
{
	MT_Strip       , //线放样
	MT_Cross       , //十字放样
	MT_Cylinder    , //圆放样
	MT_CylinderCap , //圆放样封顶
	MT_Billboard   , //
	MT_Sphere      , //
	MT_Mesh        , //
};
const char* BranchMeshTypeToString(int enumeration);
bool StringToBranchMeshType(const char* str,int& type);

//随机树配置
class TreeDef
{
public:
#define  MaxDepth        4
#define  MaxShapeStep    16
#define  MaxPathStep     8
#define  MaxSubBranch    50	

#define  MaxSubBranchDef 3
#define  MaxBranchDef    32 //128

#define  MaxGrowTex      4

	enum Method
	{
		TT_StaticAndUV,  //静态树 纹理动画
		TT_AnimBone   ,  //绑定骨骼动画
	};

	//每一级分枝
	class BranchDef
	{
	public:
		char  name[64]         ;
		int   ID               ;
		int   parentID         ;
		int   depth            ;  //层深度
		//int   seed             ;  //单独seed
		BranchType     branchType;  //分支类型
		BranchMeshType meshType; 
		RectF texRect          ;  //纹理
		int   branchNum        ;  //当前层枝条数
		//todo枝长曲线  lengthBottom lengthCen lengthTop
		float length           ;  //枝干长度
		float radiusBottom     ;  //枝干底部半径
		float radiusTop        ;  //枝干顶部半径
		float sizeVariation    ;  //   
		float bend             ;  //枝干弯曲度(1-笔直度) 弯曲度和俯仰角效果有重叠? 点在和路径垂直的横截面上偏移 起点无偏移 末端在横截面的圆环上
		float gravityW         ;  //全局重力(world) 路径受重力的影响
		float gravityL         ;  //局部重力(local平行父枝干方向)
		float gravityC         ;  //局部向心力(center垂直指向父枝干方向) 适用于花瓣内收
		int   pathStepNum      ;  //长度段数
		int   shapeStepNum     ;  //切面段数
		bool  axisXAlign       ;  //将x轴放到父枝的垂直面上,树叶(花瓣)的放样才正确

		//
		float pitchAng         ;  //当前层枝条与父枝的夹角 pitch
		float pitchAngVariation;  //pitchAng差异范围 
		float rotAng           ;  //当前层枝条与父枝的旋转递增夹角 rot 一般为黄金角137.51,君子兰对称为180 十字花90
		//float rotAngVariation  ; 

		float minOffset        ;  //当前层枝条在父枝上分叉点高度偏移 
		float maxOffset        ;  
		float offsetRelax      ;  //分叉均匀度,0度时-分叉点集中到每节的起始处

		//叶子参数
		float    leafConvex    ; //叶子横向凹凸 (放样形状)
		String   leafMesh      ;

		//风力动画
		float windWeight       ; //风动系数
		float windWeightSq     ; //修正风力权重呈平方增长,且避免段数越大弯曲累积越多

		//生长动画
		float matureTime       ; //成熟时间[0~1],成熟后大小不变,成熟前线性长大并且被父枝的生长大小叠加缩放
		//生长期纹理融合 in treedef

		BranchDef* parent;
		int        subDefNum;
		BranchDef* subDef[MaxSubBranchDef];

		int   numW;
		RectF uiRect; //for diagram edit
	};

	TreeDef();

	void RandGen();
	void RandChange();
	void Load(const char* fileName);
	void Save(const char* fileName);
	BranchDef* GetBranchDef(int ID);
	void GenTopology();
	void UpdateAllItems(ListItemUpdater& updater);

public:
	String m_fileName;
	String m_texture ;
	//生长期纹理融合 
	int    m_growTexNum;
	String m_growTexture[MaxGrowTex];
	float  m_growTexTime[MaxGrowTex]; //在此生长时间点开始改变材质

	Method m_method  ;
	int    m_seed    ;  

	BranchDef* m_trunkDef;  //主干,规定只有一个trunkdef,其它未连接parentID的分枝不被构造。
	int        m_branchDefNum;
	BranchDef  m_branchDef[MaxBranchDef];

	//mesh
	//RendSys::MovieClip* m_movieLib;
};

//随机树模型生成及动画更新器 
class TreeProducer
{
public:
	class TBone;
	class Branch;
	TreeProducer();
	~TreeProducer();
	void Free();
	void EditRender();
	void GenRandTree(Mesh* mesh,Skeleton* skeleton,TreeDef* def,int randSeed=-1);

//private:
	//生成静态树:树叶和树枝纹理drawcall分开,树叶可以单独uv动画
	void GenRandTreeStaticAndUV(Mesh* mesh,TreeDef* def,int randSeed=-1);	

	//生成骨骼动画驱动的树:骨骼动画为了加速后期可以塌陷到morph,当skeleton为空时生成静态树
	void GenRandTreeAnimBone (Mesh* mesh,Skeleton* skeleton,TreeDef* def,int randSeed=-1);	
	void CalTBoneMatrix      (Branch* branch);
	void GenRendMeshSkeleton (Mesh* mesh,Skeleton* skeleton);	
	void RefreshRendSkeleton   (Skeleton* skeleton,int frame,int offset);
	void RefreshRendMixSkeleton(MixSkeleton* mixSkeleton,int frame,int offset);
	//生长 0~1
	void RefreshGrow(float life);
	//风的角度和风力,如果叠加生长动画,则RefreshGrow调用要在RefreshWind前面
	void RefreshWind(TreeWind* wind);

	class Branch
	{
	public :
		Branch();
		~Branch();

	public:
		int   indexG;  //在全局分枝中的索引
		int   indexL;  //在父枝分枝中的索引
		int   indexLT; //在父枝同类型分枝中的索引
		TreeDef::BranchDef* def; //
		TBone* bones; //arr[path step num+1]


		float lengthScale  ;  //枝干长度半径缩放系数

		float stemOffset   ;  //在父枝干上的分叉位置[0~parentDef->pathStepNum]
		float stemRot      ;  //分支方向
		float stemAngle    ;  //pitch

		//枝成熟期原始造型
		vec3  shapePos   [MaxShapeStep+1] ;   //放样形状坐标    
		vec3  shapeNormal[MaxShapeStep+1] ;   //放样形状法线  
		vec3  pathPos    [MaxPathStep+1 ] ;   //放样路径坐标    

		int      subBranchNum; //multi type
		Branch** subBranchs;
		Branch*  parent;
		TexturePtr m_texture;
	};

	class TBone
	{
	public :
		TBone();
		~TBone();

	public:
		int     index      ; //indexW
		Branch* branch     ; //
		TBone*  parent     ;
		int     subBoneNum ;
		TBone** subBones   ;	

		mat4    matL       ; //原始局部矩阵
		mat4    matLGrow   ; //经过生长动画模拟后的局部矩阵,不叠加风力,若未执行生长模拟则等于matL
		mat4    matLWind   ; //经过生长动画模拟+风力后的局部矩阵,使用matLGrow作为原始局部矩阵叠加风力,若未执行生长模拟则等于matLGrow
		mat4    matW       ; //世界矩阵
		vec3    axisYW     ;

		风动
		//vec3  windRotSpeed; //角速度 此处使用简化的快速模拟,真实的物理模拟对每根骨骼创建刚体速度太慢

		//生长
		float   matureTime ; //成熟时间[0~1],大小随时间呈ln曲线变化,成熟时达到0.8f,成熟后大小微变,成熟前线性长大并且被父枝的生长大小叠加缩放
		float   scaleW     ;
	};

//private:
public :
	int      m_seed;

	Branch*  m_branchs  ; //主干chunk可以有多个
	int      m_branchNum;	
	TBone*   m_bones;
	int      m_boneNum;
};

生长动画算法:

很短也许不是最好的,但是比较快速,主要是逐级改变骨骼缩放。

void TreeProducer::RefreshGrow(float life)
{
	Clamp(life,0.0f,1.0f);
	mat4  matScale;
	float scaleL;
	float scaleL2; //y轴非线性缩放
	float matureTime;
	TBone* bone = m_bones;
	for(int i = 0; i < m_boneNum; ++i,++bone)
	{
		if(bone->parent)
		{
			matureTime = bone->branch->def->matureTime;
			//scaleW-time 为过三点的折线
			if(matureTime==1)
				bone->scaleW = 0.8f * life / matureTime;
			else if(life >= matureTime || matureTime==0)
				bone->scaleW = 0.8f + 0.2f*(life-matureTime)/(1-matureTime);
			else 
				bone->scaleW = 0.8f * life / matureTime;

			if (bone->parent->scaleW<_EPSILON)
			{
				scaleL  = 0;
				scaleL2 = 0;
			}
			else
			{
				scaleL  = bone->scaleW/bone->parent->scaleW;
				scaleL2 = bone->scaleW/sqrt(bone->parent->scaleW);
			}

			//优化展开
			matScale.FromScale(scaleL,scaleL2,scaleL);
			bone->matLGrow = matScale * bone->matL; 
			bone->matW = bone->parent->matW * bone->matLGrow;
		}
		else
		{
			bone->scaleW = 1;
		}
	}
}

随风舞动动画模拟:

        此处使用简化的快速模拟。真实的物理模拟需要对每根骨骼创建刚体,模拟风的作用力,速度太慢。如果同时开启生长模拟和风力模拟,则风力模拟需要放在生长模拟之后计算。

//简化的快速风动模拟
class TreeWind
{
public:
	TreeWind();
	void Random();
	void Update();
	const vec3& GetWindDir();
	float GetWindForce();

	//参数
	float strength  ; //强度
	float turbulence; //湍流强度
	float frequency ; //湍流频率  周期=TWOPI/frequency

private:
	//输出
	vec3    m_tree3DWindDir;
	float   m_tree3DWindForce;
	vec3    m_tree3DWindAngSpeed;
};

void TreeWind::Update()
{
	float acumTime = G_Timer->GetAccumTime();
	float time     = G_Timer->GetStepTimeLimited();
	m_tree3DWindForce = (
		sin(acumTime*frequency)*0.7f
		+sin(acumTime*frequency*2.337f+0.123f)*0.2f
		//+sin(acumTime*frequency*5.537f+0.823f)*0.1f
						 )*strength; //周期=TWOPI/frequency

	vec3 angAcc;
	RandDir(angAcc);
	m_tree3DWindAngSpeed += angAcc*0.2f*time;
	m_tree3DWindAngSpeed.Normalize();
	mat4 mat;
	mat.FromAxisAngle(m_tree3DWindAngSpeed,RandRange(60.0f,100.0f)*turbulence*DEG2RAD*time);
	m_tree3DWindDir = mat*m_tree3DWindDir;
}

void TreeProducer::RefreshWind(TreeWind* wind)
{
	const vec3& windDir = wind->GetWindDir();
	float windForce = wind->GetWindForce();
	float windWeight;
	vec3  axisY(0,1,0);
	mat4  rot;
	vec3  axis;
	TBone* bone = m_bones;
	TreeDef::BranchDef* branchDef;
	for(int i = 0; i < m_boneNum; ++i,++bone)
	{
		branchDef  = bone->branch->def;
		if(bone->parent)
		{
			windWeight = branchDef->windWeightSq;
			if (windWeight>_EPSILON)
			{
				CrossVec3(axis,bone->axisYW,windDir);
				float angRad = (windForce+0.4f) * windWeight * LengthVec3(axis); //LengthVec3(axis)减少抖动
				rot.FromAxisAngle(axis,angRad);
				bone->matLWind = bone->matLGrow * rot;
			}
			else
			{
				bone->matLWind = bone->matLGrow;
			}
			bone->matW = bone->parent->matW * bone->matLWind;
			bone->axisYW = axisY;
			bone->matW.AffectNormal(bone->axisYW);
		}
	}
}

<think>好的,用户想了解单片机如何检测输电线路的断线和舞动情况。首先,我需要回顾一下相关的技术方法。根据之前看到的引用资料,比如单片机通过运放检测电流的方法,这可能和断线检测相关。断线通常会导致电流变化,所以可能用电流检测来判断。而舞动可能涉及振动或位移的测量,可能需要加速度传感器或摄像头。 输电线路断线检测方面,常用的方法可能有电流监测、电压监测和光纤传感。电流监测的话,可以用运放电路来检测电流是否突然下降或为零,这可能表示断线。引用[1]提到低端运放电流检测方法,可能可以应用在这里。电压监测的话,断线后电压可能会异常,比如出现电压骤升或骤降,通过分压电路和ADC检测。光纤传感技术如果有断线,光信号会变化,但需要相应的光电转换模块。 输电线路舞动检测方面,通常需要检测导线的振动和位移。加速度传感器可以监测振动频率和幅度,比如使用MEMS传感器,通过I2C或SPI接口连接单片机。图像识别的话,可能需要摄像头或红外传感器,但这对单片机处理能力要求较高,可能需要外置处理单元。角度检测可以用陀螺仪或倾角传感器,测量导线的摆动角度。 然后考虑如何整合这些传感器数据到单片机。可能需要多路ADC来采集电流、电压信号,数字接口连接加速度计和陀螺仪。对于图像处理,可能需要额外的模块。通信方面,断线和舞动的数据可能需要通过无线模块(如4G、LoRa)传输到监控中心,所以单片机需要集成或外接通信模块。 还要考虑环境因素,比如高压输电线路的电磁干扰,需要隔离和保护电路,比如光耦隔离和屏蔽措施。电源方面,可能需要太阳能加蓄电池,或者从输电线路感应取电,但需要稳定电源设计,引用[2]提到掉电检测和数据保存,可能用到法拉电容来应对突然断电。 然后需要验证方案的可行性,比如电流检测的精度是否足够,加速度传感器的灵敏度是否适合检测舞动,通信模块的可靠性等。可能需要实际测试和调整参数。 最后,用户可能需要相关的扩展问题,比如如何优化检测算法,如何处理多节点数据,或者如何提升抗干扰能力。根据这些思考,组织回答的结构,分断线和舞动的不同检测方法,硬件设计和数据处理,并给出相关问题。</think>### 单片机检测输电线路断线与舞动的实现方法 #### 一、断线检测方案 1. **电流监测法** - 通过运放电路检测线路电流突变,断线时电流骤降或归零。 - 使用低端运放电流检测电路,将采样电阻串联在线路中,运放放大电压信号后由单片机ADC采集[^1]。 - 算法:设定阈值,当电流低于阈值时触发断线报警。 - 电路示例: ``` 线路 → 采样电阻 → 运放差分放大 → ADC输入 → 单片机 ``` 2. **电压监测法** - 检测线路电压异常(如电压骤升或骤降),需配合分压电路和光耦隔离。 - 使用高压分压电阻将输电电压降至单片机可接受范围(如0-3.3V)。 - 光耦隔离保护单片机免受高压干扰。 3. **光纤传感法** - 在输电线内嵌光纤,通过光信号变化判断断线。 - 断线导致光纤弯曲或断裂,光强衰减,光电转换模块输出信号变化。 - 单片机通过GPIO或ADC监测光强信号。 #### 二、舞动检测方案 1. **加速度传感器监测** - 使用MEMS加速度传感器(如MPU6050)检测导线振动频率和幅度。 - 传感器通过I2C接口与单片机通信,实时采集三轴加速度数据。 - 算法:分析振动频谱,当频率超过阈值(如1-5Hz)且幅度大时判定为舞动。 2. **图像识别法** - 部署摄像头或红外传感器拍摄导线运动轨迹。 - 单片机通过USB或并行接口连接图像处理模块(如OpenMV),识别导线摆动幅度[^3]。 - 局限性:需较高算力,通常需外置处理器。 3. **倾角/陀螺仪检测** - 使用倾角传感器(如SCA60C)测量导线摆动角度。 - 角度数据通过SPI传输至单片机,结合时间序列分析舞动周期。 #### 三、系统整合与通信 1. **硬件设计** - 多传感器集成:电流/电压检测、加速度计、倾角传感器共用单片机ADC和数字接口。 - 抗干扰设计: - 高压侧与低压侧采用光耦或磁耦隔离。 - PCB布局时模拟与数字部分分开,加入屏蔽层。 2. **数据处理** - 实时滤波:对ADC采样数据使用滑动平均或卡尔曼滤波。 - 异常判定:设定多级阈值(如电流连续10个周期低于阈值才触发报警)。 3. **通信方案** - 无线传输:通过4G/LoRa模块上报数据至监控中心。 - 本地存储:使用SPI Flash或SD卡缓存历史数据,防止通信中断导致数据丢失[^2]。 #### 四、典型应用场景 - **山区输电线路**:采用加速度传感器+LoRa无线传输,适应复杂地形。 - **城市电网**:结合图像识别和电流监测,实现高精度断线定位。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值