codebee 0.1.104 → 0.1.106

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/app/ui/hive3d.js CHANGED
@@ -5,6 +5,11 @@ window.Hive3D = (function () {
5
5
  const clamp = (v, lo, hi) => Math.max(lo, Math.min(hi, v));
6
6
  const STATUS = { running:"运行中", done:"完成", queued:"排队中", failed:"失败", timeout:"超时", cancelled:"已取消" };
7
7
  const STAGES = ["规划","起草","评审","执行","打磨","合成"];
8
+ const WING_OUTLINE=[
9
+ [-.50,0,.008],[-.43,.19,.035],[-.28,.30,.075],[-.06,.34,.10],
10
+ [.18,.30,.086],[.38,.19,.05],[.50,0,.008],[.38,-.19,.05],
11
+ [.18,-.30,.086],[-.06,-.34,.10],[-.28,-.30,.075],[-.43,-.19,.035]
12
+ ];
8
13
  const SCREENS = [
9
14
  [163,441,127,55],[389,441,126,55],[616,441,125,55],[843,441,125,55],
10
15
  [1073,441,122,55],[1298,441,125,55],[1531,441,120,55],[1750,441,125,55],
@@ -91,17 +96,21 @@ window.Hive3D = (function () {
91
96
  function organicGeometry(gl){
92
97
  // A polished, tapered leaf/wing silhouette with a gently domed center and two-sided normals.
93
98
  let outline=[
94
- [-.5,0,0],[-.34,.17,.018],[-.12,.255,.040],[.14,.225,.044],
95
- [.38,.13,.026],[.5,0,0],[.38,-.13,.026],[.14,-.225,.044],
96
- [-.12,-.255,.040],[-.34,-.17,.018]
99
+ [-.5,0,.006],[-.34,.17,.028],[-.12,.255,.064],[.14,.225,.078],
100
+ [.38,.13,.038],[.5,0,.006],[.38,-.13,.038],[.14,-.225,.078],
101
+ [-.12,-.255,.064],[-.34,-.17,.028]
97
102
  ];
98
103
  const area=outline.reduce((sum,p,i)=>{const q=outline[(i+1)%outline.length];return sum+p[0]*q[1]-q[0]*p[1];},0);
99
104
  if(area<0)outline=outline.reverse();
100
105
  const positions=[],normals=[],indices=[];
101
106
  const emitSide=(sign)=>{
102
107
  const centerIndex=positions.length/3;
103
- positions.push(0,0,sign*.045);normals.push(0,0,sign);
104
- for(const p of outline){positions.push(p[0],p[1],p[2]*sign);normals.push(0,0,sign);}
108
+ positions.push(0,0,sign*.105);normals.push(0,0,sign);
109
+ for(const p of outline){
110
+ positions.push(p[0],p[1],p[2]*sign);
111
+ const n=norm([-.20*p[0],-.28*p[1],sign]);
112
+ normals.push(...n);
113
+ }
105
114
  for(let i=0;i<outline.length;i++){
106
115
  const a=centerIndex+1+i,b=centerIndex+1+(i+1)%outline.length;
107
116
  if(sign>0)indices.push(centerIndex,a,b);else indices.push(centerIndex,b,a);
@@ -110,6 +119,30 @@ window.Hive3D = (function () {
110
119
  emitSide(1);emitSide(-1);
111
120
  return geometry(gl,positions,indices,normals);
112
121
  }
122
+ function wingGeometry(gl){
123
+ // Separate convex, oval wing mesh for the bee assistants. Plants retain organicGeometry.
124
+ let outline=WING_OUTLINE.map(p=>p.slice());
125
+ // WebGL back-face culling requires consistent counter-clockwise front faces.
126
+ const wingArea=outline.reduce((sum,p,i)=>{const q=outline[(i+1)%outline.length];return sum+p[0]*q[1]-q[0]*p[1];},0);
127
+ if(wingArea<0)outline=outline.reverse();
128
+ const positions=[],normals=[],indices=[],colors=[];
129
+ const emitSide=(sign)=>{
130
+ const centerIndex=positions.length/3;
131
+ positions.push(0,0,sign*.42);normals.push(0,0,sign);colors.push(.80,.91,.99,.74);
132
+ for(const p of outline){
133
+ positions.push(p[0],p[1],p[2]*sign);
134
+ normals.push(...norm([-.48*p[0],-.48*p[1],sign]));
135
+ const edge=Math.min(1,Math.hypot(p[0]*.82,p[1]*1.15));
136
+ colors.push(.70-.06*edge,.86-.04*edge,.98-.02*edge,.70-.08*edge);
137
+ }
138
+ for(let i=0;i<outline.length;i++){
139
+ const a=centerIndex+1+i,b=centerIndex+1+(i+1)%outline.length;
140
+ if(sign>0)indices.push(centerIndex,a,b);else indices.push(centerIndex,b,a);
141
+ }
142
+ };
143
+ emitSide(1);emitSide(-1);
144
+ const mesh=geometry(gl,positions,indices,normals);mesh.colors=colors;return mesh;
145
+ }
113
146
  function hexGroutGeometry(gl){
114
147
  // Build the complete staggered flat-top hex lattice as one static mesh. Drawing only
115
148
  // three consecutive edges per tile creates crosses and broken hexes; unique full edges
@@ -141,6 +174,26 @@ window.Hive3D = (function () {
141
174
  }
142
175
  return geometry(gl,positions,indices,normals);
143
176
  }
177
+ function radialShadowGeometry(gl,segments=48,rings=7){
178
+ // Smooth, triangulated light falloff avoids the low-poly ellipsoid stamps that read
179
+ // like floating objects on the pale hex floor. Vertex alpha fades completely at the rim.
180
+ const positions=[0,0,0],normals=[0,1,0],colors=[1,1,1,.24],indices=[];
181
+ for(let ring=1;ring<=rings;ring++)for(let i=0;i<segments;i++){
182
+ const a=i/segments*Math.PI*2,r=ring/rings;
183
+ positions.push(Math.cos(a)*r,0,Math.sin(a)*r);normals.push(0,1,0);
184
+ const fade=1-r;colors.push(1,1,1,.24*fade*fade);
185
+ }
186
+ for(let i=0;i<segments;i++){
187
+ const next=(i+1)%segments;
188
+ indices.push(0,1+next,1+i);
189
+ }
190
+ for(let ring=1;ring<rings;ring++)for(let i=0;i<segments;i++){
191
+ const next=(i+1)%segments,a=1+(ring-1)*segments+i,b=1+(ring-1)*segments+next;
192
+ const c=1+ring*segments+i,d=1+ring*segments+next;
193
+ indices.push(a,d,c,a,b,d);
194
+ }
195
+ const mesh=geometry(gl,positions,indices,normals);mesh.colors=colors;return mesh;
196
+ }
144
197
  function cylinderGeometry(gl,segments=20){
145
198
  const p=[],n=[],ix=[];
146
199
  // Side wall has independent normals from the flat end caps, avoiding pinched highlights.
@@ -167,7 +220,7 @@ window.Hive3D = (function () {
167
220
  function Scene(opts){
168
221
  this.opts=opts;this.canvas=opts.canvas;this.overlay=opts.overlay;this.host=this.canvas.parentElement;
169
222
  this.onCellActivate=opts.onCellActivate||function(){};this.cellRefresh=opts.cellRefresh||function(){return{};};
170
- this.model=null;this.cells=[];this.page=0;this.zoom=1;this.yaw=0;this.pitch=0.42;this.panX=0;this.panY=0;this.active=false;this.raf=0;this.timer=0;this.listeners=[];this.screenMeta=[];this.displayMode="reference";
223
+ this.model=null;this.cells=[];this.page=0;this.zoom=1;this.yaw=0;this.pitch=0.48;this.panX=0;this.panY=0;this.active=false;this.raf=0;this.timer=0;this.listeners=[];this.screenMeta=[];this.displayMode="reference";this.eyeDirection=norm([0,3.1+Math.sin(this.pitch)*13.88-.62,25.4-1.3]);
171
224
  this.host.dataset.renderer="webgl";this.canvas.hidden=false;this.canvas.classList.add("hive-gl-live");
172
225
  this.referenceImage=this.host.querySelector(".hive-reference");
173
226
  this.layer=node("div","hive-reference-layer",this.host);
@@ -186,9 +239,9 @@ window.Hive3D = (function () {
186
239
  const gl=this.canvas.getContext("webgl",{alpha:true,antialias:true,powerPreference:"high-performance"})||this.canvas.getContext("experimental-webgl");
187
240
  if(!gl)throw Error("WebGL unavailable");this.gl=gl;
188
241
  const vs="attribute vec3 aPosition; attribute vec3 aNormal; attribute vec4 aColor; uniform mat4 uViewProj; varying vec4 vColor; varying float vShade; varying vec3 vWorld; varying vec3 vNormal; void main(){gl_Position=uViewProj*vec4(aPosition,1.0);vColor=aColor;vWorld=aPosition;vNormal=normalize(aNormal);vec3 n=normalize(aNormal);float sky=clamp(n.y*0.5+0.5,0.0,1.0);float side=abs(n.x)*0.035;float heightShade=mix(0.94,1.0,smoothstep(-0.2,3.6,aPosition.y));vShade=(0.88+0.08*sky+side)*heightShade;}";
189
- const fs="precision mediump float; varying vec4 vColor; varying float vShade; varying vec3 vWorld; varying vec3 vNormal; uniform vec3 uEyePosition; void main(){vec3 n=normalize(vNormal);vec3 v=normalize(uEyePosition-vWorld);vec3 key=normalize(vec3(-0.48,0.86,0.42));vec3 fill=normalize(vec3(0.62,0.32,-0.72));vec3 warm=normalize(vec3(0.34,0.46,0.82));float ndl=max(dot(n,key),0.0);float fillN=max(dot(n,fill),0.0);float warmN=max(dot(n,warm),0.0);float hemi=clamp(n.y*0.5+0.5,0.0,1.0);vec3 base=vColor.rgb;float wallBlue=step(0.38,base.b)*step(0.35,base.g)*step(base.r*1.65,base.g)*step(1.7,vWorld.y);float wallGradient=0.86+0.14*smoothstep(1.7,4.5,vWorld.y)+0.055*exp(-pow((vWorld.x+2.1)*0.24,2.0));base=mix(base,base*wallGradient,wallBlue);vec3 h=normalize(key+v);vec3 hf=normalize(fill+v);float chroma=max(max(base.r,base.g),base.b)-min(min(base.r,base.g),base.b);float gloss=mix(0.10,0.34,smoothstep(0.10,0.82,chroma));float spec=pow(max(dot(n,h),0.0),mix(24.0,58.0,gloss))*gloss*0.42;float specFill=pow(max(dot(n,hf),0.0),36.0)*0.075;float fresnel=pow(1.0-max(dot(n,v),0.0),3.0);vec3 ambient=base*(0.55+0.13*hemi);vec3 direct=base*(0.34*ndl+0.16*fillN+0.08*warmN);vec3 highlight=vec3(1.0,0.91,0.78)*spec+vec3(0.52,0.84,1.0)*specFill+vec3(0.18,0.48,0.68)*fresnel*0.075;float cyan=max(0.0,min(base.g,base.b)-base.r)*0.65;vec3 emissive=base*cyan;vec3 lit=ambient+direct+highlight+emissive;lit*=vShade;gl_FragColor=vec4(min(lit,vec3(1.0)),vColor.a);}";
242
+ const fs="precision mediump float; varying vec4 vColor; varying float vShade; varying vec3 vWorld; varying vec3 vNormal; uniform vec3 uEyePosition; void main(){vec3 n=normalize(vNormal);vec3 v=normalize(uEyePosition-vWorld);vec3 key=normalize(vec3(-0.48,0.86,0.42));vec3 fill=normalize(vec3(0.62,0.32,-0.72));vec3 warm=normalize(vec3(0.34,0.46,0.82));float ndl=max(dot(n,key),0.0);float fillN=max(dot(n,fill),0.0);float warmN=max(dot(n,warm),0.0);float hemi=clamp(n.y*0.5+0.5,0.0,1.0);vec3 base=vColor.rgb;float chroma=max(max(base.r,base.g),base.b)-min(min(base.r,base.g),base.b);float wallBlue=step(0.38,base.b)*step(0.35,base.g)*step(base.r*1.65,base.g)*step(1.7,vWorld.y);float wallGradient=0.78+0.12*smoothstep(1.0,4.5,vWorld.y)+0.24*exp(-pow((vWorld.x-0.2)*0.19,2.0));base=mix(base,base*wallGradient,wallBlue);vec3 h=normalize(key+v);vec3 hf=normalize(fill+v);float gloss=mix(0.10,0.34,smoothstep(0.10,0.82,chroma));float spec=pow(max(dot(n,h),0.0),mix(24.0,58.0,gloss))*gloss*0.42;float specFill=pow(max(dot(n,hf),0.0),36.0)*0.075;float fresnel=pow(1.0-max(dot(n,v),0.0),3.0);vec3 ambient=base*(0.55+0.13*hemi);vec3 direct=base*(0.34*ndl+0.16*fillN+0.08*warmN);vec3 highlight=vec3(1.0,0.91,0.78)*spec+vec3(0.52,0.84,1.0)*specFill+vec3(0.18,0.48,0.68)*fresnel*0.075;float cyan=max(0.0,min(base.g,base.b)-base.r)*0.65;vec3 emissive=base*cyan;float honey=step(0.68,base.r)*step(0.34,base.g)*step(base.g*0.96,base.r)*step(base.b*1.4,base.g);vec3 lit=ambient+direct+highlight+emissive+vec3(0.18,0.105,0.018)*honey;lit*=vShade;gl_FragColor=vec4(min(lit,vec3(1.0)),vColor.a);}";
190
243
  const program=gl.createProgram();gl.attachShader(program,shader(gl,gl.VERTEX_SHADER,vs));gl.attachShader(program,shader(gl,gl.FRAGMENT_SHADER,fs));gl.linkProgram(program);if(!gl.getProgramParameter(program,gl.LINK_STATUS))throw Error(gl.getProgramInfoLog(program));this.program=program;gl.useProgram(program);
191
- this.aPosition=gl.getAttribLocation(program,"aPosition");this.aNormal=gl.getAttribLocation(program,"aNormal");this.aColor=gl.getAttribLocation(program,"aColor");this.uViewProj=gl.getUniformLocation(program,"uViewProj");this.uEyePosition=gl.getUniformLocation(program,"uEyePosition");this.box=boxGeometry(gl);this.roundBox=roundedBoxGeometry(gl,6,.12);this.sphere=sphereGeometry(gl,16,24);this.cylinder=cylinderGeometry(gl,20);this.organic=organicGeometry(gl);this.grout=hexGroutGeometry(gl);this.bandGoldTop=sphereBandGeometry(gl,-.29,-.03);this.bandGoldBottom=sphereBandGeometry(gl,-.97,-.61);this.bandSeparator=sphereBandGeometry(gl,-.51,-.38);this.bandGoldTop.smooth=true;this.bandGoldBottom.smooth=true;this.bandSeparator.smooth=true;gl.enable(gl.DEPTH_TEST);gl.enable(gl.CULL_FACE);gl.cullFace(gl.BACK);gl.enable(gl.BLEND);gl.blendFunc(gl.SRC_ALPHA,gl.ONE_MINUS_SRC_ALPHA);gl.clearColor(0,0,0,0);
244
+ this.aPosition=gl.getAttribLocation(program,"aPosition");this.aNormal=gl.getAttribLocation(program,"aNormal");this.aColor=gl.getAttribLocation(program,"aColor");this.uViewProj=gl.getUniformLocation(program,"uViewProj");this.uEyePosition=gl.getUniformLocation(program,"uEyePosition");this.box=boxGeometry(gl);this.roundBox=roundedBoxGeometry(gl,6,.12);this.sphere=sphereGeometry(gl,16,24);this.cylinder=cylinderGeometry(gl,20);this.organic=organicGeometry(gl);this.wing=wingGeometry(gl);this.grout=hexGroutGeometry(gl);this.shadow=radialShadowGeometry(gl);this.bandGoldTop=sphereBandGeometry(gl,-.08,.18);this.bandGoldBottom=sphereBandGeometry(gl,-.97,-.61);this.bandSeparator=sphereBandGeometry(gl,-.51,-.38);this.bandGoldTop.smooth=true;this.bandGoldBottom.smooth=true;this.bandSeparator.smooth=true;gl.enable(gl.DEPTH_TEST);gl.enable(gl.CULL_FACE);gl.cullFace(gl.BACK);gl.enable(gl.BLEND);gl.blendFunc(gl.SRC_ALPHA,gl.ONE_MINUS_SRC_ALPHA);gl.clearColor(0,0,0,0);
192
245
  };
193
246
  Scene.prototype.listen=function(el,type,fn,opts){el.addEventListener(type,fn,opts);this.listeners.push(()=>el.removeEventListener(type,fn,opts));};
194
247
  Scene.prototype.bindSurface=function(){
@@ -204,10 +257,13 @@ window.Hive3D = (function () {
204
257
  Scene.prototype.addLink=function(a,b,radius,color){const dx=b[0]-a[0],dy=b[1]-a[1],dz=b[2]-a[2],length=Math.hypot(dx,dy,dz)||.001,rotX=Math.asin(clamp(dz/length,-1,1)),rotZ=Math.atan2(-dx,dy);this.add(this.cylinder,(a[0]+b[0])/2,(a[1]+b[1])/2,(a[2]+b[2])/2,radius,length,radius,color,0,rotZ,rotX);};
205
258
  Scene.prototype.buildWorld=function(){
206
259
  const b=this.box,rb=this.roundBox,s=this.sphere;this.objects=[];
207
- // Bright, calm blue-white studio with a continuous floor and a single clean feature wall.
208
- this.add(b,0,-.18,5.5,23,.28,34,[.97,.985,1,1]);
209
- this.add(b,0,2.70,-6.45,19,3.65,.24,[.10,.52,.89,1]);
210
- this.add(b,0,5.10,-6.30,19,.16,.18,[.98,.99,1,1]);
260
+ // Bright, calm blue-white studio with a continuous floor and a single clean feature wall. The free camera is framed wide enough to keep all 14 stations visible at desktop aspect ratios.
261
+ // Keep the studio bright and photographic: the reference has a nearly white
262
+ // floor with cool blue bounce, while the feature wall carries the saturated
263
+ // accent. The old values made the whole room read as a blue plastic box.
264
+ this.add(b,0,-.18,5.5,23,.28,34,[.86,.90,.94,1]);
265
+ this.add(b,0,2.70,-6.45,19,3.65,.24,[.19,.53,.83,1]);
266
+ this.add(b,0,4.58,-6.30,19,.075,.12,[.985,.99,1,1]);
211
267
  // Wall seams, lower trim, and glass side windows.
212
268
  this.add(b,0,.90,-6.28,18.7,.12,.06,[.08,.42,.78,1]);
213
269
  this.add(b,0,.82,-6.285,18.7,.035,.035,[.25,.67,.94,1]);
@@ -222,8 +278,8 @@ window.Hive3D = (function () {
222
278
  }
223
279
  // Ceiling light fixtures and soft-colored illuminated panels.
224
280
  for(let x=-6.3;x<=6.4;x+=6.3){
225
- this.add(b,x,5.36,-3.35,2.65,.075,.30,[.12,.17,.23,1]);
226
- this.add(b,x,5.29,-3.35,2.40,.045,.24,[1,.99,.91,1]);
281
+ this.add(b,x,4.66,-3.35,2.65,.065,.30,[.12,.17,.23,1]);
282
+ this.add(b,x,4.595,-3.35,2.40,.045,.24,[1,.99,.91,1]);
227
283
  }
228
284
  // Six white phase controls are projected on this wall by the live HTML overlay.
229
285
  for(let i=0;i<6;i++){
@@ -236,12 +292,12 @@ window.Hive3D = (function () {
236
292
  this.add(b,x,2.34,-6.005,.035,.48,.024,[.22,.89,1,1]);
237
293
  }
238
294
  // Continuous, pale hex grout is one static mesh: no crossed edges and no thousands of scene objects.
239
- this.add(this.grout,0,0,0,1,1,1,[.77,.83,.88,1]);
295
+ this.add(this.grout,0,0,0,1,1,1,[.88,.91,.92,1]);
240
296
  // Reference layout: eight compact rear stations and six wider front stations.
241
297
  // Every monitor overlay is projected from these exact same coordinates below.
242
298
  const deskRows=[
243
- {row:0,z:-1.80,width:2.03,screenWidth:1.28,screenHeight:.70,xs:[-8.4,-6.0,-3.6,-1.2,1.2,3.6,6.0,8.4]},
244
- {row:1,z:5.85,width:2.42,screenWidth:1.48,screenHeight:.82,xs:[-6.0,-3.6,-1.2,1.2,3.6,6.0]}
299
+ {row:0,z:-1.80,width:2.03,screenWidth:1.28,screenHeight:.54,xs:[-8.4,-6.0,-3.6,-1.2,1.2,3.6,6.0,8.4]},
300
+ {row:1,z:5.85,width:2.42,screenWidth:1.62,screenHeight:.64,xs:[-6.0,-3.6,-1.2,1.2,3.6,6.0]}
245
301
  ];
246
302
  this.deskPositions=[];
247
303
  for(const config of deskRows)for(const x of config.xs){
@@ -254,17 +310,35 @@ window.Hive3D = (function () {
254
310
  this.add(b,x,.455,z-.565,width*.64,.028,.024,[.61,.70,.79,1]);
255
311
  this.add(rb,x-width*.36,.25,z+.03,.43,.52,1.02,[.90,.93,.97,1]);
256
312
  this.add(rb,x+width*.36,.25,z+.03,.43,.52,1.02,[.90,.93,.97,1]);
313
+ // Full-height acoustic partitions give every workstation the enclosed, built-in
314
+ // cubicle silhouette of the reference while preserving a clear sightline to screens.
315
+ for(const side of [-1,1]){
316
+ const dividerX=x+side*(width*.49);
317
+ this.add(rb,dividerX,.88,z+.005,.058,1.06,1.16,[.91,.93,.93,1]);
318
+ this.add(rb,dividerX-side*.012,.90,z-.015,.020,.96,1.08,[.98,.98,.96,1]);
319
+ this.add(rb,dividerX,.88,z-.595,.066,1.04,.045,[.95,.96,.94,1]);
320
+ this.add(rb,dividerX,.88,z+.595,.066,1.04,.045,[.95,.96,.94,1]);
321
+ this.add(b,dividerX-side*.014,.88,z-.55,.009,.76,.010,[.75,.81,.83,1]);
322
+ this.add(b,dividerX-side*.014,.88,z+.55,.009,.76,.010,[.75,.81,.83,1]);
323
+ }
257
324
  // A compact three-drawer pedestal gives each station the white office furniture silhouette of the reference.
258
- this.add(rb,x+width*.30,.27,z+.445,width*.30,.42,.26,[.90,.935,.98,1]);
325
+ this.add(rb,x+width*.30,.27,z+.445,width*.30,.42,.26,[.97,.965,.94,1]);
259
326
  for(const drawerY of [.18,.28,.38])this.add(b,x+width*.30,drawerY,z+.579,width*.22,.012,.012,[.62,.71,.80,1]);
260
327
  this.add(b,x+width*.30,.41,z+.581,width*.085,.018,.014,[.45,.58,.70,1]);
328
+ for(const drawerY of [.18,.28,.38])this.add(rb,x+width*.30,drawerY,z+.593,width*.105,.026,.018,[.80,.86,.92,1]);
261
329
  this.add(rb,x,.43,z-.22,width*.78,.045,.74,[.89,.93,.97,1]);
262
- this.add(rb,x,screenY,z-.48,screenWidth+.18,screenHeight+.18,.13,[.035,.055,.075,1]);
263
- this.add(rb,x,screenY,z-.432,screenWidth+.08,screenHeight+.08,.045,[.15,.20,.25,1]);
264
- this.add(b,x,screenY,z-.405,screenWidth,screenHeight,.018,[.018,.105,.18,1]);
265
- this.add(this.cylinder,x,.78,z-.43,.072,.40,.072,[.32,.39,.45,1]);
266
- this.add(b,x,.49,z-.27,.54,.045,.34,[.16,.20,.24,1]);
330
+ this.add(rb,x,screenY,z-.48,screenWidth+.055,screenHeight+.095,.060,[.025,.040,.055,1]);
331
+ this.add(rb,x,screenY,z-.447,screenWidth+.028,screenHeight+.060,.020,[.12,.17,.21,1]);
332
+ this.add(b,x,screenY,z-.408,screenWidth,screenHeight,.018,[.025,.145,.235,1]);
333
+ this.add(this.cylinder,x,.78,z-.43,.034,.36,.034,[.55,.60,.63,1]);
334
+ this.add(rb,x,.60,z-.45,.30,.18,.028,[.76,.79,.79,1]);
335
+ this.add(rb,x,.935,z-.451,.20,.10,.038,[.43,.49,.52,1]);
336
+ this.add(b,x,.49,z-.27,.54,.045,.34,[.12,.17,.20,1]);
267
337
  this.add(b,x,screenY-screenHeight*.42,z-.382,screenWidth*.84,.018,.014,[.10,.83,.94,1]);
338
+ // A tiny centered webcam and a restrained power indicator give the monitor a finished hardware read.
339
+ this.add(rb,x,screenY+screenHeight*.5+.064,z-.386,.070,.026,.018,[.025,.035,.045,1]);
340
+ this.add(s,x,screenY+screenHeight*.5+.064,z-.374,.012,.012,.008,[.14,.82,.98,1]);
341
+ this.add(s,x+screenWidth*.39,screenY-screenHeight*.5+.025,z-.375,.010,.010,.008,[.28,.94,.62,1]);
268
342
  this.add(rb,x-.18,.635,z+.25,.60,.035,.20,[.11,.14,.17,1]);
269
343
  this.add(s,x+.40,.648,z+.22,.060,.025,.075,[.055,.065,.075,1]);
270
344
  this.add(b,x+.40,.674,z+.20,.009,.005,.024,[.30,.66,.78,1]);
@@ -272,101 +346,150 @@ window.Hive3D = (function () {
272
346
  const keyColor=(row===0&&col%4===0)?[.54,.61,.68,1]:((row+col)%5===0?[.40,.48,.56,1]:[.22,.27,.32,1]);
273
347
  this.add(b,x-.45+col*.049,.659,z+.185+row*.045,.030,.009,.024,keyColor);
274
348
  }
275
- this.add(b,x-.18,.62,z+.25,.66,.012,.24,[.68,.75,.82,1]);
349
+ this.add(b,x-.18,.62,z+.25,.66,.012,.24,[.78,.82,.83,1]);
350
+ for(let key=0;key<4;key++)this.add(b,x-.43+key*.14,.669,z+.17,.11,.004,.012,[.54,.61,.64,1]);
351
+ for(let key=0;key<4;key++)this.add(b,x-.43+key*.14,.669,z+.32,.11,.004,.012,[.54,.61,.64,1]);
276
352
  const binderStart=x+width/2-.48;
277
353
  for(let k=0;k<4;k++)this.add(b,binderStart+k*.095,.83,z+.35,.075,.43,.22,[[.13,.43,.78,1],[.95,.42,.24,1],[.16,.66,.46,1],[.93,.72,.28,1]][k]);
278
354
  const blueBinderStart=x-width/2+.19;
279
355
  for(let k=0;k<3;k++)this.add(b,blueBinderStart+k*.095,.79,z+.34,.075,.35,.18,[[.08,.34,.80,1],[.10,.51,.91,1],[.06,.25,.65,1]][k]);
280
356
  this.add(this.cylinder,x,.08,z+1.10,.075,.28,.075,[.12,.15,.18,1]);
281
357
  this.add(rb,x,.30,z+1.10,.94,.12,.68,[.11,.14,.17,1]);
282
- this.add(rb,x,.50,z+.82,1.02,.60,.15,[.12,.17,.22,1]);
283
- this.add(rb,x,.50,z+.91,.76,.43,.024,[.20,.25,.29,1]);
358
+ this.add(rb,x,.68,z+.82,.82,.68,.15,[.17,.20,.23,1]);
359
+ this.add(rb,x,.68,z+.91,.62,.56,.024,[.24,.28,.31,1]);
284
360
  this.add(rb,x,.42,z+1.10,.48,.12,.12,[.18,.22,.25,1]);
285
361
  // Contrasting graphite upholstery separates the chair from the bee chassis.
286
- this.add(rb,x,.50,z+.90,.78,.43,.050,[.16,.20,.24,1]);
287
- for(const ribY of [.34,.48,.62])this.add(rb,x,ribY,z+.93,.58,.026,.020,[.34,.39,.43,1]);
288
- for(const side of [-1,1])this.add(rb,x+side*.49,.50,z+.93,.035,.48,.025,[.23,.30,.36,1]);
362
+ this.add(rb,x,.68,z+.90,.62,.56,.050,[.22,.25,.28,1]);
363
+ for(const ribY of [.45,.64,.82,.94])this.add(rb,x,ribY,z+.93,.44,.018,.020,[.38,.41,.43,1]);
364
+ for(const side of [-1,1])this.add(rb,x+side*.40,.68,z+.93,.026,.60,.025,[.29,.34,.38,1]);
289
365
  for(const side of [-1,1]){
290
366
  this.add(rb,x+side*.39,.63,z+.96,.10,.075,.38,[.14,.18,.21,1]);
291
367
  this.add(b,x+side*.39,.48,z+.97,.035,.28,.045,[.10,.13,.16,1]);
292
368
  }
293
369
  for(let k=0;k<5;k++){const a=k*Math.PI*2/5;const wx=x+Math.cos(a)*.48,wz=z+1.10+Math.sin(a)*.40;this.add(b,x+Math.cos(a)*.38,.035,z+1.10+Math.sin(a)*.32,.30,.06,.075,[.08,.10,.12,1],a);this.add(s,wx,.005,wz,.085,.065,.075,[.035,.045,.055,1]);}
294
- const by=1.25,bz=z+.98;
370
+ const by=1.25,bz=z+.98,beeObjectStart=this.objects.length;
295
371
  // Bee assistant: distinct head, dark chassis, striped abdomen, headset and translucent wings.
296
- this.add(s,x,by-.42,bz+.005,.31,.31,.235,[.075,.09,.11,1]);
372
+ this.add(s,x,by-.48,bz+.005,.325,.355,.250,[.075,.09,.11,1]);
297
373
  // Slightly enlarge the band ellipsoid to avoid depth-buffer z-fighting with the shell.
298
- const shellX=.315,shellY=.315,shellZ=.239,shellCenterY=by-.42,shellCenterZ=bz+.005;
299
- this.add(this.bandGoldTop,x,shellCenterY,shellCenterZ,shellX,shellY,shellZ,[1,.66,.035,1]);
300
- this.add(this.bandGoldBottom,x,shellCenterY,shellCenterZ,shellX,shellY,shellZ,[.99,.56,.018,1]);
374
+ const shellX=.330,shellY=.360,shellZ=.255,shellCenterY=by-.48,shellCenterZ=bz+.005;
375
+ this.add(this.bandGoldTop,x,shellCenterY,shellCenterZ,shellX,shellY,shellZ,[1,.86,.12,1]);
376
+ this.add(this.bandGoldBottom,x,shellCenterY,shellCenterZ,shellX,shellY,shellZ,[1,.70,.045,1]);
301
377
  this.add(this.bandSeparator,x,shellCenterY,shellCenterZ,shellX,shellY,shellZ,[.045,.055,.065,1]);
302
- this.add(s,x,by,bz,.405,.37,.345,[1,.70,.045,1]);
303
- // The face points toward the monitor; the rear silhouette carries the headset details.
378
+ this.add(s,x,by,bz,.515,.465,.415,[1,.94,.22,1]);
379
+ // The reference bee has a clear glossy black cheek/eye patch visible from behind.
380
+ // Put it on the camera-facing quarter of the head so the character reads at scene scale.
381
+ this.add(s,x+.340,by+.025,bz+.265,.190,.205,.074,[.008,.012,.018,1]);
382
+ this.add(s,x+.355,by+.085,bz+.315,.034,.038,.018,[.32,.86,.98,1]);
383
+ // A slim graphite cowl across the crown catches light like the reference's
384
+ // dark headset band and separates the head from the rounded thorax below.
385
+ this.add(rb,x,by+.405,bz+.004,.52,.060,.32,[.12,.14,.15,1]);
386
+ // A forward-facing graphite visor makes the assistant read as a character, not a plain yellow sphere.
387
+ // The workstation monitor is on -Z, so the faceplate and optical details sit on that side of the head.
388
+ this.add(rb,x,by+.012,bz-.166,.258,.142,.026,[.035,.055,.070,1]);
389
+ this.add(rb,x,by-.006,bz-.183,.214,.092,.012,[.055,.085,.105,1]);
390
+ for(const side of [-1,1]){
391
+ this.add(s,x+side*.066,by+.022,bz-.194,.045,.046,.020,[.20,.91,.98,1]);
392
+ this.add(s,x+side*.066,by+.029,bz-.205,.017,.018,.008,[.92,.99,1,1]);
393
+ }
394
+ this.add(rb,x,by-.043,bz-.197,.070,.010,.008,[.98,.61,.035,1]);
395
+ // The rear silhouette carries the headset details.
304
396
  for(const side of [-1,1]){
305
397
  this.add(s,x+side*.382,by+.015,bz+.035,.078,.118,.094,[.045,.055,.065,1]);
306
398
  this.add(s,x+side*.407,by+.015,bz+.052,.030,.052,.048,[.99,.68,.045,1]);
307
- this.add(this.cylinder,x+side*.105,by+.43,bz+.105,.020,.30,.020,[.045,.055,.065,1],0,-side*.18);
308
- this.add(s,x+side*.13,by+.57,bz+.108,.032,.032,.032,[.10,.12,.14,1]);
309
- this.add(s,x+side*.135,by+.595,bz+.132,.024,.024,.024,[1,.73,.10,1]);
399
+ const antennaBase=[x+side*.125,by+.39,bz+.14];
400
+ const antennaBend=[x+side*.16,by+.55,bz+.16];
401
+ const antennaTip=[x+side*.205,by+.65,bz+.19];
402
+ this.addLink(antennaBase,antennaBend,.013,[.045,.055,.065,1]);
403
+ this.addLink(antennaBend,antennaTip,.011,[.045,.055,.065,1]);
404
+ this.add(s,antennaTip[0],antennaTip[1],antennaTip[2],.030,.030,.030,[.10,.12,.14,1]);
405
+ this.add(s,antennaTip[0],antennaTip[1]+.012,antennaTip[2]+.012,.012,.012,.012,[1,.73,.10,1]);
310
406
  }
311
407
  this.add(rb,x,by-.335,bz+.156,.32,.026,.018,[.045,.055,.065,1]);
312
408
  this.add(s,x,by+.30,bz+.18,.115,.035,.06,[.045,.055,.065,1]);
313
409
  // Short forward-projecting thighs tuck under the desktop and visually anchor the bee to its chair.
314
410
  for(const side of [-1,1]){
315
411
  this.add(s,x+side*.125,.465,z+.71,.075,.13,.14,[.075,.09,.11,1]);
316
- this.add(rb,x+side*.13,.315,z+.66,.10,.060,.15,[.99,.66,.035,1]);
412
+ this.add(rb,x+side*.13,.315,z+.66,.10,.060,.15,[1,.86,.12,1]);
317
413
  }
318
- // Shoulders, articulated elbows and forearms form continuous 3D links down to the keyboard.
414
+ // A wider elbow arc keeps forearms visible around the torso in the rear camera view;
415
+ // the left hand stays over the keyboard and the right hand aligns with the mouse.
319
416
  for(const side of [-1,1]){
320
- const shoulder=[x+side*.32,.99,z+.56],elbow=[x+side*.43,.83,z+.30],wrist=[x+side*.36,.675,z-.12],hand=[x+side*.35,.655,z-.20];
321
- this.addLink(shoulder,elbow,.039,[.19,.22,.25,1]);
322
- this.addLink(elbow,wrist,.034,[.20,.24,.27,1]);
417
+ const shoulder=[x+side*.36,.99,z+.94],elbow=[x+side*.55,.83,z+.82],wrist=[x+(side<0?-.49:.50),.675,z+.54],hand=[x+(side<0?-.42:.42),.655,z+.28];
418
+ this.addLink(shoulder,elbow,.042,[.12,.15,.18,1]);
419
+ this.addLink(elbow,wrist,.036,[.18,.22,.25,1]);
323
420
  this.addLink(wrist,hand,.030,[.96,.63,.035,1]);
324
- this.add(s,shoulder[0],shoulder[1],shoulder[2],.075,.075,.085,[.19,.22,.25,1]);
325
- this.add(s,elbow[0],elbow[1],elbow[2],.065,.065,.065,[.32,.36,.39,1]);
326
- this.add(s,wrist[0],wrist[1],wrist[2],.052,.052,.052,[.24,.29,.32,1]);
421
+ this.add(s,shoulder[0],shoulder[1],shoulder[2],.080,.080,.085,[.14,.17,.20,1]);
422
+ this.add(s,elbow[0],elbow[1],elbow[2],.068,.068,.068,[.26,.30,.33,1]);
423
+ this.add(s,wrist[0],wrist[1],wrist[2],.052,.052,.052,[.21,.25,.28,1]);
327
424
  this.add(rb,hand[0],hand[1],hand[2],.085,.045,.070,[.96,.63,.035,1]);
328
- this.add(s,x+side*.35,.655,z-.20,.070,.035,.055,[.98,.68,.045,1]);
329
- for(let finger=0;finger<3;finger++)this.add(b,x+side*.35+(finger-1)*.026,.638,z-.255,.012,.010,.046,[.22,.29,.35,1]);
425
+ this.add(s,hand[0],hand[1],hand[2],.070,.035,.055,[.98,.68,.045,1]);
426
+ for(let finger=0;finger<3;finger++)this.add(b,hand[0]+(finger-1)*.026,.638,hand[2]-.06,.012,.010,.046,[.22,.29,.35,1]);
330
427
  }
331
428
  // Small cyan service badge on the back of the chassis.
332
429
  this.add(rb,x,by-.585,bz+.205,.13,.068,.018,[.025,.10,.14,1]);
333
430
  this.add(rb,x,by-.585,bz+.218,.082,.016,.010,[.16,.88,.98,1]);
334
- // Smooth glassy oval wings sit at the shoulder line. Sphere geometry keeps their
335
- // silhouette rounded from every camera angle and lets the opaque body occlude them.
336
- this.add(s,x-.43,by-.245,bz+.12,.43,.105,.075,[.58,.84,1,.32],0,.10);
337
- this.add(s,x+.43,by-.245,bz+.12,.43,.105,.075,[.58,.84,1,.32],0,-.10);
338
- this.add(s,x-.40,by-.225,bz+.155,.28,.060,.040,[.93,.99,1,.18],0,.08);
339
- this.add(s,x+.40,by-.225,bz+.155,.28,.060,.040,[.93,.99,1,.18],0,-.08);
431
+ // Angled, translucent wings emerge from behind the head; a leading-edge highlight
432
+ // defines the outward/upward sweep without enclosing the wing like a headset ring.
433
+ this.add(this.wing,x-.49,by+.17,bz+.14,.82,.60,.10,[.88,.96,1,.96],0,-.48);
434
+ this.add(this.wing,x+.49,by+.17,bz+.14,.82,.60,.10,[.88,.96,1,.96],0,.48);
435
+ for(const side of [-1,1]){
436
+ const rotation=side*.48,ca=Math.cos(rotation),sa=Math.sin(rotation);
437
+ const edgePoints=WING_OUTLINE.map(p=>[
438
+ x+side*.49+ca*p[0]*.82-sa*p[1]*.60,
439
+ by+.17+sa*p[0]*.82+ca*p[1]*.60,
440
+ bz+.125
441
+ ]);
442
+ for(let i=0;i<6;i++)this.addLink(edgePoints[i],edgePoints[i+1],.0052,[.59,.80,.91,.56]);
443
+ const root=[x+side*.38,by+.04,bz+.17];
444
+ const mid=[x+side*.50,by+.17,bz+.17];
445
+ const tip=[x+side*.76,by+.29,bz+.17];
446
+ this.addLink(root,mid,.0055,[.83,.93,.98,.70]);
447
+ this.addLink(mid,tip,.0042,[.83,.93,.98,.62]);
448
+ this.addLink([x+side*.43,by+.15,bz+.13],[x+side*.55,by+.22,bz+.13],.0032,[.90,.96,1,.54]);
449
+ }
450
+ // Slightly smaller assistants read as seated workers within the furniture scale.
451
+ for(const assistant of this.objects.slice(beeObjectStart)){
452
+ assistant.x=x+(assistant.x-x)*.92;
453
+ assistant.y=by+(assistant.y-by)*.92;
454
+ assistant.z=bz+(assistant.z-bz)*.92;
455
+ assistant.sx*=.84;assistant.sy*=.84;assistant.sz*=.84;
456
+ }
340
457
 
341
458
  }
342
459
  // Planters and stylized leaves soften the room edges.
343
460
  for(const x of [-9.05,9.05]){
344
- this.add(s,x,-.032,-4.70,.66,.012,.70,[.055,.09,.14,.24]);
345
- this.add(rb,x,.17,-4.70,.58,.34,.58,[.70,.77,.81,1]);
461
+ this.add(s,x,-.032,-4.70,.78,.012,.82,[.055,.09,.14,.24]);
462
+ this.add(rb,x,.17,-4.70,.68,.34,.68,[.70,.77,.81,1]);
346
463
  this.add(this.cylinder,x,.70,-4.70,.065,.78,.065,[.31,.28,.19,1]);
347
464
  // Layered foliage with varied yaw and drooping leaf tips, so the planters read as broad indoor plants.
348
465
  for(let k=0;k<18;k++){
349
466
  const a=k/18*Math.PI*2,spread=Math.sin(a)*.62;
350
- const y=.94+(k%5)*.145,rad=.20+(k%4)*.095;
351
- const leafColor=[[.045,.30,.15,1],[.055,.38,.19,1],[.075,.45,.22,1],[.15,.50,.27,1]][k%4];
467
+ const y=1.00+(k%5)*.17,rad=.24+(k%4)*.115;
468
+ const leafColor=[[.10,.43,.15,1],[.15,.55,.18,1],[.20,.64,.22,1],[.28,.70,.28,1]][k%4];
352
469
  this.add(this.organic,x+Math.sin(a)*rad,y,-4.70+Math.cos(a)*rad*.62,
353
- .58+(k%3)*.065,.78+(k%4)*.10,.05,leafColor,a*.62-Math.PI*.18,Math.PI/2+spread);
470
+ .68+(k%3)*.075,.90+(k%4)*.12,.05,leafColor,a*.62-Math.PI*.18,Math.PI/2+spread);
354
471
  }
355
472
  for(let k=0;k<7;k++){
356
473
  const a=k/7*Math.PI*2,spread=Math.sin(a)*.48;
357
- this.add(this.organic,x+Math.sin(a)*.12,1.62+(k%3)*.10,-4.70+Math.cos(a)*.14,
358
- .38,.60,.035,[.07,.40,.20,1],a*.65,Math.PI/2+spread);
474
+ this.add(this.organic,x+Math.sin(a)*.14,1.82+(k%3)*.12,-4.70+Math.cos(a)*.16,
475
+ .44,.72,.035,[.16,.64,.23,1],a*.65,Math.PI/2+spread);
359
476
  }
360
477
  }
361
478
  // Low-opacity contact shadows share the same 8+6 workstation positions.
362
479
  for(const desk of this.deskPositions){
363
480
  const {x,z,width}=desk;
364
- this.add(s,x+.07,-.033,z+.04,width*.80,.012,.92,[.055,.09,.14,.30]);
365
- this.add(s,x,-.032,z+1.00,.94,.010,.64,[.065,.11,.17,.24]);
481
+ // Place the soft shadow just above the coplanar hex grout so depth testing can
482
+ // blend the contact shade without z-fighting or the grout rejecting it.
483
+ this.add(this.shadow,x,-.029,z+.02,width*.78,.009,.88,[.20,.29,.39,.90]);
484
+ this.add(this.shadow,x+.02,-.028,z+1.0,.84,.009,.54,[.20,.29,.39,.76]);
485
+ for(const dx of [-.36,.36])for(const dz of [-.39,.38]){
486
+ this.add(this.shadow,x+width*dx,-.028,z+dz,.39,.008,.38,[.20,.29,.39,.66]);
487
+ this.add(this.shadow,x+width*dx,.462,z+dz,.40,.009,.40,[.20,.29,.39,.36]);
488
+ }
366
489
  }
367
490
  };
368
491
  Scene.prototype.buildBatches=function(){
369
- const channels={opaque:{positions:[],normals:[],colors:[]},transparent:{positions:[],normals:[],colors:[]}};
492
+ const channels={opaque:{positions:[],normals:[],colors:[]},transparent:{positions:[],normals:[],colors:[],triangles:[]}};
370
493
  const emit=(target,p,n,color)=>{
371
494
  target.positions.push(p[0],p[1],p[2]);target.normals.push(n[0],n[1],n[2]);
372
495
  target.colors.push(color[0],color[1],color[2],color[3]==null?1:color[3]);
@@ -382,19 +505,30 @@ window.Hive3D = (function () {
382
505
  const world=local.map(v=>worldPoint(o,v));
383
506
  const smooth=mesh===this.sphere||mesh.smooth;
384
507
  const face=smooth?null:faceNormal(world[0],world[1],world[2]);
508
+ const triangle=[];
385
509
  for(let k=0;k<3;k++){
386
510
  const ix3=ix[i+k]*3,normal=[normals[ix3],normals[ix3+1],normals[ix3+2]];
387
- emit(target,world[k],face||smoothNormal(o,normal),o.color);
511
+ const color=mesh.colors?mesh.colors.slice(ix[i+k]*4,ix[i+k]*4+4):o.color;
512
+ if(mesh.colors)color[3]*=o.color[3]==null?1:o.color[3];
513
+ const vertex={position:world[k],normal:face||smoothNormal(o,normal),color};
514
+ if(target===channels.transparent)triangle.push(vertex);else emit(target,vertex.position,vertex.normal,vertex.color);
388
515
  }
516
+ if(triangle.length)channels.transparent.triangles.push({vertices:triangle,depth:triangle.reduce((sum,v)=>sum+v.position[0]*this.eyeDirection[0]+v.position[1]*this.eyeDirection[1]+v.position[2]*this.eyeDirection[2],0)/3});
389
517
  }
390
518
  }
519
+ // Transparent objects retain depth testing but disable depth writes. Keep their
520
+ // triangles so the packed draw batch can be depth-sorted again when the camera moves.
521
+ this.transparentTriangles=channels.transparent.triangles;
522
+ for(const triangle of this.transparentTriangles)triangle.depth=triangle.vertices.reduce((sum,v)=>sum+dot(v.position,this.eyeDirection),0)/3;
523
+ this.transparentTriangles.sort((a,b)=>a.depth-b.depth);
524
+ for(const triangle of this.transparentTriangles)for(const vertex of triangle.vertices)emit(channels.transparent,vertex.position,vertex.normal,vertex.color);
391
525
  const gl=this.gl,upload=data=>{if(!data.length)return null;const buffer=gl.createBuffer();gl.bindBuffer(gl.ARRAY_BUFFER,buffer);gl.bufferData(gl.ARRAY_BUFFER,new Float32Array(data),gl.STATIC_DRAW);return buffer;};
392
526
  this.batchBuffers={};
393
527
  for(const key of ["opaque","transparent"]){
394
528
  const channel=channels[key];
395
529
  this.batchBuffers[key]={positions:upload(channel.positions),normals:upload(channel.normals),colors:upload(channel.colors),count:channel.positions.length/3};
396
530
  }
397
- for(const mesh of [this.box,this.roundBox,this.sphere,this.cylinder,this.organic,this.grout,this.bandGoldTop,this.bandGoldBottom,this.bandSeparator])if(mesh){if(mesh.p)gl.deleteBuffer(mesh.p);if(mesh.ix)gl.deleteBuffer(mesh.ix);mesh.p=null;mesh.ix=null;}
531
+ for(const mesh of [this.box,this.roundBox,this.sphere,this.cylinder,this.organic,this.wing,this.grout,this.shadow,this.bandGoldTop,this.bandGoldBottom,this.bandSeparator])if(mesh){if(mesh.p)gl.deleteBuffer(mesh.p);if(mesh.ix)gl.deleteBuffer(mesh.ix);mesh.p=null;mesh.ix=null;}
398
532
  };
399
533
  Scene.prototype.drawBatch=function(batch){
400
534
  if(!batch||!batch.count)return;
@@ -404,11 +538,19 @@ window.Hive3D = (function () {
404
538
  gl.bindBuffer(gl.ARRAY_BUFFER,batch.colors);gl.enableVertexAttribArray(this.aColor);gl.vertexAttribPointer(this.aColor,4,gl.FLOAT,false,0,0);
405
539
  gl.drawArrays(gl.TRIANGLES,0,batch.count);
406
540
  };
541
+ Scene.prototype.sortTransparentBatch=function(direction){
542
+ if(!this.transparentTriangles||!this.batchBuffers||!this.batchBuffers.transparent)return;
543
+ for(const triangle of this.transparentTriangles)triangle.depth=triangle.vertices.reduce((sum,v)=>sum+dot(v.position,direction),0)/3;
544
+ this.transparentTriangles.sort((a,b)=>a.depth-b.depth);
545
+ const positions=[],normals=[],colors=[];
546
+ for(const triangle of this.transparentTriangles)for(const vertex of triangle.vertices){positions.push(...vertex.position);normals.push(...vertex.normal);colors.push(...vertex.color);}
547
+ const gl=this.gl,batch=this.batchBuffers.transparent;gl.bindBuffer(gl.ARRAY_BUFFER,batch.positions);gl.bufferData(gl.ARRAY_BUFFER,new Float32Array(positions),gl.DYNAMIC_DRAW);gl.bindBuffer(gl.ARRAY_BUFFER,batch.normals);gl.bufferData(gl.ARRAY_BUFFER,new Float32Array(normals),gl.DYNAMIC_DRAW);gl.bindBuffer(gl.ARRAY_BUFFER,batch.colors);gl.bufferData(gl.ARRAY_BUFFER,new Float32Array(colors),gl.DYNAMIC_DRAW);
548
+ };
407
549
  Scene.prototype.render=function(){
408
550
  if(!this.gl||!this.active||this.displayMode!=="live3d")return;const gl=this.gl;const w=Math.max(1,this.cssW),h=Math.max(1,this.cssH);const dpr=Math.min(window.devicePixelRatio||1,1.6);
409
551
  const bw=Math.floor(w*dpr),bh=Math.floor(h*dpr);if(this.canvas.width!==bw||this.canvas.height!==bh){this.canvas.width=bw;this.canvas.height=bh;}
410
552
  gl.viewport(0,0,bw,bh);gl.clear(gl.COLOR_BUFFER_BIT|gl.DEPTH_BUFFER_BIT);gl.useProgram(this.program);
411
- const radius=27.0/this.zoom;const eye=[Math.sin(this.yaw)*radius,3.1+Math.sin(this.pitch)*radius*.58,Math.cos(this.yaw)*radius-1.3];const view=mat4.lookAt(eye,[0,.62,0],[0,1,0]);const proj=mat4.perspective(.40,w/h,.1,70);const vp=mat4.multiply(mat4.multiply(proj,view),mat4.translate(this.panX,this.panY,0));this.viewProj=vp;
553
+ const radius=25.4/this.zoom;const eye=[Math.sin(this.yaw)*radius,3.1+Math.sin(this.pitch)*radius*.52,Math.cos(this.yaw)*radius-1.3];const view=mat4.lookAt(eye,[0,.62,0],[0,1,0]);const proj=mat4.perspective(.46,w/h,.1,80);const vp=mat4.multiply(mat4.multiply(proj,view),mat4.translate(this.panX,this.panY,0));this.viewProj=vp;this.eyeDirection=norm(sub(eye,[0,.62,0]));this.sortTransparentBatch(this.eyeDirection);
412
554
  gl.uniformMatrix4fv(this.uViewProj,false,vp);gl.uniform3f(this.uEyePosition,eye[0],eye[1],eye[2]);
413
555
  this.drawBatch(this.batchBuffers&&this.batchBuffers.opaque);
414
556
  const translucent=this.batchBuffers&&this.batchBuffers.transparent;
@@ -497,14 +639,14 @@ window.Hive3D = (function () {
497
639
  Scene.prototype.setActive=function(active){this.active=!!active;if(this.timer)clearInterval(this.timer);if(this.raf)cancelAnimationFrame(this.raf);this.raf=0;this.layer.hidden=!this.active;this.canvas.hidden=!this.active||this.displayMode!=="live3d";if(this.active){this.updateOverlay();if(this.displayMode==="live3d")this.render();else if(this.displayMode==="reference")this.layoutOverlayPositions();this.timer=setInterval(()=>{if(!document.hidden&&this.host.offsetParent!==null)this.updateOverlay();},1000);}};
498
640
  Scene.prototype.resize=function(){this.cssW=this.host.clientWidth;this.cssH=this.host.clientHeight;if(!this.cssW||!this.cssH)return;if(this.displayMode==="live3d")this.render();else if(this.displayMode==="reference")this.layoutOverlayPositions();};
499
641
  Scene.prototype.zoomAt=function(factor){this.zoom=clamp(this.zoom/factor,.72,2.4);this.applyArtboardTransform();this.render();};
500
- Scene.prototype.resetView=function(){this.zoom=1;this.yaw=0;this.pitch=.42;this.panX=0;this.panY=0;this.applyArtboardTransform();this.render();};
501
- Scene.prototype.setDisplayMode=function(mode){const next=mode==="live3d"?"live3d":mode==="2d"?"2d":"reference";const changed=next!==this.displayMode;this.displayMode=next;if(changed){this.zoom=1;this.yaw=0;this.pitch=.42;this.panX=0;this.panY=0;}this.canvas.hidden=!this.active||this.displayMode!=="live3d";this.updateOverlay();this.applyArtboardTransform();if(this.displayMode==="live3d")this.render();else if(this.displayMode==="reference")this.layoutOverlayPositions();};
642
+ Scene.prototype.resetView=function(){this.zoom=1;this.yaw=0;this.pitch=.48;this.panX=0;this.panY=0;this.applyArtboardTransform();this.render();};
643
+ Scene.prototype.setDisplayMode=function(mode){const next=mode==="live3d"?"live3d":mode==="2d"?"2d":"reference";const changed=next!==this.displayMode;this.displayMode=next;if(changed){this.zoom=1;this.yaw=0;this.pitch=.48;this.panX=0;this.panY=0;}this.canvas.hidden=!this.active||this.displayMode!=="live3d";this.updateOverlay();this.applyArtboardTransform();if(this.displayMode==="live3d")this.render();else if(this.displayMode==="reference")this.layoutOverlayPositions();};
502
644
  Scene.prototype.applyArtboardTransform=function(){if(!this.artboard)return;this.artboard.style.transform=this.displayMode==="reference"?"translate3d("+(this.panX*100)+"%,"+(this.panY*100)+"%,0) scale("+this.zoom+")":"none";};
503
645
  Scene.prototype.info=function(){return{renderer:"webgl",cells:(this.cells||[]).length,screens:SCREENS.length,lanes:this.model?this.model.lanes.length:0,page:this.page,zoom:this.zoom,cssW:this.cssW,cssH:this.cssH,objects:this.objects?this.objects.length:0,deskRows:this.deskPositions?[this.deskPositions.filter(d=>d.row===0).length,this.deskPositions.filter(d=>d.row===1).length]:[]};};
504
646
  Scene.prototype.projectCell=function(rel){const i=this.screenMeta.findIndex(c=>c.rel===rel),desk=this.deskPositions&&this.deskPositions[i];if(i<0||!desk)return null;const p=this.projectWorld(desk.x,desk.row===1?1.38:1.205,desk.z-.418);return p&&p.visible?{x:p.x,y:p.y}:null;};
505
- Scene.prototype.dispose=function(){this.setActive(false);if(this.resizeObserver)this.resizeObserver.disconnect();if(this.languageObserver)this.languageObserver.disconnect();this.listeners.forEach(fn=>fn());if(this.pager)this.pager.remove();if(this.gl){const gl=this.gl;if(this.batchBuffers)for(const pass of Object.values(this.batchBuffers))for(const key of ["positions","normals","colors"])if(pass&&pass[key])gl.deleteBuffer(pass[key]);for(const o of [this.box,this.roundBox,this.sphere])if(o){if(o.p)gl.deleteBuffer(o.p);if(o.ix)gl.deleteBuffer(o.ix);}if(this.program)gl.deleteProgram(this.program);}if(window.__hive3d===this)delete window.__hive3d;};
647
+ Scene.prototype.dispose=function(){this.setActive(false);if(this.resizeObserver)this.resizeObserver.disconnect();if(this.languageObserver)this.languageObserver.disconnect();this.listeners.forEach(fn=>fn());if(this.pager)this.pager.remove();if(this.gl){const gl=this.gl;if(this.batchBuffers)for(const pass of Object.values(this.batchBuffers))for(const key of ["positions","normals","colors"])if(pass&&pass[key])gl.deleteBuffer(pass[key]);for(const o of [this.box,this.roundBox,this.sphere,this.cylinder,this.organic,this.wing,this.grout,this.shadow,this.bandGoldTop,this.bandGoldBottom,this.bandSeparator])if(o){if(o.p)gl.deleteBuffer(o.p);if(o.ix)gl.deleteBuffer(o.ix);}if(this.program)gl.deleteProgram(this.program);}if(window.__hive3d===this)delete window.__hive3d;};
506
648
  const originalCreate=(opts)=>new Scene(opts);
507
649
  // Initialize geometry after the constructor has successfully acquired WebGL.
508
650
  const create=(opts)=>{const scene=originalCreate(opts);if(scene.failed||!scene.gl)return null;scene.buildWorld();scene.buildBatches();scene.render();return scene;};
509
651
  return {create};
510
- })();
652
+ })();