DirectX11 Tutorial 13: Direct Input

강좌번역/DirectX 11 2013. 3. 25. 17:55 by 빠재

원문: http://rastertek.com/dx11tut13.html




이 튜토리얼은 DirectX 11의 Direct input(다이렉트인풋)을 다룹니다. 역시나 소스코드는 이전 튜토리얼에서 이어집니다.


Direct input은 DirectX API가 제공하는 고속의 입력 함수의 모음입니다. Direct input은 DirectX 11에서도 그 내용이 바뀌지 않았으며 현재 버전은 8입니다. 하지만 Direct input은 애초에 잘 만들어져 있기 때문에 더 이상 업데이트를 할 필요가 없나 봅니다(Direct Sound도 비슷합니다). Direct input은 일반적인 윈도우의 입력 시스템의 비해 믿을 수 없을 정도로 빠릅니다. 입력에 대한 빠른 응답시간을 요구하는 어플리케이션이라면 Direct input을 사용해야 합니다.


이 튜토리얼에서는 키보드와 마우스 장치에 대한 Direct input의 구현을 다룰 것입니다. 또한 TextClass를 사용하여 현재 마우스 포인터의 위치를 표시하게 할 것입니다. 이전 튜토리얼에 InputClass가 있기 때문에 전에 사용했던 기본 입력 대신 Direct input을 사용하도록 바꿀 것입니다.





Inputclass.h


////////////////////////////////////////////////////////////////////////////////
// Filename: inputclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _INPUTCLASS_H_
#define _INPUTCLASS_H_



Direct input을 사용하는 헤더에 그 버전을 define으로 선언합니다. 그렇지 않으면 컴파일러는 기본값인 버전 8로 간다는 메세지를 만들어낼 것입니다.


///////////////////////////////
// PRE-PROCESSING DIRECTIVES //
///////////////////////////////
#define DIRECTINPUT_VERSION 0x0800



Direct input이 동작하기 위해서는 다음 두 라이브러리가 링크되어야 합니다.


/////////////
// LINKING //
/////////////
#pragma comment(lib, "dinput8.lib")
#pragma comment(lib, "dxguid.lib")



Direct input에 필요한 헤더파일입니다.


//////////////
// INCLUDES //
//////////////
#include <dinput.h>


////////////////////////////////////////////////////////////////////////////////
// Class name: InputClass
////////////////////////////////////////////////////////////////////////////////
class InputClass
{
public:
	InputClass();
	InputClass(const InputClass&);
	~InputClass();

	bool Initialize(HINSTANCE, HWND, int, int);
	void Shutdown();
	bool Frame();

	bool IsEscapePressed();
	void GetMouseLocation(int&, int&);

private:
	bool ReadKeyboard();
	bool ReadMouse();
	void ProcessInput();

private:



처음 세 전용 변수들은 각각 Direct input, 키보드 장치, 마우스 장치에 대한 인터페이스입니다.


	IDirectInput8* m_directInput;
	IDirectInputDevice8* m_keyboard;
	IDirectInputDevice8* m_mouse;



다음 두 변수들은 현재 키보드와 마우스의 상태를 기록하는 데 사용됩니다.


	unsigned char m_keyboardState[256];
	DIMOUSESTATE m_mouseState;

	int m_screenWidth, m_screenHeight;
	int m_mouseX, m_mouseY;
};

#endif






Inputclass.cpp



////////////////////////////////////////////////////////////////////////////////
// Filename: inputclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "inputclass.h"



클래스 생성자에서는 Direct input의 인터페이스 변수들을 null로 설정합니다.


InputClass::InputClass()
{
	m_directInput = 0;
	m_keyboard = 0;
	m_mouse = 0;
}


InputClass::InputClass(const InputClass& other)
{
}


InputClass::~InputClass()
{
}


bool InputClass::Initialize(HINSTANCE hinstance, HWND hwnd, int screenWidth, int screenHeight)
{
	HRESULT result;


	// Store the screen size which will be used for positioning the mouse cursor.
	m_screenWidth = screenWidth;
	m_screenHeight = screenHeight;

	// Initialize the location of the mouse on the screen.
	m_mouseX = 0;
	m_mouseY = 0;



이 함수를 통해 Direct input의 인터페이스를 초기화합니다. 일단 Direct input 객체를 얻게 되면 다른 입력 장치들을 초기화할 수 있습니다.


	// Initialize the main direct input interface.
	result = DirectInput8Create(hinstance, DIRECTINPUT_VERSION, IID_IDirectInput8, (void**)&m_directInput, NULL);
	if(FAILED(result))
	{
		return false;
	}



첫번째로 초기화하는 입력 장치는 키보드입니다.


	// Initialize the direct input interface for the keyboard.
	result = m_directInput->CreateDevice(GUID_SysKeyboard, &m_keyboard, NULL);
	if(FAILED(result))
	{
		return false;
	}

	// Set the data format.  In this case since it is a keyboard we can use the predefined data format.
	result = m_keyboard->SetDataFormat(&c_dfDIKeyboard);
	if(FAILED(result))
	{
		return false;
	}



키보드의 협력 레벨(cooperative level)을 정하는 것은 이 장치가 무엇을 하는지, 그리고 어떻게 사용될 것인지를 결정하므로 중요합니다. 여기서는 DISCL_EXCLUSIVE 플래그로 다른 프로그램들과 공유하지 않는다고 알려줍니다(배제 상태). 이렇게 하면 오직 이 어플리케이션에서만 이 입력을 감지할 수 있게 됩니다. 하지만 만일 다른 어플리케이션에서도 키보드의 입력에 접근하게 하고 싶다면 DISCL_NONEXCLUSIVE를 사용하여 그렇게 할 수 있습니다(비배제 상태). 그러면 print screen key로 다시 스크린샷을 찍을 수 있게 되고 마찬가지로 다른 어플리케이션들도 키보드로 제어할 수 있게 됩니다. 기억해야 할 것은 비배제 상태이고 풀스크린이 아니라면 장치가 다른 윈도우로 포커스가 이동했는지, 그리고 다시 포커스를 얻어서 장치를 사용할 수 있게 되었는지 확인해야 한다는 것입니다. 이런 포커스를 잃어버리는 경우는 대개 다른 윈도우가 메인 윈도우가 되어 포커스를 얻었다던가 아니면 우리 어플리케이션의 윈도우가 최소화되는 경우 발생합니다.


	// Set the cooperative level of the keyboard to not share with other programs.
	result = m_keyboard->SetCooperativeLevel(hwnd, DISCL_FOREGROUND | DISCL_EXCLUSIVE);
	if(FAILED(result))
	{
		return false;
	}



키보드가 세팅되면 Acquire 함수를 호출하여 이 포인터로 키보드에 대한 접근을 취득합니다.


	// Now acquire the keyboard.
	result = m_keyboard->Acquire();
	if(FAILED(result))
	{
		return false;
	}



다음 입력 장치는 마우스입니다.


	// Initialize the direct input interface for the mouse.
	result = m_directInput->CreateDevice(GUID_SysMouse, &m_mouse, NULL);
	if(FAILED(result))
	{
		return false;
	}

	// Set the data format for the mouse using the pre-defined mouse data format.
	result = m_mouse->SetDataFormat(&c_dfDIMouse);
	if(FAILED(result))
	{
		return false;
	}


마우스에 대해서는 비배제 상태를 설정합니다. 따라서 매번 마우스가 포커스를 잃었는지 다시 찾았는지 확인해야 합니다.


	// Set the cooperative level of the mouse to share with other programs.
	result = m_mouse->SetCooperativeLevel(hwnd, DISCL_FOREGROUND | DISCL_NONEXCLUSIVE);
	if(FAILED(result))
	{
		return false;
	}



마우스가 설정되면 Acquire 함수를 사용하여 장치를 사용할 수 있도록 합니다.


	// Acquire the mouse.
	result = m_mouse->Acquire();
	if(FAILED(result))
	{
		return false;
	}

	return true;
}




Shutdown 함수는 두 장치와 direct input에 대한 인터페이스를 해제합니다. 이 절차는 항상 Unacquire->Release 순으로 이루어집니다.


void InputClass::Shutdown()
{
	// Release the mouse.
	if(m_mouse)
	{
		m_mouse->Unacquire();
		m_mouse->Release();
		m_mouse = 0;
	}

	// Release the keyboard.
	if(m_keyboard)
	{
		m_keyboard->Unacquire();
		m_keyboard->Release();
		m_keyboard = 0;
	}

	// Release the main interface to direct input.
	if(m_directInput)
	{
		m_directInput->Release();
		m_directInput = 0;
	}

	return;
}




InputClass 클래스의 Frame 함수는 장치의 현재 상태를 읽어 앞서 만든 상태 버퍼에 기록합니다. 각 장치의 상태를 읽은 뒤 변경사항을 처리하게 합니다.


bool InputClass::Frame()
{
	bool result;


	// Read the current state of the keyboard.
	result = ReadKeyboard();
	if(!result)
	{
		return false;
	}

	// Read the current state of the mouse.
	result = ReadMouse();
	if(!result)
	{
		return false;
	}

	// Process the changes in the mouse and keyboard.
	ProcessInput();

	return true;
}



ReadKeyboard 함수는 키보드의 상태를 m_keyboardState 변수에 저장합니다. 이 상태 변수는 모든 키에 대해 현재 눌렸는지 그렇지 않은지를 보여줍니다. 만약 키보드 상태를 읽는 데 실패한다면 다음 다섯 가지 요인들 중의 하나입니다. 여기서 다룰 두 요인은 키보드가 포커스를 잃거나 취득 불가 상태인 경우입니다. 이런 경우라면 매 프레임마다 제어권을 돌려받을 때까지 Acquire 함수를  호출해야 합니다. 윈도우가 최소화된 경우 Acquire 함수가 실패하게 됩니다. 하지만 다시 되돌아온 경우 Acquire 함수는 성공하고 다시 키보드 상태를 읽을 수 있게 됩니다. 다른 에러 타입들은 이 튜토리얼에서 다루지 않을 것입니다.


bool InputClass::ReadKeyboard()
{
	HRESULT result;


	// Read the keyboard device.
	result = m_keyboard->GetDeviceState(sizeof(m_keyboardState), (LPVOID)&m_keyboardState);
	if(FAILED(result))
	{
		// If the keyboard lost focus or was not acquired then try to get control back.
		if((result == DIERR_INPUTLOST) || (result == DIERR_NOTACQUIRED))
		{
			m_keyboard->Acquire();
		}
		else
		{
			return false;
		}
	}
		
	return true;
}



ReadMouse 함수는 키보드의 상태를 읽는 ReadKeyboard 함수처럼 마우스의 상태를 읽습니다. 하지만 마우스의 상태는 이전 프레임과 달라진 위치(변위)만을 저장합니다. 예를 들어 마우스를 5단위만큼 오른쪽으로 이동시켰다면 화면상의 위치를 알려주지는 않습니다. 하지만 변위(delta) 정보는 알아낼 수 있기 때문에 이를 이용하여 마우스의 위치를 자체적으로 관리할 수 있습니다.


bool InputClass::ReadMouse()
{
	HRESULT result;


	// Read the mouse device.
	result = m_mouse->GetDeviceState(sizeof(DIMOUSESTATE), (LPVOID)&m_mouseState);
	if(FAILED(result))
	{
		// If the mouse lost focus or was not acquired then try to get control back.
		if((result == DIERR_INPUTLOST) || (result == DIERR_NOTACQUIRED))
		{
			m_mouse->Acquire();
		}
		else
		{
			return false;
		}
	}

	return true;
}



ProcessInput 함수는 이전 프레임에서의 변경사항이 실제로 적용되는 곳입니다. 이 튜토리얼에서는 단순히 윈도우에서 마우스의 위치를 잡는 것처럼 현재 마우스의 위치를 갱신하도록 할 것입니다. 그렇게 하기 위해서 m_mouseX, m_mouseY 변수를 사용하여 마우스의 위치를 기록합니다. 이렇게 하여 유저가 마우스를 움직여도 그 위치를 계속 따라갑니다.


주의해야 할 것은 마우스가 화면 바깥으로 나가지 않게 해야 한다는 것입니다. 심지어 유저가 마우스를 0의 위치에서 계속 왼쪽으로 움직인다 하더라도 다시 오른쪽으로 가기 전까지는 마우스의 위치를 계속 0으로 고정시켜야 합니다.


void InputClass::ProcessInput()
{
	// Update the location of the mouse cursor based on the change of the mouse location during the frame.
	m_mouseX += m_mouseState.lX;
	m_mouseY += m_mouseState.lY;

	// Ensure the mouse location doesn't exceed the screen width or height.
	if(m_mouseX < 0)  { m_mouseX = 0; }
	if(m_mouseY < 0)  { m_mouseY = 0; }
	
	if(m_mouseX > m_screenWidth)  { m_mouseX = m_screenWidth; }
	if(m_mouseY > m_screenHeight) { m_mouseY = m_screenHeight; }
	
	return;
}



IsEscapePressed라는 함수를 추가하였습니다. 이 함수를 통해 어떻게 특정 키가 눌렸는지 확인할 수 있는 방법을 알 수 있을 것입니다. 또 어플리케이션에서 필요한 다른 키들을 확인하는 함수를 만들 수도 있습니다.


bool InputClass::IsEscapePressed()
{
	// Do a bitwise and on the keyboard state to check if the escape key is currently being pressed.
	if(m_keyboardState[DIK_ESCAPE] & 0x80)
	{
		return true;
	}

	return false;
}



GetMouseLocation 함수는 현재 마우스의 위치를 알려주는 도우미 함수입니다. GraphicsClass는 이 정보를 통해 TextClass에게 마우스의 x와 y좌표를 그리도록 합니다.


void InputClass::GetMouseLocation(int& mouseX, int& mouseY)
{
	mouseX = m_mouseX;
	mouseY = m_mouseY;
	return;
}






Systemclass.cpp


윈도우 입력 시스템을 없애고 Direct input을 사용하기 때문에 바뀐 함수들을 위주로 설명하겠습니다.


////////////////////////////////////////////////////////////////////////////////
// Filename: systemclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "systemclass.h"


bool SystemClass::Initialize()
{
	int screenWidth, screenHeight;
	bool result;


	// Initialize the width and height of the screen to zero before sending the variables into the function.
	screenWidth = 0;
	screenHeight = 0;

	// Initialize the windows api.
	InitializeWindows(screenWidth, screenHeight);

	// Create the input object.  This object will be used to handle reading the keyboard input from the user.
	m_Input = new InputClass;
	if(!m_Input)
	{
		return false;
	}



입력 객체의 초기화는 이제 윈도우의 핸들과, 인스턴스에 대한 핸들 그리고 화면의 크기를 인자로 받는 것으로 바뀌었습니다. 또한 Direct input을 성공적으로 기동시켰는지도 확인하는 boolean 변수를 반환합니다.


	// Initialize the input object.
	result = m_Input->Initialize(m_hinstance, m_hwnd, screenWidth, screenHeight);
	if(!result)
	{
		MessageBox(m_hwnd, L"Could not initialize the input object.", L"Error", MB_OK);
		return false;
	}

	// Create the graphics object.  This object will handle rendering all the graphics for this application.
	m_Graphics = new GraphicsClass;
	if(!m_Graphics)
	{
		return false;
	}

	// Initialize the graphics object.
	result = m_Graphics->Initialize(screenWidth, screenHeight, m_hwnd);
	if(!result)
	{
		return false;
	}
	
	return true;
}


void SystemClass::Shutdown()
{
	// Release the graphics object.
	if(m_Graphics)
	{
		m_Graphics->Shutdown();
		delete m_Graphics;
		m_Graphics = 0;
	}



입력 객체의 해제는 delete를 수행하기 전에 Shutdown 함수를 호출하도록 바뀌었습니다.


	// Release the input object.
	if(m_Input)
	{
		m_Input->Shutdown();
		delete m_Input;
		m_Input = 0;
	}

	// Shutdown the window.
	ShutdownWindows();
	
	return;
}


void SystemClass::Run()
{
	MSG msg;
	bool done, result;


	// Initialize the message structure.
	ZeroMemory(&msg, sizeof(MSG));
	
	// Loop until there is a quit message from the window or the user.
	done = false;
	while(!done)
	{
		// Handle the windows messages.
		if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
		{
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}

		// If windows signals to end the application then exit out.
		if(msg.message == WM_QUIT)
		{
			done = true;
		}
		else
		{
			// Otherwise do the frame processing.  If frame processing fails then exit.
			result = Frame();
			if(!result)
			{
				MessageBox(m_hwnd, L"Frame Processing Failed", L"Error", MB_OK);
				done = true;
			}
		}



Run 함수에서 esc키를 확인하는 함수는 InputClass의 도우미 함수의 결과값을 확인하는 것으로 살짝 바뀌었습니다.


		// Check if the user pressed escape and wants to quit.
		if(m_Input->IsEscapePressed() == true)
		{
			done = true;
		}
	}

	return;
}


bool SystemClass::Frame()
{
	bool result;
	int mouseX, mouseY;



Frame 함수 중에는 입력 객체의 Frame 함수를 호출하여 키보드와 마우스의 상태를 갱신하도록 합니다. 이 함수는 실패할 수 있기 때문에 반환값을 체크해야 합니다.


	// Do the input frame processing.
	result = m_Input->Frame();
	if(!result)
	{
		return false;
	}



입력 객체가 잘 갱신되었다면 GraphicsClass에 마우스의 위치가 바뀐것이 갱신되도록 합니다.


	// Get the location of the mouse from the input object,
	m_Input->GetMouseLocation(mouseX, mouseY);

	// Do the frame processing for the graphics object.
	result = m_Graphics->Frame(mouseX, mouseY);
	if(!result)
	{
		return false;
	}

	// Finally render the graphics to the screen.
	result = m_Graphics->Render();
	if(!result)
	{
		return false;
	}

	return true;
}



MessageHandler 함수를 통해 윈도우 키보드를 읽는 부분을 제거했습니다. 이제는 Direct input을 통해 모든 것을 관리합니다.


LRESULT CALLBACK SystemClass::MessageHandler(HWND hwnd, UINT umsg, WPARAM wparam, LPARAM lparam)
{
	return DefWindowProc(hwnd, umsg, wparam, lparam);
}






Graphicsclass.h


////////////////////////////////////////////////////////////////////////////////
// Filename: graphicsclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _GRAPHICSCLASS_H_
#define _GRAPHICSCLASS_H_


/////////////
// GLOBALS //
/////////////
const bool FULL_SCREEN = true;
const bool VSYNC_ENABLED = true;
const float SCREEN_DEPTH = 1000.0f;
const float SCREEN_NEAR = 0.1f;


///////////////////////
// MY CLASS INCLUDES //
///////////////////////
#include "d3dclass.h"
#include "cameraclass.h"
#include "textclass.h"


////////////////////////////////////////////////////////////////////////////////
// Class name: GraphicsClass
////////////////////////////////////////////////////////////////////////////////
class GraphicsClass
{
public:
	GraphicsClass();
	GraphicsClass(const GraphicsClass&);
	~GraphicsClass();

	bool Initialize(int, int, HWND);
	void Shutdown();



Frame 함수는 매 프레임마다 마우스의 위치에 해당하는 두 인자를 받습니다.


	bool Frame(int, int);
	bool Render();

private:
	D3DClass* m_D3D;
	CameraClass* m_Camera;
	TextClass* m_Text;
};

#endif






Graphicsclass.cpp


이전 튜토리얼과 달라진 것을 설명하겠습니다.


////////////////////////////////////////////////////////////////////////////////
// Filename: graphicsclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "graphicsclass.h"



Frame 함수는 마우스의 x,y좌표를 받아 TextClass를 통해 갱신된 좌표를 화면에 문자열로 찍어낼 수 있도록 합니다.


bool GraphicsClass::Frame(int mouseX, int mouseY)
{
	bool result;


	// Set the location of the mouse.
	result = m_Text->SetMousePosition(mouseX, mouseY, m_D3D->GetDeviceContext());
	if(!result)
	{
		return false;
	}

	// Set the position of the camera.
	m_Camera->SetPosition(0.0f, 0.0f, -10.0f);

	return true;
}






Textclass.h


////////////////////////////////////////////////////////////////////////////////
// Filename: textclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _TEXTCLASS_H_
#define _TEXTCLASS_H_

///////////////////////
// MY CLASS INCLUDES //
///////////////////////
#include "fontclass.h"
#include "fontshaderclass.h"


////////////////////////////////////////////////////////////////////////////////
// Class name: TextClass
////////////////////////////////////////////////////////////////////////////////
class TextClass
{
private:
	struct SentenceType
	{
		ID3D11Buffer *vertexBuffer, *indexBuffer;
		int vertexCount, indexCount, maxLength;
		float red, green, blue;
	};

	struct VertexType
	{
		D3DXVECTOR3 position;
		D3DXVECTOR2 texture;
	};

public:
	TextClass();
	TextClass(const TextClass&);
	~TextClass();

	bool Initialize(ID3D11Device*, ID3D11DeviceContext*, HWND, int, int, D3DXMATRIX);
	void Shutdown();
	bool Render(ID3D11DeviceContext*, D3DXMATRIX, D3DXMATRIX);



TextClass에 마우스의 위치를 설정하는 새 함수를 선언합니다.


	bool SetMousePosition(int, int, ID3D11DeviceContext*);

private:
	bool InitializeSentence(SentenceType**, int, ID3D11Device*);
	bool UpdateSentence(SentenceType*, char*, int, int, float, float, float, ID3D11DeviceContext*);
	void ReleaseSentence(SentenceType**);
	bool RenderSentence(ID3D11DeviceContext*, SentenceType*, D3DXMATRIX, D3DXMATRIX);

private:
	FontClass* m_Font;
	FontShaderClass* m_FontShader;
	int m_screenWidth, m_screenHeight;
	D3DXMATRIX m_baseViewMatrix;
	SentenceType* m_sentence1;
	SentenceType* m_sentence2;
};

#endif






Textclass.cpp


이전 튜토리얼과 달라진 함수들만 설명하겠습니다.


///////////////////////////////////////////////////////////////////////////////
// Filename: textclass.cpp
///////////////////////////////////////////////////////////////////////////////
#include "textclass.h"



TextClass에 마우스의 x,y좌표를 두 문자열로 변환하고 각 문장들을 갱신하여 그 위치가 화면에 그려질 수 있게 합니다.


bool TextClass::SetMousePosition(int mouseX, int mouseY, ID3D11DeviceContext* deviceContext)
{
	char tempString[16];
	char mouseString[16];
	bool result;


	// Convert the mouseX integer to string format.
	_itoa_s(mouseX, tempString, 10);

	// Setup the mouseX string.
	strcpy_s(mouseString, "Mouse X: ");
	strcat_s(mouseString, tempString);

	// Update the sentence vertex buffer with the new string information.
	result = UpdateSentence(m_sentence1, mouseString, 20, 20, 1.0f, 1.0f, 1.0f, deviceContext);
	if(!result)
	{
		return false;
	}

	// Convert the mouseY integer to string format.
	_itoa_s(mouseY, tempString, 10);

	// Setup the mouseY string.
	strcpy_s(mouseString, "Mouse Y: ");
	strcat_s(mouseString, tempString);

	// Update the sentence vertex buffer with the new string information.
	result = UpdateSentence(m_sentence2, mouseString, 20, 40, 1.0f, 1.0f, 1.0f, deviceContext);
	if(!result)
	{
		return false;
	}

	return true;
}






마치면서


보신 것처럼 DirectX 11에서 direct input을 설정하는 것은 정말 간단하지만 입력 장치에 고속의 접근을 가능하게 해 줍니다.










연습 문제


1. 프로그램을 다시 컴파일하고 실행해 보십시오. 마우스를 화면 내에서 이동하고 위치를 표시하는 글자가 바뀌는 것을 확인해 보십시오.


2. 2D 렌더링 튜토리얼의 내용을 활용하여 마우스 이동이 반영되는 마우스 포인터를 만들어 보십시오.


3. 키보드 버퍼를 읽어 화면에 어떤 글자가 타이핑되는지 표시하는 함수를 만들어 보십시오.









소스 코드


Visual Studio 2008 프로젝트: dx11tut13.zip


소스 코드: dx11src13.zip


실행 파일: dx11exe13.zip

Nav
1" /> ···" /> 50" /> 51" /> 52" /> 53" /> 54" /> 55" /> 56" /> ···" /> 76" />