From 2ab70a92ae39cebc6166ef15545ebcbd31a31c38 Mon Sep 17 00:00:00 2001
From: Daniel Karbach <daniel.karbach@localhorst.tv>
Date: Tue, 28 Nov 2017 21:39:09 +0100
Subject: [PATCH] creature skin overhaul

---
 assets                        |  2 +-
 src/app/app.cpp               | 15 +++++-------
 src/app/states.cpp            |  2 ++
 src/creature/Creature.hpp     | 13 ++++++++++
 src/creature/Genome.hpp       |  5 +++-
 src/creature/creature.cpp     | 45 ++++++++++++++++++++++++++++-------
 src/graphics/CreatureSkin.hpp |  6 ++++-
 src/graphics/shader.cpp       | 19 ++++++++++++---
 src/math/glm.hpp              |  4 ++--
 src/world/world.cpp           | 20 ++++++++--------
 10 files changed, 96 insertions(+), 35 deletions(-)

diff --git a/assets b/assets
index 51cf5cf..3c715b3 160000
--- a/assets
+++ b/assets
@@ -1 +1 @@
-Subproject commit 51cf5cf9b1bda806e44dd77fb31041f90d06ce2b
+Subproject commit 3c715b39e3aece21fc1f0fd6042611e79bfbeb44
diff --git a/src/app/app.cpp b/src/app/app.cpp
index f2d0e59..a732ca6 100644
--- a/src/app/app.cpp
+++ b/src/app/app.cpp
@@ -216,15 +216,12 @@ Assets::Assets()
 
 	textures.skins.Bind();
 	textures.skins.Reserve(256, 256, 9, format);
-	LoadSkinTexture("1", textures.skins, 0);
-	LoadSkinTexture("2", textures.skins, 1);
-	LoadSkinTexture("3", textures.skins, 2);
-	LoadSkinTexture("4", textures.skins, 3);
-	LoadSkinTexture("5", textures.skins, 4);
-	LoadSkinTexture("6", textures.skins, 5);
-	LoadSkinTexture("7", textures.skins, 6);
-	LoadSkinTexture("8", textures.skins, 7);
-	LoadSkinTexture("9", textures.skins, 8);
+	LoadSkinTexture("plain", textures.skins, 0);
+	LoadSkinTexture("stripes", textures.skins, 1);
+	LoadSkinTexture("dots", textures.skins, 2);
+	LoadSkinTexture("lines", textures.skins, 3);
+	LoadSkinTexture("spots", textures.skins, 4);
+	LoadSkinTexture("circles", textures.skins, 5);
 	textures.skins.FilterTrilinear();
 }
 
diff --git a/src/app/states.cpp b/src/app/states.cpp
index d1e092e..b1a20db 100644
--- a/src/app/states.cpp
+++ b/src/app/states.cpp
@@ -135,6 +135,8 @@ void MasterState::OnRender(graphics::Viewport &viewport) {
 	// TODO: extend to nearby bodies as well
 	for (auto c : cam.Reference().Creatures()) {
 		assets.shaders.creature_skin.SetM(cam.Model(cam.Reference()) * glm::mat4(c->LocalTransform()));
+		assets.shaders.creature_skin.SetBaseColor(c->BaseColor());
+		assets.shaders.creature_skin.SetHighlightColor(c->HighlightColor());
 		c->Draw(viewport);
 	}
 
diff --git a/src/creature/Creature.hpp b/src/creature/Creature.hpp
index f443e48..f36a2db 100644
--- a/src/creature/Creature.hpp
+++ b/src/creature/Creature.hpp
@@ -57,6 +57,15 @@ public:
 	Genome::Properties<double> &GetProperties() noexcept { return properties; }
 	const Genome::Properties<double> &GetProperties() const noexcept { return properties; }
 
+	const Genome::PropertySet<double> &CurProps() const noexcept { return properties.props[cur_prop]; }
+	const Genome::PropertySet<double> &NextProps() const noexcept { return properties.props[cur_prop + 1]; }
+
+	void BaseColor(const glm::dvec3 &c) noexcept { base_color = c; }
+	const glm::dvec3 &BaseColor() const noexcept { return base_color; }
+
+	void HighlightColor(const glm::dvec3 &c) noexcept { highlight_color = c; }
+	glm::dvec4 HighlightColor() const noexcept;
+
 	void Mass(double m) noexcept { mass = m; size = std::cbrt(mass / density); }
 	double Mass() const noexcept { return mass; }
 	void Grow(double amount) noexcept;
@@ -66,6 +75,7 @@ public:
 
 	double Size() const noexcept;
 	double Age() const noexcept;
+	double AgeLerp(double from, double to) const noexcept;
 	// change of giving birth per tick
 	double Fertility() const noexcept;
 
@@ -110,6 +120,9 @@ private:
 	Genome::Properties<double> properties;
 	int cur_prop;
 
+	glm::dvec3 base_color;
+	glm::dvec3 highlight_color;
+
 	double mass;
 	double density;
 	double size;
diff --git a/src/creature/Genome.hpp b/src/creature/Genome.hpp
index a7be0e1..b14efc1 100644
--- a/src/creature/Genome.hpp
+++ b/src/creature/Genome.hpp
@@ -3,6 +3,7 @@
 
 #include "../math/Distribution.hpp"
 #include "../math/GaloisLFSR.hpp"
+#include "../math/glm.hpp"
 
 #include <vector>
 
@@ -22,6 +23,7 @@ struct Genome {
 		T age;
 		T mass;
 		T fertility;
+		T highlight;
 	};
 	template<class T>
 	struct Properties {
@@ -70,7 +72,8 @@ struct Genome {
 		return {
 			p.age.FakeNormal(rand.SNorm()),
 			p.mass.FakeNormal(rand.SNorm()),
-			p.fertility.FakeNormal(rand.SNorm())
+			p.fertility.FakeNormal(rand.SNorm()),
+			glm::clamp(p.highlight.FakeNormal(rand.SNorm()), 0.0, 1.0)
 		};
 	}
 
diff --git a/src/creature/creature.cpp b/src/creature/creature.cpp
index 1196a3c..92fa367 100644
--- a/src/creature/creature.cpp
+++ b/src/creature/creature.cpp
@@ -33,6 +33,8 @@ Creature::Creature(world::Simulation &sim)
 , genome()
 , properties()
 , cur_prop(0)
+, base_color(1.0)
+, highlight_color(0.0)
 , mass(1.0)
 , density(1.0)
 , size(1.0)
@@ -51,8 +53,13 @@ Creature::Creature(world::Simulation &sim)
 Creature::~Creature() {
 }
 
+glm::dvec4 Creature::HighlightColor() const noexcept {
+	return glm::dvec4(highlight_color, AgeLerp(CurProps().highlight, NextProps().highlight));
+}
+
 void Creature::Grow(double amount) noexcept {
-	Mass(std::min(properties.props[cur_prop].mass, mass + amount));
+	const double max_mass = AgeLerp(CurProps().mass, NextProps().mass);
+	Mass(std::min(max_mass, mass + amount));
 }
 
 void Creature::Hurt(double dt) noexcept {
@@ -82,9 +89,12 @@ double Creature::Age() const noexcept {
 	return sim.Time() - birth;
 }
 
+double Creature::AgeLerp(double from, double to) const noexcept {
+	return glm::mix(from, to, glm::smoothstep(CurProps().age, NextProps().age, Age()));
+}
+
 double Creature::Fertility() const noexcept {
-	// TODO: lerp based on age?
-	return properties.props[cur_prop].fertility / 3600.0;
+	return AgeLerp(CurProps().fertility, NextProps().fertility) / 3600.0;
 }
 
 void Creature::AddGoal(std::unique_ptr<Goal> &&g) {
@@ -103,11 +113,12 @@ bool GoalCompare(const std::unique_ptr<Goal> &a, const std::unique_ptr<Goal> &b)
 
 void Creature::Tick(double dt) {
 	if (cur_prop < 5 && Age() > properties.props[cur_prop + 1].age) {
-		++cur_prop;
-		if (cur_prop == 5) {
+		if (cur_prop == 4) {
 			std::cout << "[" << int(sim.Time()) << "s] "
 				<< name << " died of old age" << std::endl;
 			Die();
+		} else {
+			++cur_prop;
 		}
 	}
 
@@ -301,26 +312,32 @@ void Spawn(Creature &c, world::Planet &p) {
 	genome.properties.Birth().age = { 0.0, 0.0 };
 	genome.properties.Birth().mass = { 0.5, 0.05 };
 	genome.properties.Birth().fertility = { 0.0, 0.0 };
+	genome.properties.Birth().highlight = { 0.0, 0.0 };
 
 	genome.properties.Child().age = { 30.0, 1.0 };
 	genome.properties.Child().mass = { 0.7, 0.05 };
 	genome.properties.Child().fertility = { 0.0, 0.0 };
+	genome.properties.Child().highlight = { 0.2, 0.05 };
 
 	genome.properties.Youth().age = { 60.0, 5.0 };
 	genome.properties.Youth().mass = { 0.9, 0.1 };
 	genome.properties.Youth().fertility = { 0.5, 0.03 };
+	genome.properties.Youth().highlight = { 0.9, 0.1 };
 
 	genome.properties.Adult().age = { 120.0, 10.0 };
 	genome.properties.Adult().mass = { 1.2, 0.1 };
 	genome.properties.Adult().fertility = { 0.4, 0.01 };
+	genome.properties.Adult().highlight = { 0.7, 0.1 };
 
 	genome.properties.Elder().age = { 360.0, 30.0 };
 	genome.properties.Elder().mass = { 1.0, 0.05 };
 	genome.properties.Elder().fertility = { 0.1, 0.01 };
+	genome.properties.Elder().highlight = { 0.6, 0.1 };
 
 	genome.properties.Death().age = { 480.0, 60.0 };
 	genome.properties.Death().mass = { 0.9, 0.05 };
 	genome.properties.Death().fertility = { 0.0, 0.0 };
+	genome.properties.Death().highlight = { 0.5, 0.1 };
 
 	glm::dvec3 color_avg(0.0);
 	double color_divisor = 0.0;
@@ -333,7 +350,7 @@ void Spawn(Creature &c, world::Planet &p) {
 			{ 0.1,  0.0005 },  // penalty
 			{ 0.0,  0.0 },     // growth
 		});
-		color_avg += c.GetSimulation().Resources()[p.Atmosphere()].base_color;
+		color_avg += c.GetSimulation().Resources()[p.Atmosphere()].base_color * 0.1;
 		color_divisor += 0.1;
 	}
 	if (liquid > -1) {
@@ -344,7 +361,7 @@ void Spawn(Creature &c, world::Planet &p) {
 			{ 0.01, 0.002 }, // penalty
 			{ 0.1, 0.0 },   // growth
 		});
-		color_avg += c.GetSimulation().Resources()[liquid].base_color;
+		color_avg += c.GetSimulation().Resources()[liquid].base_color * 0.5;
 		color_divisor += 0.5;
 	}
 	if (solid > -1) {
@@ -405,6 +422,19 @@ void Genome::Configure(Creature &c) const {
 		c.AddNeed(std::move(need));
 	}
 
+	glm::dvec3 base_color(
+		std::fmod(base_hue.FakeNormal(random.SNorm()) + 1.0, 1.0),
+		glm::clamp(base_saturation.FakeNormal(random.SNorm()), 0.0, 1.0),
+		glm::clamp(base_lightness.FakeNormal(random.SNorm()), 0.0, 1.0)
+	);
+	glm::dvec3 highlight_color(
+		std::fmod(base_color.x + 0.5, 1.0),
+		1.0 - base_color.y,
+		1.0 - base_color.z
+	);
+	c.BaseColor(hsl2rgb(base_color));
+	c.HighlightColor(hsl2rgb(highlight_color));
+
 	c.Mass(c.GetProperties().props[0].mass);
 	c.Density(mass / volume);
 	c.GetSteering().MaxAcceleration(1.4);
@@ -416,7 +446,6 @@ void Genome::Configure(Creature &c) const {
 void Split(Creature &c) {
 	Creature *a = new Creature(c.GetSimulation());
 	const Situation &s = c.GetSituation();
-	// TODO: generate names
 	a->Name(c.GetSimulation().Assets().name.Sequential());
 	// TODO: mutate
 	c.GetGenome().Configure(*a);
diff --git a/src/graphics/CreatureSkin.hpp b/src/graphics/CreatureSkin.hpp
index c24a746..b61c108 100644
--- a/src/graphics/CreatureSkin.hpp
+++ b/src/graphics/CreatureSkin.hpp
@@ -31,6 +31,8 @@ public:
 	void SetV(const glm::mat4 &v) noexcept;
 	void SetVP(const glm::mat4 &v, const glm::mat4 &p) noexcept;
 	void SetMVP(const glm::mat4 &m, const glm::mat4 &v, const glm::mat4 &p) noexcept;
+	void SetBaseColor(const glm::vec3 &) noexcept;
+	void SetHighlightColor(const glm::vec4 &) noexcept;
 	void SetTexture(ArrayTexture &) noexcept;
 	void SetLight(int n, const glm::vec3 &pos, const glm::vec3 &color, float strength) noexcept;
 	void SetNumLights(int n) noexcept;
@@ -55,8 +57,10 @@ private:
 	GLuint m_handle;
 	GLuint mv_handle;
 	GLuint mvp_handle;
-	GLuint sampler_handle;
 
+	GLuint base_color_handle;
+	GLuint highlight_color_handle;
+	GLuint sampler_handle;
 	GLuint num_lights_handle;
 	GLuint light_handle[MAX_LIGHTS * 3];
 
diff --git a/src/graphics/shader.cpp b/src/graphics/shader.cpp
index 6a08bb5..0890dfd 100644
--- a/src/graphics/shader.cpp
+++ b/src/graphics/shader.cpp
@@ -546,6 +546,8 @@ CreatureSkin::CreatureSkin()
 		"in vec3 frag_tex_uv;\n"
 		"in vec3 normal;\n"
 
+		"uniform vec3 base_color;\n"
+		"uniform vec4 highlight_color;\n"
 		"uniform sampler2DArray tex_sampler;\n"
 		"uniform int num_lights;\n"
 		"uniform LightSource light[8];\n"
@@ -553,14 +555,15 @@ CreatureSkin::CreatureSkin()
 		"out vec3 color;\n"
 
 		"void main() {\n"
-			"vec3 tex_color = texture(tex_sampler, frag_tex_uv).rgb;\n"
-			"vec3 total_light = tex_color * vec3(0.1, 0.1, 0.1);\n"
+			"vec4 tex_color = texture(tex_sampler, frag_tex_uv);\n"
+			"vec3 mat_color = mix(base_color, highlight_color.rgb, tex_color.r * tex_color.a * highlight_color.a);\n"
+			"vec3 total_light = mat_color * vec3(0.1, 0.1, 0.1);\n"
 			"for (int i = 0; i < num_lights; ++i) {\n"
 				"vec3 to_light = light[i].position - vtx_viewspace;\n"
 				"float distance = length(to_light) + length(vtx_viewspace);\n"
 				"vec3 light_dir = normalize(to_light);\n"
 				"float attenuation = light[i].strength / (distance * distance);\n"
-				"vec3 diffuse = attenuation * max(0.0, dot(normal, light_dir)) * light[i].color * tex_color;\n"
+				"vec3 diffuse = attenuation * max(0.0, dot(normal, light_dir)) * light[i].color * mat_color;\n"
 				"vec3 view_dir = vec3(0.0, 0.0, 1.0);\n"
 				"vec3 specular = vec3(0.0, 0.0, 0.0);\n"
 				"if (dot(normal, light_dir) >= 0.0) {\n"
@@ -579,6 +582,8 @@ CreatureSkin::CreatureSkin()
 	m_handle = prog.UniformLocation("M");
 	mv_handle = prog.UniformLocation("MV");
 	mvp_handle = prog.UniformLocation("MVP");
+	base_color_handle = prog.UniformLocation("base_color");
+	highlight_color_handle = prog.UniformLocation("highlight_color");
 	sampler_handle = prog.UniformLocation("tex_sampler");
 	num_lights_handle = prog.UniformLocation("num_lights");
 	for (int i = 0; i < MAX_LIGHTS; ++i) {
@@ -636,6 +641,14 @@ void CreatureSkin::SetMVP(const glm::mat4 &mm, const glm::mat4 &vv, const glm::m
 	prog.Uniform(mvp_handle, mvp);
 }
 
+void CreatureSkin::SetBaseColor(const glm::vec3 &c) noexcept {
+	prog.Uniform(base_color_handle, c);
+}
+
+void CreatureSkin::SetHighlightColor(const glm::vec4 &c) noexcept {
+	prog.Uniform(highlight_color_handle, c);
+}
+
 void CreatureSkin::SetTexture(ArrayTexture &tex) noexcept {
 	glActiveTexture(GL_TEXTURE0);
 	tex.Bind();
diff --git a/src/math/glm.hpp b/src/math/glm.hpp
index 46843db..80126aa 100644
--- a/src/math/glm.hpp
+++ b/src/math/glm.hpp
@@ -55,8 +55,8 @@ template<class T>
 inline T hsl2rgb(const T &hsl) {
 	using Vec3 = glm::tvec3<typename T::value_type>;
 	using Vec4 = glm::tvec4<typename T::value_type>;
-	const Vec4 K(0.0, -1.0/3.0, 2.0/3.0, -1.0);
-	const Vec3 p(glm::abs(glm::fract(Vec3(hsl.h) + Vec3(K)) * 6.0 - Vec3(K.w)));
+	const Vec4 K(1.0, 2.0/3.0, 1.0/3.0, 3.0);
+	const Vec3 p(glm::abs(glm::fract(Vec3(hsl.x) + Vec3(K)) * 6.0 - Vec3(K.w)));
 	T rgb = hsl.z * glm::mix(Vec3(K.x), glm::clamp(p - Vec3(K.x), 0.0, 1.0), hsl.y);
 	return rgb;
 }
diff --git a/src/world/world.cpp b/src/world/world.cpp
index eee1283..dd7d28d 100644
--- a/src/world/world.cpp
+++ b/src/world/world.cpp
@@ -347,34 +347,34 @@ void Planet::BuildVAO(const Set<TileType> &ts) {
 			for (int y = 0; y < sidelength; ++y) {
 				for (int x = 0; x < sidelength; ++x, ++index) {
 					float tex = ts[TileAt(surface, x, y).type].texture;
-					const float tex_u_begin = surface < 3 ? 1.0f : 0.0f;
-					const float tex_u_end = surface < 3 ? 0.0f : 1.0f;
+					const float tex_v_begin = surface < 3 ? 1.0f : 0.0f;
+					const float tex_v_end = surface < 3 ? 0.0f : 1.0f;
 					attrib[4 * index + 0].position[(surface + 0) % 3] = x + 0 - offset;
 					attrib[4 * index + 0].position[(surface + 1) % 3] = y + 0 - offset;
 					attrib[4 * index + 0].position[(surface + 2) % 3] = surface < 3 ? offset : -offset;
-					attrib[4 * index + 0].tex_coord[0] = tex_u_begin;
-					attrib[4 * index + 0].tex_coord[1] = 1.0f;
+					attrib[4 * index + 0].tex_coord[0] = 0.0f;
+					attrib[4 * index + 0].tex_coord[1] = tex_v_begin;
 					attrib[4 * index + 0].tex_coord[2] = tex;
 
 					attrib[4 * index + 1].position[(surface + 0) % 3] = x + 0 - offset;
 					attrib[4 * index + 1].position[(surface + 1) % 3] = y + 1 - offset;
 					attrib[4 * index + 1].position[(surface + 2) % 3] = surface < 3 ? offset : -offset;
-					attrib[4 * index + 1].tex_coord[0] = tex_u_end;
-					attrib[4 * index + 1].tex_coord[1] = 1.0f;
+					attrib[4 * index + 1].tex_coord[0] = 0.0f;
+					attrib[4 * index + 1].tex_coord[1] = tex_v_end;
 					attrib[4 * index + 1].tex_coord[2] = tex;
 
 					attrib[4 * index + 2].position[(surface + 0) % 3] = x + 1 - offset;
 					attrib[4 * index + 2].position[(surface + 1) % 3] = y + 0 - offset;
 					attrib[4 * index + 2].position[(surface + 2) % 3] = surface < 3 ? offset : -offset;
-					attrib[4 * index + 2].tex_coord[0] = tex_u_begin;
-					attrib[4 * index + 2].tex_coord[1] = 0.0f;
+					attrib[4 * index + 2].tex_coord[0] = 1.0f;
+					attrib[4 * index + 2].tex_coord[1] = tex_v_begin;
 					attrib[4 * index + 2].tex_coord[2] = tex;
 
 					attrib[4 * index + 3].position[(surface + 0) % 3] = x + 1 - offset;
 					attrib[4 * index + 3].position[(surface + 1) % 3] = y + 1 - offset;
 					attrib[4 * index + 3].position[(surface + 2) % 3] = surface < 3 ? offset : -offset;
-					attrib[4 * index + 3].tex_coord[0] = tex_u_end;
-					attrib[4 * index + 3].tex_coord[1] = 0.0f;
+					attrib[4 * index + 3].tex_coord[0] = 1.0f;
+					attrib[4 * index + 3].tex_coord[1] = tex_v_end;
 					attrib[4 * index + 3].tex_coord[2] = tex;
 				}
 			}
-- 
2.39.5