Files
computer-graphics/HouseLightingModel/TestView.cpp
T
2024-11-14 10:48:14 +08:00

501 lines
14 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// TestView.cpp : implementation of the CTestView class
//
#include "stdafx.h"
#include "Test.h"
#include "TestDoc.h"
#include "TestView.h"
#include "math.h"
#define Round(d) int(floor(d + 0.5)) // 四舍五入宏定义
#define PI 3.1415926
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
/////////////////////////////////////////////////////////////////////////////
// CTestView
IMPLEMENT_DYNCREATE(CTestView, CView)
BEGIN_MESSAGE_MAP(CTestView, CView)
//{{AFX_MSG_MAP(CTestView)
ON_WM_KEYDOWN()
ON_WM_TIMER()
ON_WM_ERASEBKGND()
ON_COMMAND(IDM_PLAY, OnPlay)
ON_UPDATE_COMMAND_UI(IDM_PLAY, OnUpdatePlay)
//}}AFX_MSG_MAP
// Standard printing commands
ON_COMMAND(ID_FILE_PRINT, CView::OnFilePrint)
ON_COMMAND(ID_FILE_PRINT_DIRECT, CView::OnFilePrint)
ON_COMMAND(ID_FILE_PRINT_PREVIEW, CView::OnFilePrintPreview)
END_MESSAGE_MAP()
/////////////////////////////////////////////////////////////////////////////
// CTestView construction/destruction
CTestView::CTestView() {
// TODO: add construction code here
bPlay = FALSE;
R = 1000.0, d = 800.0, Phi = 70.0, Theta = 0.0; // 视点位于正前方
Near = 200.0, Far = 1200.0; // 近剪切面与远剪切面
Beta = 0.0; // 旋转角
LightNum = 1; // 光源个数
pLight = new CLighting(LightNum); // 一维光源动态数组
pLight->Light[0].SetPosition(800, 800, 800); // 设置光源位置坐标
for (int i = 0; i < LightNum; i++) {
pLight->Light[i].L_Diffuse = CRGB(1.0, 1.0, 1.0); // 光源的漫反射颜色
pLight->Light[i].L_Specular = CRGB(1.0, 1.0, 1.0); // 光源镜面高光颜色
pLight->Light[i].L_C0 = 1.0; // 常数衰减系数
pLight->Light[i].L_C1 = 0.0000001; // 线性衰减系数
pLight->Light[i].L_C2 = 0.00000001; // 二次衰减系数
pLight->Light[i].L_OnOff = TRUE; // 光源开启
}
pMaterial = new CMaterial; // 一维材质动态数组
pMaterial->SetAmbient(CRGB(0.247, 0.200, 0.075)); // 材质对环境光光的反射率
pMaterial->SetDiffuse(CRGB(0.752, 0.606, 0.226)); // 材质对漫反射光的反射率
pMaterial->SetSpecular(CRGB(1.0, 1.0, 1.0)); // 材质对镜面反射光的反射率
pMaterial->SetEmit(CRGB(0.05, 0.05, 0.002)); // 材质自身发散的颜色
pMaterial->M_n = 30.0; // 高光指数
}
CTestView::~CTestView() {
if (pLight != NULL) {
delete pLight;
pLight = NULL;
}
if (pMaterial != NULL) {
delete pMaterial;
pMaterial = NULL;
}
}
BOOL CTestView::PreCreateWindow(CREATESTRUCT &cs) {
// TODO: Modify the Window class or styles here by modifying
// the CREATESTRUCT cs
return CView::PreCreateWindow(cs);
}
/////////////////////////////////////////////////////////////////////////////
// CTestView drawing
void CTestView::OnDraw(CDC *pDC) {
CTestDoc *pDoc = GetDocument();
ASSERT_VALID(pDoc);
// TODO: add draw code for native data here
DoubleBuffer();
}
/////////////////////////////////////////////////////////////////////////////
// CTestView printing
BOOL CTestView::OnPreparePrinting(CPrintInfo *pInfo) {
// default preparation
return DoPreparePrinting(pInfo);
}
void CTestView::OnBeginPrinting(CDC * /*pDC*/, CPrintInfo * /*pInfo*/) {
// TODO: add extra initialization before printing
}
void CTestView::OnEndPrinting(CDC * /*pDC*/, CPrintInfo * /*pInfo*/) {
// TODO: add cleanup after printing
}
/////////////////////////////////////////////////////////////////////////////
// CTestView diagnostics
#ifdef _DEBUG
void CTestView::AssertValid() const {
CView::AssertValid();
}
void CTestView::Dump(CDumpContext &dc) const {
CView::Dump(dc);
}
CTestDoc *CTestView::GetDocument() // non-debug version is inline
{
ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CTestDoc)));
return (CTestDoc *)m_pDocument;
}
#endif //_DEBUG
/////////////////////////////////////////////////////////////////////////////
// CTestView message handlers
void CTestView::ReadCubeVertex() // 读入立方体顶点坐标
{
// // 顶点的三维坐标(x,y,z),立方体边长为2a
// double a = 160;
// VCube[0].x = -a;
// VCube[0].y = -a;
// VCube[0].z = -a;
// VCube[1].x = +a;
// VCube[1].y = -a;
// VCube[1].z = -a;
// VCube[2].x = +a;
// VCube[2].y = +a;
// VCube[2].z = -a;
// VCube[3].x = -a;
// VCube[3].y = +a;
// VCube[3].z = -a;
// VCube[4].x = -a;
// VCube[4].y = -a;
// VCube[4].z = +a;
// VCube[5].x = +a;
// VCube[5].y = -a;
// VCube[5].z = +a;
// VCube[6].x = +a;
// VCube[6].y = +a;
// VCube[6].z = +a;
// VCube[7].x = -a;
// VCube[7].y = +a;
// VCube[7].z = +a;
VCube[0].z = 0;
VCube[0].x = 0;
VCube[0].y = 0;
VCube[1].z = 100;
VCube[1].x = 0;
VCube[1].y = 0;
VCube[2].z = 100;
VCube[2].x = 200;
VCube[2].y = 0;
VCube[3].z = 0;
VCube[3].x = 200;
VCube[3].y = 0;
VCube[4].z = 0;
VCube[4].x = 0;
VCube[4].y = 100;
VCube[5].z = 100;
VCube[5].x = 0;
VCube[5].y = 100;
VCube[6].z = 100;
VCube[6].x = 200;
VCube[6].y = 100;
VCube[7].z = 0;
VCube[7].x = 200;
VCube[7].y = 100;
VCube[8].z = 50;
VCube[8].x = 0;
VCube[8].y = 150;
VCube[9].z = 50;
VCube[9].x = 200;
VCube[9].y = 150;
}
void CTestView::ReadCubeFace() // 读入立方体面表
{
// // 面的顶点数、面的顶点索引号与面的颜色
// FCube[0].SetNum(4);
// FCube[0].vI[0] = 4;
// FCube[0].vI[1] = 5;
// FCube[0].vI[2] = 6;
// FCube[0].vI[3] = 7;
// FCube[1].SetNum(4);
// FCube[1].vI[0] = 0;
// FCube[1].vI[1] = 3;
// FCube[1].vI[2] = 2;
// FCube[1].vI[3] = 1;
// FCube[2].SetNum(4);
// FCube[2].vI[0] = 0;
// FCube[2].vI[1] = 4;
// FCube[2].vI[2] = 7;
// FCube[2].vI[3] = 3;
// FCube[3].SetNum(4);
// FCube[3].vI[0] = 1;
// FCube[3].vI[1] = 2;
// FCube[3].vI[2] = 6;
// FCube[3].vI[3] = 5;
// FCube[4].SetNum(4);
// FCube[4].vI[0] = 2;
// FCube[4].vI[1] = 3;
// FCube[4].vI[2] = 7;
// FCube[4].vI[3] = 6;
// FCube[5].SetNum(4);
// FCube[5].vI[0] = 0;
// FCube[5].vI[1] = 1;
// FCube[5].vI[2] = 5;
// FCube[5].vI[3] = 4;
FCube[0].SetNum(4);
FCube[0].vI[0] = 1;
FCube[0].vI[1] = 2;
FCube[0].vI[2] = 6;
FCube[0].vI[3] = 5; // 前面
FCube[1].SetNum(4);
FCube[1].vI[0] = 0;
FCube[1].vI[1] = 4;
FCube[1].vI[2] = 7;
FCube[1].vI[3] = 3; // 后面
FCube[2].SetNum(5);
FCube[2].vI[0] = 0;
FCube[2].vI[1] = 1;
FCube[2].vI[2] = 5;
FCube[2].vI[3] = 8;
FCube[2].vI[4] = 4; // 左面
FCube[3].SetNum(5);
FCube[3].vI[0] = 2;
FCube[3].vI[1] = 3;
FCube[3].vI[2] = 7;
FCube[3].vI[3] = 9;
FCube[3].vI[4] = 6; // 右面
FCube[4].SetNum(4);
FCube[4].vI[0] = 4;
FCube[4].vI[1] = 8;
FCube[4].vI[2] = 9;
FCube[4].vI[3] = 7; // 顶面1
FCube[5].SetNum(4);
FCube[5].vI[0] = 0;
FCube[5].vI[1] = 3;
FCube[5].vI[2] = 2;
FCube[5].vI[3] = 1; // 底面
FCube[6].SetNum(4);
FCube[6].vI[0] = 5;
FCube[6].vI[1] = 6;
FCube[6].vI[2] = 9;
FCube[6].vI[3] = 8; // 顶面2
}
void CTestView::ReadGroundVertex() // 读入地面点表
{
int Length = 800, Width = 800, Depth = 0;
VGround[0].x = -Length;
VGround[0].y = -Depth;
VGround[0].z = -Width;
VGround[0].c = CRGB(0.5, 0.5, 0.5);
VGround[1].x = -Length;
VGround[1].y = -Depth;
VGround[1].z = Width;
VGround[1].c = CRGB(0.5, 0.5, 0.5);
VGround[2].x = Length;
VGround[2].y = -Depth;
VGround[2].z = Width;
VGround[2].c = CRGB(0.5, 0.5, 0.5);
VGround[3].x = Length;
VGround[3].y = -Depth;
VGround[3].z = -Width;
VGround[3].c = CRGB(0.5, 0.5, 0.5);
}
void CTestView::InitParameter() {
k[1] = sin(PI * Theta / 180);
k[2] = sin(PI * Phi / 180);
k[3] = cos(PI * Theta / 180);
k[4] = cos(PI * Phi / 180);
k[5] = k[2] * k[3];
k[6] = k[2] * k[1];
k[7] = k[4] * k[3];
k[8] = k[4] * k[1];
ViewPoint.x = R * k[6];
ViewPoint.y = R * k[4];
ViewPoint.z = R * k[5];
}
void CTestView::PerProject(CP3 P) {
CP3 ViewP;
ViewP.x = P.x * k[3] - P.z * k[1]; // 观察坐标系三维坐标
ViewP.y = -P.x * k[8] + P.y * k[2] - P.z * k[7];
ViewP.z = -P.x * k[6] - P.y * k[4] - P.z * k[5] + R;
ViewP.c = P.c;
ScreenP.x = d * ViewP.x / ViewP.z; // 屏幕坐标系三维坐标
ScreenP.y = Round(d * ViewP.y / ViewP.z);
ScreenP.z = Far * (1 - Near / ViewP.z) / (Far - Near);
ScreenP.c = ViewP.c;
}
void CTestView::DoubleBuffer() // 双缓冲
{
CDC *pDC = GetDC();
CRect rect; // 定义客户区
GetClientRect(&rect); // 获得客户区的大小
pDC->SetMapMode(MM_ANISOTROPIC); // pDC自定义坐标系
pDC->SetWindowExt(rect.Width(), rect.Height()); // 设置窗口范围
pDC->SetViewportExt(rect.Width(), -rect.Height()); // x轴水平向右,y轴垂直向上
pDC->SetViewportOrg(rect.Width() / 2, rect.Height() / 2); // 屏幕中心为原点
CDC MemDC; // 内存DC
CBitmap NewBitmap, *pOldBitmap; // 内存中承载图像的临时位图
MemDC.CreateCompatibleDC(pDC); // 建立与屏幕pDC兼容的MemDC
NewBitmap.CreateCompatibleBitmap(pDC, rect.Width(), rect.Height()); // 创建兼容位图
pOldBitmap = MemDC.SelectObject(&NewBitmap); // 将兼容位图选入MemDC
MemDC.SetMapMode(MM_ANISOTROPIC); // MemDC自定义坐标系
MemDC.SetWindowExt(rect.Width(), rect.Height());
MemDC.SetViewportExt(rect.Width(), -rect.Height());
MemDC.SetViewportOrg(rect.Width() / 2, rect.Height() / 2);
DrawObject(&MemDC);
pDC->BitBlt(-rect.Width() / 2, -rect.Height() / 2, rect.Width(), rect.Height(), &MemDC, -rect.Width() / 2, -rect.Height() / 2, SRCCOPY); // 将内存位图拷贝到屏幕
MemDC.SelectObject(pOldBitmap); // 恢复位图
NewBitmap.DeleteObject(); // 删除位图
ReleaseDC(pDC); // 释放DC
}
void CTestView::DrawObject(CDC *pDC) // 绘制物体
{
CZBuffer *zbuf = new CZBuffer; // 申请深度缓冲内存
zbuf->InitDeepBuffer(1500, 1000, 1000); // 初始化深度缓冲器
DrawGround(pDC);
DrawCube(pDC, zbuf);
DrawShadow(pDC, zbuf);
delete zbuf; // 释放内存
}
void CTestView::DrawCube(CDC *pDC, CZBuffer *zbuffer) // 绘制立方体
{
CPi3 Point[5]; // 面的二维顶点数组
for (int nFace = 0; nFace < 7; nFace++) // 面循环
{
CVector ViewVector(VCube[FCube[nFace].vI[0]], ViewPoint); // 面的视矢量
ViewVector = ViewVector.Normalize(); // 单位化视矢量
FCube[nFace].SetFaceNormal(VCube[FCube[nFace].vI[0]], VCube[FCube[nFace].vI[1]], VCube[FCube[nFace].vI[2]]);
FCube[nFace].fNormal.Normalize(); // 单位化法矢量
if (Dot(ViewVector, FCube[nFace].fNormal) >= 0) // 背面剔除
{
for (int nVertex = 0; nVertex < FCube[nFace].vN; nVertex++) // 顶点循环
{
PerProject(VCube[FCube[nFace].vI[nVertex]]); // 透视投影
Point[nVertex] = ScreenP;
Point[nVertex].c = pLight->Lighting(ViewPoint, VCube[FCube[nFace].vI[nVertex]], FCube[nFace].fNormal, pMaterial);
}
zbuffer->SetPoint(Point, FCube[nFace].vN); // 设置顶点
zbuffer->CreateBucket(); // 创建桶表
zbuffer->CreateEdge(); // 创建边表
zbuffer->Gouraud(pDC); // 填充四边形
zbuffer->ClearMemory();
}
}
}
void CTestView::DrawGround(CDC *pDC) // 绘制地面
{
CPi3 Point[4]; // 面的二维顶点数组
for (int nVertex = 0; nVertex < 4; nVertex++) {
PerProject(VGround[nVertex]);
Point[nVertex] = ScreenP;
}
CBrush NewBrush;
NewBrush.CreateSolidBrush(RGB(100, 100, 100));
CBrush *pOldBrush = pDC->SelectObject(&NewBrush);
pDC->BeginPath();
pDC->MoveTo(Round(Point[0].x), Point[0].y); // 绘制左侧多边形
for (int i = 1; i < 4; i++)
pDC->LineTo(Round(Point[i].x), Point[i].y);
pDC->LineTo(Round(Point[0].x), Point[0].y);
pDC->EndPath();
pDC->FillPath(); // FillPath填充路径层
pDC->SelectObject(pOldBrush);
NewBrush.DeleteObject();
}
void CTestView::DrawShadow(CDC *pDC, CZBuffer *zbuffer) // 绘制阴影
{
CPi3 Point[5]; // 面的二维顶点数组
for (int nFace = 0; nFace < 7; nFace++) // 遍历表面
{
// 光源做为视点
CVector ViewVector(VCube[FCube[nFace].vI[0]], pLight->Light[0].L_Position); // 面的视矢量
ViewVector = ViewVector.Normalize(); // 单位化视矢量
FCube[nFace].SetFaceNormal(VCube[FCube[nFace].vI[0]], VCube[FCube[nFace].vI[1]], VCube[FCube[nFace].vI[2]]);
FCube[nFace].fNormal.Normalize(); // 单位化法矢量
if (Dot(ViewVector, FCube[nFace].fNormal) < 0) // 绘制阴影
{
for (int nVertex = 0; nVertex < FCube[nFace].vN; nVertex++) // 顶点循环
{
VCube[FCube[nFace].vI[nVertex]].c = CRGB(0.2, 0.2, 0.2); // 阴影颜色
PerProject(CalculateCrossPoint(pLight->Light[0].L_Position, VCube[FCube[nFace].vI[nVertex]])); // 计算该背光面上的顶点和光线连线和地面的交点
Point[nVertex] = ScreenP;
}
zbuffer->SetPoint(Point, FCube[nFace].vN); // 设置顶点
zbuffer->CreateBucket(); // 创建桶表
zbuffer->CreateEdge(); // 创建边表
zbuffer->Gouraud(pDC); // 颜色渐变填充四边形
zbuffer->ClearMemory();
}
}
}
CP3 CTestView::CalculateCrossPoint(CP3 p0, CP3 p1) // 计算光线和地面的交点(第一个参数是光源,第二个参数是物体顶点)
{
CP3 p;
double A, B, C, D; // 平面方程Ax+By+Cz+D=0的系数
CVector V01(VGround[0], VGround[1]), V02(VGround[0], VGround[2]);
CVector VN = Cross(V01, V02);
A = VN.x;
B = VN.y;
C = VN.z;
D = -A * VGround[0].x - B * VGround[0].y - C * VGround[0].z;
double t; // 计算直线参数方程的公共系数t
t = -(A * p0.x + B * p0.y + C * p0.z + D) / (A * (p1.x - p0.x) + B * (p1.y - p0.y) + C * (p1.z - p0.z));
p.x = p0.x + t * (p1.x - p0.x); // 代入参数方程计算交点坐标
p.y = p0.y + t * (p1.y - p0.y);
p.z = p0.z + t * (p1.z - p0.z);
p.c = CRGB(p1.c.red, p1.c.green, p1.c.blue);
return p;
}
void CTestView::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) {
// TODO: Add your message handler code here and/or call default
if (!bPlay) {
switch (nChar) {
case VK_LEFT:
Beta = -5;
tran.RotateY(Beta);
break;
case VK_RIGHT:
Beta = 5;
tran.RotateY(Beta);
break;
default:
break;
}
Invalidate(FALSE);
}
CView::OnKeyDown(nChar, nRepCnt, nFlags);
}
void CTestView::OnPlay() {
// TODO: Add your command handler code here
bPlay = bPlay ? FALSE : TRUE;
if (bPlay) // 设置定时器
SetTimer(1, 150, NULL);
else
KillTimer(1);
}
void CTestView::OnTimer(UINT nIDEvent) {
// TODO: Add your message handler code here and/or call default
Beta = 5;
tran.RotateY(Beta);
Invalidate(FALSE);
CView::OnTimer(nIDEvent);
}
BOOL CTestView::OnEraseBkgnd(CDC *pDC) // 禁止背景刷新
{
// TODO: Add your message handler code here and/or call default
return TRUE;
}
void CTestView::OnUpdatePlay(CCmdUI *pCmdUI) {
// TODO: Add your command update UI handler code here
if (bPlay) {
pCmdUI->SetCheck(TRUE);
pCmdUI->SetText("停止");
}
else {
pCmdUI->SetCheck(FALSE);
pCmdUI->SetText("播放");
}
}
void CTestView::OnInitialUpdate() {
CView::OnInitialUpdate();
// TODO: Add your specialized code here and/or call the base class
ReadGroundVertex();
ReadCubeVertex();
ReadCubeFace();
tran.SetMat(VCube, 10);
InitParameter();
}