.NET实时2D渲染入门·动态时钟

前言

说来这是个我和我老婆的爱情故事。

从小以来“坦克大战”、“魂斗罗”等游戏总令我魂牵梦绕。这些游戏的基础就是 2D实时渲染,以前没意识,直到后来找到了 Direct2D。我的 2D实时渲染入门,是从这个 动态时钟开始的。

本文将使用我写的“准游戏引擎” FlysEngine完成。它是对 Direct2D.NETSharpDX浅层次的封装,隐藏了一些细节,简化了一些调用。同时还保留了 Direct2D的原汁原味。

本文的最终效果如下:

640?wx_fmt=gif

绘制动态时钟

要绘制动态时钟,需要有以下步骤:

  • 创建一个实时渲染窗口;

  • 画一个圆圈,表示时钟边缘;

  • 在圆圈内等距离画上 60个分钟刻度,其中 12个比较长,为小时刻度;

  • 用不同粗细、不同长短、不同颜色的画笔画上时钟、分钟和秒钟。

实时渲染窗口

using var form = new RenderWindow { ClientSize = new System.Drawing.Size(400, 400) };	
form.Draw += (RenderWindow sender, DeviceContext ctx) =>	
{	ctx.Clear(Color.CornflowerBlue);	
};	
RenderLoop.Run(form, () => form.Render(1, PresentFlags.None));

其中 form.Render(1,...)中的 1表示垂直同步,玩过游戏的可能见过,这个设置可以在尽可能节省 CPU/GPU资源的同时得到最佳的呈现效果。

熟悉 glut的肯定知道,这种写法和 glut非常像,执行效果如下:

640?wx_fmt=png

注意:

RenderWindow其实继承于 System.Windows.Forms.Form,确实是基于“ WinForm”,但实质却和“拖控件”完全不一样。“控件”是模态的,本身有状态,但 Direct2D是实时渲染,界面完全没有状态,需要动态每隔一个垂直同步时间(如 1/60秒)全部清除,然后再重绘一次。

画圆圈

RenderWindow简单封装了 Direct2D,可以直接使用里面的 XResource属性来访问 DirectX相关资源,包括:

  • Direct2DFactory

  • Direct2DDeviceContext

  • DirectWriteFactory

  • TransitionLibrary 动画库

  • AnimationManager 动画管理器

  • SwapChain

  • WICImagingFactory2

除此之外,还进一步封装了以下组件,以简化图片、文字、颜色等调用和渲染:

  • TextFormatManager 简化创建 TextFormat

  • BitmapManager 简化加载图片

  • TextLayoutManager 简化创建 TextLayout

  • .GetColor(color) 方法,简化使用颜色

这里我们将使用 Direct2DDeviceContext,这在 COM中的名字叫 ID2D1DeviceContext

回到 Draw事件,它包含两个参数:(RenderWindowsender,DeviceContextctx)

  • 其中 sender就是原窗口,可以用外层的 form代替;

  • ctx参数就是 D2D绘图的核心,我们将围绕它进行绘制。

要画圆圈,得先算出一个能放下一个完整圆的半径,并留下少许空间( 5):

float r = Math.Min(ctx.Size.Width, ctx.Size.Height) / 2 - 5

然后调用 ctx参数,使用黑色画笔将圆画出来,线宽为 1/40半径:

ctx.DrawEllipse(new Ellipse(Vector2.Zero, r, r), sender.XResource.GetColor(Color.Black), r/40);

执行效果如下:

640?wx_fmt=png

可见圆只显示了四分之一,要显示完整的圆,必须将其“移动”到屏幕正中心,我们可以调整圆的参数,将中心点从 Vector2.Zero改成 newVector2(ctx.Size.Width/2,ctx.Size.Height/2),或者用更简单的办法,通过矩阵变换:

ctx.Transform = Matrix3x2.Translation(ctx.Size.Width/2, ctx.Size.Height/2);

注意:“矩阵变换”这几个字听起来总令人联想到“高数”,挺吓人的。但实际是并不是非要知道线性代码基础才能使用。首先只要知道它能完成任务即可,之后再慢慢理解也行。

有多种方法可以完成像平移这样的任务,但通常来说使用“矩阵变换”更简单,更不伤脑筋,尤其是多个对象,进行旋转、扭曲等复杂、或者组合操作等,这些操作如果不使用“矩阵变换”会非常非常麻烦。

这样,即可将该圆“平移”至屏幕正中心,执行效果如下:

640?wx_fmt=png

Draw方法完整代码:

ctx.Clear(Color.CornflowerBlue);	
float r = Math.Min(ctx.Size.Width, ctx.Size.Height) / 2 - 5;	
ctx.Transform = Matrix3x2.Translation(ctx.Size.Width/2, ctx.Size.Height/2);	
ctx.DrawEllipse(new Ellipse(Vector2.Zero, r, r), sender.XResource.GetColor(Color.Black), r/40);

画刻度

刻度就是线条,共 60-12=48个分钟刻度和 12个时钟刻度,其中分钟刻度较短,时钟刻度较长。

刻度的一端是沿着圆的边缘,另一端朝着圆的中心,边缘位置可以通过 sin/cos等三角函数计算出来……呃,可能早忘记了,不怕,我们有“矩阵变换”。

利用矩阵变换,可以非常容易地完成这项工作:

for (var i = 0; i < 60; ++i)	
{	ctx.Transform = 	Matrix3x2.Rotation(MathF.PI * 2 / 60 * i) * 	Matrix3x2.Translation(ctx.Size.Width/2, ctx.Size.Height/2);	ctx.DrawLine(new Vector2(r-r/30,0), new Vector2(r,0), form.XResource.GetColor(Color.Black),r/200);	
}

执行效果如下:

640?wx_fmt=png

注意:此处用到了矩阵乘法:

Matrix3x2.Rotation(MathF.PI * 2 / 60 * i) * 	
Matrix3x2.Translation(ctx.Size.Width/2, ctx.Size.Height/2);

注意乘法是有顺序的,这符合空间逻辑,可以这样想想,先旋转再平移,和先平移再旋转显然是有区别的。

然后再加上长时钟,只需在原代码基础上加个判断即可,如果 i%5==0,则为长时钟,粗细设置为 r/100

for (var i = 0; i < 60; ++i)	
{	ctx.Transform =	Matrix3x2.Rotation(MathF.PI * 2 / 60 * i) *	Matrix3x2.Translation(ctx.Size.Width / 2, ctx.Size.Height / 2);	if (i % 5 == 0)	{   // 时钟	ctx.DrawLine(new Vector2(r - r / 15, 0), new Vector2(r, 0), form.XResource.GetColor(Color.Black), r/100);	}	else	{   // 分钟	ctx.DrawLine(new Vector2(r - r / 30, 0), new Vector2(r, 0), form.XResource.GetColor(Color.Black), r/200);	}	
}

执行效果如下:

640?wx_fmt=png

画时、分、秒钟

时、分、秒钟是动态的,必须随着时间变化而变化;其中时钟最短、最粗,分钟次之,秒钟最细长,然后时钟必须叠在分钟和秒钟之上。

用代码实现,可以先画秒钟、再画分钟和时钟,即可实现重叠效果。还可以通过设置一定的透明度和不同的颜色,可以让它们区分更明显。

获取当前时间可以通过 DateTime.Now来完成, DateTime提供了时、分、秒和毫秒,可以轻松地计算各个指针应该指向的位置。

画秒钟的代码如下,显示为蓝色,长度为 0.9倍半径,宽度为 1/50半径:

// 秒钟	
ctx.Transform = 	Matrix3x2.Rotation(MathF.PI * 2 / 60 * time.Second) * 	Matrix3x2.Translation(ctx.Size.Width / 2, ctx.Size.Height / 2);	
ctx.DrawLine(Vector2.Zero, new Vector2(0,-r*0.9f), form.XResource.GetColor(Color.Blue), r/50);

效果如下:

640?wx_fmt=gif

依法炮制,可以画出分钟和时钟:

// 分钟	
ctx.Transform =	Matrix3x2.Rotation(MathF.PI * 2 / 60 * time.Minute) *	Matrix3x2.Translation(ctx.Size.Width / 2, ctx.Size.Height / 2);	
ctx.DrawLine(Vector2.Zero, new Vector2(0, -r * 0.8f), form.XResource.GetColor(Color.Green), r / 35);	
// 时钟	
ctx.Transform =	Matrix3x2.Rotation(MathF.PI * 2 / 12 * time.Hour) *	Matrix3x2.Translation(ctx.Size.Width / 2, ctx.Size.Height / 2);	
ctx.DrawLine(Vector2.Zero, new Vector2(0, -r * 0.7f), form.XResource.GetColor(Color.Red), r / 20);

效果如下:

640?wx_fmt=gif

优化

其实到了这一步,已经是一个完整的,可运行的时钟了,但还能再优化优化。

半透明时钟

首先可以设置一定的半透明度,使三根钟重叠时不显得很突兀,代码如下:

var blue = new Color(red: 0.0f, green: 0.0f, blue: 1.0f, alpha: 0.7f);	
ctx.DrawLine(Vector2.Zero, new Vector2(0,-r*0.9f), form.XResource.GetColor(blue), r/50);

只需将原本的 Color.Blue等颜色改成自定义,并且指定 alpha参数为 0.7(表示 70%半透明)即可,效果如下:

640?wx_fmt=png

时钟两端的尖角或者圆角

Direct2D可以很方便地控制绘制的线段两端,有许多风格可供选择,具体可以参见 CapStyle枚举:

public enum CapStyle	
{	/// <unmanaged>D2D1_CAP_STYLE_FLAT</unmanaged>	Flat,	/// <unmanaged>D2D1_CAP_STYLE_SQUARE</unmanaged>	Square,	/// <unmanaged>D2D1_CAP_STYLE_ROUND</unmanaged>	Round,	/// <unmanaged>D2D1_CAP_STYLE_TRIANGLE</unmanaged>	Triangle	
}

此处我们将使用 Round用于做中心点,用 Triangle用于做针尖,首先创建一个 StrokeStyle对象:

using var clockLineStyle = new StrokeStyle(form.XResource.Direct2DFactory, new StrokeStyleProperties 	
{ 	StartCap = CapStyle.Round, 	EndCap = CapStyle.Triangle, 	
});

然后在调用 ctx.DrawLine()时,将 clockLineStyle参数传入最后一个参数即可:

ctx.DrawLine(Vector2.Zero, new Vector2(0,-r*0.9f), form.XResource.GetColor(blue), r/50, clockLineStyle);

执行效果如下(可见有那么点意思了):

640?wx_fmt=gif

平滑移动

Direct2D是实时渲染,我们不能浪费这实时二字带来的好处。更何况显示出来的时钟也不太合理,因为当时时间是 9:57,此时时钟应该指向偏 10的位置。但现在由于忽略了这一分量,指向的是 9,这不符合实时的时钟。

因此计算小时角度时,可以加入分钟分量,计算分钟角度时,可以加入秒钟分量,计算秒钟角度时,也可以加入毫秒的分量。代码只需将矩阵变换代码稍微变动一点点即可:

// 秒钟	
ctx.Transform = 	Matrix3x2.Rotation(MathF.PI * 2 / 60 * (time.Second + time.Millisecond / 1000.0f)) * 	Matrix3x2.Translation(ctx.Size.Width / 2, ctx.Size.Height / 2);	
// ...	
// 分钟	
ctx.Transform =	Matrix3x2.Rotation(MathF.PI * 2 / 60 * (time.Minute + time.Second / 60.0f)) *	Matrix3x2.Translation(ctx.Size.Width / 2, ctx.Size.Height / 2);	
// ...	
// 时钟	
ctx.Transform =	Matrix3x2.Rotation(MathF.PI * 2 / 12 * (time.Hour + time.Minute / 60.0f)) *	Matrix3x2.Translation(ctx.Size.Width / 2, ctx.Size.Height / 2);

执行效果如下:

640?wx_fmt=gif

阴影效果

和边缘刻度不一样,时钟多少是和窗口底层有距离的,因此怎么说也会显示一些阴影效果。这在 Direct2D中也能轻易实现。代码会复杂一点,过程如下:

  • 先将创建一个临时的 Bitmap1;

  • 将时、分、秒钟绘制到这个 Bitmap中;

  • 创建一个 ShadowEffect,传入这个 Bitmap的内容生成一个阴影贴图;

  • 调用 ctx.DrawImage()将 ShadowEffect先绘制;

  • 调用 ctx.DrawBitmap()绘制最后真正的时、分、秒钟。

注意这个过程的顺序不能错,否则可能出现阴影显示的真实物体上的虚幻效果。

临时的 Bitmap1ShadowEffect可以在 CreateDeviceSizeResourcesReleaseDeviceSizeResources事件中创建和销毁:

Bitmap1 bitmap = null;	
Shadow shadowEffect = null;	
form.CreateDeviceSizeResources += (RenderWindow sender) =>	
{	bitmap = new Bitmap1(form.XResource.RenderTarget, form.XResource.RenderTarget.PixelSize,	new BitmapProperties1(new PixelFormat(Format.B8G8R8A8_UNorm, SharpDX.Direct2D1.AlphaMode.Premultiplied),	dpi, dpi, BitmapOptions.Target));	shadowEffect = new SharpDX.Direct2D1.Effects.Shadow(form.XResource.RenderTarget);	
};	
form.ReleaseDeviceSizeResources += o =>	
{	bitmap.Dispose();	shadowEffect.Dispose();	
};

其中 dpiDirect2DFactory.DesktopDpi.Width进行获取。

先将 ctxTarget属性指定这个 bitmap,但又同时保存老的 Target属性用于稍后绘制:

var oldTarget = ctx.Target;	
ctx.Target = bitmap;	
ctx.BeginDraw();	
{	ctx.Clear(Color.Transparent);	// 上文中的绘制时钟部分...	
}	
ctx.EndDraw();

注意 ctx.Clear(Color.Transparent);是有必要的,否则将出现重影:

640?wx_fmt=png

这样即可将时钟单独绘制到 bitmap中,对这个 bitmap生成一个阴影:

shadowEffect.SetInput(0, ctx.Target, invalidate: new RawBool(false));

最后进行绘制,绘制时记得顺序:

ctx.Target = oldTarget;	
ctx.BeginDraw();	
{	ctx.Transform = Matrix3x2.Identity;	ctx.UnitMode = UnitMode.Pixels;	ctx.DrawImage(shadowEffect);	ctx.DrawBitmap(bitmap, 1.0f, InterpolationMode.NearestNeighbor);	ctx.UnitMode = UnitMode.Dips;	
}

注意两点:

首先,设置 ctx.Transform=identity是有必要的,否则会上文的矩阵变换会一直保持作用;

然后两次设置 ctx.UnitMode=pixels/dips也是有必要的,因为此时的绘制相当于是图片,按照默认的高 DPI显示会导致显示模糊,因此显示图片时需要改成点对点显示;

效果如下:

640?wx_fmt=png

这个阴影默认是完全重叠的,现实中这种光线较小,加一点点平移效果可能会更好:

ctx.DrawImage(shadowEffect, new Vector2(r/20,r/20));

效果如下(显然更逼真了):

640?wx_fmt=gif

更好的动画

有些时钟的秒确实是这样动的,但我印象中儿时的记忆,秒是一格一格地动,它是每动一下,停顿一下再动的那种感觉。

为了实现这种感觉,我加入了 WindowsAnimationManager的功能,这也是 COM组件的一部分,我的 FlysEngine中稍微封装了一下。使用时需要引入一个 timer进行配合:

float secondPosition = DateTime.Now.Second;	
Variable secondVariable = null;	
var timer = new System.Windows.Forms.Timer { Enabled = true, Interval = 1000 };	
timer.Tick += (o, e) =>	
{	secondVariable?.Dispose();	secondVariable = form.XResource.CreateAnimation(secondPosition, DateTime.Now.Second, 0.2f);	
};	
form.FormClosing += delegate { timer.Dispose(); };	
form.UpdateLogic += (window, dt) =>	
{	secondPosition = (float)(secondVariable?.Value ?? 0.0f);	
};

注意此处我使用了 UpdateLogic事件,这也是 FlysEngine中封装的,可以在绘制呈现前执行一段更新逻辑的代码。

然后后面的绘制时,将获取秒的矩阵变换参数改为 secondPosition变量即可:

ctx.Transform = 	Matrix3x2.Rotation(MathF.PI * 2 / 60 * secondPosition) * 	Matrix3x2.Translation(ctx.Size.Width / 2, ctx.Size.Height / 2);

最后的执行效果如下:

640?wx_fmt=gif

看起来一切正常,但……如果经过分钟满时,会出现这种情况:

640?wx_fmt=gif

这是因为秒数从 59秒到 00秒的动画,是一个递减的过程( 59->00),因此秒钟反向转了一圈,这明显不对。

解决这个问题可以这样考虑,如果当前是 59秒,我们假装它是 -1秒即可,这时计算角度不会出错,矩阵变换也没任何问题,通过 C# 8.0强大的 switchexpression功能,可以不需要额外语句,在表达式内即可解决:

secondVariable = form.XResource.CreateAnimation(secondPosition switch	
{	59 => -1, 	var x => x, 	
}, DateTime.Now.Second, 0.2f);

最后的最后,最终效果如下:

640?wx_fmt=gif

结语

记得6年前我老婆第一次来我出租房玩,然后……我给她感受了作为一个程序员的“浪漫”,花了一整个下午时间,把这个 demo0开始做了出来给她看,不过那时我还在用 C++。多年后和她说起这个入门 demo,她仍记忆尤新。

本文中最终效果的代码,可以从我的 github仓库下载:https://github.com/sdcb/blog-data/blob/master/2019/20191021-render-clock-using-dotnet/clock.linq

有了 .NET,那些代码已经远比当年简单,我的确是从这个例子出发,做出了许多好玩的东西,以后有机会我会慢慢介绍,敬请期待。

喜欢的朋友 请关注我的微信公众号:【DotNet骚操作】

640?wx_fmt=jpeg

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/313259.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ASP.NET Core在 .NET Core 3.1 Preview 1中的更新

.NET Core 3.1 Preview 1现在可用。此版本主要侧重于错误修复&#xff0c;但同时也包含一些新功能。对Razor components的部分类支持将参数传递给顶级组件在HttpSysServer中支持共享队列在SameSite cookies的重大更改除了.NET Core 3.1 Preview版本发布之外&#xff0c;我们还发…

小波变换学习(1)

转自&#xff1a;https://www.zhihu.com/question/22864189/answer/40772083从傅里叶变换到小波变换&#xff0c;并不是一个完全抽象的东西&#xff0c;可以讲得很形象。小波变换有着明确的物理意义&#xff0c;如果我们从它的提出时所面对的问题看起&#xff0c;可以整理出非常…

.NET Core 3.0 新 JSON API - JsonDocument

JsonDocument类 JsonDocument是基于Utf8JsonReader 构建的。JsonDocument 可分析 JSON 数据并生成只读文档对象模型 (DOM)&#xff0c;可对模型进行查询&#xff0c;以支持随机访问和枚举。使用 JsonDocument 分析常规 JSON 有效负载并访问其所有成员比使用 Json.NET 快 2-3 倍…

微软推出 Microsoft.Data.SqlClient,替代 System.Data.SqlClient

背景在 .NET 创建之初&#xff0c;System.Data 框架是一个重要的组件。它为创建 .NET 数据库驱动程序提供了一种方式&#xff0c;类似 Visual Basic 的 ActiveX Data Objects。虽然 API 不一样&#xff0c;但重用了它的名称&#xff0c;所以才有了 ADO .NET 这个绰号。ADO 和 A…

C++ 从文件夹中读取文件

OpenCV从文件夹中读取内含文件方法 参考&#xff1a;http://www.2cto.com/kf/201407/316515.html http://www.it610.com/article/5126146.htm http://blog.csdn.net/adong76/article/details/39432467 windows平台代码&#xff1a; [cpp] view plaincopy #include <io.h&…

你必须知道的容器监控 (1) Docker自带子命令与Weave Scope

本篇已加入《》&#xff0c;可以点击查看更多容器化技术相关系列文章。本篇会介绍几个目前比较常用且流行的容器监控工具&#xff0c;首先我们来看看Docker自带的几个监控子命令&#xff1a;ps、top以及stats&#xff0c;然后是一个功能更强的开源监控工具Weave Scope。# 实验环…

char *与char []

由于指针的灵活性&#xff0c;导致指针能代替数组使用&#xff0c;或者混合使用&#xff0c;这些导致了许多指针和数组的迷惑&#xff0c;因此&#xff0c;刻意再次深入探究了指针和数组这玩意儿&#xff0c;其他类型的数组比较简单&#xff0c;容易混淆的是字符数组和字符指针…

.NET Core ORM 类库Petapoco中对分页Page添加Order By对查询的影响

介绍最近一直在使用PetapocoEntity Framework Core结合开发一套系统。使用EFCore进行Code First编码&#xff0c;使用使用Petapoco进行数据库的常规操作。并且结合PetaPoco.SqlKata的使用&#xff0c;减少了编写SQL语句的工作量&#xff0c;对提升开发效率有很大的帮助。Petapo…

.Net Core 3.0 IdentityServer4 快速入门

一、简介IdentityServer4是用于ASP.NET Core的OpenID Connect和OAuth 2.0框架。将IdentityServer4部署到您的应用中具备如下特点&#xff1a;1&#xff09;、认证服务2&#xff09;、单点登陆3&#xff09;、API访问控制4&#xff09;、联合网关5&#xff09;、专注于定制6&…

.NET Core3.0创建Worker Services

.NET CORE 3.0新增了Worker Services的新项目模板&#xff0c;可以编写长时间运行的后台服务&#xff0c;并且能轻松的部署成windows服务或linux守护程序。如果安装的vs2019是中文版本&#xff0c;Worker Services变成了辅助角色服务。Worker Services 咱也不知道怎么翻译成了这…

OpenCV Mat数据类型像素操作

转自&#xff1a;http://blog.csdn.net/skeeee/article/details/13297457 OpenCV图像像素操作及效率分析 在计算机视觉应用中&#xff0c;对于图像内容的读取分析是第一步&#xff0c;所以学习高效的处理图像是很有用的。一个图像有可能包含数以万计的像素&#xff0c;从根本上…

Bumblebee微服务网关之Url重写

为了提高Url访问的统一和友好性&#xff0c;一般访问的Url和服务定义的Url有所不同;为了解决这一问题Bumblebee提供Url重写功能;通过Url重写功能可以在转发前进行一个重写后再转发到后台服务。引用插件Bumblebee中使用Url重写需要引用两个插件&#xff0c;分别是Bumblebee.Conf…

依赖注入:一个Mini版的依赖注入框架

前面的章节中&#xff0c;我们从纯理论的角度对依赖注入进行了深入论述&#xff0c;我们接下来会对.NET Core依赖注入框架进行单独介绍。为了让读者朋友能够更好地理解.NET Core依赖注入框架的设计与实现&#xff0c;我们按照类似的原理创建了一个简易版本的依赖注入框架&#…

.NET Core 3.0 新 JSON API - JsonSerializer

JsonSerializer 前面几节的内容可能稍微有点底层&#xff0c;我们大部分时候可能只需要对C#的类进行串行化或者将JSON数据反串行化成C#类&#xff0c;在.NET Core 3.0里面&#xff0c;我们可以使用JsonSerializer这个类来做这些事情。 例子 还是使用之前用到的json数据&#xf…

Caffe查看每一层学习出来的pattern

Filter visualization http://www.cnblogs.com/dupuleng/articles/4244877.html 这一节参考http://nbviewer.ipython.org/github/BVLC/caffe/blob/master/examples/filter_visualization.ipynb&#xff0c;主要介绍如何显示每一层的参数及输出&#xff0c;这一部分非常重要&am…

.NET Core 3.0 新 JSON API - Utf8JsonWriter

Utf8JsonWriter类 下面研究一下如何写入json文件。这里需要使用Utf8JsonWriter类。 直接看代码&#xff1a; 这个类需要传递的参数类型是Stream或者Buffer&#xff0c;也就是向Stream或Buffer里面写入数据。 那么就提供一个buffer&#xff1a; 下面单独写一个方法&#xff0c;来…

python查看CNN训练模型参数

参照&#xff1a;http://blog.csdn.net/u011762313/article/details/49851795 #!/usr/bin/env python# 引入“咖啡” import caffeimport numpy as np# 使输出的参数完全显示 # 若没有这一句&#xff0c;因为参数太多&#xff0c;中间会以省略号“……”的形式代替 np.set_prin…

Bumblebee微服务网关之consul服务发现

网关需要维护相关负载的服务器&#xff0c;手动添加相对来说是一件比较麻烦的工作&#xff1b;为了解决这一问题组件扩展了一个基于consul服务发现插件&#xff0c;通过配置这个插件和启用后网关会自动从consul服务中获取服务并添加到网关对应的路由负载规则中。引用插件Bumble…

Github带来的不止是开源,还有折叠的认知

几乎每个程序员都知道github&#xff0c;但是知道目前上面有多少repositories的估计没几个。Z哥今天去看了下&#xff0c;最新的数量显示是1.39亿个。▲截图来自于github.com而这个数字在2008年那会只是3.3万个。这个增长速度可谓真的是“爆炸式增长”。与此同时&#xff0c;大…

最优间隔分类器-SVM

http://blog.csdn.net/Andrewseu/article/details/46991541 本讲大纲&#xff1a; 1.最优间隔分类器(optimal margin classifier) 2.原始/对偶优化问题&#xff08;KKT&#xff09;&#xff08;primal/dual optimization problem&#xff09; 3.SVM对偶(SVM dual) 4.核方法(…