| | |
| | let scene, camera, renderer, buildings = {}, residents = {}; |
| | let controls; |
| |
|
| | function init() { |
| | |
| | scene = new THREE.Scene(); |
| | scene.background = new THREE.Color(0x87CEEB); |
| |
|
| | |
| | camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); |
| | camera.position.set(50, 50, 50); |
| | camera.lookAt(0, 0, 0); |
| |
|
| | |
| | renderer = new THREE.WebGLRenderer({ antialias: true }); |
| | renderer.setSize(window.innerWidth, window.innerHeight); |
| | renderer.shadowMap.enabled = true; |
| | document.getElementById('three-container').appendChild(renderer.domElement); |
| |
|
| | |
| | const ambientLight = new THREE.AmbientLight(0xffffff, 0.5); |
| | scene.add(ambientLight); |
| | const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8); |
| | directionalLight.position.set(50, 50, 50); |
| | directionalLight.castShadow = true; |
| | scene.add(directionalLight); |
| |
|
| | |
| | const groundGeometry = new THREE.PlaneGeometry(100, 100); |
| | const groundMaterial = new THREE.MeshStandardMaterial({ |
| | color: 0x90EE90, |
| | side: THREE.DoubleSide |
| | }); |
| | const ground = new THREE.Mesh(groundGeometry, groundMaterial); |
| | ground.rotation.x = -Math.PI / 2; |
| | ground.receiveShadow = true; |
| | scene.add(ground); |
| |
|
| | |
| | controls = new THREE.OrbitControls(camera, renderer.domElement); |
| | controls.enableDamping = true; |
| | controls.dampingFactor = 0.05; |
| |
|
| | |
| | animate(); |
| |
|
| | |
| | window.addEventListener('resize', onWindowResize, false); |
| | } |
| |
|
| | function createBuilding(name, position, type) { |
| | let geometry, material, color; |
| | |
| | |
| | switch(type) { |
| | case '住宅': |
| | geometry = new THREE.BoxGeometry(8, 6, 8); |
| | color = 0xFFB6C1; |
| | break; |
| | case '公園': |
| | geometry = new THREE.CylinderGeometry(5, 5, 2, 32); |
| | color = 0x228B22; |
| | break; |
| | case '図書館': |
| | geometry = new THREE.BoxGeometry(12, 8, 12); |
| | color = 0x8B4513; |
| | break; |
| | case 'カフェ': |
| | geometry = new THREE.BoxGeometry(10, 6, 10); |
| | color = 0xDEB887; |
| | break; |
| | case '商店': |
| | geometry = new THREE.BoxGeometry(10, 5, 10); |
| | color = 0x4682B4; |
| | break; |
| | default: |
| | geometry = new THREE.BoxGeometry(10, 10, 10); |
| | color = 0x808080; |
| | } |
| |
|
| | material = new THREE.MeshStandardMaterial({ |
| | color: color, |
| | roughness: 0.7, |
| | metalness: 0.2 |
| | }); |
| | |
| | const building = new THREE.Mesh(geometry, material); |
| | building.position.set(position.x, geometry.parameters.height / 2, position.z); |
| | building.castShadow = true; |
| | building.receiveShadow = true; |
| | scene.add(building); |
| | buildings[name] = building; |
| |
|
| | |
| | const canvas = document.createElement('canvas'); |
| | const context = canvas.getContext('2d'); |
| | canvas.width = 256; |
| | canvas.height = 64; |
| | context.fillStyle = '#ffffff'; |
| | context.font = '24px Arial'; |
| | context.fillText(name, 10, 32); |
| | const texture = new THREE.CanvasTexture(canvas); |
| | const labelMaterial = new THREE.SpriteMaterial({ map: texture }); |
| | const label = new THREE.Sprite(labelMaterial); |
| | label.position.set(position.x, geometry.parameters.height + 2, position.z); |
| | scene.add(label); |
| | } |
| |
|
| | function createResident(name, position, profession) { |
| | let geometry, material, color; |
| | |
| | |
| | switch(profession) { |
| | case '教師': |
| | color = 0x0000FF; |
| | break; |
| | case '看護師': |
| | color = 0xFF0000; |
| | break; |
| | case 'エンジニア': |
| | color = 0x00FF00; |
| | break; |
| | case 'デザイナー': |
| | color = 0xFF00FF; |
| | break; |
| | case '商店主': |
| | color = 0xFFA500; |
| | break; |
| | default: |
| | color = 0xFFFFFF; |
| | } |
| |
|
| | |
| | const headGeometry = new THREE.SphereGeometry(1, 32, 32); |
| | const headMaterial = new THREE.MeshStandardMaterial({ color: 0xFFE4C4 }); |
| | const head = new THREE.Mesh(headGeometry, headMaterial); |
| | head.position.y = 2; |
| | head.castShadow = true; |
| |
|
| | |
| | const bodyGeometry = new THREE.CylinderGeometry(0.7, 0.7, 2, 32); |
| | const bodyMaterial = new THREE.MeshStandardMaterial({ color: color }); |
| | const body = new THREE.Mesh(bodyGeometry, bodyMaterial); |
| | body.position.y = 0.5; |
| | body.castShadow = true; |
| |
|
| | |
| | const resident = new THREE.Group(); |
| | resident.add(head); |
| | resident.add(body); |
| | resident.position.set(position.x, 0, position.z); |
| | scene.add(resident); |
| | residents[name] = resident; |
| |
|
| | |
| | const canvas = document.createElement('canvas'); |
| | const context = canvas.getContext('2d'); |
| | canvas.width = 256; |
| | canvas.height = 64; |
| | context.fillStyle = '#ffffff'; |
| | context.font = '24px Arial'; |
| | context.fillText(name, 10, 32); |
| | const texture = new THREE.CanvasTexture(canvas); |
| | const labelMaterial = new THREE.SpriteMaterial({ map: texture }); |
| | const label = new THREE.Sprite(labelMaterial); |
| | label.position.set(position.x, 4, position.z); |
| | scene.add(label); |
| | } |
| |
|
| | function updatePositions(state) { |
| | |
| | state.locations.forEach(location => { |
| | if (!buildings[location.name]) { |
| | const type = location.name.includes('住宅') ? '住宅' : |
| | location.name.includes('公園') ? '公園' : |
| | location.name.includes('図書館') ? '図書館' : |
| | location.name.includes('カフェ') ? 'カフェ' : |
| | location.name.includes('商店') ? '商店' : 'その他'; |
| | createBuilding(location.name, location.position, type); |
| | } |
| | }); |
| |
|
| | |
| | state.residents.forEach(resident => { |
| | if (!residents[resident.name]) { |
| | createResident(resident.name, resident.position, resident.profession); |
| | } else { |
| | |
| | const currentPos = residents[resident.name].position; |
| | const targetPos = new THREE.Vector3( |
| | resident.position.x, |
| | 0, |
| | resident.position.z |
| | ); |
| | |
| | |
| | const speed = 0.1; |
| | |
| | |
| | const direction = targetPos.sub(currentPos).normalize(); |
| | |
| | |
| | currentPos.add(direction.multiplyScalar(speed)); |
| | |
| | |
| | if (direction.length() > 0) { |
| | residents[resident.name].lookAt(targetPos); |
| | } |
| | } |
| | }); |
| | } |
| |
|
| | function onWindowResize() { |
| | camera.aspect = window.innerWidth / window.innerHeight; |
| | camera.updateProjectionMatrix(); |
| | renderer.setSize(window.innerWidth, window.innerHeight); |
| | } |
| |
|
| | function animate() { |
| | requestAnimationFrame(animate); |
| | controls.update(); |
| | renderer.render(scene, camera); |
| | } |
| |
|
| | |
| | init(); |