瀏覽代碼

Added Portal system to main code base

undefined
Benjamin ‘Touky’ Huet Sam Hocevar <sam@hocevar.net> 10 年之前
父節點
當前提交
8268513786
共有 5 個檔案被更改,包括 525 行新增136 行删除
  1. +1
    -1
      src/Makefile.am
  2. +132
    -123
      src/lol/algorithm/aabb_tree.h
  3. +1
    -0
      src/lol/algorithm/all.h
  4. +391
    -0
      src/lol/algorithm/portal.h
  5. +0
    -12
      src/lol/debug/lines.h

+ 1
- 1
src/Makefile.am 查看文件

@@ -42,7 +42,7 @@ liblolcore_headers = \
lol/math/constants.h \
\
lol/algorithm/all.h \
lol/algorithm/sort.h \
lol/algorithm/sort.h lol/algorithm/portal.h lol/algorithm/aabb_tree.h \
\
lol/sys/all.h \
lol/sys/init.h lol/sys/file.h lol/sys/thread.h lol/sys/atomic.h \


+ 132
- 123
src/lol/algorithm/aabb_tree.h 查看文件

@@ -9,8 +9,8 @@
// http://www.wtfpl.net/ for more details.
//

#if !defined __LOL_SPACE_PARTITIONING_H__
#define __LOL_SPACE_PARTITIONING_H__
#if !defined __LOL_AABB_TREE_H__
#define __LOL_AABB_TREE_H__

#include <lol/base/array.h>
#include <lol/debug/lines.h>
@@ -19,6 +19,114 @@ namespace lol
{

//------ AABB TREE SYSTEM -----
template <typename TE, typename TV, typename TB, size_t child_nb> class AABBTree;
template <typename TE> class QuadTree;
template <typename TE> class OcTree;

//--
namespace Debug {
//--
#define GET_DRAW_DATA(DEF_TREE, DEF_BOXES, DEF_ELEM, CHILD_NB, TBB) \
boxes.Push(tree->GetAABB(), vec4::one); \
leaves.Push(0, boxes.Last().m1); \
while (leaves.Count() > 0) \
{ \
for (int j = 0; j < tree->m_tree[leaves[0].m1].m_elements.Count(); j++) \
{ \
bool done = false; \
for (int k = 0; k < elements.Count(); k++) \
{ \
if (elements[k].m1 == tree->m_elements[tree->m_tree[leaves[0].m1].m_elements[j]].m_element) \
{ \
elements[k].m2++; \
done = true; \
break; \
} \
} \
if (!done) \
elements.Push(tree->m_elements[tree->m_tree[leaves[0].m1].m_elements[j]].m_element, 1, vec4::v1001); \
} \
\
for (int i = 0; i < CHILD_NB; i++) \
{ \
if (tree->m_tree[leaves[0].m1].m_children[i] != 0) \
{ \
TBB bbox = tree->GetSubAABB(leaves[0].m2, i); \
leaves.Push(tree->m_tree[leaves[0].m1].m_children[i], bbox); \
boxes.Push(bbox, color); \
} \
} \
leaves.Remove(0); \
}

//--
template <typename TE>
void Draw(QuadTree<TE>* tree, vec4 color)
{
Array<box2, vec4> boxes;
Array<TE*, int, vec4> elements;
Array<int, box2> leaves;

GET_DRAW_DATA(tree, boxes, elements, 4, box2);

vec3 off = vec3::v010 * .1f;
vec3 add = vec3::v010 * .1f;
while (boxes.Count() > 0)
{
Debug::DrawBox(vec3(boxes[0].m1.A.x, tree->m_debug_y_offset, boxes[0].m1.A.y),
vec3(boxes[0].m1.B.x, tree->m_debug_y_offset, boxes[0].m1.B.y),
boxes[0].m2);
boxes.Remove(0);
}
while (elements.Count() > 0)
{
while (elements[0].m2 > 0)
{
Debug::DrawBox(vec3(elements[0].m1->GetAABB().A.x, tree->m_debug_y_offset, elements[0].m1->GetAABB().A.y) + off * (float)elements[0].m2,
vec3(elements[0].m1->GetAABB().B.x, tree->m_debug_y_offset, elements[0].m1->GetAABB().B.y) + off * (float)elements[0].m2,
elements[0].m3);
elements[0].m2--;
}
elements.Remove(0);
}
}
//--
template <typename TE>
void Draw(OcTree<TE>* tree, vec4 color)
{
Array<vec3, vec4> boxes;
Array<TE*, int, vec4> elements;
Array<int, box3> leaves;

GET_DRAW_DATA(tree, boxes, elements, 8, box3);

vec3 off = vec3::v010 * .1f;
vec3 add = vec3::v010 * .1f;
while (boxes.Count() > 0)
{
float size = boxes[0].m1.B.x - boxes[0].m1.A.x;
Debug::DrawBox(vec3(boxes[0].m1.A.x, boxes[0].m1.A.y, boxes[0].m1.A.z) /* + off * (m_size.x / size) */,
vec3(boxes[0].m1.B.x, boxes[0].m1.B.y, boxes[0].m1.B.z) /* + off * (m_size.x / size) */,
boxes[0].m2);
//off += add;
boxes.Remove(0);
}
while (elements.Count() > 0)
{
while (elements[0].m2 > 0)
{
Debug::DrawBox(vec3(elements[0].m1->GetAABB().A.x, elements[0].m1->GetAABB().A.y, elements[0].m1->GetAABB().A.z) + off * (float)elements[0].m2,
vec3(elements[0].m1->GetAABB().B.x, elements[0].m1->GetAABB().B.y, elements[0].m1->GetAABB().B.z) + off * (float)elements[0].m2,
elements[0].m3);
elements[0].m2--;
}
elements.Remove(0);
}
}
//--
}

//--
template <typename TE, typename TV, typename TB, size_t child_nb>
class AABBTree
{
@@ -53,9 +161,7 @@ public:
m_max_element = 1;
AddLeaf(0);
}
~AABBTree()
{
}
~AABBTree() { }

private:
//--
@@ -127,16 +233,6 @@ private:
return idx;
}

//--
virtual TV GetSubOffset(int sub) = 0;
virtual TB GetAABB() { return TB(-m_size * .5f, m_size * .5f); }
virtual TB GetSubAABB(const TB& bbox, int sub)
{
TV v(GetSubOffset(sub));
TV half_vec = (bbox.B - bbox.A) * .5f;
return TB(bbox.A + half_vec * v,
bbox.A + half_vec * (v + TV::one));
}
//--
bool TestLeaf(int leaf, const TB& leaf_bb, const TB& test_bb, Array<TE*>& elements)
{
@@ -218,53 +314,20 @@ public:
m_elements.Empty();
}

void SetSize(TV size) { m_size = size; }
void SetMaxDepth(int max_depth) { m_max_depth = max_depth; }

#if LOL_BUILD_DEBUG
//DEBUG DRAW
virtual void DebugDraw(vec4 color)
//--
virtual TV GetSubOffset(int sub) = 0;
virtual TB GetAABB() { return TB(-m_size * .5f, m_size * .5f); }
virtual TB GetSubAABB(const TB& bbox, int sub)
{
Array<int, TB> leaves;
Array<TB, vec4> boxes;
Array<TE*, int, vec4> elements;
boxes.Push(GetAABB(), vec4::one);
leaves.Push(0, boxes.Last().m1);
while (leaves.Count() > 0)
{
for (int j = 0; j < m_tree[leaves[0].m1].m_elements.Count(); j++)
{
bool done = false;
for (int k = 0; k < elements.Count(); k++)
{
if (elements[k].m1 == m_elements[m_tree[leaves[0].m1].m_elements[j]].m_element)
{
elements[k].m2++;
done = true;
break;
}
}
if (!done)
elements.Push(m_elements[m_tree[leaves[0].m1].m_elements[j]].m_element, 1, vec4::v1001);
}

for (int i = 0; i < child_nb; i++)
{
if (m_tree[leaves[0].m1].m_children[i] != 0)
{
TB bbox = GetSubAABB(leaves[0].m2, i);
leaves.Push(m_tree[leaves[0].m1].m_children[i], bbox);
boxes.Push(bbox, color);
}
}
leaves.Remove(0);
}
DebugDrawBoxes(boxes, elements);
TV v(GetSubOffset(sub));
TV half_vec = (bbox.B - bbox.A) * .5f;
return TB(bbox.A + half_vec * v,
bbox.A + half_vec * (v + TV::one));
}
protected:
//DEBUG DRAW
virtual void DebugDrawBoxes(Array<TB, vec4> boxes, Array<TE*, int, vec4> elements) = 0;
#endif //LOL_BUILD_DEBUG

//--
void SetSize(TV size) { m_size = size; }
void SetMaxDepth(int max_depth) { m_max_depth = max_depth; }

protected:
Array<NodeLeaf> m_tree; //actual tree
@@ -275,86 +338,32 @@ protected:
int m_max_element; //Maximum element per leaf
};

//--
template <typename TE>
class QuadTree : public AABBTree<TE, vec2, box2, 4>
{
template <typename TE> friend void Debug::Draw(QuadTree<TE>* tree, vec4 color);
public:
QuadTree()
{
#if LOL_BUILD_DEBUG
m_debug_y_offset = 0;
#endif //LOL_BUILD_DEBUG
}
#if LOL_BUILD_DEBUG
public:
QuadTree() { m_debug_y_offset = 0.f; }
virtual ~QuadTree() { }
float m_debug_y_offset;
protected:
virtual void DebugDrawBoxes(Array<box2, vec4> boxes, Array<TE*, int, vec4> elements)
{
vec3 off = vec3::v010 * .1f;
vec3 add = vec3::v010 * .1f;
while (boxes.Count() > 0)
{
Debug::DrawBox(vec3(boxes[0].m1.A.x, m_debug_y_offset, boxes[0].m1.A.y),
vec3(boxes[0].m1.B.x, m_debug_y_offset, boxes[0].m1.B.y),
boxes[0].m2);
boxes.Remove(0);
}
while (elements.Count() > 0)
{
while (elements[0].m2 > 0)
{
Debug::DrawBox(vec3(elements[0].m1->GetAABB().A.x, m_debug_y_offset, elements[0].m1->GetAABB().A.y) + off * (float)elements[0].m2,
vec3(elements[0].m1->GetAABB().B.x, m_debug_y_offset, elements[0].m1->GetAABB().B.y) + off * (float)elements[0].m2,
elements[0].m3);
elements[0].m2--;
}
elements.Remove(0);
}
}
#endif //LOL_BUILD_DEBUG
protected:
virtual vec2 GetSubOffset(int sub) { return vec2(ivec2(sub % 2, sub / 2)); }
};

//--
template <typename TE>
class OcTree : public AABBTree<TE, vec3, box3, 8>
{
template <typename TE> friend void Debug::Draw(OcTree<TE>* tree, vec4 color);
public:
virtual ~OcTree() {}
OcTree() { }
virtual ~OcTree() { }
protected:
#if LOL_BUILD_DEBUG
virtual void DebugDrawBoxes(Array<box3, vec4> boxes, Array<TE*, int, vec4> elements)
{
vec3 off = vec3::v010 * .1f;
vec3 add = vec3::v010 * .1f;
while (boxes.Count() > 0)
{
float size = boxes[0].m1.B.x - boxes[0].m1.A.x;
Debug::DrawBox(vec3(boxes[0].m1.A.x, boxes[0].m1.A.y, boxes[0].m1.A.z) /* + off * (m_size.x / size) */,
vec3(boxes[0].m1.B.x, boxes[0].m1.B.y, boxes[0].m1.B.z) /* + off * (m_size.x / size) */,
boxes[0].m2);
//off += add;
boxes.Remove(0);
}
while (elements.Count() > 0)
{
while (elements[0].m2 > 0)
{
Debug::DrawBox(vec3(elements[0].m1->GetAABB().A.x, elements[0].m1->GetAABB().A.y, elements[0].m1->GetAABB().A.z) + off * (float)elements[0].m2,
vec3(elements[0].m1->GetAABB().B.x, elements[0].m1->GetAABB().B.y, elements[0].m1->GetAABB().B.z) + off * (float)elements[0].m2,
elements[0].m3);
elements[0].m2--;
}
elements.Remove(0);
}
}
#endif //LOL_BUILD_DEBUG
virtual vec3 GetSubOffset(int sub) { return vec3(ivec3(sub % 2, sub / 4, (sub % 4) / 2)); }
};

} /* namespace lol */

#endif // __LOL_SPACE_PARTITIONING_H__
#endif // __LOL_AABB_TREE_H__


+ 1
- 0
src/lol/algorithm/all.h 查看文件

@@ -13,6 +13,7 @@

#include <lol/algorithm/sort.h>
#include <lol/algorithm/aabb_tree.h>
#include <lol/algorithm/portal.h>

#endif // __LOL_ALGORITHM_ALL_H__


+ 391
- 0
src/lol/algorithm/portal.h 查看文件

@@ -0,0 +1,391 @@
//
// Lol Engine
//
// Copyright: (c) 2010-2014 Sam Hocevar <sam@hocevar.net>
// (c) 2013-2014 Benjamin "Touky" Huet <huet.benjamin@gmail.com>
// This program is free software; you can redistribute it and/or
// modify it under the terms of the Do What The Fuck You Want To
// Public License, Version 2, as published by Sam Hocevar. See
// http://www.wtfpl.net/ for more details.
//

#if !defined __LOL_PORTAL_H__
#define __LOL_PORTAL_H__

#include <lol/base/array.h>
#include <lol/debug/lines.h>

namespace lol
{

//------ PORTAL SYSTEM --------
template <typename TE> class PortalRoom;
template <typename TE> class PortalDoor;
template <typename TE> class PortalSet;

//--
namespace Debug {
template <typename TE>
void Draw(PortalDoor<TE>& port, vec4 color)
{
vec3 points[4]; port.GetPoints(points);

//Draw normal
vec3 p = port.m_center + port.m_up * port.m_size.y * .5f;
Debug::DrawLine(p, p + port.m_normal, vec4::v1001);
Debug::DrawLine(p, p + port.m_up, vec4::v0101);
//draw door
for (int l = 0; l < 4; l++)
Debug::DrawLine(points[l], points[(l + 1) % 4], color);
Debug::DrawLine(points[0], points[2], color);
Debug::DrawLine(points[1], points[3], color);
}
}

//PortalDoor base class
template <typename TE>
class PortalDoor
{
template<typename TE> friend class PortalSet;
template<typename TE> friend class PortalRoom;
template<typename TE> friend void Debug::Draw(PortalDoor<TE>& port, vec4 color);

private:
void Init()
{
m_center = vec3::zero;
m_normal = vec3::zero;
m_up = vec3::zero;
m_size = vec2::zero;

m_view = mat4::identity;
m_proj = mat4::identity;

m_rooms[0] = nullptr;
m_rooms[1] = nullptr;
}

public:
//Normal portal
PortalDoor(vec3 center, vec3 normal, vec3 up, vec2 size)
{
Init();

m_center = center;
m_normal = normal;
m_up = up;
m_size = size;
}
//Camera portal
PortalDoor(mat4 view, mat4 proj)
{
Init();

m_view = view;
m_proj = proj;
}
//D.Tor
~PortalDoor()
{
ConnectRooms(nullptr, nullptr);
}

//Connect door to room
void ConnectRooms(class PortalRoom<TE>* front_room, class PortalRoom<TE>* back_room)
{
for (int i = 0; i < 2; i++)
if (m_rooms[i] != nullptr)
*m_rooms[i] >> this;

m_rooms[0] = back_room;
m_rooms[1] = front_room;

for (int i = 0; i < 2; i++)
if (m_rooms[i] != nullptr)
*m_rooms[i] << this;
}

//--
void DisconnectRoom(class PortalRoom<TE>* room)
{
for (int i = 0; i < 2; i++)
if (m_rooms[i] != nullptr && m_rooms[i] == room)
m_rooms[i] = nullptr;
}

//--
PortalRoom<TE>* GetRoom(bool front) { return m_rooms[(int)front]; }
PortalRoom<TE>* GetRoom(PortalRoom<TE>* room) { return (m_rooms[0] == room) ? m_rooms[1] : m_rooms[0]; }

//Get Four portal point
void GetPoints(vec3 *points) const
{
vec3 right = cross(m_normal, m_up);
points[0] = m_center + right * m_size.x * .5f + m_up * m_size.y;
points[1] = m_center + right * m_size.x * .5f;
points[2] = m_center + right * m_size.x * -.5f;
points[3] = m_center + right * m_size.x * -.5f + m_up * m_size.y;
}
//Builds the portal view proj.
//Returns false if portal is out of the view or points are on each others.
bool BuildViewProj(mat4 view, mat4 proj)
{
mat4 cam_mx = proj * view;
mat4 inv_proj_mx = inverse(proj);

//First: Check normal dot
if (lol::abs(dot((inverse(view) * vec4::v0010).xyz, m_normal)) < .00001f)
return false;

//Second: convert in screen
vec3 port_2d[2] = { vec3(FLT_MAX), vec3(-FLT_MAX) };
vec3 door_points[4];
vec4 proj_p[4];

GetPoints(door_points);

for (int i = 0; i < 4; i++)
{
//W to S calculations
proj_p[i] = cam_mx * vec4(door_points[i], 1.f);
proj_p[i] /= proj_p[i].w;

//Clamp points within screen
port_2d[0] = lol::min(proj_p[i].xyz, port_2d[0]);
port_2d[1] = lol::max(proj_p[i].xyz, port_2d[1]);
port_2d[0] = vec3(lol::clamp(port_2d[0].xy, vec2::onen, vec2::one), port_2d[0].z);
port_2d[1] = vec3(lol::clamp(port_2d[1].xy, vec2::onen, vec2::one), port_2d[1].z);
}

//Quit if door not within the screen
for (int i = 0; i < 3; i++)
if (port_2d[0][i] == port_2d[1][i])
return false;

//Third: Convert back to view
ivec2 j[4] = { ivec2::v00, ivec2::v01, ivec2::v11, ivec2::v10 };
vec3 frust[2] = { vec3::one * FLT_MAX, vec3::one * -FLT_MAX };
for (int i = 0; i < 5; i++)
{
int k = i % 4;
//world calculations
proj_p[k] = inv_proj_mx * vec4(port_2d[j[k].x].x, port_2d[j[k].y].y, (i<4)?(port_2d[0].z):(1.f), 1.f);
proj_p[k] /= proj_p[k].w;
proj_p[k].z = lol::abs(proj_p[k].z);

for (int h = 0; h < 3; h++)
{
if (i < 4 || h > 1)
{
frust[0][h] = lol::min(frust[0][h], proj_p[k][h]);
frust[1][h] = lol::max(frust[1][h], proj_p[k][h]);
}
}
}

//Fourth: Create frustum
m_proj = mat4::frustum(frust[0].x, frust[1].x, frust[0].y, frust[1].y, frust[0].z, frust[1].z);
m_view = view;

return true;
}

//View proj getter (doesn't check matrix validity)
mat4 GetViewProj() { return m_proj * m_view; }

//--
bool TestCollide(const vec3& point)
{
return TestPointVsFrustum(point, GetViewProj());
}

//--
bool TestCollide(const PortalDoor& door)
{
vec3 door_points[4];
vec3 res_points[4];
ivec3 pos_test = ivec3::zero;
bool is_in = false;

//Get points and test them on frustum
door.GetPoints(door_points);
for (int i = 0; i < 4; i++)
{
is_in = is_in || TestPointVsFrustum(door_points[i], GetViewProj(), &res_points[i]);

if (is_in)
return true;

//Add points on test stuff
pos_test += ivec3(lol::clamp(res_points[i], vec3::onen * 1.1f, vec3::one * 1.1f));
}

return false;

//Check if at least one point is not on the same side as the others
for (int i = 0; i < 3; i++)
if (lol::abs(pos_test[i]) == 4)
return false;

return true;
}

private:
mat4 m_view;
mat4 m_proj;
vec3 m_center;
vec3 m_normal;
vec3 m_up;
vec2 m_size;
PortalRoom<TE>* m_rooms[2]; //0: Back, 1: Front
};

//--
template <typename TE>
class PortalRoom
{
template<typename TE> friend class PortalSet;
template<typename TE> friend class PortalDoor;

public:
PortalRoom(TE* element=nullptr)
{
m_element = element;
}
~PortalRoom()
{
for (int i = 0; i < m_doors.Count(); i++)
m_doors[i]->DisconnectRoom(this);
m_doors.Empty();
}

PortalRoom& operator<<(class PortalDoor<TE>* door)
{
m_doors.PushUnique(door);
return *this;
}
PortalRoom& operator>>(class PortalDoor<TE>* door)
{
m_doors.RemoveSwapItem(door);
return *this;
}

int GetDoorCount() { return m_doors.Count(); }
PortalDoor<TE>* GetDoor(int i) { return m_doors[i]; }

private:
//Portals associated with this room.
Array<PortalDoor<TE>*> m_doors;
TE* m_element;
};

//--
template <typename TE>
class PortalSet
{
public:
~PortalSet()
{
for (int i = 0; i < m_doors.Count(); i++)
delete m_doors[i];
for (int i = 0; i < m_rooms.Count(); i++)
delete m_rooms[i];
m_doors.Empty();
m_rooms.Empty();
}

//Visible room getter
void GetVisibleRooms(PortalDoor<TE>* see_through, PortalRoom<TE>* start_room, Array<PortalRoom<TE>*>& visible_rooms)
{
Array<PortalDoor<TE>*> ignore_doors;
GetVisibleRooms(see_through, start_room, visible_rooms, ignore_doors);
#if LOL_BUILD_DEBUG
for (int j = 0; j < visible_rooms.Count(); j++)
{
vec4 tmp = vec4::zero;
for (int i = 0; i < visible_rooms[j]->m_doors.Count(); i++)
{
PortalDoor<TE>* port = visible_rooms[j]->m_doors[i];
Debug::Draw(*port, vec4::v0111);
tmp += vec4(port->m_center, 1.f);
}
tmp /= tmp.w;
Debug::DrawBox(tmp.xyz-vec3::one,tmp.xyz+vec3::one,vec4::v1101);
}
for (int i = 0; i < ignore_doors.Count(); i++)
{
PortalDoor<TE>* port = ignore_doors[i];
Debug::Draw(*port, vec4::v1011);
Debug::DrawViewProj(port->m_view, port->m_proj, vec4::v1011);
}
#endif //LOL_BUILD_DEBUG
}
private:
void GetVisibleRooms(PortalDoor<TE>* see_through, PortalRoom<TE>* start_room, Array<PortalRoom<TE>*>& visible_rooms, Array<PortalDoor<TE>*>& ignore_doors)
{
for (int i = 0; i < start_room->m_doors.Count(); i++)
{
PortalDoor<TE>* door = start_room->m_doors[i];
if (door == see_through || ignore_doors.Find(door) != INDEX_NONE)
continue;

if (see_through->TestCollide(*door))
{
PortalRoom<TE>* other_room = door->GetRoom(start_room);
if (visible_rooms.Find(other_room) != INDEX_NONE)
continue;

ignore_doors.PushUnique(door);
visible_rooms.PushUnique(other_room);
door->BuildViewProj(see_through->m_view, see_through->m_proj);
GetVisibleRooms(door, other_room, visible_rooms, ignore_doors);
}
}
}
public:

//Operator
PortalSet<TE>& operator<<(class PortalRoom<TE>* room)
{
m_rooms.PushUnique(room);
for (int i = 0; i < room->m_doors.Count(); i++)
m_doors.PushUnique(room->m_doors[i]);
return *this;
}
//--
PortalSet<TE>& operator>>(class PortalRoom<TE>* room)
{
for (int i = 0; i < room->m_doors.Count(); i++)
*this >> room->m_doors[i];
m_rooms.RemoveItem(room);
return *this;
}
//--
PortalSet<TE>& operator<<(class PortalDoor<TE>* door)
{
m_doors.PushUnique(door);
return *this;
}
//--
PortalSet<TE>& operator>>(class PortalDoor<TE>* door)
{
m_doors.RemoveItem(door);
return *this;
}

//--
int GetDoorCount() { return m_doors.Count(); }
PortalDoor<TE>* GetDoor(int i) { return m_doors[i]; }
int GetRoomCount() { return m_rooms.Count(); }
PortalRoom<TE>* GetRoom(int i) { return m_rooms[i]; }

private:
//Portals associated with this room.
Array<PortalRoom<TE>*> m_rooms;
Array<PortalDoor<TE>*> m_doors;
};

} /* namespace lol */

#endif // __LOL_PORTAL_H__


+ 0
- 12
src/lol/debug/lines.h 查看文件

@@ -24,18 +24,6 @@ namespace lol
namespace Debug
{

//This funcs MUST be specialized
template<typename T>
void Draw(T& x, vec4 color)
{
ASSERT(0);
}
template<template <typename> class B, typename T>
void Draw(B<T>* x, vec4 color)
{
x->DebugDraw(color);
}

void DrawLine(vec3 a, vec3 b, vec4 color);
void DrawBox(box3 a, vec4 color);
void DrawBox(vec3 a, vec3 b, vec4 color);


Loading…
取消
儲存