Got BSP loading to a visual state. Needs more testing to determine if culling/visibility testing was effective.
This commit is contained in:
parent
dfd39257c0
commit
87fbb1c603
@ -83,7 +83,8 @@ void Input::handleMovementKeys(Renderer& ren) {
|
||||
else
|
||||
forward = glm::vec3(glm::cos(ren.cam.phi), 0.0, glm::sin(ren.cam.phi));
|
||||
const auto right = glm::cross(forward, glm::vec3(0.0, 1.0, 0.0));
|
||||
const auto speed = glfwGetKey(in, GLFW_KEY_LEFT_SHIFT)? 2.0f : 1.0f;
|
||||
auto speed = glfwGetKey(in, GLFW_KEY_LEFT_SHIFT)? 2.0f : 1.0f;
|
||||
speed *= ren.speed;
|
||||
|
||||
if(glfwGetKey(in, GLFW_KEY_UP)) {
|
||||
ren.cam.theta -= 0.01;
|
||||
@ -105,28 +106,28 @@ void Input::handleMovementKeys(Renderer& ren) {
|
||||
}
|
||||
/* move "forward" or "backward" */
|
||||
if (glfwGetKey(in, GLFW_KEY_W)) {
|
||||
ren.cam.pos += forward * 0.1f * speed;
|
||||
ren.cam.pos += forward * 1.0f * speed;
|
||||
}
|
||||
|
||||
if (glfwGetKey(in, GLFW_KEY_S)) {
|
||||
ren.cam.pos += forward * -0.1f * speed;
|
||||
ren.cam.pos += forward * -1.0f * speed;
|
||||
}
|
||||
|
||||
/* move "left" or "right" */
|
||||
if (glfwGetKey(in, GLFW_KEY_A)) {
|
||||
ren.cam.pos -= right * 0.1f * speed;
|
||||
ren.cam.pos -= right * 1.0f * speed;
|
||||
}
|
||||
|
||||
if (glfwGetKey(in, GLFW_KEY_D)) {
|
||||
ren.cam.pos += right * 0.1f * speed;
|
||||
ren.cam.pos += right * 1.0f * speed;
|
||||
}
|
||||
|
||||
if(glfwGetKey(in, GLFW_KEY_SPACE)) {
|
||||
ren.cam.pos.y += 0.1;
|
||||
ren.cam.pos.y += 1.0 * speed;
|
||||
}
|
||||
|
||||
if(glfwGetKey(in, GLFW_KEY_LEFT_CONTROL)) {
|
||||
ren.cam.pos.y -= 0.1;
|
||||
ren.cam.pos.y -= 1.0 * speed;
|
||||
}
|
||||
|
||||
ren.cam.theta = glm::clamp(ren.cam.theta, 0.01f, glm::pi<float>() - 0.01f);
|
||||
|
||||
@ -58,26 +58,26 @@ void Model::initVertices(Node* node, const tinygltf::Primitive& prim) {
|
||||
|
||||
auto loc = prim.attributes.find("POSITION");
|
||||
if(loc != prim.attributes.end()) {
|
||||
auto accessor = model->accessors[loc->second];
|
||||
auto& accessor = model->accessors[loc->second];
|
||||
const auto& view = model->bufferViews[accessor.bufferView];
|
||||
pos_buff = reinterpret_cast<const float*>(&model->buffers[view.buffer].data[accessor.byteOffset+view.byteOffset]);
|
||||
vertex_count = accessor.count;
|
||||
}
|
||||
loc = prim.attributes.find("NORMAL");
|
||||
if(loc != prim.attributes.end()) {
|
||||
auto accessor = model->accessors[loc->second];
|
||||
auto& accessor = model->accessors[loc->second];
|
||||
const auto& view = model->bufferViews[accessor.bufferView];
|
||||
norm_buff = reinterpret_cast<const float*>(&model->buffers[view.buffer].data[accessor.byteOffset+view.byteOffset]);
|
||||
}
|
||||
loc = prim.attributes.find("TEXCOORD_0");
|
||||
if(loc != prim.attributes.end()) {
|
||||
auto accessor = model->accessors[loc->second];
|
||||
auto& accessor = model->accessors[loc->second];
|
||||
const auto& view = model->bufferViews[accessor.bufferView];
|
||||
uv_buff = reinterpret_cast<const float*>(&model->buffers[view.buffer].data[accessor.byteOffset+view.byteOffset]);
|
||||
}
|
||||
|
||||
for(size_t i = 0; i < vertex_count; i++) {
|
||||
vertices.push_back(Vertex {
|
||||
vertices.push_back(BasicVertex {
|
||||
.pos = pos_buff? glm::make_vec3(pos_buff+i*3) : glm::vec3(0.0),
|
||||
.norm = norm_buff? glm::normalize(glm::make_vec3(norm_buff+i*3)) : glm::vec3(0.0),
|
||||
.uv = uv_buff? glm::make_vec2(uv_buff+i*2) : glm::vec2(0.0),
|
||||
|
||||
@ -36,7 +36,7 @@ struct Model {
|
||||
|
||||
std::unique_ptr<VertexBuffer> vertex_buffer;
|
||||
std::unique_ptr<Buffer> index_buffer;
|
||||
std::vector<Vertex> vertices;
|
||||
std::vector<BasicVertex> vertices;
|
||||
std::vector<uint16_t> indices;
|
||||
|
||||
/* recusively initialize nodes with an accumulative vertex and index buffer collector */
|
||||
|
||||
@ -71,12 +71,6 @@ void CommandBuffer::bind(vk::PipelineLayout layout, vk::ArrayProxy<vk::Descripto
|
||||
command_buffer.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, layout, 0, desc_sets, nullptr);
|
||||
}
|
||||
|
||||
void CommandBuffer::bind(const VertexBuffer& vertex_buffer, uint32_t binding) {
|
||||
const std::array<vk::DeviceSize, 1> offsets = {0};
|
||||
|
||||
command_buffer.bindVertexBuffers(binding, vertex_buffer.buffer->buffer, offsets);
|
||||
}
|
||||
|
||||
void CommandBuffer::bind(std::shared_ptr<Model> model) {
|
||||
bind(*model->vertex_buffer);
|
||||
command_buffer.bindIndexBuffer(*model->index_buffer, 0, vk::IndexType::eUint16);
|
||||
@ -87,6 +81,14 @@ void CommandBuffer::bind(Terrain* terrain) {
|
||||
command_buffer.bindIndexBuffer(*terrain->index_buffer, 0, vk::IndexType::eUint32);
|
||||
}
|
||||
|
||||
void CommandBuffer::bind(Q3BSP::BSP* bsp) {
|
||||
bind(*bsp->pipeline);
|
||||
bind(*bsp->vertex_buffer);
|
||||
command_buffer.bindIndexBuffer(*bsp->index_buffer, 0, vk::IndexType::eUint32);
|
||||
bind(bsp->pipeline->layout, bsp->pipeline->desc_set);
|
||||
}
|
||||
|
||||
|
||||
void CommandBuffer::draw(uint32_t vertex_count, uint32_t instance_count, uint32_t first_vertex, uint32_t first_instance) {
|
||||
command_buffer.draw(vertex_count, instance_count, first_vertex, first_instance);
|
||||
}
|
||||
|
||||
@ -7,11 +7,14 @@
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <Renderer/VertexBuffer.hpp>
|
||||
|
||||
#include <Scene/BSP.hpp>
|
||||
|
||||
struct Buffer;
|
||||
struct Image;
|
||||
struct GraphicsPipeline;
|
||||
struct ComputePipeline;
|
||||
struct VertexBuffer;
|
||||
struct Model;
|
||||
struct Terrain;
|
||||
|
||||
@ -30,9 +33,17 @@ struct CommandBuffer {
|
||||
|
||||
void bind(const GraphicsPipeline& pipeline);
|
||||
void bind(vk::PipelineLayout layout, vk::ArrayProxy<vk::DescriptorSet> desc_sets);
|
||||
void bind(const VertexBuffer& vertex_buffer, uint32_t binding = 0);
|
||||
template <typename Vertex>
|
||||
|
||||
void bind(const GeneralVertexBuffer<Vertex>& vertex_buffer, uint32_t binding = 0) {
|
||||
const std::array<vk::DeviceSize, 1> offsets = { 0 };
|
||||
|
||||
command_buffer.bindVertexBuffers(binding, vertex_buffer.buffer->buffer, offsets);
|
||||
}
|
||||
|
||||
void bind(std::shared_ptr<Model> model);
|
||||
void bind(Terrain* terrain);
|
||||
void bind(Q3BSP::BSP* bsp);
|
||||
|
||||
void draw(uint32_t vertex_count, uint32_t instance_count, uint32_t first_vertex = 0, uint32_t first_instance = 0);
|
||||
|
||||
|
||||
@ -9,7 +9,8 @@
|
||||
|
||||
#include <util/log.hpp>
|
||||
|
||||
GraphicsPipeline::GraphicsPipeline(vk::Device dev, const std::vector<Shader>& shaders, const vk::Extent2D& extent, const RenderPass& render_pass, vk::ArrayProxy<vk::DescriptorSetLayoutBinding> bindings, const VertexBuffer& vertex_buffer, enum Type type) : dev(dev) {
|
||||
|
||||
GraphicsPipeline::GraphicsPipeline(vk::Device dev, const std::vector<Shader>& shaders, const vk::Extent2D& extent, const RenderPass& render_pass, vk::ArrayProxy<vk::DescriptorSetLayoutBinding> bindings, const vk::VertexInputBindingDescription& vertex_binding, const std::vector<vk::VertexInputAttributeDescription>& vertex_attrs, enum Type type) : dev(dev) {
|
||||
/* create layout
|
||||
* Eventually add a graphicspipline constructor that allows specification of layouts etc
|
||||
* kinda like how Image::Image has all those versions
|
||||
@ -62,16 +63,14 @@ GraphicsPipeline::GraphicsPipeline(vk::Device dev, const std::vector<Shader>& sh
|
||||
|
||||
/* vertex input setup */
|
||||
const std::vector<vk::VertexInputBindingDescription> vertex_bindings = {
|
||||
vertex_buffer.binding(0),
|
||||
vertex_binding,
|
||||
};
|
||||
|
||||
auto attrs = vertex_buffer.attrs(0);
|
||||
|
||||
const auto vertex_input_info = vk::PipelineVertexInputStateCreateInfo{
|
||||
.vertexBindingDescriptionCount = static_cast<uint32_t>(vertex_bindings.size()),
|
||||
.pVertexBindingDescriptions = vertex_bindings.data(),
|
||||
.vertexAttributeDescriptionCount = static_cast<uint32_t>(attrs.size()),
|
||||
.pVertexAttributeDescriptions = attrs.data(),
|
||||
.vertexAttributeDescriptionCount = static_cast<uint32_t>(vertex_attrs.size()),
|
||||
.pVertexAttributeDescriptions = vertex_attrs.data(),
|
||||
};
|
||||
|
||||
const auto input_asm_info = vk::PipelineInputAssemblyStateCreateInfo{
|
||||
@ -93,9 +92,9 @@ GraphicsPipeline::GraphicsPipeline(vk::Device dev, const std::vector<Shader>& sh
|
||||
|
||||
const auto raster_info = vk::PipelineRasterizationStateCreateInfo {
|
||||
.depthClampEnable = vk::False,
|
||||
.polygonMode = type == Type::eGLTF? vk::PolygonMode::eFill : vk::PolygonMode::eLine,
|
||||
.cullMode = vk::CullModeFlagBits::eNone,
|
||||
.frontFace = Type::eGLTF ? vk::FrontFace::eClockwise : vk::FrontFace::eCounterClockwise,
|
||||
.polygonMode = vk::PolygonMode::eLine,
|
||||
.cullMode = vk::CullModeFlagBits::eBack,
|
||||
.frontFace = vk::FrontFace::eCounterClockwise,
|
||||
.depthBiasEnable = vk::False,
|
||||
.lineWidth = 1.0,
|
||||
};
|
||||
|
||||
@ -3,9 +3,10 @@
|
||||
#define VULKAN_HPP_NO_STRUCT_CONSTRUCTORS
|
||||
#include <vulkan/vulkan.hpp>
|
||||
|
||||
#include <Renderer/VertexBuffer.hpp>
|
||||
|
||||
struct Shader;
|
||||
struct UniformBuffer;
|
||||
struct VertexBuffer;
|
||||
struct RenderPass;
|
||||
struct Texture;
|
||||
|
||||
@ -18,7 +19,7 @@ struct GraphicsPipeline {
|
||||
GraphicsPipeline(vk::Device dev, const std::vector<Shader>& shaders,
|
||||
const vk::Extent2D& extent, const RenderPass& render_pass,
|
||||
vk::ArrayProxy<vk::DescriptorSetLayoutBinding> bindings,
|
||||
const VertexBuffer& vertex_buffer, enum Type type = Type::eGLTF);
|
||||
const vk::VertexInputBindingDescription& vertex_binding, const std::vector<vk::VertexInputAttributeDescription>& vertex_attrs, enum Type type = Type::eGLTF);
|
||||
vk::Device dev;
|
||||
vk::Pipeline pipeline;
|
||||
vk::PipelineLayout layout;
|
||||
|
||||
@ -200,7 +200,13 @@ Renderer::Renderer(Window& win) : win(win) {
|
||||
});
|
||||
|
||||
std::vector<Shader> shaders = {
|
||||
{dev, "assets/shaders/fraglight.vert.spv", vk::ShaderStageFlagBits::eVertex },
|
||||
{ dev, "assets/shaders/basic.vert.spv", vk::ShaderStageFlagBits::eVertex },
|
||||
{ dev, "assets/shaders/basic.geom.spv", vk::ShaderStageFlagBits::eGeometry },
|
||||
{ dev, "assets/shaders/basic.frag.spv", vk::ShaderStageFlagBits::eFragment },
|
||||
};
|
||||
|
||||
std::vector<Shader> model_shaders = {
|
||||
{ dev, "assets/shaders/fraglight.vert.spv", vk::ShaderStageFlagBits::eVertex },
|
||||
{ dev, "assets/shaders/fraglight.geom.spv", vk::ShaderStageFlagBits::eGeometry },
|
||||
{ dev, "assets/shaders/lambert.frag.spv", vk::ShaderStageFlagBits::eFragment },
|
||||
};
|
||||
@ -210,6 +216,22 @@ Renderer::Renderer(Window& win) : win(win) {
|
||||
textures[0].binding(1),
|
||||
};
|
||||
|
||||
/* Create default pipeline */
|
||||
vertex_buffer = std::make_unique<VertexBuffer>(phys_dev, dev, 0x1000000 / sizeof(BasicVertex));
|
||||
vertex_pipeline = std::make_unique<GraphicsPipeline>(dev, shaders, swapchain->extent, *render_pass, bindings, vertex_buffer->binding(0), vertex_buffer->attrs(0));
|
||||
vertex_pipeline->update(0, *uniform_buffer);
|
||||
vertex_pipeline->update(1, textures[1]);
|
||||
|
||||
/* BSP loader */
|
||||
bsp = std::make_unique<Q3BSP::BSP>(phys_dev, dev, "assets/maps/git.bsp");
|
||||
std::vector<Shader> bsp_shaders = {
|
||||
{ dev, "assets/shaders/bsp.vert.spv", vk::ShaderStageFlagBits::eVertex },
|
||||
{ dev, "assets/shaders/bsp.frag.spv", vk::ShaderStageFlagBits::eFragment },
|
||||
};
|
||||
bsp->pipeline = std::make_unique<GraphicsPipeline>(dev, bsp_shaders, swapchain->extent, *render_pass, bindings, bsp->vertex_buffer->binding(0), bsp->vertex_buffer->attrs(0));
|
||||
bsp->pipeline->update(0, *uniform_buffer);
|
||||
bsp->pipeline->update(1, textures[1]);
|
||||
|
||||
/* initialize models */
|
||||
Timer model_timer;
|
||||
models.push_back(std::make_shared<Model>(phys_dev, dev, "assets/models/dragon.gltf"));
|
||||
@ -217,10 +239,10 @@ Renderer::Renderer(Window& win) : win(win) {
|
||||
|
||||
Log::debug("Models loaded in %lf milliseconds\n", model_timer.read());
|
||||
|
||||
pipeline = std::make_unique<GraphicsPipeline>(dev, shaders, swapchain->extent, *render_pass, bindings, *models[0]->vertex_buffer);
|
||||
model_pipeline = std::make_unique<GraphicsPipeline>(dev, model_shaders, swapchain->extent, *render_pass, bindings, models[0]->vertex_buffer->binding(0), models[0]->vertex_buffer->attrs(0));
|
||||
|
||||
pipeline->update(0, *uniform_buffer);
|
||||
pipeline->update(1, textures[1]);
|
||||
model_pipeline->update(0, *uniform_buffer);
|
||||
model_pipeline->update(1, textures[1]);
|
||||
|
||||
/* create Terrain */
|
||||
terrain = std::make_unique<Terrain>(phys_dev, dev, textures[1]);
|
||||
@ -232,13 +254,17 @@ Renderer::Renderer(Window& win) : win(win) {
|
||||
{ dev, "assets/shaders/terrain.frag.spv", vk::ShaderStageFlagBits::eFragment },
|
||||
};
|
||||
|
||||
terrain_pipeline = std::make_unique<GraphicsPipeline>(dev, terrain_shaders, swapchain->extent, *render_pass, bindings, *terrain->vertex_buffer, GraphicsPipeline::eTERRAIN);
|
||||
terrain_pipeline = std::make_unique<GraphicsPipeline>(dev, terrain_shaders, swapchain->extent, *render_pass, bindings, terrain->vertex_buffer->binding(0), terrain->vertex_buffer->attrs(0), GraphicsPipeline::eTERRAIN);
|
||||
|
||||
terrain_pipeline->update(0, *uniform_buffer);
|
||||
terrain_pipeline->update(1, textures[1]);
|
||||
|
||||
for (auto& shader : shaders)
|
||||
shader.cleanup();
|
||||
for (auto& shader : bsp_shaders)
|
||||
shader.cleanup();
|
||||
for (auto& shader : model_shaders)
|
||||
shader.cleanup();
|
||||
for (auto& shader : terrain_shaders)
|
||||
shader.cleanup();
|
||||
|
||||
@ -338,7 +364,7 @@ void Renderer::draw() {
|
||||
|
||||
auto sz = win.getDimensions();
|
||||
|
||||
const auto p = glm::perspective(glm::radians(90.0f), static_cast<float>(sz.width) / static_cast<float>(sz.height), 0.01f, 2000.0f);
|
||||
const auto p = glm::perspective(glm::radians(90.0f), static_cast<float>(sz.width) / static_cast<float>(sz.height), 0.01f, 200000.0f);
|
||||
|
||||
auto uni = UniformData{
|
||||
.view = cam.view(),
|
||||
@ -355,6 +381,7 @@ void Renderer::draw() {
|
||||
|
||||
uniform_buffer->upload(uni);
|
||||
|
||||
/*
|
||||
command_buffer->bind(*terrain_pipeline);
|
||||
command_buffer->command_buffer.setViewport(0, viewport);
|
||||
command_buffer->command_buffer.setScissor(0, scissor);
|
||||
@ -363,10 +390,16 @@ void Renderer::draw() {
|
||||
command_buffer->bind(terrain.get());
|
||||
command_buffer->command_buffer.drawIndexed(terrain->indices.size(), 1, 0, 0, 0);
|
||||
|
||||
command_buffer->bind(*pipeline);
|
||||
command_buffer->bind(pipeline->layout, pipeline->desc_set);
|
||||
command_buffer->bind(*model_pipeline);
|
||||
command_buffer->bind(model_pipeline->layout, model_pipeline->desc_set);
|
||||
command_buffer->bind(models[0]);
|
||||
command_buffer->command_buffer.drawIndexed(models[0]->indices.size(), 10, 0, 0, 0);
|
||||
command_buffer->command_buffer.drawIndexed(models[0]->indices.size(), 10, 0, 0, 0);*/
|
||||
|
||||
bsp->load_indices(cam.pos);
|
||||
command_buffer->bind(bsp.get());
|
||||
command_buffer->command_buffer.setViewport(0, viewport);
|
||||
command_buffer->command_buffer.setScissor(0, scissor);
|
||||
command_buffer->command_buffer.drawIndexed(bsp->indices.size(), 1, 0, 0, 0);
|
||||
|
||||
/* draw User Interface stuff */
|
||||
ui->newFrame();
|
||||
@ -432,8 +465,10 @@ Renderer::~Renderer() {
|
||||
|
||||
uniform_buffer.reset();
|
||||
vertex_buffer.reset();
|
||||
vertex_pipeline.reset();
|
||||
terrain_pipeline.reset();
|
||||
pipeline.reset();
|
||||
model_pipeline.reset();
|
||||
bsp.reset();
|
||||
|
||||
|
||||
for (auto& tex : textures) {
|
||||
|
||||
@ -9,16 +9,19 @@
|
||||
#include <Renderer/CommandBuffer.hpp>
|
||||
#include <Renderer/RenderPass.hpp>
|
||||
|
||||
#include <Renderer/VertexBuffer.hpp>
|
||||
|
||||
#include <Scene/Camera.hpp>
|
||||
#include <Scene/BSP.hpp>
|
||||
#include <Scene/Terrain.hpp>
|
||||
|
||||
#include <Model/Model.hpp>
|
||||
|
||||
#include <UI/UI.hpp>
|
||||
|
||||
|
||||
struct Window;
|
||||
struct UniformBuffer;
|
||||
struct VertexBuffer;
|
||||
struct Texture;
|
||||
|
||||
/* Contains all of the Vulkan objects involved in rendering the scene */
|
||||
@ -48,12 +51,13 @@ struct Renderer {
|
||||
std::unique_ptr<CommandBuffer> command_buffer;
|
||||
std::unique_ptr<RenderPass> render_pass;
|
||||
|
||||
|
||||
std::unique_ptr<GraphicsPipeline> pipeline;
|
||||
std::unique_ptr<GraphicsPipeline> vertex_pipeline;
|
||||
std::unique_ptr<GraphicsPipeline> model_pipeline;
|
||||
std::unique_ptr<GraphicsPipeline> terrain_pipeline;
|
||||
std::unique_ptr<VertexBuffer> vertex_buffer;
|
||||
std::unique_ptr<UniformBuffer> uniform_buffer;
|
||||
|
||||
std::unique_ptr<Q3BSP::BSP> bsp;
|
||||
std::unique_ptr<Terrain> terrain;
|
||||
|
||||
std::vector<Texture> textures;
|
||||
|
||||
@ -1,19 +0,0 @@
|
||||
#include <Renderer/VertexBuffer.hpp>
|
||||
|
||||
#include <Memory/Buffer.hpp>
|
||||
|
||||
#include <tinygltf/tiny_gltf.h>
|
||||
|
||||
VertexBuffer::VertexBuffer(vk::PhysicalDevice phys_dev, vk::Device dev, size_t n_vertices) {
|
||||
buffer = std::make_unique<Buffer>(phys_dev, dev, n_vertices * sizeof(Vertex),
|
||||
vk::BufferUsageFlagBits::eVertexBuffer,
|
||||
vk::MemoryPropertyFlagBits::eHostCoherent | vk::MemoryPropertyFlagBits::eHostVisible);
|
||||
}
|
||||
|
||||
void VertexBuffer::upload(const std::vector<Vertex>& data) {
|
||||
buffer->upload(reinterpret_cast<const uint8_t*>(data.data()), sizeof(Vertex)*data.size());
|
||||
}
|
||||
|
||||
void VertexBuffer::upload(const tinygltf::Buffer& buff, const tinygltf::BufferView& view) {
|
||||
std::memcpy(buffer->p, buff.data.data() + view.byteOffset, view.byteLength);
|
||||
}
|
||||
@ -8,21 +8,55 @@
|
||||
|
||||
#include <tinygltf/tiny_gltf.h>
|
||||
|
||||
struct Vertex {
|
||||
struct BasicVertex {
|
||||
glm::vec3 pos;
|
||||
glm::vec3 norm;
|
||||
glm::vec2 uv;
|
||||
glm::vec3 color;
|
||||
|
||||
static inline std::vector<vk::VertexInputAttributeDescription> attrs(uint32_t binding) {
|
||||
return std::vector<vk::VertexInputAttributeDescription> {
|
||||
{
|
||||
.location = 0,
|
||||
.binding = binding,
|
||||
.format = vk::Format::eR32G32B32Sfloat,
|
||||
.offset = offsetof(BasicVertex, pos),
|
||||
}, {
|
||||
.location = 1,
|
||||
.binding = binding,
|
||||
.format = vk::Format::eR32G32B32Sfloat,
|
||||
.offset = offsetof(BasicVertex, norm),
|
||||
}, {
|
||||
.location = 2,
|
||||
.binding = binding,
|
||||
.format = vk::Format::eR32G32Sfloat,
|
||||
.offset = offsetof(BasicVertex, uv),
|
||||
}, {
|
||||
.location = 3,
|
||||
.binding = binding,
|
||||
.format = vk::Format::eR32G32B32Sfloat,
|
||||
.offset = offsetof(BasicVertex, color),
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
struct VertexBuffer {
|
||||
template<typename Vertex>
|
||||
struct GeneralVertexBuffer {
|
||||
std::unique_ptr<Buffer> buffer;
|
||||
|
||||
GeneralVertexBuffer(vk::PhysicalDevice phys_dev, vk::Device dev, size_t n_vertices) {
|
||||
buffer = std::make_unique<Buffer>(phys_dev, dev, n_vertices * sizeof(Vertex),
|
||||
vk::BufferUsageFlagBits::eVertexBuffer,
|
||||
vk::MemoryPropertyFlagBits::eHostCoherent | vk::MemoryPropertyFlagBits::eHostVisible);
|
||||
}
|
||||
|
||||
VertexBuffer(vk::PhysicalDevice phys_dev, vk::Device dev, size_t n_vertices);
|
||||
|
||||
void upload(const std::vector<Vertex>& data);
|
||||
void upload(const tinygltf::Buffer& buff, const tinygltf::BufferView& view);
|
||||
void upload(const std::vector<Vertex>& data) {
|
||||
buffer->upload(reinterpret_cast<const uint8_t*>(data.data()), sizeof(Vertex) * data.size());
|
||||
}
|
||||
void upload(const tinygltf::Buffer& buff, const tinygltf::BufferView& view) {
|
||||
std::memcpy(buffer->p, buff.data.data() + view.byteOffset, view.byteLength);
|
||||
}
|
||||
|
||||
inline vk::VertexInputBindingDescription binding(uint32_t binding, vk::ShaderStageFlags stages = vk::ShaderStageFlagBits::eVertex) const {
|
||||
return vk::VertexInputBindingDescription {
|
||||
@ -33,28 +67,9 @@ struct VertexBuffer {
|
||||
}
|
||||
|
||||
inline std::vector<vk::VertexInputAttributeDescription> attrs(uint32_t binding) const {
|
||||
return std::vector<vk::VertexInputAttributeDescription> {
|
||||
{
|
||||
.location = 0,
|
||||
.binding = binding,
|
||||
.format = vk::Format::eR32G32B32Sfloat,
|
||||
.offset = offsetof(Vertex, pos),
|
||||
}, {
|
||||
.location = 1,
|
||||
.binding = binding,
|
||||
.format = vk::Format::eR32G32B32Sfloat,
|
||||
.offset = offsetof(Vertex, norm),
|
||||
},{
|
||||
.location = 2,
|
||||
.binding = binding,
|
||||
.format = vk::Format::eR32G32Sfloat,
|
||||
.offset = offsetof(Vertex, uv),
|
||||
},{
|
||||
.location = 3,
|
||||
.binding = binding,
|
||||
.format = vk::Format::eR32G32B32Sfloat,
|
||||
.offset = offsetof(Vertex, color),
|
||||
}
|
||||
};
|
||||
return Vertex::attrs(binding);
|
||||
}
|
||||
};
|
||||
|
||||
using VertexBuffer = GeneralVertexBuffer<BasicVertex>;
|
||||
|
||||
|
||||
103
Scene/BSP.cpp
103
Scene/BSP.cpp
@ -1,8 +1,12 @@
|
||||
#include <Scene/BSP.hpp>
|
||||
|
||||
#include <Renderer/Pipeline.hpp>
|
||||
|
||||
#include <util/file.hpp>
|
||||
#include <util/log.hpp>
|
||||
|
||||
#include <set>
|
||||
|
||||
#include <cstring>
|
||||
|
||||
using namespace Q3BSP;
|
||||
@ -14,14 +18,93 @@ static inline void copy_data(void* file_data, std::string& dst, Lump& lump) {
|
||||
|
||||
template<typename T>
|
||||
static inline void copy_data(void* file_data, std::vector<T>& dst, Lump& lump) {
|
||||
Log::debug("Size: %zu (%zu)\n", lump.len/sizeof(T), lump.len);
|
||||
dst.resize(lump.len / sizeof(T));
|
||||
puts("ALLOC'D");
|
||||
//Log::debug("%p %p\n", dst.data(), (u8*)file_data + lump.offset);
|
||||
std::memcpy(dst.data(), ((u8*)file_data) + lump.offset, lump.len);
|
||||
}
|
||||
|
||||
BSP::BSP(const std::string& fname) : filename(fname) {
|
||||
void BSP::load_indices(const glm::vec3& cam_pos) {
|
||||
std::set<int> present_faces;
|
||||
std::vector<Face> visible_faces;
|
||||
auto leaf_idx = determine_leaf(cam_pos);
|
||||
if (leaf_idx == last_leaf)
|
||||
return;
|
||||
|
||||
last_leaf = leaf_idx;
|
||||
auto& cam_leaf = leafs[leaf_idx];
|
||||
|
||||
|
||||
std::vector<Leaf> visible_leafs;
|
||||
for (auto& leaf : leafs) {
|
||||
if (determine_visibility(cam_leaf.cluster_idx, leaf.cluster_idx))
|
||||
visible_leafs.push_back(leaf);
|
||||
}
|
||||
|
||||
Log::debug("%zu visible leafs.\n", visible_leafs.size());
|
||||
|
||||
for (const auto& leaf : visible_leafs) {
|
||||
// Log::debug("Faces: %zu vs %zu\n", leaf.first_leaf_face_idx + leaf.n_leaf_faces, faces.size());
|
||||
for (size_t i = 0; i < leaf.n_leaf_faces; i++) {
|
||||
auto idx = leaf_faces[leaf.first_leaf_face_idx + i].face_idx;
|
||||
if (present_faces.contains(idx))
|
||||
continue;
|
||||
|
||||
present_faces.insert(idx);
|
||||
// Log::debug("Face idx: %zu (v.s. %zu)\n", idx, faces.size());
|
||||
visible_faces.push_back(faces[idx]);
|
||||
}
|
||||
}
|
||||
Log::debug("%zu visible faces.\n", visible_leafs.size());
|
||||
|
||||
for (auto& face : visible_faces) {
|
||||
switch (face.type) {
|
||||
case Face::ePATCH:
|
||||
break;
|
||||
case Face::ePOLYGON:
|
||||
case Face::eMESH:
|
||||
for (size_t i = 0; i < face.n_mesh_vertices; i++)
|
||||
indices.push_back(face.first_vertex_idx + mesh_vertices[face.first_mesh_vertex_idx+i].idx);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
index_buffer->upload(indices);
|
||||
}
|
||||
|
||||
int BSP::determine_leaf(glm::vec3 cam_pos) {
|
||||
/* camera coordinate transformation */
|
||||
float tmp = cam_pos.y;
|
||||
cam_pos.y = cam_pos.z;
|
||||
cam_pos.z = tmp;
|
||||
|
||||
/* use SDF of planes to determine relative position with respect to partitioning planes */
|
||||
int idx = 0;
|
||||
/* positive values are node indices, negative values are leaf indices */
|
||||
while (idx >= 0) {
|
||||
const auto& plane = planes[nodes[idx].plane];
|
||||
const auto dist = glm::dot(plane.norm, cam_pos) - plane.dist;
|
||||
|
||||
if (dist >= 0)
|
||||
idx = nodes[idx].children[0];
|
||||
else
|
||||
idx = nodes[idx].children[1];
|
||||
}
|
||||
|
||||
return -idx - 1;
|
||||
}
|
||||
|
||||
|
||||
bool BSP::determine_visibility(int vis, int cluster) {
|
||||
if (vis_info.vectors.size() == 0 || vis < 0)
|
||||
return true;
|
||||
|
||||
int i = (vis * vis_info.sz_vectors) + (cluster >> 3);
|
||||
u8 set = vis_info.vectors[i];
|
||||
|
||||
return !!(set & (1 << (cluster & 0x7)));
|
||||
}
|
||||
|
||||
BSP::BSP(vk::PhysicalDevice phys_dev, vk::Device dev, const std::string& fname) : dev(dev), filename(fname) {
|
||||
file_data = file::slurpb(fname);
|
||||
Log::debug("File size: %zu\n", file_data.size());
|
||||
header = reinterpret_cast<Header*>(file_data.data());
|
||||
@ -32,11 +115,6 @@ BSP::BSP(const std::string& fname) : filename(fname) {
|
||||
Log::error("BSP file missing magic!\n");
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < std::size(header->lumps); i++) {
|
||||
Log::debug("%i: Offset: %u | Length: %u\n", i, header->lumps[i].offset, header->lumps[i].len);
|
||||
Log::debug("\tPointer: 0x%p\n", (u8*)file_data.data() + (size_t)header->lumps[i].offset);
|
||||
}
|
||||
|
||||
copy_data(file_data.data(), entities, header->entities);
|
||||
copy_data(file_data.data(), textures, header->textures);
|
||||
copy_data(file_data.data(), planes, header->planes);
|
||||
@ -56,7 +134,14 @@ BSP::BSP(const std::string& fname) : filename(fname) {
|
||||
|
||||
vis_info.sz_vectors = reinterpret_cast<u32*>(file_data.data() + header->vis_info.offset)[1];
|
||||
auto sz = header->vis_info.len;
|
||||
Log::debug("Size: %u\n", sz);
|
||||
vis_info.vectors.resize(sz);
|
||||
std::memcpy(vis_info.vectors.data(), file_data.data() + header->vis_info.offset + 2*sizeof(u32), sz);
|
||||
|
||||
vertex_buffer = std::make_unique<GeneralVertexBuffer<Vertex>>(phys_dev, dev, vertices.size());
|
||||
vertex_buffer->upload(vertices);
|
||||
|
||||
/* set limit at 256Mi indices */
|
||||
index_buffer = std::make_unique<Buffer>(phys_dev, dev, 0x1000000 * sizeof(u32),
|
||||
vk::BufferUsageFlagBits::eIndexBuffer, vk::MemoryPropertyFlagBits::eHostCoherent | vk::MemoryPropertyFlagBits::eHostVisible
|
||||
);
|
||||
}
|
||||
@ -6,6 +6,12 @@
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
#define VULKAN_HPP_NO_STRUCT_CONSTRUCTORS
|
||||
#include <vulkan/vulkan.hpp>
|
||||
|
||||
#include <Renderer/VertexBuffer.hpp>
|
||||
#include <Renderer/Pipeline.hpp>
|
||||
|
||||
/* contains loading functions for Quake III-style BSPs */
|
||||
namespace Q3BSP {
|
||||
struct Lump {
|
||||
@ -110,11 +116,42 @@ namespace Q3BSP {
|
||||
};
|
||||
|
||||
struct Vertex {
|
||||
glm::vec3 position;
|
||||
glm::vec2 tex_coords;
|
||||
glm::vec3 pos;
|
||||
glm::vec2 uv;
|
||||
glm::vec2 lightmap_coords;
|
||||
glm::vec3 normal;
|
||||
glm::vec3 norm;
|
||||
glm::u8vec4 color;
|
||||
|
||||
static inline std::vector<vk::VertexInputAttributeDescription> attrs(uint32_t binding) {
|
||||
return std::vector<vk::VertexInputAttributeDescription> {
|
||||
{
|
||||
.location = 0,
|
||||
.binding = binding,
|
||||
.format = vk::Format::eR32G32B32Sfloat,
|
||||
.offset = offsetof(Vertex, pos),
|
||||
}, {
|
||||
.location = 1,
|
||||
.binding = binding,
|
||||
.format = vk::Format::eR32G32Sfloat,
|
||||
.offset = offsetof(Vertex, uv),
|
||||
}, {
|
||||
.location = 2,
|
||||
.binding = binding,
|
||||
.format = vk::Format::eR32G32Sfloat,
|
||||
.offset = offsetof(Vertex, lightmap_coords),
|
||||
}, {
|
||||
.location = 3,
|
||||
.binding = binding,
|
||||
.format = vk::Format::eR32G32B32Sfloat,
|
||||
.offset = offsetof(Vertex, norm),
|
||||
}, {
|
||||
.location = 4,
|
||||
.binding = binding,
|
||||
.format = vk::Format::eR8G8B8A8Uint,
|
||||
.offset = offsetof(Vertex, color),
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
struct MeshVertex {
|
||||
@ -169,8 +206,13 @@ namespace Q3BSP {
|
||||
};
|
||||
|
||||
struct BSP {
|
||||
BSP(vk::PhysicalDevice phys_dev, vk::Device dev, const std::string& fname);
|
||||
void load_indices(const glm::vec3& cam_pos);
|
||||
int determine_leaf(glm::vec3 cam_pos);
|
||||
bool determine_visibility(int vis, int cluster);
|
||||
|
||||
vk::Device dev;
|
||||
Header* header;
|
||||
BSP(const std::string& fname);
|
||||
std::string filename;
|
||||
std::vector<u8> file_data;
|
||||
std::string entities;
|
||||
@ -190,5 +232,17 @@ namespace Q3BSP {
|
||||
std::vector<Lightmap> lightmaps;
|
||||
std::vector<Lightvol> lightvols;
|
||||
VisibilityInfo vis_info;
|
||||
|
||||
std::vector<u32> indices;
|
||||
std::unique_ptr<Buffer> index_buffer;
|
||||
std::unique_ptr<GeneralVertexBuffer<Vertex>> vertex_buffer;
|
||||
std::unique_ptr<GraphicsPipeline> pipeline;
|
||||
/* to eliminate needless re-loading*/
|
||||
int last_leaf = -0x1337;
|
||||
|
||||
~BSP() {
|
||||
vertex_buffer.reset();
|
||||
pipeline.reset();
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -34,7 +34,7 @@ Terrain::Terrain(vk::PhysicalDevice phys_dev, vk::Device dev, Texture& tex) : ph
|
||||
|
||||
for (size_t x = 0; x < patch_size; x++)
|
||||
for (size_t y = 0; y < patch_size; y++)
|
||||
vertices[x + y*patch_size] = (Vertex {
|
||||
vertices[x + y*patch_size] = (BasicVertex {
|
||||
.pos = glm::vec3(
|
||||
2.0f * x + 1.0f - patch_size,
|
||||
0.0f,
|
||||
|
||||
@ -16,7 +16,7 @@ struct Terrain {
|
||||
|
||||
std::unique_ptr<VertexBuffer> vertex_buffer;
|
||||
std::unique_ptr<Buffer> index_buffer;
|
||||
std::vector<Vertex> vertices;
|
||||
std::vector<BasicVertex> vertices;
|
||||
std::vector<uint32_t> indices;
|
||||
|
||||
Terrain(vk::PhysicalDevice phys_dev, vk::Device dev, Texture& hieghtmap);
|
||||
|
||||
BIN
assets/maps/git.bsp
Normal file
BIN
assets/maps/git.bsp
Normal file
Binary file not shown.
@ -5,8 +5,15 @@ layout (location = 1) in vec2 texCoord;
|
||||
layout (location = 0) out vec4 FragColor;
|
||||
|
||||
layout (set = 0, binding = 0) uniform Matrices {
|
||||
mat4 mvp;
|
||||
mat4 view;
|
||||
mat4 proj;
|
||||
float time;
|
||||
vec3 cam_pos;
|
||||
vec3 cam_dir;
|
||||
vec4 frustum[6];
|
||||
vec2 viewport;
|
||||
float tess_factor;
|
||||
float tess_edge_size;
|
||||
};
|
||||
|
||||
layout (set = 0, binding = 1) uniform sampler2D tex;
|
||||
|
||||
Binary file not shown.
@ -11,14 +11,21 @@ layout (location = 0) out vec3 _norm;
|
||||
layout (location = 1) out vec2 _texCoord;
|
||||
|
||||
layout (set = 0, binding = 0) uniform Matrices {
|
||||
mat4 mvp;
|
||||
mat4 view;
|
||||
mat4 proj;
|
||||
float time;
|
||||
vec3 cam_pos;
|
||||
vec3 cam_dir;
|
||||
vec4 frustum[6];
|
||||
vec2 viewport;
|
||||
float tess_factor;
|
||||
float tess_edge_size;
|
||||
};
|
||||
|
||||
|
||||
void main(void) {
|
||||
for(int i = 0; i < gl_in.length(); i++) {
|
||||
gl_Position = mvp * gl_in[i].gl_Position;
|
||||
gl_Position = proj * view * gl_in[i].gl_Position;
|
||||
_norm = norm[i];
|
||||
_texCoord = texCoord[i];
|
||||
EmitVertex();
|
||||
|
||||
Binary file not shown.
@ -7,12 +7,19 @@ layout (location = 0) out vec3 norm;
|
||||
layout (location = 1) out vec2 texCoord;
|
||||
|
||||
layout (set = 0, binding = 0) uniform Matrices {
|
||||
mat4 mvp;
|
||||
mat4 view;
|
||||
mat4 proj;
|
||||
float time;
|
||||
vec3 cam_pos;
|
||||
vec3 cam_dir;
|
||||
vec4 frustum[6];
|
||||
vec2 viewport;
|
||||
float tess_factor;
|
||||
float tess_edge_size;
|
||||
};
|
||||
|
||||
void main() {
|
||||
gl_Position = vec4(aPos + (vec3(10.0) * gl_InstanceIndex), 1.0);
|
||||
gl_Position = proj * view * vec4(aPos, 1.0);
|
||||
texCoord = aTexCoord;
|
||||
norm = aNorm;
|
||||
}
|
||||
Binary file not shown.
23
assets/shaders/bsp.frag
Normal file
23
assets/shaders/bsp.frag
Normal file
@ -0,0 +1,23 @@
|
||||
#version 460 core
|
||||
|
||||
layout (location = 0) in vec3 norm;
|
||||
layout (location = 1) in vec2 texCoord;
|
||||
layout (location = 0) out vec4 FragColor;
|
||||
|
||||
layout (set = 0, binding = 0) uniform Matrices {
|
||||
mat4 view;
|
||||
mat4 proj;
|
||||
float time;
|
||||
vec3 cam_pos;
|
||||
vec3 cam_dir;
|
||||
vec4 frustum[6];
|
||||
vec2 viewport;
|
||||
float tess_factor;
|
||||
float tess_edge_size;
|
||||
};
|
||||
|
||||
layout (set = 0, binding = 1) uniform sampler2D tex;
|
||||
|
||||
void main() {
|
||||
FragColor = vec4((abs(norm)*0.5+vec3(0.5)).r);
|
||||
}
|
||||
BIN
assets/shaders/bsp.frag.spv
Normal file
BIN
assets/shaders/bsp.frag.spv
Normal file
Binary file not shown.
33
assets/shaders/bsp.vert
Normal file
33
assets/shaders/bsp.vert
Normal file
@ -0,0 +1,33 @@
|
||||
#version 460 core
|
||||
layout (location = 0) in vec3 aPos;
|
||||
layout (location = 1) in vec2 aTexCoord;
|
||||
layout (location = 2) in vec2 lightmapCoord;
|
||||
layout (location = 3) in vec3 aNorm;
|
||||
|
||||
layout (location = 0) out vec3 norm;
|
||||
layout (location = 1) out vec2 texCoord;
|
||||
|
||||
layout (set = 0, binding = 0) uniform Matrices {
|
||||
mat4 view;
|
||||
mat4 proj;
|
||||
float time;
|
||||
vec3 cam_pos;
|
||||
vec3 cam_dir;
|
||||
vec4 frustum[6];
|
||||
vec2 viewport;
|
||||
float tess_factor;
|
||||
float tess_edge_size;
|
||||
};
|
||||
|
||||
void main() {
|
||||
mat4 zup_to_yup = mat4(
|
||||
1.0, 0.0, 0.0, 0.0,
|
||||
0.0, 0.0, 1.0, 0.0,
|
||||
0.0, 1.0, 0.0, 0.0,
|
||||
0.0, 0.0, 0.0, 1.0
|
||||
);
|
||||
|
||||
gl_Position = proj * view * zup_to_yup * vec4(aPos, 1.0);
|
||||
texCoord = aTexCoord;
|
||||
norm = aNorm;
|
||||
}
|
||||
BIN
assets/shaders/bsp.vert.spv
Normal file
BIN
assets/shaders/bsp.vert.spv
Normal file
Binary file not shown.
944
assets/textures/Rendering Quake 3 Maps.htm
Normal file
944
assets/textures/Rendering Quake 3 Maps.htm
Normal file
@ -0,0 +1,944 @@
|
||||
<html>
|
||||
<head><script src="//archive.org/includes/analytics.js?v=cf34f82" type="text/javascript"></script>
|
||||
<script type="text/javascript">window.addEventListener('DOMContentLoaded',function(){var v=archive_analytics.values;v.service='wb';v.server_name='wwwb-app217.us.archive.org';v.server_ms=413;archive_analytics.send_pageview({});});</script>
|
||||
<script type="text/javascript" src="https://web-static.archive.org/_static/js/bundle-playback.js?v=t1Bf4PY_" charset="utf-8"></script>
|
||||
<script type="text/javascript" src="https://web-static.archive.org/_static/js/wombat.js?v=txqj7nKC" charset="utf-8"></script>
|
||||
<script>window.RufflePlayer=window.RufflePlayer||{};window.RufflePlayer.config={"autoplay":"on","unmuteOverlay":"hidden"};</script>
|
||||
<script type="text/javascript" src="https://web-static.archive.org/_static/js/ruffle/ruffle.js"></script>
|
||||
<script type="text/javascript">
|
||||
__wm.init("https://web.archive.org/web");
|
||||
__wm.wombat("http://graphics.cs.brown.edu:80/games/quake/quake3.html","20160507164611","https://web.archive.org/","web","https://web-static.archive.org/_static/",
|
||||
"1462639571");
|
||||
</script>
|
||||
<link rel="stylesheet" type="text/css" href="https://web-static.archive.org/_static/css/banner-styles.css?v=S1zqJCYt" />
|
||||
<link rel="stylesheet" type="text/css" href="https://web-static.archive.org/_static/css/iconochive.css?v=qtvMKcIJ" />
|
||||
<!-- End Wayback Rewrite JS Include -->
|
||||
|
||||
<title>
|
||||
Rendering Quake 3 Maps
|
||||
</title>
|
||||
</head>
|
||||
|
||||
<body bgcolor="#FFFFFF"><!-- BEGIN WAYBACK TOOLBAR INSERT -->
|
||||
<script>__wm.rw(0);</script>
|
||||
<div id="wm-ipp-base" lang="en" style="display:none;direction:ltr;">
|
||||
<div id="wm-ipp" style="position:fixed;left:0;top:0;right:0;">
|
||||
<div id="donato" style="position:relative;width:100%;">
|
||||
<div id="donato-base">
|
||||
<iframe id="donato-if" src="https://archive.org/includes/donate.php?as_page=1&platform=wb&referer=https%3A//web.archive.org/web/20160507164611/http%3A//graphics.cs.brown.edu/games/quake/quake3.html"
|
||||
scrolling="no" frameborder="0" style="width:100%; height:100%">
|
||||
</iframe>
|
||||
</div>
|
||||
</div><div id="wm-ipp-inside">
|
||||
<div id="wm-toolbar" style="position:relative;display:flex;flex-flow:row nowrap;justify-content:space-between;">
|
||||
<div id="wm-logo" style="/*width:110px;*/padding-top:12px;">
|
||||
<a href="/web/" title="Wayback Machine home page"><img src="https://web-static.archive.org/_static/images/toolbar/wayback-toolbar-logo-200.png" srcset="https://web-static.archive.org/_static/images/toolbar/wayback-toolbar-logo-100.png, https://web-static.archive.org/_static/images/toolbar/wayback-toolbar-logo-150.png 1.5x, https://web-static.archive.org/_static/images/toolbar/wayback-toolbar-logo-200.png 2x" alt="Wayback Machine" style="width:100px" border="0" /></a>
|
||||
</div>
|
||||
<div class="c" style="display:flex;flex-flow:column nowrap;justify-content:space-between;flex:1;">
|
||||
<form class="u" style="display:flex;flex-direction:row;flex-wrap:nowrap;" target="_top" method="get" action="/web/submit" name="wmtb" id="wmtb"><input type="text" name="url" id="wmtbURL" value="http://graphics.cs.brown.edu/games/quake/quake3.html" onfocus="this.focus();this.select();" style="flex:1;"/><input type="hidden" name="type" value="replay" /><input type="hidden" name="date" value="20160507164611" /><input type="submit" value="Go" />
|
||||
</form>
|
||||
<div style="display:flex;flex-flow:row nowrap;align-items:flex-end;">
|
||||
<div class="s" id="wm-nav-captures" style="flex:1;">
|
||||
</div>
|
||||
<div class="k">
|
||||
<a href="" id="wm-graph-anchor">
|
||||
<div id="wm-ipp-sparkline" title="Explore captures for this URL" style="position: relative">
|
||||
<canvas id="wm-sparkline-canvas" width="725" height="27" border="0"></canvas>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="n">
|
||||
<table>
|
||||
<tbody>
|
||||
<!-- NEXT/PREV MONTH NAV AND MONTH INDICATOR -->
|
||||
<tr class="m">
|
||||
<td class="b" nowrap="nowrap"><a href="https://web.archive.org/web/20150624071647/http://graphics.cs.brown.edu:80/games/quake/quake3.html" title="24 Jun 2015"><strong>Jun</strong></a></td>
|
||||
<td class="c" id="displayMonthEl" title="You are here: 16:46:11 May 07, 2016">MAY</td>
|
||||
<td class="f" nowrap="nowrap"><a href="https://web.archive.org/web/20160607203752/http://graphics.cs.brown.edu:80/games/quake/quake3.html" title="07 Jun 2016"><strong>Jun</strong></a></td>
|
||||
</tr>
|
||||
<!-- NEXT/PREV CAPTURE NAV AND DAY OF MONTH INDICATOR -->
|
||||
<tr class="d">
|
||||
<td class="b" nowrap="nowrap"><a href="https://web.archive.org/web/20150624071647/http://graphics.cs.brown.edu:80/games/quake/quake3.html" title="07:16:47 Jun 24, 2015"><img src="https://web-static.archive.org/_static/images/toolbar/wm_tb_prv_on.png" alt="Previous capture" width="14" height="16" border="0" /></a></td>
|
||||
<td class="c" id="displayDayEl" style="width:34px;font-size:22px;white-space:nowrap;" title="You are here: 16:46:11 May 07, 2016">07</td>
|
||||
<td class="f" nowrap="nowrap"><a href="https://web.archive.org/web/20160607203752/http://graphics.cs.brown.edu:80/games/quake/quake3.html" title="20:37:52 Jun 07, 2016"><img src="https://web-static.archive.org/_static/images/toolbar/wm_tb_nxt_on.png" alt="Next capture" width="14" height="16" border="0" /></a></td>
|
||||
</tr>
|
||||
<!-- NEXT/PREV YEAR NAV AND YEAR INDICATOR -->
|
||||
<tr class="y">
|
||||
<td class="b" nowrap="nowrap"><a href="https://web.archive.org/web/20150422041533/http://graphics.cs.brown.edu:80/games/quake/quake3.html" title="22 Apr 2015"><strong>2015</strong></a></td>
|
||||
<td class="c" id="displayYearEl" title="You are here: 16:46:11 May 07, 2016">2016</td>
|
||||
<td class="f" nowrap="nowrap"><a href="https://web.archive.org/web/20170519174315/http://graphics.cs.brown.edu:80/games/quake/quake3.html" title="19 May 2017"><strong>2017</strong></a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="r" style="display:flex;flex-flow:column nowrap;align-items:flex-end;justify-content:space-between;">
|
||||
<div id="wm-btns" style="text-align:right;height:23px;">
|
||||
<span class="xxs">
|
||||
<div id="wm-save-snapshot-success">success</div>
|
||||
<div id="wm-save-snapshot-fail">fail</div>
|
||||
<a id="wm-save-snapshot-open" href="#" title="Share via My Web Archive" >
|
||||
<span class="iconochive-web"></span>
|
||||
</a>
|
||||
<a href="https://archive.org/account/login.php" title="Sign In" id="wm-sign-in">
|
||||
<span class="iconochive-person"></span>
|
||||
</a>
|
||||
<span id="wm-save-snapshot-in-progress" class="iconochive-web"></span>
|
||||
</span>
|
||||
<a class="xxs" href="http://faq.web.archive.org/" title="Get some help using the Wayback Machine" style="top:-6px;"><span class="iconochive-question" style="color:rgb(87,186,244);font-size:160%;"></span></a>
|
||||
<a id="wm-tb-close" href="#close" style="top:-2px;" title="Close the toolbar"><span class="iconochive-remove-circle" style="color:#888888;font-size:240%;"></span></a>
|
||||
</div>
|
||||
<div id="wm-share" class="xxs">
|
||||
<a href="/web/20160507164611/http://web.archive.org/screenshot/http://graphics.cs.brown.edu/games/quake/quake3.html"
|
||||
id="wm-screenshot"
|
||||
title="screenshot">
|
||||
<span class="wm-icon-screen-shot"></span>
|
||||
</a>
|
||||
<a href="#" id="wm-video" title="video">
|
||||
<span class="iconochive-movies"></span>
|
||||
</a>
|
||||
<a id="wm-share-facebook" href="#" data-url="https://web.archive.org/web/20160507164611/http://graphics.cs.brown.edu:80/games/quake/quake3.html" title="Share on Facebook" style="margin-right:5px;" target="_blank"><span class="iconochive-facebook" style="color:#3b5998;font-size:160%;"></span></a>
|
||||
<a id="wm-share-twitter" href="#" data-url="https://web.archive.org/web/20160507164611/http://graphics.cs.brown.edu:80/games/quake/quake3.html" title="Share on Twitter" style="margin-right:5px;" target="_blank"><span class="iconochive-twitter" style="color:#1dcaff;font-size:160%;"></span></a>
|
||||
</div>
|
||||
<div style="padding-right:2px;text-align:right;white-space:nowrap;">
|
||||
<a id="wm-expand" class="wm-btn wm-closed" href="#expand" onclick="__wm.ex(event);return false;"><span id="wm-expand-icon" class="iconochive-down-solid"></span> <span class="xxs" style="font-size:80%;">About this capture</span></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="wm-capinfo" style="border-top:1px solid #777;display:none; overflow: hidden">
|
||||
<div id="wm-capinfo-notice" source="api"></div>
|
||||
<div id="wm-capinfo-collected-by">
|
||||
<div style="background-color:#666;color:#fff;font-weight:bold;text-align:center">COLLECTED BY</div>
|
||||
<div style="padding:3px;position:relative" id="wm-collected-by-content">
|
||||
<div style="display:inline-block;vertical-align:top;width:50%;">
|
||||
<span class="c-logo" style="background-image:url(https://archive.org/services/img/alexacrawls);"></span>
|
||||
Organization: <a style="color:#33f;" href="https://archive.org/details/alexacrawls" target="_new"><span class="wm-title">Alexa Crawls</span></a>
|
||||
<div style="max-height:75px;overflow:hidden;position:relative;">
|
||||
<div style="position:absolute;top:0;left:0;width:100%;height:75px;background:linear-gradient(to bottom,rgba(255,255,255,0) 0%,rgba(255,255,255,0) 90%,rgba(255,255,255,255) 100%);"></div>
|
||||
Starting in 1996, <a href="http://www.alexa.com/">Alexa Internet</a> has been donating their crawl data to the Internet Archive. Flowing in every day, these data are added to the <a href="http://web.archive.org/">Wayback Machine</a> after an embargo period.
|
||||
</div>
|
||||
</div>
|
||||
<div style="display:inline-block;vertical-align:top;width:49%;">
|
||||
<span class="c-logo" style="background-image:url(https://archive.org/services/img/alexacrawls)"></span>
|
||||
<div>Collection: <a style="color:#33f;" href="https://archive.org/details/alexacrawls" target="_new"><span class="wm-title">Alexa Crawls</span></a></div>
|
||||
<div style="max-height:75px;overflow:hidden;position:relative;">
|
||||
<div style="position:absolute;top:0;left:0;width:100%;height:75px;background:linear-gradient(to bottom,rgba(255,255,255,0) 0%,rgba(255,255,255,0) 90%,rgba(255,255,255,255) 100%);"></div>
|
||||
Starting in 1996, <a href="http://www.alexa.com/">Alexa Internet</a> has been donating their crawl data to the Internet Archive. Flowing in every day, these data are added to the <a href="http://web.archive.org/">Wayback Machine</a> after an embargo period.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="wm-capinfo-timestamps">
|
||||
<div style="background-color:#666;color:#fff;font-weight:bold;text-align:center" title="Timestamps for the elements of this page">TIMESTAMPS</div>
|
||||
<div>
|
||||
<div id="wm-capresources" style="margin:0 5px 5px 5px;max-height:250px;overflow-y:scroll !important"></div>
|
||||
<div id="wm-capresources-loading" style="text-align:left;margin:0 20px 5px 5px;display:none"><img src="https://web-static.archive.org/_static/images/loading.gif" alt="loading" /></div>
|
||||
</div>
|
||||
</div>
|
||||
</div></div></div></div><div id="wm-ipp-print">The Wayback Machine - https://web.archive.org/web/20160507164611/http://graphics.cs.brown.edu:80/games/quake/quake3.html</div>
|
||||
<script type="text/javascript">//<![CDATA[
|
||||
__wm.bt(725,27,25,2,"web","http://graphics.cs.brown.edu/games/quake/quake3.html","20160507164611",1996,"https://web-static.archive.org/_static/",["https://web-static.archive.org/_static/css/banner-styles.css?v=S1zqJCYt","https://web-static.archive.org/_static/css/iconochive.css?v=qtvMKcIJ"], false);
|
||||
__wm.rw(1);
|
||||
//]]></script>
|
||||
<!-- END WAYBACK TOOLBAR INSERT -->
|
||||
|
||||
|
||||
<a name="#top">
|
||||
<b><font size="6">
|
||||
Rendering Quake 3 Maps
|
||||
</font></b>
|
||||
|
||||
<br>
|
||||
<br>
|
||||
Morgan McGuire
|
||||
<br>July 11, 2003
|
||||
<br>
|
||||
<br>
|
||||
|
||||
<!---------------------------------------------------------------------------->
|
||||
<a name="Intro">
|
||||
<p>
|
||||
<table border="1" cellpadding="0" cellspacing="0" width="100%" bgcolor="#ffffd0">
|
||||
<td width="100%">
|
||||
<table border="0" cellpadding="0" cellspacing="0" width="100%">
|
||||
<td>
|
||||
<b><font size="4">Introduction</font></b>
|
||||
</td>
|
||||
<td align="right">
|
||||
<a href="#top">[top]</a>
|
||||
</td>
|
||||
</table>
|
||||
</table>
|
||||
<p>
|
||||
This document describes how to render the basic geometry of a Quake 3
|
||||
map using OpenGL. It describes how to texture and lightmap this
|
||||
geometry, but does not describe how to render shaders, effects,
|
||||
characters, or movers (elevators and doors).
|
||||
<p>
|
||||
|
||||
This is intended as a sequel to Kekoa Proudfoot's <a href="https://web.archive.org/web/20160507164611/http://graphics.stanford.edu/~kekoa/q3/">Unofficial Quake 3 Map
|
||||
Specs</a> document, which describes how to parse BSP files. It
|
||||
therefore uses the same notation.
|
||||
|
||||
<p>This is an unofficial document. Quake 3 is a registered trademark
|
||||
of <a href="https://web.archive.org/web/20160507164611/http://www.idsoftware.com/">id Software</a>, which does
|
||||
not sponsor, authorize, or endorse this document.
|
||||
|
||||
<p>
|
||||
This document describes the Quake 3 BSP file format as the author
|
||||
understands it. While every effort has been made to ensure that the
|
||||
contents of this document are accurate, the author does not guarantee that
|
||||
any portion of this document is actually correct. In addition, the author
|
||||
cannot be held responsible the consequences of the any use or misuse of the
|
||||
information contained in this document.
|
||||
|
||||
<!---------------------------------------------------------------------------->
|
||||
<a name="Overview">
|
||||
<p>
|
||||
<table border="1" cellpadding="0" cellspacing="0" width="100%" bgcolor="#ffffd0">
|
||||
<td width="100%">
|
||||
<table border="0" cellpadding="0" cellspacing="0" width="100%">
|
||||
<td>
|
||||
<b><font size="4">Overview</font></b>
|
||||
</td>
|
||||
<td align="right">
|
||||
<a href="#top">[top]</a>
|
||||
</td>
|
||||
</table>
|
||||
</table>
|
||||
|
||||
<p>
|
||||
The rendering process has five steps:
|
||||
|
||||
<blockquote>
|
||||
<br>1. Determine the set of visible faces.
|
||||
<br>2. Partition the visible faces into <i>transparent</i> and <i>opaque</i> lists.
|
||||
<br>3. Clear the frame buffer and render the sky box
|
||||
<br>4. Render the opaque list in front-to-back order.
|
||||
<br>5. Render the transparent list in back-to-front order.
|
||||
</blockquote>
|
||||
|
||||
The first two steps do not involve any OpenGL calls. Step 3 renders a
|
||||
cube centered at the viewer with a pre-warped texture to create the
|
||||
illusion of a detailed 3D environment. The practice of creating and
|
||||
rendering sky boxes is discussed elsewhere and is not detailed further
|
||||
in this document. Steps 4 and 5 render the actual visible surfaces of
|
||||
the map. The opaque list contains triangles that will be rendered
|
||||
without alpha blending. It is sorted from front to back to take
|
||||
advantage of early-out depth tests on newer graphics hardware. The
|
||||
transparent list contains alpha blended surfaces which must be
|
||||
rendered from back to front to generate a correct image. A
|
||||
straightforward quicksort on the camera-space depth of first vertex of
|
||||
each surface is sufficient for these purposes. For the kinds of maps
|
||||
involved, splitting overlapping polygons for truly correct render
|
||||
order or using a radix sort for faster sorting will only increase the
|
||||
complexity of a renderer with improving the resulting image quality or
|
||||
frame rate.
|
||||
|
||||
<p>
|
||||
The <a href="#VisibleSurface">Visible Surface Determination</a>
|
||||
section explains how to use the BSP tree and
|
||||
precomputed visibility data to find visible faces.
|
||||
|
||||
<p>
|
||||
The remaining steps are straightforward and not discussed in detail,
|
||||
except for the actual face rendering. The vertex indexing scheme of
|
||||
Quake 3 files can be confusing because there are two levels of
|
||||
indirection. The <a href="#RenderingFaces">Rendering Faces</a>
|
||||
section explains how to generate triangle sets from
|
||||
the indices stored in a face.
|
||||
|
||||
<!---------------------------------------------------------------------------->
|
||||
<a name="Data">
|
||||
<p>
|
||||
<table border="1" cellpadding="0" cellspacing="0" width="100%" bgcolor="#ffffd0">
|
||||
<td width="100%">
|
||||
<table border="0" cellpadding="0" cellspacing="0" width="100%">
|
||||
<td>
|
||||
<b><font size="4">Data Structures</font></b>
|
||||
</td>
|
||||
<td align="right">
|
||||
<a href="#top">[top]</a>
|
||||
</td>
|
||||
</table>
|
||||
</table>
|
||||
|
||||
<p>
|
||||
A Quake 3 map contains multiple models. The first of these is always
|
||||
a static mesh that is the map itself and the remainders are "movers"
|
||||
like doors and elevators. This document is restricted to model[0]; it
|
||||
does not address the movers.
|
||||
|
||||
<p>
|
||||
Assume the in-memory data structures mimic those in the file
|
||||
structure, and that the overarching class is named Q3Map. There are a
|
||||
few cases where I used a Vector3 in this document instead of float[3]
|
||||
to make the code easier to read. Not all data from the file is used
|
||||
for rendering. For example, the brushs are used for collision
|
||||
detection but ignored during rendering. The subset of data used
|
||||
during rendering is (links are to Proudfoot's structure definitions):
|
||||
|
||||
<p>
|
||||
<table border="0" cellspacing="0" cellpadding="0" width="100%">
|
||||
<th align="left">Lump Index
|
||||
<th align="left">Lump Name
|
||||
<th align="left">Description
|
||||
<tr>
|
||||
|
||||
<tr><td valign="top">1<td valign="top"><a href="https://web.archive.org/web/20160507164611/http://graphics.stanford.edu/~kekoa/q3/#Textures">Textures</a>
|
||||
<td>Surface descriptions (assume these have been converted to OpenGL textures).
|
||||
|
||||
<tr><td valign="top">2<td valign="top"><a href="https://web.archive.org/web/20160507164611/http://graphics.stanford.edu/~kekoa/q3/#Planes">Planes</a>
|
||||
<td>Planes used by map geometry.
|
||||
|
||||
<tr><td valign="top">3<td valign="top"><a href="https://web.archive.org/web/20160507164611/http://graphics.stanford.edu/~kekoa/q3/#Nodes">Nodes</a>
|
||||
<td>BSP tree nodes.
|
||||
|
||||
<tr><td valign="top">4<td valign="top"><a href="https://web.archive.org/web/20160507164611/http://graphics.stanford.edu/~kekoa/q3/#Leaves">Leaves</a>
|
||||
<td>BSP tree leaves.
|
||||
|
||||
<tr><td valign="top">5<td valign="top"><a href="https://web.archive.org/web/20160507164611/http://graphics.stanford.edu/~kekoa/q3/#Leaffaces">Leaffaces</a>
|
||||
<td>Lists of face indices, one list per leaf.
|
||||
|
||||
<tr><td valign="top">7<td valign="top"><a href="https://web.archive.org/web/20160507164611/http://graphics.stanford.edu/~kekoa/q3/#Models">Models</a>
|
||||
<td>Descriptions of rigid world geometry in map (we only use model[0]).
|
||||
|
||||
<tr><td valign="top">10<td valign="top"><a href="https://web.archive.org/web/20160507164611/http://graphics.stanford.edu/~kekoa/q3/#Vertexes">Vertexes</a>
|
||||
<td>Vertices used to describe faces.
|
||||
|
||||
<tr><td valign="top">11<td valign="top"><a href="https://web.archive.org/web/20160507164611/http://graphics.stanford.edu/~kekoa/q3/#Meshverts">Meshverts</a>
|
||||
<td>Lists of offsets, one list per mesh.
|
||||
|
||||
<tr><td valign="top">13<td valign="top"><a href="https://web.archive.org/web/20160507164611/http://graphics.stanford.edu/~kekoa/q3/#Faces">Faces</a>
|
||||
<td>Surface geometry.
|
||||
|
||||
<tr><td valign="top">14<td valign="top"><a href="https://web.archive.org/web/20160507164611/http://graphics.stanford.edu/~kekoa/q3/#Lightmaps">Lightmaps</a>
|
||||
<td>Packed lightmap data (assume these have been converted to an OpenGL texture)
|
||||
|
||||
<tr><td valign="top">16<td valign="top"><a href="https://web.archive.org/web/20160507164611/http://graphics.stanford.edu/~kekoa/q3/#Visdata">Visdata</a>
|
||||
<td>Cluster-cluster visibility data.
|
||||
</table>
|
||||
|
||||
<p>
|
||||
|
||||
There are additional data used during rendering that do not appear in
|
||||
the file. These are:
|
||||
|
||||
<p>
|
||||
<a name="Camera">
|
||||
<b><font size="4">
|
||||
Camera camera
|
||||
</font></b>
|
||||
<p>
|
||||
A camera description that contains viewer position and frustum
|
||||
parameters. The Camera class must have accessors for these parameters
|
||||
and a method, isVisible(float min[3], float max[3]). This method
|
||||
returns true if the world space bounding box with the specified
|
||||
corners has non-zero intersection with the camera's view frustum.
|
||||
<p>
|
||||
|
||||
<a name="AlreadyVisible">
|
||||
<b><font size="4">
|
||||
Set<int> alreadyVisible
|
||||
</font></b>
|
||||
|
||||
<p>Set of indices of faces that are already visible. This is used to
|
||||
prevent the same face from being rendered multiple times. A general
|
||||
set implementation is not necessary. Because the face indices are
|
||||
consecutive integers, a bit-set can provide an efficient
|
||||
implementation.
|
||||
</td></tr>
|
||||
<p>
|
||||
|
||||
<a name="VisibleFace">
|
||||
<b><font size="4">
|
||||
Array<int> visibleFace
|
||||
</font></b>
|
||||
<p>
|
||||
Set of indices of faces that are visible; that is, the members of the
|
||||
alreadyVisible set. For efficiency this is maintained as a separate
|
||||
array instead of iterating through the set.
|
||||
<p>
|
||||
|
||||
<a name="Patch">
|
||||
<b><font size="4">
|
||||
Array<Patch> patch
|
||||
</font></b>
|
||||
<p>
|
||||
Patches are <a href="https://web.archive.org/web/20160507164611/http://graphics.stanford.edu/~kekoa/q3/#Faces">Faces</a> that
|
||||
describe sets of biquadratic Bezier surfaces. Each Patch contains an
|
||||
array of <a href="Bezier">Bezier</a> instances, which are described later
|
||||
in this document.
|
||||
<p>
|
||||
These Beziers are tessellated into triangles during loading so they
|
||||
can be rendered as triangle strips. Your implementation must create
|
||||
this tessellation and add an index into the patch array for patch
|
||||
faces.
|
||||
<p>
|
||||
|
||||
<!---------------------------------------------------------------------------->
|
||||
<a name="Coordinates">
|
||||
<p>
|
||||
<table border="1" cellpadding="0" cellspacing="0" width="100%" bgcolor="#ffffd0">
|
||||
<td width="100%">
|
||||
<table border="0" cellpadding="0" cellspacing="0" width="100%">
|
||||
<td>
|
||||
<b><font size="4">Coordinate System</font></b>
|
||||
</td>
|
||||
<td align="right">
|
||||
<a href="#top">[top]</a>
|
||||
</td>
|
||||
</table>
|
||||
</table>
|
||||
|
||||
<p>
|
||||
Quake 3 uses a coordinate system where the x-axis points East, the
|
||||
y-axis points South, and the z-axis points vertically downward. If
|
||||
you prefer a coordinate system where the y-axis points vertically
|
||||
upward and the z-axis points South, you can use the following function
|
||||
to convert from Quake 3 coordinates to your coordinate system.
|
||||
|
||||
<p>
|
||||
<table bgcolor="#E5EEEE" width="75%" align="CENTER"><tr><td>
|
||||
<blockquote><pre>
|
||||
|
||||
void swizzle(Vector3& v) {
|
||||
float temp = v.y;
|
||||
v.y = v.z;
|
||||
v.z = -temp;
|
||||
}</pre></blockquote></td></tr></table>
|
||||
|
||||
<p>
|
||||
|
||||
When swizzling data, you must convert the vertex positions, vertex
|
||||
normals, plane normals, and all bounding box min and max vectors. The
|
||||
Quake coordinate system is also scaled so that one meter is about 0.03
|
||||
units. You may wish to change this scale factor. If you scale vertex
|
||||
positions remember to also scale plane distances, and min and max
|
||||
vectors appropriately.
|
||||
<p>
|
||||
|
||||
Depending on the conventions of your rendering system, you may also
|
||||
want to invert Quake's lightmap texture coordinates to (1 - s, 1 - t)
|
||||
or (s, 1 - t). It is usually easy to tell when light map texture
|
||||
coordinates need to be inverted by looking at a rendering.
|
||||
|
||||
<p>
|
||||
|
||||
<!---------------------------------------------------------------------------->
|
||||
<a name="VisibleSurface">
|
||||
<p>
|
||||
<table border="1" cellpadding="0" cellspacing="0" width="100%" bgcolor="#ffffd0">
|
||||
<td width="100%">
|
||||
<table border="0" cellpadding="0" cellspacing="0" width="100%">
|
||||
<td>
|
||||
<b><font size="4">Visible Face Determination</font></b>
|
||||
</td>
|
||||
<td align="right">
|
||||
<a href="#top">[top]</a>
|
||||
</td>
|
||||
</table>
|
||||
</table>
|
||||
|
||||
<p>
|
||||
The input to the visible face determination step is the camera (and
|
||||
the map). The output is the visibleFace array, which contains the
|
||||
indices of all faces that are potentially visible to that camera.
|
||||
During the step, the alreadyVisible set is used to prevent a face
|
||||
index from being added to the visibleFace array more than once.
|
||||
|
||||
<p>
|
||||
Two notes before we look at this process in more detail. First, the
|
||||
output is an array of <i>potentially</i> visible faces. A z-buffer
|
||||
test and frustum clipping (both typically provided by hardware) are
|
||||
still needed to generate the exactly visible set. Second,
|
||||
as Max McGuire says in his <a href="https://web.archive.org/web/20160507164611/http://www.flipcode.com/tutorials/tut_q2levels.shtml">Quake 2 BSP File Format</a>,
|
||||
|
||||
<blockquote><blockquote>Many people incorrectly associate the BSP tree with the visibility
|
||||
algorithm used by Quake and similar engines. As described above, the
|
||||
visible surface determination is done using a precomputed PVS. The BSP
|
||||
tree is primarily used to divide the map into regions and to quickly
|
||||
determine which region the camera is in. As a result, it isn't that
|
||||
fundamental to any of the rendering algorithms used in Quake and any
|
||||
data structure giving a spatial subdivision (like an octree or a k-D
|
||||
tree) could be used instead. BSP trees are very simple however, and
|
||||
they are useful for some of the other non-rendering tasks in the Quake
|
||||
engine.
|
||||
</blockquote></blockquote>
|
||||
|
||||
<p>
|
||||
|
||||
|
||||
To determine the set of visible faces:
|
||||
|
||||
<blockquote>
|
||||
1. <a href="#VisFindCluster">Find <i>visCluster</i></a>, the index of the cluster containing the camera position.
|
||||
<br>2. <a href="#SelectVisibleLeaves">Select all leaves visible from that cluster</a>.
|
||||
<br>3. <a href="#FaceIterate">Iterate through all faces in those clusters</a>.
|
||||
</blockquote>
|
||||
|
||||
<p>
|
||||
<a name="VisFindCluster">
|
||||
<p>
|
||||
<b><font size="4">
|
||||
Find the camera cluster (visCluster)
|
||||
</font></b>
|
||||
<p>
|
||||
Recall that a Quake 3 map is divided into convex spaces called
|
||||
<i>leaves</i>. Adjacent leaves are joined into <i>clusters</i>. The
|
||||
map file contains precomputed visibility information at the cluster
|
||||
level, which is stored in the <a href="https://web.archive.org/web/20160507164611/http://graphics.stanford.edu/~kekoa/q3/#Visdata">visData</a> bit
|
||||
array.
|
||||
|
||||
<p>
|
||||
|
||||
The index of the cluster containing the camera is
|
||||
<code>leaf[index].cluster</code>, where <i>index</i> is the index of
|
||||
the leaf containing the camera. To find the index of the leaf
|
||||
containing the camera, walk the BSP tree.
|
||||
<p>
|
||||
The root node is index 0 in the nodeArray. Each node has a splitting
|
||||
plane associated with it. This plane divides space into two child
|
||||
subnodes. If the camera lies in front of the splitting plane, recurse
|
||||
into the front node. Otherwise recurse into the back node. We repeat
|
||||
this process until a BSP leaf is reached.
|
||||
<p>
|
||||
In the Node data structure, a leaf is denoted by a negative child node
|
||||
index. To convert the negative index into a legal leaf index, negate
|
||||
and subtract one.
|
||||
<p>
|
||||
|
||||
The following function takes a camera position as input. It walks the
|
||||
BSP tree until a leaf is found and then returns the index of that
|
||||
leaf. Remember that this is return value is a leaf index, not a
|
||||
cluster index. The cluster index is stored in the leaf.
|
||||
|
||||
<p>
|
||||
<table bgcolor="#E5EEEE" width="75%" align="CENTER"><tr><td>
|
||||
<blockquote><pre>
|
||||
|
||||
int Q3Map::findLeaf(const Vector3& camPos) const {
|
||||
|
||||
int index = 0;
|
||||
|
||||
while (index >= 0) {
|
||||
const Node& node = nodeArray[index];
|
||||
const Plane& plane = planeArray[node.plane];
|
||||
|
||||
// Distance from point to a plane
|
||||
const double distance =
|
||||
plane.normal.dot(camPos) - plane.distance;
|
||||
|
||||
if (distance >= 0) {
|
||||
index = node.front;
|
||||
} else {
|
||||
index = node.back;
|
||||
}
|
||||
}
|
||||
|
||||
return -index - 1;
|
||||
}
|
||||
</pre></blockquote></td></tr></table>
|
||||
|
||||
<p>
|
||||
<a name="SelectVisibleLeaves">
|
||||
<p>
|
||||
<b><font size="4">
|
||||
Select visible leaves
|
||||
</font></b>
|
||||
<p>
|
||||
|
||||
To find all visible leaves, iterate through the entire leaf array and
|
||||
cull all leaves that are not in visible clusters or are outside the
|
||||
view frustum. The visible cluster test should be performed first
|
||||
because it is very efficient and more likely to fail.
|
||||
|
||||
<p>
|
||||
|
||||
The visData bit array contains precomputed visibility data between
|
||||
clusters. If the cluster with index <i>a</i> can potentially be seen
|
||||
by a viewer in the cluster with index <i>b</i>, then bit (a + b *
|
||||
visData.sz_vecs * 8) of visData.vecs has value 1. Otherwise, that bit
|
||||
has value 0.
|
||||
|
||||
<p>
|
||||
|
||||
The following function uses bitwise operators to efficiently extract
|
||||
the relevant bit. The inputs are the index of the cluster containing
|
||||
the camera and the index of the cluster to be tested. The function
|
||||
returns true if the test cluster is potentially visible, otherwise it
|
||||
returns false. A call to the function typically looks like
|
||||
<code>isClusterVisible(visCluster, leaf[L].cluster)</code>.
|
||||
|
||||
<p>
|
||||
<table bgcolor="#E5EEEE" width="75%" align="CENTER"><tr><td>
|
||||
<blockquote><pre>
|
||||
|
||||
bool Q3Map::isClusterVisible(int visCluster, int testCluster) const {
|
||||
|
||||
if ((visData.bitsets == NULL) || (visCluster < 0)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
int i = (visCluster * visData.bytesPerCluster) + (testCluster >> 3);
|
||||
uint8 visSet = visData.bitsets[i];
|
||||
|
||||
return (visSet & (1 << (testCluster & 7))) != 0;
|
||||
}
|
||||
</pre></blockquote></td></tr></table>
|
||||
<p>
|
||||
|
||||
In the function, the expression (testCluster >> 3) computes
|
||||
(testCluster / 8), i.e. the byte within visData that contains
|
||||
information about the given cluster. The expression (1 <<
|
||||
(testCluster & 7)) creates a bit mask that selects bit (testCluster
|
||||
mod 8) within that byte.
|
||||
<p>
|
||||
|
||||
The visData information only considers the position of the viewer and
|
||||
not the orientation. Orientation is handled by the frustum culling
|
||||
step. The leaf contains two corners of its bounding box, min and max.
|
||||
If <code>camera.isVisible(leaf[L].min, leaf[L].max)</code> returns
|
||||
false, the leaf should be dropped from consideration because it is
|
||||
outside the view frustum. Note that some of the faces in the leaf are
|
||||
also in adjacent leaves and may therefore still be visible-- the other
|
||||
leaves will take care of that when we iterate through them.
|
||||
|
||||
<p>
|
||||
|
||||
<p>
|
||||
<a name="FaceIterate">
|
||||
<p>
|
||||
<b><font size="4">
|
||||
Iterate through faces
|
||||
</font></b>
|
||||
<p>
|
||||
A leaf contains all faces that have non-zero intersection with the
|
||||
leaf volume. The faces in leaf with index L have indices
|
||||
<code>leaf[L].firstFace</code> through <code>(leaf[L].firstFace +
|
||||
leaf[L].facesCount - 1)</code>.
|
||||
|
||||
<p>
|
||||
|
||||
Because a face may protrude out of the leaf, the same face may be in
|
||||
multiple leaves. Use the <a href="#AlreadyVisible">alreadyVisible</a>
|
||||
set to avoid touching the same face twice. A simple code snippet for
|
||||
this is:
|
||||
|
||||
<p>
|
||||
<table bgcolor="#E5EEEE" width="75%" align="CENTER"><tr><td>
|
||||
<blockquote><pre>
|
||||
|
||||
for (int i = 0; i < leaf[L].facesCount; ++i) {
|
||||
const int f = i + leaf[L].firstFace;
|
||||
if (! alreadyVisible.contains(f)) {
|
||||
alreadyVisible.insert(f);
|
||||
visibleFaces.append(f);
|
||||
}
|
||||
}
|
||||
</pre></blockquote></td></tr></table>
|
||||
<p>
|
||||
|
||||
<p>
|
||||
|
||||
<!---------------------------------------------------------------------------->
|
||||
<a name="#RenderingFaces">
|
||||
<p>
|
||||
<table border="1" cellpadding="0" cellspacing="0" width="100%" bgcolor="#ffffd0">
|
||||
<td width="100%">
|
||||
<table border="0" cellpadding="0" cellspacing="0" width="100%">
|
||||
<td>
|
||||
<b><font size="4">Rendering Faces</font></b>
|
||||
</td>
|
||||
<td align="right">
|
||||
<a href="#top">[top]</a>
|
||||
</td>
|
||||
</table>
|
||||
</table>
|
||||
|
||||
<p>
|
||||
|
||||
There are four kinds of <a href="https://web.archive.org/web/20160507164611/http://graphics.stanford.edu/~kekoa/q3/#Faces">Faces</a> in a
|
||||
Quake 3 map: polygons, patches, meshes, and billboards. Polygons and
|
||||
meshes are collections of triangles. A patch is a bezier-spline patch that
|
||||
must be tessellated into triangles for rendering. Billboards are polygons
|
||||
whose orientation changes to always face the viewer.
|
||||
|
||||
<p>
|
||||
|
||||
<a href="#RenderMesh">Polygons and meshes</a> are rendered in the same
|
||||
manner. The position, texture coordinate, and lightmap coordinate are
|
||||
stored in the vertex array. Using OpenGL vertex arrays, these can be
|
||||
referenced with a single index by setting the active vertex pointer
|
||||
and tex coord pointers into the same array, offset by the memory
|
||||
location within a Vertex for each type of coordinate.
|
||||
|
||||
<p>
|
||||
<a href="#RenderPatch">Patches</a> are tessellated into triangles,
|
||||
either during loading or per-frame, and rendered as triangle strips.
|
||||
The tessellation process creates vertices that did not exist in the
|
||||
original mesh, so each patch contains its own vertex array instead
|
||||
of using the global one stored in the map file.
|
||||
|
||||
<p>
|
||||
|
||||
Although handling the shaders and effects that can be stored in Quake
|
||||
3 maps is more complicated, simple alpha blending can be supported to
|
||||
render translucent surfaces correctly. When a texture contains an
|
||||
alpha channel, enable blending and select the
|
||||
<code>glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)</code>
|
||||
blending mode. Alpha blended faces should not be backfaced culled;
|
||||
they appear to have only one polygon for both sides (there is probably
|
||||
a two-sided polygon flag somewhere that is the correct place to obtain
|
||||
such information).
|
||||
|
||||
<p>
|
||||
<a name="RenderMesh">
|
||||
<p>
|
||||
<b><font size="4">
|
||||
Render a mesh
|
||||
</font></b>
|
||||
<p>
|
||||
|
||||
Each face mesh, <i>curFace</i> of type Face describes a mesh
|
||||
containing (curFace.meshVertexesCount / 3) triangles. The indices
|
||||
into the vertex array for the vertices of these triangles are
|
||||
themselves indirected. The <a href="https://web.archive.org/web/20160507164611/http://graphics.stanford.edu/~kekoa/q3/#Meshverts>">meshVertex</a>
|
||||
array stores the indices of the vertices, in the correct order to
|
||||
create triangle lists. For a given face, these are
|
||||
<code>meshVertex[curFace.firstMeshVertex]</code> through
|
||||
<code>meshVertex[curFace.firstMeshVertex + curFace.mushVertexesCount -
|
||||
1]</code>. The meshVertex values are also offet by
|
||||
<code>curFace.firstVertex</code>.
|
||||
<p>
|
||||
|
||||
This indexing scheme, although confusing, is arranged conveniently for
|
||||
using glDrawElements to render the triangle lists. The following code
|
||||
renders a mesh using this function.
|
||||
|
||||
<p>
|
||||
|
||||
<table bgcolor="#E5EEEE" width="75%" align="CENTER"><tr><td>
|
||||
<blockquote><pre>
|
||||
|
||||
const Face& curFace = face[visibleFace[f]];
|
||||
static const stride = sizeof(Vertex); // BSP Vertex, not float[3]
|
||||
const int offset = face.firstVertex;
|
||||
|
||||
glVertexPointer(3, GL_FLOAT, stride, &(vertex[offset].position));
|
||||
|
||||
glClientActiveTextureARB(GL_TEXTURE0_ARB);
|
||||
glTexCoordPointer(2, GL_FLOAT, stride, &(vertex[offset].textureCoord));
|
||||
|
||||
glClientActiveTextureARB(GL_TEXTURE1_ARB);
|
||||
glTexCoordPointer(2, GL_FLOAT, stride, &(vertex[offset].lightmapCoord));
|
||||
|
||||
glDrawElements(GL_TRIANGLES, curFace.meshVertexesCount,
|
||||
GL_UNSIGNED_INT, &meshVertex;[curFace.firstMeshVertex]);
|
||||
</pre></blockquote></td></tr></table>
|
||||
<p>
|
||||
|
||||
In the above code, the firstMeshVertex offset is applied directly to
|
||||
the vertex pointer since there is no other provision for offsetting
|
||||
the indices with glDrawElements.
|
||||
|
||||
<a name="RenderPatch">
|
||||
<p>
|
||||
<b><font size="4">
|
||||
Render a patch
|
||||
</font></b>
|
||||
<p>
|
||||
|
||||
Patches are surfaces defined by an array of Bezier curves. These
|
||||
curves are represented by the following data structure.
|
||||
<p>
|
||||
<a name="Bezier">
|
||||
<table bgcolor="#E5EEEE" width="75%" align="CENTER"><tr><td>
|
||||
<blockquote><pre>
|
||||
class Bezier {
|
||||
private:
|
||||
int level;
|
||||
Array<Vertex> vertex;
|
||||
Array<uint32> indexes;
|
||||
Array<int32> trianglesPerRow;
|
||||
Array<uint32*> rowIndexes;
|
||||
|
||||
public:
|
||||
Vertex controls[9];
|
||||
|
||||
void tessellate(int level);
|
||||
void render();
|
||||
};
|
||||
</pre></blockquote></td></tr></table>
|
||||
<p>
|
||||
|
||||
The controls array contains the 9 control points for this curve. The
|
||||
Beziers form a grid within a patch, so adjacent Beziers will share
|
||||
three of these. The Bezier curves must be tessellated prior to
|
||||
rendering. The <i>level</i> of the tessellation is the number of
|
||||
edges into which each side of a 2D curve is subdivided. The total
|
||||
number of triangles in the tessellation is <code>(2 * pow(level,
|
||||
2))</code>. The remainder of the private data is the tessellation,
|
||||
stored in a form we can pass directly to glMultiDrawElements. The
|
||||
pointers in the rowIndexes array point into the indexes array; they
|
||||
are not referring to separately allocated memory.
|
||||
|
||||
<p>
|
||||
The tessellate method computes the private data for rendering from the
|
||||
control points (which must themselves be set up during loading of the
|
||||
containing patch). Any number between five and 10 is a reasonable
|
||||
subdivision level for most maps. The intent of subdivision is to
|
||||
provide smoother curves on faster machines by increasing the level at
|
||||
runtime. Another use for subdivision is to allocate more polygons to
|
||||
larger curves-- implementors are free to provide their own metric for
|
||||
choosing a good subdivision level.
|
||||
<p>
|
||||
|
||||
The following is an implementation of the tessellate method with
|
||||
structure based on the tessellator in the Paul Baker's <a href="https://web.archive.org/web/20160507164611/http://users.ox.ac.uk/~univ1234/opengl/octagon/octagon.htm">Octagon</a> project.</i>
|
||||
|
||||
<p>
|
||||
<table bgcolor="#E5EEEE" width="75%" align="CENTER"><tr><td>
|
||||
<blockquote><pre>
|
||||
|
||||
void Bezier::tessellate(int L) {
|
||||
level = L;
|
||||
|
||||
// The number of vertices along a side is 1 + num edges
|
||||
const int L1 = L + 1;
|
||||
|
||||
vertex.resize(L1 * L1);
|
||||
|
||||
// Compute the vertices
|
||||
int i;
|
||||
|
||||
for (i = 0; i <= L; ++i) {
|
||||
double a = (double)i / L;
|
||||
double b = 1 - a;
|
||||
|
||||
vertex[i] =
|
||||
controls[0] * (b * b) +
|
||||
controls[3] * (2 * b * a) +
|
||||
controls[6] * (a * a);
|
||||
}
|
||||
|
||||
for (i = 1; i <= L; ++i) {
|
||||
double a = (double)i / L;
|
||||
double b = 1.0 - a;
|
||||
|
||||
BSPVertex temp[3];
|
||||
|
||||
int j;
|
||||
for (j = 0; j < 3; ++j) {
|
||||
int k = 3 * j;
|
||||
temp[j] =
|
||||
controls[k + 0] * (b * b) +
|
||||
controls[k + 1] * (2 * b * a) +
|
||||
controls[k + 2] * (a * a);
|
||||
}
|
||||
|
||||
for(j = 0; j <= L; ++j) {
|
||||
double a = (double)j / L;
|
||||
double b = 1.0 - a;
|
||||
|
||||
vertex[i * L1 + j]=
|
||||
temp[0] * (b * b) +
|
||||
temp[1] * (2 * b * a) +
|
||||
temp[2] * (a * a);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Compute the indices
|
||||
int row;
|
||||
indexes.resize(L * (L + 1) * 2);
|
||||
|
||||
for (row = 0; row < L; ++row) {
|
||||
for(int col = 0; col <= L; ++col) {
|
||||
indexes[(row * (L + 1) + col) * 2 + 1] = row * L1 + col;
|
||||
indexes[(row * (L + 1) + col) * 2] = (row + 1) * L1 + col;
|
||||
}
|
||||
}
|
||||
|
||||
trianglesPerRow.resize(L);
|
||||
rowIndexes.resize(L);
|
||||
for (row = 0; row < L; ++row) {
|
||||
trianglesPerRow[row] = 2 * L1;
|
||||
rowIndexes[row] = &indexes;[row * 2 * L1];
|
||||
}
|
||||
|
||||
}</pre></blockquote></td></tr></table>
|
||||
<p>
|
||||
|
||||
Once constructed, this data can be rendered directly with vertex arrays:
|
||||
<p>
|
||||
|
||||
<table bgcolor="#E5EEEE" width="75%" align="CENTER"><tr><td>
|
||||
<blockquote><pre>
|
||||
|
||||
void Bezier::render() {
|
||||
glVertexPointer(3, GL_FLOAT,sizeof(BSPVertex), &vertex;[0].position);
|
||||
|
||||
glClientActiveTextureARB(GL_TEXTURE0_ARB);
|
||||
glTexCoordPointer(2, GL_FLOAT,sizeof(BSPVertex), &vertex;[0].textureCoord);
|
||||
|
||||
glClientActiveTextureARB(GL_TEXTURE1_ARB);
|
||||
glTexCoordPointer(2, GL_FLOAT, sizeof(BSPVertex), &vertex;[0].lightmapCoord);
|
||||
|
||||
glMultiDrawElementsEXT(GL_TRIANGLE_STRIP, trianglesPerRow.getCArray(),
|
||||
GL_UNSIGNED_INT, (const void **)(rowIndexes.getCArray()), patch.level);
|
||||
}</pre></blockquote></td></tr></table>
|
||||
|
||||
<p>
|
||||
<hr>
|
||||
<table border="0" width="100%" cellspacing="0" cellpadding="0">
|
||||
<td align="left" valign="top">
|
||||
<em><font size="3">Copyright © 2003 Morgan McGuire. All rights
|
||||
reserved.</font></em>
|
||||
<td align="right" valign="top">
|
||||
<em><font size="3">morgan@cs.brown.edu</font></em>
|
||||
</table>
|
||||
|
||||
<p>
|
||||
<b>Acknowledgements</b>
|
||||
<br>Kris Taeleman answered a question while I was working on this document, and I used the following resources:
|
||||
<br>Max McGuire's <a href="https://web.archive.org/web/20160507164611/http://www.flipcode.com/tutorials/tut_q2levels.shtml">Quake 2 BSP File Format</a>,
|
||||
<br>Kekoa Proudfoot's
|
||||
<a href="https://web.archive.org/web/20160507164611/http://graphics.stanford.edu/~kekoa/q3/">Unofficial Quake 3 Map Specs</a>,
|
||||
<br>Ben "Digiben" Humphrey's <a href="https://web.archive.org/web/20160507164611/http://www.misofruit.co.kr/seojewoo/programming/opengl/Quake3Format.htm">Unofficial Quake 3 BSP Format</a>,
|
||||
<br>Nathan Ostgard's <a href="https://web.archive.org/web/20160507164611/http://www.nathanostgard.com/tutorials/quake3/collision/">Quake 3 BSP Collision Detection</a>,
|
||||
<br>Leonardo Boselli's <a href="https://web.archive.org/web/20160507164611/http://sourceforge.net/projects/apocalyx/">Apocalyx</a> source code,
|
||||
<br>Paul Baker's <a href="https://web.archive.org/web/20160507164611/http://users.ox.ac.uk/~univ1234/opengl/octagon/octagon.htm">Octagon</a> source code,
|
||||
<br>The Aside Software <a href="https://web.archive.org/web/20160507164611/http://talika.eii.us.es/~titan/titan/rnews.html">Titan</a> source code,
|
||||
<br>The <a href="https://web.archive.org/web/20160507164611/http://etud.epita.fr:8000/~bilalt_j/q3tools/q3radiant_man/q3rmanhtml.htm">Q3Radient Manual</a>
|
||||
|
||||
|
||||
<p>
|
||||
<font size="2">
|
||||
Keywords: quake 3 quake3 q3 arena quake3arena q3arena map bsp file render opengl spec specs format vertex order
|
||||
</font>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
<!--
|
||||
FILE ARCHIVED ON 16:46:11 May 07, 2016 AND RETRIEVED FROM THE
|
||||
INTERNET ARCHIVE ON 21:12:17 Feb 18, 2024.
|
||||
JAVASCRIPT APPENDED BY WAYBACK MACHINE, COPYRIGHT INTERNET ARCHIVE.
|
||||
|
||||
ALL OTHER CONTENT MAY ALSO BE PROTECTED BY COPYRIGHT (17 U.S.C.
|
||||
SECTION 108(a)(3)).
|
||||
-->
|
||||
<!--
|
||||
playback timings (ms):
|
||||
exclusion.robots: 2.144 (7)
|
||||
exclusion.robots.policy: 2.002 (7)
|
||||
cdx.remote: 0.758 (7)
|
||||
esindex: 0.111 (7)
|
||||
LoadShardBlock: 1023.154 (24)
|
||||
PetaboxLoader3.datanode: 713.073 (25)
|
||||
PetaboxLoader3.resolve: 88.188 (2)
|
||||
load_resource: 93.243
|
||||
-->
|
||||
507
assets/textures/Rendering Quake 3 Maps_files/banner-styles.css
Normal file
507
assets/textures/Rendering Quake 3 Maps_files/banner-styles.css
Normal file
@ -0,0 +1,507 @@
|
||||
@import 'record.css'; /* for SPN1 */
|
||||
|
||||
#wm-ipp-base {
|
||||
height:65px;/* initial height just in case js code fails */
|
||||
padding:0;
|
||||
margin:0;
|
||||
border:none;
|
||||
background:none transparent;
|
||||
}
|
||||
#wm-ipp {
|
||||
z-index: 2147483647;
|
||||
}
|
||||
#wm-ipp, #wm-ipp * {
|
||||
font-family:Lucida Grande, Helvetica, Arial, sans-serif;
|
||||
font-size:12px;
|
||||
line-height:1.2;
|
||||
letter-spacing:0;
|
||||
width:auto;
|
||||
height:auto;
|
||||
max-width:none;
|
||||
max-height:none;
|
||||
min-width:0 !important;
|
||||
min-height:0;
|
||||
outline:none;
|
||||
float:none;
|
||||
text-align:left;
|
||||
border:none;
|
||||
color: #000;
|
||||
text-indent: 0;
|
||||
position: initial;
|
||||
background: none;
|
||||
}
|
||||
#wm-ipp div, #wm-ipp canvas {
|
||||
display: block;
|
||||
}
|
||||
#wm-ipp div, #wm-ipp tr, #wm-ipp td, #wm-ipp a, #wm-ipp form {
|
||||
padding:0;
|
||||
margin:0;
|
||||
border:none;
|
||||
border-radius:0;
|
||||
background-color:transparent;
|
||||
background-image:none;
|
||||
/*z-index:2147483640;*/
|
||||
height:auto;
|
||||
}
|
||||
#wm-ipp table {
|
||||
border:none;
|
||||
border-collapse:collapse;
|
||||
margin:0;
|
||||
padding:0;
|
||||
width:auto;
|
||||
font-size:inherit;
|
||||
}
|
||||
#wm-ipp form input {
|
||||
padding:1px !important;
|
||||
height:auto;
|
||||
display:inline;
|
||||
margin:0;
|
||||
color: #000;
|
||||
background: none #fff;
|
||||
border: 1px solid #666;
|
||||
}
|
||||
#wm-ipp form input[type=submit] {
|
||||
padding:0 8px !important;
|
||||
margin:1px 0 1px 5px !important;
|
||||
width:auto !important;
|
||||
border: 1px solid #000 !important;
|
||||
background: #fff !important;
|
||||
color: #000 !important;
|
||||
}
|
||||
#wm-ipp form input[type=submit]:hover {
|
||||
background: #eee !important;
|
||||
cursor: pointer !important;
|
||||
}
|
||||
#wm-ipp form input[type=submit]:active {
|
||||
transform: translateY(1px);
|
||||
}
|
||||
#wm-ipp a {
|
||||
display: inline;
|
||||
}
|
||||
#wm-ipp a:hover{
|
||||
text-decoration:underline;
|
||||
}
|
||||
#wm-ipp a.wm-btn:hover {
|
||||
text-decoration:none;
|
||||
color:#ff0 !important;
|
||||
}
|
||||
#wm-ipp a.wm-btn:hover span {
|
||||
color:#ff0 !important;
|
||||
}
|
||||
#wm-ipp #wm-ipp-inside {
|
||||
margin: 0 6px;
|
||||
border:5px solid #000;
|
||||
border-top:none;
|
||||
background-color:rgba(255,255,255,0.9);
|
||||
-moz-box-shadow:1px 1px 4px #333;
|
||||
-webkit-box-shadow:1px 1px 4px #333;
|
||||
box-shadow:1px 1px 4px #333;
|
||||
border-radius:0 0 8px 8px;
|
||||
}
|
||||
/* selectors are intentionally verbose to ensure priority */
|
||||
#wm-ipp #wm-logo {
|
||||
padding:0 10px;
|
||||
vertical-align:middle;
|
||||
min-width:100px;
|
||||
flex: 0 0 100px;
|
||||
}
|
||||
#wm-ipp .c {
|
||||
padding-left: 4px;
|
||||
}
|
||||
#wm-ipp .c .u {
|
||||
margin-top: 4px !important;
|
||||
}
|
||||
#wm-ipp .n {
|
||||
padding:0 0 0 5px !important;
|
||||
vertical-align: bottom;
|
||||
}
|
||||
#wm-ipp .n a {
|
||||
text-decoration:none;
|
||||
color:#33f;
|
||||
font-weight:bold;
|
||||
}
|
||||
#wm-ipp .n .b {
|
||||
padding:0 6px 0 0 !important;
|
||||
text-align:right !important;
|
||||
overflow:visible;
|
||||
white-space:nowrap;
|
||||
color:#99a;
|
||||
vertical-align:middle;
|
||||
}
|
||||
#wm-ipp .n .y .b {
|
||||
padding:0 6px 2px 0 !important;
|
||||
}
|
||||
#wm-ipp .n .c {
|
||||
background:#000;
|
||||
color:#ff0;
|
||||
font-weight:bold;
|
||||
padding:0 !important;
|
||||
text-align:center;
|
||||
}
|
||||
#wm-ipp.hi .n td.c {
|
||||
color:#ec008c;
|
||||
}
|
||||
#wm-ipp .n td.f {
|
||||
padding:0 0 0 6px !important;
|
||||
text-align:left !important;
|
||||
overflow:visible;
|
||||
white-space:nowrap;
|
||||
color:#99a;
|
||||
vertical-align:middle;
|
||||
}
|
||||
#wm-ipp .n tr.m td {
|
||||
text-transform:uppercase;
|
||||
white-space:nowrap;
|
||||
padding:2px 0;
|
||||
}
|
||||
#wm-ipp .c .s {
|
||||
padding:0 5px 0 0 !important;
|
||||
vertical-align:bottom;
|
||||
}
|
||||
#wm-ipp #wm-nav-captures {
|
||||
white-space: nowrap;
|
||||
}
|
||||
#wm-ipp .c .s a.t {
|
||||
color:#33f;
|
||||
font-weight:bold;
|
||||
line-height: 1.8;
|
||||
}
|
||||
#wm-ipp .c .s div.r {
|
||||
color: #666;
|
||||
font-size:9px;
|
||||
white-space:nowrap;
|
||||
}
|
||||
#wm-ipp .c .k {
|
||||
padding-bottom:1px;
|
||||
}
|
||||
#wm-ipp .c .s {
|
||||
padding:0 5px 2px 0 !important;
|
||||
}
|
||||
#wm-ipp td#displayMonthEl {
|
||||
padding: 2px 0 !important;
|
||||
}
|
||||
#wm-ipp td#displayYearEl {
|
||||
padding: 0 0 2px 0 !important;
|
||||
}
|
||||
|
||||
div#wm-ipp-sparkline {
|
||||
position:relative;/* for positioning markers */
|
||||
white-space:nowrap;
|
||||
background-color:#fff;
|
||||
cursor:pointer;
|
||||
line-height:0.9;
|
||||
}
|
||||
#sparklineImgId, #wm-sparkline-canvas {
|
||||
position:relative;
|
||||
z-index:9012;
|
||||
max-width:none;
|
||||
}
|
||||
#wm-ipp-sparkline div.yt {
|
||||
position:absolute;
|
||||
z-index:9010 !important;
|
||||
background-color:#ff0 !important;
|
||||
top: 0;
|
||||
}
|
||||
#wm-ipp-sparkline div.mt {
|
||||
position:absolute;
|
||||
z-index:9013 !important;
|
||||
background-color:#ec008c !important;
|
||||
top: 0;
|
||||
}
|
||||
#wm-ipp .r {
|
||||
margin-left: 4px;
|
||||
}
|
||||
#wm-ipp .r a {
|
||||
color:#33f;
|
||||
border:none;
|
||||
position:relative;
|
||||
background-color:transparent;
|
||||
background-repeat:no-repeat !important;
|
||||
background-position:100% 100% !important;
|
||||
text-decoration: none;
|
||||
}
|
||||
#wm-ipp #wm-capinfo {
|
||||
/* prevents notice div background from sticking into round corners of
|
||||
#wm-ipp-inside */
|
||||
border-radius: 0 0 4px 4px;
|
||||
}
|
||||
#wm-ipp #wm-capinfo .c-logo {
|
||||
display:block;
|
||||
float:left;
|
||||
margin-right:3px;
|
||||
width:90px;
|
||||
min-height:90px;
|
||||
max-height: 290px;
|
||||
border-radius:45px;
|
||||
overflow:hidden;
|
||||
background-position:50%;
|
||||
background-size:auto 90px;
|
||||
box-shadow: 0 0 2px 2px rgba(208,208,208,128) inset;
|
||||
}
|
||||
#wm-ipp #wm-capinfo .c-logo span {
|
||||
display:inline-block;
|
||||
}
|
||||
#wm-ipp #wm-capinfo .c-logo img {
|
||||
height:90px;
|
||||
position:relative;
|
||||
left:-50%;
|
||||
}
|
||||
#wm-ipp #wm-capinfo .wm-title {
|
||||
font-size:130%;
|
||||
}
|
||||
#wm-ipp #wm-capinfo a.wm-selector {
|
||||
display:inline-block;
|
||||
color: #aaa;
|
||||
text-decoration:none !important;
|
||||
padding: 2px 8px;
|
||||
}
|
||||
#wm-ipp #wm-capinfo a.wm-selector.selected {
|
||||
background-color:#666;
|
||||
}
|
||||
#wm-ipp #wm-capinfo a.wm-selector:hover {
|
||||
color: #fff;
|
||||
}
|
||||
#wm-ipp #wm-capinfo.notice-only #wm-capinfo-collected-by,
|
||||
#wm-ipp #wm-capinfo.notice-only #wm-capinfo-timestamps {
|
||||
display: none;
|
||||
}
|
||||
#wm-ipp #wm-capinfo #wm-capinfo-notice .wm-capinfo-content {
|
||||
background-color:#ff0;
|
||||
padding:5px;
|
||||
font-size:14px;
|
||||
text-align:center;
|
||||
}
|
||||
#wm-ipp #wm-capinfo #wm-capinfo-notice .wm-capinfo-content * {
|
||||
font-size:14px;
|
||||
text-align:center;
|
||||
}
|
||||
#wm-ipp #wm-expand {
|
||||
right: 1px;
|
||||
bottom: -1px;
|
||||
color: #ffffff;
|
||||
background-color: #666 !important;
|
||||
padding:0 5px 0 3px !important;
|
||||
border-radius: 3px 3px 0 0 !important;
|
||||
}
|
||||
#wm-ipp #wm-expand span {
|
||||
color: #ffffff;
|
||||
}
|
||||
#wm-ipp #wm-expand #wm-expand-icon {
|
||||
display: inline-block;
|
||||
transition: transform 0.5s;
|
||||
transform-origin: 50% 45%;
|
||||
}
|
||||
#wm-ipp #wm-expand.wm-open #wm-expand-icon {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
#wm-ipp #wmtb {
|
||||
text-align:right;
|
||||
}
|
||||
#wm-ipp #wmtb #wmtbURL {
|
||||
width: calc(100% - 45px);
|
||||
}
|
||||
#wm-ipp #wm-graph-anchor {
|
||||
border-right:1px solid #ccc;
|
||||
}
|
||||
/* time coherence */
|
||||
html.wb-highlight {
|
||||
box-shadow: inset 0 0 0 3px #a50e3a !important;
|
||||
}
|
||||
.wb-highlight {
|
||||
outline: 3px solid #a50e3a !important;
|
||||
}
|
||||
#wm-ipp-print {
|
||||
display:none !important;
|
||||
}
|
||||
@media print {
|
||||
#wm-ipp-base {
|
||||
display:none !important;
|
||||
}
|
||||
#wm-ipp-print {
|
||||
display:block !important;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
@media (max-width:414px) {
|
||||
#wm-ipp .xxs {
|
||||
display:none !important;
|
||||
}
|
||||
}
|
||||
@media (min-width:1055px) {
|
||||
#wm-ipp #wm-graph-anchor {
|
||||
display:block !important;
|
||||
}
|
||||
}
|
||||
@media (max-width:1054px) {
|
||||
#wm-ipp #wm-graph-anchor {
|
||||
display:none !important;
|
||||
}
|
||||
}
|
||||
@media (max-width:1163px) {
|
||||
#wm-logo {
|
||||
display:none !important;
|
||||
}
|
||||
}
|
||||
|
||||
#wm-btns {
|
||||
white-space: nowrap;
|
||||
margin-top: -2px;
|
||||
}
|
||||
|
||||
#wm-btns #wm-save-snapshot-open {
|
||||
margin-right: 7px;
|
||||
top: -6px;
|
||||
}
|
||||
|
||||
#wm-btns #wm-sign-in {
|
||||
box-sizing: content-box;
|
||||
display: none;
|
||||
margin-right: 7px;
|
||||
top: -8px;
|
||||
|
||||
/*
|
||||
round border around sign in button
|
||||
*/
|
||||
border: 2px #000 solid;
|
||||
border-radius: 14px;
|
||||
padding-right: 2px;
|
||||
padding-bottom: 2px;
|
||||
width: 11px;
|
||||
height: 11px;
|
||||
}
|
||||
|
||||
#wm-btns #wm-sign-in>.iconochive-person {
|
||||
font-size: 12.5px;
|
||||
}
|
||||
|
||||
#wm-save-snapshot-open > .iconochive-web {
|
||||
color:#000;
|
||||
font-size:160%;
|
||||
}
|
||||
|
||||
#wm-ipp #wm-share {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
#wm-share > #wm-screenshot {
|
||||
display: inline-block;
|
||||
margin-right: 3px;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
#wm-screenshot > .iconochive-image {
|
||||
color:#000;
|
||||
font-size:160%;
|
||||
}
|
||||
|
||||
#wm-share > #wm-video {
|
||||
display: inline-block;
|
||||
margin-right: 3px;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
#wm-video > .iconochive-movies {
|
||||
color: #000;
|
||||
display: inline-block;
|
||||
font-size: 150%;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
#wm-btns #wm-save-snapshot-in-progress {
|
||||
display: none;
|
||||
font-size:160%;
|
||||
opacity: 0.5;
|
||||
position: relative;
|
||||
margin-right: 7px;
|
||||
top: -5px;
|
||||
}
|
||||
|
||||
#wm-btns #wm-save-snapshot-success {
|
||||
display: none;
|
||||
color: green;
|
||||
position: relative;
|
||||
top: -7px;
|
||||
}
|
||||
|
||||
#wm-btns #wm-save-snapshot-fail {
|
||||
display: none;
|
||||
color: red;
|
||||
position: relative;
|
||||
top: -7px;
|
||||
}
|
||||
|
||||
.wm-icon-screen-shot {
|
||||
background: url("../images/web-screenshot.svg") no-repeat !important;
|
||||
background-size: contain !important;
|
||||
width: 22px !important;
|
||||
height: 19px !important;
|
||||
|
||||
display: inline-block;
|
||||
}
|
||||
#donato {
|
||||
/* transition effect is disable so as to simplify height adjustment */
|
||||
/*transition: height 0.5s;*/
|
||||
height: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border-bottom: 1px solid #999 !important;
|
||||
}
|
||||
body.wm-modal {
|
||||
height: auto !important;
|
||||
overflow: hidden !important;
|
||||
}
|
||||
#donato #donato-base {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
/*bottom: 0;*/
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
position: absolute;
|
||||
z-index: 2147483639;
|
||||
}
|
||||
body.wm-modal #donato #donato-base {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 2147483640;
|
||||
}
|
||||
|
||||
.wb-autocomplete-suggestions {
|
||||
font-family: Lucida Grande, Helvetica, Arial, sans-serif;
|
||||
font-size: 12px;
|
||||
text-align: left;
|
||||
cursor: default;
|
||||
border: 1px solid #ccc;
|
||||
border-top: 0;
|
||||
background: #fff;
|
||||
box-shadow: -1px 1px 3px rgba(0,0,0,.1);
|
||||
position: absolute;
|
||||
display: none;
|
||||
z-index: 2147483647;
|
||||
max-height: 254px;
|
||||
overflow: hidden;
|
||||
overflow-y: auto;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.wb-autocomplete-suggestion {
|
||||
position: relative;
|
||||
padding: 0 .6em;
|
||||
line-height: 23px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
font-size: 1.02em;
|
||||
color: #333;
|
||||
}
|
||||
.wb-autocomplete-suggestion b {
|
||||
font-weight: bold;
|
||||
}
|
||||
.wb-autocomplete-suggestion.selected {
|
||||
background: #f0f0f0;
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
116
assets/textures/Rendering Quake 3 Maps_files/iconochive.css
Normal file
116
assets/textures/Rendering Quake 3 Maps_files/iconochive.css
Normal file
@ -0,0 +1,116 @@
|
||||
@font-face{font-family:'Iconochive-Regular';src:url('https://archive.org/includes/fonts/Iconochive-Regular.eot?-ccsheb');src:url('https://archive.org/includes/fonts/Iconochive-Regular.eot?#iefix-ccsheb') format('embedded-opentype'),url('https://archive.org/includes/fonts/Iconochive-Regular.woff?-ccsheb') format('woff'),url('https://archive.org/includes/fonts/Iconochive-Regular.ttf?-ccsheb') format('truetype'),url('https://archive.org/includes/fonts/Iconochive-Regular.svg?-ccsheb#Iconochive-Regular') format('svg');font-weight:normal;font-style:normal}
|
||||
[class^="iconochive-"],[class*=" iconochive-"]{font-family:'Iconochive-Regular'!important;speak:none;font-style:normal;font-weight:normal;font-variant:normal;text-transform:none;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}
|
||||
.iconochive-Uplevel:before{content:"\21b5"}
|
||||
.iconochive-exit:before{content:"\1f6a3"}
|
||||
.iconochive-beta:before{content:"\3b2"}
|
||||
.iconochive-logo:before{content:"\1f3db"}
|
||||
.iconochive-audio:before{content:"\1f568"}
|
||||
.iconochive-movies:before{content:"\1f39e"}
|
||||
.iconochive-software:before{content:"\1f4be"}
|
||||
.iconochive-texts:before{content:"\1f56e"}
|
||||
.iconochive-etree:before{content:"\1f3a4"}
|
||||
.iconochive-image:before{content:"\1f5bc"}
|
||||
.iconochive-web:before{content:"\1f5d4"}
|
||||
.iconochive-collection:before{content:"\2211"}
|
||||
.iconochive-folder:before{content:"\1f4c2"}
|
||||
.iconochive-data:before{content:"\1f5c3"}
|
||||
.iconochive-tv:before{content:"\1f4fa"}
|
||||
.iconochive-article:before{content:"\1f5cf"}
|
||||
.iconochive-question:before{content:"\2370"}
|
||||
.iconochive-question-dark:before{content:"\3f"}
|
||||
.iconochive-info:before{content:"\69"}
|
||||
.iconochive-info-small:before{content:"\24d8"}
|
||||
.iconochive-comment:before{content:"\1f5e9"}
|
||||
.iconochive-comments:before{content:"\1f5ea"}
|
||||
.iconochive-person:before{content:"\1f464"}
|
||||
.iconochive-people:before{content:"\1f465"}
|
||||
.iconochive-eye:before{content:"\1f441"}
|
||||
.iconochive-rss:before{content:"\221e"}
|
||||
.iconochive-time:before{content:"\1f551"}
|
||||
.iconochive-quote:before{content:"\275d"}
|
||||
.iconochive-disc:before{content:"\1f4bf"}
|
||||
.iconochive-tv-commercial:before{content:"\1f4b0"}
|
||||
.iconochive-search:before{content:"\1f50d"}
|
||||
.iconochive-search-star:before{content:"\273d"}
|
||||
.iconochive-tiles:before{content:"\229e"}
|
||||
.iconochive-list:before{content:"\21f6"}
|
||||
.iconochive-list-bulleted:before{content:"\2317"}
|
||||
.iconochive-latest:before{content:"\2208"}
|
||||
.iconochive-left:before{content:"\2c2"}
|
||||
.iconochive-right:before{content:"\2c3"}
|
||||
.iconochive-left-solid:before{content:"\25c2"}
|
||||
.iconochive-right-solid:before{content:"\25b8"}
|
||||
.iconochive-up-solid:before{content:"\25b4"}
|
||||
.iconochive-down-solid:before{content:"\25be"}
|
||||
.iconochive-dot:before{content:"\23e4"}
|
||||
.iconochive-dots:before{content:"\25a6"}
|
||||
.iconochive-columns:before{content:"\25af"}
|
||||
.iconochive-sort:before{content:"\21d5"}
|
||||
.iconochive-atoz:before{content:"\1f524"}
|
||||
.iconochive-ztoa:before{content:"\1f525"}
|
||||
.iconochive-upload:before{content:"\1f4e4"}
|
||||
.iconochive-download:before{content:"\1f4e5"}
|
||||
.iconochive-favorite:before{content:"\2605"}
|
||||
.iconochive-heart:before{content:"\2665"}
|
||||
.iconochive-play:before{content:"\25b6"}
|
||||
.iconochive-play-framed:before{content:"\1f3ac"}
|
||||
.iconochive-fullscreen:before{content:"\26f6"}
|
||||
.iconochive-mute:before{content:"\1f507"}
|
||||
.iconochive-unmute:before{content:"\1f50a"}
|
||||
.iconochive-share:before{content:"\1f381"}
|
||||
.iconochive-edit:before{content:"\270e"}
|
||||
.iconochive-reedit:before{content:"\2710"}
|
||||
.iconochive-gear:before{content:"\2699"}
|
||||
.iconochive-remove-circle:before{content:"\274e"}
|
||||
.iconochive-plus-circle:before{content:"\1f5d6"}
|
||||
.iconochive-minus-circle:before{content:"\1f5d5"}
|
||||
.iconochive-x:before{content:"\1f5d9"}
|
||||
.iconochive-fork:before{content:"\22d4"}
|
||||
.iconochive-trash:before{content:"\1f5d1"}
|
||||
.iconochive-warning:before{content:"\26a0"}
|
||||
.iconochive-flash:before{content:"\1f5f2"}
|
||||
.iconochive-world:before{content:"\1f5fa"}
|
||||
.iconochive-lock:before{content:"\1f512"}
|
||||
.iconochive-unlock:before{content:"\1f513"}
|
||||
.iconochive-twitter:before{content:"\1f426"}
|
||||
.iconochive-facebook:before{content:"\66"}
|
||||
.iconochive-googleplus:before{content:"\67"}
|
||||
.iconochive-reddit:before{content:"\1f47d"}
|
||||
.iconochive-tumblr:before{content:"\54"}
|
||||
.iconochive-pinterest:before{content:"\1d4df"}
|
||||
.iconochive-popcorn:before{content:"\1f4a5"}
|
||||
.iconochive-email:before{content:"\1f4e7"}
|
||||
.iconochive-embed:before{content:"\1f517"}
|
||||
.iconochive-gamepad:before{content:"\1f579"}
|
||||
.iconochive-Zoom_In:before{content:"\2b"}
|
||||
.iconochive-Zoom_Out:before{content:"\2d"}
|
||||
.iconochive-RSS:before{content:"\1f4e8"}
|
||||
.iconochive-Light_Bulb:before{content:"\1f4a1"}
|
||||
.iconochive-Add:before{content:"\2295"}
|
||||
.iconochive-Tab_Activity:before{content:"\2318"}
|
||||
.iconochive-Forward:before{content:"\23e9"}
|
||||
.iconochive-Backward:before{content:"\23ea"}
|
||||
.iconochive-No_Audio:before{content:"\1f508"}
|
||||
.iconochive-Pause:before{content:"\23f8"}
|
||||
.iconochive-No_Favorite:before{content:"\2606"}
|
||||
.iconochive-Unike:before{content:"\2661"}
|
||||
.iconochive-Song:before{content:"\266b"}
|
||||
.iconochive-No_Flag:before{content:"\2690"}
|
||||
.iconochive-Flag:before{content:"\2691"}
|
||||
.iconochive-Done:before{content:"\2713"}
|
||||
.iconochive-Check:before{content:"\2714"}
|
||||
.iconochive-Refresh:before{content:"\27f3"}
|
||||
.iconochive-Headphones:before{content:"\1f3a7"}
|
||||
.iconochive-Chart:before{content:"\1f4c8"}
|
||||
.iconochive-Bookmark:before{content:"\1f4d1"}
|
||||
.iconochive-Documents:before{content:"\1f4da"}
|
||||
.iconochive-Newspaper:before{content:"\1f4f0"}
|
||||
.iconochive-Podcast:before{content:"\1f4f6"}
|
||||
.iconochive-Radio:before{content:"\1f4fb"}
|
||||
.iconochive-Cassette:before{content:"\1f4fc"}
|
||||
.iconochive-Shuffle:before{content:"\1f500"}
|
||||
.iconochive-Loop:before{content:"\1f501"}
|
||||
.iconochive-Low_Audio:before{content:"\1f509"}
|
||||
.iconochive-First:before{content:"\1f396"}
|
||||
.iconochive-Invisible:before{content:"\1f576"}
|
||||
.iconochive-Computer:before{content:"\1f5b3"}
|
||||
3
assets/textures/Rendering Quake 3 Maps_files/ruffle.js
Normal file
3
assets/textures/Rendering Quake 3 Maps_files/ruffle.js
Normal file
File diff suppressed because one or more lines are too long
21
assets/textures/Rendering Quake 3 Maps_files/wombat.js
Normal file
21
assets/textures/Rendering Quake 3 Maps_files/wombat.js
Normal file
File diff suppressed because one or more lines are too long
Loading…
x
Reference in New Issue
Block a user