jsgui3-server 0.0.134 → 0.0.136
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +6 -0
- package/README.md +114 -18
- package/TODO.md +81 -0
- package/cli.js +96 -0
- package/docs/simple-server-api-design.md +702 -0
- package/examples/controls/14d) window, canvas globe/ARCBALL-README.md +146 -0
- package/examples/controls/14d) window, canvas globe/Clipping.js +0 -0
- package/examples/controls/14d) window, canvas globe/EarthGlobeRenderer.js +280 -799
- package/examples/controls/14d) window, canvas globe/RenderingPipeline.js +43 -0
- package/examples/controls/14d) window, canvas globe/arcball-drag-behaviour.js +250 -0
- package/examples/controls/14d) window, canvas globe/arcball-drag-behaviour.test.js +141 -0
- package/examples/controls/14d) window, canvas globe/drag-behaviour-base.js +70 -0
- package/examples/controls/14d) window, canvas globe/drag-controller.js +181 -0
- package/examples/controls/14d) window, canvas globe/math.test.js +281 -0
- package/examples/controls/14d) window, canvas globe/pipeline/BaseStage.js +46 -0
- package/examples/controls/14d) window, canvas globe/pipeline/ClippingStage.js +29 -0
- package/examples/controls/14d) window, canvas globe/pipeline/ComposeStage.js +15 -0
- package/examples/controls/14d) window, canvas globe/pipeline/ContinentsStage.js +116 -0
- package/examples/controls/14d) window, canvas globe/pipeline/GridStage.js +105 -0
- package/examples/controls/14d) window, canvas globe/pipeline/HUDStage.js +10 -0
- package/examples/controls/14d) window, canvas globe/pipeline/ShadeSphereStage.js +153 -0
- package/examples/controls/14d) window, canvas globe/pipeline/TransformStage.js +23 -0
- package/examples/controls/14d) window, canvas globe/pipeline/clipping/FrontFaceStrategy.js +46 -0
- package/examples/controls/14d) window, canvas globe/pipeline/clipping/PlaneClipStrategy.js +339 -0
- package/module.js +3 -1
- package/package.json +10 -4
- package/resources/_old_website-resource.js +10 -2
- package/resources/jsbuilder/babel/deep_iterate/deep_iterate_babel.js +37 -0
- package/resources/local-server-info-resource.js +1 -1
- package/serve-factory.js +221 -0
- package/serve-helpers.js +95 -0
- package/server.js +93 -7
- package/tests/cli.test.js +66 -0
- package/tests/dummy-client.js +10 -0
- package/tests/serve.test.js +210 -0
- package/.vscode/settings.json +0 -13
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
const BaseStage = require('./BaseStage');
|
|
2
|
+
|
|
3
|
+
class ShadeSphereStage extends BaseStage {
|
|
4
|
+
/**
|
|
5
|
+
* Per-pixel shading of the sphere into an offscreen buffer, then composited.
|
|
6
|
+
* Performance note: inner loop kept monolithic to encourage engine inlining
|
|
7
|
+
* and avoid function call overhead per pixel.
|
|
8
|
+
* @param {RenderState} rs
|
|
9
|
+
*/
|
|
10
|
+
execute(rs) {
|
|
11
|
+
const rInst = this.r;
|
|
12
|
+
|
|
13
|
+
const q = rInst.opts.quality * (
|
|
14
|
+
rs.interactive ? rInst.opts.interactiveQualityScale : 1
|
|
15
|
+
);
|
|
16
|
+
const d = Math.max(2, (2 * rs.radius * q) | 0);
|
|
17
|
+
const off = rInst._getOff(d, d);
|
|
18
|
+
|
|
19
|
+
if (rInst._mapSize !== d) {
|
|
20
|
+
rInst._buildNormalMap(d);
|
|
21
|
+
}
|
|
22
|
+
if (!rInst._imgData || rInst._imgData.width !== d) {
|
|
23
|
+
rInst._imgData = off.ctx.createImageData(d, d);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const data = rInst._imgData.data;
|
|
27
|
+
const base = rInst.opts.baseColor;
|
|
28
|
+
const amb = rInst.opts.ambient;
|
|
29
|
+
const kd = rInst.opts.diffuse;
|
|
30
|
+
const ks = rInst.opts.specular;
|
|
31
|
+
const shin = rInst.opts.shininess;
|
|
32
|
+
const termSoft = rInst.opts.terminatorSoftness;
|
|
33
|
+
const atm = rInst.opts.atmosphere;
|
|
34
|
+
const back = rInst.opts.backlight;
|
|
35
|
+
const Lx = rInst.sun[0], Ly = rInst.sun[1], Lz = rInst.sun[2];
|
|
36
|
+
|
|
37
|
+
// Half vector (approx: light + view(0,0,1))
|
|
38
|
+
let Hx = Lx, Hy = Ly, Hz = Lz + 1;
|
|
39
|
+
{
|
|
40
|
+
const invH = 1 / Math.hypot(Hx, Hy, Hz);
|
|
41
|
+
Hx *= invH; Hy *= invH; Hz *= invH;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const Rt = rInst.Rt;
|
|
45
|
+
const Rt00 = Rt[0], Rt01 = Rt[1], Rt02 = Rt[2];
|
|
46
|
+
const Rt10 = Rt[3], Rt11 = Rt[4], Rt12 = Rt[5];
|
|
47
|
+
const Rt20 = Rt[6], Rt21 = Rt[7], Rt22 = Rt[8];
|
|
48
|
+
|
|
49
|
+
const Nx = rInst._Nx, Ny = rInst._Ny, Nz = rInst._Nz, A8 = rInst._A8;
|
|
50
|
+
const texAlb = rInst.tex.albedo, texWater = rInst.tex.water, texIce = rInst.tex.ice;
|
|
51
|
+
const needTex = !!(texAlb || texWater || texIce);
|
|
52
|
+
|
|
53
|
+
const toneLUT = rInst._toneLUT;
|
|
54
|
+
const sLinToSRGB = rInst._lin2sU8;
|
|
55
|
+
const toneScale = (toneLUT.length - 1) / 4;
|
|
56
|
+
|
|
57
|
+
const nPix = d * d;
|
|
58
|
+
let p = 0;
|
|
59
|
+
for (let i = 0; i < nPix; i++) { // hot loop
|
|
60
|
+
const nz = Nz[i];
|
|
61
|
+
if (nz < 0) {
|
|
62
|
+
data[p] = data[p + 1] = data[p + 2] = data[p + 3] = 0;
|
|
63
|
+
p += 4;
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const nx = Nx[i];
|
|
68
|
+
const ny = Ny[i];
|
|
69
|
+
const NL = nx * Lx + ny * Ly + nz * Lz;
|
|
70
|
+
const NV = nz; // view = (0,0,1)
|
|
71
|
+
const NH = nx * Hx + ny * Hy + nz * Hz;
|
|
72
|
+
|
|
73
|
+
let t = (NL + termSoft) / (2 * termSoft);
|
|
74
|
+
if (t < 0) t = 0; else if (t > 1) t = 1;
|
|
75
|
+
const dayMask = (3 - 2 * t) * t * t * (NL > 0 ? NL : 0);
|
|
76
|
+
|
|
77
|
+
let albR = base[0], albG = base[1], albB = base[2];
|
|
78
|
+
let oceanW = 1, iceW = 0;
|
|
79
|
+
|
|
80
|
+
if (needTex) {
|
|
81
|
+
const Wx = Rt00 * nx + Rt01 * ny + Rt02 * nz;
|
|
82
|
+
const Wy = Rt10 * nx + Rt11 * ny + Rt12 * nz;
|
|
83
|
+
const Wz = Rt20 * nx + Rt21 * ny + Rt22 * nz;
|
|
84
|
+
const lon = Math.atan2(Wx, Wz);
|
|
85
|
+
const WyC = Wy < -1 ? -1 : (Wy > 1 ? 1 : Wy);
|
|
86
|
+
const lat = Math.asin(WyC);
|
|
87
|
+
if (texAlb) {
|
|
88
|
+
const s = rInst._sampleSRGB(texAlb, lon, lat);
|
|
89
|
+
const lut = rInst._s2l;
|
|
90
|
+
albR = lut[s.r]; albG = lut[s.g]; albB = lut[s.b];
|
|
91
|
+
}
|
|
92
|
+
if (texWater) {
|
|
93
|
+
const sw = rInst._sampleSRGB(texWater, lon, lat);
|
|
94
|
+
oceanW = (sw.r + sw.g + sw.b) / 765;
|
|
95
|
+
}
|
|
96
|
+
if (texIce) {
|
|
97
|
+
const si = rInst._sampleSRGB(texIce, lon, lat);
|
|
98
|
+
iceW = (si.r + si.g + si.b) / 765;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const diff = kd * dayMask;
|
|
103
|
+
const specW = oceanW * (1 - 0.9 * iceW);
|
|
104
|
+
const spec = NL > 0 ? ks * specW * Math.pow(NH > 0 ? NH : 0, shin) : 0;
|
|
105
|
+
const NVc = NV < 0 ? 0 : (NV > 1 ? 1 : NV);
|
|
106
|
+
const rimDay = atm * Math.pow(1 - NVc, 2.4) * (NL + 0.15 > 0 ? NL + 0.15 : 0);
|
|
107
|
+
let rimBack = 0;
|
|
108
|
+
if (Lz < 0 && NL < 0) {
|
|
109
|
+
rimBack = back * Math.pow(1 - NVc, 3.2) * (-NL);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
let rLin = albR * (amb + diff) + spec + 0.40 * rimDay + 0.25 * rimBack;
|
|
113
|
+
let gLin = albG * (amb + diff) + spec + 0.65 * rimDay + 0.40 * rimBack;
|
|
114
|
+
let bLin = albB * (amb + diff) + spec + 1.00 * rimDay + 0.80 * rimBack;
|
|
115
|
+
|
|
116
|
+
if (rLin < 0) rLin = 0; else if (rLin > 4) rLin = 4;
|
|
117
|
+
if (gLin < 0) gLin = 0; else if (gLin > 4) gLin = 4;
|
|
118
|
+
if (bLin < 0) bLin = 0; else if (bLin > 4) bLin = 4;
|
|
119
|
+
|
|
120
|
+
rLin = toneLUT[(rLin * toneScale) | 0];
|
|
121
|
+
gLin = toneLUT[(gLin * toneScale) | 0];
|
|
122
|
+
bLin = toneLUT[(bLin * toneScale) | 0];
|
|
123
|
+
|
|
124
|
+
if (rLin > 1) rLin = 1;
|
|
125
|
+
if (gLin > 1) gLin = 1;
|
|
126
|
+
if (bLin > 1) bLin = 1;
|
|
127
|
+
|
|
128
|
+
data[p] = sLinToSRGB[(rLin * 4095) | 0];
|
|
129
|
+
data[p + 1] = sLinToSRGB[(gLin * 4095) | 0];
|
|
130
|
+
data[p + 2] = sLinToSRGB[(bLin * 4095) | 0];
|
|
131
|
+
data[p + 3] = A8[i];
|
|
132
|
+
p += 4;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
off.ctx.putImageData(rInst._imgData, 0, 0);
|
|
136
|
+
const drawSize = d / q;
|
|
137
|
+
const ctx = rInst.ctx;
|
|
138
|
+
ctx.save();
|
|
139
|
+
ctx.beginPath();
|
|
140
|
+
ctx.arc(rs.cx, rs.cy, rs.radius, 0, Math.PI * 2);
|
|
141
|
+
ctx.clip();
|
|
142
|
+
ctx.drawImage(
|
|
143
|
+
off.canvas,
|
|
144
|
+
rs.cx - drawSize / 2,
|
|
145
|
+
rs.cy - drawSize / 2,
|
|
146
|
+
drawSize,
|
|
147
|
+
drawSize
|
|
148
|
+
);
|
|
149
|
+
ctx.restore();
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
module.exports = ShadeSphereStage;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
const BaseStage = require('./BaseStage');
|
|
2
|
+
|
|
3
|
+
class TransformStage extends BaseStage {
|
|
4
|
+
/** @param {RenderState} rs */
|
|
5
|
+
execute(rs) {
|
|
6
|
+
const r = this.r;
|
|
7
|
+
r._updateRot();
|
|
8
|
+
|
|
9
|
+
const rect = r.canvas.getBoundingClientRect();
|
|
10
|
+
const width = rect.width || r.canvas.width / r.opts.dpr;
|
|
11
|
+
const height = rect.height || r.canvas.height / r.opts.dpr;
|
|
12
|
+
rs.width = width;
|
|
13
|
+
rs.height = height;
|
|
14
|
+
|
|
15
|
+
const pad = r.opts.padding;
|
|
16
|
+
const minSide = Math.min(width, height);
|
|
17
|
+
rs.radius = Math.max(1, minSide / 2 - pad);
|
|
18
|
+
rs.cx = width * 0.5;
|
|
19
|
+
rs.cy = height * 0.5;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
module.exports = TransformStage;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// FrontFaceStrategy: current simple hemisphere clipping (z >= 0 fully)
|
|
2
|
+
// Exports a process(renderer, continent, R) function.
|
|
3
|
+
|
|
4
|
+
function ensureTriangulated(continent) {
|
|
5
|
+
if (continent.tri) return;
|
|
6
|
+
const polyXYZ = continent.polyXYZ;
|
|
7
|
+
const n = polyXYZ.length / 3;
|
|
8
|
+
if (n < 3) { continent.tri = null; return; }
|
|
9
|
+
// Simple average normal basis projection + ear clipping (duplicated lightweight version)
|
|
10
|
+
let nx=0, ny=0, nz=0; for (let i=0;i<n;i++){ const k=i*3; nx+=polyXYZ[k]; ny+=polyXYZ[k+1]; nz+=polyXYZ[k+2]; }
|
|
11
|
+
const nlen = Math.hypot(nx,ny,nz) || 1; nx/=nlen; ny/=nlen; nz/=nlen;
|
|
12
|
+
let ax = Math.abs(nx) < 0.8 ? 1 : 0; let ay = ax?0:1; let az = 0;
|
|
13
|
+
let ux = ny*az - nz*ay, uy = nz*ax - nx*az, uz = nx*ay - ny*ax; const ulen = Math.hypot(ux,uy,uz)||1; ux/=ulen; uy/=ulen; uz/=ulen;
|
|
14
|
+
let vx = ny*uz - nz*uy, vy = nz*ux - nx*uz, vz = nx*uy - ny*ux;
|
|
15
|
+
const px = new Array(n), py = new Array(n);
|
|
16
|
+
for (let i=0;i<n;i++){ const k=i*3; const x=polyXYZ[k], y=polyXYZ[k+1], z=polyXYZ[k+2]; px[i]=x*ux+y*uy+z*uz; py[i]=x*vx+y*vy+z*vz; }
|
|
17
|
+
const V = []; for (let i=0;i<n;i++) V.push(i);
|
|
18
|
+
function area(){ let a=0; for (let i=0;i<V.length;i++){ const i0=V[i], i1=V[(i+1)%V.length]; a+=px[i0]*py[i1]-px[i1]*py[i0]; } return a*0.5; }
|
|
19
|
+
const orientCW = area()<0;
|
|
20
|
+
function isConvex(a,b,c){ const ax=px[a], ay=py[a], bx=px[b], by=py[b], cx=px[c], cy=py[c]; const cross=(bx-ax)*(cy-ay)-(by-ay)*(cx-ax); return orientCW? cross<0: cross>0; }
|
|
21
|
+
function inTri(a,b,c,p){ const x=px[p], y=py[p]; const ax=px[a], ay=py[a], bx=px[b], by=py[b], cx=px[c], cy=py[c]; const v0x=cx-ax,v0y=cy-ay,v1x=bx-ax,v1y=by-ay,v2x=x-ax,v2y=y-ay; const den=v0x*v1y-v1x*v0y; if(Math.abs(den)<1e-12) return false; const inv=1/den; const A=(v2x*v1y-v1x*v2y)*inv; const B=(v0x*v2y-v2x*v0y)*inv; const C=1-A-B; return A>0&&B>0&&C>0; }
|
|
22
|
+
const out=[]; let guard=0;
|
|
23
|
+
while (V.length>3 && guard<10000){ let ear=false; for (let i=0;i<V.length;i++){ const iPrev=V[(i+V.length-1)%V.length], iCurr=V[i], iNext=V[(i+1)%V.length]; if(!isConvex(iPrev,iCurr,iNext)) continue; let inside=false; for (let k=0;k<V.length;k++){ const idx=V[k]; if(idx===iPrev||idx===iCurr||idx===iNext) continue; if(inTri(iPrev,iCurr,iNext,idx)){ inside=true; break; } } if(inside) continue; out.push(iPrev,iCurr,iNext); V.splice(i,1); ear=true; break; } if(!ear) break; guard++; }
|
|
24
|
+
if (V.length===3) out.push(V[0],V[1],V[2]);
|
|
25
|
+
continent.tri = new Uint16Array(out);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function process(renderer, continent, R){
|
|
29
|
+
const poly = continent.polyXYZ; if(!poly || poly.length < 9) { continent._visTrisLen=0; continent._strokeRuns=[]; return; }
|
|
30
|
+
ensureTriangulated(continent);
|
|
31
|
+
const nVerts = poly.length/3;
|
|
32
|
+
if(!continent._rotated || continent._rotated.length !== poly.length){ continent._rotated = new Float32Array(poly.length); }
|
|
33
|
+
const rot = continent._rotated;
|
|
34
|
+
const R00=R[0],R01=R[3],R02=R[6]; const R10=R[1],R11=R[4],R12=R[7]; const R20=R[2],R21=R[5],R22=R[8];
|
|
35
|
+
for(let i=0;i<poly.length;i+=3){ const x=poly[i], y=poly[i+1], z=poly[i+2]; rot[i]=R00*x+R01*y+R02*z; rot[i+1]=R10*x+R11*y+R12*z; rot[i+2]=R20*x+R21*y+R22*z; }
|
|
36
|
+
// Mask
|
|
37
|
+
let mask = continent._frontMask; if(!mask || mask.length!==nVerts) mask = continent._frontMask = new Uint8Array(nVerts);
|
|
38
|
+
let front=0; for(let v=0,off=2; v<nVerts; v++, off+=3){ const f = rot[off] >= 0 ? 1:0; mask[v]=f; front+=f; }
|
|
39
|
+
if(front===0){ continent._visTrisLen=0; continent._strokeRuns=[]; return; }
|
|
40
|
+
const allFront = front===nVerts; const tri = continent.tri;
|
|
41
|
+
if(tri){ if(!continent._visTris || continent._visTris.length !== tri.length) continent._visTris = new Uint16Array(tri.length); if(allFront){ continent._visTris.set(tri); continent._visTrisLen=tri.length; } else { let w=0; for(let i=0;i<tri.length;i+=3){ const a=tri[i], b=tri[i+1], c=tri[i+2]; if(mask[a]&mask[b]&mask[c]){ continent._visTris[w++]=a; continent._visTris[w++]=b; continent._visTris[w++]=c; } } continent._visTrisLen=w; } }
|
|
42
|
+
if(allFront){ continent._strokeRuns=[{ idx: Array.from({length:nVerts}, (_,i)=>i) }]; return; }
|
|
43
|
+
const runs=[]; let curr=null; for(let i=0;i<=nVerts;i++){ const idx=i % nVerts; if(mask[idx]){ if(!curr) curr={idx:[]}; curr.idx.push(idx); } else if(curr){ if(curr.idx.length>1) runs.push(curr); curr=null; } } if(curr && curr.idx.length>1) runs.push(curr); continent._strokeRuns=runs;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
module.exports = { process, name:'frontFace' };
|
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
// PlaneClipStrategy: spherical clipping of a polygon against the visible hemisphere (z >= 0)
|
|
2
|
+
// Produces clipped fill triangles and a stroke polyline in rotated (view) space.
|
|
3
|
+
|
|
4
|
+
// Helper math
|
|
5
|
+
function norm3(x, y, z) {
|
|
6
|
+
const l = Math.hypot(x, y, z) || 1; return [x / l, y / l, z / l];
|
|
7
|
+
}
|
|
8
|
+
function slerp(A, B, t) {
|
|
9
|
+
const [ax, ay, az] = norm3(A[0], A[1], A[2]);
|
|
10
|
+
const [bx, by, bz] = norm3(B[0], B[1], B[2]);
|
|
11
|
+
let dot = ax * bx + ay * by + az * bz; dot = Math.max(-1, Math.min(1, dot));
|
|
12
|
+
const th = Math.acos(dot);
|
|
13
|
+
if (th === 0) return [ax, ay, az];
|
|
14
|
+
const s = Math.sin(th), s1 = Math.sin((1 - t) * th) / s, s2 = Math.sin(t * th) / s;
|
|
15
|
+
return [ax * s1 + bx * s2, ay * s1 + by * s2, az * s1 + bz * s2];
|
|
16
|
+
}
|
|
17
|
+
// Sample a great-circle arc between A and B with optional horizon-aware refinement
|
|
18
|
+
function sampleGreatCircle(A, B, stepRad = 0.03, refineFn) {
|
|
19
|
+
const [ax, ay, az] = norm3(A[0],A[1],A[2]), [bx, by, bz] = norm3(B[0],B[1],B[2]);
|
|
20
|
+
let dot = ax*bx + ay*by + az*bz; dot = Math.max(-1, Math.min(1, dot));
|
|
21
|
+
const th = Math.acos(dot);
|
|
22
|
+
if (th === 0) return [[ax, ay, az]];
|
|
23
|
+
const n = Math.max(1, Math.ceil(th / stepRad));
|
|
24
|
+
const out = [];
|
|
25
|
+
let prev = null;
|
|
26
|
+
for (let i = 0; i <= n; i++) {
|
|
27
|
+
const t = i / n;
|
|
28
|
+
const p = slerp([ax,ay,az],[bx,by,bz], t);
|
|
29
|
+
if (refineFn && prev) {
|
|
30
|
+
const toAdd = refineFn(prev, p);
|
|
31
|
+
if (toAdd && toAdd.length) out.push(...toAdd);
|
|
32
|
+
}
|
|
33
|
+
out.push(p);
|
|
34
|
+
prev = p;
|
|
35
|
+
}
|
|
36
|
+
return out;
|
|
37
|
+
}
|
|
38
|
+
function intersectHorizon(A, B) {
|
|
39
|
+
const fz = (t) => slerp(A, B, t)[2];
|
|
40
|
+
let t0 = 0, t1 = 1, z0 = fz(t0), z1 = fz(t1);
|
|
41
|
+
if (z0 === 0) return slerp(A, B, 0);
|
|
42
|
+
if (z1 === 0) return slerp(A, B, 1);
|
|
43
|
+
if ((z0 > 0) === (z1 > 0)) return null;
|
|
44
|
+
for (let i = 0; i < 30; i++) {
|
|
45
|
+
const tm = 0.5 * (t0 + t1), zm = fz(tm);
|
|
46
|
+
if (Math.abs(zm) < 1e-8) return norm3(...slerp(A, B, tm));
|
|
47
|
+
if ((z0 > 0) === (zm > 0)) { t0 = tm; z0 = zm; } else { t1 = tm; z1 = zm; }
|
|
48
|
+
}
|
|
49
|
+
return norm3(...slerp(A, B, 0.5 * (t0 + t1)));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function clipPolygonHemisphere_SH(poly3) {
|
|
53
|
+
if (!poly3 || poly3.length < 3) return [];
|
|
54
|
+
const isInside = (p) => p[2] >= 0;
|
|
55
|
+
const out = [];
|
|
56
|
+
for (let i = 0; i < poly3.length; i++) {
|
|
57
|
+
const A = poly3[i];
|
|
58
|
+
const B = poly3[(i + 1) % poly3.length];
|
|
59
|
+
const Ain = isInside(A), Bin = isInside(B);
|
|
60
|
+
if (Ain && Bin) {
|
|
61
|
+
out.push(B);
|
|
62
|
+
} else if (Ain && !Bin) {
|
|
63
|
+
const X = intersectHorizon(A, B); if (X) out.push(X);
|
|
64
|
+
} else if (!Ain && Bin) {
|
|
65
|
+
const X = intersectHorizon(A, B); if (X) out.push(X);
|
|
66
|
+
out.push(B);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return out;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Tesselate polygon into small spherical triangles and cull to hemisphere
|
|
73
|
+
function tessellateClipHemisphere(poly3, opts = {}) {
|
|
74
|
+
const stepRad = opts.stepRad || 0.05; // densify edges before triangulation
|
|
75
|
+
const maxDepth = opts.maxDepth || 6;
|
|
76
|
+
const horizonRefine = opts.horizonRefine || { enabled: true, zThresh: 0.15, midSubdiv: 1 };
|
|
77
|
+
if (!poly3 || poly3.length < 3) return [];
|
|
78
|
+
|
|
79
|
+
// Densify the ring to improve initial triangulation
|
|
80
|
+
const dense = [];
|
|
81
|
+
const refineFn = horizonRefine && horizonRefine.enabled ? (P, Q) => {
|
|
82
|
+
const zP = P[2], zQ = Q[2];
|
|
83
|
+
if (Math.abs(zP) < horizonRefine.zThresh || Math.abs(zQ) < horizonRefine.zThresh || (zP > 0) !== (zQ > 0)) {
|
|
84
|
+
const mids = [];
|
|
85
|
+
for (let k = 1; k <= horizonRefine.midSubdiv; k++) {
|
|
86
|
+
const t = k / (horizonRefine.midSubdiv + 1);
|
|
87
|
+
mids.push(norm3(...slerp(P, Q, t)));
|
|
88
|
+
}
|
|
89
|
+
return mids;
|
|
90
|
+
}
|
|
91
|
+
return null;
|
|
92
|
+
} : null;
|
|
93
|
+
|
|
94
|
+
for (let i = 0; i < poly3.length; i++) {
|
|
95
|
+
const A = poly3[i], B = poly3[(i + 1) % poly3.length];
|
|
96
|
+
const seg = sampleGreatCircle(A, B, stepRad, refineFn);
|
|
97
|
+
if (i > 0 && dense.length) seg.shift(); // avoid duplicates between consecutive segments
|
|
98
|
+
dense.push(...seg);
|
|
99
|
+
}
|
|
100
|
+
if (dense.length && (dense[0] !== dense[dense.length - 1])) dense.push(dense[0]);
|
|
101
|
+
|
|
102
|
+
// Build a fan from a stable anchor (first vertex) to avoid degenerate centroid artifacts
|
|
103
|
+
const anchor = dense[0];
|
|
104
|
+
const tris = [];
|
|
105
|
+
for (let i = 1; i < dense.length - 1; i++) {
|
|
106
|
+
const A = dense[i], B = dense[i + 1];
|
|
107
|
+
tris.push([anchor, A, B]);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const result = [];
|
|
111
|
+
const allSign = (tri) => tri.every(p => p[2] >= 0) ? 1 : (tri.every(p => p[2] < 0) ? -1 : 0);
|
|
112
|
+
|
|
113
|
+
const subdivide = (tri, depth) => {
|
|
114
|
+
const sign = allSign(tri);
|
|
115
|
+
if (sign === 1) { result.push(tri); return; }
|
|
116
|
+
if (sign === -1) { return; }
|
|
117
|
+
if (depth >= maxDepth) {
|
|
118
|
+
const clipped = clipPolygonHemisphere_SH(tri);
|
|
119
|
+
if (clipped.length >= 3) {
|
|
120
|
+
for (let i = 1; i < clipped.length - 1; i++) {
|
|
121
|
+
result.push([clipped[0], clipped[i], clipped[i+1]]);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
const [A, B, C] = tri;
|
|
127
|
+
const AB = norm3(...slerp(A, B, 0.5));
|
|
128
|
+
const BC = norm3(...slerp(B, C, 0.5));
|
|
129
|
+
const CA = norm3(...slerp(C, A, 0.5));
|
|
130
|
+
subdivide([A, AB, CA], depth + 1);
|
|
131
|
+
subdivide([AB, B, BC], depth + 1);
|
|
132
|
+
subdivide([CA, BC, C], depth + 1);
|
|
133
|
+
subdivide([AB, BC, CA], depth + 1);
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
for (const t of tris) subdivide(t, 0);
|
|
137
|
+
return result; // array of triangles (arrays of 3 pts)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Simple 2D ear clipping on XY projection (for polygons entirely within hemisphere)
|
|
141
|
+
function triangulateXY(poly) {
|
|
142
|
+
const n = poly.length; if (n < 3) return [];
|
|
143
|
+
const px = new Array(n), py = new Array(n);
|
|
144
|
+
for (let i = 0; i < n; i++) { px[i] = poly[i][0]; py[i] = poly[i][1]; }
|
|
145
|
+
const V = []; for (let i = 0; i < n; i++) V.push(i);
|
|
146
|
+
function area() { let a = 0; for (let i = 0; i < V.length; i++) { const i0 = V[i], i1 = V[(i + 1) % V.length]; a += px[i0] * py[i1] - px[i1] * py[i0]; } return a * 0.5; }
|
|
147
|
+
const orientCW = area() < 0;
|
|
148
|
+
function isConvex(a, b, c) { const ax = px[a], ay = py[a], bx = px[b], by = py[b], cx = px[c], cy = py[c]; const cross = (bx - ax) * (cy - ay) - (by - ay) * (cx - ax); return orientCW ? cross < 0 : cross > 0; }
|
|
149
|
+
function inTri(a, b, c, p) { const x = px[p], y = py[p]; const ax = px[a], ay = py[a], bx = px[b], by = py[b], cx = px[c], cy = py[c]; const v0x = cx - ax, v0y = cy - ay, v1x = bx - ax, v1y = by - ay, v2x = x - ax, v2y = y - ay; const den = v0x * v1y - v1x * v0y; if (Math.abs(den) < 1e-12) return false; const inv = 1 / den; const A = (v2x * v1y - v1x * v2y) * inv; const B = (v0x * v2y - v2x * v0y) * inv; const C = 1 - A - B; return A > 0 && B > 0 && C > 0; }
|
|
150
|
+
const out = [];
|
|
151
|
+
let guard = 0;
|
|
152
|
+
while (V.length > 3 && guard < 10000) {
|
|
153
|
+
let ear = false;
|
|
154
|
+
for (let i = 0; i < V.length; i++) {
|
|
155
|
+
const iPrev = V[(i + V.length - 1) % V.length], iCurr = V[i], iNext = V[(i + 1) % V.length];
|
|
156
|
+
if (!isConvex(iPrev, iCurr, iNext)) continue;
|
|
157
|
+
let inside = false;
|
|
158
|
+
for (let k = 0; k < V.length; k++) { const idx = V[k]; if (idx === iPrev || idx === iCurr || idx === iNext) continue; if (inTri(iPrev, iCurr, iNext, idx)) { inside = true; break; } }
|
|
159
|
+
if (inside) continue;
|
|
160
|
+
out.push(iPrev, iCurr, iNext);
|
|
161
|
+
V.splice(i, 1);
|
|
162
|
+
ear = true;
|
|
163
|
+
break;
|
|
164
|
+
}
|
|
165
|
+
if (!ear) break; guard++;
|
|
166
|
+
}
|
|
167
|
+
if (V.length === 3) out.push(V[0], V[1], V[2]);
|
|
168
|
+
return out; // indices into poly
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Remove duplicate consecutive vertices and nearly collinear points (XY plane)
|
|
172
|
+
function cleanRingXY(pts, eps = 1e-10) {
|
|
173
|
+
if (!pts || pts.length < 3) return pts || [];
|
|
174
|
+
const out = [];
|
|
175
|
+
// remove consecutive duplicates
|
|
176
|
+
for (let i = 0; i < pts.length; i++) {
|
|
177
|
+
const p = pts[i];
|
|
178
|
+
if (!out.length) { out.push(p); continue; }
|
|
179
|
+
const q = out[out.length - 1];
|
|
180
|
+
if (Math.hypot(p[0] - q[0], p[1] - q[1]) > eps) out.push(p);
|
|
181
|
+
}
|
|
182
|
+
// if first equals last, drop last
|
|
183
|
+
if (out.length >= 2) {
|
|
184
|
+
const a = out[0], b = out[out.length - 1];
|
|
185
|
+
if (Math.hypot(a[0] - b[0], a[1] - b[1]) <= eps) out.pop();
|
|
186
|
+
}
|
|
187
|
+
if (out.length < 3) return out;
|
|
188
|
+
// remove collinear points by checking cross product with previous anchor
|
|
189
|
+
const res = [];
|
|
190
|
+
for (let i = 0; i < out.length; i++) {
|
|
191
|
+
const A = out[(i + out.length - 1) % out.length];
|
|
192
|
+
const B = out[i];
|
|
193
|
+
const C = out[(i + 1) % out.length];
|
|
194
|
+
const cross = (B[0] - A[0]) * (C[1] - A[1]) - (B[1] - A[1]) * (C[0] - A[0]);
|
|
195
|
+
if (Math.abs(cross) > eps) res.push(B);
|
|
196
|
+
}
|
|
197
|
+
return res;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function process(renderer, continent, R) {
|
|
201
|
+
const poly = continent.polyXYZ; if (!poly || poly.length < 9) { continent._clipFillTris = null; continent._clipStrokeXY = null; continent._visTrisLen = 0; return; }
|
|
202
|
+
// Rotate vertices to view space
|
|
203
|
+
if (!continent._rotated || continent._rotated.length !== poly.length) continent._rotated = new Float32Array(poly.length);
|
|
204
|
+
const rot = continent._rotated;
|
|
205
|
+
const R00 = R[0], R01 = R[3], R02 = R[6]; const R10 = R[1], R11 = R[4], R12 = R[7]; const R20 = R[2], R21 = R[5], R22 = R[8];
|
|
206
|
+
for (let i = 0; i < poly.length; i += 3) { const x = poly[i], y = poly[i + 1], z = poly[i + 2]; rot[i] = R00 * x + R01 * y + R02 * z; rot[i + 1] = R10 * x + R11 * y + R12 * z; rot[i + 2] = R20 * x + R21 * y + R22 * z; }
|
|
207
|
+
|
|
208
|
+
// Build polygon in rotated space
|
|
209
|
+
const n = poly.length / 3; const ring = new Array(n);
|
|
210
|
+
for (let i = 0; i < n; i++) { const k = i * 3; ring[i] = [rot[k], rot[k + 1], rot[k + 2]]; }
|
|
211
|
+
|
|
212
|
+
// Detect if entire polygon is strictly in front (including along edges)
|
|
213
|
+
const isAllFront = (() => {
|
|
214
|
+
const step = 0.15; // coarse check
|
|
215
|
+
for (let i = 0, n = ring.length; i < n; i++) {
|
|
216
|
+
const A = ring[i], B = ring[(i + 1) % n];
|
|
217
|
+
const seg = sampleGreatCircle(A, B, step);
|
|
218
|
+
for (const p of seg) if (p[2] < 0) return false;
|
|
219
|
+
}
|
|
220
|
+
return true;
|
|
221
|
+
})();
|
|
222
|
+
|
|
223
|
+
if (isAllFront) {
|
|
224
|
+
// Fast path: ear-clip the original ring in XY
|
|
225
|
+
const stepRad = 0.05;
|
|
226
|
+
const dense = [];
|
|
227
|
+
for (let i = 0; i < ring.length; i++) {
|
|
228
|
+
const A = ring[i], B = ring[(i + 1) % ring.length];
|
|
229
|
+
const seg = sampleGreatCircle(A, B, stepRad);
|
|
230
|
+
if (i > 0 && dense.length) seg.shift();
|
|
231
|
+
dense.push(...seg);
|
|
232
|
+
}
|
|
233
|
+
let poly2 = cleanRingXY(dense.map(p => [p[0], p[1], p[2]]), 1e-12);
|
|
234
|
+
// Provide a clean fill path to avoid AA seams
|
|
235
|
+
continent._clipFillPathXY = new Float32Array(poly2.length * 2);
|
|
236
|
+
for (let i = 0, j = 0; i < poly2.length; i++) { continent._clipFillPathXY[j++] = poly2[i][0]; continent._clipFillPathXY[j++] = poly2[i][1]; }
|
|
237
|
+
const triIdx = triangulateXY(poly2);
|
|
238
|
+
if (triIdx && triIdx.length >= 3 && (triIdx.length % 3) === 0) {
|
|
239
|
+
if (!continent._clipFillTris || continent._clipFillTris.length !== triIdx.length * 2) continent._clipFillTris = new Float32Array(triIdx.length * 2);
|
|
240
|
+
let w = 0; for (let i = 0; i < triIdx.length; i++) { const p = poly2[triIdx[i]]; continent._clipFillTris[w++] = p[0]; continent._clipFillTris[w++] = p[1]; }
|
|
241
|
+
continent._clipFillTrisLen = triIdx.length;
|
|
242
|
+
} else {
|
|
243
|
+
continent._clipFillTris = null; continent._clipFillTrisLen = 0;
|
|
244
|
+
}
|
|
245
|
+
// Single stroke run around full boundary
|
|
246
|
+
const run = new Float32Array(dense.length * 2);
|
|
247
|
+
for (let i = 0, j = 0; i < dense.length; i++) { run[j++] = dense[i][0]; run[j++] = dense[i][1]; }
|
|
248
|
+
continent._clipStrokeRuns = [run];
|
|
249
|
+
continent._visTrisLen = 0;
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Partial visibility: clip to hemisphere and ear-clip the resulting polygon in XY
|
|
254
|
+
let clipped = clipPolygonHemisphere_SH(ring);
|
|
255
|
+
if (!clipped || clipped.length < 3) { continent._clipFillTris = null; continent._clipStrokeRuns = []; continent._visTrisLen = 0; return; }
|
|
256
|
+
const stepRadFill = 0.025;
|
|
257
|
+
const dens = [];
|
|
258
|
+
for (let i = 0; i < clipped.length; i++) {
|
|
259
|
+
const A = clipped[i], B = clipped[(i + 1) % clipped.length];
|
|
260
|
+
const seg = sampleGreatCircle(A, B, stepRadFill, (P, Q) => {
|
|
261
|
+
const zP = P[2], zQ = Q[2];
|
|
262
|
+
if ((zP >= 0 && zQ >= 0) && (Math.abs(zP) < 0.12 || Math.abs(zQ) < 0.12)) {
|
|
263
|
+
// bias sampling toward the horizon for visible edges
|
|
264
|
+
return [norm3(...slerp(P, Q, 0.33)), norm3(...slerp(P, Q, 0.66))];
|
|
265
|
+
}
|
|
266
|
+
if ((zP > 0) !== (zQ > 0)) {
|
|
267
|
+
// crossing: ensure mid sample for stability
|
|
268
|
+
return [norm3(...slerp(P, Q, 0.5))];
|
|
269
|
+
}
|
|
270
|
+
return null;
|
|
271
|
+
});
|
|
272
|
+
if (i > 0 && dens.length) seg.shift();
|
|
273
|
+
dens.push(...seg);
|
|
274
|
+
}
|
|
275
|
+
let poly2 = cleanRingXY(dens.map(p => [p[0], p[1], p[2]]), 1e-12);
|
|
276
|
+
// Provide a clean fill path for partial visibility as well
|
|
277
|
+
continent._clipFillPathXY = new Float32Array(poly2.length * 2);
|
|
278
|
+
for (let i = 0, j = 0; i < poly2.length; i++) { continent._clipFillPathXY[j++] = poly2[i][0]; continent._clipFillPathXY[j++] = poly2[i][1]; }
|
|
279
|
+
let triIdx = triangulateXY(poly2);
|
|
280
|
+
if (!triIdx || triIdx.length < 3 || (triIdx.length % 3) !== 0) {
|
|
281
|
+
// Final fallback: spherical tessellation
|
|
282
|
+
const smallTris = tessellateClipHemisphere(ring, { stepRad: 0.03, maxDepth: 6, horizonRefine: { enabled: true, zThresh: 0.1, midSubdiv: 1 } });
|
|
283
|
+
if (!smallTris || !smallTris.length) { continent._clipFillTris = null; continent._clipStrokeRuns = []; continent._visTrisLen = 0; }
|
|
284
|
+
else {
|
|
285
|
+
const idxCount = smallTris.length * 3;
|
|
286
|
+
const floats = idxCount * 2;
|
|
287
|
+
if (!continent._clipFillTris || continent._clipFillTris.length !== floats) continent._clipFillTris = new Float32Array(floats);
|
|
288
|
+
let w = 0; for (const tri of smallTris) { for (let k = 0; k < 3; k++) { const p = tri[k]; continent._clipFillTris[w++] = p[0]; continent._clipFillTris[w++] = p[1]; } }
|
|
289
|
+
continent._clipFillTrisLen = idxCount;
|
|
290
|
+
}
|
|
291
|
+
} else {
|
|
292
|
+
if (!continent._clipFillTris || continent._clipFillTris.length !== triIdx.length * 2) continent._clipFillTris = new Float32Array(triIdx.length * 2);
|
|
293
|
+
let w = 0; for (let i = 0; i < triIdx.length; i++) { const p = poly2[triIdx[i]]; continent._clipFillTris[w++] = p[0]; continent._clipFillTris[w++] = p[1]; }
|
|
294
|
+
continent._clipFillTrisLen = triIdx.length;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// Build visible coastline stroke runs
|
|
298
|
+
const runs = [];
|
|
299
|
+
let curr = [];
|
|
300
|
+
const pushXY = (pt) => {
|
|
301
|
+
const x = pt[0], y = pt[1];
|
|
302
|
+
const L = curr.length;
|
|
303
|
+
if (L >= 2) { const dx = x - curr[L-2], dy = y - curr[L-1]; if (dx*dx + dy*dy < 1e-12) return; }
|
|
304
|
+
curr.push(x, y);
|
|
305
|
+
};
|
|
306
|
+
for (let i = 0, n = ring.length; i < n; i++) {
|
|
307
|
+
const A = ring[i], B = ring[(i + 1) % n];
|
|
308
|
+
const Ain = A[2] >= 0, Bin = B[2] >= 0;
|
|
309
|
+
if (Ain && Bin) {
|
|
310
|
+
const seg = sampleGreatCircle(A, B, stepRadFill);
|
|
311
|
+
if (!curr.length && seg.length) pushXY(seg[0]);
|
|
312
|
+
for (let s = 1; s < seg.length; s++) pushXY(seg[s]);
|
|
313
|
+
} else if (Ain && !Bin) {
|
|
314
|
+
const X = intersectHorizon(A, B); if (X) {
|
|
315
|
+
const seg = sampleGreatCircle(A, X, stepRadFill);
|
|
316
|
+
if (!curr.length && seg.length) pushXY(seg[0]);
|
|
317
|
+
for (let s = 1; s < seg.length; s++) pushXY(seg[s]);
|
|
318
|
+
}
|
|
319
|
+
if (curr.length >= 4) runs.push(new Float32Array(curr));
|
|
320
|
+
curr = [];
|
|
321
|
+
} else if (!Ain && Bin) {
|
|
322
|
+
const X = intersectHorizon(A, B); if (X) {
|
|
323
|
+
const seg = sampleGreatCircle(X, B, stepRadFill);
|
|
324
|
+
curr = [];
|
|
325
|
+
if (seg.length) { pushXY(seg[0]); for (let s = 1; s < seg.length; s++) pushXY(seg[s]); }
|
|
326
|
+
}
|
|
327
|
+
} else {
|
|
328
|
+
if (curr.length >= 4) runs.push(new Float32Array(curr));
|
|
329
|
+
curr = [];
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
if (curr.length >= 4) runs.push(new Float32Array(curr));
|
|
333
|
+
continent._clipStrokeRuns = runs;
|
|
334
|
+
|
|
335
|
+
// Disable legacy front-face fill for this continent (handled via _clipFillTris)
|
|
336
|
+
continent._visTrisLen = 0;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
module.exports = { process, name: 'planeClip' };
|
package/module.js
CHANGED
|
@@ -16,7 +16,9 @@ jsgui.controls.Active_HTML_Document = require('./controls/Active_HTML_Document')
|
|
|
16
16
|
|
|
17
17
|
//const Resource_Publisher = require('./publishing/http-resource-publisher');
|
|
18
18
|
//jsgui.Resource_Publisher = Resource_Publisher;
|
|
19
|
-
|
|
19
|
+
const Server = require('./server');
|
|
20
|
+
jsgui.Server = Server;
|
|
21
|
+
jsgui.serve = Server.serve;
|
|
20
22
|
jsgui.fs2 = require('./fs2');
|
|
21
23
|
//jsgui.Resource = Resource;
|
|
22
24
|
//console.log('pre scs');
|
package/package.json
CHANGED
|
@@ -7,14 +7,15 @@
|
|
|
7
7
|
"@babel/generator": "^7.28.3",
|
|
8
8
|
"@babel/parser": "^7.28.4",
|
|
9
9
|
"cookies": "^0.9.1",
|
|
10
|
-
"esbuild": "^0.25.
|
|
10
|
+
"esbuild": "^0.25.11",
|
|
11
11
|
"fnl": "^0.0.36",
|
|
12
12
|
"fnlfs": "^0.0.33",
|
|
13
13
|
"jsgui3-client": "^0.0.120",
|
|
14
|
-
"jsgui3-html": "^0.0.
|
|
14
|
+
"jsgui3-html": "^0.0.168",
|
|
15
15
|
"jsgui3-webpage": "^0.0.8",
|
|
16
16
|
"jsgui3-website": "^0.0.8",
|
|
17
|
-
"lang-tools": "^0.0.
|
|
17
|
+
"lang-tools": "^0.0.39",
|
|
18
|
+
"mocha": "^11.7.4",
|
|
18
19
|
"multiparty": "^4.2.3",
|
|
19
20
|
"ncp": "^2.0.0",
|
|
20
21
|
"obext": "^0.0.31",
|
|
@@ -38,5 +39,10 @@
|
|
|
38
39
|
"type": "git",
|
|
39
40
|
"url": "https://github.com/metabench/jsgui3-server.git"
|
|
40
41
|
},
|
|
41
|
-
"version": "0.0.
|
|
42
|
+
"version": "0.0.136",
|
|
43
|
+
"scripts": {
|
|
44
|
+
"cli": "node cli.js",
|
|
45
|
+
"serve": "node cli.js serve",
|
|
46
|
+
"test": "mocha tests/**/*.test.js"
|
|
47
|
+
}
|
|
42
48
|
}
|
|
@@ -440,6 +440,14 @@ class Website_Resource extends Resource {
|
|
|
440
440
|
var remoteAddress = req.connection.remoteAddress;
|
|
441
441
|
var router = this.router;
|
|
442
442
|
var res_process = router.process(req, res);
|
|
443
|
+
var processed_handled = false;
|
|
444
|
+
if (res_process) {
|
|
445
|
+
if (typeof res_process === 'object') {
|
|
446
|
+
processed_handled = !!res_process.handled;
|
|
447
|
+
} else if (res_process === true) {
|
|
448
|
+
processed_handled = true;
|
|
449
|
+
}
|
|
450
|
+
}
|
|
443
451
|
|
|
444
452
|
// Likely will need to make this more advanced, possibly to suit a spec.
|
|
445
453
|
// Website_Resource_Publisher may be the best place to deal with this.
|
|
@@ -448,7 +456,7 @@ class Website_Resource extends Resource {
|
|
|
448
456
|
// Possibly we need a Control_Publisher?
|
|
449
457
|
// To publish a Control at an address on the web.
|
|
450
458
|
|
|
451
|
-
|
|
459
|
+
console.log('Website_Resource !!res_process', processed_handled);
|
|
452
460
|
|
|
453
461
|
// Need more HTTP request to response handlers.
|
|
454
462
|
// Or just HTTP handlers really.
|
|
@@ -463,7 +471,7 @@ class Website_Resource extends Resource {
|
|
|
463
471
|
|
|
464
472
|
|
|
465
473
|
|
|
466
|
-
|
|
474
|
+
if (!processed_handled) {
|
|
467
475
|
if (req.url === "/") {
|
|
468
476
|
// Seems like too much of a special case.
|
|
469
477
|
|