Cesium for unity (可加载3dTileset)

如果不联网,可以加载本地3dTileset文件,可以获取大地坐标。(参考下图)

如果联网可以加载地球瓦片高程,附近地形会显现

登上账号后

没有登账号

剖分网格配合Cesium for unity

cs 复制代码
using CesiumForUnity;
using System;
using System.Collections.Generic;
using UnityEngine;
using Vector3 = UnityEngine.Vector3;
using Matrix4x4 = UnityEngine.Matrix4x4;

public class GeoSotManager : MonoBehaviour
{
    public static GeoSotManager instance;

    [Header("场景原点(自动从Cesium读取)")]
    [HideInInspector] public double originLon = 116.3972;
    [HideInInspector] public double originLat = 39.9075;
    [HideInInspector] public double originHeight = 35.0;

    [Header("GeoSOT分层级配置【推荐:14/8】")]
    [Range(1, 32)] public int lonLatLevel = 14;
    [Range(1, 24)] public int heightLevel = 8;

    [Header("GeoSOT全局范围配置")]
    public double heightMin = -500.0;
    public double heightMax = 1500.0;

    [Header("Cesium 入口")]
    public CesiumGeoreference geoReference;
    public Camera mainCamera;

    // ✅ 替换BigInteger → GeoSOT96
    private Dictionary<GeoSOT96, List<ModelPart>> dic_model = new Dictionary<GeoSOT96, List<ModelPart>>();
    private List<ModelPart> registeredParts = new List<ModelPart>();

    #region 单例与初始化
    private void Awake()
    {
        if (instance == null)
        {
            instance = this;
            DontDestroyOnLoad(gameObject);
        }
        else if (instance != this)
        {
            Destroy(gameObject);
            return;
        }
        InitOriginData();
    }

    private void OnDestroy()
    {
        if (instance == this) instance = null;
    }

    private void Start()
    {
        RebuildAllIndex();
    }

    private void InitOriginData()
    {
        if (geoReference != null)
        {
            originLon = geoReference.longitude;
            originLat = geoReference.latitude;
            originHeight = geoReference.height;
            Debug.Log($"✅ Cesium原点读取成功:lon={originLon:F6}, lat={originLat:F6}, height={originHeight:F2}");
        }
        else
        {
            Debug.LogWarning("⚠️ 未赋值CesiumGeoreference,使用默认原点");
        }
    }

    public void RebuildAllIndex()
    {
        dic_model.Clear();
        foreach (var part in registeredParts)
        {
            if (part != null && part.isActiveAndEnabled)
            {
                part.InitValue();
                AddPartToIndex(part);
            }
        }
        Debug.Log($"✅ 全局索引重建完成,共注册{registeredParts.Count}个模型部件");
    }

    public void RegisterPart(ModelPart part)
    {
        if (part == null || registeredParts.Contains(part)) return;
        registeredParts.Add(part);
    }

    public void UnregisterPart(ModelPart part)
    {
        if (part == null || !registeredParts.Contains(part)) return;
        registeredParts.Remove(part);
        RemovePartFromIndex(part);
    }

    private void AddPartToIndex(ModelPart part)
    {
        foreach (var code in part.geoSOTAllCodes)
        {
            if (!dic_model.ContainsKey(code))
                dic_model[code] = new List<ModelPart>();
            if (!dic_model[code].Contains(part))
                dic_model[code].Add(part);
        }
    }

    private void RemovePartFromIndex(ModelPart part)
    {
        foreach (var code in part.geoSOTAllCodes)
        {
            if (dic_model.TryGetValue(code, out var list))
            {
                list.Remove(part);
                if (list.Count == 0)
                    dic_model.Remove(code);
            }
        }
    }
    #endregion

    #region 基础工具
    public Vector3[] GetBoxCorners(Bounds bounds)
    {
        Vector3 min = bounds.min;
        Vector3 max = bounds.max;
        return new Vector3[8]
        {
            new Vector3(min.x, min.y, min.z),
            new Vector3(max.x, min.y, min.z),
            new Vector3(min.x, max.y, min.z),
            new Vector3(max.x, max.y, min.z),
            new Vector3(min.x, min.y, max.z),
            new Vector3(max.x, min.y, max.z),
            new Vector3(min.x, max.y, max.z),
            new Vector3(max.x, max.y, max.z)
        };
    }
    #endregion

    #region 坐标转换
    public LonLatHeight[] ConvertBoxToWGS84(Bounds aabb, Matrix4x4 localToWorldMatrix)
    {
        Vector3[] localCorners = GetBoxCorners(aabb);
        LonLatHeight[] wgs84Points = new LonLatHeight[8];
        for (int i = 0; i < 8; i++)
        {
            Vector3 worldPos = localToWorldMatrix.MultiplyPoint3x4(localCorners[i]);
            wgs84Points[i] = CameraGeoSOTUtility.UnityToWGS84(
                worldPos, originLon, originLat, originHeight);
        }
        return wgs84Points;
    }

    public void GetBox3DRange(LonLatHeight[] points,
        out double lonMin, out double lonMax,
        out double latMin, out double latMax,
        out double hMin, out double hMax)
    {
        lonMin = latMin = hMin = double.MaxValue;
        lonMax = latMax = hMax = double.MinValue;

        foreach (var p in points)
        {
            lonMin = Math.Min(lonMin, p.lon);
            lonMax = Math.Max(lonMax, p.lon);
            latMin = Math.Min(latMin, p.lat);
            latMax = Math.Max(latMax, p.lat);
            hMin = Math.Min(hMin, p.height);
            hMax = Math.Max(hMax, p.height);
        }
    }
    #endregion

    #region 核心编码解码(96bit 工业级)
    /// <summary>
    /// 经纬度高度 → 96bit GeoSOT96
    /// </summary>
    public static GeoSOT96 GetGeoSOTCode_SplitLevel(
        double lon, double lat, double height,
        int lonLatLevel, int heightLevel,
        double heightMin = -500.0, double heightMax = 1500.0)
    {
        lon = Math.Clamp(lon, -180.0, 180.0);
        lat = Math.Clamp(lat, -90.0, 90.0);
        height = Math.Clamp(height, heightMin, heightMax);
        double hRange = heightMax - heightMin;

        long x = (long)Math.Floor((lon + 180.0) / 360.0 * (1L << lonLatLevel));
        long y = (long)Math.Floor((lat + 90.0) / 180.0 * (1L << lonLatLevel));
        long h = (long)Math.Floor((height - heightMin) / hRange * (1L << heightLevel));

        x = Math.Clamp(x, 0, (1L << lonLatLevel) - 1);
        y = Math.Clamp(y, 0, (1L << lonLatLevel) - 1);
        h = Math.Clamp(h, 0, (1L << heightLevel) - 1);

        return EncodeIndex_SplitLevel(x, y, h);
    }

    /// <summary>
    /// x/y/h 索引 → 96bit GeoSOT96(核心位打包)
    /// 规则:x(32bit) | y(32bit) → high64;h(32bit) → low32
    /// </summary>
    public static GeoSOT96 EncodeIndex_SplitLevel(long x, long y, long h)
    {
        // 安全截断到 32bit(工业级严格限制)
        x &= 0xFFFFFFFF;
        y &= 0xFFFFFFFF;
        h &= 0xFFFFFFFF;

        // 高64位 = x(32) << 32 | y(32)
        long high = (x << 32) | y;
        // 低32位 = h
        long low = h;

        return new GeoSOT96(high, low);
    }

    /// <summary>
    /// 96bit GeoSOT96 → x/y/h 索引
    /// </summary>
    public static GridIndexLong DecodeGeoSOT_SplitLevel(GeoSOT96 code)
    {
        long x = (code.high >> 32) & 0xFFFFFFFF;
        long y = code.high & 0xFFFFFFFF;
        long h = code.low & 0xFFFFFFFF;
        return new GridIndexLong(x, y, h);
    }

    /// <summary>
    /// 空间范围 → 所有覆盖的96bit网格
    /// </summary>
    public static List<GeoSOT96> GetGridsInRange_SplitLevel(
        double lonMin, double lonMax,
        double latMin, double latMax,
        double hMin, double hMax,
        int lonLatLevel, int heightLevel,
        double heightMin = -500.0, double heightMax = 1500.0)
    {
        List<GeoSOT96> codes = new List<GeoSOT96>();
        double hRange = heightMax - heightMin;

        lonMin = Math.Clamp(lonMin, -180, 180);
        lonMax = Math.Clamp(lonMax, -180, 180);
        latMin = Math.Clamp(latMin, -90, 90);
        latMax = Math.Clamp(latMax, -90, 90);
        hMin = Math.Clamp(hMin, heightMin, heightMax);
        hMax = Math.Clamp(hMax, heightMin, heightMax);

        long xCount = 1L << lonLatLevel;
        long yCount = 1L << lonLatLevel;
        long hCount = 1L << heightLevel;

        long x1 = (long)Math.Floor((lonMin + 180) / 360 * xCount);
        long x2 = (long)Math.Floor((lonMax + 180) / 360 * xCount);
        long y1 = (long)Math.Floor((latMin + 90) / 180 * yCount);
        long y2 = (long)Math.Floor((latMax + 90) / 180 * yCount);
        long h1 = (long)Math.Floor((hMin - heightMin) / hRange * hCount);
        long h2 = (long)Math.Floor((hMax - heightMin) / hRange * hCount);

        x1 = Math.Clamp(x1, 0, xCount - 1);
        x2 = Math.Clamp(x2, 0, xCount - 1);
        y1 = Math.Clamp(y1, 0, yCount - 1);
        y2 = Math.Clamp(y2, 0, yCount - 1);
        h1 = Math.Clamp(h1, 0, hCount - 1);
        h2 = Math.Clamp(h2, 0, hCount - 1);

        if (x1 > x2) (x1, x2) = (x2, x1);
        if (y1 > y2) (y1, y2) = (y2, y1);
        if (h1 > h2) (h1, h2) = (h2, h1);

        for (long x = x1; x <= x2; x++)
        {
            for (long y = y1; y <= y2; y++)
            {
                for (long h = h1; h <= h2; h++)
                {
                    codes.Add(EncodeIndex_SplitLevel(x, y, h));
                }
            }
        }
        return codes;
    }
    #endregion

    #region 辅助方法
    public void GetGridPhysicalSize(out float eastSize, out float northSize, out float heightSize)
    {
        double lonStep = 360.0 / (1L << lonLatLevel);
        double latStep = 180.0 / (1L << lonLatLevel);
        double heightRange = heightMax - heightMin;
        double hStep = heightRange / (1L << heightLevel);

        double lon1 = originLon;
        double lon2 = originLon + lonStep;
        double lat1 = originLat;
        double lat2 = originLat + latStep;

        Vector3 p1 = CameraGeoSOTUtility.WGS84ToUnity(lon1, lat1, originHeight, originLon, originLat, originHeight);
        Vector3 p2 = CameraGeoSOTUtility.WGS84ToUnity(lon2, lat1, originHeight, originLon, originLat, originHeight);
        Vector3 p3 = CameraGeoSOTUtility.WGS84ToUnity(lon1, lat2, originHeight, originLon, originLat, originHeight);

        eastSize = Vector3.Distance(p1, p2);
        northSize = Vector3.Distance(p1, p3);
        heightSize = (float)hStep;
    }
    /// <summary>
    /// 获取剖分网格中心坐标
    /// </summary>
    /// <param name="code"></param>
    /// <returns></returns>
    public Vector3 GetGridWorldCenter(GeoSOT96 code)
    {
        if (geoReference != null)
        {
            originLon = geoReference.longitude;
            originLat = geoReference.latitude;
            originHeight = geoReference.height;
        }

        GridIndexLong idx = DecodeGeoSOT_SplitLevel(code);
        double lonStep = 360.0 / (1L << lonLatLevel);
        double latStep = 180.0 / (1L << lonLatLevel);
        double heightRange = heightMax - heightMin;
        double hStep = heightRange / (1L << heightLevel);

        double lon = (double)idx.x * lonStep - 180.0 + lonStep * 0.5;
        double lat = (double)idx.y * latStep - 90.0 + latStep * 0.5;
        double h = (double)idx.h * hStep + heightMin + hStep * 0.5;
        //lon, lat, h  为真实WGS84 空间坐标
        return CameraGeoSOTUtility.WGS84ToUnity(lon, lat, h, originLon, originLat, originHeight);
    }
    #endregion

    #region 测试方法
#if UNITY_EDITOR
    [UnityEditor.MenuItem("测试/验证GeoSOT编解码可逆性(96bit)")]
    public static void TestGeoSOTCodec()
    {
        int lonLatLevel = 14;
        int heightLevel = 8;
        double heightMin = -500;
        double heightMax = 1500;

        for (int i = 0; i < 1000; i++)
        {
            double lon = UnityEngine.Random.Range(-180f, 180f);
            double lat = UnityEngine.Random.Range(-90f, 90f);
            double h = UnityEngine.Random.Range((float)heightMin, (float)heightMax);

            GeoSOT96 code = GetGeoSOTCode_SplitLevel(lon, lat, h, lonLatLevel, heightLevel, heightMin, heightMax);
            GridIndexLong idx = DecodeGeoSOT_SplitLevel(code);
            GeoSOT96 reCode = EncodeIndex_SplitLevel(idx.x, idx.y, idx.h);

            if (code != reCode)
            {
                Debug.LogError($"❌ 测试失败!原code:{code} 重编码:{reCode}");
                return;
            }
        }
        Debug.Log("✅ 所有测试点编解码完全可逆(96bit工业版)!");
    }
#endif
    #endregion

    #region 【3D 国标 G 串 双向互转】带高度:GxxxxHxxxx(行业标准)
    /// <summary>
    /// 96bit → 3D国标G串:G[经纬度]H[高度]
    /// 示例:G312320103300010130202302H001011101000  
    /// </summary>
    public static string GeoSOT96ToG3DCode(GeoSOT96 code, int lonLatLevel, int heightLevel)
    {
        // 1. 解码 x,y,h
        GridIndexLong idx = DecodeGeoSOT_SplitLevel(code);
        long x = idx.x;
        long y = idx.y;
        long h = idx.h;

        // 2. 生成经纬度四进制串(G部分)
        char[] lonLatChars = new char[lonLatLevel];
        for (int i = 0; i < lonLatLevel; i++)
        {
            int bx = (int)(x >> i) & 1;
            int by = (int)(y >> i) & 1;
            int q = (by << 1) | bx;
            lonLatChars[lonLatLevel - 1 - i] = (char)('0' + q);
        }

        // 3. 生成高度二进制串(H部分)
        char[] hChars = new char[heightLevel];
        for (int i = 0; i < heightLevel; i++)
        {
            int bh = (int)(h >> i) & 1;
            hChars[heightLevel - 1 - i] = (char)('0' + bh);
        }

        // 4. 拼接 3D 国标格式
        return $"G{new string(lonLatChars)}H{new string(hChars)}";
    }

    /// <summary>
    /// 3D国标G串 → 96bit 编码
    /// 输入:G312320103300010130202302H001011101000
    /// </summary>
    public static GeoSOT96 G3DCodeToGeoSOT96(string g3dCode, int lonLatLevel, int heightLevel)
    {
        if (string.IsNullOrEmpty(g3dCode) || !g3dCode.StartsWith("G") || !g3dCode.Contains("H"))
            throw new ArgumentException("无效的3D GeoSOT编码:" + g3dCode);

        // 拆分 G 和 H 部分
        var parts = g3dCode.TrimStart('G').Split('H');
        string lonLatQuad = parts[0];
        string hQuad = parts[1];

        // 解析经纬度
        long x = 0, y = 0;
        for (int i = 0; i < lonLatQuad.Length; i++)
        {
            int q = lonLatQuad[i] - '0';
            int by = (q >> 1) & 1;
            int bx = q & 1;
            int shift = lonLatLevel - 1 - i;
            x |= (long)bx << shift;
            y |= (long)by << shift;
        }

        // 解析高度
        long h = 0;
        for (int i = 0; i < hQuad.Length; i++)
        {
            int bit = hQuad[i] - '0';
            int shift = heightLevel - 1 - i;
            h |= (long)bit << shift;
        }

        return EncodeIndex_SplitLevel(x, y, h);
    }
    #endregion
}
cs 复制代码
using System;

/// <summary>
/// 标准 GeoSOT-3D 96bit 编码(工业级·GB/T40087-2021)
/// 经度32bit + 纬度32bit + 高度32bit = 96bit
/// 变电站设备大规模存储最优结构
/// </summary>
[Serializable]
public struct GeoSOT96 : IEquatable<GeoSOT96>
{
    // high=高64bit,low=低32bit(共96bit)
    public long high;
    public long low;

    // 构造
    public GeoSOT96(long high, long low)
    {
        this.high = high;
        this.low = low;
    }

    // 必须:字典Key/去重必须重写Equals+GetHashCode
    public bool Equals(GeoSOT96 other)
    {
        return high == other.high && low == other.low;
    }
    public override bool Equals(object obj) => obj is GeoSOT96 g && Equals(g);
    public override int GetHashCode() => HashCode.Combine(high, low);

    // 字符串化(存Json/日志用)
    public override string ToString() => $"{high}:{low}";

    // 比较符
    public static bool operator ==(GeoSOT96 a, GeoSOT96 b) => a.Equals(b);
    public static bool operator !=(GeoSOT96 a, GeoSOT96 b) => !a.Equals(b);
}
/// <summary>
/// 真实WGS84经纬度高度
/// </summary>
public struct LonLatHeight
{
    public double lon;
    public double lat;
    public double height;

    public LonLatHeight(double lon_, double lat_, double height_)
    {
        lon = lon_;
        lat = lat_;
        height = height_;
    }
}
/// <summary>
/// GeoSOT 剖分网格空间索引(x=经度索引,y=纬度索引,h=高度索引)
/// </summary>
public struct GridIndexLong
{
    public long x;
    public long y;
    public long h;

    public GridIndexLong(long x, long y, long h)
    {
        this.x = x;
        this.y = y;
        this.h = h;
    }
    public override string ToString() => $"x:{x} y:{y} h:{h}";
}
cs 复制代码
using System;
using UnityEngine;
using Vector3 = UnityEngine.Vector3;

public static class CameraGeoSOTUtility
{
    private const double WGS84_A = 6378137.0;
    private const double WGS84_B = 6356752.314245;
    private const double RAD = Math.PI / 180.0;
    private const double E2 = (WGS84_A * WGS84_A - WGS84_B * WGS84_B) / (WGS84_A * WGS84_A);
    private const double EP2 = (WGS84_A * WGS84_A - WGS84_B * WGS84_B) / (WGS84_B * WGS84_B);
    private const int ITERATION_COUNT = 5;

    public static LonLatHeight UnityToWGS84(Vector3 pos, double orgLon, double orgLat, double orgH)
    {
        double phi = orgLat * RAD;
        double lam = orgLon * RAD;

        double N = WGS84_A / Math.Sqrt(1 - E2 * Math.Sin(phi) * Math.Sin(phi));
        double x0 = (N + orgH) * Math.Cos(phi) * Math.Cos(lam);
        double y0 = (N + orgH) * Math.Cos(phi) * Math.Sin(lam);
        double z0 = (N * (1 - E2) + orgH) * Math.Sin(phi);

        double sl = Math.Sin(lam);
        double cl = Math.Cos(lam);
        double sp = Math.Sin(phi);
        double cp = Math.Cos(phi);

        double east = pos.x;
        double north = pos.z;
        double up = pos.y;

        double dx = -sl * east - cl * sp * north + cl * cp * up;
        double dy = cl * east - sl * sp * north + sl * cp * up;
        double dz = cp * north + sp * up;

        return EcefToWgs84(x0 + dx, y0 + dy, z0 + dz);
    }

    private static LonLatHeight EcefToWgs84(double x, double y, double z)
    {
        double p = Math.Sqrt(x * x + y * y);
        if (p < 1e-10)
        {
            return new LonLatHeight(0, z > 0 ? 90 : -90, Math.Abs(z) - WGS84_B);
        }

        double theta = Math.Atan2(z * WGS84_A, p * WGS84_B);
        double lat = Math.Atan2(z + EP2 * WGS84_B * Math.Pow(Math.Sin(theta), 3), p - E2 * WGS84_A * Math.Pow(Math.Cos(theta), 3));
        double lon = Math.Atan2(y, x);

        for (int i = 0; i < ITERATION_COUNT; i++)
        {
            double N = WGS84_A / Math.Sqrt(1 - E2 * Math.Sin(lat) * Math.Sin(lat));
            lat = Math.Atan2(z + N * E2 * Math.Sin(lat), p);
        }

        double NFinal = WGS84_A / Math.Sqrt(1 - E2 * Math.Sin(lat) * Math.Sin(lat));
        double hFinal = p / Math.Cos(lat) - NFinal;

        return new LonLatHeight(lon / RAD, lat / RAD, hFinal);
    }

    public static Vector3 WGS84ToUnity(double lon, double lat, double h,
                                      double orgLon, double orgLat, double orgH)
    {
        double phi = lat * RAD;
        double lam = lon * RAD;
        double ophi = orgLat * RAD;
        double olam = orgLon * RAD;

        double N = WGS84_A / Math.Sqrt(1 - E2 * Math.Sin(phi) * Math.Sin(phi));
        double x = (N + h) * Math.Cos(phi) * Math.Cos(lam);
        double y = (N + h) * Math.Cos(phi) * Math.Sin(lam);
        double z = (N * (1 - E2) + h) * Math.Sin(phi);

        double No = WGS84_A / Math.Sqrt(1 - E2 * Math.Sin(ophi) * Math.Sin(ophi));
        double xo = (No + orgH) * Math.Cos(ophi) * Math.Cos(olam);
        double yo = (No + orgH) * Math.Cos(ophi) * Math.Sin(olam);
        double zo = (No * (1 - E2) + orgH) * Math.Sin(ophi);

        double dx = x - xo;
        double dy = y - yo;
        double dz = z - zo;

        double sl = Math.Sin(olam);
        double cl = Math.Cos(olam);
        double sp = Math.Sin(ophi);
        double cp = Math.Cos(ophi);

        double east = -sl * dx + cl * dy;
        double north = -cl * sp * dx - sl * sp * dy + cp * dz;
        double up = cl * cp * dx + sl * cp * dy + sp * dz;

        return new Vector3((float)east, (float)up, (float)north);
    }
}
cs 复制代码
using UnityEngine;
using System;

public class GeoSOTDebugDrawer : MonoBehaviour
{
    [Header("显示开关")]
    public bool drawBounds = true;
    public bool drawCorners = true;
    public bool drawRealGeoSOT = true;

    [Header("颜色")]
    public Color boundsColor = Color.green;
    public Color cornerColor = Color.red;
    public Color geoSOTColor = Color.blue;

    [Header("绘制设置")]
    [Range(0.1f, 2f)] public float gridScale = 0.95f;
    public bool autoMatchGridSize = true;
    public float fixedGridSize = 1.0f;

    [Header("调试限制")]
    public bool showDebugLog = true;
    public int maxDrawGrids = 200;
    public bool drawOnlyFirstGrid = false;

    private ModelPart[] cachedParts;
    private float lastUpdateTime = 0f;
    private const float UPDATE_INTERVAL = 0.5f;

    private void OnDrawGizmos()
    {
        if (GeoSotManager.instance == null) return;

        if (cachedParts == null || Time.time - lastUpdateTime > UPDATE_INTERVAL)
        {
            cachedParts = FindObjectsOfType<ModelPart>();
            lastUpdateTime = Time.time;
        }

        if (cachedParts.Length == 0) return;

        foreach (var part in cachedParts)
        {
            if (!part.isActiveAndEnabled) continue;

            if (drawBounds) DrawModelBounds(part);
            if (drawCorners) DrawModelCorners(part);
            if (drawRealGeoSOT && part.geoSOTAllCodes != null && part.geoSOTAllCodes.Count > 0)
            {
                DrawGeoSOTGrids(part);
            }
        }
    }
    private void Start()
    {
        GeoSOT96 code = GeoSotManager.GetGeoSOTCode_SplitLevel(116.3975, 39.9042, 45, 24, 12, -500, 1500);

        string g_str = GeoSotManager.GeoSOT96ToG3DCode(code, 24, 12);
        print("国标3DG串:"+ g_str);
        GridIndexLong idx = GeoSotManager.DecodeGeoSOT_SplitLevel(code);
        print("网格索引:"+ idx.ToString());
        print("GeoSOT96_old:" + code.ToString());
        GeoSOT96 code1= GeoSotManager.G3DCodeToGeoSOT96(g_str, 24, 12);
        print("GeoSOT96_new:" + code1.ToString());

    }
    void DrawGeoSOTGrids(ModelPart part)
    {
        GeoSotManager man = GeoSotManager.instance;
        int drawCount = drawOnlyFirstGrid ? 1 : Math.Min(part.geoSOTAllCodes.Count, maxDrawGrids);
        Gizmos.color = geoSOTColor;

        man.GetGridPhysicalSize(out float eastSize, out float northSize, out float heightSize);
        Vector3 realGridSize = new Vector3(eastSize, heightSize, northSize) * gridScale;

        for (int i = 0; i < drawCount; i++)
        {
            GeoSOT96 code = part.geoSOTAllCodes[i];
            Vector3 gridWorldPos = man.GetGridWorldCenter(code);

            Gizmos.DrawWireCube(gridWorldPos, realGridSize);
            Gizmos.DrawSphere(gridWorldPos, realGridSize.magnitude * 0.05f);
        }
    }

    void DrawModelBounds(ModelPart part)
    {
        MeshFilter mf = part.GetComponent<MeshFilter>();
        if (!mf || !mf.sharedMesh) return;

        Bounds localBounds = mf.sharedMesh.bounds;
        Vector3 worldCenter = part.transform.TransformPoint(localBounds.center);
        Vector3 worldSize = Vector3.Scale(localBounds.size, part.transform.lossyScale);

        Gizmos.color = boundsColor;
        Gizmos.DrawWireCube(worldCenter, worldSize);
    }

    void DrawModelCorners(ModelPart part)
    {
        MeshFilter mf = part.GetComponent<MeshFilter>();
        if (!mf || !mf.sharedMesh) return;

        Bounds b = mf.sharedMesh.bounds;
        Vector3[] localCorners = new Vector3[]
        {
            new(b.min.x,b.min.y,b.min.z), new(b.max.x,b.min.y,b.min.z),
            new(b.min.x,b.max.y,b.min.z), new(b.max.x,b.max.y,b.min.z),
            new(b.min.x,b.min.y,b.max.z), new(b.max.x,b.min.y,b.max.z),
            new(b.min.x,b.max.y,b.max.z), new(b.max.x,b.max.y,b.max.z)
        };

        Gizmos.color = cornerColor;
        foreach (var corner in localCorners)
        {
            Gizmos.DrawSphere(part.transform.TransformPoint(corner), 0.08f);
        }
    }
}
cs 复制代码
using UnityEngine;
using System.Collections.Generic;
using Vector3 = UnityEngine.Vector3;
using Matrix4x4 = UnityEngine.Matrix4x4;
using System;

public class ModelPart : MonoBehaviour
{
    [Header("部件信息")]
    public string partId;
    public string partName;

    [Header("GeoSOT空间索引(自动生成·96bit)")]
    public List<GeoSOT96> geoSOTAllCodes = new List<GeoSOT96>();
    public string[] geoSOTCodeStrings;

    private MeshFilter meshFilter;
    private Mesh targetMesh;

    #region 生命周期
    private void OnEnable()
    {
        GeoSotManager.instance?.RegisterPart(this);
    }

    private void OnDisable()
    {
        GeoSotManager.instance?.UnregisterPart(this);
    }

    private void Awake()
    {
        meshFilter = GetComponent<MeshFilter>();
        if (meshFilter != null)
        {
            targetMesh = meshFilter.sharedMesh;
        }
    }

    private void Start()
    {
        if (GeoSotManager.instance != null)
        {
            InitValue();
        }
        else
        {
            Debug.LogError($"[Error] {gameObject.name}:GeoSotManager未找到,无法初始化!");
        }
    }
    #endregion

    public void InitValue()
    {
        if (GeoSotManager.instance == null)
        {
            Debug.LogError($"❌ {transform.name}:GeoSotManager未初始化!");
            return;
        }

        if (meshFilter == null || targetMesh == null)
        {
            meshFilter = GetComponent<MeshFilter>();
            if (meshFilter == null || meshFilter.sharedMesh == null)
            {
                Debug.LogError($"❌ {transform.name}:未找到MeshFilter或Mesh!");
                return;
            }
            targetMesh = meshFilter.sharedMesh;
        }

        if (string.IsNullOrEmpty(partName)) partName = transform.name;
        if (string.IsNullOrEmpty(partId)) partId = transform.name;

        Bounds localBounds = targetMesh.bounds;
        Matrix4x4 localToWorld = transform.localToWorldMatrix;
        LonLatHeight[] cornersWGS84 = GeoSotManager.instance.ConvertBoxToWGS84(localBounds, localToWorld);
        GeoSotManager.instance.GetBox3DRange(cornersWGS84,
            out double lonMin, out double lonMax,
            out double latMin, out double latMax,
            out double hMin, out double hMax);

        int lonLatLevel = GeoSotManager.instance.lonLatLevel;
        int heightLevel = GeoSotManager.instance.heightLevel;

        geoSOTAllCodes = GeoSotManager.GetGridsInRange_SplitLevel(
            lonMin, lonMax,
            latMin, latMax,
            hMin, hMax,
            lonLatLevel,
            heightLevel,
            GeoSotManager.instance.heightMin,
            GeoSotManager.instance.heightMax);

        geoSOTCodeStrings = new string[geoSOTAllCodes.Count];
        for (int i = 0; i < geoSOTAllCodes.Count; i++)
        {
            geoSOTCodeStrings[i] = geoSOTAllCodes[i].ToString();
        }

        if (geoSOTAllCodes.Count > 0)
        {
            GeoSOT96 testCode = geoSOTAllCodes[0];
            GridIndexLong idx = GeoSotManager.DecodeGeoSOT_SplitLevel(
                testCode);

            GeoSOT96 reEncodeCode = GeoSotManager.EncodeIndex_SplitLevel(
                idx.x, idx.y, idx.h);

            if (testCode == reEncodeCode)
            {
                Debug.Log($"✅ {partName} 编码解码一致性校验通过(96bit)!");
            }
            else
            {
                Debug.LogError($"❌ {partName} 编码解码一致性校验失败(96bit)!");
            }
        }

        Debug.Log($"✅ {partName} 最终生成网格数:{geoSOTAllCodes.Count}");
    }
}
相关推荐
鲲穹AI种草1 小时前
家装设计工具记录:多款室内 3D 设计工具能力边界整理
3d·家装设计工具
杀生丸学AI14 小时前
【前馈三维重建】SAF3R:前馈式3D重建Transformer的动态稀疏注意力(加速)
深度学习·3d·transformer
2601_9507607916 小时前
3D细胞活力检测技术:从传统方法到三维体系的跨越
人工智能·3d·蛋白
牛企老板俱乐部1 天前
广东手板3D打印三大核心产区 采购视角看珠海深圳东莞
3d
数智工坊1 天前
SAMosaic3D:以可学习马赛克组装实现实时在线3D实例分割 | CVPR 2026 论文精读
学习·3d
tzc_fly1 天前
KDD 2026 | PRIME:一种用于从头设计binder的 3D 分子模型
3d·binder
dalong101 天前
WPF:3D型材模型
3d·wpf
杀生丸学AI1 天前
【前馈三维重建】AdaptiveSplat:前馈重建的纹理感知可控3DGS分配方法(ECCV 2026)
3d·三维重建·扩散模型·视觉大模型·高斯泼溅·前馈模型·大场景重建
数智工坊1 天前
UECP:不确定性增强的协同感知 | 基于物理先验的BEV融合新范式
linux·ubuntu·3d·机器人