functionality for adding multiple pendula

main
Benjamin Kraft 1 year ago
parent 75fdcebf73
commit 5f93512fac
  1. 75
      public/data/scripts/ts/manager.ts
  2. 10
      public/data/scripts/ts/pendulum.ts
  3. 88
      public/data/styles/color_picker.css
  4. 7
      public/data/styles/range_input.css
  5. 24
      public/index.html
  6. 2
      public/styles.css

@ -2,12 +2,13 @@ class Manager {
pendula: Pendulum[] = []
timescale = 1;
gravity = 9.81;
timescale: number;
gravity: number;
playing = false;
static Size = 200;
static SubSteps: number;
static Size = 20;
init(){
// @ts-ignore
@ -15,8 +16,9 @@ class Manager {
createApp({
data() {
return {
gravity: manager.gravity,
timescale: manager.timescale,
gravity: 0,
timescale: 0,
subSteps: 0,
playingBtn: "play"
};
},
@ -26,18 +28,29 @@ class Manager {
},
timescale(newScale: number){
manager.timescale = newScale;
},
subSteps(newSteps: number){
Manager.SubSteps = newSteps;
}
},
methods: {
togglePlay(){
manager.playing = !manager.playing;
this.playingBtn = manager.playing ? "pause" : "play";
},
resetSimulationControls(){
this.gravity = 9.81;
this.timescale = 1;
this.subSteps = 30;
}
},
mounted() {
this.resetSimulationControls();
},
name: "Simulation"
}).mount("#simulation");
createApp({
let app = createApp({
data(){
return {
segmentCount: 1,
@ -50,26 +63,52 @@ class Manager {
multiple: false,
pendulumCount: 10,
changeProperty: "angle",
changeAmount: 0.05,
changeAmount: 0.0005,
changeIndex: 0
}
},
methods: {
add() {
if (this.multiple){
let changeAmount = this.changeAmount;
let changeIndex = this.changeIndex;
let color = p.color(this.color);
p.colorMode(p.HSB, 100);
for (let i = 0; i < this.pendulumCount; i++){
let M = this.masses.slice(0, this.segmentCount);
let L = this.lengths.slice(0, this.segmentCount);
let startAngle = this.startAngle;
let progress = i / this.pendulumCount - 0.5;
switch (this.changeProperty){
case "angle":
startAngle += progress * 360 * changeAmount;
break;
case "mass":
M[changeIndex] += progress * M[changeIndex] * changeAmount;
break;
case "length":
L[changeIndex] += progress * L[changeIndex] * changeAmount;
break;
}
if (this.rainbow){
let hue = (progress + 0.5) * 100;
color = p.color(hue, 100, 100);
}
let newPendulum = new Pendulum(M, L, color, startAngle);
manager.pendula.push(newPendulum);
}
p.colorMode(p.RGB);
} else {
let color = p.color(this.color)
let M = this.masses.slice(0, this.segmentCount);
let L = this.lengths.slice(0, this.segmentCount);
let color = p.color(this.color);
let newPendulum = new Pendulum(M, L, color, this.startAngle);
manager.pendula.push(newPendulum);
}
p.colorMode(p.HSB, 100);
p.colorMode(p.RGB);
},
deleteAll(){
if (confirm("Delete all pendulums?")){
if (confirm("Delete all pendula?")){
manager.pendula.splice(0);
}
},
@ -95,8 +134,16 @@ class Manager {
this.resetMasses();
this.resetLengths();
},
name: "Preparation"
}).mount("#preparation");
name: "Add Pendula"
});
app.config.globalProperties.$filters = {
round(value: number, n: number){
if (!value)
return "0";
return +value.toFixed(n);
}
}
app.mount("#preparation");
}

@ -22,11 +22,9 @@ class Pendulum {
// using position based dynamics
update(h: number) {
const subSteps = 50;
h /= Manager.SubSteps;
h /= subSteps;
for (let k = 0; k < subSteps; k++) {
for (let k = 0; k < Manager.SubSteps; k++) {
// Classic PBD needs multiple loops
// Here, I can put all operations safely into one single loop,
@ -38,7 +36,7 @@ class Pendulum {
for (let i = 0; i < this.size; i++) {
// apply external force (gravity)
this.V[i].addC(0, manager.gravity * h * 50);
this.V[i].addC(0, manager.gravity * h);
// euler step
let currentP = Vector.Add(this.X[i],Vector.Mult(this.V[i], h));
@ -79,7 +77,7 @@ class Pendulum {
p.strokeWeight(1);
p.fill(255);
let scale = p.height / Manager.Size;
let scale = p.height * 0.95 / Manager.Size;
let p1 = new Vector(0, 0);
for (let p2 of this.X){
p2 = p2.copy();

@ -1,88 +0,0 @@
#color_picker{
width: 300px;
height: 25%;
margin: 20px;
margin-top: 50px;
border: 5px solid #000;
background-color: #000;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
position: relative;
}
#color_picker_numeric{
width: 80%;
padding: 5%;
margin: 5%;
background-color: #888;
border-radius: 10px;
overflow: hidden;
}
.color_picker_rgb{
float: left;
width: 22%;
height: 35px;
font-size: 25px;
color: #000;
}
.color_picker_rgb:nth-child(1){
margin-right: 10%;
margin-left: 3%;
background-color: #F00;
}
.color_picker_rgb:nth-child(2){
background-color: #0F0;
}
.color_picker_rgb:nth-child(3){
margin-left: 10%;
background-color: #00F;
color: #FFF;
}
#color_picker_hex{
width: 50%;
height: 30px;
font-size: 25px;
margin: 10% 25% 0 25%;
}
#saturation{
position: relative;
width: calc(100% - 33px);
height: 100%;
background: linear-gradient(to right, #FFF 0%, #F00 100%);
float: left;
margin-right: 6px;
}
#value {
width: 100%;
height: 100%;
background: linear-gradient(to top, #000 0%, rgba(255,255,255,0) 100%);
}
#sb_picker{
border: 2px solid;
border-color: #FFF;
position: absolute;
width: 14px;
height: 14px;
border-radius: 10px;
bottom: 50px;
left: 50px;
box-sizing: border-box;
z-index: 10;
}
#hue {
width: 27px;
height: 100%;
position: relative;
float: left;
background: linear-gradient(to bottom, #F00 0%, #F0F 17%, #00F 34%, #0FF 50%, #0F0 67%, #FF0 84%, #F00 100%);
}
#hue_picker {
position: absolute;
background: #000;
border-bottom: 1px solid #000;
top: 0;
width: 27px;
height: 2px;
}

@ -2,6 +2,7 @@ input[type=range] {
-webkit-appearance: none;
margin: 10px 0;
width: 100%;
height: 20px;
background: none;
}
input[type=range]:focus {
@ -9,7 +10,7 @@ input[type=range]:focus {
}
input[type=range]::-webkit-slider-runnable-track {
width: 100%;
height: 8.4px;
height: 9px;
cursor: pointer;
animate: 0.2s;
box-shadow: 1px 1px 1px #000000, 0px 0px 1px #0d0d0d;
@ -33,7 +34,7 @@ input[type=range]:focus::-webkit-slider-runnable-track {
}
input[type=range]::-moz-range-track {
width: 100%;
height: 8.4px;
height: 9px;
cursor: pointer;
animate: 0.2s;
box-shadow: 1px 1px 1px #000000, 0px 0px 1px #0d0d0d;
@ -52,7 +53,7 @@ input[type=range]::-moz-range-thumb {
}
input[type=range]::-ms-track {
width: 100%;
height: 8.4px;
height: 9px;
cursor: pointer;
animate: 0.2s;
background: transparent;

@ -7,7 +7,6 @@
<script src="data/lib/vue.global.js"></script>
<script src="data/scripts/js/main.js" type="text/javascript"></script>
<link href="styles.css" rel="stylesheet">
<link href="data/styles/color_picker.css" rel="stylesheet">
<link href="data/styles/range_input.css" rel="stylesheet">
<title>Pendulum</title>
</head>
@ -27,14 +26,14 @@
<span class="segment_label">({{ i - 1 }})</span>
<span class="segment_label">{{ masses[i - 1] }}kg</span>
<input type="range" v-model.number="masses[i - 1]" min="0.1" max="10" step=".1">
<span class="segment_label">{{ lengths[i - 1] }}m</span>
<span class="segment_label">{{ $filters.round(lengths[i - 1], 1) }}m</span>
<input type="range" v-model.number="lengths[i - 1]" min="0.1" max="10" step=".1">
</template>
</div>
<div class="horizontal_group">
<button class="reset_segments_btn" @click="resetMasses" title="Reset masses to 1"></button>
<button class="reset_btn" @click="resetMasses" title="Reset masses to 1"></button>
<button id="normalize_btn" @click="normalize" title="Normalize lengths to fit screen"></button>
<button class="reset_segments_btn" @click="resetLengths" title="Reset lengths to 1"></button>
<button class="reset_btn" @click="resetLengths" title="Reset lengths to 1"></button>
</div>
<label>
Starting Angle: {{startAngle}}°
@ -75,13 +74,13 @@
<input type="range" v-model.number="changeIndex" min="0" :max="segmentCount - 1" step="1">
</label>
<label>
Change Amount: {{ changeAmount * 100 }}%
<input type="range" v-model.number="changeAmount" min="0.0001" max="0.1" step="0.0001">
Change Amount: {{ $filters.round(changeAmount * 100, 3) }}%
<input type="range" v-model.number="changeAmount" min="0.00001" max="0.001" step="0.00001">
</label>
</template>
<div class="horizontal_group">
<button @click="add" id="add_btn"></button>
<button @click="deleteAll" id="delete_btn"></button>
<button @click="add" id="add_btn" :title="'Add configured ' + (multiple ? 'pendula' : 'pendulum')"></button>
<button @click="deleteAll" id="delete_btn" title="Delete all pendula"></button>
</div>
</fieldset>
<fieldset id="simulation">
@ -94,7 +93,14 @@
Timescale: x{{ timescale }}
<input type="range" v-model.number="timescale" min="0.01" max="3" step=".01">
</label>
<button @click="togglePlay" id="play_btn" :class="playingBtn"></button>
<label>
PBD Substeps: {{ subSteps }}
<input type="range" v-model.number="subSteps" min="1" max="100" step="1">
</label>
<div class="horizontal_group">
<button class="reset_btn" @click="resetSimulationControls" title="Reset simulation controls"></button>
<button @click="togglePlay" id="play_btn" :class="playingBtn" title="Pause/Resume simulation"></button>
</div>
</fieldset>
</div>
<div id="canvas_holder"></div>

@ -150,7 +150,7 @@ button:active {
border-color: #9a9a9a;
}
.reset_segments_btn {
.reset_btn {
background-image: url("data/images/refresh.svg");
background-color: #cbcbcb;
border-color: #9a9a9a;

Loading…
Cancel
Save