/* * Procedural 3D 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) */ #include #include "vulkanexamplebase.h" #include "VulkanglTFModel.h" #include #include #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 vertices; std::vector 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 vertices; std::vector 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 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(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(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(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(objects.size()); } // Selection management void setSelectedIndex(int index) { if (index >= -1 && index < static_cast(objects.size())) { selectedIndex = index; } } int getSelectedIndex() const { return selectedIndex; } SceneObject* getSelectedObject() { if (selectedIndex >= 0 && selectedIndex < static_cast(objects.size())) { return &objects[selectedIndex]; } return nullptr; } bool hasSelection() const { return selectedIndex >= 0 && selectedIndex < static_cast(objects.size()); } void clearSelection() { selectedIndex = -1; } } sceneManager; // Options and values to display/toggle from the UI struct UISettings { bool displayModels = false; bool displayBackground = false; bool animateLight = false; float lightSpeed = 0.25f; std::array frameTimes{}; float frameTimeMin = 9999.0f, frameTimeMax = 0.0f; float lightTimer = 0.0f; 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 } 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; VkPhysicalDeviceDriverProperties driverProperties = {}; VulkanExampleBase *example; ImGuiStyle vulkanStyle; int selectedStyle = 0; public: // UI params are set via push constants struct PushConstBlock { glm::vec2 scale; glm::vec2 translate; } pushConstBlock; ImGUI(VulkanExampleBase *example) : example(example) { device = example->vulkanDevice; ImGui::CreateContext(); //SRS - Set ImGui font and style scale factors to handle retina and other HiDPI displays ImGuiIO& io = ImGui::GetIO(); io.FontGlobalScale = example->ui.scale; ImGuiStyle& style = ImGui::GetStyle(); style.ScaleAllSizes(example->ui.scale); }; ~ImGUI() { ImGui::DestroyContext(); // 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 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); // Dimensions ImGuiIO& io = ImGui::GetIO(); io.DisplaySize = ImVec2(width, height); io.DisplayFramebufferScale = ImVec2(1.0f, 1.0f); #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; io.KeyMap[ImGuiKey_Space] = VK_SPACE; io.KeyMap[ImGuiKey_Delete] = VK_DELETE; #endif } 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; 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; } } } // Initialize all Vulkan resources used by the ui void initResources(VkRenderPass renderPass, VkQueue copyQueue, const std::string& shadersPath, VkFormat colorFormat = VK_FORMAT_UNDEFINED, VkFormat depthFormat = VK_FORMAT_UNDEFINED) { 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); //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); } // 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 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 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 writeDescriptorSets = { vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 0, &fontDescriptor) }; vkUpdateDescriptorSets(device->logicalDevice, static_cast(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 dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }; VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables); std::array shaderStages{}; // 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); } 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(shaderStages.size()); pipelineCreateInfo.pStages = shaderStages.data(); // Vertex bindings an attributes based on ImGui vertex definition std::vector vertexInputBindings = { vks::initializers::vertexInputBindingDescription(0, sizeof(ImDrawVert), VK_VERTEX_INPUT_RATE_VERTEX), }; std::vector 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(vertexInputBindings.size()); vertexInputState.pVertexBindingDescriptions = vertexInputBindings.data(); vertexInputState.vertexAttributeDescriptionCount = static_cast(vertexInputAttributes.size()); vertexInputState.pVertexAttributeDescriptions = vertexInputAttributes.data(); pipelineCreateInfo.pVertexInputState = &vertexInputState; // 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; } 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); VK_CHECK_RESULT(vkCreateGraphicsPipelines(device->logicalDevice, pipelineCache, 1, &pipelineCreateInfo, nullptr, &pipeline)); } // Starts a new imGui frame and sets up windows and ui elements void newFrame(VulkanExampleBase *example, bool updateFrameGraph) { ImGui::NewFrame(); // 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(); } // Init imGui windows and elements // Debug window 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); ImGui::TextUnformatted(example->title.c_str()); ImGui::TextUnformatted(device->properties.deviceName); //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); // 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)); 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(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 ImGui::End(); } // End Inspector Panel visibility check // 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'; } ImGui::End(); } // End Console Panel visibility check //SRS - ShowDemoWindow() sets its own initial position and size, cannot override here // ImGui::ShowDemoWindow(); // 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); if ((vertexBufferSize == 0) || (indexBufferSize == 0)) { return; } // 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; 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++) { 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; } #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 vertexOffset += cmd_list->VtxBuffer.Size; #endif } } } }; // ---------------------------------------------------------------------------- // VulkanExample // ---------------------------------------------------------------------------- class VulkanExample : public VulkanExampleBase { public: // Dynamic rendering function pointers PFN_vkCmdBeginRenderingKHR vkCmdBeginRenderingKHR{ VK_NULL_HANDLE }; PFN_vkCmdEndRenderingKHR vkCmdEndRenderingKHR{ VK_NULL_HANDLE }; VkPhysicalDeviceDynamicRenderingFeaturesKHR enabledDynamicRenderingFeaturesKHR{}; ImGUI *imGui = nullptr; struct Models { vkglTF::Model models; vkglTF::Model logos; vkglTF::Model background; } models; vks::Buffer uniformBufferVS; struct UBOVS { glm::mat4 projection; glm::mat4 model; glm::vec4 lightPos; } uboVS; VkPipelineLayout pipelineLayout; VkPipeline pipeline; VkDescriptorSetLayout descriptorSetLayout; VkDescriptorSet descriptorSet; // 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)) { title = "ProceduralEngine - Vulkan 3D Viewport"; camera.type = Camera::CameraType::lookat; camera.setPosition(glm::vec3(0.0f, 0.0f, -8.0f)); camera.setRotation(glm::vec3(4.5f, -380.0f, 0.0f)); camera.setPerspective(45.0f, (float)width / (float)height, 0.1f, 256.0f); // 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 //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); // 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; // Don't use the ImGui overlay of the base framework in this sample settings.overlay = false; } ~VulkanExample() { vkDestroyPipeline(device, pipeline, nullptr); vkDestroyPipelineLayout(device, pipelineLayout, nullptr); vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr); vkDestroyPipeline(device, proceduralPipeline, nullptr); vkDestroyPipelineLayout(device, proceduralPipelineLayout, nullptr); vkDestroyDescriptorSetLayout(device, proceduralDescriptorSetLayout, nullptr); uniformBufferVS.destroy(); delete imGui; } void setupRenderPass() { // With VK_KHR_dynamic_rendering we no longer need a render pass, so skip the sample base render pass setup renderPass = VK_NULL_HANDLE; } void setupFrameBuffer() { // With VK_KHR_dynamic_rendering we no longer need a frame buffer, so skip the sample base framebuffer setup } 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(obj.indices.size()), 1, 0, 0, 0); } } } void buildCommandBuffers() { VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo(); imGui->newFrame(this, (frameCounter == 0)); imGui->updateBuffers(); for (int32_t i = 0; i < drawCmdBuffers.size(); ++i) { VK_CHECK_RESULT(vkBeginCommandBuffer(drawCmdBuffers[i], &cmdBufInfo)); // 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); 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); // Render vkglTF models with original pipeline 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) { models.background.draw(drawCmdBuffers[i]); } if (uiSettings.displayModels) { models.models.draw(drawCmdBuffers[i]); } // 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]); // Render imGui if (ui.visible) { imGui->drawFrame(drawCmdBuffers[i]); } // 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 }); VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i])); } } void setupLayoutsAndDescriptors() { // descriptor pool (increased to handle procedural pipeline too) std::vector poolSizes = { vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 4), vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1) }; VkDescriptorPoolCreateInfo descriptorPoolInfo = vks::initializers::descriptorPoolCreateInfo(poolSizes, 4); VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool)); // Set layout std::vector 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 VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayout, 1); VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &pipelineLayout)); // Descriptor set VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayout, 1); VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet)); std::vector writeDescriptorSets = { vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBufferVS.descriptor), }; vkUpdateDescriptorSets(device, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr); // 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 writeDescriptorSets = { vks::initializers::writeDescriptorSet(proceduralDescriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBufferVS.descriptor), }; vkUpdateDescriptorSets(device, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr); } void preparePipelines() { // 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_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 dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }; VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables); std::array shaderStages; // Create pipeline without render pass for dynamic rendering VkGraphicsPipelineCreateInfo pipelineCI = vks::initializers::pipelineCreateInfo(); pipelineCI.layout = pipelineLayout; 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(shaderStages.size()); pipelineCI.pStages = shaderStages.data(); pipelineCI.pVertexInputState = vkglTF::Vertex::getPipelineVertexInputState({ vkglTF::VertexComponent::Position, vkglTF::VertexComponent::Normal, vkglTF::VertexComponent::Color });; // 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; 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, &pipeline)); // 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 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 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(vertexInputAttributes.size()); vertexInputState.pVertexAttributeDescriptions = vertexInputAttributes.data(); std::array 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(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)); } // 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() { // 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); // 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(); // 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); } buildCommandBuffers(); submitInfo.commandBufferCount = 1; submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer]; VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE)); VulkanExampleBase::submitFrame(); } void loadAssets() { const uint32_t glTFLoadingFlags = vkglTF::FileLoadingFlags::PreTransformVertices | vkglTF::FileLoadingFlags::PreMultiplyVertexColors | vkglTF::FileLoadingFlags::FlipY; // 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); } void prepareImGui() { imGui = new ImGUI(this); imGui->init((float)width, (float)height); imGui->initResources(renderPass, queue, getShadersPath(), swapChain.colorFormat, depthFormat); } void prepare() { VulkanExampleBase::prepare(); // Get dynamic rendering function pointers vkCmdBeginRenderingKHR = reinterpret_cast(vkGetDeviceProcAddr(device, "vkCmdBeginRenderingKHR")); vkCmdEndRenderingKHR = reinterpret_cast(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; loadAssets(); prepareUniformBuffers(); setupLayoutsAndDescriptors(); preparePipelines(); setupProceduralDescriptorSet(); prepareImGui(); buildCommandBuffers(); prepared = true; } virtual void render() { if (!prepared) return; updateUniformBuffers(); // Update imGui ImGuiIO& io = ImGui::GetIO(); io.DisplaySize = ImVec2((float)width, (float)height); io.DeltaTime = frameTimer; io.MousePos = ImVec2(mouseState.position.x, mouseState.position.y); io.MouseDown[0] = mouseState.buttons.left && ui.visible; io.MouseDown[1] = mouseState.buttons.right && ui.visible; io.MouseDown[2] = mouseState.buttons.middle && ui.visible; // Handle mouse wheel for camera zoom if (io.MouseWheel != 0.0f) { orbitCamera.ZoomImmediate(-io.MouseWheel * 10.0f, frameTimer); } draw(); } // 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(); // Handle mouse wheel for ImGui (needed for camera zoom) if (uMsg == WM_MOUSEWHEEL) { io.MouseWheel += (float)GET_WHEEL_DELTA_WPARAM(wParam) / (float)WHEEL_DELTA; } // 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 // 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); } } }; VULKAN_EXAMPLE_MAIN()