in a Cmake directory
mkdir build
cd build
cmake ../
cmake --build ./Every aspect of engine is either a class (cube, texture, scene, sound...) or a function (DrawTexture(), DrawMaterial()...), nothing is "hidden" in the engine. All functions and classes can be seen in Cmake/Source/engine/Components/Include like UI.h, Drawing.h etc.
Drawing functions can be used at any point of execution, the only exception is multitheading. When color gets higher than 1.0f, it gets affected by bloom (glow effect). Drawing, as any other feature does not require any steup or deletion after use, Calling function - drawing, stopped calling the function - not drawing. Simple. Height maps with notmalmaps give a full 3d lighting in a 2d scenes. they can be drawn separatly, like DrawNormalMap(), or much more efficiently with Materials example
void Process(float dt)
{
//Draw white circle at mouse position
DrawCircle(MousePosition, 10.0f);
// Draw circle with stock circle notmalmap
DrawCircle({-200.0f,0.0f}, 100.0f,{1.0f,1.0f,1.0f,1.0f},true);
// Draw light sphere from mouse
DrawLight(MousePosition,{1000.0f,1000.0f},{1.0f,1.0f,1.0f,1.0f});
Material m;
m.Texture = FlatColorCircleTexture; // autogenerated texture
// normalmaps and heightmaps are giving the 3d lightng in a 2d scenes
m.NormalMap = CubeNormalMapTexture; // another autogenerated texture
//Draw a red glowing cube with texture
DrawQuadWithMaterial({0.0f,0.0f},{100.0f,100.0f},m,0.0f,{10.0f,0.0f,0.0f,1.0f});
}Result:
Also there are Windows. They are the only thing that is "hidden in the engine", and can be accessed by IDs.
unsigned int ExampleWindow;
void Ready()
{
// create a window
ExampleWindow = CreateWindow();
// get a temporary pointer to it
Window* w = GetWindow(ExampleWindow);
// Init window with size
w->Init({800,600});
//active windows have input, inactive windows inputs are 0.0f, or false
//this will stop window from going inactive when mouse isnt howering over it.
w->AutoActive = false;
w->Autoclear = false;
w->AutoDraw = false;
}
void Process(float dt)
{
// This will change everything in engine, like mouseposition, keyboardinputs, ect.
// Every Draw after this will go to this window
UseWindow(ExampleWindow);
// Drawing functions...
EndOfWindow();
//get window
Window* w = GetWindow(ExampleWindow);
// Draw everything that is inside this window manually
// this function called automatically every frame if w->AutoDraw == true;
w->_Draw();
// Clear window with color
// again called automatically if w->Autoclear == true, sets color to w->backgroundColor
w->Clear({0.0f,0.0f,0.0f,1.0f});
// Set position to mouse position for fun
w->Position = MousePosition;
// change scale (render will happen at inited size, but Draw() will make it smaller)
w->Scale = {0.5f,0.5f};
// Draw window to screen or other window
w->Draw();
}UI works the same way as drawing. every function that starts with "UI_" draws to screen space, and is not affected by CameraScale or CameraPosition. Example UI_DrawCircle() or UI_DrawQuadWithMaterial. there are functions to make actual user interface, like UI_Button, UI_Slider, UI_TextBox, etc. And as every part of engine, nothing requeres setup, freing memory or anything else, Calling function - button exists, stoppen calling it - button disapears Redactor was made using this stuff and Windows, so here is a preview. source https://github.com/gfifgfifofich/Engine/blob/main/Cmake/Source/engine/Components/Redactor.h
###Scenes and redactor. Check the redactor.cpp and ECS.h in components/objects to see structure, redactor.cpp is the main UI of the engine, it runs everything that is on cpu, Engine.cpp does everything that is GPU related. To add custom object type to the scene:
// derive from Node or its children
class CustomNode : public Object
{
public:
CustomNode()
{
// set type to be your type (lastnode == big number, just to avoid collision with engine stuff)
// each class need unique type
type = NodeType::LASTNODE + 1;
// starting name, can be changed from redactor
Name = "Abobus";
// just call this
ObjectPreconstructor();
}
// one of the functions, activates on resize atempt (hold alt + LMB while object selected and move mouse)
virtual void OnResize(glm::vec2 prevdif,glm::vec2 mp, glm::vec2 prevmp) override
{
Scale -= prevdif;
Scale += mp-prevmp;
}
};
// this funcion starts before everything in enhine, use to setup stuff like new object constructors etc.
void PreReady()
{
// just copy this code and change CustomNode to YourNode, and NodeType::LASTNODE + 1 to your id
NodeConstructors.insert({NodeType::LASTNODE + 1,[](){ return (Node*)new CustomNode();}});
// this setups name in editor, and id again
NodeConstructorNames.insert({NodeType::LASTNODE + 1,"Customstussdsdsd"});
/// other code
}There are 3 types of objects: Balls, Cubes and Polygons. Calling a Process(delta) function will update forces, position and velocity of object. For collisions there are many functions like ball to ball colission BTBCollision(ball* a, ball* b) or even BallToStaticQuadCollision(ball* a, cube b) Also there are Struts, ropes, and springs.
ball b1;
ball b2;
cube c;
// object for creating a rope visually
DecorativeRope dr;
void Ready()
{
c.width = 300.0f;
c.height = 100.0f;
c.position = {0.0f,-100.0f};
b1.position = {0.0f, 0.0f};
b1.r = 10.0f;
b1.mass = 1.0f;
b2.position = {1.0f, 100.0f};
b2.r = 5.0f;
b2.mass = 5.0f;
dr.Init(&b1,&b2,100.0f);
}
void Process(float dt)
{
// add gravity to first ball
b2.Force.y = -100.0f;
//Collide balls
BtBCollision(&b1,&b2);
// collide balls with the quad, leave quad static
BallToStaticQuadCollision(&b1,c);
BallToStaticQuadCollision(&b2,c);
//Make a rope between balls
Rope(&b1,&b2,100.0f);
b1.Process(dt);
b2.Process(dt);
dr.Process(dt);
// Drawing functions can take some objects directly
DrawCircle(b1);
DrawCircle(b2);
DrawCube(c);
//Draw decorative rope
dr.Draw();
}Again, no need for setups or freing memory, calling function - collisions/physics happens, stopped calling function - collision/physics no longer processed
Audio works almost the same way as drawing. The problem with sound system was that OpenAL supports only a certain amount of soundSources per device (most of the time its 255). But when i was making one project, where there are few thousands of parts and each of them was making a sound, it was became a problem. For this to be not a problem i implemented sound pools - an amount of sources that play one sound when needed. When there are more instances of sound that there are sources in sound pool, it will move some of them between instances and combine volume, averege pith and other parameters. This will slightly decreese sound quality than raw sources, but supports Unlimited amount of sources. This system was compacted into a few simple calls. Load sound from .wav file, addsound() to create a pool of this sound (there are different pools for looping sounds and one time sounds), and then, when its needed, just playsound() with needed pitch, position, volume ect. All the source management will be processed automatically.
unsigned int Sound;
void Ready()
{
//load sound from file (right now supported only .wav)
Sound = LoadSound("path/to/sound.wav");
//add sound source pool
addsound(Sound,false,10);
}
void Process(float dt)
{
// play ine sound from this position
// for sounds to work properly in stereo, Sound should be Mono.
playsound(Sound,MousePosition);
}Other stuff that this engine has.
ParticleEmmiter class (Should be renamed to just particles sometime).
Set its material, set its mode with a string(will change to enum sometime), make other setup, like lifetime of particles, startsize, endsize, etc. (just check all the parametrs in the class)
than at any point of time you can call one of the Spawn functions of this particles, and this is a particle system. Drawing works multithreaded here. and dont forget to Process(dt) somewhere.
simple particle simulation:

There are a simple NeuralNetwork class with easy setup for simulations and games. check its headerfile to see more. its highly optimized (whole neural network is cramped into 2 c-arrays, and it is easily runnable in multithreading).
There is also functions to run any parts/whole neural network on a GPU, without any CUDA or anithyng else.
// all the textures
unsigned int T_Weights = 0;
unsigned int T_Biases = 0;
unsigned int T_Weights2 = 0;
unsigned int T_Biases2 = 0;
unsigned int T_Weights3 = 0;
unsigned int T_Biases3 = 0;
unsigned int T_In = 0;
unsigned int T_Out = 0;
unsigned int T_Out2 = 0;
unsigned int T_Out3 = 0;
//visualization textures
unsigned int Visual_T_Weights = 0;
unsigned int Visual_T_Biases = 0;
unsigned int Visual_T_In = 0;
unsigned int Visual_T_Out = 0;
// NN architecture
const int insize = 20'000;//20k inputs,
//200'000'000 weights
const int outsize = 10'000;// 10k intermidiate
//30'000'000 weights
const int outsize2 = 3'000;//3k intermidiate
//150'000 weights
const int outsize3 = 50;// 50 output
// 230'150'000 weights,
// on 1660 (slightly weaker than 1070) it runs at 20-22 fps
// 4 603 000 000/s. 4.5 BILLION, full 32bit float parrameters per second.
// And that is with all other Engine-stuff like UI, PBR, lighting, bloom etc
// And each layer can have its own activation function
// on a architecture that can run on GPU's starting from 1992. (its just textures)
// Since NeuralNetwork class consists of litteraly 2 arrays,
// all the weights and biases can be grabbed from its arrays directly, wia poiters to a start of each layer.
// data layouts are fully compatible
// hardcoded arrays of NN. Representation of NN on CPU.
float Weights[insize * outsize];
float Biases[outsize];
float Weights2[outsize * outsize2];
float Biases2[outsize2];
float Weights3[outsize2 * outsize3];
float Biases3[outsize3];
float In[insize];
float Out[outsize];
float Out2[outsize2];
float Out3[outsize3];
void Ready()
{
// generating random garbaje weights/biases/inputData
for (int i=0;i<insize * outsize;i++)
Weights[i] = rand() %2000 * 0.001f - 1.0f;
for (int i=0;i<outsize * outsize2;i++)
Weights2[i] = rand() %2000 * 0.001f - 1.0f;
for (int i=0;i<outsize2 * outsize3;i++)
Weights3[i] = rand() %2000 * 0.001f - 1.0f;
for (int i=0;i<insize;i++)
{
Biases[i] = rand() %1000 * 0.0001f;
In[i] = rand() %1000 * 0.0001f;
}
for (int i=0;i<outsize;i++)
{
Out[i] = 0.0f;
Biases[i] = rand() %1000 * 0.0001f;
}
for (int i=0;i<outsize2;i++)
{
Biases2[i] = rand() %1000 * 0.0001f;
Out2[i] = 0.0f;
}
for (int i=0;i<outsize3;i++)
{
Biases3[i] = rand() %1000 * 0.0001f;
Out3[i] = 0.0f;
}
// fload all textures into gpu
fLoadTextureFromData(&T_Weights,insize,outsize,Weights,1);
fLoadTextureFromData(&T_Biases,outsize,1,Biases,1);
fLoadTextureFromData(&T_Weights2,outsize,outsize2,Weights2,1);
fLoadTextureFromData(&T_Biases2,outsize2,1,Biases2,1);
fLoadTextureFromData(&T_Weights3,outsize2,outsize3,Weights3,1);
fLoadTextureFromData(&T_Biases3,outsize3,1,Biases3,1);
fLoadTextureFromData(&T_In,insize,1,In,1);
fLoadTextureFromData(&T_Out,outsize,1,Out,1);
fLoadTextureFromData(&T_Out2,outsize2,1,Out2,1);
fLoadTextureFromData(&T_Out3,outsize3,1,Out3,1);
// for visulization, current render just draws black in case of 1d, 1 component texture. in case of multiple instances, inputs/outputs can be drawn directly.
CopyTexture({insize,outsize},&Visual_T_Weights,T_Weights,4);
CopyTexture({insize,10.0f},&Visual_T_Biases,T_Biases,4);
CopyTexture({insize,10.0f},&Visual_T_In,T_In,4);
RunNNLayerOnGPU(
insize,//inputsize,
outsize,//outputsize,
1,//count of input/output data (yes it can run multiple instances of NN in parallel, each instance is a Y dimension of input/output texture),
3,//Activation function (0: sigmoidApprox(sum) 1: ReLu(sum) 2:sigmoid(sum); 3 sum; // sigmoidApprox is custom, much faster then regular sigmoid hardcoded function, works in 99% cases
&T_Out,T_In,T_Weights,T_Biases); //and all the PackedTextures in order
// repeat for each layer of NN.
// Whole NN can be "rewired" on fly, just by calling this functions in different order and by resizing textures.
RunNNLayerOnGPU(outsize,outsize2,1,3,&T_Out2,T_Out,T_Weights2,T_Biases2);
RunNNLayerOnGPU(outsize2,outsize3,1,3,&Visual_T_Out,T_Out2,T_Weights3,T_Biases3);
// read the output of nn into a buffer, thi will create new array!
float* buff = freadTexture(Visual_T_Out,{insize,1},1);
//delete new array
delete[] buff;
}
void Process(float dt)
{
RunNNLayerOnGPU(insize,outsize,1,2,&T_Out,T_In,T_Weights,T_Biases);
RunNNLayerOnGPU(outsize,outsize2,1,2,&T_Out2,T_Out,T_Weights2,T_Biases2);
RunNNLayerOnGPU(outsize2,outsize3,1,2,&Visual_T_Out,T_Out2,T_Weights3,T_Biases3);
DrawTexturedQuad({-200.0f,200.0f}, {100.0f,100.0f},Visual_T_Weights);
DrawTexturedQuad({200.0f,200.0f},{100.0f,100.0f},Visual_T_Biases);
DrawTexturedQuad({-200.0f,-200.0f},{100.0f,100.0f},Visual_T_In);
DrawTexturedQuad({200.0f,-200.0f},{100.0f,100.0f},Visual_T_Out);
}
22.4 fps. Upper left texture - all 200 million weights of the first layer. As it turns out, 20k textures are quite problematic to display. Bottom riht texture - 1d output.
All of the libraries are copied inside, thats why "Languages" tab is broken, but project requeres no setup. just clone and build all.
Used libraries: Dear imgui, GLM, GLFW, glew, glad, freetype, OpenAL, stb_image,


