mirror of
https://github.com/Nexoniarz/Fry3DX.git
synced 2026-11-03 04:24:26 +00:00
Fry3DX 0.4: an easy 3D modeller
Shapes with live settings, shape editing, knife and hole tools, drawing shapes onto other objects, combining objects (add / subtract / overlap), copies, thickness, painting with stickers and layers, a Layout tab, a Print tab with checks, packed .fry files with autosave, export to GLB, glTF, FBX, OBJ, 3MF, STL and PLY, folders in Layers, its own crash screen, and a website for GitHub Pages in docs/. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NMSrd5oe1GpUD7E6d825KP
This commit is contained in:
commit
f3e8eca87b
135 changed files with 23423 additions and 0 deletions
260
engine/render/renderer.h
Normal file
260
engine/render/renderer.h
Normal file
|
|
@ -0,0 +1,260 @@
|
|||
#pragma once
|
||||
#include "render/backend.h"
|
||||
#include "render/clusters.h"
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace keel {
|
||||
|
||||
class Window;
|
||||
struct KmdlModel;
|
||||
|
||||
struct Camera {
|
||||
glm::vec3 origin{0.0f};
|
||||
float yaw = 0.0f; // degrees, 0 = +X, counter-clockwise
|
||||
float pitch = 0.0f; // degrees, positive looks down (Quake convention)
|
||||
float roll = 0.0f; // degrees, positive tilts right
|
||||
float fovY = 74.0f; // degrees
|
||||
glm::vec2 shift{0.0f}; // where the picture's middle sits on the screen (-1..1 across, down): a view beside panels
|
||||
};
|
||||
|
||||
// Baked light for a moving model (from the map's probes): ambient cube + sun visibility.
|
||||
struct ModelLighting {
|
||||
glm::vec3 cube[6]; // light arriving from +X -X +Y -Y +Z -Z
|
||||
float sunVisibility = 1.0f;
|
||||
};
|
||||
|
||||
// Liquid shading: a thin film on a surface (puddles, splats; thicker towards the middle of the
|
||||
// model's UV v = 0 end, with an uneven edge), or a glossy body (drops, what's in a bottle).
|
||||
enum class LiquidStyle { None = 0, Film = 1, Body = 2, Water = 3, Decal = 4 }; // Water: open water's surface (waves, deep colour)
|
||||
|
||||
struct ModelId {
|
||||
uint32_t index = UINT32_MAX;
|
||||
bool valid() const { return index != UINT32_MAX; }
|
||||
};
|
||||
|
||||
// World space is Quake-like: Z up, units roughly inches. The scene renders in HDR and is
|
||||
// tonemapped to the screen; UI draws on top. The graphics API sits behind a RenderBackend
|
||||
// (Vulkan or OpenGL 4.6), picked at startup.
|
||||
class Renderer {
|
||||
public:
|
||||
bool init(const Window& window, bool validation, RenderApi api);
|
||||
void shutdown();
|
||||
const char* apiName() const { return backend_ ? backend_->name() : "none"; }
|
||||
GpuInfo gpuInfo() const { return backend_ ? backend_->gpuInfo() : GpuInfo{}; }
|
||||
// Starts a Dear ImGui frame; UI code runs between this and drawFrame().
|
||||
void beginUi();
|
||||
|
||||
// Loaded through the virtual filesystem and cached by path.
|
||||
TextureId loadTexture(std::string_view path); // .yapf
|
||||
// A picture made in memory (8-bit RGBA, sRGB), cached under `name` like a loaded file, so
|
||||
// models whose material names it use it; made again in place if the name's taken. (Fry3DX:
|
||||
// reference images.)
|
||||
TextureId createTexture(std::string_view name, uint32_t w, uint32_t h, const uint8_t* rgba);
|
||||
// Hot reload: textures and models whose files changed since they were loaded are loaded
|
||||
// again in place (same ids). Returns their paths.
|
||||
std::vector<std::string> reloadChanged();
|
||||
ModelId loadModel(std::string_view path); // .kmdl
|
||||
// A model built in memory (e.g. map geometry), cached under `name`.
|
||||
ModelId createModel(std::string_view name, const KmdlModel& model);
|
||||
// Replaces (or creates) the model cached under `name`; the old GPU buffers are freed
|
||||
// once no frame in flight can still use them. For meshes that change (breaking glass).
|
||||
ModelId updateModel(std::string_view name, const KmdlModel& model);
|
||||
glm::vec3 modelMins(ModelId m) const { return m.valid() ? models_[m.index].mins : glm::vec3(0); }
|
||||
glm::vec3 modelMaxs(ModelId m) const { return m.valid() ? models_[m.index].maxs : glm::vec3(0); }
|
||||
|
||||
// A loaded texture for ImGui::Image (ImTextureID); 0 if it isn't one.
|
||||
uint64_t uiTexture(TextureId texture) { return backend_ ? backend_->uiTexture(texture) : 0; }
|
||||
// Pixel size of a loaded texture.
|
||||
glm::ivec2 textureSize(TextureId texture) const {
|
||||
const auto it = textureSizes_.find(texture);
|
||||
return it == textureSizes_.end() ? glm::ivec2(0) : it->second;
|
||||
}
|
||||
|
||||
void setCamera(const Camera& cam) { camera_ = cam; }
|
||||
const Camera& camera() const { return camera_; }
|
||||
// Volumetric smoke this frame (SmokeField::bake over kSmokeDims cells from `corner`); `clock`
|
||||
// turns its billows. Nothing set: none.
|
||||
void setSmoke(const glm::vec3& corner, std::vector<uint16_t> cells, float clock) {
|
||||
packet_.smoke = std::move(cells);
|
||||
packet_.smokeOrigin = glm::vec4(corner, clock);
|
||||
}
|
||||
// The camera's view-projection (clip space: Vulkan's, Y down), for placing 2D overlays on the world.
|
||||
glm::mat4 cameraViewProjection(float aspect) const;
|
||||
void drawBox(const glm::vec3& mins, const glm::vec3& maxs, const glm::vec4& color, TextureId texture = kWhiteTexture);
|
||||
// A particle: a camera-facing square `size` across, unlit (color can be HDR-bright).
|
||||
// additive: light added on (fire, sparks, flashes); else blended (smoke, dust).
|
||||
void drawSprite(const glm::vec3& at, float size, const glm::vec4& color, TextureId texture, float rotation = 0.0f, bool additive = true);
|
||||
// A sprite stretched from `from` to `to`, `width` across, turned to face the eye as much as
|
||||
// it can (rain).
|
||||
void drawStreak(const glm::vec3& from, const glm::vec3& to, float width, const glm::vec4& color, TextureId texture, bool additive = false);
|
||||
// skin: one matrix per joint (anim::skinMatrices) for skinned models; ignored otherwise.
|
||||
// lighting: probe light for moving things (nullptr = lightmap or simple sun shading).
|
||||
void drawModel(ModelId model, const glm::mat4& transform, const glm::vec4& tint = glm::vec4(1.0f),
|
||||
const std::vector<glm::mat4>* skin = nullptr, const ModelLighting* lighting = nullptr, bool castShadow = true,
|
||||
const glm::vec4* liquidPlane = nullptr, LiquidStyle liquid = LiquidStyle::None);
|
||||
// liquidPlane (world space: xyz normal, w distance): draws a closed mesh as liquid filling it
|
||||
// up to the plane — above it is cut away, and its back faces show as the flat surface.
|
||||
// Alpha below 1 (tint × material) makes a submesh translucent: sorted, blended, after opaque.
|
||||
// The map's sun; dir points towards it. realtime: the lightmap has no direct sun, so it is
|
||||
// drawn here for every surface with cascaded shadows (the world casts too). Otherwise
|
||||
// it only lights probe-lit models and their shadows darken the baked light.
|
||||
void setSun(bool enabled, const glm::vec3& dir, const glm::vec3& color, bool realtime = false);
|
||||
// The sky (a .ksky; "" = none, the flat r_skycolor) and the air: its brightness and height
|
||||
// fog (density per unit at baseZ, thinning out by `falloff` per unit up).
|
||||
struct Atmosphere {
|
||||
std::string sky;
|
||||
float skyIntensity = 0.35f;
|
||||
float fogDensity = 0.00005f, fogFalloff = 0.0005f, fogMax = 0.9f, fogBaseZ = 0.0f;
|
||||
};
|
||||
void setAtmosphere(const Atmosphere& a);
|
||||
// This frame's weather (FrameData::weather): x wet, y snow cover, z night, w lightning flash.
|
||||
// Per frame, like draws: not set, it's clear.
|
||||
void setWeather(const glm::vec4& w, float cloud = 0.0f) { weather_ = w, cloud_ = cloud; }
|
||||
// The game's clock (seconds): water's swells move with it, so floating things match them.
|
||||
void setClock(double t) { clock_ = t, hasClock_ = true; }
|
||||
// Reflection probes (env_cubemap, baked by assetc as .ksky): glossy surfaces reflect the
|
||||
// nearest one. Call with the map's list every frame (it's only reloaded when it changes).
|
||||
void setProbes(const std::vector<std::pair<glm::vec3, std::string>>& probes);
|
||||
// Draws made while this is on only cast shadows (sun, dynamic lights) and aren't seen:
|
||||
// the first-person player's own body.
|
||||
void setShadowOnly(bool on) { shadowOnly_ = on; }
|
||||
// What's drawn next is the first-person view model (mirrors show your body, not these).
|
||||
void setViewModel(bool on) { viewModel_ = on; }
|
||||
// Models drawn next wear this picture instead of their own (0: their own): one
|
||||
// model, many pictures.
|
||||
void setTextureOverride(TextureId texture) { textureOverride_ = texture; }
|
||||
// A real-time light for this frame only (call every frame, like drawModel).
|
||||
void addLight(const DynamicLight& light) { lights_.push_back(light); }
|
||||
// Stats from the last frame, for r_lightinfo / tools.
|
||||
struct LightStats { uint32_t lights = 0, shadowed = 0, shadowViews = 0, indices = 0; };
|
||||
const LightStats& lightStats() const { return lightStats_; }
|
||||
// What the last frame drew (r_speeds): submesh draws in the view, culled away, into the sun's
|
||||
// cascades and lights' shadows.
|
||||
// Where the last frame's time went (r_profile): CPU (ms) and each GPU pass.
|
||||
struct Timings {
|
||||
float wait = 0, build = 0, record = 0; // waiting for a free frame (the GPU), building the draw list, recording + submitting
|
||||
std::vector<std::pair<const char*, float>> gpu;
|
||||
};
|
||||
const Timings& timings() const { return timings_; }
|
||||
struct DrawStats { uint32_t drawn = 0, culled = 0, occluded = 0, translucent = 0, sunShadow = 0, lightShadow = 0, triangles = 0; };
|
||||
const DrawStats& drawStats() const { return drawStats_; }
|
||||
void drawFrame();
|
||||
// Saves the next presented frame (UI included) as a PNG.
|
||||
void requestScreenshot(std::string path) { screenshotPath_ = std::move(path); }
|
||||
void onResize() {
|
||||
if (backend_) backend_->onResize();
|
||||
}
|
||||
|
||||
private:
|
||||
struct Submesh {
|
||||
uint32_t firstIndex, indexCount;
|
||||
TextureId texture, lightmap;
|
||||
glm::vec4 color;
|
||||
TextureId material = 0; // "<texture>_n": relief (tangent-space normal) + gloss in alpha; 0 = none
|
||||
TextureId skyVisibility = 0; // "<lightmap>_sky": how much sky each luxel sees; 0 = none
|
||||
glm::vec3 lo{0.0f}, hi{0.0f}; // model-space bounds, for culling
|
||||
bool mirror = false; // its texture's a mirror ("mirror" in its path): shows what's in front
|
||||
glm::vec4 plane{0.0f}; // a mirror's plane (model space): normal out of it, distance
|
||||
};
|
||||
struct Model {
|
||||
std::vector<Submesh> submeshes;
|
||||
glm::vec3 mins{0.0f}, maxs{0.0f};
|
||||
bool skinned = false;
|
||||
};
|
||||
struct ModelDraw {
|
||||
uint32_t model;
|
||||
glm::mat4 transform;
|
||||
glm::vec4 tint;
|
||||
uint32_t firstJoint = UINT32_MAX; // into the packet's joints, skinned draws only
|
||||
uint32_t drawIndex = 0; // into the packet's draws
|
||||
bool castShadow = true; // sun shadow map (the world is baked, so it doesn't)
|
||||
bool shadowOnly = false; // not drawn in the view
|
||||
bool liquid = false; // sorted just behind what holds it (liquid in a glass bottle)
|
||||
bool viewModel = false; // your hands and tool in first person (not seen in mirrors)
|
||||
TextureId texture = 0; // worn instead of the model's own pictures (0: its own)
|
||||
glm::vec3 boundsMin, boundsMax; // world space, for culling shadow casters per light
|
||||
};
|
||||
|
||||
Model describeModel(const KmdlModel& kmdl);
|
||||
TextureId newTexture(uint32_t w, uint32_t h, bool srgb, const std::vector<std::vector<uint8_t>>& mips, TexFormat format = TexFormat::RGBA8);
|
||||
void buildPacket(uint32_t width, uint32_t height);
|
||||
void clearFrame();
|
||||
|
||||
std::unique_ptr<RenderBackend> backend_;
|
||||
TextureId nextTexture_ = kFirstTextureSlot;
|
||||
std::unordered_map<std::string, TextureId> textureCache_;
|
||||
std::unordered_map<TextureId, glm::ivec2> textureSizes_;
|
||||
std::vector<Model> models_;
|
||||
std::unordered_map<std::string, ModelId> modelCache_;
|
||||
std::unordered_map<std::string, int64_t> fileTimes_; // loaded files' times (hot reload)
|
||||
bool decodeTexture(const std::string& path, uint32_t& w, uint32_t& h, bool& srgb, std::vector<std::vector<uint8_t>>& mips, TexFormat& format);
|
||||
|
||||
DrawStats drawStats_;
|
||||
// TAA: last frame's camera.
|
||||
glm::mat4 taaPrevViewProj_{1.0f};
|
||||
glm::vec3 taaPrevOrigin_{0.0f};
|
||||
uint32_t taaPrevW_ = 0, taaPrevH_ = 0, taaFrame_ = 0;
|
||||
bool taaPrevValid_ = false;
|
||||
// Occlusion (hierarchical Z): the newest grid back from the GPU as a max-depth pyramid,
|
||||
// with the camera it was drawn from. Frames are tagged; their cameras are remembered until
|
||||
// their grid returns.
|
||||
struct HzbFrame { glm::mat4 viewProj; glm::vec3 origin, forward; };
|
||||
std::vector<std::vector<float>> hzbLevels_; // level 0 = kHzbWidth × kHzbHeight
|
||||
HzbFrame hzbFrame_{};
|
||||
struct { bool valid = false; glm::mat4 viewProj{1.0f}; glm::vec3 origin{0.0f}, forward{1, 0, 0}; } lastCull_; // GPU culling: last frame's camera
|
||||
bool hzbValid_ = false;
|
||||
uint64_t hzbTag_ = 0;
|
||||
std::vector<std::pair<uint64_t, HzbFrame>> hzbPending_;
|
||||
void takeOcclusion();
|
||||
bool occluded(const glm::mat4& transform, const glm::vec3& lo, const glm::vec3& hi) const;
|
||||
Camera camera_;
|
||||
bool sunEnabled_ = false, sunRealtime_ = false;
|
||||
struct Sky {
|
||||
TextureId texture = 0;
|
||||
uint32_t levels = 0;
|
||||
glm::vec3 sunDir{0, 0, 1};
|
||||
glm::vec3 sh[9]{};
|
||||
};
|
||||
std::unordered_map<std::string, Sky> skies_;
|
||||
const Sky* loadSky(const std::string& path);
|
||||
struct Probe {
|
||||
glm::vec3 at;
|
||||
TextureId texture;
|
||||
float top;
|
||||
};
|
||||
std::vector<Probe> probes_;
|
||||
bool adaptOn_ = false;
|
||||
float dynScale_ = 1.0f; // dynamic resolution's current scale
|
||||
double dynFrame_ = 0.016; // smoothed frame time
|
||||
std::vector<std::pair<glm::vec3, std::string>> probeList_;
|
||||
Sky sky_;
|
||||
Atmosphere atmosphere_;
|
||||
glm::vec3 sunDir_{0, 0, 1}, sunColor_{1.0f};
|
||||
std::vector<ModelDraw> modelDraws_;
|
||||
std::vector<DynamicLight> lights_;
|
||||
ClusterGrid clusters_;
|
||||
LightStats lightStats_;
|
||||
Timings timings_;
|
||||
std::string screenshotPath_;
|
||||
bool shadowOnly_ = false, viewModel_ = false;
|
||||
TextureId textureOverride_ = 0;
|
||||
glm::mat4 mirrorReflect_{1.0f}; // this frame's mirror: its reflection, and its pass's cut plane
|
||||
glm::vec4 mirrorClip_{0.0f};
|
||||
glm::vec4 weather_{0.0f};
|
||||
float cloud_ = 0.0f;
|
||||
double clock_ = 0.0;
|
||||
bool hasClock_ = false;
|
||||
|
||||
FramePacket packet_; // reused every frame
|
||||
std::vector<MeshDraw> sunCasters_[4]; // per cascade, culled to it
|
||||
std::vector<std::vector<MeshDraw>> atlasCasters_;
|
||||
};
|
||||
|
||||
} // namespace keel
|
||||
Loading…
Reference in a new issue