본문으로 바로가기

200727_Circle_Collision_Obb.zip
1.57MB

 

 

 

1. Rect Collider class

  - rect<->circle : circle class에서 실행

더보기
bool RectCollider::IsCollision(CircleCollider* circle)
{ // AABB 미구현
	if (isObb)
		return circle->OBB(this);
	//else
	//	return circle->AABB(this);
}

bool RectCollider::OBB(CircleCollider* circle)
{ // circle class에서 구현
	return circle->OBB(this);
}

 

2. Circle Collider class

  a. Collider::CreateData() ( b. Collision 코드 참조)

    - Vector2 Rotate() ( radian 각을 이용한 Vector 회전)

더보기
Vector2 Vector2::GetRotatedVector(double angle)
{
	Vector2 tmp{};
	tmp.x = x * cos(angle) - y * sin(angle);
	tmp.y = x * sin(angle) + y * cos(angle);
	return tmp;
}

void Vector2::Rotate(double angle)
{
	Vector2 tmp{};
	tmp.x = x * cos(angle) - y * sin(angle);
	tmp.y = x * sin(angle) + y * cos(angle);
	//
	x = tmp.x;
	y = tmp.y;
}

  b. Collision

    b.1 Point <-> Circle (center <-> point distnce / radius 비교)

    b.2 Circle <-> Circle (circle 1&2 center distance / radius 1+2 비교)

    b.3 Rect <-> Circle (OBB 충돌 처리 방식)

      b.3.a. Rect의 가로축, 세로축을 이용한 사영값 비교

      b.3.b. Rect의 꼭지점과 Circle 중점 사이의 거리 / radius 비교(제곱값(Sqr)으로 비교)

더보기
#pragma once

class CircleCollider : public Collider
{
public:
	float radius;
	bool isObb;

public:
	CircleCollider(float radius);
	CircleCollider(float radius, Transform* target);
	virtual ~CircleCollider();
	//
	virtual void Update() override;
	virtual void CreateData() override;
	// Collider을(를) 통해 상속됨
	// Circle <-> Point
	virtual bool IsCollision(Vector2 position) override;
	// overlap 사용x
	virtual bool IsCollision(IN RectCollider* rect, OUT Vector2* overlap = nullptr) override;
	// Circle <-> Circle
	virtual bool IsCollision(CircleCollider* circle) override;
	// Circle <-> Collider(Rect or Circle)
	virtual bool IsCollision(IN Collider* collider, OUT Vector2* overlap = nullptr);
	//
	bool OBB(class RectCollider* rect);
	//
	float SeparateAxis(Vector2 separate, Vector2 e1, Vector2 e2);
	ObbDesc GetObb();
	//
	float Left()	{ return pos.x - radius + offset.x; }
	float Right()	{ return pos.x + radius + offset.x; }
	float Top()		{ return pos.y + radius + offset.y; }
	float Bottom()	{ return pos.y - radius + offset.y; }
};
//////////////////////////////////////////////////////////////////////////////////////////
#include "Framework.h"

CircleCollider::CircleCollider(float radius)
    :radius(radius), isObb(true)
{
	CreateData();
}

CircleCollider::CircleCollider(float radius, Transform* target)
	: radius(radius), isObb(true), Collider(target)
{
	CreateData();
}

CircleCollider::~CircleCollider()
{
}

void CircleCollider::Update()
{
	Collider::Update();
}

void CircleCollider::CreateData()
{
	type = Type::CIRCLE;

 	// 원의 중심 + 12시 방향 반지름 위치에서 시작
	Vector2 startPos = { offset.x, radius + offset.y }; // 시작점
	vertices.emplace_back(startPos.x, startPos.y);
    //
	Vector2 vertex = startPos;
	double angle = PI / 18.0;
	int count = 2 * PI / angle;
	for (int i = 0; i < count; ++i)
	{
		vertex.Rotate(angle);
		vertices.emplace_back(vertex.x, vertex.y);
	} // 마지막점이 시작점과 동일한 위치에 생성

	Collider::CreateData();
}

bool CircleCollider::IsCollision(Vector2 position)
{
	Vector2 v = position - pos;
	float dist = v.Length();
	if (dist < radius)
		return true;

	return false;
}

bool CircleCollider::IsCollision(IN RectCollider* rect, OUT Vector2* overlap)
{ // Circle class에서 구현하고, Rect class에서 호출시 Circle class로 접근하여 실행
	return OBB(rect);
}

bool CircleCollider::IsCollision(CircleCollider* circle)
{
	float dist = (circle->pos - pos).Length();
	if (dist > radius + circle->radius)
		return false;

	return true;
}

bool CircleCollider::IsCollision(IN Collider* collider, OUT Vector2* overlap)
{
	return Collider::IsCollision(collider, overlap);
}

bool CircleCollider::OBB(RectCollider* rect)
{
	ObbDesc obb = rect->GetObb();
	//
	Vector2 nea1 = obb.direction[0];
	Vector2 ea1 = nea1 * obb.length[0];
	Vector2 nea2 = obb.direction[1];
	Vector2 ea2 = nea2 * obb.length[1];
	//
	Vector2 distance = pos - obb.position;
	//
	float lengthA{}, lengthB{}, length{};
	// 1. sep Axis : nea1
	lengthA = radius;
	lengthB = SeparateAxis(nea1, ea1, ea2); // b
	length = abs(distance.Dot(nea1)); // d
	if (length > lengthA + lengthB)
		return false;
	// 2. sep Axis : nea2
	lengthA = radius;
	lengthB = SeparateAxis(nea2, ea1, ea2);
	length = abs(distance.Dot(nea2));
	if (length > lengthA + lengthB)
		return false;

	// 3. Rect's center to vertex dist(Circle <-> Point(rect's 4 vertex)
	float corner_Width = abs(distance.Dot(nea1)) - obb.length[0];
	float corner_Height = abs(distance.Dot(nea2)) - obb.length[1];
	float corner_LengthSqr = (corner_Width * corner_Width) + (corner_Height * corner_Height);
	lengthB = radius * radius;
	if (corner_LengthSqr > lengthB)
		return false;

	//
	return true;
}

float CircleCollider::SeparateAxis(Vector2 separate, Vector2 e1, Vector2 e2)
{
	float r1 = abs(separate.Dot(e1));
	float r2 = abs(separate.Dot(e2));

	return (r1 + r2);
}

CircleCollider::ObbDesc CircleCollider::GetObb()
{
	ObbDesc obbDesc{};
	obbDesc.position = pos;
	//
	obbDesc.length[0] = radius;
	obbDesc.length[1] = radius;
	//
	Float4x4 world{};
	XMStoreFloat4x4(&world, matrix);
	obbDesc.direction[0] = { 1.0f, 0 }; // Right vector
	obbDesc.direction[0].Normalize();
	obbDesc.direction[1] = { 0, 1.0f }; // Up vector
	obbDesc.direction[1].Normalize();
	//
	return obbDesc;
}

 

3. TestScene

  a. RectCollider*, CircleCollider*를 업캐스팅, vector<Collider*>로

일관적인 객체 처리 ( Update, Render, Collision)

 

더보기
#pragma once

class CollisionTest : public Scene
{
public:
	vector<Collider*> colliders;
	//
	CircleCollider* circle;
	CircleCollider* circle2;
	RectCollider* rect;
	RectCollider* rect2;
	//
public:
	CollisionTest();
	~CollisionTest();

private:
	// Scene을(를) 통해 상속됨
	virtual void Update() override;
	virtual void Render() override;
	virtual void PostRender() override;
	//
	void Collision();
	void CollisionMouse();
	//
	void InitTest();
	void DeleteTest();
	//
	void CollisionTest();
	//
	void CircleMove();
	void RectRotate();
};
//////////////////////////////////////////////////////////////////////////////////////////
#include "Framework.h"
#include "CollisionTest.h"

CollisionTest::CollisionTest()
	:colliders{}
{
	InitTest();
}

CollisionTest::~CollisionTest()
{
	DeleteTest();
}

void CollisionTest::Update()
{
	Collision();
	//
	CircleMove();
	RectRotate();
	//
	circle->Update();
	circle2->Update();
	rect->Update();
	rect2->Update();
}

void CollisionTest::Render()
{
	circle->Render();
	circle2->Render();
	rect->Render();
	rect2->Render();
}

void CollisionTest::PostRender()
{
}

void CollisionTest::Collision()
{
	for (auto collider : colliders)
	{
		Vector2* overlap{};
		if(circle->IsCollision(collider, overlap))
			collider->SetColor({ 1.0f, 0, 0, 1.0f });
		else
			collider->SetColor({ 0, 1.0f, 0, 1.0f });
	}
}

void CollisionTest::CollisionMouse()
{
	for (auto collider : colliders)
	{
		if (collider->IsCollision(MOUSE->GetMousePos()))
			collider->SetColor({ 1.0f, 0, 0, 1.0f });
		else
			collider->SetColor({ 0, 1.0f, 0, 1.0f });
	}
}

void CollisionTest::InitTest()
{
	// Player 역할을 하는 circle은 colliders에 추가하지 않는다
	circle = new CircleCollider(80.0f);
	circle->pos = { (float)WinWidth * 0.5f, (float)WinHeight * 0.2f };
	//
	circle2 = new CircleCollider(80.0f);
	circle2->pos = { (float)WinWidth * 0.2f, (float)WinHeight * 0.8f };
	circle2->Update();
	colliders.emplace_back(circle2);
	//
	rect = new RectCollider({80.0f,40.0f});
	rect->pos = { (float)WinWidth * 0.5f, (float)WinHeight * 0.8f };
	rect->angle += PI / 3.0f;
	rect->Update();
	colliders.emplace_back(rect);
	//
	rect2 = new RectCollider({80.0f,40.0f});
	rect2->pos = { (float)WinWidth * 0.8f, (float)WinHeight * 0.8f };
	rect2->Update();
	colliders.emplace_back(rect2);
}

void CollisionTest::DeleteTest()
{
	delete circle;
	//
	for (auto collider : colliders)
		delete collider;
	colliders.clear();
}

void CollisionTest::CircleMove()
{
	if (KeyPress(VK_RIGHT))
		circle->pos.x += DELTA * 300.0f;
	else if (KeyPress(VK_LEFT))
		circle->pos.x -= DELTA * 300.0f;
	if (KeyPress(VK_UP))
		circle->pos.y += DELTA * 300.0f;
	else if (KeyPress(VK_DOWN))
		circle->pos.y -= DELTA * 300.0f;

}

void CollisionTest::RectRotate()
{
	if (KeyPress('W'))
		rect->angle += DELTA;
	else if (KeyPress('S'))
		rect->angle -= DELTA;
}