mirror of
https://github.com/pyscript/pyscript.git
synced 2026-02-15 22:00:37 -05:00
* Fix #2302 - Updated Polyscript to its latest
This commit is contained in:
committed by
GitHub
parent
55031f2347
commit
290eb03388
@@ -28,6 +28,7 @@ repos:
|
||||
rev: 25.1.0
|
||||
hooks:
|
||||
- id: black
|
||||
exclude: core/tests
|
||||
args: ["-l", "88", "--skip-string-normalization"]
|
||||
|
||||
- repo: https://github.com/codespell-project/codespell
|
||||
@@ -42,10 +43,11 @@ repos:
|
||||
rev: v0.9.6
|
||||
hooks:
|
||||
- id: ruff
|
||||
exclude: core/tests
|
||||
|
||||
- repo: https://github.com/hoodmane/pyscript-prettier-precommit
|
||||
rev: "v3.0.0-alpha.6"
|
||||
hooks:
|
||||
- id: prettier
|
||||
exclude: core/test|core/dist|core/types|core/src/stdlib/pyscript.js|pyscript\.sw/|core/src/3rd-party
|
||||
exclude: core/tests|core/dist|core/types|core/src/stdlib/pyscript.js|pyscript\.sw/|core/src/3rd-party
|
||||
args: [--tab-width, "4"]
|
||||
|
||||
24
core/package-lock.json
generated
24
core/package-lock.json
generated
@@ -1,19 +1,19 @@
|
||||
{
|
||||
"name": "@pyscript/core",
|
||||
"version": "0.6.33",
|
||||
"version": "0.6.35",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@pyscript/core",
|
||||
"version": "0.6.33",
|
||||
"version": "0.6.35",
|
||||
"license": "APACHE-2.0",
|
||||
"dependencies": {
|
||||
"@ungap/with-resolvers": "^0.1.0",
|
||||
"@webreflection/idb-map": "^0.3.2",
|
||||
"add-promise-listener": "^0.1.3",
|
||||
"basic-devtools": "^0.1.6",
|
||||
"polyscript": "^0.16.15",
|
||||
"polyscript": "^0.16.17",
|
||||
"sabayon": "^0.6.6",
|
||||
"sticky-module": "^0.1.1",
|
||||
"to-json-callback": "^0.1.1",
|
||||
@@ -1734,9 +1734,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/electron-to-chromium": {
|
||||
"version": "1.5.105",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.105.tgz",
|
||||
"integrity": "sha512-ccp7LocdXx3yBhwiG0qTQ7XFrK48Ua2pxIxBdJO8cbddp/MvbBtPFzvnTchtyHQTsgqqczO8cdmAIbpMa0u2+g==",
|
||||
"version": "1.5.107",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.107.tgz",
|
||||
"integrity": "sha512-dJr1o6yCntRkXElnhsHh1bAV19bo/hKyFf7tCcWgpXbuFIF0Lakjgqv5LRfSDaNzAII8Fnxg2tqgHkgCvxdbxw==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
@@ -2747,9 +2747,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/polyscript": {
|
||||
"version": "0.16.15",
|
||||
"resolved": "https://registry.npmjs.org/polyscript/-/polyscript-0.16.15.tgz",
|
||||
"integrity": "sha512-E2rFTz1CzqFpuA4ALdko6FJNeQ4Bb3EAZHhcx4bFqc6zB188iqJ+GJM0pcsZUqn/ExZpvd8CPLqHIyaNDRW2SQ==",
|
||||
"version": "0.16.17",
|
||||
"resolved": "https://registry.npmjs.org/polyscript/-/polyscript-0.16.17.tgz",
|
||||
"integrity": "sha512-6dWyWoZn6Z915bizfjftc82p2BhkWn6pPgd/u9GBXmdXTZBPC1ezDizqtgwpceTK6GNHPuNQ4elLWr5G0W+Mrw==",
|
||||
"license": "APACHE-2.0",
|
||||
"dependencies": {
|
||||
"@ungap/structured-clone": "^1.3.0",
|
||||
@@ -3913,9 +3913,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/update-browserslist-db": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.2.tgz",
|
||||
"integrity": "sha512-PPypAm5qvlD7XMZC3BujecnaOxwhrtoFR+Dqkk5Aa/6DssiH0ibKoketaj9w8LP7Bont1rYeoV5plxD7RTEPRg==",
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz",
|
||||
"integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@pyscript/core",
|
||||
"version": "0.6.33",
|
||||
"version": "0.6.35",
|
||||
"type": "module",
|
||||
"description": "PyScript",
|
||||
"module": "./index.js",
|
||||
@@ -66,7 +66,7 @@
|
||||
"@webreflection/idb-map": "^0.3.2",
|
||||
"add-promise-listener": "^0.1.3",
|
||||
"basic-devtools": "^0.1.6",
|
||||
"polyscript": "^0.16.15",
|
||||
"polyscript": "^0.16.17",
|
||||
"sabayon": "^0.6.6",
|
||||
"sticky-module": "^0.1.1",
|
||||
"to-json-callback": "^0.1.1",
|
||||
|
||||
File diff suppressed because one or more lines are too long
BIN
core/tests/manual/issue-2302/assets/genuary25-18.m4a
Normal file
BIN
core/tests/manual/issue-2302/assets/genuary25-18.m4a
Normal file
Binary file not shown.
20
core/tests/manual/issue-2302/glue/multipyjs.py
Normal file
20
core/tests/manual/issue-2302/glue/multipyjs.py
Normal file
@@ -0,0 +1,20 @@
|
||||
from pyscript import config
|
||||
|
||||
MICROPYTHON = config["type"] == "mpy"
|
||||
|
||||
if MICROPYTHON:
|
||||
def new(obj, *args, **kwargs):
|
||||
return obj.new(*args, kwargs) if kwargs else obj.new(*args)
|
||||
def call(obj, *args, **kwargs):
|
||||
return obj(*args, kwargs) if kwargs else obj(*args)
|
||||
else:
|
||||
def new(obj, *args, **kwargs):
|
||||
return obj.new(*args, **kwargs)
|
||||
def call(obj, *args, **kwargs):
|
||||
return obj(*args, **kwargs)
|
||||
|
||||
if not MICROPYTHON:
|
||||
import pyodide_js
|
||||
pyodide_js.setDebug(True)
|
||||
|
||||
from pyscript.ffi import to_js, create_proxy
|
||||
Binary file not shown.
69
core/tests/manual/issue-2302/index.html
Normal file
69
core/tests/manual/issue-2302/index.html
Normal file
@@ -0,0 +1,69 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Genuary</title>
|
||||
|
||||
<!-- Recommended meta tags -->
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
|
||||
|
||||
<!-- PyScript CSS -->
|
||||
<link rel="stylesheet" href="../../../dist/core.css">
|
||||
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
background-color: #4a315e;
|
||||
color: white;
|
||||
font-family: Inconsolata, Consolas, Monaco, Courier New;
|
||||
}
|
||||
|
||||
.gutter {
|
||||
background-color: #eee;
|
||||
background-repeat: no-repeat;
|
||||
background-position: 50%;
|
||||
}
|
||||
|
||||
.gutter.gutter-vertical {
|
||||
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAFAQMAAABo7865AAAABlBMVEVHcEzMzMzyAv2sAAAAAXRSTlMAQObYZgAAABBJREFUeF5jOAMEEAIEEFwAn3kMwcB6I2AAAAAASUVORK5CYII=');
|
||||
cursor: row-resize;
|
||||
}
|
||||
|
||||
py-terminal {
|
||||
max-height: 7em;
|
||||
max-width: calc(100vw - 90px);
|
||||
}
|
||||
|
||||
#pyterm {
|
||||
background-color: #191a1a;
|
||||
}
|
||||
|
||||
#pyterm,
|
||||
#threejs {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- This script tag bootstraps PyScript -->
|
||||
<script type="importmap">
|
||||
{
|
||||
"imports": {
|
||||
"three": "https://cdn.jsdelivr.net/npm/three@v0.173.0/build/three.module.js"
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<script type="module" src="../../../dist/core.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/split.js/1.6.5/split.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="stats"></div>
|
||||
<div id="stats-off"></div>
|
||||
<div class="split">
|
||||
<div id="pyterm"></div>
|
||||
<div id="threejs"></div>
|
||||
</div>
|
||||
<script type="py" src="./main.py" config="./pyscript.toml" async terminal></script>
|
||||
</body>
|
||||
</html>
|
||||
83
core/tests/manual/issue-2302/libfft.py
Normal file
83
core/tests/manual/issue-2302/libfft.py
Normal file
@@ -0,0 +1,83 @@
|
||||
from dataclasses import dataclass, field
|
||||
import sys
|
||||
|
||||
@dataclass
|
||||
class BeatSync:
|
||||
fft_res: int = field()
|
||||
|
||||
on_beat: bool = False
|
||||
beat: int = -1
|
||||
since_last_beat: float = sys.maxsize
|
||||
|
||||
_prev: int = 0
|
||||
_count: int = 0
|
||||
_bins: list[int] = field(default_factory=list)
|
||||
_last_detection: float = -1.0
|
||||
_threshold: int = 50
|
||||
_diff: int = 40
|
||||
_cooldown: float = 0.2
|
||||
|
||||
_highest: int = 0
|
||||
|
||||
def __post_init__(self):
|
||||
self._bins = [int(13/16*self.fft_res/2)+17, int(13/16*self.fft_res/2)+18]
|
||||
|
||||
def reset(self):
|
||||
self.beat = -1
|
||||
self._prev = 0
|
||||
self._count = 0
|
||||
self._last_detection = -1.0
|
||||
self.since_last_beat = sys.maxsize
|
||||
# print('bs reset')
|
||||
|
||||
def update(self, data, running_time):
|
||||
self._count += 1
|
||||
self.since_last_beat = running_time - self._last_detection
|
||||
d = sum(data[bin] for bin in self._bins)
|
||||
if d < self._threshold:
|
||||
self.on_beat = False
|
||||
elif d - self._prev < self._diff:
|
||||
self.on_beat = False
|
||||
elif self.since_last_beat < self._cooldown:
|
||||
self.on_beat = False
|
||||
else:
|
||||
self._last_detection = running_time
|
||||
self.since_last_beat = 0
|
||||
self.on_beat = True
|
||||
self.beat += 1
|
||||
self._prev = d
|
||||
|
||||
@dataclass
|
||||
class FreqIntensity:
|
||||
freq: float = field()
|
||||
fft_res: int = field()
|
||||
|
||||
intensity: float = 0.0
|
||||
intensity_slew: float = 0.0
|
||||
scale_min: float = 0.0
|
||||
scale_max: float = 350
|
||||
max: float = 0.0
|
||||
_sample_rate: int = 48000
|
||||
_bin_indexes: list[int] = field(default_factory=list)
|
||||
_harmonics: int = 8
|
||||
_slew_factor: float = 0.8
|
||||
|
||||
def __post_init__(self):
|
||||
self._bin_indexes = [
|
||||
round((harmonic+1) * self.freq / self._sample_rate * self.fft_res / 2)
|
||||
for harmonic in range(self._harmonics)
|
||||
]
|
||||
print(self._bin_indexes)
|
||||
|
||||
def update(self, data):
|
||||
intensity = 0.0
|
||||
for bin in range(self._harmonics):
|
||||
intensity += data[self._bin_indexes[bin]]/(bin+1)
|
||||
self.intensity = intensity
|
||||
self.intensity_slew = self._slew_factor * self.intensity_slew + (1 - self._slew_factor) * intensity
|
||||
self.max = max(intensity, self.max)
|
||||
|
||||
@property
|
||||
def intensity_scaled(self):
|
||||
raw = max(0, min(1.0, (self.intensity_slew - self.scale_min)/(self.scale_max - self.scale_min)))
|
||||
return raw * raw
|
||||
189
core/tests/manual/issue-2302/libthree.py
Normal file
189
core/tests/manual/issue-2302/libthree.py
Normal file
@@ -0,0 +1,189 @@
|
||||
import asyncio
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Callable
|
||||
|
||||
from pyscript import document, window
|
||||
|
||||
from pyscript.js_modules import three as THREE
|
||||
from pyscript.js_modules.stats_gl import default as StatsGL
|
||||
from pyscript.js_modules import lsgeo, line2, linemat
|
||||
|
||||
from multipyjs import MICROPYTHON, new, call, to_js, create_proxy
|
||||
|
||||
@dataclass
|
||||
class SoundPlayer:
|
||||
sound: THREE.Audio = field()
|
||||
on_start: Callable[[], None] = field()
|
||||
on_stop: Callable[[], None] = field(default=lambda: None)
|
||||
|
||||
_start_time: float = -1.0
|
||||
|
||||
def play(self):
|
||||
self.sound.stop()
|
||||
self.on_start()
|
||||
self._start_time = self.sound.context.currentTime
|
||||
self.sound.play()
|
||||
|
||||
def stop(self):
|
||||
self.sound.stop()
|
||||
self.on_stop()
|
||||
self._start_time = -1.0
|
||||
|
||||
def toggle(self):
|
||||
if self.sound.isPlaying:
|
||||
self.stop()
|
||||
else:
|
||||
self.play()
|
||||
|
||||
@property
|
||||
def running_time(self):
|
||||
if self.sound.isPlaying:
|
||||
return self.sound.context.currentTime - self._start_time
|
||||
elif self._start_time != -1.0:
|
||||
self.stop()
|
||||
return 0.0
|
||||
|
||||
def get_renderer():
|
||||
renderer = new(THREE.WebGLRenderer, antialias=True)
|
||||
renderer.setSize(window.innerWidth, window.innerHeight)
|
||||
renderer.setPixelRatio(window.devicePixelRatio)
|
||||
renderer.setClearColor(0xF5F0DC)
|
||||
pyterms = list(document.getElementsByTagName("py-terminal"))
|
||||
if pyterms:
|
||||
pyterm = pyterms[0]
|
||||
pyterm.parentNode.removeChild(pyterm)
|
||||
document.getElementById("pyterm").appendChild(pyterm)
|
||||
|
||||
document.getElementById("threejs").appendChild(renderer.domElement)
|
||||
|
||||
initial = {0: "115px", 1: "calc(100vh - 120px)"}
|
||||
@create_proxy
|
||||
def split_element_style(dimension, size, gutter_size, index):
|
||||
if index in initial:
|
||||
result = {dimension: initial.pop(index)}
|
||||
else:
|
||||
result = {dimension: f"calc({int(size)}vh - {gutter_size}px)"}
|
||||
return to_js(result)
|
||||
|
||||
call(
|
||||
window.Split,
|
||||
["#pyterm", "#threejs"],
|
||||
direction="vertical",
|
||||
elementStyle=split_element_style,
|
||||
minSize=0,
|
||||
maxSize=to_js([120, 10000]),
|
||||
)
|
||||
return renderer
|
||||
|
||||
def get_ortho_camera(view_size):
|
||||
aspect_ratio = window.innerWidth / window.innerHeight
|
||||
camera = new(
|
||||
THREE.OrthographicCamera,
|
||||
-view_size * aspect_ratio, # Left
|
||||
view_size * aspect_ratio, # Right
|
||||
view_size, # Top
|
||||
-view_size, # Bottom
|
||||
-view_size, # Near plane
|
||||
view_size, # Far plane
|
||||
)
|
||||
camera.updateProjectionMatrix()
|
||||
camera.position.set(0, 0, 0)
|
||||
return camera
|
||||
|
||||
def get_loading_manager():
|
||||
loading_mgr = new(THREE.LoadingManager)
|
||||
ev = asyncio.Event()
|
||||
|
||||
@create_proxy
|
||||
def on_start(url, itemsLoaded, itemsTotal):
|
||||
print(f'[{itemsLoaded}/{itemsTotal}] Started loading file: {url}')
|
||||
loading_mgr.onStart = on_start
|
||||
|
||||
@create_proxy
|
||||
def on_progress(url, itemsLoaded, itemsTotal):
|
||||
print(f'[{itemsLoaded}/{itemsTotal}] Loading file: {url}')
|
||||
loading_mgr.onProgress = on_progress
|
||||
|
||||
@create_proxy
|
||||
def on_error(url):
|
||||
print(f'There was a problem loading {url}')
|
||||
loading_mgr.onError = on_error
|
||||
|
||||
@create_proxy
|
||||
def on_load():
|
||||
print('Loading assets complete!')
|
||||
ev.set()
|
||||
loading_mgr.onLoad = on_load
|
||||
|
||||
return loading_mgr, ev
|
||||
|
||||
|
||||
def get_perspective_camera():
|
||||
aspect_ratio = window.innerWidth / window.innerHeight
|
||||
camera = new(
|
||||
THREE.PerspectiveCamera,
|
||||
45, # fov
|
||||
aspect_ratio,
|
||||
0.25, # near plane
|
||||
300, # far plane
|
||||
)
|
||||
camera.position.set(0, 0, 30)
|
||||
return camera
|
||||
|
||||
def get_stats_gl(renderer):
|
||||
stats = new(StatsGL, trackGPU=True, horizontal=False)
|
||||
stats.init(renderer)
|
||||
stats.dom.style.removeProperty("left")
|
||||
stats.dom.style.right = "90px"
|
||||
document.getElementById("stats").appendChild(stats.dom)
|
||||
return stats
|
||||
|
||||
def bg_from_v(*vertices):
|
||||
geometry = new(THREE.BufferGeometry)
|
||||
vertices_f32a = new(Float32Array, vertices)
|
||||
attr = new(THREE.Float32BufferAttribute, vertices_f32a, 3)
|
||||
return geometry.setAttribute('position', attr)
|
||||
|
||||
def bg_from_p(*points):
|
||||
buf = new(THREE.BufferGeometry)
|
||||
buf.setFromPoints(
|
||||
[new(THREE.Vector3, p[0], p[1], p[2]) for p in points]
|
||||
)
|
||||
return buf
|
||||
|
||||
def clear():
|
||||
# toggle stats and terminal?
|
||||
stats_style = document.getElementById("stats-off").style
|
||||
if stats_style.display == "none":
|
||||
# turn stuff back on
|
||||
stats_style.removeProperty("display")
|
||||
document.getElementById("pyterm").style.height = "115px"
|
||||
document.getElementById("threejs").style.height = "calc(100vh - 120px)"
|
||||
for e in document.getElementsByClassName("gutter"):
|
||||
e.style.removeProperty("display")
|
||||
for e in document.getElementsByClassName("xterm-helper-textarea"):
|
||||
e.focus()
|
||||
break
|
||||
return
|
||||
|
||||
# no longer focus on xterm
|
||||
document.activeElement.blur()
|
||||
# hide stats
|
||||
document.getElementById("stats-off").style.display = "none"
|
||||
# hide pyterm and split gutter
|
||||
document.getElementById("pyterm").style.height = "0vh"
|
||||
document.getElementById("threejs").style.height = "100vh"
|
||||
for e in document.getElementsByClassName("gutter"):
|
||||
e.style.display = "none"
|
||||
# hide ltk ad
|
||||
for e in document.getElementsByClassName("ltk-built-with"):
|
||||
e.style.display = "none"
|
||||
# hide pyscript ad
|
||||
for e in document.getElementsByTagName("div"):
|
||||
style = e.getAttribute("style")
|
||||
if style and style.startswith("z-index:999"):
|
||||
e.style.display = "none"
|
||||
for e in document.getElementsByTagName("svg"):
|
||||
style = e.getAttribute("style")
|
||||
if style and style.startswith("z-index:999"):
|
||||
e.style.display = "none"
|
||||
285
core/tests/manual/issue-2302/main.py
Normal file
285
core/tests/manual/issue-2302/main.py
Normal file
@@ -0,0 +1,285 @@
|
||||
print("Starting up...")
|
||||
|
||||
from array import array
|
||||
import asyncio
|
||||
import math
|
||||
import time
|
||||
|
||||
from pyscript import document, window, PyWorker
|
||||
|
||||
from libthree import THREE, clear, SoundPlayer
|
||||
from libthree import get_renderer, get_ortho_camera
|
||||
from libthree import get_loading_manager, get_stats_gl
|
||||
from libthree import lsgeo, line2, linemat, lsgeo
|
||||
from libfft import BeatSync
|
||||
|
||||
from multipyjs import MICROPYTHON, new, call, to_js, create_proxy
|
||||
|
||||
from js import Float32Array
|
||||
|
||||
scene = new(THREE.Scene)
|
||||
|
||||
view_size = 1
|
||||
renderer = get_renderer()
|
||||
camera = get_ortho_camera(view_size)
|
||||
loading_mgr, loaded_event = get_loading_manager()
|
||||
|
||||
t_loader = new(THREE.TextureLoader, loading_mgr)
|
||||
t_loader.setPath('assets/')
|
||||
|
||||
light = new(THREE.AmbientLight, 0xffffff, 1.0)
|
||||
scene.add(light)
|
||||
|
||||
fft_res = 2048
|
||||
audio_listener = new(THREE.AudioListener)
|
||||
camera.add(audio_listener)
|
||||
sound = new(THREE.Audio, audio_listener)
|
||||
audio_loader = new(THREE.AudioLoader, loading_mgr)
|
||||
analyser = new(THREE.AudioAnalyser, sound, fft_res)
|
||||
|
||||
@create_proxy
|
||||
def on_audio_load(buffer):
|
||||
sound.setBuffer(buffer)
|
||||
sound.setVolume(0.9)
|
||||
sound.setLoop(False)
|
||||
|
||||
audio_loader.load("assets/genuary25-18.m4a", on_audio_load)
|
||||
|
||||
spheres = new(THREE.Group)
|
||||
scene.add(spheres)
|
||||
|
||||
line_basic_mat = new(
|
||||
THREE.LineBasicMaterial,
|
||||
color=0xffffff,
|
||||
)
|
||||
|
||||
zero_mat = new(
|
||||
linemat.LineMaterial,
|
||||
color=0x662503,
|
||||
linewidth=3,
|
||||
)
|
||||
|
||||
other_mat = new(
|
||||
linemat.LineMaterial,
|
||||
color=0x662503,
|
||||
linewidth=1.5,
|
||||
)
|
||||
|
||||
grid_mat = new(
|
||||
linemat.LineMaterial,
|
||||
color=0x662503,
|
||||
linewidth=1,
|
||||
dashed=True,
|
||||
dashScale=1,
|
||||
dashSize=0.5,
|
||||
gapSize=1,
|
||||
dashOffset=0,
|
||||
)
|
||||
|
||||
lines = [new(THREE.Group), new(THREE.Group)]
|
||||
scene.add(lines[0])
|
||||
scene.add(lines[1])
|
||||
|
||||
def draw_lines(line_coords, mat_name, spy=False):
|
||||
if spy:
|
||||
line_coords_f32a = new(Float32Array, line_coords.length)
|
||||
_it = line_coords.items
|
||||
for i in range(line_coords.length):
|
||||
line_coords_f32a[i] = _it[i]
|
||||
else:
|
||||
line_coords_f32a = new(Float32Array, line_coords)
|
||||
if mat_name == 'zero':
|
||||
mat = zero_mat
|
||||
elif mat_name == 'grid':
|
||||
mat = grid_mat
|
||||
else:
|
||||
mat = other_mat
|
||||
|
||||
geo = new(THREE.BufferGeometry)
|
||||
geo.setAttribute('position', new(THREE.BufferAttribute, line_coords_f32a, 3))
|
||||
seg = new(THREE.LineSegments, geo, line_basic_mat)
|
||||
|
||||
lsg = new(lsgeo.LineSegmentsGeometry)
|
||||
lsg.fromLineSegments(seg)
|
||||
l1 = new(line2.Line2, lsg, mat)
|
||||
l1.computeLineDistances()
|
||||
l2 = new(line2.Line2, lsg, mat)
|
||||
l2.computeLineDistances()
|
||||
lines[0].add(l1)
|
||||
lines[1].add(l2)
|
||||
|
||||
seg.geometry.dispose()
|
||||
del geo
|
||||
del seg
|
||||
|
||||
def drawing_done():
|
||||
maybe_with_spy = "with SPy" if USE_SPY else "with pure Python"
|
||||
print(f"Time elapsed computing {maybe_with_spy}:", time.time() - start_ts)
|
||||
drawing_event.set()
|
||||
|
||||
grid_width = 0
|
||||
grid_height = 0
|
||||
scroll_offset = 0
|
||||
def scale_lines(grid_ws=None, grid_hs=None, offset=None):
|
||||
global grid_width, grid_height, scroll_offset
|
||||
|
||||
if grid_ws:
|
||||
grid_width = grid_ws
|
||||
else:
|
||||
grid_ws = grid_width
|
||||
|
||||
if grid_hs:
|
||||
grid_height = grid_hs
|
||||
else:
|
||||
grid_hs = grid_height
|
||||
|
||||
if offset:
|
||||
scroll_offset = offset
|
||||
else:
|
||||
offset = scroll_offset
|
||||
|
||||
scale = 2.04/grid_hs
|
||||
lines[0].scale.set(scale, scale, scale)
|
||||
lines[1].scale.set(scale, scale, scale)
|
||||
lines[0].position.set((offset - grid_ws/2) * scale, -grid_hs/2 * scale, 0)
|
||||
lines[1].position.set((offset + grid_ws/2) * scale, -grid_hs/2 * scale, 0)
|
||||
|
||||
def append_p(lines, p1, p2):
|
||||
lines.append(p1[0])
|
||||
lines.append(p1[1])
|
||||
lines.append(0)
|
||||
lines.append(p2[0])
|
||||
lines.append(p2[1])
|
||||
lines.append(0)
|
||||
|
||||
def initial_calc():
|
||||
grid_w = int(1920 * 4)
|
||||
grid_h = 1080 * 2
|
||||
grid_scale = 10
|
||||
noise_factor = 500
|
||||
grid_hs = int(grid_h/grid_scale)
|
||||
grid_ws = int(grid_w/grid_scale)
|
||||
crossfade_range = int(grid_ws/12.5)
|
||||
|
||||
def grid_lines():
|
||||
lines = array("d")
|
||||
grid_goal = 24
|
||||
grid_size_i = int(round((grid_ws - crossfade_range) / grid_goal))
|
||||
grid_actual = (grid_ws - crossfade_range) / grid_size_i
|
||||
for i in range(0, grid_size_i):
|
||||
x = i * grid_actual
|
||||
append_p(lines, (x, 0), (x, grid_hs))
|
||||
for y in range(0, grid_hs, grid_goal):
|
||||
append_p(lines, (0, y), (grid_ws-crossfade_range, y))
|
||||
return lines
|
||||
|
||||
import perlin
|
||||
spy_perlin = perlin.lib
|
||||
spy_perlin.init()
|
||||
spy_perlin.seed(44)
|
||||
scale_lines(grid_ws - crossfade_range, grid_hs)
|
||||
print("Computing the height map")
|
||||
spy_perlin.make_height_map(grid_ws, grid_hs)
|
||||
spy_perlin.update_height_map(grid_ws, grid_hs, grid_scale / noise_factor, 0)
|
||||
print("Cross-fading the height map")
|
||||
spy_perlin.crossfade_height_map(grid_ws, grid_hs, crossfade_range)
|
||||
print("Drawing grid")
|
||||
draw_lines(grid_lines(), 'grid')
|
||||
print("Marching squares")
|
||||
draw_lines(spy_perlin.marching_squares(grid_ws, grid_hs, 0), 'zero', spy=True)
|
||||
draw_lines(spy_perlin.marching_squares(grid_ws, grid_hs, 0.3), 'positive', spy=True)
|
||||
draw_lines(spy_perlin.marching_squares(grid_ws, grid_hs, -0.3), 'negative', spy=True)
|
||||
draw_lines(spy_perlin.marching_squares(grid_ws, grid_hs, 0.45), 'positive', spy=True)
|
||||
draw_lines(spy_perlin.marching_squares(grid_ws, grid_hs, -0.45), 'negative', spy=True)
|
||||
draw_lines(spy_perlin.marching_squares(grid_ws, grid_hs, 0.6), 'positive', spy=True)
|
||||
draw_lines(spy_perlin.marching_squares(grid_ws, grid_hs, -0.6), 'negative', spy=True)
|
||||
draw_lines(spy_perlin.marching_squares(grid_ws, grid_hs, -0.8), 'negative', spy=True)
|
||||
draw_lines(spy_perlin.marching_squares(grid_ws, grid_hs, 0.8), 'positive', spy=True)
|
||||
drawing_done()
|
||||
|
||||
drawing_event = asyncio.Event()
|
||||
start_ts = time.time()
|
||||
|
||||
USE_SPY = True
|
||||
if USE_SPY:
|
||||
initial_calc()
|
||||
else:
|
||||
worker = PyWorker("./worker.py", type="pyodide", configURL="./pyscript.toml")
|
||||
worker.sync.draw_lines = draw_lines
|
||||
worker.sync.drawing_done = drawing_done
|
||||
worker.sync.scale_lines = scale_lines
|
||||
worker.sync.print = print
|
||||
|
||||
@create_proxy
|
||||
def on_tap(event):
|
||||
clear()
|
||||
player.toggle()
|
||||
document.addEventListener("click", on_tap)
|
||||
|
||||
@create_proxy
|
||||
def on_key_down(event):
|
||||
element = document.activeElement
|
||||
_class = element.getAttribute("class")
|
||||
in_xterm = element.tagName != "BODY" and _class and "xterm" in _class
|
||||
|
||||
if event.code == "Backquote":
|
||||
# Screenshot mode.
|
||||
clear()
|
||||
elif not in_xterm:
|
||||
# Don't react to those bindings when typing code.
|
||||
if event.code == "Space":
|
||||
player.toggle()
|
||||
document.addEventListener("keydown", on_key_down)
|
||||
|
||||
@create_proxy
|
||||
def on_window_resize(event):
|
||||
aspect_ratio = window.innerWidth / window.innerHeight
|
||||
if camera.type == "OrthographicCamera":
|
||||
camera.left = -view_size * aspect_ratio
|
||||
camera.right = view_size * aspect_ratio
|
||||
camera.top = view_size
|
||||
camera.bottom = -view_size
|
||||
camera.updateProjectionMatrix()
|
||||
elif camera.type == "PerspectiveCamera":
|
||||
camera.aspect = window.innerWidth / window.innerHeight
|
||||
camera.updateProjectionMatrix()
|
||||
else:
|
||||
raise ValueError("Unknown camera type")
|
||||
renderer.setSize(window.innerWidth, window.innerHeight)
|
||||
scale_lines()
|
||||
|
||||
window.addEventListener("resize", on_window_resize)
|
||||
|
||||
@create_proxy
|
||||
def animate(now=0.0):
|
||||
data = analyser.getFrequencyData()#.to_py() in Pyodide
|
||||
audio_now = player.running_time
|
||||
bs.update(data, audio_now)
|
||||
|
||||
if grid_width:
|
||||
offset = -((20 * audio_now) % grid_width)
|
||||
scale_lines(offset=offset)
|
||||
|
||||
renderer.render(scene, camera)
|
||||
stats_gl.update()
|
||||
|
||||
def reset():
|
||||
global scroll_offset
|
||||
bs.reset()
|
||||
scale_lines()
|
||||
|
||||
def on_stop():
|
||||
global scroll_offset
|
||||
bs.reset()
|
||||
scale_lines()
|
||||
|
||||
await loaded_event.wait()
|
||||
|
||||
stats_gl = get_stats_gl(renderer)
|
||||
player = SoundPlayer(sound=sound, on_start=reset, on_stop=on_stop)
|
||||
bs = BeatSync(fft_res=fft_res)
|
||||
renderer.setAnimationLoop(animate)
|
||||
print("Waiting for the contours...")
|
||||
|
||||
await drawing_event.wait()
|
||||
print("Tap the map to start...")
|
||||
110
core/tests/manual/issue-2302/perlin_py.py
Normal file
110
core/tests/manual/issue-2302/perlin_py.py
Normal file
@@ -0,0 +1,110 @@
|
||||
# Translated from https://github.com/josephg/noisejs.
|
||||
from libthree import THREE
|
||||
from multipyjs import new
|
||||
|
||||
class V3:
|
||||
def __init__(self, x, y, z):
|
||||
self.x = x
|
||||
self.y = y
|
||||
self.z = z
|
||||
|
||||
def __repr__(self):
|
||||
return f"V3({self.x}, {self.y}, {self.z})"
|
||||
|
||||
def dot2(self, x, y):
|
||||
return self.x * x + self.y * y
|
||||
|
||||
def dot3(self, x, y, z):
|
||||
return self.x * x + self.y * y + self.z * z
|
||||
|
||||
def to_js(self, scale=1.0):
|
||||
return new(THREE.Vector3, self.x * scale, self.y * scale, self.z * scale)
|
||||
|
||||
PERM = [0] * 512
|
||||
V3_P = [0] * 512 # assigned V3s in seed()
|
||||
P = [151, 160, 137, 91, 90, 15,
|
||||
131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69, 142, 8, 99, 37, 240, 21, 10, 23,
|
||||
190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, 35, 11, 32, 57, 177, 33,
|
||||
88, 237, 149, 56, 87, 174, 20, 125, 136, 171, 168, 68, 175, 74, 165, 71, 134, 139, 48, 27, 166,
|
||||
77, 146, 158, 231, 83, 111, 229, 122, 60, 211, 133, 230, 220, 105, 92, 41, 55, 46, 245, 40, 244,
|
||||
102, 143, 54, 65, 25, 63, 161, 1, 216, 80, 73, 209, 76, 132, 187, 208, 89, 18, 169, 200, 196,
|
||||
135, 130, 116, 188, 159, 86, 164, 100, 109, 198, 173, 186, 3, 64, 52, 217, 226, 250, 124, 123,
|
||||
5, 202, 38, 147, 118, 126, 255, 82, 85, 212, 207, 206, 59, 227, 47, 16, 58, 17, 182, 189, 28, 42,
|
||||
223, 183, 170, 213, 119, 248, 152, 2, 44, 154, 163, 70, 221, 153, 101, 155, 167, 43, 172, 9,
|
||||
129, 22, 39, 253, 19, 98, 108, 110, 79, 113, 224, 232, 178, 185, 112, 104, 218, 246, 97, 228,
|
||||
251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241, 81, 51, 145, 235, 249, 14, 239, 107,
|
||||
49, 192, 214, 31, 181, 199, 106, 157, 184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254,
|
||||
138, 236, 205, 93, 222, 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215, 61, 156, 180]
|
||||
V3_I = [V3(1, 1, 0), V3(-1, 1, 0), V3(1, -1, 0), V3(-1, -1, 0),
|
||||
V3(1, 0, 1), V3(-1, 0, 1), V3(1, 0, -1), V3(-1, 0, -1),
|
||||
V3(0, 1, 1), V3(0, -1, 1), V3(0, 1, -1), V3(0, -1, -1)]
|
||||
|
||||
def seed(s):
|
||||
if isinstance(s, float) and 0.0 < s < 1.0:
|
||||
s *= 65536
|
||||
|
||||
s = int(s)
|
||||
if s < 256:
|
||||
s |= s << 8
|
||||
|
||||
for i in range(256):
|
||||
if i & 1:
|
||||
v = P[i] ^ (s & 255)
|
||||
else:
|
||||
v = P[i] ^ ((s >> 8) & 255)
|
||||
|
||||
PERM[i] = PERM[i + 256] = v
|
||||
V3_P[i] = V3_P[i + 256] = V3_I[v % 12]
|
||||
|
||||
seed(0)
|
||||
|
||||
def fade(t):
|
||||
return t * t * t * (t * (t * 6 - 15) + 10)
|
||||
|
||||
def lerp(a, b, t):
|
||||
return (1 - t) * a + t * b
|
||||
|
||||
def perlin3(x, y, z):
|
||||
# grid cells
|
||||
x_c = int(x)
|
||||
y_c = int(y)
|
||||
z_c = int(z)
|
||||
# relative coords within the cell
|
||||
x -= x_c
|
||||
y -= y_c
|
||||
z -= z_c
|
||||
# wrap cells
|
||||
x_c &= 255
|
||||
y_c &= 255
|
||||
z_c &= 255
|
||||
# noise contributions to corners
|
||||
n000 = V3_P[x_c + PERM[y_c + PERM[z_c]]].dot3(x, y, z)
|
||||
n001 = V3_P[x_c + PERM[y_c + PERM[z_c + 1]]].dot3(x, y, z - 1)
|
||||
n010 = V3_P[x_c + PERM[y_c + 1 + PERM[z_c]]].dot3(x, y - 1, z)
|
||||
n011 = V3_P[x_c + PERM[y_c + 1 + PERM[z_c + 1]]].dot3(x, y - 1, z - 1)
|
||||
n100 = V3_P[x_c + 1 + PERM[y_c + PERM[z_c]]].dot3(x - 1, y, z)
|
||||
n101 = V3_P[x_c + 1 + PERM[y_c + PERM[z_c + 1]]].dot3(x - 1, y, z - 1)
|
||||
n110 = V3_P[x_c + 1 + PERM[y_c + 1 + PERM[z_c]]].dot3(x - 1, y - 1, z)
|
||||
n111 = V3_P[x_c + 1 + PERM[y_c + 1 + PERM[z_c + 1]]].dot3(x - 1, y - 1, z - 1)
|
||||
# fade curve
|
||||
u = fade(x)
|
||||
v = fade(y)
|
||||
w = fade(z)
|
||||
# interpolation
|
||||
return lerp(
|
||||
lerp(lerp(n000, n100, u), lerp(n001, n101, u), w),
|
||||
lerp(lerp(n010, n110, u), lerp(n011, n111, u), w),
|
||||
v,
|
||||
)
|
||||
|
||||
def curl2(x, y, z):
|
||||
# https://www.bit-101.com/2017/2021/07/curl-noise/
|
||||
delta = 0.01
|
||||
n1 = perlin3(x + delta, y, z)
|
||||
n2 = perlin3(x - delta, y, z)
|
||||
cy = -(n1 - n2) / (delta * 2)
|
||||
n1 = perlin3(x, y + delta, z)
|
||||
n2 = perlin3(x, y - delta, z)
|
||||
cx = -(n1 - n2) / (delta * 2)
|
||||
print(n1, n2)
|
||||
return V3(cx, cy, 0)
|
||||
16
core/tests/manual/issue-2302/pyscript.toml
Normal file
16
core/tests/manual/issue-2302/pyscript.toml
Normal file
@@ -0,0 +1,16 @@
|
||||
name = "Marching Squares with SPy Copy Copy"
|
||||
packages = [ "cffi", "./glue/perlin-0.0.0-cp312-cp312-pyodide_2024_0_wasm32.whl",]
|
||||
|
||||
[files]
|
||||
"./libthree.py" = ""
|
||||
"./libfft.py" = ""
|
||||
"./perlin_py.py" = ""
|
||||
"./worker.py" = ""
|
||||
"./glue/multipyjs.py" = "./multipyjs.py"
|
||||
|
||||
[js_modules.main]
|
||||
"https://cdn.jsdelivr.net/npm/three@v0.173.0/build/three.module.js" = "three"
|
||||
"https://cdn.jsdelivr.net/npm/three@v0.173.0/examples/jsm/lines/LineMaterial.js" = "linemat"
|
||||
"https://cdn.jsdelivr.net/npm/three@v0.173.0/examples/jsm/lines/Line2.js" = "line2"
|
||||
"https://cdn.jsdelivr.net/npm/three@v0.173.0/examples/jsm/lines/LineSegmentsGeometry.js" = "lsgeo"
|
||||
"https://cdn.jsdelivr.net/npm/stats-gl@3.6.0/dist/main.js" = "stats_gl"
|
||||
141
core/tests/manual/issue-2302/worker.py
Normal file
141
core/tests/manual/issue-2302/worker.py
Normal file
@@ -0,0 +1,141 @@
|
||||
from array import array
|
||||
|
||||
from pyscript import sync, window
|
||||
from perlin_py import perlin3, seed
|
||||
|
||||
grid_w = int(1920 * 4)
|
||||
grid_h = 1080 * 2
|
||||
grid_scale = 10
|
||||
noise_factor = 500
|
||||
grid_hs = int(grid_h/grid_scale)
|
||||
grid_ws = int(grid_w/grid_scale)
|
||||
crossfade_range = int(grid_ws/12.5)
|
||||
height_map = array("d", [0.0] * (grid_hs * grid_ws))
|
||||
edge_table = [
|
||||
(), # 0
|
||||
((3, 2),), # 1
|
||||
((2, 1),), # 2
|
||||
((3, 1),), # 3
|
||||
((0, 1),), # 4
|
||||
((0, 3), (1, 2)), # 5 (ambiguous)
|
||||
((0, 2),), # 6
|
||||
((0, 3),), # 7
|
||||
((0, 3),), # 8
|
||||
((0, 2),), # 9
|
||||
((0, 1), (2, 3)), # 10 (ambiguous)
|
||||
((0, 1),), # 11
|
||||
((3, 1),), # 12
|
||||
((2, 1),), # 13
|
||||
((3, 2),), # 14
|
||||
(), # 15
|
||||
]
|
||||
|
||||
def update_height_map(z):
|
||||
i = 0
|
||||
for y in range(0, grid_h, grid_scale):
|
||||
for x in range(0, grid_w, grid_scale):
|
||||
# 3 octaves of noise
|
||||
n = perlin3(x/noise_factor, y/noise_factor, z)
|
||||
n += 0.50 * perlin3(2*x/noise_factor, 2*y/noise_factor, z)
|
||||
n += 0.25 * perlin3(4*x/noise_factor, 4*y/noise_factor, z)
|
||||
height_map[i] = n
|
||||
i += 1
|
||||
|
||||
def crossfade_height_map():
|
||||
for y in range(grid_hs):
|
||||
for x in range(crossfade_range):
|
||||
pos_i = y*grid_ws + x
|
||||
neg_i = y*grid_ws + grid_ws - crossfade_range + x
|
||||
weight = x/crossfade_range
|
||||
old_pos = height_map[pos_i]
|
||||
old_neg = height_map[neg_i]
|
||||
height_map[neg_i] = height_map[pos_i] = weight * old_pos + (1.0 - weight) * old_neg
|
||||
|
||||
|
||||
def _crossfade_height_map():
|
||||
for y in range(grid_hs):
|
||||
for x in range(crossfade_range):
|
||||
pos_i = y*grid_ws + x
|
||||
neg_i = y*grid_ws + grid_ws - x - 1
|
||||
old_pos = height_map[pos_i]
|
||||
old_neg = height_map[neg_i]
|
||||
weight = 0.5 - x/crossfade_range/2
|
||||
height_map[pos_i] = (1.0 - weight) * old_pos + weight * old_neg
|
||||
height_map[neg_i] = (1.0 - weight) * old_neg + weight * old_pos
|
||||
|
||||
def interpolate(sq_threshold, v1, v2):
|
||||
if v1 == v2:
|
||||
return v1
|
||||
return (sq_threshold - v1) / (v2 - v1)
|
||||
|
||||
stats = {'maxx': 0, 'maxy': 0, 'minx': 0, 'miny': 0}
|
||||
def append_p(lines, p1, p2):
|
||||
lines.append(p1[0])
|
||||
lines.append(p1[1])
|
||||
lines.append(0)
|
||||
lines.append(p2[0])
|
||||
lines.append(p2[1])
|
||||
lines.append(0)
|
||||
stats['maxy'] = max(p1[1], p2[1], stats['maxy'])
|
||||
stats['miny'] = min(p1[1], p2[1], stats['miny'])
|
||||
stats['maxx'] = max(p1[0], p2[0], stats['maxx'])
|
||||
stats['minx'] = min(p1[0], p2[0], stats['minx'])
|
||||
|
||||
def marching_squares(height_map, sq_threshold):
|
||||
lines = array("d")
|
||||
|
||||
for y in range(grid_hs-1):
|
||||
for x in range(grid_ws-1): #cf
|
||||
tl = height_map[y*grid_ws + x]
|
||||
tr = height_map[y*grid_ws + x+1]
|
||||
bl = height_map[(y+1)*grid_ws + x]
|
||||
br = height_map[(y+1)*grid_ws + x+1]
|
||||
|
||||
sq_idx = 0
|
||||
if tl > sq_threshold:
|
||||
sq_idx |= 8
|
||||
if tr > sq_threshold:
|
||||
sq_idx |= 4
|
||||
if br > sq_threshold:
|
||||
sq_idx |= 2
|
||||
if bl > sq_threshold:
|
||||
sq_idx |= 1
|
||||
|
||||
edge_points = [
|
||||
(x + interpolate(sq_threshold, tl, tr), y),
|
||||
(x + 1, y + interpolate(sq_threshold, tr, br)),
|
||||
(x + interpolate(sq_threshold, bl, br), y + 1),
|
||||
(x, y + interpolate(sq_threshold, tl, bl)),
|
||||
]
|
||||
|
||||
for a, b in edge_table[sq_idx]:
|
||||
append_p(lines, edge_points[a], edge_points[b])
|
||||
|
||||
return lines
|
||||
|
||||
def grid_lines():
|
||||
lines = array("d")
|
||||
for x in range(0, grid_ws - crossfade_range, 26):
|
||||
append_p(lines, (x, 0), (x, grid_hs))
|
||||
for y in range(0, grid_hs, 24):
|
||||
append_p(lines, (0, y), (grid_ws-crossfade_range, y))
|
||||
return lines
|
||||
|
||||
seed(44)
|
||||
sync.scale_lines(grid_ws - crossfade_range, grid_hs)
|
||||
sync.print("Computing the height map")
|
||||
update_height_map(0)
|
||||
sync.print("Cross-fading the height map")
|
||||
crossfade_height_map()
|
||||
sync.draw_lines(grid_lines(), 'grid')
|
||||
sync.draw_lines(marching_squares(height_map, 0), 'zero')
|
||||
sync.draw_lines(marching_squares(height_map, 0.3), 'positive')
|
||||
sync.draw_lines(marching_squares(height_map, -0.3), 'negative')
|
||||
sync.draw_lines(marching_squares(height_map, 0.45), 'positive')
|
||||
sync.draw_lines(marching_squares(height_map, -0.45), 'negative')
|
||||
sync.draw_lines(marching_squares(height_map, 0.6), 'positive')
|
||||
sync.draw_lines(marching_squares(height_map, -0.6), 'negative')
|
||||
sync.draw_lines(marching_squares(height_map, -0.8), 'negative')
|
||||
sync.draw_lines(marching_squares(height_map, 0.8), 'positive')
|
||||
print(stats)
|
||||
sync.drawing_done()
|
||||
Reference in New Issue
Block a user