| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>Vitalis DevCore — 3D Blueprint</title> |
| <style> |
| *{margin:0;padding:0;box-sizing:border-box;} |
| body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;background:#0a0a1a;overflow:hidden;color:#fff;} |
| #canvas-container{width:100vw;height:100vh;display:block;} |
| #info-overlay{position:fixed;top:16px;left:16px;background:rgba(10,10,30,0.85);border:1px solid rgba(100,60,255,0.3);border-radius:12px;padding:16px 20px;max-width:340px;font-size:13px;line-height:1.5;backdrop-filter:blur(8px);z-index:10;} |
| #info-overlay h2{font-size:16px;margin-bottom:6px;color:#a78bfa;} |
| #info-overlay p{color:#c4b5fd;margin:2px 0;font-size:12px;} |
| .badge{display:inline-block;padding:2px 8px;border-radius:6px;font-size:11px;margin:2px;background:rgba(100,60,255,0.15);color:#a78bfa;border:1px solid rgba(100,60,255,0.2);} |
| .badge-dim{background:rgba(50,180,100,0.15);color:#6ee7a7;border-color:rgba(50,180,100,0.2);} |
| #controls-hint{position:fixed;bottom:20px;left:50%;transform:translateX(-50%);background:rgba(10,10,30,0.7);border:1px solid rgba(255,255,255,0.1);border-radius:8px;padding:8px 16px;font-size:12px;color:#888;backdrop-filter:blur(4px);z-index:10;display:flex;gap:16px;} |
| #controls-hint span{cursor:pointer;padding:2px 8px;border-radius:4px;transition:background 0.2s;} |
| #controls-hint span:hover{background:rgba(100,60,255,0.2);color:#a78bfa;} |
| #controls-hint .active{background:rgba(100,60,255,0.3);color:#a78bfa;} |
| .tool-btn{position:fixed;bottom:20px;right:20px;background:rgba(100,60,255,0.2);border:1px solid rgba(100,60,255,0.3);border-radius:8px;padding:8px 14px;font-size:12px;color:#a78bfa;cursor:pointer;backdrop-filter:blur(4px);z-index:10;transition:all 0.2s;} |
| .tool-btn:hover{background:rgba(100,60,255,0.4);} |
| </style> |
| </head> |
| <body> |
| <div id="info-overlay"> |
| <h2>🏗️ <span id="style-name">Blueprint</span></h2> |
| <p id="summary-text">Loading...</p> |
| <div id="features" style="margin-top:6px;"></div> |
| <div id="dimensions" style="margin-top:4px;font-size:11px;color:#6ee7a7;"></div> |
| </div> |
| <div id="controls-hint"> |
| <span data-mode="orbit" class="active">🔄 Orbit</span> |
| <span data-mode="pan">✋ Pan</span> |
| <span data-mode="tour">🎬 Tour</span> |
| </div> |
| <button class="tool-btn" id="export-btn">📥 Export JSON</button> |
| <div id="canvas-container"></div> |
|
|
| <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script> |
| <script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/controls/OrbitControls.js"></script> |
|
|
| <script> |
| |
| |
| var BP = BLUEPRINT; |
| var scene, camera, renderer, controls; |
| var tourActive = false, tourPoints = [], tourIdx = 0; |
| |
| function initScene() { |
| var container = document.getElementById('canvas-container'); |
| var w = container.clientWidth, h = container.clientHeight; |
| |
| scene = new THREE.Scene(); |
| scene.background = new THREE.Color(0x0a0a1a); |
| scene.fog = new THREE.Fog(0x0a0a1a, 120, 200); |
| |
| camera = new THREE.PerspectiveCamera(40, w/h, 0.1, 500); |
| renderer = new THREE.WebGLRenderer({antialias:true}); |
| renderer.setSize(w, h); |
| renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); |
| renderer.shadowMap.enabled = true; |
| renderer.shadowMap.type = THREE.PCFSoftShadowMap; |
| renderer.toneMapping = THREE.ACESFilmicToneMapping; |
| renderer.toneMappingExposure = 1.2; |
| container.appendChild(renderer.domElement); |
| |
| controls = new THREE.OrbitControls(camera, renderer.domElement); |
| controls.enableDamping = true; |
| controls.dampingFactor = 0.08; |
| controls.maxPolarAngle = Math.PI / 2.05; |
| controls.minDistance = 3; |
| controls.maxDistance = 120; |
| |
| |
| scene.add(new THREE.AmbientLight(0x404060, 0.5)); |
| var sun = new THREE.DirectionalLight(0xffeedd, 1.4); |
| sun.position.set(40, 60, 30); |
| sun.castShadow = true; |
| sun.shadow.mapSize.width = 2048; |
| sun.shadow.mapSize.height = 2048; |
| scene.add(sun); |
| scene.add(new THREE.DirectionalLight(0x8888ff, 0.3).position.set(-30, 20, -20)); |
| scene.add(new THREE.HemisphereLight(0x8888ff, 0x444422, 0.5)); |
| |
| |
| var gnd = new THREE.Mesh( |
| new THREE.PlaneGeometry(200, 200), |
| new THREE.MeshStandardMaterial({color:0x181830,roughness:0.9,metalness:0,transparent:true,opacity:0.8}) |
| ); |
| gnd.rotation.x = -Math.PI/2; |
| gnd.position.y = -0.25; |
| gnd.receiveShadow = true; |
| scene.add(gnd); |
| var grid = new THREE.GridHelper(100, 40, 0x444488, 0x333366); |
| grid.position.y = 0.01; |
| scene.add(grid); |
| |
| var sp = BP.scene; |
| var sw = sp.width, sd = sp.depth; |
| |
| |
| var ol = new THREE.Line( |
| new THREE.BufferGeometry().setAttribute('position', new THREE.BufferAttribute(new Float32Array([0,0,0,sw,0,0,sw,0,sd,0,0,sd,0,0,0]),3)), |
| new THREE.LineBasicMaterial({color:0x6666cc,transparent:true,opacity:0.4}) |
| ); |
| ol.position.y = 0.02; |
| scene.add(ol); |
| |
| |
| BP.floors.forEach(function(f) { |
| var g = new THREE.BoxGeometry(f.w, f.h, f.d); |
| var m = new THREE.MeshStandardMaterial({roughness:0.7,metalness:0.05,color:f.color||0x444466,transparent:true,opacity:f.exterior?0.4:0.8}); |
| var mesh = new THREE.Mesh(g, m); |
| mesh.position.set(f.x+f.w/2, f.y, f.z+f.d/2); |
| mesh.castShadow = true; mesh.receiveShadow = true; |
| scene.add(mesh); |
| if (!f.exterior) { |
| var e = new THREE.LineSegments(new THREE.EdgesGeometry(g), new THREE.LineBasicMaterial({color:0x8888cc,transparent:true,opacity:0.15})); |
| e.position.copy(mesh.position); |
| scene.add(e); |
| } |
| }); |
| |
| |
| BP.walls.forEach(function(w) { |
| var wx, wz, ww, wd; |
| if (w.side === 'north') { wx = w.x; wz = w.z; ww = w.w; wd = 0.15; } |
| else if (w.side === 'south') { wx = w.x; wz = w.z+w.d; ww = w.w; wd = 0.15; } |
| else if (w.side === 'east') { wx = w.x+w.w; wz = w.z; ww = 0.15; wd = w.d; } |
| else { wx = w.x; wz = w.z; ww = 0.15; wd = w.d; } |
| var g = new THREE.BoxGeometry(ww, w.h, wd); |
| var m = new THREE.MeshStandardMaterial({color:0x555599,roughness:0.6,metalness:0.1,transparent:true,opacity:0.1}); |
| var mesh = new THREE.Mesh(g, m); |
| mesh.position.set(wx+ww/2, w.h/2, wz+wd/2); |
| scene.add(mesh); |
| }); |
| |
| |
| var rf = BP.roof; |
| var roofMesh; |
| if (rf.style === 'flat') { |
| var rg = new THREE.BoxGeometry(rf.w, rf.h*0.3, rf.d); |
| var rm = new THREE.MeshStandardMaterial({color:rf.color||0x5a4a3a,roughness:0.8,metalness:0}); |
| roofMesh = new THREE.Mesh(rg, rm); |
| roofMesh.position.set(sw/2, rf.y, sd/2); |
| } else if (rf.style === 'gable' || rf.style === 'steep-gable' || rf.style === 'low-gable') { |
| var shape = new THREE.Shape(); |
| var pitch = rf.pitch || 0.4; |
| var ridgeH = rf.w * pitch * 0.5; |
| shape.moveTo(-rf.w/2, 0); |
| shape.lineTo(0, ridgeH); |
| shape.lineTo(rf.w/2, 0); |
| shape.lineTo(-rf.w/2, 0); |
| var extrudeSettings = {steps:1,depth:rf.d,bevelEnabled:true,bevelThickness:0.5,bevelSize:0.3,bevelSegments:2}; |
| var rg = new THREE.ExtrudeGeometry(shape, extrudeSettings); |
| var rm = new THREE.MeshStandardMaterial({color:rf.color||0x5a4a3a,roughness:0.8,metalness:0,side:THREE.DoubleSide}); |
| roofMesh = new THREE.Mesh(rg, rm); |
| roofMesh.position.set(sw/2, rf.y, sd/2 - rf.d/2); |
| roofMesh.rotation.x = 0; |
| } else { |
| var rg = new THREE.ConeGeometry(Math.max(rf.w,rf.d)*0.6, rf.h, 4); |
| var rm = new THREE.MeshStandardMaterial({color:rf.color||0x5a4a3a,roughness:0.8,metalness:0}); |
| roofMesh = new THREE.Mesh(rg, rm); |
| roofMesh.position.set(sw/2, rf.y + rf.h/2, sd/2); |
| roofMesh.rotation.y = Math.PI/4; |
| } |
| if (roofMesh) { roofMesh.castShadow = true; scene.add(roofMesh); } |
| |
| |
| BP.measurements.forEach(function(m) { |
| var canvas = document.createElement('canvas'); |
| var ctx = canvas.getContext('2d'); |
| canvas.width = 256; canvas.height = 64; |
| ctx.fillStyle = 'rgba(50,180,100,0.15)'; |
| ctx.beginPath(); ctx.roundRect(20,8,216,48,6); ctx.fill(); |
| ctx.strokeStyle = 'rgba(50,180,100,0.4)'; ctx.lineWidth=1; ctx.stroke(); |
| ctx.fillStyle = '#6ee7a7'; ctx.font = 'bold 24px monospace'; ctx.textAlign='center'; ctx.textBaseline='middle'; |
| ctx.fillText(m.text, 128, 32); |
| |
| var tex = new THREE.CanvasTexture(canvas); |
| tex.needsUpdate = true; |
| var sp = new THREE.Sprite(new THREE.SpriteMaterial({map:tex,transparent:true,depthTest:false,sizeAttenuation:true})); |
| sp.position.set(m.x, m.y + 0.5, m.z); |
| sp.scale.set(m.dim === 'width' ? 4 : 2.5, 1, 1); |
| scene.add(sp); |
| }); |
| |
| |
| BP.labels.forEach(function(l) { |
| var canvas = document.createElement('canvas'); |
| var ctx = canvas.getContext('2d'); |
| canvas.width = 512; canvas.height = 100; |
| ctx.shadowColor = 'rgba(0,0,0,0.8)'; ctx.shadowBlur = 10; |
| ctx.fillStyle = 'rgba(20,20,50,0.5)'; |
| ctx.beginPath(); ctx.roundRect(50,12,412,76,10); ctx.fill(); |
| ctx.strokeStyle = 'rgba(100,60,255,0.2)'; ctx.lineWidth=1; ctx.stroke(); |
| ctx.shadowColor = 'rgba(0,0,0,0.5)'; ctx.shadowBlur = 4; |
| ctx.fillStyle = l.text.includes('Upper') ? 'rgba(180,180,220,0.6)' : '#ffffff'; |
| ctx.font = 'bold 28px -apple-system, BlinkMacSystemFont, sans-serif'; |
| ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; |
| ctx.fillText(l.text, 256, 50); |
| var tex = new THREE.CanvasTexture(canvas); |
| tex.needsUpdate = true; |
| var sp = new THREE.Sprite(new THREE.SpriteMaterial({map:tex,transparent:true,depthTest:false,sizeAttenuation:true})); |
| sp.position.set(l.x, l.y + 2.5, l.z); |
| sp.scale.set(6, 1.2, 1); |
| scene.add(sp); |
| }); |
| |
| |
| var dist = Math.max(sw, sd) * 1.2 + 8; |
| camera.position.set(dist*0.6, dist*0.6, dist*0.7); |
| controls.target.set(sw/2, 1, sd/2); |
| controls.update(); |
| |
| |
| tourPoints = BP.labels.map(function(l) { return {x:l.x, y:3, z:l.z}; }); |
| |
| |
| window.addEventListener('resize', function() { |
| var w2 = container.clientWidth, h2 = container.clientHeight; |
| camera.aspect = w2/h2; camera.updateProjectionMatrix(); |
| renderer.setSize(w2, h2); |
| }); |
| |
| animate(); |
| } |
| |
| function animate() { |
| requestAnimationFrame(animate); |
| if (tourActive && tourPoints.length > 0) { |
| var p = tourPoints[tourIdx]; |
| controls.target.lerp(new THREE.Vector3(p.x, p.y, p.z), 0.03); |
| var dist = controls.target.distanceTo(camera.position); |
| if (dist < 2) { tourIdx = (tourIdx + 1) % tourPoints.length; } |
| } |
| controls.update(); |
| renderer.render(scene, camera); |
| } |
| |
| |
| document.getElementById('summary-text').textContent = BP.summary || 'No description.'; |
| document.getElementById('style-name').textContent = (BP.scene.style || 'Modern').charAt(0).toUpperCase() + (BP.scene.style||'Modern').slice(1); |
| |
| var feat = BP.features || {}; |
| var featHtml = ''; |
| var dimHtml = ''; |
| if (feat.bedrooms !== undefined) featHtml += '<span class="badge">🛏️ ' + feat.bedrooms + ' bed</span>'; |
| if (feat.bathrooms !== undefined) featHtml += '<span class="badge">🚿 ' + feat.bathrooms + ' bath</span>'; |
| if (feat.stories !== undefined) featHtml += '<span class="badge">🏠 ' + feat.stories + ' story</span>'; |
| if (feat.garage !== undefined) featHtml += '<span class="badge">🚗 ' + (feat.garage ? 'Garage' : 'No garage') + '</span>'; |
| featHtml += '<span class="badge badge-dim">📐 ' + BP.scene.width + 'ft x ' + BP.scene.depth + 'ft</span>'; |
| document.getElementById('features').innerHTML = featHtml; |
| |
| var sqft = BP.scene.width * BP.scene.depth * (BP.scene.stories || 1); |
| document.getElementById('dimensions').textContent = '≈ ' + sqft + ' sq ft total'; |
| |
| |
| document.querySelectorAll('#controls-hint span').forEach(function(el) { |
| el.addEventListener('click', function() { |
| document.querySelectorAll('#controls-hint span').forEach(function(e) { e.classList.remove('active'); }); |
| el.classList.add('active'); |
| var mode = el.dataset.mode; |
| if (mode === 'orbit') { tourActive = false; controls.enableRotate = true; controls.enablePan = true; } |
| else if (mode === 'pan') { tourActive = false; controls.enableRotate = false; controls.enablePan = true; } |
| else if (mode === 'tour') { tourActive = true; tourIdx = 0; controls.enableRotate = true; controls.enablePan = true; } |
| }); |
| }); |
| |
| |
| document.getElementById('export-btn').addEventListener('click', function() { |
| var json = JSON.stringify(BP, null, 2); |
| var blob = new Blob([json], {type:'application/json'}); |
| var a = document.createElement('a'); |
| a.href = URL.createObjectURL(blob); |
| a.download = 'blueprint.json'; |
| a.click(); |
| }); |
| |
| initScene(); |
| </script> |
| </body> |
| </html> |
|
|