如何为凸包算法的中间步骤设置动画?

2023-12-13

我正在尝试制作某种动画,以便用户可以理解或看到查找点集的凸包所采取的步骤。例如,假设我使用下面的代码进行 Graham Scan,有哪些方法可以对线条添加和删除进行动画处理?即使对于很多点,也需要时间来处理,然后几乎立即将它们全部绘制出来,而且我不确定如何帮助用户体验正在发生的事情......

function GrahamScan(points) {
  points.sort(function(a, b){return a.x - b.x})

  var stack1 = [];
  var stack2 = [];

  stack1.push(points[0])
  stack1.push(points[1])

  for (i=2; i < points.length; i++) {
     len = stack1.length > 1;
     turn = RTT(stack1[stack1.length-2], stack1[stack1.length-1], points[i]) === 1;
     ctx.beginPath();
     ctx.moveTo(stack1[stack1.length-2].x,stack1[stack1.length-2].y);
     ctx.lineTo(stack1[stack1.length-1].x,stack1[stack1.length-1].y);
     ctx.stroke();
     while (len && !turn) {
           stack1.pop();
           reDraw(points, stack1, stack2);
           len = stack1.length > 1;
           if (!len) {
              break
           }
           turn = RTT(stack1[stack1.length-2], stack1[stack1.length-1], points[i]) === 1;
     }
     stack1.push(points[i]);

  }
  ctx.beginPath();
  ctx.moveTo(stack1[stack1.length-2].x,stack1[stack1.length-2].y);
  ctx.lineTo(stack1[stack1.length-1].x,stack1[stack1.length-1].y);
  ctx.stroke();

  stack2 = [];
  stack2.push(points[points.length-1])
  stack2.push(points[points.length-2])

  for (i=2; i < points.length; i++) {
     len = stack2.length > 1;
     turn = RTT(stack2[stack2.length-2], stack2[stack2.length-1], points[points.length-i-1]) === 1;
     ctx.beginPath();
     ctx.moveTo(stack2[stack2.length-2].x,stack2[stack2.length-2].y);
     ctx.lineTo(stack2[stack2.length-1].x,stack2[stack2.length-1].y);
     ctx.stroke();
     while (len && !turn) {
           stack2.pop();
           reDraw(points, stack1, stack2);
           len = stack2.length > 1;
           if (!len) {
              break
           }
           turn = RTT(stack2[stack2.length-2], stack2[stack2.length-1], points[points.length-i-1]) === 1;
     }
     stack2.push(points[points.length-i-1]);

  }
  ctx.beginPath();
  ctx.moveTo(stack2[stack2.length-2].x,stack2[stack2.length-2].y);
  ctx.lineTo(stack2[stack2.length-1].x,stack2[stack2.length-1].y);
  ctx.stroke();

}
   function reDraw(points,stack1,stack2) {
      ctx.clearRect(0, 0, w, h);
      document.getElementById("canvasimg").style.display = "none";
      for (j = 0; j < points.length; j++) {
         ctx.beginPath();
         ctx.fillStyle = x;
         ctx.fillRect(points[j].x-1, points[j].y-1, 3, 3);
         ctx.closePath();
      }
      for (k = 1; k < stack1.length; k++) {
         ctx.beginPath();
         ctx.moveTo(stack1[k-1].x-1,stack1[k-1].y-1);
         ctx.lineTo(stack1[k].x-1,stack1[k].y-1);
         ctx.stroke();
      }
      for (l = 1; l < stack2.length; l++) {
         ctx.beginPath();
         ctx.moveTo(stack2[l-1].x-1,stack2[l-1].y-1);
         ctx.lineTo(stack2[l].x-1,stack2[l].y-1);
         ctx.stroke();
      }
   }

   function RTT(a, b, c) {
      return Math.sign((b.x - a.x)*(c.y-a.y) - (b.y-a.y)*(c.x-a.x));
   }

使用生成器函数对算法进行动画处理。

最简单的方法是使用生成器函数创建可以停止算法执行并允许动画循环显示和控制执行速度的位置。它不会干扰算法的功能。看生成器函数声明 MDN

普通的生成器函数用于生成数据,但在这种情况下,我们对数据不感兴趣,而是对算法中内置的可视化过程感兴趣。

要制作动画,只需创建一个标准动画循环。创建一个背景画布来保存每次更新/步进算法时不想绘制的任何图形。设置可视化的帧速率,然后每一帧清除画布,绘制背景,从生成器函数调用下一个值(它将渲染算法的下一部分),然后等待下一帧。

当算法完成时,生成器将返回未定义的值,您就知道它已完成。

一个简单的例子

我已经转换了你的grahamScan函数转化为生成器函数。然后创建一个生成器vis = grahamScan(points)然后我每 4 帧渲染一次步骤,因此约为 15fps。我不确定您想要在哪里进行视觉中断,并且我还添加了一些额外的渲染,因为找到的线条不断闪烁(在内部 while 循环内,未绘制外部线条)。

我随机生成点数组,并在完成后大约 2 秒重新启动动画。

主动画循环位于底部,我添加了一些代码来创建随机点并将它们渲染到背景画布上。唯一的限制是船体垂直计数如果非常高会减慢速度。这些点是预先渲染的,因此不会影响帧速率,您可以有 100 个数千到数百万个(尽管预渲染时间需要一点时间。我测试了 500,000 个点,渲染背景大约需要 4 秒,但可视化运行以全帧速率。

"use strict"
var canvas = document.createElement("canvas");
canvas.width = innerWidth - 20;
canvas.height = innerHeight - 20;
var ctx = canvas.getContext("2d");
document.body.appendChild(canvas)
var w = canvas.width;
var h = canvas.height;
var points;
var background = document.createElement("canvas");
background.width = w;
background.height = h;
background.ctx = background.getContext("2d");
const frameRate = 4; // How many frames between renders (normal update renders every 1/60 second so val of 1 is 60 times a second)
var frameCount = 0;
var restartIn = 120; // frameCount befor restart
var restartCount = 120;
var restart = true;
var globalTime;
var vis;


function *grahamScan(points) {
    points.sort(function (a, b) {
        return a.x - b.x
    })

    var stack1 = [];
    var stack2 = [];

    stack1.push(points[0])
    stack1.push(points[1])

    for (var i = 2; i < points.length; i++) {
        var len = stack1.length > 1;
        var turn = RTT(stack1[stack1.length - 2], stack1[stack1.length - 1], points[i]) === 1;
        reDraw(points, stack1, stack2);
        ctx.strokeStyle = "red";
        ctx.beginPath();
        ctx.moveTo(stack1[stack1.length - 2].x, stack1[stack1.length - 2].y);
        ctx.lineTo(stack1[stack1.length - 1].x, stack1[stack1.length - 1].y);
        ctx.stroke();
        yield null; // not interested in what is returned just want to code to stop here
        while (len && !turn) {
            stack1.pop();
            reDraw(points, stack1, stack2);
            yield null; // not interested in what is returned just want to code to stop here
            len = stack1.length > 1;
            if (!len) {
                break
            }
            turn = RTT(stack1[stack1.length - 2], stack1[stack1.length - 1], points[i]) === 1;
        }
        stack1.push(points[i]);

    }
    reDraw(points, stack1, stack2);
    ctx.strokeStyle = "red";
    ctx.beginPath();
    ctx.moveTo(stack1[stack1.length - 2].x, stack1[stack1.length - 2].y);
    ctx.lineTo(stack1[stack1.length - 1].x, stack1[stack1.length - 1].y);
    ctx.stroke();
    yield null; // not interested in what is returned just want to code to stop here

    stack2 = [];
    stack2.push(points[points.length - 1])
    stack2.push(points[points.length - 2])

    for (i = 2; i < points.length; i++) {
        len = stack2.length > 1;
        turn = RTT(stack2[stack2.length - 2], stack2[stack2.length - 1], points[points.length - i - 1]) === 1;
        reDraw(points, stack1, stack2);
        ctx.strokeStyle = "red";
        ctx.beginPath();
        ctx.moveTo(stack2[stack2.length - 2].x, stack2[stack2.length - 2].y);
        ctx.lineTo(stack2[stack2.length - 1].x, stack2[stack2.length - 1].y);
        ctx.stroke();
        yield null; // not interested in what is returned just want to code to stop here
        while (len && !turn) {
            stack2.pop();
            reDraw(points, stack1, stack2);
            yield null; // not interested in what is returned just want to code to stop here
            len = stack2.length > 1;
            if (!len) {
                break
            }
            turn = RTT(stack2[stack2.length - 2], stack2[stack2.length - 1], points[points.length - i - 1]) === 1;
        }
        stack2.push(points[points.length - i - 1]);

    }
    ctx.beginPath();
    ctx.moveTo(stack2[stack2.length - 2].x, stack2[stack2.length - 2].y);
    ctx.lineTo(stack2[stack2.length - 1].x, stack2[stack2.length - 1].y);
    ctx.stroke();
    reDraw(points, stack1, stack2);
    yield "allDone";

}
function reDraw(points, stack1, stack2) {
    ctx.strokeStyle = "blue";
    ctx.lineWidth = 3;
    for (var k = 1; k < stack1.length; k++) {
        ctx.beginPath();
        ctx.moveTo(stack1[k - 1].x , stack1[k - 1].y );
        ctx.lineTo(stack1[k].x , stack1[k].y );
        ctx.stroke();
    }
    ctx.strokeStyle = "green";
    ctx.lineWidth = 2;
    for (var l = 1; l < stack2.length; l++) {
        ctx.beginPath();
        ctx.moveTo(stack2[l - 1].x , stack2[l - 1].y );
        ctx.lineTo(stack2[l].x , stack2[l].y );
        ctx.stroke();
    }
    ctx.lineWidth = 1;
}

function RTT(a, b, c) {
    return Math.sign((b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x));
}

function randomBell(min,max){  // over kill but smooth distrabution
    var r = 0;
    for(var i = 0; i < 4; i++){
        r += Math.random()+Math.random()+Math.random()+Math.random()+Math.random()+Math.random();
    }
    r /= (4*6);
    return (max-min)*r + min;
}
function createRandomPoints(count){
    var p = []; // points;
    for(var i = 0; i < count; i ++){
        p.push({x : randomBell(10,canvas.width-20), y : randomBell(10,canvas.height-20)});
    }
    return p;
}
function renderPoints(points,ctx){
    ctx.clearRect(0,0,ctx.canvas.width,ctx.canvas.height);
    ctx.strokeStyle = "red";
    ctx.lineWidth = "1";
    ctx.lineJoin = "round";
    points.forEach(function(p){
        ctx.strokeRect(p.x-1.5,p.y-1.5,3,3);
    });
}

function rescalePointsToFit(points,w,h){
    points.sort(function(a,b){return a.x - b.x});
    var minx = points[0].x;
    var maxx = points[points.length-1].x;
    points.sort(function(a,b){return a.y - b.y});
    var miny = points[0].y;
    var maxy = points[points.length-1].y;
    var scale = Math.min((w-20)/(maxx-minx),(h-20)/(maxy-miny));
    var midx = (maxx-minx) * 0.5 + minx;
    var midy = (maxy-miny) * 0.5 + miny;
    points.forEach(function(p){
        p.x = (p.x - midx) * scale + midx;
        p.y = (p.y - midy) * scale + midy;
    });
    return points;
}
// main update function
function update(timer){
    globalTime = timer;
    frameCount += 1;
    if(restart){
        restartCount += 1;
        if(restartCount >= restartIn){  // restart visulisation
            points = rescalePointsToFit(createRandomPoints(Math.floor(randomBell(10,500))),w-30,h-30);
            renderPoints(points,background.ctx);            
            vis = grahamScan(points); // create new generator 
            restart = false;
            frameCount = 0;
            
        }
    }
    if(!restart && (frameCount % frameRate === 0)){
        ctx.setTransform(1,0,0,1,0,0); // reset transform
        ctx.globalAlpha = 1;           // reset alpha
        ctx.clearRect(0,0,w,h);
        ctx.drawImage(background,0,0); // draw backround containing points;
        if(vis.next().value !== null){  // step the algorithum and check if done
            restart = true;
            restartCount = 0;
        }
    }
    requestAnimationFrame(update);

}
requestAnimationFrame(update);
 
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何为凸包算法的中间步骤设置动画? 的相关文章

随机推荐

  • 致命错误:对非对象调用成员函数 close()。 MySQLi问题

    当我上传到实时服务器时 出现以下错误 它在本地主机上运行正常 我认为这很奇怪 致命错误 在非对象上调用成员函数 close 它所指的行 stmt gt close 与数据库的连接 connection new mysqli MYSQL HO
  • Android 搜索对话框的样式/主题

    tl dr 搜索对话框拾取应用程序主题中的白色文本样式 使搜索文本不可见 我正在为一个看似微不足道的问题而苦苦挣扎 我的应用程序使用深色背景 并且我使用 EEEEEE 将文本颜色调整为比标准灰色更亮 我已经实现了一个搜索对话框 Honeyc
  • Redis 主动-主动复制

    我在用redis version 2 8 3 我想建立一个redis簇 但这个簇中应该有multiple master 这意味着我需要多个对所有其他节点具有写入访问权限和应用能力的节点 我可以构建一个包含一个主服务器和多个从服务器的集群 我
  • 仅使用传递的参数子集创建一个namedtuple对象

    我使用以下方法从 MySQL 数据库中提取行作为字典 使用 SSDictCursor 并进行一些处理 from collections import namedtuple class Foo namedtuple Foo id name a
  • 是否可以在 iframe 上停止/暂停视频?

    这是视频 我想停止或暂停 可以吗 只想坚持在 iframe 上 http jsfiddle net karimkhan 2Lgxk5h3 7 js jquery 中有相同的函数吗
  • Qt的IP地址小部件,类似于MFC的IP地址控制

    我正在Qt中寻找一个类似于MFC的IP地址控制的小部件 有谁知道这样的小部件 或者我如何创建一个小部件 我不知道什么是 MFC IP Widget 但看起来它是一个输入 IP 地址的 Widget 您需要使用带有 inputMask 000
  • WPF 组合框显示成员路径

    好吧 我查看了其他问题 似乎没有得到答案 所以希望这里有人能得到答案 非常简单的问题为什么 DisplayMemberPath 属性不绑定到该项目
  • jsonp comet 挂起请求导致浏览器上丑陋的“正在加载”状态

    我正在使用 jsonp 进行跨域 comet 请求 正在加载 状态确实很烦人 有什么方法可以用javascript抑制这种情况吗 对于那些不熟悉 jsonp 的人来说 它基本上会注入一个脚本标记 但在我的情况下 我将请求挂在我的服务器上 直
  • 嵌套函数定义和范围(UnboundLocalError)[重复]

    这个问题在这里已经有答案了 为什么下面的代码无效 def foo1 x 5 def bar if x 5 x 6 print x bar 虽然此代码有效 def foo2 x 5 def bar if x 5 print ok print
  • 使用 jQuery 获取 html 元素的宽度(以百分比 % 表示)

    If I alertcss 选择器设置为的元素width 100 我明白了px 有什么方法可以让它进来吗 根据 css 设置 我需要它来修复一些具有流畅布局的脚本 css my element width 100 javascript al
  • 对指针列表进行排序时出现问题

    我正在尝试对指针列表进行排序 在我的例子中 每个指针都是 Job 类型 我的目的是按序列号对作业进行排序 void Container jobSort list
  • ionic 2 如何使用索引动态加载

    对于 ionic 1 我已经这样做了ng if index 3 0 但我需要在网格视图中动态加载数据 其中一行有两个列 我怎样才能做到这一点 我尝试了下面的代码 in my schudle ts ResourceData name ksjs
  • 比较两个二维数组并获取交集和差异

    我想比较两个数组之间整行的数据并生成 相交行的数组和 一个数组 其中第一个数组中的行在第二个数组中找不到 并且 一个数组 其中在第一个数组中找不到第二个数组中的行 我有两个多维数组 array1 sight id gt 13 locatio
  • JavaScript createElementNS 和 SVG

    我想使用 Javascript 创建内联 SVG 图形 然而 似乎createElementNS函数应用一些标准化并将所有标签转换为小写 这对于 HTML 来说很好 但对于 XML SVG 来说就不行了 我使用的NS是http www w3
  • 如何在 PHP 中传递系统命令时转义字符

    我有一个运行 PHP HTML 页面的 Linux Web 服务器 for loop instruction I m constructing the instruction here instruction lspci grep i vg
  • 如何使用 python 3.2 安装 MySQLdb

    我正在尝试使用 python 连接到 mySQL 据我了解 你需要有 MySQLdb 它是一些 python 连接器模块 我的第一个问题是没有正确版本的 mySQLdb 可以找到here 然后我应该打开一个命令行窗口并导航到我刚刚下载的文件
  • Python 子集和

    我正在尝试编写一个函数 该函数不仅可以确定集合的子集之和是否添加到所需的目标数字 而且还可以打印作为解决方案的子集 这是我用于查找子集是否存在的代码 def subsetsum array num if num 0 or num lt 1
  • 如何在 React js Web 应用程序中添加 PAYTM 网关集成?

    我从这里开始工作https github com paytm paytm pg node sdk sample blob master javascript DemoApp js我在 Express 中使用了它 它正在工作 但现在我想从反应
  • 与尺度无关的元素

    我正在开发一个 2D 计算几何库 我希望能够吐出图片来帮助调试 我想要的图元是点 线段和文本 但我事先不知道我有兴趣查看什么比例 也许只有一小部分多边形无法正常工作 所以我也需要能够缩放和平移图像 我挂了SVGPan当我在 Chrome 中
  • 如何为凸包算法的中间步骤设置动画?

    我正在尝试制作某种动画 以便用户可以理解或看到查找点集的凸包所采取的步骤 例如 假设我使用下面的代码进行 Graham Scan 有哪些方法可以对线条添加和删除进行动画处理 即使对于很多点 也需要时间来处理 然后几乎立即将它们全部绘制出来