#!python # -*- mode: python; Encoding: utf-8; coding: utf-8 -*- # Last updated: <2026/07/07 13:31:33 +0900> """ スプライトで球を表示するサンプル(オーダリングテーブル版・半径可変モデル)。 Google Gemini に作ってもらった。 Windows11 x64 25H2 + Python 3.10.10 64bit """ import pygame import sys import math import asyncio # 初期設定 pygame.init() WIDTH, HEIGHT = 1280, 720 screen = pygame.display.set_mode((WIDTH, HEIGHT)) pygame.display.set_caption("3D Point Cloud Sphere with Sprites (Ordering Table)") clock = pygame.time.Clock() # --- 球体上の点群(3D座標)を生成 --- NUM_POINTS = 256 BASE_RADIUS = 300 # 基準となる半径 PULSE_AMPLITUDE = 200 # 半径の振り幅 # 最初は半径 1.0 の単位球として座標を保持しておくと、後から半径を掛け算しやすくなります base_points = [] phi = math.pi * (math.sqrt(5.0) - 1.0) for i in range(NUM_POINTS): y = 1.0 - (i / float(NUM_POINTS - 1)) * 2.0 radius_at_y = math.sqrt(1.0 - y * y) theta = phi * i x = math.cos(theta) * radius_at_y z = math.sin(theta) * radius_at_y # 単位球の座標として格納 base_points.append([x, y, z]) # --- オーダリングテーブルの設定 --- TABLE_SIZE = 200 # 解像度(バケツの数) async def main(): # 画像の読み込み try: sprite_image = pygame.image.load("./image.png").convert_alpha() except Exception: print("image.png が見つからないため、仮のスプライトを作成します。") sprite_image = pygame.Surface((32, 32), pygame.SRCALPHA) pygame.draw.circle(sprite_image, (255, 0, 0), (16, 16), 16) pygame.draw.circle(sprite_image, (255, 255, 255), (12, 12), 4) SPRITE_W, SPRITE_H = sprite_image.get_size() angle_y = 0 angle_x = 0 angle_radius = 0 # 半径変化用のラジアン値 running = True while running: for event in pygame.event.get(): if event.type == pygame.QUIT: running = False elif event.type == pygame.KEYDOWN: if event.key == pygame.K_ESCAPE: running = False elif event.key == pygame.K_q: running = False # 画面のクリア screen.fill((15, 15, 15)) # アニメーション速度 angle_y += 0.015 angle_x += 0.001 angle_radius += 0.01 # 半径が変化するスピード(好みに応じて調整) # --- 現在のフレームの半径を計算 --- # BASE_RADIUS を中心に、±PULSE_AMPLITUDE の範囲で波打つ current_radius = BASE_RADIUS + math.sin(angle_radius) * PULSE_AMPLITUDE # --- オーダリングテーブルの初期化 --- ordering_table = [[] for _ in range(TABLE_SIZE)] # 3次元回転と座標変換、およびテーブルへの登録 for pt in base_points: # 単位球の座標に、現在のフレームの半径を掛け合わせる x = pt[0] * current_radius y = pt[1] * current_radius z = pt[2] * current_radius # Y軸周りの回転 cos_y, sin_y = math.cos(angle_y), math.sin(angle_y) x1 = x * cos_y - z * sin_y z1 = x * sin_y + z * cos_y # X軸周りの回転 cos_x, sin_x = math.cos(angle_x), math.sin(angle_x) y2 = y * cos_x - z1 * sin_x z2 = y * sin_x + z1 * cos_x # --- Z値をテーブルのインデックスに変換 --- # z2 はおおよそ -current_radius から +current_radius の範囲。 norm_z = (z2 + current_radius) / (2 * current_radius) # 念のため範囲外をクリップ table_idx = int(norm_z * (TABLE_SIZE - 1)) table_idx = max(0, min(TABLE_SIZE - 1, table_idx)) # 画面座標の計算 screen_x = int(x1 + (WIDTH // 2) - (SPRITE_W // 2)) screen_y = int(y2 + (HEIGHT // 2) - (SPRITE_H // 2)) # テーブルの該当する深度のバケツに追加 ordering_table[table_idx].append((screen_x, screen_y)) # --- 描画処理 (奥から手前へ) --- for bucket in ordering_table: for screen_x, screen_y in bucket: screen.blit(sprite_image, (screen_x, screen_y)) # 画面更新 pygame.display.flip() clock.tick(60) await asyncio.sleep(0) pygame.quit() sys.exit() if __name__ == "__main__": asyncio.run(main())