Materials shown in the view: each object's metal and rough reach the shader (Solid and Rendered); a Material picker with sixteen to start from (gold, chrome, wood, ceramic...); o.material and fry.materials() in scripts

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Nexoniarz 2026-10-20 17:14:55 +02:00
commit e93ae44684
9 changed files with 101 additions and 5 deletions

View file

@ -4576,7 +4576,8 @@ private:
for (const Object& o : scene_.objects) {
if (!o.visible) continue;
const glm::vec4 tint = painted(o) ? glm::vec4(1.0f) : glm::vec4(glm::pow(o.color, glm::vec3(2.2f)), 1.0f); // (colours are picked as seen: sRGB)
r.drawModel(modelOf(o), toUnits * objectMatrix(o), tint, nullptr, &light, rendered);
const keel::Surface surface{o.metal, o.rough};
r.drawModel(modelOf(o), toUnits * objectMatrix(o), tint, nullptr, &light, rendered, nullptr, keel::LiquidStyle::None, &surface);
}
// Rendered: a ground to stand on and catch the shadows (not part of what you make).
if (rendered && look.ground) {
@ -5710,6 +5711,7 @@ private:
if (ImGui::Button("Save a picture (F12)", ImVec2(w, 0))) dialog(5);
if (Object* o = selected_.size() == 1 ? find(active_) : nullptr) {
ImGui::SeparatorText(o->name.c_str());
materialPicker(*o);
undoable([&] { return ImGui::ColorEdit3("Colour", &o->color.x); });
undoable([&] { return ImGui::SliderFloat("Metal", &o->metal, 0, 1); });
undoable([&] { return ImGui::SliderFloat("Rough", &o->rough, 0, 1); });
@ -5916,6 +5918,7 @@ private:
const glm::vec3 lo = boundsMin(shownMesh(*o)) * glm::abs(o->scale), hi = boundsMax(shownMesh(*o)) * glm::abs(o->scale);
ImGui::TextDisabled("%.3f x %.3f x %.3f m", hi.x - lo.x, hi.y - lo.y, hi.z - lo.z);
ImGui::SeparatorText("Look");
materialPicker(*o);
undoable([&] { return ImGui::ColorEdit3("Colour", &o->color.x); });
undoable([&] { return ImGui::SliderFloat("Metal", &o->metal, 0, 1); });
undoable([&] { return ImGui::SliderFloat("Rough", &o->rough, 0, 1); });
@ -5970,6 +5973,31 @@ private:
ImGui::End();
}
// A material to start from: sets colour, metal and rough in one go (each a little ball of it).
void materialPicker(Object& o) {
const MaterialPreset* now = nullptr;
for (const MaterialPreset& m : materialPresets())
if (std::abs(m.metal - o.metal) < 0.01f && std::abs(m.rough - o.rough) < 0.01f && (m.keepColor || glm::all(glm::lessThan(glm::abs(m.color - o.color), glm::vec3(0.01f))))) now = &m;
ImGui::SetNextItemWidth(ImGui::CalcItemWidth());
if (!ImGui::BeginCombo("Material", now ? now->name : "(its own)", ImGuiComboFlags_HeightLarge)) return;
for (const MaterialPreset& m : materialPresets()) {
const ImVec2 p = ImGui::GetCursorScreenPos();
const float h = ImGui::GetTextLineHeight();
ImGui::PushID(m.id);
if (ImGui::Selectable("##m", now == &m, 0, ImVec2(0, h))) pushUndo(), applyMaterial(o, m), touched(o.id);
ImGui::PopID();
// A ball: its colour, darker round the edge, a highlight as sharp as it's smooth.
const glm::vec3 c = m.keepColor ? o.color : m.color;
const ImVec2 mid(p.x + h * 0.5f, p.y + h * 0.5f);
ImDrawList* dl = ImGui::GetWindowDrawList();
dl->AddCircleFilled(mid, h * 0.45f, ImGui::ColorConvertFloat4ToU32(ImVec4(c.r * 0.55f, c.g * 0.55f, c.b * 0.55f, 1)));
dl->AddCircleFilled(ImVec2(mid.x - h * 0.05f, mid.y - h * 0.05f), h * 0.36f, ImGui::ColorConvertFloat4ToU32(ImVec4(c.r, c.g, c.b, 1)));
dl->AddCircleFilled(ImVec2(mid.x - h * 0.14f, mid.y - h * 0.14f), h * (0.06f + 0.1f * m.rough), IM_COL32(255, 255, 255, int(230 * (1.0f - m.rough * 0.8f))));
dl->AddText(ImVec2(p.x + h * 1.4f, p.y), ImGui::GetColorU32(ImGuiCol_Text), m.name);
}
ImGui::EndCombo();
}
void editProperties(Object& o, float w) {
ImGui::Text("Editing the shape of %s", o.name.c_str());
ImGui::TextDisabled("%zu points, %zu faces", o.mesh.verts.size(), o.mesh.faces.size());

View file

@ -32,6 +32,31 @@ glm::mat4 objectMatrix(const Object& o) {
return glm::translate(glm::mat4(1.0f), o.position) * glm::mat4_cast(o.rotation) * glm::scale(glm::mat4(1.0f), o.scale);
}
const std::vector<MaterialPreset>& materialPresets() {
static const std::vector<MaterialPreset> list = {
{"matte", "Matte (its colour)", {}, 0.0f, 0.9f, true}, {"satin", "Satin (its colour)", {}, 0.0f, 0.45f, true},
{"glossy", "Glossy (its colour)", {}, 0.0f, 0.12f, true}, {"plastic", "White plastic", {0.9f, 0.9f, 0.88f}, 0.0f, 0.35f},
{"rubber", "Black rubber", {0.08f, 0.08f, 0.08f}, 0.0f, 0.95f}, {"wood", "Wood", {0.62f, 0.42f, 0.26f}, 0.0f, 0.7f},
{"clay", "Clay", {0.72f, 0.4f, 0.28f}, 0.0f, 0.95f}, {"ceramic", "Ceramic", {0.95f, 0.95f, 0.93f}, 0.0f, 0.08f},
{"concrete", "Concrete", {0.6f, 0.6f, 0.58f}, 0.0f, 1.0f}, {"steel", "Brushed steel", {0.75f, 0.76f, 0.77f}, 1.0f, 0.4f},
{"chrome", "Chrome", {0.95f, 0.95f, 0.96f}, 1.0f, 0.04f}, {"aluminium", "Aluminium", {0.91f, 0.92f, 0.92f}, 1.0f, 0.3f},
{"gold", "Gold", {1.0f, 0.78f, 0.34f}, 1.0f, 0.18f}, {"copper", "Copper", {0.96f, 0.64f, 0.54f}, 1.0f, 0.25f},
{"brass", "Brass", {0.89f, 0.75f, 0.42f}, 1.0f, 0.3f}, {"iron", "Cast iron", {0.3f, 0.3f, 0.31f}, 1.0f, 0.75f},
};
return list;
}
const MaterialPreset* findMaterial(std::string_view id) {
for (const MaterialPreset& m : materialPresets())
if (id == m.id) return &m;
return nullptr;
}
void applyMaterial(Object& o, const MaterialPreset& m) {
if (!m.keepColor) o.color = m.color;
o.metal = m.metal, o.rough = m.rough;
}
Mesh finishedMesh(const Object& o, bool mirrorOnly) {
Mesh m = o.mesh;
for (int axis = 0; axis < 3; ++axis) {

View file

@ -11,6 +11,7 @@
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <vector>
namespace fry {
@ -128,6 +129,19 @@ struct Scene {
uint32_t nextId = 1;
};
// Materials to start from: a colour (unless `keepColor`: only how metal and rough it is) and
// how metal and rough. Shown in the view (Solid and Rendered) and exported with the object.
struct MaterialPreset {
const char* id; // as scripts name it
const char* name; // as shown
glm::vec3 color; // sRGB
float metal, rough;
bool keepColor = false;
};
const std::vector<MaterialPreset>& materialPresets();
const MaterialPreset* findMaterial(std::string_view id);
void applyMaterial(Object& o, const MaterialPreset& m);
glm::mat4 objectMatrix(const Object& o);
// A turn as degrees about X, then Y, then Z (what the Properties panel shows), and back.
glm::vec3 toEuler(const glm::quat& q);

View file

@ -202,6 +202,10 @@ bool setProperty(lua_State* L, Object& o, const std::string& k, int v) {
else if (k == "mirror") {
const std::string s = lower(luaL_checkstring(L, v));
o.mirror = (s.find('x') != std::string::npos ? 1 : 0) | (s.find('y') != std::string::npos ? 2 : 0) | (s.find('z') != std::string::npos ? 4 : 0);
} else if (k == "material") {
const MaterialPreset* m = findMaterial(lower(luaL_checkstring(L, v)));
if (!m) luaL_error(L, "there's no material called %s (fry.materials() lists them)", lua_tostring(L, v));
else applyMaterial(o, *m);
} else if (k == "round") o.round = std::max(0.0f, float(luaL_checknumber(L, v)));
else if (k == "round_steps") o.roundSteps = std::clamp(int(luaL_checkinteger(L, v)), 1, 12);
else if (k == "smooth_levels") o.smoothLevels = std::clamp(int(luaL_checkinteger(L, v)), 0, 4);
@ -640,6 +644,12 @@ int fryShapes(lua_State* L) {
}
return 1;
}
int fryMaterials(lua_State* L) { // fry.materials(): every material's name, for o.material = ...
lua_newtable(L);
int i = 0;
for (const MaterialPreset& m : materialPresets()) lua_pushstring(L, m.id), lua_rawseti(L, -2, ++i);
return 1;
}
int fryLook(lua_State* L) { // fry.look{sun_turn = 40, sky = false, ...}; returns the look as it is
Look& k = scene(L).look;
struct Num {
@ -696,7 +706,7 @@ int fryHelp(lua_State* L) {
const luaL_Reg kFry[] = {
{"add", fryAdd}, {"mesh", fryMesh}, {"lathe", fryLathe}, {"extrude", fryExtrude}, {"objects", fryObjects}, {"find", fryFind},
{"selected", frySelected}, {"select", frySelect}, {"clear", fryClear}, {"group", fryGroup}, {"shapes", fryShapes}, {"look", fryLook},
{"selected", frySelected}, {"select", frySelect}, {"clear", fryClear}, {"group", fryGroup}, {"shapes", fryShapes}, {"look", fryLook}, {"materials", fryMaterials},
{"vec", fryVec}, {"say", fryPrint}, {"frame", fryFrame}, {"help", fryHelp}, {nullptr, nullptr},
};
@ -781,6 +791,7 @@ const char* scriptHelp() {
fry.add(shape, {setting = value, property = value, ...}) a library shape: fry.add("cube", {width = 2, color = "#ff8000"})
fry.shapes() every shape: {id, name, settings}
fry.materials() every material's name, for o.material
fry.mesh(points, faces, name) your own: points {{x,y,z}, ...}, faces {{1,2,3,4}, ...} (counter-clockwise from outside)
fry.lathe(profile, sides, name) a profile of {out, up} points turned round the Z axis (bottles, vases, bowls)
fry.extrude(outline, height, name) an outline of {x, y} points on the ground, made that tall
@ -792,6 +803,7 @@ fry.frame() fry.say(text) print(...) fry.help()
An object (o):
o.name o.position o.turn o.size o.color o.metal o.rough o.smooth o.visible o.locked
o.material = "gold" sets colour, metal and rough ("matte", "satin", "glossy" keep the colour)
o.mirror ("x", "xy"...) o.round o.round_steps o.smooth_levels (finishing touches, not baked)
o.width, o.sides ... a library shape's own settings, changeable until its shape is changed by hand
o.id o.shape o.point_count o.face_count (to read)