feature: add interactive applets
This commit is contained in:
@@ -0,0 +1,465 @@
|
||||
<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>
|
||||
Reference in New Issue
Block a user