video-generation-mcp 0.1.0__tar.gz

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.
@@ -0,0 +1,6 @@
1
+ dist/
2
+ build/
3
+ *.egg-info/
4
+ __pycache__/
5
+ *.py[cod]
6
+ .DS_Store
@@ -0,0 +1,55 @@
1
+ Metadata-Version: 2.5
2
+ Name: video-generation-mcp
3
+ Version: 0.1.0
4
+ Summary: MCP server for automated video generation and tech reels
5
+ Author-email: salinda hulangamuwa <salindahulangamuwa3@gmail.com>
6
+ Requires-Python: >=3.10
7
+ Requires-Dist: fastmcp<3,>=2.10
8
+ Requires-Dist: httpx>=0.27
9
+ Requires-Dist: numpy>=1.26
10
+ Requires-Dist: pillow>=10
11
+ Requires-Dist: playwright>=1.40.0
12
+ Requires-Dist: pocketsphinx>=5.1
13
+ Description-Content-Type: text/markdown
14
+
15
+ # bitxbyte-studio MCP server
16
+
17
+ Makes BitXbyte "tech explained" reels end to end: script → Gemini voice → line timings → 3D (three.js) or 2D (phone-mockup) visuals → stills check → background render → music mix → final mp4.
18
+ Lives in `BitXbyteStudio/studio-mcp/` and works on the folders in `BitXbyteStudio/reels/`. Posting stays with the facebook MCP (`create_reel`).
19
+
20
+ ## Tools
21
+ | Tool | Does |
22
+ |---|---|
23
+ | `list_reels`, `reel_status` | What exists, what is done, the next step |
24
+ | `create_reel(slug, title, engine='3d', template='blank'|'360-camera')` | New reel folder with starter code |
25
+ | `write_script(reel, lines, title, caption, comment)` | Save narration + caption; warns about long lines / digits |
26
+ | `generate_voice(reel, voice='Iapetus')` | Gemini TTS → `voice.wav` (key from `BitXbyteStudio/.env`) |
27
+ | `align_voice(reel)`, `set_line_times(reel, times)` | When each line is spoken → `vo_times.json` (+ `3d/timing.js`) |
28
+ | `list_files`, `read_file`, `write_file` | Read/write reel code (`3d/*.js`, `scenes.js`, `reel.json`) |
29
+ | `render_stills(reel, times)` | Contact sheet image of chosen frames — look before rendering |
30
+ | `start_render`, `render_status`, `cancel_render` | Full render in the background (+ music/voice mix) |
31
+ | `finish_reel(reel)` | Re-mix music + voice onto the rendered video |
32
+ | `preview_video(reel)`, `compress_video(reel, max_mb)` | Check the final mp4; make a smaller copy |
33
+
34
+ ## 3D reel contract (files in `<reel>/3d/`, loaded in the order of `reel.json` → `files`)
35
+ - `world.js` — build the scene (`scene`, `R`, `ENV`, `cam` exist already).
36
+ - `pipeline.js` (optional) — `PANELS.<kind> = p => {...}` draws a panel inside its rectangle; `WARM.push(fn)` runs once before frame 0.
37
+ - `shots.js` — `const SHOTS=[{at:T(line, offset), f(lt){...}}]`; `RESET.push(fn)` clears per-frame state.
38
+ - Helpers: `look(pos, target, fov)`, `orbit(r, h, angle)`, `topLook(h)`, `W2S(point)`, `E()`/`O()` easing, `V(x,y,z)`,
39
+ overlay `ov.callout / tag / frame / ring / cross / line / rect / big / brand`. `S.mainVP` = 3D viewport, `S.panels` = extra views.
40
+ - `templates/360-camera` is the full worked example (simulated fisheye cameras, top-down mapping, stitching).
41
+
42
+ ## Install on the Mac (once)
43
+ 1. `brew install ffmpeg` (skip if `ffmpeg -version` works).
44
+ 2. Browser for rendering: `uv run --with playwright==1.56.0 playwright install chromium`
45
+ 3. Add to `~/Library/Application Support/Claude/claude_desktop_config.json` under `mcpServers`
46
+ (use the same `uv` path as your `bitxbyte-voice` entry):
47
+ ```json
48
+ "bitxbyte-studio": {
49
+ "command": "uv",
50
+ "args": ["run", "--script", "/Users/user/Movies/BitXbyteStudio/studio-mcp/server.py"]
51
+ }
52
+ ```
53
+ 4. Quit Claude (Cmd+Q) and reopen. First start downloads Python packages (~1 min).
54
+
55
+ Env options: `BX_GL=gpu|swiftshader` (default: GPU on Mac, falls back to software), `BX_STUDIO=<path>` (default: parent folder).
@@ -0,0 +1,41 @@
1
+ # bitxbyte-studio MCP server
2
+
3
+ Makes BitXbyte "tech explained" reels end to end: script → Gemini voice → line timings → 3D (three.js) or 2D (phone-mockup) visuals → stills check → background render → music mix → final mp4.
4
+ Lives in `BitXbyteStudio/studio-mcp/` and works on the folders in `BitXbyteStudio/reels/`. Posting stays with the facebook MCP (`create_reel`).
5
+
6
+ ## Tools
7
+ | Tool | Does |
8
+ |---|---|
9
+ | `list_reels`, `reel_status` | What exists, what is done, the next step |
10
+ | `create_reel(slug, title, engine='3d', template='blank'|'360-camera')` | New reel folder with starter code |
11
+ | `write_script(reel, lines, title, caption, comment)` | Save narration + caption; warns about long lines / digits |
12
+ | `generate_voice(reel, voice='Iapetus')` | Gemini TTS → `voice.wav` (key from `BitXbyteStudio/.env`) |
13
+ | `align_voice(reel)`, `set_line_times(reel, times)` | When each line is spoken → `vo_times.json` (+ `3d/timing.js`) |
14
+ | `list_files`, `read_file`, `write_file` | Read/write reel code (`3d/*.js`, `scenes.js`, `reel.json`) |
15
+ | `render_stills(reel, times)` | Contact sheet image of chosen frames — look before rendering |
16
+ | `start_render`, `render_status`, `cancel_render` | Full render in the background (+ music/voice mix) |
17
+ | `finish_reel(reel)` | Re-mix music + voice onto the rendered video |
18
+ | `preview_video(reel)`, `compress_video(reel, max_mb)` | Check the final mp4; make a smaller copy |
19
+
20
+ ## 3D reel contract (files in `<reel>/3d/`, loaded in the order of `reel.json` → `files`)
21
+ - `world.js` — build the scene (`scene`, `R`, `ENV`, `cam` exist already).
22
+ - `pipeline.js` (optional) — `PANELS.<kind> = p => {...}` draws a panel inside its rectangle; `WARM.push(fn)` runs once before frame 0.
23
+ - `shots.js` — `const SHOTS=[{at:T(line, offset), f(lt){...}}]`; `RESET.push(fn)` clears per-frame state.
24
+ - Helpers: `look(pos, target, fov)`, `orbit(r, h, angle)`, `topLook(h)`, `W2S(point)`, `E()`/`O()` easing, `V(x,y,z)`,
25
+ overlay `ov.callout / tag / frame / ring / cross / line / rect / big / brand`. `S.mainVP` = 3D viewport, `S.panels` = extra views.
26
+ - `templates/360-camera` is the full worked example (simulated fisheye cameras, top-down mapping, stitching).
27
+
28
+ ## Install on the Mac (once)
29
+ 1. `brew install ffmpeg` (skip if `ffmpeg -version` works).
30
+ 2. Browser for rendering: `uv run --with playwright==1.56.0 playwright install chromium`
31
+ 3. Add to `~/Library/Application Support/Claude/claude_desktop_config.json` under `mcpServers`
32
+ (use the same `uv` path as your `bitxbyte-voice` entry):
33
+ ```json
34
+ "bitxbyte-studio": {
35
+ "command": "uv",
36
+ "args": ["run", "--script", "/Users/user/Movies/BitXbyteStudio/studio-mcp/server.py"]
37
+ }
38
+ ```
39
+ 4. Quit Claude (Cmd+Q) and reopen. First start downloads Python packages (~1 min).
40
+
41
+ Env options: `BX_GL=gpu|swiftshader` (default: GPU on Mac, falls back to software), `BX_STUDIO=<path>` (default: parent folder).
@@ -0,0 +1,3 @@
1
+ window.__render=render; window.__END=END;
2
+ window.__gl=(()=>{try{const g=R.getContext(); const d=g.getExtension('WEBGL_debug_renderer_info'); return d?g.getParameter(d.UNMASKED_RENDERER_WEBGL):g.getParameter(g.RENDERER);}catch(e){return 'unknown'}})();
3
+ document.fonts.ready.then(()=>{ WARM.forEach(f=>f()); render(Math.min(0.5,END)); window.__ready=true; });
@@ -0,0 +1,73 @@
1
+ /* ======================= BitXbyte 3D reel engine — core =======================
2
+ Contract for a reel's own files (loaded after this, in order):
3
+ world.js builds the scene (uses scene, R, ENV, cam)
4
+ pipeline.js optional: PANELS.<kind> = p => {...} draws a panel inside its rect; WARM.push(fn) runs once before frame 0
5
+ shots.js const SHOTS=[{at:T(line,offset), f(lt){...}}, ...]; RESET.push(fn) clears per-frame state
6
+ timing.js (generated) defines VO=[[start,end],...] per narration line and END (seconds).
7
+ Every frame: S={mainVP,panels}, RESET hooks, active shot f(lt), 3D render into S.mainVP, panels, SVG overlay. */
8
+ const clamp=(x,a=0,b=1)=>Math.max(a,Math.min(b,x)); const lerp=(a,b,k)=>a+(b-a)*k;
9
+ const ioSine=k=>-(Math.cos(Math.PI*k)-1)/2; const oCubic=k=>1-Math.pow(1-k,3); const oBack=k=>{const c1=1.70158,c3=c1+1; return 1+c3*Math.pow(k-1,3)+c1*Math.pow(k-1,2);};
10
+ const WHT='#F4F6FA', GRY='#8A90A0', AMB='#FFB547', ONE='#3DD68C', ZERO='#FF5A5F', BTB='#4C8DFF', AIC='#B48CFF';
11
+ const QS=new URLSearchParams(location.search);
12
+ const W=1080, H=1920;
13
+ const canvas=document.getElementById('c');
14
+ const R=new THREE.WebGLRenderer({canvas,antialias:true,preserveDrawingBuffer:true});
15
+ R.setPixelRatio(parseFloat(QS.get('pr')||'0.72')); R.setSize(W,H,false);
16
+ R.toneMapping=THREE.ACESFilmicToneMapping; R.toneMappingExposure=1.05; R.outputColorSpace=THREE.SRGBColorSpace;
17
+ R.shadowMap.enabled=true; R.shadowMap.type=THREE.PCFSoftShadowMap; R.shadowMap.autoUpdate=false; R.shadowMap.needsUpdate=true;
18
+ R.autoClear=false;
19
+ let BG=0x07080b;
20
+ const scene=new THREE.Scene(); scene.background=new THREE.Color(BG); scene.fog=new THREE.Fog(BG,16,42);
21
+ const ENV=new THREE.PMREMGenerator(R).fromScene(new RoomEnvironment(),0.04).texture; scene.environment=ENV;
22
+ const cam=new THREE.PerspectiveCamera(35,W/H,0.05,200);
23
+ const V=(x,y,z)=>new THREE.Vector3(x,y,z);
24
+ const T=(i,o=0)=>VO[i][0]+o;
25
+ const E=(lt,a,len)=>ioSine(clamp((lt-a)/len)); const O=(lt,a,len=0.35)=>clamp((lt-a)/len);
26
+ const FULL={x:0,y:330,w:1080,h:1260};
27
+ const RESET=[], WARM=[], PANELS={};
28
+ let S={mainVP:[0,0,W,H],panels:[]};
29
+ function asp(){cam.aspect=S.mainVP?S.mainVP[2]/S.mainVP[3]:W/H;}
30
+ function look(p,t,fov=35){asp(); cam.position.copy(p); cam.up.set(0,1,0); cam.lookAt(t); cam.fov=fov; cam.updateProjectionMatrix(); cam.updateMatrixWorld();}
31
+ function topLook(h,rot=0,cx=0,cz=0,fov=35){asp(); cam.position.set(cx,h,cz); cam.up.set(Math.sin(rot),0,-Math.cos(rot)); cam.lookAt(cx,0,cz); cam.fov=fov; cam.updateProjectionMatrix(); cam.updateMatrixWorld();}
32
+ function orbit(rad,hgt,ang,tgt=V(0,0.5,0),fov=35){look(V(Math.sin(ang)*rad,hgt,Math.cos(ang)*rad),tgt,fov);}
33
+ function W2S(P){ const v=P.clone().project(cam); const [x,y,w,h]=S.mainVP; return [x+(v.x+1)/2*w, y+(1-v.y)/2*h]; }
34
+ function setOp(obj,o){obj.visible=o>0.001; obj.traverse(m=>{if(m.material){m.material.transparent=true; m.material.opacity=o;}});}
35
+
36
+ /* ---------- SVG overlay (callouts, tags, frames) ---------- */
37
+ const svg=document.getElementById('ov');
38
+ const esc=s=>String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;');
39
+ const FOOTNOTE=QS.get('foot')??'Simulated · illustration';
40
+ const ov={buf:[],
41
+ begin(){this.buf=[]; this.foot=true;},
42
+ end(){ if(this.foot) this.buf.push(`<text x="40" y="1800" font-family="Inter" font-weight="600" font-size="22" fill="#5d6270">${esc(FOOTNOTE)}</text>`); svg.innerHTML=this.buf.join('');},
43
+ g(o,s){ if(o>0.001) this.buf.push(`<g opacity="${Math.min(1,o).toFixed(3)}">${s}</g>`); },
44
+ frame(p,label){ this.buf.push(`<rect x="${p.x+1}" y="${p.y+1}" width="${p.w-2}" height="${p.h-2}" fill="none" stroke="rgba(255,255,255,.16)" stroke-width="2"/>`);
45
+ const w=label.length*15+40; this.buf.push(`<rect x="${p.x+30}" y="${p.y+26}" width="${w}" height="44" rx="10" fill="rgba(0,0,0,.6)"/><circle cx="${p.x+52}" cy="${p.y+48}" r="7" fill="${ZERO}"/><text x="${p.x+68}" y="${p.y+56}" font-family="JetBrains Mono" font-weight="700" font-size="22" fill="#e9ecf2" letter-spacing="1">${esc(label)}</text>`);},
46
+ tag(x,y,t,col,o){ const w=t.length*20+56; this.g(o,`<rect x="${x-w/2}" y="${y-44}" width="${w}" height="64" rx="32" fill="rgba(8,9,12,.72)" stroke="${col}" stroke-opacity=".45" stroke-width="2"/><text x="${x}" y="${y}" text-anchor="middle" font-family="Inter" font-weight="700" font-size="36" fill="${col}">${esc(t)}</text>`);},
47
+ big(x,y,t,col,o){ this.g(o,`<text x="${x}" y="${y}" text-anchor="middle" font-family="Inter" font-weight="800" font-size="96" fill="${col}" style="filter:drop-shadow(0 6px 18px rgba(0,0,0,.8))">${esc(t)}</text>`);},
48
+ text(x,y,t,size,col,o){ this.g(o,`<text x="${x}" y="${y}" text-anchor="middle" font-family="Inter" font-weight="800" font-size="${size}" fill="${col}">${esc(t)}</text>`);},
49
+ dot(x,y,col,o){ this.g(o,`<circle cx="${x}" cy="${y}" r="16" fill="${col}" opacity=".35"/><circle cx="${x}" cy="${y}" r="8" fill="${col}"/>`);},
50
+ ring(x,y,r,col,o){ this.g(o,`<circle cx="${x}" cy="${y}" r="${r}" fill="none" stroke="${col}" stroke-width="4" stroke-dasharray="12 9"/>`);},
51
+ cross(x,y,s,col,o){ this.g(o,`<path d="M ${x-s} ${y-s} L ${x+s} ${y+s} M ${x+s} ${y-s} L ${x-s} ${y+s}" stroke="${col}" stroke-width="10" stroke-linecap="round"/>`);},
52
+ line(x1,y1,x2,y2,col,o,w=4,dash){ this.g(o,`<line x1="${x1}" y1="${y1}" x2="${x2}" y2="${y2}" stroke="${col}" stroke-width="${w}" stroke-linecap="round" ${dash?'stroke-dasharray="14 10"':''}/>`);},
53
+ rect(x,y,w,h,col,o){ this.g(o,`<rect x="${x}" y="${y}" width="${w}" height="${h}" rx="10" fill="none" stroke="${col}" stroke-width="5" stroke-dasharray="16 10"/>`);},
54
+ callout(ax,ay,lx,ly,t,sub,col,o){ const right=lx>=ax; lx=Math.max(60,Math.min(1020,lx)); const tw=Math.max(t.length*19,(sub||'').length*14);
55
+ let tx=right?lx+14:lx-14; if(right&&tx+tw>1050) tx=1050-tw; if(!right&&tx-tw<30) tx=30+tw; const an=right?'start':'end';
56
+ this.g(o,`<circle cx="${ax}" cy="${ay}" r="7" fill="${col}"/><circle cx="${ax}" cy="${ay}" r="15" fill="none" stroke="${col}" stroke-width="2" opacity=".7"/>
57
+ <polyline points="${ax},${ay} ${lx},${ly} ${right?lx+8:lx-8},${ly}" fill="none" stroke="${col}" stroke-width="2.5"/>
58
+ <text x="${tx}" y="${ly+12}" text-anchor="${an}" font-family="Inter" font-weight="700" font-size="34" fill="#F4F6FA" style="filter:drop-shadow(0 2px 6px rgba(0,0,0,.9))">${esc(t)}</text>
59
+ ${sub?`<text x="${tx}" y="${ly+50}" text-anchor="${an}" font-family="Inter" font-weight="600" font-size="25" fill="${col}" style="filter:drop-shadow(0 2px 6px rgba(0,0,0,.9))">${esc(sub)}</text>`:''}`);},
60
+ lut(lt,o){ let rows=''; const k=Math.floor(Math.max(0,lt-2.6)*10);
61
+ for(let i=0;i<6;i++){const n=k+i; rows+=`<text x="96" y="${1330+i*46}" font-family="JetBrains Mono" font-weight="500" font-size="25" fill="${i==0?AMB:'#cfd5e0'}">pixel (${(n*37+211)%1280}, ${(n*53+97)%800}) → ground (${((n*13)%80/10-4).toFixed(1)} m, ${(-2.6-(n*7)%60/10).toFixed(1)} m)</text>`;}
62
+ this.g(o,`<rect x="60" y="1230" width="960" height="370" rx="22" fill="rgba(10,12,16,.82)" stroke="rgba(255,255,255,.18)" stroke-width="2"/><text x="96" y="1280" font-family="JetBrains Mono" font-weight="700" font-size="22" fill="#8A90A0" letter-spacing="2">LOOKUP TABLE · WORKED OUT ONCE</text>${rows}`);},
63
+ brand(o){ this.foot=false; this.g(o,`<rect x="140" y="1430" width="800" height="230" rx="30" fill="rgba(8,9,12,.78)"/><text x="540" y="1545" text-anchor="middle" font-family="Inter" font-weight="800" font-size="92" fill="#F4F6FA">BitXbyte</text><text x="540" y="1610" text-anchor="middle" font-family="Inter" font-weight="600" font-size="32" fill="#8A90A0">follow for more · explained from scratch</text>`);},
64
+ };
65
+
66
+ function render(t){
67
+ S={mainVP:[0,0,W,H],panels:[]}; RESET.forEach(f=>f());
68
+ let sh=SHOTS[0]; for(const s of SHOTS) if(t>=s.at) sh=s; ov.begin(); sh.f(t-sh.at);
69
+ R.setRenderTarget(null); R.setScissorTest(false); R.setViewport(0,0,W,H); R.setClearColor(BG,1); R.clear();
70
+ if(S.mainVP){ const [x,y,w,h]=S.mainVP; asp(); cam.updateProjectionMatrix(); R.setScissorTest(true); R.setScissor(x,H-y-h,w,h); R.setViewport(x,H-y-h,w,h); R.render(scene,cam); R.setScissorTest(false); R.setViewport(0,0,W,H);}
71
+ S.panels.forEach(p=>{ const fn=PANELS[p.k]; if(!fn) return; const y0=H-p.y-p.h; R.setScissorTest(true); R.setScissor(p.x,y0,p.w,p.h); R.setViewport(p.x,y0,p.w,p.h); R.setClearColor(0x000000,1); R.clear(); fn(p); R.setRenderTarget(null); R.setScissorTest(false); R.setViewport(0,0,W,H); });
72
+ ov.end();
73
+ }
@@ -0,0 +1,140 @@
1
+ /**
2
+ * https://github.com/google/model-viewer/blob/master/packages/model-viewer/src/three-components/EnvironmentScene.ts
3
+ */
4
+
5
+ const {BackSide,BoxGeometry,Mesh,MeshBasicMaterial,MeshStandardMaterial,PointLight,Scene}=THREE;
6
+
7
+ class RoomEnvironment extends Scene {
8
+
9
+ constructor( renderer = null ) {
10
+
11
+ super();
12
+
13
+ const geometry = new BoxGeometry();
14
+ geometry.deleteAttribute( 'uv' );
15
+
16
+ const roomMaterial = new MeshStandardMaterial( { side: BackSide } );
17
+ const boxMaterial = new MeshStandardMaterial();
18
+
19
+ let intensity = 5;
20
+
21
+ if ( renderer !== null && renderer._useLegacyLights === false ) intensity = 900;
22
+
23
+ const mainLight = new PointLight( 0xffffff, intensity, 28, 2 );
24
+ mainLight.position.set( 0.418, 16.199, 0.300 );
25
+ this.add( mainLight );
26
+
27
+ const room = new Mesh( geometry, roomMaterial );
28
+ room.position.set( - 0.757, 13.219, 0.717 );
29
+ room.scale.set( 31.713, 28.305, 28.591 );
30
+ this.add( room );
31
+
32
+ const box1 = new Mesh( geometry, boxMaterial );
33
+ box1.position.set( - 10.906, 2.009, 1.846 );
34
+ box1.rotation.set( 0, - 0.195, 0 );
35
+ box1.scale.set( 2.328, 7.905, 4.651 );
36
+ this.add( box1 );
37
+
38
+ const box2 = new Mesh( geometry, boxMaterial );
39
+ box2.position.set( - 5.607, - 0.754, - 0.758 );
40
+ box2.rotation.set( 0, 0.994, 0 );
41
+ box2.scale.set( 1.970, 1.534, 3.955 );
42
+ this.add( box2 );
43
+
44
+ const box3 = new Mesh( geometry, boxMaterial );
45
+ box3.position.set( 6.167, 0.857, 7.803 );
46
+ box3.rotation.set( 0, 0.561, 0 );
47
+ box3.scale.set( 3.927, 6.285, 3.687 );
48
+ this.add( box3 );
49
+
50
+ const box4 = new Mesh( geometry, boxMaterial );
51
+ box4.position.set( - 2.017, 0.018, 6.124 );
52
+ box4.rotation.set( 0, 0.333, 0 );
53
+ box4.scale.set( 2.002, 4.566, 2.064 );
54
+ this.add( box4 );
55
+
56
+ const box5 = new Mesh( geometry, boxMaterial );
57
+ box5.position.set( 2.291, - 0.756, - 2.621 );
58
+ box5.rotation.set( 0, - 0.286, 0 );
59
+ box5.scale.set( 1.546, 1.552, 1.496 );
60
+ this.add( box5 );
61
+
62
+ const box6 = new Mesh( geometry, boxMaterial );
63
+ box6.position.set( - 2.193, - 0.369, - 5.547 );
64
+ box6.rotation.set( 0, 0.516, 0 );
65
+ box6.scale.set( 3.875, 3.487, 2.986 );
66
+ this.add( box6 );
67
+
68
+
69
+ // -x right
70
+ const light1 = new Mesh( geometry, createAreaLightMaterial( 50 ) );
71
+ light1.position.set( - 16.116, 14.37, 8.208 );
72
+ light1.scale.set( 0.1, 2.428, 2.739 );
73
+ this.add( light1 );
74
+
75
+ // -x left
76
+ const light2 = new Mesh( geometry, createAreaLightMaterial( 50 ) );
77
+ light2.position.set( - 16.109, 18.021, - 8.207 );
78
+ light2.scale.set( 0.1, 2.425, 2.751 );
79
+ this.add( light2 );
80
+
81
+ // +x
82
+ const light3 = new Mesh( geometry, createAreaLightMaterial( 17 ) );
83
+ light3.position.set( 14.904, 12.198, - 1.832 );
84
+ light3.scale.set( 0.15, 4.265, 6.331 );
85
+ this.add( light3 );
86
+
87
+ // +z
88
+ const light4 = new Mesh( geometry, createAreaLightMaterial( 43 ) );
89
+ light4.position.set( - 0.462, 8.89, 14.520 );
90
+ light4.scale.set( 4.38, 5.441, 0.088 );
91
+ this.add( light4 );
92
+
93
+ // -z
94
+ const light5 = new Mesh( geometry, createAreaLightMaterial( 20 ) );
95
+ light5.position.set( 3.235, 11.486, - 12.541 );
96
+ light5.scale.set( 2.5, 2.0, 0.1 );
97
+ this.add( light5 );
98
+
99
+ // +y
100
+ const light6 = new Mesh( geometry, createAreaLightMaterial( 100 ) );
101
+ light6.position.set( 0.0, 20.0, 0.0 );
102
+ light6.scale.set( 1.0, 0.1, 1.0 );
103
+ this.add( light6 );
104
+
105
+ }
106
+
107
+ dispose() {
108
+
109
+ const resources = new Set();
110
+
111
+ this.traverse( ( object ) => {
112
+
113
+ if ( object.isMesh ) {
114
+
115
+ resources.add( object.geometry );
116
+ resources.add( object.material );
117
+
118
+ }
119
+
120
+ } );
121
+
122
+ for ( const resource of resources ) {
123
+
124
+ resource.dispose();
125
+
126
+ }
127
+
128
+ }
129
+
130
+ }
131
+
132
+ function createAreaLightMaterial( intensity ) {
133
+
134
+ const material = new MeshBasicMaterial();
135
+ material.color.setScalar( intensity );
136
+ return material;
137
+
138
+ }
139
+
140
+ window.RoomEnvironment=RoomEnvironment;