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}");
    }
}
相关推荐
做cv的小昊13 小时前
结合代码读3DGS&世界模型论文(3)——3DGS & 世界模型新工作GaussianDream论文及代码解读
3d
lilian2331 天前
HarmonyOS 7 新特性(十三)|3DGS 模型加载、交互与性能回退
3d·交互·harmonyos
DolitD1 天前
云流技术深度剖析:单服务器下如何实现3D应用的多实例并发?
java·服务器·前端·3d·云原生·云计算
Liudef061 天前
腾讯混元Hunyuan3D-Part:重新定义3D部件生成的革命性架构
人工智能·3d·架构·腾讯混元hunyuan3d
2601_967659892 天前
大学实训室网络仿真环境搭建:Ranplan 3D射线跟踪双版本技术解析
网络·3d
七77.3 天前
SceneAssistant: A Visual Feedback Agent for Open-Vocabulary 3D Scene Generation
3d·agent·世界模型
rockingdingo3 天前
Codex Claude 智能体做3D/潮玩/IP设计——装上Craftsman Agent工匠智能体Skills 游泳男孩IP案例分享
网络协议·tcp/ip·3d
3D可视化大侠3 天前
数字孪生时空数据可视化:CIMPro 孪大师中的轨迹与粒子实现
3d·信息可视化
LONGZETECH3 天前
新能源汽车动力电池实训教学痛点与虚拟仿真技术解决方案
c语言·3d·unity·架构·汽车·汽车教学软件
爱分享的康康3 天前
从“识别已知”到“发现未知”:3D通用目标检测如何打开智驾感知新边界
人工智能·目标检测·3d