这篇文章主要通过SpatialMap_Sparse_ImageTarget这个场景,说明一下实现稀疏空间地图的脚本。
一、SparseSpatialMapController脚本
public enum DataSource
{
MapBuilder,
MapManager,
}
public class SparseSpatialMapInfo
{
public string ID = string.Empty;
public string Name = string.Empty;
}
这是指定稀疏地图的数据来自哪里,MapBuilder是自身建造,就是通过相机自己识别空间。MapManager是加载从服务器下载的数据使用。 SparseSpatialMapInfo就是用来区别地图信息的
private void LoadMapBuilderInfo()
{
MapInfo = new SparseSpatialMapInfo(); //地图信息
if (MapInfoAvailable != null) //地图信息可用,回调
{
MapInfoAvailable();
}
UpdateMapInLocalizer();
}
private void LoadMapManagerInfo(MapManagerSourceData source)
{
if (!string.IsNullOrEmpty(source.ID))
{
MapInfo = new SparseSpatialMapInfo() { ID = source.ID, Name = source.Name };
if (MapInfoAvailable != null)
{
MapInfoAvailable();
}
UpdateMapInLocalizer();
}
}
MapBuilder首先创建一个默认的地图信息ID和Name,然后更新地图
MapManager会传入MapManagerSourceData,这个类包含的数据和SparseSpatialMapInfo一样,就相当于传入准备好的地图的id和name
private void UpdateMapInLocalizer() //在定位器中更新地图
{
if (MapInfo == null) //不管哪种方式都需要SparseSpatialMapInfo数据
{
return;
}
if ((SourceType == DataSource.MapBuilder && !string.IsNullOrEmpty(MapInfo.ID)) ||
(SourceType != DataSource.MapBuilder && string.IsNullOrEmpty(MapInfo.ID)))
{ //如果属于MapBuilder,那么你的地图id就应该是空的
return; //如果属于MapManager,地图id就不能是空的
}
if (loader && loader != mapWorker) //loader没有被刷新,相当于使用之前的loader
{
switch (SourceType)
{
case DataSource.MapBuilder:
loader.UnloadSparseSpatialMapBuild(this); //卸载这个地图
loader = null;
break;
case DataSource.MapManager:
loader.UnloadSparseSpatialMap(this, (map, status, error) =>
{
if (MapUnload != null) //卸载地图回调
{
MapUnload(map, status, error);
}
});
loader = null;
break;
default:
break;
}
}
if (mapWorker && mapWorker != loader) //mapWorker 保存对应的地图处理SparseSpatialMapWorkerFrameFilter类
{
var worker = mapWorker;
switch (SourceType)
{
case DataSource.MapBuilder:
mapWorker.LoadSparseSpatialMapBuild(this); //初始化地图建造
loader = mapWorker;
break;
case DataSource.MapManager:
mapWorker.LoadSparseSpatialMap(this, (map, status, error) => //加载地图数据
{
if (worker == mapWorker && !status)
{
loader = null;
}
if (MapLoad != null)
{
MapLoad(map, status, error);
}
});
loader = mapWorker;
break;
default:
break;
}
}
}
internal void UpdatePointCloud(Buffer buffer)
{
var bufferFloat = new float[buffer.size() / 4];
if (buffer.size() > 0)
{
Marshal.Copy(buffer.data(), bufferFloat, 0, bufferFloat.Length);
}
PointCloud = Enumerable.Range(0, bufferFloat.Length / 3).Select(k =>
{
return new Vector3(bufferFloat[k * 3], bufferFloat[k * 3 + 1], -bufferFloat[k * 3 + 2]);
}).ToList();
UpdatePointCloud();
}
获取到数据后SparseSpatialMapWorkerFrameFilter会调用UpdatePointCloud让你更新数据,Buffer这个类保存和数据地址,和数据长度。数据保存的方式是Byte,因此创建float数据,长度除以4。Marshal.Copy通过地址和长度复制出数据。float数组就是3个为一组,组成一个点的坐标保存再PointCloud中。
private void UpdatePointCloud()
{
if (!PointCloudParticleSystem)
{
return;
}
if (!ShowPointCloud || PointCloud == null)
{
PointCloudParticleSystem.Clear();
return;
}
var particles = PointCloud.Select(p =>
{
var particle = new ParticleSystem.Particle();
particle.position = p;
particle.startLifetime = pointCloudParticleParameter.StartLifetime;
particle.remainingLifetime = pointCloudParticleParameter.RemainingLifetime;
particle.startSize = pointCloudParticleParameter.StartSize;
particle.startColor = pointCloudParticleParameter.StartColor;
return particle;
}).ToArray();
PointCloudParticleSystem.SetParticles(particles, particles.Length);
}
这个就是根据点数据,更新粒子的数据。就是屏幕上看到的很多点。要修改点的效果,就可以把粒子改成其他的东西
// pointInView should be normalized to [0, 1]
public List<Vector3> HitTest(Vector2 pointInView)
{
var points = new List<Vector3>();
if (!IsLocalizing || !mapWorker || mapWorker.Localizer == null)
{
return points;
}
var session = mapWorker.Session;
if (!session || session.FrameCameraParameters.OnNone || session.Assembly == null || !session.Assembly.Camera)
{
return points;
}
var coord = EasyARController.Instance.Display.ImageCoordinatesFromScreenCoordinates(pointInView, session.FrameCameraParameters.Value, session.Assembly.Camera);
var hitPoints = mapWorker.Localizer.hitTestAgainstPointCloud(coord.ToEasyARVector());
foreach (var p in hitPoints)
{
points.Add(new Vector3(p.data_0, p.data_1, -p.data_2));
}
return points;
}
这个函数是测试是否点击到点云的,如果点击到,那么就会按照点云从近到远的顺序返回一个坐标数组。传入的是屏幕坐标
public void Host(string name, Optional<Image> preview)
{
if (SourceType != DataSource.MapBuilder || MapInfo == null || !string.IsNullOrEmpty(MapInfo.ID) || !mapWorker || mapWorker.Builder == null)
{
throw new Exception("Map Unhostable");
}
mapWorker.HostSparseSpatialMap(this, name, preview, (map, status, error) =>
{
if (MapHost != null)
{
MapHost(map, status, error);
}
});
}
上传数据到服务器 preview是一张相机的截屏,就是方便你区分那个场景。上传成功会回调一个id,用于下载。
下篇文章讲解SparseSpatialMapWorkerFrameFilter这个类。