mosaicmap/lib/tile-mesh.ts
2025-07-28 07:25:32 +08:00

446 lines
14 KiB
TypeScript
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.

/**
* 根据经纬度范围和瓦片级别生成细分网格
* 用于globe模式下的球面渲染
*/
export interface TileMeshOptions {
/** 经纬度边界 [west, south, east, north] */
bounds: [number, number, number, number];
/** 瓦片缩放级别 */
z: number;
/** 细分级别默认为瓦片级别的2倍 */
subdivisions?: number;
}
export interface TileMeshResult {
/** 顶点数据 (x, y, u, v) 坐标对:位置和纹理坐标 */
vertices: Float32Array;
/** 索引数据 */
indices: Uint16Array | Uint32Array;
/** 是否使用32位索引 */
uses32bitIndices: boolean;
/** 顶点数量 */
vertexCount: number;
/** 三角形数量 */
triangleCount: number;
}
/**
* 设备性能等级
*/
export enum PerformanceLevel {
LOW = 'low', // 低端设备
MEDIUM = 'medium', // 中端设备
HIGH = 'high' // 高端设备
}
/**
* 根据当前缩放级别和设备性能计算最佳细分数量
*/
export function calculateOptimalSubdivisions(
zoomLevel: number,
performanceLevel: PerformanceLevel = PerformanceLevel.MEDIUM,
options?: {
/** 最小细分数量默认4 */
minSubdivisions?: number;
/** 最大细分数量默认64 */
maxSubdivisions?: number;
/** 是否为Globe模式Globe模式需要更多细分默认true */
isGlobeMode?: boolean;
/** 视口区域大小(像素),影响所需细分程度 */
viewportSize?: { width: number; height: number };
}
): number {
const {
minSubdivisions = 4,
maxSubdivisions = 64,
isGlobeMode = true,
viewportSize
} = options || {};
// 基础细分计算:随着缩放级别指数增长
let baseSubdivisions: number;
if (zoomLevel <= 2) {
// 非常低的缩放级别,使用最少细分
baseSubdivisions = minSubdivisions;
} else if (zoomLevel <= 6) {
// 低到中等缩放级别:线性增长
baseSubdivisions = minSubdivisions + (zoomLevel - 2) * 2;
} else if (zoomLevel <= 12) {
// 中等到高缩放级别:较快增长
baseSubdivisions = 12 + (zoomLevel - 6) * 3;
} else {
// 高缩放级别:平缓增长避免性能问题
baseSubdivisions = 30 + (zoomLevel - 12) * 1.5;
}
// 设备性能调整系数
const performanceMultipliers = {
[PerformanceLevel.LOW]: 0.6, // 低端设备减少40%细分
[PerformanceLevel.MEDIUM]: 1.0, // 中端设备保持基础细分
[PerformanceLevel.HIGH]: 1.4 // 高端设备增加40%细分
};
baseSubdivisions *= performanceMultipliers[performanceLevel];
// Globe模式调整球面渲染需要更多细分来避免失真
if (isGlobeMode) {
baseSubdivisions *= 1.2;
}
// 视口大小调整:大视口需要更多细分保证质量
if (viewportSize) {
const viewportArea = viewportSize.width * viewportSize.height;
const standardArea = 1920 * 1080; // 标准1080p面积
const sizeMultiplier = Math.sqrt(viewportArea / standardArea);
baseSubdivisions *= Math.min(1.5, Math.max(0.7, sizeMultiplier));
}
// 确保在合理范围内并且为2的幂次对GPU更友好
const clampedSubdivisions = Math.max(minSubdivisions, Math.min(maxSubdivisions, baseSubdivisions));
// 向最近的2的幂次取整4, 8, 16, 32, 64等
const powerOfTwo = Math.pow(2, Math.round(Math.log2(clampedSubdivisions)));
return Math.max(minSubdivisions, Math.min(maxSubdivisions, powerOfTwo));
}
/**
* 检测设备性能等级(简化版本)
*/
export function detectPerformanceLevel(): PerformanceLevel {
if (typeof window === 'undefined') return PerformanceLevel.HIGH;
// 检查硬件并发数
const hardwareConcurrency = navigator.hardwareConcurrency || 4;
// 检查内存信息(如果可用)
const memory = (navigator as any).deviceMemory;
// 检查WebGL能力
const canvas = document.createElement('canvas');
const gl = canvas.getContext('webgl2') || canvas.getContext('webgl');
if (!gl) return PerformanceLevel.LOW;
const renderer = gl.getParameter(gl.RENDERER) || '';
const vendor = gl.getParameter(gl.VENDOR) || '';
// 基于多个指标综合判断
let score = 0;
// CPU核心数评分
if (hardwareConcurrency >= 8) score += 3;
else if (hardwareConcurrency >= 4) score += 2;
else score += 1;
// 内存评分
if (memory) {
if (memory >= 8) score += 3;
else if (memory >= 4) score += 2;
else score += 1;
} else {
score += 2; // 默认中等
}
// GPU评分简化判断
if (renderer.toLowerCase().includes('intel')) {
score += 1; // 集成显卡通常性能较低
} else if (renderer.toLowerCase().includes('nvidia') || renderer.toLowerCase().includes('amd')) {
score += 3; // 独立显卡性能较好
} else {
score += 2; // 默认
}
// 根据总分判断性能等级
if (score <= 4) return PerformanceLevel.LOW;
else if (score <= 7) return PerformanceLevel.MEDIUM;
else return PerformanceLevel.HIGH;
}
/**
* 将经纬度转换为Web Mercator坐标 (归一化到0-1范围)
*/
function lonLatToMercator(lon: number, lat: number): [number, number] {
const x = (lon + 180) / 360;
const latRad = (lat * Math.PI) / 180;
const y = (1 - Math.log(Math.tan(latRad / 2 + Math.PI / 4)) / Math.PI) / 2;
return [x, y];
}
/**
* 创建细分的瓦片网格
*/
export function createSubdividedTileMesh(options: TileMeshOptions): TileMeshResult {
const { bounds, z, subdivisions } = options;
const [west, south, east, north] = bounds;
// 根据瓦片级别确定细分级别
// 更高的瓦片级别需要更多的细分来在globe模式下保持平滑
const subdivLevel = subdivisions ?? Math.max(8, Math.min(32, Math.pow(2, Math.max(0, z - 5))));
// 将经纬度边界转换为归一化的Web Mercator坐标
const [mercWest, mercNorth] = lonLatToMercator(west, north);
const [mercEast, mercSouth] = lonLatToMercator(east, south);
// 创建顶点网格
const verticesPerRow = subdivLevel + 1;
const verticesPerCol = subdivLevel + 1;
const totalVertices = verticesPerRow * verticesPerCol;
// 每个顶点包含4个float值x, y, u, v (位置 + 纹理坐标)
const vertices = new Float32Array(totalVertices * 4);
// 生成顶点
for (let row = 0; row < verticesPerCol; row++) {
for (let col = 0; col < verticesPerRow; col++) {
const vertexIndex = (row * verticesPerRow + col) * 4;
// 在归一化空间中插值
const u = col / subdivLevel;
const v = row / subdivLevel;
// 计算实际的mercator坐标 (位置)
const x = mercWest + (mercEast - mercWest) * u;
const y = mercNorth + (mercSouth - mercNorth) * v;
// 设置顶点数据:位置(x, y) + 纹理坐标(u, v)
vertices[vertexIndex] = x; // 位置 x
vertices[vertexIndex + 1] = y; // 位置 y
vertices[vertexIndex + 2] = u; // 纹理坐标 u
vertices[vertexIndex + 3] = v; // 纹理坐标 v
}
}
// 创建三角形索引
const trianglesPerRow = subdivLevel;
const trianglesPerCol = subdivLevel;
const totalTriangles = trianglesPerRow * trianglesPerCol * 2;
const totalIndices = totalTriangles * 3;
// 判断是否需要32位索引
const uses32bitIndices = totalVertices > 65535;
const indices = uses32bitIndices
? new Uint32Array(totalIndices)
: new Uint16Array(totalIndices);
let indexOffset = 0;
// 生成三角形索引 (每个四边形分成两个三角形)
for (let row = 0; row < trianglesPerCol; row++) {
for (let col = 0; col < trianglesPerRow; col++) {
// 四边形的四个顶点索引
const topLeft = row * verticesPerRow + col;
const topRight = topLeft + 1;
const bottomLeft = (row + 1) * verticesPerRow + col;
const bottomRight = bottomLeft + 1;
// 第一个三角形 (左上角)
indices[indexOffset++] = topLeft;
indices[indexOffset++] = bottomLeft;
indices[indexOffset++] = topRight;
// 第二个三角形 (右下角)
indices[indexOffset++] = topRight;
indices[indexOffset++] = bottomLeft;
indices[indexOffset++] = bottomRight;
}
}
return {
vertices,
indices,
uses32bitIndices,
vertexCount: totalVertices,
triangleCount: totalTriangles
};
}
/**
* 根据瓦片坐标和级别创建细分网格
*/
export function createSubdividedTileMeshFromTileCoords(
x: number,
y: number,
z: number,
subdivisions?: number
): TileMeshResult {
// 计算瓦片的经纬度边界
const n = Math.pow(2, z);
const west = (x / n) * 360 - 180;
const east = ((x + 1) / n) * 360 - 180;
const latRad1 = Math.atan(Math.sinh(Math.PI * (1 - 2 * y / n)));
const latRad2 = Math.atan(Math.sinh(Math.PI * (1 - 2 * (y + 1) / n)));
const north = (latRad1 * 180) / Math.PI;
const south = (latRad2 * 180) / Math.PI;
return createSubdividedTileMesh({
bounds: [west, south, east, north],
z,
subdivisions
});
}
/**
* 创建覆盖整个世界的细分网格
*/
export function createWorldSubdividedMesh(z: number, subdivisions?: number): TileMeshResult {
return createSubdividedTileMesh({
bounds: [-180, -85.0511, 180, 85.0511], // Web Mercator的纬度范围
z,
subdivisions
});
}
/**
* 使用示例函数:为特定地理区域创建高精度细分网格
*/
export function createRegionSubdividedMesh(
/** 中心经度 */
centerLon: number,
/** 中心纬度 */
centerLat: number,
/** 经度范围(度) */
lonRange: number,
/** 纬度范围(度) */
latRange: number,
/** 瓦片缩放级别 */
z: number,
/** 自定义细分级别 */
subdivisions?: number
): TileMeshResult {
const west = centerLon - lonRange / 2;
const east = centerLon + lonRange / 2;
const south = centerLat - latRange / 2;
const north = centerLat + latRange / 2;
return createSubdividedTileMesh({
bounds: [west, south, east, north],
z,
subdivisions
});
}
/**
* 预设区域网格创建器
*/
export const RegionMeshPresets = {
/** 新加坡区域 */
singapore: (z: number, subdivisions?: number) => createSubdividedTileMesh({
bounds: [103.6, 1.2, 104.0, 1.5],
z,
subdivisions
}),
/** 中国大陆区域 */
china: (z: number, subdivisions?: number) => createSubdividedTileMesh({
bounds: [65.24686921730095, 11.90274236858339, 138.85323419021077, 55.34323805611308],
z,
subdivisions
}),
/** 美国本土区域 */
usa: (z: number, subdivisions?: number) => createSubdividedTileMesh({
bounds: [-125, 25, -66, 49],
z,
subdivisions
}),
/** 欧洲区域 */
europe: (z: number, subdivisions?: number) => createSubdividedTileMesh({
bounds: [-10, 35, 40, 70],
z,
subdivisions
})
};
/**
* 便捷函数:自动计算最佳细分并创建世界网格
*/
export function createOptimalWorldMesh(
zoomLevel: number,
performanceLevel?: PerformanceLevel,
options?: {
viewportSize?: { width: number; height: number };
isGlobeMode?: boolean;
minSubdivisions?: number;
maxSubdivisions?: number;
}
): TileMeshResult {
const detectedPerformance = performanceLevel ?? detectPerformanceLevel();
const optimalSubdivisions = calculateOptimalSubdivisions(
zoomLevel,
detectedPerformance,
options
);
return createWorldSubdividedMesh(zoomLevel, optimalSubdivisions);
}
/**
* 便捷函数:自动计算最佳细分并创建指定区域网格
*/
export function createOptimalRegionMesh(
bounds: [number, number, number, number],
zoomLevel: number,
performanceLevel?: PerformanceLevel,
options?: {
viewportSize?: { width: number; height: number };
isGlobeMode?: boolean;
minSubdivisions?: number;
maxSubdivisions?: number;
}
): TileMeshResult {
const detectedPerformance = performanceLevel ?? detectPerformanceLevel();
const optimalSubdivisions = calculateOptimalSubdivisions(
zoomLevel,
detectedPerformance,
options
);
return createSubdividedTileMesh({
bounds,
z: zoomLevel,
subdivisions: optimalSubdivisions
});
}
/**
* 获取细分级别建议的可读描述
*/
export function getSubdivisionRecommendation(zoomLevel: number, performanceLevel: PerformanceLevel): {
subdivisions: number;
description: string;
triangleCount: number;
estimatedMemoryMB: number;
} {
const subdivisions = calculateOptimalSubdivisions(zoomLevel, performanceLevel);
const triangleCount = subdivisions * subdivisions * 2;
const vertexCount = (subdivisions + 1) * (subdivisions + 1);
// 估算内存使用 (顶点 + 索引)
const vertexMemory = vertexCount * 4 * 4; // 4个float32 per vertex
const indexMemory = triangleCount * 3 * (vertexCount > 65535 ? 4 : 2); // 3 indices per triangle
const estimatedMemoryMB = (vertexMemory + indexMemory) / (1024 * 1024);
let description: string;
if (subdivisions <= 8) {
description = "低细分 - 高性能,适合远距离视图";
} else if (subdivisions <= 16) {
description = "中等细分 - 平衡性能和质量";
} else if (subdivisions <= 32) {
description = "高细分 - 高质量,适合近距离视图";
} else {
description = "超高细分 - 最高质量,需要高端设备";
}
return {
subdivisions,
description,
triangleCount,
estimatedMemoryMB: Math.round(estimatedMemoryMB * 100) / 100
};
}