// // Lol Engine // // Copyright: (c) 2010-2014 Sam Hocevar // This program is free software; you can redistribute it and/or // modify it under the terms of the Do What The Fuck You Want To // Public License, Version 2, as published by Sam Hocevar. See // http://www.wtfpl.net/ for more details. // #if HAVE_CONFIG_H # include "config.h" #endif #include #if defined(_WIN32) # define WIN32_LEAN_AND_MEAN 1 # include #endif #include #include "lolgl.h" LOLFX_RESOURCE_DECLARE(tile); LOLFX_RESOURCE_DECLARE(palette); LOLFX_RESOURCE_DECLARE(line); namespace lol { /* * The global g_scene object, initialised by Video::Init */ Scene *g_scene = nullptr; /* * A quick and dirty Tile structure for 2D blits */ struct Tile { TileSet *tileset; uint32_t prio; vec3 pos; vec2 scale; float angle; int id, o; }; /* * Scene implementation class */ class SceneData { friend class Scene; private: /* New scenegraph */ array m_primitives; /* Old API */ float m_new_line_time; int m_new_line_mask; float m_new_line_segment_size; vec4 m_new_line_color; array m_lines; int m_debug_mask; Shader *m_line_shader; VertexDeclaration *m_line_vdecl; int m_tile_cam; array m_tiles; array m_palettes; array m_lights; Shader *m_tile_shader; Shader *m_palette_shader; VertexDeclaration *m_tile_vdecl; array m_tile_bufs; Camera *m_default_cam; array m_camera_stack; }; /* * Public Scene class */ Scene::Scene(ivec2 size) : data(new SceneData()) { /* Create a default orthographic camera, in case the user doesn’t. */ data->m_default_cam = new Camera(); mat4 proj = mat4::ortho(0.f, size.x, 0.f, size.y, -1000.f, 1000.f); data->m_default_cam->SetProjection(proj); PushCamera(data->m_default_cam); data->m_tile_cam = -1; data->m_tile_shader = 0; data->m_palette_shader = 0; data->m_tile_vdecl = new VertexDeclaration(VertexStream(VertexUsage::Position), VertexStream(VertexUsage::TexCoord)); data->m_line_shader = 0; data->m_line_vdecl = new VertexDeclaration(VertexStream(VertexUsage::Position, VertexUsage::Color)); data->m_debug_mask = 1; SetLineTime(); SetLineMask(); SetLineSegmentSize(); SetLineColor(); } Scene::~Scene() { PopCamera(data->m_default_cam); /* FIXME: this must be done while the GL context is still active. * Change the code architecture to make sure of that. */ /* FIXME: also, make sure we do not add code to Reset() that will * reallocate stuff */ Reset(); delete data->m_line_vdecl; delete data->m_tile_vdecl; delete data; } int Scene::PushCamera(Camera *cam) { ASSERT(this, "trying to push a camera before g_scene is ready"); Ticker::Ref(cam); data->m_camera_stack.Push(cam); return data->m_camera_stack.Count() - 1; } void Scene::PopCamera(Camera *cam) { ASSERT(this, "trying to pop a camera before g_scene is ready"); /* Parse from the end because that’s probably where we’ll find * our camera first. */ for (int i = data->m_camera_stack.Count(); i--; ) { if (data->m_camera_stack[i] == cam) { Ticker::Unref(cam); data->m_camera_stack.Remove(i); return; } } ASSERT(false, "trying to pop a nonexistent camera from the scene"); } void Scene::SetTileCam(int cam_idx) { data->m_tile_cam = cam_idx; } Camera *Scene::GetCamera(int cam_idx) { return (0 <= cam_idx && cam_idx < data->m_camera_stack.Count()) ? data->m_camera_stack[cam_idx] : data->m_camera_stack.Last(); } void Scene::Reset() { for (int i = 0; i < data->m_tile_bufs.Count(); i++) delete data->m_tile_bufs[i]; data->m_tile_bufs.Empty(); data->m_lights.Empty(); data->m_primitives.Empty(); } void Scene::AddPrimitive(Mesh const &mesh, mat4 const &matrix) { for (int i = 0; i < mesh.m_submeshes.Count(); ++i) { data->m_primitives.Push(Primitive(mesh.m_submeshes[i], matrix)); } } void Scene::AddTile(TileSet *tileset, int id, vec3 pos, int o, vec2 scale, float angle) { ASSERT(id < tileset->GetTileCount()); Tile t; /* FIXME: this sorting only works for a 45-degree camera */ t.prio = -pos.y - (int)(2 * 32 * pos.z) + (o ? 0 : 32); t.tileset = tileset; t.id = id; t.pos = pos; t.o = o; t.scale = scale; t.angle = angle; if (tileset->GetPalette()) data->m_palettes.Push(t); else data->m_tiles.Push(t); } void Scene::SetLineTime(float new_time) { data->m_new_line_time = new_time; } void Scene::SetLineMask(int new_mask) { data->m_new_line_mask = new_mask; } void Scene::SetLineSegmentSize(float new_segment_size) { data->m_new_line_segment_size = new_segment_size; } float Scene::GetLineSegmentSize() { return data->m_new_line_segment_size; } void Scene::SetLineColor(vec4 new_color) { data->m_new_line_color = new_color; } vec4 Scene::GetLineColor() { return data->m_new_line_color; } void Scene::AddLine(vec3 a, vec3 b, vec4 color) { data->m_lines.Push(a, b, color, data->m_new_line_time, data->m_new_line_mask, false, false); } void Scene::AddLine(vec2 a, vec3 b, vec4 color, float az) { data->m_lines.Push(vec3(a, az), b, color, data->m_new_line_time, data->m_new_line_mask, true, false); } void Scene::AddLine(vec2 a, vec2 b, vec4 color, float az, float bz) { data->m_lines.Push(vec3(a, az), vec3(b, bz), color, data->m_new_line_time, data->m_new_line_mask, true, true); } void Scene::AddLight(Light *l) { data->m_lights.Push(l); } array const &Scene::GetLights() const { return data->m_lights; } void Scene::RenderPrimitives() { /* TODO: this should be the main entry for rendering of all * primitives found in the scene graph. When we have one. */ Shader *shader = nullptr; ShaderUniform u_model, u_modelview, u_normalmat, uni_tex, uni_texsize; ShaderAttrib a_pos, a_tex; for (Primitive const &p : data->m_primitives) { /* If this primitive uses a new shader, update attributes */ if (p.m_submesh->GetShader() != shader) { shader = p.m_submesh->GetShader(); a_pos = shader->GetAttribLocation(VertexUsage::Position, 0); a_tex = shader->GetAttribLocation(VertexUsage::TexCoord, 0); shader->Bind(); /* Per-scene matrices */ ShaderUniform u_mat; u_mat = shader->GetUniformLocation("u_projection"); shader->SetUniform(u_mat, GetCamera()->GetProjection()); u_mat = shader->GetUniformLocation("u_view"); shader->SetUniform(u_mat, GetCamera()->GetView()); u_mat = shader->GetUniformLocation("u_inv_view"); shader->SetUniform(u_mat, inverse(GetCamera()->GetView())); /* Per-object matrices, will be set later */ u_model = shader->GetUniformLocation("u_model"); u_modelview = shader->GetUniformLocation("u_modelview"); u_normalmat = shader->GetUniformLocation("u_normalmat"); /* Per-scene environment */ array const &lights = GetLights(); array light_data; /* FIXME: the 4th component of the position can be used for other things */ /* FIXME: GetUniform("blabla") is costly */ for (auto l : lights) light_data << vec4(l->GetPosition(), l->GetType()) << l->GetColor(); while (light_data.Count() < LOL_MAX_LIGHT_COUNT) light_data << vec4::zero << vec4::zero; ShaderUniform u_lights = shader->GetUniformLocation("u_lights"); shader->SetUniform(u_lights, light_data); } shader->SetUniform(u_model, p.m_matrix); mat4 modelview = GetCamera()->GetView() * p.m_matrix; shader->SetUniform(u_modelview, modelview); shader->SetUniform(u_normalmat, transpose(inverse(mat3(modelview)))); p.m_submesh->Render(); } } void Scene::RenderTiles() // XXX: rename to Blit() { RenderContext rc; /* Early test if nothing needs to be rendered */ if (!data->m_tiles.Count() && !data->m_palettes.Count()) return; rc.SetDepthFunc(DepthFunc::LessOrEqual); rc.SetBlendFunc(BlendFunc::SrcAlpha, BlendFunc::OneMinusSrcAlpha); rc.SetBlendEquation(BlendEquation::Add, BlendEquation::Max); rc.SetAlphaFunc(AlphaFunc::GreaterOrEqual, 0.01f); #if defined USE_D3D9 || defined _XBOX #elif !defined HAVE_GLES_2X glEnable(GL_TEXTURE_2D); #endif if (!data->m_tile_shader) data->m_tile_shader = Shader::Create(LOLFX_RESOURCE_NAME(tile)); if (!data->m_palette_shader) data->m_palette_shader = Shader::Create(LOLFX_RESOURCE_NAME(palette)); for (int p = 0; p < 2; p++) { Shader *shader = (p == 0) ? data->m_tile_shader : data->m_palette_shader; array& tiles = (p == 0) ? data->m_tiles : data->m_palettes; if (tiles.Count() == 0) continue; ShaderUniform uni_mat, uni_tex, uni_pal, uni_texsize; ShaderAttrib attr_pos, attr_tex; attr_pos = shader->GetAttribLocation(VertexUsage::Position, 0); attr_tex = shader->GetAttribLocation(VertexUsage::TexCoord, 0); shader->Bind(); uni_mat = shader->GetUniformLocation("u_projection"); shader->SetUniform(uni_mat, GetCamera(data->m_tile_cam)->GetProjection()); uni_mat = shader->GetUniformLocation("u_view"); shader->SetUniform(uni_mat, GetCamera(data->m_tile_cam)->GetView()); uni_mat = shader->GetUniformLocation("u_model"); shader->SetUniform(uni_mat, mat4(1.f)); uni_tex = shader->GetUniformLocation("u_texture"); uni_pal = data->m_palette_shader->GetUniformLocation("u_palette"); uni_texsize = shader->GetUniformLocation("u_texsize"); for (int buf = 0, i = 0, n; i < tiles.Count(); i = n, buf += 2) { /* Count how many quads will be needed */ for (n = i + 1; n < tiles.Count(); n++) if (tiles[i].tileset != tiles[n].tileset) break; /* Create a vertex array object */ VertexBuffer *vb1 = new VertexBuffer(6 * (n - i) * sizeof(vec3)); vec3 *vertex = (vec3 *)vb1->Lock(0, 0); VertexBuffer *vb2 = new VertexBuffer(6 * (n - i) * sizeof(vec2)); vec2 *texture = (vec2 *)vb2->Lock(0, 0); data->m_tile_bufs.Push(vb1); data->m_tile_bufs.Push(vb2); for (int j = i; j < n; j++) { tiles[i].tileset->BlitTile(tiles[j].id, tiles[j].pos, tiles[j].o, tiles[j].scale, tiles[j].angle, vertex + 6 * (j - i), texture + 6 * (j - i)); } vb1->Unlock(); vb2->Unlock(); /* Bind texture */ if (tiles[i].tileset->GetPalette()) { if (tiles[i].tileset->GetTexture()) shader->SetUniform(uni_tex, tiles[i].tileset->GetTexture()->GetTextureUniform(), 0); if (tiles[i].tileset->GetPalette()->GetTexture()) shader->SetUniform(uni_pal, tiles[i].tileset->GetPalette()->GetTexture()->GetTextureUniform(), 1); } else { shader->SetUniform(uni_tex, 0); if (tiles[i].tileset->GetTexture()) shader->SetUniform(uni_tex, tiles[i].tileset->GetTexture()->GetTextureUniform(), 0); tiles[i].tileset->Bind(); } shader->SetUniform(uni_texsize, (vec2)tiles[i].tileset->GetTextureSize()); /* Bind vertex and texture coordinate buffers */ data->m_tile_vdecl->Bind(); data->m_tile_vdecl->SetStream(vb1, attr_pos); data->m_tile_vdecl->SetStream(vb2, attr_tex); /* Draw arrays */ data->m_tile_vdecl->DrawElements(MeshPrimitive::Triangles, 0, (n - i) * 6); data->m_tile_vdecl->Unbind(); tiles[i].tileset->Unbind(); } tiles.Empty(); shader->Unbind(); } #if defined USE_D3D9 || defined _XBOX /* TODO */ #elif !defined HAVE_GLES_2X glDisable(GL_TEXTURE_2D); #endif } void Scene::RenderLines(float seconds) // XXX: rename to Blit() { RenderContext rc; if (!data->m_lines.Count()) return; rc.SetDepthFunc(DepthFunc::LessOrEqual); rc.SetBlendFunc(BlendFunc::SrcAlpha, BlendFunc::OneMinusSrcAlpha); rc.SetBlendEquation(BlendEquation::Add, BlendEquation::Max); rc.SetAlphaFunc(AlphaFunc::GreaterOrEqual, 0.01f); int linecount = data->m_lines.Count(); if (!data->m_line_shader) data->m_line_shader = Shader::Create(LOLFX_RESOURCE_NAME(line)); array buff; buff.Resize(linecount); int real_linecount = 0; mat4 const inv_view_proj = inverse(g_scene->GetCamera()->GetProjection() * g_scene->GetCamera()->GetView()); for (int i = 0; i < linecount; i++) { if (data->m_lines[i].m5 & data->m_debug_mask) { buff[real_linecount].m1 = vec4(data->m_lines[i].m1, (float)data->m_lines[i].m6); buff[real_linecount].m2 = data->m_lines[i].m3; buff[real_linecount].m3 = vec4(data->m_lines[i].m2, (float)data->m_lines[i].m7); buff[real_linecount].m4 = data->m_lines[i].m3; real_linecount++; } data->m_lines[i].m4 -= seconds; if (data->m_lines[i].m4 < 0.f) { data->m_lines.RemoveSwap(i--); linecount--; } } int vb_size = sizeof(vec4) * 4 * real_linecount; VertexBuffer *vb = new VertexBuffer(vb_size); float *vertex = (float *)vb->Lock(0, 0); memcpy(vertex, buff.Data(), vb_size); vb->Unlock(); data->m_line_shader->Bind(); ShaderUniform uni_mat, uni_tex; ShaderAttrib attr_pos, attr_col; attr_pos = data->m_line_shader->GetAttribLocation(VertexUsage::Position, 0); attr_col = data->m_line_shader->GetAttribLocation(VertexUsage::Color, 0); data->m_line_shader->Bind(); uni_mat = data->m_line_shader->GetUniformLocation("u_projection"); data->m_line_shader->SetUniform(uni_mat, GetCamera()->GetProjection()); uni_mat = data->m_line_shader->GetUniformLocation("u_view"); data->m_line_shader->SetUniform(uni_mat, GetCamera()->GetView()); data->m_line_vdecl->Bind(); data->m_line_vdecl->SetStream(vb, attr_pos, attr_col); data->m_line_vdecl->DrawElements(MeshPrimitive::Lines, 0, 2 * real_linecount); data->m_line_vdecl->Unbind(); data->m_line_shader->Unbind(); //data->m_lines.Empty(); delete vb; } } /* namespace lol */