2017-03-30 19:27:57 +02:00
/*
2025-08-17 18:56:17 +02:00
* Procedural 3 D Engine
* Copyright ( c ) 2025 Your Project
*
* This software is licensed under the MIT License .
* See LICENSE . md for full license information .
*
* Based on Vulkan examples by Sascha Willems ( MIT License )
*/
2017-03-30 19:27:57 +02:00
# include <imgui.h>
# include "vulkanexamplebase.h"
2020-07-28 20:20:38 +02:00
# include "VulkanglTFModel.h"
2025-08-17 18:56:17 +02:00
# include <cmath>
# include <iostream>
# ifndef M_PI
# define M_PI 3.14159265358979323846
# endif
// Enhanced Camera System with Maya-style controls
class OrbitCamera {
public :
// Constructor
OrbitCamera ( glm : : vec3 initialPosition = glm : : vec3 ( 0.0f , 0.0f , 8.0f ) ,
glm : : vec3 focusPoint = glm : : vec3 ( 0.0f , 0.0f , 0.0f ) ) {
m_currentFocusPoint = focusPoint ;
m_targetFocusPoint = focusPoint ;
// Calculate initial spherical coordinates from position
glm : : vec3 offset = initialPosition - focusPoint ;
m_currentDistance = glm : : length ( offset ) ;
m_targetDistance = m_currentDistance ;
// Calculate initial angles
CartesianToSpherical ( initialPosition , focusPoint , m_currentDistance , m_currentAzimuth , m_currentElevation ) ;
m_targetAzimuth = m_currentAzimuth ;
m_targetElevation = m_currentElevation ;
m_currentPosition = initialPosition ;
// Set default parameters
m_fov = 45.0f ;
m_smoothingFactor = 0.1f ;
m_orbitSensitivity = 0.5f ;
m_panSensitivity = 0.00003f ;
m_zoomSensitivity = 0.1f ;
// Set constraints
m_minDistance = 0.5f ;
m_maxDistance = 100.0f ;
m_minElevation = - 89.0f ;
m_maxElevation = 89.0f ;
m_viewMatrixDirty = true ;
}
// Core camera operations
void Orbit ( float deltaAzimuth , float deltaElevation , float deltaTime ) {
// Apply input to target angles (convert degrees to radians)
m_targetAzimuth + = glm : : radians ( deltaAzimuth * m_orbitSensitivity ) ;
m_targetElevation + = glm : : radians ( deltaElevation * m_orbitSensitivity ) ;
ClampConstraints ( ) ;
}
void Pan ( float deltaX , float deltaY , float deltaTime ) {
// Get camera's right and up vectors for screen-space panning (like legacy)
glm : : mat4 viewMatrix = GetViewMatrix ( ) ;
glm : : vec3 right = glm : : vec3 ( viewMatrix [ 0 ] [ 0 ] , viewMatrix [ 1 ] [ 0 ] , viewMatrix [ 2 ] [ 0 ] ) ;
glm : : vec3 up = glm : : vec3 ( viewMatrix [ 0 ] [ 1 ] , viewMatrix [ 1 ] [ 1 ] , viewMatrix [ 2 ] [ 1 ] ) ;
// Scale pan speed by distance to focus point for consistent behavior (like legacy)
float panScale = m_currentDistance * m_panSensitivity ;
// Move focus point (like legacy)
glm : : vec3 panOffset = right * deltaX * panScale + up * deltaY * panScale ;
m_targetFocusPoint + = panOffset ;
}
void Zoom ( float deltaDistance , float deltaTime ) {
// Apply zoom with distance-based scaling for consistent behavior (like legacy)
m_targetDistance + = deltaDistance * m_zoomSensitivity * m_currentDistance * 0.1f ;
m_targetDistance = glm : : clamp ( m_targetDistance , m_minDistance , m_maxDistance ) ;
}
void ZoomImmediate ( float deltaDistance , float deltaTime ) {
// Apply zoom with immediate response (no smoothing/velocity)
float zoomAmount = deltaDistance * m_zoomSensitivity * m_currentDistance * 0.1f ;
// Set both target AND current immediately (no smoothing)
m_targetDistance + = zoomAmount ;
m_targetDistance = glm : : clamp ( m_targetDistance , m_minDistance , m_maxDistance ) ;
m_currentDistance = m_targetDistance ;
m_viewMatrixDirty = true ;
}
void Update ( float deltaTime ) {
// Smooth interpolation towards target values
float lerpFactor = 1.0f - std : : pow ( m_smoothingFactor , deltaTime ) ;
m_currentDistance = glm : : mix ( m_currentDistance , m_targetDistance , lerpFactor ) ;
m_currentAzimuth = LerpAngle ( m_currentAzimuth , m_targetAzimuth , lerpFactor ) ;
m_currentElevation = glm : : mix ( m_currentElevation , m_targetElevation , lerpFactor ) ;
m_currentFocusPoint = glm : : mix ( m_currentFocusPoint , m_targetFocusPoint , lerpFactor ) ;
UpdatePosition ( ) ;
}
// Matrix access
glm : : mat4 GetViewMatrix ( ) const {
if ( m_viewMatrixDirty ) {
m_viewMatrix = glm : : lookAt ( m_currentPosition , m_currentFocusPoint , glm : : vec3 ( 0.0f , 1.0f , 0.0f ) ) ;
m_viewMatrixDirty = false ;
}
return m_viewMatrix ;
}
glm : : mat4 GetProjectionMatrix ( float aspectRatio , float nearPlane = 0.1f , float farPlane = 256.0f ) const {
return glm : : perspective ( glm : : radians ( m_fov ) , aspectRatio , nearPlane , farPlane ) ;
}
// Getters
glm : : vec3 GetPosition ( ) const { return m_currentPosition ; }
glm : : vec3 GetFocusPoint ( ) const { return m_currentFocusPoint ; }
float GetDistance ( ) const { return m_currentDistance ; }
float GetFOV ( ) const { return m_fov ; }
// Focus functionality
void SetFocusToSelection ( glm : : vec3 selectionCenter , float selectionRadius = 1.0f ) {
m_targetFocusPoint = selectionCenter ;
// Adjust distance based on selection size
if ( selectionRadius > 0.0f ) {
float recommendedDistance = selectionRadius * 3.0f ; // Good viewing distance
recommendedDistance = glm : : clamp ( recommendedDistance , m_minDistance , m_maxDistance ) ;
m_targetDistance = recommendedDistance ;
}
std : : cout < < " OrbitCamera: Focus set to selection at ( "
< < selectionCenter . x < < " , " < < selectionCenter . y < < " , " < < selectionCenter . z
< < " ) with radius " < < selectionRadius < < std : : endl ;
}
void FrameAll ( glm : : vec3 sceneCenter , float sceneRadius ) {
SetFocusToSelection ( sceneCenter , sceneRadius ) ;
}
// Configuration
void SetSensitivity ( float orbitSens , float panSens , float zoomSens ) {
m_orbitSensitivity = orbitSens ;
m_panSensitivity = panSens ;
m_zoomSensitivity = zoomSens ;
}
void SetSmoothingFactor ( float factor ) {
m_smoothingFactor = glm : : clamp ( factor , 0.0f , 1.0f ) ;
}
// Immediate (non-smoothed) operations for F key focus
void SetFocusPointImmediate ( glm : : vec3 focusPoint ) {
m_targetFocusPoint = focusPoint ;
m_currentFocusPoint = focusPoint ; // Immediate change, no smoothing
m_viewMatrixDirty = true ;
}
void SetDistanceImmediate ( float distance ) {
m_targetDistance = glm : : clamp ( distance , m_minDistance , m_maxDistance ) ;
m_currentDistance = m_targetDistance ; // Immediate change, no smoothing
UpdatePosition ( ) ;
m_viewMatrixDirty = true ;
}
void SetFocusToSelectionImmediate ( glm : : vec3 selectionCenter , float selectionRadius = 1.0f ) {
SetFocusPointImmediate ( selectionCenter ) ;
// Adjust distance based on selection size
if ( selectionRadius > 0.0f ) {
float recommendedDistance = selectionRadius * 3.0f ; // Good viewing distance
recommendedDistance = glm : : clamp ( recommendedDistance , m_minDistance , m_maxDistance ) ;
SetDistanceImmediate ( recommendedDistance ) ;
}
std : : cout < < " OrbitCamera: Focus set immediately to selection at ( "
< < selectionCenter . x < < " , " < < selectionCenter . y < < " , " < < selectionCenter . z
< < " ) with radius " < < selectionRadius < < std : : endl ;
}
void FrameAllImmediate ( glm : : vec3 sceneCenter , float sceneRadius ) {
SetFocusPointImmediate ( sceneCenter ) ;
// Calculate distance needed to frame the scene
float distance = sceneRadius / glm : : tan ( glm : : radians ( m_fov * 0.5f ) ) * 1.5f ; // 1.5x padding
SetDistanceImmediate ( distance ) ;
}
void PanImmediate ( float deltaX , float deltaY , float deltaTime ) {
// Get camera's right and up vectors for screen-space panning (like Pan method)
glm : : mat4 viewMatrix = GetViewMatrix ( ) ;
glm : : vec3 right = glm : : vec3 ( viewMatrix [ 0 ] [ 0 ] , viewMatrix [ 1 ] [ 0 ] , viewMatrix [ 2 ] [ 0 ] ) ;
glm : : vec3 up = glm : : vec3 ( viewMatrix [ 0 ] [ 1 ] , viewMatrix [ 1 ] [ 1 ] , viewMatrix [ 2 ] [ 1 ] ) ;
// Scale pan speed by distance to focus point for consistent behavior
float panScale = m_currentDistance * m_panSensitivity ;
// Calculate pan offset
glm : : vec3 panOffset = right * deltaX * panScale + up * deltaY * panScale ;
// Set both target AND current immediately (no smoothing)
m_targetFocusPoint + = panOffset ;
m_currentFocusPoint + = panOffset ;
m_viewMatrixDirty = true ;
}
private :
// Current state (interpolated)
glm : : vec3 m_currentPosition ;
glm : : vec3 m_currentFocusPoint ;
float m_currentDistance ;
float m_currentAzimuth ; // Horizontal angle around focus point
float m_currentElevation ; // Vertical angle (pitch)
// Target state (immediate input response)
glm : : vec3 m_targetFocusPoint ;
float m_targetDistance ;
float m_targetAzimuth ;
float m_targetElevation ;
// Camera parameters
float m_fov ; // Field of view in degrees
float m_smoothingFactor ; // 0.0 = no smoothing, 1.0 = maximum smoothing
// Sensitivity settings
float m_orbitSensitivity ;
float m_panSensitivity ;
float m_zoomSensitivity ;
// Constraints
float m_minDistance ;
float m_maxDistance ;
float m_minElevation ; // In degrees
float m_maxElevation ; // In degrees
// Cached matrices to avoid recalculation
mutable glm : : mat4 m_viewMatrix ;
mutable bool m_viewMatrixDirty ;
// Internal methods
void UpdatePosition ( ) {
m_currentPosition = SphericalToCartesian ( m_currentDistance , m_currentAzimuth , m_currentElevation ) + m_currentFocusPoint ;
m_viewMatrixDirty = true ;
}
void ClampConstraints ( ) {
m_targetDistance = glm : : clamp ( m_targetDistance , m_minDistance , m_maxDistance ) ;
m_targetElevation = glm : : clamp ( m_targetElevation , glm : : radians ( m_minElevation ) , glm : : radians ( m_maxElevation ) ) ;
// Normalize azimuth to 0-2π range
while ( m_targetAzimuth > 2.0f * M_PI ) m_targetAzimuth - = 2.0f * M_PI ;
while ( m_targetAzimuth < 0.0f ) m_targetAzimuth + = 2.0f * M_PI ;
}
glm : : vec3 SphericalToCartesian ( float distance , float azimuth , float elevation ) const {
// Standard spherical coordinate conversion (input in radians)
float x = distance * glm : : cos ( elevation ) * glm : : cos ( azimuth ) ;
float y = distance * glm : : sin ( elevation ) ;
float z = distance * glm : : cos ( elevation ) * glm : : sin ( azimuth ) ;
return glm : : vec3 ( x , y , z ) ;
}
void CartesianToSpherical ( glm : : vec3 position , glm : : vec3 center , float & distance , float & azimuth , float & elevation ) const {
glm : : vec3 offset = position - center ;
distance = glm : : length ( offset ) ;
if ( distance < 0.001f ) {
// Very close to center, use default values
azimuth = 0.0f ;
elevation = 0.0f ;
return ;
}
// Normalize offset for angle calculations
glm : : vec3 normalized = offset / distance ;
// Calculate azimuth (horizontal angle) - using atan for proper quadrant
azimuth = glm : : atan ( normalized . z , normalized . x ) ;
// Calculate elevation (vertical angle) - use asin but clamp input to avoid NaN
float sinElevation = glm : : clamp ( normalized . y , - 1.0f , 1.0f ) ;
elevation = glm : : asin ( sinElevation ) ;
}
float LerpAngle ( float from , float to , float t ) const {
// Handle angle wraparound for smooth interpolation (using radians)
float difference = to - from ;
// Wrap difference to [-π, π] range
if ( difference > M_PI ) difference - = 2.0f * M_PI ;
if ( difference < - M_PI ) difference + = 2.0f * M_PI ;
return from + difference * t ;
}
} ;
// Procedural Geometry Generation
struct ProceduralVertex {
glm : : vec3 position ;
glm : : vec3 normal ;
glm : : vec3 color ;
} ;
struct ProceduralShape {
std : : vector < ProceduralVertex > vertices ;
std : : vector < uint32_t > indices ;
std : : string name ;
int type ; // 0=cube, 1=sphere, 2=cylinder, 3=plane, 4=cone, 5=torus
// Shape parameters
struct {
float width = 2.0f , height = 2.0f , depth = 2.0f ;
int subdivisions = 1 ;
float radius = 1.0f ;
int segments = 16 ;
float majorRadius = 1.0f , minorRadius = 0.3f ;
} params ;
} ;
class ProceduralGeometry {
public :
static ProceduralShape generateCube ( float width = 4.0f , float height = 4.0f , float depth = 4.0f ) {
ProceduralShape shape ;
shape . name = " Cube " ;
shape . type = 0 ;
shape . params . width = width ;
shape . params . height = height ;
shape . params . depth = depth ;
float w = width * 0.5f ;
float h = height * 0.5f ;
float d = depth * 0.5f ;
// Define 24 vertices (4 per face, 6 faces) with colors
shape . vertices = {
// Front face (red)
{ { - w , - h , d } , { 0 , 0 , 1 } , { 1 , 0 , 0 } } , { { w , - h , d } , { 0 , 0 , 1 } , { 1 , 0 , 0 } } ,
{ { w , h , d } , { 0 , 0 , 1 } , { 1 , 0 , 0 } } , { { - w , h , d } , { 0 , 0 , 1 } , { 1 , 0 , 0 } } ,
// Back face (green)
{ { w , - h , - d } , { 0 , 0 , - 1 } , { 0 , 1 , 0 } } , { { - w , - h , - d } , { 0 , 0 , - 1 } , { 0 , 1 , 0 } } ,
{ { - w , h , - d } , { 0 , 0 , - 1 } , { 0 , 1 , 0 } } , { { w , h , - d } , { 0 , 0 , - 1 } , { 0 , 1 , 0 } } ,
// Left face (blue)
{ { - w , - h , - d } , { - 1 , 0 , 0 } , { 0 , 0 , 1 } } , { { - w , - h , d } , { - 1 , 0 , 0 } , { 0 , 0 , 1 } } ,
{ { - w , h , d } , { - 1 , 0 , 0 } , { 0 , 0 , 1 } } , { { - w , h , - d } , { - 1 , 0 , 0 } , { 0 , 0 , 1 } } ,
// Right face (yellow)
{ { w , - h , d } , { 1 , 0 , 0 } , { 1 , 1 , 0 } } , { { w , - h , - d } , { 1 , 0 , 0 } , { 1 , 1 , 0 } } ,
{ { w , h , - d } , { 1 , 0 , 0 } , { 1 , 1 , 0 } } , { { w , h , d } , { 1 , 0 , 0 } , { 1 , 1 , 0 } } ,
// Top face (magenta)
{ { - w , h , d } , { 0 , 1 , 0 } , { 1 , 0 , 1 } } , { { w , h , d } , { 0 , 1 , 0 } , { 1 , 0 , 1 } } ,
{ { w , h , - d } , { 0 , 1 , 0 } , { 1 , 0 , 1 } } , { { - w , h , - d } , { 0 , 1 , 0 } , { 1 , 0 , 1 } } ,
// Bottom face (cyan)
{ { - w , - h , - d } , { 0 , - 1 , 0 } , { 0 , 1 , 1 } } , { { w , - h , - d } , { 0 , - 1 , 0 } , { 0 , 1 , 1 } } ,
{ { w , - h , d } , { 0 , - 1 , 0 } , { 0 , 1 , 1 } } , { { - w , - h , d } , { 0 , - 1 , 0 } , { 0 , 1 , 1 } }
} ;
// Define indices for 12 triangles (2 per face)
shape . indices = {
0 , 1 , 2 , 0 , 2 , 3 , // Front
4 , 5 , 6 , 4 , 6 , 7 , // Back
8 , 9 , 10 , 8 , 10 , 11 , // Left
12 , 13 , 14 , 12 , 14 , 15 , // Right
16 , 17 , 18 , 16 , 18 , 19 , // Top
20 , 21 , 22 , 20 , 22 , 23 // Bottom
} ;
return shape ;
}
static ProceduralShape generateSphere ( float radius = 1.0f , int segments = 16 ) {
ProceduralShape shape ;
shape . name = " Sphere " ;
shape . type = 1 ;
shape . params . radius = radius ;
shape . params . segments = segments ;
// Generate sphere vertices using spherical coordinates
for ( int lat = 0 ; lat < = segments ; + + lat ) {
float theta = lat * M_PI / segments ;
float sinTheta = sin ( theta ) ;
float cosTheta = cos ( theta ) ;
for ( int lon = 0 ; lon < = segments ; + + lon ) {
float phi = lon * 2 * M_PI / segments ;
float sinPhi = sin ( phi ) ;
float cosPhi = cos ( phi ) ;
glm : : vec3 pos ( radius * sinTheta * cosPhi , radius * cosTheta , radius * sinTheta * sinPhi ) ;
glm : : vec3 normal = glm : : normalize ( pos ) ;
glm : : vec3 color ( 0.8f , 0.8f , 0.8f ) ; // Light gray color for sphere
shape . vertices . push_back ( { pos , normal , color } ) ;
}
}
// Generate indices
for ( int lat = 0 ; lat < segments ; + + lat ) {
for ( int lon = 0 ; lon < segments ; + + lon ) {
int first = lat * ( segments + 1 ) + lon ;
int second = first + segments + 1 ;
shape . indices . push_back ( first ) ;
shape . indices . push_back ( second ) ;
shape . indices . push_back ( first + 1 ) ;
shape . indices . push_back ( second ) ;
shape . indices . push_back ( second + 1 ) ;
shape . indices . push_back ( first + 1 ) ;
}
}
return shape ;
}
static ProceduralShape generatePlane ( float width = 2.0f , float height = 2.0f , int subdivisions = 1 ) {
ProceduralShape shape ;
shape . name = " Plane " ;
shape . type = 3 ;
shape . params . width = width ;
shape . params . height = height ;
shape . params . subdivisions = subdivisions ;
float w = width * 0.5f ;
float h = height * 0.5f ;
// Simple quad for now
shape . vertices = {
{ { - w , 0 , - h } , { 0 , 1 , 0 } , { 0.5f , 0.5f , 0.5f } } , // Gray plane
{ { w , 0 , - h } , { 0 , 1 , 0 } , { 0.5f , 0.5f , 0.5f } } ,
{ { w , 0 , h } , { 0 , 1 , 0 } , { 0.5f , 0.5f , 0.5f } } ,
{ { - w , 0 , h } , { 0 , 1 , 0 } , { 0.5f , 0.5f , 0.5f } }
} ;
shape . indices = { 0 , 1 , 2 , 0 , 2 , 3 } ;
return shape ;
}
static ProceduralShape generateGrid ( float size = 10.0f , int divisions = 10 ) {
ProceduralShape shape ;
shape . name = " Grid " ;
shape . type = 6 ; // Grid type
shape . params . width = size ;
shape . params . subdivisions = divisions ;
float step = size / divisions ;
float halfSize = size * 0.5f ;
// Create grid lines (white grid)
glm : : vec3 gridColor ( 1.0f , 1.0f , 1.0f ) ;
for ( int i = 0 ; i < = divisions ; + + i ) {
float pos = - halfSize + i * step ;
// Horizontal lines
shape . vertices . push_back ( { { - halfSize , 0 , pos } , { 0 , 1 , 0 } , gridColor } ) ;
shape . vertices . push_back ( { { halfSize , 0 , pos } , { 0 , 1 , 0 } , gridColor } ) ;
// Vertical lines
shape . vertices . push_back ( { { pos , 0 , - halfSize } , { 0 , 1 , 0 } , gridColor } ) ;
shape . vertices . push_back ( { { pos , 0 , halfSize } , { 0 , 1 , 0 } , gridColor } ) ;
}
// Generate indices for lines
for ( int i = 0 ; i < ( divisions + 1 ) * 4 ; i + = 2 ) {
shape . indices . push_back ( i ) ;
shape . indices . push_back ( i + 1 ) ;
}
return shape ;
}
static ProceduralShape generateCone ( float radius = 1.0f , float height = 2.0f , int segments = 16 ) {
ProceduralShape shape ;
shape . name = " Cone " ;
shape . type = 4 ; // Cone type
shape . params . radius = radius ;
shape . params . height = height ;
shape . params . segments = segments ;
// Add tip vertex (red cone tip)
shape . vertices . push_back ( { { 0 , height * 0.5f , 0 } , { 0 , 1 , 0 } , { 1.0f , 0.0f , 0.0f } } ) ;
// Add center vertex for base (dark red)
shape . vertices . push_back ( { { 0 , - height * 0.5f , 0 } , { 0 , - 1 , 0 } , { 0.5f , 0.0f , 0.0f } } ) ;
// Generate base vertices
for ( int i = 0 ; i < = segments ; + + i ) {
float angle = ( float ) i / segments * 2.0f * M_PI ;
float x = cos ( angle ) * radius ;
float z = sin ( angle ) * radius ;
glm : : vec3 color ( 0.8f , 0.2f , 0.2f ) ; // Light red
// Base vertex
shape . vertices . push_back ( { { x , - height * 0.5f , z } , { 0 , - 1 , 0 } , color } ) ;
// Side vertex (for side triangles)
glm : : vec3 sideNormal = glm : : normalize ( glm : : vec3 ( x , radius / height , z ) ) ;
shape . vertices . push_back ( { { x , - height * 0.5f , z } , sideNormal , color } ) ;
}
// Generate indices
// Side triangles (tip to base edge)
for ( int i = 0 ; i < segments ; + + i ) {
int baseStart = 2 + segments + 1 ;
shape . indices . push_back ( 0 ) ; // tip
shape . indices . push_back ( baseStart + ( i + 1 ) * 2 ) ;
shape . indices . push_back ( baseStart + i * 2 ) ;
}
// Base triangles
for ( int i = 0 ; i < segments ; + + i ) {
shape . indices . push_back ( 1 ) ; // center
shape . indices . push_back ( 2 + i ) ;
shape . indices . push_back ( 2 + ( ( i + 1 ) % ( segments + 1 ) ) ) ;
}
return shape ;
}
static ProceduralShape generateCylinder ( float radius = 1.0f , float height = 2.0f , int segments = 16 ) {
ProceduralShape shape ;
shape . name = " Cylinder " ;
shape . type = 5 ; // Cylinder type
shape . params . radius = radius ;
shape . params . height = height ;
shape . params . segments = segments ;
float halfHeight = height * 0.5f ;
// Add center vertices for caps (blue cylinder)
shape . vertices . push_back ( { { 0 , halfHeight , 0 } , { 0 , 1 , 0 } , { 0.0f , 0.0f , 1.0f } } ) ; // top center
shape . vertices . push_back ( { { 0 , - halfHeight , 0 } , { 0 , - 1 , 0 } , { 0.0f , 0.0f , 1.0f } } ) ; // bottom center
// Generate side vertices (double for proper normals)
for ( int i = 0 ; i < = segments ; + + i ) {
float angle = ( float ) i / segments * 2.0f * M_PI ;
float x = cos ( angle ) * radius ;
float z = sin ( angle ) * radius ;
glm : : vec3 normal = glm : : normalize ( glm : : vec3 ( x , 0 , z ) ) ;
glm : : vec3 color ( 0.2f , 0.4f , 1.0f ) ; // Light blue
// Top vertices
shape . vertices . push_back ( { { x , halfHeight , z } , { 0 , 1 , 0 } , color } ) ; // top cap
shape . vertices . push_back ( { { x , halfHeight , z } , normal , color } ) ; // top side
// Bottom vertices
shape . vertices . push_back ( { { x , - halfHeight , z } , { 0 , - 1 , 0 } , color } ) ; // bottom cap
shape . vertices . push_back ( { { x , - halfHeight , z } , normal , color } ) ; // bottom side
}
// Generate indices
for ( int i = 0 ; i < segments ; + + i ) {
int topCapStart = 2 ;
int bottomCapStart = 4 ;
// Top cap triangles
shape . indices . push_back ( 0 ) ; // top center
shape . indices . push_back ( topCapStart + ( ( i + 1 ) % ( segments + 1 ) ) * 4 ) ;
shape . indices . push_back ( topCapStart + i * 4 ) ;
// Bottom cap triangles
shape . indices . push_back ( 1 ) ; // bottom center
shape . indices . push_back ( bottomCapStart + i * 4 ) ;
shape . indices . push_back ( bottomCapStart + ( ( i + 1 ) % ( segments + 1 ) ) * 4 ) ;
// Side quads (as two triangles)
int topSide1 = topCapStart + 1 + i * 4 ;
int topSide2 = topCapStart + 1 + ( ( i + 1 ) % ( segments + 1 ) ) * 4 ;
int bottomSide1 = bottomCapStart + 1 + i * 4 ;
int bottomSide2 = bottomCapStart + 1 + ( ( i + 1 ) % ( segments + 1 ) ) * 4 ;
// First triangle
shape . indices . push_back ( topSide1 ) ;
shape . indices . push_back ( bottomSide1 ) ;
shape . indices . push_back ( topSide2 ) ;
// Second triangle
shape . indices . push_back ( topSide2 ) ;
shape . indices . push_back ( bottomSide1 ) ;
shape . indices . push_back ( bottomSide2 ) ;
}
return shape ;
}
static ProceduralShape generateTorus ( float majorRadius = 1.0f , float minorRadius = 0.3f , int majorSegments = 16 , int minorSegments = 8 ) {
ProceduralShape shape ;
shape . name = " Torus " ;
shape . type = 6 ; // Torus type
shape . params . majorRadius = majorRadius ;
shape . params . minorRadius = minorRadius ;
shape . params . segments = majorSegments ;
shape . params . subdivisions = minorSegments ;
// Generate vertices
for ( int i = 0 ; i < = majorSegments ; + + i ) {
float majorAngle = ( float ) i / majorSegments * 2.0f * M_PI ;
float cosMajor = cos ( majorAngle ) ;
float sinMajor = sin ( majorAngle ) ;
for ( int j = 0 ; j < = minorSegments ; + + j ) {
float minorAngle = ( float ) j / minorSegments * 2.0f * M_PI ;
float cosMinor = cos ( minorAngle ) ;
float sinMinor = sin ( minorAngle ) ;
// Calculate position
float x = ( majorRadius + minorRadius * cosMinor ) * cosMajor ;
float y = minorRadius * sinMinor ;
float z = ( majorRadius + minorRadius * cosMinor ) * sinMajor ;
// Calculate normal
glm : : vec3 center ( majorRadius * cosMajor , 0 , majorRadius * sinMajor ) ;
glm : : vec3 position ( x , y , z ) ;
glm : : vec3 normal = glm : : normalize ( position - center ) ;
// Use orange color for torus
glm : : vec3 color ( 1.0f , 0.5f , 0.0f ) ;
shape . vertices . push_back ( { { x , y , z } , normal , color } ) ;
}
}
// Generate indices
for ( int i = 0 ; i < majorSegments ; + + i ) {
for ( int j = 0 ; j < minorSegments ; + + j ) {
int current = i * ( minorSegments + 1 ) + j ;
int next = ( ( i + 1 ) % ( majorSegments + 1 ) ) * ( minorSegments + 1 ) + j ;
// First triangle
shape . indices . push_back ( current ) ;
shape . indices . push_back ( next ) ;
shape . indices . push_back ( current + 1 ) ;
// Second triangle
shape . indices . push_back ( next ) ;
shape . indices . push_back ( next + 1 ) ;
shape . indices . push_back ( current + 1 ) ;
}
}
return shape ;
}
} ;
// Simple Scene Object for basic hierarchy
struct SceneObject {
std : : string name ;
std : : string type ;
std : : string subtype ; // For procedural shapes (Cube, Sphere, etc.)
bool visible = true ;
// Simple geometry data (for procedural shapes)
std : : vector < ProceduralVertex > vertices ;
std : : vector < uint32_t > indices ;
glm : : vec3 position = { 0.0f , 0.0f , 0.0f } ; // Place objects at world origin
glm : : vec3 rotation = { 0.0f , 0.0f , 0.0f } ;
glm : : vec3 scale = { 1.0f , 1.0f , 1.0f } ; // Default unit scale
// Procedural shape parameters (for real-time editing)
struct ProceduralParams {
// Cube parameters
float cubeWidth = 2.0f ;
float cubeHeight = 2.0f ;
float cubeDepth = 2.0f ;
int cubeSubdivisions = 1 ;
// Sphere parameters
float sphereRadius = 1.0f ;
int sphereSegments = 16 ;
// Cylinder parameters
float cylinderRadius = 1.0f ;
float cylinderHeight = 2.0f ;
int cylinderSegments = 16 ;
// Cone parameters
float coneRadius = 1.0f ;
float coneHeight = 2.0f ;
int coneSegments = 16 ;
// Plane parameters
float planeWidth = 2.0f ;
float planeHeight = 2.0f ;
int planeSubdivisions = 1 ;
// Torus parameters
float torusMajorRadius = 1.0f ;
float torusMinorRadius = 0.3f ;
int torusMajorSegments = 16 ;
int torusMinorSegments = 8 ;
} proceduralParams ;
// Persistent Vulkan buffers (created once, used multiple times)
vks : : Buffer vertexBuffer ;
vks : : Buffer indexBuffer ;
bool buffersCreated = false ;
SceneObject ( const std : : string & objName , const std : : string & objType = " Object " )
: name ( objName ) , type ( objType ) { }
// Cleanup buffers when object is destroyed
void destroyBuffers ( vks : : VulkanDevice * device ) {
if ( buffersCreated ) {
vertexBuffer . destroy ( ) ;
indexBuffer . destroy ( ) ;
buffersCreated = false ;
}
}
// Calculate bounding box center for camera focus
glm : : vec3 getBoundingBoxCenter ( ) const {
if ( vertices . empty ( ) ) return position ;
glm : : vec3 minPos = vertices [ 0 ] . position ;
glm : : vec3 maxPos = vertices [ 0 ] . position ;
for ( const auto & vertex : vertices ) {
minPos = glm : : min ( minPos , vertex . position ) ;
maxPos = glm : : max ( maxPos , vertex . position ) ;
}
// Apply object transformation
glm : : vec3 center = ( minPos + maxPos ) * 0.5f ;
center = position + center * scale ; // Simple transform (rotation not included for simplicity)
return center ;
}
// Calculate bounding radius for camera focus
float getBoundingRadius ( ) const {
if ( vertices . empty ( ) ) return 1.0f ;
glm : : vec3 center = getBoundingBoxCenter ( ) ;
float maxRadius = 0.0f ;
for ( const auto & vertex : vertices ) {
glm : : vec3 transformedPos = position + vertex . position * scale ;
float distance = glm : : length ( transformedPos - center ) ;
maxRadius = std : : max ( maxRadius , distance ) ;
}
return std : : max ( maxRadius , 0.5f ) ; // Minimum radius of 0.5
}
// Regenerate geometry based on current procedural parameters
void regenerateGeometry ( ) {
if ( type ! = " Procedural " ) return ;
ProceduralShape shape ;
if ( subtype = = " Cube " ) {
shape = ProceduralGeometry : : generateCube ( proceduralParams . cubeWidth ,
proceduralParams . cubeHeight ,
proceduralParams . cubeDepth ) ;
} else if ( subtype = = " Sphere " ) {
shape = ProceduralGeometry : : generateSphere ( proceduralParams . sphereRadius ,
proceduralParams . sphereSegments ) ;
} else if ( subtype = = " Cylinder " ) {
shape = ProceduralGeometry : : generateCylinder ( proceduralParams . cylinderRadius ,
proceduralParams . cylinderHeight ,
proceduralParams . cylinderSegments ) ;
} else if ( subtype = = " Cone " ) {
shape = ProceduralGeometry : : generateCone ( proceduralParams . coneRadius ,
proceduralParams . coneHeight ,
proceduralParams . coneSegments ) ;
} else if ( subtype = = " Plane " ) {
shape = ProceduralGeometry : : generatePlane ( proceduralParams . planeWidth ,
proceduralParams . planeHeight ,
proceduralParams . planeSubdivisions ) ;
} else if ( subtype = = " Torus " ) {
shape = ProceduralGeometry : : generateTorus ( proceduralParams . torusMajorRadius ,
proceduralParams . torusMinorRadius ,
proceduralParams . torusMajorSegments ,
proceduralParams . torusMinorSegments ) ;
}
// Update geometry data
vertices = shape . vertices ;
indices = shape . indices ;
// Destroy existing buffers if they exist (to handle size changes)
if ( buffersCreated ) {
vertexBuffer . destroy ( ) ;
indexBuffer . destroy ( ) ;
}
// Mark buffers as needing recreation
buffersCreated = false ;
}
} ;
// Simple scene management
struct SimpleSceneManager {
std : : vector < SceneObject > objects ;
int selectedIndex = - 1 ;
vks : : VulkanDevice * device = nullptr ;
void addObject ( const std : : string & name , const std : : string & type = " Procedural " ) {
objects . emplace_back ( name , type ) ;
selectedIndex = static_cast < int > ( objects . size ( ) - 1 ) ;
}
void addProceduralShape ( const std : : string & shapeName , const std : : string & shapeType ) {
std : : string fullName = shapeName + " " + std : : to_string ( getObjectCount ( ) + 1 ) ;
objects . emplace_back ( fullName , " Procedural " ) ;
// Generate actual geometry based on shape type
SceneObject & newObj = objects . back ( ) ;
newObj . subtype = shapeType ; // Set the shape subtype (Cube, Sphere, etc.)
// Place all objects at world origin (0,0,0)
// Users can move them via transform controls in Inspector
newObj . position . x = 0.0f ;
newObj . position . y = 0.0f ;
newObj . position . z = 0.0f ;
// Initialize procedural parameters based on shape type (use regenerateGeometry to create initial geometry)
newObj . regenerateGeometry ( ) ;
// Create Vulkan buffers for the geometry (delayed until first render)
// createBuffersForObject(newObj);
selectedIndex = static_cast < int > ( objects . size ( ) - 1 ) ;
}
void createBuffersForObject ( SceneObject & obj ) {
// Enhanced validation
if ( ! device ) {
std : : cout < < " Error: Device not available for buffer creation " < < std : : endl ;
return ;
}
if ( obj . vertices . empty ( ) | | obj . indices . empty ( ) ) {
std : : cout < < " Error: Cannot create buffers for " < < obj . name < < " - geometry data is empty " < < std : : endl ;
return ;
}
if ( obj . buffersCreated ) {
std : : cout < < " Info: Buffers already created for " < < obj . name < < std : : endl ;
return ;
}
// Validate geometry data size
if ( obj . vertices . size ( ) > 1000000 | | obj . indices . size ( ) > 3000000 ) {
std : : cout < < " Warning: Large geometry for " < < obj . name < < " - vertices: " < < obj . vertices . size ( ) < < " , indices: " < < obj . indices . size ( ) < < std : : endl ;
}
try {
std : : cout < < " Creating buffers for " < < obj . name < < " with " < < obj . vertices . size ( ) < < " vertices and " < < obj . indices . size ( ) < < " indices " < < std : : endl ;
// Create vertex buffer
VkDeviceSize vertexBufferSize = obj . vertices . size ( ) * sizeof ( ProceduralVertex ) ;
if ( vertexBufferSize = = 0 ) {
std : : cout < < " Error: Zero vertex buffer size for " < < obj . name < < std : : endl ;
return ;
}
VkResult result = device - > createBuffer (
VK_BUFFER_USAGE_VERTEX_BUFFER_BIT ,
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT ,
& obj . vertexBuffer ,
vertexBufferSize ,
( void * ) obj . vertices . data ( )
) ;
if ( result ! = VK_SUCCESS ) {
std : : cout < < " Failed to create vertex buffer for " < < obj . name < < " - VkResult: " < < result < < std : : endl ;
return ;
}
// Validate vertex buffer creation
if ( obj . vertexBuffer . buffer = = VK_NULL_HANDLE ) {
std : : cout < < " Error: Vertex buffer handle is null for " < < obj . name < < std : : endl ;
return ;
}
// Create index buffer
VkDeviceSize indexBufferSize = obj . indices . size ( ) * sizeof ( uint32_t ) ;
if ( indexBufferSize = = 0 ) {
std : : cout < < " Error: Zero index buffer size for " < < obj . name < < std : : endl ;
obj . vertexBuffer . destroy ( ) ;
return ;
}
result = device - > createBuffer (
VK_BUFFER_USAGE_INDEX_BUFFER_BIT ,
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT ,
& obj . indexBuffer ,
indexBufferSize ,
( void * ) obj . indices . data ( )
) ;
if ( result ! = VK_SUCCESS ) {
std : : cout < < " Failed to create index buffer for " < < obj . name < < " - VkResult: " < < result < < std : : endl ;
obj . vertexBuffer . destroy ( ) ;
return ;
}
// Validate index buffer creation
if ( obj . indexBuffer . buffer = = VK_NULL_HANDLE ) {
std : : cout < < " Error: Index buffer handle is null for " < < obj . name < < std : : endl ;
obj . vertexBuffer . destroy ( ) ;
return ;
}
obj . buffersCreated = true ;
std : : cout < < " Successfully created buffers for " < < obj . name < < " (vertex: " < < vertexBufferSize < < " bytes, index: " < < indexBufferSize < < " bytes) " < < std : : endl ;
}
catch ( const std : : exception & e ) {
std : : cout < < " Exception creating buffers for " < < obj . name < < " : " < < e . what ( ) < < std : : endl ;
obj . buffersCreated = false ;
// Clean up any partially created buffers
if ( obj . vertexBuffer . buffer ! = VK_NULL_HANDLE ) {
obj . vertexBuffer . destroy ( ) ;
}
if ( obj . indexBuffer . buffer ! = VK_NULL_HANDLE ) {
obj . indexBuffer . destroy ( ) ;
}
}
}
void removeObject ( int index ) {
if ( index > = 0 & & index < objects . size ( ) ) {
// Clean up buffers before removing object
objects [ index ] . destroyBuffers ( device ) ;
objects . erase ( objects . begin ( ) + index ) ;
if ( selectedIndex > = objects . size ( ) ) {
selectedIndex = static_cast < int > ( objects . size ( ) - 1 ) ;
}
}
}
void clearScene ( ) {
// Clean up all buffers before clearing
for ( auto & obj : objects ) {
obj . destroyBuffers ( device ) ;
}
objects . clear ( ) ;
selectedIndex = - 1 ;
}
int getObjectCount ( ) const {
return static_cast < int > ( objects . size ( ) ) ;
}
// Selection management
void setSelectedIndex ( int index ) {
if ( index > = - 1 & & index < static_cast < int > ( objects . size ( ) ) ) {
selectedIndex = index ;
}
}
int getSelectedIndex ( ) const {
return selectedIndex ;
}
SceneObject * getSelectedObject ( ) {
if ( selectedIndex > = 0 & & selectedIndex < static_cast < int > ( objects . size ( ) ) ) {
return & objects [ selectedIndex ] ;
}
return nullptr ;
}
bool hasSelection ( ) const {
return selectedIndex > = 0 & & selectedIndex < static_cast < int > ( objects . size ( ) ) ;
}
void clearSelection ( ) {
selectedIndex = - 1 ;
}
} sceneManager ;
2017-03-30 19:27:57 +02:00
// Options and values to display/toggle from the UI
struct UISettings {
2025-08-17 18:56:17 +02:00
bool displayModels = false ;
bool displayBackground = false ;
2017-03-30 19:27:57 +02:00
bool animateLight = false ;
float lightSpeed = 0.25f ;
2017-04-22 11:50:13 +02:00
std : : array < float , 50 > frameTimes { } ;
2017-03-30 19:27:57 +02:00
float frameTimeMin = 9999.0f , frameTimeMax = 0.0f ;
float lightTimer = 0.0f ;
2025-08-17 18:56:17 +02:00
bool showGrid = true ;
float gridSize = 10.0f ;
int gridDivisions = 10 ;
// Panel visibility settings (for Windows menu)
bool showViewportPanel = true ;
bool showInspectorPanel = true ;
bool showSceneHierarchyPanel = true ;
bool showAssetBrowserPanel = true ;
bool showConsolePanel = false ; // Hidden by default
2017-03-30 19:27:57 +02:00
} uiSettings ;
// ----------------------------------------------------------------------------
// ImGUI class
// ----------------------------------------------------------------------------
class ImGUI {
private :
// Vulkan resources for rendering the UI
VkSampler sampler ;
vks : : Buffer vertexBuffer ;
vks : : Buffer indexBuffer ;
int32_t vertexCount = 0 ;
int32_t indexCount = 0 ;
VkDeviceMemory fontMemory = VK_NULL_HANDLE ;
VkImage fontImage = VK_NULL_HANDLE ;
VkImageView fontView = VK_NULL_HANDLE ;
VkPipelineCache pipelineCache ;
VkPipelineLayout pipelineLayout ;
VkPipeline pipeline ;
VkDescriptorPool descriptorPool ;
VkDescriptorSetLayout descriptorSetLayout ;
VkDescriptorSet descriptorSet ;
vks : : VulkanDevice * device ;
2022-07-12 18:46:39 -04:00
VkPhysicalDeviceDriverProperties driverProperties = { } ;
2017-03-31 09:52:26 +02:00
VulkanExampleBase * example ;
2023-02-25 10:27:47 +01:00
ImGuiStyle vulkanStyle ;
int selectedStyle = 0 ;
2017-03-30 19:27:57 +02:00
public :
// UI params are set via push constants
struct PushConstBlock {
glm : : vec2 scale ;
glm : : vec2 translate ;
} pushConstBlock ;
2020-05-29 16:08:53 +01:00
ImGUI ( VulkanExampleBase * example ) : example ( example )
2017-03-31 09:52:26 +02:00
{
device = example - > vulkanDevice ;
2018-08-29 18:24:39 +02:00
ImGui : : CreateContext ( ) ;
2022-07-12 18:46:39 -04:00
//SRS - Set ImGui font and style scale factors to handle retina and other HiDPI displays
ImGuiIO & io = ImGui : : GetIO ( ) ;
2024-05-23 21:56:42 +02:00
io . FontGlobalScale = example - > ui . scale ;
2022-07-12 18:46:39 -04:00
ImGuiStyle & style = ImGui : : GetStyle ( ) ;
2024-05-23 21:56:42 +02:00
style . ScaleAllSizes ( example - > ui . scale ) ;
2017-03-31 09:52:26 +02:00
} ;
2020-05-29 16:08:53 +01:00
2017-03-30 19:27:57 +02:00
~ ImGUI ( )
{
2018-08-29 18:24:39 +02:00
ImGui : : DestroyContext ( ) ;
2017-03-30 19:27:57 +02:00
// Release all Vulkan resources required for rendering imGui
vertexBuffer . destroy ( ) ;
indexBuffer . destroy ( ) ;
vkDestroyImage ( device - > logicalDevice , fontImage , nullptr ) ;
vkDestroyImageView ( device - > logicalDevice , fontView , nullptr ) ;
vkFreeMemory ( device - > logicalDevice , fontMemory , nullptr ) ;
vkDestroySampler ( device - > logicalDevice , sampler , nullptr ) ;
vkDestroyPipelineCache ( device - > logicalDevice , pipelineCache , nullptr ) ;
vkDestroyPipeline ( device - > logicalDevice , pipeline , nullptr ) ;
vkDestroyPipelineLayout ( device - > logicalDevice , pipelineLayout , nullptr ) ;
vkDestroyDescriptorPool ( device - > logicalDevice , descriptorPool , nullptr ) ;
vkDestroyDescriptorSetLayout ( device - > logicalDevice , descriptorSetLayout , nullptr ) ;
}
// Initialize styles, keys, etc.
void init ( float width , float height )
{
// Color scheme
2023-02-25 10:27:47 +01:00
vulkanStyle = ImGui : : GetStyle ( ) ;
vulkanStyle . Colors [ ImGuiCol_TitleBg ] = ImVec4 ( 1.0f , 0.0f , 0.0f , 0.6f ) ;
vulkanStyle . Colors [ ImGuiCol_TitleBgActive ] = ImVec4 ( 1.0f , 0.0f , 0.0f , 0.8f ) ;
vulkanStyle . Colors [ ImGuiCol_MenuBarBg ] = ImVec4 ( 1.0f , 0.0f , 0.0f , 0.4f ) ;
vulkanStyle . Colors [ ImGuiCol_Header ] = ImVec4 ( 1.0f , 0.0f , 0.0f , 0.4f ) ;
vulkanStyle . Colors [ ImGuiCol_CheckMark ] = ImVec4 ( 0.0f , 1.0f , 0.0f , 1.0f ) ;
setStyle ( 0 ) ;
2017-03-30 19:27:57 +02:00
// Dimensions
ImGuiIO & io = ImGui : : GetIO ( ) ;
io . DisplaySize = ImVec2 ( width , height ) ;
io . DisplayFramebufferScale = ImVec2 ( 1.0f , 1.0f ) ;
2023-02-25 09:37:08 +01:00
# if defined(_WIN32)
// If we directly work with os specific key codes, we need to map special key types like tab
io . KeyMap [ ImGuiKey_Tab ] = VK_TAB ;
io . KeyMap [ ImGuiKey_LeftArrow ] = VK_LEFT ;
io . KeyMap [ ImGuiKey_RightArrow ] = VK_RIGHT ;
io . KeyMap [ ImGuiKey_UpArrow ] = VK_UP ;
io . KeyMap [ ImGuiKey_DownArrow ] = VK_DOWN ;
io . KeyMap [ ImGuiKey_Backspace ] = VK_BACK ;
io . KeyMap [ ImGuiKey_Enter ] = VK_RETURN ;
2023-02-25 09:46:56 +01:00
io . KeyMap [ ImGuiKey_Space ] = VK_SPACE ;
io . KeyMap [ ImGuiKey_Delete ] = VK_DELETE ;
2023-02-25 09:37:08 +01:00
# endif
2017-03-30 19:27:57 +02:00
}
2023-02-25 10:27:47 +01:00
void setStyle ( uint32_t index )
{
switch ( index )
{
case 0 :
{
ImGuiStyle & style = ImGui : : GetStyle ( ) ;
style = vulkanStyle ;
break ;
}
case 1 :
ImGui : : StyleColorsClassic ( ) ;
break ;
case 2 :
ImGui : : StyleColorsDark ( ) ;
break ;
case 3 :
ImGui : : StyleColorsLight ( ) ;
break ;
2025-08-17 18:56:17 +02:00
case 4 : // Blue theme
{
ImGuiStyle & style = ImGui : : GetStyle ( ) ;
style = ImGui : : GetStyle ( ) ;
style . Colors [ ImGuiCol_TitleBg ] = ImVec4 ( 0.0f , 0.3f , 0.8f , 0.6f ) ;
style . Colors [ ImGuiCol_TitleBgActive ] = ImVec4 ( 0.0f , 0.4f , 1.0f , 0.8f ) ;
style . Colors [ ImGuiCol_MenuBarBg ] = ImVec4 ( 0.0f , 0.2f , 0.6f , 0.4f ) ;
style . Colors [ ImGuiCol_Header ] = ImVec4 ( 0.0f , 0.3f , 0.7f , 0.4f ) ;
style . Colors [ ImGuiCol_CheckMark ] = ImVec4 ( 0.0f , 1.0f , 1.0f , 1.0f ) ;
style . Colors [ ImGuiCol_WindowBg ] = ImVec4 ( 0.05f , 0.05f , 0.15f , 0.9f ) ;
break ;
}
case 5 : // Green theme
{
ImGuiStyle & style = ImGui : : GetStyle ( ) ;
style = ImGui : : GetStyle ( ) ;
style . Colors [ ImGuiCol_TitleBg ] = ImVec4 ( 0.0f , 0.6f , 0.2f , 0.6f ) ;
style . Colors [ ImGuiCol_TitleBgActive ] = ImVec4 ( 0.0f , 0.8f , 0.3f , 0.8f ) ;
style . Colors [ ImGuiCol_MenuBarBg ] = ImVec4 ( 0.0f , 0.4f , 0.1f , 0.4f ) ;
style . Colors [ ImGuiCol_Header ] = ImVec4 ( 0.0f , 0.5f , 0.2f , 0.4f ) ;
style . Colors [ ImGuiCol_CheckMark ] = ImVec4 ( 0.0f , 1.0f , 0.0f , 1.0f ) ;
style . Colors [ ImGuiCol_WindowBg ] = ImVec4 ( 0.05f , 0.15f , 0.05f , 0.9f ) ;
break ;
}
case 6 : // Purple theme
{
ImGuiStyle & style = ImGui : : GetStyle ( ) ;
style = ImGui : : GetStyle ( ) ;
style . Colors [ ImGuiCol_TitleBg ] = ImVec4 ( 0.5f , 0.0f , 0.8f , 0.6f ) ;
style . Colors [ ImGuiCol_TitleBgActive ] = ImVec4 ( 0.7f , 0.0f , 1.0f , 0.8f ) ;
style . Colors [ ImGuiCol_MenuBarBg ] = ImVec4 ( 0.3f , 0.0f , 0.6f , 0.4f ) ;
style . Colors [ ImGuiCol_Header ] = ImVec4 ( 0.4f , 0.0f , 0.7f , 0.4f ) ;
style . Colors [ ImGuiCol_CheckMark ] = ImVec4 ( 1.0f , 0.0f , 1.0f , 1.0f ) ;
style . Colors [ ImGuiCol_WindowBg ] = ImVec4 ( 0.1f , 0.05f , 0.15f , 0.9f ) ;
break ;
}
2023-02-25 10:27:47 +01:00
}
}
2017-03-30 19:27:57 +02:00
// Initialize all Vulkan resources used by the ui
2025-08-17 18:56:17 +02:00
void initResources ( VkRenderPass renderPass , VkQueue copyQueue , const std : : string & shadersPath , VkFormat colorFormat = VK_FORMAT_UNDEFINED , VkFormat depthFormat = VK_FORMAT_UNDEFINED )
2017-03-30 19:27:57 +02:00
{
ImGuiIO & io = ImGui : : GetIO ( ) ;
// Create font texture
unsigned char * fontData ;
int texWidth , texHeight ;
io . Fonts - > GetTexDataAsRGBA32 ( & fontData , & texWidth , & texHeight ) ;
VkDeviceSize uploadSize = texWidth * texHeight * 4 * sizeof ( char ) ;
2022-07-12 18:46:39 -04:00
//SRS - Get Vulkan device driver information if available, use later for display
if ( device - > extensionSupported ( VK_KHR_DRIVER_PROPERTIES_EXTENSION_NAME ) )
{
VkPhysicalDeviceProperties2 deviceProperties2 = { } ;
deviceProperties2 . sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2 ;
deviceProperties2 . pNext = & driverProperties ;
driverProperties . sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DRIVER_PROPERTIES ;
vkGetPhysicalDeviceProperties2 ( device - > physicalDevice , & deviceProperties2 ) ;
}
2017-03-30 19:27:57 +02:00
// Create target image for copy
VkImageCreateInfo imageInfo = vks : : initializers : : imageCreateInfo ( ) ;
imageInfo . imageType = VK_IMAGE_TYPE_2D ;
imageInfo . format = VK_FORMAT_R8G8B8A8_UNORM ;
imageInfo . extent . width = texWidth ;
imageInfo . extent . height = texHeight ;
imageInfo . extent . depth = 1 ;
imageInfo . mipLevels = 1 ;
imageInfo . arrayLayers = 1 ;
imageInfo . samples = VK_SAMPLE_COUNT_1_BIT ;
imageInfo . tiling = VK_IMAGE_TILING_OPTIMAL ;
imageInfo . usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT ;
imageInfo . sharingMode = VK_SHARING_MODE_EXCLUSIVE ;
imageInfo . initialLayout = VK_IMAGE_LAYOUT_UNDEFINED ;
VK_CHECK_RESULT ( vkCreateImage ( device - > logicalDevice , & imageInfo , nullptr , & fontImage ) ) ;
VkMemoryRequirements memReqs ;
vkGetImageMemoryRequirements ( device - > logicalDevice , fontImage , & memReqs ) ;
VkMemoryAllocateInfo memAllocInfo = vks : : initializers : : memoryAllocateInfo ( ) ;
memAllocInfo . allocationSize = memReqs . size ;
memAllocInfo . memoryTypeIndex = device - > getMemoryType ( memReqs . memoryTypeBits , VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT ) ;
VK_CHECK_RESULT ( vkAllocateMemory ( device - > logicalDevice , & memAllocInfo , nullptr , & fontMemory ) ) ;
VK_CHECK_RESULT ( vkBindImageMemory ( device - > logicalDevice , fontImage , fontMemory , 0 ) ) ;
// Image view
VkImageViewCreateInfo viewInfo = vks : : initializers : : imageViewCreateInfo ( ) ;
viewInfo . image = fontImage ;
viewInfo . viewType = VK_IMAGE_VIEW_TYPE_2D ;
viewInfo . format = VK_FORMAT_R8G8B8A8_UNORM ;
viewInfo . subresourceRange . aspectMask = VK_IMAGE_ASPECT_COLOR_BIT ;
viewInfo . subresourceRange . levelCount = 1 ;
viewInfo . subresourceRange . layerCount = 1 ;
VK_CHECK_RESULT ( vkCreateImageView ( device - > logicalDevice , & viewInfo , nullptr , & fontView ) ) ;
// Staging buffers for font data upload
vks : : Buffer stagingBuffer ;
VK_CHECK_RESULT ( device - > createBuffer (
VK_BUFFER_USAGE_TRANSFER_SRC_BIT ,
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT ,
& stagingBuffer ,
uploadSize ) ) ;
stagingBuffer . map ( ) ;
memcpy ( stagingBuffer . mapped , fontData , uploadSize ) ;
stagingBuffer . unmap ( ) ;
// Copy buffer data to font image
VkCommandBuffer copyCmd = device - > createCommandBuffer ( VK_COMMAND_BUFFER_LEVEL_PRIMARY , true ) ;
// Prepare for transfer
vks : : tools : : setImageLayout (
copyCmd ,
fontImage ,
VK_IMAGE_ASPECT_COLOR_BIT ,
VK_IMAGE_LAYOUT_UNDEFINED ,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL ,
VK_PIPELINE_STAGE_HOST_BIT ,
VK_PIPELINE_STAGE_TRANSFER_BIT ) ;
// Copy
VkBufferImageCopy bufferCopyRegion = { } ;
bufferCopyRegion . imageSubresource . aspectMask = VK_IMAGE_ASPECT_COLOR_BIT ;
bufferCopyRegion . imageSubresource . layerCount = 1 ;
bufferCopyRegion . imageExtent . width = texWidth ;
bufferCopyRegion . imageExtent . height = texHeight ;
bufferCopyRegion . imageExtent . depth = 1 ;
vkCmdCopyBufferToImage (
copyCmd ,
stagingBuffer . buffer ,
fontImage ,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL ,
1 ,
& bufferCopyRegion
) ;
// Prepare for shader read
vks : : tools : : setImageLayout (
copyCmd ,
fontImage ,
VK_IMAGE_ASPECT_COLOR_BIT ,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL ,
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL ,
VK_PIPELINE_STAGE_TRANSFER_BIT ,
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT ) ;
device - > flushCommandBuffer ( copyCmd , copyQueue , true ) ;
stagingBuffer . destroy ( ) ;
// Font texture Sampler
VkSamplerCreateInfo samplerInfo = vks : : initializers : : samplerCreateInfo ( ) ;
samplerInfo . magFilter = VK_FILTER_LINEAR ;
samplerInfo . minFilter = VK_FILTER_LINEAR ;
samplerInfo . mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR ;
samplerInfo . addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE ;
samplerInfo . addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE ;
samplerInfo . addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE ;
samplerInfo . borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE ;
VK_CHECK_RESULT ( vkCreateSampler ( device - > logicalDevice , & samplerInfo , nullptr , & sampler ) ) ;
// Descriptor pool
std : : vector < VkDescriptorPoolSize > poolSizes = {
vks : : initializers : : descriptorPoolSize ( VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER , 1 )
} ;
VkDescriptorPoolCreateInfo descriptorPoolInfo = vks : : initializers : : descriptorPoolCreateInfo ( poolSizes , 2 ) ;
VK_CHECK_RESULT ( vkCreateDescriptorPool ( device - > logicalDevice , & descriptorPoolInfo , nullptr , & descriptorPool ) ) ;
// Descriptor set layout
std : : vector < VkDescriptorSetLayoutBinding > setLayoutBindings = {
vks : : initializers : : descriptorSetLayoutBinding ( VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER , VK_SHADER_STAGE_FRAGMENT_BIT , 0 ) ,
} ;
VkDescriptorSetLayoutCreateInfo descriptorLayout = vks : : initializers : : descriptorSetLayoutCreateInfo ( setLayoutBindings ) ;
VK_CHECK_RESULT ( vkCreateDescriptorSetLayout ( device - > logicalDevice , & descriptorLayout , nullptr , & descriptorSetLayout ) ) ;
// Descriptor set
VkDescriptorSetAllocateInfo allocInfo = vks : : initializers : : descriptorSetAllocateInfo ( descriptorPool , & descriptorSetLayout , 1 ) ;
VK_CHECK_RESULT ( vkAllocateDescriptorSets ( device - > logicalDevice , & allocInfo , & descriptorSet ) ) ;
VkDescriptorImageInfo fontDescriptor = vks : : initializers : : descriptorImageInfo (
sampler ,
fontView ,
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL
) ;
std : : vector < VkWriteDescriptorSet > writeDescriptorSets = {
vks : : initializers : : writeDescriptorSet ( descriptorSet , VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER , 0 , & fontDescriptor )
} ;
vkUpdateDescriptorSets ( device - > logicalDevice , static_cast < uint32_t > ( writeDescriptorSets . size ( ) ) , writeDescriptorSets . data ( ) , 0 , nullptr ) ;
// Pipeline cache
VkPipelineCacheCreateInfo pipelineCacheCreateInfo = { } ;
pipelineCacheCreateInfo . sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO ;
VK_CHECK_RESULT ( vkCreatePipelineCache ( device - > logicalDevice , & pipelineCacheCreateInfo , nullptr , & pipelineCache ) ) ;
// Pipeline layout
// Push constants for UI rendering parameters
VkPushConstantRange pushConstantRange = vks : : initializers : : pushConstantRange ( VK_SHADER_STAGE_VERTEX_BIT , sizeof ( PushConstBlock ) , 0 ) ;
VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = vks : : initializers : : pipelineLayoutCreateInfo ( & descriptorSetLayout , 1 ) ;
pipelineLayoutCreateInfo . pushConstantRangeCount = 1 ;
pipelineLayoutCreateInfo . pPushConstantRanges = & pushConstantRange ;
VK_CHECK_RESULT ( vkCreatePipelineLayout ( device - > logicalDevice , & pipelineLayoutCreateInfo , nullptr , & pipelineLayout ) ) ;
// Setup graphics pipeline for UI rendering
VkPipelineInputAssemblyStateCreateInfo inputAssemblyState =
vks : : initializers : : pipelineInputAssemblyStateCreateInfo ( VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST , 0 , VK_FALSE ) ;
VkPipelineRasterizationStateCreateInfo rasterizationState =
vks : : initializers : : pipelineRasterizationStateCreateInfo ( VK_POLYGON_MODE_FILL , VK_CULL_MODE_NONE , VK_FRONT_FACE_COUNTER_CLOCKWISE ) ;
// Enable blending
VkPipelineColorBlendAttachmentState blendAttachmentState { } ;
blendAttachmentState . blendEnable = VK_TRUE ;
blendAttachmentState . colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT ;
blendAttachmentState . srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA ;
blendAttachmentState . dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA ;
blendAttachmentState . colorBlendOp = VK_BLEND_OP_ADD ;
blendAttachmentState . srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA ;
blendAttachmentState . dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO ;
blendAttachmentState . alphaBlendOp = VK_BLEND_OP_ADD ;
VkPipelineColorBlendStateCreateInfo colorBlendState =
vks : : initializers : : pipelineColorBlendStateCreateInfo ( 1 , & blendAttachmentState ) ;
VkPipelineDepthStencilStateCreateInfo depthStencilState =
vks : : initializers : : pipelineDepthStencilStateCreateInfo ( VK_FALSE , VK_FALSE , VK_COMPARE_OP_LESS_OR_EQUAL ) ;
VkPipelineViewportStateCreateInfo viewportState =
vks : : initializers : : pipelineViewportStateCreateInfo ( 1 , 1 , 0 ) ;
VkPipelineMultisampleStateCreateInfo multisampleState =
vks : : initializers : : pipelineMultisampleStateCreateInfo ( VK_SAMPLE_COUNT_1_BIT ) ;
std : : vector < VkDynamicState > dynamicStateEnables = {
VK_DYNAMIC_STATE_VIEWPORT ,
VK_DYNAMIC_STATE_SCISSOR
} ;
VkPipelineDynamicStateCreateInfo dynamicState =
vks : : initializers : : pipelineDynamicStateCreateInfo ( dynamicStateEnables ) ;
std : : array < VkPipelineShaderStageCreateInfo , 2 > shaderStages { } ;
2025-08-17 18:56:17 +02:00
// Create pipeline - use dynamic rendering if no render pass provided
VkGraphicsPipelineCreateInfo pipelineCreateInfo ;
if ( renderPass = = VK_NULL_HANDLE ) {
// Dynamic rendering
pipelineCreateInfo = vks : : initializers : : pipelineCreateInfo ( ) ;
pipelineCreateInfo . layout = pipelineLayout ;
} else {
// Traditional render pass
pipelineCreateInfo = vks : : initializers : : pipelineCreateInfo ( pipelineLayout , renderPass ) ;
}
2017-03-30 19:27:57 +02:00
pipelineCreateInfo . pInputAssemblyState = & inputAssemblyState ;
pipelineCreateInfo . pRasterizationState = & rasterizationState ;
pipelineCreateInfo . pColorBlendState = & colorBlendState ;
pipelineCreateInfo . pMultisampleState = & multisampleState ;
pipelineCreateInfo . pViewportState = & viewportState ;
pipelineCreateInfo . pDepthStencilState = & depthStencilState ;
pipelineCreateInfo . pDynamicState = & dynamicState ;
pipelineCreateInfo . stageCount = static_cast < uint32_t > ( shaderStages . size ( ) ) ;
pipelineCreateInfo . pStages = shaderStages . data ( ) ;
// Vertex bindings an attributes based on ImGui vertex definition
std : : vector < VkVertexInputBindingDescription > vertexInputBindings = {
vks : : initializers : : vertexInputBindingDescription ( 0 , sizeof ( ImDrawVert ) , VK_VERTEX_INPUT_RATE_VERTEX ) ,
} ;
std : : vector < VkVertexInputAttributeDescription > vertexInputAttributes = {
vks : : initializers : : vertexInputAttributeDescription ( 0 , 0 , VK_FORMAT_R32G32_SFLOAT , offsetof ( ImDrawVert , pos ) ) , // Location 0: Position
vks : : initializers : : vertexInputAttributeDescription ( 0 , 1 , VK_FORMAT_R32G32_SFLOAT , offsetof ( ImDrawVert , uv ) ) , // Location 1: UV
vks : : initializers : : vertexInputAttributeDescription ( 0 , 2 , VK_FORMAT_R8G8B8A8_UNORM , offsetof ( ImDrawVert , col ) ) , // Location 0: Color
} ;
VkPipelineVertexInputStateCreateInfo vertexInputState = vks : : initializers : : pipelineVertexInputStateCreateInfo ( ) ;
vertexInputState . vertexBindingDescriptionCount = static_cast < uint32_t > ( vertexInputBindings . size ( ) ) ;
vertexInputState . pVertexBindingDescriptions = vertexInputBindings . data ( ) ;
vertexInputState . vertexAttributeDescriptionCount = static_cast < uint32_t > ( vertexInputAttributes . size ( ) ) ;
vertexInputState . pVertexAttributeDescriptions = vertexInputAttributes . data ( ) ;
pipelineCreateInfo . pVertexInputState = & vertexInputState ;
2025-08-17 18:56:17 +02:00
// Dynamic rendering create info for ImGui pipeline (only if using dynamic rendering)
VkPipelineRenderingCreateInfoKHR pipelineRenderingCreateInfo { } ;
if ( renderPass = = VK_NULL_HANDLE & & colorFormat ! = VK_FORMAT_UNDEFINED ) {
pipelineRenderingCreateInfo . sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO_KHR ;
pipelineRenderingCreateInfo . colorAttachmentCount = 1 ;
pipelineRenderingCreateInfo . pColorAttachmentFormats = & colorFormat ;
pipelineRenderingCreateInfo . depthAttachmentFormat = depthFormat ;
pipelineRenderingCreateInfo . stencilAttachmentFormat = depthFormat ;
// Chain into the pipeline create info
pipelineCreateInfo . pNext = & pipelineRenderingCreateInfo ;
}
2020-05-29 16:36:27 +01:00
shaderStages [ 0 ] = example - > loadShader ( shadersPath + " imgui/ui.vert.spv " , VK_SHADER_STAGE_VERTEX_BIT ) ;
shaderStages [ 1 ] = example - > loadShader ( shadersPath + " imgui/ui.frag.spv " , VK_SHADER_STAGE_FRAGMENT_BIT ) ;
2017-03-30 19:27:57 +02:00
VK_CHECK_RESULT ( vkCreateGraphicsPipelines ( device - > logicalDevice , pipelineCache , 1 , & pipelineCreateInfo , nullptr , & pipeline ) ) ;
}
2025-08-17 18:56:17 +02:00
2017-03-30 19:27:57 +02:00
// Starts a new imGui frame and sets up windows and ui elements
void newFrame ( VulkanExampleBase * example , bool updateFrameGraph )
{
ImGui : : NewFrame ( ) ;
2025-08-17 18:56:17 +02:00
// Menu Bar
if ( ImGui : : BeginMainMenuBar ( ) )
{
if ( ImGui : : BeginMenu ( " File " ) )
{
if ( ImGui : : MenuItem ( " New Scene " , " Ctrl+N " ) ) {
// Clear scene
sceneManager . clearScene ( ) ;
}
if ( ImGui : : MenuItem ( " Open Scene " , " Ctrl+O " ) ) {
// TODO: Implement scene loading
}
if ( ImGui : : MenuItem ( " Save Scene " , " Ctrl+S " ) ) {
// TODO: Implement scene saving
}
ImGui : : Separator ( ) ;
if ( ImGui : : MenuItem ( " Exit " , " Alt+F4 " ) ) {
example - > prepared = false ;
}
ImGui : : EndMenu ( ) ;
}
if ( ImGui : : BeginMenu ( " Preferences " ) )
{
if ( ImGui : : BeginMenu ( " UI Theme " ) )
{
if ( ImGui : : MenuItem ( " Vulkan Red " , nullptr , selectedStyle = = 0 ) ) { setStyle ( 0 ) ; selectedStyle = 0 ; }
if ( ImGui : : MenuItem ( " Classic " , nullptr , selectedStyle = = 1 ) ) { setStyle ( 1 ) ; selectedStyle = 1 ; }
if ( ImGui : : MenuItem ( " Dark " , nullptr , selectedStyle = = 2 ) ) { setStyle ( 2 ) ; selectedStyle = 2 ; }
if ( ImGui : : MenuItem ( " Light " , nullptr , selectedStyle = = 3 ) ) { setStyle ( 3 ) ; selectedStyle = 3 ; }
if ( ImGui : : MenuItem ( " Blue " , nullptr , selectedStyle = = 4 ) ) { setStyle ( 4 ) ; selectedStyle = 4 ; }
if ( ImGui : : MenuItem ( " Green " , nullptr , selectedStyle = = 5 ) ) { setStyle ( 5 ) ; selectedStyle = 5 ; }
if ( ImGui : : MenuItem ( " Purple " , nullptr , selectedStyle = = 6 ) ) { setStyle ( 6 ) ; selectedStyle = 6 ; }
ImGui : : EndMenu ( ) ;
}
ImGui : : Separator ( ) ;
// Viewport settings moved to dedicated Viewport panel
ImGui : : EndMenu ( ) ;
}
if ( ImGui : : BeginMenu ( " Windows " ) )
{
ImGui : : MenuItem ( " Scene Hierarchy " , nullptr , & uiSettings . showSceneHierarchyPanel ) ;
ImGui : : MenuItem ( " Inspector " , nullptr , & uiSettings . showInspectorPanel ) ;
ImGui : : MenuItem ( " Viewport " , nullptr , & uiSettings . showViewportPanel ) ;
ImGui : : MenuItem ( " Asset Browser " , nullptr , & uiSettings . showAssetBrowserPanel ) ;
ImGui : : MenuItem ( " Console " , nullptr , & uiSettings . showConsolePanel ) ;
ImGui : : Separator ( ) ;
if ( ImGui : : MenuItem ( " Show All Panels " ) ) {
uiSettings . showSceneHierarchyPanel = true ;
uiSettings . showInspectorPanel = true ;
uiSettings . showViewportPanel = true ;
uiSettings . showAssetBrowserPanel = true ;
uiSettings . showConsolePanel = true ;
}
if ( ImGui : : MenuItem ( " Hide All Panels " ) ) {
uiSettings . showSceneHierarchyPanel = false ;
uiSettings . showInspectorPanel = false ;
uiSettings . showViewportPanel = false ;
uiSettings . showAssetBrowserPanel = false ;
uiSettings . showConsolePanel = false ;
}
ImGui : : EndMenu ( ) ;
}
if ( ImGui : : BeginMenu ( " Help " ) )
{
if ( ImGui : : MenuItem ( " About " ) ) {
// TODO: Show about dialog
}
ImGui : : EndMenu ( ) ;
}
ImGui : : EndMainMenuBar ( ) ;
}
2017-03-30 19:27:57 +02:00
// Init imGui windows and elements
2023-02-25 10:27:47 +01:00
// Debug window
2024-05-23 21:56:42 +02:00
ImGui : : SetWindowPos ( ImVec2 ( 20 * example - > ui . scale , 20 * example - > ui . scale ) , ImGuiSetCond_FirstUseEver ) ;
ImGui : : SetWindowSize ( ImVec2 ( 300 * example - > ui . scale , 300 * example - > ui . scale ) , ImGuiSetCond_Always ) ;
2018-05-10 17:22:42 +02:00
ImGui : : TextUnformatted ( example - > title . c_str ( ) ) ;
ImGui : : TextUnformatted ( device - > properties . deviceName ) ;
2022-07-12 18:46:39 -04:00
//SRS - Display Vulkan API version and device driver information if available (otherwise blank)
ImGui : : Text ( " Vulkan API %i.%i.%i " , VK_API_VERSION_MAJOR ( device - > properties . apiVersion ) , VK_API_VERSION_MINOR ( device - > properties . apiVersion ) , VK_API_VERSION_PATCH ( device - > properties . apiVersion ) ) ;
ImGui : : Text ( " %s %s " , driverProperties . driverName , driverProperties . driverInfo ) ;
2017-03-30 19:27:57 +02:00
// Update frame time display
if ( updateFrameGraph ) {
std : : rotate ( uiSettings . frameTimes . begin ( ) , uiSettings . frameTimes . begin ( ) + 1 , uiSettings . frameTimes . end ( ) ) ;
float frameTime = 1000.0f / ( example - > frameTimer * 1000.0f ) ;
uiSettings . frameTimes . back ( ) = frameTime ;
if ( frameTime < uiSettings . frameTimeMin ) {
uiSettings . frameTimeMin = frameTime ;
}
if ( frameTime > uiSettings . frameTimeMax ) {
uiSettings . frameTimeMax = frameTime ;
}
}
ImGui : : PlotLines ( " Frame Times " , & uiSettings . frameTimes [ 0 ] , 50 , 0 , " " , uiSettings . frameTimeMin , uiSettings . frameTimeMax , ImVec2 ( 0 , 80 ) ) ;
2025-08-17 18:56:17 +02:00
ImGui : : Text ( " Orbit Camera " ) ;
ImGui : : Text ( " Enhanced camera system active " ) ;
ImGui : : Separator ( ) ;
ImGui : : Text ( " Controls: " ) ;
ImGui : : BulletText ( " F - Focus on selection " ) ;
ImGui : : BulletText ( " Alt+LMB - Orbit " ) ;
ImGui : : BulletText ( " Alt+MMB - Pan " ) ;
ImGui : : BulletText ( " Alt+RMB - Zoom " ) ;
ImGui : : BulletText ( " Mouse Wheel - Zoom " ) ;
// Simple Scene Hierarchy Panel
if ( uiSettings . showSceneHierarchyPanel ) {
ImGui : : SetNextWindowPos ( ImVec2 ( 20 * example - > ui . scale , 360 * example - > ui . scale ) , ImGuiSetCond_FirstUseEver ) ;
ImGui : : SetNextWindowSize ( ImVec2 ( 300 * example - > ui . scale , 400 * example - > ui . scale ) , ImGuiSetCond_FirstUseEver ) ;
ImGui : : Begin ( " Scene Hierarchy " , & uiSettings . showSceneHierarchyPanel ) ;
// Header with object count
ImGui : : Text ( " Scene Objects: %d " , sceneManager . getObjectCount ( ) ) ;
ImGui : : Separator ( ) ;
// Render simple object list
for ( int i = 0 ; i < sceneManager . objects . size ( ) ; i + + ) {
const auto & obj = sceneManager . objects [ i ] ;
bool isSelected = ( sceneManager . selectedIndex = = i ) ;
// Object icon based on type
const char * icon = " [P] " ; // Default procedural icon
if ( obj . type = = " Model " ) icon = " [M] " ;
// Visibility toggle (use object name as unique ImGui ID)
ImGui : : PushStyleColor ( ImGuiCol_Button , ImVec4 ( 0 , 0 , 0 , 0 ) ) ;
ImGui : : PushID ( obj . name . c_str ( ) ) ; // Use object name for unique ImGui ID
if ( ImGui : : SmallButton ( obj . visible ? " V " : " H " ) ) {
// Toggle visibility (note: const_cast needed for modification)
const_cast < SceneObject & > ( obj ) . visible = ! obj . visible ;
}
ImGui : : PopID ( ) ;
ImGui : : PopStyleColor ( ) ;
ImGui : : SameLine ( ) ;
// Selectable object name with highlighting for selected objects
if ( isSelected ) {
ImGui : : PushStyleColor ( ImGuiCol_Text , ImVec4 ( 1.0f , 0.8f , 0.2f , 1.0f ) ) ; // Gold color for selected
}
if ( ImGui : : Selectable ( ( std : : string ( icon ) + " " + obj . name ) . c_str ( ) , isSelected ) ) {
sceneManager . setSelectedIndex ( i ) ;
std : : cout < < " Selected object: " < < obj . name < < std : : endl ;
}
if ( isSelected ) {
ImGui : : PopStyleColor ( ) ;
}
// Right-click context menu
if ( ImGui : : BeginPopupContextItem ( ) ) {
if ( ImGui : : MenuItem ( " Delete " , " Del " ) ) {
sceneManager . removeObject ( i ) ;
ImGui : : EndPopup ( ) ;
break ; // Exit loop since we modified the vector
}
ImGui : : EndPopup ( ) ;
}
}
// Show message if scene is empty
if ( sceneManager . getObjectCount ( ) = = 0 ) {
ImGui : : Spacing ( ) ;
ImGui : : TextColored ( ImVec4 ( 0.7f , 0.7f , 0.7f , 1.0f ) , " Scene is empty " ) ;
ImGui : : TextColored ( ImVec4 ( 0.7f , 0.7f , 0.7f , 1.0f ) , " Add objects from Asset Browser " ) ;
}
ImGui : : End ( ) ;
} // End Scene Hierarchy Panel visibility check
// Inspector Panel
if ( uiSettings . showInspectorPanel ) {
ImGui : : SetNextWindowPos ( ImVec2 ( 1180 * example - > ui . scale , 20 * example - > ui . scale ) , ImGuiSetCond_FirstUseEver ) ;
ImGui : : SetNextWindowSize ( ImVec2 ( 300 * example - > ui . scale , 700 * example - > ui . scale ) , ImGuiSetCond_FirstUseEver ) ;
ImGui : : Begin ( " Inspector " , & uiSettings . showInspectorPanel ) ;
ImGui : : Text ( " Object Properties: " ) ;
ImGui : : Separator ( ) ;
if ( sceneManager . selectedIndex > = 0 & & sceneManager . selectedIndex < sceneManager . objects . size ( ) ) {
auto & selectedObj = sceneManager . objects [ sceneManager . selectedIndex ] ; // Non-const for editing
// Object Header with Name and Type
ImGui : : Text ( " Selected: %s " , selectedObj . name . c_str ( ) ) ;
ImGui : : SameLine ( ) ;
ImGui : : TextColored ( ImVec4 ( 0.6f , 0.6f , 0.6f , 1.0f ) , " (%s) " , selectedObj . type . c_str ( ) ) ;
ImGui : : Separator ( ) ;
// Transform Section (collapsible like legacy 3D engine)
if ( ImGui : : CollapsingHeader ( " Transform " , ImGuiTreeNodeFlags_DefaultOpen ) ) {
// Position Vector3 control
float pos [ 3 ] = { selectedObj . position . x , selectedObj . position . y , selectedObj . position . z } ;
if ( ImGui : : DragFloat3 ( " Position " , pos , 0.1f ) ) {
selectedObj . position = glm : : vec3 ( pos [ 0 ] , pos [ 1 ] , pos [ 2 ] ) ;
}
ImGui : : SameLine ( ) ;
if ( ImGui : : SmallButton ( " Reset##pos " ) ) {
selectedObj . position = glm : : vec3 ( 0.0f , 0.0f , 0.0f ) ;
}
// Rotation Vector3 control (degrees)
float rot [ 3 ] = { selectedObj . rotation . x , selectedObj . rotation . y , selectedObj . rotation . z } ;
if ( ImGui : : DragFloat3 ( " Rotation " , rot , 1.0f , - 360.0f , 360.0f ) ) {
selectedObj . rotation = glm : : vec3 ( rot [ 0 ] , rot [ 1 ] , rot [ 2 ] ) ;
}
ImGui : : SameLine ( ) ;
if ( ImGui : : SmallButton ( " Reset##rot " ) ) {
selectedObj . rotation = glm : : vec3 ( 0.0f , 0.0f , 0.0f ) ;
}
// Scale Vector3 control
float scale [ 3 ] = { selectedObj . scale . x , selectedObj . scale . y , selectedObj . scale . z } ;
if ( ImGui : : DragFloat3 ( " Scale " , scale , 0.01f , 0.01f , 10.0f ) ) {
selectedObj . scale = glm : : vec3 ( scale [ 0 ] , scale [ 1 ] , scale [ 2 ] ) ;
}
ImGui : : SameLine ( ) ;
if ( ImGui : : SmallButton ( " Reset##scale " ) ) {
selectedObj . scale = glm : : vec3 ( 1.0f , 1.0f , 1.0f ) ; // Default scale is 1.0
}
// Transform utilities
ImGui : : Spacing ( ) ;
if ( ImGui : : Button ( " Reset All Transform " ) ) {
selectedObj . position = glm : : vec3 ( 0.0f , 0.0f , 0.0f ) ;
selectedObj . rotation = glm : : vec3 ( 0.0f , 0.0f , 0.0f ) ;
selectedObj . scale = glm : : vec3 ( 1.0f , 1.0f , 1.0f ) ;
}
}
// Object Properties Section
if ( ImGui : : CollapsingHeader ( " Object Properties " , ImGuiTreeNodeFlags_DefaultOpen ) ) {
// Visibility toggle
bool visible = selectedObj . visible ;
if ( ImGui : : Checkbox ( " Visible " , & visible ) ) {
selectedObj . visible = visible ;
}
// Object name editing
static char nameBuffer [ 256 ] ;
strncpy_s ( nameBuffer , selectedObj . name . c_str ( ) , sizeof ( nameBuffer ) - 1 ) ;
if ( ImGui : : InputText ( " Name " , nameBuffer , sizeof ( nameBuffer ) ) ) {
selectedObj . name = std : : string ( nameBuffer ) ;
}
}
// Procedural Parameters Section (for procedural shapes)
if ( selectedObj . type = = " Procedural " ) {
if ( ImGui : : CollapsingHeader ( " Procedural Parameters " , ImGuiTreeNodeFlags_DefaultOpen ) ) {
ImGui : : Text ( " Shape Type: %s " , selectedObj . subtype . c_str ( ) ) ;
// Dynamic parameter controls based on shape type
bool parametersChanged = false ;
if ( selectedObj . subtype = = " Cube " ) {
ImGui : : Text ( " Cube Parameters: " ) ;
ImGui : : Separator ( ) ;
if ( ImGui : : DragFloat ( " Width " , & selectedObj . proceduralParams . cubeWidth , 0.1f , 0.1f , 10.0f , " %.2f " ) ) {
parametersChanged = true ;
}
if ( ImGui : : DragFloat ( " Height " , & selectedObj . proceduralParams . cubeHeight , 0.1f , 0.1f , 10.0f , " %.2f " ) ) {
parametersChanged = true ;
}
if ( ImGui : : DragFloat ( " Depth " , & selectedObj . proceduralParams . cubeDepth , 0.1f , 0.1f , 10.0f , " %.2f " ) ) {
parametersChanged = true ;
}
if ( ImGui : : SliderInt ( " Subdivisions " , & selectedObj . proceduralParams . cubeSubdivisions , 1 , 10 ) ) {
parametersChanged = true ;
}
if ( ImGui : : Button ( " Reset Cube Parameters " ) ) {
selectedObj . proceduralParams . cubeWidth = 2.0f ;
selectedObj . proceduralParams . cubeHeight = 2.0f ;
selectedObj . proceduralParams . cubeDepth = 2.0f ;
selectedObj . proceduralParams . cubeSubdivisions = 1 ;
parametersChanged = true ;
}
} else if ( selectedObj . subtype = = " Sphere " ) {
ImGui : : Text ( " Sphere Parameters: " ) ;
ImGui : : Separator ( ) ;
if ( ImGui : : DragFloat ( " Radius " , & selectedObj . proceduralParams . sphereRadius , 0.05f , 0.1f , 5.0f , " %.2f " ) ) {
parametersChanged = true ;
}
if ( ImGui : : SliderInt ( " Segments " , & selectedObj . proceduralParams . sphereSegments , 4 , 64 ) ) {
parametersChanged = true ;
}
if ( ImGui : : Button ( " Reset Sphere Parameters " ) ) {
selectedObj . proceduralParams . sphereRadius = 1.0f ;
selectedObj . proceduralParams . sphereSegments = 16 ;
parametersChanged = true ;
}
} else if ( selectedObj . subtype = = " Cylinder " ) {
ImGui : : Text ( " Cylinder Parameters: " ) ;
ImGui : : Separator ( ) ;
if ( ImGui : : DragFloat ( " Radius " , & selectedObj . proceduralParams . cylinderRadius , 0.05f , 0.1f , 5.0f , " %.2f " ) ) {
parametersChanged = true ;
}
if ( ImGui : : DragFloat ( " Height " , & selectedObj . proceduralParams . cylinderHeight , 0.1f , 0.1f , 10.0f , " %.2f " ) ) {
parametersChanged = true ;
}
if ( ImGui : : SliderInt ( " Segments " , & selectedObj . proceduralParams . cylinderSegments , 3 , 64 ) ) {
parametersChanged = true ;
}
if ( ImGui : : Button ( " Reset Cylinder Parameters " ) ) {
selectedObj . proceduralParams . cylinderRadius = 1.0f ;
selectedObj . proceduralParams . cylinderHeight = 2.0f ;
selectedObj . proceduralParams . cylinderSegments = 16 ;
parametersChanged = true ;
}
} else if ( selectedObj . subtype = = " Cone " ) {
ImGui : : Text ( " Cone Parameters: " ) ;
ImGui : : Separator ( ) ;
if ( ImGui : : DragFloat ( " Radius " , & selectedObj . proceduralParams . coneRadius , 0.05f , 0.1f , 5.0f , " %.2f " ) ) {
parametersChanged = true ;
}
if ( ImGui : : DragFloat ( " Height " , & selectedObj . proceduralParams . coneHeight , 0.1f , 0.1f , 10.0f , " %.2f " ) ) {
parametersChanged = true ;
}
if ( ImGui : : SliderInt ( " Segments " , & selectedObj . proceduralParams . coneSegments , 3 , 64 ) ) {
parametersChanged = true ;
}
if ( ImGui : : Button ( " Reset Cone Parameters " ) ) {
selectedObj . proceduralParams . coneRadius = 1.0f ;
selectedObj . proceduralParams . coneHeight = 2.0f ;
selectedObj . proceduralParams . coneSegments = 16 ;
parametersChanged = true ;
}
} else if ( selectedObj . subtype = = " Plane " ) {
ImGui : : Text ( " Plane Parameters: " ) ;
ImGui : : Separator ( ) ;
if ( ImGui : : DragFloat ( " Width " , & selectedObj . proceduralParams . planeWidth , 0.1f , 0.1f , 10.0f , " %.2f " ) ) {
parametersChanged = true ;
}
if ( ImGui : : DragFloat ( " Height " , & selectedObj . proceduralParams . planeHeight , 0.1f , 0.1f , 10.0f , " %.2f " ) ) {
parametersChanged = true ;
}
if ( ImGui : : SliderInt ( " Subdivisions " , & selectedObj . proceduralParams . planeSubdivisions , 1 , 20 ) ) {
parametersChanged = true ;
}
if ( ImGui : : Button ( " Reset Plane Parameters " ) ) {
selectedObj . proceduralParams . planeWidth = 2.0f ;
selectedObj . proceduralParams . planeHeight = 2.0f ;
selectedObj . proceduralParams . planeSubdivisions = 1 ;
parametersChanged = true ;
}
} else if ( selectedObj . subtype = = " Torus " ) {
ImGui : : Text ( " Torus Parameters: " ) ;
ImGui : : Separator ( ) ;
if ( ImGui : : DragFloat ( " Major Radius " , & selectedObj . proceduralParams . torusMajorRadius , 0.05f , 0.2f , 5.0f , " %.2f " ) ) {
parametersChanged = true ;
}
if ( ImGui : : DragFloat ( " Minor Radius " , & selectedObj . proceduralParams . torusMinorRadius , 0.02f , 0.05f , 2.0f , " %.2f " ) ) {
parametersChanged = true ;
}
if ( ImGui : : SliderInt ( " Major Segments " , & selectedObj . proceduralParams . torusMajorSegments , 3 , 64 ) ) {
parametersChanged = true ;
}
if ( ImGui : : SliderInt ( " Minor Segments " , & selectedObj . proceduralParams . torusMinorSegments , 3 , 32 ) ) {
parametersChanged = true ;
}
if ( ImGui : : Button ( " Reset Torus Parameters " ) ) {
selectedObj . proceduralParams . torusMajorRadius = 1.0f ;
selectedObj . proceduralParams . torusMinorRadius = 0.3f ;
selectedObj . proceduralParams . torusMajorSegments = 16 ;
selectedObj . proceduralParams . torusMinorSegments = 8 ;
parametersChanged = true ;
}
}
// Regenerate geometry if parameters changed
if ( parametersChanged ) {
selectedObj . regenerateGeometry ( ) ;
std : : cout < < " Regenerated geometry for " < < selectedObj . name < < " ( " < < selectedObj . subtype < < " ) " < < std : : endl ;
}
ImGui : : Spacing ( ) ;
ImGui : : TextColored ( ImVec4 ( 0.4f , 1.0f , 0.4f , 1.0f ) , " ✓ Real-time parameter editing enabled " ) ;
}
}
} else {
ImGui : : Text ( " Selected: None " ) ;
ImGui : : Text ( " Select an object in the Scene Hierarchy " ) ;
ImGui : : Separator ( ) ;
}
// UI Style selection is available in Preferences > UI Theme menu
2017-03-30 19:27:57 +02:00
2025-08-17 18:56:17 +02:00
ImGui : : End ( ) ;
} // End Inspector Panel visibility check
2023-02-25 10:27:47 +01:00
2025-08-17 18:56:17 +02:00
// Viewport Panel - Settings for 3D viewport rendering
if ( uiSettings . showViewportPanel ) {
ImGui : : SetNextWindowPos ( ImVec2 ( 660 * example - > ui . scale , 350 * example - > ui . scale ) , ImGuiSetCond_FirstUseEver ) ;
ImGui : : SetNextWindowSize ( ImVec2 ( 320 * example - > ui . scale , 280 * example - > ui . scale ) , ImGuiSetCond_FirstUseEver ) ;
ImGui : : Begin ( " Viewport " , & uiSettings . showViewportPanel ) ;
ImGui : : Text ( " 3D Viewport Settings: " ) ;
ImGui : : Separator ( ) ;
// Render Mode Settings
if ( ImGui : : CollapsingHeader ( " Render Mode " , ImGuiTreeNodeFlags_DefaultOpen ) )
{
// TODO: Add render mode dropdown (Solid, X-Ray, etc.) when renderer supports it
ImGui : : Text ( " Mode: Solid " ) ; // Placeholder until render modes implemented
}
// Grid Settings (moved from Inspector)
if ( ImGui : : CollapsingHeader ( " Grid Settings " , ImGuiTreeNodeFlags_DefaultOpen ) )
{
ImGui : : Checkbox ( " Show Grid " , & uiSettings . showGrid ) ;
if ( uiSettings . showGrid ) {
ImGui : : DragFloat ( " Grid Size " , & uiSettings . gridSize , 0.5f , 1.0f , 50.0f ) ;
ImGui : : DragInt ( " Grid Divisions " , & uiSettings . gridDivisions , 1 , 2 , 50 ) ;
// Grid color picker (placeholder - needs renderer integration)
static float gridColor [ 3 ] = { 0.5f , 0.5f , 0.5f } ;
ImGui : : ColorEdit3 ( " Grid Color " , gridColor ) ;
}
// Note about grid rendering implementation
ImGui : : TextColored ( ImVec4 ( 0.7f , 0.7f , 0.3f , 1.0f ) , " Note: Grid rendering requires Vulkan renderer integration. " ) ;
ImGui : : TextColored ( ImVec4 ( 0.6f , 0.6f , 0.6f , 1.0f ) , " Settings are saved but grid is not yet rendered in viewport. " ) ;
}
// Camera Settings
if ( ImGui : : CollapsingHeader ( " Camera " , ImGuiTreeNodeFlags_DefaultOpen ) )
{
ImGui : : Text ( " Current Camera: Orbit Camera " ) ;
ImGui : : Text ( " Controls: " ) ;
ImGui : : BulletText ( " Alt + LMB: Orbit " ) ;
ImGui : : BulletText ( " Alt + MMB: Pan " ) ;
ImGui : : BulletText ( " Alt + RMB: Zoom " ) ;
ImGui : : BulletText ( " Mouse Wheel: Zoom " ) ;
ImGui : : BulletText ( " F Key: Focus Selection " ) ;
}
// Selection Tools
if ( ImGui : : CollapsingHeader ( " Selection Tools " ) )
{
ImGui : : Text ( " Active Tool: Rectangle Select " ) ;
// TODO: Add gizmo tool selection when gizmos implemented
}
// Lighting Settings (moved from Inspector panel)
if ( ImGui : : CollapsingHeader ( " Lighting " , ImGuiTreeNodeFlags_DefaultOpen ) )
{
ImGui : : Checkbox ( " Animate light " , & uiSettings . animateLight ) ;
ImGui : : SliderFloat ( " Light speed " , & uiSettings . lightSpeed , 0.1f , 1.0f ) ;
}
ImGui : : End ( ) ;
} // End Viewport Panel visibility check
// Asset Browser Panel
if ( uiSettings . showAssetBrowserPanel ) {
ImGui : : SetNextWindowPos ( ImVec2 ( 20 * example - > ui . scale , 780 * example - > ui . scale ) , ImGuiSetCond_FirstUseEver ) ;
ImGui : : SetNextWindowSize ( ImVec2 ( 600 * example - > ui . scale , 220 * example - > ui . scale ) , ImGuiSetCond_FirstUseEver ) ;
ImGui : : Begin ( " Asset Browser " , & uiSettings . showAssetBrowserPanel ) ;
ImGui : : Text ( " Project Assets: " ) ;
ImGui : : Separator ( ) ;
if ( ImGui : : CollapsingHeader ( " Procedural Shapes " , ImGuiTreeNodeFlags_DefaultOpen ) )
{
ImGui : : Text ( " Basic Geometric Shapes: " ) ;
ImGui : : Separator ( ) ;
// Create buttons for each shape type - First row
if ( ImGui : : Button ( " Cube " ) ) {
sceneManager . addProceduralShape ( " Cube " , " Cube " ) ;
}
if ( ImGui : : IsItemHovered ( ) ) ImGui : : SetTooltip ( " Add a procedural cube to the scene " ) ;
ImGui : : SameLine ( ) ;
if ( ImGui : : Button ( " Sphere " ) ) {
sceneManager . addProceduralShape ( " Sphere " , " Sphere " ) ;
}
if ( ImGui : : IsItemHovered ( ) ) ImGui : : SetTooltip ( " Add a procedural sphere to the scene " ) ;
ImGui : : SameLine ( ) ;
if ( ImGui : : Button ( " Plane " ) ) {
sceneManager . addProceduralShape ( " Plane " , " Plane " ) ;
}
if ( ImGui : : IsItemHovered ( ) ) ImGui : : SetTooltip ( " Add a procedural plane to the scene " ) ;
// Second row
if ( ImGui : : Button ( " Cone " ) ) {
sceneManager . addProceduralShape ( " Cone " , " Cone " ) ;
}
if ( ImGui : : IsItemHovered ( ) ) ImGui : : SetTooltip ( " Add a procedural cone to the scene " ) ;
ImGui : : SameLine ( ) ;
if ( ImGui : : Button ( " Cylinder " ) ) {
sceneManager . addProceduralShape ( " Cylinder " , " Cylinder " ) ;
}
if ( ImGui : : IsItemHovered ( ) ) ImGui : : SetTooltip ( " Add a procedural cylinder to the scene " ) ;
ImGui : : SameLine ( ) ;
if ( ImGui : : Button ( " Torus " ) ) {
sceneManager . addProceduralShape ( " Torus " , " Torus " ) ;
}
if ( ImGui : : IsItemHovered ( ) ) ImGui : : SetTooltip ( " Add a procedural torus to the scene " ) ;
}
if ( ImGui : : CollapsingHeader ( " Models " ) )
{
ImGui : : Text ( " • MobulaBirostris.gltf " ) ;
ImGui : : Text ( " • PolarBear.gltf " ) ;
}
if ( ImGui : : CollapsingHeader ( " Textures " ) )
{
ImGui : : Text ( " • Loading textures from glTF files... " ) ;
}
if ( ImGui : : CollapsingHeader ( " Materials " ) )
{
ImGui : : Text ( " • Default Vulkan Materials " ) ;
}
ImGui : : End ( ) ;
} // End Asset Browser Panel visibility check
// Console Panel
if ( uiSettings . showConsolePanel ) {
ImGui : : SetNextWindowPos ( ImVec2 ( 640 * example - > ui . scale , 780 * example - > ui . scale ) , ImGuiSetCond_FirstUseEver ) ;
ImGui : : SetNextWindowSize ( ImVec2 ( 840 * example - > ui . scale , 200 * example - > ui . scale ) , ImGuiSetCond_FirstUseEver ) ;
ImGui : : Begin ( " Console " , & uiSettings . showConsolePanel ) ;
ImGui : : Text ( " System Console: " ) ;
ImGui : : Separator ( ) ;
ImGui : : Text ( " [INFO] ProceduralEngine - Vulkan Renderer initialized " ) ;
ImGui : : Text ( " [INFO] Scene loaded successfully - Ready for procedural generation " ) ;
ImGui : : Separator ( ) ;
static char inputBuf [ 256 ] = " " ;
if ( ImGui : : InputText ( " Command " , inputBuf , sizeof ( inputBuf ) , ImGuiInputTextFlags_EnterReturnsTrue ) )
{
// Process command
inputBuf [ 0 ] = ' \0 ' ;
2023-02-25 10:27:47 +01:00
}
2017-03-30 19:27:57 +02:00
ImGui : : End ( ) ;
2025-08-17 18:56:17 +02:00
} // End Console Panel visibility check
2017-03-30 19:27:57 +02:00
2022-07-12 18:46:39 -04:00
//SRS - ShowDemoWindow() sets its own initial position and size, cannot override here
2025-08-17 18:56:17 +02:00
// ImGui::ShowDemoWindow();
2017-03-30 19:27:57 +02:00
// Render to generate draw buffers
ImGui : : Render ( ) ;
}
// Update vertex and index buffer containing the imGui elements when required
void updateBuffers ( )
{
ImDrawData * imDrawData = ImGui : : GetDrawData ( ) ;
// Note: Alignment is done inside buffer creation
VkDeviceSize vertexBufferSize = imDrawData - > TotalVtxCount * sizeof ( ImDrawVert ) ;
VkDeviceSize indexBufferSize = imDrawData - > TotalIdxCount * sizeof ( ImDrawIdx ) ;
2018-08-29 18:24:39 +02:00
if ( ( vertexBufferSize = = 0 ) | | ( indexBufferSize = = 0 ) ) {
return ;
}
2017-03-30 19:27:57 +02:00
// Update buffers only if vertex or index count has been changed compared to current buffer size
// Vertex buffer
if ( ( vertexBuffer . buffer = = VK_NULL_HANDLE ) | | ( vertexCount ! = imDrawData - > TotalVtxCount ) ) {
vertexBuffer . unmap ( ) ;
vertexBuffer . destroy ( ) ;
VK_CHECK_RESULT ( device - > createBuffer ( VK_BUFFER_USAGE_VERTEX_BUFFER_BIT , VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT , & vertexBuffer , vertexBufferSize ) ) ;
vertexCount = imDrawData - > TotalVtxCount ;
vertexBuffer . map ( ) ;
}
// Index buffer
if ( ( indexBuffer . buffer = = VK_NULL_HANDLE ) | | ( indexCount < imDrawData - > TotalIdxCount ) ) {
indexBuffer . unmap ( ) ;
indexBuffer . destroy ( ) ;
VK_CHECK_RESULT ( device - > createBuffer ( VK_BUFFER_USAGE_INDEX_BUFFER_BIT , VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT , & indexBuffer , indexBufferSize ) ) ;
indexCount = imDrawData - > TotalIdxCount ;
indexBuffer . map ( ) ;
}
// Upload data
ImDrawVert * vtxDst = ( ImDrawVert * ) vertexBuffer . mapped ;
ImDrawIdx * idxDst = ( ImDrawIdx * ) indexBuffer . mapped ;
for ( int n = 0 ; n < imDrawData - > CmdListsCount ; n + + ) {
const ImDrawList * cmd_list = imDrawData - > CmdLists [ n ] ;
memcpy ( vtxDst , cmd_list - > VtxBuffer . Data , cmd_list - > VtxBuffer . Size * sizeof ( ImDrawVert ) ) ;
memcpy ( idxDst , cmd_list - > IdxBuffer . Data , cmd_list - > IdxBuffer . Size * sizeof ( ImDrawIdx ) ) ;
vtxDst + = cmd_list - > VtxBuffer . Size ;
idxDst + = cmd_list - > IdxBuffer . Size ;
}
// Flush to make writes visible to GPU
vertexBuffer . flush ( ) ;
indexBuffer . flush ( ) ;
}
// Draw current imGui frame into a command buffer
void drawFrame ( VkCommandBuffer commandBuffer )
{
ImGuiIO & io = ImGui : : GetIO ( ) ;
vkCmdBindDescriptorSets ( commandBuffer , VK_PIPELINE_BIND_POINT_GRAPHICS , pipelineLayout , 0 , 1 , & descriptorSet , 0 , nullptr ) ;
vkCmdBindPipeline ( commandBuffer , VK_PIPELINE_BIND_POINT_GRAPHICS , pipeline ) ;
VkViewport viewport = vks : : initializers : : viewport ( ImGui : : GetIO ( ) . DisplaySize . x , ImGui : : GetIO ( ) . DisplaySize . y , 0.0f , 1.0f ) ;
vkCmdSetViewport ( commandBuffer , 0 , 1 , & viewport ) ;
// UI scale and translate via push constants
pushConstBlock . scale = glm : : vec2 ( 2.0f / io . DisplaySize . x , 2.0f / io . DisplaySize . y ) ;
pushConstBlock . translate = glm : : vec2 ( - 1.0f ) ;
vkCmdPushConstants ( commandBuffer , pipelineLayout , VK_SHADER_STAGE_VERTEX_BIT , 0 , sizeof ( PushConstBlock ) , & pushConstBlock ) ;
// Render commands
ImDrawData * imDrawData = ImGui : : GetDrawData ( ) ;
int32_t vertexOffset = 0 ;
int32_t indexOffset = 0 ;
2018-08-29 18:24:39 +02:00
if ( imDrawData - > CmdListsCount > 0 ) {
VkDeviceSize offsets [ 1 ] = { 0 } ;
vkCmdBindVertexBuffers ( commandBuffer , 0 , 1 , & vertexBuffer . buffer , offsets ) ;
vkCmdBindIndexBuffer ( commandBuffer , indexBuffer . buffer , 0 , VK_INDEX_TYPE_UINT16 ) ;
for ( int32_t i = 0 ; i < imDrawData - > CmdListsCount ; i + + )
2017-03-30 19:27:57 +02:00
{
2018-08-29 18:24:39 +02:00
const ImDrawList * cmd_list = imDrawData - > CmdLists [ i ] ;
for ( int32_t j = 0 ; j < cmd_list - > CmdBuffer . Size ; j + + )
{
const ImDrawCmd * pcmd = & cmd_list - > CmdBuffer [ j ] ;
VkRect2D scissorRect ;
scissorRect . offset . x = std : : max ( ( int32_t ) ( pcmd - > ClipRect . x ) , 0 ) ;
scissorRect . offset . y = std : : max ( ( int32_t ) ( pcmd - > ClipRect . y ) , 0 ) ;
scissorRect . extent . width = ( uint32_t ) ( pcmd - > ClipRect . z - pcmd - > ClipRect . x ) ;
scissorRect . extent . height = ( uint32_t ) ( pcmd - > ClipRect . w - pcmd - > ClipRect . y ) ;
vkCmdSetScissor ( commandBuffer , 0 , 1 , & scissorRect ) ;
vkCmdDrawIndexed ( commandBuffer , pcmd - > ElemCount , 1 , indexOffset , vertexOffset , 0 ) ;
indexOffset + = pcmd - > ElemCount ;
}
2025-03-29 11:21:37 -04:00
# if (defined(VK_USE_PLATFORM_IOS_MVK) || defined(VK_USE_PLATFORM_METAL_EXT)) && TARGET_OS_SIMULATOR
// Apple Device Simulator does not support vkCmdDrawIndexed() with vertexOffset > 0, so rebind vertex buffer instead
offsets [ 0 ] + = cmd_list - > VtxBuffer . Size * sizeof ( ImDrawVert ) ;
vkCmdBindVertexBuffers ( commandBuffer , 0 , 1 , & vertexBuffer . buffer , offsets ) ;
# else
2018-08-29 18:24:39 +02:00
vertexOffset + = cmd_list - > VtxBuffer . Size ;
2025-03-29 11:21:37 -04:00
# endif
2017-03-30 19:27:57 +02:00
}
}
}
} ;
// ----------------------------------------------------------------------------
// VulkanExample
// ----------------------------------------------------------------------------
class VulkanExample : public VulkanExampleBase
{
public :
2025-08-17 18:56:17 +02:00
// Dynamic rendering function pointers
PFN_vkCmdBeginRenderingKHR vkCmdBeginRenderingKHR { VK_NULL_HANDLE } ;
PFN_vkCmdEndRenderingKHR vkCmdEndRenderingKHR { VK_NULL_HANDLE } ;
VkPhysicalDeviceDynamicRenderingFeaturesKHR enabledDynamicRenderingFeaturesKHR { } ;
2017-03-30 19:27:57 +02:00
ImGUI * imGui = nullptr ;
struct Models {
2020-07-28 20:20:38 +02:00
vkglTF : : Model models ;
vkglTF : : Model logos ;
vkglTF : : Model background ;
2017-03-30 19:27:57 +02:00
} models ;
2025-08-17 18:56:17 +02:00
2017-03-30 19:27:57 +02:00
vks : : Buffer uniformBufferVS ;
struct UBOVS {
glm : : mat4 projection ;
2025-08-17 18:56:17 +02:00
glm : : mat4 model ;
2017-03-30 19:27:57 +02:00
glm : : vec4 lightPos ;
} uboVS ;
VkPipelineLayout pipelineLayout ;
VkPipeline pipeline ;
VkDescriptorSetLayout descriptorSetLayout ;
VkDescriptorSet descriptorSet ;
2025-08-17 18:56:17 +02:00
// Procedural shapes pipeline
VkPipelineLayout proceduralPipelineLayout ;
VkPipeline proceduralPipeline ;
VkDescriptorSetLayout proceduralDescriptorSetLayout ;
VkDescriptorSet proceduralDescriptorSet ;
// Enhanced orbit camera
OrbitCamera orbitCamera ;
// Mouse interaction state
bool mouseInteracting = false ;
double lastMouseX = 0.0 ;
double lastMouseY = 0.0 ;
VulkanExample ( ) : VulkanExampleBase ( ) , orbitCamera ( glm : : vec3 ( 5.0f , 3.0f , 5.0f ) , glm : : vec3 ( 0.0f , 0.0f , - 5.0f ) )
2017-03-30 19:27:57 +02:00
{
2025-08-17 18:56:17 +02:00
title = " ProceduralEngine - Vulkan 3D Viewport " ;
2017-03-30 19:27:57 +02:00
camera . type = Camera : : CameraType : : lookat ;
2025-08-17 18:56:17 +02:00
camera . setPosition ( glm : : vec3 ( 0.0f , 0.0f , - 8.0f ) ) ;
2017-03-30 19:27:57 +02:00
camera . setRotation ( glm : : vec3 ( 4.5f , - 380.0f , 0.0f ) ) ;
camera . setPerspective ( 45.0f , ( float ) width / ( float ) height , 0.1f , 256.0f ) ;
2022-07-12 18:46:39 -04:00
2025-08-17 18:56:17 +02:00
// Configure orbit camera with legacy-compatible settings
orbitCamera . SetSensitivity ( 0.5f , 0.003f , 0.1f ) ; // Match legacy sensitivity values
orbitCamera . SetSmoothingFactor ( 0.15f ) ; // Match legacy smoothing
2022-07-12 18:46:39 -04:00
//SRS - Enable VK_KHR_get_physical_device_properties2 to retrieve device driver information for display
enabledInstanceExtensions . push_back ( VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME ) ;
2025-08-17 18:56:17 +02:00
// Enable dynamic rendering extensions
enabledDeviceExtensions . push_back ( VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME ) ;
enabledDeviceExtensions . push_back ( VK_KHR_MAINTENANCE2_EXTENSION_NAME ) ;
enabledDeviceExtensions . push_back ( VK_KHR_MULTIVIEW_EXTENSION_NAME ) ;
enabledDeviceExtensions . push_back ( VK_KHR_CREATE_RENDERPASS_2_EXTENSION_NAME ) ;
enabledDeviceExtensions . push_back ( VK_KHR_DEPTH_STENCIL_RESOLVE_EXTENSION_NAME ) ;
// Enable dynamic rendering features
enabledDynamicRenderingFeaturesKHR . sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DYNAMIC_RENDERING_FEATURES_KHR ;
enabledDynamicRenderingFeaturesKHR . dynamicRendering = VK_TRUE ;
deviceCreatepNextChain = & enabledDynamicRenderingFeaturesKHR ;
2021-11-10 19:04:02 +01:00
// Don't use the ImGui overlay of the base framework in this sample
settings . overlay = false ;
2017-03-30 19:27:57 +02:00
}
~ VulkanExample ( )
{
vkDestroyPipeline ( device , pipeline , nullptr ) ;
vkDestroyPipelineLayout ( device , pipelineLayout , nullptr ) ;
vkDestroyDescriptorSetLayout ( device , descriptorSetLayout , nullptr ) ;
2025-08-17 18:56:17 +02:00
vkDestroyPipeline ( device , proceduralPipeline , nullptr ) ;
vkDestroyPipelineLayout ( device , proceduralPipelineLayout , nullptr ) ;
vkDestroyDescriptorSetLayout ( device , proceduralDescriptorSetLayout , nullptr ) ;
2017-03-30 19:27:57 +02:00
uniformBufferVS . destroy ( ) ;
delete imGui ;
}
2020-05-29 16:08:53 +01:00
2025-08-17 18:56:17 +02:00
void setupRenderPass ( )
2017-03-30 19:27:57 +02:00
{
2025-08-17 18:56:17 +02:00
// With VK_KHR_dynamic_rendering we no longer need a render pass, so skip the sample base render pass setup
renderPass = VK_NULL_HANDLE ;
}
2017-03-30 19:27:57 +02:00
2025-08-17 18:56:17 +02:00
void setupFrameBuffer ( )
{
// With VK_KHR_dynamic_rendering we no longer need a frame buffer, so skip the sample base framebuffer setup
}
2017-03-30 19:27:57 +02:00
2025-08-17 18:56:17 +02:00
void renderProceduralShapes ( VkCommandBuffer commandBuffer ) {
// Render procedural shapes using persistent buffers
static int frameCount = 0 ;
frameCount + + ;
for ( auto & obj : sceneManager . objects ) {
if ( obj . type = = " Procedural " & & obj . visible & & ! obj . indices . empty ( ) & & obj . buffersCreated ) {
// Calculate model matrix for this object
glm : : mat4 model = glm : : mat4 ( 1.0f ) ;
model = glm : : translate ( model , obj . position ) ;
model = glm : : rotate ( model , glm : : radians ( obj . rotation . x ) , glm : : vec3 ( 1.0f , 0.0f , 0.0f ) ) ;
model = glm : : rotate ( model , glm : : radians ( obj . rotation . y ) , glm : : vec3 ( 0.0f , 1.0f , 0.0f ) ) ;
model = glm : : rotate ( model , glm : : radians ( obj . rotation . z ) , glm : : vec3 ( 0.0f , 0.0f , 1.0f ) ) ;
model = glm : : scale ( model , obj . scale ) ;
// Update uniform buffer with object's model matrix while preserving camera matrices
UBOVS tempUBO ;
tempUBO . projection = orbitCamera . GetProjectionMatrix ( ( float ) width / ( float ) height , 0.1f , 256.0f ) ; // Use OrbitCamera projection
tempUBO . model = orbitCamera . GetViewMatrix ( ) * model ; // Combine view and model matrices
tempUBO . lightPos = uboVS . lightPos ; // Preserve light position
VK_CHECK_RESULT ( uniformBufferVS . map ( ) ) ;
memcpy ( uniformBufferVS . mapped , & tempUBO , sizeof ( tempUBO ) ) ;
uniformBufferVS . unmap ( ) ;
// Validate buffers before binding
if ( obj . vertexBuffer . buffer = = VK_NULL_HANDLE | | obj . indexBuffer . buffer = = VK_NULL_HANDLE ) {
std : : cout < < " Warning: Invalid buffer handles for " < < obj . name < < std : : endl ;
continue ;
}
// Bind the vertex and index buffers for this object
VkBuffer vertexBuffers [ ] = { obj . vertexBuffer . buffer } ;
VkDeviceSize offsets [ ] = { 0 } ;
vkCmdBindVertexBuffers ( commandBuffer , 0 , 1 , vertexBuffers , offsets ) ;
vkCmdBindIndexBuffer ( commandBuffer , obj . indexBuffer . buffer , 0 , VK_INDEX_TYPE_UINT32 ) ;
// Draw the object
vkCmdDrawIndexed ( commandBuffer , static_cast < uint32_t > ( obj . indices . size ( ) ) , 1 , 0 , 0 , 0 ) ;
}
}
}
void buildCommandBuffers ( )
{
VkCommandBufferBeginInfo cmdBufInfo = vks : : initializers : : commandBufferBeginInfo ( ) ;
2017-03-30 19:27:57 +02:00
imGui - > newFrame ( this , ( frameCounter = = 0 ) ) ;
imGui - > updateBuffers ( ) ;
for ( int32_t i = 0 ; i < drawCmdBuffers . size ( ) ; + + i )
{
VK_CHECK_RESULT ( vkBeginCommandBuffer ( drawCmdBuffers [ i ] , & cmdBufInfo ) ) ;
2025-08-17 18:56:17 +02:00
// Dynamic rendering requires manual image layout transitions
// Prepare color attachment for rendering
vks : : tools : : insertImageMemoryBarrier (
drawCmdBuffers [ i ] ,
swapChain . images [ i ] ,
0 ,
VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT ,
VK_IMAGE_LAYOUT_UNDEFINED ,
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL ,
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT ,
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT ,
VkImageSubresourceRange { VK_IMAGE_ASPECT_COLOR_BIT , 0 , 1 , 0 , 1 } ) ;
// Prepare depth attachment for rendering
vks : : tools : : insertImageMemoryBarrier (
drawCmdBuffers [ i ] ,
depthStencil . image ,
0 ,
VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT ,
VK_IMAGE_LAYOUT_UNDEFINED ,
VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL ,
VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT ,
VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT ,
VkImageSubresourceRange { VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT , 0 , 1 , 0 , 1 } ) ;
// Set up rendering attachments for dynamic rendering
VkRenderingAttachmentInfoKHR colorAttachment { } ;
colorAttachment . sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO_KHR ;
colorAttachment . imageView = swapChain . imageViews [ i ] ;
colorAttachment . imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL ;
colorAttachment . loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR ;
colorAttachment . storeOp = VK_ATTACHMENT_STORE_OP_STORE ;
colorAttachment . clearValue . color = { 0.2f , 0.2f , 0.2f , 1.0f } ;
VkRenderingAttachmentInfoKHR depthStencilAttachment { } ;
depthStencilAttachment . sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO_KHR ;
depthStencilAttachment . imageView = depthStencil . view ;
depthStencilAttachment . imageLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL ;
depthStencilAttachment . loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR ;
depthStencilAttachment . storeOp = VK_ATTACHMENT_STORE_OP_STORE ;
depthStencilAttachment . clearValue . depthStencil = { 1.0f , 0 } ;
VkRenderingInfoKHR renderingInfo { } ;
renderingInfo . sType = VK_STRUCTURE_TYPE_RENDERING_INFO_KHR ;
renderingInfo . renderArea = { 0 , 0 , width , height } ;
renderingInfo . layerCount = 1 ;
renderingInfo . colorAttachmentCount = 1 ;
renderingInfo . pColorAttachments = & colorAttachment ;
renderingInfo . pDepthAttachment = & depthStencilAttachment ;
renderingInfo . pStencilAttachment = & depthStencilAttachment ;
// Begin dynamic rendering
vkCmdBeginRenderingKHR ( drawCmdBuffers [ i ] , & renderingInfo ) ;
2017-03-30 19:27:57 +02:00
VkViewport viewport = vks : : initializers : : viewport ( ( float ) width , ( float ) height , 0.0f , 1.0f ) ;
vkCmdSetViewport ( drawCmdBuffers [ i ] , 0 , 1 , & viewport ) ;
VkRect2D scissor = vks : : initializers : : rect2D ( width , height , 0 , 0 ) ;
vkCmdSetScissor ( drawCmdBuffers [ i ] , 0 , 1 , & scissor ) ;
2025-08-17 18:56:17 +02:00
// Render vkglTF models with original pipeline
2017-03-30 19:27:57 +02:00
vkCmdBindDescriptorSets ( drawCmdBuffers [ i ] , VK_PIPELINE_BIND_POINT_GRAPHICS , pipelineLayout , 0 , 1 , & descriptorSet , 0 , nullptr ) ;
vkCmdBindPipeline ( drawCmdBuffers [ i ] , VK_PIPELINE_BIND_POINT_GRAPHICS , pipeline ) ;
if ( uiSettings . displayBackground ) {
2020-07-28 20:20:38 +02:00
models . background . draw ( drawCmdBuffers [ i ] ) ;
2017-03-30 19:27:57 +02:00
}
if ( uiSettings . displayModels ) {
2020-07-28 20:20:38 +02:00
models . models . draw ( drawCmdBuffers [ i ] ) ;
2017-03-30 19:27:57 +02:00
}
2025-08-17 18:56:17 +02:00
// Switch to procedural pipeline for procedural shapes
vkCmdBindDescriptorSets ( drawCmdBuffers [ i ] , VK_PIPELINE_BIND_POINT_GRAPHICS , proceduralPipelineLayout , 0 , 1 , & proceduralDescriptorSet , 0 , nullptr ) ;
vkCmdBindPipeline ( drawCmdBuffers [ i ] , VK_PIPELINE_BIND_POINT_GRAPHICS , proceduralPipeline ) ;
// Render procedural shapes
renderProceduralShapes ( drawCmdBuffers [ i ] ) ;
2017-03-30 19:27:57 +02:00
// Render imGui
2024-05-23 21:56:42 +02:00
if ( ui . visible ) {
2022-08-01 16:11:57 -04:00
imGui - > drawFrame ( drawCmdBuffers [ i ] ) ;
}
2017-03-30 19:27:57 +02:00
2025-08-17 18:56:17 +02:00
// End dynamic rendering
vkCmdEndRenderingKHR ( drawCmdBuffers [ i ] ) ;
// Prepare image for presentation
vks : : tools : : insertImageMemoryBarrier (
drawCmdBuffers [ i ] ,
swapChain . images [ i ] ,
VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT ,
0 ,
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL ,
VK_IMAGE_LAYOUT_PRESENT_SRC_KHR ,
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT ,
VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT ,
VkImageSubresourceRange { VK_IMAGE_ASPECT_COLOR_BIT , 0 , 1 , 0 , 1 } ) ;
2017-03-30 19:27:57 +02:00
VK_CHECK_RESULT ( vkEndCommandBuffer ( drawCmdBuffers [ i ] ) ) ;
}
}
void setupLayoutsAndDescriptors ( )
{
2025-08-17 18:56:17 +02:00
// descriptor pool (increased to handle procedural pipeline too)
2017-03-30 19:27:57 +02:00
std : : vector < VkDescriptorPoolSize > poolSizes = {
2025-08-17 18:56:17 +02:00
vks : : initializers : : descriptorPoolSize ( VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER , 4 ) ,
2017-03-30 19:27:57 +02:00
vks : : initializers : : descriptorPoolSize ( VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER , 1 )
} ;
2025-08-17 18:56:17 +02:00
VkDescriptorPoolCreateInfo descriptorPoolInfo = vks : : initializers : : descriptorPoolCreateInfo ( poolSizes , 4 ) ;
2017-03-30 19:27:57 +02:00
VK_CHECK_RESULT ( vkCreateDescriptorPool ( device , & descriptorPoolInfo , nullptr , & descriptorPool ) ) ;
// Set layout
std : : vector < VkDescriptorSetLayoutBinding > setLayoutBindings = {
vks : : initializers : : descriptorSetLayoutBinding ( VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER , VK_SHADER_STAGE_VERTEX_BIT , 0 ) ,
} ;
VkDescriptorSetLayoutCreateInfo descriptorLayout =
vks : : initializers : : descriptorSetLayoutCreateInfo ( setLayoutBindings ) ;
VK_CHECK_RESULT ( vkCreateDescriptorSetLayout ( device , & descriptorLayout , nullptr , & descriptorSetLayout ) ) ;
// Pipeline layout
2024-01-21 13:15:56 +01:00
VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = vks : : initializers : : pipelineLayoutCreateInfo ( & descriptorSetLayout , 1 ) ;
VK_CHECK_RESULT ( vkCreatePipelineLayout ( device , & pipelineLayoutCreateInfo , nullptr , & pipelineLayout ) ) ;
2017-03-30 19:27:57 +02:00
// Descriptor set
VkDescriptorSetAllocateInfo allocInfo = vks : : initializers : : descriptorSetAllocateInfo ( descriptorPool , & descriptorSetLayout , 1 ) ;
VK_CHECK_RESULT ( vkAllocateDescriptorSets ( device , & allocInfo , & descriptorSet ) ) ;
std : : vector < VkWriteDescriptorSet > writeDescriptorSets = {
vks : : initializers : : writeDescriptorSet ( descriptorSet , VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER , 0 , & uniformBufferVS . descriptor ) ,
} ;
vkUpdateDescriptorSets ( device , static_cast < uint32_t > ( writeDescriptorSets . size ( ) ) , writeDescriptorSets . data ( ) , 0 , nullptr ) ;
2025-08-17 18:56:17 +02:00
// Allocate procedural descriptor set (note: proceduralDescriptorSetLayout is created in prepareProceduralPipeline)
// This will be done after pipelines are prepared
}
void setupProceduralDescriptorSet ( )
{
// Allocate procedural descriptor set
VkDescriptorSetAllocateInfo allocInfo = vks : : initializers : : descriptorSetAllocateInfo ( descriptorPool , & proceduralDescriptorSetLayout , 1 ) ;
VK_CHECK_RESULT ( vkAllocateDescriptorSets ( device , & allocInfo , & proceduralDescriptorSet ) ) ;
std : : vector < VkWriteDescriptorSet > writeDescriptorSets = {
vks : : initializers : : writeDescriptorSet ( proceduralDescriptorSet , VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER , 0 , & uniformBufferVS . descriptor ) ,
} ;
vkUpdateDescriptorSets ( device , static_cast < uint32_t > ( writeDescriptorSets . size ( ) ) , writeDescriptorSets . data ( ) , 0 , nullptr ) ;
2017-03-30 19:27:57 +02:00
}
void preparePipelines ( )
{
// Rendering
2020-07-28 20:20:38 +02:00
VkPipelineInputAssemblyStateCreateInfo inputAssemblyState = vks : : initializers : : pipelineInputAssemblyStateCreateInfo ( VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST , 0 , VK_FALSE ) ;
VkPipelineRasterizationStateCreateInfo rasterizationState = vks : : initializers : : pipelineRasterizationStateCreateInfo ( VK_POLYGON_MODE_FILL , VK_CULL_MODE_BACK_BIT , VK_FRONT_FACE_COUNTER_CLOCKWISE ) ;
VkPipelineColorBlendAttachmentState blendAttachmentState = vks : : initializers : : pipelineColorBlendAttachmentState ( 0xf , VK_FALSE ) ;
VkPipelineColorBlendStateCreateInfo colorBlendState = vks : : initializers : : pipelineColorBlendStateCreateInfo ( 1 , & blendAttachmentState ) ;
VkPipelineDepthStencilStateCreateInfo depthStencilState = vks : : initializers : : pipelineDepthStencilStateCreateInfo ( VK_TRUE , VK_TRUE , VK_COMPARE_OP_LESS_OR_EQUAL ) ;
VkPipelineViewportStateCreateInfo viewportState = vks : : initializers : : pipelineViewportStateCreateInfo ( 1 , 1 , 0 ) ;
VkPipelineMultisampleStateCreateInfo multisampleState = vks : : initializers : : pipelineMultisampleStateCreateInfo ( VK_SAMPLE_COUNT_1_BIT ) ;
std : : vector < VkDynamicState > dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT , VK_DYNAMIC_STATE_SCISSOR } ;
VkPipelineDynamicStateCreateInfo dynamicState = vks : : initializers : : pipelineDynamicStateCreateInfo ( dynamicStateEnables ) ;
2017-03-30 19:27:57 +02:00
std : : array < VkPipelineShaderStageCreateInfo , 2 > shaderStages ;
2025-08-17 18:56:17 +02:00
// Create pipeline without render pass for dynamic rendering
VkGraphicsPipelineCreateInfo pipelineCI = vks : : initializers : : pipelineCreateInfo ( ) ;
pipelineCI . layout = pipelineLayout ;
2020-07-28 20:20:38 +02:00
pipelineCI . pInputAssemblyState = & inputAssemblyState ;
pipelineCI . pRasterizationState = & rasterizationState ;
pipelineCI . pColorBlendState = & colorBlendState ;
pipelineCI . pMultisampleState = & multisampleState ;
pipelineCI . pViewportState = & viewportState ;
pipelineCI . pDepthStencilState = & depthStencilState ;
pipelineCI . pDynamicState = & dynamicState ;
pipelineCI . stageCount = static_cast < uint32_t > ( shaderStages . size ( ) ) ;
pipelineCI . pStages = shaderStages . data ( ) ;
pipelineCI . pVertexInputState = vkglTF : : Vertex : : getPipelineVertexInputState ( { vkglTF : : VertexComponent : : Position , vkglTF : : VertexComponent : : Normal , vkglTF : : VertexComponent : : Color } ) ; ;
2017-03-30 19:27:57 +02:00
2025-08-17 18:56:17 +02:00
// Dynamic rendering create info to define color, depth and stencil attachments at pipeline create time
VkPipelineRenderingCreateInfoKHR pipelineRenderingCreateInfo { } ;
pipelineRenderingCreateInfo . sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO_KHR ;
pipelineRenderingCreateInfo . colorAttachmentCount = 1 ;
pipelineRenderingCreateInfo . pColorAttachmentFormats = & swapChain . colorFormat ;
pipelineRenderingCreateInfo . depthAttachmentFormat = depthFormat ;
pipelineRenderingCreateInfo . stencilAttachmentFormat = depthFormat ;
// Chain into the pipeline create info
pipelineCI . pNext = & pipelineRenderingCreateInfo ;
2020-05-29 16:08:53 +01:00
shaderStages [ 0 ] = loadShader ( getShadersPath ( ) + " imgui/scene.vert.spv " , VK_SHADER_STAGE_VERTEX_BIT ) ;
shaderStages [ 1 ] = loadShader ( getShadersPath ( ) + " imgui/scene.frag.spv " , VK_SHADER_STAGE_FRAGMENT_BIT ) ;
2020-07-28 20:20:38 +02:00
VK_CHECK_RESULT ( vkCreateGraphicsPipelines ( device , pipelineCache , 1 , & pipelineCI , nullptr , & pipeline ) ) ;
2025-08-17 18:56:17 +02:00
// Create procedural shapes pipeline
prepareProceduralPipeline ( ) ;
}
void prepareProceduralPipeline ( )
{
// Descriptor set layout for procedural shapes (uniform buffer only)
VkDescriptorSetLayoutBinding layoutBinding = vks : : initializers : : descriptorSetLayoutBinding ( VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER , VK_SHADER_STAGE_VERTEX_BIT , 0 ) ;
VkDescriptorSetLayoutCreateInfo descriptorLayout = vks : : initializers : : descriptorSetLayoutCreateInfo ( & layoutBinding , 1 ) ;
VK_CHECK_RESULT ( vkCreateDescriptorSetLayout ( device , & descriptorLayout , nullptr , & proceduralDescriptorSetLayout ) ) ;
// Pipeline layout
VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = vks : : initializers : : pipelineLayoutCreateInfo ( & proceduralDescriptorSetLayout , 1 ) ;
VK_CHECK_RESULT ( vkCreatePipelineLayout ( device , & pipelineLayoutCreateInfo , nullptr , & proceduralPipelineLayout ) ) ;
// Pipeline
VkPipelineInputAssemblyStateCreateInfo inputAssemblyState = vks : : initializers : : pipelineInputAssemblyStateCreateInfo ( VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST , 0 , VK_FALSE ) ;
VkPipelineRasterizationStateCreateInfo rasterizationState = vks : : initializers : : pipelineRasterizationStateCreateInfo ( VK_POLYGON_MODE_FILL , VK_CULL_MODE_BACK_BIT , VK_FRONT_FACE_COUNTER_CLOCKWISE ) ;
VkPipelineColorBlendAttachmentState blendAttachmentState = vks : : initializers : : pipelineColorBlendAttachmentState ( 0xf , VK_FALSE ) ;
VkPipelineColorBlendStateCreateInfo colorBlendState = vks : : initializers : : pipelineColorBlendStateCreateInfo ( 1 , & blendAttachmentState ) ;
VkPipelineDepthStencilStateCreateInfo depthStencilState = vks : : initializers : : pipelineDepthStencilStateCreateInfo ( VK_TRUE , VK_TRUE , VK_COMPARE_OP_LESS_OR_EQUAL ) ;
VkPipelineViewportStateCreateInfo viewportState = vks : : initializers : : pipelineViewportStateCreateInfo ( 1 , 1 , 0 ) ;
VkPipelineMultisampleStateCreateInfo multisampleState = vks : : initializers : : pipelineMultisampleStateCreateInfo ( VK_SAMPLE_COUNT_1_BIT ) ;
std : : vector < VkDynamicState > dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT , VK_DYNAMIC_STATE_SCISSOR } ;
VkPipelineDynamicStateCreateInfo dynamicState = vks : : initializers : : pipelineDynamicStateCreateInfo ( dynamicStateEnables ) ;
// Vertex input for procedural shapes
VkVertexInputBindingDescription vertexInputBinding = vks : : initializers : : vertexInputBindingDescription ( 0 , sizeof ( ProceduralVertex ) , VK_VERTEX_INPUT_RATE_VERTEX ) ;
std : : vector < VkVertexInputAttributeDescription > vertexInputAttributes = {
vks : : initializers : : vertexInputAttributeDescription ( 0 , 0 , VK_FORMAT_R32G32B32_SFLOAT , 0 ) , // Position
vks : : initializers : : vertexInputAttributeDescription ( 0 , 1 , VK_FORMAT_R32G32B32_SFLOAT , sizeof ( float ) * 3 ) , // Normal
vks : : initializers : : vertexInputAttributeDescription ( 0 , 2 , VK_FORMAT_R32G32B32_SFLOAT , sizeof ( float ) * 6 ) , // Color
} ;
VkPipelineVertexInputStateCreateInfo vertexInputState = vks : : initializers : : pipelineVertexInputStateCreateInfo ( ) ;
vertexInputState . vertexBindingDescriptionCount = 1 ;
vertexInputState . pVertexBindingDescriptions = & vertexInputBinding ;
vertexInputState . vertexAttributeDescriptionCount = static_cast < uint32_t > ( vertexInputAttributes . size ( ) ) ;
vertexInputState . pVertexAttributeDescriptions = vertexInputAttributes . data ( ) ;
std : : array < VkPipelineShaderStageCreateInfo , 2 > shaderStages ;
VkGraphicsPipelineCreateInfo pipelineCI = vks : : initializers : : pipelineCreateInfo ( ) ;
pipelineCI . layout = proceduralPipelineLayout ;
pipelineCI . pInputAssemblyState = & inputAssemblyState ;
pipelineCI . pRasterizationState = & rasterizationState ;
pipelineCI . pColorBlendState = & colorBlendState ;
pipelineCI . pMultisampleState = & multisampleState ;
pipelineCI . pViewportState = & viewportState ;
pipelineCI . pDepthStencilState = & depthStencilState ;
pipelineCI . pDynamicState = & dynamicState ;
pipelineCI . pVertexInputState = & vertexInputState ;
pipelineCI . stageCount = static_cast < uint32_t > ( shaderStages . size ( ) ) ;
pipelineCI . pStages = shaderStages . data ( ) ;
// Dynamic rendering create info
VkPipelineRenderingCreateInfoKHR pipelineRenderingCreateInfo { } ;
pipelineRenderingCreateInfo . sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO_KHR ;
pipelineRenderingCreateInfo . colorAttachmentCount = 1 ;
pipelineRenderingCreateInfo . pColorAttachmentFormats = & swapChain . colorFormat ;
pipelineRenderingCreateInfo . depthAttachmentFormat = depthFormat ;
pipelineRenderingCreateInfo . stencilAttachmentFormat = depthFormat ;
pipelineCI . pNext = & pipelineRenderingCreateInfo ;
shaderStages [ 0 ] = loadShader ( getShadersPath ( ) + " imgui/scene.vert.spv " , VK_SHADER_STAGE_VERTEX_BIT ) ;
shaderStages [ 1 ] = loadShader ( getShadersPath ( ) + " imgui/scene.frag.spv " , VK_SHADER_STAGE_FRAGMENT_BIT ) ;
VK_CHECK_RESULT ( vkCreateGraphicsPipelines ( device , pipelineCache , 1 , & pipelineCI , nullptr , & proceduralPipeline ) ) ;
2017-03-30 19:27:57 +02:00
}
// Prepare and initialize uniform buffer containing shader uniforms
void prepareUniformBuffers ( )
{
// Vertex shader uniform buffer block
VK_CHECK_RESULT ( vulkanDevice - > createBuffer (
VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT ,
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT ,
& uniformBufferVS ,
sizeof ( uboVS ) ,
& uboVS ) ) ;
updateUniformBuffers ( ) ;
}
void updateUniformBuffers ( )
{
2025-08-17 18:56:17 +02:00
// Update orbit camera
orbitCamera . Update ( frameTimer ) ;
// Vertex shader - use OrbitCamera matrices
uboVS . projection = orbitCamera . GetProjectionMatrix ( ( float ) width / ( float ) height , 0.1f , 256.0f ) ;
uboVS . model = orbitCamera . GetViewMatrix ( ) * glm : : mat4 ( 1.0f ) ;
2017-03-30 19:27:57 +02:00
// Light source
if ( uiSettings . animateLight ) {
uiSettings . lightTimer + = frameTimer * uiSettings . lightSpeed ;
uboVS . lightPos . x = sin ( glm : : radians ( uiSettings . lightTimer * 360.0f ) ) * 15.0f ;
uboVS . lightPos . z = cos ( glm : : radians ( uiSettings . lightTimer * 360.0f ) ) * 15.0f ;
} ;
VK_CHECK_RESULT ( uniformBufferVS . map ( ) ) ;
memcpy ( uniformBufferVS . mapped , & uboVS , sizeof ( uboVS ) ) ;
uniformBufferVS . unmap ( ) ;
}
void draw ( )
{
VulkanExampleBase : : prepareFrame ( ) ;
2025-08-17 18:56:17 +02:00
// Check if we need to create buffers for new objects
bool needsCommandBufferRebuild = false ;
for ( auto & obj : sceneManager . objects ) {
if ( obj . type = = " Procedural " & & ! obj . buffersCreated & & ! obj . vertices . empty ( ) & & ! obj . indices . empty ( ) ) {
// Wait for GPU to finish current operations before creating new buffers
vkDeviceWaitIdle ( device ) ;
sceneManager . createBuffersForObject ( obj ) ;
needsCommandBufferRebuild = true ;
}
}
// If new objects were added, ensure proper synchronization
if ( needsCommandBufferRebuild ) {
vkDeviceWaitIdle ( device ) ;
}
2017-03-30 19:27:57 +02:00
buildCommandBuffers ( ) ;
submitInfo . commandBufferCount = 1 ;
submitInfo . pCommandBuffers = & drawCmdBuffers [ currentBuffer ] ;
VK_CHECK_RESULT ( vkQueueSubmit ( queue , 1 , & submitInfo , VK_NULL_HANDLE ) ) ;
VulkanExampleBase : : submitFrame ( ) ;
}
void loadAssets ( )
{
2020-07-28 20:20:38 +02:00
const uint32_t glTFLoadingFlags = vkglTF : : FileLoadingFlags : : PreTransformVertices | vkglTF : : FileLoadingFlags : : PreMultiplyVertexColors | vkglTF : : FileLoadingFlags : : FlipY ;
2025-08-17 18:56:17 +02:00
// Models available in assets but not auto-loaded
// models.models.loadFromFile(getAssetPath() + "models/MobulaBirostris.gltf", vulkanDevice, queue, glTFLoadingFlags);
// models.background.loadFromFile(getAssetPath() + "models/PolarBear.gltf", vulkanDevice, queue, glTFLoadingFlags);
2017-03-30 19:27:57 +02:00
}
void prepareImGui ( )
{
2017-03-31 09:52:26 +02:00
imGui = new ImGUI ( this ) ;
2017-03-30 19:27:57 +02:00
imGui - > init ( ( float ) width , ( float ) height ) ;
2025-08-17 18:56:17 +02:00
imGui - > initResources ( renderPass , queue , getShadersPath ( ) , swapChain . colorFormat , depthFormat ) ;
2017-03-30 19:27:57 +02:00
}
void prepare ( )
{
VulkanExampleBase : : prepare ( ) ;
2025-08-17 18:56:17 +02:00
// Get dynamic rendering function pointers
vkCmdBeginRenderingKHR = reinterpret_cast < PFN_vkCmdBeginRenderingKHR > ( vkGetDeviceProcAddr ( device , " vkCmdBeginRenderingKHR " ) ) ;
vkCmdEndRenderingKHR = reinterpret_cast < PFN_vkCmdEndRenderingKHR > ( vkGetDeviceProcAddr ( device , " vkCmdEndRenderingKHR " ) ) ;
// Validate function pointers
if ( ! vkCmdBeginRenderingKHR | | ! vkCmdEndRenderingKHR ) {
std : : cout < < " ERROR: Failed to load dynamic rendering function pointers " < < std : : endl ;
exit ( 1 ) ;
}
// Initialize scene manager with device pointer
sceneManager . device = vulkanDevice ;
2017-03-30 19:27:57 +02:00
loadAssets ( ) ;
prepareUniformBuffers ( ) ;
setupLayoutsAndDescriptors ( ) ;
preparePipelines ( ) ;
2025-08-17 18:56:17 +02:00
setupProceduralDescriptorSet ( ) ;
2017-03-30 19:27:57 +02:00
prepareImGui ( ) ;
buildCommandBuffers ( ) ;
prepared = true ;
}
virtual void render ( )
{
if ( ! prepared )
return ;
2023-02-25 09:37:08 +01:00
updateUniformBuffers ( ) ;
2017-03-30 19:27:57 +02:00
// Update imGui
ImGuiIO & io = ImGui : : GetIO ( ) ;
io . DisplaySize = ImVec2 ( ( float ) width , ( float ) height ) ;
io . DeltaTime = frameTimer ;
2024-03-20 07:49:06 +01:00
io . MousePos = ImVec2 ( mouseState . position . x , mouseState . position . y ) ;
2024-05-23 21:56:42 +02:00
io . MouseDown [ 0 ] = mouseState . buttons . left & & ui . visible ;
io . MouseDown [ 1 ] = mouseState . buttons . right & & ui . visible ;
io . MouseDown [ 2 ] = mouseState . buttons . middle & & ui . visible ;
2017-03-30 19:27:57 +02:00
2025-08-17 18:56:17 +02:00
// Handle mouse wheel for camera zoom
if ( io . MouseWheel ! = 0.0f ) {
orbitCamera . ZoomImmediate ( - io . MouseWheel * 10.0f , frameTimer ) ;
}
2017-03-30 19:27:57 +02:00
draw ( ) ;
}
2017-10-05 21:22:29 +02:00
2023-02-25 09:37:08 +01:00
// Input handling is platform specific, to show how it's basically done this sample implements it for Windows
# if defined(_WIN32)
virtual void OnHandleMessage ( HWND hWnd , UINT uMsg , WPARAM wParam , LPARAM lParam ) {
ImGuiIO & io = ImGui : : GetIO ( ) ;
2025-08-17 18:56:17 +02:00
// Handle mouse wheel for ImGui (needed for camera zoom)
if ( uMsg = = WM_MOUSEWHEEL ) {
io . MouseWheel + = ( float ) GET_WHEEL_DELTA_WPARAM ( wParam ) / ( float ) WHEEL_DELTA ;
}
2023-02-25 09:37:08 +01:00
// Only react to keyboard input if ImGui is active
if ( io . WantCaptureKeyboard ) {
// Character input
if ( uMsg = = WM_CHAR ) {
if ( wParam > 0 & & wParam < 0x10000 ) {
io . AddInputCharacter ( ( unsigned short ) wParam ) ;
}
}
// Special keys (tab, cursor, etc.)
if ( ( wParam < 256 ) & & ( uMsg = = WM_KEYDOWN | | uMsg = = WM_SYSKEYDOWN ) ) {
io . KeysDown [ wParam ] = true ;
}
if ( ( wParam < 256 ) & & ( uMsg = = WM_KEYUP | | uMsg = = WM_SYSKEYUP ) ) {
io . KeysDown [ wParam ] = false ;
}
}
}
# endif
2025-08-17 18:56:17 +02:00
// Override keyPressed for F key focus functionality
virtual void keyPressed ( uint32_t keyCode ) override
{
// Handle F key for focus (immediate, no smoothing)
if ( keyCode = = KEY_F ) {
if ( sceneManager . hasSelection ( ) ) {
// Focus on selected object immediately
SceneObject * selectedObject = sceneManager . getSelectedObject ( ) ;
if ( selectedObject ) {
glm : : vec3 objectCenter = selectedObject - > getBoundingBoxCenter ( ) ;
float objectRadius = selectedObject - > getBoundingRadius ( ) ;
orbitCamera . SetFocusToSelectionImmediate ( objectCenter , objectRadius ) ;
std : : cout < < " Focused camera on selected object: " < < selectedObject - > name < < std : : endl ;
}
} else {
// No selection, focus on scene center immediately
orbitCamera . FrameAllImmediate ( glm : : vec3 ( 0.0f , 0.0f , - 5.0f ) , 3.0f ) ;
std : : cout < < " Focused camera on scene center (no selection) " < < std : : endl ;
}
}
// Call base implementation for other keys
VulkanExampleBase : : keyPressed ( keyCode ) ;
}
// Override mouseMoved for camera orbit/pan controls
virtual void mouseMoved ( double x , double y , bool & handled ) override
{
ImGuiIO & io = ImGui : : GetIO ( ) ;
// Calculate mouse delta
double deltaX = x - lastMouseX ;
double deltaY = y - lastMouseY ;
lastMouseX = x ;
lastMouseY = y ;
// Handle industry-standard Maya/3ds Max style camera controls (Alt + mouse)
// Use Windows API directly for Alt key detection since ImGui's KeyAlt is not reliable in this Vulkan framework
# if defined(_WIN32)
bool altPressed = ( GetAsyncKeyState ( VK_MENU ) & 0x8000 ) ! = 0 ;
# else
bool altPressed = io . KeyAlt ; // Fallback for non-Windows platforms
# endif
// Alt key detection is now working properly using Windows GetAsyncKeyState API
if ( altPressed & & mouseState . buttons . left ) {
// Alt + Left mouse: orbit around focus point
if ( std : : abs ( deltaX ) > 0.1 | | std : : abs ( deltaY ) > 0.1 ) {
orbitCamera . Orbit ( deltaX * 0.5f , deltaY * 0.5f , frameTimer ) ;
handled = true ;
}
} else if ( altPressed & & mouseState . buttons . middle ) {
// Alt + Middle mouse: pan viewport (immediate, no smoothing)
if ( std : : abs ( deltaX ) > 0.1 | | std : : abs ( deltaY ) > 0.1 ) {
orbitCamera . PanImmediate ( - deltaX , deltaY , frameTimer ) ;
handled = true ;
}
} else if ( altPressed & & mouseState . buttons . right ) {
// Alt + Right mouse: zoom (dolly camera)
if ( std : : abs ( deltaY ) > 0.1 ) {
orbitCamera . Zoom ( deltaY * 0.01f , frameTimer ) ;
handled = true ;
}
}
// Call base implementation if not handled
if ( ! handled ) {
VulkanExampleBase : : mouseMoved ( x , y , handled ) ;
}
}
2017-03-30 19:27:57 +02:00
} ;
VULKAN_EXAMPLE_MAIN ( )