aardio纯Plus控制绘制3D曲面波形数据 - Gemini AI编写

By admin at 6 天前 • 0人收藏 • 74人看过

image.png

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();

关键点解析

  1. 渲染器 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 天前
    5 天前   #1

    screenshots.gif

    提示ci:

    用aardio帮我编写一个显示3d曲面图的程序,要求模拟3d高斯曲线数据并显示出来,另外高斯曲线顶点跟随着鼠标在波形图中移动而移动

    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();


    登录后方可回帖

    登 录
    信息栏
    本站域名

    ChengXu.XYZ

    投诉联系:  popdes@126.com



    快速上位机开发学习,本站主要记录了学习过程中遇到的问题和解决办法及上位机代码分享

    这里主要专注于学习交流和经验分享.
    纯私人站,当笔记本用的,学到哪写到哪.
    如果侵权,联系 Popdes@126.com

    友情链接
    Aardio官方
    Aardio资源网


    才仁机械


    网站地图SiteMap

    Loading...