mirror of
https://github.com/Azgaar/Fantasy-Map-Generator.git
synced 2026-03-23 07:37:24 +01:00
feat: refactor draw-relief-icons renderer to utilize WebGL2LayerFramework
- Removed global renderer, camera, and scene management in favor of layer framework integration. - Implemented terrain layer registration with setup, render, and dispose methods. - Enhanced texture loading and caching mechanisms. - Updated geometry building to return Mesh objects directly. - Added performance benchmarking story for render performance validation. - Created bundle size audit story to ensure effective tree-shaking and size constraints.
This commit is contained in:
parent
30f74373b8
commit
a285d450c8
12 changed files with 1152 additions and 491 deletions
|
|
@ -1,135 +1,135 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<coverage generated="1773320690465" clover="3.2.0">
|
||||
<project timestamp="1773320690465" name="All files">
|
||||
<metrics statements="126" coveredstatements="115" conditionals="82" coveredconditionals="58" methods="19" coveredmethods="16" elements="227" coveredelements="189" complexity="0" loc="126" ncloc="126" packages="1" files="1" classes="1"/>
|
||||
<coverage generated="1773323271919" clover="3.2.0">
|
||||
<project timestamp="1773323271919" name="All files">
|
||||
<metrics statements="126" coveredstatements="115" conditionals="82" coveredconditionals="63" methods="19" coveredmethods="16" elements="227" coveredelements="194" complexity="0" loc="126" ncloc="126" packages="1" files="1" classes="1"/>
|
||||
<file name="webgl-layer-framework.ts" path="/Users/azgaar/Fantasy-Map-Generator/src/modules/webgl-layer-framework.ts">
|
||||
<metrics statements="126" coveredstatements="115" conditionals="82" coveredconditionals="58" methods="19" coveredmethods="16"/>
|
||||
<line num="27" count="11" type="stmt"/>
|
||||
<metrics statements="126" coveredstatements="115" conditionals="82" coveredconditionals="63" methods="19" coveredmethods="16"/>
|
||||
<line num="25" count="11" type="stmt"/>
|
||||
<line num="39" count="8" type="cond" truecount="2" falsecount="0"/>
|
||||
<line num="40" count="8" type="stmt"/>
|
||||
<line num="41" count="8" type="cond" truecount="2" falsecount="0"/>
|
||||
<line num="42" count="8" type="stmt"/>
|
||||
<line num="43" count="8" type="cond" truecount="2" falsecount="0"/>
|
||||
<line num="44" count="6" type="stmt"/>
|
||||
<line num="45" count="6" type="stmt"/>
|
||||
<line num="46" count="8" type="stmt"/>
|
||||
<line num="60" count="4" type="cond" truecount="2" falsecount="0"/>
|
||||
<line num="61" count="3" type="stmt"/>
|
||||
<line num="62" count="3" type="cond" truecount="1" falsecount="1"/>
|
||||
<line num="63" count="0" type="cond" truecount="0" falsecount="2"/>
|
||||
<line num="64" count="4" type="stmt"/>
|
||||
<line num="66" count="4" type="cond" truecount="0" falsecount="2"/>
|
||||
<line num="89" count="26" type="stmt"/>
|
||||
<line num="90" count="26" type="stmt"/>
|
||||
<line num="91" count="26" type="stmt"/>
|
||||
<line num="92" count="26" type="stmt"/>
|
||||
<line num="93" count="26" type="stmt"/>
|
||||
<line num="94" count="26" type="stmt"/>
|
||||
<line num="95" count="26" type="stmt"/>
|
||||
<line num="96" count="26" type="stmt"/>
|
||||
<line num="97" count="26" type="stmt"/>
|
||||
<line num="102" count="26" type="stmt"/>
|
||||
<line num="105" count="2" type="stmt"/>
|
||||
<line num="111" count="5" type="stmt"/>
|
||||
<line num="112" count="5" type="cond" truecount="2" falsecount="0"/>
|
||||
<line num="114" count="4" type="stmt"/>
|
||||
<line num="115" count="4" type="cond" truecount="2" falsecount="0"/>
|
||||
<line num="116" count="1" type="stmt"/>
|
||||
<line num="119" count="1" type="stmt"/>
|
||||
<line num="42" count="6" type="stmt"/>
|
||||
<line num="43" count="6" type="stmt"/>
|
||||
<line num="44" count="8" type="stmt"/>
|
||||
<line num="58" count="4" type="cond" truecount="2" falsecount="0"/>
|
||||
<line num="59" count="3" type="stmt"/>
|
||||
<line num="60" count="3" type="cond" truecount="1" falsecount="1"/>
|
||||
<line num="61" count="0" type="cond" truecount="0" falsecount="2"/>
|
||||
<line num="62" count="4" type="stmt"/>
|
||||
<line num="64" count="4" type="cond" truecount="0" falsecount="2"/>
|
||||
<line num="85" count="35" type="stmt"/>
|
||||
<line num="86" count="35" type="stmt"/>
|
||||
<line num="87" count="35" type="stmt"/>
|
||||
<line num="88" count="35" type="stmt"/>
|
||||
<line num="89" count="35" type="stmt"/>
|
||||
<line num="90" count="35" type="stmt"/>
|
||||
<line num="91" count="35" type="stmt"/>
|
||||
<line num="92" count="35" type="stmt"/>
|
||||
<line num="93" count="35" type="stmt"/>
|
||||
<line num="94" count="35" type="stmt"/>
|
||||
<line num="97" count="3" type="stmt"/>
|
||||
<line num="101" count="5" type="stmt"/>
|
||||
<line num="102" count="5" type="cond" truecount="2" falsecount="0"/>
|
||||
<line num="104" count="4" type="stmt"/>
|
||||
<line num="105" count="4" type="cond" truecount="2" falsecount="0"/>
|
||||
<line num="106" count="1" type="stmt"/>
|
||||
<line num="109" count="1" type="stmt"/>
|
||||
<line num="113" count="3" type="stmt"/>
|
||||
<line num="114" count="3" type="stmt"/>
|
||||
<line num="115" count="3" type="stmt"/>
|
||||
<line num="116" count="3" type="stmt"/>
|
||||
<line num="117" count="3" type="stmt"/>
|
||||
<line num="118" count="3" type="stmt"/>
|
||||
<line num="121" count="3" type="stmt"/>
|
||||
<line num="122" count="3" type="stmt"/>
|
||||
<line num="123" count="3" type="stmt"/>
|
||||
<line num="124" count="3" type="stmt"/>
|
||||
<line num="125" count="3" type="stmt"/>
|
||||
<line num="126" count="3" type="stmt"/>
|
||||
<line num="127" count="3" type="stmt"/>
|
||||
<line num="128" count="3" type="stmt"/>
|
||||
<line num="131" count="3" type="stmt"/>
|
||||
<line num="132" count="3" type="stmt"/>
|
||||
<line num="133" count="3" type="stmt"/>
|
||||
<line num="134" count="3" type="stmt"/>
|
||||
<line num="135" count="3" type="stmt"/>
|
||||
<line num="136" count="3" type="stmt"/>
|
||||
<line num="137" count="3" type="stmt"/>
|
||||
<line num="138" count="3" type="cond" truecount="1" falsecount="1"/>
|
||||
<line num="139" count="5" type="cond" truecount="1" falsecount="1"/>
|
||||
<line num="128" count="3" type="cond" truecount="1" falsecount="1"/>
|
||||
<line num="129" count="5" type="cond" truecount="1" falsecount="1"/>
|
||||
<line num="130" count="5" type="stmt"/>
|
||||
<line num="131" count="5" type="stmt"/>
|
||||
<line num="134" count="5" type="stmt"/>
|
||||
<line num="139" count="5" type="stmt"/>
|
||||
<line num="140" count="5" type="stmt"/>
|
||||
<line num="141" count="5" type="stmt"/>
|
||||
<line num="144" count="5" type="stmt"/>
|
||||
<line num="149" count="5" type="stmt"/>
|
||||
<line num="150" count="5" type="stmt"/>
|
||||
<line num="151" count="5" type="stmt"/>
|
||||
<line num="160" count="5" type="stmt"/>
|
||||
<line num="163" count="5" type="stmt"/>
|
||||
<line num="164" count="1" type="stmt"/>
|
||||
<line num="165" count="1" type="stmt"/>
|
||||
<line num="166" count="1" type="stmt"/>
|
||||
<line num="167" count="1" type="stmt"/>
|
||||
<line num="168" count="1" type="stmt"/>
|
||||
<line num="170" count="3" type="stmt"/>
|
||||
<line num="172" count="3" type="stmt"/>
|
||||
<line num="174" count="3" type="stmt"/>
|
||||
<line num="178" count="4" type="cond" truecount="1" falsecount="1"/>
|
||||
<line num="180" count="4" type="stmt"/>
|
||||
<line num="181" count="4" type="stmt"/>
|
||||
<line num="184" count="0" type="stmt"/>
|
||||
<line num="185" count="0" type="stmt"/>
|
||||
<line num="186" count="0" type="stmt"/>
|
||||
<line num="187" count="0" type="stmt"/>
|
||||
<line num="188" count="0" type="stmt"/>
|
||||
<line num="192" count="2" type="cond" truecount="1" falsecount="1"/>
|
||||
<line num="193" count="2" type="stmt"/>
|
||||
<line num="194" count="2" type="cond" truecount="3" falsecount="1"/>
|
||||
<line num="195" count="2" type="stmt"/>
|
||||
<line num="196" count="2" type="stmt"/>
|
||||
<line num="197" count="2" type="stmt"/>
|
||||
<line num="198" count="2" type="stmt"/>
|
||||
<line num="199" count="2" type="stmt"/>
|
||||
<line num="200" count="2" type="cond" truecount="3" falsecount="1"/>
|
||||
<line num="204" count="4" type="cond" truecount="1" falsecount="1"/>
|
||||
<line num="205" count="4" type="stmt"/>
|
||||
<line num="206" count="4" type="cond" truecount="1" falsecount="1"/>
|
||||
<line num="207" count="4" type="stmt"/>
|
||||
<line num="208" count="5" type="stmt"/>
|
||||
<line num="209" count="4" type="cond" truecount="3" falsecount="1"/>
|
||||
<line num="210" count="4" type="cond" truecount="2" falsecount="0"/>
|
||||
<line num="214" count="3" type="cond" truecount="1" falsecount="1"/>
|
||||
<line num="215" count="3" type="stmt"/>
|
||||
<line num="216" count="3" type="cond" truecount="1" falsecount="1"/>
|
||||
<line num="217" count="3" type="stmt"/>
|
||||
<line num="221" count="10" type="cond" truecount="1" falsecount="1"/>
|
||||
<line num="222" count="10" type="cond" truecount="2" falsecount="0"/>
|
||||
<line num="223" count="6" type="stmt"/>
|
||||
<line num="224" count="3" type="stmt"/>
|
||||
<line num="225" count="3" type="stmt"/>
|
||||
<line num="230" count="3" type="cond" truecount="3" falsecount="1"/>
|
||||
<line num="231" count="3" type="stmt"/>
|
||||
<line num="232" count="3" type="cond" truecount="2" falsecount="0"/>
|
||||
<line num="233" count="3" type="cond" truecount="2" falsecount="0"/>
|
||||
<line num="234" count="3" type="cond" truecount="2" falsecount="0"/>
|
||||
<line num="235" count="3" type="cond" truecount="2" falsecount="0"/>
|
||||
<line num="236" count="3" type="cond" truecount="2" falsecount="0"/>
|
||||
<line num="237" count="3" type="stmt"/>
|
||||
<line num="244" count="3" type="stmt"/>
|
||||
<line num="245" count="3" type="stmt"/>
|
||||
<line num="246" count="3" type="stmt"/>
|
||||
<line num="247" count="3" type="stmt"/>
|
||||
<line num="153" count="5" type="stmt"/>
|
||||
<line num="154" count="1" type="stmt"/>
|
||||
<line num="155" count="1" type="stmt"/>
|
||||
<line num="156" count="1" type="stmt"/>
|
||||
<line num="157" count="1" type="stmt"/>
|
||||
<line num="158" count="1" type="stmt"/>
|
||||
<line num="160" count="3" type="stmt"/>
|
||||
<line num="161" count="3" type="stmt"/>
|
||||
<line num="163" count="3" type="stmt"/>
|
||||
<line num="167" count="6" type="cond" truecount="1" falsecount="1"/>
|
||||
<line num="169" count="6" type="stmt"/>
|
||||
<line num="170" count="6" type="stmt"/>
|
||||
<line num="173" count="0" type="stmt"/>
|
||||
<line num="174" count="0" type="stmt"/>
|
||||
<line num="175" count="0" type="stmt"/>
|
||||
<line num="176" count="0" type="stmt"/>
|
||||
<line num="177" count="0" type="stmt"/>
|
||||
<line num="181" count="4" type="cond" truecount="2" falsecount="0"/>
|
||||
<line num="182" count="2" type="stmt"/>
|
||||
<line num="183" count="2" type="cond" truecount="3" falsecount="1"/>
|
||||
<line num="184" count="2" type="stmt"/>
|
||||
<line num="185" count="2" type="stmt"/>
|
||||
<line num="186" count="2" type="stmt"/>
|
||||
<line num="187" count="2" type="stmt"/>
|
||||
<line num="188" count="2" type="stmt"/>
|
||||
<line num="189" count="2" type="cond" truecount="3" falsecount="1"/>
|
||||
<line num="193" count="7" type="cond" truecount="2" falsecount="0"/>
|
||||
<line num="194" count="4" type="stmt"/>
|
||||
<line num="195" count="4" type="cond" truecount="1" falsecount="1"/>
|
||||
<line num="196" count="4" type="stmt"/>
|
||||
<line num="197" count="5" type="stmt"/>
|
||||
<line num="198" count="4" type="cond" truecount="3" falsecount="1"/>
|
||||
<line num="199" count="4" type="cond" truecount="2" falsecount="0"/>
|
||||
<line num="203" count="5" type="cond" truecount="2" falsecount="0"/>
|
||||
<line num="204" count="3" type="stmt"/>
|
||||
<line num="205" count="3" type="cond" truecount="1" falsecount="1"/>
|
||||
<line num="206" count="3" type="stmt"/>
|
||||
<line num="210" count="12" type="cond" truecount="2" falsecount="0"/>
|
||||
<line num="211" count="10" type="cond" truecount="2" falsecount="0"/>
|
||||
<line num="212" count="6" type="stmt"/>
|
||||
<line num="213" count="3" type="stmt"/>
|
||||
<line num="214" count="3" type="stmt"/>
|
||||
<line num="219" count="5" type="cond" truecount="4" falsecount="0"/>
|
||||
<line num="220" count="3" type="stmt"/>
|
||||
<line num="221" count="3" type="cond" truecount="2" falsecount="0"/>
|
||||
<line num="222" count="5" type="cond" truecount="2" falsecount="0"/>
|
||||
<line num="223" count="5" type="cond" truecount="2" falsecount="0"/>
|
||||
<line num="224" count="5" type="cond" truecount="2" falsecount="0"/>
|
||||
<line num="225" count="5" type="cond" truecount="2" falsecount="0"/>
|
||||
<line num="226" count="5" type="stmt"/>
|
||||
<line num="233" count="5" type="stmt"/>
|
||||
<line num="234" count="5" type="stmt"/>
|
||||
<line num="235" count="5" type="stmt"/>
|
||||
<line num="236" count="5" type="stmt"/>
|
||||
<line num="237" count="5" type="stmt"/>
|
||||
<line num="242" count="3" type="cond" truecount="1" falsecount="1"/>
|
||||
<line num="243" count="0" type="stmt"/>
|
||||
<line num="247" count="3" type="cond" truecount="3" falsecount="1"/>
|
||||
<line num="248" count="3" type="stmt"/>
|
||||
<line num="255" count="3" type="cond" truecount="1" falsecount="1"/>
|
||||
<line num="256" count="0" type="stmt"/>
|
||||
<line num="260" count="3" type="cond" truecount="3" falsecount="1"/>
|
||||
<line num="261" count="3" type="stmt"/>
|
||||
<line num="262" count="0" type="stmt"/>
|
||||
<line num="263" count="0" type="cond" truecount="0" falsecount="4"/>
|
||||
<line num="264" count="0" type="stmt"/>
|
||||
<line num="265" count="0" type="stmt"/>
|
||||
<line num="268" count="3" type="stmt"/>
|
||||
<line num="272" count="3" type="cond" truecount="6" falsecount="0"/>
|
||||
<line num="273" count="2" type="stmt"/>
|
||||
<line num="274" count="2" type="stmt"/>
|
||||
<line num="275" count="2" type="stmt"/>
|
||||
<line num="276" count="2" type="stmt"/>
|
||||
<line num="277" count="2" type="stmt"/>
|
||||
<line num="278" count="2" type="cond" truecount="2" falsecount="0"/>
|
||||
<line num="279" count="1" type="stmt"/>
|
||||
<line num="282" count="2" type="stmt"/>
|
||||
<line num="292" count="1" type="stmt"/>
|
||||
<line num="249" count="0" type="stmt"/>
|
||||
<line num="250" count="0" type="cond" truecount="0" falsecount="4"/>
|
||||
<line num="251" count="0" type="stmt"/>
|
||||
<line num="252" count="0" type="stmt"/>
|
||||
<line num="255" count="3" type="stmt"/>
|
||||
<line num="259" count="3" type="cond" truecount="6" falsecount="0"/>
|
||||
<line num="260" count="2" type="stmt"/>
|
||||
<line num="261" count="2" type="stmt"/>
|
||||
<line num="262" count="2" type="stmt"/>
|
||||
<line num="263" count="2" type="stmt"/>
|
||||
<line num="264" count="2" type="stmt"/>
|
||||
<line num="265" count="2" type="cond" truecount="2" falsecount="0"/>
|
||||
<line num="266" count="1" type="stmt"/>
|
||||
<line num="269" count="2" type="stmt"/>
|
||||
<line num="276" count="1" type="stmt"/>
|
||||
</file>
|
||||
</project>
|
||||
</coverage>
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -23,16 +23,16 @@
|
|||
<div class='clearfix'>
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">85.13% </span>
|
||||
<span class="strong">88.51% </span>
|
||||
<span class="quiet">Statements</span>
|
||||
<span class='fraction'>126/148</span>
|
||||
<span class='fraction'>131/148</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">70.73% </span>
|
||||
<span class="strong">76.82% </span>
|
||||
<span class="quiet">Branches</span>
|
||||
<span class='fraction'>58/82</span>
|
||||
<span class='fraction'>63/82</span>
|
||||
</div>
|
||||
|
||||
|
||||
|
|
@ -80,13 +80,13 @@
|
|||
</thead>
|
||||
<tbody><tr>
|
||||
<td class="file high" data-value="webgl-layer-framework.ts"><a href="webgl-layer-framework.ts.html">webgl-layer-framework.ts</a></td>
|
||||
<td data-value="85.13" class="pic high">
|
||||
<div class="chart"><div class="cover-fill" style="width: 85%"></div><div class="cover-empty" style="width: 15%"></div></div>
|
||||
<td data-value="88.51" class="pic high">
|
||||
<div class="chart"><div class="cover-fill" style="width: 88%"></div><div class="cover-empty" style="width: 12%"></div></div>
|
||||
</td>
|
||||
<td data-value="85.13" class="pct high">85.13%</td>
|
||||
<td data-value="148" class="abs high">126/148</td>
|
||||
<td data-value="70.73" class="pct medium">70.73%</td>
|
||||
<td data-value="82" class="abs medium">58/82</td>
|
||||
<td data-value="88.51" class="pct high">88.51%</td>
|
||||
<td data-value="148" class="abs high">131/148</td>
|
||||
<td data-value="76.82" class="pct medium">76.82%</td>
|
||||
<td data-value="82" class="abs medium">63/82</td>
|
||||
<td data-value="84.21" class="pct high">84.21%</td>
|
||||
<td data-value="19" class="abs high">16/19</td>
|
||||
<td data-value="91.26" class="pct high">91.26%</td>
|
||||
|
|
@ -101,7 +101,7 @@
|
|||
<div class='footer quiet pad2 space-top1 center small'>
|
||||
Code coverage generated by
|
||||
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||
at 2026-03-12T13:04:50.459Z
|
||||
at 2026-03-12T13:47:51.911Z
|
||||
</div>
|
||||
<script src="prettify.js"></script>
|
||||
<script>
|
||||
|
|
|
|||
|
|
@ -23,16 +23,16 @@
|
|||
<div class='clearfix'>
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">85.13% </span>
|
||||
<span class="strong">88.51% </span>
|
||||
<span class="quiet">Statements</span>
|
||||
<span class='fraction'>126/148</span>
|
||||
<span class='fraction'>131/148</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">70.73% </span>
|
||||
<span class="strong">76.82% </span>
|
||||
<span class="quiet">Branches</span>
|
||||
<span class='fraction'>58/82</span>
|
||||
<span class='fraction'>63/82</span>
|
||||
</div>
|
||||
|
||||
|
||||
|
|
@ -339,25 +339,7 @@
|
|||
<a name='L274'></a><a href='#L274'>274</a>
|
||||
<a name='L275'></a><a href='#L275'>275</a>
|
||||
<a name='L276'></a><a href='#L276'>276</a>
|
||||
<a name='L277'></a><a href='#L277'>277</a>
|
||||
<a name='L278'></a><a href='#L278'>278</a>
|
||||
<a name='L279'></a><a href='#L279'>279</a>
|
||||
<a name='L280'></a><a href='#L280'>280</a>
|
||||
<a name='L281'></a><a href='#L281'>281</a>
|
||||
<a name='L282'></a><a href='#L282'>282</a>
|
||||
<a name='L283'></a><a href='#L283'>283</a>
|
||||
<a name='L284'></a><a href='#L284'>284</a>
|
||||
<a name='L285'></a><a href='#L285'>285</a>
|
||||
<a name='L286'></a><a href='#L286'>286</a>
|
||||
<a name='L287'></a><a href='#L287'>287</a>
|
||||
<a name='L288'></a><a href='#L288'>288</a>
|
||||
<a name='L289'></a><a href='#L289'>289</a>
|
||||
<a name='L290'></a><a href='#L290'>290</a>
|
||||
<a name='L291'></a><a href='#L291'>291</a>
|
||||
<a name='L292'></a><a href='#L292'>292</a>
|
||||
<a name='L293'></a><a href='#L293'>293</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<a name='L277'></a><a href='#L277'>277</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
|
|
@ -441,27 +423,19 @@
|
|||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">35x</span>
|
||||
<span class="cline-any cline-yes">35x</span>
|
||||
<span class="cline-any cline-yes">35x</span>
|
||||
<span class="cline-any cline-yes">35x</span>
|
||||
<span class="cline-any cline-yes">35x</span>
|
||||
<span class="cline-any cline-yes">35x</span>
|
||||
<span class="cline-any cline-yes">35x</span>
|
||||
<span class="cline-any cline-yes">35x</span>
|
||||
<span class="cline-any cline-yes">35x</span>
|
||||
<span class="cline-any cline-yes">35x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">26x</span>
|
||||
<span class="cline-any cline-yes">26x</span>
|
||||
<span class="cline-any cline-yes">26x</span>
|
||||
<span class="cline-any cline-yes">26x</span>
|
||||
<span class="cline-any cline-yes">26x</span>
|
||||
<span class="cline-any cline-yes">26x</span>
|
||||
<span class="cline-any cline-yes">26x</span>
|
||||
<span class="cline-any cline-yes">26x</span>
|
||||
<span class="cline-any cline-yes">26x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">26x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">2x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">3x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
|
|
@ -525,17 +499,16 @@
|
|||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">3x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">3x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">3x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">4x</span>
|
||||
<span class="cline-any cline-yes">6x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">4x</span>
|
||||
<span class="cline-any cline-yes">4x</span>
|
||||
<span class="cline-any cline-yes">6x</span>
|
||||
<span class="cline-any cline-yes">6x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
|
|
@ -546,19 +519,19 @@
|
|||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">2x</span>
|
||||
<span class="cline-any cline-yes">2x</span>
|
||||
<span class="cline-any cline-yes">2x</span>
|
||||
<span class="cline-any cline-yes">2x</span>
|
||||
<span class="cline-any cline-yes">2x</span>
|
||||
<span class="cline-any cline-yes">2x</span>
|
||||
<span class="cline-any cline-yes">2x</span>
|
||||
<span class="cline-any cline-yes">2x</span>
|
||||
<span class="cline-any cline-yes">2x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">4x</span>
|
||||
<span class="cline-any cline-yes">2x</span>
|
||||
<span class="cline-any cline-yes">2x</span>
|
||||
<span class="cline-any cline-yes">2x</span>
|
||||
<span class="cline-any cline-yes">2x</span>
|
||||
<span class="cline-any cline-yes">2x</span>
|
||||
<span class="cline-any cline-yes">2x</span>
|
||||
<span class="cline-any cline-yes">2x</span>
|
||||
<span class="cline-any cline-yes">2x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">7x</span>
|
||||
<span class="cline-any cline-yes">4x</span>
|
||||
<span class="cline-any cline-yes">4x</span>
|
||||
<span class="cline-any cline-yes">4x</span>
|
||||
|
|
@ -568,14 +541,14 @@
|
|||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">3x</span>
|
||||
<span class="cline-any cline-yes">5x</span>
|
||||
<span class="cline-any cline-yes">3x</span>
|
||||
<span class="cline-any cline-yes">3x</span>
|
||||
<span class="cline-any cline-yes">3x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">10x</span>
|
||||
<span class="cline-any cline-yes">12x</span>
|
||||
<span class="cline-any cline-yes">10x</span>
|
||||
<span class="cline-any cline-yes">6x</span>
|
||||
<span class="cline-any cline-yes">3x</span>
|
||||
|
|
@ -584,27 +557,25 @@
|
|||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">5x</span>
|
||||
<span class="cline-any cline-yes">3x</span>
|
||||
<span class="cline-any cline-yes">3x</span>
|
||||
<span class="cline-any cline-yes">3x</span>
|
||||
<span class="cline-any cline-yes">3x</span>
|
||||
<span class="cline-any cline-yes">3x</span>
|
||||
<span class="cline-any cline-yes">3x</span>
|
||||
<span class="cline-any cline-yes">3x</span>
|
||||
<span class="cline-any cline-yes">3x</span>
|
||||
<span class="cline-any cline-yes">5x</span>
|
||||
<span class="cline-any cline-yes">5x</span>
|
||||
<span class="cline-any cline-yes">5x</span>
|
||||
<span class="cline-any cline-yes">5x</span>
|
||||
<span class="cline-any cline-yes">5x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">3x</span>
|
||||
<span class="cline-any cline-yes">3x</span>
|
||||
<span class="cline-any cline-yes">3x</span>
|
||||
<span class="cline-any cline-yes">3x</span>
|
||||
<span class="cline-any cline-yes">3x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">5x</span>
|
||||
<span class="cline-any cline-yes">5x</span>
|
||||
<span class="cline-any cline-yes">5x</span>
|
||||
<span class="cline-any cline-yes">5x</span>
|
||||
<span class="cline-any cline-yes">5x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
|
|
@ -643,14 +614,9 @@
|
|||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-neutral"> </span></td><td class="text"><pre class="prettyprint lang-js">import { Group, OrthographicCamera, Scene, WebGLRenderer } from "three";
|
||||
|
||||
// ─── Pure exports (testable without DOM or WebGL) ────────────────────────────
|
||||
|
||||
/**
|
||||
* Converts a D3 zoom transform into orthographic camera bounds.
|
||||
*
|
||||
|
|
@ -732,8 +698,6 @@ interface RegisteredLayer {
|
|||
group: Group; // framework-owned; passed to all callbacks — abstraction boundary
|
||||
}
|
||||
|
||||
// ─── Class ───────────────────────────────────────────────────────────────────
|
||||
|
||||
export class WebGL2LayerFrameworkClass {
|
||||
private canvas: HTMLCanvasElement | null = null;
|
||||
private renderer: WebGLRenderer | null = null;
|
||||
|
|
@ -744,17 +708,11 @@ export class WebGL2LayerFrameworkClass {
|
|||
private resizeObserver: ResizeObserver | null = null;
|
||||
private rafId: number | null = null;
|
||||
private container: HTMLElement | null = null;
|
||||
|
||||
// Backing field — MUST NOT be declared readonly.
|
||||
// readonly fields can only be assigned in the constructor; init() sets _fallback
|
||||
// post-construction, which would cause a TypeScript type error with readonly.
|
||||
private _fallback = false;
|
||||
|
||||
get hasFallback(): boolean {
|
||||
return this._fallback;
|
||||
}
|
||||
|
||||
// ─── Public API ────────────────────────────────────────────────────────────
|
||||
|
||||
init(): boolean {
|
||||
this._fallback = !detectWebGL2();
|
||||
|
|
@ -817,7 +775,6 @@ export class WebGL2LayerFrameworkClass {
|
|||
this.layers.set(config.id, { config, group });
|
||||
}
|
||||
this.pendingConfigs = [];
|
||||
|
||||
this.observeResize();
|
||||
|
||||
return true;
|
||||
|
|
@ -838,7 +795,7 @@ export class WebGL2LayerFrameworkClass {
|
|||
}
|
||||
|
||||
unregister(id: string): void {
|
||||
<span class="missing-if-branch" title="if path not taken" >I</span>if (this._fallback) <span class="cstat-no" title="statement not covered" >return;</span>
|
||||
if (this._fallback) return;
|
||||
const layer = this.layers.get(id);
|
||||
<span class="missing-if-branch" title="if path not taken" >I</span>if (!layer || !this.scene) <span class="cstat-no" title="statement not covered" >return;</span>
|
||||
const scene = this.scene;
|
||||
|
|
@ -850,7 +807,7 @@ export class WebGL2LayerFrameworkClass {
|
|||
}
|
||||
|
||||
setVisible(id: string, visible: boolean): void {
|
||||
<span class="missing-if-branch" title="if path not taken" >I</span>if (this._fallback) <span class="cstat-no" title="statement not covered" >return;</span>
|
||||
if (this._fallback) return;
|
||||
const layer = this.layers.get(id);
|
||||
<span class="missing-if-branch" title="if path not taken" >I</span>if (!layer) <span class="cstat-no" title="statement not covered" >return;</span>
|
||||
layer.group.visible = visible;
|
||||
|
|
@ -860,14 +817,14 @@ export class WebGL2LayerFrameworkClass {
|
|||
}
|
||||
|
||||
clearLayer(id: string): void {
|
||||
<span class="missing-if-branch" title="if path not taken" >I</span>if (this._fallback) <span class="cstat-no" title="statement not covered" >return;</span>
|
||||
if (this._fallback) return;
|
||||
const layer = this.layers.get(id);
|
||||
<span class="missing-if-branch" title="if path not taken" >I</span>if (!layer) <span class="cstat-no" title="statement not covered" >return;</span>
|
||||
layer.group.clear();
|
||||
}
|
||||
|
||||
requestRender(): void {
|
||||
<span class="missing-if-branch" title="if path not taken" >I</span>if (this._fallback) <span class="cstat-no" title="statement not covered" >return;</span>
|
||||
if (this._fallback) return;
|
||||
if (this.rafId !== null) return;
|
||||
this.rafId = requestAnimationFrame(() => {
|
||||
this.rafId = null;
|
||||
|
|
@ -876,7 +833,7 @@ export class WebGL2LayerFrameworkClass {
|
|||
}
|
||||
|
||||
syncTransform(): void {
|
||||
<span class="missing-if-branch" title="if path not taken" >I</span>if (this._fallback || !this.camera) <span class="cstat-no" title="statement not covered" >return;</span>
|
||||
if (this._fallback || !this.camera) return;
|
||||
const camera = this.camera;
|
||||
const viewX = (globalThis as any).viewX ?? 0;
|
||||
const viewY = (globalThis as any).viewY ?? 0;
|
||||
|
|
@ -896,8 +853,6 @@ export class WebGL2LayerFrameworkClass {
|
|||
camera.bottom = bounds.bottom;
|
||||
camera.updateProjectionMatrix();
|
||||
}
|
||||
|
||||
// ─── Private helpers ───────────────────────────────────────────────────────
|
||||
|
||||
private subscribeD3Zoom(): void {
|
||||
// viewbox is a D3 selection global available in the browser; guard for Node test env
|
||||
|
|
@ -932,9 +887,6 @@ export class WebGL2LayerFrameworkClass {
|
|||
}
|
||||
}
|
||||
|
||||
// ─── Global registration (MUST be last line) ─────────────────────────────────
|
||||
// Uses globalThis (≡ window in browsers) to support both browser runtime and
|
||||
// Node.js test environments without a ReferenceError.
|
||||
declare global {
|
||||
var WebGL2LayerFramework: WebGL2LayerFrameworkClass;
|
||||
}
|
||||
|
|
@ -946,7 +898,7 @@ globalThis.WebGL2LayerFramework = new WebGL2LayerFrameworkClass();
|
|||
<div class='footer quiet pad2 space-top1 center small'>
|
||||
Code coverage generated by
|
||||
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||
at 2026-03-12T13:04:50.459Z
|
||||
at 2026-03-12T13:47:51.911Z
|
||||
</div>
|
||||
<script src="prettify.js"></script>
|
||||
<script>
|
||||
|
|
|
|||
|
|
@ -559,3 +559,75 @@ describe("WebGL2LayerFrameworkClass — lifecycle & render loop (Story 1.3)", ()
|
|||
expect(canvas.style.display).toBe("none");
|
||||
});
|
||||
});
|
||||
|
||||
// ─── WebGL2LayerFramework fallback no-op path (Story 2.3) ───────────────────
|
||||
|
||||
describe("WebGL2LayerFramework — fallback no-op path (Story 2.3)", () => {
|
||||
let framework: WebGL2LayerFrameworkClass;
|
||||
|
||||
const makeConfig = () => ({
|
||||
id: "terrain",
|
||||
anchorLayerId: "terrain",
|
||||
renderOrder: 2,
|
||||
setup: vi.fn(),
|
||||
render: vi.fn(),
|
||||
dispose: vi.fn(),
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
framework = new WebGL2LayerFrameworkClass();
|
||||
(framework as any)._fallback = true;
|
||||
});
|
||||
|
||||
it("hasFallback getter returns true when _fallback is set", () => {
|
||||
expect(framework.hasFallback).toBe(true);
|
||||
});
|
||||
|
||||
it("register() queues config but does not call setup() when fallback is active", () => {
|
||||
// When _fallback=true, scene is null (init() exits early without creating scene).
|
||||
// register() therefore queues into pendingConfigs[] — setup() is never called.
|
||||
const config = makeConfig();
|
||||
expect(() => framework.register(config)).not.toThrow();
|
||||
expect(config.setup).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("setVisible() is a no-op when fallback is active — no exception for false", () => {
|
||||
expect(() => framework.setVisible("terrain", false)).not.toThrow();
|
||||
});
|
||||
|
||||
it("setVisible() is a no-op when fallback is active — no exception for true", () => {
|
||||
expect(() => framework.setVisible("terrain", true)).not.toThrow();
|
||||
});
|
||||
|
||||
it("clearLayer() is a no-op when fallback is active", () => {
|
||||
expect(() => framework.clearLayer("terrain")).not.toThrow();
|
||||
});
|
||||
|
||||
it("requestRender() is a no-op when fallback is active — RAF not scheduled", () => {
|
||||
const rafMock = vi.fn().mockReturnValue(1);
|
||||
vi.stubGlobal("requestAnimationFrame", rafMock);
|
||||
expect(() => framework.requestRender()).not.toThrow();
|
||||
expect(rafMock).not.toHaveBeenCalled();
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
it("unregister() is a no-op when fallback is active", () => {
|
||||
expect(() => framework.unregister("terrain")).not.toThrow();
|
||||
});
|
||||
|
||||
it("syncTransform() is a no-op when fallback is active", () => {
|
||||
expect(() => framework.syncTransform()).not.toThrow();
|
||||
});
|
||||
|
||||
it("NFR-C1: no console.error emitted during fallback operations", () => {
|
||||
const errorSpy = vi.spyOn(console, "error");
|
||||
framework.register(makeConfig());
|
||||
framework.setVisible("terrain", false);
|
||||
framework.clearLayer("terrain");
|
||||
framework.requestRender();
|
||||
framework.unregister("terrain");
|
||||
framework.syncTransform();
|
||||
expect(errorSpy).not.toHaveBeenCalled();
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,40 +1,71 @@
|
|||
import * as THREE from "three";
|
||||
import {
|
||||
BufferAttribute,
|
||||
BufferGeometry,
|
||||
DoubleSide,
|
||||
type Group,
|
||||
LinearFilter,
|
||||
LinearMipmapLinearFilter,
|
||||
Mesh,
|
||||
MeshBasicMaterial,
|
||||
SRGBColorSpace,
|
||||
type Texture,
|
||||
TextureLoader,
|
||||
} from "three";
|
||||
import { RELIEF_SYMBOLS } from "../config/relief-config";
|
||||
import type { ReliefIcon } from "../modules/relief-generator";
|
||||
import { generateRelief } from "../modules/relief-generator";
|
||||
import { getLayerZIndex } from "../modules/webgl-layer-framework";
|
||||
import { byId } from "../utils";
|
||||
|
||||
let glCanvas: HTMLCanvasElement | null = null;
|
||||
let renderer: THREE.WebGLRenderer | null = null;
|
||||
let camera: THREE.OrthographicCamera | null = null;
|
||||
let scene: THREE.Scene | null = null;
|
||||
|
||||
const textureCache = new Map<string, THREE.Texture>(); // set name → THREE.Texture
|
||||
|
||||
const textureCache = new Map<string, Texture>(); // set name → Texture
|
||||
let terrainGroup: Group | null = null;
|
||||
let lastBuiltIcons: ReliefIcon[] | null = null;
|
||||
let lastBuiltSet: string | null = null;
|
||||
|
||||
WebGL2LayerFramework.register({
|
||||
id: "terrain",
|
||||
anchorLayerId: "terrain",
|
||||
renderOrder: getLayerZIndex("terrain"),
|
||||
setup(group: Group): void {
|
||||
terrainGroup = group;
|
||||
preloadTextures();
|
||||
},
|
||||
render(_group: Group): void {
|
||||
// no-op: relief geometry is static between drawRelief() calls
|
||||
},
|
||||
dispose(group: Group): void {
|
||||
group.traverse((obj) => {
|
||||
if (obj instanceof Mesh) {
|
||||
obj.geometry.dispose();
|
||||
(obj.material as MeshBasicMaterial).map?.dispose();
|
||||
(obj.material as MeshBasicMaterial).dispose();
|
||||
}
|
||||
});
|
||||
disposeTextureCache();
|
||||
},
|
||||
});
|
||||
|
||||
function preloadTextures(): void {
|
||||
for (const set of Object.keys(RELIEF_SYMBOLS)) loadTexture(set);
|
||||
}
|
||||
|
||||
function loadTexture(set: string): Promise<THREE.Texture | null> {
|
||||
function loadTexture(set: string): Promise<Texture | null> {
|
||||
if (textureCache.has(set))
|
||||
return Promise.resolve(textureCache.get(set) || null);
|
||||
return Promise.resolve(textureCache.get(set) ?? null);
|
||||
|
||||
return new Promise((resolve) => {
|
||||
const loader = new THREE.TextureLoader();
|
||||
const loader = new TextureLoader();
|
||||
loader.load(
|
||||
`images/relief/${set}.png`,
|
||||
(texture) => {
|
||||
texture.flipY = false;
|
||||
texture.colorSpace = THREE.SRGBColorSpace;
|
||||
texture.colorSpace = SRGBColorSpace;
|
||||
texture.needsUpdate = true;
|
||||
texture.minFilter = THREE.LinearMipmapLinearFilter;
|
||||
texture.magFilter = THREE.LinearFilter;
|
||||
texture.minFilter = LinearMipmapLinearFilter;
|
||||
texture.magFilter = LinearFilter;
|
||||
texture.generateMipmaps = true;
|
||||
if (renderer)
|
||||
texture.anisotropy = renderer.capabilities.getMaxAnisotropy();
|
||||
// renderer.capabilities.getMaxAnisotropy() removed: renderer is now owned by
|
||||
// WebGL2LayerFramework. LinearMipmapLinearFilter provides sufficient quality.
|
||||
textureCache.set(set, texture);
|
||||
resolve(texture);
|
||||
},
|
||||
|
|
@ -47,64 +78,6 @@ function loadTexture(set: string): Promise<THREE.Texture | null> {
|
|||
});
|
||||
}
|
||||
|
||||
function ensureRenderer(): boolean {
|
||||
if (!byId("terrain")) return false;
|
||||
|
||||
if (renderer) {
|
||||
if (renderer.getContext().isContextLost()) {
|
||||
// Recover from WebGL context loss
|
||||
renderer.forceContextRestore();
|
||||
renderer.dispose();
|
||||
renderer = null;
|
||||
camera = null;
|
||||
scene = null;
|
||||
glCanvas = null;
|
||||
disposeTextureCache();
|
||||
lastBuiltIcons = null;
|
||||
lastBuiltSet = null;
|
||||
} else {
|
||||
// Re-attach if the canvas was removed from the DOM externally.
|
||||
if (glCanvas && !glCanvas.isConnected) {
|
||||
const terrainSvg = byId("map-layer-terrain");
|
||||
if (terrainSvg)
|
||||
terrainSvg.parentElement!.insertBefore(glCanvas, terrainSvg);
|
||||
else document.body.appendChild(glCanvas);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
glCanvas = document.createElement("canvas");
|
||||
glCanvas.id = "terrainCanvas";
|
||||
glCanvas.style.cssText =
|
||||
"display:block;pointer-events:none;position:absolute;top:0;left:0";
|
||||
const map = byId("map");
|
||||
if (map) document.body.insertAdjacentElement("afterend", glCanvas);
|
||||
|
||||
try {
|
||||
renderer = new THREE.WebGLRenderer({
|
||||
canvas: glCanvas,
|
||||
alpha: true,
|
||||
antialias: false,
|
||||
});
|
||||
renderer.setClearColor(0x000000, 0);
|
||||
renderer.setPixelRatio(window.devicePixelRatio || 1);
|
||||
renderer.setSize(graphWidth, graphHeight);
|
||||
} catch (e) {
|
||||
console.error("Relief: WebGL init failed", e);
|
||||
glCanvas.remove();
|
||||
glCanvas = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Camera in SVG coordinate space: top=0, bottom=H puts map y=0 at screen-top.
|
||||
camera = new THREE.OrthographicCamera(0, graphWidth, 0, graphHeight, -1, 1);
|
||||
scene = new THREE.Scene();
|
||||
|
||||
preloadTextures();
|
||||
return true;
|
||||
}
|
||||
|
||||
// map a symbol href to its atlas set and tile index
|
||||
function resolveSprite(symbolHref: string): {
|
||||
set: string;
|
||||
|
|
@ -118,12 +91,12 @@ function resolveSprite(symbolHref: string): {
|
|||
throw new Error(`Relief: unknown symbol href "${symbolHref}"`);
|
||||
}
|
||||
|
||||
// Build a BufferGeometry with all icon quads for one atlas set.
|
||||
// Build a Mesh with all icon quads for one atlas set.
|
||||
function buildSetMesh(
|
||||
entries: Array<{ icon: ReliefIcon; tileIndex: number }>,
|
||||
set: string,
|
||||
texture: any,
|
||||
): any {
|
||||
texture: Texture,
|
||||
): Mesh {
|
||||
const ids = RELIEF_SYMBOLS[set] ?? [];
|
||||
const n = ids.length || 1;
|
||||
const cols = Math.ceil(Math.sqrt(n));
|
||||
|
|
@ -142,6 +115,12 @@ function buildSetMesh(
|
|||
u1 = (col + 1) / cols;
|
||||
const v0 = row / rows,
|
||||
v1 = (row + 1) / rows;
|
||||
// FR15 rotation verification (Story 2.1): r.i is a sequential icon index (0-based),
|
||||
// NOT a rotation angle. pack.relief entries contain no rotation field.
|
||||
// Both the WebGL path (this function) and the SVG fallback (drawSvg) produce
|
||||
// unrotated icons — visual parity maintained per FR19.
|
||||
// If per-icon rotation is required in a future story, add `rotation: number` (radians)
|
||||
// to ReliefIcon and apply quad rotation around center (r.x + r.s/2, r.y + r.s/2).
|
||||
const x0 = r.x,
|
||||
x1 = r.x + r.s;
|
||||
const y0 = r.y,
|
||||
|
|
@ -163,20 +142,20 @@ function buildSetMesh(
|
|||
ii += 6;
|
||||
}
|
||||
|
||||
const geo = new THREE.BufferGeometry();
|
||||
geo.setAttribute("position", new THREE.BufferAttribute(positions, 3));
|
||||
geo.setAttribute("uv", new THREE.BufferAttribute(uvs, 2));
|
||||
geo.setIndex(new THREE.BufferAttribute(indices, 1));
|
||||
const geo = new BufferGeometry();
|
||||
geo.setAttribute("position", new BufferAttribute(positions, 3));
|
||||
geo.setAttribute("uv", new BufferAttribute(uvs, 2));
|
||||
geo.setIndex(new BufferAttribute(indices, 1));
|
||||
|
||||
const mat = new THREE.MeshBasicMaterial({
|
||||
const mat = new MeshBasicMaterial({
|
||||
map: texture,
|
||||
transparent: true,
|
||||
side: THREE.DoubleSide,
|
||||
side: DoubleSide,
|
||||
depthTest: false,
|
||||
depthWrite: false,
|
||||
});
|
||||
|
||||
return new THREE.Mesh(geo, mat);
|
||||
return new Mesh(geo, mat);
|
||||
}
|
||||
|
||||
function disposeTextureCache(): void {
|
||||
|
|
@ -184,25 +163,15 @@ function disposeTextureCache(): void {
|
|||
textureCache.clear();
|
||||
}
|
||||
|
||||
function disposeScene(): void {
|
||||
if (!scene) return;
|
||||
while (scene.children.length) {
|
||||
const mesh = scene.children[0] as THREE.Mesh<
|
||||
THREE.BufferGeometry,
|
||||
THREE.Material
|
||||
>;
|
||||
scene.remove(mesh);
|
||||
mesh.geometry?.dispose();
|
||||
if (mesh.material && "map" in mesh.material) {
|
||||
mesh.material.map = null;
|
||||
mesh.material.dispose();
|
||||
function buildReliefScene(icons: ReliefIcon[]): void {
|
||||
if (!terrainGroup) return;
|
||||
terrainGroup.traverse((obj) => {
|
||||
if (obj instanceof Mesh) {
|
||||
obj.geometry.dispose();
|
||||
(obj.material as MeshBasicMaterial).dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function buildScene(icons: ReliefIcon[]): void {
|
||||
if (!scene) return;
|
||||
disposeScene();
|
||||
});
|
||||
terrainGroup.clear();
|
||||
|
||||
const bySet = new Map<
|
||||
string,
|
||||
|
|
@ -221,40 +190,7 @@ function buildScene(icons: ReliefIcon[]): void {
|
|||
for (const [set, setEntries] of bySet) {
|
||||
const texture = textureCache.get(set);
|
||||
if (!texture) continue;
|
||||
scene.add(buildSetMesh(setEntries, set, texture));
|
||||
}
|
||||
}
|
||||
|
||||
function renderFrame(): void {
|
||||
if (!renderer || !camera || !scene) return;
|
||||
|
||||
const x = -viewX / scale;
|
||||
const y = -viewY / scale;
|
||||
const w = graphWidth / scale;
|
||||
const h = graphHeight / scale;
|
||||
|
||||
camera.left = x;
|
||||
camera.right = x + w;
|
||||
camera.top = y;
|
||||
camera.bottom = y + h;
|
||||
camera.updateProjectionMatrix();
|
||||
renderer.render(scene, camera);
|
||||
}
|
||||
|
||||
function drawWebGl(icons: ReliefIcon[], parentEl: HTMLElement): void {
|
||||
const set = parentEl.getAttribute("set") || "simple";
|
||||
|
||||
if (ensureRenderer()) {
|
||||
loadTexture(set).then(() => {
|
||||
if (icons !== lastBuiltIcons || set !== lastBuiltSet) {
|
||||
buildScene(icons);
|
||||
lastBuiltIcons = icons;
|
||||
lastBuiltSet = set;
|
||||
}
|
||||
renderFrame();
|
||||
});
|
||||
} else {
|
||||
WARN && console.warn("Relief: WebGL renderer failed");
|
||||
terrainGroup.add(buildSetMesh(setEntries, set, texture));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -274,46 +210,36 @@ window.drawRelief = (
|
|||
if (!parentEl) throw new Error("Relief: parent element not found");
|
||||
|
||||
parentEl.innerHTML = "";
|
||||
parentEl.dataset.mode = "webGL";
|
||||
parentEl.dataset.mode = type;
|
||||
|
||||
const icons = pack.relief?.length ? pack.relief : generateRelief();
|
||||
if (!icons.length) return;
|
||||
|
||||
if (type === "svg") drawSvg(icons, parentEl);
|
||||
else drawWebGl(icons, parentEl);
|
||||
if (type === "svg" || WebGL2LayerFramework.hasFallback) {
|
||||
drawSvg(icons, parentEl);
|
||||
} else {
|
||||
const set = parentEl.getAttribute("set") || "simple";
|
||||
loadTexture(set).then(() => {
|
||||
if (icons !== lastBuiltIcons || set !== lastBuiltSet) {
|
||||
buildReliefScene(icons);
|
||||
lastBuiltIcons = icons;
|
||||
lastBuiltSet = set;
|
||||
}
|
||||
WebGL2LayerFramework.requestRender();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
window.undrawRelief = () => {
|
||||
WebGL2LayerFramework.clearLayer("terrain");
|
||||
lastBuiltIcons = null;
|
||||
lastBuiltSet = null;
|
||||
const terrainEl = byId("terrain");
|
||||
const mode = terrainEl?.dataset.mode || "webGL";
|
||||
if (mode === "webGL") {
|
||||
disposeScene();
|
||||
disposeTextureCache();
|
||||
if (renderer) {
|
||||
renderer.dispose();
|
||||
renderer = null;
|
||||
}
|
||||
if (glCanvas) {
|
||||
if (glCanvas.isConnected) glCanvas.remove();
|
||||
glCanvas = null;
|
||||
}
|
||||
camera = null;
|
||||
scene = null;
|
||||
lastBuiltIcons = null;
|
||||
lastBuiltSet = null;
|
||||
}
|
||||
|
||||
if (terrainEl) terrainEl.innerHTML = "";
|
||||
};
|
||||
|
||||
// re-render the current WebGL frame (called on pan/zoom); coalesced to one GPU draw per animation frame
|
||||
let rafId: number | null = null;
|
||||
window.rerenderReliefIcons = () => {
|
||||
if (rafId !== null) cancelAnimationFrame(rafId);
|
||||
rafId = requestAnimationFrame(() => {
|
||||
rafId = null;
|
||||
renderFrame();
|
||||
});
|
||||
WebGL2LayerFramework.requestRender();
|
||||
};
|
||||
|
||||
declare global {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue