-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathNotes.nt
More file actions
1563 lines (994 loc) · 135 KB
/
Copy pathNotes.nt
File metadata and controls
1563 lines (994 loc) · 135 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
[11/29/24]
so, almost there, but when implementing the renderer and rendercmd structs, i used "void*" like a dumbass. i have to fuck with Templates; this'll suck, but it'll be good for me. it's all just so i can use arrays of unknown lengths in constructors for the vertex attributes and shit :3
[12/02/24]
Fuck that; the entire codebase is flawed. okay, not the entire thing, but I'm abstracting files too much. need to rethink and keep it stupid simple. I want all "gl" functions to be handled by one class/struct/whatever, so that I keep it contained. That's why I'll need a "Renderer" class. It should be given simple commands to then perform with the "gl" functions. Basically, it should be told what textures to use, what vertex data to use, etc. Keeping everything in one area should be nice for my brain; once it's all done, I'll tackle if and how the "Renderer" should be split up (i.e: separating mesh code from texture code blah blah blah).
Okay, so i think i know what i should do. sadmansk's engine is flawed (or, rather, just very hard-coded) but that's just because it's super bare bones. However, that's actually fine for me since the code still works and I can still use it as good reference but in a different context. That context is my "Renderer" instead of sadmansk's "CoreEngine" and "Engine" and separate "Mesh", "Vertex", and other classes. In place of those classes, I'll use functions or small structs. I'll take notes on what all the classes in sadmansk's engine do and how I can translate them.
sadmansk Vertex class:
basically a small container for vertex position, texture coordinate, and normal data. there only exists a single header for this class, since all it does is store data that you can retrieve later. obviously, this shouldn't be a class.
my Vertex enum:
I'm choosing an enum (for now), since vertex data shouldn't change once it's loaded... I think. If I'm wrong, switching from an enum to a struct shouldn't be too difficult, and I think that keeping things simple in the beginning is best.
my Vertex struct:
I'm wrong! lmfao, I forgot how enums worked. I am basically desribing a struct. So I'll use a struct.
-- Why is the renderer a class?
Why is the renderer a class when there will only ever be one renderer? That's dumb. The idea is to keep rendering code contained in one spot, but a class is just overkill. I'll make rendering functions in a separate rendering file (or files), and then I will consciously limit the use of those functions to one part of the program (i.e: the highest level of the game-loop). This does the same thing as using a class, but without the arbitrary limitations and overhead and blah blah blah of using a class. Just keep the code in one spot and don't put up "baby gates" like limiting rendering to a class so you don't "accidentally" write code for rendering in other places. That's just... kinda stupid.
Okay, let's try this again. Here's a heirarchy:
The Renderer (rendering code)
- init meshes
- buffer verts
- buffer indices
- draw meshes
- call shader(s)
The Meshes (structs)
- contain formatted data
- return that data in any required format
- will NOT buffer anything
- will NOT make calls to "gl" functions
The Shader(s) (classes? functions?)
- interact with shaders (i.e: uniforms and such)
- bind relevant textures
-- Actors and Thinkers
I was talking to myself about my Actor.isRenderable() check which uses a Mesh.isEmpty() check, and was thinking about how I used a function instead of a variable (which was a good idea). However, while thinking about this, I eventually realized that I should make Thinkers as well as Actors. This is because an Actor will have all this code for 3D positioning and rotation and all sorts of stuff for rendered objects, but if you want to make something that doesn't need all that, making an Actor with no mesh (or setting the Actor.visible flag to "false") leaves you with all of this bloat-code for 3D handling that you're not going to use. Instead, Thinkers should be a distinct type from Actors and shouldn't have meshes or any other code relating to 3D rendering. This... unfortunately... does end up nullifying the need to check if an Actor's mesh is loaded when decided if it's renderable or not. Even if I am still mildly proud of my Mesh.isEmpty() idea. Also, as a side-note: the game should not crash if an Actor has no mesh, so either a check still needs to be made, or the rendering code should be able to skip rendering a mesh if it's empty. My little idea may still be of use; however, it may be better to simply write the rendering code in such a way that failing to render a mesh results in a simple error/warning message and nothing else. Basically, I could give up on rendering a mesh if it says it's empty (my Mesh.isEmpty() check), or I could try to render all meshes but not let empty/unrenderable meshes crash the game (or cause massive issues). The only problem with a missing mesh is that something that you should be able to see is "invisible", but everything else about the game can still function. Since rendering and game logic are separate, one way or another I'd like to make sure that these potentially inconsequential rendering problems do not result in a game crash. I mean, it'd suck if the game crashed just because a random-ass prop somewhere had a missing mesh. This does bring up the potential problem of invisible actors causing problems with gameplay (i.e: invisible physics objects blocking the player), so perhaps all Actors could have an "important" flag (that's true by default), and any Actors that aren't "important" and have missing/unusable meshes can just get removed all-together. This might be too granular of an approach, however, and a simple replacement/default mesh (and maybe a toggle for people who actually want to use an Actor with no mesh for some reason) may be the best approach. This is a very high-level problem/thought that doesn't concern me yet, but is good to keep in mind.
Okay, first things first: I need to choose a heirarchy. I'm stuck between these two:
Actor
- Mesh
- Verts
- Material(s)
- Texture(s)
Actor
- Model
- Mesh
- Verts
- Material(s)
- Texture(s)
I think I like the second one better, but it means more arbitrary classes/abstraction potentially. I'll test a Model struct idea and see how I like it.
Also: can I make some of these functions instead of structs/classes?
I'm going to look back at that gamedev.stackexchange answer and follow it. Using a Renderable interface and such.
That's too difficult for me. I will stick to simple things and change them later. I know this isn't great practice, but I'm still learning and this isn't a very big or important project.
FUUUUUUCK me, dude, i don't know what to fucking do.
okay, I wrote down some shit in my book; basicallly, have the model hold rendercmds that will get plopped into the renderarray every frame (with the init commands being plopped in only once, during the init call). then, render functions will go through that array and execute each "command" and then toss it out and move on to the next one. this'll require a custom resizeable array that can be initialized with an indeterminate size, but if i can do this then i'll finally fucking be rid of the "Renderer" class archetype and can rub it in everyones' faces. fuck yeah. okay, i need to un-autism myself and spend some time with my boyfriend who i fucking love. fuck, man, fuck fuck FAHK. aight.
[12/03/24]
Okay, here's what I'm going to do. I'll first block out the basic design of an engine. Everything to do with the core loop, basic game logic, and basic rendering. Then I'll implement it with code. Hoepfully this will help me break through this wall and I presume that after I have a way to render things dynamically, building the rest of the engine should come naturally. This means I'll be doing a lot of writing and planning before I actually code, but that's good. That should limit the amount of times I redo things and should also just limit confusion and give me a path to follow. Even if I end up tossing it out and redoing everything, it'll be a much better experience overall.
I'm also going to download the Godot source code and use it as another source of inspiration; specifically, I'll start looking at how they structure rendering tasks, what their pipeline for rendering is, and how they delegate rendering tasks (or if they do any of this at all!).
Here's a thought:
Replace the main loop with a recursive function. The function will check if it should still be going and if it should, it will call itself at the very bottom, thus emulating a loop without using a while() loop.
I've made peace with object oriented programming. In reality, programming requires a balance of all three forms: Imperative, Object Oriented, & Functional. Now, I have a bias against object oriented as it is still technically a flawed form of programming (in my opinion), but it aids the human aspect of programming. I just wish it didn't also directly interfere with the end result (like how naming variables won't impact how fast the code runs since the computer never sees them).
Okay, I've got an idea now; here's a rough estimate of fixed and unfixed parts of the render pipeline:
Fixed:
- storage
- meshes
- vertex data
- GPU buffers
Unfixed:
- rendercmds
- rendering functions (obviously)
The reason for separating these is for my brain mostly. Mesh data will be stored once per time it's loaded (i.e: loading a map), and commands are generated every frame which can use/be based on the mesh data/buffers. I don't think there'd be a reason or a condition where render commands wouldn't be based on mesh/vertex data (aside from 2D rendering), so I won't account for that unless I run into it later. This means I should design the code to be flexible, which means abstracting the rendering from the API(s).
I'm also foregoing the old file-naming conventions, but I'll paste them here for sake of documentation/logging:
:::C++
// File Naming Conventions:
// 1. Source code file names should clearly represent the contents of the file (i.e: code in "g_rendering_F.cpp" deals with rendering)
// 2. All file names are prepended with a single letter which acts as a "grouping tag" (i.e: in "g_rendering_F.cpp", "g" means "game")
// 3. All source files are split between "Functional" code and "Object-Orientated" code but will share header files, for convenience
// 3a. Functional source files are appended with "_F"
// 3b. Object-Orientated source files are appended with "_OO"
// 4. Header code may be one file specific to one pair of source files, or many files relating to one pair of source files
// 4a. When naming the latter, the "grouping tags" should represent the name of the source files (i.e: in "r_common.hpp", "r" means "rendering")
//
// Ex:
// The source code for rendering is split between g_rendering_F.cpp and g_rendering_OO.cpp
// The header code for rendering is split between many files, including "r_common.hpp" and "r_renderer.hpp"
// The source code for game actors is split between g_actors_F.cpp and g_actors_OO.cpp
// The header code for game actors is all in g_actors.hpp
:::
OI!! REMEMBER THIS!! -> you can have up to ~16 vertex attriutes BUT YOU CAN HAVE AS MANY BUFFERS AS YOU LIKE (more or less)
Hmmpf, I think the reason you make a renderer class and abstract so much is because globals are a pain in the ass and while they may be programmatically more efficient, they are not kind to humans. Still, I'd like to try with a few.
[12/05/24]
I need to re-think and re-word the problems that I am trying to tackle whilst making this game engine; specifically following the lessons from "Algorithms in C (Third Edition)".
[12/06/24]
Brain blast happened; I'm going to keep the gl functions in the main.cpp file and that should give me a more accurate way of testing my abstraction and isolation (instead of putting them in the Mesh class??? like, why was I doing that???).
I've managed to get rendering working, but there's a catch: only when there's one object in the scene. In order to render multiple objects at different locations, I need to send their location to the shader *AND* draw them all one by one (in a for loop). There's probably a better way of doing this, but that better way will still require me to make the same changes to the rendering pipeline that I'd make in implementing the for loop version, so I'll start with that.
[12/09/24]
I forgot to write this down, so I'll just paste in the git commit note from yesterday:
"FUCK FUCKING YEAH!!!! GAMETICK AND FRAMERATE ARE SEPARATE AND GAMETICK CAN BE WHATEVER I SET IT TO BE!! (that and also I can render two or more separate objects at different locations). Now, this system is far from perfect or complete in any way, shape, or fashion, BUT it works and it's mildly flexible (the gameticks are on a separate thread and loop), so that means that the core idea is working and I can make changes to it without having to re-write the entire thing. One step closer to a full engine. holy shit"
Okay, now for today; I'm going to attempt to separate/abstract more of the rendering code and not touch the gametick code until I've chipped away at making rendering more stable/solid. Mainly the core idea of rendering, as I have a few throwaway functions and global vectors and that's it. I want to get to a place where I can render any and all renderable objects in their proper orientations. Once I've got a small system that works decently well, then I'll let myself work on game logic.
More concrete goal: remove all "gl" functions from main.cpp and isolate them to either r_renderer.cpp or whichever file I decide the renderer will stay if I change it (but keep it in ONE place!).
The original idea for rendering was something like this:
1. All renderables are stored in a vector
2. All meshes belonging to the renderables generate their buffers and store their IDs in a vector
2a. For now, they should also store the number of indices in a separate vector until I streamline that better
3. The render function (whatever that may be), will go through the vectors and render the buffers with the global positions from the renderables
A better way would be to combine all this render-related data into one vector, using a struct (that's the idea of the RenderCmd class, dumbass). This'll be
what I work on first: a RenderCmd struct that'll house all relevant data for rendering, that the relevant Actor can generate and store.
There's at least one problem, though: vertex data is only ever stored once (when it's first loaded) and should never be re-generated/re-stored. However, other data like global position will be allowed to change and thus must either be buffered when it's changed or simply re-loaded every frame. I'd rather it be buffered once it's changed, but that might conflict with the RenderCmd design. However, since the call to load vertex data should only get called once, I might not actually have to worry about this too much. Remember: consolidate the problem into its most basic form; i.e: get the problem down to "what data do we need and where does it need to go?" or as close as possible to that.
Big thing to note: switching VAOs is costly. Try to remember what exactly a VAO is doing/containing.
Okay, I think I'll be making VAOs for specific sets of meshes (i.e: one for environment/geometry, one for characters, etc.), then I'll be making sure they take up a very specific slot in the global vector (i.e: environment VAO is in the first slot). Finally, every actor/actor's mesh will have an associated type that corresponds to that VAO, as well as having offsets for the vertex and index data (at least, I assume). Then we can render using as few VAOs as possible.
FUCK
[12/10/24]
Okay, I've finished the function for processing all the RenderStorageCmds, but now a problem: RenderCmds. See, at first I thought that since we want to sort the queue by VAO ID (to minimize CPU -> GPU data transfer), then we'd be sorting the queue every frame, which at first glance seems wildly inefficient. However, even using my simplistic modified bubble sort, since we'd be replacing RenderCmds who's relevant data has been changed (i.e: global position) and not refreshing the entire queue every frame, the average time cost would be closer to O(N) and not the worst case O(N^2). This means that my new problem is finding a way to not only populate the queue, but replace RenderCmds specific to objects that need to replace them. One thought is to give each RenderCmd a pointer/reference to the object that created them, and then upon sorting the vector update a value/pointer/reference on that object that corresponds to the RenderCmd's new index in the vector. However, this might not even be neccessary since intuitively it makes sense that using references/pointers, the object could just always know where its RenderCmd is in the vector. Since I don't know, though, I'll need to do some research.
OH! WAIT!
The RenderCmd queue could be a vector of REFERENCES to RenderCmds of each renderable object; they get added to the vector when they enter the scene or transition from being "not renderable" to "renderable". This would not just remove the need to replace RenderCmds, but it would also remove the need to run the modified bubble sort every frame, which would always have a chance of somehow being a worst case of O(N^2). There might be downsides to this (in fact, there most likely are, since I'm not an expert programmer and am new to this entire field of game engine programming), but it seems like a good solution so let's try it.
I just finished a simple test implementation and the idea will work! However, I need to remember that if this RenderCmd queue is not being refreshed every frame, then I need safe ways of adding and removing RenderCmds from it... is what I thought until I literally when writing that just realized I could instead give each RenderCmd a flag that basically says "skip me!" and either implement a fast way of iterating through the vector that reduces the runtime from the inevitable O(N) or just... not care and let it be. The problem I forsee is related to every object that COULD be renderable having a RenderCmd in the queue. However, I could do a tricky little trick to skip these RenderCmds without iterating over them. Something like setting their "vao_id" to "-2" which would (because of the nature of the modified bubble sort and iterator) cause the "R_Render" function to just... not even consider them... I think... actually, I don't think that specific implementation would work, but something similar could. Basically, find a sneaky way of making every RenderCmd that has an "unrenderable" or whatever flag avoid being iterated by the "R_Render" function entirely. At the end of the day, overhead could be very small, however, since checking a flag SHOULD (if I'm correct) take extremely less time than getting values and sending them places (i.e: sending render_position to the shader).
What needs to be done right now:
- change the RenderCmd vector to a RenderCmd* vector
- give each RenderCmd a "skip me!" flag
- implement skipping the relevant RenderCmds (or a sneaky way of avoiding them entirely)
- figure out how and when to populate RenderCmd* vector (could just be at scene load, like the RenderStorageCmd vector)
NOPE! I've found the problem with my technique: rendering interpolation. It can't work if we don't have a queue of RenderCmds that gets updated each tick and instead have a static list of RenderCmd pointers to structs that could have their values changed every tick. We need to know the past. We need a queue.
and i thought i was so fucking smart.
Well, okay, I didn't; that's why I realized this is (I assume) the reason for using a queue and not a static list. I knew my idea sounded too simple and too different. It might work with fenangling, but then the benefits of its simplicity would fall apart.
Fuck, here's where the book starts to come in handy.
The problem is that I can't just sort the RenderCmds by their VAO, since the whole reason for the queue is to be able to depend on it being sorted by time. Of course, the real problem is I don't fully know how the queue should work with interpolation, since I don't know how to keep track of the amount of gameticks per frame and use that in the interpolation code. I also don't have any ideas for the interpolation code. This is ESSENTIAL for my independant gametick and framerate based design. I need to do more research...
fuck me
Okay, so for implementing interpolation, lag is out of the equation (should have been obvious) and should be something to avoid/remove. What we're really concerned about is offsets due to having a higher framerate than tickrate or, more specifically, having a framerate that does not divide evenly by the tickrate. This leads to a worst case of any frame being one game tick behind, and a best case of any frame being exactly on time. At a shared tick and frame rate greater than ~59, this is a delay of either ~1/60th of a second or less. This is acceptable.
This is also why we store a "previous state" and a "current state" instead of accounting for every tick processed during the frame (since more than one tick per frame is a result of lag, NOT differing rates).
For my first implementation, I might actually end up using my static list after all, and simply giving RenderCmds a "previous_state" and "current_state". Probably by making the queue out of a different struct that contains two different RenderCmds for each state or something like that.
I'm also going to be choosing the official name for chunks of stuff in GraphX: "spaces". Basically what Godot and Unity call "scenes".
Final notes: as per the comments I left in main.cpp, the current gametick testing function (which is a surrogate for the eventual final gametick function) should call the tick() function of every Actor in the current Space, then handle the state swapping and buffering logic for each respective Actor... unless I decide to have the Actors handle that themselves (in the Tick function or with another function that's called after Tick in the gametick function). If future me is confused by my thought process, I'm partially following this (https://gamedev.stackexchange.com/questions/12754/how-to-interpolate-between-two-game-states) but I'm also sticking to DOOM's structure a bit as well. The result is (hopefully) every actor having buffered state structs that either contain the variables like "global_position" and such, or get populated with the data from the Actor's relevant variables (I like that idea more). These state structs will be used to render the Actors with the relevant (and potentially interpolated) information. How the RenderCmd queue will work and implement with this is unknown; either I link Actors' states with their relevant RenderCmd, or store it in the RenderCmd, or have the RenderCmd be a struct containing the vao_id and vertex+index offsets AND the buffered states, or some other idea.
Piece by piece, you can do this; just focus, learn, and always be open to failure. Failure is always an option. Failure is progress. Success is just a reward for your consistent failures and the information you learn from them. This is a positive affirmation. Failure is positive.
I love you. You are capable of this
:3
[12/11/24]
Thank you, past me. Okay, let's do this! I think I can get back to my thought process pretty quickly, actually. Based.
I'm also biting the bullet and changing Actor from struct to class; it needs private functions (and potentially variables).
Sidenote: sanity.hpp might be causing the long compiling times (or just all the included header files in general). Check if gatekeeping header includes behind an #ifndef would break the program or benefit the compile times and also check if aborting use of sanity.hpp would likewise benefit compile times.
Addendum to sidenote: You can (and probably should) use include guards (#ifndef) in source files when including header files (the examples I'm seeing are specifically for custom header files and not libraries so experiment with these). Since, to the compiler and linker, it really doesn't matter which file includes a header as long as one of them do, this should benefit compile times *and* satisfy clangd.
Secondary sidenote: Also, you can use #else to forward declare things that other files need but would cause cyclic #include errors (i.e: class A needs class B but class B needs class A). This is actually a problem I've run into and could use this solution.
I'm also making a change to render storage and instead of using commands, I'll just store pointers to the meshes since they're the exact same thing in this context. As it is right now, the Mesh struct is just passing data to a new RenderStorageCmd struct and then pushing that to the storage commands vector. The engine shouldn't have such inefficiencies; however, a higher-level application that interfaces with the engine (e.g the Godot editor, the Unity workspace, etc) can have such inefficiencies as they serve to benefit human interaction (i.e: a higher level Mesh class that just passes information to various structs withing the engine is fine in that context as it leaves these inefficiencies out of the engine and they exist to serve the human interface).
All that to say: I'm replacing RenderStorageCmd with Mesh as they are functionally the same.
Had to pump the emergency brakes for Dexter; he feels like shit and he's more important to me than this. I'll make inline comment notes on things but I'm only giving myself five minutes for this and I'm down the last 55 seconds. Anyways, it's fine; the idea I have is to replace RenderStorageCmds with Meshes since they serve the same purpose, and the rest will be in the comment(s)
Cya later!
:3
[12/12/24]
Alright, time to finish what I started yesterday (hopefully, at least).
Idea: in the Actor constructor, instead of making an empty RenderCmd, what if the constructor was passed a reference to the Actor's mesh and the RenderCmd constructor handled all the data storage?
I'm getting rid of RenderCmd and just iterating through a list of Actor pointers. This is because the RenderCmd structure is just over-engineered for my simple and beginner-level knowledge. It's also more suitable for multiplayer and I'm not touching that with a ten foot pole yet. I also plan on re-writing most, if not all, of the engine when I'm done, so this should be suitable for now.
Okay, after a lot of coding I have this: the render function works, the Space struct works, the multithreading works (and I have yet to run into a race condition bc I used a mutex lock_guard), and the only thing that might be broken is the vertex+index data. Basically, I don't use any offsets when rendering vertices but I don't see anything wrong, however, since all the testers use the same cube verts and indices, it could look fine but they're all using the same exact vertices (when they shouldn't).
[12/13/24]
OK, here's a list of things that need to (or that I'd like to) get done before I can fully start making a game and working on things other than rendering:
- Render interpolation (!!!)
- Textures
- Mesh/model files (.obj, .glb, etc.)
- Addition of using a sprite instead of a mesh (with toggleable billboarding?)
Also, I'll move the rendering loop to it's own thread.
Oh, another important note: I currently only use one vertex and one fragment shader each for all rendering right now; I'll definitely start using more than one for different types of objects and whatever else, so I'll need to keep in mind while building the rendering pipeline backbone that it'll need to accomodate multiple different shaders at some point.
Running into issues with the GLShader class and multithreading (mainly abstraction, actually). However, it leads me to a good question: why am I making a GLShader class object in the main file still? Also, do I really still need a GLShader class?
I'm fucking stupid holy shit. I wasted almost an hour on an error that was being caused because I put the fucking generic_shader instantiation at the top of the main file BEFORE GLFW OR GLAD EVEN GET FUCKING INSTANCED.
Pissed off lmfao.
Note for later: put the mouse callback and input handling functions in "r_common.cpp" and any time a context changes to another window, assign/reassign the neccessary callback functions, i.e: assign the new window's mouse callback function to the global mouse callback function in "r_common.cpp". This also allows me to make different callback functions and assign specific ones for specific purposes/windows. Definitely less important than the rendering stuff, but still a good note.
Oh, also, I'll have to write a higher-level key-handler system at some point down the road. (https://gamedev.stackexchange.com/questions/150157/how-to-improve-my-input-handling-in-glfw) is a good reference, as well as (https://stackoverflow.com/questions/46631814/handling-multiple-keys-input-at-once-with-glfw)
By the way, I used this (https://stackoverflow.com/questions/20390028/c-using-glfwgettime-for-a-fixed-time-step) as the structure for the current game logic function and tick update tracking.
Fuck, man, I wish I could make this work but I need to practice restraint with my perfectionism and flow-state/drive.
Only notes for next time I pick this up are:
work on interpolation; it's so close, but something is messing up. could possibly be that the buffer is either not working or is working against the interpolation (because I'm doing it wrong). It does go smoothly at first (when the previous state buffers are empty), but then it breaks.
You got this; I love you.
:3
[12/15/24]
I picked up a shift for today (Sunday) from 8AM-4PM. It's currently 10:11AM.
Anyways, I think that my problem was that the interpolation code was written assuming that the largest difference between FPS and TPS would be a single tick behind at worst (which is the case for using interpolation) which is why my tests weren't "working". If I want pure interpolation for any value of tickrate and framerate, I'd have to use some real fancy and over-engineered code (at least for my purposes).
So, I'm leaving the interpolation as-is and hopefully we won't see any problems; however, put a pin in this for the future as I would like to have it working like GZDoom's interpolation, where a super low tickrate results in a "slo-mo" effect.
I've also implemented a ticker that counts up from 0 and resets when it reaches the tickrate number. This is then supplied to the Actors' Tick functions as "current_tick". As a side-effect of this, I assume, tickrates must be integer values.
I'm also going to set the default tickrate for my use case to 120 TPS; this should be high enough that interpolation won't be a problem at very high refresh rates, if my assumptions about it are wrong. It's also a nice, smooth tickrate that modern machines should have no trouble handling, especially since my shitty 2016 laptop can run it fine (for now). However, performance is the most important aspect, so I should write my code keeping in mind that I might have to adjust the tickrate in the future, and thus adjust every value linked to it (i.e: movement speed and such).
Okay, so I've put almost all of my custom functions inside one of the two loops. Rendering, storing buffers, etc are all done in a loop (instead of storing buffers being done once, outside the loop). Now on to one of the things I wrote down to do lmfao. To be fair, I have done (I think) proper interpolation, so that's one done.
The list is now as follows:
- Textures
- Addition of using a sprite instead of a mesh (with toggleable billboarding?)
- Mesh/model files (.obj, .glb, etc.)
Also, fuck you, Dexter, the idea of renaming "Space" to "Theatre" is fucking brilliant (because of the "Actors") and I'm mad I didn't have that idea...
And I'm gonna use that.
But fuck you, regardless.
I love you, Dex <3
The list is now:
- Addition of using a sprite instead of a mesh (with toggleable billboarding?)
- Mesh/model files (.obj, .glb, etc.)
- Better texture loading (basically just more than one texture)
While fiddling with player movement, I've realized that I want to do inputs during the gametick loop and I want to have it be like DOOM/Quake/Source esque where when you hit a key, a command is sent that the player reads and then does (i.e: +jump). My system would be a lot simpler than their implementation (mostly because of the skill gap), but the relative premise is similar.
Oh, I also removed the delta_time I accidentally left in the player movement function. Woops.
:3
[12/16/24]
First thing I wanna do today is give Actors the choice between using a mesh or a sprite; billboarding will be an option, but will be off by default. This is because in true, hardware 3D it looks wonky for most default cases (i.e: enemies) but is very useful for particles and such, so I'll leave the ability in (plus, options are always nice).
An off topic thing I'd like to do is download the Godot repo, compile it, and then make some custom tweaks to it for fun and for learning and maybe even try to find things I could optimize (as practice; I don't think I'm near that level yet, lol).
--------> Big note!!!!
static variables in a function can be changed, but will only stick around until that function ends and this means they will only be created once. Here's a huge example of how this can be used:
:::C++
for(int x=0; x<10; x++)
{
for(int y=0; y<10; y++)
{
static int number_of_times = 0; // number_of_times only gets set to 0 the first time and won't get reset
number_of_times++;
}
}
:::
Okay, so I've been dicking around with sprites and while getting them working I made a huge realization: R_StoreBuffers should be completely re-written because the whole Mesh vector idea was a holdover from when I was considering the whole RenderCmd and RenderStorageCmd idea. Now that I'm not doing that, R_StoreBuffers has so much bloat and is also not fit for my current implementation of rendering and buffering.
I'm going to re-write it to instead do what R_Render does at first and bubble sort the current_theatre->actors vector, then go through it and generate+store buffers. I'll also make sure it can handle Actors being added to the Theatre after the first load, and if all of this is done correctly, R_Render won't need to bubble sort the vector every frame! This will potentially remove a ton of overhead (depending on how straining the modified bubble sort actually is, lol).
I'm stuck and I don't know what's wrong. I think that my sorting algorithm is wrong, because I'm trying to sort a vector full of pointers but I don't know how to do that properly and I'm just having errors everywhere. It's okay, I'll take a breath and attack this later, probably tommorow.
Such is the life of a programmer.
:3
[12/17/24]
Alright, time to pick up where I left off.
Okay, so my main problem was that I was trying to sort a vector of pointers and that may or may not have been working but I really didn't know what it was doing bc pointers are annoying. However, instead of sorting the vector directly, I can sort a vector of indices and then render Actors based on their index (kinda like with VAOs).
Never mind!! I can sort a vector of pointers!! I just had to fucking learn how to use std::sort (goodbye, bubble sort, you will be missed)
Also, I think I might be fucking stupid because if my original problem was that nothing was being rendered... I think it's because I accidentally forgot to flip on the flag to start rendering...
Hmm... I quick-enabled it and nothing's being rendered still.
Yeah I'm stupid... in R_Render and R_StoreBuffers, I compare actor->vao_id with current_vao_id; in order to avoid copying code, I decided to set current_vao_id to -1 so that it would "switch" VAOs to the first VAO at the top of the loop on the first go around... only one small problem...
actor->vao_id is an unsigned integer.
Okay, I'm all done with that fucking shit; I actually have something else I want to do, however, and that's make a separate struct for Theatre geometry/terrain/environment. The reasoning is that it should be separate from Actors since it's always just a static mesh and making it a separate struct means it won't have to be put in a giant Actor class full of code it doesn't need.
Also, quick note about static functions (copied from StackOverflow): "static functions are functions that are only visible to other functions in the same file..." (https://stackoverflow.com/questions/558122/what-is-a-static-function-in-c)
Aight, I wanna name the different parts of Theatre geometry "Flats", but currently in my code there's no need for a "Flat" struct, since that's more of an editor term (i.e: brushes in Hammer) and it'll get turned into one big fat geometry Mesh.
I do need a name for a Theatre's geometry, though.
Mental note: dribs and drabs (DAD)
Also, this link is a good reference for functions that change the variable passed to them as an argument.
(https://stackoverflow.com/questions/11736306/when-pass-a-variable-to-a-function-why-the-function-only-gets-a-duplicate-of-th)
So, the way I render things is flawed; currently, I sort and iterate through a list of Actors when all I really need is their Meshes. However, if I made a list of Meshes, it would be an arbitrary list for no reason. I may just have to bite the bullet on that one. The reason I wanna change this up is because A: Actors shouldn't be involved in rendering (or have vao_id pointers), and B: all Theatres will have a bunch of Actors and one Mesh for geometry, which complicates the current rendering process. I could involve typenames but that would be a bandaid fix.
Okay, I have to go, but I've solved the problem: implement ownership between a Mesh and its Actor, then in R_Render, instead of actor->previous_state_buffer... I can use mesh->owner->previous_state_buffer. It also solves the sorting problem! No need to sort Actors!
Time to go take care of Dex :3
[12/18/24]
Okay, so I've finished up most of the ownership code, but there's a problem: when rendering, I expect the current Mesh's owner to be an Actor, but one of them will be the Theatre Stage. I need to account for this by either skipping all that code if VAO_ID == VAO_FLATS or if the Mesh's owner is NULL which honestly is the better choice, since it also accounts for orphan meshes which is a nice way to prevent crashing (I should also throw an exception or at least print out a message stating why you're not seeing that Mesh).
It works!! Now time to go through and remove any unused code and do some spring cleaning before pushing this to prod real quick to get a working build up there.
Theatres should have a vector of Actors, not a vector of Actor pointers; Actors should be created by the Theatre and stored within that Theatre. This is a bit higher level than what I have right now, since this is pretty much Godot Scenes/Root Nodes or Unity Scenes but I think it's a very important high level concept that should at least be started now.
Hmmm... should I actually have there be a global Mesh vector that holds every single mesh in the current game (e.g: WAD), then every Actor has a reference to the Mesh that they use? Because currently, I'm doing this in a way that I don't think is very efficient because I don't think that if you make a clone of an Actor that it should also make a clone of that Actor's Mesh since it's the same exact data regardless.
Yeah, because the whole idea of "oh but it's easier to just make a Mesh in the Actor constructor instead of arbitrarily making it in a vector and giving the Actor a pointer" is only relevant if that's ALSO how the (to be created) editor would show it, but the editor can show you whatever it wants to and then at the end of the day, do things according to the engine. Meaning, with this implementation, the editor could (and I'm stealing from Godot's editor here) only let you make Meshes inside of an Actor, but behind the scenes it's actually making a Mesh, putting it in a global vector, and giving the Actor a reference to that Mesh. Yeah, I like that a lot.
Now, do I want to separate Sprites and Meshes? Separating them would look nicer and be easier to manage, but combining them would be more efficient...
For now, I'll keep them combined.
Nope, lol, I didn't realize that you can't combine types in vectors, even if they're parent/child due to object slicing and general nastiness (at least without pointers/references which I'm trying to avoid in the first place). Separate vectors it is, then!
While I don't like how this feels like it could be combined, Sprites will probably have a lot of unique stuff in them later on that'll justify this decision.
UGHHHHHHHHH I just realized this totally fucks up the whole inheritance thing if one Mesh can have multiple Actor owners UGHHHHHHHHH.
There may be a solution! It might not be that bad to nest another for loop in R_Render. Basically, the idea goes like this for R_Render:
1. Get a Mesh from meshes
2. If we're using a new VAO now, switch VAOs via the Mesh's vao_id
3. Go through all Actors in the current Theatre
4. If an Actor uses this Mesh, get it's state and render it
5. Continue for all Meshes
This could definitely be optimized, since a worst case scenario is that the nested loop goes through every single Actor for every Mesh that we use.
Also, I should keep track of which Meshes should be rendered based on the Theatre and Actors... which means it might make more sense to go back to going through a list of Actors. However, I'd like to avoid that.
Wait, I've thought of something wrong: Meshes are just bundles of vertices and indices and I don't think you can "share" them; I think that trying to would just be one Actor moving the vertices somewhere, then the next one moving the same vertices instead of having its own vertices, etc. You might have to just duplicate Meshes (which I guess isn't really duplication). I'll need to test this.
Yup! I'm right! You can't "share" Meshes (vertex+index data), at least not the way I'm thinking and anything other than that is over-engineered (for now).
So back to how it was before! Lmfao.
I've run into a problem: I'm slicing classes if I try to make a vector of Actors in Theatre instead of a vector of Actor pointers. I guess I'll just do that instead; shouldn't be any overhead, tbh, and should work fine.
I thought I had a problem where the player was rendering a mesh and thought I would solve it by implementing a render check to skip renering a mesh if the owner's gatekeepRenderer function returned true. Then, I realized that I'm still doing a form of class slicing with pointers I think because I'm pretty sure that the virtual functions are getting lopped. I don't fully know, though...
Okay, it's not; that means something else is going on.
Oh my god... I think that random thing is... the fucking geometry.
Okay, my thoughts:
Theatres are tricky, because all the hardcoding I'm doing in the main file (i.e: making actors and meshes and theatres) should be done inside each Theatre, so I'm making a new rendering function (T_LoadTheatre) that's responsible for switching theatres and notifying the render loop to start buffering and such (or could just buffer them in the function itself, tbh), and I'll be simulating pre-made Theatres with "graphxtheatre" files which are just C++ files that create a new Theatre object, some Actors and Meshes, and put those Actors+Meshes into that new Theatre. Then, T_LoadTheatre will be given a reference to that theatre and will be responsible for loading it and such. Honestly, that whole "graphxtheatre" thing aint such a bad idea as a real solution...
[12/19/24]
I'm home rn and just trying to use Blender and manual .obj editing; I think it's gonna work but it's almost 3PM so I gotta be quick!
Update: it doesn't work quite right but I have the raw data in there now so that's something, I guess. I might add the .blend file to the repo just for my laptop.
Okay, I'm at work now; I'll-holy shit my fucking stupid nokia ringtone alarm just went off for no reason at 4pm. That scared the fuck outta me.
Anyways, I'm gonna try and either fix the vertex data in Blender to be between -1.0 and 1.0, or just say fuck it and make my own from scratch by hand (in blender first so I at least have something visual and not just using my brain).
I just gave up before realizing that the problem I'm having is that I've hardcoded my vertex data to be three floats for position AND TWO FLOATS FOR TEXTURE COORDINATES I AM SO FUCKING DUMB I HAVE TO REDO EVERYTHING UGGGGHHH!!
YUP ITS ALMOST THERE GOD DAMNIT!
Aight, I'm gonna make an OBJ loader and then maybe just use it to get vertex+index data that I'll hardcode (bc even if I do support OBJ files, I don't want them to be the main 3D files used). I'll be following this tutorial: (http://www.opengl-tutorial.org/beginners-tutorials/tutorial-7-model-loading)
[12/22/24]
Hmm, there seems to be mesh problems; when using a theatre, it seems like the last mesh to get loaded overrides all meshes (similar to a worry I had a while back with "shared" vertices). Look into this later.
[12/23/24]
Aight, I'm mildly hung over and ready to figure out this rendering situation. Based!
Hmm... the mesh problem only appears when both meshes use VAO_TESTING... I set them to both be VAO_ACTORS and they had the proper meshes.
[12/24/24]
I FIGURED IT OUT!! While talking to myself just now about removing "moving parts" in my code (bits that do things that could be removed or simplified to remove a point of failure), I talked about the vao_id switching done in R_Render and R_StoreBuffers and I realized that THAT is EXACTLY what's wrong with the VAOs! If the next vao_id is > 1 index from the last one, it just goes to the next one and continues trying to render, but because I iterate through the Meshes and not the VAOs, it doesn't eventually get to the right VAO and it uses the wrong one! It's a simple fix! At least, I hope...
Half right, half wrong. I think I better understand VAO vs VBO; you should keep VAOs to a minimum, but they're used for Vertex Attributes (duh), so they're used for different vertex data types (i.e: one VAO for vertex data with only position and texture coordinates, one VAO for vertex data with position, uv, and normal, etc.). Now my question is: should I use a new VBO and EBO for each mesh, or should I use VBOs like I was using VAOs?
And I think that's what I'm gonna do: use VBOs like VAOs (and I think that includes EBOs).
Okay I have some notes on a simple version of the render process:
Render a set of indices.
Store how many you rendered for the next render call.
Render a set of indices at an offset of the number of indices you rendered last (using that stored number).
Okay, so there are a few things to note right now:
1. You can combine VBO and IBO(EBO) data into one buffer and you should!
2. VAOs are for vertex attributes (duh)
3. Buffers are for raw data
4. Use offsets
This is a big source of help: (https://github.com/fendevel/Guide-to-Modern-OpenGL-Functions)
First off, I need to combine VBO and EBO into one "Buffer". Then, follow that link above as a guide to rendering with multiple Buffers all filled with tons of different Mesh vertex data.
Aight, make one buffer and allocate some size of data that seems not too big but not too small. Then, if it's filled up, make a new buffer. This means I won't have a set of specific buffers but that's fine.
OR I could do a set of specific buffer vectors and start off with only one buffer in each; give them a certain amount of storage and make new buffers when they get full. This seems like a good compromise.
Of course, that's kinda expensive so I could just opt to make big ass buffers (i.e: in the megabytes) and call it a day (for now).
Okay this is way too much for me right now; I'm sticking to VBOs and IBOs and using a few large VBOs+IBOs for every mesh.
I'm fucking burnt out lmfao. I think that buffer condensing and modern OpenGL DSA shit is wayyyyyyy over-engineered for my usecase. I'm going to try and go back to basics. Separate VBOs and EBOs, made using older OpenGL syntax (> 3.1 but < 4.5) and very simple and easy to understand... hopefully.
ALSO HEADS UP!!! YOU WILL NEED TO KNOW/REMEMBER THAT/HOW MANY VAOS CAN REFERENCE THE SAME VBOS (SEE LINK BELOW)
-----> (https://computergraphics.stackexchange.com/questions/4623/multiple-vao-share-a-vbo)
[12/26/24]
At work early again to pick up a shift. Gonna work on finishing the graphics engine re-work.
Ooh ooh, this is important: you do need to bind the correct VBO and IBO during render-time. This is made easier if you use modern OpenGL functions (not the ones in learnopengl.com). This stackoverflow answer has a good example of how to do this: (https://stackoverflow.com/questions/40652905/render-one-vao-containing-two-vbos)
Running into some issues; what I think is going on is that either the wrong indices or the wrong vertices are being used. This could be because I'm either not binding the correct buffers during storage/rendering, the offset is incorrect, or (what I think is most likely happening) I'm not correctly binding buffers when storing them and I'm overwriting the same buffer every time.
I'm also gonna be on call with Dex for a lil bit so I won't be able to focus super hard but I should be able to fix this.
Okay, I might just say fuck it and have every single object just have its own VBO and IBO because even though that's expensive, for my use case (and knowledge level) I think it's fine.
Aight, the rendering pipeline is fully functional again and it's doing things slightly better than before (still kinda ass tho). I'd like to optimize it (and the way it is right now is actually a much better starting point for optimization), but my knowledge level isn't where it needs to be for that, yet.
One of the next things I'd like to crack at is something quick and (hopefully) easy but pretty important: player movement. Specifically, the player-camera movement is in a "flying" mode; if the camera is pointed down and the "backwards" movement key is pressed, the player-camera moves up in the world-space. However, I want it to be in a "walking" mode; if the camera is pointed down and the "backwards" movement key is pressed, the camera should move backwards instead of upwards. This just means that movement code should only take X and Z vectors into consideration when applying orientation to movement. As a side note: I might want to also add in a "move up" and "move down" set of inputs to add back in that Y-Axis movement for testing and such.
I'll also maybe start working on a "Character Controller" Actor that has gravity and collisions! However, I need to learn how to do collisions...
I didn't do collisions or fix the player movement lmfao but I did clean up lots of loose code, made the interpolation code better, and changed Actors to allow euler angle and quaternion rotation. Now time to decomp.
:3
[12/31/24]
I feel reinvigorated about working on more GraphX. I want to continue with LearnOpenGL and move on to more advanced topics. I'll make a new branch for this, so I don't contaminate my code. Hopefully I don't fuck up the notes file lmfao. The only way I could is if I merge the branches, but I don't plan on merging them. I plan on learning things and then deleting the learning branch and using what I learned to write code for the production branch.
I see a problem that I should address: I need a second VAO for handelling vertex data with vertex colors instead of texture coordinates. Yippee! More rendering pipeline stuff...
This is fucking funny... I do actually need the vao_id, LMFAO!!
Mental note: the way I'm doing this isn't terribly wrong, but there's a lot of hardcoding that could be annoying to detangle if I don't make this note. Basically, while I will have a Shader for every VAO, I won't have a VAO for every Shader. This means that I'll have to keep track of one of them in the Mesh, but make sure that they correspond (i.e: VAO_COLOR would be the index for the VAO that accounts for vertex colors instead of texture coordinates, but it would also be the infex for the Shader that does the same). Maybe change this entirely down the line (why have multiple VAOs when I'm assigning the attributes and enabling them in the render thread? Obviously I'm doing something poorly).
LMFAO IM STUPID. thought i had a bug but no i just put the wrong verts and indices into one of the meshes in the theatre.
STOP GETTING HUNG UP ON THE PLAYER CAMERA ITS FINE!!
Okay, all done! Now lets actually do the fucking thing.
And this is just to be able to differentiate the production branch from the LearnOpenGL branch
:3
Oop, note: I'll have to move the vertexattribpointer functions into the switch statement with the way I'm doing things right now; yes, this will have copy paste code but that's fine for now. I'll also have to check if you can do that before binding the VBO+IBO or not.
I also want to rename and combine the shaders like I did in the LearnOpenGL_Advanced branch
:3
[01/02/25]
Caught a huge oversight/mistake while working in the LearnOpenGL_Advanced branch! Here's a copy-paste of the notes from there:
"Just caught a huge oversight/mistake: I had a ticker counting every tick but it was actually ticking every CPU clock cycle (or frame idk which)! I had it in the wrong part of the loop! Fixed it and saved a lot of future headache! I'm actually gonna fix it in the Production branch rq lmfao."
[01/05/25]
Gonna work on updating the Production branch to have Phong lighting (and some other features I added in the LearnOpenGL_Advanced branch).
[01/06/25]
It snowed a lot! I'm at home rn (it's 1:05) and I'm continuing with updating the Production branch. I'm implementing Environment and Material structs right now, but there's a problem with the renderer. See, I only have one VAO since (for now) I only have one format for vertex attributes. This means, though, that my little hacky solution in the LearnOpenGL_Advanced branch for making sure the current_light pointer was always defined before being used won't work. The hack was that light meshes would use a unique VAO that would get processed first in the renderer, thus the current_light pointer would always get set. This, however, shouldn't fly in the Production branch. This, however, leads to me realizing that I don't really need to sort Meshes anymore... I need to sort Actors.
So we've come full fucking circle. I'm going to go through the Actor pointer vector in the current Theatre instead of the Mesh pointer vector. However, I need to account for the Theatre Stage/Flats Mesh not having an Actor owner. Maybe I still use the Mesh vector but I sort it by mesh->owner->type?
Just finished getting the snow off my car; realized two things. First thing: I'm stupid and didn't have a light Actor in the Theatre and that's why it was crashing. Second thing: I should actually make sure that the engine doesn't crash if there are no lights in the Theatre.
Okay, so, Theatres have a vector of Actor pointers. This means that if I were to check for and sort by Mesh->owner->type, it would always return ACTOR_ACTOR. I need to either mess with casting, give meshes a type, or figure out why this is happening even though LightActorGeneric and Actor both have the same variable "type". Maybe I should define it in the constructor of LightActorGeneric instead...
Okay, I've actually figure out a fix: LightActorGeneric has to set "type" inside the body of its constructor. The light's Mesh has been fucked, though, for some reason. I don't know exactly why, since the other Mesh is fine and they should both be using the cube models, but I'll check rn.
Ah, it's using the default Mesh constructor which uses the ERROR models, which isn't updated to use the new vertex attributes format I chose. I could actually use this to make a new VAO and make R_Renderer not just use the one semi-hardcoded format. However, I have roughly 30-45 minutes to do this before I have to start packing up for work, so I'll be against the clock. As well, the Phong shader requires normal values. I think I'll just update the rest of the models to match the new format.
Aight, I updated them! Haven't tested how they look, but I assume I'll have to redo them to "decompress" them so the normal values (and texture coordinates) don't get distorted. For now, though, they don't/won't crash the engine lmfao.
[01/06/25]
After writing some notes in the LearnOpenGL_Advanced branch, I realized that they should probably be added to the Production branch's notes, since the changes they suggest are more suited to this branch. Here are the notes:
It snowed today! I'm at work and I'm about to continue with LearnOpenGL's lighting shit because after Materials and Lighting Maps, it'll get to Light Casters and Multiple Lights, which I really wanna get to hopefully today or tommorow :3
I'm gonna eat rq but I was thinking about Textures and how I'll have to make them a struct because I'll be learning about adding things like normal textures and roughness textures and such to Materials, and while thinking about that I remembered how generating Textures would always fail if done in the game logic thread instead of the render (main) thread. That made me think and semi-realize that what may be happening is that it failed because in order to access GLFW/GLAD/whatever I needed to access, I can only access it in the thread that it was instantiated/called/whatever. I'll have to read up on GLFW/GLAD again and look up how multithreading works with them, but I have a hunch that I could actually keep the Texture generation in the game logic thread if I "told" GLFW/GLAD which thread to use or something like that. I want to do this because if I'm gonna make a Texture struct, I'd like to keep Texture generation in a constructor instead of a separate function that I need to call somewhere else, and since Textures would be generated once they get loaded and that's usually in the game logic thread, I need to figure this out.
Oh my god, wait, I think I might know the reason for the Texture thing: Meshes get created and added to their Actor and Theatre when that Theatre file is included, which is at the top of main.cpp and definitely far before any GLFW/GLAD initialization. I'll either need a way to automatically have Textures know when it's safe to generate their texture data or have a function and call it in one place (R_GenerateBuffers or R_Render).
Another idea is to ditch storing Actors and Meshes in their Theatre as a vector of pointers and instead store them in a JSON file or perhaps a header file that gets loaded when the Theatre is ready to be loaded; that or make texture data generation happen in the constructor's body, so it's not called on compile-time (I think) and then only instantiate the Theatre when it's ready to be loaded. I'd need a way to load that Theatre without it being compiled, which makes me consider using a form of "map files" that get loaded and processed. Basically, instead of Theatre files being simply a glorified header file that defines a Theatre and all its Actors, it would have data that is read by some sort of Theatre loading function, and that function would construct the necessary Actors and Theatres and all that jazz. That way I can keep stuff like texture data generation in the constructor, since I'd be calling that constructor only when I know it'll work (i.e: after GLFW/GLAD instantiation).
I might be able to do this with a struct or something; e.g: for Actors, I could use a struct called "ActorOptions" or something that has all the data for creating the Actors (i.e: which subclass to use and what to set all the default values to in the constructor). Then, my TheatreLoader function would construct Actors with data from that Struct. I'd just need to figure out how to make the function create the right subclass (maybe use a Template, instead!).
This also might be something for the Production branch, so I'm gonna copy-paste these notes over there rq.
[01/16/25]
Welcome back, Production branch! Time to update this bad boy with all the shit I did in LearnOpenGL_Advanced; mainly the lighting stuff, but I also made some changes to various parts of the engine that I'd like to fully implement, as well as made some notes for other features I didn't include but want to in the Production branch.
Okay, first hour over and while I didn't get a lot done in terms of amount of code written, I did get a lot done in terms of refactoring and planning out what I'm gonna write and how. Lets see if I can get the rest done faster.
I'm overhauling the Material struct so it can use textures for its properties but will still be able to use simple data values if no textures are specified.
I'm going to try and refactor the GLShader code.
I don't need the shader error handler function, since I have (i think) full OpenGL error handling in the main file.
GLShader is now a struct and now uses a Template "setUniform" function to handle all uniform changes. This means that I can use an unordered map (bascially a dictionary in C++) to do bulk uniform assignments!
Ooh, but since std::unordered_map can't just take arbitrary values, I might need another template function or a template class or just a new struct with clever functionality.
Or, I could just not use a map and deal with setting them individually for now, lol.
Okay, finishing up; I have all the code written, but something's broken. It runs and renders, but the lighting doesn't work. However, that's an easy problem to fix. I'll do that tommorow.
:3
[01/17/25]
Time to finish this feckin Production branch update!
Hmm... the shader isn't processing lighting because certain uniforms aren't getting set. This reeks of my problem in the LearnOpenGL_Advanced branch where uniforms weren't being set because of an int not being set to a string but no errors being thrown.
It could also be because I SET THEM TO FUCKING 0 AT THE TOP OF R_RENDERACTORS GOD DAMNIT!!!
Hmm, still a problem: the flashlight spot light isn't getting used but it is being rendered. Is the array not being properly updated? Is "spot_lights_count" wrong? Is there a minor bug in the shader code that's there because I tried to refactor too much at the same time? I'll find out.
Or it could be because I forgot that I didn't set the values specific to spot lights in R_RenderActors... man...
Aight, finally fuckin' fixed everything.
I've made a realisation: Meshes don't need their Actor *owner variable anymore...
I've finished the moving Light! I also want to be rid of the Actor *owner variable in Mesh; I don't need it and if I use a Mesh pointer in Actor instead, I can make a couple Meshes and then distribute them to many Actors. This will cut down on the cost of buffering Mesh data. Here's some pseudo-code:
:::C++
//
// test.graphxtheatre
//
//...
Mesh test_mesh(/*...*/);
Actor test_actor("test actor", &test_mesh/*...*/);
Theatre test_theatre("test theatre", std::vector<Actor *>{&test_actor});
//
// g_actors.hpp
//
class Actor
{
//...
Mesh *mesh = NULL;
//...
};
//
// r_rendering.cpp
//
R_Render(/*...*/)
{
//...
for(Actor *actor : current_theatre->troupe)
{
//...
actor->mesh->doStuff;
//...
}
//...
}
:::
Fun stuff!
[01/19/25]
Ok, I'm gonna work on switching to Mesh pointers in Actors. I'm also thinking of making the Production branch the main branch, since that's kinda how that works, now that I think about it.
[01/20/25]
I'm gonna try to work on embedding resources into the binary so I the binary can run by itself with all the resources inside (i.e: texture images, shaders, etc). I'm gonna take a look at "XD" (eXtended Dump and load utility). However, the #embed directive is in C23 and while I've had a bugger time with C++23 and #embed, I think embedding the files in a .c source file is better than wrangling a fucked C++ implementation.
Aight, so in order to use #embed, I need clang19... and that requires me to compile all of clang19... all 11060 files... if it's not close to done when I'm finished eating these burritos, I'm finding a different method of embedding resources into my binary. Ah hell nah, yo ass tweakin', Jigsaw!!
Yeah, fuck compiling allat. I'm finding a new way of doing this; shame, but once clang19 becomes the new defacto version (i.e: I CAN DOWNLOAD PRE-COMPILED BINARIES!!), I might change the code to work with #embed.
okay i almost have it bt the problem is that the fucking embedded .h file has fucking problems with being included too many times or not enough times so fuck it im done today.
[01/21/25]
I've got embedded textures working! However, a few more steps to take before the second alpha is fully done:
- Account for Materials with less than both textures (e.g: no specular texture, color data only, etc)
- Windows implementation, eughh
For the first one, Materials can always have a specular texture since in the constructor that doesn't take a specular texture, specular strength is 0.0f, and in the constructor where it takes a specular strength but no specular texture, the default texture will be "NO_TEXTURE" which is just a white jpg, which is essentially a glm::vec3(1.0f). Before starting on Windows embedded resources, I also want to automate the embedding of the shader code. That should actually be pretty simple (in concept) since shader code is parsed as a giant string.
Compiling an executable on Linux using xxd works! This means that I can use xxd.exe to do it on Windows (theoretically)! I just need to test that theory on my dual-booted Win10 install.
I think I've figured out a style/direction to take GraphX into: a modern game engine designed to simulate nostalgic games from the fruitiger metro/aero era. all the charm and nostalgia of older games but with modern sensibilities, capabilities, and improvements.
I've just learned something about reality. If you bathe your room in pure red color, red things appear to be white but that's actually not the case. They aren't "turning white", they're just bouncing more of the light so they appear brighter than everything else in the room but only because the whole room is bathed in that light, so everything is reflecting or absorbing that light. It's relative. That's cool.
[01/23/25]
Back to work on collisions! I'm going to ignore physics and work on just collision detection. Physics will be implemented with an external engine like Jolt or Bullet.
Ooh, real quick I wanna add a new default "Stage" Mesh to Theatre that's a 3D axis with colored axes. I'll do this later.
So I wasted all this time trying to bugfix the collision code... when the Colliders weren't even having their positions changed to match the Actors... man.
Refactored code to remove all temporary Actor stuff (e.g: removed Actor->sleeping)
[01/24/25]
Back to collisions. I want to try and implement a simple gravity system. I might take inspiration from Godot and make Device much more like a Godot Node, letting Devices give Actors certain abilities/behaviours. Either way, I want to fully implement the Collider into Actor and try to keep it as a generic std::vector<Device *> or std::vector<Device>. Then I'll make a very simple gravity implementor (either Collider or a variant of Collider). Then learn how to use Jolt.
Aight, to avoid a shit ton of for loops, I'm gonna use an unordered_map instead of a vector to hold Actor Device pointers; that way I can give them a unique identifier and search for certain Devices like a Collider much more quickly.
Currently, the code will only support having one Collider at a time, but I could expand it by having all Colliders combine(?).
Or I could use a multimap! I'll keep it simple for now, but a multimap sounds like it's best for expansion.
Note: yes, change unordered_map to multi_map and make collision checks go through every Collider in each Actor when checking collisions.
Collision is implemented and a rough draft of gravity is also implemented. I tried to get it to work on the player but it doesn't yet. Part of the problem is that the movement will override the gravity changes because of how it works with orientation_front; I've reverted movement back to normal which includes this problem, so be mindful of that when next working on this. However, it does work on a cube. It currently is in a very rough draft stage but it does work. Jolt implementation was started (i.e: copying the Jolt Hello World) but not finished. I'm very fucking excited!
:3
[01/26/25]
Note on Actors and physics: currently, if a new Actor type inherits PhysicsActor, it always gets a box rigidbody that changes its position and rotation. This means that, for instance, the player can't derive PhysicsActor and has to have its own special code in g_theatre.cpp and custom init and Tick functions.
[01/26/25] (at 12:26)
Fucking finally! I have (poorly) implemented Jolt Physics into GraphX! All PhysicsActors now create box physics bodies for themselves and are affected by gravity. Note that the floor is currently hardcoded, because the current implementation of physics is very bare bones and I haven't created distinction between static and dynamic bodies in PhysicsActor. This took a while to fucking wrap my head around, but I am so happy that I have Jolt Physics in GraphX!!
Shame that my custom Collider code will go unused but that's fine by me if it means I don't have to fucking create my own physics engine from scratch (that will be for a later project).
Added in physics rotations at the last second!
[01/26/25]
Note: rotation is fucked up currently because (i think) in updateRotation, I convert from JPH euler angles to glm euler angles and they may be in a different order. Check that. Maybe not but for some reason, rotations are wack when updateRotation sets the euler angles to be the quaternion value (i think).
Oh, also, don't forget to compile libJolt.a with mingw-cmake on Arch linux for executable compiling.
Note: The next thing I want to do is make a dev console. I want to be able to, for instance, change properties of certain Actors on the fly. Using either their name and/or making a new id system.
Hey also, the scale of Actor is the half-scale of, say JPH::CapsuleShape.
Oh, also, the player movement is not correctly following the facing direction; fix that, lol.
Aight, at work now; I'm gonna abstract the camera into its own class. I also want to abstract usage of Jolt so that way I can isolate all calls to and from Jolt into just a few functions/structs/classes.
First, though, I want to clean up and refactor Actor and its subclasses. That'll make everything else easier. I think I might switch to a different branch for that, or just comment out all Jolt stuff while I refactor.
I'm also going to delete g_state.hpp finally; it has an old comment that I'd like to keep a record of, so here it is:
:::C++
// g_state.hpp
/*
State
-
i know that i'm going to need to compare states to either check if it's changed or to confirm i'm using the same state (mainly for double buffering).
the problem is how i'm going to do that. one idea was to implement a sort of "hash" creator function that would generate a unique number based on
all of the state's variables. however, this won't work if literally any of my variables are floats (which many will be), since in order to not cause
the hash generation to be gargantuan, I'd have to know a lot more about hash generation and I don't wanna spend all that time doing something barely
useful in this situation.
instead, i'm going to implement a simple check, like a flag that gets set when the state is created, for code that doesn't care too much about the
inner details of that state (like exact location, rotation, etc), OR include an inner state machine that can be customized, like for enemy and player
state regarding higher-level game mechanics (like, "crouching", "standing", "shooting", "chasing player", etc).
but that won't be all; i'll also implement a way to check on certain details for code that really needs to (i.e: the gametick interpolation logic).
this could just boil down to testing the inner variable "global_location" and I don't have to write any custom checker function. that sounds nice,
but it also sounds too easy, so I may be misinterpreting how this kind of "state" is supposed to be used.
I could also use pointers? like, can you make sure that an object's state hasn't changed by comparing the pointer to that state with the previous
value of the state's pointer? I'll have to check.
this also means that I'm going to need to know for sure whether or not I want to make this "state" struct mutable or immutable.
I could also try learning/using Templates.
I could also make an abstract "State" struct, then make inherited structs for more specific cases (i.e: ActorState, RenderState, etc.).
If I do this, I'd need to make sure the abstract "State" struct is where I keep all the generic functions, variables, and constructors; which means that
the abstract "State" struct is where I'll need to write any generic state checking functions and also account for any number of variables. This is where
Templates could come in handy.
*/
:::
I might also add a simple implementation of an ID system for Actors.
Okay, on refactoring:
- I want to move away from Theatres being header files so I can have better instantiation
- if it could work like imgui or Jolt, that'd be sweet
- I want to be able to create Actors/Meshes/Materials/etc, populate their specific variables, and initialize them before doing any sort of buffering or rendering actions.
I think refactoring is almost done, just the fucking player head rotation is fucked bc I'm trying to use Quaternions and I'm fucking shit at using Quaternions. Anyways, time to decomp.
[01/28/25]
I'm doing what I should've done yesterday. The camera Actor will use euler_angles, but will supply/use a quaternion for rendering/orientation.
Also, huge note: uhhh I forgot that euler angles in GLM are supposed to be radians... they're degrees by default.
Oh my fucking god... was I... was that the problem?
Okay, finally... I'm a bit skeptical of it, but I've implemented a very basic PlayerCamera class that GraphXPlayer will have and use for all viewing-related things. It's decent but may need a little more refactoring. As for the rest, I'm pretty happy with everything so far and I think that tommorow I'll start integrating Jolt back into the codebase and refactor everything a bit more.
:3
[01/29/25]
Okay, time to continue refactoring while slowly integrating Jolt. I think I can hopefully get this up to if not past the point it was before refactoring.
I set up the Jolt boilerplate/setup code; Jolt source files will be prefixed with "j_", but the common header file for all Jolt code (for now) will be "g_jolt.hpp". I'm tempted to start work on my custom string-based JSON-like Theater setup file syntax, but I will hold off until I am fully done with Jolt integration. I'm going to split up my code for Jolt integration into at least two source files: j_boilerplate.cpp for the setup/boilerplate (duh) and then from there I'll put my custom and original source code into other files starting with "j_common.cpp" (or something like that). As much as it pains me, I want to keep all of the Jolt header code in one header file (or as few as possible), especially because of the header files that I'll need to include in every source file that uses Jolt (e.g: Jolt.h).
Okay, small detour from Jolt integration to refactor some more; I'm taking all class constructor definitions out of the header files and putting them into the source files (at least for Actors).
Note: for primitive shapes, you can just call constructors for them directly (i.e: JPH::BoxShape), but for custom shapes, you have to create a specific shape setting, but you don't have to save the shape setting, so no need to make a generic JPH::ShapeSetting pointer member in PhysicsActor. Just abstract the settings values. Although, it may be easier to actually go with a generic pointer member and just supply it before initializing the PhysicsActor.
Actually, yeah, I'm gonna go with the generic pointer member since I shouldn't abstract just to abstract; instead of having a whole bunch of input variables that would just get put into a new specific shape settings object just for that to be trashed after the collider is made is dumb.
Note: keep JPH::ShapeSettings::SetEmbedded() in mind as it might be used to keep static colliders like the world floor from being freed. Here's an excerpt from the Jolt HelloWorld.cpp:
:::C++
// Next we can create a rigid body to serve as the floor, we make a large box
// Create the settings for the collision volume (the shape).
// Note that for simple shapes (like boxes) you can also directly construct a BoxShape.
BoxShapeSettings floor_shape_settings(Vec3(100.0f, 1.0f, 100.0f));
floor_shape_settings.SetEmbedded(); // A ref counted object on the stack (base class RefTarget) should be marked as such to prevent it from being freed when its reference count goes to 0.
:::
Okay, I have abstracted collider creation a bit, but each PhysicsActor will have a vector of "colliders" (Jolt Bodies) that will be created from at least one jolt_collider_options struct; this is a custom struct I wrote that just holds a JPH::ShapeSettings pointer, a JPH::EMotionType value, a JPH::EActivation value, and a JPH::ObjectLayer value.
[01/30/25]
I'm getting burnt out; I need to take a break. When I come back, big takeaway should be: Don't waste time learning Jolt inside and out for GraphX; maybe for the next engine, but for this one just do the sloppy HelloWorld integration. Everything in the main.cpp file, JPH:: functions in random files, the whole nasty works. Just get this shit out the door. It won't be pretty, but that's okay. This isn't your magnum opus so stop treating it like one.
[01/31/25]
I just fucking put all the Jolt stuff in the main file... and it compiled, ran, and had correct active physics on the first try. Fuck my perfectionist life.
Aight, I'm thinking that I still want to abstract Jolt functions and calls aside from the setup and shit in the main file. This means keeping J_AddAndCreate and J_RemoveAndDestroy.
Ughhhh wasted time again. Watch yourself, man, you're getting pigeon-holed. I don't even need to make a specific type of shape settings object, I can skip over that to JPH::BodyCreationSettings.
Yeah, the jolt_collider_recipe abstraction is unneeded. Just use JPH::BodyCreationSettings...
I have lots of errors but I need a break. Tomorrow, here's what to do:
Decide whether I want to create a full JPH::BodyCreationSettings object in the Theatre or create a partial one and have the PhysicsActor decide whether or not to override its Shape (for default BoxShape colliders and such).
:3
[02/02/25]
I want to do the first option: create a full JPH::BodyCreationSettings object in the Theatre.
I'm also implementing a way for PhysicsActors to create a default BoxShape with user-supplied JPH::BodyCreationSettings arguments (see the floor and wall Actors in the collision testing Theatre).
Okay, so, JPH::BodyCreationSettings objects can only be created after I've set up Jolt, which of course is done in the game logic thread function. This means I can't #include the Theatre header before the function but I obviously can't #include it in the middle of the function or after it. My current duck-tape solution is inside the Theatre header, I make a custom struct that has all the objects in it, then I create a variable called "new_theatre" from that struct and I set current_theatre to &new_theatre.collision_testing_theatre. This is pretty shit and it also causes the program to exit ungracefully (probably due to some scoping or destructor shit). I want to get rid of this and the only idea I have is to basically abstract the arguments in JPH::BodyCreationSettings to the PhysicsActor constructor and store them in vectors so I can grab them in PhysicsActor::callToStage.
You know what? I might just have to make that custom file format...
Oh my fucking god, I can use std::any to avoid using std::vector<Actor *> troupe. I think. I don't want to fucking convert all my fucking code, so I'll give Theatre a second vector of std::any (which would be for Actors). The main reason is that I need to make sure all the classes in a Theatre get kept alive.
[02/03/25]
I'm going to continue with the interpreter, however, making a note that I need Theatres to store ALL the data that they require (i.e: Materials, Meshes, etc.), and/or give GraphX the ability to store certain objects globally (i.e: for a Material that's used a lot). Either way, I can't use pointers for everything since I'll be changing Theatre instantiation from a header file to a function, which means it'll be scoped. I need all the objects in a Theater to stay alive, so I can either pre-allocate a maximum number of them (not a good idea), or figure out a way to keep them alive dynamically; std::any seems like the perfect solution... which is why I'm skeptical of it. It seems too good to be true, so keep an eye on it.
BIG NOTE: a prediction on a possible failure point for std::any would be that under certain conditions it doesn't keep its objects alive (i.e: somewhere deep in std::any, it's using references/pointers or some shit). This is just a note of "hey I have a guess at what's going wrong IF anything goes wrong".
The parser works! Mostly... the first line "Theatre [name_of_theatre]" throws it off because there are no curly brackets. The short term fix would be to just add empty curly brackets (or include all of the objects in the curly brackets? could be interesting), but I want to fix it for good.
Tomorrow.
:3
[02/04/25]
What I wrote yesterday is more accurately a "lexer" and a "parser" put together (or maybe just a parser). Either way, I need to fix that bug which shouldn't be too hard, and then I need to write the interpreter. Once this file format is done, I think I should be able to make a full on game with the pieces I have here. Sure, I'll need more derivations of Actor and maybe even Mesh or other classes, but I shouldn't need any new unique ones for a simple game. For the bug, I think I'll stick with the simple fix of just putting an open curly bracket after Theater and a closing one at the very end of the file.
Oh, also, why not just make the Theatre's name the name of the fucking file? Duh. Fixes the whitespace problem, too (kind of).
Oh my god I'm seeing the puzzle pieces as they're being put together: writing the embedding process for Theatres made me realize why using functions to access the current Theatre, switch Theatres, etc is REQUIRED! Because the Theatres will be embedded as string-data in source file format, and need to be parsed and (more importantly) actual objects need to be created from that parsed data! That means that the Theatre objects won't exist at compile time, but rather when they are requested to be loaded/instantiated!
Okay, Theatres will be embedded as std::pair<int, std::string> with the integer being the order of importance/load. This number will be written in the filename like so:
#.name.graphxtheatre
Theatre names must also not include whitespace.
Almost there! I have the Theatres correctly embedding, I have one map with pairs of integers and Theatre string data, and a second map with pairs of integers and Theatre names (as strings). The integers in both should match the same Theatre (and are taken directly from the filename). I have a printout test in the interpreter that works as well! It uses a switch statement to match vector indices in order to determine which typename a string is referring to! I'm super excited because once I finish this, I can start working on a game!!
:3
[02/05/25]
It's 11:16. I want to see how far into the interpreter I can get before 12/12:30.
I need to re-write the parser entirely. It's way too finniky. I started work on it but I need to stop and decomp.
Here's my plan for the parser & interpreter:
- Store all object definitions in a map; the pairs will be of an integer uid, and a pair of strings, one for the class name and one for the actual name of the class object.
- There will be at least three maps for the three different types of data values (C++ references, Theatre references, and raw data). These maps will have a pair of an integer uid corresponding to an object's uid, and a vector/pair. The vector/pair will contain the definition/variable as a string, and the data/value as either a string or an integer uid corresponding to another object's uid. I won't need to use std::any, since linked values will be in the "linked values" multimap.
- I'm limiting the ':' to only allow layers of Theatre references
- e.g: Mesh:Material <Cube>:<Doom_Shiny> is allowed
- e.g: Mesh:MeshData <Cube>:[GRAPHX_PYRAMID] is not allowed, a new Mesh must be made
I'm almost done!!! A few bugs with how I access the data after parsing, I don't think the parser has any bugs right now. It fucking works!!!! Tomorrow I should be able to write the interpreter portion very quickly. This is really fucking exciting!!!
Note: Theatre files must include "@<theatre name>" at the top, and all layered expressions (e.g: Mesh:Material <Cube>:<Doom_Shiny>) must always be Theatre references (the angled brackets). Also, no whitespace at the beginning of the file (which also means the embedded data, too, so I removed the "\n" from "R"~(\n" in the makefile).
[02/06/25]
Gonna work on the parser a bit while at home.
The parser is finally actually finished! Not only that, but it's added to the main file and doesn't cause crashes! It might cause crashes/memory leaks if I keep all of the maps in the global scope and start including JPH headers+objects and other things, so NOTE: gracefully move the map definitions into either functions or a struct that you can create when loading a new Theatre.
At work; time to finish this thing! The interpreter is gonna be a glorified nested if/else (or, hopefully, switch/case) tree, so it shouldn't be difficult to get the main structure set up. I just want to try and keep the design modular enough that adding to it (i.e: new classes/subclasses) will only take a couple lines of "this = that" code (header file stuff, basically).
Back in g_common.hpp to tweak Theatre... I don't want to use std::any. The solution is to use pointers but to instantiate them like this:
:::C++
Actor *test;
in_scope
{
test = new Actor();
}
:::
Or with a pre-allocated array like this:
:::C++
Actor *actors[MAX_NUM_OF_ACTORS];
in_scope
{
actors[num] = new Actor();
}
:::
I want to avoid the pre-allocated array as I'd like to keep this dynamic, but those are my two options. Actually, I wonder if I can use "new Actor" on the Actor pointer vector to the same effect?
Yeah, I need to create the Theatre and it's objects on the heap, not the stack; however, this means I need to either manually clear the heap, or use a smart pointer class (look this up) instead. If the heap overflows, I'll probably see an std::bad_alloc.
Okay, so I'm going to put all the default values for variables that you can set in a Theatre file in unordered maps for every class and sub-class. Yeah, it'll be a little bit annoying, but worst case scenario is I have to write out, like, 20 unordered maps. If I do this correctly, I'll only have to make these maps once. Then, I just make sure that the order of variables in the maps matches the order of variables in the class constructor or, a separate function that I can even overload a few times so the order could be mixed a little bit (but that wouldn't account for mixed variables of the same type so maybe don't do overloading, it's just a quick thought). I like the function idea because it means I don't have to re-write the maps every fucking time I change a constructor. I think that's what I'll do.
Figured out why I changed troupe from a vector to an unordered map: theatre references. I won't know which object I'm referencing (not easily, at least) unless I store that object's uid.
ALMOST DONE!!!! THE INTERPRETER CODE IS... untested, but more importantly ITS ALMOST DONE!! I just have to figure out how to do layered variables (I call 'em sandwiches). Once that's done, though, I'll be home free! I think... I still have to test it and finish writing up the actual Theatre setup, but that should be as easy as "new_actor.youGotACallBack(data...)".
I'M SO FUCKING EXCITED!!
:3
[02/07/25]
Dexter gets here tomorrow! Today I'm gonna try and get the interpreter finished!
Holy shit. I've just discovered a way to instantiate objects based on an arbitrary input:
(https://stackoverflow.com/questions/582331/is-there-a-way-to-instantiate-objects-from-a-string-holding-their-class-name)
Example C++ code creating a class based on a string of their classname (i.e: "Actor")
:::C++
template<typename T> Base * createInstance() { return new T; }
typedef std::map<std::string, Base*(*)()> map_type;
map_type map;
map["DerivedA"] = &createInstance<DerivedA>;
map["DerivedB"] = &createInstance<DerivedB>;
// In the example, they use "return map[some_string]();" to demonstrate how to get the
// type, but I added this pseudo main function which I think should do the same thing,
// just more directly.
int main()
{
auto new_instance = map[some_string]();
}
:::
It's 12:40, here's a changelog (no testing has been done yet):
- New typedef called "gSettings" which defines the type:
- std::unordered_map<std::string, std::any>
- Theatre now has an unordered map of <int, Actor *>, so that the interpreter can properly interpret Theatre references.
- Theatre also has an unordered map of <int, Device *> that should actually be a vector (TODO: Change to vector)
- Created new Collider struct that will hold all the relevant data for creating a JPH::Body
- Created new Device struct that will provide a virtual function for loading settings
- Device is inherited by:
- Environment
- Material
- Mesh/Sprite
- Collider
- Interpreter now populates a Theatre with new Actors and Devices, as well as storing their relevant settings (both Actor and Device have a new gSettings member called "settings")
Here's what I think should be up next:
- Create unique gSettings templates for each class
- or find a way to avoid having to do this
- one giant gSettings template for both Actor and Device with a range specifier for individual classes?
- Specify how each class handles loading settings
- Replace deprecated Theatre loading in main file with new Theatre loading
:3
I'm at work now; Dexter (my boyfriend) is very anxious today about his flight tomorrow, so I'm going to try and finish as much as I can as quickly as possible and call him earlier than usual. Hopefully I can get this finished in an hour but if not, I want to at least be in an improved state with little to no errors; basically, I'd rather be almost done with no errors than done with errors when I have to stop today.
I've implemented a way for Actors and Devices to both set and get their UID using a virtual function. Using a function means that I can implement this without having to change every single class.
HUGE NOTE: I should replace all raw pointers (*) with smart pointers (std::unique_ptr)
Okay, I'm gonna try to build and run it for the first time. Expected problems: objects that aren't pointers (e.g: Mesh.material). Oh, and none of the Actors or Devices know what to do with their settings and the settings they're receiving are all for Actor LMFAO.
I'm going to implement Collider, then start work on the unique settings, then write down ideas for how Actors and Devices should load settings.
Aight, it's 6:02 and I've finished up Collider. Here's what's up next (tomorrow):
- Create unique gSettings for each sub-class
- Implement unique gSettings function override for each sub-class (i.e: youGotACallBack in Actor)
- Maybe try to avoid having to write a new function for every single class?
- That's not sarcastic, it might just be less headache to not bother
:3
[02/08/25]
Dexter is arriving today! It's 10:50 and I'm gonna fiddle with the code some more. An idea for gSettings is to have them waterfall; i.e: RigidBodyActor settings would include Actor settings.
[02/10/25]
Chilling with Dex; it's 12:20. My throat is sore and my head stuffy so I'm a little sick but nothing too bad. Anyways, time to noodle with the code.
Got to work late, but I'm here; it's 16:32 and I'm going to finish debugging GraphX. I'm assuming that most of the problems are going to either be me misusing something or not accounting for certain cases (specifically with pointers), or data transfer is being initiated with no data (i.e: Collider's loadSettings function is called but its an empty function that does nothing).
Note: the compiler throws a lot of warnings about deleting pointers to class instances of a class that has virtual functions but no virtual destructor (at least I think that's what it's referring to).
Problem: layered references can't exist (at least on my knowledge level) because it's far too much headache and they couldn't just be simple references/pointers to another object, they'd have to be new instantiations, and at that point just make a new object in the Theatre file.
Wait a minute! I can load settings directly through the function! It's almost like I pre-planned for this although I know I didn't. This might save it!
There's a problem with RigidBodyActor; "Mass" has no value in gSettings, which is a PhysicsActor setting. I need to figure out why.
Fuck me, man, I don't have any Devices settings in settings_map.
I HAVE NO SETTINGS FOR DEVICSE UGHHHHHHH!!
Okay, settings can't be a vector of gSettings objects, they have to be one big gSettings object. Because of the way the function iterates and fills the map keys.
I'm getting closer; the crashes are now when an object attempts to get a Material pointer from their settings. I think that this is a problem with how the pointer is stored; it's probably not stored at all (or the pointer is on the stack and disappears?).
[02/11/25]
Dexter is making music on my pc and I'm gonna keep on debugging.
:3
I'm at work now; it's snowing! Working on that std::bad_any_cast exception. Right now, it's in Collider and it's when trying to get any numerical value; a cast of std::any_cast<std::vector<float>>
Alright, I've got a partially specialized template function that correctly uses if constexpr to figure out whether or not the input is a number, a glm::vec#, a glm::quat, or anything else. However, I'm still getting std::bad_any_cast exceptions for Collider's position value.
I might want to change the way the interpreter gets raw data, because currently I have to write all default values in vectors of strings and parse through them as such; if I could find a way to properly put the values in directly (numerical, boolean, glm::vec#, glm::quat, etc.) that would be best. For now, this should work.
I DID IT!! THE ONLY ERROR NOW IS A JOLT ERROR FROM THE BOX SHAPE!! GSETTINGS ARE LOADED PROPERLY!!!!!!! WAAAHOOOOOO!!!
:3
[02/12/25]
At work; me and Dexter went shopping at Spencer's and Hot Topic. It was very fun. Time to (hopefully) finish this code!
I also had an idea last night: instead of having default values for every variable in each class' gSettings, implement a way to detect and skip over settings that aren't filled out. That way, default settings are kept in C++ (and I avoid having to fucking use gRawData).
Just learned the difference between "NULL" and "nullptr"; NULL is just 0, but nullptr is of type std::nullptr_t, so you can do type comparisons and stuff like that. This is exactly the usecase I need for gSettings, but I'm hesitant to convert every instance of NULL to nullptr, so keep this as a mental note.
Ok, I couldn't do a typeid check in the template functions so I've done things a better way. Before the function createNewClass creates the new class, it goes through the settings and if any of them have nullptr for a value, their key is set to "NULL". This means that when the classes try to set their variables to settings that aren't set, they'll be trying to access a key that doesn't exist. This does mean that I'll have to make sure this doesn't cause a crash or an error.
Okay, so if I'm sticking with the "if constexpr" in the template functions, I need to try to insert a constexpr if that forces an early return if the setting is the pair {"NULL", nullptr}.
I've found a solution! std::any::has_value will return 0 even if unordered_map[key] is used (which creates a new empty pair if the key is not found)!
None of these worked; abandoning plan. New plan: gSettings second value is now a pair of a bool and an std::any. If the bool is false, the std::any is a nullptr. If it's true, it's not. Simple as.
Huge problem: ummmm I forgot to fill out graphx_class_names??? I should write an exception for that case.
YES!! BACK TO JOLT ERRORS!! I THINK THIS MEANS I'VE FIXED IT AND GOTTEN IT BACK TO WHERE IT WAS BEFORE!!
IT WORKS IT WORKS IT WORKS IT LOADS A THEATRE IT WORKS!!!!!!!!!!!!!!