If you have studied physics in 3 dimensions, then you know what a pain it is to do vector math. It's abstract, it's tricky to draw on paper...
So I devised a
little aid. It's much easier to visualize the sum of Force and Moment (torque) vectors when you can actually turn it around, and compare at different angles.
With a little help from WebGL, it wasn't too hard to draw some axes, planes and arrows (although adding text was
a bit trickier).
This little script lets you create up to 6 forces (or moments) and it will sum them into a resultant force and moment. If the resultants are 0, then the forces are in equilibrium. This is very handy if you need to check your homework, of course. However, it won't solve it for you... unless you are looking for a single force to cancel the existing ones!
Features so far:
- add up to 6 colored vectors
- rotate the graph 90 degrees in each direction (click the graph once to unlock, again to lock)
- scale the graph
- calculates resultant force and moment vector displayed in white
Planned features:
- adding larger number of vector inputs
- animated vectors
- unit adjustments, degrees and radians
- vector solver...? (to determine unknown value of a vector with known direction)
The app currently runs on glMatrix 0.9.5. Next version will likely be updated to 2.0 (more info on http://glmatrix.net). It is a nice library already optimized for use with WebGL.
Enjoy the app. I use it to check my homework...
Feel free to fork
my repo on GitHub and let me know if you have any feature suggestions or bugs!
It's been awhile so I figured: it is time for a random post. Seriously, this is a post about random numbers.
If you code in Javascript, then Math.random() is probably already familiar to you. It does a nice job of generating (pretty) uniform variables from 0 to 1. A little multiplying and rounding and you can get integers from 0 to x, or -x to x, or whatever your needs.
But what if you want NON-uniform randoms? For statistics freaks: what if you need normal distribution? or Gamma? or Beta? Exponential? Chi-squared?
Well you are in luck! I found myself in need of some common distributions for a little side game I'm making, and went the extra mile to read some thesis papers about how to generate these useful distributions.
Rather than bore you with how it all works, here is the code for a RANDOM object you can insert into your code. I'm still working on Betas, they are bit trickier.
RANDOM.norm(n)
This returns an (approximately) normal bell curve centered on mean 0.5. The parameter "n" can be between 2 and 5 (default 2). Higher values of "n" makes the bell curve more narrow. The value returned is between 0 and 1.
RANDOM.inorm(n)
This
returns an inverse bell curve centered on mean 0.5. The
parameter "n" can be between 2 and 5 (default 2). Higher n makes output more likely to be near 0 or 1.
RANDOM.exp(n)
A nice easy exponential variable from 0 to 1. Increasing "n" makes the graph skewed towards 0. Default value for "n" is 2.
RANDOM.invexp(n)
This is just RANDOM.exp(n) times -1.
RANDOM.gamma(alpha,beta)
This is a conglomeration of a few generators. For alpha > 1 and < 0, it uses an algorithm developed by Kundu and Gupta. No beta is used for this algorithm.
For alpha >= 1 it uses the algorithms proposed by Martino and Luengo. It generates very quick results due to a low rejection rate. Default values are 1 and 1.
I could not have completed this without those great algorithms! If you use this code, please leave the credits they deserve.
TIP: RANDOM.gamma(2,2) returns a very nice gamma distribution. Be aware: the values returned may be higher than 1 (especially for high alpha and beta values). Alpha values below 1 resemble exponential distributions.
RANDOM.igamma(alpha,beta)
Simply RANDOM.gamma() times -1.
RANDOM.chi2(k)
A commonly used distribution resembling gamma distributions (I use this one a lot). Again, returned values may be higher than 1, especially for high "k".
RANDOM.coinFlip(weight)
A handy weighted coin flip that returns 0 or 1. Default weight is 2 (50/50) and MUST be > 1. Weights over 2 favor a positive outcome (i.e. weight of 3 will have 2:3 odds of returning 1).
The code:
As usual, the code is free to use under the GNU. Please give credit though, and I'd love to hear how this has been useful for anyone! Also, feel free to play around with the fiddle! (The code is adjusted slightly for display).
I'll post a follow-up if I ever get around to adding Beta randoms too!
var RANDOM = { //returns random between 0 and 1, normal distribution centered on 0.5
"norm": function(n) {
if (!(n > 2 && n <= 5)) n = 2;
var nrand = 0;
n = Math.floor(n);
for (var i = 1;i<=n*2;i++){
nrand += Math.random();
}
return nrand/(2*n);
},
"inorm": function(n) { //returns random between 0 and 1
if (!(n > 2 && n <= 5)) n = 2;
var nrand = 0;
n = Math.floor(n);
for (var i = 1;i<=n*2;i++){
nrand += Math.random();
}
return ((1 - Math.abs((nrand-n) / n))*(Math.abs(nrand-n)/(nrand-n)) + 1)/2;
},
"exp": function(n) { //returns greater than 0
if (!(n > 2 && n <= 5)) n = 2;
var nrand = Math.random();
for (var i = 2;i<=n;i++){
nrand *= Math.random();
}
return 2*nrand;
},
"invexp": function(n) { //returns less than 0
return -RANDOM.exp(n);
},
"gamma3": function(alpha) { //Kundu and Gupta algorithm 3 http://home.iitk.ac.in/~kundu/paper120.pdf
if (!alpha || Math.abs(alpha) > 1) alpha = 1; //alpha between 0 and 1
var d = 1.0334 - (0.0766*Math.exp(2.2942*alpha));
var a = Math.pow(2,alpha)*Math.pow(1-Math.exp(-d/2),alpha);
var b = alpha*Math.pow(d,alpha-1)*Math.exp(-d);
var c = a + b;
var U = Math.random();
var X = (U <= a/(a+b)) ? -2*Math.log(1-(Math.pow(c*U,1/alpha)/2)) : -Math.log(c*(1-U)/(alpha*Math.pow(d,alpha-1)));
var V = Math.random();
if (X <= d) {
var mess = (Math.pow(X,alpha-1)*Math.exp(-X/2))/(Math.pow(2,alpha-1)*Math.pow(1-Math.exp(-X/2),alpha-1));
if (V <= mess) return X;
else return this.gamma3(alpha);
} else { //X > d
if (V <= Math.pow(d/X,1-alpha)) return X;
else return this.gamma3(alpha);
}
//output is > 0 and possibly > 1
},
"gamma": function(alpha,beta) { //Martino and Luengo http://arxiv.org/pdf/1304.3800.pdf luca@tsc.uc3m.es luengod@ieee.org
if (!alpha || alpha <= 0) alpha = 1; //alpha >= 1 if negative or 0
if (alpha > 0 && alpha < 1) return this.gamma3(alpha); // use different algorithm
if (!beta || beta <= 0) beta = 1; //beta > 0
var alphap = Math.floor(alpha);
var X = Math.random();
for (var i=2;i<=alphap;i++){
X *= Math.random();
}
var betap = (alpha < 2) ? beta/alpha : beta*(alphap-1)/(alpha-1);
X = -Math.log(X)/betap;
var Kp = (alpha < 2) ? Math.exp(1-alpha)*Math.pow(alpha/beta,alpha-1) : Math.exp(alphap-alpha)*Math.pow((alpha-1)/beta,alpha-alphap);
//then accept with prob p(X)/pi(X)
if (alphap >= 2) {
if (Kp*Math.pow(X,alphap-1)*Math.exp(-betap*X) >= Math.pow(X,alpha-1)*Math.exp(-beta*X)) return X/alpha;
else return this.gamma(alpha,beta);
}
else if (alphap < 2) {
if (Kp*Math.exp(-betap*X) >= Math.pow(X,alpha-1)*Math.exp(-beta*X)) return X/alpha;
else return this.gamma(alpha,beta);
}
},
"igamma": function(alpha,beta) { // returns less than 0
return -RANDOM.gamma(alpha,beta);
},
"chi2": function(k) { // returns greater than 0
var nrand = RANDOM.norm(2);
nrand = nrand*nrand;
if (!k || k <= 1) return nrand;
for (var i=2;i<=k;i++){
var krand = RANDOM.norm(2);
krand = krand*krand;
nrand += krand;
}
return nrand;
},
"coinFlip": function(weight){
if (!weight || weight < 1) weight = 2;
if (Math.random() > 1/weight) return 1;
else return 0;
}
};
//Copyright 2016 Trevor Gast: codeandcompose.com
//RANDOM, non-uniform random generators (5 May 2016)
//GNU General Public License
Dice gif courtesy of http://bestanimations.com/Games/Dice/Dice.html
If the game has been too much fun for you...
I kid. I'll finish it someday. Probably.
In the meantime, I've been taking an Introduction to Aeronautical Engineering course on edx.org. It's a great course (and free) from Delft Technical University. If you are interested in Aerospace, start there. It'll help you determine if you like math enough...
It turns out: I do like math.
So much so, that I programmed this International Standard Atmosphere Calculator. Using the 1976 international standard (which apparently hasn't changed since) you can plug in your geopotential altitude and get the air density and pressure. Very handy if you want check your flight altitude to make sure you won't collide with any other airplanes. Or if you need to do your Aerospace homework.
I added a nice graph, so you can just click the altitude rather than typing it. I hope to add unit conversions and also altitude prediction based on pressure or air density. One thing at a time.
Programming this little job also gave me a good excuse to use a recursive function.
Re...CURSES!
If you have ever taken a programming course, you've been taught about recursion. And then promptly forgotten it thinking, "that's too confusing! I'll just do it a different way..."
And you can solve most problems without ever using recursion. So why bother right?
Because it is so much TIDIER!
In the case of ISA, we have a piece-wise function that stacks on more function pieces as we progress to higher altitudes. Lapse rates change and we glide through isothermal layers... nevermind. Back to recursion!
Without getting too into depth on ISA calculations, let's just say this:
If we want to calculate the air density for an altitude of 40 kilometers, we need to first calculate it for 11 using one equation, then up to 20 with another, then up to 32; all based on the calculations made for lower layers.
If we want to calculate at higher altitudes, we need even MORE equations dependent on all those below.
So how does recursion help?
Recursion allows us to call one function for any altitude, that calls itself for the lower calculations. Then it spits out just the answer we need.
Without recursion, we'd have to write separate if.. then... statements for each case (retyping a lot of the same code). OR (as some ISA calculators do) we could cheat and store the key values for each layer in an array. But what's the fun in that?
Here's the commented code:
//this function takes two parameters:
//alt is altitude in meters
//To is temperature offset in Kelvin
function getAtmosphere(alt,To) {
// constants!
var R = 287.058; //gas constant
var TEMP = 288.15; //in Kelvin
var ATM1 = 101325; //in Pascals
var DENSITY = 1.225; //in kg/cubic meter
// range check (altitude in meters)
if (alt > 84852) alt = 84852;
if (alt < 0) alt = 0;
//this is our OUT. no infinite recursion!
//it returns 1 standard atmosphere at sea level in an object
if (alt === 0) { //sea level
return {"alt":alt,"T":TEMP+To,"To":To,"p":ATM1,"rho":DENSITY};
}
// THIS IS WHERE THE MAGIC HAPPENS!
// the function calls ITSELF...
var atm0 = getAtmosphere(getAltRange(alt),To);
// getAltRange() is a simple function that returns the altitude for
// the "layer" beneath. This is used to find the correct lapse rate
// and equation below...
// lapseRate() returns the rate used to calculate temp for a given layer
// here is where the calculation is done.
// notice it uses output from itself (atm0)
if (lapseRate(alt) != 0) {
var T1 = atm0.T + lapseRate(alt)*(alt-getAltRange(alt));
var rho1 = atm0.rho*Math.pow(T1/atm0.T,(-9.80665/(lapseRate(alt)*R)-1));
var p1 = atm0.p*Math.pow(T1/atm0.T,-9.80665/(lapseRate(alt)*R));
} else { // lapseRate = 0
var con = Math.pow(Math.E,-9.80665/(R*atm0.T)*(alt-getAltRange(alt)));
var T1 = atm0.T
var p1 = atm0.p * con;
var rho1 = atm0.rho * con;
}
// calculations "complete" it returns the data as an object
return {"alt":alt,"T":T1,"To":To,"p":p1,"rho":rho1};
}
So wait? How can a function call itself and then use its own output to calculate. If it doesn't have the input... how can it give output... wouldn't it fail or return 'undefined'??
Well, no. Let's look at an example:
Let's say we call getAtmosphere(25000).
The function needs input from getAtmosphere(20000), so it calls that and waits...
THAT call needs input from getAtmosphere(11000), so it calls that and waits...
THAT call needs input from getAtmosphere(0), so it calls it and... WAIT! That's the out!
Once it calls that, it gets data returned! This is the KEY to recursion. It hits an end, now it can finish!
Then, getAtmosphere(11000) takes the input from (0) and runs the calculation. It passes that to (20000) which calculates and passes to (25000). And like magic, we get our data.
And no matter what altitude we call it on, it knows just how many times to call itself to get the right answer! Brilliant!
I think I've waited 15 years to have a need for recursion. And I'm proud to say I only froze my browser once...
Don't forget the OUT!
EDIT:
I have updated the calculator to properly return the density when there is a temperature offset. You can now change units and get the temperature up to 1000 km. A few other minor adjustments for accuracy too. Coming soon: density and pressure over 86 km!
Also, feel free to fork on GitHub!
In the case of my game concept, so is user input.
"So what is this crazy game concept you keep talking about?"
Here it is:
Do you remember Super Mario RPG and timed hits? It was a fun addition to the usual turn-based RPG style game that added an extra layer of player interaction.
It was a nice touch that made the game very unique, in my opinion. I was surprised that it didn't seem to catch on. But it got me thinking...
I'm a music composer and sound designer; why not combine those skills with the concept of timed hits?
For this demo, I (quickly) made some sound effects using musical sounds. There is a background layer to keep time -- a simple 4/4 beat. When you send a character to attack, he first must arrive close enough to attack, and then he begins "priming".
During this "priming" phase, the character will pulse in time with the music. Then you click on the target and if you are on the beat, you get an extra power boost. Yay!
Depending on other factors, there will be other ways to maximize attacks. However be aware, in this demo, the enemies can fight back if you get too close!
Music, Audio and Timing
Before we can time our hits, we need to time our audio! For this we use the Web Audio API. I learned from this great information here.
For those of you into instant gratification: here is the code used to schedule the music.
var lastBeatTime = null;
var lastBeat = null;
var nextBeatTime = null; // when will the next beat occur
var nextBeat = null; // 32 total (8 bars of 4)
var nextFrameTime = null; // when will the next frame draw?
// tempo (in beats per minute)
var tempo = 125.0;
// these must be recalculated if the tempo changes
var secondsPerBeat = 60.0 / tempo;
var framesPerBeat = 3600 / tempo;
var calibration = -0.001;
// use this to fudge the click time back for accuracy (clicking late "sounds" correct)
window.onload = function (){
//make sure the webkit Audio API is available
try { // Fix up for prefixing
window.AudioContext = window.AudioContext||window.webkitAudioContext;
audioctx = new AudioContext();
} catch(e) {
alert('Web Audio API is not supported in this browser');
}
//load the audio (code for this class is below)
gMusic.parseAudioFile();
}
function startMusic(){
secondsPerBeat = 60.0 / tempo;
framesPerBeat = 3600 / tempo;
nextBeat = 32;
lastBeat = 31;
nextBeatTime = audioctx.currentTime;
lastBeatTime = nextBeatTime - secondsPerBeat;
}
function scheduler(time){
//schedule the next beat
lastBeat = nextBeat;
lastBeatTime = nextBeatTime;
if (nextBeat === 16) nextBeat = 1;
else nextBeat++;
nextBeatTime += 60.0 / tempo; // schedule the next quarter note
scheduleSound(nextBeat,nextBeatTime);
}
function scheduleSound(beatNumber,time){
// create an oscillator -- it makes the beep
var osc = audioctx.createOscillator();
osc.connect( audioctx.destination );
if (beatNumber % 32 === 1) {
gMusic.playSound("Pad1.wav",time);
}
if (beatNumber % 16 === 1) {
gMusic.playSound("Beat1.wav",time);
gMusic.playSound("Drums3.5.wav",time + (secondsPerBeat*2.5));
}
}
function animate(){
var NOW = audioctx.currentTime; //gets the time of the last draw...when is now?
nextFrameTime = NOW + (1 / 60); // calculate when this frame draw will occur
if (nextBeatTime <= nextFrameTime) scheduler(nextFrameTime);
//calculate the part of the beat the frame will draw at (as a percentage)
var bp = Math.round((nextFrameTime - lastBeatTime) / (nextBeatTime - lastBeatTime) * 100);
// ALL OTHER ANIMATION OCCURS
}
gMusic is an instance of an AudioFontClass I created. More on that later, but this is the playSound function it uses when scheduling audio.
playSound: function (name,time) {
//first find the buffer
var b = null;
for(var i = this.sounds.length - 1; i >=0; i--) {
// find the buffer for the name
if(this.sounds[i].name === name) b = this.sounds[i].buffer;
}
// if we don't find the sound, do nothing
if (!b) return;
var soundSource = audioctx.createBufferSource();
//schedule the sound
soundSource.buffer = b;
soundSource.connect(audioctx.destination);
soundSource.start(time);
}
How does it work?
Simple: each animation frame checks to see if the next frame will happen after the current beat. If it will, it calls the scheduler to schedule the audio for the next beat. The scheduled keeps track of which beat it is on, and loops the music every 4 bars (or 1 or 2).
The nice thing about the Web Audio API is that you can schedule audio way ahead of time, so nothing plays late.
However, one of the future challenges in making this web game will be keeping the load time down. Too much audio means longer load times.
For now, try the demo here! Enjoy the terrible artwork!
Controls are as follows:
Click and drag from a character to aim your "wave attack".
A single click will select him. If you release on the character, you can click elsewhere to move, or an enemy to attack. (Don't forget to click the enemy when you arrive for a "timed" attack! On the beat gives you a boost!)
Once selected, Z and X cycle through the colors of the "wave attack". Different waves will have a different effect on the enemies.
A second click on a selected character will bring up a menu.
You can choose a formation: some formations require a certain arrangement to already exist. Try moving characters around first. Be aware that the enemies react to formation changes.
Taunting an enemy or protecting an ally can help keep the enemies away from wounded allies.
And escape... well, you don't need that.
Ok, So I am taking a short break from the game to do some real work. Sort of.
In learning Javascript, I decided it would be worth my while to learn some jQuery as well. Turns out it is super-easy and useful for making simple applications that have "real work" uses.
I developed two small tools that could be used in a number of ways. One is just a big-old table of contents style drop down, complete with link descriptions.
The other is what I call a "workflow" that could be used for giving instructions or even trouble-shooting common issues. Check it out here.
Both are easily adaptable and open source. So feel free to pilfer the code. Feel free to ask me questions if you need help using it.
Table of Contents
For this little app, I just needed a list that I could easily add links to and would fold itself up, so there wouldn't be too many on a page to look at at once. You can just look at the relevant section and ignore the rest.
First, each "category" is listed inside list tag with class "slide". Then underneath, we create a list of links with class "articles". You can make unlimited categories.
<ul class='slide'>
<li><h3>Category 1</H3></li>
<ul class="articles">
+Each line is split at the plus
+type "#plus" if you want to actually see a plus sign as text
+It automatically parses addresses as links that are listed with http:
+the script parses out the part after "Description:" for the mouseover
+download the code here! http://games.codeandcompose.com/WorkApps/sourcecode.zip
Description:The best code ever written! Or not.
</ul>
...
The app splits each line in the "articles" list by the +. Then it creates a link with an address (if there is one starting with http:// or https://). If there is a description (split at "Description:"), that will show up when you hover.
There is a tiny cosmetic glitch with the hover descriptions. I haven't yet come up with a great fix, but it's not too noticeable.
The string parsing was pretty simple. It uses some regular expressions and Javascript string.replace() function. After breaking the strings, it puts them into list elements and links (when used).
$.fn.extend({
stringSplitToList: function() //this function parses the html body into a list for the jquery
{
var htmlList = '', aclass = 'none';
$.each($(this).html().split('+'), function(i, v) {
v = v.replace(/#plus/g,"+");//put plusses back in
var tempSplit = v.split("Description:");
v = tempSplit[0];
//makes sure the description not undefined.
var description = (tempSplit[1]) ? tempSplit[1] : "No description.";
if (v.match(/(https:\/\/|http:\/\/)\S+/igm)) {
var alink = v.match(/(https:\/\/|http:\/\/)\S+/igm)[0];
var title = v.replace(alink,"");
if (title.match(/\*/gm)){
title = title.replace("*","");
aclass = 'internal';
} else if (title.match(/\^/gm)){
title = title.replace("^","");
aclass = 'archive';
} else {
aclass = 'none';
}
htmlList += '<li><a href="' + alink + '" class="' + aclass + '">' + title + '</a><div class="des">' + description + '</div></li>';
//handle categories header
} else if (v.match(/(href=)\S+/igm)) {
var alink = v.match(/(href=)\S+/igm)[0];
var title = v.replace(alink,"");
alink = alink.replace("href=","");
if (title.match(/\*/gm)){
title = title.replace("*","");
aclass = 'internal';
} else {
aclass = 'none';
}
htmlList += '<li><a href="' + alink + '" class="' + aclass + '">' + title + '</a><div class="des">' + description + '</div></li>';
//handle categories header
} else {
htmlList += '<li><div class="category">' + v + '</div></li>';
}
});
$(this).html(htmlList);
}
});
Each link can also be marked as "internal" with a * or "archived" with a ^. You can even use internal links marked by href=
Just call this line once the page loads and it parses the text for you.
$('.articles').each(function(index){$(this).stringSplitToList();});
Workflow App
The workflow app can take very complex paths. By following the formula in each js file, you can add unlimited questions and answers. As the user chooses their answers, links, pictures and tips are given to help them out. The logic can be created to direct to a certain path based on previous answers form one or multiple questions.
I used it to make a wi-fi router trouble-shooting guide. The example given on this website is just for fun though.
Here is a graffle showing how the questions are programmed.
Each question is loaded in the following format:
addQuestion(1, //the question number
//the question
"This is workflow 1. Use this workflow?",
//answer options (as many as you want)
["Maybe","No","Yes"],
//answer direction, either a question number or LOGIC or RESET
[1,"LOGIC",1],1, // the extra number is default
//tips text
"This area will show helpful tips. Or not...",
//image urls (as many as you want) or null
["image1.png","image2.png"],
// url and text for helpful links
[{"url":"http://tagsounds.com","title":"A Link to TAGSOUNDS.COM"}],
// callback function for when the direction of an answer is LOGIC
function(){
if (AA[0] === "Red"){
nextQuestion(201);
return;
} else loadWorkflow("workflow2.js");
}
);
If you like it, or would like to know more about how to adapt it for your needs, please contact me using the contact form at tagsounds.com.