透视相机与投影边界
SqsMap 的实际平面三维相机复用轻量共享相机模块的透视基、投影和射线,渲染与拾取使用相同的焦距与坐标系。现有视场为45度,倾角范围0–65度;自由轨道保留原有姿态语义。此数学接口可编译为原生与WASM,且不依赖GPU、UI或HTTP。
Rust 透视计算
use sqs_map::camera::perspective::PerspectiveCamera;
let camera = PerspectiveCamera::new([1000., 2000., 50.], 800., 600., 2., 45., 30.)?;
let pixel = camera.project([1010., 2020., 50.]).ok_or("在相机后方")?;
let ground = camera.ground_at([pixel[0], pixel[1]]).ok_or("射线未命中地面")?;
focus、世界位置与resolution必须使用相同的渲染坐标单位,宽高和像素使用同一逻辑或物理单位。相同物理视野改变DPR时,应同比放大宽高、同比缩小每像素分辨率。地面高程由focus的第三项明确指定;射线指向天空时没有地面交点,不伪造z=0命中。背向点与非有限输入没有投影结果。
共同投影切换
map.setProjection({type:'globe'}) 保留中心和方向,将进入球面的缩放规范到0–24。这允许新建原生地图的二维全图负缩放直接进入地球仪;返回平面保留实际球面缩放。直接在球面设置负缩放或大于24仍返回参数错误。非零球面边距仍明确拒绝,失败不会改变原有相机。事件反映实际缩放变化,起始/后续状态遵循现有快照规则。
完整投影HTML列出缩放、球面/平面、moveend、错误和销毁的全部处理器;共同脚本使用同名MapLibre风格调用。语法参考MapLibre相机API,缩放范围与45度视场为SqsMap当前规则,未承诺与其他引擎画面逐像素相同。
当前倾斜边界
Rust三维渲染已经使用共享透视数学。显式Rust透视相机与绑定已按下文接通;原生C与内置QuickJS已接通非零平面倾角、实际图层及地面查询;WASM/主线程/严格Worker已经接通;桌面共同适配已接通,见桌面倾斜地图;styleRasterPitchedSources 保持false。这些开发入口已接通共同pitch,旧npm或动态库发行包仍需使用匹配版本。球面倾角与球面动画继续可用。设备/正式发行须单独验收。
示例的背景样式通过公开样式 URL加载;完整代码在样式状态为 ready 后启用按钮,避免把命令确认当作资源已经下载。
背景样式将 maxzoom 显式设为 33,覆盖共同平面 32 级与球面 24 级;图层 maxzoom 是不包含自身的显示上限。省略时默认 24,因此在恰好 24 级该背景图层不可见。
倾斜与留白的 Rust 对齐
PitchedMercatorCamera 以逻辑像素计算留白和锚点,将中心留白位置的地面射线反算成真实相机焦点。方向、显式世界副本和设备像素比共同使用这一焦点;非对称留白不是投影完成后的二维平移。
use sqs_map::camera::{CameraPose, Padding, Viewport};
use sqs_map::camera::pitched_mercator::PitchedMercatorCamera;
use sqs_map::basemap::Camera3d;
let camera = PitchedMercatorCamera::new(Viewport::new(800., 600., 2.)?,
CameraPose {center: [181., 40.], zoom: 8., pitch: 40., bearing: 30.,
padding: Padding {left: 100., right: 20., top: 40., bottom: 10.},
..Default::default()})?;
let pixel = camera.project([181., 40.])?;
let location = camera.unproject(pixel)?;
let anchored = camera.with_anchor([181.01, 40.01], [440., 315.])?;
let scene_camera = Camera3d::from_pitched_mercator(anchored)?;
数学相机支持 0–65° 倾角、零 roll,以及已有共同相机的计算级别范围。physical_perspective 按 DPR 缩放视口并反向缩放每像素距离,保持同一物理视野。天空、非有限输入和超出墨卡托纬度域的地面命中会明确失败;锚点不能靠裁剪中心纬度来伪造成功。
实际 Camera3d 适配器目前要求 0–24 级,且解出的真实焦点仍在墨卡托范围;否则拒绝旧渲染器的静默裁剪。该适配器输出逻辑视口相机;渲染目标与最终物理视口的换算由宿主负责。Rust 的带留白透视投影与实际三维像素已验证。原生C和内置QuickJS的非零平面pitch已接通;WASM/主线程/严格Worker已经接通,桌面已接通共同倾斜场景,高 DPI 和完整查询继续审查,styleRasterPitchedSources 仍为 false。
透视范围、范围定位与渲染状态回读
Rust PitchedMercatorCamera 新增 bounds、camera_for_bounds、raw 与 from_raw。它们是接通共同相机所需的共享计算接口;生产 C、内置 QuickJS、WASM 与主线程/严格 Worker 已接通共同非零倾角,桌面同形门面继续后续适配。
use sqs_map::camera::{CameraPose, FitBoundsOptions, Padding, Viewport};
use sqs_map::camera::pitched_mercator::PitchedMercatorCamera;
let camera = PitchedMercatorCamera::new(Viewport::new(800., 600., 2.)?,
CameraPose {pitch: 50., ..Default::default()})?;
let pose = camera.camera_for_bounds([179., 39., -179., 41.],
FitBoundsOptions {bearing: 30., offset: [20., -10.],
padding: Some(Padding {left: 80., right: 20., top: 30., bottom: 10.}),
..Default::default()})?;
let fitted = camera.with_pose(pose)?;
let visible = fitted.bounds()?;
let raw = fitted.raw()?;
let restored = PitchedMercatorCamera::from_raw(raw, 2., pose.padding, pose.pitch)?;
范围为 西、南、东、北。东小于西时按跨日期线处理,181度仍保留该世界副本。定位保留当前倾角,并使用传入的方向、边距和逻辑像素偏移:墨卡托范围中点投影到留白中心加偏移的位置,搜索使四个角都落在留白矩形内的最大缩放。默认缩放限制为0–24;点范围取 max_zoom。无效范围、边距、偏移或无法容纳的范围明确失败,不修改原相机。纬度边界附近,低级别可能要求裁剪公共中心;如果更高的级别可以合法容纳,仍返回有效定位结果。
可见范围将地面视口多边形裁剪到墨卡托南北纬度界限。边界交点保留真实经度,不能只裁剪四角纬度而把不可见地面的经度包含进来。天空仍没有有效反投影。raw 使用物理像素与比例;from_raw 需另传原逻辑边距和倾角,反算公共中心,拒绝超出支持纬度的中心,不隐式裁剪。透视回读采用共享计算的−32–32缩放范围;旧二维超范围回读仍由 PlanarCamera 单独处理。
命名参考 MapLibre 相机 API;保留倾角、纬度检查及上述拟合规则属于当前 SqsMap Rust 语义。30度和65度定位均在DPR2真实三维像素中验证了跨日期线四角。Camera3d 适配仍要求缩放0–24和未裁剪的实际焦点。共同绑定、动画及已支持的样式图层/地面查询已接通,完整三维/地形/符号查询继续实施,styleRasterPitchedSources 仍为 false。
共同 Rust 相机命令与动画
Rust 宿主可使用 PlanarCamera::from_perspective 显式启用共同透视计算。普通 PlanarCamera 构造仍拒绝非零倾角,数学接通不代表宿主画面已匹配。显式启用后,投影、反投影、锚点、可见范围及范围定位复用同一 PitchedMercatorCamera;倾角回到零仍保留透视能力。非零倾角不能切换到 geographic;零倾角切换 geographic 时退出透视计算模式。
use sqs_map::camera::{CameraPose, PlanarCamera, Viewport};
use sqs_map::camera::pitched_mercator::PitchedMercatorCamera;
use sqs_map::camera::motion::CameraMotion;
let camera = PlanarCamera::from_perspective(PitchedMercatorCamera::new(
Viewport::new(800., 600., 2.)?,
CameraPose {center: [181., 40.], zoom: 8., pitch: 20., ..Default::default()})?);
let action = CameraMotion::default().prepare(camera, "easeTo", &[
serde_json::json!({"pitch":60., "linear":true, "duration":1000.})], 0.)?;
let middle = action.next.advance(camera, 500.)?;
assert_eq!(middle.camera.pose().pitch, 40.);
每个动画采样保留倾角。倾角变化提供 pitchstart、pitch 和 pitchend,携带历史起始状态与实际中间/结束状态;取消时在当前画面闭合倾角事件。用户另外修改倾角时中断动画,不忽略外部变化。around 锚点在倾角、缩放和方向变化中仍停在指定逻辑像素。宿主时钟须单调,核心不提供定时器。
实际绘制由 CameraBinding::connect_perspective 显式接入墨卡托渲染宿主。prepare/advance 返回不可变候选;candidate_scene_camera 先计算渲染比例量化为 f32 后的真实三维相机,以便在 commit 前准备资源。提交后的 scene_camera 返回同一实际相机。候选不合法、场景范围不支持、量化后纬度越界或候选的视口/投影已经过期时,原可见状态保持不变。宿主仍须在提交前预约输出/事件资源,并按最终渲染目标的 DPR 绘制该场景相机;不能只在应用状态中记录倾角。
非零倾角场景要求缩放0–24及未裁剪的墨卡托焦点。独立数学getMinZoom/getMaxZoom仍描述共享计算的−32–32范围,接入宿主须在写入前落实场景限制。带倾角的 flyTo 默认 minZoom 为0,负 minZoom 或端点无法使用此下限时明确拒绝。规划检查65个预检采样和目标;有限预检不证明非线性轨迹的每个瞬间都合法,每个实际 advance 仍在修改状态前重新检查。天空、非有限值与无效地面位置仍是错误。
倾斜视图中的 sqs.scaleAt 使用水平屏幕射线的解析地面导数,结果仍是米/逻辑CSS像素的球面局部近似比例,独立于 DPR,不是地形表面长度。计算避免高缩放下对大墨卡托绝对坐标相减造成的精度损失。
完整 Rust 渲染宿主 包含设备创建、全部相机调用、候选资源上传、提交、实际三维绘制、像素检查和事件输出。同版本核心检出运行 cargo run --locked -p sqs-map --example shared_pitched_camera 即可。示例仅用公开接口,不依赖UI或网络;没有可用图形适配器时直接失败。示例与绑定已在DPR2、32.5度和65度真实像素中验收。
此阶段交付共同Rust宿主契约。生产原生C和内置QuickJS现已调用connect_perspective并接通实际绘制及地面查询;WASM/主线程/严格Worker已经接通,桌面共同适配已接通,见桌面倾斜地图,styleRasterPitchedSources 仍为 false。这些宿主绑定与设备验收继续后续阶段。
共同底图宿主的实际透视绘制
HostBasemap和BasemapSession新增poll_pitched与snapshot_pitched_rgba,接受经过检查的PitchedMercatorCamera。它们复用既有三维场景、文字、影像及业务图层的深度缓冲,不另建绘制或下载实现。逻辑相机控制样式表达式、可见范围和瓦片级别;实际相机只按DPR调整绘制镜头和目标尺寸,不把物理缩放误用于minzoom/maxzoom。
scene_only可在无远程样式时绘制业务图层,使用调用方指定的RGBA背景,不产生网络请求。内部背景不构成调用方的公共样式、图源或图层。下面是完整的Rust绘制函数;宿主提供地图和设备句柄,并持续持有HostBasemap时可复用资源。
use sqs_map::basemap::{host::HostBasemap, session::BasemapGpu};
use sqs_map::camera::{CameraError, CameraPose, Viewport};
use sqs_map::camera::pitched_mercator::PitchedMercatorCamera;
fn draw(map: &sqs_map::MapRenderer, gpu: BasemapGpu<'_>)
-> Result<Option<(u32, u32, Vec<u8>)>, CameraError> {
let camera = PitchedMercatorCamera::new(Viewport::new(160., 120., 2.)?,
CameraPose {center: [181., 40.], zoom: 19., pitch: 65.,
..Default::default()})?;
let mut host = HostBasemap::scene_only(camera, [255, 255, 255, 255])?;
host.poll_pitched(gpu, camera, map, &[])?;
Ok(host.snapshot_pitched_rgba(camera))
}
对于远程样式,先用HostBasemap::start创建宿主,通过requests/respond接入使用方的下载,再持续poll_pitched。返回的等待时间控制下一次推进;调用成功不保证资源或后台几何已经就绪。snapshot_pitched_rgba仅返回该姿态、倍率实际绘制的画面,拒绝二维、球面、不同姿态及已失效的底图缓存。它允许交互中的流式资源尚不完整;外部业务图层或影像改变后先推进绘制,不能仅靠相机相等证明外部数据已更新。
当前场景内容级别为0–24,实际镜头级别为逻辑级别加log2(DPR),须在0–26内;物理宽高须为至少1的整数。不可表示的降采样、高倍率高缩放和小数物理尺寸在改变下载队列或画面前拒绝。独立数学Viewport仍允许0.125–16倍率,这不代表旧场景渲染器支持所有组合。
平面标准影像先在f64中裁剪到保守地面视野,再转换为局部f32顶点,修复高缩放时低级别整世界瓦片因深度精度消失的问题。子瓦片与父瓦片淡入的UV同时保留裁剪;球面继续使用曲面网格。实际验证包含DPR1/2、30°/65°、181°世界副本、非对称留白、无底图及标准影像共六帧。这里描述Rust底图宿主;生产原生C和内置QuickJS现已进一步接入,见原生倾斜地图。WASM/主线程/严格Worker已经接通,桌面共同适配已接通,见桌面倾斜地图,styleRasterPitchedSources保持false。
当前原生 C 与内置 QuickJS 已接入实际倾斜绘制,完整范围见原生倾斜地图。其它共同宿主继续接通。
WASM、网页主线程和严格 Worker 已接通实际共同倾斜绘制,见网页倾斜地图。桌面同形倾斜脚本、完整样式/三维查询及跨入口验收继续后续阶段。