array(2) { ["docs"]=> array(10) { [0]=> array(10) { ["id"]=> string(3) "428" ["text"]=> string(77) "Visual Studio 2017 单独启动MSDN帮助(Microsoft Help Viewer)的方法" ["intro"]=> string(288) "目录 ECharts 异步加载 ECharts 数据可视化在过去几年中取得了巨大进展。开发人员对可视化产品的期望不再是简单的图表创建工具,而是在交互、性能、数据处理等方面有更高的要求。 chart.setOption({ color: [ " ["username"]=> string(8) "DonetRen" ["tagsname"]=> string(55) "Visual Studio 2017|MSDN帮助|C#程序|.NET|Help Viewer" ["tagsid"]=> string(23) "[401,402,403,"300",404]" ["catesname"]=> string(0) "" ["catesid"]=> string(2) "[]" ["createtime"]=> string(10) "1511400964" ["_id"]=> string(3) "428" } [1]=> array(10) { ["id"]=> string(3) "427" ["text"]=> string(42) "npm -v;报错 cannot find module "wrapp"" ["intro"]=> string(288) "目录 ECharts 异步加载 ECharts 数据可视化在过去几年中取得了巨大进展。开发人员对可视化产品的期望不再是简单的图表创建工具,而是在交互、性能、数据处理等方面有更高的要求。 chart.setOption({ color: [ " ["username"]=> string(4) "zzty" ["tagsname"]=> string(50) "node.js|npm|cannot find module "wrapp“|node" ["tagsid"]=> string(19) "[398,"239",399,400]" ["catesname"]=> string(0) "" ["catesid"]=> string(2) "[]" ["createtime"]=> string(10) "1511400760" ["_id"]=> string(3) "427" } [2]=> array(10) { ["id"]=> string(3) "426" ["text"]=> string(54) "说说css中pt、px、em、rem都扮演了什么角色" ["intro"]=> string(288) "目录 ECharts 异步加载 ECharts 数据可视化在过去几年中取得了巨大进展。开发人员对可视化产品的期望不再是简单的图表创建工具,而是在交互、性能、数据处理等方面有更高的要求。 chart.setOption({ color: [ " ["username"]=> string(12) "zhengqiaoyin" ["tagsname"]=> string(0) "" ["tagsid"]=> string(2) "[]" ["catesname"]=> string(0) "" ["catesid"]=> string(2) "[]" ["createtime"]=> string(10) "1511400640" ["_id"]=> string(3) "426" } [3]=> array(10) { ["id"]=> string(3) "425" ["text"]=> string(83) "深入学习JS执行--创建执行上下文(变量对象,作用域链,this)" ["intro"]=> string(288) "目录 ECharts 异步加载 ECharts 数据可视化在过去几年中取得了巨大进展。开发人员对可视化产品的期望不再是简单的图表创建工具,而是在交互、性能、数据处理等方面有更高的要求。 chart.setOption({ color: [ " ["username"]=> string(7) "Ry-yuan" ["tagsname"]=> string(33) "Javascript|Javascript执行过程" ["tagsid"]=> string(13) "["169","191"]" ["catesname"]=> string(0) "" ["catesid"]=> string(2) "[]" ["createtime"]=> string(10) "1511399901" ["_id"]=> string(3) "425" } [4]=> array(10) { ["id"]=> string(3) "424" ["text"]=> string(30) "C# 排序技术研究与对比" ["intro"]=> string(288) "目录 ECharts 异步加载 ECharts 数据可视化在过去几年中取得了巨大进展。开发人员对可视化产品的期望不再是简单的图表创建工具,而是在交互、性能、数据处理等方面有更高的要求。 chart.setOption({ color: [ " ["username"]=> string(9) "vveiliang" ["tagsname"]=> string(0) "" ["tagsid"]=> string(2) "[]" ["catesname"]=> string(8) ".Net Dev" ["catesid"]=> string(5) "[199]" ["createtime"]=> string(10) "1511399150" ["_id"]=> string(3) "424" } [5]=> array(10) { ["id"]=> string(3) "423" ["text"]=> string(72) "【算法】小白的算法笔记:快速排序算法的编码和优化" ["intro"]=> string(288) "目录 ECharts 异步加载 ECharts 数据可视化在过去几年中取得了巨大进展。开发人员对可视化产品的期望不再是简单的图表创建工具,而是在交互、性能、数据处理等方面有更高的要求。 chart.setOption({ color: [ " ["username"]=> string(9) "penghuwan" ["tagsname"]=> string(6) "算法" ["tagsid"]=> string(7) "["344"]" ["catesname"]=> string(0) "" ["catesid"]=> string(2) "[]" ["createtime"]=> string(10) "1511398109" ["_id"]=> string(3) "423" } [6]=> array(10) { ["id"]=> string(3) "422" ["text"]=> string(64) "JavaScript数据可视化编程学习(二)Flotr2,雷达图" ["intro"]=> string(288) "目录 ECharts 异步加载 ECharts 数据可视化在过去几年中取得了巨大进展。开发人员对可视化产品的期望不再是简单的图表创建工具,而是在交互、性能、数据处理等方面有更高的要求。 chart.setOption({ color: [ " ["username"]=> string(7) "chengxs" ["tagsname"]=> string(28) "数据可视化|前端学习" ["tagsid"]=> string(9) "[396,397]" ["catesname"]=> string(18) "前端基本知识" ["catesid"]=> string(5) "[198]" ["createtime"]=> string(10) "1511397800" ["_id"]=> string(3) "422" } [7]=> array(10) { ["id"]=> string(3) "421" ["text"]=> string(36) "C#表达式目录树(Expression)" ["intro"]=> string(288) "目录 ECharts 异步加载 ECharts 数据可视化在过去几年中取得了巨大进展。开发人员对可视化产品的期望不再是简单的图表创建工具,而是在交互、性能、数据处理等方面有更高的要求。 chart.setOption({ color: [ " ["username"]=> string(4) "wwym" ["tagsname"]=> string(0) "" ["tagsid"]=> string(2) "[]" ["catesname"]=> string(4) ".NET" ["catesid"]=> string(7) "["119"]" ["createtime"]=> string(10) "1511397474" ["_id"]=> string(3) "421" } [8]=> array(10) { ["id"]=> string(3) "420" ["text"]=> string(47) "数据结构 队列_队列实例:事件处理" ["intro"]=> string(288) "目录 ECharts 异步加载 ECharts 数据可视化在过去几年中取得了巨大进展。开发人员对可视化产品的期望不再是简单的图表创建工具,而是在交互、性能、数据处理等方面有更高的要求。 chart.setOption({ color: [ " ["username"]=> string(7) "idreamo" ["tagsname"]=> string(40) "C语言|数据结构|队列|事件处理" ["tagsid"]=> string(23) "["246","247","248",395]" ["catesname"]=> string(12) "数据结构" ["catesid"]=> string(7) "["133"]" ["createtime"]=> string(10) "1511397279" ["_id"]=> string(3) "420" } [9]=> array(10) { ["id"]=> string(3) "419" ["text"]=> string(47) "久等了,博客园官方Android客户端发布" ["intro"]=> string(288) "目录 ECharts 异步加载 ECharts 数据可视化在过去几年中取得了巨大进展。开发人员对可视化产品的期望不再是简单的图表创建工具,而是在交互、性能、数据处理等方面有更高的要求。 chart.setOption({ color: [ " ["username"]=> string(3) "cmt" ["tagsname"]=> string(0) "" ["tagsid"]=> string(2) "[]" ["catesname"]=> string(0) "" ["catesid"]=> string(2) "[]" ["createtime"]=> string(10) "1511396549" ["_id"]=> string(3) "419" } } ["count"]=> int(200) } 222 C++瓦片地图坐标转换的实现详解 - 爱码网

一、前言

严格来说,瓦片的角度并不是45度。因为为了美术作图方便,图片的宽高比一般为2:1,如下图所示,它的实际角度为arctan(1/2),不过这个数值对我们不重要。正如鱼香肉丝没有鱼一般,叫它45度瓦片也无妨,由于它是一个菱形,所以这里我们称它为菱形瓦片。

C++瓦片地图坐标转换的实现详解

宽高比为2:1的菱形瓦片

或许有人认为任意角度的瓦片都是可以的,其实不然,因为我们要考虑线条锯齿的画法,如果采用非整数比,则线条不是规律的(非像素游戏或许可以试试)。所以最常见的比例为2:1,其次是1:1。

还有一个问题,我们观察菱形的四分之一部分,它将一个矩形一分为二。我们当然期望它是平分的,然而这根本做不到,因为它不是理论的对角线。对于正方形瓦片来说,边缘是不会重叠的。而菱形瓦片不可避免的边缘存在重叠。

C++瓦片地图坐标转换的实现详解

边缘必然重叠

二、定义

我们定义地图上的一个点为世界(World)坐标,它是连续的,用浮点数表示。然后格子的索引叫地图(Map)坐标,它是离散的,用有符号整数表示。不过这里地图坐标的取值未考虑负数,如要使用负数的地图坐标则需要对代码略微修改。

比如下图的p点,我们假设格子宽10像素。则其世界坐标为(54,67),而地图坐标为(5,6)。

C++瓦片地图坐标转换的实现详解

矩形瓦片示例

三、矩形瓦片

矩形瓦片的代码很简单,如下:

//! 矩形瓦片地图
template<Vector2 TILE_SIZE>
class Rectangle
{
public:
	/**
	* @brief 地图坐标 -> 世界坐标
	*/
	constexpr Vector2 Map2World(const Point& xy)
	{
		return toVector2(xy) * TILE_SIZE;
	}
	/**
	* @brief 世界坐标 -> 地图坐标
	*/
	constexpr Point World2Map(const Vector2& pos)
	{
		return toPoint(pos / TILE_SIZE);
	}
};

四、菱形瓦片

1.斜菱形瓦片

这里的斜指的是,整个地图拼出来是斜着的,也是一个菱形,如下图所示(这是常用的算法):

C++瓦片地图坐标转换的实现详解

斜菱形瓦片

我们令x'y'为地图(格子)坐标,xy为世界(像素)坐标,其中wh为瓦片宽高,则有如下关系:

C++瓦片地图坐标转换的实现详解

上面这个式子通过简单的变换,就可以得出:

C++瓦片地图坐标转换的实现详解

转换代码如下,这里就体现出了将瓦片大小(TILE_SIZE)作为模板的好处了,其中除2的操作会自动合并为常量表达式,世界坐标到地图坐标的转换其中加了0.5,是为了四舍五入。

//! 斜45度瓦片地图
template<Vector2 TILE_SIZE>
class DiamondSlant
{
public:
	/**
	* @brief 地图坐标 -> 世界坐标
	*/
	constexpr Vector2 Map2World(const Point& xy)
	{
		return { (xy[1] + xy[0]) * TILE_SIZE[0] / 2.0,  (xy[1] - xy[0]) * TILE_SIZE[1] / 2.0};
	}
	/**
	* @brief 世界坐标 -> 地图坐标
	*/
	constexpr Point World2Map(const Vector2& pos)
	{
		Vector2 xy_div = pos / TILE_SIZE;
		return toPoint(Vector2{ xy_div[0] - xy_div[1] + 0.5, xy_div[0] + xy_div[1] - 0.5 });
	}
};

2.正菱形瓦片

下面这种整体也是一个矩形,它的特点是x轴移动瓦片宽度,y轴只移动半个瓦片高度,当y为奇数时,x再往右移动半个瓦片宽度。(有些文章是y为偶数时x移动,原理相同)

C++瓦片地图坐标转换的实现详解

正菱形瓦片

容易得到,从格子坐标到世界坐标,如下:

当y为偶数时:

C++瓦片地图坐标转换的实现详解

当y为奇数时:

C++瓦片地图坐标转换的实现详解

这里出现和上面不一样的事了,无法简单的逆推公式来表示x'y'。因为通过世界(像素)坐标无法轻松得到它的地图(格子)坐标的y是奇数还是偶数。

从格子坐标到世界坐标的代码如下:

/**
* @brief 地图坐标 -> 世界坐标
*/
constexpr Vector2 Map2World(const Point& xy)
{
	Vector2 pos = { TILE_SIZE[0] * xy[0] , TILE_SIZE[1] / 2 * xy[1] };
	if (xy[1] % 2 != 0)
	{//奇数行向右偏移 w / 2
		pos[0] += TILE_SIZE[0] / 2;
	}
	return pos;
}

而从世界坐标到格子坐标则比较麻烦了,如下,我们划分网格:

C++瓦片地图坐标转换的实现详解

划分网格

明显格子大小为(w,h),记世界坐标pos所在的格子为p,则有:

C++瓦片地图坐标转换的实现详解

来看单个划分网格内,如下:

C++瓦片地图坐标转换的实现详解

单个划分格子

设瓦片格子坐标为xy,则当 pos在菱形内时,有:

C++瓦片地图坐标转换的实现详解

当 pos在菱形外时,四个角则分别判断:右下角偏移(0,1);左下角偏移(-1,1);左上角偏移(-1,-1);右上角偏移(0,-1)。

所以最终实现代码如下:

//! 平菱形瓦片地图
template<Vector2 TILE_SIZE>
class DiamondFlat
{
public:
	/**
	* @brief 地图坐标 -> 世界坐标
	*/
	constexpr Vector2 Map2World(const Point& xy)
	{
		Vector2 pos = { TILE_SIZE[0] * xy[0] , TILE_SIZE[1] / 2 * xy[1] };
		if (xy[1] % 2 != 0)
		{//奇数行向右偏移 w / 2
			pos[0] += TILE_SIZE[0] / 2;
		}
		return pos;
	}
	/**
	* @brief 世界坐标 -> 地图坐标
	*/
	constexpr Point World2Map(const Vector2& pos)
	{
		constexpr Vector2 TILE_SIZE_HALF = TILE_SIZE / 2.0;
		//四分之一矩形面积
		constexpr real s = Each::AccumulateMul(TILE_SIZE_HALF);
		//先计算矩形下标
		Point p = toPoint(pos / TILE_SIZE);
		//在矩形内坐标
		Vector2 p1 = pos - toVector2(p) * TILE_SIZE - TILE_SIZE_HALF;
		//点围成矩形面积
		real sp = abs(p1[0] * TILE_SIZE_HALF[1]) + abs(p1[1] * TILE_SIZE_HALF[0]);
		p[1] *= 2;
		if (s < sp)
		{
			if (p1[0] > 0 && p1[1] > 0)
				return p + Point{ 0, 1 };
			else if (p1[0] < 0 && p1[1] > 0)
				return p + Point{ -1, 1 };
			else if (p1[0] < 0 && p1[1] < 0)
				return p + Point{ -1, -1 };
			else if (p1[0] > 0 && p1[1] < 0)
				return  p + Point{ 0, -1 };
			else
				return p;
		}
		else
		{
			return p;
		}
	}
};

五、点在菱形内判断

如下图所示,以菱形中心为原点建立坐标系:

C++瓦片地图坐标转换的实现详解

p在对角线上时

当p点在菱形上时,红绿区域面积相等(对角线平分面积),所以:

C++瓦片地图坐标转换的实现详解

(红色区域加了两次,将其中变成一个绿色区域)

则当p点在菱形外时,

C++瓦片地图坐标转换的实现详解

;在菱形内时

C++瓦片地图坐标转换的实现详解

源码位置:传送门

原文地址:https://blog.csdn.net/u014629601/article/details/125282025

相关文章: