QoL improvement: made it so the mouse/keyboard isn't captured by ImGui unless in_menu is active
This commit is contained in:
parent
479722185f
commit
a9e53cc4cb
@ -74,7 +74,7 @@ void Input::setCursor(bool enabled) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Input::handleMovementKeys(Renderer& ren) {
|
void Input::handleMovementKeys(Renderer& ren) {
|
||||||
if (ImGui::GetIO().WantCaptureKeyboard)
|
if (ImGui::GetIO().WantCaptureKeyboard && ren.in_menu)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
glm::vec3 forward;
|
glm::vec3 forward;
|
||||||
@ -141,7 +141,7 @@ void Input::handleCursorMovement(Renderer& ren, double x, double y) {
|
|||||||
int rel_mouse_y = static_cast<int>(y) - last_mouse.y;
|
int rel_mouse_y = static_cast<int>(y) - last_mouse.y;
|
||||||
|
|
||||||
auto& io = ImGui::GetIO();
|
auto& io = ImGui::GetIO();
|
||||||
if (io.WantCaptureMouse)
|
if (io.WantCaptureMouse && ren.in_menu)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (ren.in_menu) {
|
if (ren.in_menu) {
|
||||||
|
|||||||
@ -1,944 +0,0 @@
|
|||||||
<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
|
|
||||||
-->
|
|
||||||
@ -1,507 +0,0 @@
|
|||||||
@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
@ -1,116 +0,0 @@
|
|||||||
@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"}
|
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Loading…
x
Reference in New Issue
Block a user