ShinBlog for Programmer and Engineer

【Three.js】3DCGで表示した地球の指定位置にピンを指す方法を解説

プログラミング

どうも、shincodeです。

今回はThree.jsで3DCGで表示した地球の指定位置にピンを指す方法を解説します。あまり情報が落ちていなかったので記事にしました。

3DCGで表示した地球の指定位置にピンを指す方法

↑こんな感じでピンをさせます。

必要なものは「ピンを置きたい場所の緯度・経度」の情報です。僕はこんな感じで別ファイルで用意しました↓

//countriesData.js 
const countries = [
  {
    name: "アメリカ",
    lat: 38.8954503,
    lon: -77.0158701,
    link: "https://example.com",
    flag: "http://localhost:8080/wordpress/wp-content/uploads/2023/08/america_flag.png",
  },
  {
    name: "アラブ",
    lat: 24.487729,
    lon: 54.3787498,
    link: "https://example.com",
    flag: "http://localhost:8080/wordpress/wp-content/uploads/2023/08/arab_flag.png",
  }];

window.countries = countries;

ここでは適当にアメリカとアラブの緯度と経度を置いています。latとlonのとこですね。linkやflagは各自適当においてみてください。ピンをクリックして違うリンクに飛ばすとか、国旗の画像を出してみる、とか後で使うものを準備します。

用意出来たら、Three.jsの記述を書いているファイルにcontries変数を読み込ませる必要があります。fucntions.php等に以下を記述して読み込ませてみましょう。

//functions.php
//...他の記述は省略
function load_custom_threejs_script()
{
	if (is_page(8)) {
		// countriesData.js をモジュールとして読み込む
		wp_enqueue_script('countries-data', get_template_directory_uri() . '/js/countriesData.js', array(), '1.0', true);
		wp_script_add_data('countries-data', 'type', 'module');

		wp_enqueue_script('custom-threejs', get_template_directory_uri() . '/js/threejs-test.js', array('threejs'), '1.0', true);
		wp_script_add_data('custom-threejs', 'type', 'module');  // これを追加
	}
}
add_action('wp_enqueue_scripts', 'load_custom_threejs_script');

function load_custom_styles()
{
	if (is_page(8)) {
		// スタイルシートをキューに追加
		wp_enqueue_style('custom-css', get_template_directory_uri() . '/css/style.css', array(), '1.0');
	}
}
add_action('wp_enqueue_scripts', 'load_custom_styles');


function load_threejs()
{
	wp_enqueue_script('countries-data', get_template_directory_uri() . '/js/countriesData.js', array('threejs'), '1.0', true);
	wp_enqueue_script('custom-threejs', get_template_directory_uri() . '/js/threejs-test.js', array('threejs', 'countries-data'), '1.0', true);

	// Three.js の読み込み
	wp_enqueue_script('threejs', 'https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js', array(), 'r128');
	// OrbitControls の読み込み
	wp_enqueue_script('orbitcontrols', 'https://unpkg.com/three@0.142.0/examples/js/controls/OrbitControls.js', array('threejs'), 'r128');
}
add_action('wp_enqueue_scripts', 'load_threejs');

Three.jsライブラリの読み込みと、threejs-test.jsというファイルに読み込ませるように記述をしています。また、cssファイルも読み込ませるように記述しています。必要に応じて変更、または必要ないであれば削除してOKです。

3DCGで表示した地球の指定位置にピンを指す方法

ピンを指す方法が以下になります。該当箇所だけコードを示しておきます。

//threejs-test.js 
  //ピンの配置
  const pins = countries.map((country) => {
    // 球の形状を作成
    const pinGeometry = new THREE.CircleGeometry(0.043, 32);
    const pinMaterial = new THREE.MeshBasicMaterial({
      color: "#C9550D",
      side: THREE.DoubleSide,
      depthWrite: true,
    });

    const pin = new THREE.Mesh(pinGeometry, pinMaterial);

    // White circle with a larger radius
    const whiteCircleGeometry = new THREE.CircleGeometry(0.051, 32);
    const whiteCircleMaterial = new THREE.MeshBasicMaterial({
      color: "#FFFFFF",
      side: THREE.DoubleSide,
    });

    const whiteCircle = new THREE.Mesh(
      whiteCircleGeometry,
      whiteCircleMaterial
    );

    // 当たり判定用の透明な円を作成
    const hitAreaGeometry = new THREE.CircleGeometry(0.18, 32); // 半径を大きく
    const hitAreaMaterial = new THREE.MeshBasicMaterial({
      transparent: true,
      opacity: 0,
    }); // 透明
    const hitArea = new THREE.Mesh(hitAreaGeometry, hitAreaMaterial);

    // ここでピンと白い円の局所的なZオフセットを設定
    pin.position.set(0, 0, -0.00098); // Change these offsets
    whiteCircle.position.set(0, 0, -0.0009); // Change these offsets

    // Create a group and add the pin and white circle to it
    const group = new THREE.Group();
    group.add(pin);
    group.add(whiteCircle);
    group.add(hitArea); // これを追加

    // 緯度と経度から位置を計算
    const radius = 1.5;
    const latRad = (country.lat * Math.PI) / 180;
    const lonRad = (-country.lon * Math.PI) / 180;

    // Set the position of the group
    const x = radius * Math.cos(latRad) * Math.cos(lonRad);
    const y = radius * Math.sin(latRad);
    const z = radius * Math.cos(latRad) * Math.sin(lonRad);
    group.position.set(x, y, z);

    // Make the group look at the earth's center
    group.lookAt(new THREE.Vector3(0, 0, 0));

    // Store related data in userData
    pin.userData = country;
    whiteCircle.userData = country;
    hitArea.userData = country; // これも追加
    group.userData = country;

    pin.renderOrder = 1;
    whiteCircle.renderOrder = 2;

    // Add the group to the earth
    earth.add(group);

    return group; // return the group instead of just the pin
  });

ピンの形状はThree.jsライブラリのcircleGeometoryを2つ重ねて利用しています。z-fightingを防ぐためにz座標の位置を少しずらして設置しています。

また、透明なhitAreaを追加することであたり判定を大きくしています。これは各自大きさを調整してみてください。ピンをクリックしてツールチップを表示させる仕組みについては各自実装してみてください。ChatGPTに聞いたら分かります。

今回は以上になります。