Lindenmayer System
A Simple L-system Image Generator Featuring Turtle Graphics
CMain.cpp
Go to the documentation of this file.
1 
4 // MIT License
5 //
6 // Copyright (c) 2020 Ian Parberry
7 //
8 // Permission is hereby granted, free of charge, to any person obtaining a copy
9 // of this software and associated documentation files (the "Software"), to
10 // deal in the Software without restriction, including without limitation the
11 // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
12 // sell copies of the Software, and to permit persons to whom the Software is
13 // furnished to do so, subject to the following conditions:
14 //
15 // The above copyright notice and this permission notice shall be included in
16 // all copies or substantial portions of the Software.
17 //
18 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23 // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
24 // IN THE SOFTWARE.
25 
26 #include "CMain.h"
27 #include "WindowsHelpers.h"
28 
30 // Constructors and destructors
31 
32 #pragma region Constructors and destructors
33 
40 
41 CMain::CMain(const HWND hwnd):
42  m_hWnd(hwnd)
43 {
45 
46  m_pFontFamily = new Gdiplus::FontFamily(L"Consolas");
47  m_pFont = new Gdiplus::Font(m_pFontFamily, 14, Gdiplus::FontStyleRegular,
48  Gdiplus::UnitPixel);
49 
50  SetRules(); //create the first set of rules
51 
52  //create and init menus
53 
54  CreateMenus(); //create the drop-down menu
55  SetLSystemMenuChecks(); //set menu checkmarks
56  EnableGenerateMenuEntry(); //enable or disable the generate menu entry
57 
58  const UINT bShowRules = m_bShowRules? MF_CHECKED: MF_UNCHECKED;
59  CheckMenuItem(m_hViewMenu, IDM_VIEW_RULES, bShowRules);
60 
61  if(m_bThickLines)
62  CheckMenuItem(m_hViewMenu, IDM_VIEW_THICKLINES, MF_CHECKED);
63 
64  //generate and draw the first object
65 
66  Generate();
67  Draw();
68 } //constructor
69 
71 
73  delete m_pBitmap;
74  delete m_pFontFamily;
75  delete m_pFont;
76 
77  Gdiplus::GdiplusShutdown(m_gdiplusToken);
78 } //destructor
79 
80 #pragma endregion Constructors and destructors
81 
83 // Drawing functions
84 
85 #pragma region Drawing functions
86 
89 
91  PAINTSTRUCT ps; //paint structure
92  HDC hdc = BeginPaint(m_hWnd, &ps); //device context
93  Gdiplus::Graphics graphics(hdc); //GDI+ graphics object
94 
95  //dirty rectangle width and height
96 
97  const int w = m_pBitmap->GetWidth();
98  const int h = m_pBitmap->GetHeight();
99 
100  //get client rectangle
101 
102  RECT rectClient; //for client rectangle
103  GetClientRect(m_hWnd, &rectClient); //get client rectangle
104  const int nClientWidth = rectClient.right - rectClient.left; //client width
105  const int nClientHt = rectClient.bottom - rectClient.top; //client height
106 
107  //draw the bitmap to the center of the client area
108 
109  int textwidth = m_bShowRules? (int)std::ceil(GetRuleStrWidth(graphics)): 0;
110 
111  const int margin = 10; //default margin
112 
113  const float xscale = float(nClientWidth
114  - (textwidth > 0? 3: 2)*margin - textwidth)/w;
115  const float yscale = float(nClientHt - 2*margin)/h;
116  const float scale = min(min(xscale, yscale), 1);
117 
118  Gdiplus::Rect rectDest;
119 
120  rectDest.Width = (int)std::floor(scale*w);
121  rectDest.Height = (int)std::floor(scale*h);
122 
123  rectDest.X = max(2*margin + textwidth,
124  (nClientWidth - rectDest.Width + textwidth)/2);
125  rectDest.Y = max(margin, (nClientHt - rectDest.Height)/2);
126 
127  graphics.DrawImage(m_pBitmap, rectDest);
128 
129  //draw the rules on the screen (note: NOT on the bitmap)
130 
131  if(m_bShowRules)
132  DrawRules(graphics, Gdiplus::PointF(margin, margin));
133 
134  EndPaint(m_hWnd, &ps); //this must be done last
135 } //OnPaint
136 
140 
141 void CMain::DrawRules(Gdiplus::Graphics& graphics, Gdiplus::PointF p){
142  Gdiplus::SolidBrush brush(Gdiplus::Color::DarkCyan);
143 
144  std::wstring temp = m_cLSystem.GetRuleString();
145  temp += std::to_wstring(m_cLSystem.GetGenerations()) + L" generations\n";
146 
147  graphics.DrawString(temp.c_str(), -1, m_pFont, p, &brush);
148 } //DrawRules
149 
157 
158 void CMain::Draw(const TurtleDesc& d){
159  const std::wstring& s = m_cLSystem.GetString(); //shorthand for generated string
160  std::stack<StackFrame> stack; //stack frame
161 
162  //prepare to draw
163 
164  Gdiplus::Graphics* pGraphics = nullptr;
165 
166  Gdiplus::PointF ptCur; //current position, the start of the line
167  float angle = 0; //current orientation
168  float len = d.m_fLength; //current branch length
169 
170  Gdiplus::Pen pen(Gdiplus::Color::Black);
171  pen.SetWidth(d.m_fPointSize);
172 
173  //initialize the dirty rectangle to the start pixel
174 
175  RECT r;
176 
177  r.left = int(std::floor(ptCur.X));
178  r.right = int(std::ceil (ptCur.X));
179  r.top = int(std::floor(ptCur.Y));
180  r.bottom = int(std::ceil (ptCur.Y));
181 
182  //measure once, draw once
183 
184  for(int i: {0, 1}){ //i==0 means measure, i==1 means draw
185  for(size_t j=0; j<s.size(); j++){ //loop through characters of s
186  Gdiplus::PointF ptNext; //next position (the end of the line)
187 
188  switch(s[j]){
189  case 'L':
190  case 'R':
191  case 'F':
192  ptNext = ptCur - Gdiplus::PointF(-len*sinf(angle), len*cosf(angle));
193 
194  if(i == 0)AddPointToRect(r, ptNext); //measure
195  else pGraphics->DrawLine(&pen, ptCur, ptNext); //draw
196 
197  ptCur = ptNext;
198  break;
199 
200  case '+': angle -= d.m_fAngleDelta; break;
201  case '-': angle += d.m_fAngleDelta; break;
202 
203  case '[':
204  stack.push(StackFrame(ptCur, angle, len));
205  len *= d.m_fLenMultiplier;
206  break;
207 
208  case ']': {
209  const StackFrame& sf = stack.top();
210 
211  ptCur = sf.m_ptPos;
212  angle = sf.m_fAngle;
213  len = sf.m_fLength;
214 
215  stack.pop(); //this must be last, obviously
216  } //case
217  break;
218  } //switch
219  } //for
220 
221  if(i == 0){ //done measuring, prepare for drawing
222  delete m_pBitmap;
223 
224  //make the dirty rectangle slightly larger to include lines on the edge
225 
226  const int delta = (int)std::ceil(d.m_fPointSize/2.0f); //amount to add
227  r.right += delta;
228  r.bottom += delta;
229 
230  //create new bitmap of exactly the right size
231 
232  const int w = r.right - r.left;
233  const int h = r.bottom - r.top;
234  m_pBitmap = new Gdiplus::Bitmap(w, h, PixelFormat32bppARGB);
235  ptCur = Gdiplus::PointF(-(float)r.left, -(float)r.top);
236 
237  //create graphics object open on bitmap for drawing in the next iteration
238 
239  pGraphics = new Gdiplus::Graphics(m_pBitmap);
240  pGraphics->SetSmoothingMode(Gdiplus::SmoothingModeHighQuality);
241  pGraphics->Clear(Gdiplus::Color::Transparent); //transparent background
242  } //if
243  } //for
244 
245  delete pGraphics; //clean up
246 } //Draw
247 
253 
254 void CMain::Draw(){
255  TurtleDesc d; //turtle graphics descriptor
256 
257  switch(m_nType){ //the angle deltas are cribbed from ABOP
258  case IDM_LSYS_PLANT_A: d = TurtleDesc(22.7f, 8.0f); break;
259  case IDM_LSYS_PLANT_B: d = TurtleDesc(20.0f, 20.0f); break;
260  case IDM_LSYS_PLANT_C: d = TurtleDesc(22.5f, 12.0f); break;
261  case IDM_LSYS_PLANT_D: d = TurtleDesc(20.0f, 5.0f); break;
262  case IDM_LSYS_PLANT_E: d = TurtleDesc(25.7f, 5.0f); break;
263  case IDM_LSYS_PLANT_F: d = TurtleDesc(22.5f, 16.0f); break;
264 
265  case IDM_LSYS_BRANCHING: d = TurtleDesc(21.2f, 8.0f); break;
266  case IDM_LSYS_HEXGOSPER: d = TurtleDesc(60.0f, 12.0f); break;
267  } //switch
268 
269  d.m_fPointSize = m_bThickLines? 2.0f: 1.0f;
270 
271  Draw(d);
272  InvalidateRect(m_hWnd, nullptr, TRUE);
273 } //Draw
274 
275 #pragma endregion Drawing functions
276 
278 // Menu functions
279 
280 #pragma region Menu functions
281 
284 
286  HMENU hMenubar = CreateMenu(); //handle to the menu bar
287 
288  //set the FILE menu
289 
290  m_hFileMenu = CreateMenu();
291  AppendMenuW(m_hFileMenu, MF_STRING, IDM_FILE_GENERATE, L"Generate");
292  AppendMenuW(m_hFileMenu, MF_STRING, IDM_FILE_SAVE, L"Save...");
293  AppendMenuW(m_hFileMenu, MF_STRING, IDM_FILE_QUIT, L"Quit");
294 
295  AppendMenuW(hMenubar, MF_POPUP, (UINT_PTR)m_hFileMenu, L"&File");
296 
297  //set the LSYS menu
298 
299  m_hLSMenu = CreateMenu();
300 
301  AppendMenuW(m_hLSMenu, MF_STRING, IDM_LSYS_PLANT_A,
302  L"Plant-like (Fig. 1.24a)");
303  AppendMenuW(m_hLSMenu, MF_STRING, IDM_LSYS_PLANT_B,
304  L"Plant-like (Fig. 1.24b)");
305  AppendMenuW(m_hLSMenu, MF_STRING, IDM_LSYS_PLANT_C,
306  L"Plant-like (Fig. 1.24c)");
307  AppendMenuW(m_hLSMenu, MF_STRING, IDM_LSYS_PLANT_D,
308  L"Plant-like (Fig. 1.24d)");
309  AppendMenuW(m_hLSMenu, MF_STRING, IDM_LSYS_PLANT_E,
310  L"Plant-like (Fig. 1.24e)");
311  AppendMenuW(m_hLSMenu, MF_STRING, IDM_LSYS_PLANT_F,
312  L"Plant-like (Fig. 1.24f)");
313 
314  AppendMenuW(m_hLSMenu, MF_SEPARATOR, 0, nullptr);
315 
316  AppendMenuW(m_hLSMenu, MF_STRING, IDM_LSYS_BRANCHING,
317  L"Stochastic branching (Fig. 1.27)");
318  AppendMenuW(m_hLSMenu, MF_STRING, IDM_LSYS_HEXGOSPER,
319  L"Hexagonal Gosper curve (Fig. 1.11a)");
320 
321  AppendMenuW(hMenubar, MF_POPUP, (UINT_PTR)m_hLSMenu, L"&L-System");
322 
323  //set the VIEW menu
324 
325  m_hViewMenu = CreateMenu();
326 
327  AppendMenuW(m_hViewMenu, MF_STRING, IDM_VIEW_THICKLINES, L"Thick lines");
328  AppendMenuW(m_hViewMenu, MF_STRING, IDM_VIEW_RULES, L"Show rules");
329 
330  AppendMenuW(hMenubar, MF_POPUP, (UINT_PTR)m_hViewMenu, L"&View");
331 
332  //set the menu bar
333 
334  SetMenu(m_hWnd, hMenubar);
335 } //CreateMenus
336 
340 
342  for(UINT i=IDM_LSYS_BRANCHING; i<=IDM_LSYS_HEXGOSPER; i++)
343  CheckMenuItem(m_hLSMenu, i, MF_UNCHECKED);
344 
345  CheckMenuItem(m_hLSMenu, m_nType, MF_CHECKED);
346 } //SetLSystemMenuChecks
347 
350 
352  if(m_nType == IDM_LSYS_BRANCHING) //the only stochastic type
353  EnableMenuItem(m_hFileMenu, 0, MF_ENABLED | MF_BYPOSITION);
354  else EnableMenuItem(m_hFileMenu, 0, MF_GRAYED | MF_BYPOSITION);
355 } //EnableGenerateMenuEntry
356 
357 #pragma endregion Menu functions
358 
360 // Settings functions (functions that change CMain's state)
361 
362 #pragma region Settings functions
363 
368 
370  m_cLSystem.Clear();
371 
372  switch(m_nType){
373  case IDM_LSYS_BRANCHING:
374  m_cLSystem.SetRoot(L"F");
375  m_cLSystem.AddRule(LProduction(L'F', L"F[+F]F[-F]F", 0.33f));
376  m_cLSystem.AddRule(LProduction(L'F', L"F[+F]F", 0.33f));
377  m_cLSystem.AddRule(LProduction(L'F', L"F[-F]F", 0.34f));
378  break;
379 
380  case IDM_LSYS_PLANT_A:
381  m_cLSystem.SetRoot(L"F");
382  m_cLSystem.AddRule(LProduction(L'F', L"F[+F]F[-F]F"));
383  break;
384 
385  case IDM_LSYS_PLANT_B:
386  m_cLSystem.SetRoot(L"F");
387  m_cLSystem.AddRule(LProduction(L'F', L"F[+F]F[-F][F]"));
388  break;
389 
390  case IDM_LSYS_PLANT_C:
391  m_cLSystem.SetRoot(L"F");
392  m_cLSystem.AddRule(LProduction(L'F', L"FF-[-F+F+F]+[+F-F-F]"));
393  break;
394 
395  case IDM_LSYS_PLANT_D:
396  m_cLSystem.SetRoot(L"X");
397  m_cLSystem.AddRule(LProduction(L'X', L"F[+X]F[-X]+X"));
398  m_cLSystem.AddRule(LProduction(L'F', L"FF"));
399  break;
400 
401  case IDM_LSYS_PLANT_E:
402  m_cLSystem.SetRoot(L"X");
403  m_cLSystem.AddRule(LProduction(L'X', L"F[+X][-X]FX"));
404  m_cLSystem.AddRule(LProduction(L'F', L"FF"));
405  break;
406 
407  case IDM_LSYS_PLANT_F:
408  m_cLSystem.SetRoot(L"X");
409  m_cLSystem.AddRule(LProduction(L'X', L"F-[ [X]+X]+F[+FX]-X"));
410  m_cLSystem.AddRule(LProduction(L'F', L"FF"));
411  break;
412 
413  case IDM_LSYS_HEXGOSPER:
414  m_cLSystem.SetRoot(L"L");
415  m_cLSystem.AddRule(LProduction(L'L', L"L+R++R-L--LL-R+"));
416  m_cLSystem.AddRule(LProduction(L'R', L"-L+RR++R+L--L-R"));
417  break;
418  } //switch
419 } //SetRules
420 
427 
428 void CMain::SetType(UINT t){
429  if(m_nType != t){ //if it's a change of state
430  m_nType = t;
431 
434  SetRules();
435  Generate();
436  Draw();
437  } //if
438 } //SetType
439 
442 
445  const UINT status = m_bThickLines? MF_CHECKED: MF_UNCHECKED;
446  CheckMenuItem(m_hViewMenu, IDM_VIEW_THICKLINES, status);
447  Draw(); //redraw with new line thickness
448 } //ToggleLineThickness
449 
452 
455  const UINT status = m_bShowRules? MF_CHECKED: MF_UNCHECKED;
456  CheckMenuItem(m_hViewMenu, IDM_VIEW_RULES, status);
457  InvalidateRect(m_hWnd, nullptr, TRUE);
458 } //ToggleShowRules
459 
460 #pragma endregion Settings functions
461 
463 // Other functions
464 
466 
468  int nNumGenerations = 0; //number of generations
469 
470  switch(m_nType){
471  case IDM_LSYS_PLANT_A: nNumGenerations = 5; break;
472  case IDM_LSYS_PLANT_B: nNumGenerations = 5; break;
473  case IDM_LSYS_PLANT_C: nNumGenerations = 5; break;
474  case IDM_LSYS_PLANT_D: nNumGenerations = 7; break;
475  case IDM_LSYS_PLANT_E: nNumGenerations = 7; break;
476  case IDM_LSYS_PLANT_F: nNumGenerations = 5; break;
477  case IDM_LSYS_BRANCHING: nNumGenerations = 6; break;
478  case IDM_LSYS_HEXGOSPER: nNumGenerations = 5; break;
479  } //switch
480 
481  m_cLSystem.Generate(nNumGenerations);
482 } //Generate
483 
488 
489 Gdiplus::Bitmap* CMain::GetBitmap(){
490  return m_pBitmap;
491 } //GetBitmap
492 
495 
496 const bool CMain::IsStochastic() const{
497  return m_cLSystem.IsStochastic();
498 } //IsStochastic
499 
508 
509 int CMain::GetRuleStrWidth(Gdiplus::Graphics& graphics){
510  const std::wstring& strRules = m_cLSystem.GetRuleString(); //shorthand
511  const Gdiplus::RectF r = GetClientRectF(m_hWnd); //client rectangle
512 
513  const Gdiplus::CharacterRange charRange(0, (int)strRules.size());
514  Gdiplus::StringFormat sf;
515  sf.SetMeasurableCharacterRanges(1, &charRange);
516 
517  const int n = sf.GetMeasurableCharacterRangeCount();
518  Gdiplus::Region* rg = new Gdiplus::Region[n]; //array of regions
519  graphics.MeasureCharacterRanges(strRules.c_str(), -1, m_pFont, r, &sf, n, rg);
520 
521  //find the maximum region width in pixels
522 
523  int width = 0; //return result
524 
525  for(int i=0; i<n; i++){ //for each region
526  Gdiplus::Rect r; //for bounding rectangle of current region
527  rg[i].GetBounds(&r, &graphics); //get bounding rectangle of current region
528  width = max(width, r.Width); //see if current region width is largest so far
529  } //for
530 
531  //cleanup and exit
532 
533  delete [] rg;
534  return width;
535 } //GetRuleStrWidth
void SetLSystemMenuChecks()
Set L-system menu checkmarks.
Definition: CMain.cpp:341
UINT m_nType
Current L-system type.
Definition: CMain.h:53
ULONG_PTR m_gdiplusToken
GDI+ token.
Definition: CMain.h:47
float m_fPointSize
Line point size.
Definition: Types.h:46
Turtle graphics descriptor.
Definition: Types.h:41
float m_fLength
Length.
Definition: Types.h:75
float m_fAngleDelta
Line angle delta in radians.
Definition: Types.h:43
#define IDM_LSYS_PLANT_D
Menu id for Fig. 1.24d.
int GetRuleStrWidth(Gdiplus::Graphics &graphics)
Get rule string width.
Definition: CMain.cpp:509
Gdiplus::Font * m_pFont
Font.
Definition: CMain.h:58
float m_fAngle
Rotation angle.
Definition: Types.h:74
void Clear()
Clear the rules, buffers, and settings.
Definition: Lsystem.cpp:101
~CMain()
Destructor.
Definition: CMain.cpp:72
Gdiplus::Bitmap * m_pBitmap
Pointer to a bitmap image.
Definition: CMain.h:49
#define IDM_VIEW_THICKLINES
Menu id for thick lines.
#define IDM_LSYS_PLANT_B
Menu id for Fig. 1.24b.
void CreateMenus()
Create menus.
Definition: CMain.cpp:285
Gdiplus::Bitmap * GetBitmap()
Get pointer to bitmap.
Definition: CMain.cpp:489
const bool IsStochastic() const
Is a stochastic L-system.
Definition: Lsystem.cpp:197
#define IDM_LSYS_BRANCHING
Menu id for branching structure.
#define IDM_FILE_QUIT
Menu id for Quit.
void AddPointToRect(RECT &r, Gdiplus::PointF point)
Add point to rectangle.
Interface for some helpful Windows-specific functions.
void EnableGenerateMenuEntry()
Enable Generate in File menu.
Definition: CMain.cpp:351
#define IDM_FILE_SAVE
Menu id for Save.
Stochastic context-free production.
Definition: Lsystem.h:43
void Generate()
Generate L-system string.
Definition: CMain.cpp:467
void ToggleShowRules()
Toggle the show rules flag.
Definition: CMain.cpp:453
#define IDM_LSYS_PLANT_C
Menu id for Fig. 1.24c.
const std::wstring & GetRuleString() const
Get rule string.
Definition: Lsystem.cpp:183
HMENU m_hViewMenu
Handle to the View menu.
Definition: CMain.h:45
Interface for the main class CMain.
CMain(const HWND hwnd)
Constructor.
Definition: CMain.cpp:41
const bool IsStochastic() const
Is a stochastic L-system.
Definition: CMain.cpp:496
void SetRules()
Create the L-system rules.
Definition: CMain.cpp:369
Stack frame.
Definition: Types.h:71
LSystem m_cLSystem
The L-system.
Definition: CMain.h:51
#define IDM_FILE_GENERATE
Menu id for Save.
HMENU m_hFileMenu
Handle to the File menu.
Definition: CMain.h:43
#define IDM_LSYS_PLANT_E
Menu id for Fig. 1.24e.
#define IDM_LSYS_HEXGOSPER
Menu id for hexagonal Gosper curve.
const std::wstring & GetString() const
Get generated string.
Definition: Lsystem.cpp:176
const UINT GetGenerations() const
Get number of generations.
Definition: Lsystem.cpp:190
void AddRule(const LProduction &rule)
AddRule rule.
Definition: Lsystem.cpp:59
bool m_bShowRules
Whether to show the rules.
Definition: CMain.h:55
bool m_bThickLines
Line thickness flag.
Definition: CMain.h:54
HMENU m_hLSMenu
Handle to the L-System menu.
Definition: CMain.h:44
void Generate(const UINT n)
Generate L-system from stored root and rules.
Definition: Lsystem.cpp:125
#define IDM_LSYS_PLANT_A
Menu id for Fig. 1.24a.
void ToggleLineThickness()
Toggle the line thickness flag.
Definition: CMain.cpp:443
float m_fLenMultiplier
Line length multiplier.
Definition: Types.h:45
#define IDM_VIEW_RULES
Menu id for showing rules.
ULONG_PTR InitGDIPlus()
Initialize GDI+.
void OnPaint()
Paint the client area.
Definition: CMain.cpp:90
#define IDM_LSYS_PLANT_F
Menu id for Fig. 1.24f.
Gdiplus::PointF m_ptPos
Position.
Definition: Types.h:73
void SetRoot(const std::wstring &omega)
Set the root string.
Definition: Lsystem.cpp:93
void DrawRules(Gdiplus::Graphics &graphics, Gdiplus::PointF p)
Draw rules.
Definition: CMain.cpp:141
Gdiplus::FontFamily * m_pFontFamily
Font family.
Definition: CMain.h:57
void SetType(UINT t)
Set type.
Definition: CMain.cpp:428
void Draw()
Draw turtle graphics.
Definition: CMain.cpp:254
HWND m_hWnd
Window handle.
Definition: CMain.h:42
float m_fLength
Line length.
Definition: Types.h:44
Gdiplus::RectF GetClientRectF(HWND hwnd)
Get client rectangle as a RectF.