465 lines
14 KiB
HTML
465 lines
14 KiB
HTML
<html>
|
|
<style>
|
|
/* HTML5 display-role reset for older browsers */
|
|
article, aside, details, figcaption, figure,
|
|
footer, header, hgroup, menu, nav, section {
|
|
display: block;
|
|
}
|
|
blockquote, q {
|
|
quotes: none;
|
|
}
|
|
blockquote:before, blockquote:after,
|
|
q:before, q:after {
|
|
content: '';
|
|
content: none;
|
|
}
|
|
table {
|
|
border-collapse: collapse;
|
|
border-spacing: 0;
|
|
}
|
|
|
|
:focus {
|
|
outline: 0;
|
|
}
|
|
|
|
/* END RESET */
|
|
|
|
|
|
#presets,
|
|
#cameraPresets,
|
|
#numberOfFreqs {
|
|
display: inline-block;
|
|
margin-left: 1em;
|
|
}
|
|
|
|
button {
|
|
border: 0;
|
|
background: #ddd;
|
|
color: #000;
|
|
padding: 5px 10px;
|
|
font-weight: bold;
|
|
font-size: 12pt;
|
|
border-radius: 5px;
|
|
cursor: pointer;
|
|
margin-left: 0.5em;
|
|
}
|
|
|
|
|
|
#input {
|
|
margin: 20px;
|
|
display: grid;
|
|
grid-template-columns: 5em 1fr 1fr;
|
|
}
|
|
#input .head {
|
|
font-weight: bold;
|
|
text-align: center;
|
|
}
|
|
#input .frequency {
|
|
text-align: right;
|
|
padding-right: 0.3em;
|
|
}
|
|
canvas {
|
|
margin-top: 20px;
|
|
width: 100%;
|
|
background: #fff;
|
|
}
|
|
#out1 {
|
|
width: 400px;
|
|
}
|
|
</style>
|
|
<br>
|
|
<p><b>Zeit-/Frequenzdomäne</b></p>
|
|
<p>Im folgenden Applett kann man das ausprobieren: Mit den Frequenzreglern kann man die Amplitude der einzelnen Sinusschwingungen erhöhen. Mit dem Preset "Rechteck" kann man sehen, dass ein Rechteck aus vielen Sinusschwingungen besteht.</p>
|
|
<div id="presets">
|
|
Presets:
|
|
</div>
|
|
<div id="cameraPresets">
|
|
Ansicht:
|
|
</div>
|
|
<div id="numberOfFreqs">
|
|
Anzahl Frequenzen:
|
|
<input type="range" id="numOfFreqs" min=1 max=30 value=9>
|
|
</div>
|
|
<div>
|
|
<canvas></canvas>
|
|
</div>
|
|
<div id="input">
|
|
</div>
|
|
|
|
<script src="https://50ohm.de/assets/three/three.js"></script>
|
|
<script>
|
|
|
|
var amplitudesSquare = [];
|
|
|
|
for (var i = 0; i < 30; i++) {
|
|
if (i % 2 != 0) amplitudesSquare[i] = 0;
|
|
else amplitudesSquare[i] = 1/(i + 1);
|
|
}
|
|
|
|
const presets = [
|
|
{
|
|
label: "Minimum",
|
|
defaultAmplitude: 0,
|
|
amplitudes: [],
|
|
defaultPhase: 0,
|
|
phases: []
|
|
},
|
|
{
|
|
label: "Maximum",
|
|
defaultAmplitude: 1,
|
|
amplitudes: [],
|
|
defaultPhase: 0,
|
|
phases: []
|
|
},
|
|
{
|
|
label: "Rechteck",
|
|
defaultAmplitude: 0,
|
|
amplitudes: amplitudesSquare,
|
|
defaultPhase: 0,
|
|
phases: []
|
|
}
|
|
];
|
|
|
|
|
|
|
|
const cameraPresets = [
|
|
{ label: "Standard", camera: 0, d: 3, theta: 90, phi: 48 },
|
|
{ label: "Zeit", camera: 0, d: 3, theta: 20, phi: 48 },
|
|
{ label: "Frequenz", camera: 0, d: 3, theta: 160, phi: 48 },
|
|
// { label: "Zeit", camera: 1, d: 1, theta: 90, phi: 40 },
|
|
// { label: "Frequenz", camera: 1, d: 1, theta: 90, phi: 0 }
|
|
];
|
|
|
|
const steps = 200;
|
|
|
|
var numOfFreqs;
|
|
|
|
var lines = [];
|
|
var timeline;
|
|
var freqlines = [];
|
|
var amplitudes = [];
|
|
var phases = [];
|
|
var cameras = [];
|
|
var camera;
|
|
var isMouseDown = false;
|
|
|
|
const scene = new THREE.Scene();
|
|
scene.background = new THREE.Color( 0xffffff );
|
|
|
|
|
|
var aspect = 2;
|
|
var d = 3;
|
|
var theta = 90;
|
|
var oldTheta;
|
|
var phi = 48;
|
|
var oldPhi;
|
|
|
|
cameras[0] = new THREE.PerspectiveCamera( 25, 2, 0.1, 1000 );
|
|
cameras[1] = new THREE.OrthographicCamera(- 0.5 * aspect, 0.5 * aspect, 0.5, -0.5, 0.01, 1000 );
|
|
camera = cameras[0];
|
|
const canvas = document.querySelector("canvas")
|
|
|
|
const renderer = new THREE.WebGLRenderer({
|
|
premultipliedAlpha: false,
|
|
antialias: true,
|
|
canvas: canvas
|
|
});
|
|
|
|
function cameraPresetClickedCallback(cameraPreset) {
|
|
return function() {
|
|
camera = cameras[cameraPreset.camera];
|
|
d = cameraPreset.d;
|
|
theta = cameraPreset.theta;
|
|
phi = cameraPreset.phi;
|
|
render();
|
|
}
|
|
}
|
|
|
|
function presetClickedCallback(i) {
|
|
return function() {
|
|
const preset = presets[i];
|
|
for (var j = 0; j < numOfFreqs; j++) {
|
|
var amplitude = preset.amplitudes[j];
|
|
if (! amplitude)
|
|
amplitude = preset.defaultAmplitude;
|
|
amplitudes[j] = amplitude;
|
|
document.getElementById('amplitude' + j).value = amplitude;
|
|
var phase = preset.phases[j];
|
|
if (! phase)
|
|
phase = preset.defaultPhase;
|
|
phases[j] = phase;
|
|
document.getElementById('phase' + j).value = phase;
|
|
}
|
|
render();
|
|
}
|
|
}
|
|
|
|
function inputChangeCallback(i, norender) {
|
|
return function() {
|
|
amplitudes[i] = document.getElementById('amplitude' + i).value;
|
|
phases[i] = document.getElementById('phase' + i).value;
|
|
if (lines[0] && ! norender) {
|
|
render();
|
|
}
|
|
}
|
|
}
|
|
|
|
function initInput() {
|
|
|
|
const numOfFreqsEl = document.getElementById("numOfFreqs");
|
|
numOfFreqsEl.addEventListener("input", function() {
|
|
const numOfFreqsEl = document.getElementById("numOfFreqs");
|
|
numOfFreqs = numOfFreqsEl.value;
|
|
initFreqInput(true);
|
|
initScene();
|
|
render();
|
|
})
|
|
|
|
const cameraPresetsEl = document.getElementById("cameraPresets");
|
|
for (var i = 0; i < cameraPresets.length; i++) {
|
|
const cameraPreset = cameraPresets[i];
|
|
const el = document.createElement("button");
|
|
cameraPresetsEl.appendChild(el);
|
|
el.innerHTML = cameraPreset.label;
|
|
el.addEventListener("click", cameraPresetClickedCallback(cameraPreset));
|
|
}
|
|
|
|
const presetsEl = document.getElementById("presets");
|
|
for (var i = 0; i < presets.length; i++) {
|
|
const preset = presets[i];
|
|
const el = document.createElement("button");
|
|
presetsEl.appendChild(el);
|
|
el.innerHTML = preset.label;
|
|
el.addEventListener("click", presetClickedCallback(i));
|
|
}
|
|
|
|
numOfFreqs = numOfFreqsEl.value;
|
|
initFreqInput();
|
|
|
|
}
|
|
|
|
function initFreqInput(norender = false) {
|
|
const inputEl = document.getElementById("input");
|
|
inputEl.innerHTML = '<div class="head"></div><div class="head">Amplitude</div><div class="head">Phase</div>';
|
|
for (var i = 0; i < numOfFreqs; i++) {
|
|
const labelEl = document.createElement("div");
|
|
inputEl.appendChild(labelEl);
|
|
labelEl.classList.add("frequency");
|
|
labelEl.innerHTML = i + 1 + " Hz";
|
|
const range1El = document.createElement("input");
|
|
inputEl.appendChild(range1El);
|
|
range1El.type = "range";
|
|
range1El.id = "amplitude" + i;
|
|
range1El.min = 0;
|
|
range1El.max = 1;
|
|
range1El.step = 0.01;
|
|
var amplitude = amplitudes[i];
|
|
if (!amplitude) amplitude = 0;
|
|
range1El.value = amplitude;
|
|
range1El.addEventListener("input", inputChangeCallback(i));
|
|
const range2El = document.createElement("input");
|
|
inputEl.appendChild(range2El);
|
|
range2El.type = "range";
|
|
range2El.id = "phase" + i;
|
|
range2El.min = 0;
|
|
range2El.max = 1;
|
|
range2El.step = 0.01;
|
|
var phase = phases[i];
|
|
if (!phase) phase = 0;
|
|
range2El.value = phase;
|
|
range2El.value = 0;
|
|
range2El.addEventListener("input", inputChangeCallback(i));
|
|
inputChangeCallback(i, norender)();
|
|
}
|
|
}
|
|
|
|
function line(x1, y1, z1, x2, y2, z2, material) {
|
|
const points = [];
|
|
points.push( new THREE.Vector3( x1, y1, z1 ) );
|
|
points.push( new THREE.Vector3( x2, y2, z2 ) );
|
|
const geometry = new THREE.BufferGeometry().setFromPoints( points );
|
|
const line = new THREE.Line( geometry, material );
|
|
scene.add(line);
|
|
return line;
|
|
}
|
|
|
|
function updateLine(i) {
|
|
var line = lines[i];
|
|
var freqline = freqlines[i];
|
|
var z = 1 - (i + 1) / numOfFreqs;
|
|
var minY = 0.5;
|
|
var maxY = 0.5;
|
|
for (var j = 0; j <= steps; j++) {
|
|
var step = 1 / steps;
|
|
var x = j * step;
|
|
var y = signal(i, x);
|
|
if (y > maxY) maxY = y;
|
|
if (y < minY) minY = y;
|
|
line.geometry.attributes.position.array[j * 3] = x;
|
|
line.geometry.attributes.position.array[j * 3 + 1] = y;
|
|
}
|
|
line.geometry.attributes.position.needsUpdate = true;
|
|
freqline.geometry.attributes.position.array[1] = minY;
|
|
freqline.geometry.attributes.position.array[4] = maxY;
|
|
freqline.geometry.attributes.position.needsUpdate = true;
|
|
}
|
|
|
|
function updateLines() {
|
|
for (var i = 0; i < numOfFreqs; i++) {
|
|
updateLine(i);
|
|
}
|
|
for (var j = 0; j <= steps; j++) {
|
|
var step = 1 / steps;
|
|
var x = j * step;
|
|
var y = 0;
|
|
for (var i = 0; i < numOfFreqs; i++) {
|
|
y += signalFunc(i, x);
|
|
}
|
|
y = y / 20 + 0.5
|
|
timeline.geometry.attributes.position.array[j * 3] = x;
|
|
timeline.geometry.attributes.position.array[j * 3 + 1] = y;
|
|
timeline.geometry.attributes.position.array[j * 3 + 2] = 1.1;
|
|
}
|
|
timeline.geometry.attributes.position.needsUpdate = true;
|
|
}
|
|
|
|
function signalFunc(freq, x) {
|
|
phase = phases[freq];
|
|
amplitude = amplitudes[freq];
|
|
return Math.sin(((freq + 1) * x - phase) * 2 * Math.PI) * amplitude;
|
|
}
|
|
|
|
function signal(freq, x) {
|
|
return signalFunc(freq, x) / 20 + 0.5
|
|
}
|
|
|
|
function initScene() {
|
|
while(scene.children.length > 0){
|
|
scene.remove(scene.children[0]);
|
|
}
|
|
lines = [];
|
|
freqlines = [];
|
|
const materialBlack = new THREE.LineBasicMaterial({
|
|
color: 0x666666
|
|
});
|
|
const materialBlue = new THREE.LineBasicMaterial({
|
|
linewidth: 2,
|
|
color: 0x0000ff
|
|
});
|
|
const materialRed = new THREE.LineBasicMaterial({
|
|
linewidth: 2,
|
|
color: 0xff0000
|
|
});
|
|
const plane_material = new THREE.MeshBasicMaterial({
|
|
color: 0xcccccc,
|
|
opacity: 0.7,
|
|
transparent: true,
|
|
side: THREE.DoubleSide
|
|
});
|
|
|
|
for (var i = 0; i < numOfFreqs; i++) {
|
|
var z = 1 - (i + 0.5) / numOfFreqs;
|
|
line(0, 0.5, z, 1, 0.5, z, materialBlack);
|
|
const points = [];
|
|
for (var j = 0; j <= steps; j++) {
|
|
points.push( new THREE.Vector3( 0, 0, z) );
|
|
}
|
|
const geometry = new THREE.BufferGeometry().setFromPoints(points);
|
|
const l = new THREE.Line(geometry, materialBlue);
|
|
scene.add(l);
|
|
lines[i] = l;
|
|
freqlines[i] = line(1.1, 0.5, z, 1.1, 0.5, z, materialRed);
|
|
}
|
|
|
|
const timepoints = [];
|
|
for (var j = 0; j <= steps; j++) {
|
|
timepoints.push( new THREE.Vector3( 0, 0, 0) );
|
|
}
|
|
const timegeometry = new THREE.BufferGeometry().setFromPoints(timepoints);
|
|
timeline = new THREE.Line(timegeometry, materialRed);
|
|
scene.add(timeline);
|
|
|
|
line(0, 0.5, 1.1, 1, 0.5, 1.1, materialBlack);
|
|
|
|
const freqpoints = [];
|
|
for (var j = 0; j < numOfFreqs; j++) {
|
|
freqpoints.push( new THREE.Vector3( 0, 0, 0) );
|
|
}
|
|
const freqgeometry = new THREE.BufferGeometry().setFromPoints(freqpoints);
|
|
freqline = new THREE.Line(freqgeometry, materialRed);
|
|
scene.add(freqline);
|
|
|
|
line(1.1, 0.5, 0, 1.1, 0.5, 1, materialBlack);
|
|
|
|
const plane_geometry = new THREE.PlaneGeometry( 1, 0.5 );
|
|
|
|
const planeX = new THREE.Mesh( plane_geometry, plane_material );
|
|
planeX.position.x = 0.5;
|
|
planeX.position.y = 0.5;
|
|
planeX.position.z = 1.1;
|
|
scene.add( planeX );
|
|
|
|
const planeY = new THREE.Mesh( plane_geometry, plane_material );
|
|
planeY.position.x = 1.1;
|
|
planeY.position.y = 0.5;
|
|
planeY.position.z = 0.5;
|
|
planeY.rotation.y = Math.PI / 2 ;
|
|
scene.add( planeY );
|
|
|
|
updateLines();
|
|
|
|
const canvas_geometry = canvas.getBoundingClientRect();
|
|
renderer.setSize(
|
|
Math.round(canvas_geometry.width),
|
|
Math.round(canvas_geometry.height)
|
|
);
|
|
|
|
canvas.addEventListener("mousedown", function(event) {
|
|
isMouseDown = event;
|
|
oldTheta = theta;
|
|
oldPhi = phi;
|
|
});
|
|
|
|
window.addEventListener("mousemove", function(event) {
|
|
if (isMouseDown) {
|
|
diffX = event.clientX - isMouseDown.clientX;
|
|
diffY = event.clientY - isMouseDown.clientY;
|
|
theta = oldTheta - diffX * 0.5;
|
|
phi = oldPhi + diffY * 0.5;
|
|
phi = Math.min(90, Math.max(-50, phi));
|
|
render();
|
|
}
|
|
});
|
|
|
|
window.addEventListener("mouseup", function(event) {
|
|
isMouseDown = false;
|
|
});
|
|
|
|
}
|
|
|
|
function render() {
|
|
updateLines();
|
|
|
|
// theta += 0.1;
|
|
|
|
// camera.position.x = radius * Math.sin( THREE.MathUtils.degToRad( theta ) );
|
|
// camera.position.y = radius * Math.sin( THREE.MathUtils.degToRad( theta ) );
|
|
// camera.position.z = radius * Math.cos( THREE.MathUtils.degToRad( theta ) );
|
|
camera.position.x = d * Math.sin(theta * Math.PI / 360)
|
|
* Math.cos(phi * Math.PI / 360);
|
|
camera.position.y = d * Math.sin(phi * Math.PI / 360);
|
|
camera.position.z = d * Math.cos(theta * Math.PI / 360)
|
|
* Math.cos(phi * Math.PI / 360);
|
|
camera.updateMatrix();
|
|
target_position = new THREE.Vector3(0.5,0.25,0.5);
|
|
camera.lookAt( target_position );
|
|
|
|
renderer.render( scene, camera );
|
|
}
|
|
|
|
initInput();
|
|
initScene();
|
|
render();
|
|
|
|
</script>
|
|
</html> |