This commit is contained in:
@@ -2,139 +2,63 @@ import { MATCH3D_VISUAL_SEEDS } from '../../services/match3d-runtime';
|
||||
|
||||
type Match3DVisualSeed = (typeof MATCH3D_VISUAL_SEEDS)[number];
|
||||
|
||||
export type Match3DGeometryShape =
|
||||
| 'circle'
|
||||
| 'triangle'
|
||||
| 'diamond'
|
||||
| 'square'
|
||||
| 'star'
|
||||
| 'hexagon'
|
||||
| 'capsule'
|
||||
| 'heart'
|
||||
| 'trapezoid'
|
||||
| 'parallelogram';
|
||||
export type Match3DBlockShape =
|
||||
| 'brick'
|
||||
| 'tile'
|
||||
| 'slope'
|
||||
| 'cylinder'
|
||||
| 'ring'
|
||||
| 'arch'
|
||||
| 'cone';
|
||||
|
||||
export type Match3DGeometryShape = Match3DBlockShape;
|
||||
|
||||
export type Match3DGeometryAsset = {
|
||||
shape: Match3DGeometryShape;
|
||||
shape: Match3DBlockShape;
|
||||
fill: string;
|
||||
stroke: string;
|
||||
studsX: number;
|
||||
studsY: number;
|
||||
heightScale: number;
|
||||
transparent?: boolean;
|
||||
};
|
||||
|
||||
const MATCH3D_GEOMETRY_ASSETS: Record<string, Match3DGeometryAsset> = {
|
||||
'watermelon-green': {
|
||||
shape: 'circle',
|
||||
fill: '#16a34a',
|
||||
stroke: '#14532d',
|
||||
},
|
||||
'apple-red': {
|
||||
shape: 'heart',
|
||||
fill: '#ef4444',
|
||||
stroke: '#991b1b',
|
||||
},
|
||||
'banana-yellow': {
|
||||
shape: 'parallelogram',
|
||||
fill: '#facc15',
|
||||
stroke: '#a16207',
|
||||
},
|
||||
'grape-purple': {
|
||||
shape: 'star',
|
||||
fill: '#8b5cf6',
|
||||
stroke: '#5b21b6',
|
||||
},
|
||||
'melon-green': {
|
||||
shape: 'hexagon',
|
||||
fill: '#84cc16',
|
||||
stroke: '#3f6212',
|
||||
},
|
||||
'berry-blue': {
|
||||
shape: 'diamond',
|
||||
fill: '#2563eb',
|
||||
stroke: '#1e3a8a',
|
||||
},
|
||||
'peach-pink': {
|
||||
shape: 'trapezoid',
|
||||
fill: '#fb7185',
|
||||
stroke: '#be123c',
|
||||
},
|
||||
'plum-indigo': {
|
||||
shape: 'capsule',
|
||||
fill: '#4f46e5',
|
||||
stroke: '#312e81',
|
||||
},
|
||||
'lime-lime': {
|
||||
shape: 'square',
|
||||
fill: '#65a30d',
|
||||
stroke: '#365314',
|
||||
},
|
||||
'orange-orange': {
|
||||
shape: 'triangle',
|
||||
fill: '#f97316',
|
||||
stroke: '#9a3412',
|
||||
},
|
||||
'pear-cyan': {
|
||||
shape: 'parallelogram',
|
||||
fill: '#06b6d4',
|
||||
stroke: '#155e75',
|
||||
},
|
||||
red_circle: {
|
||||
shape: 'circle',
|
||||
fill: '#ef4444',
|
||||
stroke: '#991b1b',
|
||||
},
|
||||
yellow_triangle: {
|
||||
shape: 'triangle',
|
||||
fill: '#facc15',
|
||||
stroke: '#a16207',
|
||||
},
|
||||
purple_diamond: {
|
||||
shape: 'diamond',
|
||||
fill: '#7c3aed',
|
||||
stroke: '#4c1d95',
|
||||
},
|
||||
green_square: {
|
||||
shape: 'square',
|
||||
fill: '#16a34a',
|
||||
stroke: '#14532d',
|
||||
},
|
||||
blue_star: {
|
||||
shape: 'star',
|
||||
fill: '#0ea5e9',
|
||||
stroke: '#075985',
|
||||
},
|
||||
orange_hexagon: {
|
||||
shape: 'hexagon',
|
||||
fill: '#f97316',
|
||||
stroke: '#9a3412',
|
||||
},
|
||||
cyan_capsule: {
|
||||
shape: 'capsule',
|
||||
fill: '#06b6d4',
|
||||
stroke: '#155e75',
|
||||
},
|
||||
pink_heart: {
|
||||
shape: 'heart',
|
||||
fill: '#ec4899',
|
||||
stroke: '#9d174d',
|
||||
},
|
||||
lime_leaf: {
|
||||
shape: 'trapezoid',
|
||||
fill: '#84cc16',
|
||||
stroke: '#3f6212',
|
||||
},
|
||||
white_moon: {
|
||||
shape: 'parallelogram',
|
||||
fill: '#e2e8f0',
|
||||
stroke: '#64748b',
|
||||
'block-red-2x4': blockAsset('brick', '#e31818', '#8f1111', 4, 2, 0.72),
|
||||
'block-blue-1x2': blockAsset('brick', '#1478d4', '#0b4f91', 2, 1, 0.82),
|
||||
'block-yellow-2x2': blockAsset('brick', '#f7c51d', '#a66f00', 2, 2, 0.76),
|
||||
'block-green-1x4': blockAsset('brick', '#079447', '#055c2f', 4, 1, 0.72),
|
||||
'block-orange-1x6': blockAsset('brick', '#ff7a12', '#b84708', 6, 1, 0.64),
|
||||
'block-white-1x1': blockAsset('brick', '#f3f2ec', '#b7b8b2', 1, 1, 0.86),
|
||||
'block-black-1x8': blockAsset('brick', '#101214', '#030405', 8, 1, 0.54),
|
||||
'block-tan-2x3': blockAsset('brick', '#d8bd72', '#9b7a35', 3, 2, 0.68),
|
||||
'block-lime-1x2': blockAsset('brick', '#a5df18', '#6d990b', 2, 1, 0.58),
|
||||
'block-darkred-2x2': blockAsset('brick', '#b51217', '#76090d', 2, 2, 0.7),
|
||||
'block-blue-1x4': blockAsset('brick', '#1688df', '#0b5c9e', 4, 1, 0.58),
|
||||
'block-pink-2x4': blockAsset('brick', '#f66bb5', '#ba2e7e', 4, 2, 0.56),
|
||||
'block-gray-1x6': blockAsset('brick', '#4c5456', '#232829', 6, 1, 0.5),
|
||||
'block-lavender-tile-2x2': blockAsset('tile', '#c99fe6', '#8b63ad', 2, 2, 0.28),
|
||||
'block-teal-tile-1x3': blockAsset('tile', '#11adb0', '#087377', 3, 1, 0.26),
|
||||
'block-mint-tile-1x4': blockAsset('tile', '#a7c6ac', '#6e9275', 4, 1, 0.24),
|
||||
'block-magenta-tile-2x2': blockAsset('tile', '#cf0f68', '#8e0644', 2, 2, 0.28),
|
||||
'block-orange-tile-2x2-stud': blockAsset('tile', '#ff970f', '#b65b05', 2, 2, 0.3),
|
||||
'block-purple-slope-1x2': blockAsset('slope', '#5e42b6', '#342070', 2, 1, 0.82),
|
||||
'block-brown-slope-1x2': blockAsset('slope', '#8b421f', '#552414', 2, 1, 0.94),
|
||||
'block-sky-slope-2x2': blockAsset('slope', '#4db3f2', '#1f78b7', 2, 2, 0.9),
|
||||
'block-green-cylinder': blockAsset('cylinder', '#159554', '#076236', 1, 1, 1.08),
|
||||
'block-clear-ring': {
|
||||
...blockAsset('ring', '#d9e1df', '#aebbbb', 2, 2, 0.38),
|
||||
transparent: true,
|
||||
},
|
||||
'block-mint-arch': blockAsset('arch', '#c4ded2', '#83a996', 4, 1, 1.0),
|
||||
'block-gold-cone': blockAsset('cone', '#d39a10', '#8c6105', 1, 1, 1.18),
|
||||
};
|
||||
|
||||
const MATCH3D_UNKNOWN_GEOMETRY_ASSETS: Match3DGeometryAsset[] = [
|
||||
{ shape: 'circle', fill: '#f43f5e', stroke: '#9f1239' },
|
||||
{ shape: 'triangle', fill: '#f59e0b', stroke: '#92400e' },
|
||||
{ shape: 'diamond', fill: '#8b5cf6', stroke: '#5b21b6' },
|
||||
{ shape: 'star', fill: '#10b981', stroke: '#065f46' },
|
||||
{ shape: 'trapezoid', fill: '#0ea5e9', stroke: '#075985' },
|
||||
{ shape: 'parallelogram', fill: '#14b8a6', stroke: '#115e59' },
|
||||
blockAsset('brick', '#e11d48', '#9f1239', 2, 2, 0.68),
|
||||
blockAsset('tile', '#f59e0b', '#92400e', 3, 1, 0.28),
|
||||
blockAsset('slope', '#8b5cf6', '#5b21b6', 2, 1, 0.86),
|
||||
blockAsset('cylinder', '#10b981', '#065f46', 1, 1, 1.0),
|
||||
];
|
||||
|
||||
const MATCH3D_UNKNOWN_VISUAL_SEEDS: Match3DVisualSeed[] = [
|
||||
@@ -162,14 +86,26 @@ const MATCH3D_UNKNOWN_VISUAL_SEEDS: Match3DVisualSeed[] = [
|
||||
colorClassName: 'from-emerald-300 to-green-600',
|
||||
label: '四',
|
||||
},
|
||||
{
|
||||
itemTypeId: 'unknown-sky',
|
||||
visualKey: 'unknown-sky',
|
||||
colorClassName: 'from-sky-300 to-blue-600',
|
||||
label: '五',
|
||||
},
|
||||
];
|
||||
|
||||
function blockAsset(
|
||||
shape: Match3DBlockShape,
|
||||
fill: string,
|
||||
stroke: string,
|
||||
studsX: number,
|
||||
studsY: number,
|
||||
heightScale: number,
|
||||
): Match3DGeometryAsset {
|
||||
return {
|
||||
shape,
|
||||
fill,
|
||||
stroke,
|
||||
studsX,
|
||||
studsY,
|
||||
heightScale,
|
||||
};
|
||||
}
|
||||
|
||||
export function hashVisualKey(visualKey: string) {
|
||||
let hash = 0;
|
||||
for (const char of visualKey) {
|
||||
@@ -199,48 +135,80 @@ export function resolveGeometryAsset(visualKey: string): Match3DGeometryAsset {
|
||||
);
|
||||
}
|
||||
|
||||
function renderGeometryShape(asset: Match3DGeometryAsset) {
|
||||
function renderBlockIcon(asset: Match3DGeometryAsset) {
|
||||
const shapeProps = {
|
||||
fill: asset.fill,
|
||||
stroke: asset.stroke,
|
||||
strokeWidth: 6,
|
||||
strokeWidth: 5,
|
||||
strokeLinejoin: 'round' as const,
|
||||
opacity: asset.transparent ? 0.72 : 1,
|
||||
};
|
||||
|
||||
switch (asset.shape) {
|
||||
case 'circle':
|
||||
return <circle cx="50" cy="50" r="36" {...shapeProps} />;
|
||||
case 'triangle':
|
||||
return <path d="M50 12 L89 84 H11Z" {...shapeProps} />;
|
||||
case 'diamond':
|
||||
return <path d="M50 9 L91 50 L50 91 L9 50Z" {...shapeProps} />;
|
||||
case 'square':
|
||||
return <rect x="16" y="16" width="68" height="68" rx="8" {...shapeProps} />;
|
||||
case 'star':
|
||||
return (
|
||||
<path
|
||||
d="M50 8 L61 36 L91 38 L68 58 L76 88 L50 72 L24 88 L32 58 L9 38 L39 36Z"
|
||||
{...shapeProps}
|
||||
/>
|
||||
);
|
||||
case 'hexagon':
|
||||
return <path d="M28 12 H72 L94 50 L72 88 H28 L6 50Z" {...shapeProps} />;
|
||||
case 'capsule':
|
||||
return <rect x="10" y="28" width="80" height="44" rx="22" {...shapeProps} />;
|
||||
case 'heart':
|
||||
return (
|
||||
<path
|
||||
d="M50 86 C25 66 13 52 17 34 C20 18 40 16 50 31 C60 16 80 18 83 34 C87 52 75 66 50 86Z"
|
||||
{...shapeProps}
|
||||
/>
|
||||
);
|
||||
case 'trapezoid':
|
||||
return <path d="M27 18 H73 L90 82 H10Z" {...shapeProps} />;
|
||||
case 'parallelogram':
|
||||
return <path d="M34 16 H88 L66 84 H12Z" {...shapeProps} />;
|
||||
default:
|
||||
return <circle cx="50" cy="50" r="36" {...shapeProps} />;
|
||||
if (asset.shape === 'cylinder') {
|
||||
return (
|
||||
<>
|
||||
<rect x="34" y="22" width="32" height="56" rx="12" {...shapeProps} />
|
||||
<ellipse cx="50" cy="24" rx="16" ry="8" fill={asset.fill} stroke={asset.stroke} strokeWidth={5} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
if (asset.shape === 'ring') {
|
||||
return (
|
||||
<>
|
||||
<ellipse cx="50" cy="50" rx="34" ry="24" {...shapeProps} />
|
||||
<ellipse cx="50" cy="50" rx="17" ry="11" fill="rgba(255,255,255,0.88)" stroke={asset.stroke} strokeWidth={5} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
if (asset.shape === 'arch') {
|
||||
return (
|
||||
<path
|
||||
d="M14 78 V28 H86 V78 H66 V46 C66 34 58 27 50 27 C42 27 34 34 34 46 V78Z"
|
||||
{...shapeProps}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (asset.shape === 'cone') {
|
||||
return (
|
||||
<path d="M50 12 C66 28 78 62 78 82 H22 C22 62 34 28 50 12Z" {...shapeProps} />
|
||||
);
|
||||
}
|
||||
|
||||
if (asset.shape === 'slope') {
|
||||
return <path d="M16 76 L84 76 L84 30 L16 60Z" {...shapeProps} />;
|
||||
}
|
||||
|
||||
const width = Math.min(76, 16 + asset.studsX * 14);
|
||||
const height = Math.min(54, 18 + asset.studsY * 13);
|
||||
const x = 50 - width / 2;
|
||||
const y = 54 - height / 2;
|
||||
const studRadius = asset.shape === 'tile' ? 0 : 5;
|
||||
return (
|
||||
<>
|
||||
<rect x={x} y={y} width={width} height={height} rx="7" {...shapeProps} />
|
||||
{Array.from({ length: asset.studsX * asset.studsY }, (_, index) => {
|
||||
if (studRadius <= 0) {
|
||||
return null;
|
||||
}
|
||||
const column = index % asset.studsX;
|
||||
const row = Math.floor(index / asset.studsX);
|
||||
return (
|
||||
<circle
|
||||
key={index}
|
||||
cx={x + ((column + 0.5) * width) / asset.studsX}
|
||||
cy={y + ((row + 0.5) * height) / asset.studsY}
|
||||
r={studRadius}
|
||||
fill={asset.fill}
|
||||
stroke={asset.stroke}
|
||||
strokeWidth={3}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export function Match3DVisualIcon({
|
||||
@@ -261,7 +229,7 @@ export function Match3DVisualIcon({
|
||||
data-testid={`match3d-visual-${visualKey}`}
|
||||
data-shape={asset.shape}
|
||||
>
|
||||
{renderGeometryShape(asset)}
|
||||
{renderBlockIcon(asset)}
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user