Created
February 6, 2025 21:31
-
-
Save VitalyErmilov/6f875145369b97bc9c59b37581934e30 to your computer and use it in GitHub Desktop.
ECG [TSL]
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <script type="importmap"> | |
| { | |
| "imports": { | |
| "three": "https://unpkg.com/three@0.173.0/build/three.webgpu.js", | |
| "three/webgpu": "https://unpkg.com/three@0.173.0/build/three.webgpu.js", | |
| "three/tsl": "https://unpkg.com/three@0.173.0/build/three.tsl.js", | |
| "three/addons/": "https://unpkg.com/three@0.173.0/examples/jsm/" | |
| } | |
| } | |
| </script> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import * as THREE from "three"; | |
| import * as tsl from "three/tsl"; | |
| import { OrbitControls } from "three/addons/controls/OrbitControls.js"; | |
| import { pass, mrt, output, emissive } from "three/tsl"; | |
| import { bloom } from 'three/addons/tsl/display/BloomNode.js'; | |
| console.clear(); | |
| // load fonts | |
| await (async function () { | |
| async function loadFont(fontface) { | |
| await fontface.load(); | |
| document.fonts.add(fontface); | |
| } | |
| let fonts = [ | |
| new FontFace( | |
| "Orbitron", | |
| "url(https://fonts.gstatic.com/s/orbitron/v31/yMJRMIlzdpvBhQQL_Qq7dy0.woff2) format('woff2')" | |
| ) | |
| ]; | |
| for (let font in fonts) { | |
| await loadFont(fonts[font]); | |
| } | |
| })(); | |
| class Postprocessing extends THREE.PostProcessing { | |
| constructor(renderer) { | |
| const scenePass = pass(scene, camera); | |
| scenePass.setMRT( | |
| mrt({ | |
| output, | |
| emissive | |
| }) | |
| ); | |
| const outputPass = scenePass.getTextureNode(); | |
| const emissivePass = scenePass.getTextureNode("emissive"); | |
| const bloomPass = bloom(emissivePass, 0.15, 0); | |
| super(renderer); | |
| this.outputNode = outputPass.add(bloomPass); | |
| } | |
| } | |
| class DataScreen extends THREE.Mesh{ | |
| constructor(){ | |
| let g = new THREE.PlaneGeometry(3, 5); | |
| let m = new THREE.MeshLambertMaterial({ | |
| color: new THREE.Color(0xface8d).multiplyScalar(0.1), | |
| emissive: 0xff2200, | |
| emissiveIntensity: 5, | |
| side: THREE.DoubleSide, | |
| transparent: true | |
| }); | |
| super(g, m); | |
| this.init(); | |
| } | |
| init(){ | |
| let c = document.createElement("canvas"); | |
| c.width = 600; | |
| c.height = 1000; | |
| let ctx = c.getContext("2d"); | |
| let tex = new THREE.CanvasTexture(c); | |
| tex.anisotropy = 8; | |
| this.onScreenData = { | |
| canvas: c, | |
| ctx: ctx, | |
| texture: tex, | |
| cardiogramPoints: [ | |
| [-45, 0], | |
| [-45, 0], | |
| [-30, 0], | |
| [-20, -10], | |
| [-15, 0], | |
| [-10, 10], | |
| [0, -50], | |
| [10, 10], | |
| [15, 0], | |
| [25, -10], | |
| [30, 0], | |
| [45, 0], | |
| [45, 0] | |
| ].map(p => { | |
| let v = new THREE.Vector2(...p); | |
| v.x *= 0.5; | |
| return v; | |
| }), | |
| totalLength: 0, | |
| lineWidth: c.height * 0.01 * 3, | |
| u: (val) => {return val * c.height * 0.01} | |
| }; | |
| for(let i = 0; i < this.onScreenData.cardiogramPoints.length - 1; i++){ | |
| let v1 = this.onScreenData.cardiogramPoints[i]; | |
| let v2 = this.onScreenData.cardiogramPoints[i + 1]; | |
| this.onScreenData.totalLength += this.onScreenData.u(v1.distanceTo(v2)); | |
| } | |
| this.mediators = { | |
| v: new THREE.Vector2 | |
| } | |
| this.material.emissiveMap = this.onScreenData.texture; | |
| this.material.alphaMap = this.onScreenData.texture; | |
| } | |
| update(t){ | |
| let points = this.onScreenData.cardiogramPoints; | |
| let cnv = this.onScreenData.canvas; | |
| let ctx = this.onScreenData.ctx; | |
| let u = this.onScreenData.u; | |
| ctx.clearRect(0, 0, cnv.width, cnv.height); | |
| ctx.strokeStyle = ctx.fillStyle = "rgba(255, 255, 255, 1)"; | |
| ctx.lineJoin = "round"; | |
| ctx.lineCap = "round"; | |
| ctx.lineWidth = u(2); | |
| ctx.save(); | |
| ctx.setLineDash([this.onScreenData.totalLength, this.onScreenData.totalLength]); | |
| ctx.lineDashOffset = this.onScreenData.totalLength - t * (this.onScreenData.totalLength / 1.5); | |
| ctx.translate(cnv.width * 0.5, cnv.height * 0.5); | |
| ctx.beginPath(); | |
| let pMinusOne = points[0]; | |
| let pZero = points[1]; | |
| ctx.moveTo(u((pMinusOne.x + pZero.x) * 0.5), u((pMinusOne.y + pZero.y) * 0.5)); | |
| for(let pIdx = 1; pIdx < points.length - 1; pIdx++) { | |
| let p = points[pIdx]; | |
| let pNext = points[pIdx + 1]; | |
| ctx.quadraticCurveTo(u(p.x), u(p.y), u((p.x + pNext.x) * 0.5), u((p.y + pNext.y) * 0.5)); | |
| }; | |
| ctx.stroke(); | |
| ctx.restore(); | |
| ctx.lineWidth = u(0.5); | |
| ctx.beginPath(); | |
| ctx.roundRect(u(2), u(2), cnv.width - u(4), cnv.height - u(4), u(5)); | |
| ctx.stroke(); | |
| ctx.font = `${u(15)}px Orbitron`; | |
| ctx.textAlign = "center"; | |
| ctx.textBaseline = "middle"; | |
| ctx.fillText("ECG", cnv.width * 0.5, u(75)); | |
| this.onScreenData.texture.needsUpdate = true; | |
| } | |
| } | |
| let scene = new THREE.Scene(); | |
| scene.backgroundNode = tsl.color(0x000000); | |
| let camera = new THREE.PerspectiveCamera(30, innerWidth / innerHeight, 1, 100); | |
| camera.position.set(0, 0, 1).setLength(10); | |
| let renderer = new THREE.WebGPURenderer({ antialias: false }); | |
| renderer.setPixelRatio(devicePixelRatio); | |
| renderer.setSize(innerWidth, innerHeight); | |
| renderer.toneMapping = THREE.ACESFilmicToneMapping; | |
| document.body.appendChild(renderer.domElement); | |
| window.addEventListener("resize", (event) => { | |
| camera.aspect = innerWidth / innerHeight; | |
| camera.updateProjectionMatrix(); | |
| renderer.setSize(innerWidth, innerHeight); | |
| }); | |
| let postprocessing = new Postprocessing(renderer); | |
| let controls = new OrbitControls(camera, renderer.domElement); | |
| controls.enableDamping = true; | |
| let light = new THREE.DirectionalLight(0xffffff, Math.PI); | |
| light.position.setScalar(1); | |
| let screenLight = new THREE.DirectionalLight(0xff2200, Math.PI); | |
| screenLight.position.z = 1; | |
| scene.add(light, screenLight, new THREE.AmbientLight(0xffffff, Math.PI * 0.5)); | |
| let dataScreen = new DataScreen(); | |
| //dataScreen.update(0); | |
| scene.add(dataScreen); | |
| let dodec = new THREE.Mesh( | |
| new THREE.DodecahedronGeometry(1, 0), | |
| new THREE.MeshLambertMaterial({color: 0x888888}) | |
| ); | |
| dodec.position.z = -1.5; | |
| scene.add(dodec); | |
| let clock = new THREE.Clock(); | |
| let t = 0; | |
| renderer.setAnimationLoop(() => { | |
| controls.update(); | |
| let dt = clock.getDelta(); | |
| t += dt; | |
| dataScreen.update(t); | |
| dodec.rotation.y = t * 0.31; | |
| dodec.rotation.x = t * 0.27; | |
| //renderer.render(scene, camera); | |
| postprocessing.render(); | |
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| body{ | |
| overflow: hidden; | |
| margin: 0; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment