Newer
Older
SmTIAS-LightSim / tests / scene / test_materials.py
"""scene/materials.py の仕様ベーステスト.

仕様出典:
- docs/04_SPEC/SPEC_02_シーン定義仕様.md(materials セクション)
- docs/03_PLAN/PLAN_02_開発ステップ.md(Step 4: scene/materials.py の役割)

build_material_dict の戻り値 dict 構造のみを検証する.Mitsuba の load_dict は呼ばない.
"""

from __future__ import annotations

import pytest

from sm_tias_lightsim.scene.materials import build_material_dict
from sm_tias_lightsim.scene.schema import (
    DielectricMaterial,
    DiffuseMaterial,
    ThinDielectricMaterial,
)

# ---------------------------------------------------------------------------
# DiffuseMaterial
# ---------------------------------------------------------------------------


def test_diffuse_material_type_is_diffuse():
    """DiffuseMaterial の dict type が "diffuse" であること.

    仕様: type: diffuse → Mitsuba BSDF "diffuse" (SPEC_02 materials セクション)
    """
    mat = DiffuseMaterial(type="diffuse", reflectance=0.5)
    result = build_material_dict(mat)
    assert result["type"] == "diffuse"


def test_diffuse_scalar_reflectance_expands_to_rgb():
    """スカラ reflectance が RGB 3 要素リストに展開されること.

    仕様: reflectance スカラ可 (SPEC_02 materials セクション white_lambert: 0.95 例)
    """
    mat = DiffuseMaterial(type="diffuse", reflectance=0.95)
    result = build_material_dict(mat)
    assert result["reflectance"]["type"] == "rgb"
    assert result["reflectance"]["value"] == pytest.approx([0.95, 0.95, 0.95])


def test_diffuse_rgb_reflectance_preserved():
    """RGB reflectance がそのまま保持されること.

    仕様: reflectance: [0.05, 0.05, 0.05] の RGB 表記 (SPEC_02 materials セクション)
    """
    mat = DiffuseMaterial(type="diffuse", reflectance=(0.05, 0.05, 0.05))
    result = build_material_dict(mat)
    assert result["reflectance"]["type"] == "rgb"
    assert result["reflectance"]["value"] == pytest.approx([0.05, 0.05, 0.05])


def test_diffuse_reflectance_has_rgb_type_key():
    """DiffuseMaterial の reflectance dict に type="rgb" キーが存在すること.

    仕様: Mitsuba RGB スペクトル型の構造 (SPEC_01 scene/materials 責務)
    """
    mat = DiffuseMaterial(type="diffuse", reflectance=1.0)
    result = build_material_dict(mat)
    assert "type" in result["reflectance"]
    assert "value" in result["reflectance"]


# ---------------------------------------------------------------------------
# DielectricMaterial
# ---------------------------------------------------------------------------


def test_dielectric_material_type_is_dielectric():
    """DielectricMaterial の dict type が "dielectric" であること.

    仕様: type: dielectric → Mitsuba BSDF "dielectric" (SPEC_02 materials セクション)
    """
    mat = DielectricMaterial(type="dielectric", int_ior=1.5, ext_ior=1.0)
    result = build_material_dict(mat)
    assert result["type"] == "dielectric"


def test_dielectric_int_ior_in_dict():
    """DielectricMaterial の int_ior が dict に格納されること.

    仕様: dielectric → {"type": "dielectric", "int_ior": ..., "ext_ior": ...}
    (SPEC_01 scene/materials 責務)
    """
    mat = DielectricMaterial(type="dielectric", int_ior=1.5, ext_ior=1.0)
    result = build_material_dict(mat)
    assert result["int_ior"] == pytest.approx(1.5)


def test_dielectric_ext_ior_in_dict():
    """DielectricMaterial の ext_ior が dict に格納されること.

    仕様: dielectric → {"type": "dielectric", "int_ior": ..., "ext_ior": ...}
    (SPEC_01 scene/materials 責務)
    """
    mat = DielectricMaterial(type="dielectric", int_ior=1.5, ext_ior=1.0)
    result = build_material_dict(mat)
    assert result["ext_ior"] == pytest.approx(1.0)


def test_dielectric_ior_values_preserved():
    """DielectricMaterial の IOR 値が正確に dict に格納されること.

    仕様: PMMA 屈折率 int_ior: 1.49 例 (SPEC_02 materials セクション)
    """
    mat = DielectricMaterial(type="dielectric", int_ior=1.49, ext_ior=1.0003)
    result = build_material_dict(mat)
    assert result["int_ior"] == pytest.approx(1.49)
    assert result["ext_ior"] == pytest.approx(1.0003)


# ---------------------------------------------------------------------------
# ThinDielectricMaterial
# ---------------------------------------------------------------------------


def test_thindielectric_material_type_is_thindielectric():
    """ThinDielectricMaterial の dict type が "thindielectric" であること.

    仕様: type: thindielectric → Mitsuba BSDF "thindielectric" (SPEC_02 materials セクション)
    """
    mat = ThinDielectricMaterial(type="thindielectric", int_ior=1.49, ext_ior=1.0)
    result = build_material_dict(mat)
    assert result["type"] == "thindielectric"


def test_thindielectric_int_ior_in_dict():
    """ThinDielectricMaterial の int_ior が dict に格納されること.

    仕様: thindielectric → {"type": "thindielectric", "int_ior": ..., "ext_ior": ...}
    (SPEC_01 scene/materials 責務)
    """
    mat = ThinDielectricMaterial(type="thindielectric", int_ior=1.49, ext_ior=1.0)
    result = build_material_dict(mat)
    assert result["int_ior"] == pytest.approx(1.49)


def test_thindielectric_ext_ior_in_dict():
    """ThinDielectricMaterial の ext_ior が dict に格納されること.

    仕様: thindielectric → {"type": "thindielectric", "int_ior": ..., "ext_ior": ...}
    (SPEC_01 scene/materials 責務)
    """
    mat = ThinDielectricMaterial(type="thindielectric", int_ior=1.49, ext_ior=1.0)
    result = build_material_dict(mat)
    assert result["ext_ior"] == pytest.approx(1.0)


def test_thindielectric_does_not_have_reflectance():
    """ThinDielectricMaterial の dict に reflectance キーが含まれないこと.

    仕様: thindielectric は IOR のみで反射率を持たない (SPEC_02 materials セクション)
    """
    mat = ThinDielectricMaterial(type="thindielectric", int_ior=1.49, ext_ior=1.0)
    result = build_material_dict(mat)
    assert "reflectance" not in result