提交测试脚本
This commit is contained in:
388
Assets/Scripts/Test/Buoyancy2.cs
Normal file
388
Assets/Scripts/Test/Buoyancy2.cs
Normal file
@@ -0,0 +1,388 @@
|
||||
using UnityEngine;
|
||||
|
||||
[RequireComponent(typeof(Rigidbody))]
|
||||
[RequireComponent(typeof(Collider))]
|
||||
public class Buoyancy2 : MonoBehaviour
|
||||
{
|
||||
[Header("物理参数")] [SerializeField] private float waterDensity = 1000f; // 水的密度 (kg/m³)
|
||||
[SerializeField] private float gravity = 9.81f; // 重力加速度 (m/s²)
|
||||
[SerializeField] private float buoyancyMultiplier = 1f; // 浮力倍数
|
||||
|
||||
[Header("阻尼设置")] [SerializeField] private float linearDragInWater = 1f; // 水中线性阻尼
|
||||
[SerializeField] private float angularDragInWater = 0.5f; // 水中角阻尼
|
||||
[SerializeField] private float airLinearDrag = 0.1f; // 空气中线性阻尼
|
||||
[SerializeField] private float airAngularDrag = 0.05f; // 空气中角阻尼
|
||||
|
||||
[Header("水面设置")] [SerializeField] private float waterSurfaceLevel = 0f; // 水面高度
|
||||
[SerializeField] private bool autoDetectWaterLevel = true; // 自动检测水面高度
|
||||
|
||||
[Header("采样精度")] [SerializeField] private int sampleResolution = 5; // 采样分辨率 (每维采样点数)
|
||||
[SerializeField] private bool useAdaptiveSampling = true; // 使用自适应采样
|
||||
[SerializeField] private int maxSamples = 125; // 最大采样点数
|
||||
|
||||
[Header("稳定性控制")] [SerializeField] private float stabilityFactor = 0.3f; // 稳定性因子
|
||||
[SerializeField] private float rightingTorqueStrength = 2f; // 扶正扭矩强度
|
||||
[SerializeField] private bool enableMetacentricStability = true; // 启用稳心稳定性
|
||||
|
||||
[Header("调试显示")] [SerializeField] private bool showDebugInfo = false;
|
||||
[SerializeField] private bool drawGizmos = true;
|
||||
[SerializeField] private Color immersedColor = Color.blue;
|
||||
[SerializeField] private Color surfaceColor = Color.cyan;
|
||||
|
||||
// 私有变量
|
||||
private Rigidbody rb;
|
||||
private Collider objCollider;
|
||||
private Vector3[] samplePoints;
|
||||
private Vector3 centerOfBuoyancy; // 浮心位置
|
||||
private Vector3 centerOfGravity; // 重心位置
|
||||
private float totalVolume; // 总体积 (m³)
|
||||
private float immersedVolume; // 浸入体积 (m³)
|
||||
private float immersedRatio; // 浸入比例
|
||||
private Vector3 metacenter; // 稳心位置
|
||||
private bool isPartiallySubmerged;
|
||||
|
||||
// 缓存值
|
||||
private Vector3 lastPosition;
|
||||
private Quaternion lastRotation;
|
||||
private float lastWaterLevel;
|
||||
|
||||
void Start()
|
||||
{
|
||||
InitializeComponents();
|
||||
CalculatePhysicalProperties();
|
||||
InitializeSamplingGrid();
|
||||
ValidateSetup();
|
||||
}
|
||||
|
||||
void FixedUpdate()
|
||||
{
|
||||
UpdateWaterLevel();
|
||||
CalculateBuoyancy();
|
||||
ApplyForces();
|
||||
UpdateDrag();
|
||||
ApplyStabilityCorrection();
|
||||
}
|
||||
|
||||
void InitializeComponents()
|
||||
{
|
||||
rb = GetComponent<Rigidbody>();
|
||||
objCollider = GetComponent<Collider>();
|
||||
|
||||
if (rb == null)
|
||||
{
|
||||
Debug.LogError($"{gameObject.name}: 需要 Rigidbody 组件!", this);
|
||||
enabled = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (objCollider == null)
|
||||
{
|
||||
Debug.LogError($"{gameObject.name}: 需要 Collider 组件!", this);
|
||||
enabled = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// 保存初始重心位置
|
||||
centerOfGravity = rb.centerOfMass;
|
||||
}
|
||||
|
||||
void CalculatePhysicalProperties()
|
||||
{
|
||||
// 计算物体的真实体积
|
||||
totalVolume = CalculateRealVolume();
|
||||
|
||||
if (showDebugInfo)
|
||||
{
|
||||
Debug.Log(
|
||||
$"{gameObject.name} - 体积: {totalVolume:F4} m³, 质量: {rb.mass:F2} kg, 密度: {rb.mass / totalVolume:F2} kg/m³");
|
||||
}
|
||||
}
|
||||
|
||||
float CalculateRealVolume()
|
||||
{
|
||||
// 根据碰撞器类型精确计算体积
|
||||
if (objCollider is BoxCollider box)
|
||||
{
|
||||
Vector3 size = Vector3.Scale(box.size, transform.lossyScale);
|
||||
return size.x * size.y * size.z;
|
||||
}
|
||||
else if (objCollider is SphereCollider sphere)
|
||||
{
|
||||
float radius = sphere.radius *
|
||||
Mathf.Max(transform.lossyScale.x, transform.lossyScale.y, transform.lossyScale.z);
|
||||
return (4f / 3f) * Mathf.PI * Mathf.Pow(radius, 3);
|
||||
}
|
||||
else if (objCollider is CapsuleCollider capsule)
|
||||
{
|
||||
float radius = capsule.radius * Mathf.Max(transform.lossyScale.x, transform.lossyScale.z);
|
||||
float height = capsule.height * transform.lossyScale.y;
|
||||
return Mathf.PI * radius * radius * height;
|
||||
}
|
||||
else if (objCollider is MeshCollider mesh)
|
||||
{
|
||||
// 对于网格碰撞器,使用包围盒近似计算
|
||||
Bounds bounds = mesh.bounds;
|
||||
Vector3 size = Vector3.Scale(bounds.size, transform.lossyScale);
|
||||
return size.x * size.y * size.z * 0.6f; // 乘以填充系数
|
||||
}
|
||||
else
|
||||
{
|
||||
// 其他类型使用包围盒
|
||||
Bounds bounds = objCollider.bounds;
|
||||
return bounds.size.x * bounds.size.y * bounds.size.z;
|
||||
}
|
||||
}
|
||||
|
||||
void InitializeSamplingGrid()
|
||||
{
|
||||
int resolution = sampleResolution;
|
||||
|
||||
// 根据物体大小自适应调整采样精度
|
||||
if (useAdaptiveSampling)
|
||||
{
|
||||
float objectSize = objCollider.bounds.size.magnitude;
|
||||
resolution = Mathf.Clamp(Mathf.RoundToInt(objectSize * 10), 3, 8);
|
||||
}
|
||||
|
||||
// 限制最大采样点数
|
||||
while (resolution * resolution * resolution > maxSamples && resolution > 2)
|
||||
{
|
||||
resolution--;
|
||||
}
|
||||
|
||||
int totalSamples = resolution * resolution * resolution;
|
||||
samplePoints = new Vector3[totalSamples];
|
||||
|
||||
int index = 0;
|
||||
for (int x = 0; x < resolution; x++)
|
||||
{
|
||||
for (int y = 0; y < resolution; y++)
|
||||
{
|
||||
for (int z = 0; z < resolution; z++)
|
||||
{
|
||||
// 生成标准化坐标 [-0.5, 0.5]
|
||||
float nx = ((float)x / (resolution - 1)) - 0.5f;
|
||||
float ny = ((float)y / (resolution - 1)) - 0.5f;
|
||||
float nz = ((float)z / (resolution - 1)) - 0.5f;
|
||||
|
||||
samplePoints[index] = new Vector3(nx, ny, nz);
|
||||
index++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (showDebugInfo)
|
||||
{
|
||||
Debug.Log($"{gameObject.name} - 采样点数: {totalSamples}, 分辨率: {resolution}");
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateWaterLevel()
|
||||
{
|
||||
if (autoDetectWaterLevel)
|
||||
{
|
||||
// 可以在这里添加自动检测水面高度的逻辑
|
||||
// 例如射线检测或其他方法
|
||||
}
|
||||
}
|
||||
|
||||
void CalculateBuoyancy()
|
||||
{
|
||||
immersedVolume = 0f;
|
||||
centerOfBuoyancy = Vector3.zero;
|
||||
int immersedCount = 0;
|
||||
|
||||
Bounds bounds = objCollider.bounds;
|
||||
Vector3 boundsCenter = bounds.center;
|
||||
Vector3 boundsExtents = bounds.extents;
|
||||
|
||||
foreach (Vector3 normalizedPoint in samplePoints)
|
||||
{
|
||||
// 将标准化坐标转换为世界坐标
|
||||
Vector3 worldPoint = boundsCenter + Vector3.Scale(normalizedPoint, boundsExtents * 2f);
|
||||
|
||||
if (worldPoint.y < waterSurfaceLevel)
|
||||
{
|
||||
immersedVolume += 1f;
|
||||
centerOfBuoyancy += worldPoint;
|
||||
immersedCount++;
|
||||
}
|
||||
}
|
||||
|
||||
// 计算浸入比例
|
||||
immersedRatio = (float)immersedCount / samplePoints.Length;
|
||||
|
||||
// 计算实际浸入体积
|
||||
immersedVolume = totalVolume * immersedRatio;
|
||||
|
||||
// 计算浮心位置
|
||||
if (immersedCount > 0)
|
||||
{
|
||||
centerOfBuoyancy /= immersedCount;
|
||||
}
|
||||
else
|
||||
{
|
||||
centerOfBuoyancy = rb.worldCenterOfMass;
|
||||
}
|
||||
|
||||
isPartiallySubmerged = immersedRatio > 0f && immersedRatio < 1f;
|
||||
}
|
||||
|
||||
void ApplyForces()
|
||||
{
|
||||
if (immersedRatio <= 0f) return;
|
||||
|
||||
// 计算浮力: F = ρ × V × g
|
||||
float buoyancyForceMagnitude = waterDensity * immersedVolume * gravity * buoyancyMultiplier;
|
||||
|
||||
// 浮力方向始终向上
|
||||
Vector3 buoyancyForce = Vector3.up * buoyancyForceMagnitude;
|
||||
|
||||
// 在浮心位置施加浮力
|
||||
rb.AddForceAtPosition(buoyancyForce, centerOfBuoyancy, ForceMode.Force);
|
||||
|
||||
// 添加由于浮力分布不均产生的扭矩
|
||||
if (isPartiallySubmerged)
|
||||
{
|
||||
Vector3 buoyancyTorque = Vector3.Cross(centerOfBuoyancy - rb.worldCenterOfMass, buoyancyForce);
|
||||
rb.AddTorque(buoyancyTorque * stabilityFactor, ForceMode.Force);
|
||||
}
|
||||
|
||||
if (showDebugInfo && immersedRatio > 0.1f)
|
||||
{
|
||||
Debug.DrawRay(centerOfBuoyancy, buoyancyForce * 0.01f, Color.blue, Time.fixedDeltaTime);
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateDrag()
|
||||
{
|
||||
// 根据浸入程度插值阻尼系数
|
||||
float currentLinearDrag = Mathf.Lerp(airLinearDrag, linearDragInWater, immersedRatio);
|
||||
float currentAngularDrag = Mathf.Lerp(airAngularDrag, angularDragInWater, immersedRatio);
|
||||
|
||||
rb.linearDamping = currentLinearDrag;
|
||||
rb.angularDamping = currentAngularDrag;
|
||||
}
|
||||
|
||||
void ApplyStabilityCorrection()
|
||||
{
|
||||
if (!enableMetacentricStability || immersedRatio <= 0f) return;
|
||||
|
||||
// 计算稳心位置(简化模型)
|
||||
CalculateMetacenter();
|
||||
|
||||
// 应用扶正扭矩
|
||||
if (isPartiallySubmerged)
|
||||
{
|
||||
Vector3 rightingArm = metacenter - rb.worldCenterOfMass;
|
||||
Vector3 restoringTorque = Vector3.Cross(rightingArm, Vector3.down) * rightingTorqueStrength * immersedRatio;
|
||||
rb.AddTorque(restoringTorque, ForceMode.Force);
|
||||
}
|
||||
}
|
||||
|
||||
void CalculateMetacenter()
|
||||
{
|
||||
// 简化的稳心计算:假设稳心在浮心上方一定距离
|
||||
float metacentricHeight = Mathf.Max(objCollider.bounds.size.y * 0.3f, 0.1f);
|
||||
metacenter = centerOfBuoyancy + Vector3.up * metacentricHeight;
|
||||
}
|
||||
|
||||
void ValidateSetup()
|
||||
{
|
||||
// 检查物体密度是否合理
|
||||
float objectDensity = rb.mass / totalVolume;
|
||||
if (objectDensity < waterDensity * 0.1f)
|
||||
{
|
||||
Debug.LogWarning($"{gameObject.name}: 物体密度过低 ({objectDensity:F2} kg/m³),可能导致异常行为");
|
||||
}
|
||||
else if (objectDensity > waterDensity * 3f)
|
||||
{
|
||||
Debug.LogWarning($"{gameObject.name}: 物体密度过高 ({objectDensity:F2} kg/m³),可能快速沉没");
|
||||
}
|
||||
}
|
||||
|
||||
// 公共接口方法
|
||||
public float GetImmersedRatio() => immersedRatio;
|
||||
public float GetImmersedVolume() => immersedVolume;
|
||||
public Vector3 GetCenterOfBuoyancy() => centerOfBuoyancy;
|
||||
public bool IsSubmerged() => immersedRatio >= 0.99f;
|
||||
public bool IsFloating() => immersedRatio > 0f && immersedRatio < 0.99f;
|
||||
|
||||
public void SetWaterLevel(float level)
|
||||
{
|
||||
waterSurfaceLevel = level;
|
||||
autoDetectWaterLevel = false;
|
||||
}
|
||||
|
||||
public void SetBuoyancyMultiplier(float multiplier)
|
||||
{
|
||||
buoyancyMultiplier = Mathf.Clamp(multiplier, 0.1f, 5f);
|
||||
}
|
||||
|
||||
void OnDrawGizmosSelected()
|
||||
{
|
||||
if (!drawGizmos || !Application.isPlaying) return;
|
||||
|
||||
// 绘制浮心
|
||||
Gizmos.color = Color.blue;
|
||||
Gizmos.DrawSphere(centerOfBuoyancy, 0.05f);
|
||||
|
||||
// 绘制重心
|
||||
if (rb != null)
|
||||
{
|
||||
Gizmos.color = Color.red;
|
||||
Gizmos.DrawSphere(rb.worldCenterOfMass, 0.05f);
|
||||
}
|
||||
|
||||
// 绘制稳心
|
||||
if (enableMetacentricStability)
|
||||
{
|
||||
Gizmos.color = Color.green;
|
||||
Gizmos.DrawSphere(metacenter, 0.03f);
|
||||
}
|
||||
|
||||
// 绘制采样点
|
||||
if (samplePoints != null && objCollider != null)
|
||||
{
|
||||
Bounds bounds = objCollider.bounds;
|
||||
Vector3 boundsCenter = bounds.center;
|
||||
Vector3 boundsExtents = bounds.extents;
|
||||
|
||||
foreach (Vector3 normalizedPoint in samplePoints)
|
||||
{
|
||||
Vector3 worldPoint = boundsCenter + Vector3.Scale(normalizedPoint, boundsExtents * 2f);
|
||||
|
||||
if (worldPoint.y < waterSurfaceLevel)
|
||||
{
|
||||
Gizmos.color = immersedColor;
|
||||
}
|
||||
else
|
||||
{
|
||||
Gizmos.color = surfaceColor;
|
||||
}
|
||||
|
||||
Gizmos.DrawSphere(worldPoint, 0.01f);
|
||||
}
|
||||
}
|
||||
|
||||
// 绘制水面线
|
||||
if (Camera.current != null)
|
||||
{
|
||||
Vector3 cameraPos = Camera.current.transform.position;
|
||||
Vector3 waterCenter = new Vector3(cameraPos.x, waterSurfaceLevel, cameraPos.z);
|
||||
Vector3 waterSize = new Vector3(10f, 0f, 10f);
|
||||
|
||||
Gizmos.color = Color.cyan;
|
||||
Gizmos.DrawWireCube(waterCenter, waterSize);
|
||||
}
|
||||
}
|
||||
|
||||
void OnValidate()
|
||||
{
|
||||
// 参数验证
|
||||
sampleResolution = Mathf.Clamp(sampleResolution, 2, 10);
|
||||
buoyancyMultiplier = Mathf.Clamp(buoyancyMultiplier, 0.1f, 5f);
|
||||
stabilityFactor = Mathf.Clamp(stabilityFactor, 0f, 1f);
|
||||
rightingTorqueStrength = Mathf.Clamp(rightingTorqueStrength, 0f, 10f);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user