diff --git a/page/assets/index-CMlOR9Mg.js b/page/assets/index-CMlOR9Mg.js new file mode 100644 index 0000000..3f63521 --- /dev/null +++ b/page/assets/index-CMlOR9Mg.js @@ -0,0 +1,375 @@ +var ln=Object.defineProperty;var an=(e,t,n)=>t in e?ln(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n;var N=(e,t,n)=>an(e,typeof t!="symbol"?t+"":t,n);(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const r of document.querySelectorAll('link[rel="modulepreload"]'))i(r);new MutationObserver(r=>{for(const s of r)if(s.type==="childList")for(const a of s.addedNodes)a.tagName==="LINK"&&a.rel==="modulepreload"&&i(a)}).observe(document,{childList:!0,subtree:!0});function n(r){const s={};return r.integrity&&(s.integrity=r.integrity),r.referrerPolicy&&(s.referrerPolicy=r.referrerPolicy),r.crossOrigin==="use-credentials"?s.credentials="include":r.crossOrigin==="anonymous"?s.credentials="omit":s.credentials="same-origin",s}function i(r){if(r.ep)return;r.ep=!0;const s=n(r);fetch(r.href,s)}})();const wt=!1;var tt=Array.isArray,nt=Array.from,fn=Object.defineProperty,oe=Object.getOwnPropertyDescriptor,Bt=Object.getOwnPropertyDescriptors,un=Object.prototype,cn=Array.prototype,Ae=Object.getPrototypeOf;const St=()=>{};function dn(e){return typeof(e==null?void 0:e.then)=="function"}function pn(e){return e()}function $e(e){for(var t=0;t=_.v&&L(_,h+1)}ct(a)}return!0},ownKeys(l){b(a);var u=Reflect.ownKeys(l).filter(c=>{var d=r.get(c);return d===void 0||d.v!==I});for(var[o,f]of r)f.v!==I&&!(o in l)&&u.push(o);return u},setPrototypeOf(){Bn()}})}function ct(e,t=1){L(e,e.v+t)}var dt,Ut,Yt;function Mn(){if(dt===void 0){dt=window;var e=Element.prototype,t=Node.prototype;Ut=oe(t,"firstChild").get,Yt=oe(t,"nextSibling").get,e.__click=void 0,e.__className="",e.__attributes=null,e.__styles=null,e.__e=void 0,Text.prototype.__t=void 0}}function Nt(e=""){return document.createTextNode(e)}function Ne(e){return Ut.call(e)}function Fe(e){return Yt.call(e)}function w(e,t){return Ne(e)}function Ze(e,t){{var n=Ne(e);return n instanceof Comment&&n.data===""?Fe(n):n}}function D(e,t=1,n=!1){let i=e;for(;t--;)i=Fe(i);return i}function Xn(e){e.textContent=""}function ke(e){var t=k|$;m===null?t|=re:m.f|=Ot;var n=P!==null&&P.f&k?P:null;const i={children:null,ctx:C,deps:null,equals:Lt,f:t,fn:e,reactions:null,v:null,version:0,parent:n??m};return n!==null&&(n.children??(n.children=[])).push(i),i}function Fn(e){const t=ke(e);return t.equals=Tt,t}function kt(e){var t=e.children;if(t!==null){e.children=null;for(var n=0;n{Z(t)}}function st(e){return xe(It,e,!1)}function Mt(e){return xe(Me,e,!0)}function ue(e){return Ce(e)}function Ce(e,t=0){return xe(Me|rt|t,e,!0)}function z(e,t=!0){return xe(Me|R,e,!0,t)}function Xt(e){var t=e.teardown;if(t!==null){const n=lt,i=P;vt(!0),K(null);try{t.call(null)}finally{vt(n),K(i)}}}function Ft(e){var t=e.deriveds;if(t!==null){e.deriveds=null;for(var n=0;n{Z(e),t&&t()})}function jt(e,t){var n=e.length;if(n>0){var i=()=>--n||t();for(var r of e)r.out(i)}else t()}function ot(e,t,n){if(!(e.f&X)){if(e.f^=X,e.transitions!==null)for(const a of e.transitions)(a.is_global||n)&&t.push(a);for(var i=e.first;i!==null;){var r=i.next,s=(i.f&Xe)!==0||(i.f&R)!==0;ot(i,t,s?n:!1),i=r}}}function ne(e){Vt(e,!0)}function Vt(e,t){if(e.f&X){Ee(e)&&je(e),e.f^=X;for(var n=e.first;n!==null;){var i=n.next,r=(n.f&Xe)!==0||(n.f&R)!==0;Vt(n,r?t:!1),n=i}if(e.transitions!==null)for(const s of e.transitions)(s.is_global||t)&&s.in()}}let Ge=!1,Je=[];function Ht(){Ge=!1;const e=Je.slice();Je=[],$e(e)}function Wt(e){Ge||(Ge=!0,queueMicrotask(Ht)),Je.push(e)}function Wn(){Ge&&Ht()}const $t=0,$n=1;let Le=!1,Te=$t,ge=!1,_e=null,ae=!1,lt=!1;function pt(e){ae=e}function vt(e){lt=e}let ee=[],fe=0;let P=null;function K(e){P=e}let m=null;function q(e){m=e}let F=null;function Kn(e){F=e}let B=null,T=0,W=null;function Zn(e){W=e}let Kt=0,se=!1,C=null;function gt(e){C=e}function Zt(){return++Kt}function qe(){return!be||C!==null&&C.l===null}function Ee(e){var a,v;var t=e.f;if(t&$)return!0;if(t&Pe){var n=e.deps,i=(t&re)!==0;if(n!==null){var r;if(t&Ue){for(r=0;re.version)return!0}}i||j(e,A)}return!1}function Jn(e,t){for(var n=t;n!==null;){if(n.f&Ke)try{n.fn(e);return}catch{n.f^=Ke}n=n.parent}throw Le=!1,e}function Qn(e){return(e.f&de)===0&&(e.parent===null||(e.parent.f&Ke)===0)}function ze(e,t,n,i){if(Le){if(n===null&&(Le=!1),Qn(t))throw e;return}n!==null&&(Le=!0);{Jn(e,t);return}}function Jt(e){var c;var t=B,n=T,i=W,r=P,s=se,a=F,v=C,l=e.f;B=null,T=0,W=null,P=l&(R|me)?null:e,se=!ae&&(l&re)!==0,F=null,C=e.ctx;try{var u=(0,e.fn)(),o=e.deps;if(B!==null){var f;if(he(e,T),o!==null&&T>0)for(o.length=T+B.length,f=0;f1e3){fe=0;try{Cn()}catch(e){if(_e!==null)ze(e,_e,null);else throw e}}fe++}function en(e){var t=e.length;if(t!==0){Qt();var n=ae;ae=!0;try{for(var i=0;i1001)return;const e=ee;ee=[],en(e),ge||(fe=0,_e=null)}function Ve(e){Te===$t&&(ge||(ge=!0,queueMicrotask(nr))),_e=e;for(var t=e;t.parent!==null;){t=t.parent;var n=t.f;if(n&(me|R)){if(!(n&A))return;t.f^=A}}ee.push(t)}function tn(e,t){var n=e.first,i=[];e:for(;n!==null;){var r=n.f,s=(r&R)!==0,a=s&&(r&A)!==0,v=n.next;if(!a&&!(r&X))if(r&Me){if(s)n.f^=A;else try{Ee(n)&&je(n)}catch(f){ze(f,n,null,n.ctx)}var l=n.first;if(l!==null){n=l;continue}}else r&It&&i.push(n);if(v===null){let f=n.parent;for(;f!==null;){if(e===f)break e;var u=f.next;if(u!==null){n=u;continue e}f=f.parent}}n=v}for(var o=0;o0||r.length>0)&&nn(),fe=0,_e=null,i}finally{Te=t,ee=n}}function b(e){var o;var t=e.f,n=(t&k)!==0;if(n&&t&de){var i=Rt(e);return it(e),i}if(P!==null){F!==null&&F.includes(e)&&Sn();var r=P.deps;B===null&&r!==null&&r[T]===e?T++:B===null?B=[e]:B.push(e),W!==null&&m!==null&&m.f&A&&!(m.f&R)&&W.includes(e)&&(j(m,$),Ve(m))}else if(n&&e.deps===null)for(var s=e,a=s.parent,v=s;a!==null;)if(a.f&k){var l=a;v=l,a=l.parent}else{var u=a;(o=u.deriveds)!=null&&o.includes(v)||(u.deriveds??(u.deriveds=[])).push(v);break}return n&&(s=e,Ee(s)&&Gt(s)),e.v}function at(e){const t=P;try{return P=null,e()}finally{P=t}}const rr=~($|Pe|A);function j(e,t){e.f=e.f&rr|t}function we(e,t=!1,n){C={p:C,c:null,e:null,m:!1,s:e,x:null,l:null},be&&!t&&(C.l={s:null,u:null,r1:[],r2:O(!1)})}function Be(e){const t=C;if(t!==null){const a=t.e;if(a!==null){var n=m,i=P;t.e=null;try{for(var r=0;r{throw E});throw c}}finally{e.__root=t,delete e.currentTarget,K(o),q(f)}}}function or(e){var t=document.createElement("template");return t.innerHTML=e,t.content}function et(e,t){var n=m;n.nodes_start===null&&(n.nodes_start=e,n.nodes_end=t)}function J(e,t){var n=(t&Rn)!==0,i=(t&Gn)!==0,r,s=!e.startsWith("");return()=>{r===void 0&&(r=or(s?e:""+e),n||(r=Ne(r)));var a=i?document.importNode(r,!0):r.cloneNode(!0);if(n){var v=Ne(a),l=a.lastChild;et(v,l)}else et(a,a);return a}}function ht(){var e=document.createDocumentFragment(),t=document.createComment(""),n=Nt();return e.append(t,n),et(t,n),e}function M(e,t){e!==null&&e.before(t)}const lr=["touchstart","touchmove"];function ar(e){return lr.includes(e)}function ce(e,t){var n=t==null?"":typeof t=="object"?t+"":t;n!==(e.__t??(e.__t=e.nodeValue))&&(e.__t=n,e.nodeValue=n==null?"":n+"")}function fr(e,t){return ur(e,t)}const ie=new Map;function ur(e,{target:t,anchor:n,props:i={},events:r,context:s,intro:a=!0}){Mn();var v=new Set,l=f=>{for(var c=0;c{var f=n??t.appendChild(Nt());return z(()=>{if(s){we({});var c=C;c.c=s}r&&(i.$$events=r),u=e(f,i)||{},s&&Be()}),()=>{var p;for(var c of v){t.removeEventListener(c,Se);var d=ie.get(c);--d===0?(document.removeEventListener(c,Se),ie.delete(c)):ie.set(c,d)}_t.delete(l),yt.delete(u),f!==n&&((p=f.parentNode)==null||p.removeChild(f))}});return yt.set(u,o),u}let yt=new WeakMap;const He=0,Ie=1,We=2;function cr(e,t,n,i,r){var s=e,a=qe(),v=C,l=I,u,o,f,c=(a?O:Ye)(void 0),d=(a?O:Ye)(void 0),p=!1;function g(_,h){p=!0,h&&(q(y),K(y),gt(v));try{_===He&&n&&(u?ne(u):u=z(()=>n(s))),_===Ie&&i&&(o?ne(o):o=z(()=>i(s,c))),_===We&&r&&(f?ne(f):f=z(()=>r(s,d))),_!==He&&u&&le(u,()=>u=null),_!==Ie&&o&&le(o,()=>o=null),_!==We&&f&&le(f,()=>f=null)}finally{h&&(gt(null),K(null),q(null),nn())}}var y=Ce(()=>{if(l!==(l=t())){if(dn(l)){var _=l;p=!1,_.then(h=>{_===l&&(ve(c,h),g(Ie,!0))},h=>{if(_===l&&(ve(d,h),g(We,!0),!r))throw d.v}),Wt(()=>{p||g(He,!0)})}else ve(c,l),g(Ie,!1);return()=>l=I}})}function rn(e,t,n=!1){var i=e,r=null,s=null,a=null,v=n?Xe:0,l=!1;const u=(f,c=!0)=>{l=!0,o(c,f)},o=(f,c)=>{a!==(a=f)&&(a?(r?ne(r):c&&(r=z(()=>c(i))),s&&le(s,()=>{s=null})):(s?ne(s):c&&(s=z(()=>c(i))),r&&le(r,()=>{r=null})))};Ce(()=>{l=!1,t(u),l||o(null,null)},v)}function dr(e,t){return t}function pr(e,t,n,i){for(var r=[],s=t.length,a=0;a0&&r.length===0&&n!==null;if(v){var l=n.parentNode;Xn(l),l.append(n),i.clear(),H(e,t[0].prev,t[s-1].next)}jt(r,()=>{for(var u=0;u{var o=n(),f=tt(o)?o:o==null?[]:nt(o),c=f.length;if(!(u&&c===0)){u=c===0;{var d=P;gr(f,v,a,r,t,(d.f&X)!==0,i)}s!==null&&(c===0?l?ne(l):l=z(()=>s(a)):l!==null&&le(l,()=>{l=null})),n()}})}function gr(e,t,n,i,r,s,a){var v=e.length,l=t.items,u=t.first,o=u,f,c=null,d=[],p=[],g,y,_,h;for(h=0;h0){var on=null;pr(t,Y,on,l)}}m.first=t.first&&t.first.e,m.last=c&&c.e}function _r(e,t,n,i){ve(e.v,t),e.i=n}function hr(e,t,n,i,r,s,a,v,l){var u=(l&Tn)!==0,o=(l&Un)===0,f=u?o?Ye(r):O(r):r,c=l&An?O(a):a,d={i:c,v:f,k:s,a:null,e:null,prev:n,next:i};try{return d.e=z(()=>v(e,f,c),Dn),d.e.prev=n&&n.e,d.e.next=i&&i.e,n===null?t.first=d:(n.next=d,n.e.next=d.e),i!==null&&(i.prev=d,i.e.prev=d.e),d}finally{}}function mt(e,t,n){for(var i=e.next?e.next.e.nodes_start:n,r=t?t.e.nodes_start:n,s=e.e.nodes_start;s!==i;){var a=Fe(s);r.before(s),s=a}}function H(e,t,n){t===null?e.first=n:(t.next=n,t.e.next=n&&n.e),n!==null&&(n.prev=t,n.e.prev=t&&t.e)}function yr(e,t,...n){var i=e,r=St,s;Ce(()=>{r!==(r=t())&&(s&&(Z(s),s=null),s=z(()=>r(i,...n)))},Xe)}function Pt(e,t,n,i){var r=e.__attributes??(e.__attributes={});r[t]!==(r[t]=n)&&(t==="style"&&"__styles"in e&&(e.__styles={}),t==="loading"&&(e[yn]=n),n==null?e.removeAttribute(t):typeof n!="string"&&mr(e).includes(t)?e[t]=n:e.setAttribute(t,n))}var bt=new Map;function mr(e){var t=bt.get(e.nodeName);if(t)return t;bt.set(e.nodeName,t=[]);for(var n,i=Ae(e),r=Element.prototype;r!==i;){n=Bt(i);for(var s in n)n[s].set&&t.push(s);i=Ae(i)}return t}function xt(e,t){return e===t||(e==null?void 0:e[te])===t}function Pr(e={},t,n,i){return st(()=>{var r,s;return Mt(()=>{r=s,s=[],at(()=>{e!==n(...s)&&(t(e,...s),r&&xt(n(...r),e)&&t(null,...r))})}),()=>{Wt(()=>{s&&xt(n(...s),e)&&t(null,...s)})}}),e}function br(e=!1){const t=C,n=t.l.u;if(!n)return;let i=()=>ir(t.s);if(e){let r=0,s={};const a=ke(()=>{let v=!1;const l=t.s;for(const u in l)l[u]!==s[u]&&(s[u]=l[u],v=!0);return v&&r++,r});i=()=>b(a)}n.b.length&&jn(()=>{Ct(t,i),$e(n.b)}),Re(()=>{const r=at(()=>n.m.map(pn));return()=>{for(const s of r)typeof s=="function"&&s()}}),n.a.length&&Re(()=>{Ct(t,i),$e(n.a)})}function Ct(e,t){if(e.l.s)for(const n of e.l.s)b(n);t()}let Oe=!1;function xr(e){var t=Oe;try{return Oe=!1,[e(),Oe]}finally{Oe=t}}function Et(e){for(var t=m,n=m;t!==null&&!(t.f&(R|me));)t=t.parent;try{return q(t),e()}finally{q(n)}}function Cr(e,t,n,i){var V;var r=(n&Yn)!==0,s=!be||(n&Nn)!==0,a=(n&kn)!==0,v=!1,l;[l,v]=xr(()=>e[t]);var u=te in e||hn in e,o=((V=oe(e,t))==null?void 0:V.set)??(u&&a&&t in e?x=>e[t]=x:void 0),f=i,c=!0,d=!1,p=()=>(d=!0,c&&(c=!1,f=i),f);l===void 0&&i!==void 0&&(o&&s&&En(),l=p(),o&&o(l));var g;if(s)g=()=>{var x=e[t];return x===void 0?p():(c=!0,d=!1,x)};else{var y=Et(()=>(r?ke:Fn)(()=>e[t]));y.f|=gn,g=()=>{var x=b(y);return x!==void 0&&(f=void 0),x===void 0?f:x}}if(o){var _=e.$$legacy;return function(x,Y){return arguments.length>0?((!s||!Y||_||v)&&o(Y?g():x),x):g()}}var h=!1,E=!1,U=Ye(l),S=Et(()=>ke(()=>{var x=g(),Y=b(U);return h?(h=!1,E=!0,Y):(E=!1,U.v=x)}));return function(x,Y){if(arguments.length>0){const Q=Y?b(S):s&&a?G(x):x;return S.equals(Q)||(h=!0,L(U,Q),d&&f!==void 0&&(f=Q),at(()=>b(S))),x}return b(S)}}const Er="5";typeof window<"u"&&(window.__svelte||(window.__svelte={v:new Set})).v.add(Er);On();async function wr(){if(!navigator.gpu)return Promise.reject("WebGPU is not supported on this browser");const e=await navigator.gpu.requestAdapter();return e?e.requestDevice():Promise.reject("Failed to get GPU adapter")}const De=[[{X:50,Y:50},{X:450,Y:50},{X:450,Y:450},{X:50,Y:450},{X:50,Y:50}],[{X:150,Y:150},{X:200,Y:150},{X:200,Y:200},{X:150,Y:200},{X:150,Y:150}],[{X:300,Y:100},{X:350,Y:100},{X:350,Y:150},{X:300,Y:150},{X:300,Y:100}],[{X:100,Y:300},{X:150,Y:300},{X:150,Y:350},{X:100,Y:350},{X:100,Y:300}],[{X:300,Y:300},{X:400,Y:300},{X:400,Y:400},{X:300,Y:400},{X:300,Y:300}]];class ye{constructor(t,n,i,r){N(this,"edgesBuffer");N(this,"maxIntersectionsPerSegment",32);N(this,"bindGroupLayout");N(this,"pipeline");N(this,"edgesCount");this.device=r;const s=ye.convertPolygonToEdges(t),a=new Float32Array(s);this.edgesCount=s.length,this.edgesBuffer=this.device.createBuffer({size:a.byteLength,usage:GPUBufferUsage.STORAGE|GPUBufferUsage.COPY_SRC,mappedAtCreation:!0,label:"edgesBuffer"}),new Float32Array(this.edgesBuffer.getMappedRange()).set(a),this.edgesBuffer.unmap(),this.bindGroupLayout=this.device.createBindGroupLayout({entries:n}),this.pipeline=this.device.createComputePipeline({layout:this.device.createPipelineLayout({bindGroupLayouts:[this.bindGroupLayout]}),compute:{module:this.device.createShaderModule({code:i}),entryPoint:"main"}})}static convertPolygonToEdges(t){const n=[];for(const i of t)for(let r=0;r[n.X,n.Y])}}const Br=` +@group(0) @binding(0) var lines: array; +@group(0) @binding(1) var edges: array; +@group(0) @binding(2) var intersectionsBuffer: array; +@group(0) @binding(3) var clippedLinesBuffer: array; + +fn lineIntersection(p1: vec2f, p2: vec2f, p3: vec2f, p4: vec2f) -> vec3f { + let s1 = vec2(p2.x - p1.x, p2.y - p1.y); + let s2 = vec2(p4.x - p3.x, p4.y - p3.y); + + let denom = -s2.x * s1.y + s1.x * s2.y; + let epsilon = 1e-6; + + if (abs(denom) < epsilon) { // Adjust epsilon as needed + return vec3f(-1.0, -1.0, 0.0); // No intersection + } + + let s = (-s1.y * (p1.x - p3.x) + s1.x * (p1.y - p3.y)) / denom; + let t = (s2.x * (p1.y - p3.y) - s2.y * (p1.x - p3.x)) / denom; + + if (s >= -epsilon && s <= 1.0 + epsilon && t >= -epsilon && t <= 1.0 + epsilon) { + return vec3f(p1.x + t * s1.x, p1.y + t * s1.y, 1.0); + } + + return vec3f(-1.0, -1.0, 0.0); // No intersection +} + +fn isPointInsidePolygon(testPoint: vec2) -> bool { + var leftNodes = 0; + var rightNodes = 0; + + for (var i = 0u; i < arrayLength(&edges); i = i + 1u) { + let edge = edges[i]; + + // Check if the edge crosses the Y threshold of the test point + if ((edge.y <= testPoint.y && edge.w > testPoint.y) || + (edge.y > testPoint.y && edge.w <= testPoint.y)) { + + // Calculate the X-coordinate of the intersection + let slope = (edge.z - edge.x) / (edge.z - edge.y); + let intersectX = edge.x + (testPoint.y - edge.y) * slope; + + // Count nodes on the left or right side + if (intersectX < testPoint.x) { + leftNodes = leftNodes + 1; + } else { + rightNodes = rightNodes + 1; + } + } + } + + // Determine if the point is inside the polygon + return (leftNodes % 2 != 0) && (rightNodes % 2 != 0); +} + +@compute @workgroup_size(1) +fn main(@builtin(global_invocation_id) id: vec3) { + let lineIndex = id.x; + if (lineIndex >= arrayLength(&lines)) { + return; + } + + // Calculate buffer offsets dynamically + let totalIntersections = arrayLength(&intersectionsBuffer); // Total intersections in the buffer + let intersectionsPerLine = totalIntersections / arrayLength(&lines); + let baseOffset = lineIndex * intersectionsPerLine; + + // Clipped lines offset + let clippedBaseOffset = lineIndex * intersectionsPerLine; + + var count = 0u; + var clippedCount = 0u; + + // Process edges and find intersections + for (var i = 0u; i < arrayLength(&edges); i = i + 1u) { + let edge = edges[i]; + let result = lineIntersection(lines[lineIndex].xy, lines[lineIndex].zw, edge.xy, edge.zw); + + if (result.z == 1.0) { // check if intersection is valid + if (count < intersectionsPerLine) { + intersectionsBuffer[baseOffset + count] = result; + count = count + 1u; + } + } + } + + // Sort intersections directly in the buffer + for (var i = 0u; i < count; i = i + 1u) { + for (var j = i + 1u; j < count; j = j + 1u) { + let d1 = distance(vec2( + intersectionsBuffer[baseOffset + i].x, intersectionsBuffer[baseOffset + i].y), + vec2(lines[lineIndex].x, lines[lineIndex].y) + ); + let d2 = distance(vec2( + intersectionsBuffer[baseOffset + j].x, intersectionsBuffer[baseOffset + j].y), + vec2(lines[lineIndex].x, lines[lineIndex].y) + ); + + if (d2 < d1) { + let temp = intersectionsBuffer[baseOffset + i]; + intersectionsBuffer[baseOffset + i] = intersectionsBuffer[baseOffset + j]; + intersectionsBuffer[baseOffset + j] = temp; + } + } + } + + let p1 = lines[lineIndex].xy; + let p2 = lines[lineIndex].zw; + + let p1Inside = isPointInsidePolygon(p1); + let p2Inside = isPointInsidePolygon(p2); + + if (clippedCount == 1u) { + if (!p1Inside) { + clippedLinesBuffer[clippedBaseOffset + clippedCount] = vec4f( + intersectionsBuffer[baseOffset].xy, + lines[lineIndex].zw + ); + clippedCount = clippedCount + 1u; + } else if (!p2Inside) { + clippedLinesBuffer[clippedBaseOffset + clippedCount] = vec4f( + lines[lineIndex].xy, + intersectionsBuffer[baseOffset].xy, + ); + clippedCount = clippedCount + 1u; + } + } else { + if (!p1Inside && !p2Inside) { + // Create clipped line segments from pairs of intersections + for (var i = 0u; i + 1u < count; i = i + 2u) { + if (clippedCount < intersectionsPerLine) { + clippedLinesBuffer[clippedBaseOffset + clippedCount] = vec4f( + intersectionsBuffer[baseOffset + i].xy, + intersectionsBuffer[baseOffset + i + 1u].xy + ); + clippedCount = clippedCount + 1u; + } + } + } else if (p1Inside && !p2Inside) { + clippedLinesBuffer[clippedBaseOffset + clippedCount] = vec4f( + lines[lineIndex].xy, + intersectionsBuffer[baseOffset].xy, + ); + clippedCount = clippedCount + 1u; + + for (var i = 1u; i + 1u < count; i = i + 2u) { + if (clippedCount < intersectionsPerLine) { + clippedLinesBuffer[clippedBaseOffset + clippedCount] = vec4f( + intersectionsBuffer[baseOffset + i].xy, + intersectionsBuffer[baseOffset + i + 1u].xy + ); + clippedCount = clippedCount + 1u; + } + } + } else if (!p1Inside && p2Inside) { + for (var i = 0u; i + 1u < count - 1u; i = i + 2u) { + if (clippedCount < intersectionsPerLine) { + clippedLinesBuffer[clippedBaseOffset + clippedCount] = vec4f( + intersectionsBuffer[baseOffset + i].xy, + intersectionsBuffer[baseOffset + i + 1u].xy + ); + clippedCount = clippedCount + 1u; + } + } + clippedLinesBuffer[clippedBaseOffset + clippedCount] = vec4f( + intersectionsBuffer[baseOffset + count - 1u].xy, + p2, + ); + clippedCount = clippedCount + 1u; + } else { + clippedLinesBuffer[clippedBaseOffset + clippedCount] = vec4f( + lines[lineIndex].xy, + intersectionsBuffer[baseOffset].xy, + ); + clippedCount = clippedCount + 1u; + + // Create clipped line segments from pairs of intersections + for (var i = 1u; i + 1u < count - 1; i = i + 2u) { + if (clippedCount < intersectionsPerLine) { + clippedLinesBuffer[clippedBaseOffset + clippedCount] = vec4f( + intersectionsBuffer[baseOffset + i].xy, + intersectionsBuffer[baseOffset + i + 1u].xy + ); + clippedCount = clippedCount + 1u; + } + } + + clippedLinesBuffer[clippedBaseOffset + clippedCount] = vec4f( + intersectionsBuffer[baseOffset + count - 1].xy, + lines[lineIndex].zw, + ); + clippedCount = clippedCount + 1u; + } + } + + // Optional: Mark unused slots in buffers with a sentinel value + for (var i = count; i < intersectionsPerLine; i = i + 1u) { + intersectionsBuffer[baseOffset + i] = vec3f(-1.0, -1.0, 0.0); + } +} + +`,Sr=[{binding:0,visibility:GPUShaderStage.COMPUTE,buffer:{type:"read-only-storage"}},{binding:1,visibility:GPUShaderStage.COMPUTE,buffer:{type:"read-only-storage"}},{binding:2,visibility:GPUShaderStage.COMPUTE,buffer:{type:"storage"}},{binding:3,visibility:GPUShaderStage.COMPUTE,buffer:{type:"storage"}}];class ft extends ye{constructor({device:n,polygon:i,maxIntersectionsPerLine:r=128}){super(i,Sr,Br,n);N(this,"maxIntersectionsPerLine");this.maxIntersectionsPerLine=r;const s=new Float32Array(ye.convertPolygonToEdges(i));this.edgesBuffer=this.device.createBuffer({size:s.byteLength,usage:GPUBufferUsage.STORAGE|GPUBufferUsage.COPY_SRC,mappedAtCreation:!0,label:"edgesBuffer"}),new Float32Array(this.edgesBuffer.getMappedRange()).set(s),this.edgesBuffer.unmap()}async clip(n){const i=new Float32Array(ft.flattenPointList(n.flat())),r=this.device.createBuffer({size:i.byteLength,usage:GPUBufferUsage.STORAGE|GPUBufferUsage.COPY_SRC,mappedAtCreation:!0});new Float32Array(r.getMappedRange()).set(i),r.unmap();const s=this.device.createBuffer({size:n.length*this.maxIntersectionsPerLine*4*Float32Array.BYTES_PER_ELEMENT,usage:GPUBufferUsage.STORAGE|GPUBufferUsage.COPY_SRC}),a=this.device.createBuffer({size:s.size,usage:GPUBufferUsage.COPY_DST|GPUBufferUsage.MAP_READ}),v=3*Float32Array.BYTES_PER_ELEMENT,l=n.length*this.maxIntersectionsPerLine,u=this.device.createBuffer({size:l*v,usage:GPUBufferUsage.STORAGE|GPUBufferUsage.COPY_SRC|GPUBufferUsage.COPY_DST});(()=>{const y=new Float32Array(l*2).fill(-1);this.device.queue.writeBuffer(u,0,y)})();const f=this.device.createBindGroup({layout:this.pipeline.getBindGroupLayout(0),entries:[{binding:0,resource:{buffer:r}},{binding:1,resource:{buffer:this.edgesBuffer}},{binding:2,resource:{buffer:u}},{binding:3,resource:{buffer:s}}]}),c=this.device.createCommandEncoder(),d=c.beginComputePass();d.setPipeline(this.pipeline),d.setBindGroup(0,f),d.dispatchWorkgroups(n.length),d.end(),c.copyBufferToBuffer(s,0,a,0,s.size),this.device.queue.submit([c.finish()]),await a.mapAsync(GPUMapMode.READ);const p=new Float32Array(a.getMappedRange()),g=[];for(let y=0;y!y.every(_=>_.X===0&&_.Y===0))}}function Ir(e,t){return` +@group(0) @binding(0) var vertices: array; +@group(0) @binding(1) var edges: array; +@group(0) @binding(2) var clippedPolylineBuffer: array; +@group(0) @binding(3) var maxClippedVerticesPerSegment: u32; + +var threadIndex: u32; +var bufferIndex: u32; + +fn lineIntersection(p1: vec2f, p2: vec2f, p3: vec2f, p4: vec2f) -> vec3f { + let s1 = vec2(p2.x - p1.x, p2.y - p1.y); + let s2 = vec2(p4.x - p3.x, p4.y - p3.y); + + let denom = -s2.x * s1.y + s1.x * s2.y; + let epsilon = 1e-6; + + if (abs(denom) < epsilon) { // Adjust epsilon as needed + return vec3f(-1.0, -1.0, 0.0); // No intersection + } + + let s = (-s1.y * (p1.x - p3.x) + s1.x * (p1.y - p3.y)) / denom; + let t = (s2.x * (p1.y - p3.y) - s2.y * (p1.x - p3.x)) / denom; + + if (s >= -epsilon && s <= 1.0 + epsilon && t >= -epsilon && t <= 1.0 + epsilon) { + return vec3f(p1.x + t * s1.x, p1.y + t * s1.y, 1.0); + } + + return vec3f(-1.0, -1.0, 0.0); // No intersection +} + +struct LineIntersectionsData { + intersections: array, + intersectionCount: u32 +} + +fn getLineIntersectionsData(p1: vec4f, p2: vec4f) -> LineIntersectionsData { + var intersections: array; + var intersectionCount = 0u; + + for (var j = 0u; j < arrayLength(&edges); j = j + 1u) { + let edge = edges[j]; + let intersection = lineIntersection(p1.xy, p2.xy, edge.xy, edge.zw); + + if (intersection.z == 1.0) { + intersections[intersectionCount] = intersection.xy; + intersectionCount = intersectionCount + 1u; + } + } + + if (intersectionCount > 1u) { + for (var k = 0u; k < intersectionCount - 1u; k = k + 1u) { + for (var l = k + 1u; l < intersectionCount; l = l + 1u) { + if (distance(p1.xy, intersections[l]) < distance(p1.xy, intersections[k])) { + let temp = intersections[k]; + intersections[k] = intersections[l]; + intersections[l] = temp; + } + } + } + } + + return LineIntersectionsData(intersections, intersectionCount); +} + +fn isPointInsidePolygon(point: vec2f) -> bool { + var leftNodes = 0; + for (var i = 0u; i < arrayLength(&edges); i = i + 1u) { + let edge = edges[i]; + let start = edge.xy; + let end = edge.zw; + if ((start.y <= point.y && end.y > point.y) || (start.y > point.y && end.y <= point.y)) { + let slope = (end.x - start.x) / (end.y - start.y); + let intersectX = start.x + (point.y - start.y) * slope; + if (point.x < intersectX) { + leftNodes = leftNodes + 1; + } + } + } + return (leftNodes % 2) != 0; +} + +fn addPoint(point: vec2f) { + clippedPolylineBuffer[bufferIndex] = vec4f(point, 0.0, 0.0); + bufferIndex = bufferIndex + 1u; + let segmentStart = threadIndex * maxClippedVerticesPerSegment; + clippedPolylineBuffer[segmentStart].w = f32(bufferIndex - segmentStart); +} + +fn addSentinel() { + clippedPolylineBuffer[bufferIndex] = vec4f(-1.0, -1.0, -1.0, -1.0); + bufferIndex = bufferIndex + 1u; + let segmentStart = threadIndex * maxClippedVerticesPerSegment; + clippedPolylineBuffer[segmentStart].w = f32(bufferIndex - segmentStart); +} + +@compute @workgroup_size(${e}) +fn main(@builtin(global_invocation_id) globalId: vec3) { + threadIndex = globalId.x; + + if (threadIndex == 0u || threadIndex >= arrayLength(&vertices) - 1u) { + return; // No segment to process + } + + let p1 = vertices[threadIndex - 1u]; + let p2 = vertices[threadIndex]; + + if (u32(p1.z) != u32(p2.z)) { + return; // Skip processing, p1 and p2 are from different polylines + } + + let p1Inside = isPointInsidePolygon(p1.xy); + let p2Inside = isPointInsidePolygon(p2.xy); + + let intersectionsData = getLineIntersectionsData(p1, p2); + let intersections = intersectionsData.intersections; + let intersectionCount = intersectionsData.intersectionCount; + + bufferIndex = threadIndex * maxClippedVerticesPerSegment; + + if (p1Inside && p2Inside) { + addPoint(p1.xy); + + if (intersectionCount == 0u) { + addPoint(p2.xy); + } + else { + addPoint(intersections[0u]); + addSentinel(); + + for (var i = 1u; i < intersectionCount - 1u; i = i + 2u) { + addPoint(intersections[i]); + addPoint(intersections[i + 1u]); + addSentinel(); + } + + addPoint(intersections[intersectionCount - 1u]); + addPoint(p2.xy); + } + } else if (p1Inside && !p2Inside) { + addPoint(p1.xy); + addPoint(intersections[0]); + addSentinel(); + + if (intersectionCount > 1u) { + for (var i = 1u; i < intersectionCount; i = i + 2u) { + addPoint(intersections[i]); + addPoint(intersections[i + 1u]); + addSentinel(); + } + } + } else if (!p1Inside && p2Inside) { + if (intersectionCount == 1u) { + addPoint(intersections[0]); + addPoint(p2.xy); + } else { + for (var i = 0u; i < intersectionCount - 1u; i = i + 2u) { + addPoint(intersections[i]); + addPoint(intersections[i + 1u]); + addSentinel(); + } + addPoint(intersections[intersectionCount - 1u]); + addPoint(p2.xy); + addSentinel(); + } + } else { + for (var i = 0u; i + 1u < intersectionCount; i = i + 2u) { + addPoint(intersections[i]); + addPoint(intersections[i + 1u]); + addSentinel(); + } + } +} +`}const Or=[{binding:0,visibility:GPUShaderStage.COMPUTE,buffer:{type:"read-only-storage"}},{binding:1,visibility:GPUShaderStage.COMPUTE,buffer:{type:"read-only-storage"}},{binding:2,visibility:GPUShaderStage.COMPUTE,buffer:{type:"storage"}},{binding:3,visibility:GPUShaderStage.COMPUTE,buffer:{type:"uniform"}}];class ut extends ye{constructor({device:n,polygon:i,maxIntersectionsPerSegment:r,maxClippedVerticesPerSegment:s,workgroupSize:a}){const v=a??64,l=r??64,u=s??64;super(i,Or,Ir(v,l),n);N(this,"maxClippedVerticesPerSegment");N(this,"workgroupSize");N(this,"polylinesLength",0);N(this,"verticesLength",0);N(this,"segmentsCount",0);this.maxIntersectionsPerSegment=l,this.workgroupSize=v,this.maxClippedVerticesPerSegment=u}async clip(n){performance.mark("rawClippingStart"),this.polylinesLength=n.length;const i=n.flatMap((_,h)=>_.flatMap((E,U)=>[E.X,E.Y,h,U]));this.verticesLength=i.length;const r=new Float32Array(i),s=this.device.createBuffer({size:r.byteLength,usage:GPUBufferUsage.STORAGE|GPUBufferUsage.COPY_SRC,mappedAtCreation:!0});new Float32Array(s.getMappedRange()).set(r),s.unmap();const a=n.reduce((_,h)=>(_+=h.length>2?h.length-1:h.length,_),0);console.log(`Segments: ${a}`),this.segmentsCount=a;const v=this.maxClippedVerticesPerSegment*4,l=this.device.createBuffer({size:a*v*Float32Array.BYTES_PER_ELEMENT,usage:GPUBufferUsage.STORAGE|GPUBufferUsage.COPY_SRC}),u=this.device.createBuffer({size:4,usage:GPUBufferUsage.UNIFORM|GPUBufferUsage.COPY_DST});this.device.queue.writeBuffer(u,0,new Uint32Array([this.maxClippedVerticesPerSegment]));const o=Math.ceil(a/this.workgroupSize),f=this.device.createBindGroup({layout:this.pipeline.getBindGroupLayout(0),entries:[{binding:0,resource:{buffer:s}},{binding:1,resource:{buffer:this.edgesBuffer}},{binding:2,resource:{buffer:l}},{binding:3,resource:{buffer:u}}]}),c=this.device.createCommandEncoder(),d=c.beginComputePass();d.setPipeline(this.pipeline),d.setBindGroup(0,f),d.dispatchWorkgroups(o),d.end();const p=this.device.createBuffer({size:l.size,usage:GPUBufferUsage.MAP_READ|GPUBufferUsage.COPY_DST,label:"readBuffer"});c.copyBufferToBuffer(l,0,p,0,l.size),this.device.queue.submit([c.finish()]),await p.mapAsync(GPUMapMode.READ);const g=new Float32Array(p.getMappedRange());performance.mark("rawClippingEnd"),performance.measure("rawClipping","rawClippingStart","rawClippingEnd"),console.log(`Raw clipping takes ${performance.getEntriesByName("rawClipping")[0].duration/1e3} sec`);const y=this.parseClippedPolyline(g,a);return p.unmap(),y}parseClippedPolyline(n,i){const r=[];for(let s=0;sg===0)||(d===-1?l.length>0&&(r.push(l),l=[]):f!==void 0&&c!==void 0&&l.push({X:f,Y:c}))}l.length>0&&r.push(l)}return r.reduce((s,a,v)=>{if(v===0)s.push(a);else{const l=s[s.length-1],u=l[l.length-1],o=a[0];ut.arePointsEqual(u,o)?l.push(...a.slice(1)):s.push(a)}return s},[])}static arePointsEqual(n,i){return Math.abs(n.X-i.X)<1e-6&&Math.abs(n.Y-i.Y)<1e-6}}var Lr=J(`

Clipping (instantiation, loading, clipping, and reading the + results):

`),Tr=J('
');function sn(e,t){we(t,!0);let n=Cr(t,"canvas",15);var i=Tr(),r=w(i),s=w(r),a=D(r,2),v=w(a),l=w(v);Pr(l,d=>n(d),()=>n());var u=D(l,2),o=w(u);{var f=d=>{var p=Lr(),g=D(w(p)),y=w(g);ue(()=>ce(y,`${t.timing.toFixed(4)??""} sec`)),M(d,p)};rn(o,d=>{t.timing&&d(f)})}var c=D(o,2);yr(c,()=>t.children??St),ue(()=>{ce(s,t.title),Pt(l,"width",t.canvasSize),Pt(l,"height",t.canvasSize)}),M(e,i),Be()}var Ar=J("Polygon edges: Lines to clip: ",1);function Ur(e,t){we(t,!0);let n=pe(void 0);const i=50,r=500,s=r/i,a=new Array(i).fill(null).map((f,c)=>{const p=r;return[{X:0,Y:c*s},{X:p,Y:c*s}]});let v=pe(null);performance.mark("LineClipperStart");const l=new ft({device:t.device,polygon:De});let u=G(l.edgesCount);const o=async()=>{if(b(n)){const f=b(n).getContext("2d"),c=await l.clip(a);performance.mark("LineClipperEnd"),performance.measure("LineClipping","LineClipperStart","LineClipperEnd"),L(v,performance.getEntriesByName("LineClipping")[0].duration/1e3),f.strokeStyle="white",De.forEach(d=>{f.beginPath(),d.forEach((p,g)=>{g===0?f.moveTo(p.X,p.Y):f.lineTo(p.X,p.Y)}),f.closePath(),f.stroke()}),f.strokeStyle="rgba(255, 0, 0, 0.45)",a.forEach(d=>{d.forEach((p,g)=>{g===0?(f.beginPath(),f.moveTo(p.X,p.Y)):(f.lineTo(p.X,p.Y),f.stroke())})}),f.strokeStyle="rgba(0, 245, 0)",c.forEach(d=>{d.forEach((p,g)=>{g===0?(f.beginPath(),f.moveTo(p.X,p.Y)):(f.lineTo(p.X,p.Y),f.stroke())})})}};Re(()=>{o()}),sn(e,{title:"LineClipper",get timing(){return b(v)},canvasSize:r,get canvas(){return b(n)},set canvas(f){L(n,G(f))},children:(f,c)=>{var d=Ar(),p=Ze(d),g=D(w(p)),y=w(g),_=D(p,2),h=D(w(_));h.textContent=i,ue(()=>ce(y,u)),M(f,d)},$$slots:{default:!0}}),Be()}var Yr=J(" ");function Nr(e,t){we(t,!0);let n=pe(void 0),i=pe(void 0);const r=500;let s=pe(null);const a=Array.from({length:10},(o,f)=>{const c=130+f*10,d=.01+f*.005,p=0,g=r,y=100,_=[];for(let h=p;h<=g;h+=(g-p)/y){const E=250+c*Math.sin(d*h);_.push({X:h,Y:E})}return _}),v=o=>{switch(o){case"edges":return"Polygon edges";case"polylines":return"Polylines to clip";case"segments":return"Polyline segments";case"vertices":return"Total vertices"}};performance.mark("PolylineClipperStart");const l=new ut({device:t.device,polygon:De}),u=async()=>{if(b(n)){const o=b(n).getContext("2d"),f=await l.clip(a);performance.mark("PolylineClipperEnd"),performance.measure("PolylineClipping","PolylineClipperStart","PolylineClipperEnd"),L(s,performance.getEntriesByName("PolylineClipping")[0].duration/1e3),o.strokeStyle="white",De.forEach(c=>{o.beginPath(),c.forEach((d,p)=>{p===0?o.moveTo(d.X,d.Y):o.lineTo(d.X,d.Y)}),o.closePath(),o.stroke()}),o.strokeStyle="rgba(255, 0, 0, 0.45)",a.forEach(c=>{c.forEach((d,p,g)=>{p===0?(o.beginPath(),o.moveTo(d.X,d.Y)):p===g.length-1?(o.lineTo(d.X,d.Y),o.stroke()):o.lineTo(d.X,d.Y)})}),o.strokeStyle="rgba(0, 245, 0)",f.forEach(c=>{c.forEach((d,p)=>{p===0?(o.beginPath(),o.moveTo(d.X,d.Y)):o.lineTo(d.X,d.Y)}),o.stroke()}),L(i,G({edges:l.edgesCount,polylines:l.polylinesLength,segments:l.segmentsCount,vertices:l.verticesLength}))}};Re(()=>{u()}),sn(e,{title:"PolylineClipper",get timing(){return b(s)},canvasSize:r,get canvas(){return b(n)},set canvas(o){L(n,G(o))},children:(o,f)=>{var c=ht(),d=Ze(c);{var p=g=>{var y=ht(),_=Ze(y);vr(_,17,()=>Object.entries(b(i)),dr,(h,E)=>{let U=()=>b(E)[0],S=()=>b(E)[1];var V=Yr(),x=w(V);ue(()=>ce(x,`${v(U())??""}: `));var Y=D(x),Q=w(Y);ue(()=>ce(Q,S())),M(h,V)}),M(g,y)};rn(d,g=>{b(i)&&g(p)})}M(o,c)},$$slots:{default:!0}}),Be()}var kr=J('
'),Rr=J('
'),Gr=J("

Loading

"),Dr=J('

Line & Polyline Clipping With WebGPU Compute Shaders

Both utilities are not fully tested and might produce incorrect results


');function Mr(e,t){we(t,!1);const n=wr();br();var i=Dr(),r=D(w(i),2),s=w(r);cr(s,()=>n,a=>{var v=Gr();M(a,v)},(a,v)=>{var l=kr(),u=w(l);Ur(u,{get device(){return b(v)}});var o=D(u,2);Nr(o,{get device(){return b(v)}}),M(a,l)},(a,v)=>{var l=Rr(),u=w(l),o=w(u,!0);ue(()=>ce(o,b(v))),M(a,l)}),M(e,i),Be()}fr(Mr,{target:document.getElementById("app")}); diff --git a/page/assets/index-RvceDu1y.css b/page/assets/index-RvceDu1y.css new file mode 100644 index 0000000..eb96688 --- /dev/null +++ b/page/assets/index-RvceDu1y.css @@ -0,0 +1 @@ +/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}main{display:block}h1{font-size:2em;margin:.67em 0}hr{box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace,monospace;font-size:1em}a{background-color:transparent}abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}img{border-style:none}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button}button::-moz-focus-inner,[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner{border-style:none;padding:0}button:-moz-focusring,[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:.35em .75em .625em}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details{display:block}summary{display:list-item}template{display:none}[hidden]{display:none}fieldset.svelte-1ohsw66{width:50%;margin-bottom:1rem;min-height:750px}legend.svelte-1ohsw66{font-family:monospace;font-size:16px;padding:10px}.example.svelte-1ohsw66{display:flex;flex-direction:column;align-items:center}.container.svelte-1ohsw66{display:flex;flex-direction:column}canvas.svelte-1ohsw66{border:1px solid rgba(255,255,255,.1)}.results.svelte-1ohsw66{display:flex;flex-direction:column}:root{font-family:Inter,system-ui,Avenir,Helvetica,Arial,sans-serif;line-height:1.5;font-weight:400;color-scheme:light dark;color:#ffffffde;background-color:#242424;font-synthesis:none;text-rendering:optimizeLegibility;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}header.svelte-q590e0{text-align:center}.container.svelte-q590e0{width:100%;height:100vh;max-height:100%;display:flex;flex-direction:column;align-items:center}.container.svelte-q590e0 hr:where(.svelte-q590e0){border:1px solid rgba(127,255,212,.1)}main.svelte-q590e0{width:100%;display:flex;align-items:center;flex-direction:column;padding:16px;box-sizing:border-box}.content.svelte-q590e0{max-width:1100px;display:flex}.error.svelte-q590e0{padding:20px;min-width:120px;background-color:#976060;text-align:center;color:#fcfcfc;font-weight:700;border-radius:10px;margin-top:10px;margin-bottom:10px} diff --git a/page/demo.png b/page/demo.png new file mode 100644 index 0000000..e9305eb Binary files /dev/null and b/page/demo.png differ diff --git a/page/index.html b/page/index.html new file mode 100644 index 0000000..5087f7d --- /dev/null +++ b/page/index.html @@ -0,0 +1,14 @@ + + + + + + + WebGPU line clip + + + + +
+ + diff --git a/page/vite.svg b/page/vite.svg new file mode 100644 index 0000000..e7b8dfb --- /dev/null +++ b/page/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file