<h5 dir="auto">Description of the problem</h5>
<p dir="auto">Given the code below, only one request should be fired.</p>
<div class="highlight highlight-source-js notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="THREE.Cache.enabled = true
var fileLoader = new THREE.FileLoader();
fileLoader.load('foo.jpg');  // A
fileLoader.load('foo.jpg');  // B"><pre class="notranslate"><span class="pl-c1">THREE</span><span class="pl-kos">.</span><span class="pl-c1">Cache</span><span class="pl-kos">.</span><span class="pl-c1">enabled</span> <span class="pl-c1">=</span> <span class="pl-c1">true</span>
<span class="pl-k">var</span> <span class="pl-s1">fileLoader</span> <span class="pl-c1">=</span> <span class="pl-k">new</span> <span class="pl-c1">THREE</span><span class="pl-kos">.</span><span class="pl-c1">FileLoader</span><span class="pl-kos">(</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
<span class="pl-s1">fileLoader</span><span class="pl-kos">.</span><span class="pl-en">load</span><span class="pl-kos">(</span><span class="pl-s">'foo.jpg'</span><span class="pl-kos">)</span><span class="pl-kos">;</span>  <span class="pl-c">// A</span>
<span class="pl-s1">fileLoader</span><span class="pl-kos">.</span><span class="pl-en">load</span><span class="pl-kos">(</span><span class="pl-s">'foo.jpg'</span><span class="pl-kos">)</span><span class="pl-kos">;</span>  <span class="pl-c">// B</span></pre></div>
<ol dir="auto">
<li><code class="notranslate">A</code> checks the cache and sees no pending requests.</li>
<li><code class="notranslate">A</code> makes the request and stores the pending request in the cache as a Promise. The Promise will resolve the file.</li>
<li><code class="notranslate">B</code> checks the cache and sees a pending request. <code class="notranslate">B</code> waits for the Promise to resolve the file.</li>
<li>The request finishes, and the Promise resolves.</li>
<li><code class="notranslate">A</code> and <code class="notranslate">B</code>, both waiting on the Promise, triggers their callbacks at the same time with the same file response.</li>
</ol>
<p dir="auto">I'd like to volunteer to work on this. In A-Frame, we often have been having to maintain a cache of promises to prevent duplicate requests. If it sounds reasonable, below are several possible implementations:</p>
<ol dir="auto">
<li>
<p dir="auto">Change THREE.Cache to store promises that resolve files, rather than directly storing files. I think this is the optimal solution because requests will always be asynchronous. This is sort of a breaking change, but three.js applications don't often enable the cache, or don't care about the internals of the cache.</p>
</li>
<li>
<p dir="auto">Create a separate cache object (<code class="notranslate">RequestCache</code>) for pending requests. The current Cache stays intact, but there is more logic to maintain both the pending and complete caches.</p>
</li>
<li>
<p dir="auto">Keep the pending request cache internal to the FileLoader. It'll just be a variable <code class="notranslate">var requestCache = {}</code>.</p>
</li>
</ol>
<p dir="auto">We'd also need a Promise polyfill. They're less than 1KB gzipped if that sounds fine.</p>
<h5 dir="auto">Three.js version</h5>
<ul class="contains-task-list">
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" checked=""> Dev</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" checked=""> r84</li>
</ul>
<h5 dir="auto">Browser</h5>
<ul class="contains-task-list">
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" checked=""> All of them</li>
</ul>
<h5 dir="auto">OS</h5>
<ul class="contains-task-list">
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" checked=""> All of them</li>
</ul>
<h5 dir="auto">Hardware Requirements (graphics card, VR Device, ...)</h5>
<p dir="auto">N/A</p>
<h5 dir="auto">Description of the problem</h5>
<p dir="auto">If a Hole in the form of one or more absarc/absellipse/arc/ellipse segments forming a full circle or ellipse is added to a 2D Shape and rendered as a ShapeBufferGeometry, this can wreck the Shape, anything from removing two triangles between one vertice in the outer Path and the three vertices around an absarc/absellipse/arc/ellipse start/end, to not rendering the Shape at all, depending on the Shape outer Path geometry. It gives the warning "THREE.ShapeUtils: Unable to triangulate polygon! in triangulate()". This does not happen if the Hole is a single closed bezierCurveTo or any other Curve segments, independent of what Curve segments make up the outer Path of the Shape. It does however give a warning for each Path start/end "THREE.ShapeUtils: Duplicate point".</p>
<p dir="auto">All vertices are correctly added to the geometry position attribute, but the problem seems to be with vertex indices, which are added oddly, incomplete or not at all, depending on the Shape outer Path. The fact that the glitch always seems to emanate around an absarc/absellipse/arc/ellipse start or end indicates that it may be the wrapping there working incorrectly. The actual reason seems to be that the start and end points at the angles 0 and 2 * Math.PI can become unequal, because Math.sin( 0 ) === 0 but Math.sin( 2 * Math.PI ) ===  -2.4492935982947064e-16, so the "duplicate" end point is not removed following an equal comparison (ShapeUtils.js line 38-40). In some cases, this still works, because rounding errors compensate for the sin error.</p>
<p dir="auto">There are several ways to solve this. The probably most correct one, to handle any kind of rounding errors, would be to replace the straightforward function</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="Vector3.equals = function ( v ) {
    return ( ( v.x === this.x ) &amp;&amp; ( v.y === this.y ) &amp;&amp; ( v.z === this.z ) );
}"><pre class="notranslate"><code class="notranslate">Vector3.equals = function ( v ) {
    return ( ( v.x === this.x ) &amp;&amp; ( v.y === this.y ) &amp;&amp; ( v.z === this.z ) );
}
</code></pre></div>
<p dir="auto">with a more complex function similar to</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="Vector3.approximates = function ( v, re ) {
    // re = permitted relative error
    return (
        Math.abs( v.x - this.x ) / ( 1 + Math.abs( v.x ) + Math.abs( this.x ) ) &lt;= re &amp;&amp;
        Math.abs( v.y - this.y ) / ( 1 + Math.abs( v.y ) + Math.abs( this.y ) ) &lt;= re &amp;&amp;
        Math.abs( v.z - this.z ) / ( 1 + Math.abs( v.z ) + Math.abs( this.z ) ) &lt;= re
    );
}"><pre class="notranslate"><code class="notranslate">Vector3.approximates = function ( v, re ) {
    // re = permitted relative error
    return (
        Math.abs( v.x - this.x ) / ( 1 + Math.abs( v.x ) + Math.abs( this.x ) ) &lt;= re &amp;&amp;
        Math.abs( v.y - this.y ) / ( 1 + Math.abs( v.y ) + Math.abs( this.y ) ) &lt;= re &amp;&amp;
        Math.abs( v.z - this.z ) / ( 1 + Math.abs( v.z ) + Math.abs( this.z ) ) &lt;= re
    );
}
</code></pre></div>
<p dir="auto">or a joined function, keeping (most of) the simplicity and speed in the simple case</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="Vector3.equals = function ( v, re ) {
    // re = permitted relative error, optional
    if ( v.x === this.x &amp;&amp; v.y === this.y &amp;&amp; v.z === this.z )
        return true;
    if ( re === undefined || re &lt;= 0 )
        return false;
    return (
        Math.abs( v.x - this.x ) &lt;= re * ( 1 + Math.abs( v.x ) + Math.abs( this.x ) ) &amp;&amp;
        Math.abs( v.y - this.y ) &lt;= re * ( 1 + Math.abs( v.y ) + Math.abs( this.y ) ) &amp;&amp;
        Math.abs( v.z - this.z ) &lt;= re * ( 1 + Math.abs( v.z ) + Math.abs( this.z ) )
    );
}"><pre class="notranslate"><code class="notranslate">Vector3.equals = function ( v, re ) {
    // re = permitted relative error, optional
    if ( v.x === this.x &amp;&amp; v.y === this.y &amp;&amp; v.z === this.z )
        return true;
    if ( re === undefined || re &lt;= 0 )
        return false;
    return (
        Math.abs( v.x - this.x ) &lt;= re * ( 1 + Math.abs( v.x ) + Math.abs( this.x ) ) &amp;&amp;
        Math.abs( v.y - this.y ) &lt;= re * ( 1 + Math.abs( v.y ) + Math.abs( this.y ) ) &amp;&amp;
        Math.abs( v.z - this.z ) &lt;= re * ( 1 + Math.abs( v.z ) + Math.abs( this.z ) )
    );
}
</code></pre></div>
<p dir="auto">(edited function to handle origin position case and add joined function)</p>
<h5 dir="auto">Three.js version</h5>
<ul class="contains-task-list">
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox"> Dev</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" checked=""> r89</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox"> ...</li>
</ul>
<h5 dir="auto">Browser</h5>
<ul class="contains-task-list">
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox"> All of them</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox"> Chrome</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox"> Firefox</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox"> Internet Explorer</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" checked=""> Safari</li>
</ul>
<h5 dir="auto">OS</h5>
<ul class="contains-task-list">
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox"> All of them</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox"> Windows</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" checked=""> macOS</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox"> Linux</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox"> Android</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox"> iOS</li>
</ul>
<h5 dir="auto">Hardware Requirements (graphics card, VR Device, ...)</h5>