Ogre 渲染目标解析与多文本合并渲染
实现目标
因为需求,想找一个在Ogre中好用的文本显示,经过查找和一些比对.有三种方案
一利用Overlay的2D显示来达到效果.
http://www.ogre3d.org/tikiwiki/tiki-index.php?page=MovableTextOverlay
二重写Renderable与MovableObject,利用对应字体查找到每个字符元素纹理坐标.
http://www.ogre3d.org/tikiwiki/tiki-index.php?page=MovableText
三利用BillboardSet在3D空间显示(公告板技术),这个有点意思的是对于字体的处理是自己用GUI画成纹理.
http://www.ogre3d.org/tikiwiki/tiki-index.php?page=MOGRE+MovableText+by+Billboards
我的要求应该是这样的,文本会比较多,每个文本通常关联3D空间里一个点,在动画效果时点会移动要求文本也能移动,方便添加,删除,修改文本.
其中第一与第二没有用到视图变换,透视变换,直接根据3D位置转化成对应屏幕xy中的0到1之间,丢失第三维深度信息,这里倒是没有多大关系.但是这三种都有一个问题,单个显示没什么,如果有100个点,就一百个文本,就有一百个Renderable,一百次渲染,在这个需求里,这是完全存在的.
所以自己动手,丰衣足食(好吧,是因为找到的都不满足需求),先细清下要达到的目标及思路.
首先.文本是否保持3维,如果3维显示,那么需要一直保证文本面对摄像机.如果2维显示,会简单一些,只需要去掉对应视图,透视变换,然后把对应点的位置直接转化成对应二维的-1到1之间就OK.
其次,如何把所有文本一次渲染,先说明在Ogre里如何渲染的,如果是一个字母A,那么我们画一个四边形,然后对应图片(这个图片画着一个A)放在这个四边形上就OK了.那么如果有4个点,分别显示A,B,C,D.我们可以这样考虑,先把ABCD这四个字母画到一张图片上,然后取每个字母在这个图片上的纹理坐标,这样我们就能根据一张纹理一下显示4个文本.
最后,因为有动画效果以及添加,删除,修改文本,考虑每桢更新显示,本来想着是用Mesh-Entity,但是感觉是在用高射炮打蚊子,花费高效果不好.如果能最简单方便的达到这个目的,我们需要先看下最基本的渲染元素.
Ogre 渲染基类
Renderable则负责渲染,主要包含获取材质,材质技术,得到渲染体RenderOperation,这个类里面比较简单,就是顶点数据,索引数据这些.
主要字段:
mUseIdentityProjection:是否启用投影矩阵.前面说过,投影矩阵(正交,透视)是把视图矩阵里的空间转化成各边为-1到1的立方体.(ogre中,caram里mFoVy,mFarDist,mNearDist,mAspect可生成二种类型投影矩阵).
mUseIdentityView:是否启用视图变换,就是启用不视图变换,如果不启用,那么渲染物体和抽象机的位置无关.(Ogre中,caram里mPosition,mOrientation生成以视图矩阵,其实就是一个以mPosition为原点,以lookat方向为Z值的三维坐标轴).
这二个属性一般为false,除非在Overlay(多用于UI)中,都设置false,因为这种UI位置与摄像机位置无关,所以mUseIdentityView,至于把mUseIdentityProjection设为false,则是把对应值(0,1)转化成(-1,1),不然用户需要先把在顶点在视图中的位置除viewport的长宽再转化成(-1,1),会很麻烦. mCustomParameters:当用户渲染需要用到着色器,并且需要设置参数时,提供的一种简单方法.mPolygonModeOverrideable:简单来说,就是是否受到摄像机针对显示模式的修改而影响.(显示点,线,面).
主要方法:
getMaterial(纯虚函数):当前要渲染的纹理.
getRenderOperation(纯虚函数):渲染中的顶点缓冲区,顶点索引缓冲区,渲染类型(点,线,三角形这些).
preRender:getRenderOperation后,渲染前,可以得到当前场景管理与渲染系统的对象.
postRender:和preRender,只是发生在渲染后.
MovableObject负责与场景管理中的SceneNode交互,是否可见,包围体,包围圈.查询标识,更新对应渲染体Renderable到渲染列表中.
主要字段:
mParentNode: 附加上的节点.
mVisible:是否可见.
mRenderQueneID:一般设置大于0,少于100,值大的会覆盖值少的,越大越显示在上面.
mQueryFlags: 场景查询相交测试标示相与,是则查询.
mVisibilityFlags: 与对应viewport对应标示相与,是则显示.
mWorldAABB:包围圈
mCastShadows:是否启用阴影
主要方法:
_updateRenderQueue(纯虚函数):把自己要渲染的内容(Renderable)更新到渲染列表.
_notifyCurrentCamera:在_updateRenderQueue之前,获取当前摄像机的信息,用于后期要和摄像机有关的渲染处理.
Renderable与MovableObject组合渲染物体
Renderable可以直接用于渲染,但是Renderable需要依靠MovableObject置入场景管理,更新渲染列表中.所以大部分需要渲染的元素都是Renderable和MovableObject的派生类.在渲染中,一般是根据MovableObject所附加的SceneNode位置来决定对应Renderable是否添加对应到渲染列表(_updateRenderQueue),然后渲染 渲染列表中的物体时,根据Renderable的getMaterial来设置渲染环境(如果有着色器代码,则启用),然后getRenderOperation设置要渲染顶点与索引,开始渲染.
Ogre中大致有三种组合方式来渲染.下面列举其中一些比较常见的:
第一种:只从Renderable继承,不需要附加到SceneNode上.一般用于OgreUI系统.
OverlayElement: Overlay组件里所有元素的基类.如panel,textArea,borderPanel.从基类中得到可以渲染的能力.
BorderRenderable:borderPanel比panel多出来要渲染的部分,这个元素会分二次渲染.
第二种:从Renderable与MovableObject继承.
SimpleRenderable: Ogre中帮我们定义的一个简单实现,Ogre内部有一些此类的派生实现,大家可以简单看下.
BillboardSet:公告板技术的一种实现,绘制多个始终面对摄像机的方形框.用于一些特效如草地啥的,还有Ogre中的Particle粒子效果也是交给BillboardSet处理的.
BillboardChain:实现线条特效,其子类RibbonTrail实现轨迹特效,如刀光,流星等.
Frustum:画对应投影可视体,如正交是一个长方体,透视则是一个立方锥.
第三种:类A从MovableObject继承,然后类A中包含类B的列表,而类B从Renderable继承.
Entity-SubEntity:Entity继承MovableObject,可以附加到SceneNode上,而SubEntity继承Renderable,才是真正用于渲染.比如一个人是一个整体,但是我们需要分开渲染,先渲染头,手,身体就是这个道理.同时也是内置模型Mesh的包装类,其中,封装了如姿态,顶点,骨骼动画.
ManualObject-ManualObjectSection:同Entity-SubEntity一样,让用户能方便实现一个物体包含多个组件的模型,并且参考了opengl即时模式的API,不同之后,最后还是以缓冲区模式提交数据.
实现代码
回到我们需求,我们采用最合适的方式当是第二种,从Renderable与MovableObject继承,如果第一种,我们能控制的比较少,第三种我们又只需要一次渲染,用不着.先确定下,我们采用公告板技术,启用视图,投影矩阵.
下面是Axiom代码,要转成Ogre,MOgre相应代码都非常方便.
public class ALabel
{
public string Label { get; set; }
public Vector3 Position { get; set; }
} public class ALabelSet : MovableObject, IRenderable
{
protected AxisAlignedBox aab = new AxisAlignedBox();
public ALabelList labelList = new ALabelList();
protected RenderOperation renderOperation = new RenderOperation(); private Font _font;
private string _fontName;
protected Material material; //字体名,设置后加载对应字体的纹理,上面有各个字符在纹理中的坐标以及大小
public string FontName
{
get
{
return this._fontName;
}
set
{
if (this._fontName != value || material == null || this._font == null)
{
this._fontName = value;
this._font = (Font)FontManager.Instance[this._fontName];
if (this._font == null)
{
throw new Exception(String.Format("Could not find font '{0}'.", this._fontName));
}
this._font.Load();
if (material != null)
{
if (material.Name != "BaseWhite")
{
MaterialManager.Instance.Unload(material);
}
material = null;
}
material = this._font.Material.Clone(name + "Material", false, this._font.Material.Group);
if (material.IsLoaded == true)
{
material.Load();
}
material.DepthCheck = false;
//material.CullingMode = CullingMode.None;
//material.ManualCullingMode = ManualCullingMode.None;
material.Lighting = false;
}
}
} private int _spaceWidth;
private int _characterHeight; public int CharacterHeight
{
get
{
return this._characterHeight;
}
set
{
this._characterHeight = value;
}
}
public int SpaceWidth
{
get
{
return this._spaceWidth;
}
set
{
this._spaceWidth = value;
}
} public bool buffersCreated; protected VertexData vertexData = null;
protected HardwareVertexBuffer mainBuffer = null;
private BufferBase lockPtr = null;
private int ptrOffset = ; //多摄像机,在每个viewport渲染时,需要记录对应摄像机,在渲染时要计算对应位置
protected Camera currentCamera; private ColorEx _color = ColorEx.White;
private int iColor = ;
public ColorEx Color
{
get
{
return this._color;
}
set
{
this._color = value;
}
} public ALabelSet(string fontName, int charHeight)
{
this.FontName = fontName;
this._characterHeight = charHeight;
castShadows = false;
} protected override void dispose(bool disposeManagedResources)
{
if (!IsDisposed)
{
if (disposeManagedResources)
{
if (this.renderOperation != null)
{
if (!this.renderOperation.IsDisposed)
{
this.renderOperation.Dispose();
}
this.renderOperation = null;
}
}
} base.dispose(disposeManagedResources);
} //先检查是否需要重新申请缓冲区,然后获取当前缓冲区句柄
private void BeginRender()
{
if (this.labelList.Count == )
return;
if (this.buffersCreated)
{
int count = this.labelList.TextAll.Length;
if (count * != this.vertexData.vertexCount)
{
this.DestroyBuffers();
}
}
if (!this.buffersCreated)
{
this.CreateBuffer();
}
this.iColor = Root.Instance.ConvertColor(this._color);
this.lockPtr = this.mainBuffer.Lock(BufferLocking.Discard);
this.ptrOffset = ;
}
//根据当前摄像机,得到每个字符的位置.根据字体纹理,得到每个字符纹理坐标.
private void Rendering(ALabel label)
{
if (!this.currentCamera.IsObjectVisible(label.Position))
{
return;
}
var charlen = label.Label.Length;
var camera = this.currentCamera;
var camPos = this.parentNode.FullTransform * camera.DerivedPosition;
//camera to pos
var camTo = camPos - label.Position;
camTo.Normalize();
//var xAxis = camTo.Cross(Vector3.UnitY);
//var yAxis = camTo.Cross(xAxis);
//xAxis = camTo.Cross(yAxis);
//var camQ = Quaternion.FromAxes(xAxis, yAxis, camTo);
var labelTo = Vector3.UnitZ;
if (camTo.z < )
labelTo = Vector3.NegativeUnitZ;
var dotAngle = camTo.Dot(labelTo);
var angle = Math.Acos(dotAngle);
var axis = labelTo.Cross(camTo);
var camQ = Quaternion.FromAngleAxis(angle, axis);
//if (camera.Name == "PerspectiveViewportCamera")
// System.Diagnostics.Debug.WriteLine("{0}<->{1}<->{2}", dotAngle, angle, camTo.z);
var left = 0.0f;
for (var i = ; i != charlen; i++)
{
char cr = label.Label[i];
var clyph = this._font.Glyphs[cr];
var width = clyph.aspectRatio;// this._font.GetGlyphAspectRatio(cr);
//Real u1, u2, v1, v2;this._font.GetGlyphTexCoords(cr, out u1, out v1, out u2, out v2);
Real u1 = clyph.uvRect.Top;
Real u2 = clyph.uvRect.Bottom;
Real v1 = clyph.uvRect.Left;
Real v2 = clyph.uvRect.Right; var xLeft = camQ * Vector3.UnitX * left * 2.0f * labelTo.z;
var xRigth = camQ * Vector3.UnitX * (left + width) * 2.0f * labelTo.z;
var y = camQ * (Vector3.UnitY * this._characterHeight * 2.0f);
var tl = label.Position + xLeft;
var bl = label.Position + xLeft - y;
var tr = label.Position + xRigth;
var br = label.Position - y + xRigth;
left += width;
unsafe
{
var posPtr = this.lockPtr.ToFloatPointer();
var colPtr = this.lockPtr.ToIntPointer();
var texPtr = posPtr; //first tri
//top left
posPtr[ptrOffset++] = tl.x;
posPtr[ptrOffset++] = tl.y;
posPtr[ptrOffset++] = tl.z;// ml.Position.z;
colPtr[ptrOffset++] = this.iColor;
texPtr[ptrOffset++] = u1;
texPtr[ptrOffset++] = v1;
//2 bottom left
posPtr[ptrOffset++] = bl.x;
posPtr[ptrOffset++] = bl.y;
posPtr[ptrOffset++] = bl.z;// ml.Position.z;
colPtr[ptrOffset++] = this.iColor;
texPtr[ptrOffset++] = u1;
texPtr[ptrOffset++] = v2;
//3 top right
posPtr[ptrOffset++] = tr.x;
posPtr[ptrOffset++] = tr.y;
posPtr[ptrOffset++] = tr.z;// ml.Position.z;
colPtr[ptrOffset++] = this.iColor;
texPtr[ptrOffset++] = u2;
texPtr[ptrOffset++] = v1; //second tri
//1 top right
posPtr[ptrOffset++] = tr.x;
posPtr[ptrOffset++] = tr.y;
posPtr[ptrOffset++] = tr.z;// ml.Position.z;
colPtr[ptrOffset++] = this.iColor;
texPtr[ptrOffset++] = u2;
texPtr[ptrOffset++] = v1;
//2 bottom left
posPtr[ptrOffset++] = bl.x;
posPtr[ptrOffset++] = bl.y;
posPtr[ptrOffset++] = bl.z;// ml.Position.z;
colPtr[ptrOffset++] = this.iColor;
texPtr[ptrOffset++] = u1;
texPtr[ptrOffset++] = v2;
//3 bottom right
posPtr[ptrOffset++] = br.x;
posPtr[ptrOffset++] = br.y;
posPtr[ptrOffset++] = br.z;// ml.Position.z;
colPtr[ptrOffset++] = this.iColor;
texPtr[ptrOffset++] = u2;
texPtr[ptrOffset++] = v2;
}
}
} //提交修改后的缓冲区数据
private void EndRender()
{
this.mainBuffer.Unlock();
this.lockPtr = null;
} private void CreateBuffer()
{
this.vertexData = new VertexData();
this.vertexData.vertexStart = ;
this.vertexData.vertexCount = this.labelList.TextAll.Length * ; var decl = this.vertexData.vertexDeclaration;
var binding = this.vertexData.vertexBufferBinding; var offset = ;
decl.AddElement(, offset, VertexElementType.Float3, VertexElementSemantic.Position);
offset += VertexElement.GetTypeSize(VertexElementType.Float3);
decl.AddElement(, offset, VertexElementType.Color, VertexElementSemantic.Diffuse);
offset += VertexElement.GetTypeSize(VertexElementType.Color);
decl.AddElement(, offset, VertexElementType.Float2, VertexElementSemantic.TexCoords, ); this.mainBuffer = HardwareBufferManager.Instance.CreateVertexBuffer(decl.Clone(), this.vertexData.vertexCount,
BufferUsage.DynamicWriteOnlyDiscardable);
binding.SetBinding(, this.mainBuffer);
this.buffersCreated = true;
} private void DestroyBuffers()
{
this.vertexData = null;
this.mainBuffer = null;
this.buffersCreated = false;
} #region MovableObject
//不需要场景查询.
public override AxisAlignedBox BoundingBox
{
get { return (AxisAlignedBox)this.aab.Clone(); }
}
//同上
public override Real BoundingRadius
{
get { return 1.0f; }
}
//检查物体是否渲染时,更新当前Renderable到渲染列表中
public override void UpdateRenderQueue(RenderQueue queue)
{
//if (bCameraMove)
{
BeginRender();
foreach (var label in this.labelList)
{
Rendering(label);
}
EndRender();
}
queue.AddRenderable(this);//, RenderQueue.DEFAULT_PRIORITY, renderQueueID);
} private bool bCameraMove = false;
private Vector3 prePosition = Vector3.Zero;
//我们有多个摄像机,而每次渲染需要根据摄像机位置更新缓冲区
public override void NotifyCurrentCamera(Camera camera)
{
var currPos = Root.Instance.SceneManager.GetCamera("PerspectiveViewportCamera").Position;
bCameraMove = currentCamera == null || currPos != prePosition;
if (camera.Name == "PerspectiveViewportCamera")
prePosition = currPos;
this.currentCamera = camera;
}
#endregion #region IRenderable
//不需要阴影
public bool CastsShadows
{
get { return false; }
}
//没有启用着色器.不需要
public Vector4 GetCustomParameter(int index)
{
return Vector4.Zero;
} public Real GetSquaredViewDepth(Camera camera)
{
return parentNode.GetSquaredViewDepth(camera);
}
//当前模型矩阵
public void GetWorldTransforms(Matrix4[] matrices)
{
matrices[] = parentNode.FullTransform;
} public Axiom.Core.Collections.LightList Lights
{
get { return QueryLights(); }
}
//字体纹理
public Material Material
{
get { return material; }
}
//格式化法线
public bool NormalizeNormals
{
get { return false; }
}
//一个
public ushort NumWorldTransforms
{
get { return ; }
}
//是否和摄像机同PolygonMode
public bool PolygonModeOverrideable
{
get { return true; }
} public RenderOperation RenderOperation
{
get
{
if (bCameraMove)
{
this.renderOperation.vertexData = this.vertexData;
this.renderOperation.vertexData.vertexStart = ;
this.renderOperation.operationType = OperationType.TriangleList;
this.renderOperation.useIndices = false;
this.renderOperation.indexData = null;
this.renderOperation.vertexData.vertexCount = this.vertexData.vertexCount;
}
return this.renderOperation;
}
}
//着色器
public void SetCustomParameter(int index, Vector4 val)
{
} public Technique Technique
{
get { return this.material.GetBestTechnique(); }
}
//着色器
public void UpdateCustomGpuParameter(GpuProgramParameters.AutoConstantEntry constant, GpuProgramParameters parameters)
{
}
//是否启用投影变换
public bool UseIdentityProjection
{
get { return false; }
}
//是否启用视图变换
public bool UseIdentityView
{
get { return false; }
}
//当前模型在世界空间方位
public Quaternion WorldOrientation
{
get { return parentNode.DerivedOrientation; }
}
//当前模型在世界空间顶点
public Vector3 WorldPosition
{
get { return parentNode.DerivedPosition; }
}
#endregion
} public class ALabelList : List<ALabel>
{
public ALabelList()
{
}
public void Add(string label, Vector3 position)
{
ALabel ab = new ALabel();
ab.Label = label ?? this.Count.ToString();
ab.Position = position;
this.Add(ab);
} public string TextAll
{
get
{
string text = string.Empty;
foreach (var txt in this)
{
text += txt.Label;
}
return text;
}
}
}
ALabelSet
代码其实还没有完成,不过大部分已经实现,对照前面Renderable与MovableObject的实现,说下代码中要注意的是.
代码中,所有方法都加入了注释.主要重载Renderable与MovableObject相关属性,实现其方法.包含是否启用投影变换,视图变换,阴影,纹理等.
注意二个地方,一是当我们设置字体时,从字体中我们能得到一张纹理,里面包含所有字符,通过提供的方法,对得到对应字符在纹理中的坐标.二是当我们渲染时,label要一直面对摄像机方向,而我们提供的是XY方向的位置,如何保证XY变换成垂直于摄像机到Label的方向的平面,
我们得到字符的长宽,在水平面的长度,也就是XY方向,水平面的法线是Z轴,如果当我们摄像机与Label方向垂直XY正平面时,那时我们的字符位置就不需要转化.我们已知字符转化前法向量是z轴,而方向矢量(摄像机-label位置)是变化后的法向量.如果我们知道如何从Z轴转化成label位置到摄像机的方向矢量,那么我们把顶点也进行这个转化就OK了.(这里法向量旋转等于顶点的旋转,如果元素发生大小的变化,这个逻辑不再正确,大家具体可以找相关旋转变化的书).
前面有说过,二个向量的点乘可以得到二个向量的角度A,二个向量的叉乘可以得到垂直于这个向量的方向向量V,简单来说,在方向向量V上,旋转角度A就能得到达到前面目录.我们知道,四元数的定义就是由着方向轴旋转角度,由此,得到前面的V与A,我们能很容易就得到对应四元数,不过需要注意的,二个向量的角度是计算的0-180度,而我们这明显是360度,比如旋转了270度和90度都是计算为90度的,所以在这里,我们需要做点改变,当摄像机跑到label后面去时,我们把对应的Z轴变为负Z轴.
这里我们还可以换个角度来想,Z轴变换后成为(摄像机-label位置)矢量,这个矢量也是新坐标轴的Z轴,知道Z轴,如何求XY轴,想起视图矩阵是如何得到的没?很简单,Z corss Y(0,1,0)得到X,然后Z cross X得到Y,新的三轴我们得到了,或者后变换后的坐标轴我们得到了,根据3轴得到对应矩阵也是一样的效果.
下面是效果图,可以看出,有个视图还是有问题,大家可以猜下是那导致的.
中间,我想做个尝试,现在是每桢都更新,这是没必要的,只有启用动画这里才需要如此,我开始想的是,如果数据没变化,就不更新,可惜的是,我这里是多视图,多摄像机,然后顶点位置计算又和摄像机有关,而当前缓冲区数据只有一个,这样必需每桢更新,除非每个摄像机声明一个缓冲区才行,这样一想感觉又不划算.
最后,如上面问题,我们可以关闭视图变换与投影变换,如Overlay一样,那么我们如何修改了,记的前面说过,关闭视图变换与投影变换后,我们设定的x,y值要在-1到1之间,否则是看不到的.所以如果大家记的把Node上的点变换成对应的-1到1就完成了,比上面公告板技术要简单一点,顺便可以参考Overlay里的计算.
Ogre 渲染目标解析与多文本合并渲染的更多相关文章
- Ogre RTSS组件解析
我们为什么要用RTSS. Ogre如计算物体位置,纹理,光照都有固定API如(glMatrixFrustumEXT, glLoadmatrix, glTexture, glLight ),使用这些AP ...
- Django_rest_framework_渲染器/解析器/路由控制/分页
目录 渲染器 解析器 路由控制 分页 渲染器 简介 什么是渲染器 根据 用户请求URL 或 用户可接受的类型,筛选出合适的 渲染组件. 渲染器的作用 序列化.友好的展示数据 渲染器配置 首先要在set ...
- CSharpGL(9)解析OBJ文件并用CSharpGL渲染
CSharpGL(9)解析OBJ文件并用CSharpGL渲染 2016-08-13 由于CSharpGL一直在更新,现在这个教程已经不适用最新的代码了.CSharpGL源码中包含10多个独立的Demo ...
- CSharpGL(5)解析3DS文件并用CSharpGL渲染
CSharpGL(5)解析3DS文件并用CSharpGL渲染 我曾经写过一个简单的*.3ds文件的解析器,但是只能解析最基本的顶点.索引信息,且此解析器是仿照别人的C++代码改写的,设计的也不好,不方 ...
- 如何让手游更省带宽,耗电量更少?TBR渲染架构解析!
如何让手游更省带宽,耗电量更少?渲染或是其中一个可突破的点.本文中,腾讯游戏学院专家Hailong将从为大家解析TBR渲染架构的特点. 什么是TBR? 全称是Tile Based Rendering, ...
- Vue视图渲染原理解析,从构建VNode到生成真实节点树
前言 在 Vue 核心中除了响应式原理外,视图渲染也是重中之重.我们都知道每次更新数据,都会走视图渲染的逻辑,而这当中牵扯的逻辑也是十分繁琐. 本文主要解析的是初始化视图渲染流程,你将会了解到从挂载组 ...
- css与dom的渲染与解析
js阻塞文档渲染与解析那么css呢? 结论一.css:阻塞渲染,不阻塞dom解析 <head> <script> document.addEventListener('DOMC ...
- 什么是渲染目标(render target)&& 渲染到纹理(Render To Texture, RTT)详解
渲染到纹理(Render To Texture, RTT)详解 RTT是现在很多特效里面都会用到的一项很基本的技术,实现起来很简单,也很重要.但是让人不解的是网上搜索了半天只找到很少的文章说这个事儿, ...
- Vuejs - 花式渲染目标元素
Vue.js是什么 摘自官方文档: Vue (读音 /vjuː/,类似于 view) 是一套用于构建用户界面的渐进式框架.与其它大型框架不同的是,Vue 被设计为可以自底向上逐层应用.Vue 的核心库 ...
随机推荐
- zabbix 安装时 到第三步时 database type 没有mysql选项
没有MySQL选项: 思路首选想到httpd: 一些问题都会从日志中反映出来: # tail -f error_log PHP Warning: PHP Startup: Unable to load ...
- 行为类模式(三):解释器(Interpreter)
定义 给定一个语言, 定义它的文法的一种表示,并定义一个解释器,该解释器使用该表示来解释语言中的句子. UML 优点 将每一个语法规则表示成一个类,方便事先语言. 因为语法由许多类表示,所以你可以轻易 ...
- 哈佛大学 Machine Learning
https://am207.github.io/2017/material.html https://am207.github.io/2017/topics.html https://am207.gi ...
- shell快捷方式总结
Linux使用Shell时总是有各种不太方便,需要对输入的命令行做编辑.跳转.这里整理下我遇到的Shell下快捷键. 注意下文中所有的ctrl表示键盘中的控制按键,所有的ctrl + x的格式都是同时 ...
- Java获取 ISO 8601格式时间
https://blog.csdn.net/fang323619/article/details/74909587 ****************************************** ...
- Lintcode: Implement Queue by Stacks 解题报告
Implement Queue by Stacks 原题链接 : http://lintcode.com/zh-cn/problem/implement-queue-by-stacks/# As th ...
- Python实现敏感词过滤替换
[本文出自天外归云的博客园] 问题 最近在网上搜到了一些练习题,对第十二题稍作修改如下: 敏感词文本文件“filtered_words.txt”,里面的内容: 北京人 人大 北京 程序员 公务员 领导 ...
- hdu3038(种类并查集,推荐)
题目大意:有n次询问,给出a到b区间的总和,问这n次给出的总和中有几次是和前面已近给出的是矛盾的?? 很有意思的一道题目,要是没有做过种类并查集,我肯定会以为这种题目是线段树题目...... 思路:我 ...
- java开发篇---验证码
验证码的作用:防止恶意破解密码.刷票.论坛灌水.刷页. 有效防止某个黑客对某一个特定注册用户用特定程序暴力破解方式进行不断的登录尝试,实际上使用验证码是现在很多网站通行的方式(比如招商银行的网上个人银 ...
- Python进阶(三十五)-Fiddler命令行和HTTP断点调试
Python进阶(三十五)-Fiddler命令行和HTTP断点调试 一. Fiddler内置命令 上一节(使用Fiddler进行抓包分析)中,介绍到,在web session(与我们通常所说的se ...