feat: Implement weight cutout boundary smoothing for image depth cutout
- Added a new proposal for boundary smoothing in the weight cutout process, differentiating between Split edges and Outline smoothing requirements. - Introduced a new capability `cutout-boundary-smoothing` to control smoothing strength and default values. - Updated specifications to define the behavior of boundary-specific smoothing strength and default settings. - Created tasks to verify the implementation of boundary behavior and asset updates. - Developed comprehensive tests for depth symmetry and normal scaling to ensure expected behavior and performance. - Introduced new attributes for normal mapping to facilitate persistent face attributes sharing between depth symmetry and image materials. - Built a new node group for O Image Depth Symmetry, integrating the new smoothing capabilities and ensuring compatibility with existing workflows.
This commit is contained in:
@@ -27,7 +27,7 @@
|
||||
</div>
|
||||
<canvas id="layout-canvas" role="img" aria-label="按实际排列坐标绘制的节点组,尺寸和曲线使用估算模型"></canvas>
|
||||
<div class="viz-row text-small" id="layout-legend">
|
||||
<span><i class="geometry-key"></i>几何连接</span>
|
||||
<span><i class="geometry-key"></i>几何与着色连接</span>
|
||||
<span><i class="field-key"></i>字段与参数连接</span>
|
||||
<span id="layout-count"></span>
|
||||
</div>
|
||||
@@ -113,15 +113,15 @@
|
||||
return;
|
||||
}
|
||||
if (view.value === 'middle') {
|
||||
const geometryNodes = current.nodes.filter(n => n.type !== 'NodeReroute' && n.role === 'geometry');
|
||||
const midX = (Math.min(...geometryNodes.map(n => n.x)) + Math.max(...geometryNodes.map(n => n.x))) / 2;
|
||||
const target = geometryNodes.sort((a, b) => Math.abs(a.x - midX) - Math.abs(b.x - midX))[0];
|
||||
const mainNodes = current.nodes.filter(n => n.type !== 'NodeReroute' && n.role !== 'field');
|
||||
const midX = (Math.min(...mainNodes.map(n => n.x)) + Math.max(...mainNodes.map(n => n.x))) / 2;
|
||||
const target = mainNodes.sort((a, b) => Math.abs(a.x - midX) - Math.abs(b.x - midX))[0];
|
||||
fit([target.x - 850, target.y - 560, target.x + 670, target.y + 600]);
|
||||
detail.textContent = `${current.name} · 中段计算局部`;
|
||||
return;
|
||||
}
|
||||
if (view.value === 'input') {
|
||||
const target = current.nodes.filter(n => n.role === 'geometry' && n.type !== 'NodeReroute').sort((a, b) => a.x - b.x)[0];
|
||||
const target = current.nodes.filter(n => n.role !== 'field' && n.type !== 'NodeReroute').sort((a, b) => a.x - b.x)[0];
|
||||
fit([target.x - 850, target.y - 650, target.x + 750, target.y + 650]);
|
||||
detail.textContent = `${current.name} · 起点输入附近`; return;
|
||||
}
|
||||
@@ -192,9 +192,9 @@
|
||||
const x2 = wx2 * scale + dx, y2 = wy2 * scale + dy;
|
||||
const bend = Math.max(40, Math.abs(wx2 - wx1) * 0.42) * scale;
|
||||
const active = selected === source || selected === target;
|
||||
context.globalAlpha = selected !== null && !active ? 0.18 : type === 'GEOMETRY' ? 0.85 : 0.46;
|
||||
context.strokeStyle = type === 'GEOMETRY' ? geometry : fields;
|
||||
context.lineWidth = active ? 2.5 : type === 'GEOMETRY' ? 1.35 : 0.8;
|
||||
context.globalAlpha = selected !== null && !active ? 0.18 : ['GEOMETRY', 'SHADER'].includes(type) ? 0.85 : 0.46;
|
||||
context.strokeStyle = ['GEOMETRY', 'SHADER'].includes(type) ? geometry : fields;
|
||||
context.lineWidth = active ? 2.5 : ['GEOMETRY', 'SHADER'].includes(type) ? 1.35 : 0.8;
|
||||
context.beginPath(); context.moveTo(x1, y1);
|
||||
context.bezierCurveTo(x1 + bend, y1, x2 - bend, y2, x2, y2); context.stroke();
|
||||
}
|
||||
@@ -202,9 +202,9 @@
|
||||
for (const node of current.nodes) {
|
||||
const x = node.x * scale + dx, y = node.y * scale + dy, w = node.w * scale, h = node.h * scale;
|
||||
if (x + w < 0 || x > width || y + h < 0 || y > height) continue;
|
||||
const geometryNode = node.type === 'NodeReroute'
|
||||
? node.sockets.some(socket => socket[3] === 'GEOMETRY') : node.role === 'geometry';
|
||||
const hue = geometryNode ? geometry : other;
|
||||
const mainNode = node.type === 'NodeReroute'
|
||||
? node.sockets.some(socket => ['GEOMETRY', 'SHADER'].includes(socket[3])) : node.role !== 'field';
|
||||
const hue = mainNode ? geometry : other;
|
||||
if (node.type === 'NodeReroute') {
|
||||
context.fillStyle = hue; context.beginPath();
|
||||
context.arc(x + w / 2, y + h / 2, Math.max(1.5, w / 3), 0, Math.PI * 2); context.fill(); continue;
|
||||
@@ -241,7 +241,7 @@
|
||||
}
|
||||
if (scale >= 0.22) {
|
||||
for (const socket of node.sockets) {
|
||||
context.fillStyle = socket[3] === 'GEOMETRY' ? geometry : fields;
|
||||
context.fillStyle = ['GEOMETRY', 'SHADER'].includes(socket[3]) ? geometry : fields;
|
||||
context.beginPath(); context.arc(socket[2] ? x + w : x, y + socket[1] * scale,
|
||||
Math.max(1.4, 3.6 * scale), 0, Math.PI * 2); context.fill();
|
||||
}
|
||||
@@ -285,7 +285,7 @@
|
||||
const x = (event.clientX - rect.left - dx) / scale, y = (event.clientY - rect.top - dy) / scale;
|
||||
const node = current.nodes.findLast(n => x >= n.x && x <= n.x + n.w && y >= n.y && y <= n.y + n.h);
|
||||
selected = node ? node.id : null;
|
||||
detail.textContent = node ? `${node.name} · ${node.title} · ${node.role === 'geometry' ? '几何主线' : '计算或输入'}${node.settings.length ? ' · ' + node.settings.join(' / ') : ''}` : current.name;
|
||||
detail.textContent = node ? `${node.name} · ${node.title} · ${node.role !== 'field' ? '结果主线' : '计算或输入'}${node.settings.length ? ' · ' + node.settings.join(' / ') : ''}` : current.name;
|
||||
draw();
|
||||
}
|
||||
dragging = null;
|
||||
|
||||
Reference in New Issue
Block a user