66 * 宪法第一条接入:glowScale 映射学习数据(掌握度=亮度),域 0.6–1.15。
77 * 宪法第四条:三级性能降级嵌入(high/medium/low)。
88 */
9- import { useRef , useMemo , useEffect } from 'react' ;
9+ import { useRef , useMemo } from 'react' ;
1010import { useFrame } from '@react-three/fiber' ;
1111import * as THREE from 'three' ;
1212import { Float , Html } from '@react-three/drei' ;
@@ -50,18 +50,80 @@ function ModuleGeometry({ geometry }: { geometry: GeometryType }) {
5050 }
5151}
5252
53- /** 闪卡专用 */
54- function FlashcardGeometry ( { textures } : { textures ?: { mapA ?: THREE . CanvasTexture ; mapB ?: THREE . CanvasTexture } | null } ) {
53+ /**
54+ * 闪卡专用:保留平面卡片造型,与其他模块一致的交互表现
55+ * 矩形辉光线框(透明度逻辑同 WireframeGlow)+ 自转 + 悬停/激活缩放与发光增强
56+ */
57+ function FlashcardGeometry ( { textures, emissiveColor, isActive, isHovered } : {
58+ textures ?: { mapA ?: THREE . CanvasTexture ; mapB ?: THREE . CanvasTexture } | null ;
59+ emissiveColor : string ;
60+ isActive : boolean ;
61+ isHovered : boolean ;
62+ } ) {
63+ const groupRef = useRef < THREE . Group > ( null ) ;
64+ const frontRef = useRef < THREE . Mesh > ( null ) ;
65+ const backRef = useRef < THREE . Mesh > ( null ) ;
66+ const frontLineRef = useRef < THREE . LineSegments > ( null ) ;
67+ const backLineRef = useRef < THREE . LineSegments > ( null ) ;
68+
69+ // 卡片矩形边框几何体(平面四边,与卡片同尺寸)
70+ const edgesGeom = useMemo ( ( ) => {
71+ const plane = new THREE . PlaneGeometry ( 0.9 , 1.2 ) ;
72+ const edges = new THREE . EdgesGeometry ( plane ) ;
73+ plane . dispose ( ) ;
74+ return edges ;
75+ } , [ ] ) ;
76+
77+ // 与其他模块主体一致的自转配置
78+ const rotationConfig = useRef ( {
79+ axis : new THREE . Vector3 (
80+ Math . random ( ) - 0.5 , Math . random ( ) - 0.5 , Math . random ( ) - 0.5 ,
81+ ) . normalize ( ) ,
82+ speed : 0.2 + Math . random ( ) * 0.3 ,
83+ } ) . current ;
84+
85+ const targetScale = isActive ? 1.3 : isHovered ? 1.15 : 1.0 ;
86+ const targetEmissive = isActive ? 1.2 : isHovered ? 0.8 : 0.3 ;
87+
88+ useFrame ( ( _ , delta ) => {
89+ const safeDelta = Math . min ( delta , 0.1 ) ;
90+ // 自转 + 交互缩放(与其他模块一致)
91+ if ( groupRef . current ) {
92+ groupRef . current . rotateOnAxis ( rotationConfig . axis , safeDelta * rotationConfig . speed ) ;
93+ const cs = groupRef . current . scale . x ;
94+ groupRef . current . scale . setScalar ( THREE . MathUtils . lerp ( cs , targetScale , safeDelta * 4 ) ) ;
95+ }
96+ // 卡片发光增强(与其他模块一致)
97+ [ frontRef , backRef ] . forEach ( ( ref ) => {
98+ const mat = ref . current ?. material as THREE . MeshStandardMaterial | undefined ;
99+ if ( mat ) mat . emissiveIntensity = THREE . MathUtils . lerp ( mat . emissiveIntensity , targetEmissive , safeDelta * 4 ) ;
100+ } ) ;
101+ // 线框透明度(与 WireframeGlow 相同逻辑:基线 0.25,悬停 0.6,激活 0.85)
102+ const targetLine = isActive ? 0.85 : isHovered ? 0.6 : 0.25 ;
103+ [ frontLineRef , backLineRef ] . forEach ( ( ref ) => {
104+ const mat = ref . current ?. material as THREE . LineBasicMaterial | undefined ;
105+ if ( mat ) mat . opacity = THREE . MathUtils . lerp ( mat . opacity , targetLine , safeDelta * 3 ) ;
106+ } ) ;
107+ } ) ;
108+
55109 return (
56- < group >
57- < mesh position = { [ 0 , 0 , 0.05 ] } rotation = { [ 0 , 0 , 0.05 ] } >
110+ < group ref = { groupRef } >
111+ { /* 正面卡片 + 矩形辉光线框 */ }
112+ < mesh ref = { frontRef } position = { [ 0 , 0 , 0.05 ] } rotation = { [ 0 , 0 , 0.05 ] } >
58113 < planeGeometry args = { [ 0.9 , 1.2 ] } />
59114 < meshStandardMaterial color = "#3B82F6" emissive = "#3B82F6" emissiveIntensity = { 0.3 } map = { textures ?. mapA } side = { THREE . DoubleSide } />
60115 </ mesh >
61- < mesh position = { [ 0 , 0 , - 0.05 ] } rotation = { [ 0 , 0 , - 0.05 ] } >
116+ < lineSegments ref = { frontLineRef } geometry = { edgesGeom } position = { [ 0 , 0 , 0.05 ] } rotation = { [ 0 , 0 , 0.05 ] } scale = { [ 1.06 , 1.06 , 1.06 ] } >
117+ < lineBasicMaterial color = { emissiveColor } transparent opacity = { 0.25 } blending = { THREE . AdditiveBlending } />
118+ </ lineSegments >
119+ { /* 背面卡片 + 矩形辉光线框 */ }
120+ < mesh ref = { backRef } position = { [ 0 , 0 , - 0.05 ] } rotation = { [ 0 , 0 , - 0.05 ] } >
62121 < planeGeometry args = { [ 0.9 , 1.2 ] } />
63122 < meshStandardMaterial color = "#6366F1" emissive = "#6366F1" emissiveIntensity = { 0.3 } map = { textures ?. mapB } side = { THREE . DoubleSide } />
64123 </ mesh >
124+ < lineSegments ref = { backLineRef } geometry = { edgesGeom } position = { [ 0 , 0 , - 0.05 ] } rotation = { [ 0 , 0 , - 0.05 ] } scale = { [ 1.06 , 1.06 , 1.06 ] } >
125+ < lineBasicMaterial color = { emissiveColor } transparent opacity = { 0.25 } blending = { THREE . AdditiveBlending } />
126+ </ lineSegments >
65127 </ group >
66128 ) ;
67129}
@@ -100,6 +162,7 @@ function FresnelGlow({ color, isActive, isHovered, geometry }: {
100162}
101163
102164// ─── 细节层 2:辉光线框(EdgesGeometry) ────────────
165+ // 常驻显示:默认透明度 0.4 持续可见,悬停/选中时进一步增强
103166function WireframeGlow ( { color, isActive, isHovered, geometry } : {
104167 color : string ; isActive : boolean ; isHovered : boolean ; geometry : GeometryType ;
105168} ) {
@@ -123,13 +186,14 @@ function WireframeGlow({ color, isActive, isHovered, geometry }: {
123186 useFrame ( ( _ , delta ) => {
124187 if ( ! lineRef . current ) return ;
125188 const mat = lineRef . current . material as THREE . LineBasicMaterial ;
126- const target = isActive ? 0.8 : isHovered ? 0.5 : 0.15 ;
189+ // 常驻可见基线 0.4,交互时平滑增强(悬停 0.6 / 激活 0.85)
190+ const target = isActive ? 0.85 : isHovered ? 0.6 : 0.4 ;
127191 mat . opacity = THREE . MathUtils . lerp ( mat . opacity , target , Math . min ( delta * 3 , 1 ) ) ;
128192 } ) ;
129193
130194 return (
131195 < lineSegments ref = { lineRef } geometry = { edgesGeom } scale = { [ 1.06 , 1.06 , 1.06 ] } >
132- < lineBasicMaterial color = { color } transparent opacity = { 0.15 } blending = { THREE . AdditiveBlending } />
196+ < lineBasicMaterial color = { color } transparent opacity = { 0.25 } blending = { THREE . AdditiveBlending } />
133197 </ lineSegments >
134198 ) ;
135199}
@@ -263,7 +327,6 @@ export function ModuleEntity({
263327 const subtitle = getModuleSubtitle ( id ) ;
264328 const tier = useEffectiveTier ( ) ;
265329 const isLowTier = tier === 'low' ;
266- const isMidTier = tier === 'medium' ;
267330
268331 const rotationConfig = useRef ( {
269332 axis : new THREE . Vector3 (
@@ -321,7 +384,22 @@ export function ModuleEntity({
321384 onClick = { onClick } onPointerOver = { onPointerOver } onPointerOut = { onPointerOut }
322385 >
323386 { isFlashcard ? (
324- < FlashcardGeometry textures = { flashcardTextures } />
387+ < >
388+ { /* 核心呼吸光(与其他模块一致) */ }
389+ { ! isLowTier && < CoreBreath color = { emissiveColor } isActive = { isActive } isHovered = { isHovered } /> }
390+ < FlashcardGeometry
391+ textures = { flashcardTextures }
392+ emissiveColor = { emissiveColor }
393+ isActive = { isActive }
394+ isHovered = { isHovered }
395+ />
396+ { /* 轨道粒子环(与其他模块一致) */ }
397+ { ! isLowTier && ( isActive || isHovered ) && (
398+ < OrbitalRing isActive = { isActive } isHovered = { isHovered } color = { emissiveColor } />
399+ ) }
400+ { /* 地面辉光(与其他模块一致) */ }
401+ { ! isLowTier && < GroundGlow color = { emissiveColor } isActive = { isActive } /> }
402+ </ >
325403 ) : (
326404 < >
327405 { /* 核心呼吸光 */ }
@@ -353,10 +431,8 @@ export function ModuleEntity({
353431 < FresnelGlow color = { emissiveColor } isActive = { isActive } isHovered = { isHovered } geometry = { geometry } />
354432 ) }
355433
356- { /* 辉光线框 */ }
357- { ! isMidTier && ! isLowTier && (
358- < WireframeGlow color = { emissiveColor } isActive = { isActive } isHovered = { isHovered } geometry = { geometry } />
359- ) }
434+ { /* 辉光线框(全档位常驻显示:性能降级也不关闭) */ }
435+ < WireframeGlow color = { emissiveColor } isActive = { isActive } isHovered = { isHovered } geometry = { geometry } />
360436
361437 { /* 轨道粒子环 */ }
362438 { ! isLowTier && ( isActive || isHovered ) && (
@@ -371,7 +447,7 @@ export function ModuleEntity({
371447 { /* 标签 */ }
372448 { ( showLabel || isHovered ) && (
373449 < Html center distanceFactor = { 8 } position = { [ 0 , 1.3 , 0 ] } style = { { pointerEvents : 'none' } } >
374- < div className = "rounded-lg bg-slate-900/80 px-3 py-1.5 text-sm font-medium text-white backdrop-blur-sm whitespace-nowrap border border-indigo-500 /30" >
450+ < div className = "rounded-lg bg-slate-900/80 px-3 py-1.5 text-sm font-medium text-white backdrop-blur-sm whitespace-nowrap border border-brand-400 /30" >
375451 { label }
376452 { subtitle && < span className = "ml-1.5 text-xs text-white/50" > · { subtitle } </ span > }
377453 </ div >
0 commit comments