加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
文件
克隆/下载
index.html 24.39 KB
一键复制 编辑 原始数据 按行查看 历史
兰博文 提交于 2023-09-23 00:26 . 补充提交
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="initial-scale=1, maximum-scale=3, minimum-scale=1, user-scalable=no">
<title>PID Web Sim</title>
<link rel="shortcut icon" href="img/icon.png" type="image/x-icon">
<style>
/* 状态窗口移动动画 */
@keyframes status-float-down-anim{
0% {transform: translateY(0);}
100% {transform: translateY(80%);}
}
@keyframes status-float-up-anim{
0% {transform: translateY(80%);}
100% {transform: translateY(0);}
}
/* 教程窗口移动动画 */
@keyframes teach-float-right-anim{
0% {transform: translateX(-100vw);}
100% {transform: translateX(0);}
}
@keyframes teach-float-left-anim{
0% {transform: translateX(0);}
100% {transform: translateX(-100vw);}
}
/* 教程文字淡入动画 */
@keyframes teach-info-enter-anim{
0% {opacity: 0;}
100% {opacity: 0.8;}
}
</style>
<style>
/* 取消所有段落外边界 */
p{margin: 0px;}
/* 禁用所有表格中的换行 */
td{white-space: normal;}
/* 设置全屏背景 */
body{
background-color: #F4F3DF;
margin: 0;
overflow-x: hidden;
overflow-y: hidden;
}
/* 数字编辑框样式 */
input[type="number"]{
background-color: transparent;
border-left: 0px;
border-right: 0px;
border-top: 0px;
border-bottom: 2px solid #7DA3A8;
text-align: center;
max-width: 40px;
}
/* 拖动条滑动条样式 */
input[type="range"]{
-webkit-appearance: none;
height: 12px;
border-radius: 6px;
background-color: lightgrey;
}
/* 拖动条滑块样式 */
input[type="range"]::-webkit-slider-thumb{
-webkit-appearance: none;
height: 12px;
width: 12px;
border-radius: 6px;
background-color: #7DA3A8;
}
/* 单选按钮样式(隐藏按钮) */
input[type="radio"]{
height: 0;
width: 0;
}
/* 单选按钮后的标签样式 */
input[type="radio"]+label{
font-family: Georgia, serif;
font-size: small;
}
/* 选中后的单选按钮后的标签样式 */
input[type="radio"]:checked+label{
font-weight: bold;
color: #7DA3A8;
}
/* 按钮样式 */
button{
background-color: #F4F3DF;
border: 2px solid #7DA3A8;
border-radius: 5px;
font-family: Georgia, serif;
font-weight: bold;
color: #7DA3A8;
margin: 2px;
white-space: nowrap;
}
/* 按钮被点击时的样式 */
button:active{
background-color: #7DA3A8;
color: white;
}
/* 横向分布div */
.hori-flex-div{
display: flex;
flex-direction: row;
}
/* 纵向分布div */
.vert-flex-div{
display: flex;
flex-direction: column;
}
/* 竖向分界线div */
.vert-line-div{
width: 0px;
border-left: solid 2px #7DA3A8;
margin: 10px 10px;
}
/* 表头样式 */
.table-title{
font-family: Georgia, serif;
font-weight: bold;
font-size: small;
white-space: nowrap;
margin: 5px 0px;
}
/* 配置参数标签样式 */
.param-label{
font-family: Georgia, serif;
font-weight: bold;
font-size: small;
text-align: right;
white-space: nowrap;
margin: 5px 0px;
}
/* 状态窗口中标签样式 */
.status-label{
font-family: Georgia, serif;
font-size: small;
text-align: center;
white-space: nowrap;
margin: 0px 5px;
}
/* 状态浮动窗口样式 */
#status-float-div{
position: fixed;
display: flex;
flex-direction: row;
bottom: 0px;
left: 0px;
max-height: 200px;
width: 100%;
max-width: 800px;
background-color: lightgray;
opacity: 0.8;
border-radius: 10px;
}
/* 示波器canvas样式 */
#scope-canvas{
width: 100%;
border: 2px solid #7DA3A8;
margin: 20px;
flex-grow: 1;
}
/* matter.js渲染canvas样式 */
#render-canvas{
width: 100%;
height: 100%;
}
/* 顶部区域样式 */
#top-div{
display: flex;
flex-direction: column;
position: relative;
}
/* 头部区域样式 */
#header-div{
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
background-color: white;
padding: 10px 10px;
margin-bottom: 20px;
box-shadow: 0px 0px 10px 0px rgba(0,0,0,0.15);
border-bottom-left-radius: 15px;
border-bottom-right-radius: 15px;
}
/* 头部子div样式 */
#header-div > div{
display: flex;
flex-direction: row;
align-items: center;
}
/* 图标样式 */
#icon-img{
width: 25px;
height: 25px;
margin: 5px;
}
/* 标题文字样式 */
#title-text{
font-family: Georgia, serif;
font-weight: bold;
font-size: large;
margin: 0px;
padding: 0px;
}
/* 头部链接样式 */
#header-div a{
font-size: small;
font-weight: bold;
margin: 0px 5px;
padding: 0px;
text-decoration: none;
color: #7DA3A8;
justify-content: center;
display: flex;
}
/* 头部链接图标样式 */
#header-div a img{
width: 15px;
height: 15px;
margin: 0px 5px;
}
/* 配置区域样式 */
#settings-div{
display: flex;
flex-direction: row;
overflow-y: auto;
border-bottom: solid 2px lightgray;
}
/* 教程区域样式 */
#teach-div{
position: absolute;
display: flex;
flex-direction: row;
background-color: lightgray;
opacity: 0.8;
top: 100%;
left: 0px;
width: 95%;
max-width: 800px;
padding: 10px;
border-top-right-radius: 15px;
border-bottom-right-radius: 15px;
align-items: center;
transform: translateX(-100vw);
}
/* 教程页码文字样式 */
#teach-step-ratio{
font-size: x-small;
color: #7DA3A8;
font-weight: bold;
text-align: center;
}
/* 教程文字区域样式 */
#teach-info-div{
overflow-y: auto;
}
/* 教程文字样式 */
#teach-info{
font-size: medium;
margin: 5px;
}
/* 电脑端样式, 各元素居中显示 */
@media (min-width: 800px){
#settings-div{
justify-content: center;
}
#status-float-div{
margin: auto;
left: 0px;
right: 0px;
}
#teach-div{
margin: 0 auto;
left: 0px;
right: 0px;
border-top-left-radius: 15px;
border-bottom-left-radius: 15px;
}
}
</style>
<script src="scripts/matter.min.js"></script>
<script src="scripts/pid.js?v=1"></script>
<script src="scripts/teach-info.js?v=4"></script>
</head>
<body>
<div id="top-div">
<div id="header-div">
<div class="hori-flex-div">
<img id="icon-img" src="img/icon.png">
<p id="title-text">PID Web Sim</p>
</div>
<div class="hori-flex-div">
<a href="https://blog.csdn.net/skythinker616/article/details/123019829"><img src="img/csdn.png"/>CSDN</a>
<a href="https://gitee.com/skythinker/pid-simulator-web"><img src="img/gitee.png"/>Gitee</a>
</div>
</div>
<div id="settings-div">
<div>
<table>
<th colspan="3" class="table-title">全局设置</th>
<tr>
<td><p class="param-label">教程模式</p></td>
<td>
<div class="hori-flex-div">
<input id="select-teach-on" name="teach-switch" type="radio" onclick="onTeachSwitch(true);"><label for="select-teach-on"></label>
<input id="select-teach-off" name="teach-switch" type="radio" checked onclick="onTeachSwitch(false);"><label for="select-teach-off"></label>
</div>
</td>
</tr>
<tr>
<td><p class="param-label">鼠标操作</p></td>
<td>
<div class="hori-flex-div">
<input id="select-ctrl-ball" name="mouse-ctrl" type="radio" checked><label for="select-ctrl-ball">推动小球</label>
<input id="select-ctrl-target" name="mouse-ctrl" type="radio"><label for="select-ctrl-target">修改目标</label>
</div>
</td>
</tr>
<tr>
<td><p class="param-label">PID类型</p></td>
<td>
<div class="hori-flex-div">
<input id="select-pid-single" name="pid-type" type="radio" checked onclick="onPIDTypeChange(true);"><label for="select-pid-single">单级PID</label>
<input id="select-pid-cascade" name="pid-type" type="radio" onclick="onPIDTypeChange(false);"><label for="select-pid-cascade">串级PID</label>
</div>
</td>
</tr>
<tr>
<td><p class="param-label">干扰恒力</p></td>
<td><input id="static-force-range" class="input-range" type="range" min="-10" max="10" step="0.5" value="0" oninput="onRangeInput(event)"></td>
<td><input id="static-force-number" class="input-range-value" type="number" value="0" onchange="staticForce.x=parseFloat(this.value)"></td>
</tr>
</table>
</div>
<div class="vert-line-div"></div>
<div id="single-param-div">
<table>
<th colspan="3" class="table-title">单级PID参数</th>
<tr>
<td><p class="param-label">KP</p></td>
<td><input id="single-kp-range" class="input-range" type="range" min="0" max="10" step="0.01" value="0" oninput="onRangeInput(event)"></td>
<td><input id="single-kp-number" class="input-range-value" type="number" value="0" onchange="singlePID.kp=parseFloat(this.value)"></td>
</tr>
<tr>
<td><p class="param-label">KI</p></td>
<td><input id="single-ki-range" class="input-range" type="range" min="0" max="1" step="0.01" value="0" oninput="onRangeInput(event)"></td>
<td><input id="single-ki-number" class="input-range-value" type="number" value="0" onchange="singlePID.ki=parseFloat(this.value)"></td>
</tr>
<tr>
<td><p class="param-label">KD</p></td>
<td><input id="single-kd-range" class="input-range" type="range" min="0" max="10" step="0.01" value="0" oninput="onRangeInput(event)"></td>
<td><input id="single-kd-number" class="input-range-value" type="number" value="0" onchange="singlePID.kd=parseFloat(this.value)"></td>
</tr>
<tr>
<td><p class="param-label">积分限幅</p></td>
<td><input id="single-mi-range" class="input-range" type="range" min="0" max="100" step="0.01" value="0" oninput="onRangeInput(event)"></td>
<td><input id="single-mi-number" class="input-range-value" type="number" value="0" onchange="singlePID.maxInt=parseFloat(this.value)"></td>
</tr>
<tr>
<td><p class="param-label">输出限幅</p></td>
<td><input id="single-mo-range" class="input-range" type="range" min="0" max="100" step="0.01" value="0" oninput="onRangeInput(event)"></td>
<td><input id="single-mo-number" class="input-range-value" type="number" value="0" onchange="singlePID.maxOut=parseFloat(this.value)"></td>
</tr>
</table>
</div>
<div id="cascade-param-div" class="hori-flex-div" style="display: none;">
<table>
<th colspan="3" class="table-title">串级内环(速度环)参数</th>
<tr>
<td><p class="param-label">KP</p></td>
<td><input id="inner-kp-range" class="input-range" type="range" min="0" max="10" step="0.01" value="0" oninput="onRangeInput(event)"></td>
<td><input id="inner-kp-number" class="input-range-value" type="number" value="0" onchange="cascadePID.inner.kp=parseFloat(this.value)"></td>
</tr>
<tr>
<td><p class="param-label">KI</p></td>
<td><input id="inner-ki-range" class="input-range" type="range" min="0" max="1" step="0.01" value="0" oninput="onRangeInput(event)"></td>
<td><input id="inner-ki-number" class="input-range-value" type="number" value="0" onchange="cascadePID.inner.ki=parseFloat(this.value)"></td>
</tr>
<tr>
<td><p class="param-label">KD</p></td>
<td><input id="inner-kd-range" class="input-range" type="range" min="0" max="10" step="0.01" value="0" oninput="onRangeInput(event)"></td>
<td><input id="inner-kd-number" class="input-range-value" type="number" value="0" onchange="cascadePID.inner.kd=parseFloat(this.value)"></td>
</tr>
<tr>
<td><p class="param-label">积分限幅</p></td>
<td><input id="inner-mi-range" class="input-range" type="range" min="0" max="100" step="0.01" value="0" oninput="onRangeInput(event)"></td>
<td><input id="inner-mi-number" class="input-range-value" type="number" value="0" onchange="cascadePID.inner.maxInt=parseFloat(this.value)"></td>
</tr>
<tr>
<td><p class="param-label">输出限幅</p></td>
<td><input id="inner-mo-range" class="input-range" type="range" min="0" max="100" step="0.01" value="0" oninput="onRangeInput(event)"></td>
<td><input id="inner-mo-number" class="input-range-value" type="number" value="0" onchange="cascadePID.inner.maxOut=parseFloat(this.value)"></td>
</tr>
</table>
<div class="vert-line-div"></div>
<table>
<th colspan="3" class="table-title">串级外环(位置环)参数</th>
<tr>
<td><p class="param-label">KP</p></td>
<td><input id="outer-kp-range" class="input-range" type="range" min="0" max="10" step="0.01" value="0" oninput="onRangeInput(event)"></td>
<td><input id="outer-kp-number" class="input-range-value" type="number" value="0" onchange="cascadePID.outer.kp=parseFloat(this.value)"></td>
</tr>
<tr>
<td><p class="param-label">KI</p></td>
<td><input id="outer-ki-range" class="input-range" type="range" min="0" max="1" step="0.01" value="0" oninput="onRangeInput(event)"></td>
<td><input id="outer-ki-number" class="input-range-value" type="number" value="0" onchange="cascadePID.outer.ki=parseFloat(this.value)"></td>
</tr>
<tr>
<td><p class="param-label">KD</p></td>
<td><input id="outer-kd-range" class="input-range" type="range" min="0" max="10" step="0.01" value="0" oninput="onRangeInput(event)"></td>
<td><input id="outer-kd-number" class="input-range-value" type="number" value="0" onchange="cascadePID.outer.kd=parseFloat(this.value)"></td>
</tr>
<tr>
<td><p class="param-label">积分限幅</p></td>
<td><input id="outer-mi-range" class="input-range" type="range" min="0" max="100" step="0.01" value="0" oninput="onRangeInput(event)"></td>
<td><input id="outer-mi-number" class="input-range-value" type="number" value="0" onchange="cascadePID.outer.maxInt=parseFloat(this.value)"></td>
</tr>
<tr>
<td><p class="param-label">输出限幅</p></td>
<td><input id="outer-mo-range" class="input-range" type="range" min="0" max="100" step="0.01" value="0" oninput="onRangeInput(event)"></td>
<td><input id="outer-mo-number" class="input-range-value" type="number" value="0" onchange="cascadePID.outer.maxOut=parseFloat(this.value)"></td>
</tr>
</table>
</div>
</div>
<div id="teach-div">
<div class="vert-flex-div">
<button id="teach-prev-btn" onclick="onTeachStep(false);">上一步</button>
<button id="teach-next-btn" onclick="onTeachStep(true);">下一步</button>
<p id="teach-step-ratio">0/0</p>
</div>
<div id="teach-info-div">
<p id="teach-info"></p>
</div>
</div>
</div>
<canvas id="render-canvas"></canvas>
<div id="status-float-div" onclick="onStatusFloatClick(event)">
<table style="align-self: center;">
<tr><td><p class="status-label">目标位置</p></td></tr>
<tr><td><p id="status-target-pos" class="status-label" style="color: green;">000.00</p></td></tr>
<tr><td><p class="status-label">--------</p></td></tr>
<tr><td><p class="status-label">当前位置</p></td></tr>
<tr><td><p id="status-ball-pos" class="status-label" style="color: red;">000.00</p></td></tr>
<tr><td><p class="status-label">--------</p></td></tr>
<tr><td><p class="status-label">PID输出</p></td></tr>
<tr><td><p id="status-pid-out" class="status-label">000.00</p></td></tr>
</table>
<canvas id="scope-canvas"></canvas>
</div>
<!-- 物理运算逻辑 -->
<script type="text/javascript">
//全局变量定义
const ballRadius=20;//小球半径
var scrWidth=window.innerWidth;//渲染画布宽度为屏幕高度
var scrHeight=window.innerHeight-document.getElementById("settings-div").clientHeight;//渲染画布高度为屏幕高度减去头部配置区域高度
var targetPos=scrWidth/2;//目标位置
var singlePID=new PID(0,0,0,0,0);//单级PID对象
var cascadePID=new CascadePID([0,0,0,0,0],[0,0,0,0,0]);//串级PID对象
var pidForce={x:0,y:0};//PID计算产生的力
var staticForce={x:0,y:0};//干扰恒力
var isSinglePID=true;//当前PID类型 true:单级 false:串级
const histQueueLength=200;//历史数据留存个数(用于示波器绘图)
var histSamples=new Array();//历史数据队列
//matter引擎相关工具
var Engine=Matter.Engine;
var Render=Matter.Render;
var World=Matter.World;
var Bodies=Matter.Bodies;
var Body=Matter.Body;
var MouseConstraint=Matter.MouseConstraint;
var Mouse=Matter.Mouse;
var Events=Matter.Events;
//创建引擎,取消重力
var engine=Engine.create({
gravity:{x:0,y:0}
});
//在指定canvas上创建渲染器
var render=Render.create({
canvas:document.getElementById("render-canvas"),
engine:engine,
options:{
showVelocity:false, //不显示速度
width:scrWidth,
height:scrHeight,
background:"#F4F3DF", //背景颜色与body一致
wireframes:false //取消线框模式
}
});
//创建鼠标约束(用于捕获鼠标事件)
var mouse=Mouse.create(render.canvas);//仅捕捉渲染画布上的事件
var mouseConstraint=MouseConstraint.create(engine,{
mouse:mouse,
constraint:{
stiffness:0.01,
render:{visible: false}
},
collisionFilter:{group:-1} //不约束小球
});
World.add(engine.world,mouseConstraint);
//创建小球刚体
var ball=Bodies.circle(scrWidth/2,scrHeight/2,ballRadius,{
friction:0, //阻力为0
frictionAir:0,
restitution:0, //碰撞不反弹
mass:1000,
render:{fillStyle:"#F5594A"}, //小球颜色
collisionFilter:{group:-1} //不被鼠标约束
});
World.add(engine.world,ball);
Engine.run(engine);//运行引擎
Render.run(render);//运行渲染器
var renderContext=render.context;//获取渲染器的绘图对象,用于自定义绘图
//渲染器绘制直线
function renderDrawLine(x1,y1,x2,y2,width,color,dash){
if(dash)
renderContext.setLineDash([5,5]);
else
renderContext.setLineDash([5,0]);
renderContext.lineWidth=width;
renderContext.strokeStyle=color;
renderContext.beginPath();
renderContext.moveTo(x1,y1);
renderContext.lineTo(x2,y2);
renderContext.closePath();
renderContext.stroke();
}
//在渲染器完成渲染后进行自定义绘制,否则会被覆盖
Events.on(render,"afterRender",function(event){
renderDrawLine(0,scrHeight/2,scrWidth,scrHeight/2,1,"gray",true);//坐标轴线
renderDrawLine(ball.position.x,scrHeight/2,ball.position.x+pidForce.x*3,scrHeight/2,5,"#388AF5",false);//PID施力线
renderDrawLine(targetPos,scrHeight/2-ballRadius/2,targetPos,scrHeight/2+ballRadius/2,3,"#00E06D",false);//目标位置线
});
//在引擎更新计算前对小球施加力(每次更新后受力会清零)
Events.on(engine,"beforeUpdate",function(event){
Body.applyForce(ball,{x:0,y:0},staticForce);
Body.applyForce(ball,{x:0,y:0},pidForce);
});
//鼠标操作逻辑
var lastRenderMousePosX=0,isRenderMouseDown=false;//上个事件的鼠标位置,当前是否按下
//注册鼠标按下事件
Events.on(mouseConstraint,"mousedown",function(event){
isRenderMouseDown=true;
lastRenderMousePosX=event.mouse.position.x;
});
//注册鼠标移动事件
Events.on(mouseConstraint,"mousemove",function(event){
if(isRenderMouseDown){
var nowPosX=event.mouse.position.x;
var speed=nowPosX-lastRenderMousePosX;//计算鼠标移动速度(相对上次事件的移动距离)
if(document.getElementById("select-ctrl-ball").checked)//推动小球模式下修改小球位置
ball.position.x+=speed*0.1;
else //控制目标模式下修改目标位置
targetPos+=speed;
lastRenderMousePosX=nowPosX;//记录本次鼠标位置
}
});
//注册鼠标抬起事件
Events.on(mouseConstraint,"mouseup",function(event){
isRenderMouseDown=false;
});
//PID定时运算
setInterval(function(event){
if(isSinglePID){ //单级PID计算
singlePID.calc(targetPos,ball.position.x);
pidForce.x=singlePID.output;
}else{ //串级PID计算
cascadePID.calc(targetPos,ball.position.x,ball.velocity.x);
pidForce.x=cascadePID.output;
}
histSamples.push({target:targetPos,ball:ball.position.x});//将当前状态入队
if(histSamples.length>histQueueLength)//保持队列不超过指定长度
histSamples.shift();
},20);//每20ms计算一次
</script>
<!-- 界面元素响应 -->
<script type="text/javascript">
//滑动条滑动事件
function onRangeInput(event){
var valueElement=document.getElementById(event.srcElement.id.replace("range","number"));//根据id找出对应的数字编辑框
valueElement.value=event.srcElement.value;//设置编辑框的值
valueElement.onchange();//触发编辑框改变事件
}
//PID类型切换
function onPIDTypeChange(isSingle){
isSinglePID=isSingle;
if(isSingle){ //切换为单级PID模式
document.getElementById("single-param-div").style.display="flex";//显示单级PID配置项
document.getElementById("cascade-param-div").style.display="none";//隐藏串级PID配置项
cascadePID.clear();//清除串级PID历史数据
}else{ //切换为串级PID模式
document.getElementById("single-param-div").style.display="none";
document.getElementById("cascade-param-div").style.display="flex";
singlePID.clear();
}
}
//教程模式切换
function onTeachSwitch(switchon){
var teachDiv=document.getElementById("teach-div");//获取窗口div
if(switchon){ //展开窗口
teachDiv.style.animation="1s teach-float-right-anim";
teachDiv.style.transform="translateX(0)";
}else{ //收起窗口
teachDiv.style.animation="1s teach-float-left-anim";
teachDiv.style.transform="translateX(-100vw)";
}
}
//进入时询问是否开启教程
setTimeout(function(){
if(confirm("是否开启引导教程?")){
document.getElementById("select-teach-on").click();
}
},500);
//状态浮动窗口点击动画响应
var floatDivExpanded=true;//当前展开状态
function onStatusFloatClick(event){ //在窗口被点击时调用
var floatDiv=document.getElementById("status-float-div");//获取窗口div
if(floatDivExpanded){ //收起窗口
floatDiv.style.animation="1s status-float-down-anim";
floatDiv.style.transform="translateY(80%)";
floatDivExpanded=false;
}else{ //展开窗口
floatDiv.style.animation="1s status-float-up-anim";
floatDiv.style.transform="translateY(0)";
floatDivExpanded=true;
}
}
</script>
<!-- 教程窗口更新 -->
<script type="text/javascript">
var teachInfoText=document.getElementById("teach-info");
var teachStepRatioText=document.getElementById("teach-step-ratio");
var teachStep=0;
teachInfoText.innerHTML=teachInfos[0];
teachStepRatioText.innerText="1/"+teachInfos.length;
function onTeachStep(forward){
if(forward && teachStep<teachInfos.length-1){
teachStep++;
}else if(!forward && teachStep>0){
teachStep--;
}
teachInfoText.style.animation="0.5s teach-info-enter-anim";
teachInfoText.innerHTML=teachInfos[teachStep];
teachStepRatioText.innerText=""+(teachStep+1)+"/"+teachInfos.length;
}
teachInfoText.onanimationend=function(){
teachInfoText.style.animation="";
}
</script>
<!-- 状态浮动窗口更新 -->
<script type="text/javascript">
var scope=document.getElementById("scope-canvas");//获取示波器canvas元素
//定时更新状态窗口
setInterval(function(){
//绘制波形
var scopectx=scope.getContext("2d");//获取示波器2d绘图对象
scopectx.clearRect(0,0,scope.width,scope.height);//清空示波器内容
scopectx.lineWidth=1;//设置波形线宽
for(var i=0;i<histSamples.length-1;i++){ //依次绘制各历史数据点
//绘制目标位置
scopectx.strokeStyle="green";
scopectx.beginPath();
scopectx.moveTo(scope.width*i/histQueueLength,scope.height/2-(histSamples[i].target-scrWidth/2)*scope.height/scrWidth);
scopectx.lineTo(scope.width*(i+1)/histQueueLength,scope.height/2-(histSamples[i+1].target-scrWidth/2)*scope.height/scrWidth);
scopectx.closePath();
scopectx.stroke();
//绘制小球位置
scopectx.strokeStyle="red";
scopectx.beginPath();
scopectx.moveTo(scope.width*i/histQueueLength,scope.height/2-(histSamples[i].ball-scrWidth/2)*scope.height/scrWidth);
scopectx.lineTo(scope.width*(i+1)/histQueueLength,scope.height/2-(histSamples[i+1].ball-scrWidth/2)*scope.height/scrWidth);
scopectx.closePath();
scopectx.stroke();
}
//更新状态值
document.getElementById("status-target-pos").innerText=targetPos.toFixed(2);
document.getElementById("status-ball-pos").innerText=ball.position.x.toFixed(2);
document.getElementById("status-pid-out").innerText=pidForce.x.toFixed(2);
},30);//30ms更新一次
</script>
</body>
</html>
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化