3 D で 五 目 並 べ を 作 る ②
前回作ったファイルは壊れました。(なんでだよ)
ともかく、AIを駆使して
3D五目並べ
を作ります。
コード書くのってマジで大変




後は勝利条件を追加するだけ
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>Icosahedron</title>
<style>
body {
margin: 0;
overflow: hidden;
background-color: #333;
}
canvas {
display: block;
}
</style>
</head>
<body>
<div id="info"
style="position: absolute; top: 10px; left: 10px; color: white; font-family: sans-serif; pointer-events: none;">
Right-click on the shape to detect face
</div>
<script type="module">
import * as THREE from 'https://unpkg.com/three@0.160.0/build/three.module.js';
try {
// Scene setup
const scene = new THREE.Scene();
scene.background = new THREE.Color(0xf0f0f0); // 単色背景
// Camera setup
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.z = 3;
// Renderer setup
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// Icosahedron creation
const geometry = new THREE.IcosahedronGeometry(1.2, 0);
// Material: 単色, エフェクトなし (FlatShadingで立体感は出す)
const material = new THREE.MeshLambertMaterial({
color: 0x44ddff, // 鮮やかな青
flatShading: true
});
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
// Lighting (to show 3D shape with flat shading)
const light = new THREE.DirectionalLight(0xffffff, 1);
light.position.set(1, 1, 1).normalize();
scene.add(light);
const ambientLight = new THREE.AmbientLight(0x999999);
scene.add(ambientLight);
// Markings Setup
const faceMarks = []; // Store { circle: Mesh, asterisk: Group } for each face index
// Helper: Iterate over faces to place marks
// Scene setup
// This section appears to be a duplicate and will be removed to avoid re-declaration errors.
// const scene = new THREE.Scene();
// scene.background = new THREE.Color(0x333333);
// Camera setup
// const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
// camera.position.z = 3;
// Renderer setup
// const renderer = new THREE.WebGLRenderer({ antialias: true });
// renderer.setSize(window.innerWidth, window.innerHeight);
// document.body.appendChild(renderer.domElement);
// Icosahedron creation
// const geometry = new THREE.IcosahedronGeometry(1.2, 0);
// Material
// const material = new THREE.MeshLambertMaterial({
// color: 0x00aaff,
// flatShading: true
// });
// const mesh = new THREE.Mesh(geometry, material);
// scene.add(mesh);
// Lighting
// const light = new THREE.DirectionalLight(0xffffff, 1);
// light.position.set(1, 1, 1).normalize();
// scene.add(light);
// const ambientLight = new THREE.AmbientLight(0x444444);
// scene.add(ambientLight);
// Markings Setup
// const faceMarks = []; // Store { circle: Mesh, asterisk: Group } for each face index
// Helper to get vertex by index or position
function getVertex(geo, index) {
const pos = geo.getAttribute('position');
if (geo.index) {
const i = geo.index.getX(index);
return new THREE.Vector3().fromBufferAttribute(pos, i);
} else {
return new THREE.Vector3().fromBufferAttribute(pos, index);
}
}
const positionAttribute = geometry.getAttribute('position');
const indexAttribute = geometry.index;
const count = indexAttribute ? indexAttribute.count : positionAttribute.count;
// Circle Geometry (Red Torus)
const circleGeo = new THREE.TorusGeometry(0.2, 0.05, 8, 16);
const circleMat = new THREE.MeshBasicMaterial({ color: 0xff0000 });
// Asterisk Geometry (Black Group of Boxes)
const stickGeo = new THREE.BoxGeometry(0.05, 0.5, 0.05);
const stickMat = new THREE.MeshBasicMaterial({ color: 0x000000 });
for (let i = 0; i < count; i += 3) {
const vA = getVertex(geometry, i);
const vB = getVertex(geometry, i + 1);
const vC = getVertex(geometry, i + 2);
// Calculate center
const center = new THREE.Vector3().addVectors(vA, vB).add(vC).divideScalar(3);
// Calculate normal
const normal = new THREE.Vector3().crossVectors(
new THREE.Vector3().subVectors(vB, vA),
new THREE.Vector3().subVectors(vC, vA)
).normalize();
// Create Circle (A state mark)
const circle = new THREE.Mesh(circleGeo, circleMat);
circle.position.copy(center).add(normal.clone().multiplyScalar(0.01)); // Slight offset
circle.lookAt(center.clone().add(normal));
circle.visible = false;
mesh.add(circle);
// Create Asterisk (B state mark)
const asteriskGroup = new THREE.Group();
const stick1 = new THREE.Mesh(stickGeo, stickMat);
const stick2 = new THREE.Mesh(stickGeo, stickMat);
const stick3 = new THREE.Mesh(stickGeo, stickMat);
stick2.rotation.z = Math.PI / 3; // 60 deg
stick3.rotation.z = -Math.PI / 3; // -60 deg
asteriskGroup.add(stick1, stick2, stick3);
asteriskGroup.position.copy(center).add(normal.clone().multiplyScalar(0.01));
asteriskGroup.lookAt(center.clone().add(normal));
asteriskGroup.visible = false;
mesh.add(asteriskGroup);
// Store references
// Note: If non-indexed, i/3 maps correctly to faceIndex if raycaster uses triangle index
faceMarks.push({ circle, asterisk: asteriskGroup });
}
// Raycaster definition
const raycaster = new THREE.Raycaster();
const mouse = new THREE.Vector2();
const infoDiv = document.getElementById('info');
// Interaction logic
let isDragging = false;
let previousMousePosition = { x: 0, y: 0 };
renderer.domElement.addEventListener('mousedown', (e) => {
if (e.button === 0) { // 左クリックのみ
isDragging = true;
previousMousePosition = { x: e.clientX, y: e.clientY };
}
});
// Global state
let currentState = 'A';
// Right-click face detection, mark placement, and state toggle
renderer.domElement.addEventListener('contextmenu', (e) => {
e.preventDefault();
mouse.x = (e.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(e.clientY / window.innerHeight) * 2 + 1;
raycaster.setFromCamera(mouse, camera);
const intersects = raycaster.intersectObject(mesh);
if (intersects.length > 0) {
const faceIndex = intersects[0].faceIndex;
if (faceMarks[faceIndex]) {
if (currentState === 'A') {
// State A: Show Circle, Hide Asterisk
faceMarks[faceIndex].circle.visible = true;
faceMarks[faceIndex].asterisk.visible = false;
} else {
// State B: Show Asterisk, Hide Circle
faceMarks[faceIndex].asterisk.visible = true;
faceMarks[faceIndex].circle.visible = false;
}
}
// Toggle State
currentState = (currentState === 'A') ? 'B' : 'A';
infoDiv.innerText = `Detected Face Index: ${faceIndex} | State: ${currentState}`;
} else {
infoDiv.innerText = `No face detected | State: ${currentState}`;
}
});
const onStopDrag = () => {
isDragging = false;
};
window.addEventListener('mouseup', onStopDrag);
window.addEventListener('mouseleave', onStopDrag);
renderer.domElement.addEventListener('mousemove', (e) => {
if (isDragging) {
const deltaMove = {
x: e.clientX - previousMousePosition.x,
y: e.clientY - previousMousePosition.y
};
const rotateSpeed = 0.005;
const xAxis = new THREE.Vector3(1, 0, 0);
const yAxis = new THREE.Vector3(0, 1, 0);
mesh.rotateOnWorldAxis(xAxis, deltaMove.y * rotateSpeed);
mesh.rotateOnWorldAxis(yAxis, deltaMove.x * rotateSpeed);
previousMousePosition = { x: e.clientX, y: e.clientY };
}
});
// Resize handling
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
// Animation loop
function animate() {
requestAnimationFrame(animate);
renderer.render(scene, camera);
}
animate();
// Expose for debugging/analysis
window.THREE = THREE;
window.mesh = mesh;
} catch (error) {
console.error("Error during init:", error);
// Don't replace body content so the analysis script can still try to run
// document.body.innerHTML = "<div style='color:red; background:black; padding:20px;'><h1>Error</h1>" + error.toString() + "<br>" + error.stack + "</div>";
}
</script>
</body>
</html>Created with assistance from AI
まだ作成途中なので、あまり期待しないで💦。
いいなと思ったら応援しよう!
⚠️警告⚠️
もしあなたがチップをくれたら、その分あなたのお金が減ります(?)。お金は大切に。