var app = angular.module('noiseDemo', []);
app.controller('MainCtrl', function($scope) {
$scope.name = 'World';
});
<!DOCTYPE html>
<html ng-app="noiseDemo">
<head>
<meta charset="utf-8" />
<title>AngularJS Plunker</title>
<script>document.write('<base href="' + document.location + '" />');</script>
<link rel="stylesheet" href="style.css" />
<script data-require="angular.js@1.2.x" src="https://code.angularjs.org/1.2.14/angular.js" data-semver="1.2.14"></script>
<script src="app.js"></script>
<script src="noise.js"></script>
<script src="noiseChart.js"></script>
<script src="noiseAnimation.js"></script>
</head>
<h1>1D Noise Function Demo</h1>
<noise-chart></noise-chart>
<noise-animation></noise-animation>
</html>
/* Put your css in here */
* {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
}
canvas {
background: #ffffff; /* Old browsers */
background: -moz-linear-gradient(top, #ffffff 0%, #f6f6f6 47%, #ededed 100%); /* FF3.6+ */
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#ffffff), color-stop(47%,#f6f6f6), color-stop(100%,#ededed)); /* Chrome,Safari4+ */
background: -webkit-linear-gradient(top, #ffffff 0%,#f6f6f6 47%,#ededed 100%); /* Chrome10+,Safari5.1+ */
background: -o-linear-gradient(top, #ffffff 0%,#f6f6f6 47%,#ededed 100%); /* Opera 11.10+ */
background: -ms-linear-gradient(top, #ffffff 0%,#f6f6f6 47%,#ededed 100%); /* IE10+ */
background: linear-gradient(to bottom, #ffffff 0%,#f6f6f6 47%,#ededed 100%); /* W3C */
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffffff', endColorstr='#ededed',GradientType=0 ); /* IE6-9 */
}
/**
* This is a directive to test out the perlinNoise service in a visual and interactive way.
*/
app.directive('noiseChart', function($window, noise) {
var context;
var width;
var height;
var MARGIN = 10;
var NUM_POINTS = 400; // how many points to plot
var drawAxes = function() {
context.canvas.width = $window.innerWidth - 20;
height = context.canvas.height;
width = context.canvas.width;
var origin = {
x: MARGIN,
y: height - MARGIN
};
// x axis
context.beginPath();
context.moveTo(origin.x, origin.y);
context.lineTo(origin.x + width - 2*MARGIN, origin.y);
context.stroke();
// y axis
context.moveTo(origin.x, origin.y);
context.lineTo(origin.x, origin.y - (height - 2*MARGIN));
context.stroke();
};
var drawLine = function(points) {
context.clearRect(0, 0, width, height);
drawAxes();
context.strokeStyle="#006600";
context.lineWidth = 0.1;
context.lineJoin = 'round';
context.beginPath();
context.moveTo(translatePoint(points[0][0]), translatePoint(points[0][1]));
for (var i = 1; i < points.length; i++) {
var point = translatePoint(points[i]);
context.lineTo(point[0], point[1]);
context.stroke();
}
};
var translatePoint = function(point){
var stepSize = (width - 2*MARGIN) / NUM_POINTS;
var x = (point[0] * stepSize) + MARGIN;
var y = height - MARGIN - (point[1] * height/2);
return [x, y];
};
return {
restrict: 'E',
templateUrl: 'noiseChart.html',
replace: true,
scope: true,
link: function(scope, element) {
var canvas = element.children()[0];
context = canvas.getContext('2d');
var _noise = noise.newGenerator();
scope.amplitude = 1;
scope.scale = 0.2;
scope.$watch('amplitude', function(val) {
_noise.setAmplitude(val);
renderNoise();
});
scope.$watch('scale', function(val) {
_noise.setScale(val);
renderNoise();
});
var renderNoise = function() {
var points = [];
for ( var i = 0; i < NUM_POINTS; ++i ) {
var x = i;
var y = _noise.getVal(x);
points.push([x, y]);
}
drawLine(points);
};
}
};
});
<div class="noise-chart">
<canvas></canvas><br>
<label>
Amplitude: {{ amplitude }}<br>
<input type="range" min="0" max="2" step="0.01" ng-model="amplitude">
</label><br>
<label>
Scale: {{ scale }}<br>
<input type="range" min="0" max="1" step="0.01" ng-model="scale">
</label>
</div>
/**
* Created by Michael on 12/03/14.
*/
/**
* Service to generate 1-dimensional interpolated noise. Based on the excellent article at Scratchapixel:
* http://www.scratchapixel.com/lessons/3d-advanced-lessons/noise-part-1/creating-a-simple-1d-noise/
*
*/
app.factory('noise', function() {
var Simple1DNoise = function() {
var MAX_VERTICES = 256;
var MAX_VERTICES_MASK = MAX_VERTICES -1;
var amplitude = 1;
var scale = 1;
var r = [];
for ( var i = 0; i < MAX_VERTICES; ++i ) {
r.push(Math.random());
}
var getVal = function( x ){
var scaledX = x * scale;
var xFloor = Math.floor(scaledX);
var t = scaledX - xFloor;
var tRemapSmoothstep = t * t * ( 3 - 2 * t );
/// Modulo using &
var xMin = xFloor & MAX_VERTICES_MASK;
var xMax = ( xMin + 1 ) & MAX_VERTICES_MASK;
var y = lerp( r[ xMin ], r[ xMax ], tRemapSmoothstep );
return y * amplitude;
};
/**
* Linear interpolation function.
* @param a The lower integer value
* @param b The upper integer value
* @param t The value between the two
* @returns {number}
*/
var lerp = function(a, b, t ) {
return a * ( 1 - t ) + b * t;
};
// return the API
return {
getVal: getVal,
setAmplitude: function(newAmplitude) {
amplitude = newAmplitude;
},
setScale: function(newScale) {
scale = newScale;
}
};
};
return {
newGenerator: function() {
return new Simple1DNoise();
}
};
});
/**
* This is a directive to test out the perlinNoise service in a visual and interactive way.
*/
app.directive('noiseAnimation', function($timeout, noise) {
var context;
var drawWave = function(points) {
height = context.canvas.height;
width = context.canvas.width;
context.clearRect(0, 0, width, height);
context.fillStyle = "#006600";
for(var i = 0; i < points.length; i ++) {
var x = width - i;
var y = points[i];
var y = height - (points[i] * height/2);
context.fillRect(x, y, 2, 2);
}
};
return {
restrict: 'E',
templateUrl: 'noiseAnimation.html',
replace: true,
scope: true,
link: function(scope, element) {
var canvas = element.children()[0];
context = canvas.getContext('2d');
var _noise = noise.newGenerator();
scope.amplitude = 1;
scope.scale = 0.05;
scope.$watch('amplitude', function(val) {
_noise.setAmplitude(val);
});
scope.$watch('scale', function(val) {
_noise.setScale(val);
});
var points = [];
var x = 1;
var y;
var run = function() {
y = _noise.getVal(x);
var arrayLength = points.unshift(y);
if (arrayLength > 300) {
points.pop();
}
drawWave(points);
x ++;
$timeout(run, 16);
};
run();
}
};
});
<div class="noise-animation">
<canvas width="300" height="300"></canvas><br>
<label>
Amplitude: {{ amplitude }}<br>
<input type="range" min="0" max="2" step="0.05" ng-model="amplitude">
</label><br>
<label>
Scale: {{ scale }}<br>
<input type="range" min="0" max="0.5" step="0.001" ng-model="scale">
</label>
</div>
# 1D Noise Generator
This is a simple implementation of a 1-dimensional interpolated noise generator. I adapted the example in [this excellent article](http://www.scratchapixel.com/lessons/3d-advanced-lessons/noise-part-1/creating-a-simple-1d-noise/)
(which is well worth studying for a easy-to-follow explanation of the concept from basic principles).
Noise functions are very useful when modelling unpredictability in nature - undulating terrain, movement of objects and (in more complex implementations) natural textures. A 1D noise function, such as this one, takes a numeric input and returns a corresponding value between 0 and 1. Think of it as similar to
the random numbers generated by the JavaScript `Math.random()` function, but smoothly transitioning from one value to the next (this is "interpolation"), rather than just jumping all over the place. That, in essence, is exactly
what this class does.
The service is simply a wrapper around a JavaScript object which allows it to be injected into an Angular app. It can be easily used in any non-Angular app just by using the object itself, and pulling it out of the
Angular `.factory()` wrapper.
## Usage
In your Angular app, include the service by usual Angular dependency injection, then you can use it like this:
var generator = noise.newGenerator();
var x = 1; // can be any number
var y = generator.getVal(x); // number between 0 and 1, e.g. 0.231144
You can generate a continuously varying sequence like this:
var generator = noise.newGenerator();
for (var i = 0; i < 200; i ++) {
console.log(generator.getVal(i);
}
## Options
You can optionally set two properties via these methods:
- `setAmplitude(number)` : Alter the range between which the generated values will fall, e.g. setting it to 2 will generate values between 0 and 2. Default is 1.
- `setScale(number)` : Change the frequency of the function. A larger number "squashes" the values closer together, and vice-versa. When using the generator for animations (as in the second demo), this parameter can be thought of a "frequency". Default is 1.