Memuat kantor 3D…

🏢 Kantor 3D — AI Agent

Wowo · Owi · Puancan · Fufu · Megacan — klik karakter untuk info

✕

—

—
Status—
Model—
Dashboard—
Skill—
Drag: putar · Scroll: zoom · Klik karakter: info
/* ============ BAHAN ============ */ const M = { floor: new THREE.MeshStandardMaterial({ color:0x8a5a33, roughness:.7 }), floorD: new THREE.MeshStandardMaterial({ color:0x744a28, roughness:.7 }), wall: new THREE.MeshStandardMaterial({ color:0x2a3444, roughness:.9 }), glass: new THREE.MeshPhysicalMaterial({ color:0x9fc4e8, transparent:true, opacity:.18, roughness:.1, metalness:.1 }), desk: new THREE.MeshStandardMaterial({ color:0x4a3524, roughness:.6 }), deskTop:new THREE.MeshStandardMaterial({ color:0x5d4229, roughness:.5 }), metal: new THREE.MeshStandardMaterial({ color:0x3a3f4a, roughness:.4, metalness:.6 }), screen: new THREE.MeshStandardMaterial({ color:0x0a0f1a, emissive:0x3b82f6, emissiveIntensity:.9 }), screenG:new THREE.MeshStandardMaterial({ color:0x0a0f1a, emissive:0x22c55e, emissiveIntensity:.9 }), plant: new THREE.MeshStandardMaterial({ color:0x2d7a3d, roughness:.8 }), pot: new THREE.MeshStandardMaterial({ color:0xb45309, roughness:.8 }), sofa: new THREE.MeshStandardMaterial({ color:0x7c3aed, roughness:.85 }), rug: new THREE.MeshStandardMaterial({ color:0x1e293b, roughness:.95 }), skin: [0xf5c99b, 0xe8b07d, 0xd99a63, 0xc98a5a, 0xb87a4e].map(c=>new THREE.MeshStandardMaterial({color:c,roughness:.6})), hair: [0x1a1a1a, 0x3a2a1a, 0x111111, 0x4a3320, 0x222222].map(c=>new THREE.MeshStandardMaterial({color:c,roughness:.85})), }; function box(w,h,d,mat,x,y,z,shadow=true){ const m=new THREE.Mesh(new THREE.BoxGeometry(w,h,d),mat); m.position.set(x,y,z); m.castShadow=shadow; m.receiveShadow=true; scene.add(m); return m; } function cyl(rt,rb,h,mat,x,y,z,seg=12){ const m=new THREE.Mesh(new THREE.CylinderGeometry(rt,rb,h,seg),mat); m.position.set(x,y,z); m.castShadow=true; m.receiveShadow=true; scene.add(m); return m; } /* ============ GEDUNG KANTOR ============ */ const W=22, D=16; // lantai kayu pola for(let i=0;i{ const g = new THREE.Mesh(new THREE.BoxGeometry(w,3.4,d), glassMat); g.position.set(x,1.7,z); scene.add(g); box(w,.18,d,M.wall, x,.09,z); // kusen bawah box(w,.18,d,M.wall, x,3.4,z); // kusen atas }); // pilar sudut [[-W/2,-D/2],[W/2,-D/2],[-W/2,D/2],[W/2,D/2]].forEach(([x,z])=> box(.4,3.5,.4,M.wall,x,1.75,z)); // langit-langit rangka for(let i=-W/2;i<=W/2;i+=2.75) box(.12,.12,D,M.metal, i,3.55,0, false); // kota di luar (siluet gedung) for(let i=0;i<26;i++){ const h = 4+Math.random()*10, w = 1.5+Math.random()*2; const b = box(w,h,w, new THREE.MeshStandardMaterial({color:0x111827,roughness:1}), -30+Math.random()*60, h/2-1, -D/2-6-Math.random()*14, false); // jendela menyala if(Math.random()>.4){ const win = new THREE.Mesh(new THREE.PlaneGeometry(w*.7,h*.5), new THREE.MeshBasicMaterial({color:0xffd97a})); win.position.set(b.position.x, b.position.y, b.position.z+w/2+.01); scene.add(win); } } /* ============ FURNITUR ============ */ const desks = []; // {x,z,ry, occupiedBy} function makeDesk(x,z,ry=0,screenMat=M.screen){ const g = new THREE.Group(); g.position.set(x,0,z); g.rotation.y = ry; scene.add(g); const top = new THREE.Mesh(new THREE.BoxGeometry(2.2,.08,1), M.deskTop); top.position.y=.78; top.castShadow=top.receiveShadow=true; g.add(top); [[-1,-.42],[1,-.42],[-1,.42],[1,.42]].forEach(([lx,lz])=>{ const l=new THREE.Mesh(new THREE.BoxGeometry(.08,.78,.08),M.desk); l.position.set(lx,.39,lz); l.castShadow=true; g.add(l); }); // monitor const mon = new THREE.Mesh(new THREE.BoxGeometry(.9,.55,.06), M.metal); mon.position.set(0,1.15,-.2); mon.castShadow=true; g.add(mon); const scr = new THREE.Mesh(new THREE.PlaneGeometry(.82,.47), screenMat); scr.position.set(0,1.15,-.165); g.add(scr); // keyboard const kb = new THREE.Mesh(new THREE.BoxGeometry(.7,.03,.25), M.metal); kb.position.set(0,.84,.25); g.add(kb); // kursi const ch = new THREE.Group(); ch.position.set(0,0,.95); ch.rotation.y = Math.PI; g.add(ch); const seat = new THREE.Mesh(new THREE.BoxGeometry(.55,.08,.55), M.sofa); seat.position.y=.5; seat.castShadow=true; ch.add(seat); const back = new THREE.Mesh(new THREE.BoxGeometry(.55,.7,.08), M.sofa); back.position.set(0,.9,-.28); back.castShadow=true; ch.add(back); const pole = new THREE.Mesh(new THREE.CylinderGeometry(.04,.04,.5,8), M.metal); pole.position.y=.25; ch.add(pole); desks.push({x,z,ry,group:g,chair:ch,occupiedBy:null}); return g; } // 5 meja kerja makeDesk(-7,-4, .3); makeDesk(-2.5,-4.5, 0); makeDesk(2.5,-4.5, 0, M.screenG); makeDesk(7,-4, -.3); makeDesk(0,-1.5, Math.PI, M.screenG); // meja meeting (function(){ const g=new THREE.Group(); g.position.set(-4,0,4.5); scene.add(g); const t=new THREE.Mesh(new THREE.BoxGeometry(4,.1,1.8),M.deskTop); t.position.y=.75; t.castShadow=t.receiveShadow=true; g.add(t); [[-1.8,-.7],[1.8,-.7],[-1.8,.7],[1.8,.7]].forEach(([x,z])=>{const l=new THREE.Mesh(new THREE.BoxGeometry(.1,.75,.1),M.desk);l.position.set(x,.37,z);g.add(l);}); for(let i=0;i<4;i++){ const s=new THREE.Mesh(new THREE.BoxGeometry(.5,.08,.5),M.sofa); s.position.set(-1.5+i, .48, 1.3); s.castShadow=true; g.add(s); const s2=s.clone(); s2.position.z=-1.3; g.add(s2); } })(); // sofa + meja kopi (function(){ const s=new THREE.Mesh(new THREE.BoxGeometry(2.6,.7,1),M.sofa); s.position.set(6.5,.35,4.5); s.castShadow=s.receiveShadow=true; scene.add(s); const b=new THREE.Mesh(new THREE.BoxGeometry(2.6,.8,.25),M.sofa); b.position.set(6.5,.9,5); b.castShadow=true; scene.add(b); const t=cyl(.5,.5,.08,M.deskTop, 6.5,.45,2.8); cyl(.06,.06,.45,M.metal, 6.5,.22,2.8); })(); // tanaman [[-10,-7],[10,-7],[-10,7],[10,7],[-4.5,6.5]].forEach(([x,z])=>{ cyl(.28,.34,.5,M.pot, x,.25,z); cyl(.05,.07,1,M.desk, x,.9,z); const f=new THREE.Mesh(new THREE.ConeGeometry(.55,1.2,8),M.plant); f.position.set(x,1.7,z); f.castShadow=true; scene.add(f); }); // papan tulis box(3,1.6,.08, new THREE.MeshStandardMaterial({color:0xf8fafc,roughness:.4}), -9,1.8,1.5); /* ============ KARAKTER 3D (stylized ala film/game) ============ */ function textSprite(text, bg='#0b0e14', fg='#A3E635'){ const c = document.createElement('canvas'); c.width=256; c.height=72; const x = c.getContext('2d'); x.fillStyle=bg; x.beginPath(); x.roundRect(4,4,248,64,18); x.fill(); x.strokeStyle=fg; x.lineWidth=3; x.beginPath(); x.roundRect(4,4,248,64,18); x.stroke(); x.fillStyle=fg; x.font='bold 34px sans-serif'; x.textAlign='center'; x.textBaseline='middle'; x.fillText(text,128,38); const t = new THREE.CanvasTexture(c); const s = new THREE.Sprite(new THREE.SpriteMaterial({map:t, depthTest:false})); s.scale.set(1.5,.42,1); return s; } function bubbleSprite(){ // bubble chat QC const c=document.createElement('canvas'); c.width=128; c.height=128; const x=c.getContext('2d'); x.font='84px sans-serif'; x.textAlign='center'; x.textBaseline='middle'; const t=new THREE.CanvasTexture(c); const s=new THREE.Sprite(new THREE.SpriteMaterial({map:t,depthTest:false,transparent:true})); s.scale.set(.9,.9,1); s.visible=false; s.userData={canvas:c,ctx:x,tex:t}; return s; } function setBubble(s, emoji){ const {ctx,canvas,tex}=s.userData; ctx.clearRect(0,0,128,128); ctx.fillStyle='rgba(10,14,22,.9)'; ctx.beginPath(); ctx.roundRect(8,8,112,112,24); ctx.fill(); ctx.font='64px sans-serif'; ctx.textAlign='center'; ctx.textBaseline='middle'; ctx.fillText(emoji,64,68); tex.needsUpdate=true; s.visible=true; } const characters = []; function makeCharacter(t, idx){ const g = new THREE.Group(); scene.add(g); const skin = M.skin[idx%M.skin.length], hairM = M.hair[idx%M.hair.length]; const shirtM = new THREE.MeshStandardMaterial({color:t.shirt, roughness:.7}); const pantsM = new THREE.MeshStandardMaterial({color:t.pants, roughness:.7}); // kaki const legL = new THREE.Mesh(new THREE.CylinderGeometry(.09,.07,.8,10), pantsM); const legR = legL.clone(); legL.position.set(-.13,.4,0); legR.position.set(.13,.4,0); // badan (capsule) const torso = new THREE.Mesh(new THREE.CapsuleGeometry(.21,.5,6,12), shirtM); torso.position.y=1.05; // lengan (pivot di bahu) const armL = new THREE.Group(), armR = new THREE.Group(); armL.position.set(-.3,1.32,0); armR.position.set(.3,1.32,0); const armGeo = new THREE.CylinderGeometry(.06,.05,.62,8); armGeo.translate(0,-.31,0); const aL = new THREE.Mesh(armGeo, shirtM), aR = new THREE.Mesh(armGeo, shirtM); const hL = new THREE.Mesh(new THREE.SphereGeometry(.07,8,8), skin); hL.position.y=-.65; const hR = hL.clone(); aL.add(hL); aR.add(hR); armL.add(aL); armR.add(aR); // kepala const headG = new THREE.Group(); headG.position.y=1.62; const head = new THREE.Mesh(new THREE.SphereGeometry(.17,16,16), skin); headG.add(head); const hair = new THREE.Mesh(new THREE.SphereGeometry(.175,16,16,0,Math.PI*2,0,Math.PI*.55), hairM); hair.position.y=.02; headG.add(hair); // mata (gaya karakter) const eyeM = new THREE.MeshBasicMaterial({color:0x111111}); [-.06,.06].forEach(ex=>{ const e=new THREE.Mesh(new THREE.SphereGeometry(.022,6,6),eyeM); e.position.set(ex,.02,.155); headG.add(e); }); [legL,legR,torso,aL,aR,head].forEach(m=>{m.castShadow=true;}); g.add(legL,legR,torso,armL,armR,headG); // label nama const label = textSprite(t.name); label.position.y=2.15; g.add(label); const bubble = bubbleSprite(); bubble.position.y=2.6; g.add(bubble); const ch = { ...t, group:g, legL,legR,armL,armR,headG,torso,label,bubble, state:'idle', target:null, timer:2+Math.random()*4, walkPhase:Math.random()*6, desk:null, speed:1.6+Math.random()*.5, skinTone:skin }; g.traverse(o=>{ o.userData.charId = t.id; }); characters.push(ch); return ch; } TEAM.forEach((t,i)=> makeCharacter(t,i)); // posisi awal di meja masing-masing characters.forEach((c,i)=>{ const d=desks[i%desks.length]; c.desk=d; d.occupiedBy=c.id; c.group.position.set(d.x, 0, d.z+.95); c.group.rotation.y = d.ry+Math.PI; c.state='working'; }); /* ============ PERILAKU ============ */ const feedEl = document.getElementById('feed'); function log(msg, color='#A3E635'){ const d=document.createElement('div'); d.className='item'; d.style.borderLeftColor=color; d.innerHTML=`${new Date().toLocaleTimeString('id-ID',{hour:'2-digit',minute:'2-digit',second:'2-digit'})}${msg}`; feedEl.prepend(d); while(feedEl.children.length>4) feedEl.lastChild.remove(); } const SPOTS = { meeting:[[-4,3.2],[-4,5.8],[-2.5,4.5],[-5.5,4.5]], coffee:[[6.5,2.2],[5.6,3.4]], lobby:[[0,6.5],[-8,6]], }; function randSpot(){ const ks=Object.keys(SPOTS); const k=ks[(Math.random()*ks.length)|0]; const s=SPOTS[k][(Math.random()*SPOTS[k].length)|0]; return {x:s[0],z:s[1],kind:k}; } function updateCharacter(c, dt, time){ c.timer -= dt; const g = c.group; if(c.state==='walking' && c.target){ const dx=c.target.x-g.position.x, dz=c.target.z-g.position.z; const dist=Math.hypot(dx,dz); if(dist<.25){ // sampai if(c.target.kind==='desk' && c.desk){ c.state='working'; c.timer=8+Math.random()*10; g.position.set(c.desk.x,0,c.desk.z+.95); g.rotation.y=c.desk.ry+Math.PI; log(`💺 ${c.name} kembali kerja di meja`); } else { c.state='idle'; c.timer=4+Math.random()*6; const acts={meeting:'💬 '+c.name+' ikut meeting', coffee:'☕ '+c.name+' ngopi dulu', lobby:'🚶 '+c.name+' jalan-jalan'}; log(acts[c.target.kind]||('🚶 '+c.name+' sampai')); } c.target=null; } else { const vx=dx/dist*c.speed*dt, vz=dz/dist*c.speed*dt; g.position.x+=vx; g.position.z+=vz; g.rotation.y=Math.atan2(dx,dz); c.walkPhase+=dt*9; const sw=Math.sin(c.walkPhase)*.55; c.legL.rotation.x=sw; c.legR.rotation.x=-sw; c.armL.rotation.x=-sw*.8; c.armR.rotation.x=sw*.8; g.position.y=Math.abs(Math.sin(c.walkPhase))*.05; c.headG.rotation.x=0; return; } } // reset pose jalan c.legL.rotation.x*=.8; c.legR.rotation.x*=.8; c.armL.rotation.x*=.8; c.armR.rotation.x*=.8; g.position.y*=.8; if(c.state==='working'){ // mengetik: lengan ke depan + angguk c.armL.rotation.x=-.9+Math.sin(time*10+c.walkPhase)*.08; c.armR.rotation.x=-.9+Math.cos(time*11+c.walkPhase)*.08; c.headG.rotation.x=.18+Math.sin(time*2)*.03; if(c.timer<=0){ if(c.id==='wowo'){ // Wowo mulai patroli QC c.state='patrol'; c.patrolIdx=0; c.timer=0; log(`🔍 Wowo mulai patroli QC`, '#22c55e'); } else if(Math.random()<.55){ const s=randSpot(); c.target=s; c.state='walking'; } else { c.timer=6+Math.random()*8; } } } else if(c.state==='patrol'){ // kelilingi meja satu per satu const order=[...desks]; if(c.patrolIdx>=order.length){ c.state='working'; c.timer=10+Math.random()*8; g.position.set(c.desk.x,0,c.desk.z+.95); g.rotation.y=c.desk.ry+Math.PI; log(`✅ Patroli Wowo selesai — semua meja dicek`, '#22c55e'); return; } const d=order[c.patrolIdx]; const tx=d.x, tz=d.z+1.6; const dx=tx-g.position.x, dz=tz-g.position.z, dist=Math.hypot(dx,dz); if(dist<.3){ // inspeksi: beri verdict const ok=Math.random()>.18; setBubble(c.bubble, ok?'✅':'❌'); log(`${ok?'✅':'❌'} Wowo QC meja ${desks.indexOf(d)+1}: ${ok?'LULUS':'DITOLAK — perbaiki!'}` , ok?'#22c55e':'#ef4444'); c.patrolIdx++; c.timer=1.2; c.state='inspecting'; } else { g.position.x+=dx/dist*c.speed*1.2*dt; g.position.z+=dz/dist*c.speed*1.2*dt; g.rotation.y=Math.atan2(dx,dz); c.walkPhase+=dt*9; const sw=Math.sin(c.walkPhase)*.55; c.legL.rotation.x=sw; c.legR.rotation.x=-sw; } } else if(c.state==='inspecting'){ c.headG.rotation.y=Math.sin(time*3)*.4; if(c.timer<=0){ c.bubble.visible=false; c.state='patrol'; } } else { // idle di spot c.headG.rotation.y=Math.sin(time*.8+c.walkPhase)*.5; c.armL.rotation.x=Math.sin(time*1.2)*.06; c.armR.rotation.x=-Math.sin(time*1.2)*.06; if(c.timer<=0){ // kembali ke meja c.target={kind:'desk'}; c.state='walking'; } } } /* ============ KAMERA: ORBIT & JALAN (WASD) ============ */ let walkMode=false; const keys={}; addEventListener('keydown',e=>{ keys[e.code]=true; }); addEventListener('keyup',e=>{ keys[e.code]=false; }); const btnOrbit=document.getElementById('btnOrbit'), btnWalk=document.getElementById('btnWalk'); const hint=document.getElementById('hint'); let yaw=0, pitch=-.15, dragging=false, px=0, py=0; renderer.domElement.addEventListener('mousedown',e=>{ if(walkMode){dragging=true;px=e.clientX;py=e.clientY;} }); addEventListener('mouseup',()=>dragging=false); addEventListener('mousemove',e=>{ if(walkMode&&dragging){ yaw-=(e.clientX-px)*.004; pitch-=(e.clientY-py)*.004; pitch=Math.max(-1.2,Math.min(1.2,pitch)); px=e.clientX; py=e.clientY; } }); function setWalk(on){ walkMode=on; btnOrbit.classList.toggle('active',!on); btnWalk.classList.toggle('active',on); controls.enabled=!on; hint.textContent = on ? 'WASD: jalan · Drag: lihat · Klik karakter: info' : 'Drag: putar · Scroll: zoom · Klik karakter: info'; if(on){ camera.position.set(0,1.7,7); yaw=Math.PI; pitch=-.05; log('🚶 Mode jalan kaki — masuk ke kantor!'); } } btnOrbit.onclick=()=>setWalk(false); btnWalk.onclick=()=>setWalk(true); function updateWalk(dt){ const sp=(keys['ShiftLeft']?5:2.6)*dt; const fx=-Math.sin(yaw), fz=-Math.cos(yaw), rx=Math.cos(yaw), rz=-Math.sin(yaw); let mx=0,mz=0; if(keys['KeyW']){mx+=fx;mz+=fz;} if(keys['KeyS']){mx-=fx;mz-=fz;} if(keys['KeyD']){mx+=rx;mz+=rz;} if(keys['KeyA']){mx-=rx;mz-=rz;} const l=Math.hypot(mx,mz)||1; camera.position.x=Math.max(-W/2+.6,Math.min(W/2-.6,camera.position.x+mx/l*sp)); camera.position.z=Math.max(-D/2+.6,Math.min(D/2-.6,camera.position.z+mz/l*sp)); camera.position.y=1.7+Math.sin(performance.now()*.012)*.02*(mx||mz?1:0); camera.rotation.order='YXZ'; camera.rotation.y=yaw; camera.rotation.x=pitch; } /* ============ KLIK KARAKTER ============ */ const ray=new THREE.Raycaster(), mouse=new THREE.Vector2(); const infoBox=document.getElementById('info'); renderer.domElement.addEventListener('click',e=>{ if(dragging) return; mouse.set(e.clientX/innerWidth*2-1, -(e.clientY/innerHeight)*2+1); ray.setFromCamera(mouse,camera); const hits=ray.intersectObjects(characters.map(c=>c.group),true); if(hits.length){ let o=hits[0].object; const c=characters.find(ch=>ch.id===o.userData.charId); if(c){ document.getElementById('infoName').textContent=c.name; document.getElementById('infoRole').textContent=c.role; document.getElementById('infoStatus').textContent='Online 🟢'; document.getElementById('infoModel').textContent=c.model; document.getElementById('infoPort').textContent=':'+c.port; document.getElementById('infoSkill').textContent=c.skill; infoBox.style.display='block'; log(`👆 Melihat profil ${c.name}`); } } else infoBox.style.display='none'; }); /* ============ LOOP ============ */ const clock=new THREE.Clock(); log('🏢 Kantor 3D dibuka — 5 karyawan mulai bekerja'); setTimeout(()=>log('🔍 Wowo siap patroli QC keliling meja'),4000); (function animate(){ requestAnimationFrame(animate); const dt=Math.min(clock.getDelta(),.05), t=clock.elapsedTime; characters.forEach(c=>updateCharacter(c,dt,t)); // layar monitor berkedip halus scene.traverse(o=>{ if(o.material&&(o.material===M.screen||o.material===M.screenG)) o.material.emissiveIntensity=.75+Math.sin(t*3+o.position.x)*.15; }); if(walkMode) updateWalk(dt); else controls.update(); renderer.render(scene,camera); })(); addEventListener('resize',()=>{ camera.aspect=innerWidth/innerHeight; camera.updateProjectionMatrix(); renderer.setSize(innerWidth,innerHeight); }); document.getElementById('loading').style.display='none';