388 lines
13 KiB
C#
388 lines
13 KiB
C#
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);
|
||
}
|
||
} |