Skip to content

Instantly share code, notes, and snippets.

@yasuhito
Last active July 11, 2026 03:03
Show Gist options
  • Select an option

  • Save yasuhito/a19ecdcfa2f2e7d216b1e3713d4d1999 to your computer and use it in GitHub Desktop.

Select an option

Save yasuhito/a19ecdcfa2f2e7d216b1e3713d4d1999 to your computer and use it in GitHub Desktop.
qa2-love2d: diamond SWAP 承認前試作(通常幅・最大幅比較)

diamond SWAP 試作

GitHub Issue「diamond SWAP の試作を確認する」用の承認前試作です。本実装ではありません。

  • 端点: 端点自体をファセット付きの X 形状にする
  • X の腕幅: 初版から 20% 縮小した細線版
  • 接続線の中心: X 正面中心と奥行きをそろえ、透視投影後も上下位置が一致する
  • 左右: 同寸・対称
  • 色: CNOT と同じオレンジ系。試作は簡易 PBR 材質で、本実装では専用 diamond_SWAP シェーダと独立色設定を追加する
  • 比較: 通常幅(2セル)と最大幅(6セル)

生成物は prototype_diamond_swap.py で一時モデルセットを作り、qa2-love2d の実ゲーム画面を Playwright(ヘッドレス)で撮影して比較しました。

Display the source blob
Display the rendered blob
Raw
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
#!/usr/bin/env python3
"""PROTOTYPE — X 形 diamond SWAP の人間確認用 GLB。本実装には使わない。"""
import math
import os
import sys
sys.path.insert(0, os.path.abspath("docs/poc"))
import make_diamond_gate as gate
from make_diamond_cnot import GltfBuilder
ENDPOINT_SPAN = 1.5
LEFT_X = -ENDPOINT_SPAN / 2
RIGHT_X = ENDPOINT_SPAN / 2
ARM_LENGTH = 0.86
ARM_WIDTH = 0.16
ARM_BEVEL = 0.07
ARM_DEPTH = 0.20
BOND_RADIUS = 0.045
BOND_SIDES = 8
BOND_CENTER_Z = ARM_DEPTH * 0.78
def transform_point(point, angle, center_x, z_offset=0.0):
x, y, z = point
cosine = math.cos(angle)
sine = math.sin(angle)
return (x * cosine - y * sine + center_x, x * sine + y * cosine, z + z_offset)
def arm_outline(scale=1.0):
half_length = ARM_LENGTH * scale / 2
half_width = ARM_WIDTH * scale / 2
bevel = min(ARM_BEVEL * scale, half_length * 0.45, half_width * 0.8)
return [
(-half_length + bevel, -half_width),
(half_length - bevel, -half_width),
(half_length, -half_width + bevel),
(half_length, half_width - bevel),
(half_length - bevel, half_width),
(-half_length + bevel, half_width),
(-half_length, half_width - bevel),
(-half_length, -half_width + bevel),
]
def gem_arm_mesh(angle, center_x, z_offset):
outer = arm_outline(1.0)
inner = arm_outline(0.76)
positions = []
normals = []
indices = []
def add_triangle(a, b, c):
start = len(positions)
world = [
transform_point(a, angle, center_x, z_offset),
transform_point(b, angle, center_x, z_offset),
transform_point(c, angle, center_x, z_offset),
]
normal = gate.face_normal(*world)
positions.extend(world)
normals.extend([normal] * 3)
indices.extend([start, start + 1, start + 2])
outer_front_z = 0.02
inner_front_z = ARM_DEPTH / 2
crown_z = ARM_DEPTH * 0.78
back_z = -ARM_DEPTH / 2
# 正面中央を 8 枚の三角ファセットにする。
for index in range(8):
next_index = (index + 1) % 8
add_triangle((0, 0, crown_z), (*inner[index], inner_front_z), (*inner[next_index], inner_front_z))
# 正面外周の面取り。
for index in range(8):
next_index = (index + 1) % 8
add_triangle((*outer[index], outer_front_z), (*inner[index], inner_front_z), (*inner[next_index], inner_front_z))
add_triangle((*outer[index], outer_front_z), (*inner[next_index], inner_front_z), (*outer[next_index], outer_front_z))
# 側面と背面。
for index in range(8):
next_index = (index + 1) % 8
add_triangle((*outer[index], back_z), (*outer[index], outer_front_z), (*outer[next_index], outer_front_z))
add_triangle((*outer[index], back_z), (*outer[next_index], outer_front_z), (*outer[next_index], back_z))
add_triangle((0, 0, back_z), (*outer[next_index], back_z), (*outer[index], back_z))
return positions, normals, indices
def merge_meshes(meshes):
positions = []
normals = []
indices = []
for mesh_positions, mesh_normals, mesh_indices in meshes:
offset = len(positions)
positions.extend(mesh_positions)
normals.extend(mesh_normals)
indices.extend(index + offset for index in mesh_indices)
return positions, normals, indices
def endpoint_mesh(center_x):
return merge_meshes(
[
gem_arm_mesh(math.radians(45), center_x, -0.025),
gem_arm_mesh(math.radians(-45), center_x, 0.025),
]
)
def faceted_bond_mesh(min_x, max_x, radius, sides, center_z):
positions = []
normals = []
indices = []
for index in range(sides):
angle_a = math.tau * index / sides
angle_b = math.tau * (index + 1) / sides
ya, za = math.cos(angle_a) * radius, center_z + math.sin(angle_a) * radius
yb, zb = math.cos(angle_b) * radius, center_z + math.sin(angle_b) * radius
start = len(positions)
vertices = [(min_x, ya, za), (max_x, ya, za), (max_x, yb, zb), (min_x, yb, zb)]
normal = gate.face_normal(vertices[0], vertices[1], vertices[2])
positions.extend(vertices)
normals.extend([normal] * 4)
indices.extend([start, start + 1, start + 2, start, start + 2, start + 3])
return positions, normals, indices
def build(output_path):
builder = GltfBuilder()
left = endpoint_mesh(LEFT_X)
right = endpoint_mesh(RIGHT_X)
bond = faceted_bond_mesh(LEFT_X, RIGHT_X, BOND_RADIUS, BOND_SIDES, BOND_CENTER_Z)
meshes = [
{"name": "SWAP_x0_diamond_mesh", "primitives": [builder.primitive(*left, 0)]},
{"name": "SWAP_x1_diamond_mesh", "primitives": [builder.primitive(*right, 0)]},
{"name": "SWAP_bond_bar_mesh", "primitives": [builder.primitive(*bond, 0)]},
]
gltf = {
"asset": {"version": "2.0", "generator": "PROTOTYPE prototype_diamond_swap.py"},
"scene": 0,
"scenes": [{"nodes": [0, 1, 2]}],
"nodes": [
{"mesh": 0, "name": "SWAP_x0_diamond"},
{"mesh": 1, "name": "SWAP_x1_diamond"},
{"mesh": 2, "name": "SWAP_bond_bar"},
],
"meshes": meshes,
"materials": [
{
"name": "prototype_diamond_SWAP",
"alphaMode": "OPAQUE",
"emissiveFactor": [1.0, 0.62, 0.18],
"pbrMetallicRoughness": {
"baseColorFactor": [1.0, 0.62, 0.18, 1.0],
"metallicFactor": 0.7,
"roughnessFactor": 0.28,
},
}
],
"bufferViews": builder.buffer_views,
"accessors": builder.accessors,
"buffers": [{"byteLength": len(builder.bin_chunk)}],
}
gate.save_glb(output_path, 2, gltf, bytes(builder.bin_chunk))
if __name__ == "__main__":
if len(sys.argv) != 2:
raise SystemExit("usage: prototype_diamond_swap.py OUTPUT.glb")
os.makedirs(os.path.dirname(sys.argv[1]), exist_ok=True)
build(sys.argv[1])
print(sys.argv[1])
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment