snowcrystal 0.1.0
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 +28 -0
- package/LICENSE +21 -0
- package/README.md +116 -0
- package/dist/index.d.ts +43 -0
- package/dist/index.js +456 -0
- package/dist/index.js.map +1 -0
- package/package.json +59 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to **snowcrystal** are documented here.
|
|
4
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
5
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
|
+
|
|
7
|
+
## [0.1.0] — Unreleased (initial public release)
|
|
8
|
+
|
|
9
|
+
First public release.
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
- `createSnowCrystal(params)` returning a `THREE.Group` for nine snow-crystal
|
|
13
|
+
morphologies (角板 / 角柱 / 針 / さや / 骸晶角柱 / 厚角板 / 骸晶角板 / 扇形 / 樹枝状),
|
|
14
|
+
ported from the snownotes.org 3D snow-crystal viewer.
|
|
15
|
+
- Morphology selection by `temperature` / `supersaturation` (Nakaya diagram) via
|
|
16
|
+
`getCrystalType`, or directly by `morphology`.
|
|
17
|
+
- `disposeCrystal(group)` to release GPU resources.
|
|
18
|
+
- `getCrystalType` and `getGlobalLabel` classification helpers, plus
|
|
19
|
+
`Morphology` / `CrystalParams` types.
|
|
20
|
+
- Deterministic geometry: `Math.random` replaced with a seeded PRNG (mulberry32);
|
|
21
|
+
same params produce the same shape.
|
|
22
|
+
- ESM-only, tree-shakeable (`"sideEffects": false`); `three` is a peer dependency.
|
|
23
|
+
|
|
24
|
+
### Notes
|
|
25
|
+
- This is a **morphological visualization, not a physical growth model**.
|
|
26
|
+
See the README for sources (雪氷 88巻3号 / SEPPYO Vol.88 No.3).
|
|
27
|
+
|
|
28
|
+
[0.1.0]: https://github.com/Shotaro-Kamimura/snowcrystal/releases/tag/v0.1.0
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Shotaro Kamimura (SnowNotes)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# snowcrystal
|
|
2
|
+
|
|
3
|
+
Three.js geometry for snow-crystal **morphology**, ported from the
|
|
4
|
+
[snownotes.org 3D snow-crystal viewer](https://snownotes.org/).
|
|
5
|
+
|
|
6
|
+
`createSnowCrystal(params)` returns a `THREE.Group` you can drop into any scene.
|
|
7
|
+
Nine morphologies are supported, selectable directly or derived from a
|
|
8
|
+
temperature / supersaturation pair (Nakaya diagram).
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## ⚠️ これは形態の可視化です(物理モデルではありません)
|
|
13
|
+
|
|
14
|
+
本パッケージは、雪結晶の**成長を支配する物理法則や微視的な成長メカニズムを数理的に
|
|
15
|
+
再現するものではありません**。中谷ダイアグラム(気温・過飽和度と形態の対応)と、雪結晶の
|
|
16
|
+
**グローバル分類(121種)**という既存の観測図・文献を参照し、結晶の六角対称性・階層構造・
|
|
17
|
+
形態的特徴といった**外形を三次元的に可視化する試み**です。側面から見た内部構造や厚みは
|
|
18
|
+
物理的に正確なモデルではありません。
|
|
19
|
+
|
|
20
|
+
## ⚠️ This is a morphological visualization, not a physical model
|
|
21
|
+
|
|
22
|
+
This package does **not** numerically reproduce the physical laws or microscopic
|
|
23
|
+
growth mechanisms governing snow-crystal formation. It is a 3D visualization of
|
|
24
|
+
crystal **morphology** based on existing observational diagrams and literature —
|
|
25
|
+
the Nakaya diagram and the Global Classification of snow crystals (121 types).
|
|
26
|
+
It focuses on external features such as hexagonal symmetry, hierarchical
|
|
27
|
+
structure, and characteristic crystal forms, and is not a physically accurate
|
|
28
|
+
representation of internal structure or thickness.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## Install
|
|
33
|
+
|
|
34
|
+
```sh
|
|
35
|
+
npm install snowcrystal three
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
`three` is a **peer dependency** — you provide it yourself (`>=0.150.0`).
|
|
39
|
+
The package ships ESM only and is tree-shakeable (`"sideEffects": false`).
|
|
40
|
+
|
|
41
|
+
## Usage
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
import * as THREE from 'three';
|
|
45
|
+
import { createSnowCrystal, disposeCrystal } from 'snowcrystal';
|
|
46
|
+
|
|
47
|
+
const scene = new THREE.Scene();
|
|
48
|
+
|
|
49
|
+
// 1) Pick a morphology directly (9 types):
|
|
50
|
+
const crystal = createSnowCrystal({ morphology: '樹枝状' });
|
|
51
|
+
scene.add(crystal);
|
|
52
|
+
|
|
53
|
+
// 2) Or derive it from temperature (°C, negative below freezing)
|
|
54
|
+
// and supersaturation (vapor, 0–0.3), like the Nakaya diagram:
|
|
55
|
+
const fromClimate = createSnowCrystal({ temperature: -15, supersaturation: 0.25 });
|
|
56
|
+
scene.add(fromClimate);
|
|
57
|
+
|
|
58
|
+
// 3) Deterministic: the same params always produce the same shape.
|
|
59
|
+
// `seed` controls the (otherwise fixed) pseudo-randomness used by some forms.
|
|
60
|
+
const seeded = createSnowCrystal({ morphology: '針', seed: 42 });
|
|
61
|
+
|
|
62
|
+
// Clean up GPU resources when you remove a crystal:
|
|
63
|
+
scene.remove(crystal);
|
|
64
|
+
disposeCrystal(crystal);
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### API
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
type Morphology =
|
|
71
|
+
| '角板' | '角柱' | '針' | 'さや' | '骸晶角柱'
|
|
72
|
+
| '厚角板' | '骸晶角板' | '扇形' | '樹枝状';
|
|
73
|
+
|
|
74
|
+
interface CrystalParams {
|
|
75
|
+
morphology?: Morphology; // primary key (9 types)
|
|
76
|
+
temperature?: number; // °C; with supersaturation, mapped via getCrystalType
|
|
77
|
+
supersaturation?: number; // vapor, 0–0.3
|
|
78
|
+
seed?: number; // fixes pseudo-randomness (deterministic by default)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function createSnowCrystal(params?: CrystalParams): THREE.Group;
|
|
82
|
+
function disposeCrystal(group: THREE.Group): void;
|
|
83
|
+
function getCrystalType(temp: number, vapor: number): Morphology;
|
|
84
|
+
function getGlobalLabel(morphology: Morphology): string; // e.g. '樹枝状' -> 'P4g'
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
If `morphology` is omitted but `temperature` and `supersaturation` are given,
|
|
88
|
+
the morphology is derived via `getCrystalType`. If nothing is given, it defaults
|
|
89
|
+
to `'樹枝状'`.
|
|
90
|
+
|
|
91
|
+
## Playground
|
|
92
|
+
|
|
93
|
+
```sh
|
|
94
|
+
npm run dev
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
A minimal Three.js viewer with a morphology selector and `OrbitControls`.
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## Sources / 出典
|
|
102
|
+
|
|
103
|
+
- 雪氷 88巻3号 ISSN 0373-1006 p215-217 —
|
|
104
|
+
<https://snownotes.org/elsewhere/snow-crystal-3d-seppyo/>
|
|
105
|
+
- SEPPYO Vol.88, No.3 ISSN 0373-1006 p215-217 —
|
|
106
|
+
<https://snownotes.org/en/elsewhere/snow-crystal-3d-seppyo/>
|
|
107
|
+
- グローバル分類(121種)/ Global Classification —
|
|
108
|
+
<https://snownotes.org/snow-crystals/>
|
|
109
|
+
- 菊地勝弘・亀田貴雄・樋口敬二・山下晃ほか(2012): 中緯度と極域での観測に基づいた
|
|
110
|
+
新しい雪結晶の分類 ― グローバル分類 I. *雪氷*, **74**(3), 223–241.
|
|
111
|
+
- Kobayashi, T. (1960) Experimental researches on the snow crystal habit and
|
|
112
|
+
growth using a convection-mixing chamber. *J. Meteorol. Soc. Japan*, **38**, 231–238.
|
|
113
|
+
|
|
114
|
+
## License
|
|
115
|
+
|
|
116
|
+
MIT © 2026 Shotaro Kamimura (SnowNotes)
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import * as THREE_2 from 'three';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Build a snow-crystal as a `THREE.Group`. Pure: touches no scene and no DOM.
|
|
5
|
+
*
|
|
6
|
+
* Selection order:
|
|
7
|
+
* 1. `params.morphology` if given.
|
|
8
|
+
* 2. otherwise `getCrystalType(temperature, supersaturation)` if both are given.
|
|
9
|
+
* 3. otherwise the default morphology ('樹枝状').
|
|
10
|
+
*/
|
|
11
|
+
export declare function createSnowCrystal(params?: CrystalParams): THREE_2.Group;
|
|
12
|
+
|
|
13
|
+
export declare interface CrystalParams {
|
|
14
|
+
/** Primary generation key (9 types). */
|
|
15
|
+
morphology?: Morphology;
|
|
16
|
+
/** Air temperature in °C (negative below freezing). With `supersaturation`, mapped to a morphology via `getCrystalType`. */
|
|
17
|
+
temperature?: number;
|
|
18
|
+
/** Supersaturation / vapor amount (0–0.3), the y-axis of the Nakaya diagram. */
|
|
19
|
+
supersaturation?: number;
|
|
20
|
+
/** Seed for the pseudo-random details (e.g. needle lengths). Same params + seed => same shape. */
|
|
21
|
+
seed?: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Dispose all geometries and materials under a crystal group so its GPU
|
|
26
|
+
* resources are released. Call after removing the group from the scene.
|
|
27
|
+
*/
|
|
28
|
+
export declare function disposeCrystal(group: THREE_2.Group): void;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Map a Nakaya-diagram point (temperature °C, vapor 0–0.3) to a morphology.
|
|
32
|
+
* Temperatures are expected as negative values (e.g. -10 means -10°C).
|
|
33
|
+
* Ported verbatim from the snownotes viewer.
|
|
34
|
+
*/
|
|
35
|
+
export declare function getCrystalType(temp: number, vapor: number): Morphology;
|
|
36
|
+
|
|
37
|
+
/** Morphology name -> global-classification label shown in the UI. */
|
|
38
|
+
export declare function getGlobalLabel(typeName: Morphology): string;
|
|
39
|
+
|
|
40
|
+
/** The nine supported snow-crystal morphologies (primary generation key). */
|
|
41
|
+
export declare type Morphology = '角板' | '角柱' | '針' | 'さや' | '骸晶角柱' | '厚角板' | '骸晶角板' | '扇形' | '樹枝状';
|
|
42
|
+
|
|
43
|
+
export { }
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,456 @@
|
|
|
1
|
+
import * as e from "three";
|
|
2
|
+
const d = {
|
|
3
|
+
base: 13421772,
|
|
4
|
+
// 白寄りのグレー
|
|
5
|
+
highlight: 16777215,
|
|
6
|
+
// ピュアホワイト
|
|
7
|
+
edge: 8947848,
|
|
8
|
+
// やや暗いグレー
|
|
9
|
+
wing: 11184810
|
|
10
|
+
// 羽や針も白系で統一
|
|
11
|
+
};
|
|
12
|
+
function O(r, s) {
|
|
13
|
+
if (r <= 0 && r > -4) {
|
|
14
|
+
if (s >= 0.1 && s <= 0.3) return "角板";
|
|
15
|
+
} else {
|
|
16
|
+
if (r <= -4 && r > -10)
|
|
17
|
+
return s <= 0.05 ? "角柱" : s <= 0.1 ? "骸晶角柱" : s <= 0.15 ? "さや" : "針";
|
|
18
|
+
if (r <= -10 && r > -22)
|
|
19
|
+
return s <= 0.05 ? "厚角板" : s <= 0.15 ? "骸晶角板" : s <= 0.2 ? "扇形" : "樹枝状";
|
|
20
|
+
if (r <= -22 && r >= -40)
|
|
21
|
+
return s <= 0.05 ? "角柱" : s <= 0.1 ? "骸晶角柱" : "さや";
|
|
22
|
+
}
|
|
23
|
+
return "角板";
|
|
24
|
+
}
|
|
25
|
+
function V(r) {
|
|
26
|
+
switch (r) {
|
|
27
|
+
case "針":
|
|
28
|
+
return "C1a";
|
|
29
|
+
case "さや":
|
|
30
|
+
return "C2a";
|
|
31
|
+
case "角柱":
|
|
32
|
+
return "C3a";
|
|
33
|
+
case "骸晶角柱":
|
|
34
|
+
return "C3b";
|
|
35
|
+
case "角板":
|
|
36
|
+
return "P1a";
|
|
37
|
+
case "厚角板":
|
|
38
|
+
return "P1b";
|
|
39
|
+
case "骸晶角板":
|
|
40
|
+
return "P1c";
|
|
41
|
+
case "扇形":
|
|
42
|
+
return "P4f";
|
|
43
|
+
case "樹枝状":
|
|
44
|
+
return "P4g";
|
|
45
|
+
default:
|
|
46
|
+
return "";
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function j(r) {
|
|
50
|
+
let s = r >>> 0;
|
|
51
|
+
return function() {
|
|
52
|
+
s |= 0, s = s + 1831565813 | 0;
|
|
53
|
+
let t = Math.imul(s ^ s >>> 15, 1 | s);
|
|
54
|
+
return t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t, ((t ^ t >>> 14) >>> 0) / 4294967296;
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
function F() {
|
|
58
|
+
const r = new e.BufferGeometry(), s = 1.5, t = 0.8, n = 0.1, a = new Float32Array([
|
|
59
|
+
-t / 2,
|
|
60
|
+
0,
|
|
61
|
+
n / 2,
|
|
62
|
+
t / 2,
|
|
63
|
+
0,
|
|
64
|
+
n / 2,
|
|
65
|
+
0,
|
|
66
|
+
s,
|
|
67
|
+
n / 2,
|
|
68
|
+
-t / 2,
|
|
69
|
+
0,
|
|
70
|
+
n / 2,
|
|
71
|
+
t / 2,
|
|
72
|
+
0,
|
|
73
|
+
n / 2,
|
|
74
|
+
0,
|
|
75
|
+
-0.4,
|
|
76
|
+
n / 2,
|
|
77
|
+
-t / 2,
|
|
78
|
+
0,
|
|
79
|
+
-n / 2,
|
|
80
|
+
t / 2,
|
|
81
|
+
0,
|
|
82
|
+
-n / 2,
|
|
83
|
+
0,
|
|
84
|
+
s,
|
|
85
|
+
-n / 2,
|
|
86
|
+
-t / 2,
|
|
87
|
+
0,
|
|
88
|
+
-n / 2,
|
|
89
|
+
t / 2,
|
|
90
|
+
0,
|
|
91
|
+
-n / 2,
|
|
92
|
+
0,
|
|
93
|
+
-0.4,
|
|
94
|
+
-n / 2
|
|
95
|
+
]), i = [
|
|
96
|
+
0,
|
|
97
|
+
1,
|
|
98
|
+
2,
|
|
99
|
+
8,
|
|
100
|
+
7,
|
|
101
|
+
6,
|
|
102
|
+
0,
|
|
103
|
+
2,
|
|
104
|
+
8,
|
|
105
|
+
8,
|
|
106
|
+
6,
|
|
107
|
+
0,
|
|
108
|
+
2,
|
|
109
|
+
1,
|
|
110
|
+
7,
|
|
111
|
+
7,
|
|
112
|
+
8,
|
|
113
|
+
2,
|
|
114
|
+
0,
|
|
115
|
+
6,
|
|
116
|
+
7,
|
|
117
|
+
7,
|
|
118
|
+
1,
|
|
119
|
+
0,
|
|
120
|
+
3,
|
|
121
|
+
5,
|
|
122
|
+
4,
|
|
123
|
+
11,
|
|
124
|
+
9,
|
|
125
|
+
10,
|
|
126
|
+
3,
|
|
127
|
+
9,
|
|
128
|
+
11,
|
|
129
|
+
11,
|
|
130
|
+
5,
|
|
131
|
+
3,
|
|
132
|
+
5,
|
|
133
|
+
11,
|
|
134
|
+
10,
|
|
135
|
+
10,
|
|
136
|
+
4,
|
|
137
|
+
5,
|
|
138
|
+
3,
|
|
139
|
+
4,
|
|
140
|
+
10,
|
|
141
|
+
10,
|
|
142
|
+
9,
|
|
143
|
+
3
|
|
144
|
+
];
|
|
145
|
+
r.setAttribute("position", new e.BufferAttribute(a, 3)), r.setIndex(i), r.computeVertexNormals();
|
|
146
|
+
const o = new e.MeshStandardMaterial({ color: d.wing, flatShading: !0 });
|
|
147
|
+
return new e.Mesh(r, o);
|
|
148
|
+
}
|
|
149
|
+
function A() {
|
|
150
|
+
const r = new e.BufferGeometry(), s = 0.4, t = 0.3, n = 0.05, a = new Float32Array([
|
|
151
|
+
-t / 2,
|
|
152
|
+
0,
|
|
153
|
+
n / 2,
|
|
154
|
+
t / 2,
|
|
155
|
+
0,
|
|
156
|
+
n / 2,
|
|
157
|
+
0,
|
|
158
|
+
s,
|
|
159
|
+
n / 2,
|
|
160
|
+
-t / 2,
|
|
161
|
+
0,
|
|
162
|
+
n / 2,
|
|
163
|
+
t / 2,
|
|
164
|
+
0,
|
|
165
|
+
n / 2,
|
|
166
|
+
0,
|
|
167
|
+
-0.2,
|
|
168
|
+
n / 2,
|
|
169
|
+
-t / 2,
|
|
170
|
+
0,
|
|
171
|
+
-n / 2,
|
|
172
|
+
t / 2,
|
|
173
|
+
0,
|
|
174
|
+
-n / 2,
|
|
175
|
+
0,
|
|
176
|
+
s,
|
|
177
|
+
-n / 2,
|
|
178
|
+
-t / 2,
|
|
179
|
+
0,
|
|
180
|
+
-n / 2,
|
|
181
|
+
t / 2,
|
|
182
|
+
0,
|
|
183
|
+
-n / 2,
|
|
184
|
+
0,
|
|
185
|
+
-0.2,
|
|
186
|
+
-n / 2
|
|
187
|
+
]), i = [
|
|
188
|
+
0,
|
|
189
|
+
1,
|
|
190
|
+
2,
|
|
191
|
+
8,
|
|
192
|
+
7,
|
|
193
|
+
6,
|
|
194
|
+
0,
|
|
195
|
+
2,
|
|
196
|
+
8,
|
|
197
|
+
8,
|
|
198
|
+
6,
|
|
199
|
+
0,
|
|
200
|
+
2,
|
|
201
|
+
1,
|
|
202
|
+
7,
|
|
203
|
+
7,
|
|
204
|
+
8,
|
|
205
|
+
2,
|
|
206
|
+
0,
|
|
207
|
+
6,
|
|
208
|
+
7,
|
|
209
|
+
7,
|
|
210
|
+
1,
|
|
211
|
+
0,
|
|
212
|
+
3,
|
|
213
|
+
5,
|
|
214
|
+
4,
|
|
215
|
+
11,
|
|
216
|
+
9,
|
|
217
|
+
10,
|
|
218
|
+
3,
|
|
219
|
+
9,
|
|
220
|
+
11,
|
|
221
|
+
11,
|
|
222
|
+
5,
|
|
223
|
+
3,
|
|
224
|
+
5,
|
|
225
|
+
11,
|
|
226
|
+
10,
|
|
227
|
+
10,
|
|
228
|
+
4,
|
|
229
|
+
5,
|
|
230
|
+
3,
|
|
231
|
+
4,
|
|
232
|
+
10,
|
|
233
|
+
10,
|
|
234
|
+
9,
|
|
235
|
+
3
|
|
236
|
+
];
|
|
237
|
+
r.setAttribute("position", new e.BufferAttribute(a, 3)), r.setIndex(i), r.computeVertexNormals();
|
|
238
|
+
const o = new e.MeshStandardMaterial({ color: d.wing, flatShading: !0 });
|
|
239
|
+
return new e.Mesh(r, o);
|
|
240
|
+
}
|
|
241
|
+
function k(r) {
|
|
242
|
+
const s = new e.Group(), t = new e.Mesh(
|
|
243
|
+
new e.BoxGeometry(0.08, 0.08, 2),
|
|
244
|
+
new e.MeshStandardMaterial({ color: d.wing })
|
|
245
|
+
);
|
|
246
|
+
t.position.z = 1, s.add(t);
|
|
247
|
+
const n = 3, a = 0.5, i = 0.3;
|
|
248
|
+
for (let o = 0; o < n; o++) {
|
|
249
|
+
const h = a * (o + 1.5), c = A(), g = A();
|
|
250
|
+
c.rotation.x = Math.PI / 2, c.rotation.z = Math.PI / 4 + Math.PI, c.position.set(-i, 0, h), g.rotation.x = Math.PI / 2, g.rotation.z = -Math.PI / 4 + Math.PI, g.position.set(i, 0, h), s.add(c, g);
|
|
251
|
+
}
|
|
252
|
+
return s.rotation.y = -r, s;
|
|
253
|
+
}
|
|
254
|
+
function Y(r, s) {
|
|
255
|
+
switch (r) {
|
|
256
|
+
case "角柱": {
|
|
257
|
+
const t = new e.CylinderGeometry(0.4, 0.4, 1.5, 6), n = new e.MeshStandardMaterial({ color: d.base, flatShading: !0 }), a = new e.Mesh(t, n), i = new e.LineBasicMaterial({ color: d.edge }), o = new e.EdgesGeometry(t), h = new e.LineSegments(o, i), c = new e.Group();
|
|
258
|
+
return c.add(a), c.add(h), c;
|
|
259
|
+
}
|
|
260
|
+
case "角板": {
|
|
261
|
+
const t = new e.Group(), n = 4, a = 0.6, i = 0.3;
|
|
262
|
+
for (let u = 0; u < n; u++) {
|
|
263
|
+
const y = 1 - u * 0.15, G = a * y, S = i * (1 - u * 0.1), m = new e.Color(d.base), b = new e.MeshStandardMaterial({
|
|
264
|
+
color: m,
|
|
265
|
+
flatShading: !0,
|
|
266
|
+
transparent: !0,
|
|
267
|
+
opacity: 0.3
|
|
268
|
+
}), L = new e.MeshStandardMaterial({ color: m, flatShading: !0 }), B = new e.CylinderGeometry(G, G, S, 6, 1, !1), C = new e.Mesh(B, [b, L, L]);
|
|
269
|
+
t.add(C);
|
|
270
|
+
const p = new e.LineBasicMaterial({ color: d.edge }), x = new e.EdgesGeometry(B), M = new e.LineSegments(x, p);
|
|
271
|
+
t.add(M);
|
|
272
|
+
}
|
|
273
|
+
const o = a * 1.2, h = i * 0.8, c = new e.CylinderGeometry(o, o, h, 6), g = new e.MeshStandardMaterial({ color: d.wing }), f = new e.Mesh(c, g);
|
|
274
|
+
f.position.y = 0, t.add(f);
|
|
275
|
+
const w = new e.EdgesGeometry(c), l = new e.LineSegments(
|
|
276
|
+
w,
|
|
277
|
+
new e.LineBasicMaterial({ color: d.edge })
|
|
278
|
+
);
|
|
279
|
+
return t.add(l), t;
|
|
280
|
+
}
|
|
281
|
+
case "扇形": {
|
|
282
|
+
const t = new e.Group(), n = new e.CylinderGeometry(0.5, 0.5, 0.2, 6), a = new e.MeshStandardMaterial({ color: d.base }), i = new e.Mesh(n, a);
|
|
283
|
+
t.add(i);
|
|
284
|
+
const o = 6, h = 1.1, c = -Math.PI / 2;
|
|
285
|
+
for (let g = 0; g < o; g++) {
|
|
286
|
+
const f = g * Math.PI / 3, w = F();
|
|
287
|
+
w.rotation.z = Math.PI / 2, w.rotation.x = c;
|
|
288
|
+
const l = new e.Group();
|
|
289
|
+
l.add(w), l.rotation.y = -f;
|
|
290
|
+
const u = h * Math.cos(f), y = h * Math.sin(f);
|
|
291
|
+
l.position.set(u, 0, y), t.add(l);
|
|
292
|
+
}
|
|
293
|
+
return t;
|
|
294
|
+
}
|
|
295
|
+
case "樹枝状": {
|
|
296
|
+
const t = new e.Group(), n = new e.CylinderGeometry(0.5, 0.5, 0.2, 6), a = new e.MeshStandardMaterial({ color: d.base }), i = new e.Mesh(n, a);
|
|
297
|
+
t.add(i);
|
|
298
|
+
for (let o = 0; o < 6; o++) {
|
|
299
|
+
const h = o * Math.PI / 3, c = k(h);
|
|
300
|
+
c.position.set(0, 0, 0), t.add(c);
|
|
301
|
+
}
|
|
302
|
+
return t;
|
|
303
|
+
}
|
|
304
|
+
case "針": {
|
|
305
|
+
const t = new e.Group(), n = 0.4, a = 2, i = 6;
|
|
306
|
+
let o = 0;
|
|
307
|
+
const h = 0.15, c = 0.55;
|
|
308
|
+
let g = 0;
|
|
309
|
+
for (let l = 0; l < i; l++) {
|
|
310
|
+
const u = 1 - l * 0.12, y = n * u, G = a * (0.8 - l * 0.08), S = new e.Color(d.base), m = new e.MeshStandardMaterial({
|
|
311
|
+
color: S,
|
|
312
|
+
flatShading: !0,
|
|
313
|
+
transparent: !0,
|
|
314
|
+
opacity: l === 0 ? 1 : 0.3
|
|
315
|
+
}), b = new e.CylinderGeometry(y, y, G, 6, 1, l !== i - 1), L = new e.Mesh(b, m);
|
|
316
|
+
t.add(L);
|
|
317
|
+
const B = new e.EdgesGeometry(b), C = new e.LineSegments(
|
|
318
|
+
B,
|
|
319
|
+
new e.LineBasicMaterial({ color: d.edge })
|
|
320
|
+
);
|
|
321
|
+
if (t.add(C), l === 0) {
|
|
322
|
+
o = y, g = G + 1e-3;
|
|
323
|
+
const p = new e.Group();
|
|
324
|
+
for (let x = 0; x < 6; x++) {
|
|
325
|
+
const M = Math.PI / 3 * x, P = o * Math.cos(M), H = o * Math.sin(M), I = new e.BoxGeometry(c, g, h), R = new e.MeshStandardMaterial({ color: d.base }), E = new e.Mesh(I, R);
|
|
326
|
+
E.position.set(P, 0, H), E.rotation.y = -M + Math.PI / 2, p.add(E);
|
|
327
|
+
}
|
|
328
|
+
t.add(p);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
const f = new e.MeshStandardMaterial({ color: d.wing }), w = h / Math.sqrt(3);
|
|
332
|
+
for (let l = 0; l < 6; l++) {
|
|
333
|
+
const u = l * Math.PI / 3 + Math.PI / 6, y = o + h, G = y * Math.cos(u) - w * Math.cos(u), S = y * Math.sin(u) - w * Math.sin(u), m = 1.5 + s() * 0.5, b = 1.5 + s() * 0.5, L = a / 4.5 + g / 4.5, B = -a / 4.5 - g / 4.5, C = new e.CylinderGeometry(w, w, m, 6), p = new e.Mesh(C, f);
|
|
334
|
+
p.position.set(G, L + m / 2, S), p.rotation.y = 0, t.add(p);
|
|
335
|
+
const x = new e.CylinderGeometry(w, w, b, 6), M = new e.Mesh(x, f);
|
|
336
|
+
M.position.set(G, B - b / 2, S), M.rotation.y = 0, t.add(M);
|
|
337
|
+
}
|
|
338
|
+
return t;
|
|
339
|
+
}
|
|
340
|
+
case "厚角板": {
|
|
341
|
+
const t = new e.CylinderGeometry(0.6, 0.6, 0.4, 6), n = new e.MeshStandardMaterial({
|
|
342
|
+
color: d.base,
|
|
343
|
+
flatShading: !0
|
|
344
|
+
}), a = new e.Mesh(t, n), i = new e.EdgesGeometry(t), o = new e.LineBasicMaterial({ color: d.edge }), h = new e.LineSegments(i, o), c = new e.Group();
|
|
345
|
+
return c.add(a), c.add(h), c;
|
|
346
|
+
}
|
|
347
|
+
case "骸晶角板": {
|
|
348
|
+
const t = new e.Group(), n = 4, a = 0.6, i = 0.3;
|
|
349
|
+
for (let o = 0; o < n; o++) {
|
|
350
|
+
const h = 1 - o * 0.15, c = a * h, g = i * (1 - o * 0.1), f = o === n - 1 ? d.wing : d.base, w = new e.MeshStandardMaterial({
|
|
351
|
+
color: f,
|
|
352
|
+
flatShading: !0,
|
|
353
|
+
transparent: o !== n - 1,
|
|
354
|
+
opacity: o !== n - 1 ? 0 : 1
|
|
355
|
+
// 外側層は透明、中心だけ不透明
|
|
356
|
+
}), l = new e.MeshStandardMaterial({
|
|
357
|
+
color: f,
|
|
358
|
+
flatShading: !0,
|
|
359
|
+
transparent: !1
|
|
360
|
+
}), u = new e.CylinderGeometry(c, c, g, 6, 1, !1), y = new e.Mesh(u, [w, l, l]);
|
|
361
|
+
t.add(y);
|
|
362
|
+
const G = new e.LineBasicMaterial({ color: d.edge }), S = new e.EdgesGeometry(u), m = new e.LineSegments(S, G);
|
|
363
|
+
t.add(m);
|
|
364
|
+
}
|
|
365
|
+
return t;
|
|
366
|
+
}
|
|
367
|
+
case "骸晶角柱": {
|
|
368
|
+
const t = new e.Group(), n = 0.4, a = 1.5, i = new e.CylinderGeometry(n, n, a, 6), o = new e.MeshStandardMaterial({ color: d.base, flatShading: !0 }), h = new e.Mesh(i, o);
|
|
369
|
+
t.add(h);
|
|
370
|
+
const c = new e.EdgesGeometry(i);
|
|
371
|
+
t.add(new e.LineSegments(c, new e.LineBasicMaterial({ color: d.edge })));
|
|
372
|
+
const g = new e.Group(), f = 0.55, w = 0.15, l = a + 0.2;
|
|
373
|
+
for (let M = 0; M < 6; M++) {
|
|
374
|
+
const P = Math.PI / 3 * M, H = n * Math.cos(P), I = n * Math.sin(P), R = new e.BoxGeometry(f, l, w), E = new e.MeshStandardMaterial({ color: d.base }), D = new e.Mesh(R, E);
|
|
375
|
+
D.position.set(H, 0, I), D.rotation.y = -P + Math.PI / 2, g.add(D);
|
|
376
|
+
}
|
|
377
|
+
t.add(g);
|
|
378
|
+
const u = n * 0.7, y = 0.2, G = 0.15, S = new e.MeshStandardMaterial({
|
|
379
|
+
color: d.highlight,
|
|
380
|
+
// ハイライトカラーで視認性アップ
|
|
381
|
+
flatShading: !0
|
|
382
|
+
}), m = new e.Mesh(
|
|
383
|
+
new e.CylinderGeometry(u, u, y, 6),
|
|
384
|
+
S
|
|
385
|
+
);
|
|
386
|
+
m.position.y = a / 2 - y / 2 - G, t.add(m);
|
|
387
|
+
const b = m.clone();
|
|
388
|
+
b.position.y = -a / 2 + y / 2 + G, t.add(b);
|
|
389
|
+
const L = new e.Group(), B = 0.55, C = 0.4, p = 0.05, x = 0.02;
|
|
390
|
+
for (let M = 0; M < 6; M++) {
|
|
391
|
+
const P = Math.PI / 3 * M, H = (n - p / 2 - x) * Math.cos(P), I = (n - p / 2 - x) * Math.sin(P), R = new e.BoxGeometry(p, C, B), E = new e.Mesh(R, S);
|
|
392
|
+
E.position.set(H, 0, I), E.rotation.y = -P;
|
|
393
|
+
const D = new e.EdgesGeometry(R), T = new e.LineBasicMaterial({ color: 8965375 }), z = new e.LineSegments(D, T);
|
|
394
|
+
z.rotation.y = -P, z.position.set(H, 0, I), L.add(E), L.add(z);
|
|
395
|
+
}
|
|
396
|
+
return t.add(L), t;
|
|
397
|
+
}
|
|
398
|
+
case "さや": {
|
|
399
|
+
const t = new e.Group(), n = 6, a = 0.4, i = 2;
|
|
400
|
+
for (let o = 0; o < n; o++) {
|
|
401
|
+
const h = 1 - o * 0.12, c = a * h, g = i * (0.8 - o * 0.08), f = new e.Color(d.edge), w = new e.MeshStandardMaterial({
|
|
402
|
+
color: f,
|
|
403
|
+
flatShading: !0,
|
|
404
|
+
transparent: !0,
|
|
405
|
+
opacity: o === 0 ? 1 : 0.3
|
|
406
|
+
}), l = new e.CylinderGeometry(c, c, g, 6, 1, o !== n - 1), u = new e.Mesh(l, w);
|
|
407
|
+
t.add(u);
|
|
408
|
+
const y = new e.EdgesGeometry(l), G = new e.LineSegments(
|
|
409
|
+
y,
|
|
410
|
+
new e.LineBasicMaterial({ color: d.edge })
|
|
411
|
+
);
|
|
412
|
+
if (t.add(G), o === 0) {
|
|
413
|
+
const S = new e.Group(), m = c, b = 0.55, L = 0.15, B = g + 1e-3;
|
|
414
|
+
for (let C = 0; C < 6; C++) {
|
|
415
|
+
const p = Math.PI / 3 * C, x = m * Math.cos(p), M = m * Math.sin(p), P = new e.BoxGeometry(b, B, L), H = new e.MeshStandardMaterial({ color: d.base }), I = new e.Mesh(P, H);
|
|
416
|
+
I.position.set(x, 0, M), I.rotation.y = -p + Math.PI / 2, S.add(I);
|
|
417
|
+
}
|
|
418
|
+
t.add(S);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
return t;
|
|
422
|
+
}
|
|
423
|
+
default: {
|
|
424
|
+
const t = new e.Group(), n = new e.BoxGeometry(1, 1, 1), a = new e.MeshStandardMaterial({
|
|
425
|
+
color: 6728447,
|
|
426
|
+
flatShading: !0,
|
|
427
|
+
side: e.DoubleSide
|
|
428
|
+
});
|
|
429
|
+
return t.add(new e.Mesh(n, a)), t;
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
const N = 1, U = "樹枝状";
|
|
434
|
+
function W(r = {}) {
|
|
435
|
+
let s = r.morphology;
|
|
436
|
+
s === void 0 && r.temperature !== void 0 && r.supersaturation !== void 0 && (s = O(r.temperature, r.supersaturation)), s === void 0 && (s = U);
|
|
437
|
+
const t = j(r.seed ?? N);
|
|
438
|
+
return Y(s, t);
|
|
439
|
+
}
|
|
440
|
+
function X(r) {
|
|
441
|
+
r.traverse((s) => {
|
|
442
|
+
const t = s;
|
|
443
|
+
t.geometry && t.geometry.dispose();
|
|
444
|
+
const n = t.material;
|
|
445
|
+
if (Array.isArray(n))
|
|
446
|
+
for (const a of n) a.dispose();
|
|
447
|
+
else n && n.dispose();
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
export {
|
|
451
|
+
W as createSnowCrystal,
|
|
452
|
+
X as disposeCrystal,
|
|
453
|
+
O as getCrystalType,
|
|
454
|
+
V as getGlobalLabel
|
|
455
|
+
};
|
|
456
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/classify.ts","../src/random.ts","../src/geometry/parts.ts","../src/geometry/morphologies.ts","../src/createSnowCrystal.ts"],"sourcesContent":["import type { Morphology } from './types';\n\n/** Shared palette (ported verbatim from the snownotes viewer). */\nexport const COLORS = {\n base: 0xcccccc, // 白寄りのグレー\n highlight: 0xffffff, // ピュアホワイト\n edge: 0x888888, // やや暗いグレー\n wing: 0xaaaaaa, // 羽や針も白系で統一\n} as const;\n\nexport const TITLE_MAP: Record<Morphology, { ja: string; en: string }> = {\n 針: { ja: '針', en: 'Needle' },\n さや: { ja: 'さや', en: 'Sheath' },\n 角柱: { ja: '角柱', en: 'Column' },\n 骸晶角柱: { ja: '骸晶角柱', en: 'Skeleton Column' },\n 角板: { ja: '角板', en: 'Plate' },\n 厚角板: { ja: '厚角板', en: 'Thick Solid Plate' },\n 骸晶角板: { ja: '骸晶角板', en: 'Skeleton Plate' },\n 扇形: { ja: '扇形', en: 'Sector' },\n 樹枝状: { ja: '樹枝状', en: 'Dendrite' },\n};\n\n/** Short subtype lists keyed by global-classification code. */\nexport const SUBTYPE_MAP: Record<string, string[]> = {\n C1a: ['C1a - 針', 'C1b - 束状針', 'C1c - 針集合'],\n C2a: ['C2a - さや', 'C2b - 束状さや'],\n C3a: ['C3a - 角柱', 'C3b - 骸晶角柱'],\n P1a: ['P1a - 角板', 'P1b - 厚角板', 'P1c - 骸晶角板'],\n P4f: ['P4f - 扇付角板'],\n P4g: ['P4g - 樹枝付角板'],\n};\n\n/** Full bilingual subtype lists keyed by global-classification code. */\nexport const FULL_SUBTYPE_MAP: Record<string, string[]> = {\n C1a: [\n 'C - 柱状結晶群 - Column crystal group',\n 'C1 - 針状結晶 - Needle-type crystal',\n 'C1a - 針 - Needle',\n 'C1b - 束状針 - Bundle of needles',\n 'C1c - 針集合 - Combination of needles',\n ],\n C2a: [\n 'C - 柱状結晶群 - Column crystal group',\n 'C2 - 鞘状結晶 - Sheath-type crystal',\n 'C2a - 鞘 - Sheath',\n 'C2b - 束状鞘 - Bundle of sheaths',\n 'C2c - 鞘集合 - Combination of sheaths',\n ],\n C3a: [\n 'C - 柱状結晶群 - Column crystal group',\n 'C3 - 角柱状結晶 - Column-type crystal',\n 'C3a - 角柱 - Solid column',\n 'C3b - 骸晶角柱 - Skeletal column',\n 'C3c - 巻込骸晶角柱 - Skeletal column with scrolls',\n ],\n C3b: [\n 'C - 柱状結晶群 - Column crystal group',\n 'C3 - 角柱状結晶 - Column-type crystal',\n 'C3a - 角柱 - Solid column',\n 'C3b - 骸晶角柱 - Skeletal column',\n 'C3c - 巻込骸晶角柱 - Skeletal column with scrolls',\n ],\n P1a: [\n 'P - 板状結晶群 - Plane crystal group',\n 'P1 - 角板状結晶 - Plate-type crystal',\n 'P1a - 角板 - Plate',\n 'P1b - 厚角板 - Thick solid plate',\n 'P1c - 骸晶角板 - Skeletal plate',\n ],\n P1b: [\n 'P - 板状結晶群 - Plane crystal group',\n 'P1 - 角板状結晶 - Plate-type crystal',\n 'P1a - 角板 - Plate',\n 'P1b - 厚角板 - Thick solid plate',\n 'P1c - 骸晶角板 - Skeletal plate',\n ],\n P1c: [\n 'P - 板状結晶群 - Plane crystal group',\n 'P1 - 角板状結晶 - Plate-type crystal',\n 'P1a - 角板 - Plate',\n 'P1b - 厚角板 - Thick solid plate',\n 'P1c - 骸晶角板 - Skeletal plate',\n ],\n P4f: [\n 'P - 板状結晶群 - Plane crystal group',\n 'P4 - 複合板状結晶 - Composite plane-type crystal',\n 'P4a - 角板付六花 - Stellar with plates',\n 'P4b - 扇付六花 - Stellar with sectors',\n 'P4c - 角板付樹枝 - Dendrite with plates',\n 'P4d - 扇付樹枝 - Dendrite with sectors',\n 'P4e - 枝付角板 - Plate with branches',\n 'P4f - 扇付角板 - Plate with sectors',\n 'P4g - 樹枝付角板 - Plate with dendrites',\n ],\n P4g: [\n 'P - 板状結晶群 - Plane crystal group',\n 'P4 - 複合板状結晶 - Composite plane-type crystal',\n 'P4a - 角板付六花 - Stellar with plates',\n 'P4b - 扇付六花 - Stellar with sectors',\n 'P4c - 角板付樹枝 - Dendrite with plates',\n 'P4d - 扇付樹枝 - Dendrite with sectors',\n 'P4e - 枝付角板 - Plate with branches',\n 'P4f - 扇付角板 - Plate with sectors',\n 'P4g - 樹枝付角板 - Plate with dendrites',\n ],\n};\n\n/**\n * Map a Nakaya-diagram point (temperature °C, vapor 0–0.3) to a morphology.\n * Temperatures are expected as negative values (e.g. -10 means -10°C).\n * Ported verbatim from the snownotes viewer.\n */\nexport function getCrystalType(temp: number, vapor: number): Morphology {\n // ⛏️ 高温 → 低温 の順で条件を整理(0℃〜-40℃)\n if (temp <= 0 && temp > -4) {\n if (vapor >= 0.1 && vapor <= 0.3) return '角板';\n } else if (temp <= -4 && temp > -10) {\n if (vapor <= 0.05) return '角柱';\n if (vapor <= 0.1) return '骸晶角柱';\n if (vapor <= 0.15) return 'さや';\n return '針';\n } else if (temp <= -10 && temp > -22) {\n if (vapor <= 0.05) return '厚角板';\n if (vapor <= 0.15) return '骸晶角板';\n if (vapor <= 0.2) return '扇形';\n return '樹枝状';\n } else if (temp <= -22 && temp >= -40) {\n if (vapor <= 0.05) return '角柱';\n if (vapor <= 0.1) return '骸晶角柱';\n return 'さや';\n }\n\n return '角板'; // fallback\n}\n\n/** Morphology name -> global-classification subtype code. */\nexport function getSubtypeCode(typeName: Morphology | '' | undefined): string {\n if (!typeName) return '';\n if (typeName === '針') return 'C1a';\n if (typeName === 'さや') return 'C2a';\n if (typeName === '角柱') return 'C3a';\n if (typeName === '骸晶角柱') return 'C3b';\n if (typeName === '角板') return 'P1a';\n if (typeName === '厚角板') return 'P1b';\n if (typeName === '骸晶角板') return 'P1c';\n if (typeName === '扇形') return 'P4f';\n if (typeName === '樹枝状') return 'P4g';\n return '';\n}\n\n/** Morphology name -> global-classification label shown in the UI. */\nexport function getGlobalLabel(typeName: Morphology): string {\n switch (typeName) {\n case '針':\n return 'C1a';\n case 'さや':\n return 'C2a';\n case '角柱':\n return 'C3a';\n case '骸晶角柱':\n return 'C3b';\n case '角板':\n return 'P1a';\n case '厚角板':\n return 'P1b';\n case '骸晶角板':\n return 'P1c';\n case '扇形':\n return 'P4f';\n case '樹枝状':\n return 'P4g';\n default:\n return '';\n }\n}\n","/**\n * mulberry32 — a tiny, fast, deterministic PRNG.\n * Replaces the original Math.random() so the same seed yields the same crystal.\n */\nexport function mulberry32(seed: number): () => number {\n let a = seed >>> 0;\n return function (): number {\n a |= 0;\n a = (a + 0x6d2b79f5) | 0;\n let t = Math.imul(a ^ (a >>> 15), 1 | a);\n t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;\n return ((t ^ (t >>> 14)) >>> 0) / 4294967296;\n };\n}\n","import { THREE } from '../three';\nimport { COLORS } from '../classify';\n\n// Pure geometry helpers ported verbatim from snownotes main-fixed.js.\n// They depend only on THREE and return Mesh/Group — no scene, no DOM.\n\nexport function createFanShape(\n centerRadius: number,\n centerHeight: number,\n armLength: number,\n armWidth: number,\n colorHex: number,\n): THREE.Group {\n const group = new THREE.Group();\n\n const centerGeo = new THREE.CylinderGeometry(centerRadius, centerRadius, centerHeight, 6);\n const centerMat = new THREE.MeshStandardMaterial({ color: colorHex });\n // centerMesh は元実装でも group に追加されない(忠実移植)\n new THREE.Mesh(centerGeo, centerMat);\n\n // 角部に抜けを作るための補助メッシュ\n for (let i = 0; i < 6; i++) {\n const angle = (i * Math.PI) / 3;\n const x = (centerRadius + armLength / 2) * Math.cos(angle);\n const z = (centerRadius + armLength / 2) * Math.sin(angle);\n const geo = new THREE.BoxGeometry(armLength, centerHeight * 0.8, armWidth);\n const mesh = new THREE.Mesh(geo, centerMat);\n mesh.position.set(x, 0, z);\n mesh.rotation.y = -angle;\n group.add(mesh);\n }\n return group;\n}\n\nexport function createDiamondPrism(): THREE.Mesh {\n const geometry = new THREE.BufferGeometry();\n\n const topHeight = 1.5; // 上側三角の高さ\n const bottomHeight = 0.4; // 下側三角の高さ\n const width = 0.8; // 底辺の長さ\n const depth = 0.1; // 奥行き\n\n const vertices = new Float32Array([\n -width / 2, 0, depth / 2,\n width / 2, 0, depth / 2,\n 0, topHeight, depth / 2,\n\n -width / 2, 0, depth / 2,\n width / 2, 0, depth / 2,\n 0, -bottomHeight, depth / 2,\n\n -width / 2, 0, -depth / 2,\n width / 2, 0, -depth / 2,\n 0, topHeight, -depth / 2,\n\n -width / 2, 0, -depth / 2,\n width / 2, 0, -depth / 2,\n 0, -bottomHeight, -depth / 2,\n ]);\n\n const indices = [\n 0, 1, 2,\n 8, 7, 6,\n 0, 2, 8, 8, 6, 0,\n 2, 1, 7, 7, 8, 2,\n 0, 6, 7, 7, 1, 0,\n\n 3, 5, 4,\n 11, 9, 10,\n 3, 9, 11, 11, 5, 3,\n 5, 11, 10, 10, 4, 5,\n 3, 4, 10, 10, 9, 3,\n ];\n\n geometry.setAttribute('position', new THREE.BufferAttribute(vertices, 3));\n geometry.setIndex(indices);\n geometry.computeVertexNormals();\n\n const material = new THREE.MeshStandardMaterial({ color: COLORS.wing, flatShading: true });\n return new THREE.Mesh(geometry, material);\n}\n\nexport function createSmallDiamondPrism(): THREE.Mesh {\n const geometry = new THREE.BufferGeometry();\n\n const topHeight = 0.4;\n const bottomHeight = 0.2;\n const width = 0.4;\n const depth = 0.08;\n\n const vertices = new Float32Array([\n -width / 2, 0, depth / 2,\n width / 2, 0, depth / 2,\n 0, topHeight, depth / 2,\n\n -width / 2, 0, depth / 2,\n width / 2, 0, depth / 2,\n 0, -bottomHeight, depth / 2,\n\n -width / 2, 0, -depth / 2,\n width / 2, 0, -depth / 2,\n 0, topHeight, -depth / 2,\n\n -width / 2, 0, -depth / 2,\n width / 2, 0, -depth / 2,\n 0, -bottomHeight, -depth / 2,\n ]);\n\n const indices = [\n 0, 1, 2, 8, 7, 6,\n 0, 2, 8, 8, 6, 0,\n 2, 1, 7, 7, 8, 2,\n 0, 6, 7, 7, 1, 0,\n\n 3, 5, 4, 11, 9, 10,\n 3, 9, 11, 11, 5, 3,\n 5, 11, 10, 10, 4, 5,\n 3, 4, 10, 10, 9, 3,\n ];\n\n geometry.setAttribute('position', new THREE.BufferAttribute(vertices, 3));\n geometry.setIndex(indices);\n geometry.computeVertexNormals();\n\n const material = new THREE.MeshStandardMaterial({ color: COLORS.wing });\n return new THREE.Mesh(geometry, material);\n}\n\nexport function createMiniDiamondPrism(): THREE.Mesh {\n const geometry = new THREE.BufferGeometry();\n\n const topHeight = 0.4;\n const bottomHeight = 0.2;\n const width = 0.3;\n const depth = 0.05;\n\n const vertices = new Float32Array([\n -width / 2, 0, depth / 2,\n width / 2, 0, depth / 2,\n 0, topHeight, depth / 2,\n\n -width / 2, 0, depth / 2,\n width / 2, 0, depth / 2,\n 0, -bottomHeight, depth / 2,\n\n -width / 2, 0, -depth / 2,\n width / 2, 0, -depth / 2,\n 0, topHeight, -depth / 2,\n\n -width / 2, 0, -depth / 2,\n width / 2, 0, -depth / 2,\n 0, -bottomHeight, -depth / 2,\n ]);\n\n const indices = [\n 0, 1, 2, 8, 7, 6,\n 0, 2, 8, 8, 6, 0,\n 2, 1, 7, 7, 8, 2,\n 0, 6, 7, 7, 1, 0,\n\n 3, 5, 4, 11, 9, 10,\n 3, 9, 11, 11, 5, 3,\n 5, 11, 10, 10, 4, 5,\n 3, 4, 10, 10, 9, 3,\n ];\n\n geometry.setAttribute('position', new THREE.BufferAttribute(vertices, 3));\n geometry.setIndex(indices);\n geometry.computeVertexNormals();\n\n const material = new THREE.MeshStandardMaterial({ color: COLORS.wing, flatShading: true });\n return new THREE.Mesh(geometry, material);\n}\n\nexport function createRedTriangularPrism(): THREE.Mesh {\n const radius = 0.5; // 三角形の外接円半径\n const height = 1.0; // 厚み(Y方向)\n\n const geometry = new THREE.CylinderGeometry(radius, radius, height, 3);\n geometry.rotateX(Math.PI / 2); // 上向きに回転\n geometry.translate(0, 0, 0); // 中央位置合わせ\n\n const material = new THREE.MeshStandardMaterial({\n color: 0xaa3333,\n flatShading: true,\n });\n\n return new THREE.Mesh(geometry, material);\n}\n\nexport function createBranchWithChildren(angleRad: number): THREE.Group {\n const group = new THREE.Group();\n\n // 主枝(Z方向に2.0伸びる棒)\n const mainBranch = new THREE.Mesh(\n new THREE.BoxGeometry(0.08, 0.08, 2.0),\n new THREE.MeshStandardMaterial({ color: COLORS.wing }),\n );\n mainBranch.position.z = 1.0; // 半分ずらして中心から放射状に\n\n group.add(mainBranch);\n\n // 副枝の数と間隔\n const sideCount = 3;\n const spacing = 0.5;\n const offsetX = 0.3;\n\n for (let i = 0; i < sideCount; i++) {\n const offsetZ = spacing * (i + 1.5);\n\n // 左右の副枝\n const petalL = createMiniDiamondPrism();\n const petalR = createMiniDiamondPrism();\n\n // 主枝にピタッと接するようにXZ平面上に寝かせ、鋭角を内向きに\n petalL.rotation.x = Math.PI / 2;\n petalL.rotation.z = Math.PI / 4 + Math.PI; // = 5π/4\n petalL.position.set(-offsetX, 0, offsetZ);\n\n petalR.rotation.x = Math.PI / 2;\n petalR.rotation.z = -Math.PI / 4 + Math.PI; // = 3π/4\n petalR.position.set(offsetX, 0, offsetZ);\n\n group.add(petalL, petalR);\n }\n\n // 🔁 全体を角度分だけ回転(groupごと回す!)\n group.rotation.y = -angleRad;\n\n return group;\n}\n\nexport function createMainBranch(): THREE.Mesh {\n const geometry = new THREE.BoxGeometry(2.0, 0.05, 0.1); // 薄くて長い板\n const material = new THREE.MeshStandardMaterial({ color: 0x662222 });\n const mesh = new THREE.Mesh(geometry, material);\n mesh.position.x = 1.0; // 中心から伸ばす\n return mesh;\n}\n\nexport function createSubBranch(): THREE.Mesh {\n const geometry = new THREE.BufferGeometry();\n\n const topHeight = 0.4;\n const bottomHeight = 0.2;\n const width = 0.3;\n const depth = 0.05;\n\n const vertices = new Float32Array([\n -width / 2, 0, depth / 2,\n width / 2, 0, depth / 2,\n 0, topHeight, depth / 2,\n\n -width / 2, 0, depth / 2,\n width / 2, 0, depth / 2,\n 0, -bottomHeight, depth / 2,\n\n -width / 2, 0, -depth / 2,\n width / 2, 0, -depth / 2,\n 0, topHeight, -depth / 2,\n\n -width / 2, 0, -depth / 2,\n width / 2, 0, -depth / 2,\n 0, -bottomHeight, -depth / 2,\n ]);\n\n const indices = [\n 0, 1, 2, 8, 7, 6,\n 0, 2, 8, 8, 6, 0,\n 2, 1, 7, 7, 8, 2,\n 0, 6, 7, 7, 1, 0,\n\n 3, 5, 4, 11, 9, 10,\n 3, 9, 11, 11, 5, 3,\n 5, 11, 10, 10, 4, 5,\n 3, 4, 10, 10, 9, 3,\n ];\n\n geometry.setAttribute('position', new THREE.BufferAttribute(vertices, 3));\n geometry.setIndex(indices);\n geometry.computeVertexNormals();\n\n const material = new THREE.MeshStandardMaterial({ color: 0x662222, flatShading: true });\n const mesh = new THREE.Mesh(geometry, material);\n\n // 🔄 向きをXZ平面と並行に寝かせる(六角柱と同じ)\n mesh.rotation.x = Math.PI / 2;\n\n return mesh;\n}\n\nexport function createMainBlade(length = 2.0, width = 0.2, thickness = 0.05): THREE.Mesh {\n const shape = new THREE.Shape();\n shape.moveTo(0, 0);\n shape.lineTo(width, length);\n shape.lineTo(-width, length);\n shape.lineTo(0, 0); // 閉じる\n\n const extrudeSettings = {\n depth: thickness,\n bevelEnabled: false,\n };\n\n const geometry = new THREE.ExtrudeGeometry(shape, extrudeSettings);\n geometry.rotateX(Math.PI / 2); // 横倒しに\n geometry.translate(0, 0, -thickness / 2); // 中央揃え\n\n const material = new THREE.MeshStandardMaterial({ color: 0x662222, flatShading: true });\n return new THREE.Mesh(geometry, material);\n}\n\nexport function createFanHexGeometryFilled(\n radius = 1,\n height = 0.2,\n dentDepth = 0.3,\n): THREE.ExtrudeGeometry {\n const shape = new THREE.Shape();\n const dentRatio = 0.35;\n\n for (let i = 0; i < 6; i++) {\n const a0 = (Math.PI / 3) * i;\n const a1 = (Math.PI / 3) * (i + dentRatio);\n const a2 = (Math.PI / 3) * (i + 0.5);\n const a3 = (Math.PI / 3) * (i + 1 - dentRatio);\n const a4 = (Math.PI / 3) * (i + 1);\n\n if (i === 0) {\n shape.moveTo(Math.cos(a0) * radius, Math.sin(a0) * radius);\n }\n\n shape.lineTo(Math.cos(a1) * radius, Math.sin(a1) * radius);\n shape.lineTo(Math.cos(a2) * (radius - dentDepth), Math.sin(a2) * (radius - dentDepth));\n shape.lineTo(Math.cos(a3) * radius, Math.sin(a3) * radius);\n shape.lineTo(Math.cos(a4) * radius, Math.sin(a4) * radius);\n }\n\n const extrudeSettings = {\n depth: height,\n bevelEnabled: false,\n material: 1, // 側面\n extrudeMaterial: 0, // 上下の面\n };\n\n const geometry = new THREE.ExtrudeGeometry(shape, extrudeSettings);\n geometry.rotateX(Math.PI / 2);\n geometry.translate(0, 0, -height / 2);\n return geometry;\n}\n\nexport function createHexRingFrame(\n radius: number,\n thickness: number,\n height: number,\n): THREE.Group {\n const group = new THREE.Group();\n const edgeLength = radius; // 見た目調整に応じて調整可\n\n for (let i = 0; i < 6; i++) {\n const angle = (Math.PI / 3) * i;\n const x = radius * Math.cos(angle);\n const z = radius * Math.sin(angle);\n\n const boxGeo = new THREE.BoxGeometry(edgeLength, height, thickness);\n const boxMat = new THREE.MeshStandardMaterial({ color: COLORS.edge });\n const box = new THREE.Mesh(boxGeo, boxMat);\n\n box.position.set(x, 0, z);\n box.rotation.y = -angle;\n\n group.add(box);\n }\n\n return group;\n}\n","import { THREE } from '../three';\nimport { COLORS } from '../classify';\nimport type { Morphology } from '../types';\nimport { createDiamondPrism, createBranchWithChildren } from './parts';\n\n/**\n * Build the THREE.Group for a single morphology.\n * This is the `switch` body of the original `setCrystalModel(type)` — the\n * scene.remove/add, currentMesh assignment, and DOM updates have been removed.\n * `rng` is a deterministic PRNG replacing the original Math.random().\n */\nexport function buildMorphology(morphology: Morphology, rng: () => number): THREE.Group {\n switch (morphology) {\n case '角柱': {\n const geo = new THREE.CylinderGeometry(0.4, 0.4, 1.5, 6);\n const baseMat = new THREE.MeshStandardMaterial({ color: COLORS.base, flatShading: true });\n const mesh = new THREE.Mesh(geo, baseMat);\n\n // 🔷 角の線を追加(青線)\n const edgeMat = new THREE.LineBasicMaterial({ color: COLORS.edge });\n const edges = new THREE.EdgesGeometry(geo);\n const edgeLines = new THREE.LineSegments(edges, edgeMat);\n\n const group = new THREE.Group();\n group.add(mesh);\n group.add(edgeLines);\n return group;\n }\n\n case '角板': {\n const group = new THREE.Group();\n\n // 外側の凹んだ骸晶構造\n const layers = 4;\n const baseRadius = 0.6;\n const baseHeight = 0.3;\n\n for (let i = 0; i < layers; i++) {\n const ratio = 1 - i * 0.15;\n const r = baseRadius * ratio;\n const height = baseHeight * (1 - i * 0.1);\n const color = new THREE.Color(COLORS.base);\n\n const sideMat = new THREE.MeshStandardMaterial({\n color: color,\n flatShading: true,\n transparent: true,\n opacity: 0.3,\n });\n const topMat = new THREE.MeshStandardMaterial({ color: color, flatShading: true });\n const geo = new THREE.CylinderGeometry(r, r, height, 6, 1, false);\n const mesh = new THREE.Mesh(geo, [sideMat, topMat, topMat]);\n group.add(mesh);\n\n const edgeMaterial = new THREE.LineBasicMaterial({ color: COLORS.edge });\n const edges = new THREE.EdgesGeometry(geo);\n const edgeLines = new THREE.LineSegments(edges, edgeMaterial);\n group.add(edgeLines);\n }\n\n // 中心六角柱(伸びてる感)\n const centerRadius = baseRadius * 1.2;\n const centerHeight = baseHeight * 0.8;\n const centerGeo = new THREE.CylinderGeometry(centerRadius, centerRadius, centerHeight, 6);\n const centerMat = new THREE.MeshStandardMaterial({ color: COLORS.wing });\n const centerMesh = new THREE.Mesh(centerGeo, centerMat);\n centerMesh.position.y = 0; // 真ん中に収める\n group.add(centerMesh);\n\n const centerEdges = new THREE.EdgesGeometry(centerGeo);\n const centerLines = new THREE.LineSegments(\n centerEdges,\n new THREE.LineBasicMaterial({ color: COLORS.edge }),\n );\n group.add(centerLines);\n return group;\n }\n\n case '扇形': {\n const group = new THREE.Group();\n\n // 中心の六角柱\n const centerGeo = new THREE.CylinderGeometry(0.5, 0.5, 0.2, 6);\n const centerMat = new THREE.MeshStandardMaterial({ color: COLORS.base });\n const centerMesh = new THREE.Mesh(centerGeo, centerMat);\n group.add(centerMesh);\n\n const numPetals = 6;\n const radius = 1.1;\n const tilt = -Math.PI / 2; // -45度で倒す\n\n for (let i = 0; i < numPetals; i++) {\n const angle = (i * Math.PI) / 3;\n\n // 菱形羽根の生成\n const petal = createDiamondPrism();\n\n // 羽根をZ軸で90度回して鋭角を前に(重要)\n petal.rotation.z = Math.PI / 2;\n\n // 倒す(XZ平面に沿って)\n petal.rotation.x = tilt;\n\n // 中間グループで回転と配置を制御\n const petalGroup = new THREE.Group();\n petalGroup.add(petal);\n\n // 鋭角を中心方向へ向ける(Y軸回転)\n petalGroup.rotation.y = -angle;\n\n // 鋭角の先端が中心に届くよう配置\n const x = radius * Math.cos(angle);\n const z = radius * Math.sin(angle);\n petalGroup.position.set(x, 0, z);\n\n group.add(petalGroup);\n }\n return group;\n }\n\n case '樹枝状': {\n const group = new THREE.Group();\n\n // 中心の六角柱\n const centerGeo = new THREE.CylinderGeometry(0.5, 0.5, 0.2, 6);\n const centerMat = new THREE.MeshStandardMaterial({ color: COLORS.base });\n const centerMesh = new THREE.Mesh(centerGeo, centerMat);\n group.add(centerMesh);\n\n // 6方向に主枝 + 副枝を放射状に配置\n for (let i = 0; i < 6; i++) {\n const angle = (i * Math.PI) / 3; // 60度間隔\n const branch = createBranchWithChildren(angle);\n branch.position.set(0, 0, 0);\n group.add(branch);\n }\n return group;\n }\n\n case '針': {\n const group = new THREE.Group();\n const outerRadius = 0.4;\n const baseHeight = 2.0;\n const layers = 6;\n\n let topRadius = 0;\n const edgeThickness = 0.15;\n const edgeLength = 0.55;\n let edgeHeight = 0;\n\n for (let i = 0; i < layers; i++) {\n const ratio = 1 - i * 0.12;\n const r = outerRadius * ratio;\n const height = baseHeight * (0.8 - i * 0.08);\n const color = new THREE.Color(COLORS.base);\n\n const mat = new THREE.MeshStandardMaterial({\n color: color,\n flatShading: true,\n transparent: true,\n opacity: i === 0 ? 1.0 : 0.3,\n });\n\n const geo = new THREE.CylinderGeometry(r, r, height, 6, 1, i !== layers - 1);\n const mesh = new THREE.Mesh(geo, mat);\n group.add(mesh);\n\n const edges = new THREE.EdgesGeometry(geo);\n const edgeLines = new THREE.LineSegments(\n edges,\n new THREE.LineBasicMaterial({ color: COLORS.edge }),\n );\n group.add(edgeLines);\n\n if (i === 0) {\n topRadius = r;\n edgeHeight = height + 0.001;\n\n const edgeGroup = new THREE.Group();\n for (let j = 0; j < 6; j++) {\n const angle = (Math.PI / 3) * j;\n const x = topRadius * Math.cos(angle);\n const z = topRadius * Math.sin(angle);\n\n const boxGeo = new THREE.BoxGeometry(edgeLength, edgeHeight, edgeThickness);\n const boxMat = new THREE.MeshStandardMaterial({ color: COLORS.base });\n const edgeBox = new THREE.Mesh(boxGeo, boxMat);\n\n edgeBox.position.set(x, 0, z);\n edgeBox.rotation.y = -angle + Math.PI / 2;\n edgeGroup.add(edgeBox);\n }\n group.add(edgeGroup);\n }\n }\n\n // 🧩 針の配置(六角柱の角に完全接続)\n const needleMaterial = new THREE.MeshStandardMaterial({ color: COLORS.wing });\n const needleRadius = edgeThickness / Math.sqrt(3); // ✅ 六角柱角と合うサイズ\n\n for (let i = 0; i < 6; i++) {\n const angle = (i * Math.PI) / 3 + Math.PI / 6; // ← 六角柱の角方向に修正\n const baseX = topRadius + edgeThickness; // ←厚みを考慮\n const x = baseX * Math.cos(angle) - needleRadius * Math.cos(angle);\n const z = baseX * Math.sin(angle) - needleRadius * Math.sin(angle);\n\n const lengthTop = 1.5 + rng() * 0.5;\n const lengthBottom = 1.5 + rng() * 0.5;\n\n const topY = baseHeight / 4.5 + edgeHeight / 4.5;\n const bottomY = -baseHeight / 4.5 - edgeHeight / 4.5;\n\n // 上向き針\n const geo1 = new THREE.CylinderGeometry(needleRadius, needleRadius, lengthTop, 6);\n const mesh1 = new THREE.Mesh(geo1, needleMaterial);\n mesh1.position.set(x, topY + lengthTop / 2, z);\n mesh1.rotation.y = 0;\n group.add(mesh1);\n\n // 下向き針\n const geo2 = new THREE.CylinderGeometry(needleRadius, needleRadius, lengthBottom, 6);\n const mesh2 = new THREE.Mesh(geo2, needleMaterial);\n mesh2.position.set(x, bottomY - lengthBottom / 2, z);\n mesh2.rotation.y = 0;\n group.add(mesh2);\n }\n return group;\n }\n\n case '厚角板': {\n const geo = new THREE.CylinderGeometry(0.6, 0.6, 0.4, 6);\n const mat = new THREE.MeshStandardMaterial({\n color: COLORS.base,\n flatShading: true,\n });\n const mesh = new THREE.Mesh(geo, mat);\n\n // 🔷 エッジを追加(角線)\n const edgeGeo = new THREE.EdgesGeometry(geo);\n const edgeMat = new THREE.LineBasicMaterial({ color: COLORS.edge });\n const edgeLines = new THREE.LineSegments(edgeGeo, edgeMat);\n\n const group = new THREE.Group();\n group.add(mesh);\n group.add(edgeLines);\n return group;\n }\n\n case '骸晶角板': {\n const group = new THREE.Group();\n const layers = 4;\n const baseRadius = 0.6;\n const baseHeight = 0.3;\n\n for (let i = 0; i < layers; i++) {\n const ratio = 1 - i * 0.15;\n const r = baseRadius * ratio;\n const height = baseHeight * (1 - i * 0.1);\n\n // 中心だけ COLORS.wing、それ以外は側面非表示\n const color = i === layers - 1 ? COLORS.wing : COLORS.base;\n\n const sideMat = new THREE.MeshStandardMaterial({\n color: color,\n flatShading: true,\n transparent: i !== layers - 1,\n opacity: i !== layers - 1 ? 0 : 1, // 外側層は透明、中心だけ不透明\n });\n const topMat = new THREE.MeshStandardMaterial({\n color: color,\n flatShading: true,\n transparent: false,\n });\n\n const geo = new THREE.CylinderGeometry(r, r, height, 6, 1, false);\n const mesh = new THREE.Mesh(geo, [sideMat, topMat, topMat]);\n group.add(mesh);\n\n const edgeMaterial = new THREE.LineBasicMaterial({ color: COLORS.edge });\n const edges = new THREE.EdgesGeometry(geo);\n const edgeLines = new THREE.LineSegments(edges, edgeMaterial);\n group.add(edgeLines);\n }\n return group;\n }\n\n case '骸晶角柱': {\n const group = new THREE.Group();\n const baseRadius = 0.4; // 六角柱の半径\n const baseHeight = 1.5; // 六角柱の高さ\n\n // ✅ メインの六角柱(外側)\n const outerGeo = new THREE.CylinderGeometry(baseRadius, baseRadius, baseHeight, 6);\n const outerMat = new THREE.MeshStandardMaterial({ color: COLORS.base, flatShading: true });\n const outerMesh = new THREE.Mesh(outerGeo, outerMat);\n group.add(outerMesh);\n\n // ✅ 外側の角線(エッジライン)\n const outerEdges = new THREE.EdgesGeometry(outerGeo);\n group.add(new THREE.LineSegments(outerEdges, new THREE.LineBasicMaterial({ color: COLORS.edge })));\n\n // ✅ 各面に沿った四角柱の骨格構造(6方向に配置)\n const edgeGroup = new THREE.Group();\n const edgeLength = 0.55;\n const edgeThickness = 0.15;\n const edgeHeight = baseHeight + 0.2; // ここが深さ調整\n\n for (let j = 0; j < 6; j++) {\n const angle = (Math.PI / 3) * j;\n const x = baseRadius * Math.cos(angle);\n const z = baseRadius * Math.sin(angle);\n const boxGeo = new THREE.BoxGeometry(edgeLength, edgeHeight, edgeThickness);\n const boxMat = new THREE.MeshStandardMaterial({ color: COLORS.base });\n const edgeBox = new THREE.Mesh(boxGeo, boxMat);\n edgeBox.position.set(x, 0, z);\n edgeBox.rotation.y = -angle + Math.PI / 2; // 各面に接するように回転\n edgeGroup.add(edgeBox);\n }\n group.add(edgeGroup);\n\n // ✅ 中央の六角形の「凹み」部分を深く沈めるメッシュを追加(上下面に)\n const dentRadius = baseRadius * 0.7; // 中心の凹みの半径(少し小さめ)\n const dentHeight = 0.2; // 非常に薄い高さ(凹みの厚み)\n const dentDepth = 0.15; // 凹ませる深さ(通常より深くする)\n\n const dentMat = new THREE.MeshStandardMaterial({\n color: COLORS.highlight, // ハイライトカラーで視認性アップ\n flatShading: true,\n });\n\n // 上面の凹み\n const topDent = new THREE.Mesh(\n new THREE.CylinderGeometry(dentRadius, dentRadius, dentHeight, 6),\n dentMat,\n );\n topDent.position.y = baseHeight / 2 - dentHeight / 2 - dentDepth;\n group.add(topDent);\n\n // 下面の凹み(反転)\n const bottomDent = topDent.clone();\n bottomDent.position.y = -baseHeight / 2 + dentHeight / 2 + dentDepth;\n group.add(bottomDent);\n\n // ✅ 側面の凹み(Boxを貼り付け、Edgesで視認可能にする)\n const sideDentGroup = new THREE.Group();\n const dentW = 0.55; // 横幅(六角柱1辺の長さ)\n const dentH = 0.4; // 高さ(縦)\n const dentD = 0.05; // 厚み(奥行き)\n const dentInset = 0.02; // 凹ませる距離(少し内側へ)\n\n for (let j = 0; j < 6; j++) {\n const angle = (Math.PI / 3) * j;\n\n // 内側に沈めた位置\n const x = (baseRadius - dentD / 2 - dentInset) * Math.cos(angle);\n const z = (baseRadius - dentD / 2 - dentInset) * Math.sin(angle);\n\n // ジオメトリとメッシュ\n const dentGeo = new THREE.BoxGeometry(dentD, dentH, dentW);\n const dentMesh = new THREE.Mesh(dentGeo, dentMat);\n dentMesh.position.set(x, 0, z);\n dentMesh.rotation.y = -angle;\n\n // ✅ エッジライン追加(明るい色で輪郭を見せる)\n const edgeGeo = new THREE.EdgesGeometry(dentGeo);\n const edgeMat = new THREE.LineBasicMaterial({ color: 0x88ccff }); // 明るめの水色\n const edgeLines = new THREE.LineSegments(edgeGeo, edgeMat);\n edgeLines.rotation.y = -angle;\n edgeLines.position.set(x, 0, z);\n\n sideDentGroup.add(dentMesh);\n sideDentGroup.add(edgeLines);\n }\n\n group.add(sideDentGroup);\n return group;\n }\n\n case 'さや': {\n const group = new THREE.Group();\n const layers = 6;\n const baseRadius = 0.4;\n const baseHeight = 2.0;\n\n for (let i = 0; i < layers; i++) {\n const ratio = 1 - i * 0.12;\n const r = baseRadius * ratio;\n const height = baseHeight * (0.8 - i * 0.08);\n const color = new THREE.Color(COLORS.edge);\n\n const mat = new THREE.MeshStandardMaterial({\n color: color,\n flatShading: true,\n transparent: true,\n opacity: i === 0 ? 1.0 : 0.3,\n });\n\n const geo = new THREE.CylinderGeometry(r, r, height, 6, 1, i !== layers - 1);\n const mesh = new THREE.Mesh(geo, mat);\n group.add(mesh);\n\n const edges = new THREE.EdgesGeometry(geo);\n const edgeLines = new THREE.LineSegments(\n edges,\n new THREE.LineBasicMaterial({ color: COLORS.edge }),\n );\n group.add(edgeLines);\n\n // ✅ 最外層の六角柱に沿って立体的な辺(エッジ)を追加\n if (i === 0) {\n const edgeGroup = new THREE.Group();\n const edgeRadius = r;\n const edgeLength = 0.55; // 六角柱の一辺の長さ\n const edgeThickness = 0.15;\n const edgeHeight = height + 0.001;\n\n for (let j = 0; j < 6; j++) {\n const angle = (Math.PI / 3) * j;\n const x = edgeRadius * Math.cos(angle);\n const z = edgeRadius * Math.sin(angle);\n\n const boxGeo = new THREE.BoxGeometry(edgeLength, edgeHeight, edgeThickness);\n const boxMat = new THREE.MeshStandardMaterial({ color: COLORS.base });\n const edgeBox = new THREE.Mesh(boxGeo, boxMat);\n\n edgeBox.position.set(x, 0, z);\n edgeBox.rotation.y = -angle + Math.PI / 2;\n\n edgeGroup.add(edgeBox);\n }\n\n group.add(edgeGroup);\n }\n }\n return group;\n }\n\n default: {\n // 元実装の switch default(到達しないが安全側のフォールバック)\n const group = new THREE.Group();\n const geometry = new THREE.BoxGeometry(1, 1, 1);\n const material = new THREE.MeshStandardMaterial({\n color: 0x66aaff,\n flatShading: true,\n side: THREE.DoubleSide,\n });\n group.add(new THREE.Mesh(geometry, material));\n return group;\n }\n }\n}\n","import { THREE } from './three';\nimport type { CrystalParams, Morphology } from './types';\nimport { getCrystalType } from './classify';\nimport { mulberry32 } from './random';\nimport { buildMorphology } from './geometry/morphologies';\n\n/** Fixed default seed so that, with no `seed` given, output is still deterministic. */\nconst DEFAULT_SEED = 1;\n\n/** Default morphology when neither `morphology` nor a temperature/vapor pair is provided. */\nconst DEFAULT_MORPHOLOGY: Morphology = '樹枝状';\n\n/**\n * Build a snow-crystal as a `THREE.Group`. Pure: touches no scene and no DOM.\n *\n * Selection order:\n * 1. `params.morphology` if given.\n * 2. otherwise `getCrystalType(temperature, supersaturation)` if both are given.\n * 3. otherwise the default morphology ('樹枝状').\n */\nexport function createSnowCrystal(params: CrystalParams = {}): THREE.Group {\n let morphology = params.morphology;\n\n if (\n morphology === undefined &&\n params.temperature !== undefined &&\n params.supersaturation !== undefined\n ) {\n morphology = getCrystalType(params.temperature, params.supersaturation);\n }\n\n if (morphology === undefined) {\n morphology = DEFAULT_MORPHOLOGY;\n }\n\n const rng = mulberry32(params.seed ?? DEFAULT_SEED);\n return buildMorphology(morphology, rng);\n}\n\n/**\n * Dispose all geometries and materials under a crystal group so its GPU\n * resources are released. Call after removing the group from the scene.\n */\nexport function disposeCrystal(group: THREE.Group): void {\n group.traverse((obj) => {\n const withGeo = obj as THREE.Mesh | THREE.LineSegments;\n if (withGeo.geometry) {\n withGeo.geometry.dispose();\n }\n const material = (withGeo as { material?: THREE.Material | THREE.Material[] }).material;\n if (Array.isArray(material)) {\n for (const m of material) m.dispose();\n } else if (material) {\n material.dispose();\n }\n });\n}\n"],"names":["COLORS","getCrystalType","temp","vapor","getGlobalLabel","typeName","mulberry32","seed","a","createDiamondPrism","geometry","THREE","topHeight","width","depth","vertices","indices","material","createMiniDiamondPrism","createBranchWithChildren","angleRad","group","mainBranch","sideCount","spacing","offsetX","i","offsetZ","petalL","petalR","buildMorphology","morphology","rng","geo","baseMat","mesh","edgeMat","edges","edgeLines","layers","baseRadius","baseHeight","ratio","r","height","color","sideMat","topMat","edgeMaterial","centerRadius","centerHeight","centerGeo","centerMat","centerMesh","centerEdges","centerLines","numPetals","radius","tilt","angle","petal","petalGroup","x","z","branch","outerRadius","topRadius","edgeThickness","edgeLength","edgeHeight","mat","edgeGroup","j","boxGeo","boxMat","edgeBox","needleMaterial","needleRadius","baseX","lengthTop","lengthBottom","topY","bottomY","geo1","mesh1","geo2","mesh2","edgeGeo","outerGeo","outerMat","outerMesh","outerEdges","dentRadius","dentHeight","dentDepth","dentMat","topDent","bottomDent","sideDentGroup","dentW","dentH","dentD","dentInset","dentGeo","dentMesh","edgeRadius","DEFAULT_SEED","DEFAULT_MORPHOLOGY","createSnowCrystal","params","disposeCrystal","obj","withGeo","m"],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "snowcrystal",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Visualize snow crystal morphology (Nakaya diagram + Global Classification of 121 types) as Three.js geometry. A morphological visualization, not a physical growth model.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "Shotaro Kamimura (SnowNotes)",
|
|
8
|
+
"keywords": [
|
|
9
|
+
"three",
|
|
10
|
+
"threejs",
|
|
11
|
+
"snow",
|
|
12
|
+
"crystal",
|
|
13
|
+
"snowflake",
|
|
14
|
+
"nakaya-diagram",
|
|
15
|
+
"visualization",
|
|
16
|
+
"geometry"
|
|
17
|
+
],
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "git+https://github.com/Shotaro-Kamimura/snowcrystal.git"
|
|
21
|
+
},
|
|
22
|
+
"homepage": "https://github.com/Shotaro-Kamimura/snowcrystal#readme",
|
|
23
|
+
"bugs": {
|
|
24
|
+
"url": "https://github.com/Shotaro-Kamimura/snowcrystal/issues"
|
|
25
|
+
},
|
|
26
|
+
"sideEffects": false,
|
|
27
|
+
"main": "./dist/index.js",
|
|
28
|
+
"module": "./dist/index.js",
|
|
29
|
+
"types": "./dist/index.d.ts",
|
|
30
|
+
"exports": {
|
|
31
|
+
".": {
|
|
32
|
+
"types": "./dist/index.d.ts",
|
|
33
|
+
"import": "./dist/index.js"
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"files": [
|
|
37
|
+
"dist",
|
|
38
|
+
"README.md",
|
|
39
|
+
"LICENSE",
|
|
40
|
+
"CHANGELOG.md"
|
|
41
|
+
],
|
|
42
|
+
"scripts": {
|
|
43
|
+
"dev": "vite",
|
|
44
|
+
"build": "vite build",
|
|
45
|
+
"typecheck": "tsc --noEmit",
|
|
46
|
+
"prepublishOnly": "npm run build"
|
|
47
|
+
},
|
|
48
|
+
"peerDependencies": {
|
|
49
|
+
"three": ">=0.150.0"
|
|
50
|
+
},
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"@microsoft/api-extractor": "^7.43.0",
|
|
53
|
+
"@types/three": "^0.160.0",
|
|
54
|
+
"three": "^0.160.0",
|
|
55
|
+
"typescript": "^5.4.0",
|
|
56
|
+
"vite": "^5.2.0",
|
|
57
|
+
"vite-plugin-dts": "^3.9.0"
|
|
58
|
+
}
|
|
59
|
+
}
|