feat(3D): 353-3 3D 등고선 주곡선 5 m 고정 · 1 m 옅게 — 평면 색 토큰 · B05 · B04 같은 재질
- 새 공용 재질 ui_template_contour3d(isMajorContour · contourMaterials) — 주곡선 = 5 m 배수(간격 무관) - 색 = --map-contour-major(진하게 0.95) · --map-contour-minor(토큰 알파 0.45 · 깊이 안 씀) - B05 3D 뷰어 · B04 3D 지형 뷰어 둘 다 옛 주황 두 색 · 간격 × 5 갈래 걷음 · B04 높이 글도 5 m 선에만 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Se7ytqbohmBQnfYpwYFnYf
This commit is contained in:
@@ -10,6 +10,7 @@
|
||||
import * as THREE from "three";
|
||||
|
||||
import { contourAssetUrl, fetchCachedJson } from "../A00_Common/b_asset_cache";
|
||||
import { contourMaterials, isMajorContour } from "@ui/ui_template_contour3d";
|
||||
|
||||
/** 화면에 띄우는 등고 라벨 상한 — 긴 등고선부터 채운다(분리 전 상수 그대로). */
|
||||
const MAX_CONTOUR_LABELS = 40;
|
||||
@@ -93,7 +94,7 @@ export async function loadContourLinesInto(
|
||||
const points = transform(c.coordinates);
|
||||
if (points.length < 2) return;
|
||||
|
||||
const isMajor = c.level % (interval * 5) === 0;
|
||||
const isMajor = isMajorContour(c.level);
|
||||
const bucket = isMajor ? majorPoints : minorPoints;
|
||||
for (let i = 0; i < points.length - 1; i++) {
|
||||
bucket.push(points[i], points[i + 1]);
|
||||
@@ -156,18 +157,15 @@ export async function loadContourLinesInto(
|
||||
ctx.setLabelsDirty(true);
|
||||
}
|
||||
|
||||
// 합쳐 둔 점들을 주곡선·보조곡선 각각 한 덩어리로 올린다.
|
||||
// 합쳐 둔 점들을 주곡선·보조곡선 각각 한 덩어리로 올린다 — 주곡선 = 5 m 고정 · 1 m 는
|
||||
// 옅게(평면 그림과 같은 색 토큰 · 353-3).
|
||||
const materials = contourMaterials();
|
||||
[
|
||||
{ points: minorPoints, color: 0xf59e0b },
|
||||
{ points: majorPoints, color: 0xd97706 },
|
||||
].forEach(({ points, color }) => {
|
||||
{ points: minorPoints, material: materials.minor },
|
||||
{ points: majorPoints, material: materials.major },
|
||||
].forEach(({ points, material }) => {
|
||||
if (points.length === 0) return;
|
||||
const geometry = new THREE.BufferGeometry().setFromPoints(points);
|
||||
const material = new THREE.LineBasicMaterial({
|
||||
color,
|
||||
transparent: true,
|
||||
opacity: 0.8,
|
||||
});
|
||||
ctx.contourGroup.add(new THREE.LineSegments(geometry, material));
|
||||
});
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import * as THREE from "three";
|
||||
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
|
||||
import { createTerrainCompass } from "@ui/ui_template_compass";
|
||||
import { contourMaterials, isMajorContour } from "@ui/ui_template_contour3d";
|
||||
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
|
||||
import { PLYLoader } from "three/examples/jsm/loaders/PLYLoader.js";
|
||||
import { API_BASE_URL } from "@config/config_frontend";
|
||||
@@ -339,21 +340,20 @@ export function createRouteViewer(): RouteViewer {
|
||||
([x, y, z]) => new THREE.Vector3(x - cx, z - cz + 0.15, -(y - cy)),
|
||||
);
|
||||
if (points.length < 2) return;
|
||||
const bucket = contour.level % (interval * 5) === 0 ? majorPoints : minorPoints;
|
||||
const bucket = isMajorContour(contour.level) ? majorPoints : minorPoints;
|
||||
for (let index = 0; index < points.length - 1; index += 1) {
|
||||
bucket.push(points[index], points[index + 1]);
|
||||
}
|
||||
});
|
||||
// 주곡선 = 5 m 고정 · 1 m 는 옅게(평면 그림과 같은 색 토큰 · 353-3)
|
||||
const materials = contourMaterials();
|
||||
[
|
||||
{ points: minorPoints, color: 0xf59e0b },
|
||||
{ points: majorPoints, color: 0xd97706 },
|
||||
].forEach(({ points, color }) => {
|
||||
{ points: minorPoints, material: materials.minor },
|
||||
{ points: majorPoints, material: materials.major },
|
||||
].forEach(({ points, material }) => {
|
||||
if (points.length === 0) return;
|
||||
contours.add(
|
||||
new THREE.LineSegments(
|
||||
new THREE.BufferGeometry().setFromPoints(points),
|
||||
new THREE.LineBasicMaterial({ color, transparent: true, opacity: 0.75 }),
|
||||
),
|
||||
new THREE.LineSegments(new THREE.BufferGeometry().setFromPoints(points), material),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
"""353-3 3D 등고선 — 주곡선 5 m 고정 · 1 m 옅게 · 평면 색 토큰(B05 · B04 3D 같은 재질).
|
||||
|
||||
./venv/Scripts/python.exe -m pytest resources/tester/test_353_3_contour3d_style.py -q
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
STYLE = ROOT / "ui_template" / "ui_template_contour3d.ts"
|
||||
VIEWERS = [
|
||||
ROOT / "B05_Profile" / "B05_Profile_UI_Viewer.ts",
|
||||
ROOT / "B04_PreProcess" / "B04_PreProcess_UI_TerrainViewer_Contours.ts",
|
||||
]
|
||||
|
||||
|
||||
def test_재질은_평면_색_토큰과_5m_고정() -> None:
|
||||
src = STYLE.read_text("utf-8")
|
||||
assert '"--map-contour-major"' in src and '"--map-contour-minor"' in src
|
||||
assert "CONTOUR_MAJOR_STEP_M = 5;" in src
|
||||
# 1 m 선 불투명 = 토큰 알파(0.45) · 주곡선은 진하게
|
||||
assert "opacity: minor.alpha" in src and "opacity: 0.95 * major.alpha" in src
|
||||
|
||||
|
||||
def test_두_3D_뷰어가_같은_재질을_씀() -> None:
|
||||
for path in VIEWERS:
|
||||
src = path.read_text("utf-8")
|
||||
assert 'from "@ui/ui_template_contour3d"' in src, path.name
|
||||
assert "isMajorContour(" in src and "contourMaterials()" in src, path.name
|
||||
# 옛 갈래(간격 × 5 · 주황 두 색) 없음
|
||||
assert "interval * 5" not in src and "0xf59e0b" not in src, path.name
|
||||
@@ -0,0 +1,49 @@
|
||||
/* =============================================================================
|
||||
* ui_template_contour3d.ts
|
||||
* 3D 등고선 선 재질 — 평면 그림(노선 편집)과 같은 갈래 · 같은 색 토큰(353-3).
|
||||
*
|
||||
* 주곡선 = 5 m 배수 고정(간격 무관 · 평면 `B05_Profile_UI_RouteEdit_Render` 와 같음) ·
|
||||
* 색 `--map-contour-major` 진하게 · 나머지(1 m) = `--map-contour-minor` 옅게 · 낮은 불투명.
|
||||
* B05 3D(`B05_Profile_UI_Viewer`) · B04 3D(`B04_PreProcess_UI_TerrainViewer_Contours`) 가 함께 씀.
|
||||
* ========================================================================== */
|
||||
|
||||
import * as THREE from "three";
|
||||
import { themeColor } from "./ui_template_palette";
|
||||
|
||||
/** 주곡선 간격(m) — 평면 그림과 같은 5 m. */
|
||||
export const CONTOUR_MAJOR_STEP_M = 5;
|
||||
|
||||
/** 5 m 배수 높이면 주곡선. */
|
||||
export function isMajorContour(level: number): boolean {
|
||||
return Math.abs(level - Math.round(level / CONTOUR_MAJOR_STEP_M) * CONTOUR_MAJOR_STEP_M) < 1e-6;
|
||||
}
|
||||
|
||||
/** CSS 색(#rrggbb · rgba(...)) → 색 + 불투명(알파 없으면 1). */
|
||||
function parseColor(value: string): { color: THREE.Color; alpha: number } {
|
||||
const rgba = value.match(/rgba?\(([^)]+)\)/);
|
||||
if (!rgba) return { color: new THREE.Color(value), alpha: 1 };
|
||||
const [r, g, b, a] = rgba[1].split(",").map((part) => Number(part.trim()));
|
||||
return { color: new THREE.Color(r / 255, g / 255, b / 255), alpha: Number.isFinite(a) ? a : 1 };
|
||||
}
|
||||
|
||||
/** 주곡선 · 1 m 선 재질 — 1 m 는 주곡선보다 옅게. */
|
||||
export function contourMaterials(): {
|
||||
major: THREE.LineBasicMaterial;
|
||||
minor: THREE.LineBasicMaterial;
|
||||
} {
|
||||
const major = parseColor(themeColor("--map-contour-major", "#818cf8"));
|
||||
const minor = parseColor(themeColor("--map-contour-minor", "rgba(165, 180, 252, 0.45)"));
|
||||
return {
|
||||
major: new THREE.LineBasicMaterial({
|
||||
color: major.color,
|
||||
transparent: true,
|
||||
opacity: 0.95 * major.alpha,
|
||||
}),
|
||||
minor: new THREE.LineBasicMaterial({
|
||||
color: minor.color,
|
||||
transparent: true,
|
||||
opacity: minor.alpha,
|
||||
depthWrite: false,
|
||||
}),
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user