vk3cpu/antenna.html

927 wiersze
33 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<!-- @file : antenna.html -->
<!-- @author : J Miguel Vaca -->
<!-- @remark : This webpage uses Computational Electromagnetics (CEM) to solve antenna currents and fields. It then uses WebGL to visualise the currents -->
<!-- : and fields produced by the antenna. -->
<!-- : Idea - allow adding wire antennas, one-at-a-time. Then have controls at the ends to move ends, stretch or shring the wire. -->
<!-- Have a control in the middle that can be used to reposition the camera. Also a control to move the feedpoint along the wire. -->
<!-- Support multiple feedpoints, but with a single master feed (or frequency), and the others are slaved off the master, but with a controllable phase -->
<!-- shift that will allow modelling of phased-arrays. -->
<head>
<title>VK3CPU Antenna Simulator</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<link type="text/css" href="css/base.css" rel="stylesheet"/>
<link type="text/css" href="css/visualisation.css" rel="stylesheet"/>
</head>
<body>
<!--div id="info">
Visualisation by : <a href="mailto:vacamiguel@gmail.com">J Miguel Vaca</a>
</div-->
<!-- math.js library scripts -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/mathjs/7.5.1/math.min.js"></script>
<script src="./dat.gui.min.js"></script>
<script type="importmap">
{
"imports": {
"three": "https://threejs.org/build/three.module.js",
"three/addons/": "https://threejs.org/examples/jsm/"
}
}
</script>
<script type="module">
import * as THREE from 'three';
import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
// Heatmap vertex shader - Use this to update the position due to camera projection
var radiationPatternVertexShader =
"uniform vec4 hiColor; \n" +
"uniform vec4 loColor; \n" +
"uniform float maxHistogramValue; \n" +
"uniform float contrast; \n" +
"uniform float shape; \n" +
"varying vec4 vColor; \n" +
"\n" +
"void main() { \n" +
" vec4 mvPosition; \n" +
" // UV.x contains histogram value \n" +
" // UV.y is identical to UV.x \n" +
" if(maxHistogramValue > 0.0) { \n" +
" vColor = pow((uv.x / maxHistogramValue), contrast) * (hiColor-loColor) + loColor; \n" +
" mvPosition = modelViewMatrix * vec4( position.xyz*pow((uv.x / maxHistogramValue),shape), 1.0 ); \n" +
" } else { \n" +
" vColor = hiColor; \n" +
" mvPosition = modelViewMatrix * vec4( position.xyz, 1.0 ); \n" +
" } \n" +
" \n" +
" gl_PointSize = 2.0; \n" +
" gl_Position = projectionMatrix * mvPosition; \n" +
"} \n";
var container; //, stats;
var camera, scene, renderer, geometry, controls;
var clock = new THREE.Clock();
var tick = 0;
var ant = 0;
var current_antenna_object = 0;
var ground_plane = 0;
var height_above_ground = 0;
// Recalculate all variables. Triggered whenever the system parameter inputs are changed:
function recalculate() {
// Read all inputs and store values in wavelengths:
// Create segments of all the wires with appropriate lengths:
// Create 3D representation and reload the scene:
}
class AntennaTypes {
//
constructor() {
this.frequency = 3e8;
this.antenna_types = {
'order' : ['Horizontal Dipole', 'Vertical Dipole', 'Vertical Monopole', 'Inverted Vee', 'Inverted L', 'Loop Large Triangle', 'Quad', 'H Yagi 5-element', 'Spiderbeam 5'],
'antennas' : {
'Vertical Dipole' : {
'vertex' : [
[0.00, -0.25, 0.00], [0.00, 0.25, 0.00]
],
'wires' : [
[0, 1]
],
},
'Horizontal Dipole' : {
'vertex' : [
[-0.25, 0.00, 0.00], [0.25, 0.00, 0.00]
],
'wires' : [
[0, 1]
],
},
'Vertical Monopole' : {
'vertex' : [
[0.00, 0.05, 0.00], [0.00, 0.55, 0.00]
],
'wires' : [
[0, 1]
],
},
'Inverted Vee' : {
'vertex' : [
[-0.25, 0.00, 0.00], [0.00, 0.25, 0.00], [0.25, 0.00, 0.00]
],
'wires' : [
[0, 1, 2]
],
},
'Inverted L' : {
'vertex' : [
[0.00, 0.00, 0.00], [0.00, 0.35, 0.00], [0.00, 0.35, 0.20]
],
'wires' : [
[0, 1, 2]
],
},
'Loop Large Triangle' : {
'vertex' : [
[-0.05, 0.00, 0.00], [-0.35, 0.00, 0.00], [0.00, 0.35, 0.00], [0.35, 0.00, 0.00], [0.05, 0.00, 0.00]
],
'wires' : [
[0, 1, 2, 3, 4]
],
},
'Quad' : {
'vertex' : [
[-0.05, 0.00, 0.00], [-0.35, 0.00, 0.00], [-0.35, 0.35, 0.00], [0.35, 0.35, 0.00], [0.35, 0.00, 0.00], [0.05, 0.00, 0.00]
],
'wires' : [
[0, 1, 2, 3, 4, 5]
],
},
'H Yagi 5-element' : {
'vertex' : [
[-0.35, 0.00, -0.25], [0.35, 0.00, -0.25], // Reflector
[-0.25, 0.00, 0.00], [0.25, 0.00, 0.00], // Exciter
[-0.25, 0.00, 0.25], [0.25, 0.00, 0.25], // Director
[-0.25, 0.00, 0.50], [0.25, 0.00, 0.50], // Director
[-0.25, 0.00, 0.75], [0.25, 0.00, 0.75] // Director
],
'wires' : [
[0, 1], // Reflector
[2, 3], // Exciter
[4, 5], // Director
[6, 7], // Director
[8, 9] // Director
],
},
'Spiderbeam 5' : {
'vertex' : [
[-0.25, 0.00, -0.35], [-0.25, 0.00, 0.35], // Reflector
[0.00, 0.00, -0.25], [0.00, 0.00, 0.25], // Exciter
[0.25, 0.00, -0.25], [0.25, 0.00, 0.25], // Director
[0.50, 0.00, -0.25], [0.50, 0.00, 0.25], // Director
[0.75, 0.00, -0.25], [0.75, 0.00, 0.25]
],
'wires' : [
[0, 1], // Reflector
[2, 3], // Exciter
[4, 5], // Director
[6, 7], // Director
[8, 9] // Director
],
}
},
};
this.current_type = this.antenna_types['order'][3];
//console.log(this.current_type);
}
solve() {
// Fill the impedance Z matrix:
// Invert Z to produce admittance Y matrix:
// Create the V array:
// I = YV:
// Store (complex) input impedance:
// Solve radiation pattern:
}
setAntennaType(antenna_type) {
//console.log("setAntennaType" + antenna_type);
this.current_type = antenna_type;
}
// Return the wire elements for solving:
getWires() {
this.wires = [];
//
var ws = this.antenna_types['antennas'][this.current_type]['wires'];
var vs = this.antenna_types['antennas'][this.current_type]['vertex'];
ws.forEach(w => {
//
var ww = {};
// ww.length = ;
// ww.seg_len = ;
ww.radius = 0.0001;
ww.points = [];
for (let index = 0; index < (w.length-1); index++) {
const wire_length = Math.sqrt(
(vs[w[index]][0] - vs[w[index+1]][0])**2 +
(vs[w[index]][1] - vs[w[index+1]][1])**2 +
(vs[w[index]][2] - vs[w[index+1]][2])**2
);
// Minimum of 10 segments per half-wavelength => 0.05
const max_segment_length = 0.05;
const segments = Math.round(wire_length / max_segment_length);
ww.seg_len = wire_length / segments;
for (let ii = 0; ii < segments; ii++) {
var frac = 1.0 * ii / segments;
var x = vs[w[index]][0] * (1.0 - frac) + frac * vs[w[index+1]][0];
var y = vs[w[index]][1] * (1.0 - frac) + frac * vs[w[index+1]][1];
var z = vs[w[index]][2] * (1.0 - frac) + frac * vs[w[index+1]][2];
ww.points.push([x,y,z]);
frac = 1.0 * (ii + 0.5) / segments;
x = vs[w[index]][0] * (1.0 - frac) + frac * vs[w[index+1]][0];
y = vs[w[index]][1] * (1.0 - frac) + frac * vs[w[index+1]][1];
z = vs[w[index]][2] * (1.0 - frac) + frac * vs[w[index+1]][2];
ww.points.push([x,y,z]);
}
}
// Add the final point:
x = vs[w[w.length-1]][0];
y = vs[w[w.length-1]][1];
z = vs[w[w.length-1]][2];
ww.points.push([x,y,z]);
//retval.push(ww);
this.wires.push(ww);
});
return this.wires;
}
//
getThreeObject3D = function () {
const material = new THREE.LineBasicMaterial({ color: 0xffff00, linewidth: 1 });
const antenna_view = new THREE.Group();
//
const ww = this.antenna_types['antennas'][this.current_type]['wires'];
const vv = this.antenna_types['antennas'][this.current_type]['vertex'];
ww.forEach(wire => {
//console.log(wire);
var vertices = new Float32Array(wire.length * 3);
var vidx = 0;
const scale_factor = 200.0; // Roughly pixels per wavelength
// Copy the vertex locations across into a Float32Array for the geometry:
wire.forEach((vertex) => {
//console.log(vertex, vv[vertex]);
vertices[vidx++] = (vv[vertex][0] * scale_factor);
vertices[vidx++] = (vv[vertex][1] * scale_factor);
vertices[vidx++] = (vv[vertex][2] * scale_factor);
});
//
const geometry = new THREE.BufferGeometry();
geometry.setAttribute('position', new THREE.BufferAttribute(vertices, 3));
// create a new wire with
const wire_line = new THREE.Line(geometry, material);
antenna_view.add(wire_line);
});
// Add the antenna into the scene:
return antenna_view;
};
}
class WireAntennaModel {
// constructor:
constructor(ws, vs) {
// Object variables:
this.wires = [];
this.Z = [];
this.Y = [];
}
//
psi(seg_n, seg_m, point_n, point_m) {
var retval = 0.0;
const k = 2.0 * Math.PI; // Normalised wavelength is equal to 1.0 - otherwise 2*pi/wavelength
const fourPI = 4.0 * Math.PI;
var Rmn = 0.0;
// From MININEC thesis (3-36) and (3-37):
if(point_n==point_m) {
//console.log(point_n, point_m);
retval = math.complex((1.0/(2.0*Math.PI*seg_n.len)) * Math.log(seg_n.len / seg_n.radius), (-k/fourPI));
//console.log(retval, seg_n.len, seg_n.radius);
} else {
Rmn = Math.sqrt((point_m[0] - point_n[0])**2 + (point_m[1] - point_n[1])**2 + (point_m[2] - point_n[2])**2);
retval = math.multiply(math.complex(Math.cos(k * Rmn), -Math.sin(k * Rmn)), (1/(fourPI*Rmn)));
}
//console.log(n, m, retval);
return retval;
}
/*
distanceBetween(p1, p2) {
// p1 : [x1,y1,z1]; p2 : [x2,y2,z2]
var distance_in_lambda = 0.0;
distance_in_lambda = Math.sqrt((p2[0] - p1[0])**2 + (p2[1] - p1[1])**2 + (p2[2] - p1[2])**2);
return distance_in_lambda;
}
*/
calculateZMatrix() {
const w = 2.0 * Math.PI * this.frequency;
const k = 2.0 * Math.PI * this.frequency / 3e8; // 2*pi/lambda
const e0 = 8.854187e-12;
const mu0 = 4.0 * Math.PI * 1e-7;
const fourPI = 4.0 * Math.PI;
var segments = [];
this.wires.forEach(wire => {
// This assumes the length MUST always be a positive ODD number:
for (let m = 1; m < (wire.points.length-1); m+=2) {
//
var t_seg = {};
t_seg.start = wire.points[m-1];
t_seg.end = wire.points[m+1];
t_seg.mid = wire.points[m];
t_seg.len = wire.seg_len;
t_seg.radius = wire.radius;
t_seg.feedpoint = false;
segments.push(t_seg);
}
});
console.log(segments);
this.Z = [];
for (let m = 0; m < segments.length; m++) {
var row = [];
for (let n = 0; n < segments.length; n++) {
// Use Harrington's method:
//console.log(m, n);
var tmp = math.dot(math.subtract(segments[n].end, segments[n].start), math.subtract(segments[m].end, segments[m].start));
//var tmp = math.dot(math.subtract(wire.points[n+1], wire.points[n-1]), math.subtract(wire.points[m+1], wire.points[m-1]));
tmp *= w * mu0;
tmp = math.multiply(math.complex(0,tmp), this.psi(segments[n], segments[m], segments[n].mid, segments[m].mid));
// tmp = math.multiply(math.complex(0,tmp), this.psi(wire, n, m));
var tmp2 = math.add(this.psi(segments[n], segments[m], segments[n].end, segments[m].end), this.psi(segments[n], segments[m], segments[n].start, segments[m].start));
// var tmp2 = math.add(this.psi(wire, n+1, m+1), this.psi(wire, n-1, m-1));
var tmp3 = math.add(this.psi(segments[n], segments[m], segments[n].start, segments[m].end), this.psi(segments[n], segments[m], segments[n].end, segments[m].start));
//var tmp3 = math.add(this.psi(wire, n-1, m+1), this.psi(wire, n+1, m-1));
var tmp4 = math.subtract(tmp2, tmp3);
tmp2 = math.multiply(tmp4, math.complex(0,-1/(w*e0)));
row.push(math.add(tmp, tmp2));
}
this.Z.push(row);
}
//console.log(this.Z);
this.Y = math.inv(this.Z);
return this.Z;
}
createVoltageVector() {
this.V = [];
for(var i=0; i<this.Z.length; i++){
if(i == Math.round(this.Z.length/2)) {
this.V.push(math.complex(1,0));
} else {
this.V.push(math.complex(0,0));
}
}
return this.V;
}
calculateVoltage() {
var retval = [];
var x_axis = 0.0;
for(var i=0; i<V.length; i++) {
x_axis += wire.seg_len;
retval.push({x:x_axis, y:V[i].toPolar().r});
}
return retval;
}
calculateCurrent() {
var retval = [];
var x_axis = 0.0;
for(var i=0; i<I.length; i++) {
x_axis += wire.seg_len;
retval.push({x:x_axis, y:I[i].toPolar().r});
}
return retval;
}
distanceToOrigin(px) {
return(Math.sqrt(px[0]**2 + px[1]**2 + px[2]**2));
}
/*getPatternAt(el, az) {*/
getPatternAt(x, y, z) {
// Return the magnitude of the radiation pattern in the xyz direction. Later, update this to provide a composite object containing polarization-specific patterns:
// First, refer all the current segments to the origin, along the line subtending along the el-az direction:
this.wires.forEach(wire => {
wire.forEach(segment => {
// Calculate the dot-product between the observation vector, to the origin-midpoint vector:
var phase_distance = math.dot(segment.mid, );
// Use each segment's midpoint for the xyz value, relative to x0y0z0:
var distance = distanceToOrigin(segment.mid);
// Actually, we need the distance to the plane perpendicular to the observation point, that goes through the origin:
});
});
// Next, return the current components in the vertical and horizontal direction:
return 1.0;
}
}
class WireAntennaView {
// constructor:
constructor() {
//
}
loadAntenna(wire_segments) {
//
}
//
getThreeObject3D = function () {
const material = new THREE.LineBasicMaterial({ color: 0xffff00, linewidth: 1 });
const antenna_view = new THREE.Group();
//
const ww = this.antenna_types['antennas'][this.current_type]['wires'];
const vv = this.antenna_types['antennas'][this.current_type]['vertex'];
ww.forEach(wire => {
//console.log(wire);
var vertices = new Float32Array(wire.length * 3);
var vidx = 0;
const scale_factor = 200.0; // Roughly pixels per wavelength
// Copy the vertex locations across into a Float32Array for the geometry:
wire.forEach((vertex) => {
//console.log(vertex, vv[vertex]);
vertices[vidx++] = (vv[vertex][0] * scale_factor);
vertices[vidx++] = (vv[vertex][1] * scale_factor);
vertices[vidx++] = (vv[vertex][2] * scale_factor);
});
//
const geometry = new THREE.BufferGeometry();
geometry.setAttribute('position', new THREE.BufferAttribute(vertices, 3));
// create a new wire with
const wire_line = new THREE.Line(geometry, material);
antenna_view.add(wire_line);
});
// Add the antenna into the scene:
return antenna_view;
};
}
// Density map class for calculating the CVF heat/density map:
class RadiationPattern {
constructor() {
// Heatmap specific stuff:
this.radiationPatternGeometry = new THREE.IcosahedronGeometry( 98, 5 );
this.radiationPatternVertices = this.radiationPatternGeometry.getAttribute('position');
this.radiationPatternHistogram = this.radiationPatternGeometry.getAttribute('uv');
//var radiationPatternColor = new Float32Array( radiationPatternVertices.count * 3 );
// Initialise to ZERO the UV values:
for(var i = 0; i < this.radiationPatternHistogram.count; i++) {
this.radiationPatternHistogram.setX(i, 0.0);
this.radiationPatternHistogram.setY(i, 0.0);
}
this.hiColor = [0, 255, 0, 1.0]; // Let's default to RED in [R, G, B, A]
this.loColor = [0, 0, 255, 1.0]; // Let's default to RED in [R, G, B, A]
this.contrast = 1.0;
this.shape = 1.0;
this.radiationPatternUniforms = {
//cameraConstant: { value: getCameraConstant( camera ) },
hiColor: { value: new THREE.Vector4(this.hiColor[0]/255.0, this.hiColor[1]/255.0, this.hiColor[2]/255.0, this.hiColor[3]) },
loColor: { value: new THREE.Vector4(this.loColor[0]/255.0, this.loColor[1]/255.0, this.loColor[2]/255.0, this.loColor[3]) },
maxHistogramValue: { value: 0.0 },
contrast: { value: this.contrast },
shape: { value: this.shape }
};
// ShaderMaterial
this.wireframe = true;
this.material = new THREE.ShaderMaterial( {
uniforms: this.radiationPatternUniforms,
vertexShader: radiationPatternVertexShader,
//fragmentShader: cvfFragmentShader,
wireframe: this.wireframe
} );
this.material.extensions.drawBuffers = true;
this.radiationPatternMesh = new THREE.Mesh( this.radiationPatternGeometry, this.material );
this.visibility = true;
this.radiationPatternMesh.visible = this.visibility;
this.hasWork = false;
}
// Insert the geometries into the scenegraph's sceneObject:
insertScene(sceneObject) {
sceneObject.add(this.radiationPatternMesh);
}
// Set the visible geometry. No point ever displaying both, as they use the SAME vertices:
setVisibility(value) {
this.visibility = value;
this.radiationPatternMesh.visible = value;
}
// Select whether to animate or not:
setAnimate(value) {
this.radiationPatternUniforms.animate.value = (value) ? true : false;
}
// Little hack for dat.gui. Update the color by passing 4-element array in form [255, 128, 0, 1.0]:
setHiColor(value) {
this.hiColor = value;
this.radiationPatternUniforms.hiColor.value = [this.hiColor[0]/255.0, this.hiColor[1]/255.0, this.hiColor[2]/255.0, this.hiColor[3]];
}
setLoColor(value) {
this.loColor = value;
this.radiationPatternUniforms.loColor.value = [this.loColor[0]/255.0, this.loColor[1]/255.0, this.loColor[2]/255.0, this.loColor[3]];
}
setPattern(ant_obj) {
// Populate the lookup table with the vertex and its index:
for(var i = 0; i < this.radiationPatternVertices.count; i++) {
const x = this.radiationPatternVertices.getX(i);
const y = this.radiationPatternVertices.getY(i);
const z = this.radiationPatternVertices.getZ(i);
// p at first should be a scalar. But later might be a composite of 2 scalars, to denote polarization (H, V)
var p = ant_obj.getPatternAt(x, y, z);
this.radiationPatternHistogram.setX(i, p);
this.radiationPatternHistogram.setY(i, p);
}
}
reset() {
// Clear the geometry UV:
for(var i = 0; i < this.radiationPatternHistogram.count; i++) {
this.radiationPatternHistogram.setX(i, 0.0);
this.radiationPatternHistogram.setY(i, 0.0);
}
this.radiationPatternUniforms.maxHistogramValue.value = 0.0;
this.radiationPatternHistogram.needsUpdate = true;
this.i_n = 0;
this.i_m = 0;
this.i_phi = 0;
this.hasWork = true;
}
pause() {
this.hasWork = !this.hasWork;
}
// Function that solves the density map, but is capable of solving it in chunks of work, so as not to stall
// the screen refresh cycle. That way, high values of N x M x Phi, which can take minutes to solve, but
// still allows a dynamic and responsive user interface.
process(value) {
if(this.hasWork) {
var count = 0;
OUT:
for(; this.i_phi < this.PHI; ++this.i_phi) {
for(; this.i_m < this.M; ++this.i_m) {
for(; this.i_n < this.N; ++this.i_n) {
var geo = CVF.getY00Vertex (1, 98.0, this.i_n, this.N, this.i_m, this.M, this.i_phi, this.PHI);
// Obtain xyz coords of the CVF vertex:
var hx = Math.trunc(geo[0]/25.0) + 6;
var hy = Math.trunc(geo[1]/25.0) + 6;
var hz = Math.trunc(geo[2]/25.0) + 6;
// Look in the buckets/bins on either side of this point, to capture all vertices in range.
// This means looking in a 3x3 grid of bins to search. Still much quicker then iterating
// through ALL vertices:
for(var xx=-1; xx<2; ++xx) {
for(var yy=-1; yy<2; ++yy) {
for(var zz=-1; zz<2; ++zz) {
// Now find all the vertices in the bin, and increment its histogram:
for(var j=0, jl=this.fast_index[hx+xx][hy+yy][hz+zz].length; j < jl; ++j) {
var pos = this.fast_index[hx+xx][hy+yy][hz+zz][j];
var rangeSquared = Math.pow(geo[0] - this.radiationPatternVertices.getX(pos), 2)
+ Math.pow(geo[1] - this.radiationPatternVertices.getY(pos), 2)
+ Math.pow(geo[2] - this.radiationPatternVertices.getZ(pos), 2);
// Use a range of 15 units, so 15*15=225:
if(rangeSquared < 225.0) {
var num = this.radiationPatternHistogram.getX(pos);
this.radiationPatternHistogram.setX(pos, num+1.0);
// Find highest value in array:
if((num+1.0) > this.radiationPatternUniforms.maxHistogramValue.value) {
this.radiationPatternUniforms.maxHistogramValue.value = num+1.0;
}
}
}
}
}
}
// This is where we check if we've completed a certain number of searches. If we have
// done more than "value", then we finish for now, and continue during the next
// process() function call, and continue where we left-off:
if(++count >= value) {
this.radiationPatternHistogram.needsUpdate = true;
break OUT;
}
}
// Finished the N-loop, so re-zero the iterator here:
this.i_n = 0;
}
// Finished the M-loop, so re-zero the iterator here:
this.i_m = 0;
}
// This checks the condition that we are completely done iterating through NxMxPHI. The needsUpdate
// variable is how we tell THREE.js to update/reload the geometry in VRAM (GPU buffers)
if(this.i_phi >= this.PHI) {
this.hasWork = false;
this.radiationPatternHistogram.needsUpdate = true;
}
}
}
}
init();
animate();
// Setup the Three.js boiler-plate code to setup the screen/window, add controls, and EM-solve and
// display the default antenna.
function init() {
container = document.createElement( 'div' );
document.body.appendChild( container );
camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 5, 15000 );
camera.position.y = 120;
camera.position.z = 400;
scene = new THREE.Scene();
scene.background = new THREE.Color(0.0, 0.0, 0.0);
renderer = new THREE.WebGLRenderer({antialias:true});
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
container.appendChild( renderer.domElement );
controls = new OrbitControls( camera, renderer.domElement );
//stats = new Stats();
//container.appendChild( stats.dom );
window.addEventListener( 'resize', onWindowResize, false );
// Add an axis:
var axis = new THREE.AxesHelper(200);
scene.add(axis);
// Add the radiation pattern:
var pattern = new RadiationPattern();
pattern.insertScene(scene);
/*
var parameters =
{
a: 200, // numeric
b: 200, // numeric slider
c: "Hello, GUI!", // string
d: false, // boolean (checkbox)
e: "#ff8800", // color (hex)
f: function() { alert("Hello!") },
g: function() { alert( parameters.c ) },
v : 0, // dummy value, only type is important
w: "...", // dummy value, only type is important
x: 0, y: 0, z: 0
};
*/
var parameters = {
mode: true,
axis: true,
ground: true,
height: 50.0,
w: "...", // dummy value, only type is important
pattern: true,
};
const gui = new dat.GUI();
// Add a mode selector. View is to change perspective. Edit allows moving vertices. Solve runs the EM solver. (Maybe should be edit+solve?)
/*
gui.add(parameters, 'mode', ['view', 'edit', 'solve'])
.setValue('view')
.onChange(function(value){ console.log(value); });
*/
// Ground-plane
const geometry = new THREE.CircleGeometry( 150, 32 );
const material = new THREE.MeshBasicMaterial( { color: 0x006f00, wireframe: true } );
const ground_plane = new THREE.Mesh( geometry, material );
ground_plane.rotateX(Math.PI * -0.5);
scene.add( ground_plane );
// Add an enable axes-helper button:
gui.add(parameters, 'axis')
.setValue(true)
.onChange(function(value){ axis.visible = value; });
// Add an enable axes-helper button:
gui.add(parameters, 'ground')
.setValue(true)
.onChange(function(value){ ground_plane.visible = value; });
// Add an enable axes-helper button:
gui.add(parameters, 'height', 0.0, 500.0)
.setValue(parameters['height'])
.onChange(function(value){
//console.log(current_antenna_object['position']);
current_antenna_object['position'].y = value;
//console.log(parameters['height']);
});
gui.add( parameters, 'pattern')
.setValue(true)
.onChange(function(value){
pattern.setVisibility(value);
});
// Create the AntennaTypes object, which holds all the antenna types and creates the visual model on-request
ant = new AntennaTypes();
gui.add( parameters, 'w', ant.antenna_types['order'])
.setValue(ant.current_type)
.name('Types')
.onChange(function(value) {
console.log(value);
// Remove the current antenna visual model, if one was loaded:
scene.remove(current_antenna_object);
// Use selected antenna to rebuild new wire model:
ant.setAntennaType(value);
console.log(ant.getWires());
// Solve the z-matrix:
ant.calculateZMatrix();
console.log('Z', ant.Z);
var admittance = math.inv(ant.Z);
console.log('S', admittance);
var V = ant.createVoltageVector();
console.log('V', V);
var I = math.multiply(admittance, V);
console.log(I);
// Load new antenna visual model into the scene:
//console.log(ant.getWires());
current_antenna_object = ant.getThreeObject3D();
current_antenna_object['position'].y = parameters['height'];
scene.add(current_antenna_object);
});
// Add a folder for the antenna-specific controls.
//const cubeFolder = gui.addFolder('Antenna Controls');
/*
ant.antenna_types['order'].forEach(element => {
cubeFolder.add(element);
});
*/
// Create a half-wavelength long wire, with a radius of 0.001 lambda, and segmented into 10 pieces:
//wire = createWire(0.5, 0.0001, 45);
var wire = ant.getWires()[0];
//console.log(createWire(0.5, 0.0001, 45));
console.log(wire);
//console.log(wire);
//var frequency = 3.0e8;
//frequency = 1.0;
// Solve the z-matrix:
ant.calculateZMatrix();
console.log('Z', ant.Z);
var admittance = math.inv(ant.Z);
console.log('S', admittance);
var V = ant.createVoltageVector();
console.log('V', V);
var I = math.multiply(admittance, V);
console.log('I', I);
//V = math.multiply(impedance, I);
current_antenna_object = ant.getThreeObject3D();
current_antenna_object['position'].y = parameters['height'];
scene.add(current_antenna_object);
}
//
function createWire(length, wire_radius, segments) {
// dimensions in lambda
var wire = {};
wire.length = length;
wire.seg_len = length / segments;
wire.radius = wire_radius;
const offset = 0.5 * length;
wire.points = [];
wire.points.push([0.0, 0.0, -offset]);
for (let i = 0; i < segments; i++) {
wire.points.push([0.0, 0.0, i * wire.seg_len + 0.5 * wire.seg_len - offset]);
wire.points.push([0.0, 0.0, (i+1) * wire.seg_len - offset]);
}
return wire;
}
function psi_old(wire, n, m) {
var retval = 0.0;
const k = 2.0 * Math.PI; // Normalised wavelength is equal to 1.0 - otherwise 2*pi/wavelength
const fourPI = 4.0 * Math.PI;
var Rmn = 0.0;
// From MININEC thesis (3-36) and (3-37):
if(m==n) {
Rmn = Math.sqrt(wire.radius**2 + (wire.seg_len*0.5)**2);
} else {
Rmn = Math.sqrt((wire.points[m][0] - wire.points[n][0])**2 + (wire.points[m][1] - wire.points[n][1])**2 + (wire.points[m][2] - wire.points[n][2])**2);
}
retval = math.multiply(math.complex(Math.cos(k * Rmn), -Math.sin(k * Rmn)), (1/(fourPI*Rmn)));
//console.log(n, m, retval);
return retval;
}
// Use Harrington's equations (129) and (135):
function psi2(wire, n, m) {
var retval = 0.0;
var Rmn = 0.0;
// Calculate the range from the source point (n) to the observation point (m) depending whether it is the same segment or not:
if(m==n) {
Rmn = Math.sqrt(wire.radius**2 + (wire.seg_len*0.5)**2);
} else {
Rmn = Math.sqrt((wire.points[m][0] - wire.points[n][0])**2 + (wire.points[m][1] - wire.points[n][1])**2 + (wire.points[m][2] - wire.points[n][2])**2);
}
// Now if r<10a, use 129. If r>=10a, use 135:
const alpha = wire.seg_len*0.5;
const zeta = wire.points[m][2] - wire.points[n][2]; // This is z at m when n is set as the coordinate space origin. So need to transform coord-space to make it N-centric first! Uugh!
// zeta is the projection of m onto the n segment, if the n-segment were centered at the origin along the z-direction.
const mn = math.subtract(wire.points[m], wire.points[n-1])
//var zeta = math.dot(wire.points[m], wire.points[n]);
//zeta = zeta / ()
const rho = 0;
if(Rmn < (10.0 * alpha)) {
// Eq 129:
var t1 = math.complex(Math.cos(k * Rmn), -Math.sin(k * Rmn));
t1 = math.multiply((1.0/(8.0*Math.PI*alpha)), t1);
const i1 = Math.log((zeta + alpha + Math.sqrt(rho**2 + (zeta + alpha)**2)) / (zeta - alpha + Math.sqrt(rho**2 + (zeta - alpha)**2)));
const i2 = 2 * alpha;
const i3 = (0.5 * (alpha + zeta)) * Math.sqrt(rho**2 + (alpha + zeta)**2) + (0.5 * (alpha - zeta)) * Math.sqrt(rho**2 + (zeta - alpha)**2) + (0.5 * rho**2 * i1);
const i4 = (2*alpha*rho**2) + (0.333333 * (2*alpha**3 + 6*alpha*zeta**2));
const re = i1 - 0.5*k**2 * (i3 - 2*Rmn*i2 + Rmn**2*i1);
const im = -k*(i2 - Rmn*i1) + (1.0/6)*k**3*(i4 - 3*Rmn*i3 + 3*Rmn**2*i2 - Rmn**3*i1);
retval = math.complex(math.multiply(t1, re), math.multiply(t1, im));
} else {
// Eq 135:
const a_r = alpha / rho;
const z_r = zeta / rho;
const A0 = 1 + (1.0/6.0) * (a_r ** 2) * (-1 + 3 * z_r**2) + (1/40) * a_r ** 4 * (3 - 30 * z_r**2 + 35 * z_r**4);
const A1 = (1/6)*a_r*(-1 + 3 * z_r**2) + (1/40)*a_r**3 * (3 - 30 * z_r**2 + 35 * z_r**4);
const A2 = (-1/6)*(z_r)**2 - (1/40) * (a_r)**2 * (1 - 12*z_r**2 + 15 * z_r**4);
const A3 = (1/60)*a_r*(3*z_r**2 - 5 * z_r**4);
const A4 = (1/120)*(z_r**4);
const ka = k * alpha;
const kr = k * rho;
const re = (B*Math.cos(kr)) * (A0 + ka**2 * A2 + ka**4 * A4);
const im = (B*Math.sin(kr)) * (ka*A1 + ka**3 * A3);
retval = math.complex(re + im);
}
return retval;
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );
// TODO - need to notify scene object shaders (via uniforms) here if required!
// becvfUniforms.cameraConstant.value = getCameraConstant( camera );
}
function getCameraConstant( camera ) {
return window.innerHeight / ( Math.tan( THREE.Math.DEG2RAD * 0.5 * camera.fov ) / camera.zoom );
}
function animate() {
// Update the elapsed time to later provide to the shaders:
var delta = clock.getDelta();
tick += delta;
if ( tick < 0 ) tick = 0;
// Tell WebGL to call the 'animate()' function for the next screen refresh:
requestAnimationFrame( animate );
// Render the scene, and update the stats:
renderer.render( scene, camera );
//stats.update();
}
</script>
</body>
</html>