0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Three.jsをWordPressで動かして40社のAI企業を3D宇宙に配置してみた【失敗談あり】

0
Posted at

ai-galaxy-map.jpg

世界40社超のAI企業を、太陽系の天体に見立てた3Dインタラクティブマップを作った。NVIDIAが太陽(時価総額$4.31兆)、Google・Microsoftが惑星、OpenAI・Anthropicが内惑星として公転する。

完成したもの: AI Gravity Map 2026

「Three.jsをWordPressで使う」という情報が意外と少なかったので、ハマったところを中心にまとめる。


この記事でできること

  • Three.jsの基本的な3Dシーンのセットアップ手順を理解できる
  • WordPressで外部ライブラリを安全に読み込む方法がわかる
  • &&演算子がWordPressで壊れる問題と回避策を知れる
  • 3Dオブジェクトへのクリックイベント(Raycaster)の実装方法がわかる
  • 実際に動作するツールはAI Gravity Map 2026で確認できる

環境・前提

  • ブラウザ: Chrome 120以上(WebGL対応が必要)
  • Three.js: r164(CDNで読み込む)
  • WordPress: 6.x(SWELLテーマを使用)
  • JavaScript: ES5で記述(ES6+は後述の理由で避けた)
  • 外部ライブラリ: OrbitControls(Three.jsのaddons)

完成形

企業データ(時価総額・設立年・カテゴリ)をJSON形式で定義し、Three.jsで球体として3D空間に配置する。球の大きさは時価総額のログスケールで決まる。クリックで企業の詳細が表示され、カテゴリフィルターで絞り込みができる。

完成したもの: AI Gravity Map 2026


手順

Step 1: Three.jsをWordPressで読み込む

NG例(SWELLでは動かない):

<script src="https://cdn.jsdelivr.net/npm/three@0.164.0/build/three.min.js"></script>

WordPressのSWELLテーマは、カスタムHTMLブロック内の<script src="...">タグを無視する。コンソールにエラーも出ない。ただ黙って読み込まない。

OK例(動的ロード):

function loadThree(callback) {
  if (typeof THREE !== 'undefined') {
    callback();
    return;
  }
  var s = document.createElement('script');
  s.src = 'https://cdn.jsdelivr.net/npm/three@0.164.0/build/three.min.js';
  s.onload = function() {
    // OrbitControlsも連鎖してロード
    var oc = document.createElement('script');
    oc.src = 'https://cdn.jsdelivr.net/npm/three@0.164.0/examples/js/controls/OrbitControls.js';
    oc.onload = callback;
    document.head.appendChild(oc);
  };
  document.head.appendChild(s);
}

loadThree(function() {
  initScene(); // ここからThree.jsのコードを書く
});

typeof THREE !== 'undefined'のチェックが大事。同じページに複数のブロックがある場合、最初のブロックがロードしたThree.jsを全ブロックで共有できる。

つまずきポイント: script.onloadの中でさらに別のスクリプト(OrbitControls)をロードする。2つを同時にappendすると読み込み順が保証されないので、連鎖(チェーン)させる。


Step 2: && 演算子を使わない

NG例(WordPressが壊す):

if (isMobile && isLoaded) {
  initMobile();
}

WordPressのコンテンツフィルターが &&&#038;&#038; にHTMLエンティティ変換する。JavaScriptの構文エラーになり、スクリプト全体が止まる。

OK例(ネストif):

if (isMobile) {
  if (isLoaded) {
    initMobile();
  }
}

または三項演算子:

var result = (isMobile ? mobileInit() : desktopInit());

つまずきポイント: エラーメッセージが「構文エラー」としか出ないので、どこが壊れているか特定しにくい。Three.jsのコードは量が多いので、最初から&&を使わない書き方に慣れておくと楽。


Step 3: 天体データの定義

企業データを配列で定義する。valUSD(時価総額・十億ドル)が球のサイズを決める。

var BODIES = [
  {
    id: 'nvidia',
    type: 'star',        // 恒星(最大)
    name: 'NVIDIA',
    valUSD: 4310,        // 43.1億ドル = $4.31兆
    category: 'chip',
    founded: 1993,
    desc: 'AI半導体の絶対王者。GPU市場を独占しAI革命を牽引する。'
  },
  {
    id: 'openai',
    type: 'planet',      // 惑星
    name: 'OpenAI',
    valUSD: 852,         // $8520億
    parentId: null,      // 独立した天体
    // ...
  },
  {
    id: 'palantir',
    type: 'moon',        // 衛星
    name: 'Palantir',
    valUSD: 355,
    parentId: 'microsoft', // Microsoftの衛星として公転
    // ...
  }
];

ポイント: typeで天体の種別(star/planet/moon/dwarf/asteroid)を決める。moon(衛星)はparentIdで親天体を指定し、親の周りを公転させる。


Step 4: 基本シーンのセットアップ

function initScene() {
  var container = document.getElementById('galaxy-canvas');

  // レンダラー
  var isMobile = window.innerWidth <= 767;
  var renderer = new THREE.WebGLRenderer({
    antialias: !isMobile,  // スマホはfalse(パフォーマンス優先)
    alpha: true
  });
  renderer.setSize(container.offsetWidth, container.offsetHeight);
  container.appendChild(renderer.domElement);

  // シーン・カメラ
  var scene = new THREE.Scene();
  var camera = new THREE.PerspectiveCamera(60, container.offsetWidth / container.offsetHeight, 1, 5000);
  camera.position.set(0, 80, 600);

  // OrbitControls(ドラッグ回転・ズーム)
  var controls = new THREE.OrbitControls(camera, renderer.domElement);
  controls.enableDamping = true;  // 慣性
  controls.autoRotate = true;     // 自動回転
  controls.autoRotateSpeed = 0.3;

  // 照明
  scene.add(new THREE.AmbientLight(0x222244, 0.8));
  scene.add(new THREE.PointLight(0x9369A8, 0.5, 800)); // 銀河中心グロー
}

つまずきポイント: renderer.setSize()にコンテナのサイズを渡す必要がある。window.innerWidthを渡すと全画面になるが、ページの一部に配置したい場合はコンテナのサイズを使う。


Step 5: 球体の生成とクリックイベント

var starMeshes = [];

BODIES.forEach(function(body) {
  // ログスケールでサイズを計算
  var normalized = normalizeLog(body.valUSD, 0.1, 5000);
  var radius = 0.3 + normalized * 4.2;

  // スマホはポリゴン数を下げる
  var segs = isMobile ? [8, 6] : [32, 16];
  var geo = new THREE.SphereGeometry(radius, segs[0], segs[1]);

  // カテゴリ別カラー
  var color = getCategoryColor(body.catKey);
  var mat = new THREE.MeshStandardMaterial({
    color: color,
    emissive: color,
    emissiveIntensity: 0.4
  });

  var mesh = new THREE.Mesh(geo, mat);
  mesh.userData.bodyId = body.id;  // クリック時に使う
  scene.add(mesh);
  starMeshes.push(mesh);
});

// Raycaster(クリック判定)
var raycaster = new THREE.Raycaster();
var mouse = new THREE.Vector2();

renderer.domElement.addEventListener('click', function(e) {
  var rect = renderer.domElement.getBoundingClientRect();
  // canvasのオフセットを補正(ページ内に配置している場合に必要)
  mouse.x = ((e.clientX - rect.left) / rect.width) * 2 - 1;
  mouse.y = -((e.clientY - rect.top) / rect.height) * 2 + 1;

  raycaster.setFromCamera(mouse, camera);
  var hits = raycaster.intersectObjects(starMeshes);
  if (hits.length > 0) {
    var bodyId = hits[0].object.userData.bodyId;
    showDetail(bodyId); // 詳細パネルを表示する関数
  }
});

つまずきポイント: Raycasterの座標計算にgetBoundingClientRect()が必要。Three.jsの公式サンプルは全画面前提が多いので、ページ内配置の場合は必ずrect.leftrect.topで補正する。


Step 6: アニメーションループ

var animId;

function animate() {
  animId = requestAnimationFrame(animate);
  controls.update(); // damping有効時は毎フレーム必要
  updateOrbits();    // 衛星の公転位置を更新
  renderer.render(scene, camera);
}

// タブ非表示でアニメーション停止(バッテリー節約)
document.addEventListener('visibilitychange', function() {
  if (document.hidden) {
    cancelAnimationFrame(animId);
  } else {
    animate();
  }
});

animate();

つまずきポイント: controls.enableDamping = trueにした場合、controls.update()を毎フレーム呼ばないと慣性が効かない。忘れがちなポイント。


つまずきポイントまとめ

  • 外部スクリプトが読み込まれない: SWELLでは<script src="...">が無視される。document.createElement('script')で動的ロードに切り替える
  • &&演算子でスクリプト全体が止まる: WordPress(wpautop)が&&を壊す。三項演算子かネストifに書き換える
  • クリック座標がずれる: Three.jsのcanvasをページ内に配置している場合、getBoundingClientRect()でオフセット補正が必要
  • スマホが重い: SphereGeometryの分割数をスマホでは(8,6)に下げる。PCの(32,16)から約16分の1のポリゴン数になる
  • OrbitControlsが動かない: controls.update()をアニメーションループ内で毎フレーム呼ぶ必要がある

参考

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?