aardio纯Plus控制绘制3D曲面波形数据 - Gemini AI编写
By
admin
at 6 天前 • 0人收藏 • 74人看过

import win.ui;
import gdip;
import math;
/*DSG{{*/
var winform = win.form(text="3D 通用曲面绘制引擎 (数据/渲染分离版)";right=900;bottom=600)
winform.add(
btnGaussian={cls="button";text="显示高斯曲面";left=20;top=20;right=140;bottom=50;z=2};
btnRipple={cls="button";text="显示水波纹";left=150;top=20;right=270;bottom=50;z=3};
btnTwist={cls="button";text="显示扭曲平面";left=280;top=20;right=400;bottom=50;z=4};
plus={cls="plus";left=0;top=69;right=900;bottom=600;db=1;dl=1;dr=1;dt=1;notify=1;z=1}
)
/*}}*/
// ==========================================================
// 模块 1: 3D 渲染引擎 (Renderer) - 负责怎么画
// ==========================================================
var renderer3D = {
// 辅助: HSV颜色转ARGB
hsv2argb = function(h, s, v){
var r, g, b;
var i = math.floor(h * 6);
var f = h * 6 - i;
var p = v * (1 - s);
var q = v * (1 - f * s);
var t = v * (1 - (1 - f) * s);
if(i%6==0){r=v;g=t;b=p} elseif(i%6==1){r=q;g=v;b=p} elseif(i%6==2){r=p;g=v;b=t}
elseif(i%6==3){r=p;g=q;b=v} elseif(i%6==4){r=t;g=p;b=v} elseif(i%6==5){r=v;g=p;b=q}
return 0xFF000000 | (r * 255) << 16 | (g * 255) << 8 | (b * 255);
};
// 辅助: 旋转计算
rotate = function(x, y, z, ax, ay){
var y1 = y * math.cos(ax) - z * math.sin(ax);
var z1 = y * math.sin(ax) + z * math.cos(ax);
var x2 = x * math.cos(ay) + z1 * math.sin(ay);
var z2 = -x * math.sin(ay) + z1 * math.cos(ay);
return x2, y1, z2;
};
// 核心绘制函数
// graphics: GDI+画布
// meshData: 包含 vertices(顶点) 和 faces(面) 的数据对象
// state: 包含角度、缩放、偏移的视图状态
draw = function(graphics, meshData, state){
if(!meshData) return;
graphics.smoothingMode = 4; // 抗锯齿
// 1. 顶点变换 (World -> Screen)
var transVerts = {};
for(i=1; #meshData.vertices){
var v = meshData.vertices[i];
// 坐标系转换:v.z 是数学高度,但在屏幕绘制时,我们通常把 y 轴作为垂直轴
// 所以这里传入 v.x, v.z(高度), v.y(深度) 进行旋转,符合直觉
var rx, ry, rz = owner.rotate(v.x, v.z, v.y, state.angleX, state.angleY);
transVerts[i] = {
x = rx * state.scale + state.offsetX;
y = ry * state.scale + state.offsetY;
z = rz; // 变换后的Z深度(用于遮挡排序)
h = v.z; // 原始高度(用于上色)
};
}
// 2. 组装渲染列表
var drawList = {};
var maxH = meshData.maxZ;
var minH = meshData.minZ;
var hRange = (maxH - minH) || 1; // 防止除以0
for(i=1; #meshData.faces){
var f = meshData.faces[i];
var v1 = transVerts[f[1]];
var v2 = transVerts[f[2]];
var v3 = transVerts[f[3]];
var v4 = transVerts[f[4]];
// 计算面的平均深度 (用于排序)
var avgZ = (v1.z + v2.z + v3.z + v4.z) * 0.25;
// 计算面的平均高度 (用于颜色)
var avgH = (v1.h + v2.h + v3.h + v4.h) * 0.25;
// 计算颜色 (热力图:红高蓝低)
var normH = (avgH - minH) / hRange;
var hue = 0.65 - (normH * 0.65); // 0.65(蓝) -> 0.0(红)
var color = owner.hsv2argb(hue, 0.85, 1.0);
table.push(drawList, {
z = avgZ;
points = {v1.x, v1.y, v2.x, v2.y, v3.x, v3.y, v4.x, v4.y};
color = color;
});
}
// 3. 深度排序 (画家算法)
table.sort(drawList, function(b){ return owner.z < b.z });
// 4. 绘制多边形
var pen = gdip.pen(0x40FFFFFF, 1); // 半透明网格线
for(i=1; #drawList){
var item = drawList[i];
var brush = gdip.solidBrush(item.color);
graphics.fillPolygon(brush, item.points);
graphics.drawPolygon(pen, item.points);
brush.delete();
}
pen.delete();
}
}
// ==========================================================
// 模块 2: 数据生成器 (Data Generator) - 负责准备数据
// ==========================================================
var meshGenerator = {
// 通用网格生成器
// gridSize: 网格密度
// range: 坐标范围
// funcZ: 回调函数 function(x, y){ return z }
createFromFunc = function(gridSize, range, funcZ){
var mesh = { vertices={}; faces={}; maxZ=-99999; minZ=99999 };
// 生成顶点
for(i=0; gridSize){
var x = (i / gridSize) * (range * 2) - range;
for(j=0; gridSize){
var y = (j / gridSize) * (range * 2) - range;
// === 调用外部传入的公式 ===
var z = funcZ(x, y);
// 记录极值用于归一化颜色
if(z > mesh.maxZ) mesh.maxZ = z;
if(z < mesh.minZ) mesh.minZ = z;
table.push(mesh.vertices, {x=x; y=y; z=z});
}
}
// 生成面索引
var cols = gridSize + 1;
for(r=0; gridSize-1){
for(c=0; gridSize-1){
var p1 = r * cols + c + 1;
var p2 = p1 + 1;
var p3 = (r + 1) * cols + c + 2;
var p4 = p3 - 1;
table.push(mesh.faces, {p1, p2, p3, p4});
}
}
return mesh;
}
}
// ==========================================================
// 模块 3: 用户应用层 - 定义波形并调用
// ==========================================================
// 定义当前要显示的数据
var currentMesh = null;
// 1. 高斯波形数据
var loadGaussian = function(){
currentMesh = meshGenerator.createFromFunc(30, 3.0,
function(x,y){
// 高斯公式
return 2.5 * math.exp( - (x*x + y*y) / 2.0 );
}
);
winform.plus.redraw();
}
// 2. 水波纹数据 (Sinc函数)
var loadRipple = function(){
currentMesh = meshGenerator.createFromFunc(30, 4.0,
function(x,y){
var r = math.sqrt(x*x + y*y);
// 避免除以0
if(r < 0.01) return 2.0;
return 2.0 * math.sin(r*3) / (r*3);
}
);
winform.plus.redraw();
}
// 3. 扭曲平面
var loadTwist = function(){
currentMesh = meshGenerator.createFromFunc(25, 3.0,
function(x,y){
return math.sin(x) * math.cos(y);
}
);
winform.plus.redraw();
}
// 绑定按钮事件
winform.btnGaussian.oncommand = loadGaussian;
winform.btnRipple.oncommand = loadRipple;
winform.btnTwist.oncommand = loadTwist;
// 视图状态
var sceneState = { angleX=0.5; angleY=0.8; scale=50; offsetX=450; offsetY=300 };
// 绘图回调:只需要一行代码调用渲染器
winform.plus.onDrawForegroundEnd = function(graphics, rc){
graphics.clear(0xFF222222);
// !!! 核心调用:画它! !!!
renderer3D.draw(graphics, currentMesh, sceneState);
}
// 交互逻辑 (鼠标控制)
var mousePos = {x=0; y=0}; var isDragging=false;
winform.plus.wndproc = function(hwnd,msg,wp,lp){
if(msg==0x201){ isDragging=true; mousePos.x,mousePos.y = win.getMessagePos(lp); winform.plus.capture=true; }
elseif(msg==0x202){ isDragging=false; winform.plus.capture=false; }
elseif(msg==0x20A){ sceneState.scale += (wp>>16)/120*5; winform.plus.redraw(); }
}
winform.plus.onMouseMove = function(wp,lp){
var x,y = win.getMessagePos(lp);
if(isDragging){
sceneState.angleY += (x-mousePos.x)*0.01;
sceneState.angleX += (y-mousePos.y)*0.01;
winform.plus.redraw();
}
mousePos.x,mousePos.y = x,y;
}
// 启动默认显示高斯
loadGaussian();
winform.show();
win.loopMessage();关键点解析
渲染器 renderer3D.draw:
这个函数现在完全不包含任何数学公式。它只认识 meshData。
meshData 是一个标准结构:{ vertices: {...}, faces: {...} }。
只要你能把数据整理成这个格式,这个渲染器就能画出任何东西(甚至不是数学曲面,比如扫描的地形数据)。
生成器 meshGenerator.createFromFunc:
这个函数实现了依赖注入的思想。它接受一个参数 funcZ。
funcZ 是一个回调函数,形式为 function(x, y){ return z; }。
生成器内部负责处理繁琐的 for 循环、网格顶点索引计算 (p1, p2, p3, p4)。
最大优势:你以后想增加新的波形,只需要写一行 return math.sin(x) ...,而不需要去动几十行循环代码。
1 个回复 | 最后更新于 5 天前
登录后方可回帖
提示ci:
import win.ui; import gdip; import math; /*DSG{{*/ var winform = win.form(text="3D 动态高斯曲面 - 峰值跟随鼠标移动";right=900;bottom=600) winform.add( plus={cls="plus";left=0;top=0;right=900;bottom=600;db=1;dl=1;dr=1;dt=1;notify=1;z=1} ); /*}}*/ // ========================================================== // 1. 3D 渲染核心 (View Layer) // ========================================================== var renderer = { // 颜色映射 (蓝->红) hsv2argb = function(h, s, v){ var r, g, b; var i = math.floor(h * 6); var f = h * 6 - i; var p = v * (1 - s); var q = v * (1 - f * s); var t = v * (1 - (1 - f) * s); if(i%6==0){r=v;g=t;b=p} elseif(i%6==1){r=q;g=v;b=p} elseif(i%6==2){r=p;g=v;b=t} elseif(i%6==3){r=p;g=q;b=v} elseif(i%6==4){r=t;g=p;b=v} elseif(i%6==5){r=v;g=p;b=q} return 0xFF000000 | (r * 255) << 16 | (g * 255) << 8 | (b * 255); }; // 3D 旋转变换 rotate = function(x, y, z, ax, ay){ // 绕 X 轴 (俯仰) var y1 = y * math.cos(ax) - z * math.sin(ax); var z1 = y * math.sin(ax) + z * math.cos(ax); // 绕 Y 轴 (旋转) var x2 = x * math.cos(ay) + z1 * math.sin(ay); var z2 = -x * math.sin(ay) + z1 * math.cos(ay); return x2, y1, z2; }; // 绘制函数 draw = function(graphics, mesh, state){ graphics.smoothingMode = 4; // 抗锯齿 // 1. 顶点投影 (3D -> 2D) var transVerts = {}; for(i=1; #mesh.vertices){ var v = mesh.vertices[i]; // 坐标映射: x->x, z->y(高度), y->z(深度) var rx, ry, rz = owner.rotate(v.x, v.z, v.y, state.angleX, state.angleY); transVerts[i] = { x = rx * state.scale + state.offsetX; y = ry * state.scale + state.offsetY; z = rz; // 深度用于排序 h = v.z; // 原始高度用于颜色 }; } // 2. 生成绘制面 var drawList = {}; var maxH = mesh.maxZ; for(i=1; #mesh.faces){ var f = mesh.faces[i]; var v1, v2, v3, v4 = transVerts[f[1]], transVerts[f[2]], transVerts[f[3]], transVerts[f[4]]; // 平均深度 (Z-Sort) var avgZ = (v1.z + v2.z + v3.z + v4.z) * 0.25; // 平均高度 (Color) var avgH = (v1.h + v2.h + v3.h + v4.h) * 0.25; // 计算热力图颜色 var normH = avgH / 2.5; // 假设最大高度约2.5 if(normH > 1) normH = 1; if(normH < 0) normH = 0; var hue = 0.65 - (normH * 0.65); // 蓝 -> 红 var color = owner.hsv2argb(hue, 0.9, 1.0); table.push(drawList, { z = avgZ; points = {v1.x, v1.y, v2.x, v2.y, v3.x, v3.y, v4.x, v4.y}; color = color; }); } // 3. 排序与绘制 table.sort(drawList, function(b){ return owner.z < b.z }); var pen = gdip.pen(0x30FFFFFF, 1); // 微弱的网格线 for(i=1; #drawList){ var item = drawList[i]; var brush = gdip.solidBrush(item.color); graphics.fillPolygon(brush, item.points); graphics.drawPolygon(pen, item.points); brush.delete(); } pen.delete(); // 绘制辅助文字 var font = gdip.font("Segoe UI", 11); graphics.drawString("移动鼠标改变波峰位置 | 滚轮缩放 | 右键旋转", font, ::RECTF(10,10,500,50), , gdip.solidBrush(0xFFAAAAAA)); font.delete(); } } // ========================================================== // 2. 数据生成核心 (Model Layer) // ========================================================== var generator = { // 动态生成高斯网格 // peakX, peakY: 高斯峰值的中心坐标 updateMesh = function(gridSize, range, peakX, peakY){ var mesh = { vertices={}; faces={}; maxZ=0 }; // 生成顶点 for(i=0; gridSize){ var x = (i / gridSize) * (range * 2) - range; for(j=0; gridSize){ var y = (j / gridSize) * (range * 2) - range; // === 核心公式:带偏移的高斯函数 === // Z = A * exp( -((x-cx)^2 + (y-cy)^2) / 2σ² ) var dx = x - peakX; var dy = y - peakY; var z = 2.5 * math.exp( - (dx*dx + dy*dy) / 1.5 ); // σ=sqrt(0.75) if(z > mesh.maxZ) mesh.maxZ = z; table.push(mesh.vertices, {x=x; y=y; z=z}); } } // 生成面索引 (只需生成一次,这里为了简化代码每次都生成,性能影响很小) var cols = gridSize + 1; for(r=0; gridSize-1){ for(c=0; gridSize-1){ var p1 = r * cols + c + 1; var p2 = p1 + 1; var p3 = (r + 1) * cols + c + 2; var p4 = p3 - 1; table.push(mesh.faces, {p1, p2, p3, p4}); } } return mesh; } } // ========================================================== // 3. 状态管理与交互 (Controller Layer) // ========================================================== var state = { angleX = 0.6; // 俯视角度 angleY = 0.0; // 旋转角度 (初始设为0方便对齐鼠标) scale = 60; // 缩放 offsetX = 450; // 屏幕中心 X offsetY = 350; // 屏幕中心 Y // 鼠标控制的波峰位置 (逻辑坐标 -3.0 ~ 3.0) peakX = 0; peakY = 0; }; // 当前网格数据缓存 var currentMesh = null; // 渲染回调 winform.plus.onDrawForegroundEnd = function(graphics, rc){ graphics.clear(0xFF1E1E1E); // 深色背景 // 1. 根据当前状态重新计算网格 (因为波峰动了,顶点Z值变了) currentMesh = generator.updateMesh(30, 3.5, state.peakX, state.peakY); // 2. 绘制 renderer.draw(graphics, currentMesh, state); } // 鼠标交互逻辑 var mouseState = { isRightDragging = false; // 右键旋转 lastX = 0; lastY = 0; } winform.plus.wndproc = function(hwnd,msg,wp,lp){ var x, y = win.getMessagePos(lp); // [逻辑1] 鼠标移动 -> 更新波峰位置 // 只有当没有按住右键旋转时,才移动波峰 if(msg == 0x200/*WM_MOUSEMOVE*/){ if(!mouseState.isRightDragging){ // 简单的屏幕坐标 -> 逻辑坐标映射 // 假设屏幕中心对应逻辑(0,0),缩放比例对应 scale // 注意:因为有 3D 旋转,这只是一个近似映射,但在 angleY=0 时是准确的 // 为了让鼠标操作更符合直觉,我们反向旋转一下输入坐标 var dx = (x - state.offsetX) / state.scale; var dy = (y - state.offsetY) / state.scale; // 逆向解算旋转 (简化版:只处理 Y 轴旋转的平面投影) // x_logic = dx * cos(-ay) - dy * sin(-ay) // y_logic = dx * sin(-ay) + dy * cos(-ay) // 注意:由于 dy 是屏幕 Y 轴,对应 3D 里的 Z 轴(深度),且受 angleX 压缩 // 这里为了操作手感流畅,做简化映射: var cosA = math.cos(-state.angleY); var sinA = math.sin(-state.angleY); // 修正 angleX 带来的透视压缩 (除以 cos(angleX)) var dy_corrected = dy / math.cos(state.angleX); state.peakX = dx * cosA - dy_corrected * sinA; state.peakY = dx * sinA + dy_corrected * cosA; winform.plus.redraw(); } else { // 右键拖拽旋转 state.angleY += (x - mouseState.lastX) * 0.01; state.angleX += (y - mouseState.lastY) * 0.01; winform.plus.redraw(); } mouseState.lastX, mouseState.lastY = x, y; } // [逻辑2] 右键按下/抬起 -> 切换旋转模式 elseif(msg == 0x204/*RBUTTONDOWN*/){ mouseState.isRightDragging = true; winform.plus.capture = true; } elseif(msg == 0x205/*RBUTTONUP*/){ mouseState.isRightDragging = false; winform.plus.capture = false; } // [逻辑3] 滚轮缩放 elseif(msg == 0x20A){ state.scale += (wp >> 16) / 120 * 5; if(state.scale < 10) state.scale = 10; winform.plus.redraw(); } } winform.show(); win.loopMessage();