Files
Fishing2/Assets/Scripts/Test/Buoyancy2.cs
2026-03-02 00:03:29 +08:00

388 lines
13 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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);
}
}