Reflect laser beams off multiple mirrors



In some science museums there are exhibits demonstrating the paths of light rays. A typical one might have a table with small walls consisting of blocks a few inches high, with each vertical face being a mirror. A small horizontal laser can be positioned anywhere on the table. Many blocks be placed on the table to reflect the light. The room can be darkened, and chalk dust can be sprinkled to see the laser beams as the reflect from mirror to mirror. Blocks can be arranged in various shapes to vary the light patterns produced.
Creating such an environment requires a laser, several mirrors, etc. Simulating the setup with software however, is much easier.

I was talking to my son about this laser table idea and he pointed me to the The NumberPhile Illumination Problem https://www.youtube.com/watch?v=xhj5er1k6GQ

I remember years ago playing with ripple tanks years ago. A tray with e.g. circular 2 inch walls was filled with water. A point source like the tip of a vertical stick was repeatedly immersed, removed from the water to cause a point source of waves. The pattern of waves could easily be seen. If the stimulant was in the center of the circle, the ripples would reach the edges simultaneously. What if the edges were an ellipse? We learned in math class that an ellipse has many interesting properties. If you stimulate one focus of the ellipse, the other waves will converge on the other.
I remember reading about real applications of the ellipse in eliminating kidney stones with an elliptical shock wave, which pulverizes the stones without surgery using a Lithotripter

Imagine drawing arbitrary shapes on the screen with your mouse or your finger, each representing an arrangement of mirrors on the laser table. Turn on the laser and watch the laser beam bounce off the mirrors. Is it possible to create a region in which the light never goes? Sure: create a closed shape like a triangle. How about a non-closed shape?

Because the laser beam is so fast, the area gets colored very quickly. To make the light beam more visible, the code varies the color of the laser beam with time so that it’s easier to see where the laser is. If not, thousands of lasers per second would quickly fill the entire screen with the same color.
Try drawing mirrors while the light is bouncing around. Even though one thread is modifying the list of mirrors, the reflections continue.

If the walls are only horizontal or vertical, then reflecting is easy: for a horizontal wall, just negate the vertical component of the velocity. It gets more complicated when the mirrors can be any angle.

Notice how the drawing can be done in a background thread, even though WPF or GDI demands UI control access on the main thread.

Notice that there are no explicit memory allocations in this C++ program: no operator new

Various places in the code do mathematical manipulations such as multiplication, additions, square root. You can see how the optimized Release build inlines a lot of these to make the more efficient. The generated instructions are vastly different if using an Enhanced Instruction Set. Project->Properties=>C/C++->Code Generation->Enabled Enhanced Instruction Set


Below is a C++ program that models the mirror table.
A Vector (with a capital “V”) is a physics vector. (A “vector” with a lower-case “v” is a std collection of a list of elements.)
A light beam is modeled as a point (from which the laser beam emanates) and a Vector indicating direction, with x and y components.
A mirror interface IMirror is defined. A mirror can:
1.    determine if a light beam intersects it (returning the Point of intersection)
2.    draw itself
3.    determine the Vector of a reflecting incoming laser beam

A MyPoint class is a Point in the x,y plane.
A CLine class is a line, consisting of 2 points and a bool indicating whether the line is infinite (like a laser beam) or is a line segment (like a finite length mirror)

Various accelerator keys are defined, such as “R” to run/stop the simulation, “S” and “F” for slower and faster to control the reflection speed, etc.


Try turning on drag mode (right-click) and control-mouse move to draw continuous curved wall mirrors.
Try creating long angled corridors to watch the light bounce around in interesting ways. (as the light gets further down a narrowing corridor, the angles change and the light reflects out without going all the way down the corridor: a kind of light valve.
Try capturing the laser light in various shapes.
Try slowing down the light rays and follow them, while creating more mirrors
Try modifying the code to have multiple lasers at a time, simulating a point source of light.
Try comparing the performance of  32 and 64 bit builds (to build 64 bit, Menu->Build->Configurations->Active Solution Platform->x64

<code>

// ReflectCpp.cpp : Defines the entry point for the application.
//

// Team Explorer->Connections->New Repository "ReflectCPP"
// DblClick the newly created Repo to get to the Team Explorer Home pane.
// Solutions->New->C++ Win32Project. Select all the defaults in the Win32 App Wizard
// add natvis: https://msdn.microsoft.com/en-us/library/jj620914.aspx?f=255&MSPPError=-2147217396

#include "stdafx.h"
#include "ReflectCpp.h"
#include "windowsx.h"
#include "vector"
#include "memory"
#include "string"
#include "atlbase.h"

using namespace std;
using namespace ATL;

#define MAX_LOADSTRING 100

// Global Variables:
HINSTANCE g_hInstance;                                // current instance
WCHAR szTitle[MAX_LOADSTRING];                  // The title bar text
WCHAR szWindowClass[MAX_LOADSTRING];            // the main window class name

												// Forward declarations of functions included in this code module:
ATOM                MyRegisterClass(HINSTANCE hInstance);
BOOL                InitInstance(HINSTANCE, int);
LRESULT CALLBACK    WndProc(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK    About(HWND, UINT, WPARAM, LPARAM);
class BounceFrame;
BounceFrame *g_pBounceFrame;

HWND g_hWnd;

//
//  FUNCTION: MyRegisterClass()
//
//  PURPOSE: Registers the window class.
//
ATOM MyRegisterClass(HINSTANCE hInstance)
{
	WNDCLASSEXW wcex;

	wcex.cbSize = sizeof(WNDCLASSEX);

	wcex.style = CS_HREDRAW | CS_VREDRAW;
	wcex.lpfnWndProc = WndProc;
	wcex.cbClsExtra = 0;
	wcex.cbWndExtra = 0;
	wcex.hInstance = hInstance;
	wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_REFLECTCPP));
	wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
	wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
	wcex.lpszMenuName = MAKEINTRESOURCEW(IDC_REFLECTCPP);
	wcex.lpszClassName = szWindowClass;
	wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));

	return RegisterClassExW(&wcex);
}

BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
	g_hInstance = hInstance; // Store instance handle in our global variable

	HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
		CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr);

	if (!hWnd)
	{
		return FALSE;
	}

	ShowWindow(hWnd, nCmdShow);
	g_hWnd = hWnd;
	UpdateWindow(hWnd);
	return TRUE;
}

template <typename T> int sgn(T val) {
	return (T(0) < val) - (val < T(0));
}

typedef double MyPrecision;
const double epsilon = .00001;
const int SpeedMult = 1000;
class MyPoint
{
public:
	MyPoint()
	{
		this->X = 0;
		this->Y = 0;
	}
	MyPoint(MyPrecision x, MyPrecision y)
	{
		this->X = x;
		this->Y = y;
	}
	MyPoint(POINTS pt)
	{
		this->X = pt.x;
		this->Y = pt.y;
	}
	// assignment
	MyPoint & operator=(const MyPoint other)
	{
		this->X = other.X;
		this->Y = other.Y;
		return *this;
	}
	// equality
	bool operator ==(const MyPoint other)
	{
		if (abs(this->X - other.X) < epsilon && abs(this->Y - other.Y) < epsilon)
		{
			return true;
		}
		return false;
	}
	bool operator !=(const MyPoint other)
	{
		return !(*this == other);
	}
	bool IsNull()
	{
		return X == 0 && Y == 0;
	}
	void Clear()
	{
		X = Y = 0;
	}
	double DistanceFromPoint(MyPoint other)
	{
		auto deltax = this->X - other.X;
		auto deltay = this->Y - other.Y;
		auto d = sqrt(deltax * deltax + deltay * deltay);
		return d;
	}
	MyPrecision X;
	MyPrecision Y;
};

typedef MyPoint Vector; // as in physics vector with speed and direction

interface IMirror
{
	virtual MyPoint IntersectingPoint(MyPoint ptcLight, Vector vecLight) = 0;
	virtual Vector Reflect(MyPoint ptLight, Vector vecLight, MyPoint ptIntersect) = 0;
	virtual void Draw(HDC hDC) = 0;
	virtual ~IMirror() {};
};

class CLine : public IMirror
{
public:
	CLine(MyPoint &pt0, MyPoint &pt1, bool IsLimitedInLength = true)
	{
		this->pt0 = pt0;
		this->pt1 = pt1;
		this->IsLimitedInLength = IsLimitedInLength;
	}
	MyPrecision LineLength()
	{
		return sqrt(LineLengthSquared());
	}
	MyPrecision LineLengthSquared()
	{
		return deltaX()*deltaX() + deltaY()*deltaY();
	}
	MyPrecision deltaX()
	{
		return pt1.X - pt0.X;
	}
	MyPrecision deltaY()
	{
		return pt1.Y - pt0.Y;
	}

	MyPoint IntersectingPoint(MyPoint ptLight, Vector vecLight)
	{
		MyPoint ptIntersect;
		CLine lnIncident(ptLight, MyPoint(ptLight.X + vecLight.X, ptLight.Y + vecLight.Y));
		auto ptIntersectTest = IntersectingPoint(lnIncident);
		// the incident line intersects the mirror. Our mirrors have finite width
		// let's see if the intersection point is within the mirror's edges
		if (!ptIntersectTest.IsNull())
		{
			if (this->IsLimitedInLength)
			{
				if (pt0.DistanceFromPoint(ptIntersectTest) +
					ptIntersectTest.DistanceFromPoint(pt1) - LineLength() < epsilon)
				{
					if (abs(vecLight.X) < epsilon) // vert
					{
						auto ss = sgn(vecLight.Y);
						auto s2 = sgn(ptIntersectTest.Y - ptLight.Y);
						if (ss * s2 == 1) // in our direction?
						{
							ptIntersect = ptIntersectTest;
						}
					}
					else  // non-vertical
					{
						auto ss = sgn(vecLight.X);
						auto s2 = sgn(ptIntersectTest.X - ptLight.X);
						if (ss * s2 == 1) // in our direction?
						{
							ptIntersect = ptIntersectTest;
						}
					}
				}
			}
			else
			{
				ptIntersect = ptIntersectTest;
			}
		}
		return ptIntersect;
	}

	MyPoint IntersectingPoint(CLine otherLine)
	{
		MyPoint result;
		auto denom = (this->pt0.X - this->pt1.X) * (otherLine.pt0.Y - otherLine.pt1.Y) - (this->pt0.Y - this->pt1.Y) * (otherLine.pt0.X - otherLine.pt1.X);
		if (denom != 0)
		{
			result.X = ((this->pt0.X * this->pt1.Y - this->pt0.Y * this->pt1.X) * (otherLine.pt0.X - otherLine.pt1.X) - (this->pt0.X - this->pt1.X) * (otherLine.pt0.X * otherLine.pt1.Y - otherLine.pt0.Y * otherLine.pt1.X)) / denom;
			result.Y = ((this->pt0.X * this->pt1.Y - this->pt0.Y * this->pt1.X) * (otherLine.pt0.Y - otherLine.pt1.Y) - (this->pt0.Y - this->pt1.Y) * (otherLine.pt0.X * otherLine.pt1.Y - otherLine.pt0.Y * otherLine.pt1.X)) / denom;
		}
		return result;
	}

	Vector Reflect(MyPoint ptLight, Vector vecLight, MyPoint ptIntersect)
	{
		if (abs(pt0.X - pt1.X) < epsilon) // vertical line
		{
			vecLight.X = -vecLight.X;
		}
		else if (abs(pt0.Y - pt1.Y) < epsilon)// horiz line
		{
			vecLight.Y = -vecLight.Y;
		}
		else
		{
			//// create incident line endpoint to intersection with correct seg length
			CLine lnIncident(ptLight, ptIntersect);
			//var angBetween = lnIncidentTest.angleBetween(lnMirror);
			//var angClosest = Math.Atan(lnMirror.slope) / _piOver180;
			//var angIncident = Math.Atan(lnIncidentTest.slope) / _piOver180;
			//var angReflect = 2 * angClosest - angIncident;
			auto newSlope = tan(2 * atan(slope()) - atan(lnIncident.slope()));
			// now we have the slope of the desired reflection line: 
			// now we need to determine the reflection direction (x & y) along the slope
			// The incident line came from one side (half plane) of the mirror. We need to leave on the same side.
			// to do so, we assume we're going in a particular direction
			// then we create a test point using the new slope
			// we see which half plane the test point is in relation to the mirror.
			// and which half plane the light source is. If they're different, we reverse the vector

			// first set the new vector to the new slope in a guessed direction. 
			vecLight.X = SpeedMult;
			vecLight.Y = SpeedMult * newSlope;
			// create a test point along the line of reflection
			MyPoint ptTest(ptIntersect.X + vecLight.X, ptIntersect.Y + vecLight.Y);
			auto halfplaneLight = LeftHalf(ptLight);
			auto halfplaneTestPoint = LeftHalf(ptTest);
			if (halfplaneLight ^ halfplaneTestPoint) // xor
			{
				vecLight.X = -vecLight.X;
				vecLight.Y = -vecLight.Y;
			}
		}
		return vecLight;
	}

	MyPrecision slope()
	{
		return deltaY() / deltaX();
	}

	bool LeftHalf(MyPoint c)
	{
		auto a = pt0;
		auto b = pt1;
		auto res = (b.X - a.X) * (c.Y - a.Y) - (b.Y - a.Y) * (c.X - a.X);
		return res > 0;
	}
	void Draw(HDC hDC)
	{
		MoveToEx(hDC, (int)pt0.X, (int)pt0.Y, nullptr);
		LineTo(hDC, (int)pt1.X, (int)pt1.Y);
	}

	bool IsNull()
	{
		return pt0.IsNull() || pt1.IsNull();
	}
	MyPoint pt0;
	MyPoint pt1;
	bool IsLimitedInLength;
};


class BounceFrame
{
	CComAutoCriticalSection csLstMirrors;
#define LOCKMirrors CComCritSecLock<CComAutoCriticalSection> lock(csLstMirrors)
	vector<shared_ptr<IMirror>> _lstMirrors; // as in a List<MyLine>
#define  margin  5
#define xScale 1
#define yScale 1

	MyPoint _frameSize; // size of drawing area
	HPEN _clrLine;
	HPEN _clrFillReflection;
	int _colorReflection;
	int _nPenWidth;
	HBRUSH _hBrushBackGround;
	MyPoint _ptOldMouseDown;
	MyPoint _ptCurrentMouseDown;
	bool _fPenModeDrag = false;
	bool _fPenDown = false;
	bool _fIsRunning = false;
	bool _fCancelRequest = false;
	int _nDelayMsecs = 0;
	int _nBounces = 0;
	DWORD _timeStartmSecs;
	Vector _vecLightInit;
	MyPoint _ptLightInit;
	int _nOutofBounds;
public:
	BounceFrame()
	{
		_nPenWidth = 1;
		_hBrushBackGround = CreateSolidBrush(0xffffff);
		_clrLine = CreatePen(0, _nPenWidth, 0x0);
		_colorReflection = 0xff;
		_clrFillReflection = CreatePen(0, _nPenWidth, _colorReflection);
		_ptLightInit.X = 140;
		_ptLightInit.Y = 140;
	}
	~BounceFrame()
	{
		DeleteObject(_hBrushBackGround);
		DeleteObject(_clrLine);
		DeleteObject(_clrFillReflection);
	}
private:
	void ShowStatusMsg(LPCWSTR pText, ...)
	{
		va_list args;
		va_start(args, pText);
		wstring strtemp(1000, '\0');
		_vsnwprintf_s(&strtemp[0], 1000, _TRUNCATE, pText, args);
		va_end(args);
		auto len = wcslen(strtemp.c_str());
		strtemp.resize(len);
		HDC hDC = GetDC(g_hWnd);
		HFONT hFont = (HFONT)GetStockObject(ANSI_FIXED_FONT);
		HFONT hOldFont = (HFONT)SelectObject(hDC, hFont);
		TextOut(hDC, 0, (int)_frameSize.Y + 1, strtemp.c_str(), (int)strtemp.size());
		SelectObject(hDC, hOldFont);
		ReleaseDC(g_hWnd, hDC);
	}
	void DrawMirrors()
	{
		if (g_hWnd != nullptr)
		{
			HDC hDC = GetDC(g_hWnd);
			LOCKMirrors;
			SelectObject(hDC, _clrLine);
			for (auto line : _lstMirrors)
			{
				line->Draw(hDC);
			}
			//            Arc(hDC, 20, 20, 800, 400, 800, 200, 0, 200);
			ReleaseDC(g_hWnd, hDC);
		}
	}
	void SetColor(int color)
	{
		_colorReflection = color;
		DeleteObject(_clrFillReflection);
		_clrFillReflection = CreatePen(/*nPenStyle:*/ 0, _nPenWidth, _colorReflection);

	}
	void ChooseRandomStartingRay()
	{
		_ptLightInit.X = (MyPrecision)(margin + (_frameSize.X - 2 * margin)* ((double)rand()) / RAND_MAX);
		_ptLightInit.Y = (MyPrecision)(margin + (_frameSize.Y - 2 * margin)* ((double)rand()) / RAND_MAX);
		_vecLightInit.X = 1;
		_vecLightInit.Y = (MyPrecision)(((double)rand()) / RAND_MAX);
	}

	void Clear(bool fKeepUserMirrors)
	{
		MyPoint topLeft = { margin, margin };
		MyPoint topRight = { _frameSize.X - margin, margin };
		MyPoint botLeft = { margin, _frameSize.Y - margin };
		MyPoint botRight = { _frameSize.X - margin,_frameSize.Y - margin };
		_ptCurrentMouseDown.Clear();
		_ptOldMouseDown.Clear();
		_fPenDown = false;
		_fPenModeDrag = false;
		_ptLightInit.X = 140;
		_ptLightInit.Y = 140;
		_vecLightInit.X = 11;
		_vecLightInit.Y = 10;
		_nBounces = 0;
		_nOutofBounds = 0;
		_colorReflection = 0;
		if (!fKeepUserMirrors)
		{
			LOCKMirrors;
			_lstMirrors.clear();
			_lstMirrors.push_back(make_shared<CLine>(topLeft, topRight));
			_lstMirrors.push_back(make_shared<CLine>(topRight, botRight));
			_lstMirrors.push_back(make_shared<CLine>(botRight, botLeft));
			_lstMirrors.push_back(make_shared<CLine>(botLeft, topLeft));
		}
		if (g_hWnd != nullptr)
		{
			HDC hDC = GetDC(g_hWnd);
			RECT rect;
			GetClientRect(g_hWnd, &rect);
			FillRect(hDC, &rect, _hBrushBackGround);
			ReleaseDC(g_hWnd, hDC);
			DrawMirrors();
		}
	}

	int DoReflecting()
	{
		int nLastBounceWhenStagnant = 0;
		_timeStartmSecs = GetTickCount();
		_nOutofBounds = 0;
		HDC hDC = GetDC(g_hWnd);
		while (_fIsRunning && !_fCancelRequest)
		{
			//MyPoint ptEndIncident(_ptLight.x + _vecLight.x, _ptLight.y + _vecLight.y);
			//auto lnIncident = MyLine(_ptLight, ptEndIncident);
			auto lnIncident = CLine(_ptLightInit, MyPoint(_ptLightInit.X + _vecLightInit.X, _ptLightInit.Y + _vecLightInit.Y));
			auto minDist = 1000000.0;
			shared_ptr<IMirror> mirrorClosest;
			MyPoint ptIntersect = { 0,0 };
			{
				LOCKMirrors;
				for (auto mirror : _lstMirrors)
				{
					auto ptIntersectTest = mirror->IntersectingPoint(_ptLightInit, _vecLightInit);;
					if (!ptIntersectTest.IsNull())
					{
						auto dist = _ptLightInit.DistanceFromPoint(ptIntersectTest);

						if (dist > epsilon && dist < minDist)
						{
							minDist = dist;
							mirrorClosest = mirror;
							ptIntersect = ptIntersectTest;
						}
					}
				}
			}
			if (mirrorClosest == nullptr)
			{
				if (nLastBounceWhenStagnant == _nBounces)
				{// both the last bounce and this bounce were stagnant
					nLastBounceWhenStagnant = _nBounces;
					_nOutofBounds++;
					ChooseRandomStartingRay();
				}
				else
				{
					_vecLightInit.X = -_vecLightInit.X;
					nLastBounceWhenStagnant = _nBounces;
				}
				continue;
			}
			// now draw incident line from orig pt to intersection
			SelectObject(hDC, _clrFillReflection);
			if (_nBounces == 1)
			{
				MoveToEx(hDC, (int)(xScale * _ptLightInit.X), (int)(yScale * _ptLightInit.Y), nullptr);
			}
			LineTo(hDC, (int)(xScale * ptIntersect.X), (int)(yScale * ptIntersect.Y));

			// now reflect vector
			_vecLightInit = mirrorClosest->Reflect(_ptLightInit, _vecLightInit, ptIntersect);


			// now set new pt 
			_ptLightInit = ptIntersect;
			SetColor((int)_colorReflection + 1 & 0xffffff);

			if (_nDelayMsecs > 0)
			{
				Sleep(_nDelayMsecs);
			}
			if (_nBounces % 1000 == 0)
			{
				ShowStatus();
			}
			_nBounces++;

		}
		ReleaseDC(g_hWnd, hDC);
		_fCancelRequest = false;
		return 0;
	}
	static DWORD WINAPI ThreadRoutine(void *parm)
	{
		BounceFrame *pBounceFrame = (BounceFrame *)parm;
		return pBounceFrame->DoReflecting();
	}

	void CancelRunning()
	{
		if (_fIsRunning)
		{
			_fCancelRequest = true;
			_fIsRunning = false;
			while (_fCancelRequest)
			{
				Sleep(_nDelayMsecs + 10);
			}
		}
	}

	void DoRunCommand()
	{
		_fIsRunning = !_fIsRunning;
		if (_fIsRunning)
		{
			auto hThread = CreateThread(
				nullptr, // sec attr
				0,  // stack size
				ThreadRoutine,
				this, // param
				0, // creationflags
				nullptr // threadid
			);
		}
		else
		{
			CancelRunning();
		}
	}

	void ShowStatus()
	{
		int nBouncesPerSecond = 0;
		DWORD nTicks = GetTickCount() - _timeStartmSecs;
		if (_fIsRunning)
		{
			nBouncesPerSecond = (int)(_nBounces / (nTicks / 1000.0));
		}
		ShowStatusMsg(L"Drag %d PenDown %d (%d,%d)-(%d,%d) Delay=%4d #M=%d OOB=%d #Bounces=%-10d # b/sec = %d      ",
			_fPenModeDrag,
			_fPenDown,
			(int)_ptOldMouseDown.X, (int)_ptOldMouseDown.Y,
			(int)_ptCurrentMouseDown.X, (int)_ptCurrentMouseDown.Y,
			_nDelayMsecs,
			_lstMirrors.size(),
			_nOutofBounds,
			_nBounces,
			nBouncesPerSecond
		);
	}
public:
	LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
	{
		switch (message)
		{
		case WM_SIZE:
		{
			_frameSize = MAKEPOINTS(lParam);
			_frameSize.Y -= 14; // room for status
			Clear(/*fKeepUserMirrors=*/false);
		}
		break;
		case WM_RBUTTONDOWN:
			_fPenModeDrag = !_fPenModeDrag;
			_ptOldMouseDown = MAKEPOINTS(lParam);
			ShowStatus();
			break;
		case WM_LBUTTONDOWN:
			if (_fPenModeDrag)
			{
				_ptOldMouseDown = MAKEPOINTS(lParam);
			}
			else
			{
				_ptCurrentMouseDown = MAKEPOINTS(lParam);
				if (!_ptOldMouseDown.IsNull() && _ptOldMouseDown != _ptCurrentMouseDown)
				{
					LOCKMirrors;
					_lstMirrors.push_back(make_shared<CLine>(_ptOldMouseDown, _ptCurrentMouseDown));
					DrawMirrors();
				}
				_ptOldMouseDown = _ptCurrentMouseDown;
			}
			ShowStatus();
			break;
		case WM_MOUSEMOVE:
		{
			if (_fPenModeDrag)
			{
				if (GetAsyncKeyState(VK_CONTROL) & 0x8000)
				{
					if (!_ptOldMouseDown.IsNull())
					{
						_ptCurrentMouseDown = MAKEPOINTS(lParam);
						if (_ptCurrentMouseDown != _ptOldMouseDown)
						{
							LOCKMirrors;
							_lstMirrors.push_back(make_shared<CLine>(_ptOldMouseDown, _ptCurrentMouseDown));
							_ptOldMouseDown = _ptCurrentMouseDown;
							DrawMirrors();
						}
					}
				}
				else
				{
					_ptOldMouseDown.Clear();
				}
			}
			else
			{
				if (_fPenDown)
				{
					if (!_ptOldMouseDown.IsNull())
					{
						_ptCurrentMouseDown = MAKEPOINTS(lParam);
					}
				}
			}
		}
		ShowStatus();
		break;
		case WM_LBUTTONUP:
			if (_fPenDown)
			{
				_ptCurrentMouseDown = MAKEPOINTS(lParam);
				if (_ptCurrentMouseDown != _ptOldMouseDown)
				{
					LOCKMirrors;
					_lstMirrors.push_back(make_shared<CLine>(_ptOldMouseDown, _ptCurrentMouseDown));
					_ptOldMouseDown = _ptCurrentMouseDown;
					_fPenDown = false;
					DrawMirrors();
				}
			}
			ShowStatus();
			break;
		case WM_COMMAND:
		{
			int wmId = LOWORD(wParam);
			// Parse the menu selections:
			switch (wmId)
			{
			case ID_FILE_RUN:
				DoRunCommand();
				break;
			case ID_CLEAR:
				Clear(/*fKeepUserMirrors=*/true);
				break;
			case ID_CLEARMIRRORS:
				Clear(/*fKeepUserMirrors=*/false);
				break;
			case ID_FILE_SLOWER:
			{
				if (_nDelayMsecs == 0)
				{
					_nDelayMsecs = 1;
				}
				else
				{
					_nDelayMsecs *= 8;
				}
				ShowStatus();
			}
			break;
			case ID_FILE_FASTER:
			{
				_nDelayMsecs /= 8;
				ShowStatus();
			}
			break;
			case ID_FILE_UNDOLASTLINE:
				// don't erase the 4 walls
				if (_lstMirrors.size() > 4)
				{
					LOCKMirrors;
					auto last = _lstMirrors[_lstMirrors.size() - 1];
					_lstMirrors.pop_back();
					Clear(/*fKeepUserMirrors=*/true);
					auto lastLine = dynamic_pointer_cast<CLine>(last);
					if (lastLine != nullptr)
					{
						_ptOldMouseDown = lastLine->pt1;
					}
					ShowStatus();
				}
				break;
			case IDM_ABOUT:
				DialogBox(g_hInstance, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
				break;
			case IDM_EXIT:
				DestroyWindow(hWnd);
				break;
			default:
				return DefWindowProc(hWnd, message, wParam, lParam);
			}
		}
		break;
		case WM_PAINT:
		{
			PAINTSTRUCT ps;
			HDC hdc = BeginPaint(hWnd, &ps);
			DrawMirrors();
			EndPaint(hWnd, &ps);
		}
		break;
		case WM_DESTROY:
			CancelRunning();
			PostQuitMessage(0);
			break;
		default:
			return DefWindowProc(hWnd, message, wParam, lParam);
		}
		return 0;
	}
};

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	return g_pBounceFrame->WndProc(hWnd, message, wParam, lParam);
}

// Message handler for about box.
INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
	UNREFERENCED_PARAMETER(lParam);
	switch (message)
	{
	case WM_INITDIALOG:
		return (INT_PTR)TRUE;

	case WM_COMMAND:
		if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
		{
			EndDialog(hDlg, LOWORD(wParam));
			return (INT_PTR)TRUE;
		}
		break;
	}
	return (INT_PTR)FALSE;
}

int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
	_In_opt_ HINSTANCE hPrevInstance,
	_In_ LPWSTR    lpCmdLine,
	_In_ int       nCmdShow)
{
	UNREFERENCED_PARAMETER(hPrevInstance);
	UNREFERENCED_PARAMETER(lpCmdLine);

	//create an instance of BounceFrame on the stack
	// that lives until the app exits
	BounceFrame bounceFrame;
	g_pBounceFrame = &bounceFrame;

	// Initialize global strings
	LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
	LoadStringW(hInstance, IDC_REFLECTCPP, szWindowClass, MAX_LOADSTRING);
	MyRegisterClass(hInstance);

	// Perform application initialization:
	if (!InitInstance(hInstance, SW_MAXIMIZE))
	{
		return FALSE;
	}

	HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_REFLECTCPP));

	MSG msg;

	// Main message loop:
	while (GetMessage(&msg, nullptr, 0, 0))
	{
		if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
		{
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
	}

	return (int)msg.wParam;
}

</code>

 

See also
Bouncing balls sample:
https://blogs.msdn.microsoft.com/calvin_hsia/2014/07/31/dpi-aware-sample/

https://blogs.msdn.microsoft.com/calvin_hsia/2018/01/31/store-different-derived-classes-in-collections-in-c-and-c-covariance-shared_ptr-unique_ptr/

https://en.wikipedia.org/wiki/Illumination_problem
http://mathworld.wolfram.com/IlluminationProblem.html

Comments (0)

Skip to main content