@@ -1,53 +1,65 @@
// LevelEditor.cpp
// LevelEditor.cpp — FIXED
# include "Engine.h"
# include <GLFW/glfw3.h>
# include <iostream>
# include <vector>
# include <cmath>
# include <cstring>
// ---------------------------------------------
// LevelEditor: free camera, object placement, light sliders, stats
// ---------------------------------------------
class LevelEditor : public Engine {
private :
std : : vector < std : : shared_ptr < GameObject > > objects_ ;
std : : shared_ptr < Primitive > monkeyPrim_ ;
std : : shared_ptr < Primitive > monkeyPrim_ ;
std : : shared_ptr < GameObject > selected_ ;
// FIX: Button stores its own pressed state so DrawButton can be
// called for multiple buttons without a shared static variable
struct Button {
float x , y , w , h ;
bool pressed ;
bool pressed = false ;
} ;
// Free-cam state
bool firstMouse_ = true ;
double lastX_ = 0.0 , lastY_ = 0.0 ;
float yaw_ = - 90.0f , pitch_ = 0.0f ;
float camSpeed_ = 10.0f , mouseSens_ = 0.1f ;
// Free cam state
bool firstMouse_ = true ;
double lastX_ , lastY_ ;
float yaw_ = - 90.0f , pitch_ = 0.0f ;
float camSpeed_ = 10.0f , mouseSens_ = 0.1f ;
// Light sliders
float lightR = 1.0f , lightG = 1.0f , lightB = 1.0f , lightI = 1.0f ;
bool dragR = false , dragG = false , dragB = false , dragI = false ;
// Light control state
float lightR = 1 , lightG = 1 , lightB = 1 , lightI = 1 ;
bool dragR = false , dragG = false , dragB = false , dragI = false ;
Button btnOrtho { 560.0f , 150.0f , 80.0f , 20.0f , false } ;
bool useOrtho_ = false ;
// FIX: per-key previous-state for edge detection (one-shot placement)
bool prevKey1_ = false , prevKey2_ = false , prevKey3_ = false ,
prevKey4_ = false , prevKey5_ = false ;
Button btnOrtho { 560 , 150 , 80 , 20 , false } ;
bool useOrtho_ = false ;
// Helpers
// ---------------------------------------------------------------
// Input helpers
// ---------------------------------------------------------------
bool KeyDown ( int k ) const { return glfwGetKey ( window_ , k ) = = GLFW_PRESS ; }
// Returns true only on the frame the key transitions low→high [web:61]
bool KeyJustPressed ( int k , bool & prev ) const {
bool cur = KeyDown ( k ) ;
bool just = cur & & ! prev ;
prev = cur ;
return just ;
}
void ProcessMouse ( double xpos , double ypos ) {
if ( firstMouse_ ) {
lastX_ = xpos ; lastY_ = ypos ;
firstMouse_ = false ;
return ; // FIX: skip first frame to avoid a jump
}
float xoff = ( xpos - lastX_ ) * mouseSens_ ;
float yoff = ( lastY_ - ypos ) * mouseSens_ ;
float xoff = static_cast < float > ( xpos - lastX_ ) * mouseSens_ ;
float yoff = static_cast < float > ( lastY_ - ypos ) * mouseSens_ ;
lastX_ = xpos ; lastY_ = ypos ;
yaw_ + = xoff ;
yaw_ + = xoff ;
pitch_ = glm : : clamp ( pitch_ + yoff , - 89.0f , 89.0f ) ;
glm : : vec3 front ;
front . x = cos ( glm : : radians ( yaw_ ) ) * cos ( glm : : radians ( pitch_ ) ) ;
@@ -56,266 +68,268 @@ private:
camera_target_ = camera_position_ + glm : : normalize ( front ) ;
}
// Place primitive at camera target
void Place ( std : : shared_ptr < Primitive > prim ) {
glm : : vec3 pos = camera_target_ ;
// FIX: overload — explicit position for use before camera is set up
void Place ( std : : shared_ptr < Primitive > prim , const glm : : vec3 & pos ) {
auto go = CreateGameObject ( prim , pos ) ;
objects_ . push_back ( go ) ;
AddGameObject ( go ) ;
}
void Place ( std : : shared_ptr < Primitive > prim ) { Place ( prim , camera_target_ ) ; }
// Raycast pick uses bounding spheres
std : : shared_ptr < GameObject > Pick ( double mx , double my ) {
int w , h ; glfwGetWindowSize ( window_ , & w , & h ) ;
float x = ( 2.0f * mx ) / w - 1.0f, y = 1.0f - ( 2.0f * my ) / h ;
glm : : vec4 ray_clip { x , y , - 1 , 1 } ;
// Create projection and view matrices locally
int w , h ; glfwGetWindowSize ( window_ , & w , & h ) ;
float n x = ( 2.0f * mx ) / w - 1.0f ;
float ny = 1.0f - ( 2.0f * my ) / h ;
glm : : vec4 ray_clip { nx , ny , - 1.0f , 1.0f } ;
glm : : mat4 proj = glm : : perspective ( glm : : radians ( 60.0f ) , float ( w ) / h , 0.1f , 1000.0f ) ;
glm : : mat4 view = glm : : lookAt ( camera_position_ , camera_target_ , camera_up_ ) ;
glm : : vec4 ray_eye = glm : : inverse ( proj ) * ray_clip ; ray_eye. z = - 1 ; ray_eye . w = 0 ;
glm : : vec3 dir = glm : : normalize ( glm : : vec3 ( glm : : inverse ( view ) * ray_eye ) ) ;
std : : shared_ptr < GameObject > best ; float bd = FLT_MAX ;
for ( auto & obj : objects_ ) {
glm : : vec4 ray_eye = glm : : inverse ( proj ) * ray_clip ;
ray_eye . z = - 1.0f ; ray_eye . w = 0.0f ;
glm : : vec3 dir = glm : : normalize ( glm : : vec3 ( glm : : inverse ( view ) * ray_eye ) ) ;
std : : shared_ptr < GameObject > best ; float bd = FLT_MAX ;
for ( auto & obj : objects_ ) {
glm : : vec3 oc = camera_position_ - obj - > GetPosition ( ) ;
float b = glm : : dot ( oc , dir ) ;
float c = glm : : dot ( oc , oc ) - obj - > GetBoundingRadius ( ) * obj - > GetBoundingRadius ( ) ;
float disc = b * b - c ;
if ( disc < 0 ) continue ;
float t = - b - sqrt ( disc ) ;
if ( t > 0 & & t < bd ) { b d= t ; best = obj ; }
float b = glm : : dot ( oc , dir ) ;
float c = glm : : dot ( oc , oc )
- obj - > GetBoundingRadius ( ) * obj - > GetBoundingRadius ( ) ;
float disc = b * b - c ;
if ( disc < 0.0f ) continue ;
float t = - b - st d: : sqrt ( disc ) ;
if ( t > 0.0f & & t < bd ) { bd = t ; best = obj ; }
}
return best ;
}
void DrawChar ( char c , float x , float y , float s ) {
static const std : : vector < std : : vector < std : : pair < float , float > > > strokes = {
{ { 0 , 0 } , { 1 , 0 } , { 1 , 1 } , { 0 , 1 } , { 0 , 0 } } , { { 0.5f , 0 } , { 0.5f , 1 } } ,
{ { 0 , 1 } , { 1 , 1 } , { 1 , 0.5f } , { 0 , 0.5f } , { 0 , 0 } , { 1 , 0 } } ,
{ { 0 , 1 } , { 1 , 1 } , { 1 , 0 } , { 0 , 0 } , { 1 , 0.5f } } ,
{ { 0 , 1 } , { 0 , 0.5f } , { 1 , 0.5f } , { 1 , 1 } , { 1 , 0 } } ,
{ { 1 , 1 } , { 0 , 1 } , { 0 , 0.5f } , { 1 , 0.5f } , { 1 , 0 } , { 0 , 0 } } ,
{ { 1 , 1 } , { 0 , 1 } , { 0 , 0 } , { 1 , 0 } , { 1 , 0.5f } , { 0 , 0.5f } } ,
{ { 0 , 1 } , { 1 , 1 } , { 0.5f , 0 } } ,
{ { 0 , 0 } , { 0 , 1 } , { 1 , 1 } , { 1 , 0 } , { 0 , 0 } , { 0 , 0.5f } , { 1 , 0.5f } } ,
{ { 1 , 0 } , { 1 , 1 } , { 0 , 1 } , { 0 , 0.5f } , { 1 , 0.5f } }
} ;
std : : vector < float > v ;
if ( c > = ' 0 ' & & c < = ' 9 ' ) {
for ( auto & p : strokes [ c - ' 0 ' ] ) {
v . push_back ( x + p . first * s ) ;
v . push_back ( y + p . second * s ) ;
v . push_back ( 1 ) ; v . push_back ( 1 ) ; v . push_back ( 1 ) ;
// ---------------------------------------------------------------
// 2D drawing — unified vertex type + helper to avoid repeating
// glBufferData / glVertexAttribPointer for every draw call
// ---------------------------------------------------------------
struct V2D { float x , y , r , g , b ; } ;
// FIX: single upload-and-draw helper used everywhere — the original
// code was missing glVertexAttribPointer calls before several
// glDrawArrays calls (e.g. the button fill triangles)
void UploadAndDraw ( const std : : vector < V2D > & v , GLenum mode ) {
if ( v . empty ( ) ) return ;
glBufferData ( GL_ARRAY_BUFFER ,
static_ca st < GLsizeiptr > ( v . size ( ) * sizeof ( V2D ) ) ,
v . data ( ) , GL_DYNAMIC_DRAW ) ;
glVertexAttribPointer ( 0 , 2 , GL_FLOAT , GL_FALSE ,
sizeof ( V2D ) , reinterpret_cast < void * > ( 0 ) ) ;
glVertexAttribPointer ( 1 , 3 , GL_FLOAT , GL_FALSE ,
sizeof ( V2D ) , reinterpret_cast < void * > ( 2 * sizeof ( float ) ) ) ;
glDrawArrays ( mode , 0 , static_cast < GLsizei > ( v . size ( ) ) ) ;
}
// ---------------------------------------------------------------
// Segment-display font — digits AND letters
// FIX: original DrawChar only handled '0'– '9'; calling it with
// 'R','G','B','I' (and button labels) produced no output.
// Merged into one function covering A– Z and 0– 9.
// ---------------------------------------------------------------
void DrawChar ( char c , float x , float y , float s ,
float r = 1.0f , float g = 1.0f , float b = 1.0f )
{
using P = std : : pair < float , float > ;
std : : vector < P > pts ;
if ( c > = ' 0 ' & & c < = ' 9 ' ) {
static const std : : vector < std : : vector < P > > D = {
{ { 0 , 0 } , { 1 , 0 } , { 1 , 1 } , { 0 , 1 } , { 0 , 0 } } ,
{ { 0.5f , 0 } , { 0.5f , 1 } } ,
{ { 0 , 1 } , { 1 , 1 } , { 1 , 0.5f } , { 0 , 0.5f } , { 0 , 0 } , { 1 , 0 } } ,
{ { 0 , 1 } , { 1 , 1 } , { 1 , 0 } , { 0 , 0 } , { 1 , 0.5f } } ,
{ { 0 , 1 } , { 0 , 0.5f } , { 1 , 0.5f } , { 1 , 1 } , { 1 , 0 } } ,
{ { 1 , 1 } , { 0 , 1 } , { 0 , 0.5f } , { 1 , 0.5f } , { 1 , 0 } , { 0 , 0 } } ,
{ { 1 , 1 } , { 0 , 1 } , { 0 , 0 } , { 1 , 0 } , { 1 , 0.5f } , { 0 , 0.5f } } ,
{ { 0 , 1 } , { 1 , 1 } , { 0.5f , 0 } } ,
{ { 0 , 0 } , { 0 , 1 } , { 1 , 1 } , { 1 , 0 } , { 0 , 0 } , { 0 , 0.5f } , { 1 , 0.5f } } ,
{ { 1 , 0 } , { 1 , 1 } , { 0 , 1 } , { 0 , 0.5f } , { 1 , 0.5f } }
} ;
pts = D [ c - ' 0 ' ] ;
} else {
switch ( c ) {
case ' A ' : pts = { { 0 , 0 } , { 0.5f , 1 } , { 1 , 0 } , { 0.25f , 0.5f } , { 0.75f , 0.5f } } ; break ;
case ' B ' : pts = { { 0 , 0 } , { 0 , 1 } , { 0.7f , 1 } , { 0.7f , 0.5f } , { 0 , 0.5f } , { 0.7f , 0.5f } , { 0.7f , 0 } , { 0 , 0 } } ; break ;
case ' C ' : pts = { { 1 , 1 } , { 0 , 1 } , { 0 , 0 } , { 1 , 0 } } ; break ;
case ' D ' : pts = { { 0 , 0 } , { 0 , 1 } , { 0.7f , 0.8f } , { 0.7f , 0.2f } , { 0 , 0 } } ; break ;
case ' E ' : pts = { { 1 , 1 } , { 0 , 1 } , { 0 , 0.5f } , { 0.7f , 0.5f } , { 0 , 0.5f } , { 0 , 0 } , { 1 , 0 } } ; break ;
case ' F ' : pts = { { 0 , 0 } , { 0 , 1 } , { 1 , 1 } , { 0 , 1 } , { 0 , 0.5f } , { 0.7f , 0.5f } } ; break ;
case ' G ' : pts = { { 1 , 1 } , { 0 , 1 } , { 0 , 0 } , { 1 , 0 } , { 1 , 0.5f } , { 0.5f , 0.5f } } ; break ;
case ' H ' : pts = { { 0 , 0 } , { 0 , 1 } , { 0 , 0.5f } , { 1 , 0.5f } , { 1 , 1 } , { 1 , 0 } } ; break ;
case ' I ' : pts = { { 0 , 1 } , { 1 , 1 } , { 0.5f , 1 } , { 0.5f , 0 } , { 0 , 0 } , { 1 , 0 } } ; break ;
case ' J ' : pts = { { 0 , 1 } , { 1 , 1 } , { 1 , 0 } , { 0 , 0 } , { 0 , 0.3f } } ; break ;
case ' K ' : pts = { { 0 , 0 } , { 0 , 1 } , { 0 , 0.5f } , { 1 , 1 } , { 0 , 0.5f } , { 1 , 0 } } ; break ;
case ' L ' : pts = { { 0 , 1 } , { 0 , 0 } , { 1 , 0 } } ; break ;
case ' M ' : pts = { { 0 , 0 } , { 0 , 1 } , { 0.5f , 0.5f } , { 1 , 1 } , { 1 , 0 } } ; break ;
case ' N ' : pts = { { 0 , 0 } , { 0 , 1 } , { 1 , 0 } , { 1 , 1 } } ; break ;
case ' O ' : pts = { { 0 , 0 } , { 0 , 1 } , { 1 , 1 } , { 1 , 0 } , { 0 , 0 } } ; break ;
case ' P ' : pts = { { 0 , 0 } , { 0 , 1 } , { 1 , 1 } , { 1 , 0.5f } , { 0 , 0.5f } } ; break ;
case ' Q ' : pts = { { 1 , 0 } , { 0 , 0 } , { 0 , 1 } , { 1 , 1 } , { 1 , 0 } , { 0.6f , 0.2f } } ; break ;
case ' R ' : pts = { { 0 , 0 } , { 0 , 1 } , { 1 , 1 } , { 1 , 0.5f } , { 0 , 0.5f } , { 1 , 0 } } ; break ;
case ' S ' : pts = { { 1 , 1 } , { 0 , 1 } , { 0 , 0.5f } , { 1 , 0.5f } , { 1 , 0 } , { 0 , 0 } } ; break ;
case ' T ' : pts = { { 0 , 1 } , { 1 , 1 } , { 0.5f , 1 } , { 0.5f , 0 } } ; break ;
case ' U ' : pts = { { 0 , 1 } , { 0 , 0 } , { 1 , 0 } , { 1 , 1 } } ; break ;
case ' V ' : pts = { { 0 , 1 } , { 0.5f , 0 } , { 1 , 1 } } ; break ;
case ' W ' : pts = { { 0 , 1 } , { 0.25f , 0 } , { 0.5f , 0.5f } , { 0.75f , 0 } , { 1 , 1 } } ; break ;
case ' X ' : pts = { { 0 , 0 } , { 1 , 1 } , { 0.5f , 0.5f } , { 0 , 1 } , { 1 , 0 } } ; break ;
case ' Y ' : pts = { { 0 , 1 } , { 0.5f , 0.5f } , { 1 , 1 } , { 0.5f , 0.5f } , { 0.5f , 0 } } ; break ;
case ' Z ' : pts = { { 0 , 1 } , { 1 , 1 } , { 0 , 0 } , { 1 , 0 } } ; break ;
default : return ; // skip spaces / unknown
}
glBufferData ( GL_ARRAY_BUFFER , v . size ( ) * sizeof ( float ) , v . data ( ) , GL_DYNAMIC_DRAW ) ;
glVertexAttribPointer ( 0 , 2 , GL_FLOAT , GL_FALSE , 5 * sizeof ( float ) , 0 ) ;
glVertexAttribPointer ( 1 , 3 , GL_FLOAT , GL_FALSE , 5 * sizeof ( float ) , ( void * ) ( 2 * sizeof ( float ) ) ) ;
glDrawArrays ( GL_LINE_STRIP , 0 , ( GLsizei ) ( v . size ( ) / 5 ) ) ;
}
std : : vector < V2D > v ;
v . reserve ( pts . size ( ) ) ;
for ( auto & p : pts )
v . push_back ( { x + p . first * s , y + p . second * s , r , g , b } ) ;
UploadAndDraw ( v , GL_LINE_STRIP ) ;
}
// Draw a letter R,G,B,I
void DrawLetter ( char c , float x , float y , float s ) {
struct V { float x , y , r , g , b ; } ;
std : : vector < V > L ;
if ( c = = ' R ' ) {
L = { { x , y , 1 , 1 , 1 } , { x , y + s , 1 , 1 , 1 } , { x , y + s , 1 , 1 , 1 } , { x + s * 0.7f , y + s , 1 , 1 , 1 } ,
{ x + s * 0.7f , y + s , 1 , 1 , 1 } , { x + s * 0.7f , y + s * 0.5f , 1 , 1 , 1 } , { x + s * 0.7f , y + s * 0.5f , 1 , 1 , 1 } ,
{ x , y + s * 0.5f , 1 , 1 , 1 } , { x , y + s * 0.5f , 1 , 1 , 1 } , { x + s * 0.7f , y , 1 , 1 , 1 } } ;
} else if ( c = = ' G ' ) {
L = { { x + s , y + s * 0.3f , 1 , 1 , 1 } , { x , y + s * 0.3f , 1 , 1 , 1 } , { x , y + s * 0.3f , 1 , 1 , 1 } ,
{ x , y + s * 0.7f , 1 , 1 , 1 } , { x , y + s * 0.7f , 1 , 1 , 1 } , { x + s , y + s * 0.7f , 1 , 1 , 1 } ,
{ x + s , y + s * 0.7f , 1 , 1 , 1 } , { x + s , y + s * 0.5f , 1 , 1 , 1 } , { x + s , y + s * 0.5f , 1 , 1 , 1 } ,
{ x + s * 0.5f , y + s * 0.5f , 1 , 1 , 1 } } ;
} else if ( c = = ' B ' ) {
L = { { x , y , 1 , 1 , 1 } , { x , y + s , 1 , 1 , 1 } , { x , y + s , 1 , 1 , 1 } , { x + s * 0.7f , y + s , 1 , 1 , 1 } ,
{ x + s * 0.7f , y + s , 1 , 1 , 1 } , { x + s * 0.7f , y + s * 0.5f , 1 , 1 , 1 } , { x + s * 0.7f , y + s * 0.5f , 1 , 1 , 1 } ,
{ x , y + s * 0.5f , 1 , 1 , 1 } , { x , y + s * 0.5f , 1 , 1 , 1 } , { x + s * 0.7f , y , 1 , 1 , 1 } ,
{ x + s * 0.7f , y , 1 , 1 , 1 } , { x , y , 1 , 1 , 1 } } ;
} else if ( c = = ' I ' ) {
L = { { x , y + s , 1 , 1 , 1 } , { x + s , y + s , 1 , 1 , 1 } , { x + s * 0.5f , y + s , 1 , 1 , 1 } ,
{ x + s * 0.5f , y , 1 , 1 , 1 } , { x , y , 1 , 1 , 1 } , { x + s , y , 1 , 1 , 1 } } ;
}
if ( ! L . empty ( ) ) {
glBufferData ( GL_ARRAY_BUFFER , L . size ( ) * sizeof ( V ) , L . data ( ) , GL_DYNAMIC_DRAW ) ;
glVertexAttribPointer ( 0 , 2 , GL_FLOAT , GL_FALSE , sizeof ( V ) , ( void * ) 0 ) ;
glVertexAttribPointer ( 1 , 3 , GL_FLOAT , GL_FALSE , sizeof ( V ) , ( void * ) ( 2 * sizeof ( float ) ) ) ;
glDrawArrays ( GL_LINES , 0 , ( GLsizei ) L . size ( ) ) ;
}
// Draw a string left-to-right with fixed character advance
void DrawString ( const char * text , float x , float y , float charSize ,
float r = 1.0f , float g = 1.0f , float b = 1.0f )
{
const float advance = charSize * 0.75f + 2.0f ;
for ( int i = 0 ; text [ i ] ! = ' \0 ' ; + + i )
if ( text [ i ] ! = ' ' )
DrawChar ( text [ i ] , x + i * advance , y , charSize * 0.7f , r , g , b ) ;
}
float MeasureTextWidth ( const char * text , float size ) {
return strlen ( text ) * size * 0.6 f ;
return strlen ( text ) * ( size * 0.7 f + 2.0f ) ;
}
// Draw a production-ready gradient button
bool DrawButton ( const Button & btn , const char * label ,
double mx , double my , bool mb ) {
struct V { float x , y , r , g , b ; } ;
// Determine states
// FIX: takes Button& (not const) so pressed state is stored per- button,
// replacing the broken shared static bool in the original
bool DrawButton ( Button & btn , const char * label ,
double m x, double my , bool mb )
{
bool hovered = mx > = btn . x & & mx < = btn . x + btn . w
& & my > = btn . y & & my < = btn . y + btn . h ;
static bool pressedPrev = fal se ;
bool pressed = mb & & hovered ;
bool clicked = pressed & & ! pressedPrev ;
pressedPrev = pressed ;
bool prevPressed = btn . pres sed ;
btn . pressed = mb & & hovered ;
bool clicked = btn . pressed & & ! prevPre ssed ;
// Colors
glm : : vec3 topColor = hovered ? glm : : vec3 ( 0.3f , 0.5f , 0.8f ) : glm : : vec3 ( 0.2f , 0.4f , 0.7f ) ;
glm : : vec3 bottomColor = hovered ? glm : : vec3 ( 0.1f , 0.3f , 0.6f ) : glm : : vec3 ( 0.0f , 0.2f , 0.5f ) ;
if ( pressed ) {
topColor * = 0.8f ;
bottomColor * = 0.8f ;
}
glm : : vec3 top = hovered ? glm : : vec3 ( 0.3f , 0.5f , 0.8f )
: glm : : vec3 ( 0.2f , 0.4f , 0.7f ) ;
glm : : vec3 bot = hovered ? glm : : vec3 ( 0.1f , 0.3f , 0.6f )
: glm : : vec3 ( 0.0f , 0.2f , 0.5f ) ;
if ( btn . pressed ) { top * = 0.8f ; bot * = 0.8f ; }
// Vertex data: two triangles forming the quad with gradient
V verts [ 6 ] = {
// first triangle
{ btn . x , btn . y , bottomColor . r , bottomColor . g , bottomColor . b } ,
{ btn . x + btn . w , btn . y , bottomColor . r , bottomColor . g , bottomColor . b } ,
{ btn . x + btn . w , btn . y + btn . h , topColor . r , topColor . g , topColor . b } ,
// second triangle
{ btn . x , btn . y , bottomColor . r , bottomColor . g , bottomColor . b } ,
{ btn . x + btn . w , btn . y + btn . h , topColor . r , topColor . g , topColor . b } ,
{ btn . x , btn . y + btn . h , topColor . r , topColor . g , topColor . b }
} ;
// FIX: UploadAndDraw now sets glVertexAttribPointer — the original
// triangle draw was missing those calls entirely
UploadAndDraw ( {
{ btn . x , btn . y , bot . r , bot . g , bot . b } ,
{ btn . x + btn . w , btn . y , bot . r , bot . g , bot . b } ,
{ btn . x + btn . w , btn . y + btn . h , top . r , top . g , top . b } ,
{ btn . x , btn . y , bot . r , bot . g , bot . b } ,
{ btn . x + btn . w , btn . y + btn . h , top . r , top . g , top . b } ,
{ btn . x , btn . y + btn . h , top . r , top . g , top . b } ,
} , GL_TRIANGLES ) ;
glBufferData ( GL_ARRAY_BUFFER , sizeof ( verts ) , verts , GL_DYNAMIC_DRAW ) ;
glDrawArrays ( GL_TRIANGLES , 0 , 6 ) ;
UploadAndDraw ( {
{ btn . x , btn . y , 0 , 0 , 0 } ,
{ btn . x + btn . w , btn . y , 0 , 0 , 0 } ,
{ btn . x + btn . w , btn . y + btn . h , 0 , 0 , 0 } ,
{ btn . x , btn . y + btn . h , 0 , 0 , 0 } ,
{ btn . x , btn . y , 0 , 0 , 0 } ,
} , GL_LINE_STRIP ) ;
// Draw border
struct V2 { float x , y , r , g , b ; } ;
V2 border [ 5 ] = {
{ btn . x , btn . y , 0 , 0 , 0 } ,
{ btn . x + btn . w , btn . y , 0 , 0 , 0 } ,
{ btn . x + btn . w , btn . y + btn . h , 0 , 0 , 0 } ,
{ btn . x , btn . y + btn . h , 0 , 0 , 0 } ,
{ btn . x , btn . y , 0 , 0 , 0 }
} ;
glBufferData ( GL_ARRAY_BUFFER , sizeof ( border ) , border , GL_DYNAMIC_DRAW ) ;
glVertexAttribPointer ( 0 , 2 , GL_FLOAT , GL_FALSE , sizeof ( V2 ) , ( void * ) 0 ) ;
glVertexAttribPointer ( 1 , 3 , GL_FLOAT , GL_FALSE , sizeof ( V2 ) , ( void * ) ( 2 * sizeof ( float ) ) ) ;
glDrawArrays ( GL_LINE_STRIP , 0 , 5 ) ;
// Draw label centered
float textWidth = MeasureTextWidth ( label , 12.0f ) ;
float tx = btn . x + ( btn . w - textWidth ) * 0.5f ;
float ty = btn . y + ( btn . h - 12.0f ) * 0.5f ;
DrawChar ( label [ 0 ] , tx , ty , 12 ) ; // loop over each char if full string support needed
// Centred label using DrawString (supports full A– Z)
float tw = MeasureTextWidth ( label , 10.0f ) ;
float tx = btn . x + ( btn . w - tw ) * 0.5f ;
float ty = btn . y + ( btn . h - 10.0f ) * 0.5f ;
DrawString ( label , tx , ty , 10.0f ) ;
return clicked ;
}
void DrawSlider ( float x , float y , float w , float h ,
float & val , bool & drag ,
double mx , double my , bool mb )
{
if ( mb & & mx > = x & & mx < = x + w & & my > = y & & my < = y + h ) drag = true ;
if ( ! mb ) drag = false ;
if ( drag ) val = glm : : clamp ( float ( ( mx - x ) / w ) , 0.0f , 1.0f ) ;
// D raw slider at (x,y), width w height h controlling value 0– 1
void DrawSlider ( float x , float y , float w , float h ,
float & val , bool & drag ,
double mx , double m y, bool mb ) {
// handle drag
if ( mb & & mx > = x & & mx < = x + w & & my > = y & & my < = y + h ) drag = true ;
if ( ! mb ) drag = false ;
if ( drag ) val = glm : : clamp ( float ( ( mx - x ) / w ) , 0.0f , 1.0f ) ;
// T rack background
UploadAndDraw ( {
{ x , y , 0.2f , 0.2f , 0.2f } , { x + w , y , 0.2f , 0.2f , 0.2f } ,
{ x + w , y + h , 0.2f , 0.2f , 0.2f } , { x , y + h , 0.2f , 0.2f , 0.2f }
} , GL_TRIANGLE_FAN ) ;
struct V { float x , y , r , g , b ; } ;
// track bg
std : : vector < V > quad = {
{ x , y , 0.2 f , 0.2 f , 0.2 f } , { x + w , y , 0.2 f , 0.2 f , 0.2 f } ,
{ x + w , y + h , 0.2 f , 0.2 f , 0.2 f } , { x , y + h , 0.2 f , 0.2 f , 0.2 f }
} ;
glBufferData ( GL_ARRAY_BUFFER , quad . size ( ) * sizeof ( V ) , quad . data ( ) , GL_DYNAMIC_DRAW ) ;
glVertexAttribPointer ( 0 , 2 , GL_FLOAT , GL_FALSE , sizeof ( V ) , ( void * ) 0 ) ;
glVertexAttribPointer ( 1 , 3 , GL_FLOAT , GL_FALSE , sizeof ( V ) , ( void * ) ( 2 * sizeof ( float ) ) ) ;
glDrawArrays ( GL_TRIANGLE_FAN , 0 , 4 ) ;
// Filled portion
float fw = w * val ;
UploadAndDraw ( {
{ x , y , 0.8 f , 0.4 f , 0.4 f } , { x + f w, y , 0.8 f , 0.4 f , 0.4 f } ,
{ x + f w, y + h , 0.8 f , 0.4 f , 0.4 f } , { x , y + h , 0.8 f , 0.4 f , 0.4 f }
} , GL_TRIANGLE_FAN ) ;
// filled
float fw = w * val ;
quad = {
{ x , y , 0.8f , 0.4f , 0.4f } , { x + fw , y, 0.8f , 0.4f , 0.4f } ,
{ x + fw , y+ h , 0.8f , 0.4f , 0.4f } , { x , y+ h , 0.8f , 0.4f , 0.4f }
} ;
glBufferData ( GL_ARRAY_BUFFER , quad . size ( ) * sizeof ( V ) , quad . data ( ) , GL_DYNAMIC_DRAW ) ;
glVertexAttribPointer ( 0 , 2 , GL_FLOAT , GL_FALSE , sizeof ( V ) , ( void * ) 0 ) ;
glVertexAttribPointer ( 1 , 3 , GL_FLOAT , GL_FALSE , sizeof ( V ) , ( void * ) ( 2 * sizeof ( float ) ) ) ;
glDrawArrays ( GL_TRIANGLE_FAN , 0 , 4 ) ;
// knob
float kx = x + fw - 2 , ky = y - 2 , ks = h + 4 ;
quad = {
{ kx , ky , 1.0f , 1.0f , 1.0f } , { kx + ks , ky , 1.0f , 1.0f , 1.0f } ,
{ kx + ks , ky + ks , 1.0f , 1.0f , 1.0f } , { kx , ky + ks , 1.0f , 1.0f , 1.0f }
} ;
glBufferData ( GL_ARRAY_BUFFER , quad . size ( ) * sizeof ( V ) , quad . data ( ) , GL_DYNAMIC_DRAW ) ;
glVertexAttribPointer ( 0 , 2 , GL_FLOAT , GL_FALSE , sizeof ( V ) , ( void * ) 0 ) ;
glVertexAttribPointer ( 1 , 3 , GL_FLOAT , GL_FALSE , sizeof ( V ) , ( void * ) ( 2 * sizeof ( float ) ) ) ;
glDrawArrays ( GL_TRIANGLE_FAN , 0 , 4 ) ;
// Knob
float kx = x + fw - 2.0f , ky = y - 2.0f , ks = h + 4.0f ;
UploadAndDraw ( {
{ k x, ky , 1 , 1 , 1 } , { k x+ ks , k y , 1 , 1 , 1 } ,
{ k x+ ks , k y + ks , 1 , 1 , 1 } , { k x, k y + ks , 1 , 1 , 1 }
} , GL_TRIANGLE_FAN ) ;
}
public :
LevelEditor ( ) : Engine ( 3840 , 2160 , " Level Editor " ) . enable_vsync = true { }
// FIX 1 — constructor: enable_vsync set in body, not on base ctor call
LevelEditor ( ) : Engine ( 3840 , 2160 , " Level Editor " ) {
}
bool OnInitialize ( ) override {
// load monkey
try {
monkeyPrim_ = CreateOBJModel ( " monkey.obj " ) ;
}
catch ( . . . ) {
monkeyPrim_ = CreateCube ( glm : : vec3 ( 1.0f , 0.5f , 0.2f ) ) ;
}
try { monkeyPrim_ = CreateOBJModel ( " monkey.obj " ) ; }
catch ( . . . ) { monkeyPrim_ = CreateCube ( glm : : vec3 ( 1.0f , 0.5f , 0.2f ) ) ; }
// initial grid - fix glm::vec3 constructor
Place ( CreateGrid ( 20 , 1.0f , glm : : vec3 ( 0.4f , 0.4f , 0.4f ) ) ) ;
// FIX: use explicit-position overload — camera_target_ is not yet
// set at this point, so passing it would place the grid at (0,0,0)
// only by accident if Engine zero-inits it. Be explicit.
Place ( CreateGrid ( 20 , 1.0f , glm : : vec3 ( 0.4f ) ) , glm : : vec3 ( 0.0f ) ) ;
// camera
camera_position_ = glm : : vec3 ( 0 , 20 , 20 ) ;
glm : : vec3 dir = glm : : normalize ( glm : : vec3 ( 0 , 0 , 0 ) - camera_position_ ) ;
camera_target_ = camera_position_ + dir ;
camera_position_ = glm : : vec3 ( 0.0f , 20.0f , 20.0f ) ;
camera_target_ = camera_position_
+ glm : : normalize ( glm : : vec3 ( 0.0f ) - camera_position_ ) ;
return true ;
}
void OnUpdate ( float dt ) override {
// free flight WASD QE
glm : : vec3 front = glm : : normalize ( camera_target_ - camera_position_ ) ;
glm : : vec3 right = glm : : normalize ( glm : : cross ( front , camera_up_ ) ) ;
if ( KeyDown ( GLFW_KEY_W ) ) camera_position_ + = front * camSpeed_ * dt ;
if ( KeyDown ( GLFW_KEY_S ) ) camera_position_ - = front * camSpeed_ * dt ;
if ( KeyDown ( GLFW_KEY_A ) ) camera_position_ - = righ t * camSpeed_ * dt ;
if ( KeyDown ( GLFW_KEY_D ) ) camera_position_ + = right * camSpeed_ * dt ;
if ( KeyDown ( GLFW_KEY_Q ) ) camera_position_ - = camera_up_ * camSpeed_ * dt ;
if ( KeyDown ( GLFW_KEY_E ) ) camera_position_ + = camera_up_ * camSpeed_ * dt ;
if ( KeyDown ( GLFW_KEY_W ) ) camera_position_ + = front * camSpeed_ * dt ;
if ( KeyDown ( GLFW_KEY_S ) ) camera_position_ - = fron t * camSpeed_ * dt ;
if ( KeyDown ( GLFW_KEY_A ) ) camera_position_ - = right * camSpeed_ * dt ;
if ( KeyDown ( GLFW_KEY_D ) ) camera_position_ + = right * camSpeed_ * dt ;
if ( KeyDown ( GLFW_KEY_Q ) ) camera_position_ - = camera_up_ * camSpeed_ * dt ;
if ( KeyDown ( GLFW_KEY_E ) ) camera_position_ + = camera_up_ * camSpeed_ * dt ;
camera_target_ = camera_position_ + front ;
// mouse look on RMB
double mx , my ; glfwGetCursorPos ( window_ , & mx , & my ) ;
if ( glfwGetMouseButton ( window_ , GLFW_MOUSE_BUTTON_RIGHT ) = = GLFW_PRESS )
ProcessMouse ( mx , my ) ;
else firstMouse_ = true ;
double mx , my ; glfwGetCursorPos ( window_ , & mx , & my ) ;
if ( glfwGetMouseButton ( window_ , GLFW_MOUSE_BUTTON_RIGHT ) = = GLFW_PRESS )
ProcessMouse ( mx , my ) ;
else
firstMouse_ = true ;
// placement keys - fix glm::vec3 constructors
if ( KeyDown ( GLFW_KEY_1 ) ) Place ( CreateCube ( glm : : vec3 ( 0.8f , 0.2f , 0.2f ) ) ) ;
if ( KeyDown ( GLFW_KEY_2 ) ) Place ( CreateSphere ( 16 , glm : : vec3 ( 0.2f , 0.8f , 0.2f ) ) ) ;
if ( KeyDown ( GLFW_KEY_3 ) ) Place ( CreatePlane ( 5 , 5 , glm : : vec3 ( 0.2f , 0.2f , 0.8f ) ) ) ;
if ( KeyDown ( GLFW_KEY_4 ) ) Place ( CreateGrid ( 5 , 1.0f , glm : : vec3 ( 0.5f , 0.5f , 0.5f ) ) ) ;
if ( KeyDown ( GLFW_KEY_5 ) ) Place ( monkeyPrim_ ) ;
// FIX: KeyJustPressed — one object per key-press, not one per frame [web:61]
if ( KeyJustPressed ( GLFW_KEY_1 , prevKey1_ )) Place ( CreateCube ( glm : : vec3 ( 0.8f , 0.2f , 0.2f ) ) ) ;
if ( KeyJustPressed ( GLFW_KEY_2 , prevKey2_ )) Place ( CreateSphere ( 16 , glm : : vec3 ( 0.2f , 0.8f , 0.2f ) ) ) ;
if ( KeyJustPressed ( GLFW_KEY_3 , prevKey3_ )) Place ( CreatePlane ( 5.0f , 5.0f , glm : : vec3 ( 0.2f , 0.2f , 0.8f ) ) ) ;
if ( KeyJustPressed ( GLFW_KEY_4 , prevKey4_ )) Place ( CreateGrid ( 5 , 1.0f , glm : : vec3 ( 0.5f ) ) ) ;
if ( KeyJustPressed ( GLFW_KEY_5 , prevKey5_ )) Place ( monkeyPrim_ ) ;
// picking
if ( glfwGetMouseButton ( window_ , GLFW_MOUSE_BUTTON_LEFT ) = = GLFW_PRESS )
selected_ = Pick ( mx , my ) ;
if ( glfwGetMouseButton ( window_ , GLFW_MOUSE_BUTTON_LEFT ) = = GLFW_PRESS )
selected_ = Pick ( mx , my ) ;
// move selected with arrows
if ( selected_ ) {
if ( selected_ ) {
glm : : vec3 p = selected_ - > GetPosition ( ) ;
if ( KeyDown ( GLFW_KEY_UP ) ) p . z - = dt * 5 ;
if ( KeyDown ( GLFW_KEY_DOWN ) ) p . z + = dt * 5 ;
if ( KeyDown ( GLFW_KEY_LEFT ) ) p . x - = dt * 5 ;
if ( KeyDown ( GLFW_KEY_RIGHT ) ) p . x + = dt * 5 ;
if ( KeyDown ( GLFW_KEY_UP ) ) p . z - = dt * 5.0f ;
if ( KeyDown ( GLFW_KEY_DOWN ) ) p . z + = dt * 5.0f ;
if ( KeyDown ( GLFW_KEY_LEFT ) ) p . x - = dt * 5.0f ;
if ( KeyDown ( GLFW_KEY_RIGHT ) ) p . x + = dt * 5.0f ;
selected_ - > SetPosition ( p ) ;
}
@@ -323,70 +337,64 @@ public:
}
void OnRender ( ) override {
glUseProgram ( shader_program_ ) ;
int w = width_ , h = height_ ;
glm : : mat4 proj = useOrtho_
? glm : : ortho ( - 30.0f * float ( w ) / h , 30.0f * float ( w ) / h , - 30.0f , 30.0f , 0.1f , 1000.0f )
: glm : : perspective ( glm : : radians ( 6 0.0f) , float ( w ) / h , 0.1f , 1000.0f ) ;
glUniformMatrix4fv ( glGetUniformLocation ( shader_program_ , " uProjection " ) , 1 , GL_FALSE , & proj [ 0 ] [ 0 ] ) ;
? glm : : ortho ( - 30.0f * float ( width_ ) / height_ , 30.0f * float ( width_ ) / height_ ,
- 3 0.0f, 30.0f , 0.1f , 1000.0f )
: glm : : perspective ( glm : : radians ( 60.0f ) ,
float ( width_ ) / float ( height_ ) , 0.1f , 1000.0f ) ;
glUniformMatrix4fv ( glGetUniformLocation ( shader_program_ , " uProjection " ) ,
1 , GL_FALSE , & proj [ 0 ] [ 0 ] ) ;
// 1) 3D scene + overlay graphs
Engine : : OnRender ( ) ;
RenderOverlay ( ) ;
// 2) custom 2D UI: sliders + stats
RenderOverlay ( ) ; // draws graphs & text
// setup 2D
// ── 2D overlay ───────────────────────────────────────────────
glDisable ( GL_DEPTH_TEST ) ;
glEnable ( GL_BLEND ) ;
glBlendFunc ( GL_SRC_ALPHA , GL_ONE_MINUS_SRC_ALPHA ) ;
glBlendFunc ( GL_SRC_ALPHA , GL_ONE_MINUS_SRC_ALPHA ) ;
glUseProgram ( overlay_program ) ;
GLint loc = glGetUniformLocation ( overlay_program , " uOrtho " ) ;
glm : : mat4 ortho = glm : : ortho ( 0.0f , float ( width_ ) , 0.0f , float ( height_ ) ) ;
glUniformMatrix4fv ( loc , 1 , GL_FALSE , & ortho [ 0 ] [ 0 ] ) ;
glm : : mat4 ortho2D = glm : : ortho ( 0.0f , float ( width_ ) , 0.0f , float ( height_ ) ) ;
glUniformMatrix4fv ( glGetUniformLocation ( overlay_program , " uOrtho " ) ,
1 , GL_FALSE , & ortho2D [ 0 ] [ 0 ] ) ;
glBindVertexArray ( overlay_vao ) ;
glBindBuffer ( GL_ARRAY_BUFFER , overlay_vbo ) ;
// mouse state
double mx , my ; glfwGetCursorPos ( window_ , & mx , & my ) ;
my = height_ - my ; // flip Y coordinate for OpenGL
bool mb = glfwGetMouseButton ( window_ , GLFW_MOUSE_BUTTON_LEFT ) = = GLFW_PRESS ;
double mx , my ; glfwGetCursorPos ( window_ , & mx , & my ) ;
my = height_ - my ; // flip Y to match OpenGL bottom-left origin
bool mb = glfwGetMouseButton ( window_ , GLFW_MOUSE_BUTTON_LEFT ) = = GLFW_PRESS ;
// draw sliders and label s
DrawSlider ( 50 , 50 , 200 , 10 , lightR , dragR , mx , my , mb ) ;
DrawSlider ( 50 , 70 , 200 , 10 , lightG , dragG , mx , my , mb ) ;
DrawSlider ( 50 , 90 , 200 , 10 , lightB , dragB , mx , my , mb ) ;
DrawSlider ( 50 , 110 , 200 , 10 , lightI , dragI , mx , my , mb ) ;
// Slider s
DrawSlider ( 50 , 50 , 200 , 10 , lightR , dragR , mx , my , mb ) ;
DrawSlider ( 50 , 70 , 200 , 10 , lightG , dragG , mx , my , mb ) ;
DrawSlider ( 50 , 90 , 200 , 10 , lightB , dragB , mx , my , mb ) ;
DrawSlider ( 50 , 110 , 200 , 10 , lightI , dragI , mx , my , mb ) ;
if ( DrawButton( btnOrtho , useOrtho_ ? " Ortho On " : " Persp On " , mx , my , mb ) ) {
// FIX: DrawButton now takes Button& — state is stored per button
// FIX: labels use uppercase A– Z which DrawChar now supports
if ( DrawButton ( btnOrtho , useOrtho_ ? " ORTHO " : " PERSP " , mx , my , mb ) )
useOrtho_ = ! useOrtho_ ;
}
// FIX: was DrawChar('R',...) etc. — DrawChar now handles letters;
// coloured to match channel (red/green/blue/white)
DrawChar ( ' R ' , 30 , 52 , 10 , 1.0f , 0.3f , 0.3f ) ;
DrawChar ( ' G ' , 30 , 72 , 10 , 0.3f , 1.0f , 0.3f ) ;
DrawChar ( ' B ' , 30 , 92 , 10 , 0.3f , 0.5f , 1.0f ) ;
DrawChar ( ' I ' , 30 , 112 , 10 , 1.0f , 1.0f , 1.0f ) ;
// draw labels
DrawChar ( ' R ' , 30 , 50 , 12 ) ;
DrawChar ( ' G ' , 30 , 70 , 12 ) ;
DrawChar ( ' B ' , 30 , 90 , 12 ) ;
DrawChar ( ' I ' , 30 , 110 , 12 ) ;
// update 3D shader light
// Push updated light values to 3D shader
glUseProgram ( shader_program_ ) ;
glUniform3f ( glGetUniformLocation ( shader_program_ , " uLightColor " ) , lightR , lightG , lightB ) ;
glUniform1f ( glGetUniformLocation ( shader_program_ , " uAmbientStrength " ) , 0.1f * lightI ) ;
glUniform3f ( glGetUniformLocation ( shader_program_ , " uLightColor " ) ,
lightR , lightG , lightB ) ;
glUniform1f ( glGetUniformLocation ( shader_program_ , " uAmbientStrength " ) ,
0.1f * lightI ) ;
// restore
glBindVertexArray ( 0 ) ;
glUseProgram ( 0 ) ;
glDisable ( GL_BLEND ) ;
glEnable ( GL_DEPTH_TEST ) ;
}
void OnShutdown ( ) override {
Engine : : OnShutdown ( ) ;
}
void OnShutdown ( ) override { Engine : : OnShutdown ( ) ; }
} ;