Added bounding box transparent meshes, restructured shader directory, added compiled shader objects to gitignore
This commit is contained in:
parent
a9e53cc4cb
commit
e85882df04
3
.gitignore
vendored
3
.gitignore
vendored
@ -15,6 +15,9 @@
|
||||
*.userprefs
|
||||
|
||||
|
||||
# compiled shaders
|
||||
assets/shaders/*.spv
|
||||
|
||||
# Mono auto generated files
|
||||
mono_crash.*
|
||||
|
||||
|
||||
@ -25,17 +25,22 @@ add_executable (pleascach ${SOURCES})
|
||||
|
||||
# shader compilation
|
||||
file(GLOB SHADER_SOURCE_FILES
|
||||
assets/shaders/*.frag
|
||||
assets/shaders/*.vert
|
||||
assets/shaders/*.geom
|
||||
assets/shaders/*.tese
|
||||
assets/shaders/*.tesc
|
||||
assets/shaders/**/*.frag
|
||||
assets/shaders/**/*.vert
|
||||
assets/shaders/**/*.geom
|
||||
assets/shaders/**/*.tese
|
||||
assets/shaders/**/*.tesc
|
||||
assets/shaders/*/*/*.frag
|
||||
assets/shaders/*/*/*.vert
|
||||
assets/shaders/*/*/*.geom
|
||||
assets/shaders/*/*/*.tese
|
||||
assets/shaders/*/*/*.tesc
|
||||
)
|
||||
|
||||
foreach(SHADER_SOURCE ${SHADER_SOURCE_FILES})
|
||||
message("Processing shader file")
|
||||
get_filename_component(FILE_NAME ${SHADER_SOURCE} NAME)
|
||||
set(SPIRV "${CMAKE_SOURCE_DIR}/assets/shaders/${FILE_NAME}.spv")
|
||||
set(SPIRV "${CMAKE_SOURCE_DIR}/assets/shaders/bin/${FILE_NAME}.spv")
|
||||
add_custom_command(
|
||||
OUTPUT ${SPIRV}
|
||||
COMMAND ${Vulkan_GLSLC_EXECUTABLE} -o ${SPIRV} ${SHADER_SOURCE}
|
||||
|
||||
@ -77,7 +77,7 @@ void Model::initVertices(Node* node, const tinygltf::Primitive& prim) {
|
||||
}
|
||||
|
||||
for(size_t i = 0; i < vertex_count; i++) {
|
||||
vertices.push_back(BasicVertex {
|
||||
vertices.push_back(glTFVertex {
|
||||
.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<BasicVertex> vertices;
|
||||
std::vector<glTFVertex> vertices;
|
||||
std::vector<uint16_t> indices;
|
||||
|
||||
/* recusively initialize nodes with an accumulative vertex and index buffer collector */
|
||||
|
||||
@ -65,6 +65,7 @@ void CommandBuffer::copy(Buffer& src, Image& dst, vk::ImageLayout layout) {
|
||||
|
||||
void CommandBuffer::bind(const GraphicsPipeline& pipeline) {
|
||||
command_buffer.bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline.pipeline);
|
||||
command_buffer.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipeline.layout, 0, pipeline.desc_set, nullptr);
|
||||
}
|
||||
|
||||
void CommandBuffer::bind(vk::PipelineLayout layout, vk::ArrayProxy<vk::DescriptorSet> desc_sets) {
|
||||
|
||||
@ -73,12 +73,25 @@ GraphicsPipeline::GraphicsPipeline(vk::Device dev, const std::vector<Shader>& sh
|
||||
.pVertexAttributeDescriptions = vertex_attrs.data(),
|
||||
};
|
||||
|
||||
const auto input_asm_info = vk::PipelineInputAssemblyStateCreateInfo{
|
||||
.topology = type == Type::eGLTF ? vk::PrimitiveTopology::eTriangleList : vk::PrimitiveTopology::ePatchList,
|
||||
auto input_asm_info = vk::PipelineInputAssemblyStateCreateInfo {
|
||||
/* matters later if we use strip primitives */
|
||||
.primitiveRestartEnable = vk::False,
|
||||
};
|
||||
|
||||
switch (type) {
|
||||
case eVERTEX:
|
||||
case eGLTF:
|
||||
case eBSP:
|
||||
input_asm_info.topology = vk::PrimitiveTopology::eTriangleList;
|
||||
break;
|
||||
case eTERRAIN:
|
||||
input_asm_info.topology = vk::PrimitiveTopology::ePatchList;
|
||||
break;
|
||||
case eBOX:
|
||||
input_asm_info.topology = vk::PrimitiveTopology::ePointList;
|
||||
break;
|
||||
}
|
||||
|
||||
const vk::PipelineTessellationStateCreateInfo* ptesselation_info = nullptr;
|
||||
|
||||
const auto tess_info = vk::PipelineTessellationStateCreateInfo {
|
||||
@ -93,9 +106,10 @@ GraphicsPipeline::GraphicsPipeline(vk::Device dev, const std::vector<Shader>& sh
|
||||
const auto raster_info = vk::PipelineRasterizationStateCreateInfo {
|
||||
.depthClampEnable = vk::False,
|
||||
.polygonMode = vk::PolygonMode::eFill,
|
||||
.cullMode = vk::CullModeFlagBits::eBack,
|
||||
.cullMode = type == eBOX ? vk::CullModeFlagBits::eNone : vk::CullModeFlagBits::eBack,
|
||||
.frontFace = vk::FrontFace::eCounterClockwise,
|
||||
.depthBiasEnable = vk::False,
|
||||
.depthBiasEnable = type == eBOX,
|
||||
.depthBiasConstantFactor = 0.01,
|
||||
.lineWidth = 1.0,
|
||||
};
|
||||
|
||||
@ -106,7 +120,7 @@ GraphicsPipeline::GraphicsPipeline(vk::Device dev, const std::vector<Shader>& sh
|
||||
|
||||
const auto depth_stencil_info = vk::PipelineDepthStencilStateCreateInfo{
|
||||
.depthTestEnable = vk::True,
|
||||
.depthWriteEnable = vk::True,
|
||||
.depthWriteEnable = type != eBOX,
|
||||
.depthCompareOp = vk::CompareOp::eLess,
|
||||
.depthBoundsTestEnable = vk::False,
|
||||
.stencilTestEnable = vk::False,
|
||||
@ -114,14 +128,22 @@ GraphicsPipeline::GraphicsPipeline(vk::Device dev, const std::vector<Shader>& sh
|
||||
.maxDepthBounds = 1.0,
|
||||
};
|
||||
|
||||
const auto color_blend_attachment = vk::PipelineColorBlendAttachmentState{
|
||||
.blendEnable = vk::False,
|
||||
const auto color_blend_attachment = vk::PipelineColorBlendAttachmentState {
|
||||
/* only the box has blending */
|
||||
.blendEnable = type == eBOX,
|
||||
.srcColorBlendFactor = vk::BlendFactor::eSrcAlpha,
|
||||
.dstColorBlendFactor = vk::BlendFactor::eOneMinusSrcAlpha,
|
||||
.colorBlendOp = vk::BlendOp::eMax,
|
||||
.srcAlphaBlendFactor = vk::BlendFactor::eOne,
|
||||
.dstAlphaBlendFactor = vk::BlendFactor::eZero,
|
||||
.alphaBlendOp = vk::BlendOp::eAdd,
|
||||
.colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eB
|
||||
| vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eA,
|
||||
|
||||
};
|
||||
|
||||
const std::array<float, 4> blend_constants = {0.0f, 0.0f, 0.0f, 0.0f};
|
||||
const auto color_blend_info = vk::PipelineColorBlendStateCreateInfo{
|
||||
std::array<float, 4> blend_constants = {0.0f, 0.0f, 0.0f, 0.0f };
|
||||
auto color_blend_info = vk::PipelineColorBlendStateCreateInfo{
|
||||
.logicOpEnable = vk::False,
|
||||
.logicOp = vk::LogicOp::eCopy,
|
||||
.attachmentCount = 1,
|
||||
|
||||
@ -12,14 +12,17 @@ struct Texture;
|
||||
|
||||
struct GraphicsPipeline {
|
||||
enum Type {
|
||||
eVERTEX,
|
||||
eGLTF,
|
||||
eBSP,
|
||||
eTERRAIN,
|
||||
eBOX,
|
||||
};
|
||||
|
||||
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 = Type::eGLTF);
|
||||
const vk::VertexInputBindingDescription& vertex_binding, const std::vector<vk::VertexInputAttributeDescription>& vertex_attrs, enum Type type = Type::eVERTEX);
|
||||
vk::Device dev;
|
||||
vk::Pipeline pipeline;
|
||||
vk::PipelineLayout layout;
|
||||
|
||||
@ -43,7 +43,7 @@ RenderPass::RenderPass(vk::Device dev, vk::Format image_format, vk::Format depth
|
||||
|
||||
|
||||
/* designates producer and consumer of the image to position subpass */
|
||||
auto color_dep = vk::SubpassDependency{
|
||||
auto color_dep = vk::SubpassDependency {
|
||||
.srcSubpass = vk::SubpassExternal,
|
||||
.dstSubpass = 0,
|
||||
.srcStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput,
|
||||
|
||||
@ -194,79 +194,42 @@ Renderer::Renderer(Window& win) : win(win) {
|
||||
|
||||
uniform_buffer = std::make_unique<UniformBuffer>(phys_dev, dev);
|
||||
|
||||
textures = createResources({
|
||||
"assets/textures/oil.jpg",
|
||||
"assets/textures/eire.png",
|
||||
});
|
||||
|
||||
std::vector<Shader> shaders = {
|
||||
{ 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 },
|
||||
};
|
||||
|
||||
std::vector<vk::DescriptorSetLayoutBinding> bindings = {
|
||||
uniform_buffer->binding(0),
|
||||
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 },
|
||||
{ dev, "assets/shaders/bin/bsp.vert.spv", vk::ShaderStageFlagBits::eVertex },
|
||||
{ dev, "assets/shaders/bin/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"));
|
||||
auto t = model_timer.stop();
|
||||
|
||||
Log::debug("Models loaded in %lf milliseconds\n", model_timer.read());
|
||||
|
||||
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));
|
||||
|
||||
model_pipeline->update(0, *uniform_buffer);
|
||||
model_pipeline->update(1, textures[1]);
|
||||
|
||||
/* create Terrain */
|
||||
terrain = std::make_unique<Terrain>(phys_dev, dev, textures[1]);
|
||||
|
||||
std::vector<Shader> terrain_shaders = {
|
||||
{ dev, "assets/shaders/terrain.vert.spv", vk::ShaderStageFlagBits::eVertex },
|
||||
{ dev, "assets/shaders/terrain.tesc.spv", vk::ShaderStageFlagBits::eTessellationControl },
|
||||
{ dev, "assets/shaders/terrain.tese.spv", vk::ShaderStageFlagBits::eTessellationEvaluation },
|
||||
{ dev, "assets/shaders/terrain.frag.spv", vk::ShaderStageFlagBits::eFragment },
|
||||
/* bounding and hitboxs */
|
||||
std::vector<Shader> box_shaders = {
|
||||
{ dev, "assets/shaders/bin/box.vert.spv", vk::ShaderStageFlagBits::eVertex },
|
||||
{ dev, "assets/shaders/bin/box.geom.spv", vk::ShaderStageFlagBits::eGeometry },
|
||||
{ dev, "assets/shaders/bin/box.frag.spv", vk::ShaderStageFlagBits::eFragment },
|
||||
};
|
||||
|
||||
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);
|
||||
std::vector<BoxVertex> boxes;
|
||||
boxes.reserve(bsp->leafs.size());
|
||||
for (auto& leaf : bsp->leafs) {
|
||||
boxes.push_back(BoxVertex{
|
||||
.mins = leaf.bb_mins,
|
||||
.maxes = leaf.bb_maxes,
|
||||
});
|
||||
}
|
||||
box_buffer = std::make_unique<GeneralVertexBuffer<BoxVertex>>(phys_dev, dev, boxes.size());
|
||||
box_buffer->upload(boxes);
|
||||
|
||||
terrain_pipeline->update(0, *uniform_buffer);
|
||||
terrain_pipeline->update(1, textures[1]);
|
||||
box_pipeline = std::make_unique<GraphicsPipeline>(dev, box_shaders, swapchain->extent, *render_pass, bindings, box_buffer->binding(0), box_buffer->attrs(0), GraphicsPipeline::Type::eBOX);
|
||||
box_pipeline->update(0, *uniform_buffer);
|
||||
|
||||
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();
|
||||
|
||||
ui = std::make_unique<UI>(this);
|
||||
}
|
||||
@ -384,23 +347,16 @@ void Renderer::draw() {
|
||||
command_buffer->command_buffer.setViewport(0, viewport);
|
||||
command_buffer->command_buffer.setScissor(0, scissor);
|
||||
|
||||
/*command_buffer->bind(*terrain_pipeline);
|
||||
|
||||
command_buffer->bind(terrain_pipeline->layout, terrain_pipeline->desc_set);
|
||||
command_buffer->bind(terrain.get());
|
||||
command_buffer->command_buffer.drawIndexed(terrain->indices.size(), 1, 0, 0, 0);*/
|
||||
|
||||
/*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);*/
|
||||
|
||||
bsp->load_indices(cam.pos, visibility_testing, p*uni.view);
|
||||
bsp->load_indices(cam.pos, visibility_testing, p * uni.view);
|
||||
command_buffer->bind(bsp.get());
|
||||
command_buffer->command_buffer.drawIndexed(bsp->indices.size(), 1, 0, 0, 0);
|
||||
|
||||
n_indices = bsp->indices.size();
|
||||
|
||||
command_buffer->bind(*box_pipeline);
|
||||
command_buffer->bind(*box_buffer);
|
||||
command_buffer->draw(box_buffer->buffer->size / sizeof(BoxVertex), 1);
|
||||
|
||||
/* draw User Interface stuff */
|
||||
ui->newFrame();
|
||||
|
||||
@ -410,7 +366,6 @@ void Renderer::draw() {
|
||||
|
||||
command_buffer->end();
|
||||
|
||||
|
||||
vk::PipelineStageFlags stage_flags = vk::PipelineStageFlagBits::eColorAttachmentOutput;
|
||||
|
||||
/* submit our command buffer to the queue */
|
||||
@ -457,17 +412,6 @@ Renderer::~Renderer() {
|
||||
dev.waitIdle();
|
||||
|
||||
ui.reset();
|
||||
|
||||
for(auto& model : models)
|
||||
model.reset();
|
||||
|
||||
terrain.reset();
|
||||
|
||||
uniform_buffer.reset();
|
||||
vertex_buffer.reset();
|
||||
vertex_pipeline.reset();
|
||||
terrain_pipeline.reset();
|
||||
model_pipeline.reset();
|
||||
bsp.reset();
|
||||
|
||||
|
||||
|
||||
@ -51,21 +51,17 @@ struct Renderer {
|
||||
std::unique_ptr<CommandBuffer> command_buffer;
|
||||
std::unique_ptr<RenderPass> render_pass;
|
||||
|
||||
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<GraphicsPipeline> box_pipeline;
|
||||
std::unique_ptr<GeneralVertexBuffer<BoxVertex>> box_buffer;
|
||||
std::unique_ptr<UniformBuffer> uniform_buffer;
|
||||
|
||||
std::unique_ptr<Q3BSP::BSP> bsp;
|
||||
std::unique_ptr<Terrain> terrain;
|
||||
|
||||
std::vector<Texture> textures;
|
||||
|
||||
uint32_t current_image_idx;
|
||||
uint64_t frame = 0;
|
||||
|
||||
std::vector<std::shared_ptr<Model>> models;
|
||||
std::unique_ptr<UI> ui;
|
||||
|
||||
Camera cam{ .pos = glm::vec3(0.0, 5.0, 0.0), };
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
|
||||
#include <tinygltf/tiny_gltf.h>
|
||||
|
||||
struct BasicVertex {
|
||||
struct glTFVertex {
|
||||
glm::vec3 pos;
|
||||
glm::vec3 norm;
|
||||
glm::vec2 uv;
|
||||
@ -20,27 +20,63 @@ struct BasicVertex {
|
||||
.location = 0,
|
||||
.binding = binding,
|
||||
.format = vk::Format::eR32G32B32Sfloat,
|
||||
.offset = offsetof(BasicVertex, pos),
|
||||
.offset = offsetof(glTFVertex, pos),
|
||||
}, {
|
||||
.location = 1,
|
||||
.binding = binding,
|
||||
.format = vk::Format::eR32G32B32Sfloat,
|
||||
.offset = offsetof(BasicVertex, norm),
|
||||
.offset = offsetof(glTFVertex, norm),
|
||||
}, {
|
||||
.location = 2,
|
||||
.binding = binding,
|
||||
.format = vk::Format::eR32G32Sfloat,
|
||||
.offset = offsetof(BasicVertex, uv),
|
||||
.offset = offsetof(glTFVertex, uv),
|
||||
}, {
|
||||
.location = 3,
|
||||
.binding = binding,
|
||||
.format = vk::Format::eR32G32B32Sfloat,
|
||||
.offset = offsetof(BasicVertex, color),
|
||||
.offset = offsetof(glTFVertex, color),
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
enum BoxType {
|
||||
eBounding,
|
||||
eEnemyHit,
|
||||
};
|
||||
/* for hitboxes and bounding boxes */
|
||||
struct BoxVertex {
|
||||
glm::vec3 mins;
|
||||
glm::vec3 maxes;
|
||||
unsigned int id;
|
||||
|
||||
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(BoxVertex, mins),
|
||||
}, {
|
||||
.location = 1,
|
||||
.binding = binding,
|
||||
.format = vk::Format::eR32G32B32Sfloat,
|
||||
.offset = offsetof(BoxVertex, maxes),
|
||||
}, {
|
||||
.location = 2,
|
||||
.binding = binding,
|
||||
.format = vk::Format::eR32Uint,
|
||||
.offset = offsetof(BoxVertex, id),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
template<typename Vertex>
|
||||
struct GeneralVertexBuffer {
|
||||
std::unique_ptr<Buffer> buffer;
|
||||
@ -71,5 +107,5 @@ struct GeneralVertexBuffer {
|
||||
}
|
||||
};
|
||||
|
||||
using VertexBuffer = GeneralVertexBuffer<BasicVertex>;
|
||||
using VertexBuffer = GeneralVertexBuffer<glTFVertex>;
|
||||
|
||||
|
||||
@ -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] = (BasicVertex {
|
||||
vertices[x + y*patch_size] = (glTFVertex {
|
||||
.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<BasicVertex> vertices;
|
||||
std::vector<glTFVertex> vertices;
|
||||
std::vector<uint32_t> indices;
|
||||
|
||||
Terrain(vk::PhysicalDevice phys_dev, vk::Device dev, Texture& hieghtmap);
|
||||
|
||||
@ -102,15 +102,15 @@ UI::UI(Renderer* ren) : ren(ren), dev(ren->dev) {
|
||||
console = std::make_unique<ImGuiConsole>("developer console");
|
||||
console->System().RegisterCommand("pause", "Pauses or unpauses the engine", [this]() {
|
||||
this->ren->paused = !this->ren->paused;
|
||||
console->System().Log(csys::ItemType::INFO) << "Paused: " << (this->ren->paused? "True" : "False") << csys::endl;
|
||||
console->System().Log(csys::ItemType::eINFO) << "Paused: " << (this->ren->paused? "True" : "False") << csys::endl;
|
||||
});
|
||||
|
||||
console->System().RegisterCommand("toggle-visibility-testing", "Toggles visibility testings (using clusters and frustum culling)", [this]() {
|
||||
this->ren->visibility_testing = !this->ren->visibility_testing;
|
||||
console->System().Log(csys::ItemType::INFO) << "Visibility Testing: " << (this->ren->visibility_testing? "Enabled" : "Disabled") << csys::endl;
|
||||
console->System().Log(csys::ItemType::eINFO) << "Visibility Testing: " << (this->ren->visibility_testing? "Enabled" : "Disabled") << csys::endl;
|
||||
});
|
||||
|
||||
console->System().Log(csys::ItemType::INFO) << "Welcome to Pleascach!" << csys::endl;
|
||||
console->System().Log(csys::ItemType::eINFO) << "Welcome to Pleascach!" << csys::endl;
|
||||
}
|
||||
|
||||
void UI::newFrame() {
|
||||
@ -120,6 +120,7 @@ void UI::newFrame() {
|
||||
|
||||
ImGui::SetNextWindowBgAlpha(0.5f);
|
||||
ImGui::Begin("Rendering Info", nullptr);
|
||||
|
||||
ImGui::Text("# of Indices: %zu", ren->n_indices);
|
||||
ImGui::Text("FPS: %f", ren->fps);
|
||||
ImGui::Text("Time: %f", ren->time);
|
||||
|
||||
@ -1,51 +0,0 @@
|
||||
#version 450 core
|
||||
|
||||
layout (triangles) in;
|
||||
layout (triangle_strip) out;
|
||||
layout (max_vertices = 6) out;
|
||||
|
||||
layout (location = 0) in vec3 norm[];
|
||||
layout (location = 1) in vec2 texCoord[];
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
vec4 explode(vec4 pos, vec3 n) {
|
||||
float mag = 2.0;
|
||||
vec3 dir = n * (sin(time/10.0)-3.0)/10.0 * mag;
|
||||
return pos + vec4(dir, 0.0);
|
||||
}
|
||||
|
||||
|
||||
void main(void) {
|
||||
|
||||
if (time < 3.0) {
|
||||
for(int i = 0; i < gl_in.length(); i++) {
|
||||
gl_Position = proj * view * gl_in[i].gl_Position;
|
||||
_norm = norm[i];
|
||||
_texCoord = texCoord[i];
|
||||
EmitVertex();
|
||||
}
|
||||
}
|
||||
vec3 n = norm[0] + norm[1] + norm[2];
|
||||
n/=3;
|
||||
for(int i = 0; i < gl_in.length(); i++) {
|
||||
gl_Position = proj * view * explode(gl_in[i].gl_Position*abs(cos(time)), n);
|
||||
_texCoord = texCoord[i];
|
||||
_norm = n;
|
||||
EmitVertex();
|
||||
}
|
||||
EndPrimitive();
|
||||
}
|
||||
Binary file not shown.
BIN
assets/shaders/lighting/fraglight.geom.spv
Normal file
BIN
assets/shaders/lighting/fraglight.geom.spv
Normal file
Binary file not shown.
BIN
assets/shaders/lighting/fraglight.vert.spv
Normal file
BIN
assets/shaders/lighting/fraglight.vert.spv
Normal file
Binary file not shown.
BIN
assets/shaders/lighting/gooch.frag.spv
Normal file
BIN
assets/shaders/lighting/gooch.frag.spv
Normal file
Binary file not shown.
BIN
assets/shaders/lighting/lambert.frag.spv
Normal file
BIN
assets/shaders/lighting/lambert.frag.spv
Normal file
Binary file not shown.
BIN
assets/shaders/lighting/trace.frag.spv
Normal file
BIN
assets/shaders/lighting/trace.frag.spv
Normal file
Binary file not shown.
BIN
assets/shaders/map/bsp.frag.spv
Normal file
BIN
assets/shaders/map/bsp.frag.spv
Normal file
Binary file not shown.
@ -36,6 +36,5 @@ void main() {
|
||||
gl_Position = proj * view * vec4(aPos, 1.0);
|
||||
texCoord = aTexCoord;
|
||||
norm = aNorm;
|
||||
// color = vec4(length(unpackABGR(aColor).xyz));
|
||||
color = unpackABGR(aColor);
|
||||
}
|
||||
BIN
assets/shaders/map/bsp.vert.spv
Normal file
BIN
assets/shaders/map/bsp.vert.spv
Normal file
Binary file not shown.
BIN
assets/shaders/mesh/basic.frag.spv
Normal file
BIN
assets/shaders/mesh/basic.frag.spv
Normal file
Binary file not shown.
BIN
assets/shaders/mesh/basic.geom.spv
Normal file
BIN
assets/shaders/mesh/basic.geom.spv
Normal file
Binary file not shown.
BIN
assets/shaders/mesh/basic.vert.spv
Normal file
BIN
assets/shaders/mesh/basic.vert.spv
Normal file
Binary file not shown.
BIN
assets/shaders/mesh/explode.geom.spv
Normal file
BIN
assets/shaders/mesh/explode.geom.spv
Normal file
Binary file not shown.
BIN
assets/shaders/terrain/terrain.frag.spv
Normal file
BIN
assets/shaders/terrain/terrain.frag.spv
Normal file
Binary file not shown.
BIN
assets/shaders/terrain/terrain.tesc.spv
Normal file
BIN
assets/shaders/terrain/terrain.tesc.spv
Normal file
Binary file not shown.
BIN
assets/shaders/terrain/terrain.tese.spv
Normal file
BIN
assets/shaders/terrain/terrain.tese.spv
Normal file
Binary file not shown.
BIN
assets/shaders/terrain/terrain.vert.spv
Normal file
BIN
assets/shaders/terrain/terrain.vert.spv
Normal file
Binary file not shown.
14
assets/shaders/utils/box/box.frag
Normal file
14
assets/shaders/utils/box/box.frag
Normal file
@ -0,0 +1,14 @@
|
||||
#version 460 core
|
||||
layout (location = 0) flat in uint id;
|
||||
|
||||
layout (location = 0) out vec4 FragColor;
|
||||
|
||||
layout (set = 0, binding = 0) uniform Matrices {
|
||||
mat4 view;
|
||||
mat4 proj;
|
||||
float time;
|
||||
};
|
||||
|
||||
void main() {
|
||||
FragColor = vec4(0.0, 0.0, 1.0, 0.2);
|
||||
}
|
||||
42
assets/shaders/utils/box/box.geom
Normal file
42
assets/shaders/utils/box/box.geom
Normal file
@ -0,0 +1,42 @@
|
||||
#version 450 core
|
||||
|
||||
layout (points) in;
|
||||
layout (triangle_strip, max_vertices = 14) out;
|
||||
|
||||
layout (location = 0) in vec3 mins[];
|
||||
layout (location = 1) in vec3 maxes[];
|
||||
layout (location = 2) in uint id[];
|
||||
|
||||
layout (location = 0) out uint _id;
|
||||
|
||||
layout (set = 0, binding = 0) uniform Matrices {
|
||||
mat4 view;
|
||||
mat4 proj;
|
||||
};
|
||||
|
||||
|
||||
void main(void) {
|
||||
int cube_vertices[14] = {
|
||||
0, 1, 2, 3, 7, 6, 5, 4, 0, 1, 5, 6, 2, 3
|
||||
};
|
||||
|
||||
for(int i = 0; i < gl_in.length(); i++) {
|
||||
vec3 vertices[] = {
|
||||
vec3(mins[i].x, mins[i].y, mins[i].z),
|
||||
vec3(mins[i].x, mins[i].y, maxes[i].z),
|
||||
vec3(mins[i].x, maxes[i].y, mins[i].z),
|
||||
vec3(mins[i].x, maxes[i].y, maxes[i].z),
|
||||
vec3(maxes[i].x, mins[i].y, mins[i].z),
|
||||
vec3(maxes[i].x, mins[i].y, maxes[i].z),
|
||||
vec3(maxes[i].x, maxes[i].y, mins[i].z),
|
||||
vec3(maxes[i].x, maxes[i].y, maxes[i].z),
|
||||
};
|
||||
|
||||
_id = id[i];
|
||||
for(int j = 0; j < 14; j++) {
|
||||
gl_Position = proj*view*vec4(vertices[cube_vertices[j]], 1.0);
|
||||
EmitVertex();
|
||||
}
|
||||
EndPrimitive();
|
||||
}
|
||||
}
|
||||
16
assets/shaders/utils/box/box.vert
Normal file
16
assets/shaders/utils/box/box.vert
Normal file
@ -0,0 +1,16 @@
|
||||
#version 460 core
|
||||
/* simple passthrough */
|
||||
|
||||
layout (location = 0) in vec3 aMins;
|
||||
layout (location = 1) in vec3 aMaxes;
|
||||
layout (location = 2) in uint aId;
|
||||
|
||||
layout (location = 0) out vec3 mins;
|
||||
layout (location = 1) out vec3 maxes;
|
||||
layout (location = 2) out uint id;
|
||||
|
||||
void main() {
|
||||
mins = aMins;
|
||||
maxes = aMaxes;
|
||||
id = aId;
|
||||
}
|
||||
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
BIN
bsp_capture.png
BIN
bsp_capture.png
Binary file not shown.
|
Before Width: | Height: | Size: 342 KiB After Width: | Height: | Size: 547 KiB |
@ -112,9 +112,9 @@ namespace csys
|
||||
catch (Exception &ae)
|
||||
{
|
||||
// Error happened with parsing
|
||||
return Item(ERROR) << (m_Name.m_String + ": " + ae.what());
|
||||
return Item(eERROR) << (m_Name.m_String + ": " + ae.what());
|
||||
}
|
||||
return Item(NONE);
|
||||
return Item(eNONE);
|
||||
}
|
||||
|
||||
/*!
|
||||
@ -240,12 +240,12 @@ namespace csys
|
||||
catch (Exception &ae)
|
||||
{
|
||||
// Command had something passed into it
|
||||
return Item(ERROR) << (m_Name.m_String + ": " + ae.what());
|
||||
return Item(eERROR) << (m_Name.m_String + ": " + ae.what());
|
||||
}
|
||||
|
||||
// Call function
|
||||
m_Function();
|
||||
return Item(NONE);
|
||||
return Item(eNONE);
|
||||
}
|
||||
|
||||
/*!
|
||||
|
||||
@ -25,12 +25,12 @@ namespace csys
|
||||
*/
|
||||
enum ItemType
|
||||
{
|
||||
COMMAND = 0,
|
||||
LOG,
|
||||
WARNING,
|
||||
ERROR,
|
||||
INFO,
|
||||
NONE
|
||||
eCOMMAND = 0,
|
||||
eLOG,
|
||||
eWARNING,
|
||||
eERROR,
|
||||
eINFO,
|
||||
eNONE
|
||||
};
|
||||
|
||||
struct CSYS_API Item
|
||||
@ -41,7 +41,7 @@ namespace csys
|
||||
* \param type
|
||||
* Item type to be stored
|
||||
*/
|
||||
explicit Item(ItemType type = ItemType::LOG);
|
||||
explicit Item(ItemType type = ItemType::eLOG);
|
||||
|
||||
/*!
|
||||
* \brief
|
||||
|
||||
@ -39,17 +39,17 @@ namespace csys
|
||||
{
|
||||
switch (m_Type)
|
||||
{
|
||||
case COMMAND:
|
||||
case eCOMMAND:
|
||||
return s_Command.data() + m_Data;
|
||||
case LOG:
|
||||
case eLOG:
|
||||
return '\t' + m_Data;
|
||||
case WARNING:
|
||||
case eWARNING:
|
||||
return s_Warning.data() + m_Data;
|
||||
case ERROR:
|
||||
case eERROR:
|
||||
return s_Error.data() + m_Data;
|
||||
case INFO:
|
||||
case eINFO:
|
||||
return m_Data;
|
||||
case NONE:
|
||||
case eNONE:
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
|
||||
@ -106,7 +106,7 @@ namespace csys
|
||||
* \return
|
||||
* Reference to console items obj
|
||||
*/
|
||||
ItemLog &Log(ItemType type = ItemType::LOG);
|
||||
ItemLog &Log(ItemType type = ItemType::eLOG);
|
||||
|
||||
/*!
|
||||
* \brief
|
||||
@ -169,7 +169,7 @@ namespace csys
|
||||
// Check if command has a name
|
||||
else if (range.first == name.End())
|
||||
{
|
||||
Log(ERROR) << "Empty command name given" << csys::endl;
|
||||
Log(eERROR) << "Empty command name given" << csys::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
@ -192,7 +192,7 @@ namespace csys
|
||||
|
||||
// Make help command for command just added
|
||||
auto help = [this, command_name]() {
|
||||
Log(LOG) << m_Commands[command_name]->Help() << csys::endl;
|
||||
Log(eLOG) << m_Commands[command_name]->Help() << csys::endl;
|
||||
};
|
||||
|
||||
m_Commands["help " + command_name] = std::make_unique<Command<decltype(help)>>("help " + command_name,
|
||||
@ -315,7 +315,7 @@ namespace csys
|
||||
|
||||
// Get Command
|
||||
const auto GetFunction = [this, &var]() {
|
||||
m_ItemLog.log(LOG) << var << endl;
|
||||
m_ItemLog.log(eLOG) << var << endl;
|
||||
};
|
||||
|
||||
// Register get command
|
||||
|
||||
@ -107,7 +107,7 @@ namespace csys
|
||||
return;
|
||||
|
||||
// Log command.
|
||||
Log(csys::ItemType::COMMAND) << line << csys::endl;
|
||||
Log(csys::ItemType::eCOMMAND) << line << csys::endl;
|
||||
|
||||
// Parse command line.
|
||||
ParseCommandLine(line);
|
||||
@ -121,12 +121,12 @@ namespace csys
|
||||
// Exit if not found.
|
||||
if (script_pair == m_Scripts.end())
|
||||
{
|
||||
m_ItemLog.log(ERROR) << "Script \"" << script_name << "\" not found" << csys::endl;
|
||||
m_ItemLog.log(eERROR) << "Script \"" << script_name << "\" not found" << csys::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
// About to run script.
|
||||
m_ItemLog.log(INFO) << "Running \"" << script_name << "\"" << csys::endl;
|
||||
m_ItemLog.log(eINFO) << "Running \"" << script_name << "\"" << csys::endl;
|
||||
|
||||
// Load if script is empty.
|
||||
if (script_pair->second->Data().empty())
|
||||
@ -137,7 +137,7 @@ namespace csys
|
||||
}
|
||||
catch (csys::Exception &e)
|
||||
{
|
||||
Log(ERROR) << e.what() << csys::endl;
|
||||
Log(eERROR) << e.what() << csys::endl;
|
||||
}
|
||||
}
|
||||
|
||||
@ -272,7 +272,7 @@ namespace csys
|
||||
// Try to get variable name
|
||||
if ((range = line.NextPoi(line_index)).first == line.End())
|
||||
{
|
||||
Log(ERROR) << s_ErrorNoVar << endl;
|
||||
Log(eERROR) << s_ErrorNoVar << endl;
|
||||
return;
|
||||
} else
|
||||
// Append variable name.
|
||||
@ -282,7 +282,7 @@ namespace csys
|
||||
// Get runnable command
|
||||
auto command = m_Commands.find(command_name);
|
||||
if (command == m_Commands.end())
|
||||
Log(ERROR) << s_ErrorSetGetNotFound << endl;
|
||||
Log(eERROR) << s_ErrorSetGetNotFound << endl;
|
||||
// Run the command
|
||||
else
|
||||
{
|
||||
@ -293,7 +293,7 @@ namespace csys
|
||||
auto cmd_out = (*command->second)(arguments);
|
||||
|
||||
// Log output.
|
||||
if (cmd_out.m_Type != NONE)
|
||||
if (cmd_out.m_Type != eNONE)
|
||||
m_ItemLog.Items().emplace_back(cmd_out);
|
||||
}
|
||||
}
|
||||
|
||||
@ -215,7 +215,7 @@ void ImGuiConsole::LogWindow()
|
||||
continue;
|
||||
|
||||
// Spacing between commands.
|
||||
if (item.m_Type == csys::COMMAND)
|
||||
if (item.m_Type == csys::eCOMMAND)
|
||||
{
|
||||
if (m_TimeStamps) ImGui::PushTextWrapPos(ImGui::GetColumnWidth() - timestamp_width); // Wrap before timestamps start.
|
||||
if (count++ != 0) ImGui::Dummy(ImVec2(-1, ImGui::GetFontSize())); // No space for the first command.
|
||||
@ -234,7 +234,7 @@ void ImGuiConsole::LogWindow()
|
||||
|
||||
|
||||
// Time stamp.
|
||||
if (item.m_Type == csys::COMMAND && m_TimeStamps)
|
||||
if (item.m_Type == csys::eCOMMAND && m_TimeStamps)
|
||||
{
|
||||
// No wrap for timestamps
|
||||
ImGui::PopTextWrapPos();
|
||||
@ -482,10 +482,10 @@ int ImGuiConsole::InputCallback(ImGuiInputTextCallbackData* data)
|
||||
// Display suggestions on console.
|
||||
if (!console->m_CmdSuggestions.empty())
|
||||
{
|
||||
console->m_ConsoleSystem.Log(csys::COMMAND) << "Suggestions: " << csys::endl;
|
||||
console->m_ConsoleSystem.Log(csys::eCOMMAND) << "Suggestions: " << csys::endl;
|
||||
|
||||
for (const auto& suggestion : console->m_CmdSuggestions)
|
||||
console->m_ConsoleSystem.Log(csys::LOG) << suggestion << csys::endl;
|
||||
console->m_ConsoleSystem.Log(csys::eLOG) << suggestion << csys::endl;
|
||||
|
||||
console->m_CmdSuggestions.clear();
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user