@1337exp/pkt-threejs-blueprint 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +20 -16
- package/README.md +70 -70
- package/README.ru.md +70 -70
- package/dist/three/renderer.d.ts +28 -5
- package/dist/three/renderer.js +16 -7
- package/dist/three/renderer.js.map +1 -1
- package/package.json +106 -106
package/CHANGELOG.md
CHANGED
|
@@ -1,16 +1,20 @@
|
|
|
1
|
-
# Changelog
|
|
2
|
-
|
|
3
|
-
## 0.1.
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
- **`
|
|
12
|
-
- **`
|
|
13
|
-
- **`
|
|
14
|
-
- **`
|
|
15
|
-
- **`
|
|
16
|
-
- **`
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.1
|
|
4
|
+
|
|
5
|
+
- **`renderer`**: `createRenderer(container, parameters, options)` takes `options.touchAction` (default `none`, as before). A scene with no gesture of its own, such as a table you only tap, passes `pan-y`, so a finger moving along the page still scrolls it. The canvas style is now set by the small `styleCanvas`, which is tested without WebGL. Found in hexa-proof, where a finger on the table could not scroll the page.
|
|
6
|
+
|
|
7
|
+
## 0.1.0
|
|
8
|
+
|
|
9
|
+
First release. Each module is its own import (`@1337exp/pkt-threejs-blueprint/<module>`), there is no root entry.
|
|
10
|
+
|
|
11
|
+
- **`ease`**: `Ease`, our curves (`easeOutBack` overshoots), `smoothstep(edge0, edge1, x)` in GLSL argument order.
|
|
12
|
+
- **`ticker`**: `Ticker`, game time with a `speed` (0 is a hit-stop), `tween` and `wait` as promises.
|
|
13
|
+
- **`spring`**: `spring(stiffness, zeta)`, `stepSpring`, `stepSpring3`. The exact solution of the spring equation: stable at any step, the same path at any frame rate.
|
|
14
|
+
- **`rng`**: `mulberry32`, `randomInt`. The sequence is frozen by a test, so saved seeds stay valid.
|
|
15
|
+
- **`tuning`**: `createTuning`, the core of a "tweak it live" panel (live values, saving only the differences from the defaults, reset, copy as code).
|
|
16
|
+
- **`loop`**: `startLoop(frame, { maxDt })`, the frame loop with a capped delta; `maxDt` is required.
|
|
17
|
+
- **`renderer`** (needs `three`): `createRenderer`, `bindResize`.
|
|
18
|
+
- **`pointer`** (needs `three`): `clientToNdc`, `rayOnPlane`.
|
|
19
|
+
- **`dispose`** (needs `three`): `disposeObject`.
|
|
20
|
+
- **`vue/tuning-panel`** (needs `vue`): `TuningPanel`, styled with `--tbp-*` CSS variables.
|
package/README.md
CHANGED
|
@@ -1,70 +1,70 @@
|
|
|
1
|
-
# @1337exp/pkt-threejs-blueprint
|
|
2
|
-
|
|
3
|
-
**English** · [Русский](README.ru.md)
|
|
4
|
-
|
|
5
|
-
The small shared layer our Three.js prototypes keep rewriting: a game clock with tweens, springs that do not blow up, seeded random, and the core of a "tweak it live" panel. Import only what you need; nothing is pulled in that you did not ask for.
|
|
6
|
-
|
|
7
|
-
## Stack
|
|
8
|
-
|
|
9
|
-
TypeScript 6, Vitest, plain `tsc` (no bundler: one file per module is the tree-shaking). The core has no dependencies and no DOM. `three` is an optional peer, used only by the modules that say so in the table; `vue` is an optional peer of the panel. It does not depend on TaoUI or TaoUX.
|
|
10
|
-
|
|
11
|
-
```bash
|
|
12
|
-
npm install @1337exp/pkt-threejs-blueprint # plus three and vue if you use the modules that need them
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
Work on the library itself:
|
|
16
|
-
|
|
17
|
-
```bash
|
|
18
|
-
npm install
|
|
19
|
-
npm run typecheck
|
|
20
|
-
npm test
|
|
21
|
-
npm run build
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
## What it can do
|
|
25
|
-
|
|
26
|
-
Each module is its own import (`@1337exp/pkt-threejs-blueprint/<module>`), there is no root entry.
|
|
27
|
-
|
|
28
|
-
| Module | What |
|
|
29
|
-
|---|---|
|
|
30
|
-
| `ease` | `Ease = (t) => number`, our curves (`easeOutBack` overshoots), `smoothstep(edge0, edge1, x)` in GLSL argument order |
|
|
31
|
-
| `ticker` | `Ticker`: game time with a `speed` (0 is a hit-stop), `tween` and `wait` as promises, `add`, `busy`, `clear` |
|
|
32
|
-
| `spring` | `spring(stiffness, zeta)`, `stepSpring` (a number) and `stepSpring3` (a vector): the exact solution, stable at any step, the same path at any frame rate |
|
|
33
|
-
| `rng` | `mulberry32(seed)`, `randomInt`; the sequence is frozen by a test, saved seeds stay valid |
|
|
34
|
-
| `tuning` | `createTuning(values, { groups, storageKey })`: live values, saving only the differences from the defaults, reset, "copy as code" (`toSource`), safe `localStorage` |
|
|
35
|
-
| `loop` | `startLoop(frame, { maxDt })`: the frame loop; `frame(dt, now)` gets a delta capped at `maxDt` (required: you choose it); `stop()` |
|
|
36
|
-
| `renderer` | needs `three`. `createRenderer(container)
|
|
37
|
-
| `pointer` | needs `three`. `clientToNdc(event, element)`, `rayOnPlane(ndc, camera, plane)`: a pointer to the point on a plane, with the camera matrix refreshed |
|
|
38
|
-
| `vue/tuning-panel` | needs `vue`. `TuningPanel`: draws a `tuning` (groups of sliders, what differs from the defaults, Copy as code, Reset). No `.vue` file, no UI library; looks are `--tbp-*` CSS variables with our dark-theme values as fallbacks |
|
|
39
|
-
| `dispose` | needs `three`. `disposeObject(root, { textures })`: geometries and materials (arrays too) freed once however many meshes share them; textures only when asked |
|
|
40
|
-
|
|
41
|
-
```ts
|
|
42
|
-
import { Ticker } from '@1337exp/pkt-threejs-blueprint/ticker';
|
|
43
|
-
import { easeOutBack } from '@1337exp/pkt-threejs-blueprint/ease';
|
|
44
|
-
|
|
45
|
-
const ticker = new Ticker();
|
|
46
|
-
// every frame: ticker.update(Math.min(realDt, maxDt));
|
|
47
|
-
await ticker.tween(0.4, (k) => (mesh.scale.x = 1 + k), easeOutBack);
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
### The frame delta
|
|
51
|
-
|
|
52
|
-
`dt` is the time between two frames in seconds, not a simulation rate: the frame rate stays the screen's (60, 120, 144 Hz). Cap it from above (`maxDt`) before feeding the ticker, so a hidden tab or a stall does not deliver a `dt` of seconds and send every tween to its end in one frame. Our projects use `1/30`, `0.05` or `0.1`; pick one deliberately.
|
|
53
|
-
|
|
54
|
-
Springs do not need a cap to stay alive: `stepSpring` and `stepSpring3` use the exact solution of the spring equation, so they are stable for any `dt` and give the same path at 30, 60 or 144 frames per second. The usual one-line Euler step (`v += (k·(target − x) − c·v)·dt; x += v·dt`) does neither: stiffness 2000 with `zeta` 1 blows up below ~54 frames per second, and even where it holds the path depends on the frame rate. Do not write that line by hand.
|
|
55
|
-
|
|
56
|
-
## How it is built
|
|
57
|
-
|
|
58
|
-
Three layers by dependency: `core/` (nothing), `three/` (needs `three`), `components/` (ready-made interface blocks over the core, styled with our dark-theme values copied as CSS variables, no TaoUI). Design, decisions and the analysis behind them: [docs/design.md](docs/design.md), [docs/analysis-grok.md](docs/analysis-grok.md).
|
|
59
|
-
|
|
60
|
-
## Not yet
|
|
61
|
-
|
|
62
|
-
A plain-DOM panel over `tuning` (the Vue one is done). Particles, ribbons, cameras, bloom and shaders stay in the games on purpose: they differ because the pictures differ.
|
|
63
|
-
|
|
64
|
-
## License
|
|
65
|
-
|
|
66
|
-
MIT, see [LICENSE](LICENSE).
|
|
67
|
-
|
|
68
|
-
## Us
|
|
69
|
-
|
|
70
|
-
Homepage: [666.io](https://666.io). If you want to help, suggest an idea or just talk, come by.
|
|
1
|
+
# @1337exp/pkt-threejs-blueprint
|
|
2
|
+
|
|
3
|
+
**English** · [Русский](README.ru.md)
|
|
4
|
+
|
|
5
|
+
The small shared layer our Three.js prototypes keep rewriting: a game clock with tweens, springs that do not blow up, seeded random, and the core of a "tweak it live" panel. Import only what you need; nothing is pulled in that you did not ask for.
|
|
6
|
+
|
|
7
|
+
## Stack
|
|
8
|
+
|
|
9
|
+
TypeScript 6, Vitest, plain `tsc` (no bundler: one file per module is the tree-shaking). The core has no dependencies and no DOM. `three` is an optional peer, used only by the modules that say so in the table; `vue` is an optional peer of the panel. It does not depend on TaoUI or TaoUX.
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install @1337exp/pkt-threejs-blueprint # plus three and vue if you use the modules that need them
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Work on the library itself:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install
|
|
19
|
+
npm run typecheck
|
|
20
|
+
npm test
|
|
21
|
+
npm run build
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## What it can do
|
|
25
|
+
|
|
26
|
+
Each module is its own import (`@1337exp/pkt-threejs-blueprint/<module>`), there is no root entry.
|
|
27
|
+
|
|
28
|
+
| Module | What |
|
|
29
|
+
|---|---|
|
|
30
|
+
| `ease` | `Ease = (t) => number`, our curves (`easeOutBack` overshoots), `smoothstep(edge0, edge1, x)` in GLSL argument order |
|
|
31
|
+
| `ticker` | `Ticker`: game time with a `speed` (0 is a hit-stop), `tween` and `wait` as promises, `add`, `busy`, `clear` |
|
|
32
|
+
| `spring` | `spring(stiffness, zeta)`, `stepSpring` (a number) and `stepSpring3` (a vector): the exact solution, stable at any step, the same path at any frame rate |
|
|
33
|
+
| `rng` | `mulberry32(seed)`, `randomInt`; the sequence is frozen by a test, saved seeds stay valid |
|
|
34
|
+
| `tuning` | `createTuning(values, { groups, storageKey })`: live values, saving only the differences from the defaults, reset, "copy as code" (`toSource`), safe `localStorage` |
|
|
35
|
+
| `loop` | `startLoop(frame, { maxDt })`: the frame loop; `frame(dt, now)` gets a delta capped at `maxDt` (required: you choose it); `stop()` |
|
|
36
|
+
| `renderer` | needs `three`. `createRenderer(container, parameters, { touchAction })` (the canvas is `touch-action: none` unless you pass `pan-y` and the like), `bindResize(renderer, container, onResize)`: size and a pixel ratio capped at 2, read again on every resize |
|
|
37
|
+
| `pointer` | needs `three`. `clientToNdc(event, element)`, `rayOnPlane(ndc, camera, plane)`: a pointer to the point on a plane, with the camera matrix refreshed |
|
|
38
|
+
| `vue/tuning-panel` | needs `vue`. `TuningPanel`: draws a `tuning` (groups of sliders, what differs from the defaults, Copy as code, Reset). No `.vue` file, no UI library; looks are `--tbp-*` CSS variables with our dark-theme values as fallbacks |
|
|
39
|
+
| `dispose` | needs `three`. `disposeObject(root, { textures })`: geometries and materials (arrays too) freed once however many meshes share them; textures only when asked |
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
import { Ticker } from '@1337exp/pkt-threejs-blueprint/ticker';
|
|
43
|
+
import { easeOutBack } from '@1337exp/pkt-threejs-blueprint/ease';
|
|
44
|
+
|
|
45
|
+
const ticker = new Ticker();
|
|
46
|
+
// every frame: ticker.update(Math.min(realDt, maxDt));
|
|
47
|
+
await ticker.tween(0.4, (k) => (mesh.scale.x = 1 + k), easeOutBack);
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### The frame delta
|
|
51
|
+
|
|
52
|
+
`dt` is the time between two frames in seconds, not a simulation rate: the frame rate stays the screen's (60, 120, 144 Hz). Cap it from above (`maxDt`) before feeding the ticker, so a hidden tab or a stall does not deliver a `dt` of seconds and send every tween to its end in one frame. Our projects use `1/30`, `0.05` or `0.1`; pick one deliberately.
|
|
53
|
+
|
|
54
|
+
Springs do not need a cap to stay alive: `stepSpring` and `stepSpring3` use the exact solution of the spring equation, so they are stable for any `dt` and give the same path at 30, 60 or 144 frames per second. The usual one-line Euler step (`v += (k·(target − x) − c·v)·dt; x += v·dt`) does neither: stiffness 2000 with `zeta` 1 blows up below ~54 frames per second, and even where it holds the path depends on the frame rate. Do not write that line by hand.
|
|
55
|
+
|
|
56
|
+
## How it is built
|
|
57
|
+
|
|
58
|
+
Three layers by dependency: `core/` (nothing), `three/` (needs `three`), `components/` (ready-made interface blocks over the core, styled with our dark-theme values copied as CSS variables, no TaoUI). Design, decisions and the analysis behind them: [docs/design.md](docs/design.md), [docs/analysis-grok.md](docs/analysis-grok.md).
|
|
59
|
+
|
|
60
|
+
## Not yet
|
|
61
|
+
|
|
62
|
+
A plain-DOM panel over `tuning` (the Vue one is done). Particles, ribbons, cameras, bloom and shaders stay in the games on purpose: they differ because the pictures differ.
|
|
63
|
+
|
|
64
|
+
## License
|
|
65
|
+
|
|
66
|
+
MIT, see [LICENSE](LICENSE).
|
|
67
|
+
|
|
68
|
+
## Us
|
|
69
|
+
|
|
70
|
+
Homepage: [666.io](https://666.io). If you want to help, suggest an idea or just talk, come by.
|
package/README.ru.md
CHANGED
|
@@ -1,70 +1,70 @@
|
|
|
1
|
-
# @1337exp/pkt-threejs-blueprint
|
|
2
|
-
|
|
3
|
-
[English](README.md) · **Русский**
|
|
4
|
-
|
|
5
|
-
Небольшой общий слой, который наши прототипы на Three.js постоянно пишут заново: игровое время с твинами, пружины, которые не разлетаются, случайные числа с зерном и ядро панели «покрутить вживую». Подключай только то, что нужно; лишнего не тянется.
|
|
6
|
-
|
|
7
|
-
## Стек
|
|
8
|
-
|
|
9
|
-
TypeScript 6, Vitest, обычный `tsc` (без бандлера: по файлу на модуль и есть tree-shaking). Ядро без зависимостей и без DOM. `three` необязательный peer, он нужен только модулям, у которых это сказано в таблице; `vue` необязательный peer панели. От TaoUI и TaoUX не зависит.
|
|
10
|
-
|
|
11
|
-
```bash
|
|
12
|
-
npm install @1337exp/pkt-threejs-blueprint # плюс three и vue, если берёшь модули, которым они нужны
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
Работа над самой библиотекой:
|
|
16
|
-
|
|
17
|
-
```bash
|
|
18
|
-
npm install
|
|
19
|
-
npm run typecheck
|
|
20
|
-
npm test
|
|
21
|
-
npm run build
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
## Что умеет
|
|
25
|
-
|
|
26
|
-
Каждый модуль подключается отдельно (`@1337exp/pkt-threejs-blueprint/<модуль>`), общего входа нет.
|
|
27
|
-
|
|
28
|
-
| Модуль | Что |
|
|
29
|
-
|---|---|
|
|
30
|
-
| `ease` | `Ease = (t) => number`, наши кривые (`easeOutBack` с перелётом), `smoothstep(edge0, edge1, x)` в порядке аргументов GLSL |
|
|
31
|
-
| `ticker` | `Ticker`: игровое время со `speed` (0 — hit-stop), `tween` и `wait` как обещания, `add`, `busy`, `clear` |
|
|
32
|
-
| `spring` | `spring(stiffness, zeta)`, `stepSpring` (число) и `stepSpring3` (вектор): точное решение, устойчиво при любом шаге, один и тот же путь на любой частоте кадров |
|
|
33
|
-
| `rng` | `mulberry32(seed)`, `randomInt`; последовательность заморожена тестом, сохранённые зёрна остаются верными |
|
|
34
|
-
| `tuning` | `createTuning(values, { groups, storageKey })`: живые значения, сохранение только отличий от умолчаний, сброс, «скопировать как код» (`toSource`), безопасный `localStorage` |
|
|
35
|
-
| `loop` | `startLoop(frame, { maxDt })`: цикл кадров; `frame(dt, now)` получает дельту не больше `maxDt` (параметр обязателен: выбираешь ты); `stop()` |
|
|
36
|
-
| `renderer` | нужен `three`. `createRenderer(container)`, `bindResize(renderer, container, onResize)`: размер и плотность пикселей с потолком 2, читаются заново при каждом изменении размера |
|
|
37
|
-
| `pointer` | нужен `three`. `clientToNdc(event, element)`, `rayOnPlane(ndc, camera, plane)`: от указателя до точки на плоскости, с обновлением матрицы камеры |
|
|
38
|
-
| `vue/tuning-panel` | нужен `vue`. `TuningPanel`: рисует `tuning` (группы ползунков, что отличается от умолчаний, «скопировать как код», сброс). Без `.vue`-файла и без UI-библиотеки; вид задают CSS-переменные `--tbp-*`, запасные значения это наша тёмная тема |
|
|
39
|
-
| `dispose` | нужен `three`. `disposeObject(root, { textures })`: геометрии и материалы (и массивы) освобождаются один раз, сколько бы мешей их ни делило; текстуры только по просьбе |
|
|
40
|
-
|
|
41
|
-
```ts
|
|
42
|
-
import { Ticker } from '@1337exp/pkt-threejs-blueprint/ticker';
|
|
43
|
-
import { easeOutBack } from '@1337exp/pkt-threejs-blueprint/ease';
|
|
44
|
-
|
|
45
|
-
const ticker = new Ticker();
|
|
46
|
-
// каждый кадр: ticker.update(Math.min(realDt, maxDt));
|
|
47
|
-
await ticker.tween(0.4, (k) => (mesh.scale.x = 1 + k), easeOutBack);
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
### Дельта кадра
|
|
51
|
-
|
|
52
|
-
`dt` — время между двумя кадрами в секундах, а не частота симуляции: частота кадров остаётся экранной (60, 120, 144 Гц). Ограничь её сверху (`maxDt`) перед подачей в тикер, чтобы после фона вкладки или подвисания не прилетел `dt` в секунды и все твины не ушли в конец за один кадр. В наших проектах это `1/30`, `0.05` или `0.1`; выбирай осознанно.
|
|
53
|
-
|
|
54
|
-
Пружине потолок, чтобы выжить, не нужен: `stepSpring` и `stepSpring3` берут точное решение уравнения пружины, поэтому устойчивы при любом `dt` и дают один и тот же путь на 30, 60 и 144 кадрах в секунду. Привычный однострочный шаг Эйлера (`v += (k·(цель − x) − c·v)·dt; x += v·dt`) не даёт ни того ни другого: жёсткость 2000 при `zeta` 1 разлетается ниже ~54 кадров в секунду, а и там, где держится, путь зависит от частоты кадров. Не пиши эту строку руками.
|
|
55
|
-
|
|
56
|
-
## Как устроено
|
|
57
|
-
|
|
58
|
-
Три слоя по зависимостям: `core/` (ничего), `three/` (нужен `three`), `components/` (готовые блоки интерфейса поверх ядра; стили это значения нашей тёмной темы, скопированные как CSS-переменные, без TaoUI). Дизайн, решения и анализ, на котором они стоят: [docs/design.md](docs/design.md), [docs/analysis-grok.md](docs/analysis-grok.md).
|
|
59
|
-
|
|
60
|
-
## Пока нет
|
|
61
|
-
|
|
62
|
-
Панель на чистом DOM поверх `tuning` (на Vue готова). Частицы, ленты, камеры, bloom и шейдеры остаются в играх намеренно: они разные, потому что разные картинки.
|
|
63
|
-
|
|
64
|
-
## Лицензия
|
|
65
|
-
|
|
66
|
-
MIT, см. [LICENSE](LICENSE).
|
|
67
|
-
|
|
68
|
-
## Мы
|
|
69
|
-
|
|
70
|
-
Домашняя страница: [666.io](https://666.io). Хочешь помочь, предложить идею или просто поговорить — заходи.
|
|
1
|
+
# @1337exp/pkt-threejs-blueprint
|
|
2
|
+
|
|
3
|
+
[English](README.md) · **Русский**
|
|
4
|
+
|
|
5
|
+
Небольшой общий слой, который наши прототипы на Three.js постоянно пишут заново: игровое время с твинами, пружины, которые не разлетаются, случайные числа с зерном и ядро панели «покрутить вживую». Подключай только то, что нужно; лишнего не тянется.
|
|
6
|
+
|
|
7
|
+
## Стек
|
|
8
|
+
|
|
9
|
+
TypeScript 6, Vitest, обычный `tsc` (без бандлера: по файлу на модуль и есть tree-shaking). Ядро без зависимостей и без DOM. `three` необязательный peer, он нужен только модулям, у которых это сказано в таблице; `vue` необязательный peer панели. От TaoUI и TaoUX не зависит.
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install @1337exp/pkt-threejs-blueprint # плюс three и vue, если берёшь модули, которым они нужны
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Работа над самой библиотекой:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install
|
|
19
|
+
npm run typecheck
|
|
20
|
+
npm test
|
|
21
|
+
npm run build
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Что умеет
|
|
25
|
+
|
|
26
|
+
Каждый модуль подключается отдельно (`@1337exp/pkt-threejs-blueprint/<модуль>`), общего входа нет.
|
|
27
|
+
|
|
28
|
+
| Модуль | Что |
|
|
29
|
+
|---|---|
|
|
30
|
+
| `ease` | `Ease = (t) => number`, наши кривые (`easeOutBack` с перелётом), `smoothstep(edge0, edge1, x)` в порядке аргументов GLSL |
|
|
31
|
+
| `ticker` | `Ticker`: игровое время со `speed` (0 — hit-stop), `tween` и `wait` как обещания, `add`, `busy`, `clear` |
|
|
32
|
+
| `spring` | `spring(stiffness, zeta)`, `stepSpring` (число) и `stepSpring3` (вектор): точное решение, устойчиво при любом шаге, один и тот же путь на любой частоте кадров |
|
|
33
|
+
| `rng` | `mulberry32(seed)`, `randomInt`; последовательность заморожена тестом, сохранённые зёрна остаются верными |
|
|
34
|
+
| `tuning` | `createTuning(values, { groups, storageKey })`: живые значения, сохранение только отличий от умолчаний, сброс, «скопировать как код» (`toSource`), безопасный `localStorage` |
|
|
35
|
+
| `loop` | `startLoop(frame, { maxDt })`: цикл кадров; `frame(dt, now)` получает дельту не больше `maxDt` (параметр обязателен: выбираешь ты); `stop()` |
|
|
36
|
+
| `renderer` | нужен `three`. `createRenderer(container, parameters, { touchAction })` (холст с `touch-action: none`, пока не передашь `pan-y` и подобное), `bindResize(renderer, container, onResize)`: размер и плотность пикселей с потолком 2, читаются заново при каждом изменении размера |
|
|
37
|
+
| `pointer` | нужен `three`. `clientToNdc(event, element)`, `rayOnPlane(ndc, camera, plane)`: от указателя до точки на плоскости, с обновлением матрицы камеры |
|
|
38
|
+
| `vue/tuning-panel` | нужен `vue`. `TuningPanel`: рисует `tuning` (группы ползунков, что отличается от умолчаний, «скопировать как код», сброс). Без `.vue`-файла и без UI-библиотеки; вид задают CSS-переменные `--tbp-*`, запасные значения это наша тёмная тема |
|
|
39
|
+
| `dispose` | нужен `three`. `disposeObject(root, { textures })`: геометрии и материалы (и массивы) освобождаются один раз, сколько бы мешей их ни делило; текстуры только по просьбе |
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
import { Ticker } from '@1337exp/pkt-threejs-blueprint/ticker';
|
|
43
|
+
import { easeOutBack } from '@1337exp/pkt-threejs-blueprint/ease';
|
|
44
|
+
|
|
45
|
+
const ticker = new Ticker();
|
|
46
|
+
// каждый кадр: ticker.update(Math.min(realDt, maxDt));
|
|
47
|
+
await ticker.tween(0.4, (k) => (mesh.scale.x = 1 + k), easeOutBack);
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Дельта кадра
|
|
51
|
+
|
|
52
|
+
`dt` — время между двумя кадрами в секундах, а не частота симуляции: частота кадров остаётся экранной (60, 120, 144 Гц). Ограничь её сверху (`maxDt`) перед подачей в тикер, чтобы после фона вкладки или подвисания не прилетел `dt` в секунды и все твины не ушли в конец за один кадр. В наших проектах это `1/30`, `0.05` или `0.1`; выбирай осознанно.
|
|
53
|
+
|
|
54
|
+
Пружине потолок, чтобы выжить, не нужен: `stepSpring` и `stepSpring3` берут точное решение уравнения пружины, поэтому устойчивы при любом `dt` и дают один и тот же путь на 30, 60 и 144 кадрах в секунду. Привычный однострочный шаг Эйлера (`v += (k·(цель − x) − c·v)·dt; x += v·dt`) не даёт ни того ни другого: жёсткость 2000 при `zeta` 1 разлетается ниже ~54 кадров в секунду, а и там, где держится, путь зависит от частоты кадров. Не пиши эту строку руками.
|
|
55
|
+
|
|
56
|
+
## Как устроено
|
|
57
|
+
|
|
58
|
+
Три слоя по зависимостям: `core/` (ничего), `three/` (нужен `three`), `components/` (готовые блоки интерфейса поверх ядра; стили это значения нашей тёмной темы, скопированные как CSS-переменные, без TaoUI). Дизайн, решения и анализ, на котором они стоят: [docs/design.md](docs/design.md), [docs/analysis-grok.md](docs/analysis-grok.md).
|
|
59
|
+
|
|
60
|
+
## Пока нет
|
|
61
|
+
|
|
62
|
+
Панель на чистом DOM поверх `tuning` (на Vue готова). Частицы, ленты, камеры, bloom и шейдеры остаются в играх намеренно: они разные, потому что разные картинки.
|
|
63
|
+
|
|
64
|
+
## Лицензия
|
|
65
|
+
|
|
66
|
+
MIT, см. [LICENSE](LICENSE).
|
|
67
|
+
|
|
68
|
+
## Мы
|
|
69
|
+
|
|
70
|
+
Домашняя страница: [666.io](https://666.io). Хочешь помочь, предложить идею или просто поговорить — заходи.
|
package/dist/three/renderer.d.ts
CHANGED
|
@@ -1,13 +1,36 @@
|
|
|
1
1
|
import { WebGLRenderer, type WebGLRendererParameters } from 'three';
|
|
2
|
+
export interface CanvasOptions {
|
|
3
|
+
/**
|
|
4
|
+
* The CSS `touch-action` of the canvas. The default is `none`: a finger drag belongs to the game. A scene that has no gesture of its own
|
|
5
|
+
* (a table you only tap, a model you only look at) should pass `pan-y`, so that a finger moving along the page still scrolls it.
|
|
6
|
+
* CSS `touch-action` холста. По умолчанию `none`: перетаскивание пальцем принадлежит игре. Сцене без своих жестов (стол, по которому только
|
|
7
|
+
* тапают, модель, на которую только смотрят) стоит передать `pan-y`, чтобы палец, ведущий вдоль страницы, всё же её листал.
|
|
8
|
+
*/
|
|
9
|
+
touchAction?: string;
|
|
10
|
+
}
|
|
11
|
+
/** The part of a canvas that {@link styleCanvas} touches. / Часть холста, которой касается {@link styleCanvas}. */
|
|
12
|
+
export interface StylableCanvas {
|
|
13
|
+
style: {
|
|
14
|
+
display: string;
|
|
15
|
+
touchAction: string;
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* The look of the canvas that {@link createRenderer} asks for: a block (no stray line gap under it) and the chosen `touch-action`.
|
|
20
|
+
* It is a function of its own so that it can be tested without WebGL.
|
|
21
|
+
* Вид холста, которого просит {@link createRenderer}: блочный (без щели под ним) и выбранный `touch-action`. Отдельная функция, чтобы
|
|
22
|
+
* её можно было проверить без WebGL.
|
|
23
|
+
*/
|
|
24
|
+
export declare function styleCanvas(canvas: StylableCanvas, options?: CanvasOptions): void;
|
|
2
25
|
/**
|
|
3
26
|
* A renderer on a canvas inside `container`: antialiased by default, the canvas is a block (no stray line gap under it)
|
|
4
|
-
* and `touch-action: none` (a finger drag is for the game, not for scrolling the page
|
|
5
|
-
* by {@link bindResize}; without it the canvas keeps three's defaults.
|
|
27
|
+
* and `touch-action: none` (a finger drag is for the game, not for scrolling the page; change it with `options.touchAction`).
|
|
28
|
+
* The size and the pixel ratio are set by {@link bindResize}; without it the canvas keeps three's defaults.
|
|
6
29
|
* Рендерер на холсте внутри `container`: со сглаживанием по умолчанию, холст блочный (без щели под ним) и с `touch-action: none`
|
|
7
|
-
* (перетаскивание пальцем для игры, а не для прокрутки
|
|
8
|
-
* без него холст остаётся с умолчаниями three.
|
|
30
|
+
* (перетаскивание пальцем для игры, а не для прокрутки страницы; меняется через `options.touchAction`). Размер и плотность
|
|
31
|
+
* пикселей ставит {@link bindResize}; без него холст остаётся с умолчаниями three.
|
|
9
32
|
*/
|
|
10
|
-
export declare function createRenderer(container: HTMLElement, parameters?: WebGLRendererParameters): WebGLRenderer;
|
|
33
|
+
export declare function createRenderer(container: HTMLElement, parameters?: WebGLRendererParameters, options?: CanvasOptions): WebGLRenderer;
|
|
11
34
|
/** The part of a renderer that {@link bindResize} touches. / Часть рендерера, которой касается {@link bindResize}. */
|
|
12
35
|
export interface ResizableRenderer {
|
|
13
36
|
setPixelRatio(ratio: number): void;
|
package/dist/three/renderer.js
CHANGED
|
@@ -1,16 +1,25 @@
|
|
|
1
1
|
import { WebGLRenderer } from 'three';
|
|
2
|
+
/**
|
|
3
|
+
* The look of the canvas that {@link createRenderer} asks for: a block (no stray line gap under it) and the chosen `touch-action`.
|
|
4
|
+
* It is a function of its own so that it can be tested without WebGL.
|
|
5
|
+
* Вид холста, которого просит {@link createRenderer}: блочный (без щели под ним) и выбранный `touch-action`. Отдельная функция, чтобы
|
|
6
|
+
* её можно было проверить без WebGL.
|
|
7
|
+
*/
|
|
8
|
+
export function styleCanvas(canvas, options = {}) {
|
|
9
|
+
canvas.style.display = 'block';
|
|
10
|
+
canvas.style.touchAction = options.touchAction ?? 'none';
|
|
11
|
+
}
|
|
2
12
|
/**
|
|
3
13
|
* A renderer on a canvas inside `container`: antialiased by default, the canvas is a block (no stray line gap under it)
|
|
4
|
-
* and `touch-action: none` (a finger drag is for the game, not for scrolling the page
|
|
5
|
-
* by {@link bindResize}; without it the canvas keeps three's defaults.
|
|
14
|
+
* and `touch-action: none` (a finger drag is for the game, not for scrolling the page; change it with `options.touchAction`).
|
|
15
|
+
* The size and the pixel ratio are set by {@link bindResize}; without it the canvas keeps three's defaults.
|
|
6
16
|
* Рендерер на холсте внутри `container`: со сглаживанием по умолчанию, холст блочный (без щели под ним) и с `touch-action: none`
|
|
7
|
-
* (перетаскивание пальцем для игры, а не для прокрутки
|
|
8
|
-
* без него холст остаётся с умолчаниями three.
|
|
17
|
+
* (перетаскивание пальцем для игры, а не для прокрутки страницы; меняется через `options.touchAction`). Размер и плотность
|
|
18
|
+
* пикселей ставит {@link bindResize}; без него холст остаётся с умолчаниями three.
|
|
9
19
|
*/
|
|
10
|
-
export function createRenderer(container, parameters = {}) {
|
|
20
|
+
export function createRenderer(container, parameters = {}, options = {}) {
|
|
11
21
|
const renderer = new WebGLRenderer({ antialias: true, ...parameters });
|
|
12
|
-
renderer.domElement
|
|
13
|
-
renderer.domElement.style.touchAction = 'none';
|
|
22
|
+
styleCanvas(renderer.domElement, options);
|
|
14
23
|
container.appendChild(renderer.domElement);
|
|
15
24
|
return renderer;
|
|
16
25
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"renderer.js","sourceRoot":"","sources":["../../src/three/renderer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAgC,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"renderer.js","sourceRoot":"","sources":["../../src/three/renderer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAgC,MAAM,OAAO,CAAC;AAiBpE;;;;;GAKG;AACH,MAAM,UAAU,WAAW,CAAC,MAAsB,EAAE,UAAyB,EAAE;IAC3E,MAAM,CAAC,KAAK,CAAC,OAAO,GAAG,OAAO,CAAC;IAC/B,MAAM,CAAC,KAAK,CAAC,WAAW,GAAG,OAAO,CAAC,WAAW,IAAI,MAAM,CAAC;AAC7D,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,cAAc,CAAC,SAAsB,EAAE,aAAsC,EAAE,EAAE,UAAyB,EAAE;IACxH,MAAM,QAAQ,GAAG,IAAI,aAAa,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,GAAG,UAAU,EAAE,CAAC,CAAC;IACvE,WAAW,CAAC,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;IAC1C,SAAS,CAAC,WAAW,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;IAC3C,OAAO,QAAQ,CAAC;AACpB,CAAC;AAqBD;;;;;;;GAOG;AACH,MAAM,UAAU,UAAU,CACtB,QAA2B,EAC3B,SAAsB,EACtB,QAAkD,EAClD,UAAyB,EAAE;IAE3B,MAAM,EAAE,aAAa,GAAG,CAAC,EAAE,WAAW,GAAG,IAAI,EAAE,GAAG,OAAO,CAAC;IAC1D,MAAM,KAAK,GAAG,GAAS,EAAE;QACrB,MAAM,KAAK,GAAG,SAAS,CAAC,WAAW,CAAC;QACpC,MAAM,MAAM,GAAG,SAAS,CAAC,YAAY,CAAC;QACtC,IAAI,CAAC,KAAK,IAAI,CAAC,MAAM;YAAE,OAAO;QAC9B,QAAQ,CAAC,aAAa,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,gBAAgB,IAAI,CAAC,EAAE,aAAa,CAAC,CAAC,CAAC;QAC9E,QAAQ,CAAC,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,WAAW,CAAC,CAAC;QAC7C,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;IAC9B,CAAC,CAAC;IACF,MAAM,QAAQ,GAAG,IAAI,cAAc,CAAC,KAAK,CAAC,CAAC;IAC3C,QAAQ,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAC5B,KAAK,EAAE,CAAC;IACR,OAAO,GAAG,EAAE,CAAC,QAAQ,CAAC,UAAU,EAAE,CAAC;AACvC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,106 +1,106 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@1337exp/pkt-threejs-blueprint",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "The small shared layer our Three.js prototypes keep rewriting: game clock and tweens, springs, seeded random, live-tuning core. Import only what you need.",
|
|
5
|
-
"keywords": [
|
|
6
|
-
"threejs",
|
|
7
|
-
"three",
|
|
8
|
-
"tween",
|
|
9
|
-
"spring",
|
|
10
|
-
"prng",
|
|
11
|
-
"typescript"
|
|
12
|
-
],
|
|
13
|
-
"type": "module",
|
|
14
|
-
"license": "MIT",
|
|
15
|
-
"homepage": "https://github.com/1337exp/pkt-threejs-blueprint#readme",
|
|
16
|
-
"repository": {
|
|
17
|
-
"type": "git",
|
|
18
|
-
"url": "git+https://github.com/1337exp/pkt-threejs-blueprint.git"
|
|
19
|
-
},
|
|
20
|
-
"bugs": {
|
|
21
|
-
"url": "https://github.com/1337exp/pkt-threejs-blueprint/issues"
|
|
22
|
-
},
|
|
23
|
-
"sideEffects": false,
|
|
24
|
-
"files": [
|
|
25
|
-
"dist",
|
|
26
|
-
"CHANGELOG.md"
|
|
27
|
-
],
|
|
28
|
-
"exports": {
|
|
29
|
-
"./ease": {
|
|
30
|
-
"types": "./dist/core/ease.d.ts",
|
|
31
|
-
"import": "./dist/core/ease.js"
|
|
32
|
-
},
|
|
33
|
-
"./ticker": {
|
|
34
|
-
"types": "./dist/core/ticker.d.ts",
|
|
35
|
-
"import": "./dist/core/ticker.js"
|
|
36
|
-
},
|
|
37
|
-
"./spring": {
|
|
38
|
-
"types": "./dist/core/spring.d.ts",
|
|
39
|
-
"import": "./dist/core/spring.js"
|
|
40
|
-
},
|
|
41
|
-
"./rng": {
|
|
42
|
-
"types": "./dist/core/rng.d.ts",
|
|
43
|
-
"import": "./dist/core/rng.js"
|
|
44
|
-
},
|
|
45
|
-
"./tuning": {
|
|
46
|
-
"types": "./dist/core/tuning.d.ts",
|
|
47
|
-
"import": "./dist/core/tuning.js"
|
|
48
|
-
},
|
|
49
|
-
"./loop": {
|
|
50
|
-
"types": "./dist/core/loop.d.ts",
|
|
51
|
-
"import": "./dist/core/loop.js"
|
|
52
|
-
},
|
|
53
|
-
"./renderer": {
|
|
54
|
-
"types": "./dist/three/renderer.d.ts",
|
|
55
|
-
"import": "./dist/three/renderer.js"
|
|
56
|
-
},
|
|
57
|
-
"./pointer": {
|
|
58
|
-
"types": "./dist/three/pointer.d.ts",
|
|
59
|
-
"import": "./dist/three/pointer.js"
|
|
60
|
-
},
|
|
61
|
-
"./dispose": {
|
|
62
|
-
"types": "./dist/three/dispose.d.ts",
|
|
63
|
-
"import": "./dist/three/dispose.js"
|
|
64
|
-
},
|
|
65
|
-
"./vue/tuning-panel": {
|
|
66
|
-
"types": "./dist/components/vue/tuning-panel.d.ts",
|
|
67
|
-
"import": "./dist/components/vue/tuning-panel.js"
|
|
68
|
-
},
|
|
69
|
-
"./package.json": "./package.json"
|
|
70
|
-
},
|
|
71
|
-
"scripts": {
|
|
72
|
-
"typecheck": "tsc --noEmit -p tsconfig.core.json && tsc --noEmit",
|
|
73
|
-
"test": "vitest run",
|
|
74
|
-
"test:watch": "vitest",
|
|
75
|
-
"build": "tsc -p tsconfig.build.json",
|
|
76
|
-
"prepublishOnly": "npm run typecheck && npm test && npm run build"
|
|
77
|
-
},
|
|
78
|
-
"peerDependencies": {
|
|
79
|
-
"three": ">=0.186.1 <0.188.0",
|
|
80
|
-
"vue": "^3.5.0"
|
|
81
|
-
},
|
|
82
|
-
"peerDependenciesMeta": {
|
|
83
|
-
"three": {
|
|
84
|
-
"optional": true
|
|
85
|
-
},
|
|
86
|
-
"vue": {
|
|
87
|
-
"optional": true
|
|
88
|
-
}
|
|
89
|
-
},
|
|
90
|
-
"devDependencies": {
|
|
91
|
-
"@types/node": "^26.6.4",
|
|
92
|
-
"@types/three": "^0.187.0",
|
|
93
|
-
"happy-dom": "^21.0.4",
|
|
94
|
-
"three": "^0.187.0",
|
|
95
|
-
"typescript": "~6.0.3",
|
|
96
|
-
"vitest": "^5.0.3",
|
|
97
|
-
"vue": "^3.5.45"
|
|
98
|
-
},
|
|
99
|
-
"author": "1337exp",
|
|
100
|
-
"engines": {
|
|
101
|
-
"node": ">=18"
|
|
102
|
-
},
|
|
103
|
-
"publishConfig": {
|
|
104
|
-
"access": "public"
|
|
105
|
-
}
|
|
106
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@1337exp/pkt-threejs-blueprint",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "The small shared layer our Three.js prototypes keep rewriting: game clock and tweens, springs, seeded random, live-tuning core. Import only what you need.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"threejs",
|
|
7
|
+
"three",
|
|
8
|
+
"tween",
|
|
9
|
+
"spring",
|
|
10
|
+
"prng",
|
|
11
|
+
"typescript"
|
|
12
|
+
],
|
|
13
|
+
"type": "module",
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"homepage": "https://github.com/1337exp/pkt-threejs-blueprint#readme",
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "git+https://github.com/1337exp/pkt-threejs-blueprint.git"
|
|
19
|
+
},
|
|
20
|
+
"bugs": {
|
|
21
|
+
"url": "https://github.com/1337exp/pkt-threejs-blueprint/issues"
|
|
22
|
+
},
|
|
23
|
+
"sideEffects": false,
|
|
24
|
+
"files": [
|
|
25
|
+
"dist",
|
|
26
|
+
"CHANGELOG.md"
|
|
27
|
+
],
|
|
28
|
+
"exports": {
|
|
29
|
+
"./ease": {
|
|
30
|
+
"types": "./dist/core/ease.d.ts",
|
|
31
|
+
"import": "./dist/core/ease.js"
|
|
32
|
+
},
|
|
33
|
+
"./ticker": {
|
|
34
|
+
"types": "./dist/core/ticker.d.ts",
|
|
35
|
+
"import": "./dist/core/ticker.js"
|
|
36
|
+
},
|
|
37
|
+
"./spring": {
|
|
38
|
+
"types": "./dist/core/spring.d.ts",
|
|
39
|
+
"import": "./dist/core/spring.js"
|
|
40
|
+
},
|
|
41
|
+
"./rng": {
|
|
42
|
+
"types": "./dist/core/rng.d.ts",
|
|
43
|
+
"import": "./dist/core/rng.js"
|
|
44
|
+
},
|
|
45
|
+
"./tuning": {
|
|
46
|
+
"types": "./dist/core/tuning.d.ts",
|
|
47
|
+
"import": "./dist/core/tuning.js"
|
|
48
|
+
},
|
|
49
|
+
"./loop": {
|
|
50
|
+
"types": "./dist/core/loop.d.ts",
|
|
51
|
+
"import": "./dist/core/loop.js"
|
|
52
|
+
},
|
|
53
|
+
"./renderer": {
|
|
54
|
+
"types": "./dist/three/renderer.d.ts",
|
|
55
|
+
"import": "./dist/three/renderer.js"
|
|
56
|
+
},
|
|
57
|
+
"./pointer": {
|
|
58
|
+
"types": "./dist/three/pointer.d.ts",
|
|
59
|
+
"import": "./dist/three/pointer.js"
|
|
60
|
+
},
|
|
61
|
+
"./dispose": {
|
|
62
|
+
"types": "./dist/three/dispose.d.ts",
|
|
63
|
+
"import": "./dist/three/dispose.js"
|
|
64
|
+
},
|
|
65
|
+
"./vue/tuning-panel": {
|
|
66
|
+
"types": "./dist/components/vue/tuning-panel.d.ts",
|
|
67
|
+
"import": "./dist/components/vue/tuning-panel.js"
|
|
68
|
+
},
|
|
69
|
+
"./package.json": "./package.json"
|
|
70
|
+
},
|
|
71
|
+
"scripts": {
|
|
72
|
+
"typecheck": "tsc --noEmit -p tsconfig.core.json && tsc --noEmit",
|
|
73
|
+
"test": "vitest run",
|
|
74
|
+
"test:watch": "vitest",
|
|
75
|
+
"build": "tsc -p tsconfig.build.json",
|
|
76
|
+
"prepublishOnly": "npm run typecheck && npm test && npm run build"
|
|
77
|
+
},
|
|
78
|
+
"peerDependencies": {
|
|
79
|
+
"three": ">=0.186.1 <0.188.0",
|
|
80
|
+
"vue": "^3.5.0"
|
|
81
|
+
},
|
|
82
|
+
"peerDependenciesMeta": {
|
|
83
|
+
"three": {
|
|
84
|
+
"optional": true
|
|
85
|
+
},
|
|
86
|
+
"vue": {
|
|
87
|
+
"optional": true
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
"devDependencies": {
|
|
91
|
+
"@types/node": "^26.6.4",
|
|
92
|
+
"@types/three": "^0.187.0",
|
|
93
|
+
"happy-dom": "^21.0.4",
|
|
94
|
+
"three": "^0.187.0",
|
|
95
|
+
"typescript": "~6.0.3",
|
|
96
|
+
"vitest": "^5.0.3",
|
|
97
|
+
"vue": "^3.5.45"
|
|
98
|
+
},
|
|
99
|
+
"author": "1337exp",
|
|
100
|
+
"engines": {
|
|
101
|
+
"node": ">=18"
|
|
102
|
+
},
|
|
103
|
+
"publishConfig": {
|
|
104
|
+
"access": "public"
|
|
105
|
+
}
|
|
106
|
+
}
|