From 7bb75960dbf9bfdee9ac865384aca81791b3da5c Mon Sep 17 00:00:00 2001
From: Daniel Karbach <daniel.karbach@localhorst.tv>
Date: Sat, 8 Aug 2015 23:13:45 +0200
Subject: [PATCH] textures

textures
textures
textures
textures
---
 TODO                                 |   2 +-
 assets                               |   2 +-
 src/ai/Spawner.cpp                   |   2 +-
 src/app/Assets.hpp                   |   5 +
 src/app/WorldState.cpp               |   2 +-
 src/app/app.cpp                      |  35 ++++++-
 src/graphics/ArrayTexture.hpp        |  51 ++++++++++
 src/graphics/BlockLighting.hpp       |   4 +
 src/graphics/DirectionalLighting.hpp |   4 +
 src/graphics/Format.hpp              |   7 +-
 src/graphics/Texture.hpp             |   4 +-
 src/graphics/render.cpp              | 145 ++++++++++++++++++++++++++-
 src/graphics/shader.cpp              |  51 +++++++---
 src/model/BlockModel.hpp             |   6 ++
 src/model/EntityModel.hpp            |   6 ++
 src/model/Shape.hpp                  |  38 ++++---
 src/model/model.cpp                  |   8 ++
 src/model/shape.cpp                  | 138 +++++++++++++++++++++----
 src/world/BlockType.hpp              |   1 +
 src/world/Entity.cpp                 |   4 +-
 src/world/Entity.hpp                 |   2 +-
 src/world/World.cpp                  |  39 ++++++-
 src/world/World.hpp                  |   6 +-
 src/world/block.cpp                  |   7 +-
 24 files changed, 496 insertions(+), 73 deletions(-)
 create mode 100644 src/graphics/ArrayTexture.hpp

diff --git a/TODO b/TODO
index 8cea292..f3c43c6 100644
--- a/TODO
+++ b/TODO
@@ -10,7 +10,7 @@ composite entity animations
 
 textures
 
-	do I need to say anything? :)
+	okay, now I need a better solution for the crosshair ^^
 
 font rendering
 
diff --git a/assets b/assets
index 96db33a..449410e 160000
--- a/assets
+++ b/assets
@@ -1 +1 @@
-Subproject commit 96db33a3047bf3f20f5b6d4464cf4a4ee238146d
+Subproject commit 449410ec8e8e4b4bd7878b99aef9dc77ed422ff1
diff --git a/src/ai/Spawner.cpp b/src/ai/Spawner.cpp
index 612c3cc..b0b9bcb 100644
--- a/src/ai/Spawner.cpp
+++ b/src/ai/Spawner.cpp
@@ -105,7 +105,7 @@ void Spawner::Spawn(const glm::ivec3 &chunk, const glm::vec3 &pos) {
 	e.Position(chunk, pos);
 	e.Bounds({ { -0.5f, -0.5f, -0.5f }, { 0.5f, 0.5f, 0.5f } });
 	e.WorldCollidable(true);
-	e.SetShape(world.BlockTypes()[1].shape, color);
+	e.SetShape(world.BlockTypes()[1].shape, color, 2);
 	e.AngularVelocity(rot);
 	Controller *ctrl;
 	if (rand() % 2) {
diff --git a/src/app/Assets.hpp b/src/app/Assets.hpp
index 4953ddb..6e1698a 100644
--- a/src/app/Assets.hpp
+++ b/src/app/Assets.hpp
@@ -6,8 +6,10 @@
 
 namespace blank {
 
+class ArrayTexture;
 class Font;
 class Sound;
+class Texture;
 
 class Assets {
 
@@ -16,10 +18,13 @@ public:
 
 	Font LoadFont(const std::string &name, int size) const;
 	Sound LoadSound(const std::string &name) const;
+	Texture LoadTexture(const std::string &name) const;
+	void LoadTexture(const std::string &name, ArrayTexture &, int layer) const;
 
 private:
 	std::string fonts;
 	std::string sounds;
+	std::string textures;
 
 };
 
diff --git a/src/app/WorldState.cpp b/src/app/WorldState.cpp
index 6ca9959..b686ab2 100644
--- a/src/app/WorldState.cpp
+++ b/src/app/WorldState.cpp
@@ -13,7 +13,7 @@ WorldState::WorldState(
 	const World::Config &wc
 )
 : env(env)
-, world(wc)
+, world(env.assets, wc)
 , spawner(world)
 , interface(ic, env, world) {
 
diff --git a/src/app/app.cpp b/src/app/app.cpp
index 420dee7..f3c9115 100644
--- a/src/app/app.cpp
+++ b/src/app/app.cpp
@@ -7,12 +7,15 @@
 
 #include "init.hpp"
 #include "../audio/Sound.hpp"
+#include "../graphics/ArrayTexture.hpp"
 #include "../graphics/Font.hpp"
+#include "../graphics/Texture.hpp"
 #include "../world/BlockType.hpp"
 #include "../world/Entity.hpp"
 
 #include <iostream>
 #include <stdexcept>
+#include <SDL_image.h>
 
 using std::string;
 
@@ -195,7 +198,8 @@ void StateControl::Commit(Application &app) {
 
 Assets::Assets(const string &base)
 : fonts(base + "fonts/")
-, sounds(base + "sounds/") {
+, sounds(base + "sounds/")
+, textures(base + "textures/") {
 
 }
 
@@ -209,6 +213,35 @@ Sound Assets::LoadSound(const string &name) const {
 	return Sound(full.c_str());
 }
 
+Texture Assets::LoadTexture(const string &name) const {
+	string full = textures + name + ".png";
+	Texture tex;
+	SDL_Surface *srf = IMG_Load(full.c_str());
+	if (!srf) {
+		throw SDLError("IMG_Load");
+	}
+	tex.Bind();
+	tex.Data(*srf);
+	SDL_FreeSurface(srf);
+	return tex;
+}
+
+void Assets::LoadTexture(const string &name, ArrayTexture &tex, int layer) const {
+	string full = textures + name + ".png";
+	SDL_Surface *srf = IMG_Load(full.c_str());
+	if (!srf) {
+		throw SDLError("IMG_Load");
+	}
+	tex.Bind();
+	try {
+		tex.Data(layer, *srf);
+	} catch (...) {
+		SDL_FreeSurface(srf);
+		throw;
+	}
+	SDL_FreeSurface(srf);
+}
+
 
 void FrameCounter::EnterFrame() noexcept {
 	last_enter = SDL_GetTicks();
diff --git a/src/graphics/ArrayTexture.hpp b/src/graphics/ArrayTexture.hpp
new file mode 100644
index 0000000..93b560e
--- /dev/null
+++ b/src/graphics/ArrayTexture.hpp
@@ -0,0 +1,51 @@
+#ifndef BLANK_GRAPHICS_ARRAYTEXTURE_HPP_
+#define BLANK_GRAPHICS_ARRAYTEXTURE_HPP_
+
+#include "Format.hpp"
+
+#include <GL/glew.h>
+
+struct SDL_Surface;
+
+
+namespace blank {
+
+class ArrayTexture {
+
+public:
+	ArrayTexture();
+	~ArrayTexture();
+
+	ArrayTexture(ArrayTexture &&) noexcept;
+	ArrayTexture &operator =(ArrayTexture &&) noexcept;
+
+	ArrayTexture(const ArrayTexture &) = delete;
+	ArrayTexture &operator =(const ArrayTexture &) = delete;
+
+public:
+	GLsizei Width() const noexcept { return width; }
+	GLsizei Height() const noexcept { return height; }
+	GLsizei Depth() const noexcept { return depth; }
+
+	void Bind() noexcept;
+
+	void Reserve(GLsizei w, GLsizei h, GLsizei d, const Format &) noexcept;
+	void Data(GLsizei l, const SDL_Surface &);
+	void Data(GLsizei l, const Format &, GLvoid *data) noexcept;
+
+	void FilterNearest() noexcept;
+	void FilterLinear() noexcept;
+	void FilterTrilinear() noexcept;
+
+private:
+	GLuint handle;
+
+	GLsizei width, height, depth;
+
+	Format format;
+
+};
+
+}
+
+#endif
diff --git a/src/graphics/BlockLighting.hpp b/src/graphics/BlockLighting.hpp
index 321cf79..9b3e8af 100644
--- a/src/graphics/BlockLighting.hpp
+++ b/src/graphics/BlockLighting.hpp
@@ -9,6 +9,8 @@
 
 namespace blank {
 
+class ArrayTexture;
+
 class BlockLighting {
 
 public:
@@ -16,6 +18,7 @@ public:
 
 	void Activate() noexcept;
 
+	void SetTexture(ArrayTexture &) noexcept;
 	void SetFogDensity(float) noexcept;
 
 	void SetM(const glm::mat4 &m) noexcept;
@@ -37,6 +40,7 @@ private:
 
 	GLuint mv_handle;
 	GLuint mvp_handle;
+	GLuint sampler_handle;
 	GLuint light_direction_handle;
 	GLuint light_color_handle;
 	GLuint fog_density_handle;
diff --git a/src/graphics/DirectionalLighting.hpp b/src/graphics/DirectionalLighting.hpp
index e42d2cf..70492ef 100644
--- a/src/graphics/DirectionalLighting.hpp
+++ b/src/graphics/DirectionalLighting.hpp
@@ -9,6 +9,8 @@
 
 namespace blank {
 
+class ArrayTexture;
+
 class DirectionalLighting {
 
 public:
@@ -19,6 +21,7 @@ public:
 	void SetLightDirection(const glm::vec3 &) noexcept;
 	void SetLightColor(const glm::vec3 &) noexcept;
 
+	void SetTexture(ArrayTexture &) noexcept;
 	void SetFogDensity(float) noexcept;
 
 	void SetM(const glm::mat4 &m) noexcept;
@@ -41,6 +44,7 @@ private:
 	GLuint m_handle;
 	GLuint mv_handle;
 	GLuint mvp_handle;
+	GLuint sampler_handle;
 	GLuint light_direction_handle;
 	GLuint light_color_handle;
 	GLuint fog_density_handle;
diff --git a/src/graphics/Format.hpp b/src/graphics/Format.hpp
index 26ebfe0..5762aab 100644
--- a/src/graphics/Format.hpp
+++ b/src/graphics/Format.hpp
@@ -13,7 +13,12 @@ struct Format {
 	GLenum type;
 	GLenum internal;
 
-	void ReadPixelFormat(const SDL_PixelFormat &);
+	SDL_PixelFormat sdl_format;
+
+	Format();
+	explicit Format(const SDL_PixelFormat &);
+
+	bool Compatible(const Format &other) const noexcept;
 
 };
 
diff --git a/src/graphics/Texture.hpp b/src/graphics/Texture.hpp
index 240e145..9e24a8d 100644
--- a/src/graphics/Texture.hpp
+++ b/src/graphics/Texture.hpp
@@ -1,8 +1,6 @@
 #ifndef BLANK_GRAPHICS_TEXTURE_HPP_
 #define BLANK_GRAPHICS_TEXTURE_HPP_
 
-#include "Format.hpp"
-
 #include <GL/glew.h>
 
 struct SDL_Surface;
@@ -10,6 +8,8 @@ struct SDL_Surface;
 
 namespace blank {
 
+struct Format;
+
 class Texture {
 
 public:
diff --git a/src/graphics/render.cpp b/src/graphics/render.cpp
index be59d23..470cb19 100644
--- a/src/graphics/render.cpp
+++ b/src/graphics/render.cpp
@@ -1,9 +1,11 @@
-#include "BlendedSprite.hpp"
+#include "ArrayTexture.hpp"
 #include "Font.hpp"
 #include "Format.hpp"
 #include "Texture.hpp"
 #include "Viewport.hpp"
 
+#include "../app/init.hpp"
+
 #include <algorithm>
 #include <cstring>
 #include <memory>
@@ -126,8 +128,32 @@ void Font::Render(const char *text, Texture &tex) const {
 	SDL_FreeSurface(srf);
 }
 
-
-void Format::ReadPixelFormat(const SDL_PixelFormat &fmt) {
+Format::Format()
+: format(GL_BGRA)
+, type(GL_UNSIGNED_INT_8_8_8_8_REV)
+, internal(GL_RGBA8) {
+	sdl_format.format = SDL_PIXELFORMAT_ARGB8888;
+	sdl_format.palette = nullptr;
+	sdl_format.BitsPerPixel = 32;
+	sdl_format.BytesPerPixel = 4;
+	sdl_format.Rmask = 0x00FF0000;
+	sdl_format.Gmask = 0x0000FF00;
+	sdl_format.Bmask = 0x000000FF;
+	sdl_format.Amask = 0xFF000000;
+	sdl_format.Rloss = 0;
+	sdl_format.Gloss = 0;
+	sdl_format.Bloss = 0;
+	sdl_format.Aloss = 0;
+	sdl_format.Rshift = 16;
+	sdl_format.Gshift = 8;
+	sdl_format.Bshift = 0;
+	sdl_format.Ashift = 24;
+	sdl_format.refcount = 1;
+	sdl_format.next = nullptr;
+}
+
+Format::Format(const SDL_PixelFormat &fmt)
+: sdl_format(fmt) {
 	if (fmt.BytesPerPixel == 4) {
 		if (fmt.Amask == 0xFF) {
 			if (fmt.Rmask == 0xFF00) {
@@ -156,6 +182,10 @@ void Format::ReadPixelFormat(const SDL_PixelFormat &fmt) {
 	}
 }
 
+bool Format::Compatible(const Format &other) const noexcept {
+	return format == other.format && type == other.type && internal == other.internal;
+}
+
 
 Texture::Texture()
 : handle(0)
@@ -197,8 +227,7 @@ namespace {
 }
 
 void Texture::Data(const SDL_Surface &srf, bool pad2) noexcept {
-	Format format;
-	format.ReadPixelFormat(*srf.format);
+	Format format(*srf.format);
 
 	if (!pad2 || (ispow2(srf.w) && ispow2(srf.h))) {
 		int align = UnpackAlignmentFromPitch(srf.pitch);
@@ -296,4 +325,110 @@ void Texture::UnpackRowLength(GLint i) noexcept {
 	glPixelStorei(GL_UNPACK_ROW_LENGTH, i);
 }
 
+
+ArrayTexture::ArrayTexture()
+: handle(0)
+, width(0)
+, height(0)
+, depth(0) {
+	glGenTextures(1, &handle);
+}
+
+ArrayTexture::~ArrayTexture() {
+	if (handle != 0) {
+		glDeleteTextures(1, &handle);
+	}
+}
+
+ArrayTexture::ArrayTexture(ArrayTexture &&other) noexcept
+: handle(other.handle) {
+	other.handle = 0;
+	width = other.width;
+	height = other.height;
+	depth = other.depth;
+}
+
+ArrayTexture &ArrayTexture::operator =(ArrayTexture &&other) noexcept {
+	std::swap(handle, other.handle);
+	width = other.width;
+	height = other.height;
+	depth = other.depth;
+	return *this;
+}
+
+
+void ArrayTexture::Bind() noexcept {
+	glBindTexture(GL_TEXTURE_2D_ARRAY, handle);
+}
+
+
+void ArrayTexture::Reserve(GLsizei w, GLsizei h, GLsizei d, const Format &f) noexcept {
+	glTexStorage3D(
+		GL_TEXTURE_2D_ARRAY, // which
+		1,                   // mipmap count
+		f.internal,          // format
+		w, h,                // dimensions
+		d                    // layer count
+	);
+	width = w;
+	height = h;
+	depth = d;
+	format = f;
+}
+
+void ArrayTexture::Data(GLsizei l, const SDL_Surface &srf) {
+	Format fmt(*srf.format);
+	if (format.Compatible(fmt)) {
+		Data(l, fmt, srf.pixels);
+	} else {
+		SDL_Surface *converted = SDL_ConvertSurface(
+			const_cast<SDL_Surface *>(&srf),
+			&format.sdl_format,
+			0
+		);
+		if (!converted) {
+			throw SDLError("SDL_ConvertSurface");
+		}
+		Format new_fmt(*converted->format);
+		if (!format.Compatible(new_fmt)) {
+			SDL_FreeSurface(converted);
+			throw std::runtime_error("unable to convert texture input");
+		}
+		Data(l, new_fmt, converted->pixels);
+		SDL_FreeSurface(converted);
+	}
+}
+
+void ArrayTexture::Data(GLsizei l, const Format &f, GLvoid *data) noexcept {
+	glTexSubImage3D(
+		GL_TEXTURE_2D_ARRAY, // which
+		0,                   // mipmap lavel
+		0, 0,                // dest X and Y offset
+		l,                   // layer offset
+		width, height,
+		1,                   // layer count
+		f.format, f.type,
+		data
+	);
+}
+
+
+void ArrayTexture::FilterNearest() noexcept {
+	glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+	glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+}
+
+void ArrayTexture::FilterLinear() noexcept {
+	glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+	glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+}
+
+void ArrayTexture::FilterTrilinear() noexcept {
+	glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_REPEAT);
+	glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_REPEAT);
+	glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+	glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
+	glGenerateMipmap(GL_TEXTURE_2D_ARRAY);
+}
+
 }
diff --git a/src/graphics/shader.cpp b/src/graphics/shader.cpp
index 03b2edc..938dc97 100644
--- a/src/graphics/shader.cpp
+++ b/src/graphics/shader.cpp
@@ -4,6 +4,7 @@
 #include "Program.hpp"
 #include "Shader.hpp"
 
+#include "ArrayTexture.hpp"
 #include "Texture.hpp"
 #include "../app/init.hpp"
 
@@ -181,11 +182,13 @@ DirectionalLighting::DirectionalLighting()
 		GL_VERTEX_SHADER,
 		"#version 330 core\n"
 		"layout(location = 0) in vec3 vtx_position;\n"
-		"layout(location = 1) in vec3 vtx_color;\n"
-		"layout(location = 2) in vec3 vtx_normal;\n"
+		"layout(location = 1) in vec3 vtx_tex_uv;\n"
+		"layout(location = 2) in vec3 vtx_color;\n"
+		"layout(location = 3) in vec3 vtx_normal;\n"
 		"uniform mat4 M;\n"
 		"uniform mat4 MV;\n"
 		"uniform mat4 MVP;\n"
+		"out vec3 frag_tex_uv;\n"
 		"out vec3 frag_color;\n"
 		"out vec3 vtx_viewspace;\n"
 		"out vec3 normal;\n"
@@ -193,28 +196,33 @@ DirectionalLighting::DirectionalLighting()
 			"gl_Position = MVP * vec4(vtx_position, 1);\n"
 			"vtx_viewspace = (MV * vec4(vtx_position, 1)).xyz;\n"
 			"normal = (M * vec4(vtx_normal, 0)).xyz;\n"
+			"frag_tex_uv = vtx_tex_uv;\n"
 			"frag_color = vtx_color;\n"
 		"}\n"
 	);
 	program.LoadShader(
 		GL_FRAGMENT_SHADER,
 		"#version 330 core\n"
+		"in vec3 frag_tex_uv;\n"
 		"in vec3 frag_color;\n"
 		"in vec3 vtx_viewspace;\n"
 		"in vec3 normal;\n"
+		"uniform sampler2DArray tex_sampler;\n"
 		"uniform vec3 light_direction;\n"
 		"uniform vec3 light_color;\n"
 		"uniform float fog_density;\n"
 		"out vec3 color;\n"
 		"void main() {\n"
-			"vec3 ambient = vec3(0.1, 0.1, 0.1) * frag_color;\n"
+			"vec3 tex_color = texture(tex_sampler, frag_tex_uv).rgb;\n"
+			"vec3 base_color = tex_color * frag_color;\n"
+			"vec3 ambient = vec3(0.1, 0.1, 0.1) * base_color;\n"
 			// this should be the same as the clear color, otherwise looks really weird
 			"vec3 fog_color = vec3(0, 0, 0);\n"
 			"float e = 2.718281828;\n"
 			"vec3 n = normalize(normal);\n"
 			"vec3 l = normalize(light_direction);\n"
 			"float cos_theta = clamp(dot(n, l), 0, 1);\n"
-			"vec3 reflect_color = ambient + frag_color * light_color * cos_theta;\n"
+			"vec3 reflect_color = ambient + base_color * light_color * cos_theta;\n"
 			"float value = pow(e, -pow(fog_density * length(vtx_viewspace), 5));"
 			"color = mix(fog_color, reflect_color, value);\n"
 		"}\n"
@@ -228,6 +236,7 @@ DirectionalLighting::DirectionalLighting()
 	m_handle = program.UniformLocation("M");
 	mv_handle = program.UniformLocation("MV");
 	mvp_handle = program.UniformLocation("MVP");
+	sampler_handle = program.UniformLocation("tex_sampler");
 	light_direction_handle = program.UniformLocation("light_direction");
 	light_color_handle = program.UniformLocation("light_color");
 	fog_density_handle = program.UniformLocation("fog_density");
@@ -257,6 +266,12 @@ void DirectionalLighting::SetLightColor(const glm::vec3 &col) noexcept {
 	program.Uniform(light_color_handle, col);
 }
 
+void DirectionalLighting::SetTexture(ArrayTexture &tex) noexcept {
+	glActiveTexture(GL_TEXTURE0);
+	tex.Bind();
+	program.Uniform(sampler_handle, GLint(0));
+}
+
 void DirectionalLighting::SetFogDensity(float f) noexcept {
 	program.Uniform(fog_density_handle, f);
 }
@@ -293,15 +308,18 @@ BlockLighting::BlockLighting()
 		GL_VERTEX_SHADER,
 		"#version 330 core\n"
 		"layout(location = 0) in vec3 vtx_position;\n"
-		"layout(location = 1) in vec3 vtx_color;\n"
-		"layout(location = 2) in float vtx_light;\n"
+		"layout(location = 1) in vec3 vtx_tex_uv;\n"
+		"layout(location = 2) in vec3 vtx_color;\n"
+		"layout(location = 3) in float vtx_light;\n"
 		"uniform mat4 MV;\n"
 		"uniform mat4 MVP;\n"
+		"out vec3 frag_tex_uv;\n"
 		"out vec3 frag_color;\n"
 		"out vec3 vtx_viewspace;\n"
 		"out float frag_light;\n"
 		"void main() {\n"
 			"gl_Position = MVP * vec4(vtx_position, 1);\n"
+			"frag_tex_uv = vtx_tex_uv;\n"
 			"frag_color = vtx_color;\n"
 			"vtx_viewspace = (MV * vec4(vtx_position, 1)).xyz;\n"
 			"frag_light = vtx_light;\n"
@@ -310,18 +328,20 @@ BlockLighting::BlockLighting()
 	program.LoadShader(
 		GL_FRAGMENT_SHADER,
 		"#version 330 core\n"
+		"in vec3 frag_tex_uv;\n"
 		"in vec3 frag_color;\n"
 		"in vec3 vtx_viewspace;\n"
 		"in float frag_light;\n"
+		"uniform sampler2DArray tex_sampler;\n"
 		"uniform float fog_density;\n"
 		"out vec3 color;\n"
 		"void main() {\n"
-			"vec3 ambient = vec3(0.1, 0.1, 0.1) * frag_color;\n"
+			"vec3 tex_color = texture(tex_sampler, frag_tex_uv).rgb;\n"
+			"vec3 base_color = tex_color * frag_color;\n"
 			"float light_power = clamp(pow(0.8, 15 - frag_light), 0, 1);\n"
 			"vec3 fog_color = vec3(0, 0, 0);\n"
 			"float e = 2.718281828;\n"
-			//"vec3 reflect_color = ambient + frag_color * light_power;\n"
-			"vec3 reflect_color = frag_color * light_power;\n"
+			"vec3 reflect_color = base_color * light_power;\n"
 			"float value = pow(e, -pow(fog_density * length(vtx_viewspace), 5));"
 			"color = mix(fog_color, reflect_color, value);\n"
 		"}\n"
@@ -334,6 +354,7 @@ BlockLighting::BlockLighting()
 
 	mv_handle = program.UniformLocation("MV");
 	mvp_handle = program.UniformLocation("MVP");
+	sampler_handle = program.UniformLocation("tex_sampler");
 	fog_density_handle = program.UniformLocation("fog_density");
 }
 
@@ -342,15 +363,21 @@ void BlockLighting::Activate() noexcept {
 	program.Use();
 }
 
-void BlockLighting::SetM(const glm::mat4 &m) noexcept {
-	program.Uniform(mv_handle, view * m);
-	program.Uniform(mvp_handle, vp * m);
+void BlockLighting::SetTexture(ArrayTexture &tex) noexcept {
+	glActiveTexture(GL_TEXTURE0);
+	tex.Bind();
+	program.Uniform(sampler_handle, GLint(0));
 }
 
 void BlockLighting::SetFogDensity(float f) noexcept {
 	program.Uniform(fog_density_handle, f);
 }
 
+void BlockLighting::SetM(const glm::mat4 &m) noexcept {
+	program.Uniform(mv_handle, view * m);
+	program.Uniform(mvp_handle, vp * m);
+}
+
 void BlockLighting::SetProjection(const glm::mat4 &p) noexcept {
 	projection = p;
 	vp = p * view;
diff --git a/src/model/BlockModel.hpp b/src/model/BlockModel.hpp
index 0d93758..2ffd816 100644
--- a/src/model/BlockModel.hpp
+++ b/src/model/BlockModel.hpp
@@ -14,17 +14,20 @@ class BlockModel {
 
 public:
 	using Position = glm::vec3;
+	using TexCoord = glm::vec3;
 	using Color = glm::vec3;
 	using Light = float;
 	using Index = unsigned int;
 
 	using Positions = std::vector<Position>;
+	using TexCoords = std::vector<TexCoord>;
 	using Colors = std::vector<Color>;
 	using Lights = std::vector<Light>;
 	using Indices = std::vector<Index>;
 
 	enum Attribute {
 		ATTRIB_VERTEX,
+		ATTRIB_TEXCOORD,
 		ATTRIB_COLOR,
 		ATTRIB_LIGHT,
 		ATTRIB_INDEX,
@@ -34,12 +37,14 @@ public:
 	struct Buffer {
 
 		Positions vertices;
+		TexCoords tex_coords;
 		Colors colors;
 		Lights lights;
 		Indices indices;
 
 		void Clear() noexcept {
 			vertices.clear();
+			tex_coords.clear();
 			colors.clear();
 			lights.clear();
 			indices.clear();
@@ -47,6 +52,7 @@ public:
 
 		void Reserve(size_t p, size_t i) {
 			vertices.reserve(p);
+			tex_coords.reserve(p);
 			colors.reserve(p);
 			lights.reserve(p);
 			indices.reserve(i);
diff --git a/src/model/EntityModel.hpp b/src/model/EntityModel.hpp
index bd56a43..ce4ce1f 100644
--- a/src/model/EntityModel.hpp
+++ b/src/model/EntityModel.hpp
@@ -14,17 +14,20 @@ class EntityModel {
 
 public:
 	using Position = glm::vec3;
+	using TexCoord = glm::vec3;
 	using Color = glm::vec3;
 	using Normal = glm::vec3;
 	using Index = unsigned int;
 
 	using Positions = std::vector<Position>;
+	using TexCoords = std::vector<TexCoord>;
 	using Colors = std::vector<Color>;
 	using Normals = std::vector<Normal>;
 	using Indices = std::vector<Index>;
 
 	enum Attribute {
 		ATTRIB_VERTEX,
+		ATTRIB_TEXCOORD,
 		ATTRIB_COLOR,
 		ATTRIB_NORMAL,
 		ATTRIB_INDEX,
@@ -34,12 +37,14 @@ public:
 	struct Buffer {
 
 		Positions vertices;
+		TexCoords tex_coords;
 		Colors colors;
 		Normals normals;
 		Indices indices;
 
 		void Clear() noexcept {
 			vertices.clear();
+			tex_coords.clear();
 			colors.clear();
 			normals.clear();
 			indices.clear();
@@ -47,6 +52,7 @@ public:
 
 		void Reserve(size_t p, size_t i) {
 			vertices.reserve(p);
+			tex_coords.reserve(p);
 			colors.reserve(p);
 			normals.reserve(p);
 			indices.reserve(i);
diff --git a/src/model/Shape.hpp b/src/model/Shape.hpp
index c6964f9..51b950f 100644
--- a/src/model/Shape.hpp
+++ b/src/model/Shape.hpp
@@ -5,7 +5,6 @@
 #include "EntityModel.hpp"
 #include "OutlineModel.hpp"
 
-#include <vector>
 #include <glm/glm.hpp>
 
 
@@ -31,21 +30,19 @@ struct Shape {
 	/// fill given buffers with this shape's elements with an
 	/// optional transform and offset
 	void Vertices(
-		EntityModel::Positions &vertex,
-		EntityModel::Normals &normal,
-		EntityModel::Indices &index
+		EntityModel::Buffer &out,
+		float tex_offset = 0.0f
 	) const;
 	void Vertices(
-		EntityModel::Positions &vertex,
-		EntityModel::Normals &normal,
-		EntityModel::Indices &index,
+		EntityModel::Buffer &out,
 		const glm::mat4 &transform,
+		float tex_offset = 0.0f,
 		EntityModel::Index idx_offset = 0
 	) const;
 	void Vertices(
-		BlockModel::Positions &vertex,
-		BlockModel::Indices &index,
+		BlockModel::Buffer &out,
 		const glm::mat4 &transform,
+		float tex_offset = 0.0f,
 		BlockModel::Index idx_offset = 0
 	) const;
 
@@ -57,8 +54,7 @@ struct Shape {
 	/// fill given buffers with this shape's outline's elements with
 	/// an optional offset
 	void Outline(
-		OutlineModel::Positions &vertex,
-		OutlineModel::Indices &index,
+		OutlineModel::Buffer &out,
 		const OutlineModel::Position &offset = { 0.0f, 0.0f, 0.0f },
 		OutlineModel::Index idx_offset = 0
 	) const;
@@ -85,21 +81,23 @@ struct Shape {
 	) const noexcept = 0;
 
 protected:
-	void SetShape(const EntityModel::Positions &pos, const EntityModel::Normals &nrm, const EntityModel::Indices &idx) {
-		vtx_pos = pos;
-		vtx_nrm = nrm;
-		vtx_idx = idx;
-	}
-	void SetOutline(const OutlineModel::Positions &pos, const OutlineModel::Indices &idx) {
-		out_pos = pos;
-		out_idx = idx;
-	}
+	void SetShape(
+		const EntityModel::Positions &pos,
+		const EntityModel::Normals &nrm,
+		const EntityModel::Indices &idx);
+	void SetTexture(
+		const BlockModel::TexCoords &tex_coords);
+	void SetOutline(
+		const OutlineModel::Positions &pos,
+		const OutlineModel::Indices &idx);
 
 private:
 	EntityModel::Positions vtx_pos;
 	EntityModel::Normals vtx_nrm;
 	EntityModel::Indices vtx_idx;
 
+	BlockModel::TexCoords vtx_tex_coords;
+
 	OutlineModel::Positions out_pos;
 	OutlineModel::Indices out_idx;
 
diff --git a/src/model/model.cpp b/src/model/model.cpp
index b834298..72de1da 100644
--- a/src/model/model.cpp
+++ b/src/model/model.cpp
@@ -11,6 +11,9 @@ namespace blank {
 
 void EntityModel::Update(const Buffer &buf) noexcept {
 #ifndef NDEBUG
+	if (buf.tex_coords.size() < buf.vertices.size()) {
+		std::cerr << "EntityModel: not enough tex coords!" << std::endl;
+	}
 	if (buf.colors.size() < buf.vertices.size()) {
 		std::cerr << "EntityModel: not enough colors!" << std::endl;
 	}
@@ -21,6 +24,7 @@ void EntityModel::Update(const Buffer &buf) noexcept {
 
 	vao.Bind();
 	vao.PushAttribute(ATTRIB_VERTEX, buf.vertices);
+	vao.PushAttribute(ATTRIB_TEXCOORD, buf.tex_coords);
 	vao.PushAttribute(ATTRIB_COLOR, buf.colors);
 	vao.PushAttribute(ATTRIB_NORMAL, buf.normals);
 	vao.PushIndices(ATTRIB_INDEX, buf.indices);
@@ -34,6 +38,9 @@ void EntityModel::Draw() const noexcept {
 
 void BlockModel::Update(const Buffer &buf) noexcept {
 #ifndef NDEBUG
+	if (buf.tex_coords.size() < buf.vertices.size()) {
+		std::cerr << "BlockModel: not enough tex coords!" << std::endl;
+	}
 	if (buf.colors.size() < buf.vertices.size()) {
 		std::cerr << "BlockModel: not enough colors!" << std::endl;
 	}
@@ -44,6 +51,7 @@ void BlockModel::Update(const Buffer &buf) noexcept {
 
 	vao.Bind();
 	vao.PushAttribute(ATTRIB_VERTEX, buf.vertices);
+	vao.PushAttribute(ATTRIB_TEXCOORD, buf.tex_coords);
 	vao.PushAttribute(ATTRIB_COLOR, buf.colors);
 	vao.PushAttribute(ATTRIB_LIGHT, buf.lights);
 	vao.PushIndices(ATTRIB_INDEX, buf.indices);
diff --git a/src/model/shape.cpp b/src/model/shape.cpp
index e9364bd..aeea644 100644
--- a/src/model/shape.cpp
+++ b/src/model/shape.cpp
@@ -5,67 +5,97 @@
 namespace blank {
 
 void Shape::Vertices(
-	EntityModel::Positions &vertex,
-	EntityModel::Normals &normal,
-	EntityModel::Indices &index
+	EntityModel::Buffer &out,
+	float tex_offset
 ) const {
 	for (const auto &pos : vtx_pos) {
-		vertex.emplace_back(pos);
+		out.vertices.emplace_back(pos);
+	}
+	for (const auto &coord : vtx_tex_coords) {
+		out.tex_coords.emplace_back(coord.x, coord.y, coord.z + tex_offset);
 	}
 	for (const auto &nrm : vtx_nrm) {
-		normal.emplace_back(nrm);
+		out.normals.emplace_back(nrm);
 	}
 	for (auto idx : vtx_idx) {
-		index.emplace_back(idx);
+		out.indices.emplace_back(idx);
 	}
 }
 
 void Shape::Vertices(
-	EntityModel::Positions &vertex,
-	EntityModel::Normals &normal,
-	EntityModel::Indices &index,
+	EntityModel::Buffer &out,
 	const glm::mat4 &transform,
+	float tex_offset,
 	EntityModel::Index idx_offset
 ) const {
 	for (const auto &pos : vtx_pos) {
-		vertex.emplace_back(transform * glm::vec4(pos, 1.0f));
+		out.vertices.emplace_back(transform * glm::vec4(pos, 1.0f));
+	}
+	for (const auto &coord : vtx_tex_coords) {
+		out.tex_coords.emplace_back(coord.x, coord.y, coord.z + tex_offset);
 	}
 	for (const auto &nrm : vtx_nrm) {
-		normal.emplace_back(transform * glm::vec4(nrm, 0.0f));
+		out.normals.emplace_back(transform * glm::vec4(nrm, 0.0f));
 	}
 	for (auto idx : vtx_idx) {
-		index.emplace_back(idx_offset + idx);
+		out.indices.emplace_back(idx_offset + idx);
 	}
 }
 
 void Shape::Vertices(
-	BlockModel::Positions &vertex,
-	BlockModel::Indices &index,
+	BlockModel::Buffer &out,
 	const glm::mat4 &transform,
+	float tex_offset,
 	BlockModel::Index idx_offset
 ) const {
 	for (const auto &pos : vtx_pos) {
-		vertex.emplace_back(transform * glm::vec4(pos, 1.0f));
+		out.vertices.emplace_back(transform * glm::vec4(pos, 1.0f));
+	}
+	for (const auto &coord : vtx_tex_coords) {
+		out.tex_coords.emplace_back(coord.x, coord.y, coord.z + tex_offset);
 	}
 	for (auto idx : vtx_idx) {
-		index.emplace_back(idx_offset + idx);
+		out.indices.emplace_back(idx_offset + idx);
 	}
 }
 
 void Shape::Outline(
-	OutlineModel::Positions &vertex,
-	OutlineModel::Indices &index,
+	OutlineModel::Buffer &out,
 	const OutlineModel::Position &elem_offset,
 	OutlineModel::Index idx_offset
 ) const {
 	for (const auto &pos : out_pos) {
-		vertex.emplace_back(elem_offset + pos);
+		out.vertices.emplace_back(elem_offset + pos);
 	}
 	for (auto idx : out_idx) {
-		index.emplace_back(idx_offset + idx);
+		out.indices.emplace_back(idx_offset + idx);
 	}
 }
 
+void Shape::SetShape(
+	const EntityModel::Positions &pos,
+	const EntityModel::Normals &nrm,
+	const EntityModel::Indices &idx
+) {
+	vtx_pos = pos;
+	vtx_nrm = nrm;
+	vtx_idx = idx;
+}
+
+void Shape::SetTexture(
+	const BlockModel::TexCoords &tex_coords
+) {
+	vtx_tex_coords = tex_coords;
+}
+
+void Shape::SetOutline(
+	const OutlineModel::Positions &pos,
+	const OutlineModel::Indices &idx
+) {
+	out_pos = pos;
+	out_idx = idx;
+}
+
 
 NullShape::NullShape()
 : Shape() {
@@ -153,6 +183,32 @@ CuboidShape::CuboidShape(const AABB &b)
 		 16, 17, 18, 18, 17, 19, // left
 		 20, 21, 22, 22, 21, 23, // right
 	});
+	SetTexture({
+		{ 0.0f, 1.0f, 0.0f }, // front
+		{ 1.0f, 1.0f, 0.0f },
+		{ 0.0f, 0.0f, 0.0f },
+		{ 1.0f, 0.0f, 0.0f },
+		{ 1.0f, 1.0f, 0.0f }, // back
+		{ 1.0f, 0.0f, 0.0f },
+		{ 0.0f, 1.0f, 0.0f },
+		{ 0.0f, 0.0f, 0.0f },
+		{ 0.0f, 0.0f, 0.0f }, // top
+		{ 0.0f, 1.0f, 0.0f },
+		{ 1.0f, 0.0f, 0.0f },
+		{ 1.0f, 1.0f, 0.0f },
+		{ 1.0f, 0.0f, 0.0f }, // bottom
+		{ 0.0f, 0.0f, 0.0f },
+		{ 1.0f, 1.0f, 0.0f },
+		{ 0.0f, 1.0f, 0.0f },
+		{ 0.0f, 1.0f, 0.0f }, // left
+		{ 1.0f, 1.0f, 0.0f },
+		{ 0.0f, 0.0f, 0.0f },
+		{ 1.0f, 0.0f, 0.0f },
+		{ 1.0f, 1.0f, 0.0f }, // right
+		{ 1.0f, 0.0f, 0.0f },
+		{ 0.0f, 1.0f, 0.0f },
+		{ 0.0f, 0.0f, 0.0f },
+	});
 	SetOutline({
 		{ bb.min.x, bb.min.y, bb.min.z }, // back
 		{ bb.max.x, bb.min.y, bb.min.z },
@@ -286,6 +342,48 @@ StairShape::StairShape(const AABB &bb, const glm::vec2 &clip)
 		32, 33, 34, 34, 33, 35, // right, upper
 		36, 37, 38, 38, 37, 39, // right, lower
 	});
+	SetTexture({
+		{ 0.0f, 0.5f, 0.0f }, // front, upper
+		{ 1.0f, 0.5f, 0.0f },
+		{ 0.0f, 0.0f, 0.0f },
+		{ 1.0f, 0.0f, 0.0f },
+		{ 0.0f, 1.0f, 0.0f }, // front, lower
+		{ 1.0f, 1.0f, 0.0f },
+		{ 0.0f, 0.5f, 0.0f },
+		{ 1.0f, 0.5f, 0.0f },
+		{ 1.0f, 1.0f, 0.0f }, // back
+		{ 1.0f, 0.0f, 0.0f },
+		{ 0.0f, 1.0f, 0.0f },
+		{ 0.0f, 0.0f, 0.0f },
+		{ 0.0f, 0.0f, 0.0f }, // top, upper
+		{ 0.0f, 0.5f, 0.0f },
+		{ 1.0f, 0.0f, 0.0f },
+		{ 1.0f, 0.5f, 0.0f },
+		{ 0.0f, 0.5f, 0.0f }, // top, lower
+		{ 0.0f, 1.0f, 0.0f },
+		{ 1.0f, 0.5f, 0.0f },
+		{ 1.0f, 1.0f, 0.0f },
+		{ 1.0f, 0.0f, 0.0f }, // bottom
+		{ 0.0f, 0.0f, 0.0f },
+		{ 1.0f, 1.0f, 0.0f },
+		{ 0.0f, 1.0f, 0.0f },
+		{ 0.0f, 0.5f, 0.0f }, // left, upper
+		{ 0.5f, 0.5f, 0.0f },
+		{ 0.0f, 0.0f, 0.0f },
+		{ 0.5f, 0.0f, 0.0f },
+		{ 0.0f, 1.0f, 0.0f }, // left, lower
+		{ 1.0f, 1.0f, 0.0f },
+		{ 0.0f, 0.5f, 0.0f },
+		{ 1.0f, 0.5f, 0.0f },
+		{ 1.0f, 0.5f, 0.0f }, // right, upper
+		{ 1.0f, 0.0f, 0.0f },
+		{ 0.5f, 0.5f, 0.0f },
+		{ 0.5f, 0.0f, 0.0f },
+		{ 1.0f, 1.0f, 0.0f }, // right, lower
+		{ 1.0f, 0.5f, 0.0f },
+		{ 0.0f, 1.0f, 0.0f },
+		{ 0.0f, 0.5f, 0.0f },
+	});
 	SetOutline({
 		{ bot.min.x, bot.min.y, bot.min.z }, // bottom
 		{ bot.max.x, bot.min.y, bot.min.z },
diff --git a/src/world/BlockType.hpp b/src/world/BlockType.hpp
index 786c76c..95a9605 100644
--- a/src/world/BlockType.hpp
+++ b/src/world/BlockType.hpp
@@ -17,6 +17,7 @@ namespace blank {
 struct BlockType {
 
 	const Shape *shape;
+	float texture;
 	glm::vec3 color;
 	glm::vec3 outline_color;
 
diff --git a/src/world/Entity.cpp b/src/world/Entity.cpp
index 4a34a11..072bb38 100644
--- a/src/world/Entity.cpp
+++ b/src/world/Entity.cpp
@@ -30,10 +30,10 @@ Entity::Entity() noexcept
 }
 
 
-void Entity::SetShape(const Shape *s, const glm::vec3 &color) {
+void Entity::SetShape(const Shape *s, const glm::vec3 &color, float texture) {
 	shape = s;
 	model_buffer.Clear();
-	shape->Vertices(model_buffer.vertices, model_buffer.normals, model_buffer.indices);
+	shape->Vertices(model_buffer, texture);
 	model_buffer.colors.resize(shape->VertexCount(), color);
 	model.Update(model_buffer);
 }
diff --git a/src/world/Entity.hpp b/src/world/Entity.hpp
index 078daa6..f60bf4c 100644
--- a/src/world/Entity.hpp
+++ b/src/world/Entity.hpp
@@ -22,7 +22,7 @@ public:
 
 	bool HasShape() const noexcept { return shape; }
 	const Shape *GetShape() const noexcept { return shape; }
-	void SetShape(const Shape *, const glm::vec3 &color);
+	void SetShape(const Shape *, const glm::vec3 &color, float texture);
 	void SetShapeless() noexcept;
 
 	const std::string &Name() const noexcept { return name; }
diff --git a/src/world/World.cpp b/src/world/World.cpp
index e8b665c..73a870d 100644
--- a/src/world/World.cpp
+++ b/src/world/World.cpp
@@ -1,6 +1,8 @@
 #include "World.hpp"
 
 #include "WorldCollision.hpp"
+#include "../app/Assets.hpp"
+#include "../graphics/Format.hpp"
 #include "../graphics/Viewport.hpp"
 
 #include <iostream>
@@ -11,11 +13,12 @@
 
 namespace blank {
 
-World::World(const Config &config)
+World::World(const Assets &assets, const Config &config)
 : blockType()
 , blockShape({{ -0.5f, -0.5f, -0.5f }, { 0.5f, 0.5f, 0.5f }})
 , stairShape({{ -0.5f, -0.5f, -0.5f }, { 0.5f, 0.5f, 0.5f }}, { 0.0f, 0.0f })
 , slabShape({{ -0.5f, -0.5f, -0.5f }, { 0.5f, 0.0f, 0.5f }})
+, block_tex()
 , generate(config.gen)
 , chunks(config.load, blockType, generate)
 , player()
@@ -26,8 +29,17 @@ World::World(const Config &config)
 	BlockType::Faces slab_fill  = { false,  true, false, false, false, false };
 	BlockType::Faces stair_fill = { false,  true, false, false, false,  true };
 
+	block_tex.Bind();
+	block_tex.Reserve(16, 16, 4, Format());
+	assets.LoadTexture("debug", block_tex, 0);
+	assets.LoadTexture("rock-1", block_tex, 1);
+	assets.LoadTexture("rock-2", block_tex, 2);
+	assets.LoadTexture("rock-3", block_tex, 3);
+	block_tex.FilterNearest();
+
 	{ // white block
 		BlockType type(true, { 1.0f, 1.0f, 1.0f }, &blockShape);
+		type.texture = 1;
 		type.label = "White Block";
 		type.block_light = true;
 		type.collision = true;
@@ -37,6 +49,7 @@ World::World(const Config &config)
 	}
 	{ // white slab
 		BlockType type(true, { 1.0f, 1.0f, 1.0f }, &slabShape);
+		type.texture = 1;
 		type.label = "White Slab";
 		type.block_light = true;
 		type.collision = true;
@@ -46,6 +59,7 @@ World::World(const Config &config)
 	}
 	{ // white stair
 		BlockType type(true, { 1.0f, 1.0f, 1.0f }, &stairShape);
+		type.texture = 1;
 		type.label = "White Stair";
 		type.block_light = true;
 		type.collision = true;
@@ -56,6 +70,7 @@ World::World(const Config &config)
 
 	{ // red block
 		BlockType type(true, { 1.0f, 0.0f, 0.0f }, &blockShape);
+		type.texture = 3;
 		type.label = "Red Block";
 		type.block_light = true;
 		type.collision = true;
@@ -65,6 +80,7 @@ World::World(const Config &config)
 	}
 	{ // red slab
 		BlockType type(true, { 1.0f, 0.0f, 0.0f }, &slabShape);
+		type.texture = 3;
 		type.label = "Red Slab";
 		type.block_light = true;
 		type.collision = true;
@@ -74,6 +90,7 @@ World::World(const Config &config)
 	}
 	{ // red stair
 		BlockType type(true, { 1.0f, 0.0f, 0.0f }, &stairShape);
+		type.texture = 3;
 		type.label = "Red Stair";
 		type.block_light = true;
 		type.collision = true;
@@ -84,6 +101,7 @@ World::World(const Config &config)
 
 	{ // green block
 		BlockType type(true, { 0.0f, 1.0f, 0.0f }, &blockShape);
+		type.texture = 1;
 		type.label = "Green Block";
 		type.block_light = true;
 		type.collision = true;
@@ -93,6 +111,7 @@ World::World(const Config &config)
 	}
 	{ // green slab
 		BlockType type(true, { 0.0f, 1.0f, 0.0f }, &slabShape);
+		type.texture = 1;
 		type.label = "Green Slab";
 		type.block_light = true;
 		type.collision = true;
@@ -102,6 +121,7 @@ World::World(const Config &config)
 	}
 	{ // green stair
 		BlockType type(true, { 0.0f, 1.0f, 0.0f }, &stairShape);
+		type.texture = 1;
 		type.label = "Green Stair";
 		type.block_light = true;
 		type.collision = true;
@@ -112,6 +132,7 @@ World::World(const Config &config)
 
 	{ // blue block
 		BlockType type(true, { 0.0f, 0.0f, 1.0f }, &blockShape);
+		type.texture = 3;
 		type.label = "Blue Block";
 		type.block_light = true;
 		type.collision = true;
@@ -121,6 +142,7 @@ World::World(const Config &config)
 	}
 	{ // blue slab
 		BlockType type(true, { 0.0f, 0.0f, 1.0f }, &slabShape);
+		type.texture = 3;
 		type.label = "Blue Slab";
 		type.block_light = true;
 		type.collision = true;
@@ -130,6 +152,7 @@ World::World(const Config &config)
 	}
 	{ // blue stair
 		BlockType type(true, { 0.0f, 0.0f, 1.0f }, &stairShape);
+		type.texture = 3;
 		type.label = "Blue Stair";
 		type.block_light = true;
 		type.collision = true;
@@ -140,6 +163,7 @@ World::World(const Config &config)
 
 	{ // glowing yellow block
 		BlockType type(true, { 1.0f, 1.0f, 0.0f }, &blockShape);
+		type.texture = 2;
 		type.label = "Light";
 		type.luminosity = 15;
 		type.block_light = true;
@@ -149,6 +173,18 @@ World::World(const Config &config)
 		blockType.Add(type);
 	}
 
+	{ // the mysterious debug cube
+		BlockType type(true, { 1.0f, 1.0f, 1.0f }, &blockShape);
+		type.texture = 0;
+		type.label = "Debug Cube";
+		type.luminosity = 0;
+		type.block_light = true;
+		type.collision = true;
+		type.collide_block = true;
+		type.fill = block_fill;
+		blockType.Add(type);
+	}
+
 	generate.Space(0);
 	generate.Light(13);
 	generate.Solids({ 1, 4, 7, 10 });
@@ -305,6 +341,7 @@ void World::Render(Viewport &viewport) {
 	viewport.WorldPosition(player->Transform(player->ChunkCoords()));
 
 	BlockLighting &chunk_prog = viewport.ChunkProgram();
+	chunk_prog.SetTexture(block_tex);
 	chunk_prog.SetFogDensity(fog_density);
 
 	for (Chunk &chunk : chunks.Loaded()) {
diff --git a/src/world/World.hpp b/src/world/World.hpp
index b98efca..6bd8128 100644
--- a/src/world/World.hpp
+++ b/src/world/World.hpp
@@ -5,6 +5,7 @@
 #include "ChunkLoader.hpp"
 #include "Entity.hpp"
 #include "Generator.hpp"
+#include "../graphics/ArrayTexture.hpp"
 #include "../model/shapes.hpp"
 
 #include <list>
@@ -14,6 +15,7 @@
 
 namespace blank {
 
+class Assets;
 class Viewport;
 class WorldCollision;
 
@@ -35,7 +37,7 @@ public:
 		ChunkLoader::Config load = ChunkLoader::Config();
 	};
 
-	explicit World(const Config &);
+	World(const Assets &, const Config &);
 
 	bool Intersection(
 		const Ray &,
@@ -67,6 +69,8 @@ private:
 	StairShape stairShape;
 	CuboidShape slabShape;
 
+	ArrayTexture block_tex;
+
 	Generator generate;
 	ChunkLoader chunks;
 
diff --git a/src/world/block.cpp b/src/world/block.cpp
index e06f3e0..aa91e79 100644
--- a/src/world/block.cpp
+++ b/src/world/block.cpp
@@ -75,6 +75,7 @@ std::ostream &operator <<(std::ostream &out, const Block::Turn &turn) {
 
 BlockType::BlockType(bool v, const glm::vec3 &col, const Shape *s) noexcept
 : shape(s)
+, texture(0)
 , color(col)
 , outline_color(-1, -1, -1)
 , label("some block")
@@ -93,7 +94,7 @@ void BlockType::FillEntityModel(
 	const glm::mat4 &transform,
 	EntityModel::Index idx_offset
 ) const noexcept {
-	shape->Vertices(buf.vertices, buf.normals, buf.indices, transform, idx_offset);
+	shape->Vertices(buf, transform, texture, idx_offset);
 	buf.colors.insert(buf.colors.end(), shape->VertexCount(), color);
 }
 
@@ -102,7 +103,7 @@ void BlockType::FillBlockModel(
 	const glm::mat4 &transform,
 	BlockModel::Index idx_offset
 ) const noexcept {
-	shape->Vertices(buf.vertices, buf.indices, transform, idx_offset);
+	shape->Vertices(buf, transform, texture, idx_offset);
 	buf.colors.insert(buf.colors.end(), shape->VertexCount(), color);
 }
 
@@ -111,7 +112,7 @@ void BlockType::FillOutlineModel(
 	const glm::vec3 &pos_offset,
 	OutlineModel::Index idx_offset
 ) const noexcept {
-	shape->Outline(buf.vertices, buf.indices, pos_offset, idx_offset);
+	shape->Outline(buf, pos_offset, idx_offset);
 	buf.colors.insert(buf.colors.end(), shape->OutlineCount(), outline_color);
 }
 
-- 
2.39.5