Box2D一:基础知识

2023-10-27

一、box2d基础知识

1、关于

 Box2D 是一个用于游戏的 2D 刚体仿真库。从游戏的视角来看,物理引擎就是一个程序性动画(procedural animation)的系统,而不是由动画师去移动你的物体。

1、核心概念 

刚体(rigid body)
一块十分坚硬的物质,它上面的任何两点之间的距离都是完全不变的。
形状(shape)
一块严格依附于物体(body)的 2D 碰撞几何结构(collision geometry)。形状具有摩擦(friction)和恢
复(restitution)的材料性质。

约束(constraint) 

一个约束(constraint)就是消除物体自由度的物理连接。在 2D 中,一个物体有 3 个自由度。如果我
们把一个物体钉在墙上(像摆锤那样),那我们就把它约束到了墙上。这样,此物体就只能绕着这个钉子旋
转,所以这个约束消除了它 2 个自由度。
接触约束(contact constraint)
一个防止刚体穿透,以及用于模拟摩擦(friction)和恢复(restitution)的特殊约束。你永远都不必创建
一个接触约束,它们会自动被 Box2D 创建。
关节(joint)
它是一种用于把两个或多个物体固定到一起的约束。Box2D 支持的关节类型有:旋转,棱柱,距离等
等。关节可以支持限制(limits)和马达(motors)。
关节限制(joint limit)
一个关节限制(joint limit)限定了一个关节的运动范围。例如人类的胳膊肘只能做某一范围角度的运
动。
关节马达(joint motor)
一个关节马达能依照关节的自由度来驱动所连接的物体。例如,你可以使用一个马达来驱动一个肘的
旋转。
世界(world)
一个物理世界就是物体,形状和约束相互作用的集合。Box2D 支持创建多个世界,但这通常是不必要

的。 

 

 3、 创建一个世界

 每个 Box2D 程序都将从一个世界对象(world object)的创建开始。这是一个管理内存,对象和模拟的中心。

要创建一个世界对象,我们首先需要定义一个世界的包围盒。Box2D 使用包围盒来加速碰撞检测。尺寸并不关键,但合适的尺寸有助于性能。这个包围盒过大总比过小好。
b2AABB worldAABB;
worldAABB.lowerBound.Set(- 100.0f, - 100.0f);
worldAABB.upperBound.Set( 100.0f100.0f); 
接下来我们定义重力矢量。
b2Vec2 gravity( 0.0f, - 10.0f);
bool doSleep =  true// 当动态物体静止时使它休眠,减少性能开销

 现在我们创建世界对象。

b2World world(worldAABB, gravity, doSleep);//在栈上创建world

那么现在我们有了自己的物理世界,让我们再加些东西进去。

4、创建一个地面

 第一步,我们创建地面体。要创建它我们需要一个物体定义(body definition),通过物体定义我们来指定地面体的初始位置。

b2BodyDef groundBodyDef;
groundBodyDef.position.Set( 0.0f, - 10.0f);

 

 第二步,将物体定义传给世界对象来创建地面体。世界对象并不保存到物体定义的引用。地面体是作为静态物体(static body)创建的,静态物体之间并没有碰撞,它们是固定的。当一个物体具有零质量的时候 Box2D 就会确定它为静态物体,物体的默认质量是零,所以它们默认就是静态的。

b2Body* ground = world.CreateBody(&groundBodyDef);

 

第三步,我们创建一个地面的多边形定义。我们使用 SetAsBox 简捷地把地面多边形规定为一个盒子(矩形)形状,盒子的中点就位于父物体的原点上。
b2PolygonDef groundShapeDef;
groundShapeDef.SetAsBox( 50.0f10.0f);
其中,SetAsBox 函数接收了半个宽度和半个高度,这样的话,地面盒就是 100 个单位宽(x 轴)以及 20 个单位高(y 轴)。Box2D 已被调谐使用米,千克和秒来作单位,所以你可以用米来考虑长度。

 

 在第四步中,我们在地面体上创建地面多边形,以完成地面体。

groundBody->CreateShape(&groundShapeDef); // 创建形状用于碰撞检测等
5、 创建一个动态物体
首先我们用 CreateBody 创建物体。
b2BodyDef bodyDef;
bodyDef.position.Set( 0.0f4.0f);
b2Body* body = world.CreateBody(&bodyDef); 

 

接下来我们创建并添加一个多边形形状到物体上。注意我们把密度设置为 1,默认的密度是 0。并且,形状的摩擦设置到了 0.3。形状添加好以后,我们就使用 SetMassFromShapes 方法来命令物体通过形状去计算其自身的质量。这暗示了你可以给单个物体添加一个以上的形状。如果质量计算结果为 0,那么物体会变成真正的静态。

  b2PolygonDef shapeDef;

shapeDef.SetAsBox(1.0f1.0f);
shapeDef.density = 1.0f;
shapeDef.friction = 0.3f;
body->CreateShape(&shapeDef);
body->SetMassFromShapes();

 

 6、模拟(Box2D 的)世界

 

 我们已经初始化好了地面盒和一个动态盒。现在我们只有少数几个问题需要考虑。Box2D 中有一些数学代码构成的积分器(integrator),积分器在离散的时间点上模拟物理方程,它将与游戏动画循环一同运行。所以我们需要为 Box2D 选取一个时间步,通常来说游戏物理引擎需要至少 60Hz 的速度,也就是 1/60 的时间步。你可以使用更大的时间步,但是你必须更加小心地为你的世界调整定义。我们也不喜欢时间步变化得太大,所以不要把时间步关联到帧频(除非你真的必须这样做)。直截了当地,这个就是时间步:float32 timeStep = 1.0f / 60.0f

 除了积分器之外,Box2D 中还有约束求解器(constraint solver)。约束求解器用于解决模拟中的所有约束,一次一个。单个的约束会被完美的求解,然而当我们求解一个约束的时候,我们就会稍微耽误另一个。要得到良好的解,我们需要迭代所有约束多次。建议的 Box2D 迭代次数是 10 次。你可以按自己的喜好去调整这个数,但要记得它是速度与质量之间的平衡。更少的迭代会增加性能并降低精度,同样地,更多的迭代会减少性能但提高模拟质量。这是我们选择的迭代次数:

 int32 iterations = 10;//一个时间步遍历10次约束

 

 现在我们可以开始模拟循环了,在游戏中模拟循环应该并入游戏循环。每次循环你都应该调用 b2World::Step,通常调用一次就够了,这取决于帧频以及物理时间步。

 这就是模拟 1 秒钟内 60 个时间步的循环

for (int32 i =  0; i <  60; ++i)
{
    world.Step(timeStep, iterations);
}

 

 

 7、API 设计

 

单位

 

 Box2D 使用浮点数,所以必须使用一些公差来保证它正常工作。这些公差已经被调谐得适合米-千克-秒(MKS)单位。尤其是,Box2D 被调谐得能良好地处理 0.1 到 10 米之间的移动物体。这意味着从罐头盒到公共汽车大小的对象都能良好地工作。 

• 注意:Box2D 已被调谐至 MKS 单位。移动物体的尺寸大约应该保持在 0.1 到 10 米之间。你可能需要一些缩放系统来渲染你的场景和物体。Box2D 中的例子是使用 OpenGL 的视口来变换的。

 

用户数据

b2Shape,b2Body 和 b2Joint 类都允许你通过一个 void 指针来附加用户数据。这在你测试 Box2D 数据结构,以及你想把它们联系到自己的引擎中的时候是较方便的。举个典型的例子,在角色上的刚体中附加到角色的指针,这就构成了一个循环引用。如果你有角色,你就能得到刚体。如果你有刚体,你就能得到角色。
GameActor* actor = GameCreateActor();
b2BodyDef bodyDef;
bodyDef.userData = actor;
actor->body = box2Dworld->CreateBody(&bodyDef);
这是一些需要用户数据的案例: 
• 使用碰撞结果给角色施加伤害
• 当玩家进入一个包围盒时播放一段脚本事件
• 当 Box2D 通知你一个关节即将摧毁时访问一个游戏结构
记得用户数据是可选的,并且能放入任何东西。然而,你需要保持一致性。例如,如果你想在一个物体中保存一个角色的指针,那你就应该在所有物体中都保存一个角色指针。不要在一个物体中保存角色指针,却在另一个物体中保存一个其它指针。这可能会导致程序崩溃。 

 

8、世界 

 b2World 类包含着物体和关节。它管理着模拟的方方面面,并允许异步查询(就像 AABB 查询)。你与 Box2D 的大部分交互都将通过 b2World 对象来完成。

 要创建或摧毁一个世界你需要使用 new 和 delete:

b2World* myWorld =  new b2World(aabb, gravity, doSleep);
//  ... do stuff ...
delete myWorld;

 世界类用于驱动模拟。你需要指定一个时间步和一个迭代次数。例如:

float32 timeStep =  1.0f /  60.f;
int32 iterationCount =  10;
myWorld->Step(timeStep, iterationCount);

 

 在时间步完成之后,你可以调查物体和关节的信息。最可能的情况是你会获取物体的位置,这样你才能更新你的角色并渲染它们。你可以在游戏循环的任何地方执行时间步,但你应该意识到事情发生的顺序。例如,如果你想要在一帧中得到新物体的碰撞结果,你必须在时间步之前创建物体。推荐使用固定的时间步。使用大一些的时间步你可以在低帧率的情况下提升性能。1/60 的时间步通常会呈现一个高质量的模拟。

 

  扫描世界:

世界就是一个物体和关节的容器。你可以获取世界中所有物体和关节并遍历它们。例如,这段代码会唤醒世界中的所有物体:
for (b2Body* b = myWorld->GetBodyList(); b; b = b->GetNext())
{
    b->WakeUp();

 

 AABB 查询:

有时你需要求出一个区域内的所有形状。b2World 类为此使用了 broad-phase 数据结构,提供了一个 log(N) 的快速方法。你提供一个世界坐标的 AABB,而 b2World 会返回一个所有大概相交于此 AABB 的形状之数组。这不是精确的,因为函数实际上返回那些 AABB 与规定之 AABB 相交的形状。例如,下面的代码找到所有大概与指定 AABB 相交的形状并唤醒所有关联的物体。
b2AABB aabb;
aabb.minVertex.Set(- 1.0f, - 1.0f);
aabb.maxVertex.Set( 1.0f1.0f);
const int32 k_bufferSize =  10;
b2Shape *buffer[k_bufferSize];
int32 count = myWorld->Query(aabb, buffer, k_bufferSize);
for (int32 i =  0; i < count; ++i)
{
    buffer[i]->GetBody()->WakeUp();

 

 9、 物体

 物体具有位置和速度。你可以应用力,扭矩和冲量到物体。物体可以是静态的或动态的,静态物体永远不会移动,并且不会与其它静态物体发生碰撞。物体是形状的主干,物体携带形状在世界中运动。在 Box2D 中物体总是刚体,这意味着同一刚体上的两个形状永远不会相对移动。通常你会保存所有你所创建的物体的指针,这样你就能查询物体的位置,并在图形实体中更新它的位置。另外在不需要它们的时候你也需要通过它们的指针摧毁它们。

  质量性质:

1)在物体定义中显式地设置 

 bodyDef.massData.mass = 2.0f;//物体的质量是2kg

 2)显式地在物体上设置(在其创建之后)

3)基于物体上的形状来进行密度设置 

 b2PolygonDef shapeDef;

shapeDef.SetAsBox( 1.0f1.0f);
shapeDef.density =  1.0f;
body->CreateShape(&shapeDef);
body->SetMassFromShapes();//这个函数成本较高,所以你应该只在需要时使用它。
你可以在运行时调整一个物体的质量,这通常是在添加或移除物体上之形状时完成的。可能你会根据物体上的当前形状来调整其质量。
可能你也会直接设置质量。例如,你可能会改变形状,但你只想使用自己的质量公式。
void SetMass( const b2MassData* massData);
通过以下这些函数可以获得物体的质量数据:
float32 GetMass()  const;
float32 GetInertia()  const;
const b2Vec2& GetLocalCenter()  const

 

位置和角度:

bodyDef.position.Set( 0.0f2.0f);    //  the body's origin position.
bodyDef.angle =  0.25f * b2_pi;       //  the body's angle in radians.
你可以访问一个物体的位置和角度,这在你渲染相关游戏角色时很常用。你也可以设置位置,尽管这不怎么常用。
bool SetXForm( const b2Vec2& position, float32 angle);
const b2XForm& GetXForm()  const;
const b2Vec2& GetPosition()  const;
float32 GetAngle()  const

 

 你可以访问线速度与角速度,线速度是对于质心所言的。

void SetLinearVelocity( const b2Vec2& v);
b2Vec2 GetLinearVelocity()  const;
void SetAngularVelocity(float32 omega);
float32 GetAngularVelocity()  const;

 

阻尼:

阻尼用于减小物体在世界中的速率。阻尼与摩擦是不同的,因为摩擦仅在物体有接触的时候才会发生,而阻尼的模拟要比摩擦便宜多了。然而,阻尼并不能取代摩擦,往往这两个效果需要同时使用。阻尼参数的范围可以在 0 到无穷之间,0 的就是没有阻尼,无穷就是满阻尼。通常来说,阻尼的值应在 0 到 0.1 之间,我通常不使用线性阻尼,因为它会使物体看起来发飘。
bodyDef.linearDamping =  0.0f;
bodyDef.angularDamping =  0.01f;
阻尼相似于稳定性与性能,阻尼值较小的时候阻尼效应几乎不依赖于时间步,而阻尼值较大的时候阻尼效应将随着时间步而变化。如果你使用固定的时间步(推荐)这就不是问题了。 

 休眠参数:

模拟物体的成本是高昂的,所以如果物体更少,那模拟的效果就能更好。当一个物体停止了运动时,我们要停止去模拟它。 当 Box2D 确定一个物体(或一组物体)已经停止移动时,物体就会进入休眠状态,消耗很小的 CPU 开销。如果一个醒着的物体接触到了一个休眠中的物体,那么休眠中的物体就会醒来。当物体上的关节或
触点被摧毁的时候,它们同样会醒来。你也可以手动地唤醒物体。通过物体定义,你可以指定一个物体是否可以休眠,或者创建一个休眠的物体。
bodyDef.allowSleep =  true;
bodyDef.isSleeping =  false;

 

 子弹:

高速移动的物体在 Box2D 被称为子弹(bullet),你需要按照游戏的设计来决定哪些物体是子弹。如果你决定一个物体应该按照子弹去处理,使用下面的设置。
bodyDef.isBullet =  true;
子弹开关只影响动态物体。
有的时候,在一个时间步内可能会有大量的刚体同时运动。如果一个物理引擎没有处理好大幅度运动的问题,你就可能会看见一些物体错误地穿过了彼此。这种效果被称为 隧道效应(tunneling)。默认情况下,Box2D 会通过 连续碰撞检测(CCD)来防止动态物体穿越静态物体,这是通过从形状的旧位置到新位置的扫描来完成的。引擎会查找扫描中的新碰撞,并为这些碰撞计算碰撞时间(TOI)。物体会先被移动到它们的第一个 TOI,然后一直模拟到原时间步的结束。如果有必要这个步骤会重复执行。一般 CCD 不会应用于动态物体之间,这是为了保持性能。在一些游戏环境中你需要在动态物体上也使用 CCD,譬如,你可能想用一颗高速的子弹去射击薄壁。没有 CCD,子弹就可能会隧穿薄壁。 CCD 的成本是昂贵的,所以你可能不希望所有运动物体都成为子弹。所以 Box2D 默认只在动态物体和静态物体之间使用 CCD,这是防止物体逃脱游戏世界的一个有效方法。然而,可能你有一些高速移动的物体需要一直使用 CCD。 

 

 状态信息:

物体的状态含有多个方面,通过这些函数你可以访问这些状态数据:
bool IsBullet()  const;
void SetBullet( bool flag);
bool IsStatic()  const;
bool IsDynamic()  const;
bool IsFrozen()  const;
bool IsSleeping()  const;
void AllowSleeping( bool flag);
void WakeUp(); 

 

 

 力和冲量:

你可以对一个物体应用力,扭矩,以及冲量。当应用一个力或冲量时,你需要提供一个世界位置。这常常会导致对质心的一个扭矩。
void ApplyForce( const b2Vec2& force,  const b2Vec2& point);
void ApplyTorque(float32 torque);
void ApplyImpulse( const b2Vec2& impulse,  const b2Vec2& point);
应用力,扭矩或冲量会唤醒物体,有时这是不合需求的。例如,你可能想要应用一个稳定的力,并允许物体休眠来提升性能。这时,你可以使用这样的代码:
if (myBody->IsSleeping() ==  false)
{
    myBody->ApplyForce(myForce, myPoint);

 

  坐标转换:

物体类包含一些工具函数,它们可以帮助你在局部和世界坐标系之间转换点和向量。如果你不了解这些概念,请看 Jim Van Verth 和 Lars Bishop 的“Essential Mathematics for Games and Interactive Applications”。这些函数都很高效,所以可放心使用。
b2Vec2 GetWorldPoint( const b2Vec2& localPoint);
b2Vec2 GetWorldVector( const b2Vec2& localVector);
b2Vec2 GetLocalPoint( const b2Vec2& worldPoint);
b2Vec2 GetLocalVector( const b2Vec2& worldVector); 

 

 列表

你可以遍历一个物体的形状,其主要用途是帮助你访问形状的用户数据。
for (b2Shape* s = body->GetShapeList(); s; s = s->GetNext())
{
    MyShapeData* data = (MyShapeData*)s->GetUserData();
    ...  do something with data ...
}

你也可以用类似的方法遍历物体的关节列表。 

 

 

 10、 形状

 形状就是物体上的碰撞几何结构。另外形状也用于定义物体的质量。也就是说,你来指定密度,Box2D 可以帮你计算出质量。形状具有摩擦和恢复的性质。形状还可以携带筛选信息,使你可以防止某些游戏对象之间的碰撞。形状永远属于某物体,单个物体可以拥有多个形状。形状是抽象类,所以在 Box2D 中可以实现许多

类型的形状。如果你有勇气,那便可以实现出自己的形状类型(和碰撞算法)。

形状定义 :

形状定义用于创建形状。通用的形状数据会保存在 b2ShapeDef 中,特殊的形状数据会保存在其派生类中。 

1)摩擦和恢复 

 摩擦可以使对象逼真地沿其它对象滑动。Box2D 支持静摩擦和动摩擦,但使用相同的参数。摩擦参数经常会设置在 0 到 1 之间,0 意味着没有摩擦,1 会产生强摩擦。当计算两个形状之间的摩擦时,Box2D 必须联合两个形状的摩擦参数,这是通过以下公式完成的:

float32 friction;
friction = sqrtf(shape1->friction * shape2->friction);

 恢复可以使对象弹起,想象一下,在桌面上方丢下一个小球。恢复的值通常设置在 0 到 1 之间,0 的意思是小球不会弹起,这称为非弹性碰撞;1 的意思是小球的速度会得到精确的反射,这称为完全弹性碰撞。恢复是通过这样的公式计算的:

float32 restitution;
restitution = b2Max(shape1->restitution, shape2->restitution);

 当一个形状发生多碰撞时,恢复会被近似地模拟。这是因为 Box2D 使用了迭代求解器.

2) 密度

Box2D 可以根据附加形状的质量分配来计算物体的质量以及转动惯量。直接指定物体质量常常会导致不协调的模拟。因此,推荐的方法是使用b2Body::SetMassFromShape 来根据形状设置质量。 

 3) 筛选

碰撞筛选是一个防止某些形状发生碰撞的系统。 

Box2D 支持 16 个 种群,对于任何一个形状你都可以指定它属于哪个种群。你还可以指定这个形状可以和其它哪些种群发生碰撞。例如,你可以在一个多人游戏中指定玩家之间不会碰撞,怪物之间也不会碰撞,但是玩家和怪物会发生碰撞。这是通过掩码来完成的,例如:
playerShapeDef.filter.categoryBits =  0x0002;
monsterShapeDef.filter.categoryBits =  0x0004;
playerShape.filter.maskBits =  0x0004;
monsterShapeDef.filter.maskBits =  0x0002;

 

 碰撞组可以让你指定一个整数的组索引。你可以让同一个组的所有形状总是相互碰撞(正索引)或永远不碰撞(负索引)。组索引通常用于一些以某种方式关联的事物,就像自行车的那些部件。在下面的例子中,shape1 和 shape2 总是碰撞,而 shape3 和 shape4 永远不会碰撞。

shape1Def.filter.groupIndex =  2;
shape2Def.filter.groupIndex =  2;
shape3Def.filter.groupIndex = - 8;
shape4Def.filter.groupIndex = - 8;

 不同组索引之间形状的碰撞会按照种群和掩码来筛选。换句话说,组筛选比种群筛选有更高的优选权。

注意在 Box2D 中的其它碰撞筛选,这里是一个列表: 
• 静态物体上的形状永远不会与另一个静态物体上的形状发生碰撞
• 同一个物体上的形状之间永远不会发生碰撞
• 你可以有选择地启用或禁止由关节连接的物体上的形状之间是否碰撞
有时你可能希望在形状创建之后去改变其碰撞筛选,你可以使用 b2Shape::GetFilterData 以及 b2Shape::SetFilterData 来存取已存在形状之 b2FilterData 结构。Box2D 会缓存筛选结果,所以你需要使用 b2World::Refilter 手动地进行重筛选。

 4)传感器

 有时候游戏逻辑需要判断两个形状是否相交,但却不应该有碰撞反应。这可以通过传感器(sensor)来完成。传感器会侦测碰撞而不产生碰撞反应。你可以将任一形状标记为传感器,传感器可以是静态或动态的。记得,每个物体上可以有多个形状,并且传感器和实体形状是可以混合的。

 myShapeDef.isSensor = true;

 5) 圆形定义

b2CircleDef 扩充了 b2ShapeDef 并增加一个半径和一个局部位置。
b2CircleDef def;
def.radius =  1.5f;
def.localPosition.Set( 1.0f0.0f); 

 6)多边形定义

b2PolyDef 用于定义凸多边形。要正确地使用需要一点点技巧,所以请仔细阅读。最大顶点数由 b2_maxPolyVertices 定义,当前是 8。如果你需要更多顶点,你必须修改 b2Settings.h 中的 b2_maxPolyVertices。当创建多边形定义时,你需要给出所用的顶点数目。这些顶点必须按照相对于右手坐标系之 z 轴逆时
针(CCW)的顺序定义。在你的屏幕上可能是顺时针的,这取决于你的坐标系统规则。多边形必须是凸多边形,也就是,每个顶点都必须指向外面。最后,你也不应该重叠任何顶点。Box2D 会自动地封闭环路。 

 这里是一个三角形的多边形定义的例子:

b2PolygonDef triangleDef;
triangleDef.vertexCount =  3;
triangleDef.vertices[ 0].Set(- 1.0f0.0f);
triangleDef.vertices[ 1].Set( 1.0f0.0f);
triangleDef.vertices[ 2].Set( 0.0f2.0f);

 

 7)形状工厂

 初始化一个形状定义,而后将其传递给父物体;形状就是这样创建的。

b2CircleDef circleDef;
circleDef.radius =  3.0f;
circleDef.density =  2.5f;
b2Shape* myShape = myBody->CreateShape(&circleDef);

 

 

 11、关节

 关节的作用是把物体约束到世界,或约束到其它物体上。在游戏中的典型例子是木偶,跷跷板和滑轮。关节可以用许多种不同的方法结合起来,创造出有趣的运动。

有些关节提供了限制(limit),以便你控制运动范围。有些关节还提供了马达(motor),它可以以指定的速度驱动关节,直到你指定了更大的力或扭矩。
1)关节定义
各种关节类型都派生自 b2JointDef。所有关节都连接两个不同的物体,可能其中一个是静态物体。如果你想浪费内存的话,那就创建一个连接两个静态物体的关节 你可以为任何一种关节指定用户数据。你还可以提供一个标记,用于预防相连的物体发生碰撞。实际上,这是默认行为,你可以设置 collideConnected 布尔值来允许相连的物体碰撞。很多关节定义需要你提供一些几何数据。一个关节常常需要一个锚点(anchor point)来定义,这是固定于相接物体中的点。在 Box2D 中这点需要在局部坐标系中指定,这样,即便当前物体的变化违反了关节约束,关节还是可以被指定 —— 在游戏存取进度时这经常会发生。另外,有些关节定义需要默认的
物体之间的相对角度。这样才能通过关节限制或固定的相对角来正确地约束旋转。初始化几何数据可能有些乏味。所以很多关节提供了初始化函数,消除了大部分工作。然而,这些初始化函数通常只应用于原型,在产品代码中应该直接地定义几何数据。这能使关节行为更加稳固。其余的关节定义数据依赖于关节的类型。下面我们来介绍它们。 

 2)距离关节

 距离关节是最简单的关节之一,它描述了两个物体上的两个点之间的距离应该是常量。当你指定一个距离关节时,两个物体必须已在应有的位置上。随后,你指定两个世界坐标中的锚点。第一个锚点连接到物体 1,第二个锚点连接到物体 2。这些点隐含了距离约束的长度。

 

 

 

 这是一个距离关节定义的例子。在此我们允许了碰撞。

b2DistanceJointDef jointDef;
jointDef.Initialize(myBody1, myBody2, worldAnchorOnBody1, 
worldAnchorOnBody2);
jointDef.collideConnected =  true;

 3)旋转关节

 一个旋转关节会强制两个物体共享一个锚点,即所谓铰接点。旋转关节只有一个自由度:两个物体的相对旋转。这称之为关节角。

 

 要指定一个旋转关节,你需要提供两个物体以及一个世界坐标的锚点。初始化函数会假定物体已经在应有位置了。在此例中,两个物体被旋转关节连接于第一个物体的质心。

b2RevoluteJointDef jointDef;
jointDef.Initialize(myBody1, myBody2, myBody1->GetWorldCenter());

 

 这里是对上面旋转关节定义的修订;这次,关节拥有一个限制以及一个马达,后者用于模拟摩擦。

b2RevoluteJointDef jointDef;
jointDef.Initialize(body1, body2, myBody1->GetWorldCenter());//使用 Initialize() 创建关节时,旋转关节角为 0,无论两个物体当前的角度怎样。
jointDef.lowerAngle = - 0.5f * b2_pi;  //  -90 degrees最小角度
jointDef.upperAngle =  0.25f * b2_pi;  //  45 degrees最大角度
jointDef.enableLimit =  true;
jointDef.maxMotorTorque =  10.0f;//马达
jointDef.motorSpeed =  0.0f;
jointDef.enableMotor =  true;

你可以访问旋转关节的角度,速度,以及扭矩。 

float32 GetJointAngle()  const ;  

float32 GetJointSpeed() const;
float32 GetMotorTorque() const;

你也可以在每步中更新马达参数。 

void SetMotorSpeed(float32 speed);
void SetMaxMotorTorque(float32 torque);  

 关节马达有一些有趣的能力。你可以在每个时间步中更新关节速度,这可以使关节像正弦波一样来回

移动,或者按其它什么函数运动。
//  ... Game Loop Begin ...
myJoint->SetMotorSpeed(cosf( 0.5f * time));
//  ... Game Loop End ...
你还可以使用关节马达来追踪某个关节角度。例如:
//  ... Game Loop Begin ...
float32 angleError = myJoint->GetJointAngle() - angleTarget;
float32 gain =  0.1f;
myJoint->SetMotorSpeed(-gain * angleError);
//  ... Game Loop End ...
通常来讲你的增益参数不应过大,否则你的关节可能会变得不稳定。

 

4)移动关节

 移动关节(prismatic joint)允许两个物体沿指定轴相对移动,它会阻止相对旋转。因此,移动关节只有一个自由度。

 

移动关节的定义有些类似于旋转关节;只是转动角度换成了平移,扭矩换成了力。以这样的类比,我们来看一个带有关节限制以及马达摩擦的移动关节定义: 
b2PrismaticJointDef jointDef;

b2Vec2 worldAxis(1.0f0.0f);
jointDef.Initialize(myBody1, myBody2, myBody1->GetWorldCenter(), 
worldAxis);
jointDef.lowerTranslation = -5.0f;
jointDef.upperTranslation = 2.5f;
jointDef.enableLimit = true;
jointDef.motorForce = 1.0f;
jointDef.motorSpeed = 0.0f;
jointDef.enableMotor = true;

 旋转关节隐含着一个从屏幕射出的轴,而移动关节明确地需要一个平行于屏幕的轴。这个轴会固定于两个物体之上,沿着它们的运动方向。就像旋转关节一样,当使用 Initialize() 创建移动关节时,移动为 0。所以一定要确保移动限制范围内包含了 0。移动关节的用法类似于旋转关节,这是它的相关成员函数: 

float32 GetJointTranslation()  const;
float32 GetJointSpeed()  const;
float32 GetMotorForce()  const;
void SetMotorSpeed(float32 speed);

void SetMotorForce(float32 force); 

 

 5)滑轮关节

 滑轮关节用于创建理想的滑轮,它将两个物体接地(ground)并连接到彼此。这样,当一个物体升起时,另一个物体就会下降。滑轮的绳子长度取决于初始时的状态。

length1 + length2 == constant

 

 

 你还可以提供一个系数(ratio)来模拟滑轮组,这会使滑轮一侧的运动比另一侧要快。同时,一侧的约束力也比另一侧要小。你也可以用这个来模拟机械杠杆(mechanical leverage)。length1 + ratio * length2 == constant 举个例子,如果系数是 2,那么 length1 的变化会是 length2 的两倍。另外连接 body1 的绳子的约束力将会是连接 body2 绳子的一半。当滑轮的一侧完全展开时,另一侧的绳子长度为零,这可能会出问题。此时,约束方程将变得奇异。因此,滑轮关节约束了每一侧的最大长度。另外出于游戏原因你可能也希望控制这个最大长度。最大长度能提高稳定性,以及提供更多的控制。

这是一个滑轮定义的例子:
b2Vec2 anchor1 = myBody1->GetWorldCenter();
b2Vec2 anchor2 = myBody2->GetWorldCenter();
b2Vec2 groundAnchor1(p1.x, p1.y +  10.0f);
 b2Vec2 groundAnchor2(p2.x, p2.y +  12.0f);
float32 ratio =  1.0f;
b2PulleyJointDef jointDef;
jointDef.Initialize(myBody1, myBody2, groundAnchor1, groundAnchor2, 
anchor1, anchor2, ratio);
jointDef.maxLength1 =  18.0f;
jointDef.maxLength2 =  20.0f;

 滑轮关节提供了当前长度:

float32 GetLength1()  const;
float32 GetLength2()  const;  

 

 6) 齿轮关节

 如果你想要创建复杂的机械装置,你可能需要齿轮。原则上,在 Box2D 中你可以用复杂的形状来模拟轮齿,但这并不十分高效,而且这样的工作可能有些乏味。另外,你还得小心地排列齿轮,保证轮齿能平稳地啮合。Box2D 提供了一个创建齿轮的更简单的方法:齿轮关节。

 

 

 齿轮关节需要两个被旋转关节或移动关节接地(ground)的物体,你可以任意组合这些关节类型。另外,创建旋转或移动关节时,Box2D 需要地(ground)作为 body1。类似于滑轮的系数,你可以指定一个齿轮系数(ratio),齿轮系数可以为负。另外值得注意的是,当一个是旋转关节(有角度的)而另一个是移动关节(平移)时,齿轮系数是长度或长度分之一。coordinate1 + ratio * coordinate2 == constant这是一个齿轮关节的例子:

b2GearJointDef jointDef;
jointDef.body1 = myBody1;
jointDef.body2 = myBody2;
jointDef.joint1 = myRevoluteJoint;
jointDef.joint2 = myPrismaticJoint;
jointDef.ratio =  2.0f * b2_pi / myLength; 
• 注意:齿轮关节总应该先于旋转或移动关节被删除,否则你的代码将会由于齿轮关节中的无效关节
指针而导致崩溃。另外齿轮关节也应该在任何相关物体被删除之前删除。

7)关节工厂

 关节是通过世界的工厂方法来创建和摧毁的,这引出了一个旧问题: 

• 注意:不要试图在栈上创建物体或关节,也不要使用 new 或 malloc 在堆上创建。物体以及关节必须要通过 b2World 类的方法来创建或摧毁。
这是一个关于旋转关节生命期的例子:
b2RevoluteJointDef jointDef;
jointDef.body1 = myBody1;
jointDef.body2 = myBody2;
jointDef.anchorPoint = myBody1->GetCenterPosition();
b2RevoluteJoint* joint = myWorld->CreateJoint(&jointDef);
//  ... do stuff ...
myWorld->DestroyJoint(joint);
joint = NULL;

 8)使用关节

 在许多模拟中,关节被创建之后便不再被访问了。然而,关节中包含着很多有用的数据,使你可以创建出丰富的模拟。首先,你可以在关节上得到物体,锚点,以及用户数据。

b2Body* GetBody1();
b2Body* GetBody2();
b2Vec2 GetAnchor1();
b2Vec2 GetAnchor2();
void* GetUserData();

 

 11、接触

 接触(contact)是由 Box2D 创建的用于管理形状间碰撞的对象。接触有不同的种类,它们都派生自 b2Contact,用于管理不同类型形状之间的接触。例如,有管理多边形之间碰撞的类,有管理圆形之间碰撞的类。

这里是 Box2D 中的一些与碰撞有关的术语:

 触点(contact point)

两个形状相互接触的点。实际上当物体的表面相接触时可能会有一定接触区域,在 Box2D 则近似地
以少数点来接触。
接触向量(contact normal)
从 shape1 指向 shape2 的单位向量。
接触分隔(contact separation)
分隔相反于穿透,当形状相重叠时,分隔为负。可能以后的 Box2D 版本中会以正隔离来创建触点,所以当有触点的报告时你可能会检查符号。
法向力(normal force)
Box2D 使用了一个迭代接触求解器,并会以触点保存结果。你可以安全地使用法向力来判断碰撞强度。例如,你可以使用这个力来引发破碎,或者播放碰撞的声音。

 切向力(tangent force)

它是接触求解器关于摩擦力的估计量。
接触标识(contact ids)
Box2D 会试图利用一个时间步中的触点压力(contact force)结果来推测下一个时间步中的情况。接触标识用于匹配跨越时间步的触点,它包含了几何特征索引以便区分触点。 

 

 当两个形状的 AABB 重叠时,接触就被创建了。有时碰撞筛选会阻止接触的创建,有时尽管碰撞已筛选了 Box2D 还是须要创建一个接触,这种情况下它会使用 b2NullContact 来防止碰撞的发生。当 AABB 不再重叠之后接触会被摧毁。也许你会皱起眉头,为了没有发生实际碰撞的形状(只是它们的 AABB)却创建了接触。好吧,的确是这样的,这是一个“鸡或蛋”的问题。我们并不知道是否需要一个接触,除非我们创建一个接触去分析碰撞。如果形状之间没有发生碰撞,我们需要正确地删除接触,或者,我们可以一直等到 AABB 不再重叠。Box2D 选择了后面这个方法。

 1)接触监听器
 通过实现 b2ContactListener 你就可以接受接触数据。当一个触点被创建时,当它持续超过一个时间步时,以及当它被摧毁时,这个监听器(listener)就会发出报告。请留意两个形状之间可能会有多个触点。
 class MyContactListener : public b2ContactListener
{
public:
 void Add(const b2ContactPoint* point)
 {
 // handle add point
 }
 void Persist(const b2ContactPoint* point)
 {
 // handle persist point
 }
 void Remove(const b2ContactPoint* point)
 {
 // handle remove point
 }
 void Result(const b2ContactResult* point)
 {
 // handle results
 }
};
 
 2)接触筛选
 通常,你不希望游戏中的所有物体都发生碰撞。例如,你可能会创建一个只有某些角色才能通过的门。这称之为接触筛选,因为一些交互被筛选出了。
通过实现 b2ContactFilter 类,Box2D 允许定制接触筛选。这个类需要一个 ShouldCollide 函数,用于接收两个 b2Shape 的指针,如果应该碰撞那么就返回 true。默认的 ShouldCollide 实现使用了 6 形状 中的 b2FilterData。
bool b2ContactFilter::ShouldCollide(b2Shape* shape1, b2Shape* shape2)
{
  const b2FilterData& filter1 = shape1->GetFilterData();
  const b2FilterData& filter2 = shape2->GetFilterData();
  if (filter1.groupIndex == filter2.groupIndex && filter1.groupIndex !=  0)
 {
  return filter1.groupIndex >  0;
 }
  bool collide = (filter1.maskBits & filter2.categoryBits) !=  0 && 
(filter1.categoryBits & filter2.maskBits) !=  0;
  return collide;
 
 12、杂项
 1)
你可以实现一个 b2BoundaryListener,这样当有物体超出世界的 AABB 时 b2World 就能通知你。当你得到回调时,你不应该试图删除物体;取而代之的是,你可以为角色做个删除或错误处理标记,在物理时间步之后再进行这个事件的处理。
class MyBoundaryListener : public b2BoundaryListener
{
 void Violation(b2Body* body)
 {
 MyActor* myActor = (MyActor*)body->GetUserData();
 myActor->MarkForErrorHandling();
 }
};
随后你可以在世界对象中注册你的边界监听器实例,这应该安排在世界初始化过程中。
myWorld->SetListener(myBoundaryListener);
 2)隐式摧毁
如果你摧毁一个 Box2D 实体,你应该保证所有到它的引用都删除了。如果你只有实体的单个引用的话,那就简单了。但如果你有很多个引用,你可能要考虑实现一个处理类来封装原始指针。通常使用 Box2D 时你需要创建并摧毁许多物体,形状还有关节。管理这些实体有些自动化,如果你摧毁一个物体,所有它的形状,关节,以及接触都会摧毁,这称为隐式摧毁。任何连接于这些关节或接触之一的物体将被唤醒,通常这是便利的。然而,你应该意识到了一个关键问题: 
 
 
Box2D 提供了一个名为 b2WorldListener 的监听器类,你可以实现它并提供给世界对象,随后当关节将被隐式摧毁时世界对象就会提醒你。
你可以实现一个 b2DestructionListener,这样当一个形状或关节隐式摧毁时 b2World 就能通知你,这可以帮助你预防访问无效指针。
class MyDestructionListener : public b2DestructionListener
{
 void SayGoodbye(b2Joint* joint)
 {
 // remove all references to joint.
 }
};
随后你可以注册它,这应该在世界初始化过程中。
myWorld->SetListener(myDestructionListener);
 

 

 

 

 

 

 

转载于:https://www.cnblogs.com/shangdahao/archive/2013/03/30/2462477.html

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Box2D一:基础知识 的相关文章

  • C语言/C++实现栈操作

    一 栈的概念 栈是一种常用的数据结构 它遵循先入后出 Last In First Out LIFO 的原则 栈的操作只在栈的一端进行 该端被称为栈顶 而另一端称为栈底 栈的基本操作包括压栈 入栈 push 和弹栈 出栈 pop 分别用于将元
  • Mysql 数据库

    数据库基础 1 什么是数据库 用来存储数据 数据库可在硬盘及内存中存储数据 数据库与文件存储数据的区别 数据库本质也是通过文件来存储数据 数据库的概念就是系统的管理存储数据的文件 数据库介绍 本质就是存储数据的C S架构的socket套接字
  • LeetCode83: 删除排序链表中的重复元素

    给定一个已排序的链表的头 head 删除所有重复的元素 使每个元素只出现一次 返回 已排序的链表 示例 1 输入 head 1 1 2 输出 1 2 示例 2 输入 head 1 1 2 3 3 输出 1 2 3 提示 链表中节点数目在范围
  • 直线检测方法—LSD论文翻译

    附原文链接 LSD a Line Segment Detector 摘 要 LSD是一个线段检测器 能够在线性时间内得到亚像素级精度的检测结果 它无需调试参数就可以适用于任何数字图像上 并且能够自我控制错误数量的检测 平均来说 一个图像中允
  • 第二十八节、基于深度学习的目标检测算法的综述(附代码,并附有一些算法英文翻译文章链接))...

    在前面几节中 我们已经介绍了什么是目标检测 以及如何进行目标检测 还提及了滑动窗口 bounding box 以及IOU 非极大值抑制等概念 这里将会综述一下当前目标检测的研究成果 并对几个经典的目标检测算法进行概述 本文内容来自基于深度学
  • findBug 错误修改指南

    FindBugs错误修改指南 1 EC UNRELATED TYPES Bug Call to equals comparing different types Pattern id EC UNRELATED TYPES type EC c
  • 链表和线性表的优缺点

    链表和线性表的优缺点 作为我们最先接触的两个数据结构 链表和线性表的优缺点都较为明显 并且二者互相补足 文章目录 链表和线性表的优缺点 线性表 线性表的组成 线性表的缺点 线性表的优点 链表 链表的组成 链表的优点 链表的缺点 总结 线性表
  • DDP入门

    DDP 即动态动态规划 可以用于解决一类带修改的DP问题 我们从一个比较简单的东西入手 最大子段和 带修改的最大子段和其实是常规问题了 经典的解决方法是用线段树维护从左 右开始的最大子段和和区间最大子段和 然后进行合并 现在我们换一种方法来
  • Python 实现列队

    1 列队定义 队列是项的有序结合 其中添加新项的一端称为队尾 移除项的一端称为队首 当一个元素从队尾进入队列时 一直向队首移动 直到它成为下一个需要移除的元素为止 最近添加的元素必须在队尾等待 集合中存活时间最长的元素在队首 这种排序成为
  • 『Python基础-15』递归函数 Recursion Function

    什么是递归函数 一种计算过程 如果其中每一步都要用到前一步或前几步的结果 称为递归的 用递归过程定义的函数 称为递归函数 例如连加 连乘及阶乘等 凡是递归的函数 都是可计算的 即能行的 递归就是一个函数在它的函数体内调用它自身 编程语言中的
  • 数据结构之图的两种遍历实现(C语言版)

    上一期文章分享完了图的两种遍历方式 也是两种很重要的算法 DFS和BFS 这两种算法的应用和重要性我就不多说了 内行的人懂的都懂 今天这文章重要就是来上机实现这两种算法 又由于这两种算法都可以由邻接矩阵和邻接表来表示 博主分享的代码都是上机
  • 以太坊系列之十五: 以太坊数据库

    以太坊数据库中都存了什么 以太坊使用的数据库是一个NOSQL数据库 是谷歌提供的开源数据leveldb 这里尝试通过分析以太坊数据库存储了什么来分析以太坊可能为我们提供哪些关于区块链的API 存储内容 NOSQL是一个key value数据
  • 字符串09--表示数值的字符串

    字符串09 表示数值的字符串 jz53 题目概述 解析 参考答案 注意事项 说明 题目概述 算法说明 请实现一个函数用来判断字符串是否表示数值 包括整数和小数 例如 字符串 100 5e2 123 3 1416 和 1E 16 都表示数值
  • Unique Binary Search Trees -- LeetCode

    原题链接 http oj leetcode com problems unique binary search trees 这道题要求可行的二叉查找树的数量 其实二叉查找树可以任意取根 只要满足中序遍历有序的要求就可以 从处理子问题的角度来
  • 浮生六记

    浮生六记 目录 浮生六记卷一 闺房记乐 002 浮生六记卷二 闲情记趣 015 浮生六记卷三 坎坷记愁 022 浮生六记卷四 浪游记快 034 浮生六记 2 浮生六记卷一 闺房记乐 余生乾隆癸未冬十一月二十有二日 正值太平盛世 且在 衣冠之
  • 算法问题实战策略

    算法问题实战策略 基本信息作者 韩 具宗万 译者 崔盛一出版社 人民邮电出版社ISBN 9787115384621上架时间 2015 2 4出版日期 2015 年3月开本 16开页码 738版次 1 1 内容简介 算法问题实战策略 本书收录
  • 算法学习——贪心算法之币种统计

    算法描述 币种统计 单位给每一位员工发工资 精确到元 为了保证不临时换零钱 使得每个员工取款的张数最少 在取工资前统计所有员工所需要的各种票面的张数 约定票种为100 50 20 10 5 2 1元 并验证币种统计是否正确 算法思路 算法描
  • Linux 内核中的 Device Mapper 机制

    Linux 内核中的 Device Mapper 机制 尹 洋 在读博士生 尹洋 中科院计算所国家高性能计算机工程技术研究中心的在读博士生 主要从事服务部署和存储资源管理以及Linux块设备一级的开发和研究工作 简介 本文结合具体代码对 L
  • 查找数组中第二大的数

    快速找出一个数组中的最大数 第二大数 思路 如果当 前元素大于最大数 max 则让第二大数等于原来的最大数 max 再把当前元素的值赋给 max 如果当前的元素大于等于第二大数secondMax的值而小于最大数max的值 则要把当前元素的值
  • 插入排序超详解释,一看就懂

    目录 一 插入排序的相关概念 1 基本思想 2 基本操作 有序插入 二 插入排序的种类 三 直接插入排序 1 直接插入排序的过程 顺序查找法查找插入位置 2 使用 哨兵 直接插入排序 四 直接插入排序算法描述 五 折半插入排序 1 查找插入

随机推荐

  • linux+应用程序运行日志,Linux 系统运行着许多子系统和应用程序。您可以使用系统日志记录从启动时就收集有关运行中系统的数据。有时...

    概述 在本教程中 您将学习以下内容 配置 syslog 守护程序 了解标准设施 优先级和操作 配置日志轮换 了解 rsyslog 和 syslog ng 系统内部发生了什么 Linux 系统运行着许多子系统和应用程序 您可以使用系统日志记录
  • c++ 双端队列 deque用法解析

    1 deque的作用 deque即双端队列 它的用法非常强大 可以代替栈stack 队列queue 向量容器vector等等 因为它能像栈一样后进先出 也能像queue一样先进先出 还能像vector一样随机访问 同时支持sort lowe
  • java模糊查询代码_Java模糊查询方法详解

    这篇文章主要为大家详细介绍了Java模糊查询方法的实现 实例教你如何用Java做模糊查询结果 感兴趣的小伙伴们可以参考一下 当我们需要开发一个方法用来查询数据库的时候 往往会遇到这样一个问题 就是不知道用户到底会输入什么条件 那么怎么样处理
  • 【机器学习】太香啦!只需一行Python代码就可以自动完成模型训练!

    自动化机器学习 Auto ML 是指数据科学模型开发的管道组件自动化 AutoML 减少了数据科学家的工作量并加快了工作流程 AutoML 可用于自动化各种管道组件 包括数据理解 EDA 数据处理 模型训练 超参数调整等 对于端到端机器学习
  • 小米 adb 驱动_ADB禁用系统应用

    第一步 下载ADB压缩包 并解压到根目录下 自己百度找ADB包 第二步 在ADB目录下 shift 鼠标右键 打开powershell 并输入cmd 第三步 手机进入开发者模式 百度 并打开USB调试 数据线连接电脑和手机 要安装好驱动 在
  • 解决idea项目没有蓝色小方块

    导入项目后 把项目中的几个子moudle复制了一份 作为一个新模块 结果发现 项目右下角没有 蓝色小方块 因此造成maven不能识别 如下图 解决方式 在右边侧栏 maven 面板 点击 选择该项目中的pom xml文件即可
  • linux安装时 dev sda4,VMvare在CentOS7.4安装iscsi共享盘

    VMvare 在 CentOS7 4 安装 iscsi 共享盘 1 在节点 1 上添加一个 20g 的存储并 reboot 1 1 查看新添加的磁盘 root xmc1 fdisk l Disk dev sda 32 2 GB 322122
  • python多进程和多线程看这一篇就够了

    脑海中关于进程和线程的概念一直很模糊 什么时候该用多进程 什么时候该用多线程总是搞不清楚 同时python因为历史遗留问题存在GIL全局锁 就让人更加困惑 这一篇就完整整理一下python中进程和线程的概念和实现 文章目录 进程和线程 GI
  • 逻辑Flask——Flask模板

    文章目录 一 简单模板 二 模板传参 三 jinja2内置过滤器 四 Jinja2自定义过滤器 通用过滤器 自定义时间过滤器 五 控制语句 1 if 语句 2 for in语句 列表 遍历字典 获取当前遍历状态 六 宏 6 0 宏的基本模板
  • Python 基础知识7 集合

    python 集合 创建集合 重复的自动被过滤 parame apple orrange banana pear pear print parame parame1 set bird dog pig print parame1 parame
  • 各平台电脑开启虚拟化的方法

    1 Intel平台笔记本 进入BIOS 选择 Configuration 选项 找到 Intel Virtual Technology 改成Enable 2 AMD平台笔记本 进入BIOS 选择 Configuration 的选项 找到 S
  • 使用非负最小二乘回(NNLS)归进行细胞类型转移

    2019年发表在Nature上的文章 The single cell transcriptional landscape of mammalian organogenesis 在方法部分提到 使用NNLS non negative line
  • 西门子传动系统出现这些故障提示原因分析

    1 F002 Pre charging 预充电故障 对整流单元来说 其可能原因为 主进线开关断开或没有闭合 整流单元 例 复卷机整流单元四个熔断器断路 导致传动点出现F008 直流母线电压过低 在更换熔断器后 启动整流器时出现F002 原因
  • bios sgx需要开启吗_华硕主板BIOS解读:新手装机必备篇(必须收藏)

    史上最全BIOS解读 中英文对译 一字一句解析 让你从此不在BIOS中迷路 本期讲解模板BIOS来自华硕TUF X570 GAMING WIFI 今天讲解的内容为新手装机必须经历的内容 新手装机常用到的BIOS选项 并且附上中英文翻译对照
  • 快速排序算法的三种实现

    1 普通快速排序 快速排序思路 随机取一个标定点 v 将 v 放置到合适的位置 保证 v 左边的元素都小于等于 v v 右边的元素都大于 v 然后再继续分别对左边元素和右边的元素做同样的排序动作 直到整个数组有序 那么怎么实现这个快速排序呢
  • Vue-Loader 打包单文件组件实战

    本文是我在学习过程中记录学习的点点滴滴 目的是为了学完之后巩固一下顺便也和大家分享一下 日后忘记了也可以方便快速的复习 Vue Loader 打包单文件组件 前言 一 webpack 结合 Vue Loader 打包单文件组件基本认识 1
  • java 分配算法

    原文地址 http blog csdn net qq 30085577 article details 52756715 版权声明 本文为博主原创文章 未经博主允许不得转载 java view plain copy 随机分配 public
  • Mathorcup数学建模竞赛第二届-【妈妈杯】A题:最佳飞行队列(附带赛题解析&获奖论文及MATLAB代码)

    目录 赛题描述 论文 摘要 一 问题重述 二 模型假设及符号说明 1 模型假设
  • you need to resolve your current index first 解决办法

    前些天发现了一个巨牛的人工智能学习网站 通俗易懂 风趣幽默 忍不住分享一下给大家 点击跳转到教程 从一个分支A切换到另一个分支B后 对切换后的B分支进行pull操作 因为pull操作实际上包含了fetch merge操作 在执行 merge
  • Box2D一:基础知识

    一 box2d基础知识 1 关于 Box2D 是一个用于游戏的 2D 刚体仿真库 从游戏的视角来看 物理引擎就是一个程序性动画 procedural animation 的系统 而不是由动画师去移动你的物体 1 核心概念 刚体 rigid