naca-four-digit-airfoil
Version:
The equations for a NACA four-digit series airfoil
499 lines (418 loc) • 13 kB
JavaScript
(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
/* global location, window */
var createFit = require('canvas-fit')
var queryString = require('query-string')
var extend = require('util-extend')
var naca = require('../../')
var canvas, ctx, w, h
var n = 100
var xmin = -0.1 // viewport x-bounds
var xmax = 1.1
var fmax = -0.5 // viewport y-bounds
var fmin = 0.5
var xmm = xmax - xmin
var fmm = fmax - fmin
var params = extend({
naca: '2412',
c: 1
}, queryString.parse(location.search))
var c = params.c
var airfoil = naca(params.naca, Number(c))
function xToI (x) { return (x - xmin) / xmm * w }
function yToJ (f) { return (f - fmin) / fmm * h }
function dxTodI (dx) { return dx / xmm * w }
// function dyTodJ (dy) { return dy / ymm * h }
function draw () {
var i, x, y, xy, r
ctx.clearRect(0, 0, w, h)
ctx.save()
ctx.lineWidth = window.devicePixelRatio
ctx.strokeStyle = '#eee'
ctx.beginPath()
for (i = -10; i <= 10; i++) {
ctx.moveTo(xToI(-1), yToJ(i / 10))
ctx.lineTo(xToI(2), yToJ(i / 10))
}
ctx.stroke()
ctx.strokeStyle = '#eee'
ctx.beginPath()
for (i = -10; i <= 10; i++) {
ctx.moveTo(xToI(i / 10), yToJ(-1))
ctx.lineTo(xToI(i / 10), yToJ(1))
}
ctx.stroke()
ctx.strokeStyle = '#000'
ctx.beginPath()
xy = airfoil.evaluate(0)
ctx.moveTo(xToI(xy[0]), yToJ(xy[1]))
for (i = 1; i < n; i++) {
x = Math.pow(i / (n - 1), 2) * c
xy = airfoil.evaluate(x)
ctx.lineTo(xToI(xy[0]), yToJ(xy[1]))
}
ctx.stroke()
xy = airfoil.evaluate(0)
ctx.beginPath()
ctx.moveTo(xToI(xy[2]), yToJ(xy[3]))
for (i = 1; i < n; i++) {
x = Math.pow(i / (n - 1), 2) * c
xy = airfoil.evaluate(x)
ctx.lineTo(xToI(xy[2]), yToJ(xy[3]))
}
ctx.stroke()
ctx.strokeStyle = '#f00'
ctx.beginPath()
y = airfoil.camberLine(0)
ctx.moveTo(xToI(0), yToJ(y))
for (i = 1; i < n; i++) {
x = i / (n - 1) * c
y = airfoil.camberLine(x)
ctx.lineTo(xToI(x), yToJ(y))
}
ctx.stroke()
ctx.strokeStyle = '#00f'
ctx.beginPath()
r = airfoil.leadingEdgeRadius()
// var y = airfoil.camberLine(r)
var dycdx = airfoil.camberLineSlope(0)
var theta = Math.atan(dycdx)
var x0 = r * Math.cos(theta)
var y0 = r * Math.sin(theta)
ctx.arc(xToI(x0), yToJ(y0), dxTodI(r), 0, Math.PI * 2)
ctx.stroke()
}
window.onload = function () {
// Get the element:
canvas = document.getElementById('canvas')
// Create an auto-fit function:
var fit = createFit(canvas)
// Set the fit scale:
fit.scale = window.devicePixelRatio
// fit.parent = function () {
// return [ Math.min(window.innerWidth, 800), Math.min(window.innerHeight, 300) ]
// }
function resize () {
fit()
ctx = canvas.getContext('2d')
w = canvas.width
h = canvas.height
var dx = xmax - xmin
var dy = dx * h / w
fmax = -dy * 0.5
fmin = dy * 0.5
fmm = fmax - fmin
draw()
}
window.addEventListener('resize', resize, false)
resize()
// function onRaf () {
// draw()
// requestAnimationFrame(onRaf)
// }
// requestAnimationFrame(onRaf)
}
},{"../../":2,"canvas-fit":3,"query-string":5,"util-extend":7}],2:[function(require,module,exports){
airfoil.thickness = thickness
airfoil.camberLine = camberLine
airfoil.camberLineSlope = camberLineSlope
airfoil.leadingEdgeRadius = leadingEdgeRadius
airfoil.xUpper = xUpper
airfoil.yUpper = yUpper
airfoil.xLower = xLower
airfoil.yLower = yLower
airfoil.evaluate = evaluate
airfoil.parse = parse
module.exports = airfoil
var nacaCodePattern = /^([0-9])([0-9])([0-9]{2})$/
function parse (nacaCode) {
if (typeof nacaCode !== 'string') {
throw new Error('NACA airfoil code must be a string. Received ' + typeof nacaCode)
}
var match = nacaCode.match(nacaCodePattern)
if (!match) {
throw new Error('NACA airfoil expected a string containing a four-digit identifier. Received "' + nacaCode + '".')
}
var mString = match[1]
var pString = match[2]
var tString = match[3]
var m = Number(mString) * 0.01
var p = Number(pString) * 0.1
var t = Number(tString) * 0.01
return {
p: p,
m: m,
t: t
}
}
function airfoil (nacaCodeOrData, c) {
var params = (typeof nacaCodeOrData === 'string') ? parse(nacaCodeOrData) : nacaCodeOrData
if (c === undefined) {
c = 1
}
return {
thickness: function (x) {
return thickness(x, c, params.t)
},
camberLine: function (x) {
return camberLine(x, c, params.m, params.p)
},
camberLineSlope: function (x) {
return camberLineSlope(x, c, params.m, params.p)
},
leadingEdgeRadius: function () {
return leadingEdgeRadius(params.t, c)
},
xLower: function (x) {
return xLower(x, c, params.m, params.p, params.t)
},
xUpper: function (x) {
return xUpper(x, c, params.m, params.p, params.t)
},
yLower: function (x) {
return yLower(x, c, params.m, params.p, params.t)
},
yUpper: function (x) {
return yUpper(x, c, params.m, params.p, params.t)
},
evaluate: function (x) {
return evaluate(x, c, params.m, params.p, params.t)
}
}
}
function thickness (x, c, t) {
var u = x / c
var y = -0.5075 * u
y = u * (y + 1.4215)
y = u * (y - 1.7580)
y = u * (y - 0.6300)
y += 1.4845 * Math.sqrt(u)
return t * y * c * 2
}
function camberLine (x, c, m, p) {
var p1
if (x < p * c) {
return m * x / (p * p) * (2 * p - x / c)
} else {
p1 = 1 - p
return m * (c - x) / (p1 * p1) * (1 + x / c - 2 * p)
}
}
function camberLineSlope (x, c, m, p) {
var p1
if (x < p * c) {
return 2 * m / (p * p) * (p - x / c)
} else {
p1 = 1 - p
return 2 * m / (p1 * p1) * (p - x / c)
}
}
function leadingEdgeRadius (t, c) {
return 1.1019 * t * t * c
}
function xLower (x, c, m, p, t) {
var yt = thickness(x, c, t)
var dycdx = camberLineSlope(x, c, m, p)
var dscdx = Math.sqrt(1 + dycdx * dycdx)
var stheta = dycdx / dscdx
return x + 0.5 * yt * stheta
}
function xUpper (x, c, m, p, t) {
var yt = thickness(x, c, t)
var dycdx = camberLineSlope(x, c, m, p)
var dscdx = Math.sqrt(1 + dycdx * dycdx)
var stheta = dycdx / dscdx
return x - 0.5 * yt * stheta
}
function yLower (x, c, m, p, t) {
var yc = camberLine(x, c, m, p)
var yt = thickness(x, c, t)
var dycdx = camberLineSlope(x, c, m, p)
var dscdx = Math.sqrt(1 + dycdx * dycdx)
var ctheta = 1 / dscdx
return yc - 0.5 * yt * ctheta
}
function yUpper (x, c, m, p, t) {
var yc = camberLine(x, c, m, p)
var yt = thickness(x, c, t)
var dycdx = camberLineSlope(x, c, m, p)
var dscdx = Math.sqrt(1 + dycdx * dycdx)
var ctheta = 1 / dscdx
return yc + 0.5 * yt * ctheta
}
function evaluate (x, c, m, p, t) {
var yc = camberLine(x, c, m, p)
var yt = 0.5 * thickness(x, c, t)
var dycdx = camberLineSlope(x, c, m, p)
var dscdx = Math.sqrt(1 + dycdx * dycdx)
var ctheta = 1 / dscdx
var stheta = dycdx / dscdx
return [
x - yt * stheta, // xU
yc + yt * ctheta, // yU
x + yt * stheta, // xL
yc - yt * ctheta // yL
]
}
},{}],3:[function(require,module,exports){
var size = require('element-size')
module.exports = fit
var scratch = new Float32Array(2)
function fit(canvas, parent, scale) {
var isSVG = canvas.nodeName.toUpperCase() === 'SVG'
canvas.style.position = canvas.style.position || 'absolute'
canvas.style.top = 0
canvas.style.left = 0
resize.scale = parseFloat(scale || 1)
resize.parent = parent
return resize()
function resize() {
var p = resize.parent || canvas.parentNode
if (typeof p === 'function') {
var dims = p(scratch) || scratch
var width = dims[0]
var height = dims[1]
} else
if (p && p !== document.body) {
var psize = size(p)
var width = psize[0]|0
var height = psize[1]|0
} else {
var width = window.innerWidth
var height = window.innerHeight
}
if (isSVG) {
canvas.setAttribute('width', width * resize.scale + 'px')
canvas.setAttribute('height', height * resize.scale + 'px')
} else {
canvas.width = width * resize.scale
canvas.height = height * resize.scale
}
canvas.style.width = width + 'px'
canvas.style.height = height + 'px'
return resize
}
}
},{"element-size":4}],4:[function(require,module,exports){
module.exports = getSize
function getSize(element) {
// Handle cases where the element is not already
// attached to the DOM by briefly appending it
// to document.body, and removing it again later.
if (element === window || element === document.body) {
return [window.innerWidth, window.innerHeight]
}
if (!element.parentNode) {
var temporary = true
document.body.appendChild(element)
}
var bounds = element.getBoundingClientRect()
var styles = getComputedStyle(element)
var height = (bounds.height|0)
+ parse(styles.getPropertyValue('margin-top'))
+ parse(styles.getPropertyValue('margin-bottom'))
var width = (bounds.width|0)
+ parse(styles.getPropertyValue('margin-left'))
+ parse(styles.getPropertyValue('margin-right'))
if (temporary) {
document.body.removeChild(element)
}
return [width, height]
}
function parse(prop) {
return parseFloat(prop) || 0
}
},{}],5:[function(require,module,exports){
;
var strictUriEncode = require('strict-uri-encode');
exports.extract = function (str) {
return str.split('?')[1] || '';
};
exports.parse = function (str) {
if (typeof str !== 'string') {
return {};
}
str = str.trim().replace(/^(\?|#|&)/, '');
if (!str) {
return {};
}
return str.split('&').reduce(function (ret, param) {
var parts = param.replace(/\+/g, ' ').split('=');
// Firefox (pre 40) decodes `%3D` to `=`
// https://github.com/sindresorhus/query-string/pull/37
var key = parts.shift();
var val = parts.length > 0 ? parts.join('=') : undefined;
key = decodeURIComponent(key);
// missing `=` should be `null`:
// http://w3.org/TR/2012/WD-url-20120524/#collect-url-parameters
val = val === undefined ? null : decodeURIComponent(val);
if (!ret.hasOwnProperty(key)) {
ret[key] = val;
} else if (Array.isArray(ret[key])) {
ret[key].push(val);
} else {
ret[key] = [ret[key], val];
}
return ret;
}, {});
};
exports.stringify = function (obj) {
return obj ? Object.keys(obj).sort().map(function (key) {
var val = obj[key];
if (val === undefined) {
return '';
}
if (val === null) {
return key;
}
if (Array.isArray(val)) {
return val.sort().map(function (val2) {
return strictUriEncode(key) + '=' + strictUriEncode(val2);
}).join('&');
}
return strictUriEncode(key) + '=' + strictUriEncode(val);
}).filter(function (x) {
return x.length > 0;
}).join('&') : '';
};
},{"strict-uri-encode":6}],6:[function(require,module,exports){
;
module.exports = function (str) {
return encodeURIComponent(str).replace(/[!'()*]/g, function (c) {
return '%' + c.charCodeAt(0).toString(16).toUpperCase();
});
};
},{}],7:[function(require,module,exports){
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
module.exports = extend;
function extend(origin, add) {
// Don't do anything if add isn't an object
if (!add || typeof add !== 'object') return origin;
var keys = Object.keys(add);
var i = keys.length;
while (i--) {
origin[keys[i]] = add[keys[i]];
}
return origin;
}
},{}]},{},[1]);